1 /*
2  * metaseq.c
3  *
4  * Handling of the meta sequences
5  * Copyright (c) 1988, 89, 90, 91, 92, 93 Miguel Santana
6  * Copyright (c) 1995, 96, 97, 98 Akim Demaille, Miguel Santana
7  * $Id: metaseq.c,v 1.46 1998/03/02 08:57:01 demaille Exp $
8  */
9 
10 /*
11  * This file is part of a2ps.
12  *
13  * This program is free software; you can redistribute it and/or modify
14  * it under the terms of the GNU General Public License as published by
15  * the Free Software Foundation; either version 2, or (at your option)
16  * any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; see the file COPYING.  If not, write to
25  * the Free Software Foundation, 59 Temple Place - Suite 330,
26  * Boston, MA 02111-1307, USA.
27  */
28 
29 /*
30  * $Id: metaseq.c,v 1.46 1998/03/02 08:57:01 demaille Exp $
31  */
32 
33 #include "a2ps.h"
34 #include "routines.h"
35 #include "jobs.h"
36 #include "fjobs.h"
37 #include "stpncpy.h"
38 #include "message.h"
39 #include "metaseq.h"
40 #include "xobstack.h"
41 #include "pair_ht.h"
42 #include "prange.h"
43 #include "title.h"
44 
45 #define KEY_FORBIDDEN_CHARS ":(){}"
46 
47 /************************************************************************/
48 /* Handling the macro meta sequences					*/
49 /************************************************************************/
50 /*
51  * Creation
52  */
53 struct pair_htable *
macro_meta_sequence_table_new(void)54 macro_meta_sequence_table_new (void)
55 {
56   return pair_table_new ();
57 }
58 
59 /*
60  * Destruction
61  */
62 void
macro_meta_sequence_table_free(struct pair_htable * table)63 macro_meta_sequence_table_free (struct pair_htable * table)
64 {
65   pair_table_free (table);
66 }
67 
68 /*
69  * Check if is a valid name, and add.
70  * (Note, strdup is done so that no memory is shared with key and value)
71  *
72  */
73 bool
macro_meta_sequence_add(struct a2ps_job * job,const char * key,const char * value)74 macro_meta_sequence_add (struct a2ps_job * job,
75 			 const char * key, const char * value)
76 {
77   if (strpbrk (key, KEY_FORBIDDEN_CHARS))
78     return false;
79 
80   /* We want to remove any white space before the command */
81   pair_add (job->macro_meta_sequences,
82 	    key, value + strspn (value, "\t "));
83   return true;
84 }
85 
86 void
macro_meta_sequence_delete(struct a2ps_job * job,const char * key)87 macro_meta_sequence_delete (struct a2ps_job * job, const char * key)
88 {
89   pair_delete (job->macro_meta_sequences, key);
90 }
91 
92 char *
macro_meta_sequence_get(struct a2ps_job * job,const char * key)93 macro_meta_sequence_get (struct a2ps_job * job, const char * key)
94 {
95   return pair_get (job->macro_meta_sequences, key);
96 }
97 
98 void
macro_meta_sequences_list_short(struct a2ps_job * job,FILE * stream)99 macro_meta_sequences_list_short (struct a2ps_job * job, FILE * stream)
100 {
101   /* TRANS: Variables (formely called `macro meta sequences', eeeaerk)
102      are things such as #(psnup) which is substituted to a bigger strings,
103      e.g. -#v #?q|-q|| #?j|-d|| #?r||-c| -w#w -h#h */
104   fprintf (stream, _("Known Variables"));
105   putc ('\n', stream);
106   pair_table_list_short (job->macro_meta_sequences, stream);
107 }
108 
109 void
macro_meta_sequences_list_long(struct a2ps_job * job,FILE * stream)110 macro_meta_sequences_list_long (struct a2ps_job * job,
111 				FILE * stream)
112 {
113   title (stream, '=', true, _("Known Variables"));
114   putc ('\n', stream);
115   pair_table_list_long (job->macro_meta_sequences, stream);
116 }
117 
118 /************************************************************************/
119 /* Expansion of a user string						*/
120 /************************************************************************/
121 /*
122  * Help macros for expand_user_string ()
123  */
124 
125 #define APPEND_CH(ch)					\
126   do {							\
127     int a;						\
128     if (width && justification < 0)			\
129       obstack_1grow (user_string_stack, ch);		\
130     for (a = 0; a < (int) width - 1; a++)		\
131       obstack_1grow (user_string_stack, padding);	\
132     if (!width || justification > 0)			\
133       obstack_1grow (user_string_stack, ch);		\
134   } while (0)
135 
136 #define APPEND_STR(str)						\
137   do {								\
138     size_t len = ustrlen (str);					\
139     size_t nspace;						\
140 								\
141     nspace = (len > width) ? 0 : (width - len);			\
142 								\
143     if (width && justification > 0)				\
144       for (; nspace; nspace--)					\
145         obstack_1grow (user_string_stack, padding);		\
146 								\
147     obstack_grow (user_string_stack, str, len);			\
148 								\
149     if (width && justification < 0)				\
150       for (; nspace; nspace--)					\
151         obstack_1grow (user_string_stack, padding);		\
152   } while (0)
153 
154 /*
155  * We can't use strtok, that would skip the empty fields
156  */
157 #define SPLIT(to,sep,esc,cat)						\
158    do {									\
159      to = next ;							\
160      next = ustrchr (next, sep);					\
161      if (!next)								\
162        error (1, 0, _("%s: missing `%c' for %s%c escape"),		\
163 	      context_name, sep, esc, cat);				\
164      *next++ = '\0';							\
165    } while (0)
166 
167 /*
168  * An enumeration can be limited by the width.
169  * If width = 0, no limit.
170  * If width > 0, upper limit.
171  * If width < 0, limit to n - width objects
172  */
173 #define limit_by_width(_num_)		\
174      (((width > 0)			\
175        && (justification > 0)		\
176        && (width < _num_)) 		\
177       ? width				\
178       : ((width > 0)			\
179 	  && (justification < 0)	\
180 	  && (width <= _num_) 		\
181 	 ? (_num_ - width)		\
182 	 : _num_))
183 
184 #define fjob(_array_,_num_)	\
185 	((struct file_job *) _array_->content [_num_])
186 
187 
188 /*
189  * Using the data in JOB, and in the current FILE data descriptor,
190  * expand the possilbity escaped STR in the current USER_STRING_STACK.
191  * Use CONTEXT_NAME as a mean to report more understandable error/logs.
192  */
193 static void
grow_user_string_obstack(struct obstack * user_string_stack,struct a2ps_job * job,struct file_job * file,const uchar * context_name,const uchar * str)194 grow_user_string_obstack (struct obstack * user_string_stack,
195 			  struct a2ps_job * job,
196 			  struct file_job * file,
197 			  const uchar * context_name,
198 			  const uchar * str)
199 {
200   uchar * cp, * cp2;
201   size_t i = 0, j;
202   uchar padding = ' ' ;	/* Char used to complete %20 (usually ` ' or `.' */
203   uchar buf[512], buf2[512], buf3[256];
204   size_t width = 0;
205   int justification = 1;
206 
207   /* Format string. */
208   for (i = 0; str[i] != '\0'; i++)
209     {
210       int type;
211 
212       type = str[i];
213       if (type == '%' || type == '$' || type == '#' || type == '\\') {
214 	i++;
215 	width = 0;
216 	justification = 1;
217 	padding = ' ';
218 
219 	/* Get optional width and justification. */
220 	if (str[i] == '-') {
221 	  i++;
222 	  justification = -1;
223 	  if (!ISDIGIT ((int) str[i]))
224 	    padding = str[i++];
225 	}
226 	if (str[i] == '+') {
227 	  i++;
228 	  justification = 1;
229 	  if (!ISDIGIT ((int) str[i]))
230 	    padding = str[i++];
231 	}
232 	while (ISDIGIT ((int) str[i]))
233 	  width = width * 10 + str[i++] - '0';
234 
235 	/* Handle escapes. */
236 	switch (type) {
237 	  /*
238 	   *		#
239 	   *		 #
240 	   *		  #
241 	   *		   #
242 	   *		    #
243 	   *		     #
244 	   *		      #
245 	   *
246 	   * Only escapes
247 	   */
248 	case '\\':
249 	  switch (str[i]) {
250 	  case 'f':	/* `\f' character \f */
251 	    APPEND_CH ('\f');
252 	    break;
253 
254 	  case 'n':	/* `\n' character \n */
255 	    APPEND_CH ('\n');
256 	    break;
257 
258 	  default:
259 	    APPEND_CH (str [i]);
260 	    break;
261 	  }
262 	  break;
263 
264 	  /*
265 	   *		###   #
266 	   *		# #  #
267 	   *		### #
268 	   *		   #
269 	   *		  # ###
270 	   *		 #  # #
271 	   *		#   ###
272 	   *
273 	   * Related to the whole context
274 	   */
275 	case '%':
276 	  /* General state related %-escapes. */
277 	  switch (str[i]) {
278 	  case '%':	/* `%%' character `%' */
279 	    APPEND_CH ('%');
280 	    break;
281 
282 	  case '#':	/* `%#': total number of files */
283 	    APPEND_CH (JOB_NB_FILES);
284 	    break;
285 
286 	  case 'a':	/* `%a' NLS'ed `printed by USERNAME */
287 	    sprintf ((char *) buf2,
288 		     _("Printed by %s"),
289 		     macro_meta_sequence_get (job, VAR_USER_NAME));
290 	    APPEND_STR (buf2);
291 	    break;
292 
293 	  case 'A':  /* `%A' NLS'ed `printed by USERNAME from MACHINE */
294 	    cp = macro_meta_sequence_get (job, VAR_USER_NAME);
295 	    cp2 = macro_meta_sequence_get (job, VAR_USER_HOST);
296 	    if (cp2)
297 	      sprintf ((char *) buf3,
298 		       _("Printed by %s from %s"), cp, cp2);
299 	    else
300 	      sprintf ((char *) buf3, _("Printed by %s"), cp);
301 	    APPEND_STR (buf3);
302 	    break;
303 
304 	  case 'c':	/* `%c' trailing component of pwd. */
305 	    cp = (uchar *) xgetcwd ();
306 	    if (!cp)
307 	      error (1, errno,
308 		     _("cannot get current working directory"));
309 	    cp2 = ustrrchr (cp, DIRECTORY_SEPARATOR);
310 	    if (cp2)
311 	      cp2++;
312 	    else
313 	      cp2 = cp;
314 	    APPEND_STR (cp2);
315 	    XFREE (cp);
316 	    break;
317 	  case 'C':	/* `%C' runtime in `hh:mm:ss' format */
318 	    sprintf ((char *)buf, "%d:%02d:%02d", job->run_tm.tm_hour,
319 		     job->run_tm.tm_min, job->run_tm.tm_sec);
320 	    APPEND_STR (buf);
321 	    break;
322 
323 	  case 'd':	/* `%d' current working directory */
324 	    cp = (uchar *) xgetcwd ();
325 	    if (!cp)
326 	      error (1, errno,
327 		     _("cannot get current working directory"));
328 	    APPEND_STR (cp);
329 	    XFREE (cp);
330 	    break;
331 
332 	  case 'D':
333 	    if (str[i + 1] == '{')
334 	      {
335 		/* `%D{}' format run date with strftime() */
336 		for (j = 0, i += 2;
337 		     j < sizeof (buf2) && str[i] && str[i] != '}';
338 		     i++, j++)
339 		  buf2[j] = str[i];
340 		if (str[i] != '}')
341 		  error (1, 0, _("%s: too long argument for %s escape"),
342 			 context_name, "%D{}");
343 
344 		buf2[j] = '\0';
345 		strftime ((char *) buf, sizeof (buf),
346 			  (char *) buf2, &job->run_tm);
347 	      }
348 	    else
349 	      {
350 		/* `%D' run date in `yy-mm-dd' format */
351 		sprintf ((char *)buf, "%02d-%02d-%02d",
352 			 job->run_tm.tm_year % 100,
353 			 job->run_tm.tm_mon + 1,
354 			 job->run_tm.tm_mday);
355 	      }
356 	    APPEND_STR (buf);
357 	    break;
358 
359 	  case 'e':	/* `%e' run date in localized short format */
360 	    strftime ((char *) buf, sizeof (buf),
361 		      /* Translators: please make a short date format
362 		       * according to the std form in your language, using
363 		       * the standard strftime(3) */
364 		      (_("%b %d, %y")), &job->run_tm);
365 	    APPEND_STR (buf);
366 	    break;
367 
368 	  case 'E':	/* `%E' run date in localized long format */
369 	    /* Translators: please make a long date format
370 	     * according to the std form in your language, using
371 	     * the standard strftime (3) */
372 	    strftime ((char *) buf, sizeof (buf),
373 		      (_("%A %B %d, %Y")), &job->run_tm);
374 	    APPEND_STR (buf);
375 	    break;
376 
377 	  case 'F':	/* `%F' run date in `dd.mm.yyyy' format */
378 	    sprintf ((char *)buf, "%d.%d.%d",
379 		     job->run_tm.tm_mday,
380 		     job->run_tm.tm_mon + 1,
381 		     job->run_tm.tm_year+1900);
382 	    APPEND_STR (buf);
383 	    break;
384 
385 	  case 'm':	/* `%m' the hostname up to the first `.' */
386 	    cp = macro_meta_sequence_get (job, VAR_USER_HOST);
387 	    cp2 = ALLOCA (uchar, strlen (cp) + 1);
388 	    strcpy (cp2, cp);
389 	    cp = ustrchr (cp2, '.');
390 	    if (cp)
391 	      *cp = '\0';
392 	    APPEND_STR (cp2);
393 	    break;
394 
395 	  case 'M':	/* `%M' the full hostname */
396 	    APPEND_STR (macro_meta_sequence_get (job, VAR_USER_HOST));
397 	    break;
398 
399 	  case 'n':	/* `%n' user's login */
400 	    APPEND_STR (macro_meta_sequence_get (job, VAR_USER_LOGIN));
401 	    break;
402 
403 	  case 'N':	/* `%N' user's name */
404 	    APPEND_STR (macro_meta_sequence_get (job, VAR_USER_NAME));
405 	    break;
406 
407 	  case 'p':	/* `%p' related to the pages of the job */
408 	    switch (str [++i]) {
409 	    case '.':	/* `%p.' current page number */
410 	      sprintf ((char *)buf, "%d", job->pages);
411 	      APPEND_STR (buf);
412 	      break;
413 
414 	    case '#':	/* `%p#' total number of pages */
415 	      APPEND_CH (JOB_NB_PAGES);
416 	      break;
417 
418 	    default:
419 	      error (1, 0, _("%s: unknown `%s' escape `%c' (%d)"),
420 		     context_name, "%p", str [i], str [i]);
421 	      break;
422 	    }
423 	    break;
424 
425 	  case 'q':	/* `%q' localized `Page %d' */
426 	    sprintf ((char *)buf, _("Page %d"), job->pages);
427 	    APPEND_STR (buf);
428 	    break;
429 
430 	  case 'Q':	/* `%Q' localized `Page %d/%c' */
431 	    sprintf ((char *)buf, _("Page %d/%c"),
432 		     job->pages, JOB_NB_PAGES);
433 	    APPEND_STR (buf);
434 	    break;
435 
436 	  case 's':	/* `%s' related to the sheets of the job */
437 	    switch (str [++i]) {
438 	    case '.':	/* `%s.' current sheet number */
439 	      sprintf ((char *)buf, "%d", job->sheets);
440 	      APPEND_STR (buf);
441 	      break;
442 
443 	    case '#':	/* `%s#' total number of sheets */
444 	      APPEND_CH (JOB_NB_SHEETS);
445 	      break;
446 
447 	    default:
448 	      error (1, 0, _("%s: unknown `%s' escape `%c' (%d)"),
449 		     context_name, "%s", str [i], str [i]);
450 	      break;
451 	    }
452 	    break;
453 
454 	  case 't':	/* `%t' runtime in 12-hour am/pm format */
455 	    sprintf ((char *)buf, "%d:%02d%s",
456 		     job->run_tm.tm_hour > 12
457 		     ? job->run_tm.tm_hour - 12 : job->run_tm.tm_hour,
458 		     job->run_tm.tm_min,
459 		     job->run_tm.tm_hour > 12 ? "pm" : "am");
460 	    APPEND_STR (buf);
461 	    break;
462 
463 	  case 'T':	/* `%T' runtime in 24-hour format */
464 	    sprintf ((char *)buf, "%d:%02d",
465 		     job->run_tm.tm_hour, job->run_tm.tm_min);
466 	    APPEND_STR (buf);
467 	    break;
468 
469 	  case '*':	/* `%*' runtime in 24-hour format with secs */
470 	    sprintf ((char *)buf, "%d:%02d:%02d",
471 		     job->run_tm.tm_hour,
472 		     job->run_tm.tm_min,
473 		     job->run_tm.tm_sec);
474 	    APPEND_STR (buf);
475 	    break;
476 
477 	  case 'V':	/* `%V': name & version of this program */
478 	    sprintf ((char *) buf, "%s %s", PACKAGE, VERSION);
479 	    APPEND_STR (buf);
480 	    break;
481 
482 	  case 'W':	/* `%W' run date in `mm/dd/yy' format */
483 	    sprintf ((char *)buf, "%02d/%02d/%02d",
484 		     job->run_tm.tm_mon + 1,
485 		     job->run_tm.tm_mday,
486 		     job->run_tm.tm_year % 100);
487 	    APPEND_STR (buf);
488 	    break;
489 
490 	  default:
491 	    error (1, 0, _("%s: unknown `%s' escape `%c' (%d)"),
492 		   context_name, "%", str[i], str[i]);
493 	    break;
494 
495 	  }
496 	  break;
497 
498 	  /*
499 	   *		 #####
500 	   *		#  #  #
501 	   *		#  #
502 	   *		 #####
503 	   *		   #  #
504 	   *		#  #  #
505 	   *		 #####
506 	   *
507 	   * Related to the curent file
508 	   */
509 	case '$':
510 	  /* Input file related $-escapes. */
511 	  switch (str[i]) {
512 	  case '$':	/* `$$' character `$' */
513 	    APPEND_CH ('$');
514 	    break;
515 
516 	  case '*':	/* `$*' modif time in 24-hour format with secs */
517 	    sprintf ((char *)buf, "%d:%02d:%02d",
518 		     file->mod_tm.tm_hour,
519 		     file->mod_tm.tm_min,
520 		     file->mod_tm.tm_sec);
521 	    APPEND_STR (buf);
522 	    break;
523 
524 	  case '(':	/* $(ENVVAR) FIXME: Some day, remove in favor of ${} */
525 	    for (j = 0, i++;
526 		 str[i] && str[i] != ')' && j < sizeof (buf) - 1;
527 		 i++)
528 	      buf[j++] = str[i];
529 
530 	    if (str[i] == '\0')
531 	      error (1, 0,  _("%s: missing `%c' for %s%c escape"),
532 		     context_name, ')', "$(", ')');
533 	    if (str[i] != ')')
534 	      error (1, 0, _("%s: too long argument for %s escape"),
535 		     context_name, "$()");
536 	    buf[j] = '\0';
537 
538 	    cp = (uchar *) getenv ((char *)buf);
539 	    if (cp)
540 	      APPEND_STR (cp);
541 	    break;
542 
543 	  case '{':	/* ${ENVVAR} or ${ENVVAR:-word} or  ${ENVVAR:+word} */
544 	    cp2 = UNULL;
545 	    for (j = 0 , i++ ; str[i] != '}' && j < sizeof (buf) - 1 ; i++)
546 	      switch (str [i]) {
547 	      case '\0':
548 		error (1, 0,  _("%s: missing `%c' for %s%c escape"),
549 		       context_name, '}', "${", '}');
550 		break;
551 
552 	      case ':':		/* End of the name of the envvar,
553 				 * start getting the word */
554 		buf[j++] = '\0';
555 		cp2 = buf + j;
556 		break;
557 
558 	      default:
559 		buf[j++] = str[i];
560 		break;
561 	      }
562 	    if (str[i] != '}')
563 	      error (1, 0, _("%s: too long argument for %s escape"),
564 		     context_name, "${}");
565 	    buf[j] = '\0';
566 
567 	    /* Get the value of the env var */
568 	    cp = (uchar *) getenv ((char *)buf);
569 	    if (IS_EMPTY (cp2))
570 	      {
571 		/* No word specified */
572 		if (cp)
573 		  APPEND_STR (cp);
574 	      }
575 	    else
576 	      {
577 		/* A word is specified.  See how it should be used */
578 		switch (*cp2) {
579 		case '-':	/* if envvar defined then value else word */
580 		  if (!IS_EMPTY (cp))
581 		    APPEND_STR (cp);
582 		  else
583 		    APPEND_STR (cp2 + 1);
584 		  break;
585 
586 		case '+':	/* if defined, then word */
587 		  if (!IS_EMPTY (cp))
588 		    APPEND_STR (cp2 + 1);
589 		  break;
590 
591 		default:
592 		  error (1, 0,
593 			 _("%s: invalid separator `%s%c' for `%s' escape"),
594 			 context_name, ":", *cp2, "${}");
595 		}
596 	      }
597 	    break;
598 
599 	  case '[':	/* `$[]' command line options */
600 	    if (!ISDIGIT ((int) str[i]))
601 	      error (1, 0,  _("%s: invalid argument for %s%c escape"),
602 		     context_name, "$[", ']');
603 	    {
604 	      size_t value = 0;
605 	      while (ISDIGIT ((int) str[i]))
606 		value = value * 10 + str[i++] - '0';
607 	      if (str[i] == '\0')
608 		error (1, 0,  _("%s: missing `%c' for %s%c escape"),
609 		       context_name, ']', "$[", ']');
610 	      if (str[i] != ']')
611 		error (1, 0,  _("%s: invalid argument for %s%c escape"),
612 		       context_name, "$[", ']');
613 
614 	      if (value < job->argc)
615 		APPEND_STR (job->argv [value]);
616 	    }
617 	    break;
618 
619 	  case '#':	/* `$#': input file number */
620 	    sprintf ((char *)buf, "%d", file->num);
621 	    APPEND_STR (buf);
622 	    break;
623 
624 	  case 'C':	/* `$C' modtime in `hh:mm:ss' format */
625 	    sprintf ((char *)buf, "%d:%02d:%02d",
626 		     file->mod_tm.tm_hour,
627 		     file->mod_tm.tm_min,
628 		     file->mod_tm.tm_sec);
629 	    APPEND_STR (buf);
630 	    break;
631 
632 	  case 'd':	/* `$d' directory part of the current file */
633 	    cp = ustrrchr (file->name, DIRECTORY_SEPARATOR);
634 	    if (cp) {
635 	      ustrncpy (buf, file->name, cp - file->name);
636 	      buf [cp - file->name] = '\0';
637 	      APPEND_STR (buf);
638 	    } else {
639 	      APPEND_CH ('.');
640 	    }
641 	    break;
642 
643 	  case 'D':
644 	    if (str[i + 1] == '{')
645 	      {
646 		/* `$D{}' format modification date with strftime() */
647 		for (j = 0, i += 2;
648 		     j < sizeof (buf2) && str[i] && str[i] != '}';
649 		     i++, j++)
650 		  buf2[j] = str[i];
651 		if (str[i] != '}')
652 		  error (1, 0, _("%s: too long argument for %s escape"),
653 			 context_name, "$D{}");
654 
655 		buf2[j] = '\0';
656 		strftime ((char *) buf, sizeof (buf),
657 			  (char *) buf2, &(file->mod_tm));
658 	      }
659 	    else
660 	      {
661 		/* `$D' mod date in `yy-mm-dd' format */
662 		sprintf ((char *)buf, "%02d-%02d-%02d",
663 			 file->mod_tm.tm_year % 100,
664 			 file->mod_tm.tm_mon + 1,
665 			 file->mod_tm.tm_mday);
666 	      }
667 	    APPEND_STR (buf);
668 	    break;
669 
670 	  case 'e':	/* `$e' mod date in localized short format */
671 	    /* Translators: please make a short date format
672 	     * according to the std form in your language, using
673 	     * GNU strftime(3) */
674 	    strftime ((char *) buf, sizeof (buf),
675 		      (_("%b %d, %y")), &(file->mod_tm));
676 	    APPEND_STR (buf);
677 	    break;
678 
679 	  case 'E':	/* `$E' mod date in localized long format */
680 	    strftime ((char *) buf, sizeof (buf),
681 		      /* Translators: please make a long date format
682 		       * according to the std form in your language, using
683 		       * GNU strftime(3) */
684 		      (_("%A %B %d, %Y")), &(file->mod_tm));
685 	    APPEND_STR (buf);
686 	    break;
687 
688 	  case 'f':	/* `$f' full file name	*/
689 	    APPEND_STR (file->name);
690 	    break;
691 
692 	  case 'F':	/* `$F' run date in `dd.mm.yyyy' format */
693 	    sprintf ((char *)buf, "%d.%d.%d",
694 		     file->mod_tm.tm_mday,
695 		     file->mod_tm.tm_mon + 1,
696 		     file->mod_tm.tm_year+1900);
697 	    APPEND_STR (buf);
698 	    break;
699 
700 	  case 'l':	/* `$l' related to the lines of the file */
701 	    switch (str [++i]) {
702 	    case '^':	/* $l^ top most line in the current page */
703 	      sprintf ((char *)buf, "%d", file->top_line);
704 	      APPEND_STR (buf);
705 	      break;
706 
707 	    case '.':	/* `$l.' current line */
708 	      sprintf ((char *)buf, "%d", file->lines - 1);
709 	      APPEND_STR (buf);
710 	      break;
711 
712 	    case '#':		/* `$l#' number of lines in this file */
713 	      if (file != CURRENT_FILE (job)) {
714 		/* This file is finised, we do know its real number of lines */
715 		sprintf ((char *)buf, "%d", file->lines);
716 		APPEND_STR (buf);
717 	      } else {
718 		/* It is not know: delay it to the end of the job */
719 		APPEND_CH (FILE_NB_LINES);
720 	      }
721 	      break;
722 
723 	    default:
724 	      error (1, 0, _("%s: unknown `%s' escape `%c' (%d)"),
725 		     context_name, "$l", str [i], str [i]);
726 	      break;
727 	    }
728 	    break;
729 
730 	  case 'N': /* `$N' input file name without suffix nor
731 		       directory. */
732 	    /* First, skip dirname */
733 	    cp = ustrrchr (file->name, DIRECTORY_SEPARATOR);
734 	    if (cp == NULL)
735 	      cp =file->name;
736 	    else
737 	      cp ++;
738 
739 	    /* Then, until the last dot */
740 	    cp2 = ustrrchr (cp, '.');
741 	    if (cp2) {
742 	      ustrncpy (buf, cp, cp2 - cp);
743 	      buf [cp2 - cp] = '\0';
744 	      APPEND_STR (buf);
745 	    } else
746 	      APPEND_STR (cp);
747 	    break;
748 
749 	  case 'n':	/* `$n' input file name without directory */
750 	    cp = ustrrchr (file->name, DIRECTORY_SEPARATOR);
751 	    if (cp == NULL)
752 	      cp = file->name;
753 	    else
754 	      cp ++;
755 	    APPEND_STR (cp);
756 	    break;
757 
758 	  case 'p':	/* `$p' related to the pages of the file */
759 	    switch (str [++i]) {
760 	    case '^':	/* `$p^' first page number of this file
761 			 * appearing in the current sheet */
762 	      sprintf ((char *)buf, "%d", file->top_page);
763 	      APPEND_STR (buf);
764 	      break;
765 
766 	    case '-':	/* `$p-' interval of the pages of the current file
767 			 * appearing in the current sheet */
768 	      if (file->top_page == file->pages)
769 		sprintf ((char *)buf, "%d", file->top_page);
770 	      else
771 		sprintf ((char *)buf, "%d-%d", file->top_page, file->pages);
772 	      APPEND_STR (buf);
773 	      break;
774 
775 	    case '<':	/* `$p<' first page number for this file */
776 	      sprintf ((char *)buf, "%d", file->first_page);
777 	      APPEND_STR (buf);
778 	      break;
779 
780 	    case '.':	/* `$p.' current page number */
781 	      sprintf ((char *)buf, "%d", file->pages);
782 	      APPEND_STR (buf);
783 	      break;
784 
785 	    case '>':	/* `$p>' last page number for this file */
786 	      if (file != CURRENT_FILE (job)) {
787 		/* This file is finised, we do know its last page */
788 		sprintf ((char *)buf, "%d", file->last_page);
789 		APPEND_STR (buf);
790 	      } else {
791 		/* It is not know: delay it to the end of the job */
792 		APPEND_CH (FILE_LAST_PAGE);
793 	      }
794 	      break;
795 
796 	    case '#':	/* `$p#' total number of pages */
797 	      if (file != CURRENT_FILE (job)) {
798 		/* This file is finised, we do know its real number of pages */
799 		sprintf ((char *)buf, "%d", file->pages);
800 		APPEND_STR (buf);
801 	      } else {
802 		/* It is not know: delay it to the end of the job */
803 		APPEND_CH (FILE_NB_PAGES);
804 	      }
805 	      break;
806 
807 	    default:
808 	      error (1, 0, _("%s: unknown `%s' escape `%c' (%d)"),
809 		     context_name, "$p", str [i], str [i]);
810 	      break;
811 	    }
812 	    break;
813 
814 	  case 'q':	/* `$q' localized `Page $p' */
815 	    sprintf ((char *)buf, _("Page %d"), file->pages);
816 	    APPEND_STR (buf);
817 	    break;
818 
819 	  case 'Q':	/* `$Q' localized `Page $p/$P' */
820 	    if (file != CURRENT_FILE (job))
821 	      /* This file is finised, we do know its real number of pages */
822 	      sprintf ((char *) buf, _("Page %d/%d"),
823 		       file->pages, file->pages);
824 	    else
825 	      /* It is not know: delay it to the end of the job */
826 	      sprintf ((char *) buf, _("Page %d/%c"),
827 		       file->pages,
828 		       FILE_NB_PAGES);
829 	    APPEND_STR (buf);
830 	    break;
831 
832 	  case 's':	/* `$s' related to the sheets of the file */
833 	    switch (str [++i]) {
834 	    case '<':	/* `$s<' first sheet for this file */
835 	      sprintf ((char *)buf, "%d", file->first_sheet);
836 	      APPEND_STR (buf);
837 	      break;
838 
839 	    case '.':	/* `$s.' current sheet number */
840 	      sprintf ((char *)buf, "%d", file->sheets);
841 	      APPEND_STR (buf);
842 	      break;
843 
844 	    case '>':	/* `$s>' last sheet for this file */
845 	      if (file != CURRENT_FILE (job)) {
846 		/* This file is finised, we do know its last sheet */
847 		sprintf ((char *)buf, "%d", file->last_sheet);
848 		APPEND_STR (buf);
849 	      } else {
850 		/* It is not know: delay it to the end of the job */
851 		APPEND_CH (FILE_LAST_SHEET);
852 	      }
853 	      break;
854 
855 	    case '#':	/* `$s#' total number of sheets */
856 	      if (file != CURRENT_FILE (job)) {
857 		/* This file is finised, we know its number of sheets */
858 		sprintf ((char *)buf, "%d", file->sheets);
859 		APPEND_STR (buf);
860 	      } else {
861 		/* It is not know: delay it to the end of the job */
862 		APPEND_CH (FILE_NB_SHEETS);
863 	      }
864 	      break;
865 
866 	    default:
867 	      error (1, 0, _("%s: unknown `%s' escape `%c' (%d)"),
868 		     context_name, "$s", str [i], str [i]);
869 	      break;
870 	    }
871 	    break;
872 
873 	  case 't':
874 	    switch (str[i + 1]) {
875 	    case '1':	/* `$t1' first marker grabbed from file */
876 	      i++;
877 	      APPEND_STR (job->tag1);
878 	      break;
879 
880 	    case '2':	/* `$t2' second marker grabbed from file */
881 	      i++;
882 	      APPEND_STR (job->tag2);
883 	      break;
884 
885 	    case '3':	/* `$t3' third marker grabbed from file */
886 	      i++;
887 	      APPEND_STR (job->tag3);
888 	      break;
889 
890 	    case '4':	/* `$t4' fourth marker grabbed from file */
891 	      i++;
892 	      APPEND_STR (job->tag4);
893 	      break;
894 
895 	    default:	/* `$t' runtime in 12-hour am/pm format */
896 	      sprintf ((char *)buf, "%d:%02d%s",
897 		       (file->mod_tm.tm_hour > 12
898 			?file->mod_tm.tm_hour-12
899 			:file->mod_tm.tm_hour),
900 		       file->mod_tm.tm_min,
901 		       file->mod_tm.tm_hour > 12 ? "pm" : "am");
902 	      APPEND_STR (buf);
903 	    }
904 	    break;
905 
906 	  case 'T':	/* `$T' runtime in 24-hour format */
907 	    sprintf ((char *)buf, "%d:%02d",
908 		     file->mod_tm.tm_hour,
909 		     file->mod_tm.tm_min);
910 	    APPEND_STR (buf);
911 	    break;
912 
913 	  case 'W':	/* `$W' run date in `mm/dd/yy' format */
914 	    sprintf ((char *)buf, "%02d/%02d/%02d",
915 		     file->mod_tm.tm_mon + 1,
916 		     file->mod_tm.tm_mday,
917 		     file->mod_tm.tm_year % 100);
918 	    APPEND_STR (buf);
919 	    break;
920 
921 	  default:
922 	    error (1, 0, _("%s: unknown `%s' escape `%c' (%d)"),
923 		   context_name, "$", str[i], str[i]);
924 	    break;
925 	  }
926 	  break;
927 
928 	  /*
929 	   *		  # #
930 	   *		  # #
931 	   *		#######
932 	   *		  # #
933 	   *		#######
934 	   *		  # #
935 	   *		  # #
936 	   */
937 	case '#':
938 	  switch (str[i]) {
939 	  case '#':	/* `##' character `#' */
940 	    APPEND_CH ('#');
941 	    break;
942 
943 	  case '(':	/* #(macro meta sequence)  */
944 	    /* FIXME: Some day should disapear in favor of #{} */
945 	    for (j = 0, i++;
946 		 str[i] && str[i] != ')' && j < sizeof (buf) - 1;
947 		 i++)
948 	      buf[j++] = str[i];
949 
950 	    if (str[i] == '\0')
951 	      error (1, 0, _("%s: missing `%c' for %s%c escape"),
952 		     context_name, ')', "#(", ')');
953 	    if (str[i] != ')')
954 	      error (1, 0, _("%s: too long argument for %s escape"),
955 		     context_name, "#()");
956 	    buf[j] = '\0';
957 
958 	    cp = (uchar *) macro_meta_sequence_get (job,
959 						    (char *) buf);
960 	    if (cp)
961 	      grow_user_string_obstack (user_string_stack,
962 					job, file,
963 					context_name, cp);
964 	    break;
965 
966 
967 	  case '{':	/* #{macro} or #{macro:-word} or ${macro:+word} */
968 	    cp2 = UNULL;
969 	    for (j = 0 , i++ ; str[i] != '}' && j < sizeof (buf) - 1 ; i++)
970 	      switch (str [i]) {
971 	      case '\0':
972 		error (1, 0,  _("%s: missing `%c' for %s%c escape"),
973 		       context_name, '}', "#{", '}');
974 		break;
975 
976 	      case ':':		/* End of the name of the macro,
977 				 * start getting the word */
978 		buf[j++] = '\0';
979 		cp2 = buf + j;
980 		break;
981 
982 	      default:
983 		buf[j++] = str[i];
984 		break;
985 	      }
986 	    if (str[i] != '}')
987 	      error (1, 0, _("%s: too long argument for %s escape"),
988 		     context_name, "#{}");
989 	    buf[j] = '\0';
990 
991 	    /* Get the value of the macro */
992 	    cp = (uchar *) macro_meta_sequence_get (job, (char *) buf);
993 	    if (IS_EMPTY (cp2))
994 	      {
995 		/* No word specified */
996 		if (cp)
997 		  grow_user_string_obstack (user_string_stack,
998 					    job, file,
999 					    context_name, cp);
1000 	      }
1001 	    else
1002 	      {
1003 		/* A word is specified.  See how it should be used */
1004 		switch (*cp2) {
1005 		case '-':	/* if macro defined value else word */
1006 		  if (!IS_EMPTY (cp))
1007 		    grow_user_string_obstack (user_string_stack,
1008 					      job, file,
1009 					      context_name, cp);
1010 		  else
1011 		    APPEND_STR (cp2 + 1);
1012 		  break;
1013 
1014 		case '+':	/* if macro defined, word */
1015 		  if (!IS_EMPTY (cp))
1016 		    APPEND_STR (cp2 + 1);
1017 		  break;
1018 
1019 		default:
1020 		  error (1, 0,
1021 			 _("%s: invalid separator `%s%c' for `%s' escape"),
1022 			 context_name, ":", *cp2, "#{}");
1023 		}
1024 	      }
1025 	    break;
1026 
1027 	  case '.':	/* `#.' the usual extension for
1028 			 * current output language */
1029 	    APPEND_STR ("ps");
1030 	    break;
1031 
1032 	  case '?':	/* `#?' if-then meta sequence */
1033 	    {
1034 	      int test = 0;
1035 	      uchar cond, sep;
1036 	      uchar * if_true, * if_false;
1037 	      uchar * next;
1038 
1039 	      cond = str[++i];
1040 	      sep = str[++i];
1041 	      next = xustrdup(str + ++i);
1042 
1043 	      SPLIT (if_true, sep, "#?", cond);
1044 	      SPLIT (if_false, sep, "#?", cond);
1045 	      i += next - if_true - 1;
1046 
1047 	      switch (cond) {
1048 	      case '1':	/* `#?1' Is the tag1 not empty? */
1049 		test = !IS_EMPTY(job->tag1);
1050 		break;
1051 	      case '2':	/* `#?2' Is the tag2 not empty? */
1052 		test = !IS_EMPTY(job->tag2);
1053 		break;
1054 	      case '3':	/* `#?3' Is the tag3 not empty? */
1055 		test = !IS_EMPTY(job->tag3);
1056 		break;
1057 	      case '4':	/* `#?4' Is the tag4 not empty? */
1058 		test = !IS_EMPTY(job->tag4);
1059 		break;
1060 
1061 	      case 'd': /* `#?d' Double sided printing		*/
1062 		test = job->duplex == duplex || job->duplex == tumble;
1063 		break;
1064 
1065   	      case 'j': /* `#?j' Bordering is asked (-j)		*/
1066  		test = job->border;
1067   		break;
1068 
1069  	      case 'l': /* `#?l' in landscape */
1070  		test = job->orientation == landscape;
1071  		break;
1072 
1073 	      case 'o':	/* `#?o' Only one virtual page per page (-1) */
1074 		test = ((job->rows * job->columns) == 1);
1075 		break;
1076 
1077 	      case 'p':	/* `#?p' A page range is specified		*/
1078 		/* FIXME: May depend of other things (odd etc) */
1079 		test = page_range_applies_above (job->page_range, job->pages);
1080 		break;
1081 
1082 	      case 'q':	/* `#?q' in quiet mode */
1083 		test = msg_verbosity == 0;
1084 		break;
1085 
1086 	      case 'r': /* `#?r' madir = row		*/
1087 		test = job->madir == madir_rows;
1088 		break;
1089 
1090 	      case 'V':	/* `#?V' verbose mode */
1091 		test = msg_test (msg_tool);
1092 		break;
1093 
1094 	      case 'v': /* `#?v' on a verso side */
1095 		test = job->sheets & 0x1;
1096 		break;
1097 
1098 	      default:
1099 		error (1, 0, _("%s: unknown `%s' escape `%c' (%d)"),
1100 		       context_name, "#?", cond, cond);
1101 		break;
1102 	      }
1103 	      /*
1104 	       * One might think there are problem in recursing
1105 	       * grow_user_string_obstack, because of the static
1106 	       * obstack.  It is true in general, but not
1107 	       * for this precise case, where the obstack
1108 	       * while keep on growing in the same
1109 	       * direction
1110 	       */
1111 	      if (test)
1112 		grow_user_string_obstack (user_string_stack,
1113 					  job, file,
1114 					  context_name, if_true);
1115 	      else
1116 		grow_user_string_obstack (user_string_stack,
1117 					  job, file,
1118 					  context_name, if_false);
1119 	      free (if_true);
1120 	    }
1121 	    break;
1122 
1123 	  case '!':	/* `#!' a enumeration of a category		*/
1124 	    {
1125 	      uchar category, sep;
1126 	      uchar * in, * between;
1127 	      uchar * next;
1128 
1129 	      category = str[++i];
1130 	      sep = str[++i];
1131 	      next = xustrdup(str + ++i);
1132 
1133 	      SPLIT (in, sep, "#!", category);
1134 	      SPLIT (between, sep, "#!", category);
1135 	      i += next - in - 1;
1136 
1137 	      switch (category) {
1138 	      case '$':		/* `#!$': enumeration of the arguments */
1139 		{
1140 		  size_t fnum, fmax;
1141 		  fmax = limit_by_width (job->argc);
1142 		  for (fnum = 0 ; fnum < fmax ; fnum++) {
1143 		    APPEND_STR (job->argv [fnum]);
1144 		    if (fnum < fmax - 1)
1145 		      grow_user_string_obstack (user_string_stack,
1146 						job,
1147 						fjob(job->jobs, fnum),
1148 						context_name, between);
1149 		  }
1150 		}
1151 		break;
1152 
1153 	      case 'f':		/* `#!f': enumeration of the input files */
1154 		{
1155 		  size_t fnum, fmax;
1156 		  fmax = limit_by_width (job->jobs->len);
1157 		  for (fnum = 0 ; fnum < fmax ; fnum++) {
1158 		    grow_user_string_obstack (user_string_stack,
1159 					      job,
1160 					      fjob(job->jobs, fnum),
1161 					      context_name, in);
1162 		    if (fnum < fmax - 1)
1163 		      grow_user_string_obstack (user_string_stack,
1164 						job,
1165 						fjob(job->jobs, fnum),
1166 						context_name, between);
1167 		  }
1168 		}
1169 		break;
1170 
1171 	      case 'F':		/* `#!F': enumeration of the input files
1172 				 * in alpha order */
1173 		{
1174 		  size_t fnum, fmax;
1175 		  struct darray * ordered;
1176 
1177 		  /* Make a ordered clone of the jobs array */
1178 		  ordered = da_clone (job->jobs);
1179 		  ordered->cmp = (da_cmp_func_t) file_name_cmp;
1180 		  da_qsort (ordered);
1181 		  fmax = limit_by_width (job->jobs->len);
1182 		  for (fnum = 0 ; fnum < fmax ; fnum++) {
1183 		    grow_user_string_obstack (user_string_stack,
1184 					      job,
1185 					      fjob (ordered, fnum),
1186 					      context_name, in);
1187 		    if (fnum < fmax - 1)
1188 		      grow_user_string_obstack (user_string_stack,
1189 						job,
1190 						fjob (ordered, fnum),
1191 						context_name, between);
1192 		  }
1193 		  da_erase (ordered);
1194 		}
1195 		break;
1196 
1197 	      case 's':		/* `#!s': enumeration of the input files
1198 				 * appearing in the current sheets */
1199 		{
1200 		  size_t fnum, fmax;
1201 		  struct darray * selected;
1202 
1203 		  /* Make a ordered clone of the jobs array */
1204 		  selected = da_clone (job->jobs);
1205 
1206 		  /* Make the selection:
1207 		   * Only the files before this sheet are known,
1208 		   * so just test on the last page number */
1209 		  fnum = 0 ;
1210 		  while (fnum < selected->len) {
1211 		    if (fjob (selected, fnum)->last_sheet < job->sheets)
1212 		      da_remove_at (selected, fnum, NULL);
1213 		    else
1214 		      fnum++;
1215 		  }
1216 
1217 		  fmax = limit_by_width (selected->len);
1218 		  for (fnum = 0 ; fnum < fmax ; fnum++) {
1219 		    grow_user_string_obstack (user_string_stack,
1220 					      job,
1221 					      fjob (selected, fnum),
1222 					      context_name, in);
1223 		    if (fnum < fmax - 1)
1224 		      grow_user_string_obstack (user_string_stack,
1225 						job,
1226 						fjob (selected, fnum),
1227 						context_name, between);
1228 		  }
1229 		  da_erase (selected);
1230 		}
1231 		break;
1232 
1233 	      default:
1234 		error (1, 0, _("%s: unknown `%s' escape `%c' (%d)"),
1235 		       context_name, "#!", category, category);
1236 		break;
1237 	      }
1238 	      free (in);
1239 	    }
1240 	    break;
1241 
1242 	  case 'f':	/* `#f0' to `#f9': temporary file names */
1243 	    {
1244 	      int k = str [++i] - '0';
1245 	      if (k < 0 || 9 < k)
1246 		error (1, 0, _("%s: unknown `%s' escape `%c' (%d)"),
1247 		       context_name, "#f", str [i], str [i]);
1248 	      tempname_ensure (job->tmp_filenames [k]);
1249 	      APPEND_STR (job->tmp_filenames [k]);
1250 	    }
1251 	    break;
1252 
1253 	  case 'h':	/* `#h' medium height in PS points		*/
1254 	    sprintf ((char *) buf, "%d", job->medium->h);
1255 	    APPEND_STR (buf);
1256 	    break;
1257 
1258 	  case 'o':	/* `#o' name of destination, before evaluation */
1259 	    APPEND_STR (a2ps_printers_flag_output_name_get (job->printers));
1260 	    break;
1261 
1262 	  case 'O':	/* `#O' name of destination, after evaluation */
1263 	    if (a2ps_printers_flag_output_is_printer_get (job->printers))
1264 	      grow_user_string_obstack
1265 		(user_string_stack, job, file,
1266 		 (const uchar *) _("output command"),
1267 		 (const uchar *) a2ps_printers_flag_output_name_get(job->printers));
1268 	    else
1269 	      APPEND_STR (a2ps_printers_flag_output_name_get (job->printers));
1270 	    break;
1271 
1272 	  case 'p':	/* `#p' page range of what remains to be printed.
1273 			 * E.g. with a2ps -a2,4-, then #p on page 3 gives 2- */
1274 	    page_range_to_buffer (job->page_range, buf, job->pages);
1275 	    APPEND_STR (buf);
1276 	    break;
1277 
1278 	  case 'v':	/* `#v' number of virtual pages */
1279 	    sprintf ((char *) buf, "%d", job->rows * job->columns);
1280 	    APPEND_STR (buf);
1281 	    break;
1282 
1283 	  case 'w':	/* `#w' medium width in PS points		*/
1284 	    sprintf ((char *) buf, "%d", job->medium->w);
1285 	    APPEND_STR (buf);
1286 	    break;
1287 
1288 	  default:
1289 	    error (1, 0, _("%s: unknown `%s' escape `%c' (%d)"),
1290 		   context_name, "#", str[i], str[i]);
1291 	    break;
1292 	  }
1293 	  break;
1294 	}
1295 	/* Reset width so the else-arm goes ok at the next round. */
1296 	width = 0;
1297 	justification = 1;
1298       }
1299       else
1300 	APPEND_CH (str[i]);
1301     }
1302 }
1303 
1304 
1305 /* The exported function.
1306    GIGO principle: if STR is NULL, output too.  */
1307 
1308 uchar *
expand_user_string(struct a2ps_job * job,struct file_job * file,const uchar * context_name,const uchar * str)1309 expand_user_string (struct a2ps_job * job,
1310 		    struct file_job * file,
1311 		    const uchar * context_name,
1312 		    const uchar * str)
1313 {
1314   static int first_time = 1;
1315   static struct obstack user_string_stack;
1316 
1317   uchar * res;
1318 
1319   if (first_time)
1320     {
1321       first_time = 0;
1322       obstack_init (&user_string_stack);
1323     }
1324 
1325   if (!str)
1326     return NULL;
1327 
1328   message (msg_meta,
1329 	   (stderr, "Expanding of %s user string (`%s')\n",
1330 	    context_name, str));
1331 
1332   grow_user_string_obstack (&user_string_stack,
1333 			    job, file, context_name, str);
1334 
1335   obstack_1grow (&user_string_stack, '\0');
1336   res = (uchar *) obstack_finish (&user_string_stack);
1337   obstack_free (&user_string_stack, res);
1338 
1339   message (msg_meta,
1340 	   (stderr, "Expansion of %s (`%s') is `%s'\n",
1341 	    context_name, str, res));
1342   return res;
1343 }
1344