theme.c 14 KB

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