1 /*
2 * markdown: convert a single markdown document into html
3 */
4 /*
5 * Copyright (C) 2007 David L Parsons.
6 * The redistribution terms are provided in the COPYRIGHT file that must
7 * be distributed with this source code.
8 */
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <limits.h>
12 #include <mkdio.h>
13 #include <errno.h>
14 #include <string.h>
15 #include <stdarg.h>
16 #include <ctype.h>
17
18 #include "config.h"
19 #include "amalloc.h"
20 #include "pgm_options.h"
21 #include "tags.h"
22 #include "gethopt.h"
23
24 #if HAVE_LIBGEN_H
25 #include <libgen.h>
26 #endif
27
28 #ifndef HAVE_BASENAME
29 #include <string.h>
30
31 char*
basename(char * p)32 basename(char *p)
33 {
34 char *ret = strrchr(p, '/');
35
36 return ret ? (1+ret) : p;
37 }
38 #endif
39
40
41 char *pgm = "markdown";
42
43 char *
e_flags(const char * text,const int size,void * context)44 e_flags(const char *text, const int size, void *context)
45 {
46 return (char*)context;
47 }
48
49
50 void
complain(char * fmt,...)51 complain(char *fmt, ...)
52 {
53 va_list ptr;
54
55 fprintf(stderr, "%s: ", pgm);
56 va_start(ptr, fmt);
57 vfprintf(stderr, fmt, ptr);
58 va_end(ptr);
59 fputc('\n', stderr);
60 fflush(stderr);
61 }
62
63
64 char *
anchor_format(char * input,void * ctx)65 anchor_format(char *input, void *ctx)
66 {
67 int i, j, size;
68 char* ret;
69
70 if ( !input )
71 return NULL;
72
73 size = strlen(input);
74
75 ret = malloc(1+size);
76
77 if ( !ret )
78 return NULL;
79
80
81 while ( size && isspace(input[size-1]) )
82 --size;
83
84 for ( j=i=0; i < size; i++ ) {
85 if (isalnum(input[i]) || strchr("-_+", input[i]) )
86 ret[j++] = input[i];
87 else if ( input[i] == ' ' )
88 ret[j++] = '-';
89 }
90 ret[j++] = 0;
91
92 return ret;
93 }
94
95 void
free_it(char * object,void * ctx)96 free_it(char *object, void *ctx)
97 {
98 if ( object )
99 free(object);
100 }
101
102 char *
external_codefmt(char * src,int len,char * lang)103 external_codefmt(char *src, int len, char *lang)
104 {
105 int extra = 0;
106 int i, x;
107 char *res;
108
109 if ( lang == 0 )
110 lang = "generic_code";
111
112 for ( i=0; i < len; i++) {
113 if ( src[i] == '&' )
114 extra += 5;
115 else if ( src[i] == '<' || src[i] == '>' )
116 extra += 4;
117 }
118
119 /* 80 characters for the format wrappers */
120 if ( (res = malloc(len+extra+80+strlen(lang))) ==0 )
121 /* out of memory? drat! */
122 return 0;
123
124 sprintf(res, "<pre><code class=\"%s\">\n", lang);
125 x = strlen(res);
126 for ( i=0; i < len; i++ ) {
127 switch (src[i]) {
128 case '&': strcpy(&src[x], "&");
129 x += 5 /*strlen(&)*/ ;
130 break;
131 case '<': strcpy(&src[x], "<");
132 x += 4 /*strlen(<)*/ ;
133 break;
134 case '>': strcpy(&src[x], ">");
135 x += 4 /*strlen(>)*/ ;
136 break;
137 default: res[x++] = src[i];
138 break;
139 }
140 }
141 strcpy(&res[x], "</code></pre>\n");
142 return res;
143 }
144
145
146 struct h_opt opts[] = {
147 { 0, "html5", '5', 0, "recognise html5 block elements" },
148 { 0, "base", 'b', "url-base", "URL prefix" },
149 { 0, "debug", 'd', 0, "debugging" },
150 { 0, "version",'V', 0, "show version info" },
151 { 0, 0, 'E', "flags", "url flags" },
152 { 0, 0, 'F', "bitmap", "set/show hex flags" },
153 { 0, 0, 'f', "{+-}flags", "set/show named flags" },
154 { 0, 0, 'G', 0, "github flavoured markdown" },
155 { 0, 0, 'n', 0, "don't write generated html" },
156 { 0, 0, 's', "text", "format `text`" },
157 { 0, "style", 'S', 0, "output <style> blocks" },
158 { 0, 0, 't', "text", "format `text` with mkd_line()" },
159 { 0, "toc", 'T', 0, "output a TOC" },
160 { 0, 0, 'C', "prefix", "prefix for markdown extra footnotes" },
161 { 0, 0, 'o', "file", "write output to file" },
162 { 0, "squash", 'x', 0, "squash toc labels to be more like github" },
163 { 0, "codefmt",'X', 0, "use an external code formatter" },
164 };
165 #define NROPTS (sizeof opts/sizeof opts[0])
166
167 int
main(int argc,char ** argv)168 main(int argc, char **argv)
169 {
170 int rc;
171 mkd_flag_t flags = 0;
172 int debug = 0;
173 int toc = 0;
174 int content = 1;
175 int version = 0;
176 int with_html5 = 0;
177 int styles = 0;
178 int use_mkd_line = 0;
179 int use_e_codefmt = 0;
180 int github_flavoured = 0;
181 int squash = 0;
182 char *extra_footnote_prefix = 0;
183 char *urlflags = 0;
184 char *text = 0;
185 char *ofile = 0;
186 char *urlbase = 0;
187 char *q;
188 MMIOT *doc;
189 struct h_context blob;
190 struct h_opt *opt;
191
192 hoptset(&blob, argc, argv);
193 hopterr(&blob, 1);
194
195 if ( q = getenv("MARKDOWN_FLAGS") )
196 flags = strtol(q, 0, 0);
197
198 pgm = basename(argv[0]);
199
200 while ( opt=gethopt(&blob, opts, NROPTS) ) {
201 if ( opt == HOPTERR ) {
202 hoptusage(pgm, opts, NROPTS, "[file]");
203 exit(1);
204 }
205 switch (opt->optchar) {
206 case '5': with_html5 = 1;
207 break;
208 case 'b': urlbase = hoptarg(&blob);
209 break;
210 case 'd': debug = 1;
211 break;
212 case 'V': version++;
213 break;
214 case 'E': urlflags = hoptarg(&blob);
215 break;
216 case 'F': if ( strcmp(hoptarg(&blob), "?") == 0 ) {
217 show_flags(0, 0);
218 exit(0);
219 }
220 else
221 flags = strtol(hoptarg(&blob), 0, 0);
222 break;
223 case 'f': if ( strcmp(hoptarg(&blob), "?") == 0 ) {
224 show_flags(1, version);
225 exit(0);
226 }
227 else if ( q=set_flag(&flags, hoptarg(&blob)) )
228 complain("unknown option <%s>", q);
229 break;
230 case 'G': github_flavoured = 1;
231 break;
232 case 'n': content = 0;
233 break;
234 case 's': text = hoptarg(&blob);
235 break;
236 case 'S': styles = 1;
237 break;
238 case 't': text = hoptarg(&blob);
239 use_mkd_line = 1;
240 break;
241 case 'T': flags |= MKD_TOC;
242 toc = 1;
243 break;
244 case 'C': extra_footnote_prefix = hoptarg(&blob);
245 break;
246 case 'o': if ( ofile ) {
247 complain("Too many -o options");
248 exit(1);
249 }
250 if ( !freopen(ofile = hoptarg(&blob), "w", stdout) ) {
251 perror(ofile);
252 exit(1);
253 }
254 break;
255 case 'x': squash = 1;
256 break;
257 case 'X': use_e_codefmt = 1;
258 set_flag(&flags, "fencedcode");
259 break;
260 }
261 }
262
263 if ( version ) {
264 printf("%s: discount %s%s", pgm, markdown_version,
265 with_html5 ? " +html5":"");
266 if ( version > 1 )
267 mkd_flags_are(stdout, flags, 0);
268 putchar('\n');
269 exit(0);
270 }
271
272 argc -= hoptind(&blob);
273 argv += hoptind(&blob);
274
275 if ( with_html5 )
276 mkd_with_html5_tags();
277
278 if ( use_mkd_line )
279 rc = mkd_generateline( text, strlen(text), stdout, flags);
280 else {
281 if ( text ) {
282 doc = github_flavoured ? gfm_string(text, strlen(text), flags)
283 : mkd_string(text, strlen(text), flags) ;
284
285 if ( !doc ) {
286 perror(text);
287 exit(1);
288 }
289 }
290 else {
291 if ( argc && !freopen(argv[0], "r", stdin) ) {
292 perror(argv[0]);
293 exit(1);
294 }
295
296 doc = github_flavoured ? gfm_in(stdin,flags) : mkd_in(stdin,flags);
297 if ( !doc ) {
298 perror(argc ? argv[0] : "stdin");
299 exit(1);
300 }
301 }
302 if ( urlbase )
303 mkd_basename(doc, urlbase);
304 if ( urlflags ) {
305 mkd_e_data(doc, urlflags);
306 mkd_e_flags(doc, e_flags);
307 }
308 if ( squash )
309 mkd_e_anchor(doc, (mkd_callback_t) anchor_format);
310 if ( use_e_codefmt )
311 mkd_e_code_format(doc, external_codefmt);
312
313 if ( use_e_codefmt || squash )
314 mkd_e_free(doc, free_it);
315
316 if ( extra_footnote_prefix )
317 mkd_ref_prefix(doc, extra_footnote_prefix);
318
319 if ( debug )
320 rc = mkd_dump(doc, stdout, 0, argc ? basename(argv[0]) : "stdin");
321 else {
322 rc = 1;
323 if ( mkd_compile(doc, flags) ) {
324 rc = 0;
325 if ( styles )
326 mkd_generatecss(doc, stdout);
327 if ( toc )
328 mkd_generatetoc(doc, stdout);
329 if ( content )
330 mkd_generatehtml(doc, stdout);
331 }
332 }
333 mkd_cleanup(doc);
334 }
335 mkd_deallocate_tags();
336 adump();
337 exit( (rc == 0) ? 0 : errno );
338 }
339