#!/usr/bin/env ruby require "commonmarker" require "rouge" ROUGE_THEME = Rouge::Themes::Pastie COMMONMARKER_PARSE_OPTIONS = [:DEFAULT, :FOOTNOTES] COMMONMARKER_RENDER_OPTIONS = [:UNSAFE] COMMONMARKER_EXTENSIONS = [:tagfilter, :autolink, :table, :strikethrough, :tasklist] # parse arguments {{{ def get_program_name() return File.basename($PROGRAM_NAME) end def print_help() $stderr.print <] [] if is not specified, input will be read from stdin if is not specified, output will be printed to stdout EOF exit(true) end def print_error(*args) $stderr.puts("#{get_program_name()}: error:", *args) exit(false) end input_file_path = nil output_file_path = nil reading_only_positional_args = false positional_arg_index = 0 ARGV.each do |arg| is_positional = reading_only_positional_args if not is_positional case arg when "-h", "--help" print_help() when "--" reading_only_positional_args = true else if arg.start_with?("-") print_error("unknown option: #{arg}") else is_positional = true end end end if is_positional case positional_arg_index when 0 then input_file_path = arg when 1 then output_file_path = arg else print_error("unexpected argument: #{arg}") end positional_arg_index += 1 end end # }}} # parse markdown document {{{ if input_file_path.nil?() markdown = $stdin.read() else markdown = IO.read(input_file_path) end doc = CommonMarker.render_doc(markdown, COMMONMARKER_PARSE_OPTIONS, COMMONMARKER_EXTENSIONS) # }}} # add header anchors {{{ header_slug_occurences = {} doc.walk do |node| if node.type == :header header_slug = "" node.walk do |child_node| if [:text, :html, :html_inline, :code, :code_block, :footnote_reference].include?(child_node.type) header_slug += child_node.string_content end end header_slug.downcase!() header_slug.strip!() # remove special characters header_slug.delete!("\u2000-\u206F\u2E00-\u2E7F\\\\'!\"#$%&()*+,./:;<=>?@[]\\^`{|}~’") # remove emoji header_slug.delete!("\u{1F600}-\u{1F6FF}") # remove whitespace header_slug.gsub!(/\s/, "-") # make this slug unique while header_slug_occurences.key?(header_slug) occurences = header_slug_occurences[header_slug] occurences += 1 header_slug_occurences[header_slug] = occurences header_slug += "-" + occurences.to_s() end header_slug_occurences[header_slug] = 0 anchor_node = CommonMarker::Node.new(:inline_html) anchor_node.string_content = "" node.prepend_child(anchor_node) end end # }}} # highlight code blocks {{{ rouge_formatter = Rouge::Formatters::HTML.new() doc.walk do |node| if node.type == :code_block language = node.fence_info if not language.empty?() source = node.string_content lexer = Rouge::Lexer.find_fancy(language) || Rouge::Lexers::PlainText.new() highlighted_code = rouge_formatter.format(lexer.lex(source)) new_node = CommonMarker::Node.new(:html) new_node.string_content = "
#{highlighted_code}
" node.insert_after(new_node) node.delete end end end # }}} # render HTML {{{ rendered_html = <
#{doc.to_html(COMMONMARKER_RENDER_OPTIONS)}
EOF if output_file_path.nil?() $stdout.write(rendered_html) else IO.write(output_file_path, rendered_html) end # }}}