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("  | \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("  / \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("  . \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 }