tentative fix for issue 3 (ex 53)
[mplib] / src / texk / kpathsea / kpsewhich.c
1 /* kpsewhich -- standalone path lookup and variable expansion for Kpathsea.
2    Ideas from Thomas Esser, Pierre MacKay, and many others.
3
4    Copyright (C) 1995-2008 Karl Berry & Olaf Weber.
5
6    This library is free software; you can redistribute it and/or
7    modify it under the terms of the GNU Lesser General Public
8    License as published by the Free Software Foundation; either
9    version 2.1 of the License, or (at your option) any later version.
10
11    This library is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14    Lesser General Public License for more details.
15
16    You should have received a copy of the GNU Lesser General Public License
17    along with this library; if not, see <http://www.gnu.org/licenses/>.  */
18
19 #include <kpathsea/config.h>
20 #include <kpathsea/c-ctype.h>
21 #include <kpathsea/c-pathch.h>
22 #include <kpathsea/expand.h>
23 #include <kpathsea/getopt.h>
24 #include <kpathsea/line.h>
25 #include <kpathsea/pathsearch.h>
26 #include <kpathsea/proginit.h>
27 #include <kpathsea/str-list.h>
28 #include <kpathsea/tex-file.h>
29 #include <kpathsea/tex-glyph.h>
30 #include <kpathsea/variable.h>
31 #include <kpathsea/progname.h>
32
33
34 /* Base resolution. (-D, -dpi) */
35 unsigned dpi = 600;
36
37 /* For variable and path expansion.  (-expand-var, -expand-path,
38    -show-path, -separator) */
39 string var_to_expand = NULL;
40 string braces_to_expand = NULL;
41 string path_to_expand = NULL;
42 string path_to_show = NULL;
43 string var_to_value = NULL;
44
45 /* Only match files in given subdirs.  (-subdir) */
46 str_list_type subdir_paths;
47
48 /* The file type and path for lookups.  (-format, -path) */
49 kpse_file_format_type user_format = kpse_last_format;
50 string user_format_string;
51 string user_path;
52
53 /* Interactively ask for names to look up?  (-interactive) */
54 boolean interactive = false;
55
56 /* Search the disk as well as ls-R?  (-must-exist) */
57 boolean must_exist = false;
58
59 /* Return all matches, not just the first one?  (-all) */
60 boolean show_all = false;
61
62 /* The device name, for $MAKETEX_MODE.  (-mode) */
63 string mode = NULL;
64
65 /* The program name, for `.PROG' construct in texmf.cnf.  (-program) */
66 string progname = NULL;
67
68 /* The engine name, for '$engine' construct in texmf.cnf.  (-engine) */
69 string engine = NULL;
70 \f
71 /* Return the <number> substring in `<name>.<number><stuff>', if S has
72    that form.  If it doesn't, return 0.  */
73
74 static unsigned
75 find_dpi P1C(string, s)
76 {
77   unsigned dpi_number = 0;
78   string extension = find_suffix (s);
79   
80   if (extension != NULL)
81     sscanf (extension, "%u", &dpi_number);
82
83   return dpi_number;
84 }
85 \f
86 /* Use the file type from -format if that was specified, else guess
87    dynamically from NAME.  Return kpse_last_format if undeterminable.
88    This function is also used to parse the -format string, a case which
89    we distinguish by setting is_filename to false.
90
91    Note that a few filenames have been hard-coded for format types that
92    differ from what would be inferred from their extensions. */
93
94 static kpse_file_format_type
95 find_format P2C(string, name, boolean, is_filename)
96 {
97   kpse_file_format_type ret;
98   
99   if (is_filename && user_format != kpse_last_format) {
100     ret = user_format;
101   } else if (FILESTRCASEEQ (name, "pdftex.cfg")) {
102     ret = kpse_pdftex_config_format;
103   } else if (FILESTRCASEEQ (name, "config.ps")) {
104     ret = kpse_dvips_config_format;
105   } else {
106     int f;  /* kpse_file_format_type */
107     unsigned name_len = strlen (name);
108
109 /* Have to rely on `try_len' being declared here, since we can't assume
110    GNU C and statement expressions.  */
111 #define TRY_SUFFIX(ftry) (\
112   try_len = (ftry) ? strlen (ftry) : 0, \
113   (ftry) && try_len <= name_len \
114      && FILESTRCASEEQ (ftry, name + name_len - try_len))
115
116     f = 0;
117     while (f != kpse_last_format) {
118       unsigned try_len;
119       const_string *ext;
120       const_string ftry;
121       boolean found = false;
122       
123       if (!kpse_format_info[f].type)
124         kpse_init_format ((kpse_file_format_type)f);
125
126       if (!is_filename) {
127         /* Allow the long name, but only in the -format option.  We don't
128            want a filename confused with a format name.  */
129         ftry = kpse_format_info[f].type;
130         found = TRY_SUFFIX (ftry);
131       }
132       for (ext = kpse_format_info[f].suffix; !found && ext && *ext; ext++){
133         found = TRY_SUFFIX (*ext);
134       }      
135       for (ext = kpse_format_info[f].alt_suffix; !found && ext && *ext; ext++){
136         found = TRY_SUFFIX (*ext);
137       }
138
139       if (found)
140         break;
141
142       /* Some trickery here: the extensions for kpse_fmt_format can
143        * clash with other extensions in use, and we prefer for those
144        * others to be preferred.  And we don't want to change the
145        * integer value of kpse_fmt_format.  So skip it when first
146        * enountered, then use it when we've done everything else,
147        * and use it as the end-guard.
148        */
149       if (f == kpse_fmt_format) {
150         f = kpse_last_format;
151       } else if (++f == kpse_fmt_format) {
152         f++;
153       } else if (f == kpse_last_format) {
154         f = kpse_fmt_format;
155       }
156     }
157
158     /* If there was a match, f will be one past the correct value.  */
159     ret = f;
160   }
161   
162   return ret;
163 }
164
165
166 \f
167 /* Return newly-allocated NULL-terminated list of strings from MATCHES
168    that are prefixed with any of the subdirectories in SUBDIRS.  That
169    is, for a string S in MATCHES, its dirname must end with one of the
170    elements in SUBDIRS.  For instance, if subdir=foo/bar, that will
171    match a string foo/bar/baz or /some/texmf/foo/bar/baz.
172    
173    We don't reallocate the actual strings, just the list elements.
174    Perhaps later we will implement wildcards or // or something.  */
175
176 static string *
177 subdir_match P2C(str_list_type, subdirs,  string *, matches)
178 {
179   string *ret = XTALLOC1 (string);
180   unsigned len = 1;
181   unsigned m;
182   
183   for (m = 0; matches[m]; m++) {
184     unsigned loc;
185     unsigned e;
186     string s = xstrdup (matches[m]);
187     for (loc = strlen (s); loc > 0 && !IS_DIR_SEP (s[loc-1]); loc--)
188       ;
189     while (loc > 0 && IS_DIR_SEP (s[loc-1])) {
190       loc--;
191     }
192     s[loc] = 0;  /* wipe out basename */
193     
194     for (e = 0; e < STR_LIST_LENGTH (subdirs); e++) {
195       string subdir = STR_LIST_ELT (subdirs, e);
196       unsigned subdir_len = strlen (subdir);
197       while (subdir_len > 0 && IS_DIR_SEP (subdir[subdir_len-1])) {
198         subdir_len--;
199         subdir[subdir_len] = 0; /* remove trailing slashes from subdir spec */
200       }
201       if (FILESTRCASEEQ (subdir, s + loc - subdir_len)) {
202         /* matched, save this one.  */
203         XRETALLOC (ret, len + 1, string);
204         ret[len-1] = matches[m];
205         len++;
206       }
207     }
208     free (s);
209   }
210   ret[len-1] = NULL;
211   return ret;
212 }
213
214
215 \f
216 /* Look up a single filename NAME.  Return 0 if success, 1 if failure.  */
217
218 static unsigned
219 lookup P1C(string, name)
220 {
221   int i;
222   string ret = NULL;
223   string *ret_list = NULL;
224   
225   if (user_path) {
226     if (show_all) {
227       ret_list = kpse_all_path_search (user_path, name);
228     } else {
229        ret = kpse_path_search (user_path, name, must_exist);
230     }
231     
232   } else {
233     /* No user-specified search path, check user format or guess from NAME.  */
234     kpse_file_format_type fmt = find_format (name, true);
235
236     switch (fmt) {
237       case kpse_pk_format:
238       case kpse_gf_format:
239       case kpse_any_glyph_format:
240         {
241           kpse_glyph_file_type glyph_ret;
242           /* Try to extract the resolution from the name.  */
243           unsigned local_dpi = find_dpi (name);
244           if (!local_dpi)
245             local_dpi = dpi;
246           ret = kpse_find_glyph (remove_suffix (name), local_dpi, fmt,
247                                  &glyph_ret);
248         }
249         break;
250
251       case kpse_last_format:
252         /* If the suffix isn't recognized, assume it's a tex file. */
253         fmt = kpse_tex_format;
254         /* fall through */
255
256       default:
257         if (show_all) {
258           ret_list = kpse_find_file_generic (name, fmt, must_exist, true);
259         } else {
260           ret = kpse_find_file (name, fmt, must_exist);
261         }
262     }
263   }
264   
265   /* Turn single return into a null-terminated list for uniform treatment.  */
266   if (ret) {
267     ret_list = XTALLOC (2, string);
268     ret_list[0] = ret;
269     ret_list[1] = NULL;
270   }
271
272   /* Filter by subdirectories, if specified.  */
273   if (STR_LIST_LENGTH (subdir_paths) > 0) {
274     string *new_list = subdir_match (subdir_paths, ret_list);
275     free (ret_list);
276     ret_list = new_list;
277   }
278
279   /* Print output.  */
280   if (ret_list) {
281     for (i = 0; ret_list[i]; i++)
282       puts (ret_list[i]);
283     /* Save whether we found anything */
284     ret = ret_list[0];
285     free (ret_list);
286   }
287   
288   return ret == NULL;
289 }
290 \f
291 /* Reading the options.  */
292
293 #define USAGE "\n\
294 Standalone path lookup and expansion for Kpathsea.\n\
295 \n\
296 -all                   output all matches (one per line), not just the first.\n\
297 -debug=NUM             set debugging flags.\n\
298 -D, -dpi=NUM           use a base resolution of NUM; default 600.\n\
299 -engine=STRING         set engine name to STRING.\n\
300 -expand-braces=STRING  output variable and brace expansion of STRING.\n\
301 -expand-path=STRING    output complete path expansion of STRING.\n\
302 -expand-var=STRING     output variable expansion of STRING.\n\
303 -format=NAME           use file type NAME (see list below).\n\
304 -help                  print this message and exit.\n\
305 -interactive           ask for additional filenames to look up.\n\
306 [-no]-mktex=FMT        disable/enable mktexFMT generation (FMT=pk/mf/tex/tfm).\n\
307 -mode=STRING           set device name for $MAKETEX_MODE to STRING; no default.\n\
308 -must-exist            search the disk as well as ls-R if necessary.\n\
309 -path=STRING           search in the path STRING.\n\
310 -progname=STRING       set program name to STRING.\n\
311 -show-path=NAME        output search path for file type NAME (see list below).\n\
312 -subdir=STRING         only output matches whose directory part ends with STRING.\n\
313 -var-value=STRING      output the value of variable $STRING.\n\
314 -version               print version number and exit.\n \
315 "
316
317 /* Test whether getopt found an option ``A''.
318    Assumes the option index is in the variable `option_index', and the
319    option table in a variable `long_options'.  */
320 #define ARGUMENT_IS(a) STREQ (long_options[option_index].name, a)
321
322 /* SunOS cc can't initialize automatic structs.  */
323 static struct option long_options[]
324   = { { "D",                    1, 0, 0 },
325       { "all",                  0, (int *) &show_all, 1 },
326       { "debug",                1, 0, 0 },
327       { "dpi",                  1, 0, 0 },
328       { "engine",               1, 0, 0 },
329       { "expand-braces",        1, 0, 0 },
330       { "expand-path",          1, 0, 0 },
331       { "expand-var",           1, 0, 0 },
332       { "format",               1, 0, 0 },
333       { "help",                 0, 0, 0 },
334       { "interactive",          0, (int *) &interactive, 1 },
335       { "mktex",                1, 0, 0 },
336       { "mode",                 1, 0, 0 },
337       { "must-exist",           0, (int *) &must_exist, 1 },
338       { "path",                 1, 0, 0 },
339       { "no-mktex",             1, 0, 0 },
340       { "progname",             1, 0, 0 },
341       { "separator",            1, 0, 0 },
342       { "subdir",               1, 0, 0 },
343       { "show-path",            1, 0, 0 },
344       { "var-value",            1, 0, 0 },
345       { "version",              0, 0, 0 },
346       { 0, 0, 0, 0 } };
347
348 static void
349 read_command_line P2C(int, argc,  string *, argv)
350 {
351   int g;   /* `getopt' return code.  */
352   int option_index;
353
354   for (;;) {
355     g = getopt_long_only (argc, argv, "", long_options, &option_index);
356
357     if (g == -1)
358       break;
359
360     if (g == '?')
361       exit (1);  /* Unknown option.  */
362
363     assert (g == 0); /* We have no short option names.  */
364
365     if (ARGUMENT_IS ("debug")) {
366       kpathsea_debug |= atoi (optarg);
367
368     } else if (ARGUMENT_IS ("dpi") || ARGUMENT_IS ("D")) {
369       dpi = atoi (optarg);
370
371     } else if (ARGUMENT_IS ("engine")) {
372       engine = optarg;
373       
374     } else if (ARGUMENT_IS ("expand-braces")) {
375       braces_to_expand = optarg;
376       
377     } else if (ARGUMENT_IS ("expand-path")) {
378       path_to_expand = optarg;
379
380     } else if (ARGUMENT_IS ("expand-var")) {
381       var_to_expand = optarg;
382
383     } else if (ARGUMENT_IS ("format")) {
384       user_format_string = optarg;
385
386     } else if (ARGUMENT_IS ("help")) {
387       int f; /* kpse_file_format_type */
388       extern KPSEDLL char *kpse_bug_address; /* from version.c */
389       
390       printf ("Usage: %s [OPTION]... [FILENAME]...\n", argv[0]);
391       fputs (USAGE, stdout);
392       putchar ('\n');
393       fputs (kpse_bug_address, stdout);
394
395       /* Have to set this for init_format to work.  */
396       kpse_set_program_name (argv[0], progname);
397
398       puts ("\nRecognized format names and their suffixes:");
399       for (f = 0; f < kpse_last_format; f++) {
400         const_string *ext;
401         kpse_init_format ((kpse_file_format_type)f);
402         printf ("%s:", kpse_format_info[f].type);
403         for (ext = kpse_format_info[f].suffix; ext && *ext; ext++) {
404           putchar (' ');
405           fputs (*ext, stdout);
406         }
407         for (ext = kpse_format_info[f].alt_suffix; ext && *ext; ext++) {
408           putchar (' ');
409           fputs (*ext, stdout);
410         }
411         putchar ('\n');
412       }
413
414       exit (0);
415
416     } else if (ARGUMENT_IS ("mktex")) {
417       kpse_maketex_option (optarg, true);
418
419     } else if (ARGUMENT_IS ("mode")) {
420       mode = optarg;
421
422     } else if (ARGUMENT_IS ("no-mktex")) {
423       kpse_maketex_option (optarg, false);
424
425     } else if (ARGUMENT_IS ("path")) {
426       user_path = optarg;
427
428     } else if (ARGUMENT_IS ("progname")) {
429       progname = optarg;
430
431     } else if (ARGUMENT_IS ("show-path")) {
432       path_to_show = optarg;
433       user_format_string = optarg;
434
435     } else if (ARGUMENT_IS ("subdir")) {
436       str_list_add (&subdir_paths, optarg);
437
438     } else if (ARGUMENT_IS ("var-value")) {
439       var_to_value = optarg;
440
441     } else if (ARGUMENT_IS ("version")) {
442       extern KPSEDLL char *kpathsea_version_string; /* from version.c */
443       puts (kpathsea_version_string);
444       puts ("Copyright 2008 Karl Berry & Olaf Weber.\n\
445 License LGPLv2.1+: GNU Lesser GPL version 2.1 or later <http://gnu.org/licenses/lgpl.html>\n\
446 This is free software: you are free to change and redistribute it.\n\
447 There is NO WARRANTY, to the extent permitted by law.\n");
448       exit (0);
449     }
450
451     /* Else it was just a flag; getopt has already done the assignment.  */
452   }
453   
454   if (user_path && user_format_string) {
455     fprintf (stderr, "-path (%s) and -format (%s) are mutually exclusive.\n",
456              user_path, user_format_string);
457     fputs ("Try `kpsewhich --help' for more information.\n", stderr);
458     exit (1);
459   }
460
461   if (optind == argc && !var_to_expand && !braces_to_expand && !path_to_expand
462                      && !path_to_show && !var_to_value) {
463     fputs ("Missing argument. Try `kpsewhich --help' for more information.\n",
464            stderr);
465     exit (1);
466   }
467 }
468 \f
469 int
470 main P2C(int, argc,  string *, argv)
471 {
472   unsigned unfound = 0;
473
474   read_command_line (argc, argv);
475
476   kpse_set_program_name (argv[0], progname);
477
478   if (engine)
479     xputenv("engine", engine);
480   
481   /* NULL for no fallback font.  */
482   kpse_init_prog (uppercasify (kpse_program_name), dpi, mode, NULL);
483   
484   /* Have to do this after setting the program name.  */
485   if (user_format_string)
486     user_format = find_format (user_format_string, false);
487   
488   /* Variable expansion.  */
489   if (var_to_expand)
490     puts (kpse_var_expand (var_to_expand));
491
492   /* Brace expansion. */
493   if (braces_to_expand)
494     puts (kpse_brace_expand (braces_to_expand));
495   
496   /* Path expansion. */
497   if (path_to_expand)
498     puts (kpse_path_expand (path_to_expand));
499
500   /* Show a search path. */
501   if (path_to_show) {
502     if (user_format != kpse_last_format) {
503       if (!kpse_format_info[user_format].type) /* needed if arg was numeric */
504         kpse_init_format (user_format);
505       puts (kpse_format_info[user_format].path);
506     } else {
507       WARNING1 ("kpsewhich: Ignoring unknown file type `%s'", path_to_show);
508     }
509   }
510
511   /* Var to value. */
512   if (var_to_value) {
513     string value = kpse_var_value (var_to_value);
514     if (!value) {
515       unfound++;
516       value="";
517     }
518     puts (value);
519   }
520   
521   /* --subdir must imply --all, since we filter here after doing the
522      search, rather than inside the search itself.  */
523   if (STR_LIST_LENGTH (subdir_paths) > 0) {
524     show_all = 1;
525   }
526   
527   for (; optind < argc; optind++) {
528     unfound += lookup (argv[optind]);
529   }
530
531   if (interactive) {
532     for (;;) {
533       string name = read_line (stdin);
534       if (!name || STREQ (name, "q") || STREQ (name, "quit")) break;
535       unfound += lookup (name);
536       free (name);
537     }
538   }
539   
540   return unfound > 255 ? 1 : unfound;
541 }