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