dotfiles/scripts/markdown2htmldoc

189 lines
4.5 KiB
Ruby
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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 <<EOF
usage: #{get_program_name()} [<input_file>] [<output_file>]
if <input_file> is not specified, input will be read from stdin
if <output_file> 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 = "<a class=\"anchor\" name=\"#{header_slug}\" href=\"\##{header_slug}\"><span class=\"octicon octicon-link\"></span></a>"
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 = "<pre><code class=\"highlight highlight-source-#{new_node.html_escape_html(language)}\">#{highlighted_code}</code></pre>"
node.insert_after(new_node)
node.delete
end
end
end
# }}}
# render HTML {{{
rendered_html = <<EOF
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/3.0.1/github-markdown.min.css" integrity="sha256-HbgiGHMLxHZ3kkAiixyvnaaZFNjNWLYKD/QG6PWaQPc=" crossorigin="anonymous" />
<style>
html, body {
padding: 0;
margin: 0;
}
.markdown-body {
max-width: 882px;
margin: 0 auto;
padding: 32px;
}
.octicon-link {
font: normal normal 16px 'octicons-link';
line-height: 1;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.octicon-link::before {
content: '\\f05c';
}
#{ROUGE_THEME.render()}
</style>
</head>
<body>
<article class="markdown-body">
#{doc.to_html(COMMONMARKER_RENDER_OPTIONS)}
</article>
</body>
</html>
EOF
if output_file_path.nil?()
$stdout.write(rendered_html)
else
IO.write(output_file_path, rendered_html)
end
# }}}