mirror of
https://github.com/keanuplayz/dotfiles.git
synced 2024-08-15 02:33:12 +00:00
188 lines
4.5 KiB
Ruby
Executable file
188 lines
4.5 KiB
Ruby
Executable file
#!/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
|
||
|
||
# }}}
|