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