#include "document.h" #include "html.h" #include "common.h" #include /* FEATURES INFO / DEFAULTS */ enum renderer_type { RENDERER_HTML, RENDERER_HTML_TOC }; struct extension_category_info { unsigned int flags; const char *option_name; const char *label; }; struct extension_info { unsigned int flag; const char *option_name; const char *description; }; struct html_flag_info { unsigned int flag; const char *option_name; const char *description; }; static struct extension_category_info categories_info[] = { {HOEDOWN_EXT_BLOCK, "block", "Block extensions"}, {HOEDOWN_EXT_SPAN, "span", "Span extensions"}, {HOEDOWN_EXT_FLAGS, "flags", "Other flags"}, {HOEDOWN_EXT_NEGATIVE, "negative", "Negative flags"}, }; static struct extension_info extensions_info[] = { {HOEDOWN_EXT_TABLES, "tables", "Parse PHP-Markdown style tables."}, {HOEDOWN_EXT_FENCED_CODE, "fenced-code", "Parse fenced code blocks."}, {HOEDOWN_EXT_FOOTNOTES, "footnotes", "Parse footnotes."}, {HOEDOWN_EXT_AUTOLINK, "autolink", "Automatically turn safe URLs into links."}, {HOEDOWN_EXT_STRIKETHROUGH, "strikethrough", "Parse ~~stikethrough~~ spans."}, {HOEDOWN_EXT_UNDERLINE, "underline", "Parse _underline_ instead of emphasis."}, {HOEDOWN_EXT_HIGHLIGHT, "highlight", "Parse ==highlight== spans."}, {HOEDOWN_EXT_QUOTE, "quote", "Render \"quotes\" as quotes."}, {HOEDOWN_EXT_SUPERSCRIPT, "superscript", "Parse super^script."}, {HOEDOWN_EXT_MATH, "math", "Parse TeX $$math$$ syntax, Kramdown style."}, {HOEDOWN_EXT_NO_INTRA_EMPHASIS, "disable-intra-emphasis", "Disable emphasis_between_words."}, {HOEDOWN_EXT_SPACE_HEADERS, "space-headers", "Require a space after '#' in headers."}, {HOEDOWN_EXT_MATH_EXPLICIT, "math-explicit", "Instead of guessing by context, parse $inline math$ and $$always block math$$ (requires --math)."}, {HOEDOWN_EXT_DISABLE_INDENTED_CODE, "disable-indented-code", "Don't parse indented code blocks."}, }; static struct html_flag_info html_flags_info[] = { {HOEDOWN_HTML_SKIP_HTML, "skip-html", "Strip all HTML tags."}, {HOEDOWN_HTML_ESCAPE, "escape", "Escape all HTML."}, {HOEDOWN_HTML_HARD_WRAP, "hard-wrap", "Render each linebreak as
."}, {HOEDOWN_HTML_USE_XHTML, "xhtml", "Render XHTML."}, }; static const char *category_prefix = "all-"; static const char *negative_prefix = "no-"; #define DEF_IUNIT 1024 #define DEF_OUNIT 64 #define DEF_MAX_NESTING 16 /* PRINT HELP */ void print_help(const char *basename) { size_t i; size_t e; /* usage */ printf("Usage: %s [OPTION]... [FILE]\n\n", basename); /* description */ printf("Process the Markdown in FILE (or standard input) and render it to standard output, using the Hoedown library. " "Parsing and rendering can be customized through the options below. The default is to parse pure markdown and output HTML.\n\n"); /* main options */ printf("Main options:\n"); print_option('n', "max-nesting=N", "Maximum level of block nesting parsed. Default is " str(DEF_MAX_NESTING) "."); print_option('t', "toc-level=N", "Maximum level for headers included in the TOC. Zero disables TOC (the default)."); print_option( 0, "html", "Render (X)HTML. The default."); print_option( 0, "html-toc", "Render the Table of Contents in (X)HTML."); print_option('T', "time", "Show time spent in rendering."); print_option('i', "input-unit=N", "Reading block size. Default is " str(DEF_IUNIT) "."); print_option('o', "output-unit=N", "Writing block size. Default is " str(DEF_OUNIT) "."); print_option('h', "help", "Print this help text."); print_option('v', "version", "Print Hoedown version."); printf("\n"); /* extensions */ for (i = 0; i < count_of(categories_info); i++) { struct extension_category_info *category = categories_info+i; printf("%s (--%s%s):\n", category->label, category_prefix, category->option_name); for (e = 0; e < count_of(extensions_info); e++) { struct extension_info *extension = extensions_info+e; if (extension->flag & category->flags) { print_option( 0, extension->option_name, extension->description); } } printf("\n"); } /* html-specific */ printf("HTML-specific options:\n"); for (i = 0; i < count_of(html_flags_info); i++) { struct html_flag_info *html_flag = html_flags_info+i; print_option( 0, html_flag->option_name, html_flag->description); } printf("\n"); /* ending */ printf("Flags and extensions can be negated by prepending 'no' to them, as in '--no-tables', '--no-span' or '--no-escape'. " "Options are processed in order, so in case of contradictory options the last specified stands.\n\n"); printf("When FILE is '-', read standard input. If no FILE was given, read standard input. Use '--' to signal end of option parsing. " "Exit status is 0 if no errors occurred, 1 with option parsing errors, 4 with memory allocation errors or 5 with I/O errors.\n\n"); } /* OPTION PARSING */ struct option_data { char *basename; int done; /* time reporting */ int show_time; /* I/O */ size_t iunit; size_t ounit; const char *filename; /* renderer */ enum renderer_type renderer; int toc_level; hoedown_html_flags html_flags; /* parsing */ hoedown_extensions extensions; size_t max_nesting; }; int parse_short_option(char opt, char *next, void *opaque) { struct option_data *data = opaque; long int num; int isNum = next ? parseint(next, &num) : 0; if (opt == 'h') { print_help(data->basename); data->done = 1; return 0; } if (opt == 'v') { print_version(); data->done = 1; return 0; } if (opt == 'T') { data->show_time = 1; return 1; } /* options requiring value */ /* FIXME: add validation */ if (opt == 'n' && isNum) { data->max_nesting = num; return 2; } if (opt == 't' && isNum) { data->toc_level = num; return 2; } if (opt == 'i' && isNum) { data->iunit = num; return 2; } if (opt == 'o' && isNum) { data->ounit = num; return 2; } fprintf(stderr, "Wrong option '-%c' found.\n", opt); return 0; } int parse_category_option(char *opt, struct option_data *data) { size_t i; const char *name = strprefix(opt, category_prefix); if (!name) return 0; for (i = 0; i < count_of(categories_info); i++) { struct extension_category_info *category = &categories_info[i]; if (strcmp(name, category->option_name)==0) { data->extensions |= category->flags; return 1; } } return 0; } int parse_flag_option(char *opt, struct option_data *data) { size_t i; for (i = 0; i < count_of(extensions_info); i++) { struct extension_info *extension = &extensions_info[i]; if (strcmp(opt, extension->option_name)==0) { data->extensions |= extension->flag; return 1; } } for (i = 0; i < count_of(html_flags_info); i++) { struct html_flag_info *html_flag = &html_flags_info[i]; if (strcmp(opt, html_flag->option_name)==0) { data->html_flags |= html_flag->flag; return 1; } } return 0; } int parse_negative_option(char *opt, struct option_data *data) { size_t i; const char *name = strprefix(opt, negative_prefix); if (!name) return 0; for (i = 0; i < count_of(categories_info); i++) { struct extension_category_info *category = &categories_info[i]; if (strcmp(name, category->option_name)==0) { data->extensions &= ~(category->flags); return 1; } } for (i = 0; i < count_of(extensions_info); i++) { struct extension_info *extension = &extensions_info[i]; if (strcmp(name, extension->option_name)==0) { data->extensions &= ~(extension->flag); return 1; } } for (i = 0; i < count_of(html_flags_info); i++) { struct html_flag_info *html_flag = &html_flags_info[i]; if (strcmp(name, html_flag->option_name)==0) { data->html_flags &= ~(html_flag->flag); return 1; } } return 0; } int parse_long_option(char *opt, char *next, void *opaque) { struct option_data *data = opaque; long int num; int isNum = next ? parseint(next, &num) : 0; if (strcmp(opt, "help")==0) { print_help(data->basename); data->done = 1; return 0; } if (strcmp(opt, "version")==0) { print_version(); data->done = 1; return 0; } if (strcmp(opt, "time")==0) { data->show_time = 1; return 1; } /* FIXME: validation */ if (strcmp(opt, "max-nesting")==0 && isNum) { data->max_nesting = num; return 2; } if (strcmp(opt, "toc-level")==0 && isNum) { data->toc_level = num; return 2; } if (strcmp(opt, "input-unit")==0 && isNum) { data->iunit = num; return 2; } if (strcmp(opt, "output-unit")==0 && isNum) { data->ounit = num; return 2; } if (strcmp(opt, "html")==0) { data->renderer = RENDERER_HTML; return 1; } if (strcmp(opt, "html-toc")==0) { data->renderer = RENDERER_HTML_TOC; return 1; } if (parse_category_option(opt, data) || parse_flag_option(opt, data) || parse_negative_option(opt, data)) return 1; fprintf(stderr, "Wrong option '--%s' found.\n", opt); return 0; } int parse_argument(int argn, char *arg, int is_forced, void *opaque) { struct option_data *data = opaque; if (argn == 0) { /* Input file */ if (strcmp(arg, "-")!=0 || is_forced) data->filename = arg; return 1; } fprintf(stderr, "Too many arguments.\n"); return 0; } /* MAIN LOGIC */ int main(int argc, char **argv) { struct option_data data; clock_t t1, t2; FILE *file = stdin; hoedown_buffer *ib, *ob; hoedown_renderer *renderer = NULL; void (*renderer_free)(hoedown_renderer *) = NULL; hoedown_document *document; /* Parse options */ data.basename = argv[0]; data.done = 0; data.show_time = 0; data.iunit = DEF_IUNIT; data.ounit = DEF_OUNIT; data.filename = NULL; data.renderer = RENDERER_HTML; data.toc_level = 0; data.html_flags = 0; data.extensions = 0; data.max_nesting = DEF_MAX_NESTING; argc = parse_options(argc, argv, parse_short_option, parse_long_option, parse_argument, &data); if (data.done) return 0; if (!argc) return 1; /* Open input file, if needed */ if (data.filename) { file = fopen(data.filename, "r"); if (!file) { fprintf(stderr, "Unable to open input file \"%s\": %s\n", data.filename, strerror(errno)); return 5; } } /* Read everything */ ib = hoedown_buffer_new(data.iunit); if (hoedown_buffer_putf(ib, file)) { fprintf(stderr, "I/O errors found while reading input.\n"); return 5; } if (file != stdin) fclose(file); /* Create the renderer */ switch (data.renderer) { case RENDERER_HTML: renderer = hoedown_html_renderer_new(data.html_flags, data.toc_level); renderer_free = hoedown_html_renderer_free; break; case RENDERER_HTML_TOC: renderer = hoedown_html_toc_renderer_new(data.toc_level); renderer_free = hoedown_html_renderer_free; break; }; /* Perform Markdown rendering */ ob = hoedown_buffer_new(data.ounit); document = hoedown_document_new(renderer, data.extensions, data.max_nesting); t1 = clock(); hoedown_document_render(document, ob, ib->data, ib->size); t2 = clock(); /* Cleanup */ hoedown_buffer_free(ib); hoedown_document_free(document); renderer_free(renderer); /* Write the result to stdout */ (void)fwrite(ob->data, 1, ob->size, stdout); hoedown_buffer_free(ob); if (ferror(stdout)) { fprintf(stderr, "I/O errors found while writing output.\n"); return 5; } /* Show rendering time */ if (data.show_time) { double elapsed; if (t1 == -1 || t2 == -1) { fprintf(stderr, "Failed to get the time.\n"); return 1; } elapsed = (double)(t2 - t1) / CLOCKS_PER_SEC; if (elapsed < 1) fprintf(stderr, "Time spent on rendering: %7.2f ms.\n", elapsed*1e3); else fprintf(stderr, "Time spent on rendering: %6.3f s.\n", elapsed); } return 0; }