1 /*
2  * theme:  use a template to create a webpage (markdown-style)
3  *
4  * usage:  theme [-d root] [-p pagename] [-t template] [-o html] [source]
5  *
6  */
7 /*
8  * Copyright (C) 2007 David L Parsons.
9  * The redistribution terms are provided in the COPYRIGHT file that must
10  * be distributed with this source code.
11  */
12 #include "config.h"
13 #include "pgm_options.h"
14 
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #if defined(HAVE_BASENAME) && defined(HAVE_LIBGEN_H)
19 #  include <libgen.h>
20 #endif
21 #include <stdarg.h>
22 #include <sys/types.h>
23 #include <sys/stat.h>
24 #include <time.h>
25 #if HAVE_PWD_H
26 #  include <pwd.h>
27 #endif
28 #include <fcntl.h>
29 #include <errno.h>
30 #include <ctype.h>
31 #include <unistd.h>
32 
33 #include "mkdio.h"
34 #include "cstring.h"
35 #include "amalloc.h"
36 #include "gethopt.h"
37 
38 char *pgm = "theme";
39 char *output_file = 0;
40 char *pagename = 0;
41 char *root = 0;
42 int   everywhere = 0;	/* expand all <?theme elements everywhere */
43 
44 #if HAVE_PWD_H
45 struct passwd *me = 0;
46 #endif
47 struct stat *infop = 0;
48 
49 #if USE_H1TITLE
50 extern char* mkd_h1_title(MMIOT*);
51 #endif
52 
53 extern int notspecial(char *filename);
54 
55 #define INTAG 0x01
56 #define INHEAD 0x02
57 #define INBODY 0x04
58 
59 
60 #ifndef HAVE_BASENAME
61 char *
basename(char * path)62 basename(char *path)
63 {
64     char *p;
65 
66     if ( p = strrchr(path, '/') )
67 	return 1+p;
68     return path;
69 }
70 #endif
71 
72 #ifdef HAVE_FCHDIR
73 typedef int HERE;
74 #define NOT_HERE (-1)
75 
76 #define pushd(d)	open(d, O_RDONLY)
77 
78 int
popd(HERE pwd)79 popd(HERE pwd)
80 {
81     int rc = fchdir(pwd);
82     close(pwd);
83     return rc;
84 }
85 
86 #else
87 
88 typedef char* HERE;
89 #define NOT_HERE 0
90 
91 HERE
pushd(char * d)92 pushd(char *d)
93 {
94     HERE cwd;
95     int size;
96 
97     if ( chdir(d) == -1 )
98 	return NOT_HERE;
99 
100     for (cwd = malloc(size=40); cwd; cwd = realloc(cwd, size *= 2))
101 	if ( getcwd(cwd, size) )
102 	    return cwd;
103 
104     return NOT_HERE;
105 }
106 
107 int
popd(HERE pwd)108 popd(HERE pwd)
109 {
110     if ( pwd ) {
111 	int rc = chdir(pwd);
112 	free(pwd);
113 
114 	return rc;
115     }
116     return -1;
117 }
118 #endif
119 
120 typedef STRING(int) Istring;
121 
122 void
fail(char * why,...)123 fail(char *why, ...)
124 {
125     va_list ptr;
126 
127     va_start(ptr,why);
128     fprintf(stderr, "%s: ", pgm);
129     vfprintf(stderr, why, ptr);
130     fputc('\n', stderr);
131     va_end(ptr);
132     exit(1);
133 }
134 
135 
136 /* open_template() -- start at the current directory and work up,
137  *                    looking for the deepest nested template.
138  *                    Stop looking when we reach $root or /
139  */
140 FILE *
open_template(char * template)141 open_template(char *template)
142 {
143     char *cwd;
144     int szcwd;
145     HERE here = pushd(".");
146     FILE *ret;
147 
148     if ( here == NOT_HERE )
149 	fail("cannot access the current directory");
150 
151     szcwd = root ? 1 + strlen(root) : 2;
152 
153     if ( (cwd = malloc(szcwd)) == 0 )
154 	return 0;
155 
156     while ( !(ret = fopen(template, "r")) ) {
157 	if ( getcwd(cwd, szcwd) == 0 ) {
158 	    if ( errno == ERANGE )
159 		goto up;
160 	    break;
161 	}
162 
163 	if ( root && (strcmp(root, cwd) == 0) )
164 	    break;	/* ran out of paths to search */
165 	else if ( (strcmp(cwd, "/") == 0) || (*cwd == 0) )
166 	    break;	/* reached / */
167 
168     up: if ( chdir("..") == -1 )
169 	    break;
170     }
171     free(cwd);
172     popd(here);
173     return ret;
174 } /* open_template */
175 
176 
177 static Istring inbuf;
178 static int psp;
179 
180 static int
prepare(FILE * input)181 prepare(FILE *input)
182 {
183     int c;
184 
185     CREATE(inbuf);
186     psp = 0;
187     while ( (c = getc(input)) != EOF )
188 	EXPAND(inbuf) = c;
189     fclose(input);
190     return 1;
191 }
192 
193 static int
pull()194 pull()
195 {
196     return psp < S(inbuf) ? T(inbuf)[psp++] : EOF;
197 }
198 
199 static int
peek(int offset)200 peek(int offset)
201 {
202     int pos = (psp + offset)-1;
203 
204     if ( pos >= 0 && pos < S(inbuf) )
205 	return T(inbuf)[pos];
206 
207     return EOF;
208 }
209 
210 static int
shift(int shiftwidth)211 shift(int shiftwidth)
212 {
213     psp += shiftwidth;
214     return psp;
215 }
216 
217 static int*
cursor()218 cursor()
219 {
220     return T(inbuf) + psp;
221 }
222 
223 
224 static int
thesame(int * p,char * pat)225 thesame(int *p, char *pat)
226 {
227     int i;
228 
229     for ( i=0; pat[i]; i++ ) {
230 	if ( pat[i] == ' ' ) {
231 	    if ( !isspace(peek(i+1)) ) {
232 		return 0;
233 	    }
234 	}
235 	else if ( tolower(peek(i+1)) != pat[i] ) {
236 	    return 0;
237 	}
238     }
239     return 1;
240 }
241 
242 
243 static int
istag(int * p,char * pat)244 istag(int *p, char *pat)
245 {
246     int c;
247 
248     if ( thesame(p, pat) ) {
249 	c = peek(strlen(pat)+1);
250 	return (c == '>' || isspace(c));
251     }
252     return 0;
253 }
254 
255 
256 /* finclude() includes some (unformatted) source
257  */
258 static void
finclude(MMIOT * doc,FILE * out,int flags,int whence)259 finclude(MMIOT *doc, FILE *out, int flags, int whence)
260 {
261     int c;
262     Cstring include;
263     FILE *f;
264 
265     CREATE(include);
266 
267     while ( (c = pull()) != '(' )
268 	;
269 
270     while ( (c=pull()) != ')' && c != EOF )
271 	EXPAND(include) = c;
272 
273     if ( c != EOF ) {
274 	EXPAND(include) = 0;
275 	S(include)--;
276 
277 	if (( f = fopen(T(include), "r") )) {
278 	    while ( (c = getc(f)) != EOF )
279 		putc(c, out);
280 	    fclose(f);
281 	}
282     }
283     DELETE(include);
284 }
285 
286 
287 /* fdirname() prints out the directory part of a path
288  */
289 static void
fdirname(MMIOT * doc,FILE * output,int flags,int whence)290 fdirname(MMIOT *doc, FILE *output, int flags, int whence)
291 {
292     char *p;
293 
294     if ( pagename && (p = basename(pagename)) )
295 	fwrite(pagename, strlen(pagename)-strlen(p), 1, output);
296 }
297 
298 
299 /* fbasename() prints out the file name part of a path
300  */
301 static void
fbasename(MMIOT * doc,FILE * output,int flags,int whence)302 fbasename(MMIOT *doc, FILE *output, int flags, int whence)
303 {
304     char *p;
305 
306     if ( pagename ) {
307 	p = basename(pagename);
308 
309 	if ( !p )
310 	    p = pagename;
311 
312 	if ( p )
313 	    fwrite(p, strlen(p), 1, output);
314     }
315 }
316 
317 
318 /* ftitle() prints out the document title
319  */
320 static void
ftitle(MMIOT * doc,FILE * output,int flags,int whence)321 ftitle(MMIOT *doc, FILE* output, int flags, int whence)
322 {
323     char *h;
324     h = mkd_doc_title(doc);
325 
326 #if USE_H1TITLE
327     if ( !h )
328 	h = mkd_h1_title(doc);
329 #endif
330 
331     if ( !h )
332 	h = pagename;
333 
334     if ( h )
335 	mkd_generateline(h, strlen(h), output, flags);
336 }
337 
338 
339 /* fdate() prints out the document date
340  */
341 static void
fdate(MMIOT * doc,FILE * output,int flags,int whence)342 fdate(MMIOT *doc, FILE *output, int flags, int whence)
343 {
344     char *h;
345 
346     if ( (h = mkd_doc_date(doc)) || ( infop && (h = ctime(&infop->st_mtime)) ) )
347 	mkd_generateline(h, strlen(h), output, flags|MKD_TAGTEXT);
348 }
349 
350 
351 /* fauthor() prints out the document author
352  */
353 static void
fauthor(MMIOT * doc,FILE * output,int flags,int whence)354 fauthor(MMIOT *doc, FILE *output, int flags, int whence)
355 {
356     char *h = mkd_doc_author(doc);
357 
358 #if HAVE_PWD_H
359     if ( (h == 0) && me )
360 	h = me->pw_gecos;
361 #endif
362 
363     if ( h )
364 	mkd_generateline(h, strlen(h), output, flags);
365 }
366 
367 
368 /* fconfig() prints out a tabular version of
369  * tabular versions of the flags.
370  */
371 static void
fconfig(MMIOT * doc,FILE * output,int flags,int whence)372 fconfig(MMIOT *doc, FILE *output, int flags, int whence)
373 {
374     mkd_mmiot_flags(output, doc, (whence & (INHEAD|INTAG)) ? 0 : 1);
375 }
376 
377 
378 /* fversion() prints out the document version
379  */
380 static void
fversion(MMIOT * doc,FILE * output,int flags,int whence)381 fversion(MMIOT *doc, FILE *output, int flags, int whence)
382 {
383     fwrite(markdown_version, strlen(markdown_version), 1, output);
384 }
385 
386 
387 /* fbody() prints out the document
388  */
389 static void
fbody(MMIOT * doc,FILE * output,int flags,int whence)390 fbody(MMIOT *doc, FILE *output, int flags, int whence)
391 {
392     mkd_generatehtml(doc, output);
393 }
394 
395 /* ftoc() prints out the table of contents
396  */
397 static void
ftoc(MMIOT * doc,FILE * output,int flags,int whence)398 ftoc(MMIOT *doc, FILE *output, int flags, int whence)
399 {
400     mkd_generatetoc(doc, output);
401 }
402 
403 /* fstyle() prints out the document's style section
404  */
405 static void
fstyle(MMIOT * doc,FILE * output,int flags,int whence)406 fstyle(MMIOT *doc, FILE *output, int flags, int whence)
407 {
408     mkd_generatecss(doc, output);
409 }
410 
411 
412 /*
413  * theme expansions we love:
414  *   <?theme date?>	-- the document date (file or header date)
415  *   <?theme title?>	-- the document title (header title or document name)
416  *   <?theme author?>	-- the document author (header author or document owner)
417  *   <?theme version?>  -- the version#
418  *   <?theme body?>	-- the document body
419  *   <?theme source?>	-- the filename part of the document name
420  *   <?theme dir?>	-- the directory part of the document name
421  *   <?theme html?>	-- the html file name
422  *   <?theme style?>	-- document-supplied style blocks
423  *   <?theme include(file)?> -- include a file.
424  */
425 static struct _keyword {
426     char *kw;
427     int where;
428     void (*what)(MMIOT*,FILE*,int,int);
429 } keyword[] = {
430     { "author?>",  0xffff, fauthor },
431     { "body?>",    INBODY, fbody },
432     { "toc?>",     INBODY, ftoc },
433     { "date?>",    0xffff, fdate },
434     { "dir?>",     0xffff, fdirname },
435     { "include(",  0xffff, finclude },
436     { "source?>",  0xffff, fbasename },
437     { "style?>",   INHEAD, fstyle },
438     { "title?>",   0xffff, ftitle },
439     { "version?>", 0xffff, fversion },
440     { "config?>",  0xffff, fconfig },
441 };
442 #define NR(x)	(sizeof x / sizeof x[0])
443 
444 
445 /* spin() - run through the theme template, looking for <?theme expansions
446  */
447 void
spin(FILE * template,MMIOT * doc,FILE * output)448 spin(FILE *template, MMIOT *doc, FILE *output)
449 {
450     int c;
451     int *p;
452     int flags;
453     int where = 0x0;
454     int i;
455 
456     prepare(template);
457 
458     while ( (c = pull()) != EOF ) {
459 	if ( c == '<' ) {
460 	    if ( peek(1) == '!' && peek(2) == '-' && peek(3) == '-' ) {
461 		fputs("<!--", output);
462 		shift(3);
463 		do {
464 		    putc(c=pull(), output);
465 		} while ( ! (c == '-' && peek(1) == '-' && peek(2) == '>') );
466 	    }
467 	    else if ( (peek(1) == '?') && thesame(cursor(), "?theme ") ) {
468 		shift(strlen("?theme "));
469 
470 		while ( ((c = pull()) != EOF) && isspace(c) )
471 		    ;
472 
473 		shift(-1);
474 		p = cursor();
475 
476 		if ( where & INTAG )
477 		    flags = MKD_TAGTEXT;
478 		else if ( where & INHEAD )
479 		    flags = MKD_NOIMAGE|MKD_NOLINKS;
480 		else
481 		    flags = 0;
482 
483 		for (i=0; i < NR(keyword); i++)
484 		    if ( thesame(p, keyword[i].kw) ) {
485 			if ( everywhere || (keyword[i].where & where) )
486 			    (*keyword[i].what)(doc,output,flags,where);
487 			break;
488 		    }
489 
490 		while ( (c = pull()) != EOF && (c != '?' && peek(1) != '>') )
491 		    ;
492 		shift(1);
493 	    }
494 	    else
495 		putc(c, output);
496 
497 	    if ( istag(cursor(), "head") ) {
498 		where |= INHEAD;
499 		where &= ~INBODY;
500 	    }
501 	    else if ( istag(cursor(), "body") ) {
502 		where &= ~INHEAD;
503 		where |= INBODY;
504 	    }
505 	    where |= INTAG;
506 	    continue;
507 	}
508 	else if ( c == '>' )
509 	    where &= ~INTAG;
510 
511 	putc(c, output);
512     }
513 } /* spin */
514 
515 
516 struct h_opt opts[] = {
517     { 0, 0, 'c', "flags",  "set/show rendering options" },
518     { 0, 0, 'C', "bitmap", "set/show rendering options numerically" },
519     { 0, 0, 'd', "dir",    "set the document root" },
520     { 0, 0, 'E', 0,        "do all theme expansions everywhere" },
521     { 0, 0, 'f', 0,        "forcibly overwrite existing html files" },
522     { 0, 0, 'o', "file",   "write output to `file`" },
523     { 0, 0, 'p', "title",  "set the page title" },
524     { 0, 0, 't', "template",  "use `template` as template file" },
525     { 0, 0, 'V', 0,        "show version info" },
526 } ;
527 #define NROPTS (sizeof opts / sizeof opts[0])
528 
529 int
main(argc,argv)530 main(argc, argv)
531 char **argv;
532 {
533     char *template = "page.theme";
534     char *source = "stdin";
535     FILE *tmplfile;
536     mkd_flag_t flags = THEME_CF|MKD_TOC;
537     int force = 0;
538     MMIOT *doc;
539     struct stat sourceinfo;
540     char *q;
541 
542     struct h_opt *opt;
543     struct h_context blob;
544 
545     hoptset(&blob, argc, argv);
546     hopterr(&blob, 1);
547 
548     pgm = basename(argv[0]);
549 
550     while ( opt = gethopt(&blob, opts, NROPTS) ) {
551 	if ( opt == HOPTERR ) {
552 	    hoptusage(pgm, opts, NROPTS, "[file]");
553 	    exit(1);
554 	}
555 	switch ( opt->optchar ) {
556 	case 'd':   root = hoptarg(&blob);
557 		    break;
558 	case 'E':   everywhere = 1;
559 		    break;
560 	case 'p':   pagename = hoptarg(&blob);
561 		    break;
562 	case 'f':   force = 1;
563 		    break;
564 	case 't':   template = hoptarg(&blob);
565 		    break;
566 	case 'C':   if ( strcmp(hoptarg(&blob), "?") == 0 ) {
567 			show_flags(0,0);
568 			exit(0);
569 		    }
570 		    else
571 			flags = strtol(hoptarg(&blob), 0, 0);
572 		    break;
573 	case 'c':   if ( strcmp(hoptarg(&blob), "?") == 0 ) {
574 			show_flags(1,0);
575 			exit(0);
576 		    }
577 		    else if ( q = set_flag(&flags, hoptarg(&blob)) )
578 			fprintf(stderr,"%s: unknown option <%s>", pgm, q);
579 		    break;
580 	case 'o':   output_file = hoptarg(&blob);
581 		    break;
582 	case 'V':   printf("theme+discount %s\n", markdown_version);
583 		    exit(0);
584 	}
585     }
586 
587     tmplfile = open_template(template);
588 
589     argc -= hoptind(&blob);
590     argv += hoptind(&blob);
591 
592 
593     if ( argc > 0 ) {
594 	int added_text=0;
595 
596 	if ( (source = malloc(strlen(argv[0]) + strlen("/index.text") + 1)) == 0 )
597 	    fail("out of memory allocating name buffer");
598 
599 	strcpy(source,argv[0]);
600 	if ( (stat(source, &sourceinfo) == 0) && S_ISDIR(sourceinfo.st_mode) )
601 	    strcat(source, "/index");
602 
603 	if ( !freopen(source, "r", stdin) ) {
604 	    strcat(source, ".text");
605 	    added_text = 1;
606 	    if ( !freopen(source, "r", stdin) )
607 		fail("can't open either %s or %s", argv[0], source);
608 	}
609 
610 	if ( !output_file ) {
611 	    char *p, *q;
612 
613 
614 	    if ( (output_file = malloc(strlen(source) + strlen(".html") + 1)) == 0 )
615 		fail("out of memory allocating output file name buffer");
616 
617 	    strcpy(output_file, source);
618 
619 	    if (( p = strchr(output_file, '/') ))
620 		q = strrchr(p+1, '.');
621 	    else
622 		q = strrchr(output_file, '.');
623 
624 	    if ( q )
625 		*q = 0;
626 	    else
627 		q = output_file + strlen(output_file);
628 
629 	    strcat(q, ".html");
630 	}
631     }
632     if ( output_file && strcmp(output_file, "-") ) {
633 	if ( force && notspecial(output_file) )
634 	    unlink(output_file);
635 	if ( !freopen(output_file, "w", stdout) ) {
636 	    fail("can't write to %s", output_file);
637 	}
638     }
639 
640     if ( !pagename )
641 	pagename = source;
642 
643     if ( (doc = mkd_in(stdin, 0)) == 0 )
644 	fail("can't read %s", source ? source : "stdin");
645 
646     if ( fstat(fileno(stdin), &sourceinfo) == 0 )
647 	infop = &sourceinfo;
648 
649 #if HAVE_GETPWUID
650     me = getpwuid(infop ? infop->st_uid : getuid());
651 
652     if ( (root = strdup(me->pw_dir)) == 0 )
653 	fail("out of memory");
654 #endif
655 
656     if ( !mkd_compile(doc, flags) )
657 	fail("couldn't compile input");
658 
659     if ( tmplfile )
660 	spin(tmplfile,doc,stdout);
661     else
662 	mkd_generatehtml(doc, stdout);
663 
664     mkd_cleanup(doc);
665     exit(0);
666 }
667