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