/* This file is part of PODIFF.
Copyright (C) 2011-2020 Sergey Poznyakoff
PODIFF is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
PODIFF is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this PODIFF. If not, see . */
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "podiff.h"
const char *progname;
int skip_fuzzy_option;
int test_option;
unsigned maxwidth = 76;
int keep_comments_option;
int git_diff_header;
char *diff_command = "diff";
char *tmpdir = "/tmp";
char *tmppattern = "podiff.XXXXXX";
void
error(const char *fmt, ...)
{
va_list ap;
fprintf(stderr, "%s: ", progname);
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
fputc('\n', stderr);
}
void *
emalloc(size_t size)
{
void *p = malloc(size);
if (!p)
error("out of memory");
return p;
}
void *
ecalloc(size_t nmemb, size_t size)
{
void *p = calloc(nmemb, size);
if (!p)
error("out of memory");
return p;
}
#define ISWS(c) ((c) == ' ' || (c) == '\t')
void
format_string_n(FILE *fp, const char *prefix, int n, const char *str)
{
size_t off;
size_t width = 0;
size_t init = 0;
enum { s_seq, s_ws } state = ISWS(*str) ? s_ws : s_seq;
init = fprintf(fp, "%s", prefix);
if (n >= 0)
init += fprintf(fp, "[%d]", n);
fputc(' ', fp);
init++;
off = width = 0;
while (1) {
if (str[width + off] == 0 || str[width + off] == '\n') {
if (state == s_seq && init + width + off < maxwidth) {
width += off;
}
/* skip */;
} else if (state == s_seq) {
if (init + width + off >= maxwidth && width) {
;
} else {
if (ISWS(str[width + off])) {
state = s_ws;
width += off;
off = 0;
} else
off++;
continue;
}
} else /* state == s_ws */ {
if (init + width + off < maxwidth) {
if (ISWS(str[width + off]))
width++;
else
state = s_seq;
continue;
} else {
width += off;
}
}
off = 0;
if (!width)
break;
fputc('"', fp);
fwrite(str, width, 1, fp);
str += width;
width = off = 0;
if (*str == '\n') {
fwrite("\\n", 2, 1, fp);
str++;
}
fputc('"', fp);
fputc('\n', fp);
init = 0;
}
if (init)
fwrite("\"\"\n", 3, 1, fp);
}
void
format_string(FILE *fp, const char *prefix, const char *str)
{
format_string_n(fp, prefix, -1, str);
}
void
msgtrans_to_file(struct list *lp, FILE *fp)
{
struct list_entry *ent;
for (ent = lp->head; ent; ent = ent->next) {
struct msgtrans *p = ent->data;
if (p->comment) {
struct list_entry *strent;
for (strent = p->comment->head; strent;
strent = strent->next) {
char *str = strent->data;
fprintf(fp, "%s\n", str);
}
}
if (p->msgctxt)
format_string(fp, "msgctxt", p->msgctxt);
format_string(fp, "msgid", p->msgid);
if (p->msgid_plural)
format_string(fp, "msgid_plural", p->msgid_plural);
if (list_size(p->msgstr) == 1)
format_string(fp, "msgstr",
list_index(p->msgstr, 0));
else {
int i;
struct list_entry *strent;
for (strent = p->msgstr->head, i = 0; strent;
strent = strent->next, i++) {
char *str = strent->data;
format_string_n(fp, "msgstr", i, str);
}
}
fputc('\n', fp);
}
}
char *
po_file_normalize(const char *file)
{
struct list *lp;
mode_t m;
char *tmpname;
int fd;
FILE *fp;
lp = parse_po(file);
list_qsort(lp);
m = umask(077);
tmpname = emalloc (strlen(tmpdir) + 1 + strlen(tmppattern) + 1);
sprintf(tmpname, "%s/%s", tmpdir, tmppattern);
fd = mkstemp(tmpname);
umask(m);
if (fd == -1)
error("cannot create temporary: %s", strerror(errno));
fp = fdopen(fd, "w");
msgtrans_to_file(lp, fp);
fclose(fp);
return tmpname;
}
void
help(FILE *fp)
{
fprintf(fp, "usage: %s [OPTIONS] FILE1 FILE2\n", progname);
fprintf(fp, "Compare two PO files\n");
fprintf(fp, " or: %s [OPTIONS] PATH OLD-FILE OLD-HEX OLD-MODE NEW-FILE NEW-HEX NEW-MODE\n", progname);
fprintf(fp, "Compare two revisions of a Git-controlled file\n");
fprintf(fp, " or: %s -t [OPTIONS] FILE\n", progname);
fprintf(fp, "Test mode: create a normalized version of FILE on stdout\n");
fprintf(fp, " or: %s -v\n", progname);
fprintf(fp, "Print version information\n");
fprintf(fp, " or: %s -h\n", progname);
fprintf(fp, "Print this help summary\n");
fprintf(fp, "\nOPTIONS:\n");
fprintf(fp, " -d CMD use CMD instead of the default \"diff\"\n");
fprintf(fp, " -D OPT pass OPT to diff\n");
fprintf(fp, " -f skip fuzzy translations\n");
fprintf(fp, " -k keep user comments in output\n");
fprintf(fp, " -L CMD understands -L option\n");
fprintf(fp, " -w NUM set output text width to NUM colums\n");
fprintf(fp, "\nReport bugs to .\n");
}
void
show_version()
{
puts("podiff " VERSION "\n"
"Copyright (C) 2011-2020 Sergey Poznyakoff\n"
"License GPLv3+: GNU GPL version 3 or later \n"
"This is free software: you are free to change and redistribute it.\n"
"There is NO WARRANTY, to the extent permitted by law.\n");
}
int
main(int argc, char **argv)
{
int rc;
struct txtacc *diffargs;
char *tmp[2];
char *args, *command;
size_t size;
int ignore_rc;
int label_option = 0;
progname = argv[0];
diffargs = txtacc_create();
while ((rc = getopt(argc, argv, "d:D:fhkLtvw:")) != EOF) {
switch (rc) {
case 'f':
skip_fuzzy_option = 1;
break;
case 'd':
diff_command = optarg;
break;
case 'D':
txtacc_grow(diffargs, " ", 1);
txtacc_grow(diffargs, optarg, strlen(optarg));
break;
case 'k':
keep_comments_option = 1;
break;
case 'L':
label_option = 1;
break;
case 't':
test_option = 1;
break;
case 'h':
help(stdout);
exit(0);
case 'v':
show_version();
exit(0);
case 'w':
maxwidth = atoi(optarg);
if (!maxwidth) {
error("invalid width: %s", optarg);
exit(2);
}
break;
default:
exit(2);
}
}
argc -= optind;
argv += optind;
if (test_option) {
struct list *lp;
if (argc != 1) {
error("wrong number of arguments");
exit(2);
}
lp = parse_po(argv[0]);
list_qsort(lp);
msgtrans_to_file(lp, stdout);
exit(0);
}
if (argc == 0) {
help(stderr);
exit(2);
} else if (argc == 2) {
/* Assume command line mode */
tmp[0] = po_file_normalize(argv[0]);
tmp[1] = po_file_normalize(argv[1]);
} else {
/* Assume Git mode. Arguments: */
/* 0 1 2 3 4 5 6 */
/* path old-file old-hex old-mode new-file new-hex new-mode */
if (argc != 7) {
error("wrong number of arguments");
exit(2);
}
if (label_option) {
txtacc_grow(diffargs, " -La/", 5);
txtacc_grow(diffargs, argv[0], strlen(argv[0]));
txtacc_grow(diffargs, " -Lb/", 5);
txtacc_grow(diffargs, argv[0], strlen(argv[0]));
}
tmp[0] = po_file_normalize(argv[1]);
tmp[1] = po_file_normalize(argv[4]);
ignore_rc = git_diff_header = 1;
}
txtacc_grow(diffargs, " ", 1);
txtacc_grow(diffargs, tmp[0], strlen(tmp[0]));
txtacc_grow(diffargs, " ", 1);
txtacc_grow(diffargs, tmp[1], strlen(tmp[1]));
args = txtacc_finish(diffargs, 0);
size = strlen(diff_command) + 1 + strlen(args) + 1;
command = emalloc(size);
sprintf(command, "%s %s", diff_command, args);
if (git_diff_header) {
if (strcmp(argv[3], argv[6])) {
printf("old mode %s\n", argv[3]);
printf("new mode %s\n", argv[6]);
printf("index %.8s..%.8s\n", argv[2], argv[5]);
} else
printf("index %.8s..%.8s %s\n", argv[2], argv[5], argv[6]);
fflush(stdout);
}
rc = system(command);
unlink(tmp[0]);
unlink(tmp[1]);
return ignore_rc ? 0 : WEXITSTATUS(rc);
}