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.

593 lines
11 KiB

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