1/*
2 * Copyright (c) 2014, 2015, Juniper Networks, Inc.
3 * All rights reserved.
4 * This SOFTWARE is licensed under the LICENSE provided in the
5 * ../Copyright file. By downloading, installing, copying, or otherwise
6 * using the SOFTWARE, you agree to be bound by the terms of that
7 * LICENSE.
8 * Phil Shafer, July 2015
9 */
10
11#include <stdio.h>
12#include <stdlib.h>
13#include <stdarg.h>
14#include <unistd.h>
15#include <string.h>
16#include <ctype.h>
17#include <sys/queue.h>
18
19#include "xo_config.h"
20#include "xo.h"
21
22#include <getopt.h>		/* Include after xo.h for testing */
23
24#ifndef UNUSED
25#define UNUSED __attribute__ ((__unused__))
26#endif /* UNUSED */
27
28static int opt_warn;		/* Enable warnings */
29static int opt_numbers;		/* Number our fields */
30
31typedef struct xopo_msg_s {
32    TAILQ_ENTRY(xopo_msg_s) xm_link;
33    char *xm_plural;		/* If plural, points to the second part */
34    char xm_data[0];		/* Start of data */
35} xopo_msg_t;
36
37typedef TAILQ_HEAD(xopo_msg_list_s, xopo_msg_s) xopo_msg_list_t;
38
39static xopo_msg_list_t field_list;
40
41static void
42xopo_msg_cb (const char *str, unsigned len, int plural)
43{
44    int sz = sizeof(xopo_msg_t) + len + 1;
45    xopo_msg_t *xmp = malloc(sz);
46    if (xmp == NULL)
47	return;
48
49    bzero(xmp, sz);
50    memcpy(xmp->xm_data, str, len);
51    xmp->xm_data[len] = '\0';
52
53    if (plural) {
54	char *cp = strchr(xmp->xm_data, ',');
55	if (cp) {
56	    *cp++ = '\0';
57	    xmp->xm_plural = cp;
58	}
59    }
60
61    xopo_msg_t *xmp2;
62
63    TAILQ_FOREACH(xmp2, &field_list, xm_link) {
64	if (strcmp(xmp->xm_data, xmp2->xm_data) == 0) {
65	    /* Houston, we have a negative on that trajectory */
66	    free(xmp);
67	    return;
68	}
69    }
70
71    TAILQ_INSERT_TAIL(&field_list, xmp, xm_link);
72}
73
74static void
75print_version (void)
76{
77    fprintf(stderr, "libxo version %s%s\n",
78	    xo_version, xo_version_extra);
79    fprintf(stderr, "xopo version %s%s\n",
80	    LIBXO_VERSION, LIBXO_VERSION_EXTRA);
81}
82
83static void
84print_help (void)
85{
86    fprintf(stderr,
87"Usage: xopo [options] format [fields]\n"
88"    --help                Display this help text\n"
89"    --option <opts> -or -O <opts> Give formatting options\n"
90"    --output <file> -or -o <file> Use file as output destination\n"
91"    --po <file> or -f <file> Generate new msgid's for a po file\n"
92"    --simplify <text> OR -s <text> Show simplified form of the format string\n"
93"    --version             Display version information\n"
94"    --warn OR -W          Display warnings in text on stderr\n"
95);
96}
97
98static struct opts {
99    int o_help;
100    int o_version;
101} opts;
102
103static struct option long_opts[] = {
104    { "help", no_argument, &opts.o_help, 1 },
105    { "number", no_argument, NULL, 'n' },
106    { "option", required_argument, NULL, 'O' },
107    { "output", required_argument, NULL, 'o' },
108    { "po", required_argument, NULL, 'f' },
109    { "simplify", no_argument, NULL, 'S' },
110    { "warn", no_argument, NULL, 'W' },
111    { NULL, 0, NULL, 0 }
112};
113
114int
115main (int argc UNUSED, char **argv)
116{
117    char *opt_options = NULL;
118    char *opt_input = NULL;
119    char *opt_output = NULL;
120    char *opt_simplify = NULL;
121    int rc;
122
123    argc = xo_parse_args(argc, argv);
124    if (argc < 0)
125	return 1;
126
127    while ((rc = getopt_long(argc, argv, "f:no:O:s:W",
128				long_opts, NULL)) != -1) {
129	switch (rc) {
130	case 'f':
131	    opt_input = optarg;
132	    break;
133
134	case 'n':
135	    opt_numbers = 1;
136	    break;
137
138	case 'o':
139	    opt_output = optarg;
140	    break;
141
142	case 'O':
143	    opt_options = optarg;
144	    break;
145
146	case 's':
147	    opt_simplify = optarg;
148	    break;
149
150	case 'W':
151	    opt_warn = 1;
152	    xo_set_flags(NULL, XOF_WARN);
153	    break;
154
155	case ':':
156	    xo_errx(1, "missing argument");
157	    break;
158
159	case 0:
160	    if (opts.o_help) {
161		print_help();
162		return 1;
163
164	    } else if (opts.o_version) {
165		print_version();
166		return 0;
167
168	    } else {
169		print_help();
170		return 1;
171	    }
172
173	    bzero(&opts, sizeof(opts)); /* Reset all the options */
174	    break;
175
176	default:
177	    print_help();
178	    return 1;
179	}
180    }
181
182    argc -= optind;
183    argv += optind;
184
185    if (opt_options) {
186	rc = xo_set_options(NULL, opt_options);
187	if (rc < 0)
188	    xo_errx(1, "invalid options: %s", opt_options);
189    }
190
191    if (opt_simplify) {
192	char *fmt = xo_simplify_format(NULL, opt_simplify, opt_numbers, NULL);
193	if (fmt) {
194	    xo_emit("{:format}\n", fmt);
195	    free(fmt);
196	}
197	exit(0);
198    }
199
200    static char msgid[] = "msgid ";
201    char buf[BUFSIZ], *cp, *ep;
202    FILE *infile;
203    FILE *outfile;
204    TAILQ_INIT(&field_list);
205
206    if (opt_input) {
207	infile = fopen(opt_input, "r");
208	if (infile == NULL)
209	    xo_emit_err(1, "count not open input file: '{:filename}'",
210			opt_input);
211    } else
212	infile = stdin;
213
214    if (opt_output) {
215	unlink(opt_output);
216	outfile = fopen(opt_output, "w");
217	if (outfile == NULL)
218	    xo_emit_err(1, "count not open output file: '{:filename}'",
219			opt_output);
220    } else
221	outfile = stdout;
222
223    int blank = 0, line;
224
225    for (line = 1;; line++) {
226	if (fgets(buf, sizeof(buf), infile) == NULL)
227	    break;
228
229	if (buf[0] == '#' && buf[1] == '\n')
230	    continue;
231
232	blank = (buf[0] == '\n' && buf[1] == '\0');
233
234	if (strncmp(buf, msgid, sizeof(msgid) - 1) != 0) {
235	    fprintf(outfile, "%s", buf);
236	    continue;
237	}
238
239	for (cp = buf + sizeof(msgid); *cp; cp++)
240	    if (!isspace((int) *cp))
241		break;
242
243	if (*cp == '"')
244	    cp += 1;
245
246	ep = cp + strlen(cp);
247	if (ep > cp)
248	    ep -= 1;
249
250	while (isspace((int) *ep) && ep > cp)
251	    ep -= 1;
252
253	if (*ep != '"')
254	    *ep += 1;
255
256	*ep = '\0';
257
258	cp = xo_simplify_format(NULL, cp, opt_numbers, xopo_msg_cb);
259	if (cp) {
260	    fprintf(outfile, "msgid \"%s\"\n", cp);
261	    free(cp);
262	}
263    }
264
265    if (!blank)
266	fprintf(outfile, "\n");
267
268    xopo_msg_t *xmp;
269    TAILQ_FOREACH(xmp, &field_list, xm_link) {
270	if (xmp->xm_plural) {
271	    fprintf(outfile, "msgid \"%s\"\n"
272		    "msgid_plural \"%s\"\n"
273		    "msgstr[0] \"\"\n"
274		    "msgstr[1] \"\"\n\n",
275		    xmp->xm_data, xmp->xm_plural);
276	} else {
277	    fprintf(outfile, "msgid \"%s\"\nmsgstr \"\"\n\n", xmp->xm_data);
278	}
279    }
280
281    if (infile != stdin)
282	fclose(infile);
283    if (outfile != stdout)
284	fclose(outfile);
285
286    xo_finish();
287
288    return 0;
289}
290