1 /*	$Id$ */
2 /*
3  * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 #include <sys/queue.h>
18 
19 #include <ctype.h>
20 #include <err.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <unistd.h>
25 
26 #include "extern.h"
27 
28 struct	opts {
29 	const char	*prefix;
30 };
31 
32 /*
33  * Put a single character into an HTML stream on stdout.
34  * Beyond the usual, this also normalises spaces into white-space.
35  */
36 static void
safe_putchar(char c)37 safe_putchar(char c)
38 {
39 
40 	switch (c) {
41 	case ('<'):
42 		fputs("&gt;", stdout);
43 		break;
44 	case ('>'):
45 		fputs("&lt;", stdout);
46 		break;
47 	case ('"'):
48 		fputs("&quot;", stdout);
49 		break;
50 	case ('&'):
51 		fputs("&amp;", stdout);
52 		break;
53 	default:
54 		putchar(isspace((int)c) ? ' ' : c);
55 		break;
56 	}
57 }
58 
59 /*
60  * Safely put a buffer of characters into an HTML stream to stdout.
61  */
62 static void
safe_putbuf(const char * p,size_t sz)63 safe_putbuf(const char *p, size_t sz)
64 {
65 	size_t	 i;
66 
67 	for (i = 0; i < sz; i++)
68 		safe_putchar(p[i]);
69 }
70 
71 /*
72  * See safe_putbuf().
73  */
74 static void
safe_putstr(const char * p)75 safe_putstr(const char *p)
76 {
77 
78 	safe_putbuf(p, strlen(p));
79 }
80 
81 static int
escaped_streq(const char * op,const char * p,const char * str)82 escaped_streq(const char *op, const char *p, const char *str)
83 {
84 
85 	if (op != p && '\\' == p[-1])
86 		return(0);
87 	return(0 == strncmp(p, str, strlen(str)));
88 }
89 
90 /*
91  * Put a comment into the stdout HTML stream.
92  * This will automatically convert @-references into links.
93  */
94 static void
safe_putcomment(const struct opts * opts,const char * p)95 safe_putcomment(const struct opts *opts, const char *p)
96 {
97 	const char	*op, *link;
98 	char		*cp;
99 	size_t		 sz, linksz;
100 
101 	for (op = p; '\0' != *p; ) {
102 		if ('\\' == *p) {
103 			if (op != p && '\\' == p[-1])
104 				safe_putchar(*p);
105 			p++;
106 			continue;
107 		} else if (escaped_streq(op, p, "\n")) {
108 			fputs("<p></p>", stdout);
109 			p += 1;
110 			continue;
111 		} else if (escaped_streq(op, p, "``")) {
112 			fputs("&#x201c;", stdout);
113 			p += 2;
114 			continue;
115 		} else if (escaped_streq(op, p, "\'\'")) {
116 			fputs("&#x201d;", stdout);
117 			p += 2;
118 			continue;
119 		} else if (escaped_streq(op, p, "---")) {
120 			fputs("&#8212;", stdout);
121 			p += 3;
122 			continue;
123 		} else if (escaped_streq(op, p, "--")) {
124 			fputs("&#8211;", stdout);
125 			p += 2;
126 			continue;
127 		}
128 
129 		/*
130 		 * Now we catch our links: '@' for an in-document
131 		 * reference and '[' (Markdown style) for a general
132 		 * reference.
133 		 */
134 
135 		if ( ! escaped_streq(op, p, "@") &&
136 		     ! escaped_streq(op, p, "[")) {
137 			safe_putchar(*p++);
138 			continue;
139 		}
140 
141 		link = op = NULL;
142 
143 		if ('[' == *p)
144 			link = ++p;
145 		else
146 			op = ++p;
147 
148 		sz = linksz = 0;
149 
150 		if (NULL != link) {
151 			for ( ; '\0' != *p && ']' != *p; p++)
152 				linksz++;
153 			if ('\0' != *p)
154 				p++;
155 			if ('(' == *p) {
156 				op = ++p;
157 				for ( ; '\0' != *p && ')' != *p; p++)
158 					sz++;
159 				if ('\0' != *p)
160 					p++;
161 			}
162 		} else if ('"' == *p) {
163 			/* Quote-escaped @-reference. */
164 			for (op = ++p; '\0' != *p && '"' != *p; p++)
165 				sz++;
166 			if ('\0' != *p)
167 				p++;
168 		} else
169 			for ( ; '\0' != *p && ! isspace((int)*p); p++)
170 				if ('(' == *p || ')' == *p ||
171 				    ';' == *p || ',' == *p)
172 					break;
173 				else
174 					sz++;
175 
176 		if ((NULL != link && 0 == linksz) ||
177 		    (NULL == link && 0 == sz)) {
178 			putchar('@');
179 			continue;
180 		}
181 
182 		if (NULL != link) {
183 			if (NULL != op)
184 				printf("<a href=\"%.*s\">", (int)sz, op);
185 			else
186 				printf("<a href=\"%.*s\">", (int)linksz, link);
187 			safe_putbuf(link, linksz);
188 		} else {
189 			cp = sqlite_schema_idbuf(op, sz);
190 			printf("<a href=\"#%s-%s\">", opts->prefix, cp);
191 			free(cp);
192 			safe_putbuf(op, sz);
193 		}
194 		fputs("</a>", stdout);
195 	}
196 }
197 
198 static void
output(const struct opts * opts,struct parse * p)199 output(const struct opts *opts, struct parse *p)
200 {
201 	struct tab	*tab;
202 	struct col	*col;
203 	char		*cp;
204 
205 	puts("<dl class=\"tabs\">");
206 	TAILQ_FOREACH(tab, &p->tabq, entry) {
207 		cp = sqlite_schema_id(tab->name, NULL);
208 		printf("\t<dt id=\"%s-%s\">", opts->prefix, cp);
209 		free(cp);
210 		safe_putstr(tab->name);
211 		puts("</dt>");
212 		puts("\t<dd>");
213 		if (NULL != tab->comment) {
214 			puts("\t\t<div class=\"comment\">");
215 			fputs("\t\t\t", stdout);
216 			safe_putcomment(opts, tab->comment);
217 			puts("\n\t\t</div>");
218 		}
219 		puts("\t\t<dl class=\"cols\">");
220 		TAILQ_FOREACH(col, &tab->colq, entry) {
221 			cp = sqlite_schema_id
222 				(col->tab->name, col->name);
223 			printf("\t\t\t<dt id=\"%s-%s\">",
224 				opts->prefix, cp);
225 			free(cp);
226 			safe_putstr(col->name);
227 			puts("</dt>");
228 			puts("\t\t\t<dd>");
229 			if (NULL != col->fkey) {
230 				fputs("\t\t\t\t<div "
231 					"class=\"foreign\">", stdout);
232 				cp = sqlite_schema_id
233 					(col->fkey->tab->name,
234 					 col->fkey->name);
235 				printf("<a href=\"#%s-%s\">",
236 					opts->prefix, cp);
237 				free(cp);
238 				safe_putstr(col->fkey->tab->name);
239 				safe_putstr(".");
240 				safe_putstr(col->fkey->name);
241 				puts("</a></div>");
242 			}
243 			if (NULL != col->comment) {
244 				puts("\t\t\t\t<div class=\"comment\">");
245 				fputs("\t\t\t\t\t", stdout);
246 				safe_putcomment(opts, col->comment);
247 				puts("\n\t\t\t\t</div>");
248 			}
249 			puts("\t\t\t</dd>");
250 		}
251 		puts("\t\t</dl>");
252 		puts("\t</dd>");
253 	}
254 	puts("</dl>");
255 }
256 
257 int
main(int argc,char * argv[])258 main(int argc, char *argv[])
259 {
260 	int	 	 rc, c;
261 	struct parse	 p;
262 	struct opts	 opts;
263 
264 	memset(&opts, 0, sizeof(struct opts));
265 	memset(&p, 0, sizeof(struct parse));
266 	opts.prefix = "sql";
267 
268 	while (-1 != (c = getopt(argc, argv, "v")))
269 		switch (c) {
270 		case ('v'):
271 			p.verbose = 1;
272 			break;
273 		default:
274 			goto usage;
275 		}
276 
277 	argc -= optind;
278 	argv += optind;
279 
280 	if (0 == argc)
281 		rc = sqlite_schema_parsestdin(&p);
282 	else
283 		rc = sqlite_schema_parsefile(argv[0], &p);
284 
285 	if (rc > 0)
286 		output(&opts, &p);
287 
288 	sqlite_schema_free(&p);
289 	return(rc ? EXIT_SUCCESS : EXIT_FAILURE);
290 
291 usage:
292 	fprintf(stderr, "usage: %s [-v] file\n", getprogname());
293 	return(EXIT_FAILURE);
294 }
295