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