3 % Copyright 2008 Taco Hoekwater.
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.
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.
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/>.
18 \font\tenlogo=logo10 % font used for the METAFONT logo
19 \def\MP{{\tenlogo META}\-{\tenlogo POST}}
21 \def\title{MetaPost executable}
25 @* \[1] Metapost executable.
27 Now that all of \MP\ is a library, a separate program is needed to
28 have our customary command-line interface.
30 @ First, here are the C includes. |avl.h| is needed because of an
31 |avl_allocator| that is defined in |mplib.h|
42 #if defined (HAVE_SYS_TIME_H)
44 #elif defined (HAVE_SYS_TIMEB_H)
45 #include <sys/timeb.h>
52 #include <kpathsea/kpathsea.h>
53 extern char *kpathsea_version_string;
54 extern string kpse_program_name;
55 @= /*@@null@@*/ @> static char *mpost_tex_program = NULL;
56 static int debug = 0; /* debugging for makempx */
58 @ Allocating a bit of memory, with error detection:
60 @d mpost_xfree(A) do { if (A!=NULL) free(A); A=NULL; } while (0)
63 @= /*@@only@@*/ /*@@out@@*/ @> static void *mpost_xmalloc (size_t bytes) {
64 void *w = malloc (bytes);
66 fprintf(stderr,"Out of memory!\n");
71 @= /*@@only@@*/ @> static char *mpost_xstrdup(const char *s) {
75 fprintf(stderr,"Out of memory!\n");
80 static char *mpost_itoa (int i) {
83 unsigned v = (unsigned)abs(i);
84 memset(res,0,32*sizeof(char));
86 char d = (char)(v % 10);
94 return mpost_xstrdup(res+idx);
99 static void mpost_run_editor (MP mp, char *fname, int fline) {
100 char *temp, *command, *edit_value;
102 boolean sdone, ddone;
103 sdone = ddone = false;
104 edit_value = kpse_var_value ("MPEDIT");
105 if (edit_value == NULL)
106 edit_value = getenv("EDITOR");
107 if (edit_value == NULL) {
108 fprintf (stderr,"call_edit: can't find a suitable MPEDIT or EDITOR variable\n");
111 command = (string) mpost_xmalloc (strlen (edit_value) + strlen(fname) + 11 + 3);
113 while ((c = *edit_value++) != (char)0) {
115 switch (c = *edit_value++) {
118 fprintf (stderr,"call_edit: `%%d' appears twice in editor command\n");
121 char *s = mpost_itoa(fline);
132 fprintf (stderr,"call_edit: `%%s' appears twice in editor command\n");
135 while (*fname != '\0')
145 /* Back up to the null to force termination. */
158 if (system (command) != 0)
159 fprintf (stderr, "! Trouble executing `%s'.\n", command);
164 @<Register the callback routines@>=
165 options->run_editor = mpost_run_editor;
169 static string normalize_quotes (const char *name, const char *mesg) {
170 boolean quoted = false;
171 boolean must_quote = (strchr(name, ' ') != NULL);
172 /* Leave room for quotes and NUL. */
173 string ret = (string)mpost_xmalloc(strlen(name)+3);
179 for (q = name; *q != '\0'; q++) {
189 fprintf(stderr, "! Unbalanced quotes in %s %s\n", mesg, name);
196 @= /*@@null@@*/ @> static char *makempx_find_file (MPX mpx, const char *nam,
197 const char *mode, int ftype) {
201 if (mode[0] != 'r') {
204 req = true; fmt = -1;
206 case mpx_tfm_format: fmt = kpse_tfm_format; break;
207 case mpx_vf_format: fmt = kpse_vf_format; req = false; break;
208 case mpx_trfontmap_format: fmt = kpse_mpsupport_format; break;
209 case mpx_trcharadj_format: fmt = kpse_mpsupport_format; break;
210 case mpx_desc_format: fmt = kpse_troff_font_format; break;
211 case mpx_fontdesc_format: fmt = kpse_troff_font_format; break;
212 case mpx_specchar_format: fmt = kpse_mpsupport_format; break;
214 if (fmt<0) return NULL;
215 return kpse_find_file (nam, fmt, req);
218 @ Invoke makempx (or troffmpx) to make sure there is an up-to-date
219 .mpx file for a given .mp file. (Original from John Hobby 3/14/90)
221 @d default_args " --parse-first-line --interaction=nonstopmode"
223 @d TROFF "soelim | eqn -Tps -d$$ | troff -Tps"
227 #define MPXCOMMAND "makempx"
229 static int mpost_run_make_mpx (MP mp, char *mpname, char *mpxname) {
231 char *cnf_cmd = kpse_var_value ("MPXCOMMAND");
233 if (cnf_cmd != NULL && (strcmp (cnf_cmd, "0")==0)) {
234 /* If they turned off this feature, just return success. */
238 /* We will invoke something. Compile-time default if nothing else. */
240 char *tmp = normalize_quotes(mpname, "mpname");
241 char *qmpname = kpse_find_file (tmp,kpse_mp_format, true);
242 char *qmpxname = normalize_quotes(mpxname, "mpxname");
245 if (mp_troff_mode(mp)!=0)
246 cmd = concatn (cnf_cmd, " -troff ",
247 qmpname, " ", qmpxname, NULL);
248 else if (mpost_tex_program!=NULL && *mpost_tex_program != '\0')
249 cmd = concatn (cnf_cmd, " -tex=", mpost_tex_program, " ",
250 qmpname, " ", qmpxname, NULL);
252 cmd = concatn (cnf_cmd, " -tex ", qmpname, " ", qmpxname, NULL);
257 mpost_xfree(qmpname);
258 mpost_xfree(qmpxname);
260 mpx_options * mpxopt;
262 char *maincmd = NULL;
263 int mpxmode = mp_troff_mode(mp);
264 char *mpversion = mp_metapost_version () ;
265 mpxopt = mpost_xmalloc(sizeof(mpx_options));
266 if (mpost_tex_program != NULL && *mpost_tex_program != '\0') {
267 maincmd = mpost_xstrdup(mpost_tex_program);
269 if (mpxmode == mpx_tex_mode) {
270 s = kpse_var_value("TEX");
271 if (s==NULL) s = kpse_var_value("MPXMAINCMD");
272 if (s==NULL) s = mpost_xstrdup (TEX);
273 maincmd = (char *)mpost_xmalloc (strlen(s)+strlen(default_args)+1);
275 strcat(maincmd,default_args);
278 s = kpse_var_value("TROFF");
279 if (s==NULL) s = kpse_var_value("MPXMAINCMD");
280 if (s==NULL) s = mpost_xstrdup (TROFF);
284 mpxopt->mode = mpxmode;
285 mpxopt->cmd = maincmd;
286 mpxopt->mptexpre = kpse_var_value("MPTEXPRE");
287 mpxopt->debug = debug;
288 mpxopt->mpname = qmpname;
289 mpxopt->mpxname = qmpxname;
290 mpxopt->find_file = makempx_find_file;
292 char *banner = "% Written by metapost version ";
293 mpxopt->banner = mpost_xmalloc(strlen(mpversion)+strlen(banner)+1);
294 strcpy (mpxopt->banner, banner);
295 strcat (mpxopt->banner, mpversion);
297 ret = mpx_makempx(mpxopt);
298 mpost_xfree(mpxopt->cmd);
299 mpost_xfree(mpxopt->mptexpre);
300 mpost_xfree(mpxopt->banner);
301 mpost_xfree(mpxopt->mpname);
302 mpost_xfree(mpxopt->mpxname);
304 mpost_xfree(mpversion);
308 mpost_xfree (cnf_cmd);
309 return (int)(ret == 0);
313 @<Register the callback routines@>=
315 options->run_make_mpx = mpost_run_make_mpx;
319 static int get_random_seed (void) {
321 #if defined (HAVE_GETTIMEOFDAY)
323 gettimeofday(&tv, NULL);
324 ret = (tv.tv_usec + 1000000 * tv.tv_usec);
325 #elif defined (HAVE_FTIME)
328 ret = (tb.millitm + 1000 * tb.time);
330 time_t clock = time ((time_t*)NULL);
331 struct tm *tmptr = localtime(&clock);
333 ret = (tmptr->tm_sec + 60*(tmptr->tm_min + 60*tmptr->tm_hour));
338 @ @<Register the callback routines@>=
339 options->random_seed = get_random_seed();
342 static char *mpost_find_file(MP mp, const char *fname, const char *fmode, int ftype) {
348 if (ftype>=mp_filetype_text) {
349 s = kpse_find_file (fname, kpse_mp_format, 0);
352 case mp_filetype_program:
354 if (l>3 && strcmp(fname+l-3,".mf")==0) {
355 s = kpse_find_file (fname, kpse_mf_format, 0);
357 s = kpse_find_file (fname, kpse_mp_format, 0);
360 case mp_filetype_memfile:
361 s = kpse_find_file (fname, kpse_mem_format, 0);
363 case mp_filetype_metrics:
364 s = kpse_find_file (fname, kpse_tfm_format, 0);
366 case mp_filetype_fontmap:
367 s = kpse_find_file (fname, kpse_fontmap_format, 0);
369 case mp_filetype_font:
370 s = kpse_find_file (fname, kpse_type1_format, 0);
372 case mp_filetype_encoding:
373 s = kpse_find_file (fname, kpse_enc_format, 0);
379 s = mpost_xstrdup(fname); /* when writing */
384 @ @<Register the callback routines@>=
386 options->find_file = mpost_find_file;
389 static void *mpost_open_file(MP mp, const char *fname, const char *fmode, int ftype) {
392 if (ftype==mp_filetype_terminal) {
393 return (fmode[0] == 'r' ? stdin : stdout);
394 } else if (ftype==mp_filetype_error) {
397 s = mpost_find_file (mp, fname, fmode, ftype);
400 realmode[0] = *fmode;
403 ret = (void *)fopen(s,realmode);
411 @ @<Register the callback routines@>=
413 options->open_file = mpost_open_file;
416 @ At the moment, the command line is very simple.
418 @d option_is(A) ((strncmp(argv[a],"--" A, strlen(A)+2)==0) ||
419 (strncmp(argv[a],"-" A, strlen(A)+1)==0))
420 @d option_arg(B) (optarg != NULL && strncmp(optarg,B, strlen(B))==0)
423 @<Read and set command line options@>=
426 boolean ini_version_test = false;
428 mpost_optarg = strstr(argv[a],"=") ;
429 if (mpost_optarg!=NULL) {
431 if (*mpost_optarg == '\0') mpost_optarg=NULL;
433 if (option_is("ini")) {
434 ini_version_test = true;
435 } else if (option_is("debug")) {
437 } else if (option_is ("kpathsea-debug")) {
438 if (mpost_optarg!=NULL)
439 kpathsea_debug |= atoi (mpost_optarg);
440 } else if (option_is("mem")) {
441 if (mpost_optarg!=NULL) {
442 mpost_xfree(options->mem_name);
443 options->mem_name = mpost_xstrdup(mpost_optarg);
444 if (user_progname == NULL)
445 user_progname = mpost_optarg;
447 } else if (option_is("jobname")) {
448 if (mpost_optarg!=NULL) {
449 mpost_xfree(options->job_name);
450 options->job_name = mpost_xstrdup(mpost_optarg);
452 } else if (option_is ("progname")) {
453 user_progname = mpost_optarg;
454 } else if (option_is("troff")) {
455 options->troff_mode = (int)true;
456 } else if (option_is ("tex")) {
457 mpost_tex_program = mpost_optarg;
458 } else if (option_is("interaction")) {
459 if (option_arg("batchmode")) {
460 options->interaction = mp_batch_mode;
461 } else if (option_arg("nonstopmode")) {
462 options->interaction = mp_nonstop_mode;
463 } else if (option_arg("scrollmode")) {
464 options->interaction = mp_scroll_mode;
465 } else if (option_arg("errorstopmode")) {
466 options->interaction = mp_error_stop_mode;
468 fprintf(stdout,"unknown option argument %s\n", argv[a]);
470 } else if (option_is("no-kpathsea")) {
472 } else if (option_is("help")) {
473 @<Show help and exit@>;
474 } else if (option_is("version")) {
475 @<Show version and exit@>;
476 } else if (option_is("")) {
477 continue; /* ignore unknown options */
482 options->ini_version = (int)ini_version_test;
490 "Usage: mpost [OPTION] [&MEMNAME] [MPNAME[.mp]] [COMMANDS]\n"
492 " Run MetaPost on MPNAME, usually creating MPNAME.NNN (and perhaps\n"
493 " MPNAME.tfm), where NNN are the character numbers generated.\n"
494 " Any remaining COMMANDS are processed as MetaPost input,\n"
495 " after MPNAME is read.\n\n");
497 " If no arguments or options are specified, prompt for input.\n"
499 " -ini be inimpost, for dumping mem files\n"
500 " -interaction=STRING set interaction mode (STRING=batchmode/nonstopmode/\n"
501 " scrollmode/errorstopmode)\n"
502 " -jobname=STRING set the job name to STRING\n"
503 " -progname=STRING set program (and mem) name to STRING\n"
504 " -tex=TEXPROGRAM use TEXPROGRAM for text labels\n");
506 " -kpathsea-debug=NUMBER set path searching debugging flags according to\n"
507 " the bits of NUMBER\n"
508 " -mem=MEMNAME or &MEMNAME use MEMNAME instead of program name or a %%& line\n"
509 " -troff set prologues:=1 and assume TEXPROGRAM is really troff\n"
510 " -help display this help and exit\n"
511 " -version output version information and exit\n"
513 "Email bug reports to mp-implementors@@tug.org.\n"
521 char *s = mp_metapost_version();
525 "Copyright 2008 AT&T Bell Laboratories.\n"
526 "There is NO warranty. Redistribution of this software is\n"
527 "covered by the terms of both the MetaPost copyright and\n"
528 "the Lesser GNU General Public License.\n"
529 "For more information about these matters, see the file\n"
530 "named COPYING and the MetaPost source.\n"
531 "Primary author of MetaPost: John Hobby.\n"
532 "Current maintainer of MetaPost: Taco Hoekwater.\n"
538 @ The final part of the command line, after option processing, is
539 stored in the \MP\ instance, this will be taken as the first line of
542 @d command_line_size 256
544 @<Copy the rest of the command line@>=
546 mpost_xfree(options->command_line);
547 options->command_line = mpost_xmalloc(command_line_size);
548 strcpy(options->command_line,"");
554 if (k<(command_line_size-1)) {
555 options->command_line[k++] = *c;
559 options->command_line[k++] = ' ';
562 if (options->command_line[(k-1)] == ' ')
567 options->command_line[k] = '\0';
571 @ A simple function to get numerical |texmf.cnf| values
573 static int setup_var (int def, const char *var_name, boolean nokpse) {
575 char * expansion = kpse_var_value (var_name);
577 int conf_val = atoi (expansion);
587 @ @<Set up the banner line@>=
589 char * mpversion = mp_metapost_version () ;
590 const char * banner = "This is MetaPost, version ";
591 const char * kpsebanner_start = " (";
592 const char * kpsebanner_stop = ")";
593 mpost_xfree(options->banner);
594 options->banner = mpost_xmalloc(strlen(banner)+
596 strlen(kpsebanner_start)+
597 strlen(kpathsea_version_string)+
598 strlen(kpsebanner_stop)+1);
599 strcpy (options->banner, banner);
600 strcat (options->banner, mpversion);
601 strcat (options->banner, kpsebanner_start);
602 strcat (options->banner, kpathsea_version_string);
603 strcat (options->banner, kpsebanner_stop);
604 mpost_xfree(mpversion);
607 @ Precedence order is:
609 \item {} \.{-mem=MEMNAME} on the command line
610 \item {} \.{\&MEMNAME} on the command line
611 \item {} \.{\%\&MEM} as first line inside input file
612 \item {} \.{argv[0]} if all else fails
614 @<Discover the mem name@>=
616 char *m = NULL; /* head of potential |mem_name| */
617 char *n = NULL; /* a moving pointer */
618 if (options->command_line != NULL && *(options->command_line) == '&'){
619 m = mpost_xstrdup(options->command_line+1);
621 while (*n != '\0' && *n != ' ') n++;
622 while (*n == ' ') n++;
623 if (*n != '\0') { /* more command line to follow */
624 char *s = mpost_xstrdup(n);
626 while (*n == ' ' && n>m) n--;
628 *n ='\0'; /* this terminates |m| */
629 mpost_xfree(options->command_line);
630 options->command_line = s;
631 } else { /* only \.{\&MEMNAME} on command line */
633 while (*n == ' ' && n>m) n--;
635 *n ='\0'; /* this terminates |m| */
636 mpost_xfree(options->command_line);
638 if ( options->mem_name == NULL && *m != '\0') {
639 mpost_xfree(options->mem_name); /* for lint only */
640 options->mem_name = m;
646 if ( options->mem_name == NULL ) {
647 char *m = NULL; /* head of potential |job_name| */
648 char *n = NULL; /* a moving pointer */
649 if (options->command_line != NULL && *(options->command_line) != '\\'){
650 m = mpost_xstrdup(options->command_line);
652 while (*n != '\0' && *n != ' ') n++;
658 fname = kpse_find_file(m,kpse_mp_format,true);
662 FILE *F = fopen(fname,"r");
666 char *line = mpost_xmalloc(256);
667 if (fgets(line,255,F) == NULL) {
673 while (*line != '\0' && *line == ' ') line++;
676 while (*n != '\0' && *n == ' ') n++;
679 while (*n != '\0' && *n != ' ') n++;
682 while (*n == ' ' && n>m) n--;
683 *n ='\0'; /* this terminates |m| */
684 options->mem_name = mpost_xstrdup(m);
700 if ( options->mem_name == NULL )
701 if (kpse_program_name!=NULL)
702 options->mem_name = mpost_xstrdup(kpse_program_name);
705 @ Now this is really it: \MP\ starts and ends here.
708 int main (int argc, char **argv) { /* |start_here| */
709 int k; /* index into buffer */
710 int history; /* the exit status */
711 MP mp; /* a metapost instance */
712 struct MP_options * options; /* instance options */
713 int a=0; /* argc counter */
714 boolean nokpse = false; /* switch to {\it not} enable kpse */
715 char *user_progname = NULL; /* If the user overrides argv[0] with -progname. */
716 options = mp_options();
717 options->ini_version = (int)false;
718 options->print_found_names = (int)true;
719 @<Read and set command line options@>;
720 @= /*@@-nullpass@@*/ @>
722 kpse_set_program_name("mpost", user_progname);
723 @= /*@@=nullpass@@*/ @>
724 if(putenv((char *)"engine=metapost"))
725 fprintf(stdout,"warning: could not set up $engine\n");
726 options->main_memory = setup_var (50000,"main_memory",nokpse);
727 options->hash_size = (unsigned)setup_var (16384,"hash_size",nokpse);
728 options->max_in_open = setup_var (25,"max_in_open",nokpse);
729 options->param_size = setup_var (1500,"param_size",nokpse);
730 options->error_line = setup_var (79,"error_line",nokpse);
731 options->half_error_line = setup_var (50,"half_error_line",nokpse);
732 options->max_print_line = setup_var (100,"max_print_line",nokpse);
733 @<Set up the banner line@>;
734 @<Copy the rest of the command line@>;
735 if (options->ini_version!=(int)true) {
736 @<Discover the mem name@>;
738 @<Register the callback routines@>;
739 mp = mp_initialize(options);
740 mpost_xfree(options->command_line);
741 mpost_xfree(options->mem_name);
742 mpost_xfree(options->job_name);
743 mpost_xfree(options->banner);
747 history = mp_status(mp);
750 history = mp_run(mp);