From c168301e9f19aee23dd613d603396618edffb83c Mon Sep 17 00:00:00 2001 From: EuAndreh Date: Sat, 31 Jul 2021 15:29:05 -0300 Subject: src/gistatic.c: Add basic support for i18n with catgets; write initial -h help message --- src/gistatic.c | 168 +++++++++++++++++++++++++++++++++++++++++++++---------- src/gistatic.msg | 21 +++++++ 2 files changed, 160 insertions(+), 29 deletions(-) create mode 100644 src/gistatic.msg (limited to 'src') diff --git a/src/gistatic.c b/src/gistatic.c index 8ab9aa0..b356748 100644 --- a/src/gistatic.c +++ b/src/gistatic.c @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include @@ -15,16 +17,60 @@ #include "unit-test.h" #endif -// FIXME: translate -static const char *const DEFAULT_TITLE = "Repositories"; -static const char *const LOGO_ALT = "Logo image of the repository list"; -static const char *const FOOTER_TEMPLATE = "Generated with %s"; -static const char *const LANG = "en"; -static const char *const NAME = "Name"; -static const char *const DESCRIPTION = "Description"; -static const char *const LAST_COMMIT = "Last commit"; -static const char *const USAGE = "Usage: -h -o -i -x...\n"; +static const int EXIT_ERROR = 1; +static const int EXIT_USAGE = 2; + + +#define PROGNAME "gistatic" +static const char *const CATALOG_NAME = PROGNAME; +static nl_catd catalog_descriptor = NULL; + +#define MSG_DEFAULT_TITLE 1 +#define MSG_LOGO_ALT 2 +#define MSG_FOOTER_TEMPLATE 3 +#define MSG_LANG 4 +#define MSG_NAME 5 +#define MSG_DESCRIPTION 6 +#define MSG_LAST_COMMIT 7 +#define MSG_USAGE 8 +#define MSG_HELP 9 + +static const char *const MSGS[] = { + "", + [MSG_DEFAULT_TITLE]="Repositories", + [MSG_LOGO_ALT]="Logo image of the repository list", + [MSG_FOOTER_TEMPLATE]="Generated with %s", + [MSG_LANG]="en", + [MSG_NAME]="Name", + [MSG_DESCRIPTION]="Description", + [MSG_LAST_COMMIT]="Last commit", + [MSG_USAGE]="Usage: -h -o -i\n-z -x...\n", + [MSG_HELP]="help: ...\n", + NULL +}; + +#ifdef TEST +static void dump_translatable_strings() { + const size_t size = + strlen(__FILE__) - strlen(".c") + strlen(".msg") + sizeof('\0'); + char *const catalog_path = malloc(size); + assert(catalog_path); + + strcpy(catalog_path, __FILE__); + catalog_path[strlen(__FILE__) - strlen(".c")] = '\0'; + strcat(catalog_path, ".msg"); + + FILE *const f = fopen(catalog_path, "w"); + assert(f); + for (size_t i = 1; MSGS[i] != NULL; i++) { + assert(fprintf(f, "%ld %s\n\n", i, MSGS[i]) > 0); + } + + assert(fclose(f) == 0); + free(catalog_path); +} +#endif static const char *const LOGO_STR = "" "\n" @@ -119,8 +165,7 @@ static const char *const STYLE_STR = "" "}\n" "\n" "table {\n" - " margin-top: 2em;\n" - " margin-bottom: 2em;\n" + " margin: 2em auto;\n" "}\n" "\n" "thead td {\n" @@ -143,17 +188,49 @@ static const char *const STYLE_STR = "" ""; -#define PROGNAME "gistatic" +static bool verbose = false; static const char *const GIT_SUFFIX = ".git"; static const char *const PROJECT_HOMEPAGE_LINK = "" PROGNAME "."; -static const int EXIT_ERROR = 1; -static const int EXIT_USAGE = 2; +static const char *_(int msg_id) { + if (!catalog_descriptor || catalog_descriptor == (nl_catd)-1) { + return MSGS[msg_id]; + } + errno = 0; + const char *const ret = + catgets(catalog_descriptor, NL_SETD, msg_id, MSGS[msg_id]); + if (errno && verbose) { + fprintf( + stderr, + "%s:%s:%d: catgets(%d): %s\n", + PROGNAME, + __FILE__, + __LINE__, + msg_id, + strerror(errno) + ); + } + return ret; +} + +#ifdef TEST +static void test_underscore() { + test_start("test_underscore"); + const char *const original_locale = setlocale(LC_ALL, NULL); + setlocale(LC_ALL, ""); + setenv("NLSPATH", "", 1); + { + testing("a NULL value for catalog_descriptor"); + test_ok(); + } + setlocale(LC_ALL, original_locale); +} +#endif -static int usage(FILE *const fd) { - if (fprintf(fd, "%s", USAGE) < 0) { +static int print_msg(FILE *const fd, const char *const msg) { + if (fprintf(fd, "%s", msg) < 0) { fprintf( stderr, "%s:%s:%d: fprintf(): %s\n", @@ -167,6 +244,19 @@ static int usage(FILE *const fd) { return 0; } +static int print_help(FILE *const fd) { + return print_msg(fd, _(MSG_HELP)); +} + +static int print_version(FILE *const fd) { + return print_msg(fd, PROGNAME "-" VERSION " " DATE "\n"); +} + +static int print_usage(FILE *const fd) { + return print_msg(fd, _(MSG_USAGE)); +} + + static char *remove_suffix(char *const s, const char *const suffix) { if (!s || !suffix) { return NULL; @@ -390,8 +480,8 @@ static char *formatted_date(const time_t time_sec) { return NULL; } - // Expected size, plus breathing room - const size_t size = strlen("XXX-XX-XX XX:XX") * 2; + /* Expected size, plus breathing room */ + const size_t size = (strlen("XXX-XX-XX XX:XX") + sizeof('\0')) * 2; char *const formatted = malloc(size); if (!formatted) { fprintf( @@ -688,13 +778,13 @@ static int write_header(FILE *const fd, const char *const idx_title) { " \n" " \n" " \n", - LANG, + _(MSG_LANG), idx_title, - LOGO_ALT, + _(MSG_LOGO_ALT), idx_title, - NAME, - DESCRIPTION, - LAST_COMMIT + _(MSG_NAME), + _(MSG_DESCRIPTION), + _(MSG_LAST_COMMIT) ); if (e < 0) { fprintf( @@ -711,7 +801,7 @@ static int write_header(FILE *const fd, const char *const idx_title) { } static int write_footer(FILE *const fd) { - const size_t footer_size = strlen(FOOTER_TEMPLATE) - strlen("%s") + const size_t footer_size = strlen(_(MSG_FOOTER_TEMPLATE)) - strlen("%s") + strlen(PROJECT_HOMEPAGE_LINK) + sizeof('\0'); char *const footer_text = malloc(footer_size); if (!footer_text) { @@ -726,7 +816,7 @@ static int write_footer(FILE *const fd) { ); return -1; } - sprintf(footer_text, FOOTER_TEMPLATE, PROJECT_HOMEPAGE_LINK); + sprintf(footer_text, _(MSG_FOOTER_TEMPLATE), PROJECT_HOMEPAGE_LINK); const int e = fprintf( fd, @@ -1098,7 +1188,10 @@ cleanup: #ifdef TEST static void unit_tests(){ + dump_translatable_strings(); + git_libgit2_init(); + test_underscore(); test_remove_suffix(); test_strjoin(); test_formatted_date(); @@ -1114,17 +1207,21 @@ int main(int argc, char *argv[]) { return EXIT_SUCCESS; #endif + catalog_descriptor = catopen(CATALOG_NAME, NL_CAT_LOCALE); + if (argc < 2) { - usage(stderr); + if (print_usage(stderr)) { + return EXIT_ERROR; + } return EXIT_USAGE; } int ret = 0; int flag; bool index = false; - const char *idx_title = DEFAULT_TITLE; + const char *idx_title = _(MSG_DEFAULT_TITLE); const char *outdir = "."; - while ((flag = getopt(argc, argv, "o:t:ihV")) != -1) { + while ((flag = getopt(argc, argv, "o:t:ivhV")) != -1) { switch (flag) { case 'i': index = true; @@ -1135,8 +1232,21 @@ int main(int argc, char *argv[]) { case 'o': outdir = optarg; break; + case 'v': + verbose = true; + break; + case 'h': + if (print_help(stdout)) { + return EXIT_ERROR; + } + return EXIT_SUCCESS; + case 'V': + if (print_version(stdout)) { + return EXIT_ERROR; + } + return EXIT_SUCCESS; default: - if (usage(stderr)) { + if (print_usage(stderr)) { return EXIT_ERROR; } return EXIT_USAGE; diff --git a/src/gistatic.msg b/src/gistatic.msg new file mode 100644 index 0000000..2624399 --- /dev/null +++ b/src/gistatic.msg @@ -0,0 +1,21 @@ +1 Repositories + +2 Logo image of the repository list + +3 Generated with %s + +4 en + +5 Name + +6 Description + +7 Last commit + +8 Usage: -h -o -i +-z -x... + + +9 help: ... + + -- cgit v1.2.3