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|
46 #include <kpathsea/kpathsea.h>
47 extern char *kpathsea_version_string;
48 extern string kpse_program_name;
49 @= /*@@null@@*/ @> static char *mpost_tex_program = NULL;
50 static int debug = 0; /* debugging for makempx */
52 @ Allocating a bit of memory, with error detection:
54 @d mpost_xfree(A) do { if (A!=NULL) free(A); A=NULL; } while (0)
57 @= /*@@only@@*/ /*@@out@@*/ @> static void *mpost_xmalloc (size_t bytes) {
58 void *w = malloc (bytes);
60 fprintf(stderr,"Out of memory!\n");
65 @= /*@@only@@*/ @> static char *mpost_xstrdup(const char *s) {
69 fprintf(stderr,"Out of memory!\n");
74 static char *mpost_itoa (int i) {
77 unsigned v = (unsigned)abs(i);
78 memset(res,0,32*sizeof(char));
80 char d = (char)(v % 10);
88 return mpost_xstrdup(res+idx);
93 static void mpost_run_editor (MP mp, char *fname, int fline) {
94 char *temp, *command, *edit_value;
97 sdone = ddone = false;
98 edit_value = kpse_var_value ("MPEDIT");
99 if (edit_value == NULL)
100 edit_value = getenv("EDITOR");
101 if (edit_value == NULL) {
102 fprintf (stderr,"call_edit: can't find a suitable MPEDIT or EDITOR variable\n");
105 command = (string) mpost_xmalloc (strlen (edit_value) + strlen(fname) + 11 + 3);
107 while ((c = *edit_value++) != (char)0) {
109 switch (c = *edit_value++) {
112 fprintf (stderr,"call_edit: `%%d' appears twice in editor command\n");
115 char *s = mpost_itoa(fline);
126 fprintf (stderr,"call_edit: `%%s' appears twice in editor command\n");
129 while (*fname != '\0')
139 /* Back up to the null to force termination. */
152 if (system (command) != 0)
153 fprintf (stderr, "! Trouble executing `%s'.\n", command);
158 @<Register the callback routines@>=
159 options->run_editor = mpost_run_editor;
163 static string normalize_quotes (const char *name, const char *mesg) {
164 boolean quoted = false;
165 boolean must_quote = (strchr(name, ' ') != NULL);
166 /* Leave room for quotes and NUL. */
167 string ret = (string)mpost_xmalloc(strlen(name)+3);
173 for (q = name; *q != '\0'; q++) {
183 fprintf(stderr, "! Unbalanced quotes in %s %s\n", mesg, name);
190 @= /*@@null@@*/ @> static char *makempx_find_file (MPX mpx, const char *nam,
191 const char *mode, int ftype) {
195 if (mode[0] != 'r') {
198 req = true; fmt = -1;
200 case mpx_tfm_format: fmt = kpse_tfm_format; break;
201 case mpx_vf_format: fmt = kpse_vf_format; req = false; break;
202 case mpx_trfontmap_format: fmt = kpse_mpsupport_format; break;
203 case mpx_trcharadj_format: fmt = kpse_mpsupport_format; break;
204 case mpx_desc_format: fmt = kpse_troff_font_format; break;
205 case mpx_fontdesc_format: fmt = kpse_troff_font_format; break;
206 case mpx_specchar_format: fmt = kpse_mpsupport_format; break;
208 if (fmt<0) return NULL;
209 return kpse_find_file (nam, fmt, req);
212 @ Invoke makempx (or troffmpx) to make sure there is an up-to-date
213 .mpx file for a given .mp file. (Original from John Hobby 3/14/90)
215 @d default_args " --parse-first-line --interaction=nonstopmode"
217 @d TROFF "soelim | eqn -Tps -d$$ | troff -Tps"
221 #define MPXCOMMAND "makempx"
223 static int mpost_run_make_mpx (MP mp, char *mpname, char *mpxname) {
225 char *cnf_cmd = kpse_var_value ("MPXCOMMAND");
227 if (cnf_cmd != NULL && (strcmp (cnf_cmd, "0")==0)) {
228 /* If they turned off this feature, just return success. */
232 /* We will invoke something. Compile-time default if nothing else. */
234 char *tmp = normalize_quotes(mpname, "mpname");
235 char *qmpname = kpse_find_file (tmp,kpse_mp_format, true);
236 char *qmpxname = normalize_quotes(mpxname, "mpxname");
239 if (mp_troff_mode(mp)!=0)
240 cmd = concatn (cnf_cmd, " -troff ",
241 qmpname, " ", qmpxname, NULL);
242 else if (mpost_tex_program!=NULL && *mpost_tex_program != '\0')
243 cmd = concatn (cnf_cmd, " -tex=", mpost_tex_program, " ",
244 qmpname, " ", qmpxname, NULL);
246 cmd = concatn (cnf_cmd, " -tex ", qmpname, " ", qmpxname, NULL);
251 mpost_xfree(qmpname);
252 mpost_xfree(qmpxname);
254 makempx_options * mpxopt;
256 char *maincmd = NULL;
257 int mpxmode = mp_troff_mode(mp);
258 char *mpversion = mp_metapost_version () ;
259 mpxopt = mpost_xmalloc(sizeof(makempx_options));
260 if (mpost_tex_program != NULL && *mpost_tex_program != '\0') {
261 maincmd = mpost_xstrdup(mpost_tex_program);
263 if (mpxmode == mpx_tex_mode) {
264 s = kpse_var_value("TEX");
265 if (s==NULL) s = kpse_var_value("MPXMAINCMD");
266 if (s==NULL) s = mpost_xstrdup (TEX);
267 maincmd = (char *)mpost_xmalloc (strlen(s)+strlen(default_args)+1);
269 strcat(maincmd,default_args);
272 s = kpse_var_value("TROFF");
273 if (s==NULL) s = kpse_var_value("MPXMAINCMD");
274 if (s==NULL) s = mpost_xstrdup (TROFF);
278 mpxopt->mode = mpxmode;
279 mpxopt->cmd = maincmd;
280 mpxopt->mptexpre = kpse_var_value("MPTEXPRE");
281 mpxopt->debug = debug;
282 mpxopt->mpname = qmpname;
283 mpxopt->mpxname = qmpxname;
284 mpxopt->find_file = makempx_find_file;
286 char *banner = "% Written by metapost version ";
287 mpxopt->banner = mpost_xmalloc(strlen(mpversion)+strlen(banner)+1);
288 strcpy (mpxopt->banner, banner);
289 strcat (mpxopt->banner, mpversion);
291 ret = mp_makempx(mpxopt);
292 mpost_xfree(mpxopt->cmd);
293 mpost_xfree(mpxopt->mptexpre);
294 mpost_xfree(mpxopt->banner);
295 mpost_xfree(mpxopt->mpname);
296 mpost_xfree(mpxopt->mpxname);
298 mpost_xfree(mpversion);
302 mpost_xfree (cnf_cmd);
303 return (int)(ret == 0);
307 @<Register the callback routines@>=
309 options->run_make_mpx = mpost_run_make_mpx;
313 static int get_random_seed (void) {
315 #if defined (HAVE_GETTIMEOFDAY)
317 gettimeofday(&tv, NULL);
318 ret = (tv.tv_usec + 1000000 * tv.tv_usec);
319 #elif defined (HAVE_FTIME)
322 ret = (tb.millitm + 1000 * tb.time);
324 time_t clock = time ((time_t*)NULL);
325 struct tm *tmptr = localtime(&clock);
327 ret = (tmptr->tm_sec + 60*(tmptr->tm_min + 60*tmptr->tm_hour));
332 @ @<Register the callback routines@>=
333 options->random_seed = get_random_seed();
336 static char *mpost_find_file(MP mp, const char *fname, const char *fmode, int ftype) {
342 if (ftype>=mp_filetype_text) {
343 s = kpse_find_file (fname, kpse_mp_format, 0);
346 case mp_filetype_program:
348 if (l>3 && strcmp(fname+l-3,".mf")==0) {
349 s = kpse_find_file (fname, kpse_mf_format, 0);
351 s = kpse_find_file (fname, kpse_mp_format, 0);
354 case mp_filetype_memfile:
355 s = kpse_find_file (fname, kpse_mem_format, 0);
357 case mp_filetype_metrics:
358 s = kpse_find_file (fname, kpse_tfm_format, 0);
360 case mp_filetype_fontmap:
361 s = kpse_find_file (fname, kpse_fontmap_format, 0);
363 case mp_filetype_font:
364 s = kpse_find_file (fname, kpse_type1_format, 0);
366 case mp_filetype_encoding:
367 s = kpse_find_file (fname, kpse_enc_format, 0);
373 s = mpost_xstrdup(fname); /* when writing */
378 @ @<Register the callback routines@>=
380 options->find_file = mpost_find_file;
383 static void *mpost_open_file(MP mp, const char *fname, const char *fmode, int ftype) {
386 if (ftype==mp_filetype_terminal) {
387 return (fmode[0] == 'r' ? stdin : stdout);
388 } else if (ftype==mp_filetype_error) {
391 s = mpost_find_file (mp, fname, fmode, ftype);
394 realmode[0] = *fmode;
397 ret = (void *)fopen(s,realmode);
405 @ @<Register the callback routines@>=
407 options->open_file = mpost_open_file;
410 @ At the moment, the command line is very simple.
412 @d option_is(A) ((strncmp(argv[a],"--" A, strlen(A)+2)==0) ||
413 (strncmp(argv[a],"-" A, strlen(A)+1)==0))
414 @d option_arg(B) (optarg != NULL && strncmp(optarg,B, strlen(B))==0)
417 @<Read and set command line options@>=
420 boolean ini_version_test = false;
422 mpost_optarg = strstr(argv[a],"=") ;
423 if (mpost_optarg!=NULL) {
425 if (*mpost_optarg == '\0') mpost_optarg=NULL;
427 if (option_is("ini")) {
428 ini_version_test = true;
429 } else if (option_is("debug")) {
431 } else if (option_is ("kpathsea-debug")) {
432 if (mpost_optarg!=NULL)
433 kpathsea_debug |= atoi (mpost_optarg);
434 } else if (option_is("mem")) {
435 if (mpost_optarg!=NULL) {
436 mpost_xfree(options->mem_name);
437 options->mem_name = mpost_xstrdup(mpost_optarg);
438 if (user_progname == NULL)
439 user_progname = mpost_optarg;
441 } else if (option_is("jobname")) {
442 if (mpost_optarg!=NULL) {
443 mpost_xfree(options->job_name);
444 options->job_name = mpost_xstrdup(mpost_optarg);
446 } else if (option_is ("progname")) {
447 user_progname = mpost_optarg;
448 } else if (option_is("troff")) {
449 options->troff_mode = (int)true;
450 } else if (option_is ("tex")) {
451 mpost_tex_program = mpost_optarg;
452 } else if (option_is("interaction")) {
453 if (option_arg("batchmode")) {
454 options->interaction = mp_batch_mode;
455 } else if (option_arg("nonstopmode")) {
456 options->interaction = mp_nonstop_mode;
457 } else if (option_arg("scrollmode")) {
458 options->interaction = mp_scroll_mode;
459 } else if (option_arg("errorstopmode")) {
460 options->interaction = mp_error_stop_mode;
462 fprintf(stdout,"unknown option argument %s\n", argv[a]);
464 } else if (option_is("no-kpathsea")) {
466 } else if (option_is("help")) {
467 @<Show help and exit@>;
468 } else if (option_is("version")) {
469 @<Show version and exit@>;
470 } else if (option_is("")) {
471 continue; /* ignore unknown options */
476 options->ini_version = (int)ini_version_test;
484 "Usage: mpost [OPTION] [&MEMNAME] [MPNAME[.mp]] [COMMANDS]\n"
486 " Run MetaPost on MPNAME, usually creating MPNAME.NNN (and perhaps\n"
487 " MPNAME.tfm), where NNN are the character numbers generated.\n"
488 " Any remaining COMMANDS are processed as MetaPost input,\n"
489 " after MPNAME is read.\n\n");
491 " If no arguments or options are specified, prompt for input.\n"
493 " -ini be inimpost, for dumping mem files\n"
494 " -interaction=STRING set interaction mode (STRING=batchmode/nonstopmode/\n"
495 " scrollmode/errorstopmode)\n"
496 " -jobname=STRING set the job name to STRING\n"
497 " -progname=STRING set program (and mem) name to STRING\n");
499 " -tex=TEXPROGRAM use TEXPROGRAM for text labels\n"
500 " -kpathsea-debug=NUMBER set path searching debugging flags according to\n"
501 " the bits of NUMBER\n"
502 " -mem=MEMNAME or &MEMNAME use MEMNAME instead of program name or a %%& line\n"
503 " -troff set prologues:=1 and assume TEXPROGRAM is really troff\n"
504 " -help display this help and exit\n"
505 " -version output version information and exit\n"
507 "Email bug reports to mp-implementors@@tug.org.\n"
515 char *s = mp_metapost_version();
519 "Copyright 2008 AT&T Bell Laboratories.\n"
520 "There is NO warranty. Redistribution of this software is\n"
521 "covered by the terms of both the MetaPost copyright and\n"
522 "the Lesser GNU General Public License.\n"
523 "For more information about these matters, see the file\n"
524 "named COPYING and the MetaPost source.\n"
525 "Primary author of MetaPost: John Hobby.\n"
526 "Current maintainer of MetaPost: Taco Hoekwater.\n"
532 @ The final part of the command line, after option processing, is
533 stored in the \MP\ instance, this will be taken as the first line of
536 @d command_line_size 256
538 @<Copy the rest of the command line@>=
540 mpost_xfree(options->command_line);
541 options->command_line = mpost_xmalloc(command_line_size);
542 strcpy(options->command_line,"");
548 if (k<(command_line_size-1)) {
549 options->command_line[k++] = *c;
553 options->command_line[k++] = ' ';
556 if (options->command_line[(k-1)] == ' ')
561 options->command_line[k] = '\0';
565 @ A simple function to get numerical |texmf.cnf| values
567 static int setup_var (int def, const char *var_name, boolean nokpse) {
569 char * expansion = kpse_var_value (var_name);
571 int conf_val = atoi (expansion);
581 @ @<Set up the banner line@>=
583 char * mpversion = mp_metapost_version () ;
584 const char * banner = "This is MetaPost, version ";
585 const char * kpsebanner_start = " (";
586 const char * kpsebanner_stop = ")";
587 mpost_xfree(options->banner);
588 options->banner = mpost_xmalloc(strlen(banner)+
590 strlen(kpsebanner_start)+
591 strlen(kpathsea_version_string)+
592 strlen(kpsebanner_stop)+1);
593 strcpy (options->banner, banner);
594 strcat (options->banner, mpversion);
595 strcat (options->banner, kpsebanner_start);
596 strcat (options->banner, kpathsea_version_string);
597 strcat (options->banner, kpsebanner_stop);
598 mpost_xfree(mpversion);
601 @ Precedence order is:
603 \item {} \.{-mem=MEMNAME} on the command line
604 \item {} \.{\&MEMNAME} on the command line
605 \item {} \.{\%\&MEM} as first line inside input file
606 \item {} \.{argv[0]} if all else fails
608 @<Discover the mem name@>=
610 char *m = NULL; /* head of potential |mem_name| */
611 char *n = NULL; /* a moving pointer */
612 if (options->command_line != NULL && *(options->command_line) == '&'){
613 m = mpost_xstrdup(options->command_line+1);
615 while (*n != '\0' && *n != ' ') n++;
616 while (*n == ' ') n++;
617 if (*n != '\0') { /* more command line to follow */
618 char *s = mpost_xstrdup(n);
620 while (*n == ' ' && n>m) n--;
622 *n ='\0'; /* this terminates |m| */
623 mpost_xfree(options->command_line);
624 options->command_line = s;
625 } else { /* only \.{\&MEMNAME} on command line */
627 while (*n == ' ' && n>m) n--;
629 *n ='\0'; /* this terminates |m| */
630 mpost_xfree(options->command_line);
632 if ( options->mem_name == NULL && *m != '\0') {
633 mpost_xfree(options->mem_name); /* for lint only */
634 options->mem_name = m;
640 if ( options->mem_name == NULL ) {
641 char *m = NULL; /* head of potential |job_name| */
642 char *n = NULL; /* a moving pointer */
643 if (options->command_line != NULL && *(options->command_line) != '\\'){
644 m = mpost_xstrdup(options->command_line);
646 while (*n != '\0' && *n != ' ') n++;
652 fname = kpse_find_file(m,kpse_mp_format,true);
656 FILE *F = fopen(fname,"r");
660 char *line = mpost_xmalloc(256);
661 if (fgets(line,255,F) == NULL) {
667 while (*line != '\0' && *line == ' ') line++;
670 while (*n != '\0' && *n == ' ') n++;
673 while (*n != '\0' && *n != ' ') n++;
676 while (*n == ' ' && n>m) n--;
677 *n ='\0'; /* this terminates |m| */
678 options->mem_name = mpost_xstrdup(m);
694 if ( options->mem_name == NULL )
695 if (kpse_program_name!=NULL)
696 options->mem_name = mpost_xstrdup(kpse_program_name);
699 @ Now this is really it: \MP\ starts and ends here.
702 int main (int argc, char **argv) { /* |start_here| */
703 int k; /* index into buffer */
704 int history; /* the exit status */
705 MP mp; /* a metapost instance */
706 struct MP_options * options; /* instance options */
707 int a=0; /* argc counter */
708 boolean nokpse = false; /* switch to {\it not} enable kpse */
709 char *user_progname = NULL; /* If the user overrides argv[0] with -progname. */
710 options = mp_options();
711 options->ini_version = (int)false;
712 options->print_found_names = (int)true;
713 @<Read and set command line options@>;
714 @= /*@@-nullpass@@*/ @>
716 kpse_set_program_name("mpost", user_progname);
717 @= /*@@=nullpass@@*/ @>
718 if(putenv((char *)"engine=metapost"))
719 fprintf(stdout,"warning: could not set up $engine\n");
720 options->main_memory = setup_var (50000,"main_memory",nokpse);
721 options->hash_size = (unsigned)setup_var (16384,"hash_size",nokpse);
722 options->max_in_open = setup_var (25,"max_in_open",nokpse);
723 options->param_size = setup_var (1500,"param_size",nokpse);
724 options->error_line = setup_var (79,"error_line",nokpse);
725 options->half_error_line = setup_var (50,"half_error_line",nokpse);
726 options->max_print_line = setup_var (100,"max_print_line",nokpse);
727 @<Set up the banner line@>;
728 @<Copy the rest of the command line@>;
729 if (options->ini_version!=(int)true) {
730 @<Discover the mem name@>;
732 @<Register the callback routines@>;
733 mp = mp_initialize(options);
734 mpost_xfree(options->command_line);
735 mpost_xfree(options->mem_name);
736 mpost_xfree(options->job_name);
737 mpost_xfree(options->banner);
741 history = mp_status(mp);
744 history = mp_run(mp);