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(">", stdout);
43 break;
44 case ('>'):
45 fputs("<", stdout);
46 break;
47 case ('"'):
48 fputs(""", stdout);
49 break;
50 case ('&'):
51 fputs("&", 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("“", stdout);
113 p += 2;
114 continue;
115 } else if (escaped_streq(op, p, "\'\'")) {
116 fputs("”", stdout);
117 p += 2;
118 continue;
119 } else if (escaped_streq(op, p, "---")) {
120 fputs("—", stdout);
121 p += 3;
122 continue;
123 } else if (escaped_streq(op, p, "--")) {
124 fputs("–", 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