remove sprintf call
[mplib] / src / texk / web2c / mpdir / mpost.w
1 % $Id$
2 %
3 % Copyright 2008 Taco Hoekwater.
4 %
5 % This program is free software: you can redistribute it and/or modify
6 % it under the terms of the GNU General Public License as published by
7 % the Free Software Foundation, either version 2 of the License, or
8 % (at your option) any later version.
9 %
10 % This program is distributed in the hope that it will be useful,
11 % but WITHOUT ANY WARRANTY; without even the implied warranty of
12 % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 % GNU General Public License for more details.
14 %
15 % You should have received a copy of the GNU General Public License
16 % along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18 \font\tenlogo=logo10 % font used for the METAFONT logo
19 \def\MP{{\tenlogo META}\-{\tenlogo POST}}
20
21 \def\title{MetaPost executable}
22 \def\[#1]{#1.}
23 \pdfoutput=1
24
25 @* \[1] Metapost executable.
26
27 Now that all of \MP\ is a library, a separate program is needed to 
28 have our customary command-line interface. 
29
30 @ First, here are the C includes. |avl.h| is needed because of an 
31 |avl_allocator| that is defined in |mplib.h|
32
33 @d true 1
34 @d false 0
35  
36 @c
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <time.h>
41 #include <mplib.h>
42 #include <mpxout.h>
43 #ifdef WIN32
44 #include <process.h>
45 #endif
46 #include <kpathsea/kpathsea.h>
47 extern char *kpathsea_version_string;
48 static const char *mpost_tex_program = "";
49 static int debug = 0; /* debugging for makempx */
50
51 @ Allocating a bit of memory, with error detection:
52
53 @c
54 static void  *mpost_xmalloc (size_t bytes) {
55   void *w = malloc (bytes); 
56   if (w==NULL) {
57     fprintf(stderr,"Out of memory!\n");
58     exit(EXIT_FAILURE);
59   }
60   return w;
61 }
62 static char *mpost_xstrdup(const char *s) {
63   char *w; 
64   if (s==NULL) return NULL;
65   w = strdup(s);
66   if (w==NULL) {
67     fprintf(stderr,"Out of memory!\n");
68     exit(EXIT_FAILURE);
69   }
70   return w;
71 }
72 static char *mpost_itoa (int i) {
73   char res[32] ;
74   unsigned idx = 30;
75   unsigned v = (unsigned)abs(i);
76   memset(res,0,32*sizeof(char));
77   while (v>=10) {
78     char d = (char)(v % 10);
79     v = v / 10;
80     res[idx--] = d;
81   }
82   res[idx--] = (char)v;
83   if (i<0) {
84       res[idx--] = '-';
85   }
86   return mpost_xstrdup(res+idx);
87 }
88
89
90 @ @c
91 static void mpost_run_editor (MP mp, char *fname, int fline) {
92   char *temp, *command, *edit_value;
93   char c;
94   int sdone, ddone;
95   sdone = ddone = 0;
96   edit_value = kpse_var_value ("MPEDIT");
97   if (edit_value == NULL)
98     edit_value = getenv("EDITOR");
99   if (edit_value == NULL) {
100     fprintf (stderr,"call_edit: can't find a suitable MPEDIT or EDITOR variable\n");
101     exit(mp_status(mp));    
102   }
103   command = (string) mpost_xmalloc (strlen (edit_value) + strlen(fname) + 11 + 3);
104   temp = command;
105   while ((c = *edit_value++) != (char)0) {
106       if (c == '%')   {
107         switch (c = *edit_value++) {
108           case 'd':
109             if (ddone) {
110               fprintf (stderr,"call_edit: `%%d' appears twice in editor command\n");
111               exit(EXIT_FAILURE);  
112             } else {
113               char *s = mpost_itoa(fline);
114               while (*s != '\0')
115                 *temp++ = *s++;
116               ddone = 1;
117             }
118             break;
119           case 's':
120             if (sdone) {
121               fprintf (stderr,"call_edit: `%%s' appears twice in editor command\n");
122               exit(EXIT_FAILURE);
123             } else {
124               while (*fname != '\0')
125                 *temp++ = *fname++;
126               *temp++ = '.';
127               *temp++ = 'm';
128               *temp++ = 'p';
129               sdone = 1;
130             }
131             break;
132           case '\0':
133             *temp++ = '%';
134             /* Back up to the null to force termination.  */
135             edit_value--;
136             break;
137           default:
138             *temp++ = '%';
139             *temp++ = c;
140             break;
141           }
142      } else {
143         *temp++ = c;
144      }
145    }
146   *temp = '\0';
147   if (system (command) != 0)
148     fprintf (stderr, "! Trouble executing `%s'.\n", command);
149   exit(EXIT_FAILURE);
150 }
151
152
153 @<Register the callback routines@>=
154 options->run_editor = mpost_run_editor;
155
156 @
157 @c 
158 static string normalize_quotes (const char *name, const char *mesg) {
159     int quoted = false;
160     int must_quote = (strchr(name, ' ') != NULL);
161     /* Leave room for quotes and NUL. */
162     string ret = (string)mpost_xmalloc(strlen(name)+3);
163     string p;
164     const_string q;
165     p = ret;
166     if (must_quote)
167         *p++ = '"';
168     for (q = name; *q != '\0'; q++) {
169         if (*q == '"')
170             quoted = !quoted;
171         else
172             *p++ = *q;
173     }
174     if (must_quote)
175         *p++ = '"';
176     *p = '\0';
177     if (quoted) {
178         fprintf(stderr, "! Unbalanced quotes in %s %s\n", mesg, name);
179         exit(EXIT_FAILURE);
180     }
181     return ret;
182 }
183
184 @ @c 
185 static char *makempx_find_file (MPX mpx, const char *nam, const char *mode, int ftype) {
186   int fmt, req;
187   (void) mpx;
188   if (mode[0] != 'r') { 
189      return strdup(nam);
190   }
191   req = 1; fmt = -1;
192   switch(ftype) {
193   case mpx_tfm_format:       fmt = kpse_tfm_format; break;
194   case mpx_vf_format:        fmt = kpse_vf_format; req = 0; break;
195   case mpx_trfontmap_format: fmt = kpse_mpsupport_format; break;
196   case mpx_trcharadj_format: fmt = kpse_mpsupport_format; break;
197   case mpx_desc_format:      fmt = kpse_troff_font_format; break;
198   case mpx_fontdesc_format:  fmt = kpse_troff_font_format; break;
199   case mpx_specchar_format:  fmt = kpse_mpsupport_format; break;
200   }
201   if (fmt<0) return NULL;
202   return  kpse_find_file (nam, fmt, req);
203 }
204
205 @ Invoke makempx (or troffmpx) to make sure there is an up-to-date
206    .mpx file for a given .mp file.  (Original from John Hobby 3/14/90) 
207
208 @d default_args " --parse-first-line --interaction=nonstopmode"
209 @d TEX     "tex"
210 @d TROFF   "soelim | eqn -Tps -d$$ | troff -Tps"
211
212 @c
213 #ifndef MPXCOMMAND
214 #define MPXCOMMAND "makempx"
215 #endif
216 static int mpost_run_make_mpx (MP mp, char *mpname, char *mpxname) {
217   int ret;
218   string cnf_cmd = kpse_var_value ("MPXCOMMAND");
219   
220   if (cnf_cmd != NULL && (strcmp (cnf_cmd, "0")==0)) {
221     /* If they turned off this feature, just return success.  */
222     ret = 0;
223
224   } else {
225     /* We will invoke something. Compile-time default if nothing else.  */
226     string cmd;
227     string qmpname = kpse_find_file (normalize_quotes(mpname, "mpname"),kpse_mp_format, 1);
228     string qmpxname = normalize_quotes(mpxname, "mpxname");
229     if (cnf_cmd) {
230       if (mp_troff_mode(mp))
231         cmd = concatn (cnf_cmd, " -troff ",
232                      qmpname, " ", qmpxname, NULL);
233       else if (mpost_tex_program!=NULL && *mpost_tex_program != '\0')
234         cmd = concatn (cnf_cmd, " -tex=", mpost_tex_program, " ",
235                      qmpname, " ", qmpxname, NULL);
236       else
237         cmd = concatn (cnf_cmd, " -tex ", qmpname, " ", qmpxname, NULL);
238   
239       /* Run it.  */
240       ret = system (cmd);
241       free (cmd);
242     } else {
243       makempx_options * mpxopt;
244       char *s = NULL;
245       char *maincmd = NULL;
246       int mpxmode = mp_troff_mode(mp);
247       const char *mpversion = mp_metapost_version () ;
248       mpxopt = mpost_xmalloc(sizeof(makempx_options));
249       if (mpost_tex_program && *mpost_tex_program) {
250         maincmd = mpost_xstrdup(mpost_tex_program);
251       } else {
252         if (mpxmode == mpx_tex_mode) {
253           s = kpse_var_value("TEX");
254           if (!s) s = kpse_var_value("MPXMAINCMD");
255           if (!s) s = mpost_xstrdup (TEX);
256           maincmd = (char *)mpost_xmalloc (strlen(s)+strlen(default_args)+1);
257           strcpy(maincmd,s);
258           strcat(maincmd,default_args);
259           free(s);
260         } else {
261           s = kpse_var_value("TROFF");
262           if (!s) s = kpse_var_value("MPXMAINCMD");
263           if (!s) s = mpost_xstrdup (TROFF);
264           maincmd = s;
265         }
266       }
267       mpxopt->mode = mpxmode;
268       mpxopt->cmd  = maincmd;
269       mpxopt->mptexpre = kpse_var_value("MPTEXPRE");
270       mpxopt->mpname = qmpname;
271       mpxopt->mpxname = qmpxname;
272       mpxopt->debug = debug;
273       mpxopt->find_file = makempx_find_file;
274       {
275         char *banner = "% Written by metapost version ";
276         mpxopt->banner = mpost_xmalloc(strlen(mpversion)+strlen(banner)+1);
277         strcpy (mpxopt->banner, banner);
278         strcat (mpxopt->banner, mpversion);
279       }
280       ret = mp_makempx(mpxopt);
281       free(mpxopt->cmd);
282       free(mpxopt->mptexpre);
283       free(mpxopt);
284     }
285     free (qmpname);
286     free (qmpxname);
287   }
288
289   free (cnf_cmd);
290   return ret == 0;
291 }
292
293
294 @<Register the callback routines@>=
295 if (!nokpse)
296   options->run_make_mpx = mpost_run_make_mpx;
297
298
299 @ @c 
300 static int get_random_seed (void) {
301   int ret ;
302 #if defined (HAVE_GETTIMEOFDAY)
303   struct timeval tv;
304   gettimeofday(&tv, NULL);
305   ret = (tv.tv_usec + 1000000 * tv.tv_usec);
306 #elif defined (HAVE_FTIME)
307   struct timeb tb;
308   ftime(&tb);
309   ret = (tb.millitm + 1000 * tb.time);
310 #else
311   time_t clock = time ((time_t*)NULL);
312   struct tm *tmptr = localtime(&clock);
313   ret = (tmptr->tm_sec + 60*(tmptr->tm_min + 60*tmptr->tm_hour));
314 #endif
315   return ret;
316 }
317
318 @ @<Register the callback routines@>=
319 options->random_seed = get_random_seed();
320
321 @ @c 
322 static char *mpost_find_file(MP mp, const char *fname, const char *fmode, int ftype)  {
323   size_t l ;
324   char *s = NULL;
325   (void)mp;
326   if (fmode[0]=='r') {
327         if (ftype>=mp_filetype_text) {
328       s = kpse_find_file (fname, kpse_mp_format, 0); 
329     } else {
330     switch(ftype) {
331     case mp_filetype_program: 
332       l = strlen(fname);
333           if (l>3 && strcmp(fname+l-3,".mf")==0) {
334             s = kpse_find_file (fname, kpse_mf_format, 0); 
335       } else {
336             s = kpse_find_file (fname, kpse_mp_format, 0); 
337       }
338       break;
339     case mp_filetype_memfile: 
340       s = kpse_find_file (fname, kpse_mem_format, 0); 
341       break;
342     case mp_filetype_metrics: 
343       s = kpse_find_file (fname, kpse_tfm_format, 0); 
344       break;
345     case mp_filetype_fontmap: 
346       s = kpse_find_file (fname, kpse_fontmap_format, 0); 
347       break;
348     case mp_filetype_font: 
349       s = kpse_find_file (fname, kpse_type1_format, 0); 
350       break;
351     case mp_filetype_encoding: 
352       s = kpse_find_file (fname, kpse_enc_format, 0); 
353       break;
354     }
355     }
356   } else {
357     s = mpost_xstrdup(fname); /* when writing */
358   }
359   return s;
360 }
361
362 @  @<Register the callback routines@>=
363 if (!nokpse)
364   options->find_file = mpost_find_file;
365
366 @ @c 
367 static void *mpost_open_file(MP mp, const char *fname, const char *fmode, int ftype)  {
368   char realmode[3];
369   char *s;
370   if (ftype==mp_filetype_terminal) {
371     return (fmode[0] == 'r' ? stdin : stdout);
372   } else if (ftype==mp_filetype_error) {
373     return stderr;
374   } else { 
375     s = mpost_find_file (mp, fname, fmode, ftype);
376     if (s!=NULL) {
377       void *ret = NULL;
378       realmode[0] = *fmode;
379           realmode[1] = 'b';
380           realmode[2] = '\0';
381       ret = fopen(s,realmode);
382       free(s);
383       return ret;
384     }
385   }
386   return NULL;
387 }
388
389 @  @<Register the callback routines@>=
390 if (!nokpse)
391   options->open_file = mpost_open_file;
392
393
394 @ At the moment, the command line is very simple.
395
396 @d option_is(A) ((strncmp(argv[a],"--" A, strlen(A)+2)==0) || 
397        (strncmp(argv[a],"-" A, strlen(A)+1)==0))
398 @d option_arg(B) (optarg && strncmp(optarg,B, strlen(B))==0)
399
400
401 @<Read and set command line options@>=
402 {
403   char *optarg;
404   boolean ini_version_test = false;
405   while (++a<argc) {
406     optarg = strstr(argv[a],"=") ;
407     if (optarg!=NULL) {
408       optarg++;
409       if (!*optarg)  optarg=NULL;
410     }
411     if (option_is("ini")) {
412       ini_version_test = true;
413     } else if (option_is("debug")) {
414       debug = 1;
415     } else if (option_is ("kpathsea-debug")) {
416       kpathsea_debug |= atoi (optarg);
417     } else if (option_is("mem")) {
418       options->mem_name = mpost_xstrdup(optarg);
419       if (!user_progname) 
420             user_progname = optarg;
421     } else if (option_is("jobname")) {
422       options->job_name = mpost_xstrdup(optarg);
423     } else if (option_is ("progname")) {
424       user_progname = optarg;
425     } else if (option_is("troff")) {
426       options->troff_mode = true;
427     } else if (option_is ("tex")) {
428       mpost_tex_program = optarg;
429     } else if (option_is("interaction")) {
430       if (option_arg("batchmode")) {
431         options->interaction = mp_batch_mode;
432       } else if (option_arg("nonstopmode")) {
433         options->interaction = mp_nonstop_mode;
434       } else if (option_arg("scrollmode")) {
435         options->interaction = mp_scroll_mode;
436       } else if (option_arg("errorstopmode")) {
437         options->interaction = mp_error_stop_mode;
438       } else {
439         fprintf(stdout,"unknown option argument %s\n", argv[a]);
440       }
441     } else if (option_is("no-kpathsea")) {
442       nokpse=1;
443     } else if (option_is("help")) {
444       @<Show help and exit@>;
445     } else if (option_is("version")) {
446       @<Show version and exit@>;
447     } else if (option_is("")) {
448       continue; /* ignore unknown options */
449     } else {
450       break;
451     }
452   }
453   options->ini_version = ini_version_test;
454 }
455
456
457 @<Show help...@>=
458 {
459 fprintf(stdout,
460 "\n"
461 "Usage: mpost [OPTION] [MPNAME[.mp]] [COMMANDS]\n"
462 "\n"
463 "  Run MetaPost on MPNAME, usually creating MPNAME.NNN (and perhaps\n"
464 "  MPNAME.tfm), where NNN are the character numbers generated.\n"
465 "  Any remaining COMMANDS are processed as MetaPost input,\n"
466 "  after MPNAME is read.\n\n");
467 fprintf(stdout,
468 "  If no arguments or options are specified, prompt for input.\n"
469 "\n"
470 "  -ini                    be inimpost, for dumping mems\n"
471 "  -interaction=STRING     set interaction mode (STRING=batchmode/nonstopmode/\n"
472 "                          scrollmode/errorstopmode)\n"
473 "  -jobname=STRING         set the job name to STRING\n"
474 "  -progname=STRING        set program (and mem) name to STRING\n");
475 fprintf(stdout,
476 "  -tex=TEXPROGRAM         use TEXPROGRAM for text labels\n"
477 "  -kpathsea-debug=NUMBER  set path searching debugging flags according to\n"
478 "                          the bits of NUMBER\n"
479 "  -mem=MEMNAME            use MEMNAME instead of program name or a %%& line\n"
480 "  -troff                  set the prologues variable and assume TEXPROGRAM is really troff\n"
481 "  -help                   display this help and exit\n"
482 "  -version                output version information and exit\n"
483 "\n"
484 "Email bug reports to mp-implementors@@tug.org.\n"
485 "\n");
486   exit(EXIT_SUCCESS);
487 }
488
489
490 @<Show version...@>=
491 {
492 fprintf(stdout, 
493 "\n"
494 "MetaPost %s\n"
495 "Copyright 2008 AT&T Bell Laboratories.\n"
496 "There is NO warranty.  Redistribution of this software is\n"
497 "covered by the terms of both the MetaPost copyright and\n"
498 "the Lesser GNU General Public License.\n"
499 "For more information about these matters, see the file\n"
500 "named COPYING and the MetaPost source.\n"
501 "Primary author of MetaPost: John Hobby.\n"
502 "Current maintainer of MetaPost: Taco Hoekwater.\n"
503 "\n", mp_metapost_version());
504   exit(EXIT_SUCCESS);
505 }
506
507 @ The final part of the command line, after option processing, is
508 stored in the \MP\ instance, this will be taken as the first line of
509 input.
510
511 @d command_line_size 256
512
513 @<Copy the rest of the command line@>=
514 {
515   options->command_line = mpost_xmalloc(command_line_size);
516   strcpy(options->command_line,"");
517   if (a<argc) {
518     k=0;
519     for(;a<argc;a++) {
520       char *c = argv[a];
521       while (*c != '\0') {
522             if (k<(command_line_size-1)) {
523           options->command_line[k++] = *c;
524         }
525         c++;
526       }
527       options->command_line[k++] = ' ';
528     }
529         while (k>0) {
530       if (options->command_line[(k-1)] == ' ') 
531         k--; 
532       else 
533         break;
534     }
535     options->command_line[k] = '\0';
536   }
537 }
538
539 @ A simple function to get numerical |texmf.cnf| values
540 @c
541 static int setup_var (int def, const char *var_name, int nokpse) {
542   if (!nokpse) {
543     char * expansion = kpse_var_value (var_name);
544     if (expansion) {
545       int conf_val = atoi (expansion);
546       free (expansion);
547       if (conf_val > 0) {
548         return conf_val;
549       }
550     }
551   }
552   return def;
553 }
554
555 @ @<Set up the banner line@>=
556 {
557   const char *mpversion = mp_metapost_version () ;
558   const char * banner = "This is MetaPost, version ";
559   const char * kpsebanner_start = " (";
560   const char * kpsebanner_stop = ")";
561   options->banner = mpost_xmalloc(strlen(banner)+
562                             strlen(mpversion)+
563                             strlen(kpsebanner_start)+
564                             strlen(kpathsea_version_string)+
565                             strlen(kpsebanner_stop)+1);
566   strcpy (options->banner, banner);
567   strcat (options->banner, mpversion);
568   strcat (options->banner, kpsebanner_start);
569   strcat (options->banner, kpathsea_version_string);
570   strcat (options->banner, kpsebanner_stop);
571 }
572
573
574 @ Now this is really it: \MP\ starts and ends here.
575
576 @d xfree(A) if (A!=NULL) free(A)
577
578 @c 
579 int main (int argc, char **argv) { /* |start_here| */
580   int k; /* index into buffer */
581   int history; /* the exit status */
582   MP mp; /* a metapost instance */
583   struct MP_options * options; /* instance options */
584   int a=0; /* argc counter */
585   int nokpse = 0; /* switch to {\it not} enable kpse */
586   char *user_progname = NULL; /* If the user overrides argv[0] with -progname.  */
587   options = mp_options();
588   options->ini_version       = false;
589   options->print_found_names = true;
590   @<Read and set command line options@>;
591   if (!nokpse)
592     kpse_set_program_name("mpost",user_progname);  
593   if(putenv((char *)"engine=metapost"))
594     fprintf(stdout,"warning: could not set up $engine\n");
595   options->main_memory       = setup_var (50000,"main_memory",nokpse);
596   options->hash_size         = (unsigned)setup_var (16384,"hash_size",nokpse);
597   options->max_in_open       = setup_var (25,"max_in_open",nokpse);
598   options->param_size        = setup_var (1500,"param_size",nokpse);
599   options->error_line        = setup_var (79,"error_line",nokpse);
600   options->half_error_line   = setup_var (50,"half_error_line",nokpse);
601   options->max_print_line    = setup_var (100,"max_print_line",nokpse);
602   @<Set up the banner line@>;
603   @<Copy the rest of the command line@>;
604   @<Register the callback routines@>;
605   mp = mp_initialize(options);
606   xfree(options->command_line);
607   xfree(options->mem_name);
608   xfree(options->job_name);
609   xfree(options->banner);
610   free(options);
611   if (mp==NULL)
612         exit(EXIT_FAILURE);
613   history = mp_status(mp);
614   if (history)
615         exit(history);
616   history = mp_run(mp);
617   (void)mp_finish(mp);
618   exit(history);
619 }
620