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