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 28 static int opt_warn; /* Enable warnings */ 29 static int opt_numbers; /* Number our fields */ 30 31 typedef 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 37 typedef TAILQ_HEAD(xopo_msg_list_s, xopo_msg_s) xopo_msg_list_t; 38 39 static xopo_msg_list_t field_list; 40 41 static void 42 xopo_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 74 static void 75 print_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 83 static void 84 print_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 98 static struct opts { 99 int o_help; 100 int o_version; 101 } opts; 102 103 static 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 114 int 115 main (int argc UNUSED, char **argv) 116 { 117 char *fmt = NULL; 118 char *opt_options = NULL; 119 char *opt_input = NULL; 120 char *opt_output = NULL; 121 char *opt_simplify = NULL; 122 int rc; 123 124 argc = xo_parse_args(argc, argv); 125 if (argc < 0) 126 return 1; 127 128 while ((rc = getopt_long(argc, argv, "f:no:O:s:W", 129 long_opts, NULL)) != -1) { 130 switch (rc) { 131 case 'f': 132 opt_input = optarg; 133 break; 134 135 case 'n': 136 opt_numbers = 1; 137 break; 138 139 case 'o': 140 opt_output = optarg; 141 break; 142 143 case 'O': 144 opt_options = optarg; 145 break; 146 147 case 's': 148 opt_simplify = optarg; 149 break; 150 151 case 'W': 152 opt_warn = 1; 153 xo_set_flags(NULL, XOF_WARN); 154 break; 155 156 case ':': 157 xo_errx(1, "missing argument"); 158 break; 159 160 case 0: 161 if (opts.o_help) { 162 print_help(); 163 return 1; 164 165 } else if (opts.o_version) { 166 print_version(); 167 return 0; 168 169 } else { 170 print_help(); 171 return 1; 172 } 173 174 bzero(&opts, sizeof(opts)); /* Reset all the options */ 175 break; 176 177 default: 178 print_help(); 179 return 1; 180 } 181 } 182 183 argc -= optind; 184 argv += optind; 185 186 if (opt_options) { 187 rc = xo_set_options(NULL, opt_options); 188 if (rc < 0) 189 xo_errx(1, "invalid options: %s", opt_options); 190 } 191 192 fmt = *argv++; 193 194 if (opt_simplify) { 195 fmt = xo_simplify_format(NULL, opt_simplify, opt_numbers, NULL); 196 if (fmt) { 197 xo_emit("{:format}\n", fmt); 198 free(fmt); 199 } 200 exit(0); 201 } 202 203 static char msgid[] = "msgid "; 204 char buf[BUFSIZ], *cp, *ep; 205 FILE *infile; 206 FILE *outfile; 207 TAILQ_INIT(&field_list); 208 209 if (opt_input) { 210 infile = fopen(opt_input, "r"); 211 if (infile == NULL) 212 xo_emit_err(1, "count not open input file: '{:filename}'", 213 opt_input); 214 } else 215 infile = stdin; 216 217 if (opt_output) { 218 unlink(opt_output); 219 outfile = fopen(opt_output, "w"); 220 if (outfile == NULL) 221 xo_emit_err(1, "count not open output file: '{:filename}'", 222 opt_output); 223 } else 224 outfile = stdout; 225 226 int blank = 0, line; 227 228 for (line = 1;; line++) { 229 if (fgets(buf, sizeof(buf), infile) == NULL) 230 break; 231 232 if (buf[0] == '#' && buf[1] == '\n') 233 continue; 234 235 blank = (buf[0] == '\n' && buf[1] == '\0'); 236 237 if (strncmp(buf, msgid, sizeof(msgid) - 1) != 0) { 238 fprintf(outfile, "%s", buf); 239 continue; 240 } 241 242 for (cp = buf + sizeof(msgid); *cp; cp++) 243 if (!isspace((int) *cp)) 244 break; 245 246 if (*cp == '"') 247 cp += 1; 248 249 ep = cp + strlen(cp); 250 if (ep > cp) 251 ep -= 1; 252 253 while (isspace((int) *ep) && ep > cp) 254 ep -= 1; 255 256 if (*ep != '"') 257 *ep += 1; 258 259 *ep = '\0'; 260 261 cp = xo_simplify_format(NULL, cp, opt_numbers, xopo_msg_cb); 262 if (cp) { 263 fprintf(outfile, "msgid \"%s\"\n", cp); 264 free(cp); 265 } 266 } 267 268 if (!blank) 269 fprintf(outfile, "\n"); 270 271 xopo_msg_t *xmp; 272 TAILQ_FOREACH(xmp, &field_list, xm_link) { 273 if (xmp->xm_plural) { 274 fprintf(outfile, "msgid \"%s\"\n" 275 "msgid_plural \"%s\"\n" 276 "msgstr[0] \"\"\n" 277 "msgstr[1] \"\"\n\n", 278 xmp->xm_data, xmp->xm_plural); 279 } else { 280 fprintf(outfile, "msgid \"%s\"\nmsgstr \"\"\n\n", xmp->xm_data); 281 } 282 } 283 284 if (infile != stdin) 285 fclose(infile); 286 if (outfile != stdout) 287 fclose(outfile); 288 289 xo_finish(); 290 291 return 0; 292 } 293