Text::Markdown::Discount
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

628 lines
12 KiB

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