1 /***********************************************************************
3 * This software is part of the ast package *
4 * Copyright (c) 2000-2009 AT&T Intellectual Property *
5 * and is licensed under the *
6 * Common Public License, Version 1.0 *
7 * by AT&T Intellectual Property *
9 * A copy of the License is available at *
10 * http://www.opensource.org/licenses/cpl1.0.txt *
11 * (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) *
13 * Information and Software Systems Research *
17 * Glenn Fowler <gsf@research.att.com> *
19 ***********************************************************************/
26 static const char usage
[] =
27 "[-?\n@(#)$Id: msggen (AT&T Research) 2002-03-11 $\n]"
29 "[+NAME?msggen - generate a machine independent formatted message catalog]"
30 "[+DESCRIPTION?\bmsggen\b merges the message text source files \amsgfile\a"
31 " into a machine independent formatted message catalog \acatfile\a."
32 " The file \acatfile\a will be created if it does not already exist."
33 " If \acatfile\a does exist, its messages will be included in the new"
34 " \acatfile\a. If set and message numbers collide, the new message"
35 " text defined in \amsgfile\a will replace the old message text"
36 " currently contained in \acatfile\a. Non-ASCII characters must be"
37 " UTF-8 encoded. \biconv\b(1) can be used to convert to/from UTF-8.]"
38 "[f:format?List the \bprintf\b(3) format signature for each message in"
39 " \acatfile\a. A format signature is one line containing one character"
40 " per format specification:]{"
55 "[l:list?List \acatfile\a in UTF-8 \amsgfile\a form.]"
56 "[s:set?Convert the \acatfile\a operand to a message set number and"
57 " print the number on the standard output.]"
58 "[+EXTENDED DESCRIPTION?Message text source files are in \bgencat\b(1)"
59 " format, defined as follows. Note that the fields of a message text"
60 " source line are separated by a single blank character. Any other"
61 " blank characters are considered as being part of the subsequent"
62 " field. The \bNL_*\b constants are defined in one or both of"
63 " \b<limits.h>\b and \b<nl_types.h>\b.]{"
64 " [+$ \acomment\a?A line beginning with \b$\b followed by a"
65 " blank character is treated as a comment.]"
66 " [+$delset \an\a \acomment\a?This line deletes message set"
67 " \an\a from an existing message catalog. \an\a"
68 " denotes the set number [1, \bNL_SETMAX\b]]. Any"
69 " text following the set number is treated as a"
71 " [+$quote \ac\a?This line specifies an optional quote"
72 " character \ac\a, which can be used to surround"
73 " \amessage-text\a so that trailing spaces or"
74 " empty messages are visible in a message source"
75 " line. By default, or if an empty \b$quote\b"
76 " directive is supplied, no quoting of \amessage-text\a"
77 " will be recognized.]"
78 " [+$set \an\a \acomment\a?This line specifies the set"
79 " identifier of the following messages until the next"
80 " \b$set\b or end-of-file appears. \an\a denotes the set"
81 " identifier, which is defined as a number in the range"
82 " [1, \bNL_SETMAX\b]]. Set numbers need not be"
83 " contiguous. Any text following the set identifier is"
84 " treated as a comment. If no \b$set\b directive is"
85 " specified in a message text source file, all messages"
86 " will be located in message set \b1\b.]"
87 " [+$translation \aidentification\a \aYYYY-MM-DD\a[,...]]?Append"
88 " translation info to the message catalog header. Only"
89 " the newest date for a given \aidentification\a"
90 " is retained in the catalog. Multiple translation lines"
91 " are combined into a single \b,\b separated list.]"
92 " [+\am\a \amessage-text\a?\am\a denotes the message identifier,"
93 " which is defined as a number in the range"
94 " [1, \bNL_MSGMAX\b]]. The message-text is stored in the"
95 " message catalogue with the set identifier specified by"
96 " the last \b$set\b directive, and with message"
97 " identifier \am\a. If the \amessage-text\a is empty,"
98 " and a blank character field separator is present, an"
99 " empty string is stored in the message catalogue. If a"
100 " message source line has a message number, but neither"
101 " a field separator nor \amessage-text\a, the existing"
102 " message with that number (if any) is deleted from the"
103 " catalogue. Message identifiers need not be contiguous."
104 " There are no \amessage-text\a length restrictions.]"
108 "\ncatfile [ msgfile ]\n"
111 "[+SEE ALSO?\bgencat\b(1), \biconv\b(1), \bmsgcc\b(1), \btranslate\b(1),"
129 * append s to the translation list
133 translation(Xl_t
* xp
, register char* s
)
142 for (; isspace(*s
); s
++);
143 for (d
= e
= 0, t
= s
; *t
; t
++)
150 else if (isspace(*t
))
155 for (px
= xp
; px
; px
= px
->next
)
156 if (streq(px
->name
, s
))
158 if (strcoll(px
->date
, d
) < 0)
161 if (!(px
->date
= strdup(d
)))
162 error(ERROR_SYSTEM
|3, "out of space [translation]");
168 if (!(px
= newof(0, Xl_t
, 1, strlen(s
))) || !(px
->date
= strdup(d
)))
169 error(ERROR_SYSTEM
|3, "out of space [translation]");
180 * sfprintf() with ccmaps(from,to)
184 ccsfprintf(int from
, int to
, Sfio_t
* sp
, const char* format
, ...)
191 va_start(ap
, format
);
193 n
= sfvprintf(sp
, format
, ap
);
194 else if (tp
= sfstropen())
196 n
= sfvprintf(tp
, format
, ap
);
198 ccmaps(s
, n
, from
, to
);
199 n
= sfwrite(sp
, s
, n
);
208 main(int argc
, char** argv
)
233 error_info
.id
= "msggen";
236 switch (optget(argv
, usage
))
248 error(ERROR_USAGE
|4, "%s", opt_info
.arg
);
251 error(2, "%s", opt_info
.arg
);
256 argv
+= opt_info
.index
;
257 if (error_info
.errors
|| !(catfile
= *argv
++))
258 error(ERROR_USAGE
|4, "%s", optusage(NiL
));
261 * set and list only need catfile
266 sfprintf(sfstdout
, "%d\n", mcindex(catfile
, NiL
, NiL
, NiL
));
267 return error_info
.errors
!= 0;
271 if (!(sp
= sfopen(NiL
, catfile
, "r")))
272 error(ERROR_SYSTEM
|3, "%s: cannot read catalog", catfile
);
273 if (!(mc
= mcopen(sp
)))
274 error(ERROR_SYSTEM
|3, "%s: catalog content error", catfile
);
278 for (set
= 1; set
<= mc
->num
; set
++)
279 if (mc
->set
[set
].num
)
281 sfprintf(sfstdout
, "$set %d\n", set
);
282 for (num
= 1; num
<= mc
->set
[set
].num
; num
++)
283 if (s
= mc
->set
[set
].msg
[num
])
284 sfprintf(sfstdout
, "%d \"%s\"\n", num
, fmtfmt(s
));
289 if (*mc
->translation
)
291 ccsfprintf(CC_NATIVE
, CC_ASCII
, sfstdout
, "$translation ");
292 sfprintf(sfstdout
, "%s", mc
->translation
);
293 ccsfprintf(CC_NATIVE
, CC_ASCII
, sfstdout
, "\n");
295 ccsfprintf(CC_NATIVE
, CC_ASCII
, sfstdout
, "$quote \"\n");
296 for (set
= 1; set
<= mc
->num
; set
++)
297 if (mc
->set
[set
].num
)
299 ccsfprintf(CC_NATIVE
, CC_ASCII
, sfstdout
, "$set %d\n", set
);
300 for (num
= 1; num
<= mc
->set
[set
].num
; num
++)
301 if (s
= mc
->set
[set
].msg
[num
])
303 ccsfprintf(CC_NATIVE
, CC_ASCII
, sfstdout
, "%d \"", num
);
312 sfputc(sfstdout
, 0x5C);
316 sfputc(sfstdout
, 0x5C);
320 sfputc(sfstdout
, 0x5C);
324 sfputc(sfstdout
, 0x5C);
328 sfputc(sfstdout
, 0x5C);
332 sfputc(sfstdout
, 0x5C);
336 sfputc(sfstdout
, 0x5C);
343 ccsfprintf(CC_NATIVE
, CC_ASCII
, sfstdout
, "\"\n");
348 return error_info
.errors
!= 0;
350 else if (!(msgfile
= *argv
++) || *argv
)
351 error(3, "exactly one message file must be specified");
354 * open the files and handles
357 if (!(tp
= sfstropen()))
358 error(ERROR_SYSTEM
|3, "out of space [string stream]");
359 if (!(mp
= sfopen(NiL
, msgfile
, "r")))
360 error(ERROR_SYSTEM
|3, "%s: cannot read message file", msgfile
);
361 sp
= sfopen(NiL
, catfile
, "r");
362 if (!(mc
= mcopen(sp
)))
363 error(ERROR_SYSTEM
|3, "%s: catalog content error", catfile
);
366 xp
= translation(xp
, mc
->translation
);
369 * read the message file
374 error_info
.file
= msgfile
;
375 while (s
= sfgetr(mp
, '\n', 1))
382 if (!*++s
|| isspace(*s
))
384 for (t
= s
; *s
&& !isspace(*s
); s
++);
387 if (streq(t
, "delset"))
391 num
= (int)strtol(s
, NiL
, 0);
392 if (num
< mc
->num
&& mc
->set
[num
].num
)
393 for (i
= 1; i
<= mc
->set
[num
].num
; i
++)
394 mcput(mc
, num
, i
, NiL
);
396 else if (streq(t
, "quote"))
398 else if (streq(t
, "set"))
402 num
= (int)strtol(s
, &e
, 0);
406 error(2, "set number expected");
408 else if (streq(t
, "translation"))
409 xp
= translation(xp
, s
);
414 num
= (int)strtol(s
, &e
, 0);
420 if (mcput(mc
, set
, num
, NiL
))
421 error(2, "(%d,%d): cannot delete message", set
, num
);
423 else if (isspace(*s
++))
425 if (t
> (s
+ 1) && *(t
-= 2) == '\\')
427 sfwrite(tp
, s
, t
- s
);
428 while (s
= sfgetr(mp
, '\n', 0))
432 if (t
<= (s
+ 1) || *(t
-= 2) != '\\')
434 sfwrite(tp
, s
, t
- s
);
436 if (!(s
= sfstruse(tp
)))
437 error(ERROR_SYSTEM
|3, "out of space");
443 error(2, "(%d,%d): %c quote expected", set
, num
, q
);
451 c
= chresc(s
- 1, &e
);
456 error(1, "nul character ignored");
465 error(2, "(%d,%d): characters after quote not expected", set
, num
);
471 if (mcput(mc
, set
, num
, s
))
472 error(2, "(%d,%d): cannot add message", set
, num
);
475 error(2, "message text expected");
478 error(2, "message number expected");
485 * fix up the translation record
493 for (bp
= 0, px
= xp
; px
; px
= px
->next
)
494 if (px
->date
&& (!bp
|| strcoll(bp
->date
, px
->date
) < 0))
498 sfprintf(tp
, "%s%s %s", t
, bp
->name
, bp
->date
);
502 if (!(mc
->translation
= sfstruse(tp
)))
503 error(ERROR_SYSTEM
|3, "out of space");
507 * dump the catalog to a local temporary
508 * rename if no errors
511 if (!(s
= pathtemp(NiL
, 0, "", error_info
.id
, NiL
)) || !(sp
= sfopen(NiL
, s
, "w")))
512 error(ERROR_SYSTEM
|3, "%s: cannot write catalog file", catfile
);
513 if (mcdump(mc
, sp
) || mcclose(mc
) || sfclose(sp
))
516 error(ERROR_SYSTEM
|3, "%s: temporary catalog file write error", s
);
519 if (rename(s
, catfile
))
520 error(ERROR_SYSTEM
|3, "%s: cannot rename from temporary catalog file %s", catfile
, s
);
521 return error_info
.errors
!= 0;