;;; MMD by David A. Moon is licensed under a ;;; Creative Commons Attribution-ShareAlike 4.0 International License ;;; http://creativecommons.org/licenses/by-sa/4.0/ ;;; ;;; Please inform me if you find this useful, or any of the ideas embedded in it. ;;; Comments and criticisms to dave underscore moon atsign alum dot mit dot edu. ;;; Incorporates details_shim by Tyler Uebele licensed as follows: ;;; ;;; Copyright (c) 2013 Tyler Uebele ;;; ;;; Permission is hereby granted, free of charge, to any person obtaining a copy ;;; of this software and associated documentation files (the "Software"), to ;;; deal in the Software without restriction, including without limitation the ;;; rights to use, copy, modify, merge, publish, distribute, sublicense, and/or ;;; sell copies of the Software, and to permit persons to whom the Software is ;;; furnished to do so, subject to the following conditions: ;;; ;;; The above copyright notice and this permission notice shall be included in ;;; all copies or substantial portions of the Software. ;;; ;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ;;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ;;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE ;;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ;;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, ;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN ;;; THE SOFTWARE. ;;; Convert MMD to HTML module: defmodule MMD import: command.main, command.error_output import: file import: time.localtime def default_code_style = "display:table; border:1px solid black; background-color:whitesmoke; padding-left:5px; padding-right:5px; padding-top:5px; padding-bottom:5px" ;; Global state ;; g is bound to it in most functions defclass MMD(input_stream in_stream[character], output_directory string) input_stream = input_stream output_stream := false out_stream[character] | false output_directory = output_directory line_number := 0 integer title_prefix := false string | false page_number := 0 integer current_chapter_name := false string | false page_titles = stack() ;[[chapter, [section, subsection...]...]...] all_references = stack[list]() ;[[see_arg, href]...] all_anchors = set![string]() ;{"chapter#anchor"...} pre_html = stack[string]() post_html = stack[string]() this_page_file := false string | false previous_page_file := false string | false current_mode := #normal ; normal, TABLE, UL, or OL nesting_level := 0 integer code_style := default_code_style string def dot_handlers = map![string, list[function]]() defmacro defdot name "(" [ line_name [ directive_name ] ] ")" [ "before:" before_expression ] [ "after:" after_expression ] body => def before_name = name("before-dot-$name") def main_name = name("handle-dot-$name") def after_name = name("after-dot-$name") def g = name("g", previous_context) `do def $main_name($g MMD, $(line_name or `line`) string, $(directive_name or `directive`) string) $body def $before_name($g MMD) $(before_expression or `false`) def $after_name($g MMD) $(after_expression or `false`) dot_handlers[$(string(name))] := [$before_name, $main_name, $after_name]` ;;; Main Program def main(input_pathname string, optional: output_directory string | false) def g = MMD(file.open(input_pathname, character), output_directory or file.join(file.directory(input_pathname), "HTML")) file.make_directory(g.output_directory) block process_lines(g) finally: file.close(g.input_stream) if g.output_stream then file.close(g.output_stream) def main(ignore...) error("Usage: mmd input_pathname [output_directory]")) ;; Process lines until end of input file def process_lines(g MMD) for line = next_line(g) while line def first = positionf(_ ~= ' ', line) if not first ;; Blank line separates paragraphs if g.output_stream change_mode(g, #normal) output_raw(g, "

") else case line[first] '.' => directive_line(g, trim(' ', line)) '*' => list_line(g, trim(' ', line), #UL) '#' => list_line(g, trim(' ', line), #OL) '|' => table_line(g, trim(' ', line)) default: change_mode(g, #normal) output_with_flags_and_translations(g, line) ;; Input file exhausted end_page(g, false) ;; Check for broken links for [see_arg, href] in g.all_references if not (href in g.all_anchors) error_output("Broken link: .see $see_arg\n") ;; Generate index.html file def index_path = file.join(g.output_directory, "index.html") g.output_stream := file.open(index_path, character, #output) output_index(g) index_path ;;; Line Handlers def undefined_handler(g MMD, line, directive_name) error("Unrecognized directive .$directive_name on line $(g.line_number)") def undefined_handlers = [undefined_handler, undefined_handler, undefined_handler] def directive_line(g MMD, line) change_mode(g, #normal) def space = position(' ', line) def directive_name = line[1 ..< (space or line.length)] ; 1 skips initial period def dot_name = string(downcase(directive_name)) def [before, main, after] = dot_handlers[dot_name, default: undefined_handlers] before(g, line, directive_name) if space main(g, line[space+1.. 0 while g.nesting_level > level output_raw(g, "") g.nesting_level := g.nesting_level - 1 while g.nesting_level < level def type = "1ai"[g.nesting_level, default: '1'] output_raw(g, if mode = #UL then "