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