Integrate the metapost/makempx banners
[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       const char *mpversion = mp_metapost_version (mp) ;
162       mpxopt = xmalloc(sizeof(makempx_options));
163       char *s = NULL;
164       char *maincmd = NULL;
165       int mpxmode = mp_troff_mode(mp);
166       if (mpost_tex_program && *mpost_tex_program) {
167         maincmd = xstrdup(mpost_tex_program);
168       } else {
169         if (mpxmode == mpx_tex_mode) {
170           s = kpse_var_value("TEX");
171           if (!s) s = kpse_var_value("MPXMAINCMD");
172           if (!s) s = xstrdup (TEX);
173           maincmd = (char *)xmalloc (strlen(s)+strlen(default_args)+1);
174           strcpy(maincmd,s);
175           strcat(maincmd,default_args);
176           free(s);
177         } else {
178           s = kpse_var_value("TROFF");
179           if (!s) s = kpse_var_value("MPXMAINCMD");
180           if (!s) s = xstrdup (TROFF);
181           maincmd = s;
182         }
183       }
184       mpxopt->mode = mpxmode;
185       mpxopt->cmd  = maincmd;
186       mpxopt->mptexpre = kpse_var_value("MPTEXPRE");
187       mpxopt->mpname = qmpname;
188       mpxopt->mpxname = qmpxname;
189       mpxopt->debug = debug;
190       mpxopt->find_file = makempx_find_file;
191       {
192         char *banner = "% Written by metapost version ";
193         mpxopt->banner = xmalloc(strlen(mpversion)+strlen(banner)+1);
194         strcpy (mpxopt->banner, banner);
195         strcat (mpxopt->banner, mpversion);
196       }
197       ret = mp_makempx(mpxopt);
198       free(mpxopt->cmd);
199       free(mpxopt->mptexpre);
200       free(mpxopt);
201     }
202     free (qmpname);
203     free (qmpxname);
204   }
205
206   free (cnf_cmd);
207   return ret == 0;
208 }
209
210
211 @<Register the callback routines@>=
212 if (!nokpse)
213   options->run_make_mpx = mpost_run_make_mpx;
214
215
216 @ @c 
217 static int get_random_seed (void) {
218   int ret ;
219 #if defined (HAVE_GETTIMEOFDAY)
220   struct timeval tv;
221   gettimeofday(&tv, NULL);
222   ret = (tv.tv_usec + 1000000 * tv.tv_usec);
223 #elif defined (HAVE_FTIME)
224   struct timeb tb;
225   ftime(&tb);
226   ret = (tb.millitm + 1000 * tb.time);
227 #else
228   time_t clock = time ((time_t*)NULL);
229   struct tm *tmptr = localtime(&clock);
230   ret = (tmptr->tm_sec + 60*(tmptr->tm_min + 60*tmptr->tm_hour));
231 #endif
232   return ret;
233 }
234
235 @ @<Register the callback routines@>=
236 options->random_seed = get_random_seed();
237
238 @ @c 
239 char *mpost_find_file(MP mp, const char *fname, const char *fmode, int ftype)  {
240   int l ;
241   char *s = NULL;
242   (void)mp;
243   if (fmode[0]=='r') {
244         if (ftype>=mp_filetype_text) {
245       s = kpse_find_file (fname, kpse_mp_format, 0); 
246     } else {
247     switch(ftype) {
248     case mp_filetype_program: 
249       l = strlen(fname);
250           if (l>3 && strcmp(fname+l-3,".mf")==0) {
251             s = kpse_find_file (fname, kpse_mf_format, 0); 
252       } else {
253             s = kpse_find_file (fname, kpse_mp_format, 0); 
254       }
255       break;
256     case mp_filetype_memfile: 
257       s = kpse_find_file (fname, kpse_mem_format, 0); 
258       break;
259     case mp_filetype_metrics: 
260       s = kpse_find_file (fname, kpse_tfm_format, 0); 
261       break;
262     case mp_filetype_fontmap: 
263       s = kpse_find_file (fname, kpse_fontmap_format, 0); 
264       break;
265     case mp_filetype_font: 
266       s = kpse_find_file (fname, kpse_type1_format, 0); 
267       break;
268     case mp_filetype_encoding: 
269       s = kpse_find_file (fname, kpse_enc_format, 0); 
270       break;
271     }
272     }
273   } else {
274     s = xstrdup(fname); /* when writing */
275   }
276   return s;
277 }
278
279 @  @<Register the callback routines@>=
280 if (!nokpse)
281   options->find_file = mpost_find_file;
282
283 @ @c 
284 void *mpost_open_file(MP mp, const char *fname, const char *fmode, int ftype)  {
285   char realmode[3];
286   char *s;
287   if (ftype==mp_filetype_terminal) {
288     return (fmode[0] == 'r' ? stdin : stdout);
289   } else if (ftype==mp_filetype_error) {
290     return stderr;
291   } else { 
292     s = mpost_find_file (mp, fname, fmode, ftype);
293     if (s!=NULL) {
294       void *ret = NULL;
295       realmode[0] = *fmode;
296           realmode[1] = 'b';
297           realmode[2] = 0;
298       ret = fopen(s,realmode);
299       free(s);
300       return ret;
301     }
302   }
303   return NULL;
304 }
305
306 @  @<Register the callback routines@>=
307 if (!nokpse)
308   options->open_file = mpost_open_file;
309
310
311 @ At the moment, the command line is very simple.
312
313 @d option_is(A) ((strncmp(argv[a],"--" A, strlen(A)+2)==0) || 
314        (strncmp(argv[a],"-" A, strlen(A)+1)==0))
315 @d option_arg(B) (optarg && strncmp(optarg,B, strlen(B))==0)
316
317
318 @<Read and set command line options@>=
319 {
320   char *optarg;
321   while (++a<argc) {
322     optarg = strstr(argv[a],"=") ;
323     if (optarg!=NULL) {
324       optarg++;
325       if (!*optarg)  optarg=NULL;
326     }
327     if (option_is("ini")) {
328       options->ini_version = true;
329     } else if (option_is("debug")) {
330       debug = 1;
331     } else if (option_is ("kpathsea-debug")) {
332       kpathsea_debug |= atoi (optarg);
333     } else if (option_is("mem")) {
334       options->mem_name = xstrdup(optarg);
335       if (!user_progname) 
336             user_progname = optarg;
337     } else if (option_is("jobname")) {
338       options->job_name = xstrdup(optarg);
339     } else if (option_is ("progname")) {
340       user_progname = optarg;
341     } else if (option_is("troff")) {
342       options->troff_mode = true;
343     } else if (option_is ("tex")) {
344       mpost_tex_program = optarg;
345     } else if (option_is("interaction")) {
346       if (option_arg("batchmode")) {
347         options->interaction = mp_batch_mode;
348       } else if (option_arg("nonstopmode")) {
349         options->interaction = mp_nonstop_mode;
350       } else if (option_arg("scrollmode")) {
351         options->interaction = mp_scroll_mode;
352       } else if (option_arg("errorstopmode")) {
353         options->interaction = mp_error_stop_mode;
354       } else {
355         fprintf(stdout,"unknown option argument %s\n", argv[a]);
356       }
357     } else if (option_is("no-kpathsea")) {
358       nokpse=1;
359     } else if (option_is("help")) {
360       @<Show help and exit@>;
361     } else if (option_is("version")) {
362           mp = mp_new(mp_options());
363       @<Show version and exit@>;
364     } else if (option_is("")) {
365       continue; /* ignore unknown options */
366     } else {
367       break;
368     }
369   }
370 }
371
372
373 @<Show help...@>=
374 {
375 fprintf(stdout,
376 "\n"
377 "Usage: mpost [OPTION] [MPNAME[.mp]] [COMMANDS]\n"
378 "\n"
379 "  Run MetaPost on MPNAME, usually creating MPNAME.NNN (and perhaps\n"
380 "  MPNAME.tfm), where NNN are the character numbers generated.\n"
381 "  Any remaining COMMANDS are processed as MetaPost input,\n"
382 "  after MPNAME is read.\n\n");
383 fprintf(stdout,
384 "  If no arguments or options are specified, prompt for input.\n"
385 "\n"
386 "  -ini                    be inimpost, for dumping mems\n"
387 "  -interaction=STRING     set interaction mode (STRING=batchmode/nonstopmode/\n"
388 "                          scrollmode/errorstopmode)\n"
389 "  -jobname=STRING         set the job name to STRING\n"
390 "  -progname=STRING        set program (and mem) name to STRING\n");
391 fprintf(stdout,
392 "  -tex=TEXPROGRAM         use TEXPROGRAM for text labels\n"
393 "  -kpathsea-debug=NUMBER  set path searching debugging flags according to\n"
394 "                          the bits of NUMBER\n"
395 "  -mem=MEMNAME            use MEMNAME instead of program name or a %%& line\n"
396 "  -troff                  set the prologues variable, use `makempx -troff'\n"
397 "  -help                   display this help and exit\n"
398 "  -version                output version information and exit\n"
399 "\n"
400 "Email bug reports to mp-implementors@@tug.org.\n"
401 "\n");
402   exit(EXIT_SUCCESS);
403 }
404
405
406 @<Show version...@>=
407 {
408 fprintf(stdout, 
409 "\n"
410 "MetaPost %s (CWeb version %s)\n"
411 "Copyright 2008 AT&T Bell Laboratories.\n"
412 "There is NO warranty.  Redistribution of this software is\n"
413 "covered by the terms of both the MetaPost copyright and\n"
414 "the Lesser GNU General Public License.\n"
415 "For more information about these matters, see the file\n"
416 "named COPYING and the MetaPost source.\n"
417 "Primary author of MetaPost: John Hobby.\n"
418 "Current maintainer of MetaPost: Taco Hoekwater.\n"
419 "\n", mp_metapost_version(mp), mp_mplib_version(mp));
420   exit(EXIT_SUCCESS);
421 }
422
423 @ The final part of the command line, after option processing, is
424 stored in the \MP\ instance, this will be taken as the first line of
425 input.
426
427 @d command_line_size 256
428
429 @<Copy the rest of the command line@>=
430 {
431   options->command_line = xmalloc(command_line_size);
432   strcpy(options->command_line,"");
433   if (a<argc) {
434     k=0;
435     for(;a<argc;a++) {
436       char *c = argv[a];
437       while (*c) {
438             if (k<(command_line_size-1)) {
439           options->command_line[k++] = *c;
440         }
441         c++;
442       }
443       options->command_line[k++] = ' ';
444     }
445         while (k>0) {
446       if (options->command_line[(k-1)] == ' ') 
447         k--; 
448       else 
449         break;
450     }
451     options->command_line[k] = 0;
452   }
453 }
454
455 @ A simple function to get numerical |texmf.cnf| values
456 @c
457 int setup_var (int def, const char *var_name, int nokpse) {
458   if (!nokpse) {
459     char * expansion = kpse_var_value (var_name);
460     if (expansion) {
461       int conf_val = atoi (expansion);
462       free (expansion);
463       if (conf_val > 0) {
464         return conf_val;
465       }
466     }
467   }
468   return def;
469 }
470
471
472 @ Now this is really it: \MP\ starts and ends here.
473
474 @d xfree(A) if (A!=NULL) free(A)
475
476 @c 
477 int main (int argc, char **argv) { /* |start_here| */
478   int k; /* index into buffer */
479   int history; /* the exit status */
480   MP mp; /* a metapost instance */
481   struct MP_options * options; /* instance options */
482   int a=0; /* argc counter */
483   int nokpse = 0; /* switch to {\it not} enable kpse */
484   char *user_progname = NULL; /* If the user overrides argv[0] with -progname.  */
485   options = mp_options();
486   options->ini_version       = false;
487   options->print_found_names = true;
488   @<Read and set command line options@>;
489   if (!nokpse)
490     kpse_set_program_name("mpost",user_progname);  
491   if(putenv((char *)"engine=metapost"))
492     fprintf(stdout,"warning: could not set up $engine\n");
493   options->main_memory       = setup_var (50000,"main_memory",nokpse);
494   options->hash_size         = setup_var (9500,"hash_size",nokpse);
495   options->hash_prime        = 7919;
496   options->max_in_open       = setup_var (25,"max_in_open",nokpse);
497   options->param_size        = setup_var (1500,"param_size",nokpse);
498   options->error_line        = setup_var (79,"error_line",nokpse);
499   options->half_error_line   = setup_var (50,"half_error_line",nokpse);
500   options->max_print_line    = setup_var (100,"max_print_line",nokpse);
501   @<Copy the rest of the command line@>;
502   @<Register the callback routines@>;
503   mp = mp_new(options);
504   xfree(options->command_line);
505   xfree(options->mem_name);
506   xfree(options->job_name);
507   free(options);
508   if (mp==NULL)
509         exit(EXIT_FAILURE);
510   history = mp_initialize(mp);
511   if (history) 
512     exit(history);
513   history = mp_run(mp);
514   mp_free(mp);
515   exit(history);
516 }
517