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], "&amp;");
129 		    x += 5 /*strlen(&amp;)*/ ;
130 		    break;
131 	case '<':   strcpy(&src[x], "&lt;");
132 		    x += 4 /*strlen(&lt;)*/ ;
133 		    break;
134 	case '>':   strcpy(&src[x], "&gt;");
135 		    x += 4 /*strlen(&gt;)*/ ;
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