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.

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