theme.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632
  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. #include <stdio.h>
  15. #include <stdlib.h>
  16. #include <string.h>
  17. #if defined(HAVE_BASENAME) && defined(HAVE_LIBGEN_H)
  18. # include <libgen.h>
  19. #endif
  20. #if defined(HAVE_ALLOCA_H)
  21. # include <alloca.h>
  22. #endif
  23. #include <unistd.h>
  24. #include <stdarg.h>
  25. #include <sys/types.h>
  26. #include <sys/stat.h>
  27. #include <time.h>
  28. #if HAVE_PWD_H
  29. # include <pwd.h>
  30. #endif
  31. #include <fcntl.h>
  32. #include <errno.h>
  33. #include <ctype.h>
  34. #include "mkdio.h"
  35. #include "cstring.h"
  36. #include "amalloc.h"
  37. char *pgm = "theme";
  38. char *output = 0;
  39. char *pagename = 0;
  40. char *root = 0;
  41. int everywhere = 0; /* expand all <?theme elements everywhere */
  42. #if HAVE_PWD_H
  43. struct passwd *me = 0;
  44. #endif
  45. struct stat *infop = 0;
  46. #define INTAG 0x01
  47. #define INHEAD 0x02
  48. #define INBODY 0x04
  49. #ifndef HAVE_BASENAME
  50. char *
  51. basename(char *path)
  52. {
  53. char *p;
  54. if ( p = strrchr(path, '/') )
  55. return 1+p;
  56. return path;
  57. }
  58. #endif
  59. #ifdef HAVE_FCHDIR
  60. typedef int HERE;
  61. #define NOT_HERE (-1)
  62. #define pushd(d) open(d, O_RDONLY)
  63. int
  64. popd(HERE pwd)
  65. {
  66. int rc = fchdir(pwd);
  67. close(pwd);
  68. return rc;
  69. }
  70. #else
  71. typedef char* HERE;
  72. #define NOT_HERE 0
  73. HERE
  74. pushd(char *d)
  75. {
  76. HERE cwd;
  77. int size;
  78. if ( chdir(d) == -1 )
  79. return NOT_HERE;
  80. for (cwd = malloc(size=40); cwd; cwd = realloc(cwd, size *= 2))
  81. if ( getcwd(cwd, size) )
  82. return cwd;
  83. return NOT_HERE;
  84. }
  85. int
  86. popd(HERE pwd)
  87. {
  88. if ( pwd ) {
  89. int rc = chdir(pwd);
  90. free(pwd);
  91. return rc;
  92. }
  93. return -1;
  94. }
  95. #endif
  96. typedef STRING(int) Istring;
  97. void
  98. fail(char *why, ...)
  99. {
  100. va_list ptr;
  101. va_start(ptr,why);
  102. fprintf(stderr, "%s: ", pgm);
  103. vfprintf(stderr, why, ptr);
  104. fputc('\n', stderr);
  105. va_end(ptr);
  106. exit(1);
  107. }
  108. /* open_template() -- start at the current directory and work up,
  109. * looking for the deepest nested template.
  110. * Stop looking when we reach $root or /
  111. */
  112. FILE *
  113. open_template(char *template)
  114. {
  115. char *cwd;
  116. int szcwd;
  117. HERE here = pushd(".");
  118. FILE *ret;
  119. if ( here == NOT_HERE )
  120. fail("cannot access the current directory");
  121. szcwd = root ? 1 + strlen(root) : 2;
  122. if ( (cwd = malloc(szcwd)) == 0 )
  123. return 0;
  124. while ( !(ret = fopen(template, "r")) ) {
  125. if ( getcwd(cwd, szcwd) == 0 ) {
  126. if ( errno == ERANGE )
  127. goto up;
  128. break;
  129. }
  130. if ( root && (strcmp(root, cwd) == 0) )
  131. break; /* ran out of paths to search */
  132. else if ( (strcmp(cwd, "/") == 0) || (*cwd == 0) )
  133. break; /* reached / */
  134. up: if ( chdir("..") == -1 )
  135. break;
  136. }
  137. free(cwd);
  138. popd(here);
  139. return ret;
  140. } /* open_template */
  141. static Istring inbuf;
  142. static int psp;
  143. static int
  144. prepare(FILE *input)
  145. {
  146. int c;
  147. CREATE(inbuf);
  148. psp = 0;
  149. while ( (c = getc(input)) != EOF )
  150. EXPAND(inbuf) = c;
  151. fclose(input);
  152. return 1;
  153. }
  154. static int
  155. pull()
  156. {
  157. return psp < S(inbuf) ? T(inbuf)[psp++] : EOF;
  158. }
  159. static int
  160. peek(int offset)
  161. {
  162. int pos = (psp + offset)-1;
  163. if ( pos >= 0 && pos < S(inbuf) )
  164. return T(inbuf)[pos];
  165. return EOF;
  166. }
  167. static int
  168. shift(int shiftwidth)
  169. {
  170. psp += shiftwidth;
  171. return psp;
  172. }
  173. static int*
  174. cursor()
  175. {
  176. return T(inbuf) + psp;
  177. }
  178. static int
  179. thesame(int *p, char *pat)
  180. {
  181. int i;
  182. for ( i=0; pat[i]; i++ ) {
  183. if ( pat[i] == ' ' ) {
  184. if ( !isspace(peek(i+1)) ) {
  185. return 0;
  186. }
  187. }
  188. else if ( tolower(peek(i+1)) != pat[i] ) {
  189. return 0;
  190. }
  191. }
  192. return 1;
  193. }
  194. static int
  195. istag(int *p, char *pat)
  196. {
  197. int c;
  198. if ( thesame(p, pat) ) {
  199. c = peek(strlen(pat)+1);
  200. return (c == '>' || isspace(c));
  201. }
  202. return 0;
  203. }
  204. /* finclude() includes some (unformatted) source
  205. */
  206. static void
  207. finclude(MMIOT *doc, FILE *out, int flags, int whence)
  208. {
  209. int c;
  210. Cstring include;
  211. FILE *f;
  212. CREATE(include);
  213. while ( (c = pull()) != '(' )
  214. ;
  215. while ( (c=pull()) != ')' && c != EOF )
  216. EXPAND(include) = c;
  217. if ( c != EOF ) {
  218. EXPAND(include) = 0;
  219. S(include)--;
  220. if (( f = fopen(T(include), "r") )) {
  221. while ( (c = getc(f)) != EOF )
  222. putc(c, out);
  223. fclose(f);
  224. }
  225. }
  226. DELETE(include);
  227. }
  228. /* fdirname() prints out the directory part of a path
  229. */
  230. static void
  231. fdirname(MMIOT *doc, FILE *output, int flags, int whence)
  232. {
  233. char *p;
  234. if ( pagename && (p = basename(pagename)) )
  235. fwrite(pagename, strlen(pagename)-strlen(p), 1, output);
  236. }
  237. /* fbasename() prints out the file name part of a path
  238. */
  239. static void
  240. fbasename(MMIOT *doc, FILE *output, int flags, int whence)
  241. {
  242. char *p;
  243. if ( pagename ) {
  244. p = basename(pagename);
  245. if ( !p )
  246. p = pagename;
  247. if ( p )
  248. fwrite(p, strlen(p), 1, output);
  249. }
  250. }
  251. /* ftitle() prints out the document title
  252. */
  253. static void
  254. ftitle(MMIOT *doc, FILE* output, int flags, int whence)
  255. {
  256. char *h;
  257. if ( (h = mkd_doc_title(doc)) == 0 && pagename )
  258. h = pagename;
  259. if ( h )
  260. mkd_generateline(h, strlen(h), output, flags);
  261. }
  262. /* fdate() prints out the document date
  263. */
  264. static void
  265. fdate(MMIOT *doc, FILE *output, int flags, int whence)
  266. {
  267. char *h;
  268. if ( (h = mkd_doc_date(doc)) || ( infop && (h = ctime(&infop->st_mtime)) ) )
  269. mkd_generateline(h, strlen(h), output, flags|MKD_TAGTEXT);
  270. }
  271. /* fauthor() prints out the document author
  272. */
  273. static void
  274. fauthor(MMIOT *doc, FILE *output, int flags, int whence)
  275. {
  276. char *h = mkd_doc_author(doc);
  277. #if HAVE_PWD_H
  278. if ( (h == 0) && me )
  279. h = me->pw_gecos;
  280. #endif
  281. if ( h )
  282. mkd_generateline(h, strlen(h), output, flags);
  283. }
  284. /* fconfig() prints out a tabular version of
  285. * tabular versions of the flags.
  286. */
  287. static void
  288. fconfig(MMIOT *doc, FILE *output, int flags, int whence)
  289. {
  290. mkd_mmiot_flags(output, doc, (whence & (INHEAD|INTAG)) ? 0 : 1);
  291. }
  292. /* fversion() prints out the document version
  293. */
  294. static void
  295. fversion(MMIOT *doc, FILE *output, int flags, int whence)
  296. {
  297. fwrite(markdown_version, strlen(markdown_version), 1, output);
  298. }
  299. /* fbody() prints out the document
  300. */
  301. static void
  302. fbody(MMIOT *doc, FILE *output, int flags, int whence)
  303. {
  304. mkd_generatehtml(doc, output);
  305. }
  306. /* ftoc() prints out the table of contents
  307. */
  308. static void
  309. ftoc(MMIOT *doc, FILE *output, int flags, int whence)
  310. {
  311. mkd_generatetoc(doc, output);
  312. }
  313. /* fstyle() prints out the document's style section
  314. */
  315. static void
  316. fstyle(MMIOT *doc, FILE *output, int flags, int whence)
  317. {
  318. mkd_generatecss(doc, output);
  319. }
  320. /*
  321. * theme expansions we love:
  322. * <?theme date?> -- the document date (file or header date)
  323. * <?theme title?> -- the document title (header title or document name)
  324. * <?theme author?> -- the document author (header author or document owner)
  325. * <?theme version?> -- the version#
  326. * <?theme body?> -- the document body
  327. * <?theme source?> -- the filename part of the document name
  328. * <?theme dir?> -- the directory part of the document name
  329. * <?theme html?> -- the html file name
  330. * <?theme style?> -- document-supplied style blocks
  331. * <?theme include(file)?> -- include a file.
  332. */
  333. static struct _keyword {
  334. char *kw;
  335. int where;
  336. void (*what)(MMIOT*,FILE*,int,int);
  337. } keyword[] = {
  338. { "author?>", 0xffff, fauthor },
  339. { "body?>", INBODY, fbody },
  340. { "toc?>", INBODY, ftoc },
  341. { "date?>", 0xffff, fdate },
  342. { "dir?>", 0xffff, fdirname },
  343. { "include(", 0xffff, finclude },
  344. { "source?>", 0xffff, fbasename },
  345. { "style?>", INHEAD, fstyle },
  346. { "title?>", 0xffff, ftitle },
  347. { "version?>", 0xffff, fversion },
  348. { "config?>", 0xffff, fconfig },
  349. };
  350. #define NR(x) (sizeof x / sizeof x[0])
  351. /* spin() - run through the theme template, looking for <?theme expansions
  352. */
  353. void
  354. spin(FILE *template, MMIOT *doc, FILE *output)
  355. {
  356. int c;
  357. int *p;
  358. int flags;
  359. int where = 0x0;
  360. int i;
  361. prepare(template);
  362. while ( (c = pull()) != EOF ) {
  363. if ( c == '<' ) {
  364. if ( peek(1) == '!' && peek(2) == '-' && peek(3) == '-' ) {
  365. fputs("<!--", output);
  366. shift(3);
  367. do {
  368. putc(c=pull(), output);
  369. } while ( ! (c == '-' && peek(1) == '-' && peek(2) == '>') );
  370. }
  371. else if ( (peek(1) == '?') && thesame(cursor(), "?theme ") ) {
  372. shift(strlen("?theme "));
  373. while ( ((c = pull()) != EOF) && isspace(c) )
  374. ;
  375. shift(-1);
  376. p = cursor();
  377. if ( where & INTAG )
  378. flags = MKD_TAGTEXT;
  379. else if ( where & INHEAD )
  380. flags = MKD_NOIMAGE|MKD_NOLINKS;
  381. else
  382. flags = 0;
  383. for (i=0; i < NR(keyword); i++)
  384. if ( thesame(p, keyword[i].kw) ) {
  385. if ( everywhere || (keyword[i].where & where) )
  386. (*keyword[i].what)(doc,output,flags,where);
  387. break;
  388. }
  389. while ( (c = pull()) != EOF && (c != '?' && peek(1) != '>') )
  390. ;
  391. shift(1);
  392. }
  393. else
  394. putc(c, output);
  395. if ( istag(cursor(), "head") ) {
  396. where |= INHEAD;
  397. where &= ~INBODY;
  398. }
  399. else if ( istag(cursor(), "body") ) {
  400. where &= ~INHEAD;
  401. where |= INBODY;
  402. }
  403. where |= INTAG;
  404. continue;
  405. }
  406. else if ( c == '>' )
  407. where &= ~INTAG;
  408. putc(c, output);
  409. }
  410. } /* spin */
  411. void
  412. main(argc, argv)
  413. char **argv;
  414. {
  415. char *template = "page.theme";
  416. char *source = "stdin";
  417. FILE *tmplfile;
  418. int opt;
  419. mkd_flag_t flags = THEME_CF|MKD_TOC;
  420. int force = 0;
  421. MMIOT *doc;
  422. struct stat sourceinfo;
  423. opterr=1;
  424. pgm = basename(argv[0]);
  425. while ( (opt=getopt(argc, argv, "EfC:c:d:t:p:o:V")) != EOF ) {
  426. switch (opt) {
  427. case 'd': root = optarg;
  428. break;
  429. case 'E': everywhere = 1;
  430. break;
  431. case 'p': pagename = optarg;
  432. break;
  433. case 'f': force = 1;
  434. break;
  435. case 't': template = optarg;
  436. break;
  437. case 'C': if ( strcmp(optarg, "?") == 0 ) {
  438. show_flags(0);
  439. exit(0);
  440. }
  441. else
  442. flags = strtol(optarg, 0, 0);
  443. break;
  444. case 'c': if ( strcmp(optarg, "?") == 0 ) {
  445. show_flags(1);
  446. exit(0);
  447. }
  448. else if ( !set_flag(&flags, optarg) )
  449. fprintf(stderr,"%s: unknown option <%s>", pgm, optarg);
  450. break;
  451. case 'o': output = optarg;
  452. break;
  453. case 'V': printf("theme+discount %s\n", markdown_version);
  454. exit(0);
  455. default: fprintf(stderr, "usage: %s [-V] [-d dir] [-p pagename] [-t template] [-o html] [file]\n", pgm);
  456. exit(1);
  457. }
  458. }
  459. tmplfile = open_template(template);
  460. argc -= optind;
  461. argv += optind;
  462. if ( argc > 0 ) {
  463. int added_text=0;
  464. if ( (source = malloc(strlen(argv[0]) + strlen("/index.text") + 1)) == 0 )
  465. fail("out of memory allocating name buffer");
  466. strcpy(source,argv[0]);
  467. if ( (stat(source, &sourceinfo) == 0) && S_ISDIR(sourceinfo.st_mode) )
  468. strcat(source, "/index");
  469. if ( !freopen(source, "r", stdin) ) {
  470. strcat(source, ".text");
  471. added_text = 1;
  472. if ( !freopen(source, "r", stdin) )
  473. fail("can't open either %s or %s", argv[0], source);
  474. }
  475. if ( !output ) {
  476. char *p, *q;
  477. output = alloca(strlen(source) + strlen(".html") + 1);
  478. strcpy(output, source);
  479. if (( p = strchr(output, '/') ))
  480. q = strrchr(p+1, '.');
  481. else
  482. q = strrchr(output, '.');
  483. if ( q )
  484. *q = 0;
  485. else
  486. q = output + strlen(output);
  487. strcat(q, ".html");
  488. }
  489. }
  490. if ( output && strcmp(output, "-") ) {
  491. if ( force )
  492. unlink(output);
  493. if ( !freopen(output, "w", stdout) )
  494. fail("can't write to %s", output);
  495. }
  496. if ( !pagename )
  497. pagename = source;
  498. if ( (doc = mkd_in(stdin, 0)) == 0 )
  499. fail("can't read %s", source ? source : "stdin");
  500. if ( fstat(fileno(stdin), &sourceinfo) == 0 )
  501. infop = &sourceinfo;
  502. #if HAVE_GETPWUID
  503. me = getpwuid(infop ? infop->st_uid : getuid());
  504. if ( (root = strdup(me->pw_dir)) == 0 )
  505. fail("out of memory");
  506. #endif
  507. if ( !mkd_compile(doc, flags) )
  508. fail("couldn't compile input");
  509. if ( tmplfile )
  510. spin(tmplfile,doc,stdout);
  511. else
  512. mkd_generatehtml(doc, stdout);
  513. mkd_cleanup(doc);
  514. exit(0);
  515. }