1 /*
2 * Program: mkreadmes
3 *
4 * A much faster version of "make_readmes", the Perl-based FreeBSD ports tool
5 * used in FreeBSD's ports make system for (re)building the ports README.html
6 * files
7 *
8 * Unlike the Perl version, this program is designed to be used as a
9 * standalone tool, not intended for integration into the FreeBSD ports
10 * collection's system of Makefiles (although it very well could be)
11 *
12 * Depends on the following files within the ports tree:
13 *
14 * the ports index file (INDEX-${OSRELEASE}, e.g., INDEX-10), used to gather
15 * information about a specific port
16 *
17 * the top-level and category-level Makefiles (to obtain information about
18 * the existing categories and ports)
19 *
20 * The default template files (README.top, README.category and README.port),
21 * used as a "blueprint" for generating the README.html files) are installed
22 * under ${PREFIX}/share/mkreadmes/Templates directory.
23 *
24 * This file contains only the main() function and a few small auxiliary
25 * functions called directly by main(), plus a few global variables.
26 *
27 * The three real "workhorse" routines that drive the program --
28 * make_top_readme(), make_category_readme() and make_port_readme(), as well
29 * as the "wrapper" routine make_readme() which is called from main() --
30 * all are located in the mkreadmes.c file.
31 *
32 * $Id: main.c,v 1.71 2012/05/04 20:53:10 conrads Exp $
33 *
34 *****************************************************************************/
35
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <unistd.h>
40 #include <sys/param.h>
41
42 #include "mkreadmes.h"
43
44 char ports_dir[PATH_MAX]; /* top-level ports directory */
45 char ports_dir_plus[PATH_MAX]; /* ports_dir plus a trailing slash */
46
47 /* flags set by command line options */
48 int normal_paths = 0; /* make relative paths relative to current
49 working directory instead of ports_dir
50 */
51 int quiet = 0; /* silence normal output */
52 int verbose = 0; /* increase the amount of output */
53
54 /* prototypes for routines in other source files */
55
56 extern char *resolve_path(char *path); /* util.c */
57 extern char *skip_leading(const char *str, const char *pattern); /* util.c */
58 extern int initialize(void); /* init.c */
59 extern int make_readme(const char *path, int make_level); /* mkreadmes.c */
60 extern int set_ports_dir(char *path); /* init.c */
61 extern int set_templates_dir(char *path); /* init.c */
62
63 /******************************************************************************/
64
usage(void)65 static void usage(void)
66 /*
67 Display the help screen, then exit
68 */
69 {
70 /* I originally had usage_text[] declared as one giant string made up of
71 the concatenation of the following strings (i.e, the same layout as
72 below, but without the commas after each string), but the compiler
73 insisted on issuing a warning about the size of the string being larger
74 than what the standard requires an implementation to support, so I
75 redid it this way to make the warnings go away. The pointer stuff is a
76 little more complicated than before (a single printf did the whole job),
77 but it works just fine, and I hate compiler warnings uglying up my
78 compiles. :-)
79 */
80
81 const char *usage_text[] = {
82 "\n",
83 "mkreadmes -- Make README.html files for the FreeBSD Ports Collection\n",
84 "\n",
85 "Usage: mkreadmes [options] [pathname-list]\n",
86 "\n",
87 "Options:\n",
88 "-h Display this help.\n",
89 "-n Use normal handling of relative paths, instead of making them\n",
90 " relative to ${PORTSDIR} (paths beginning with \".\" or \"..\"\n",
91 " are always treated normally).\n",
92 "-p pathname Use pathname as the top level of the ports tree. Any relative\n",
93 " paths in pathname-list will be relative to this, unless \"-n\"\n",
94 " is used. Defaults to \"/usr/ports\", or the setting of the\n",
95 " ${PORTSDIR} environment variable, if defined.\n",
96 "-q Quiet mode. Don't display anything except error messages.\n",
97 "-t pathname Use the template files stored under pathname instead of the\n",
98 /* port Makefile will edit the prefix in the next line if needed */
99 " default /usr/local/share/mkreadmes/Templates.\n",
100 "-V Display program version.\n",
101 "-v Increase verbosity. May be repeated to display progressively\n",
102 " more detailed information about the program's progress.\n",
103 "\n",
104 "pathname-list is an optional whitespace-separated list of port and category\n",
105 "pathnames. If none is provided, the default is to make all of the README.html\n",
106 "files for the entire ports tree (top-level, categories and ports).\n",
107 "\n",
108 NULL
109 };
110
111 const char **ptr = &usage_text[0];
112
113 /* loop through the usage_text[] array, printing one line at a time */
114
115 while (*ptr) /* (while ptr is not pointing at a NULL pointer) */
116 {
117 printf("%s", *ptr++); /* ptr is a pointer to *pointer* to char, so
118 incrementing it bumps us to the next
119 pointer in the array, and dereferencing it
120 yield's the next string's address (yeah, it
121 confused me, too, at first) :-)
122 */
123 }
124
125 exit (0);
126 }
127
128 /******************************************************************************/
129
get_make_level(char * path)130 static int get_make_level(char *path)
131 /*
132 Distinguish whether the fully qualified 'path' is a port, category or
133 top-level directory
134
135 Returns TOP, CATEGORY or PORT if successful, or -1 on error
136 */
137 {
138 char *category; /* pointer to name of current category */
139
140 /* Is it the top-level ports directory (as in "an identical match")? */
141
142 if (strcmp(path, ports_dir) == 0)
143 {
144 return TOP;
145 }
146
147 /* Not top. OK, then, strip off the leading ports_dir/ and
148 see what we've got
149 */
150
151 category = skip_leading(path, ports_dir_plus);
152
153 if (category == path) /* path doesn't start with ports_dir/ */
154 {
155 fprintf(stderr, "\"%s\" is not inside the ports tree\n", path);
156 return -1;
157 }
158
159 /* if the string pointed to by category still contains a slash, then it's
160 not at category level, so try it as a port (may be a totally bogus path,
161 but let make_readme_port() deal with it at the point where it actually
162 fails)
163 */
164 if (strcspn(category, "/") != strlen(category))
165 {
166 return PORT;
167 }
168
169 return CATEGORY; /* let make_readme_category() determine whether it's a
170 valid category or not
171 */
172 }
173
174 /******************************************************************************/
175
main(int argc,char * argv[])176 int main(int argc, char *argv[])
177 /*
178 And so it begins...
179
180 process command line options, then process each command line pathname
181 argument (if any) in turn, or the entire ports tree if none are given
182
183 returns the number of command line arguments which resulted in some
184 type of error, or -1 if initialization failed
185 */
186 {
187 char optstr[] = ":hnp:qt:Vv"; /* options accepted by getopt */
188 int c; /* character returned from getopt() */
189
190 int make_level; /* indicator of the level within the ports tree
191 that we're starting from for a given command line
192 argument; will be set to one of TOP, CATEGORY or
193 PORT for each pathname we process
194 */
195
196 char *path = NULL; /* current pathname command line argument */
197 int return_code = 0; /* code returned by main on exit*/
198
199 /* process command line options and their arguments */
200
201 while ((c = getopt(argc, argv, optstr)) != -1)
202 {
203 switch (c)
204 {
205 case 'h':
206 usage(); /* usage() exits after displaying help */
207 case 'n':
208 /* don't output message more than once */
209 if (verbose && !normal_paths)
210 fprintf(stderr,
211 "Using normal handling of relative paths\n");
212 ++normal_paths;
213 break;
214 case 'p':
215 if (set_ports_dir(optarg) != 0)
216 ++return_code;
217 break;
218 case 'q':
219 if (verbose)
220 {
221 fprintf(stderr,
222 "option \"-q\" is incompatible with \"-v\"\n");
223 ++return_code;
224 }
225 else ++quiet; /* Shhh! Don't say anything! */
226 break;
227 case 't':
228 if (set_templates_dir(optarg) != 0)
229 ++return_code;
230 break;
231 case 'V':
232 printf("mkreadmes version " VERSION "\n");
233 exit (0);
234 case 'v':
235 if (quiet)
236 {
237 fprintf(stderr, "option \"-v\" is incompatible with \"-q\"\n");
238 ++return_code;
239 }
240 else
241 {
242 /* don't output message more than once */
243 if (!verbose)
244 fprintf(stderr, "Increasing verbosity level\n");
245 ++verbose;
246 }
247 break;
248 case ':':
249 fprintf(stderr, "Missing pathname argument to option \"-%c\"\n",
250 optopt);
251 suggest_help;
252 ++return_code;
253 break;
254 case '?':
255 fprintf(stderr, "Unknown option: -%c\n", optopt);
256 suggest_help;
257 ++return_code;
258 break;
259 default: /* something majorly wrong if we wind up here! */
260 fprintf(stderr, "?? getopt returned character code %d ??\n", c);
261 }
262 }
263
264 if (return_code) /* errors in command line processing */
265 return return_code;
266
267 /* initialize most of the data we'll be needing */
268 if (initialize() == -1)
269 {
270 return -1; /* initialization failed */
271 }
272 /* if no pathnames given as command line arguments,
273 use the default top-level ports directory as set
274 by the initialize() routine (either PORTSDIR or
275 "/usr/ports")
276 */
277 if (optind == argc)
278 {
279 return make_readme(ports_dir, TOP);
280 }
281
282 /* otherwise, process each command line argument in turn */
283
284 while (optind < argc)
285 {
286 if ((path = resolve_path(argv[optind++])) == NULL)
287 {
288 ++return_code; /* note the error and move on to next argument */
289 continue;
290 }
291
292 if ((make_level = get_make_level(path)) == -1)
293 {
294 ++return_code; /* ditto */
295 continue;
296 }
297
298 return_code += make_readme(path, make_level);
299
300 /* free the pointer malloc'ed by realpath() earlier in resolve_path() */
301 free(path);
302 }
303
304 return return_code;
305 }
306
307