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 *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