general cleanups: mplib.h is now smaller, and what is still there uses namespaces.
[mplib] / src / texk / web2c / mpdir / mpost.w
1 % $Id: mpost.w $
2 % MetaPost command-line program, by Taco Hoekwater.  Public domain.
3
4 \font\tenlogo=logo10 % font used for the METAFONT logo
5 \def\MP{{\tenlogo META}\-{\tenlogo POST}}
6
7 \def\title{MetaPost executable}
8 \def\[#1]{#1.}
9 \pdfoutput=1
10
11 @* \[1] Metapost executable.
12
13 Now that all of \MP\ is a library, a separate program is needed to 
14 have our customary command-line interface. 
15
16 @ First, here are the C includes. |avl.h| is needed because of an 
17 |avl_allocator| that is defined in |mplib.h|
18
19 @d true 1
20 @d false 0
21  
22 @c
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <time.h>
27 #include <mplib.h>
28 #define HAVE_BOOLEAN 1
29 #define HAVE_PROTOTYPES 1
30 #include <kpathsea/progname.h>
31 #include <kpathsea/tex-file.h>
32 #include <kpathsea/variable.h>
33 extern unsigned kpathsea_debug;
34 #include <kpathsea/concatn.h>
35 static string mpost_tex_program = "";
36
37
38 @c
39 void mpost_run_editor (MP mp, char *fname, int fline) {
40   if (mp)
41     fprintf(stdout,"Ok, bye (%s,%d)!",fname, fline);
42   exit(1);
43 }
44
45
46 @<Register the callback routines@>=
47 options->run_editor = mpost_run_editor;
48
49 @
50 @c 
51 string normalize_quotes (const char *name, const char *mesg) {
52     boolean quoted = false;
53     boolean must_quote = (strchr(name, ' ') != NULL);
54     /* Leave room for quotes and NUL. */
55     string ret = (string)mp_xmalloc(strlen(name)+3, sizeof(char));
56     string p;
57     const_string q;
58     p = ret;
59     if (must_quote)
60         *p++ = '"';
61     for (q = name; *q; q++) {
62         if (*q == '"')
63             quoted = !quoted;
64         else
65             *p++ = *q;
66     }
67     if (must_quote)
68         *p++ = '"';
69     *p = '\0';
70     if (quoted) {
71         fprintf(stderr, "! Unbalanced quotes in %s %s\n", mesg, name);
72         exit(1);
73     }
74     return ret;
75 }
76
77
78 @ Invoke makempx (or troffmpx) to make sure there is an up-to-date
79    .mpx file for a given .mp file.  (Original from John Hobby 3/14/90) 
80
81 @c
82
83 #ifndef MPXCOMMAND
84 #define MPXCOMMAND "makempx"
85 #endif
86 boolean mpost_run_make_mpx (MP mp, char *mpname, char *mpxname) {
87   int ret;
88   string cnf_cmd = kpse_var_value ("MPXCOMMAND");
89   
90   if (cnf_cmd && (strcmp (cnf_cmd, "0")==0)) {
91     /* If they turned off this feature, just return success.  */
92     ret = 0;
93
94   } else {
95     /* We will invoke something. Compile-time default if nothing else.  */
96     string cmd;
97     string qmpname = normalize_quotes(mpname, "mpname");
98     string qmpxname = normalize_quotes(mpxname, "mpxname");
99     if (!cnf_cmd)
100       cnf_cmd = mp_xstrdup (MPXCOMMAND);
101
102     if (mp_troff_mode(mp))
103       cmd = concatn (cnf_cmd, " -troff ",
104                      qmpname, " ", qmpxname, NULL);
105     else if (mpost_tex_program && *mpost_tex_program)
106       cmd = concatn (cnf_cmd, " -tex=", mpost_tex_program, " ",
107                      qmpname, " ", qmpxname, NULL);
108     else
109       cmd = concatn (cnf_cmd, " -tex ", qmpname, " ", qmpxname, NULL);
110
111     /* Run it.  */
112     ret = system (cmd);
113     mp_xfree (cmd);
114     mp_xfree (qmpname);
115     mp_xfree (qmpxname);
116   }
117
118   mp_xfree (cnf_cmd);
119   return ret == 0;
120 }
121
122
123 @<Register the callback routines@>=
124 if (!nokpse)
125   options->run_make_mpx = mpost_run_make_mpx;
126
127
128 @ @c scaled mpost_get_random_seed (MP mp) {
129   if (mp==NULL) exit(1); /* for -W */
130 #if defined (HAVE_GETTIMEOFDAY)
131   struct timeval tv;
132   gettimeofday(&tv, NULL);
133   return (tv.tv_usec + 1000000 * tv.tv_usec);
134 #elif defined (HAVE_FTIME)
135   struct timeb tb;
136   ftime(&tb);
137   return (tb.millitm + 1000 * tb.time);
138 #else
139   time_t clock = time ((time_t*)NULL);
140   struct tm *tmptr = localtime(&clock);
141   return (tmptr->tm_sec + 60*(tmptr->tm_min + 60*tmptr->tm_hour));
142 #endif
143 }
144
145 @ @<Register the callback routines@>=
146 options->get_random_seed = mpost_get_random_seed;
147
148
149 @c char *mpost_find_file(char *fname, char *fmode, int ftype)  {
150   char *s;
151   int l ;
152   if (fmode[0]=='r') {
153     switch(ftype) {
154     case mp_filetype_program: 
155       l = strlen(fname);
156           if (l>3 && strcmp(fname+l-3,".mf")==0) {
157             s = kpse_find_file (fname, kpse_mf_format, 0); 
158       } else {
159             s = kpse_find_file (fname, kpse_mp_format, 0); 
160       }
161       break;
162     case mp_filetype_text: 
163       s = kpse_find_file (fname, kpse_mp_format, 0); 
164       break;
165     case mp_filetype_memfile: 
166       s = kpse_find_file (fname, kpse_mem_format, 0); 
167       break;
168     case mp_filetype_metrics: 
169       s = kpse_find_file (fname, kpse_tfm_format, 0); 
170       break;
171     case mp_filetype_fontmap: 
172       s = kpse_find_file (fname, kpse_fontmap_format, 0); 
173       break;
174     case mp_filetype_font: 
175       s = kpse_find_file (fname, kpse_type1_format, 0); 
176       break;
177     case mp_filetype_encoding: 
178       s = kpse_find_file (fname, kpse_enc_format, 0); 
179       break;
180     }
181   } else {
182     s = mp_xstrdup(fname); /* when writing */
183   }
184   return s;
185 }
186
187 @  @<Register the callback routines@>=
188 if (!nokpse)
189   options->find_file = mpost_find_file;
190
191 @ At the moment, the command line is very simple.
192
193 @d option_is(A) ((strncmp(argv[a],"--" A, strlen(A)+2)==0) || 
194        (strncmp(argv[a],"-" A, strlen(A)+1)==0))
195 @d option_arg(B) (optarg && strncmp(optarg,B, strlen(B))==0)
196
197
198 @<Read and set commmand line options@>=
199 {
200   char *optarg;
201   while (++a<argc) {
202     optarg = strstr(argv[a],"=") ;
203     if (optarg!=NULL) {
204       optarg++;
205       if (!*optarg)  optarg=NULL;
206     }
207     if (option_is("ini")) {
208       options->ini_version = true;
209     } else if (option_is ("kpathsea-debug")) {
210       kpathsea_debug |= atoi (optarg);
211     } else if (option_is("mem")) {
212       options->mem_name = mp_xstrdup(optarg);
213       if (!user_progname) 
214             user_progname = optarg;
215     } else if (option_is("jobname")) {
216       options->job_name = mp_xstrdup(optarg);
217     } else if (option_is ("progname")) {
218       user_progname = optarg;
219     } else if (option_is("troff")) {
220       options->troff_mode = true;
221     } else if (option_is ("tex")) {
222       mpost_tex_program = optarg;
223     } else if (option_is("interaction")) {
224       if (option_arg("batchmode")) {
225         options->interaction = mp_batch_mode;
226       } else if (option_arg("nonstopmode")) {
227         options->interaction = mp_nonstop_mode;
228       } else if (option_arg("scrollmode")) {
229         options->interaction = mp_scroll_mode;
230       } else if (option_arg("errorstopmode")) {
231         options->interaction = mp_error_stop_mode;
232       } else {
233         fprintf(stdout,"unknown option argument %s\n", argv[a]);
234       }
235     } else if (option_is("no-kpathsea")) {
236       nokpse=1;
237     } else if (option_is("help")) {
238       @<Show help and exit@>;
239     } else if (option_is("version")) {
240       @<Show version and exit@>;
241     } else if (option_is("")) {
242       continue; /* ignore unknown options */
243     } else {
244       break;
245     }
246   }
247 }
248
249
250 @<Show help...@>=
251 {
252 fprintf(stdout,
253 "\n"
254 "Usage: mpost [OPTION] [MPNAME[.mp]] [COMMANDS]\n"
255 "\n"
256 "  Run MetaPost on MPNAME, usually creating MPNAME.NNN (and perhaps\n"
257 "  MPNAME.tfm), where NNN are the character numbers generated.\n"
258 "  Any remaining COMMANDS are processed as MetaPost input,\n"
259 "  after MPNAME is read.\n"
260 "\n"
261 "  If no arguments or options are specified, prompt for input.\n"
262 "\n"
263 "  -ini                    be inimpost, for dumping mems\n"
264 "  -interaction=STRING     set interaction mode (STRING=batchmode/nonstopmode/\n"
265 "                          scrollmode/errorstopmode)\n"
266 "  -jobname=STRING         set the job name to STRING\n"
267 "  -progname=STRING        set program (and mem) name to STRING\n"
268 "  -tex=TEXPROGRAM         use TEXPROGRAM for text labels\n"
269 "  -kpathsea-debug=NUMBER  set path searching debugging flags according to\n"
270 "                          the bits of NUMBER\n"
271 "  -mem=MEMNAME            use MEMNAME instead of program name or a %%& line\n"
272 "  -troff                  set the prologues variable, use `makempx -troff'\n"
273 "  -help                   display this help and exit\n"
274 "  -version                output version information and exit\n"
275 "\n"
276 "Email bug reports to mp-implementors@@tug.org.\n"
277 "\n");
278   exit(EXIT_SUCCESS);
279 }
280
281
282 @<Show version...@>=
283 {
284 fprintf(stdout,
285 "\n"
286 "MetaPost %s (CWeb version %s)\n"
287 "Copyright 2008 AT&T Bell Laboratories.\n"
288 "There is NO warranty.  Redistribution of this software is\n"
289 "covered by the terms of both the MetaPost copyright and\n"
290 "the Lesser GNU General Public License.\n"
291 "For more information about these matters, see the file\n"
292 "named COPYING and the MetaPost source.\n"
293 "Primary author of MetaPost: John Hobby.\n"
294 "Current maintainer of MetaPost: Taco Hoekwater.\n"
295 "\n", mp_metapost_version(mp), mp_mplib_version(mp));
296   exit(EXIT_SUCCESS);
297 }
298
299 @ The final part of the command line, after option processing, is
300 stored in the \MP\ instance, this will be taken as the first line of
301 input.
302
303 @d command_line_size 256
304
305 @<Copy the rest of the command line@>=
306 {
307   options->command_line = mp_xmalloc(command_line_size,1);
308   if (options->command_line==NULL) {
309     fprintf(stderr,"Out of memory!\n");
310     exit(EXIT_FAILURE);
311   }
312   strcpy(options->command_line,"");
313   if (a<argc) {
314     k=0;
315     for(;a<argc;a++) {
316       char *c = argv[a];
317       while (*c) {
318             if (k<(command_line_size-1)) {
319           options->command_line[k++] = *c;
320         }
321         c++;
322       }
323       options->command_line[k++] = ' ';
324     }
325         while (k>0) {
326       if (options->command_line[(k-1)] == ' ') 
327         k--; 
328       else 
329         break;
330     }
331     options->command_line[k] = 0;
332   }
333 }
334
335 @ A simple function to get numerical |texmf.cnf| values
336 @c
337 int setup_var (int def, char *var_name, int nokpse) {
338   if (!nokpse) {
339     char * expansion = kpse_var_value (var_name);
340     if (expansion) {
341       int conf_val = atoi (expansion);
342       mp_xfree (expansion);
343       if (conf_val > 0) {
344         return conf_val;
345       }
346     }
347   }
348   return def;
349 }
350
351
352 @ Now this is really it: \MP\ starts and ends here.
353
354 @c 
355 int main (int argc, char **argv) { /* |start_here| */
356   int a=0; /* argc counter */
357   int k; /* index into buffer */
358   int history; /* the exit status */
359   MP mp; /* a metapost instance */
360   struct MP_options * options; /* instance options */
361   int nokpse = 0; /* switch to {\it not} enable kpse */
362   char *user_progname = NULL; /* If the user overrides argv[0] with -progname.  */
363   options = mp_options();
364   options->ini_version       = false;
365   options->print_found_names = true;
366   @<Read and set commmand line options@>;
367   if (!nokpse)
368     kpse_set_program_name("mpost",user_progname);  
369   if(putenv("engine=newmetapost"))
370     fprintf(stdout,"warning: could not set up $engine\n");
371   options->main_memory       = setup_var (50000,"main_memory",nokpse);
372   options->hash_size         = setup_var (9500,"hash_size",nokpse);
373   options->hash_prime        = 7919;
374   options->max_in_open       = setup_var (25,"max_in_open",nokpse);
375   options->param_size        = setup_var (1500,"param_size",nokpse);
376   options->error_line        = setup_var (79,"error_line",nokpse);
377   options->half_error_line   = setup_var (50,"half_error_line",nokpse);
378   options->max_print_line    = setup_var (50,"max_print_line",nokpse);
379   @<Copy the rest of the command line@>;
380   @<Register the callback routines@>;
381   mp = mp_new(options);
382   mp_xfree((void *)options);
383   if (mp==NULL)
384         exit(EXIT_FAILURE);
385   if(!mp_initialize(mp))
386         exit(EXIT_FAILURE);
387   history = mp_run(mp);
388   mp_free(mp);
389   exit(history);
390 }
391