1 /*
2 * File: mkreadmes.c
3 *
4 * Contains three routines that drive the body of the work, one for
5 * each level of readmes -- the top level, the category level, and
6 * the individual port level -- plus a few small auxiliary functions
7 *
8 * main() will call only one of the make_readme_* routines, based on whether
9 * the pathname given as a command line argument (or the top-level ports
10 * directory, if none is provided) is determined to be at the top, category,
11 * or individual port level
12 *
13 * $Id: mkreadmes.c,v 1.128 2012/05/04 20:53:10 conrads Exp $
14 *
15 *****************************************************************************/
16
17 #include <fcntl.h>
18 #include <limits.h>
19 #include <stdio.h>
20 #include <string.h>
21 #include <unistd.h>
22 #include <sys/stat.h>
23
24 #include "mkreadmes.h"
25
26 /* placeholder names used in templates. on a successful match, the
27 placeholder's index in this array is then used as an index into the
28 index_field[] array to obtain a pointer to the corresponding index field's
29 data, which will have been safely tucked away in the index_line[] array
30 (an array of nul-terminated strings) by the split_index_line() routine
31 */
32 static const char *placeholder[] = {
33 "%%PKG%%",
34 "%%PORT%%",
35 "%%PREFIX%%",
36 "%%COMMENT%%",
37 "%%DESCR%%",
38 "%%EMAIL%%",
39 "%%CATEGORY%%",
40 "%%BUILD_DEPENDS%%",
41 "%%RUN_DEPENDS%%",
42 "%%WEBSITE%%",
43 "%%TOP%%",
44 "%%SUBDIR%%",
45 NULL /* end of list marker */
46 };
47
48 static char category_comment[LINE_MAX]; /* comment from a category's
49 Makefile, saved by
50 make_readme_category() for use
51 by make_readme_top()
52 */
53 char categories[MAXCATLIST]; /* list of categories found in the top-level
54 Makefile
55 */
56 size_t num_categories = 0; /* number of categories found */
57
58 char *(*index_categories)[] = NULL; /* pointer to an array of pointers to
59 the first line of each category's
60 section within the index buffer
61 */
62
63 extern char *index_field[]; /* pointers to current port's index fields */
64
65 char *next_index; /* pointer to the next index line to search */
66 char *next_cat; /* pointer to first index line of next category */
67
68 char templates_dir[PATH_MAX]; /* directory containing the README.*
69 template files
70 */
71 /* pointers to dynamically allocated buffers for the template files */
72 char *template_buf_top = NULL;
73 char *template_buf_category = NULL;
74 char *template_buf_port = NULL;
75
76 extern char ports_dir[PATH_MAX]; /* top-level ports directory */
77 extern char ports_dir_plus[PATH_MAX]; /* ports_dir plus trailing slash */
78
79 extern int quiet; /* silence normal output */
80 extern int verbose; /* increase the amount of output */
81
82 /* prototypes for global functions */
83
84 int make_readme(const char *path, int make_level);
85
86 /* prototypes for external functions */
87
88 extern char *load_file(const char *path); /* util.c */
89 extern char *skip_leading(const char *str, const char *pattern); /* util.c */
90 extern int search_index(const char *path); /* index.c */
91
92 /*****************************************************************************/
93
load_template(const char * ext)94 static char *load_template(const char *ext)
95 /*
96 load the template file with extension 'ext' into a dynamically allocated
97 buffer
98
99 returns a pointer to the buffer on success, or NULL on failure
100
101 Note: This function makes no attempt to determine whether the requested
102 file has already been loaded, relying instead on the caller to have
103 already checked for that before calling this function. Both it and its
104 auxiliary routine, load_file() in util.c, don't know and don't care whether
105 the pointer variable the result will be assigned to on return has already
106 been set, so use with caution!
107
108 There *is* an upside to this methodology: because of the lack of checking
109 before proceeding to load the file, both of these routines are very simple,
110 and very fast. :-)
111 */
112 {
113 char template_path[PATH_MAX];
114
115 /* generate the fully qualified pathname for the template file */
116 strcpy(template_path, templates_dir);
117 strcat(template_path, TEMPLATENAME); /* template name minus extension */
118
119 /* add extension to pathname */
120 strcat(template_path, ext);
121
122 return load_file(template_path);
123 }
124
125 /*****************************************************************************/
126
load_templates(void)127 static int load_templates(void)
128 /*
129 load all three of the template files -- README.port, README.category and
130 README.top -- into memory
131
132 this is just a "convenience" function, called from make_readme
133 for top-level builds
134
135 returns 0 on success, or -1
136 */
137 {
138 /* Don't load any files that have already been loaded */
139
140 if (template_buf_port == NULL) /* not loaded yet */
141 if ((template_buf_port = load_template("port")) == NULL)
142 return -1;
143
144 if (template_buf_category == NULL) /* same here */
145 if ((template_buf_category = load_template("category")) == NULL)
146 return -1;
147
148 if (template_buf_top == NULL) /* and here */
149 if ((template_buf_top = load_template("top")) == NULL)
150 return -1;
151
152 return 0;
153 }
154
155 /*****************************************************************************/
156
HTMLify(char * dest,char * src)157 static inline char *HTMLify(char *dest, char *src )
158 /*
159 Copy 'src' to 'dest', replacing any occurences of '&', '<' or '>'
160 with their HTML representations
161
162 Returns a pointer to the location in 'dest' one past the last
163 character copied
164
165 Uses the addstr() macro defined in mkreadmes.h
166 */
167 {
168 while (*src)
169 {
170 switch(*src)
171 {
172 case '&':
173 {
174 addstr(dest, "&");
175 ++src;
176 continue;
177 }
178 case '<':
179 {
180 addstr(dest, "<");
181 ++src;
182 continue;
183 }
184 case '>':
185 {
186 addstr(dest, ">");
187 ++src;
188 continue;
189 }
190 default:
191 *dest++ = *src++;
192 }
193 }
194
195 return dest;
196 }
197
198 /*****************************************************************************/
199
match_placeholder(const char * ptr)200 static inline int match_placeholder(const char *ptr)
201 /*
202 Try to find a matching "%%VARIABLE%%" within the placeholder array
203 for the string pointed to by 'ptr'
204
205 On entry to this routine, 'ptr' will be pointing to the first "%" in the
206 possible symbol name we're looking for
207
208 Returns a positive integer representing the variable's index in the
209 placeholder array, or -1 if no match is found
210 */
211 {
212 int j;
213
214 int i = 0;
215
216 while (placeholder[i])
217 {
218 j = 0;
219
220 while(ptr[j] == (placeholder[i])[j])
221 ++j;
222
223 if ((placeholder[i])[j] == '\0')
224 return i;
225 ++i;
226 }
227
228 /* No match found */
229 return -1;
230 }
231
232 /*****************************************************************************/
233
category_index(const char * cat)234 static inline int category_index(const char *cat)
235 /*
236 scan the list of category names to get an index to use with the
237 index_categories[] array, from which we'll obtain a pointer to
238 the first line of this category in the index buffer
239
240 returns the index at which the category was found in the list, or
241 num_categories if not found
242 */
243 {
244 char *categories_ptr = categories;
245 int i = 0;
246
247 while (*categories_ptr)
248 {
249 if (strcmp(cat, categories_ptr) == 0)
250 return i; /* found a match */
251 /* else */
252 categories_ptr += strlen(categories_ptr) + 1; /* bump pointer and */
253 ++i; /* index */
254 }
255
256 return i; /* (i == num_categories) = not found */
257 }
258
259 /*****************************************************************************/
260
make_readme_port(const char * path,int make_level)261 static int make_readme_port(const char *path, int make_level)
262 /*
263 Make the README.html file for a single port
264
265 On entry, category will already be pointing either to the "category/port"
266 part of the port's pathname (if make_level == PORT) or just the category
267 part (if make_level == CATEGORY or TOP)
268
269 Returns 0 on success, or 1 on failure
270
271 Uses the addstr() macro defined in mkreadmes.h
272 */
273 {
274 char readme_buf[MAXREADME_PORT]; /* output buffer */
275 char readme_path[PATH_MAX]; /* output pathname */
276 int readme; /* file descriptor */
277 char *catport; /* category/port portion of path */
278 char catonly[MAXCATNAME]; /* just the category name */
279 char *category; /* pointer to category name */
280 size_t catlen; /* length of category name */
281 int i; /* index into the placeholder array where a
282 template match was found
283 */
284 int cat_index; /* index into the categories[] and index_categories[]
285 arrays for this port's category (can't initialize
286 here as we do in make_readme_category() below, not
287 until we make certain that 'category' has been set
288 properly
289 */
290
291 char *readme_ptr = readme_buf; /* pointer into output buffer */
292 char *template_ptr = template_buf_port; /* pointer into template
293 buffer
294 */
295
296 /* save a pointer to the category/port portion of port's path */
297 catport = skip_leading(path, ports_dir_plus);
298
299 /* copy just the category to catonly */
300 catlen = strcspn(catport, "/");
301 strncpy(catonly, catport, catlen);
302 catonly[catlen] = '\0';
303 category = catonly; /* set category to point to the string we
304 just copied
305 */
306 if (make_level == PORT) /* need to set next_index and next_cat */
307 {
308 /* set next_index to point to the first line of this port's category's
309 section in the index buffer, so we won't waste any time searching in
310 any of the preceding categories
311 */
312 next_index = (*index_categories)[cat_index = category_index(category)];
313
314 if (next_index == NULL) /* category not found :-( */
315 {
316 fprintf(stderr, "No such category: %s\n", category);
317 return 1;
318 }
319
320 /* set next_cat to point to the first index line of the next category,
321 to use as the search cutoff point
322 */
323 next_cat = (*index_categories)[cat_index + 1];
324 }
325
326 /* Find the port's entry in the index file, saving pointers to its index
327 information in the index_field[] array
328 */
329 if (!search_index(path))
330 {
331 fprintf(stderr, "Port %s not found in INDEX\n", catport);
332 return 1;
333 }
334
335 /* For efficiency's sake, don't output the following message
336 unless this one port is all we're building, or the verbose
337 flag is set to >= 3
338 */
339 if ((verbose > 2) || ((make_level == PORT) && !quiet))
340 printf("Making README.html for port %s\n", catport);
341
342 /* Copy the template buffer to the readme buffer,
343 performing any substitutions needed
344 */
345
346 while (*template_ptr)
347 {
348 /* just copy from template buffer to readme buffer
349 unless we hit a '%'
350 */
351 if ((*template_ptr == '%') &&
352 ((i = match_placeholder(template_ptr)) >= 0))
353 {
354 /* a few fields require special handling:
355
356 for the PORT field, use the "category/port" string we saved
357 earlier in the variable catport[]
358
359 for the COMMENT field, convert any '&', '<' or '>' characters
360 into their HTML representations
361
362 for the DESCR field, adjust the path prefix to the realpath
363 of the top of our ports tree, which may differ from the
364 canonical "/usr/ports" used in the index field
365
366 for BUILD_DEPENDS and RUN_DEPENDS, don't output anything
367 if these fields are empty in the INDEX file, otherwise,
368 precede them with the introductory text #defined in
369 BUILD_DEPS or RUN_DEPS
370
371 for WEBSITE, output the lead-in text #defined in WWW_BEFORE,
372 then the field contents, and follow it up with the text
373 #defined in WWW_AFTER
374
375 */
376 switch (i)
377 {
378 case PKG:
379 addstr(readme_ptr, index_field[i]);
380 break;
381 case PORT:
382 addstr(readme_ptr, catport);
383 break;
384 case COMMENT:
385 /* the port's comment may have some bad HTML chars */
386 readme_ptr = HTMLify(readme_ptr, index_field[COMMENT]);
387 break;
388 case DESCR:
389 addstr(readme_ptr, ports_dir);
390 addstr(readme_ptr, skip_leading(index_field[i], PORTSTOP));
391 break;
392 case EMAIL:
393 addstr(readme_ptr, index_field[i]);
394 break;
395 case BUILD_DEPENDS:
396 if (strlen(index_field[i]))
397 {
398 addstr(readme_ptr, BUILD_DEPS);
399 addstr(readme_ptr, index_field[i]);
400 }
401 break;
402 case RUN_DEPENDS:
403 if (strlen(index_field[i]))
404 {
405 addstr(readme_ptr, RUN_DEPS);
406 addstr(readme_ptr, index_field[i]);
407 }
408 break;
409 case WEBSITE:
410 if (strlen(index_field[i]))
411 {
412 addstr(readme_ptr, WWW_BEFORE);
413 addstr(readme_ptr, index_field[i]);
414 addstr(readme_ptr, WWW_AFTER);
415 }
416 break;
417 case TOP:
418 addstr(readme_ptr, ports_dir);
419 break;
420 }
421 /* bump template pointer past placeholder */
422 template_ptr += strlen(placeholder[i]);
423 }
424 else *readme_ptr++ = *template_ptr++;
425 }
426
427 /* Open the port's README.html file for writing */
428
429 strcpy(readme_path, path); /* port's path in the ports tree */
430 strcat(readme_path, READMENAME); /* append "/README.html" */
431
432 if ((readme = open(readme_path, O_WRONLY|O_CREAT|O_TRUNC|O_DIRECT, MODE))
433 == -1)
434 {
435 perror(readme_path);
436 return 1;
437 }
438
439 /* write readme_buf to output file and close */
440
441 if (write(readme, readme_buf, (size_t)(readme_ptr - readme_buf)) == -1)
442 {
443 perror(readme_path);
444 close(readme);
445 return 1;
446 }
447
448 close(readme);
449
450 return 0;
451 }
452
453 /*****************************************************************************/
454
make_readme_category(const char * path,int make_level)455 static int make_readme_category(const char *path, int make_level)
456 /*
457 Make the README.html file for a single category
458
459 Calls make_readme_port() to build the README.html file for each
460 port within the category
461
462 Returns 0 on success, or 1 on failure
463
464 Uses the addstr() macro defined in mkreadmes.h
465 */
466 {
467 char readme_buf[MAXREADME_CAT]; /* output buffer */
468 char readme_path[PATH_MAX]; /* output pathname */
469 int readme; /* file descriptor */
470
471 char Makefile_buf[LINE_MAX]; /* input buffer */
472 char Makefile_path[PATH_MAX]; /* input pathname */
473 FILE *Makefile; /* input stream for category's
474 Makefile
475 */
476 char ports[MAXPORTSLIST]; /* list of ports found in a category's
477 Makefile
478 */
479 char *port; /* pointer to port in current input buffer */
480 char port_path[PATH_MAX]; /* full path to port in ports tree */
481
482 int i; /* index into the placeholder array where a
483 template match was found
484 */
485
486 char *comment_ptr = Makefile_buf; /* for locating the COMMENT line */
487 char *readme_ptr = readme_buf; /* pointer into output buffer */
488 char *ports_ptr = ports; /* pointer into ports list */
489 char *template_ptr = template_buf_category; /* pointer into template
490 buffer
491 */
492 int num_ports = 0; /* number of ports in category */
493
494 /* get the index into the categories[] and index_categories[] arrays
495 for this category
496 */
497 char *category = skip_leading(path, ports_dir_plus);
498 int cat_index = category_index(category);
499
500 /* set next_index to point to the first line of this category's section
501 in the index buffer, so we won't waste any time searching in any of
502 the preceding categories
503 */
504 if ((next_index = (*index_categories)[cat_index]) == NULL) /* not found */
505 {
506 fprintf(stderr, "No such category: %s\n", category);
507 return 1;
508 }
509
510 /* set next_cat to point to the first index line of the next category,
511 to use as the search cutoff point
512 */
513 next_cat = (*index_categories)[cat_index + 1];
514
515 /* Open the category's Makefile for reading */
516
517 strcpy(Makefile_path, path);
518 strcat(Makefile_path, MAKEFILENAME);
519
520 if ((Makefile = fopen(Makefile_path, "r")) == NULL)
521 {
522 perror(Makefile_path);
523 return 1;
524 }
525
526 /* First, find the COMMENT line, saving it externally in category_comment[]
527 in case we were called from make_readme_top(), which will need the
528 comment for its own README.html file
529 */
530
531 while ((fgets(Makefile_buf, LINE_MAX, Makefile) != NULL))
532 {
533 if ((comment_ptr = skip_leading(Makefile_buf, " COMMENT = "))
534 != Makefile_buf) /* found it */
535 {
536 /* don't want the newline */
537 comment_ptr[strlen(comment_ptr) - 1] = '\0';
538 strcpy(category_comment, comment_ptr); /* save the COMMENT */
539 break;
540 }
541 }
542
543 /* Got the comment, continue reading to get all the SUBDIRs */
544
545 while ((fgets(Makefile_buf, LINE_MAX, Makefile) != NULL))
546 {
547 if ((port = skip_leading(Makefile_buf, " SUBDIR += ")) !=
548 Makefile_buf) /* found a SUBDIR line */
549 {
550 /* don't want the newline */
551 port[strlen(port) - 1] = '\0';
552 ports_ptr = stpcpy(ports_ptr, port) + 1; /* add to ports list */
553 ++num_ports;
554 }
555 }
556
557 /* add an extra nul character to mark the end of the ports list */
558 *ports_ptr = '\0';
559
560 fclose(Makefile);
561
562 /* Done collecting the data, now we can start generating
563 the README.html file
564 */
565 if (!quiet)
566 printf("Making README.html for category %s (%d ports)\n",
567 category, num_ports);
568
569 /* Copy the template buffer to the readme buffer,
570 performing any substitutions needed
571 */
572
573 while (*template_ptr)
574 {
575 /* just copy from template buffer to readme buffer
576 unless we hit a '%'
577 */
578 if ((*template_ptr == '%') &&
579 ((i = match_placeholder(template_ptr)) >= 0))
580 {
581 /* SUBDIR requires special handling, calling make_readme_port()
582 for each port in the category to build the port's README.html
583 file and to obtain its package name and comment for the
584 category README.html file
585
586 just as in make_readme_port(), for the port's COMMENT field,
587 convert any '&', '<' or '>' characters into their HTML
588 representations
589 */
590 switch (i)
591 {
592 case COMMENT: /* the category's comment, not a port's */
593 addstr(readme_ptr, category_comment);
594 /* add the number of ports in category to end of comment */
595 readme_ptr += sprintf(readme_ptr, " (%d ports)", num_ports);
596 break;
597 case CATEGORY:
598 addstr(readme_ptr, category);
599 break;
600 case SUBDIR:
601 ports_ptr = ports;
602
603 while (*ports_ptr)
604 {
605 port = ports_ptr; /* port's base name */
606
607 /* generate the full path to the port */
608 strcpy(port_path, path); /* category's path */
609 strcat(port_path, "/");
610 strcat(port_path, port); /* append port's name */
611
612 /* make the port's README.html */
613 make_readme_port(port_path, make_level);
614
615 /* now we can use the information left over in the
616 index_field[] array by make_readme_port() */
617 addstr(readme_ptr, "<a href=\"");
618 addstr(readme_ptr, port);
619 addstr(readme_ptr, "/README.html\">");
620 addstr(readme_ptr, index_field[PKG]);
621 addstr(readme_ptr, "</a>: ");
622
623 /* the port's comment may have some bad HTML chars */
624 readme_ptr = HTMLify(readme_ptr, index_field[COMMENT]);
625 strcpy(readme_ptr++, "\n");
626
627 /* bump ports_ptr to next item in ports array */
628 ports_ptr += strlen(port) + 1;
629 }
630 }
631 /* bump template pointer past placeholder */
632 template_ptr += strlen(placeholder[i]);
633 }
634 else *readme_ptr++ = *template_ptr++;
635 }
636
637 /* Open the category's README.html file for writing */
638
639 strcpy(readme_path, path);
640 strcat(readme_path, READMENAME);
641
642 if ((readme = open(readme_path, O_WRONLY|O_CREAT|O_TRUNC|O_DIRECT, MODE))
643 == -1)
644 {
645 perror(readme_path);
646 return 1;
647 }
648
649 /* write readme_buf to output file and close */
650
651 if (write(readme, readme_buf, (size_t)(readme_ptr - readme_buf)) == -1)
652 {
653 perror(readme_path);
654 close(readme);
655 return 1;
656 }
657
658 close(readme);
659
660 return 0;
661 }
662
663 /*****************************************************************************/
664
make_readme_top(const char * path,int make_level)665 static int make_readme_top(const char *path, int make_level)
666 /*
667 Make the top-level README.html file
668
669 Calls make_readme_category() to build the README.html file for each
670 category in the categories list created earlier from the top-level Makefile
671
672 Returns 0 on success, or 1 on failure
673
674 Uses the addstr() macro defined in mkreadmes.h
675 */
676 {
677 char readme_buf[MAXREADME_TOP]; /* output buffer */
678 char readme_path[PATH_MAX]; /* output pathname */
679 int readme; /* file descriptor */
680
681 char *category; /* name of current category */
682 char category_path[PATH_MAX]; /* fully qualified path to current
683 category
684 */
685 int i; /* index into the placeholder array
686 where a template match was found
687 */
688 char *categories_ptr = categories; /* pointer into categories list */
689 char *readme_ptr = readme_buf; /* pointer into output buffer */
690 char *template_ptr = template_buf_top; /* pointer into template buffer
691 */
692 if (!quiet)
693 printf("Making top-level README.html in %s (%lu categories)\n",
694 ports_dir, num_categories);
695
696 /* Copy the template buffer to the readme buffer,
697 performing any substitutions needed
698 */
699
700 while (*template_ptr)
701 {
702 /* just copy from template buffer to readme buffer
703 unless we hit a '%'
704 */
705 if ((*template_ptr == '%') &&
706 ((i = match_placeholder(template_ptr)) >= 0))
707 {
708 /* %%SUBDIR%% is the only placeholder used in the top-level
709 template, and will cause this routine to call
710 make_readme_category() for each category found in the top-level
711 Makefile (already obtained and saved earlier in the categories[]
712 list), which in turn will call make_readme_port() for each port
713 in the category's own Makefile.
714
715 On return from make_readme_category() the category's COMMENT
716 will be stored in category_comment[] for use in the top-level
717 README.html file.
718 */
719 if (i == SUBDIR)
720 {
721 categories_ptr = categories;
722
723 while (*categories_ptr)
724 {
725 category = categories_ptr; /* base name of category */
726
727 /* generate the full path to the category */
728 strcpy(category_path, ports_dir_plus);
729 strcat(category_path, category);
730
731 /* make the category's README.html, saving its
732 COMMENT in category_comment[] */
733 make_readme_category(category_path, make_level);
734
735 /* now we have all the information we need
736 for this category
737 */
738 addstr(readme_ptr, "<a href=\"");
739 addstr(readme_ptr, category);
740 addstr(readme_ptr, "/README.html\">");
741 addstr(readme_ptr, category);
742 addstr(readme_ptr, "</a>: ");
743 addstr(readme_ptr, category_comment);
744 strcpy(readme_ptr++, "\n");
745
746 /* bump pointer to next item in categories array */
747 categories_ptr += strlen(category) + 1;
748 }
749 }
750 /* bump template pointer past placeholder */
751 template_ptr += strlen(placeholder[i]);
752 }
753 else *readme_ptr++ = *template_ptr++;
754 }
755
756 /* Open the top-level README.html file for writing */
757
758 strcpy(readme_path, path); /* fully qualified top-level path */
759 strcat(readme_path, READMENAME); /* append "/README.html" */
760
761 if ((readme = open(readme_path, O_WRONLY|O_CREAT|O_TRUNC|O_DIRECT, MODE))
762 == -1)
763 {
764 perror(readme_path);
765 return 1;
766 }
767
768 /* write readme_buf to output file and close */
769
770 if (write(readme, readme_buf, (size_t)(readme_ptr - readme_buf)) == -1)
771 {
772 perror(readme_path);
773 close(readme);
774 return 1;
775 }
776
777 close(readme);
778
779 return 0;
780 }
781
782 /*****************************************************************************/
783
make_readme(const char * path,int make_level)784 int make_readme(const char *path, int make_level)
785 /*
786 wrapper for the actual make_readme_* routines, called from main()
787
788 make sure the template files are loaded for whichever level of build
789 we're doing, then do the actual build
790
791 returns the return code of whichever make_readme_* routine it calls
792
793 (the default case in the switch statement below should never happen,
794 and is provided merely to avoid a compiler warning)
795 */
796 {
797 switch (make_level)
798 {
799 case TOP:
800 {
801 /* load_templates() will check first before trying to load a
802 a template file, to see if it has already been loaded, and
803 if so, won't try to load it again
804 */
805 if (load_templates() != 0)
806 return -1;
807 /* else */
808 return make_readme_top(path, make_level);
809 }
810
811 /* for CATEGORY and PORT, we do our checking right here first, before
812 calling load_template()
813 */
814
815 case CATEGORY:
816 {
817 if (template_buf_category == NULL) /* not loaded yet */
818 if ((template_buf_category = load_template("category")) == NULL)
819 return -1;
820 if (template_buf_port == NULL) /* ditto */
821 if ((template_buf_port = load_template("port")) == NULL)
822 return -1;
823 return make_readme_category(path, make_level);
824 }
825
826 case PORT:
827 {
828 if (template_buf_port == NULL) /* and likewise for PORT */
829 if ((template_buf_port = load_template("port")) == NULL)
830 return -1;
831 return make_readme_port(path, make_level);
832 }
833 default: return -1; /* this should never happen, but let's
834 keep the compiler happy
835 */
836 }
837 }
838
839