star-ty

Generate content for online publication
git clone git://git.meso-star.fr/star-ty.git
Log | Files | Refs | README | LICENSE

sty-genhead.c (24512B)


      1 /* Copyright (C) 2017-2025 |Méso|Star> (contact@meso-star.com)
      2  *
      3  * This program is free software: you can redistribute it and/or modify
      4  * it under the terms of the GNU General Public License as published by
      5  * the Free Software Foundation, either version 3 of the License, or
      6  * (at your option) any later version.
      7  *
      8  * This program is distributed in the hope that it will be useful,
      9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
     11  * GNU General Public License for more details.
     12  *
     13  * You should have received a copy of the GNU General Public License
     14  * along with this program. If not, see <http://www.gnu.org/licenses/>. */
     15 
     16 #define _XOPEN_SOURCE 500 /* realpath */
     17 #define _POSIX_C_SOURCE 200809L /* stndup */
     18 
     19 #include <assert.h>
     20 #include <errno.h>
     21 #include <libgen.h> /* basename & dirname */
     22 #include <stdio.h>
     23 #include <stdlib.h>
     24 #include <string.h>
     25 #include <unistd.h>
     26 
     27 #include <sys/stat.h>
     28 
     29 #define MENU_FILENAME "menu.tsv" /* File that lists the menu entries */
     30 #define MENU_MAX_ENTRIES 16 /* Maximum number of menu entries */
     31 
     32 #define INDEX_FILENAME "index.tsv" /* List the indexed content of a section */
     33 #define INDEX_MAX_ENTRIES 32 /* Maximum number of indexed items per section */
     34 
     35 #define DEFAULT_LANG "en" /* Default language */
     36 
     37 static const char LANG[] = "@LANG@";
     38 const size_t SZLANG = sizeof(LANG) - 1/* '\0' */;
     39 
     40 static const char* g_cmd = NULL; /* Command name. Setuped on start-up */
     41 
     42 struct menu {
     43   struct mentry {
     44     char* label;
     45     char* section;
     46     char* mem__; /* Allocated memory */
     47   } items[MENU_MAX_ENTRIES];
     48 
     49   int nitems;
     50 };
     51 static const struct menu MENU_NULL = {0};
     52 
     53 struct index {
     54   struct ientry {
     55     char* label;
     56     char* uri;
     57     char* lang;
     58     char* template; /* Original URI, i.e. not expanded */
     59     char* lang_list; /* List of available languages */
     60     char* mem__; /* Allocated memory */
     61   } items[INDEX_MAX_ENTRIES];
     62 
     63   int nitems;
     64 };
     65 static const struct index INDEX_NULL = {0};
     66 
     67 struct str {
     68   char* buf;
     69   size_t sz; /* Capacity */
     70 };
     71 static const struct str STR_NULL = {0};
     72 
     73 /*******************************************************************************
     74  * Helper functions
     75  ******************************************************************************/
     76 static void
     77 str_release(struct str* s)
     78 {
     79   assert(s);
     80   if(s->buf) free(s->buf);
     81   *s = STR_NULL;
     82 }
     83 
     84 /* Read from fp the next non empty line. Returns null if an error occurs */
     85 static char*
     86 rline(FILE* fp, struct str* s, int* lines_count/*#read lines*/)
     87 {
     88   size_t nlines = 0;
     89 
     90   do {
     91     size_t n = 0; /* #read chars for the current line */
     92 
     93     do {
     94       char *ptr = NULL;
     95 
     96       /* Allocate memory space */
     97            if(!s->buf) s->buf = realloc(s->buf, s->sz=128);
     98       else if(n > 0)   s->buf = realloc(s->buf, s->sz+=32);
     99       if(!s->buf) { perror("realloc"); goto error; }
    100 
    101       /* Read line data */
    102       ptr = fgets(s->buf+n, s->sz - n, fp);
    103       if(!ptr) {
    104         if(ferror(fp)) { perror("fgets"); goto error; }
    105         s->buf[n] = '\0'; /* No read char */
    106       }
    107 
    108       n = strlen(s->buf); /* Update the number of read chars */
    109 
    110     } while(!strrchr(s->buf, '\n') && !feof(fp)); /* Are there any chars left? */
    111     ++nlines;
    112 
    113     s->buf[strcspn(s->buf, "\n\r#\0")] = '\0'; /* Remove new line and comments */
    114 
    115   } while(strlen(s->buf)==strspn(s->buf, " \t") && !feof(fp)); /* Empty line */
    116 
    117 exit:
    118   *lines_count = nlines;
    119   return s->buf;
    120 error:
    121   str_release(s);
    122   goto exit;
    123 }
    124 
    125 static void
    126 menu_release(struct menu* menu)
    127 {
    128   assert(menu);
    129   for(int i=0; i < menu->nitems; ++i) {
    130     if(menu->items[i].mem__) free(menu->items[i].mem__);
    131   }
    132   *menu = MENU_NULL;
    133 }
    134 
    135 static int
    136 menu_load(struct menu* menu, const char* workdir)
    137 {
    138   struct str s = STR_NULL;
    139   char* line = NULL;
    140   FILE* fp = NULL;
    141   int iline = 0; /* Line index */
    142   int n = 0;
    143   int err = 0;
    144 
    145   assert(menu && workdir);
    146 
    147   *menu = MENU_NULL;
    148 
    149   /* Open the menu file */
    150   if(!(fp=fopen(MENU_FILENAME, "r"))) {
    151     perror(__func__);
    152     fprintf(stderr,
    153       "%s: expecting the \"%s\" file in the \"%s\" working directory\n",
    154       g_cmd, MENU_FILENAME, workdir);
    155     goto error;
    156   }
    157 
    158   for(line=rline(fp, &s, &n); line && line[0]!='\0'; line=rline(fp, &s, &n)) {
    159     struct mentry* item = menu->items + menu->nitems;
    160     char* ctx = NULL;
    161 
    162     iline += n;
    163 
    164     if(menu->nitems >= MENU_MAX_ENTRIES) {
    165       /* There is too many menu entries to parse */
    166       errno = ENOMEM;
    167       perror(__func__);
    168       goto error;
    169     }
    170 
    171     ++menu->nitems;
    172 
    173      /* Duplicate the line. It will be divided into sub-strings that the
    174       * items's member variables will point to. */
    175     if(!(item->mem__ = strdup(line))) { perror("strdup"); goto error; }
    176 
    177     /* Setup item member variables */
    178     if(!(item->label   = strtok_r(item->mem__, "\t", &ctx))
    179     || !(item->section = strtok_r(NULL, "\t", &ctx))) {
    180       fprintf(stderr, "%s:%s:%d: invalid menu entry -- %s\n",
    181         g_cmd, MENU_FILENAME, iline, line);
    182       goto error;
    183     }
    184   }
    185 
    186   if(!line) goto error; /* A null lines means there is an error */
    187 
    188 exit:
    189   if(fp) fclose(fp);
    190   str_release(&s);
    191   return err;
    192 error:
    193   menu_release(menu);
    194   err = 1;
    195   goto exit;
    196 }
    197 
    198 /* Resolve a pathname. Unlike realpath, handles paths to non-existent files */
    199 static char*
    200 get_realpath(const char* path)
    201 {
    202   char* out = NULL; /* realpath */
    203   char* buf = NULL; /* working copy of input path */
    204   char* tmp = NULL; /* temporary path */
    205   char *p0, *p1;
    206 
    207   if((out = realpath(path, NULL))) goto exit;
    208   if(errno != ENOENT) goto error;
    209 
    210   /* The path does not name an existing file */
    211 
    212   /* Ensure that path is absolute or start by "./" or "../" */
    213   if(path[0] == '/' || !strncmp(path, "./", 2) || !strncmp(path, "../", 3)) {
    214     if(!(buf = strdup(path))) goto error;
    215   } else {
    216     if(!(buf = malloc(2/*"./"*/ + strlen(path) + 1/*'\0'*/))) goto error;
    217     strcpy(buf, "./");
    218     strcat(buf, path);
    219   }
    220 
    221   /* Remove path components until a path is found that exists */
    222   for(p0=strrchr(buf, '/'); p0; p1=p0, *p1='\0', p0=strrchr(buf, '/'), *p1='/') {
    223 
    224     *p0 = '\0';
    225     tmp = realpath(buf, NULL);
    226     *p0 = '/';
    227 
    228     if(!tmp) continue; /* Keep removing path component */
    229 
    230     /* Concatante the existing path with the remaining compopnents */
    231     if(!(out = malloc(strlen(tmp) + strlen(p0) + 1/*'\0'*/))) goto error;
    232     strcpy(out, tmp);
    233     strcat(out, p0);
    234     break;
    235   }
    236 
    237 exit:
    238   if(buf) free(buf);
    239   if(tmp) free(tmp);
    240   return out;
    241 error:
    242   perror(__func__);
    243   if(out) { free(out); out = NULL; }
    244   goto exit;
    245 }
    246 
    247 /* Return the relative path from path to workdir.
    248  * Path must lie in workdir.
    249  * Return null if an error occurs */
    250 static char*
    251 get_relpath(const char* workdir, const char* path)
    252 {
    253   const size_t sz = strlen(workdir);
    254   const char* ptr = NULL;
    255   char* buf = NULL;
    256   int n = 0; /* Number of directory to travels */
    257   int i;
    258 
    259   if(strncmp(workdir, path, sz) || path[sz] != '/') {
    260     fprintf(stderr, "%s:%s: %s is not a subpath of %s\n",
    261       g_cmd, __func__, path, workdir);
    262     goto error;
    263   }
    264 
    265   /* Count the number of directories between the workdir and the path */
    266   for(n = 0, ptr=path + sz + 1; (ptr=strchr(ptr, '/')); ++n) {
    267     /* Successive '/' should be counted as a single '/' */
    268     while(++ptr, *ptr=='/');
    269   }
    270 
    271   if(!(buf = malloc(n*3/*"../"*/+1/*'\0'*/))) { perror("malloc"); goto error; }
    272   for(i=0, buf[0]='\0'; i<n; strcat(buf, "../"), ++i);
    273 
    274 exit:
    275   return buf;
    276 error:
    277   if(buf) { free(buf); buf = NULL; }
    278   goto exit;
    279 }
    280 
    281 /* Returns a pointer to a new index file in which the @LANG@ macro is resolved.
    282  * Each entry in the original index file containing an @LANG@ macro is
    283  * duplicated as many times as there are languages listed for that entry. In
    284  * each of them, the @LANG@ macro is replaced by a value from the language in
    285  * the order in which the languages are defined in the original index file. Then
    286  * follow the language used to expand the @LANG@ macro and finally the
    287  * original uri and the list of languages.
    288  *
    289  * For example, the following original string :
    290  *
    291  *  Index\tindex-@LANG@.html\tfr:en\n
    292  *
    293  * is resolved in
    294  *
    295  *  Index\tindex-fr.html\tfr\tindex-@LANG@.html\tfr:en\n
    296  *  Index\tindex-en.html\ten\tindex-@LANG@.html\tfr:en\n
    297  *
    298  * Return null if an error occurs */
    299 static FILE*
    300 resolve_index(const char* path)
    301 {
    302   char* lang_list = NULL;
    303 
    304   FILE* fp = NULL;
    305   FILE* fp2 = NULL;
    306 
    307   struct str s = STR_NULL;
    308   char* line = NULL;
    309   int iline = 0; /* Line index */
    310   int n = 0;
    311 
    312   assert(path);
    313 
    314   /* Open the original index */
    315   if(!(fp = fopen(path, "r"))) {
    316     fprintf(stderr,"%s: unable to find the file %s\n", g_cmd, path);
    317     goto error;
    318   }
    319 
    320   /* Open the resolved index */
    321   if(!(fp2 = tmpfile())) { perror("tmpfile"); goto error; }
    322 
    323   for(line=rline(fp, &s, &n); line && line[0]!='\0'; line=rline(fp, &s, &n)) {
    324     char *label, *uri, *langs, *ctx;
    325 
    326     iline += n;
    327 
    328     label = strtok_r(line,  "\t", &ctx);
    329     uri   = strtok_r(NULL,  "\t", &ctx);
    330     langs = strtok_r(NULL,  "\t", &ctx);
    331 
    332     if(!label || !uri) {
    333       fprintf(stderr, "%s:%s:%d: invalid index entry\n", g_cmd, path, iline);
    334       goto error;
    335     }
    336 
    337     if(!langs) {
    338       /* There is no several languages, just write the original entry */
    339       fprintf(fp2, "%s\t%s\n", label, uri);
    340 
    341     } else {
    342       char* l = NULL;
    343 
    344       /* Keep a copy of the string listing the available languages. This will be
    345        * the last field of the expanded entries */
    346       if(lang_list) free(lang_list);
    347       if(!(lang_list=strdup(langs))) { perror("strdup"); goto error; }
    348 
    349       /* Add as many index entries as there are languages available */
    350       for(l=strtok_r(langs, ":", &ctx); l; l=strtok_r(NULL, ":", &ctx)) {
    351         char *ptr0, *ptr1;
    352 
    353         fprintf(fp2, "%s\t", label); /* Firstly, print the entry label */
    354 
    355         /* Then print the expanded URI, i.e. the URI in which each instance of
    356          * the @LANG@ macro is replaced by the current language */
    357         for(ptr0=uri; (ptr1 = strstr(ptr0, LANG)); ptr0=ptr1+SZLANG) {
    358           *ptr1 = '\0';
    359           fprintf(fp2, "%s%s", ptr0, l);
    360           *ptr1 = LANG[0];
    361         }
    362         fprintf(fp2, "%s\t", ptr0); /* Rest of the URI  */
    363 
    364         /* Print the URI language as the 3rd field */
    365         fprintf(fp2, "%s\t", l);
    366 
    367         /* And finally, print the original URI (4th field) and the list of
    368          * available languages (5th field). */
    369         fprintf(fp2, "%s\t%s\n", uri, lang_list);
    370       }
    371     }
    372   }
    373 
    374   rewind(fp2);
    375 
    376 exit:
    377   str_release(&s);
    378   if(fp) fclose(fp);
    379   if(lang_list) free(lang_list);
    380   return fp2;
    381 error:
    382   if(fp2) { fclose(fp2); fp2 = NULL; }
    383   goto exit;
    384 }
    385 
    386 static void
    387 index_release(struct index* index)
    388 {
    389   assert(index);
    390   for(int i=0; i < index->nitems; ++i) {
    391     if(index->items[i].mem__) free(index->items[i].mem__);
    392   }
    393   *index = INDEX_NULL;
    394 }
    395 
    396 static int
    397 index_load(struct index* index, const char* section)
    398 {
    399   struct str s = STR_NULL;
    400   FILE* fp = NULL;
    401   char* path = NULL;
    402   char* line = NULL;
    403   size_t sz = 0;
    404   int i = 0;
    405   int n = 0;
    406   int err = 0;
    407 
    408   assert(index && section);
    409 
    410   /* Allocate the string to store the absolute path to the index file */
    411   sz = 2 /* "./" */
    412      + strlen(section) + 1/* '/' */
    413      + strlen(INDEX_FILENAME) + 1/* '\0' */;
    414   if(!(path = malloc(sz))) { perror("malloc"); goto error; }
    415 
    416   /* Define the absolute path to the index file */
    417   i = snprintf(path, sz, "./%s/%s", section, INDEX_FILENAME);
    418   if(i >= (int)sz) abort(); /* Unexpected error */
    419 
    420   /* Resolve the index file, i.e. expand the @LANG@ macro */
    421   if(!(fp = resolve_index(path))) goto error;
    422 
    423   for(line=rline(fp, &s, &n); line && line[0]!='\0'; line=rline(fp, &s, &n)) {
    424     struct ientry* item = index->items + index->nitems;
    425     char* ctx = NULL;
    426 
    427     if(index->nitems >= INDEX_MAX_ENTRIES) {
    428       /* There is too many menu entries to parse */
    429       errno = ENOMEM;
    430       perror(__func__);
    431       goto error;
    432     }
    433 
    434     ++index->nitems;
    435 
    436      /* Duplicate the line. It will be divided into sub-strings that the
    437       * items's member variables will point to. */
    438     if(!(item->mem__ = strdup(line))) { perror("strdup"); goto error; }
    439 
    440     item->label = strtok_r(item->mem__, "\t", &ctx);
    441     item->uri = strtok_r(NULL, "\t", &ctx);
    442     item->lang = strtok_r(NULL, "\t", &ctx);
    443     item->template = strtok_r(NULL, "\t", &ctx);
    444     item->lang_list = strtok_r(NULL, "\t", &ctx);
    445 
    446     if(!item->lang) item->lang = DEFAULT_LANG;
    447     if(!item->template) item->template = item->uri;
    448     if(!item->lang_list) item->lang_list = DEFAULT_LANG;
    449 
    450     /* Already check when resolving index */
    451     assert(item->label && item->uri);
    452   }
    453 
    454   if(!line) goto error; /* A null lines means there is an error */
    455   if(index->nitems == 0) goto error; /* An empty index is an error */
    456 
    457 exit:
    458   if(path) free(path);
    459   if(fp) fclose(fp);
    460   str_release(&s);
    461   return err;
    462 error:
    463   index_release(index);
    464   err = 1;
    465   goto exit;
    466 }
    467 
    468 /* Returns null if no entry is found */
    469 const struct mentry*
    470 menu_find(const struct menu* menu, const char* section)
    471 {
    472   int i = 0;
    473   assert(menu && section);
    474 
    475   for(i = 0; i < menu->nitems; ++i) {
    476     if(!strcmp(menu->items[i].section, section)) break;
    477   }
    478 
    479   return i < menu->nitems ? menu->items + i : NULL;
    480 }
    481 
    482 /* Returns null if no entry is found */
    483 const struct ientry*
    484 index_find
    485   (const struct index* index,
    486    const char* file) /* relative to the section */
    487 {
    488   int i = 0;
    489   assert(index && file);
    490 
    491   for(i = 0; i < index->nitems; ++i) {
    492     if(!strcmp(index->items[i].uri, file)) break;
    493   }
    494 
    495   return i < index->nitems ? index->items + i : NULL;
    496 }
    497 
    498 /* Return null if an error occurs */
    499 static char*
    500 get_section(const char* workdir, const char* path)
    501 {
    502   const size_t sz = strlen(workdir);
    503   char *buf;
    504   const char *b, *e;
    505 
    506   assert(workdir);
    507 
    508   if(strncmp(path, workdir, sz) != 0 || path[sz] != '/') {
    509     fprintf(stderr, "%s:%s: %s is not a subpath of %s\n",
    510       g_cmd, __func__, path, workdir);
    511     return NULL;
    512   }
    513 
    514   buf = (b = path+sz, *b != '/') || !(e = strchr(++b, '/'))
    515       ? strdup(b) : strndup(b, e-b);
    516 
    517   if(!buf) perror(__func__);
    518   return buf;
    519 }
    520 
    521 /* Return path relative to workdir/section. path must be a subpath into
    522  * workdir/section. Return null if an error occurs */
    523 static char*
    524 get_section_path(const char* workdir, const char* section, const char* path)
    525 {
    526   const size_t sz0 = strlen(workdir);
    527   const size_t sz1 = strlen(section);
    528   char* buf;
    529 
    530   if(strncmp(path, workdir, sz0) != 0
    531   || strncmp(path+sz0+1, section, sz1) != 0
    532   || path[sz0] != '/'
    533   || path[sz0+1+sz1] != '/') {
    534     fprintf(stderr, "%s:%s: %s is not a subpath in %s/%s\n",
    535       g_cmd, __func__, path, workdir, section);
    536     return NULL;
    537   }
    538 
    539   buf = strdup(path + sz0 + 1/*'/ */ + sz1 + 1/*'/'*/);
    540   if(!buf) perror("strdup");
    541 
    542   return buf;
    543 }
    544 
    545 /* Return null if an error occurs */
    546 static char*
    547 get_workdir(void)
    548 {
    549   char *buf, *ptr;
    550   size_t sz = 256; /* Initial buffer size */
    551 
    552   for(buf = ptr = NULL; !ptr; sz*=2) {
    553 
    554     if(!(buf = realloc(buf, sz))) {
    555       perror("realloc");
    556       goto error;
    557     }
    558 
    559     if(!(ptr = getcwd(buf, sz)) && errno != ERANGE) {
    560       perror("getcwd"); /* Unforeseen error */
    561       goto error;
    562     }
    563   }
    564 
    565 exit:
    566   return buf;
    567 error:
    568   if(buf) { free(buf); buf = NULL; }
    569   goto exit;
    570 }
    571 
    572 static void
    573 print_head(const char* root, const char* label, const char* lang)
    574 {
    575   assert(root && label && label);
    576 
    577   printf("<!DOCTYPE html>\n");
    578   printf("<html lang=\"%s\">\n", lang);
    579   printf("<head>\n");
    580   printf("  <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n");
    581   printf("  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n");
    582   printf("  <title>%s</title>\n", label);
    583   printf("  <link rel=\"stylesheet\" title=\"default\" href=\"%ssty.css\">\n", root);
    584   printf("</head>\n");
    585   printf("<body>\n");
    586 }
    587 
    588 static void
    589 print_menu1
    590   (const char* root,
    591    const struct menu* menu,
    592    const struct mentry* selected_entry)
    593 {
    594   struct index index = INDEX_NULL;
    595   assert(root && menu && selected_entry);
    596 
    597   printf("<div id=\"menu\">\n");
    598 
    599   for(int i=0; i < menu->nitems; ++i) {
    600     struct stat buf;
    601     const struct mentry* entry = menu->items + i;
    602 
    603 
    604     if(i) printf("  &emsp13;|&emsp13;\n");
    605 
    606     if(selected_entry == entry) {
    607       printf("  %s\n", entry->label);
    608 
    609     } else if(stat(entry->section, &buf) != -1
    610            && S_ISDIR(buf.st_mode)
    611            && !index_load(&index, entry->section)) {
    612       printf("  <a href=\"%s%s/%s\">%s</a>\n",
    613         root, entry->section, index.items[0].uri, entry->label);
    614       index_release(&index);
    615 
    616     } else if(!strncmp(entry->section, "http://", 7)
    617            || !strncmp(entry->section, "https://",8)) {
    618       /* The target is an URL */
    619       printf("  <a href=\"%s\">%s</a>\n", entry->section, entry->label);
    620 
    621     } else {
    622       /* The target is neither a local directory nor a URL.  Let's assume it is
    623        * a remote directory, still relative to the current root once the local
    624        * content is installed, but managed elsewhere. */
    625       printf("  <a href=\"%s%s\">%s</a>\n", root, entry->section, entry->label);
    626     }
    627   }
    628 
    629   printf("</div>\n<hr>\n");
    630 }
    631 
    632 /* Return null if an error occurs */
    633 static char*
    634 expand_lang(char* template, const char* value)
    635 {
    636   char* buf = NULL;
    637   char* p0 = NULL;
    638   char* p1 = NULL;
    639   size_t valen = 0;
    640   size_t sz = 0;
    641 
    642   assert(template && value);
    643 
    644   valen = strlen(value);
    645 
    646   /* Calculate the size of the expanded string */
    647   for(p0=template; (p1 = strstr(p0, LANG)); p0=p1+SZLANG) {
    648     *p1 = '\0';
    649     sz += strlen(p0) + valen;
    650     *p1 = LANG[0];
    651   }
    652   sz += strlen(p0)/* Rest of the template string */ + 1/* '\0' */;
    653 
    654   /* Allocate the expanded string */
    655   if(!(buf=malloc(sz))) goto error;
    656 
    657   /* Expand the string */
    658   buf[0] = '\0';
    659   for(p0=template; (p1 = strstr(p0, LANG)); p0=p1+SZLANG) {
    660     *p1 = '\0';
    661     strcat(buf, p0);
    662     strcat(buf, value);
    663     *p1 = LANG[0];
    664   }
    665   strcat(buf, p0); /* Rest of template string */
    666 
    667 exit:
    668   return buf;
    669 error:
    670   perror(__func__);
    671   if(buf) { free(buf); buf = NULL; }
    672   goto exit;
    673 }
    674 
    675 static int
    676 print_translations
    677   (const char* root,
    678    const char* section,
    679    const struct ientry* entry)
    680 {
    681   char* l = NULL;
    682   char* lang_list = NULL;
    683   char* ctx = NULL;
    684   int i = 0;
    685   int err = 0;
    686   assert(entry);
    687 
    688   /* The URI and template are identical, meaning that no translation is
    689    * available for this HTML content since its template has no @LANG@ macro */
    690   if(!strcmp(entry->uri, entry->template)) goto exit;
    691 
    692   /* Duplicate the lang list */
    693   if(!(lang_list=strdup(entry->lang_list))) { perror("malloc"); goto error; }
    694 
    695   /* Retrieve the 2 first langs in the list */
    696   l = strtok_r(lang_list, ":", &ctx);
    697   l = strtok_r(NULL, ":", &ctx);
    698 
    699   if(!l) goto exit; /* There is only one lang, i.e. there is no translation */
    700 
    701 
    702   /* Duplicate the lang list again since it was updated by tokenization */
    703   free(lang_list);
    704   if(!(lang_list=strdup(entry->lang_list))) { perror("malloc"); goto error; }
    705 
    706   printf("  <span style=\"float: right;\">\n");
    707 
    708   for(i=0, l=strtok_r(lang_list, ":", &ctx); l; l=strtok_r(NULL, ":", &ctx)) {
    709 
    710     if(i) printf("    &emsp13;/&emsp13;\n");
    711     i += (i==0);
    712 
    713     if(!strcmp(l, entry->lang)) {
    714       /* This is the current translation */
    715       printf("    <span class=\"cur\">%s</span>\n", l);
    716 
    717     } else  {
    718       char* translation = NULL;
    719 
    720       if(!(translation = expand_lang(entry->template, l))) goto error;
    721       printf("    <a href=\"%s%s/%s\">%s</a>\n", root, section, translation, l);
    722       free(translation);
    723     }
    724   }
    725 
    726   printf("  </span>\n");
    727 
    728 exit:
    729   if(lang_list) free(lang_list);
    730   return err;
    731 error:
    732   err = 1;
    733   goto exit;
    734 }
    735 
    736 static int
    737 print_menu2
    738   (const char* root,
    739    const char* section,
    740    const struct index* index,
    741    const struct ientry* selected_entry) /* Can be NULL */
    742 {
    743   char* uri = NULL;
    744   int err = 0;
    745 
    746   assert(root && section && index);
    747 
    748   printf("<div id=\"sub-menu\">\n");
    749 
    750   for(int i = 0, n = index->nitems; i < n; ++i) {
    751     const struct ientry* entry = index->items + i;
    752     const char* lang = selected_entry ? selected_entry->lang : entry->lang;
    753 
    754     if(i) printf("  &emsp13;.&emsp13;\n");
    755 
    756     /* * Resolve the URI language with the selected entry if it exists, or the
    757      * current entry if not */
    758     if(!(uri=expand_lang(entry->template, lang))) goto error;
    759 
    760     /* This is the indexed content */
    761     if(selected_entry && !strcmp(uri, selected_entry->uri)) {
    762       printf("  <span class=\"cur\">%s</span>\n", selected_entry->label);
    763       print_translations(root, section, selected_entry);
    764 
    765     /* The entry links to an http[s] URL */
    766     } else if(!strncmp(entry->uri, "http://", 7)
    767            || !strncmp(entry->uri, "https://",8)) {
    768       printf("  <a href=\"%s\">%s</a>\n", entry->uri, entry->label);
    769 
    770     /* The entry links to a local web page for the section */
    771     } else {
    772       printf("  <a href=\"%s%s/%s\">%s</a>\n",
    773         root, section, entry->uri, entry->label);
    774     }
    775 
    776      /* Remove elements with the same template for the current label to avoid
    777       * duplicates when the indexed content offers multiple translations. Note
    778       * that the following loop ensures that the index increment by the main
    779       * loop remains valid, i.e., does not skip an entry. */
    780     while(i+1 < n
    781       && !strcmp(entry->template, index->items[i+1].template)
    782       && !strcmp(entry->label, index->items[i+1].label)) {
    783       i += 1;
    784     }
    785 
    786     free(uri);
    787     uri = NULL;
    788   }
    789 
    790   printf("</div>\n");
    791 
    792 exit:
    793   if(uri) free(uri);
    794   return err;
    795 error:
    796   err = 1;
    797   goto exit;
    798 }
    799 
    800 /*******************************************************************************
    801  * The command
    802  ******************************************************************************/
    803 int
    804 main(int argc, char** argv)
    805 {
    806   struct menu menu = MENU_NULL;
    807   const struct mentry* mentry = NULL;
    808 
    809   struct index index = INDEX_NULL;
    810   const struct ientry* ientry = NULL;
    811 
    812   char* path = NULL; /* absolute path */
    813   char* dname = NULL; /* dirname of the absolute path */
    814   char* bname = NULL; /* basename of the absolute path */
    815   char* workdir = NULL; /* path from where the command is run */
    816   char* root = NULL; /* relative path from path to workdir */
    817   char* section = NULL; /* section name */
    818   char* file = NULL; /* path from the section to the file */
    819 
    820   int res = EXIT_SUCCESS;
    821 
    822   if(argc < 2) {
    823     fprintf(stderr, "usage: %s path\n", argv[0]);
    824     goto error;
    825   }
    826 
    827   g_cmd = argv[0];
    828 
    829   /* Retrieve the absolute path to the input file */
    830   if(!(path = get_realpath(argv[1]))) goto error;
    831 
    832   /* Extract the directory and file from the input path. Start with the file, as
    833    * the dirname function could add a '\0' character to the path to terminate
    834    * the directory name instead of copying it to local storage. */
    835   bname = strdup(basename(path));
    836   dname  = strdup(dirname(path));
    837 
    838   /* Still retrieve the absolute path to the input file since it was overwritten
    839    * by the basename/dirname calls */
    840   free(path);
    841   if(!(path = get_realpath(argv[1]))) goto error;
    842 
    843   /* Get the absolute path of the working directory */
    844   if(!(workdir = get_workdir())) goto error;
    845 
    846   /* Get the section to which the file belongs, i.e. the first subdirectory of
    847    * its path relative to the working directory */
    848   if(!(section = get_section(workdir, dname))) goto error;
    849 
    850   /* Get the path to the file relatively to the section */
    851   if(!(file = get_section_path(workdir, section, path))) goto error;
    852 
    853   /* Get the relative path from file to working directory. It is used to
    854    * reference the CSS file relatively to the HTML content */
    855   if(!(root = get_relpath(workdir, path))) goto error;
    856 
    857   /* Load the menu file of the working directory */
    858   if(menu_load(&menu, workdir)) goto error;
    859 
    860   /* Load the index file of the section */
    861   if(index_load(&index, section)) goto error;
    862 
    863   /* Find the menu entry corresponding to the section of the input file */
    864   if(!(mentry = menu_find(&menu, section))) goto error;
    865 
    866   /* Find the index entry corresponding to the input file. It may be NULL if the
    867    * content is not indexed by the section. */
    868   ientry = index_find(&index, file);
    869 
    870   /* Print the HTML header for the input content */
    871   print_head(root, mentry->label, ientry ? ientry->lang : DEFAULT_LANG);
    872   print_menu1(root, &menu, mentry);
    873   print_menu2(root, section, &index, ientry);
    874 
    875   printf("<div id=\"content\">\n");
    876 
    877 exit:
    878   menu_release(&menu);
    879   index_release(&index);
    880 
    881   if(path) free(path);
    882   if(dname) free(dname);
    883   if(bname) free(bname);
    884   if(workdir) free(workdir);
    885   if(root) free(root);
    886   if(section) free(section);
    887   if(file) free(file);
    888   return res;
    889 error:
    890   res = EXIT_FAILURE;
    891   goto exit;
    892 }