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