-
-
Save pi314/27f6a2cf9343fd92ffadafbd4093b5a8 to your computer and use it in GitHub Desktop.
Offline renderer for GitHub flavoured markdown
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
# Renders Markdown content offline by converting it to HTML using GitHub's own redcarpet renderer software, and their additional CSS. | |
# Does require a download of the additional GitHub CSS to a local cache. | |
# Replaces grip for preferred offline functionality: https://github.com/joeyespo/grip/issues/35 | |
# Dependencies: redcarpet | |
require 'optparse' | |
options = { | |
cache: false, | |
replace: false | |
} | |
OptionParser.new do |opts| | |
opts.banner = 'Usage: [cat <file.md> |] gfm-render [options] <file.md>' | |
opts.on('-c', '--cache', 'Only checks to locally cache the additional GitHub CSS file, if it does not exist already.') do | |
options[:cache] = true | |
end | |
opts.on('-f', '--force', 'Sames as -r|--replace.') do | |
options[:replace] = true | |
end | |
opts.on('-h', '--help', "Displays this usage text.") do | |
puts opts | |
exit | |
end | |
opts.on('-r', '--replace', 'Forces a re-download of the additional GitHub CSS file.') do | |
options[:replace] = true | |
end | |
end.parse!(ARGV) | |
gh_markdown_css_filename = File.join(ENV["XDG_CACHE_HOME"] || "#{ENV['HOME']}/.cache", "gfm-render/github-markdown.css") | |
unless File.exist?(gh_markdown_css_filename) && !options[:replace] | |
require 'net/http' | |
require 'pathname' | |
CSS_URL = 'https://raw.githubusercontent.com/sindresorhus/github-markdown-css/main/github-markdown.css' | |
STDERR.puts "Downloading \"#{CSS_URL}\" to the cache at \"#{gh_markdown_css_filename}\"." | |
Pathname.new(gh_markdown_css_filename).dirname.mkpath | |
File.open(gh_markdown_css_filename, "w") do |css| | |
css.write(Net::HTTP.get(URI(CSS_URL))) | |
end | |
end | |
if options[:cache] | |
return | |
end | |
gh_markdown_css = File.read(gh_markdown_css_filename) | |
if !ARGV[0] || ARGV[0] == "-" | |
input = STDIN | |
else | |
input = File.open(ARGV[0]) | |
end | |
if !ARGV[1] | |
if input == "<STDIN>" | |
output = STDOUT | |
else | |
output = File.open(File.basename(ARGV[0], File.extname(ARGV[0])) + ".html", "w") | |
end | |
elsif ARGV[1] == "-" | |
output = STDOUT | |
else | |
output = File.open(ARGV[1], "w") | |
end | |
File.join(ENV["XDG_CACHE_HOME"] || "#{ENV['HOME']}/.cache", "gfm-render") | |
require 'redcarpet' | |
require 'erb' | |
class RenderWithTaskLists < Redcarpet::Render::HTML | |
def list_item(text, list_type) | |
if text.start_with?("[x]", "[X]") | |
text[0..2] = %(<input type="checkbox" class="task-list-item-checkbox" disabled="" checked="checked">) | |
%(<li class="task-list-item">#{text}</li>) | |
elsif text.start_with?("[ ]") | |
text[0..2] = %(<input type="checkbox" class="task-list-item-checkbox" disabled="">) | |
%(<li class="task-list-item">#{text}</li>) | |
else | |
%(<li>#{text}</li>) | |
end | |
end | |
end | |
markdown_renderer = Redcarpet::Markdown.new( | |
RenderWithTaskLists.new(with_toc_data: true), | |
no_intra_emphasis: true, | |
autolink: true, | |
tables: true, | |
fenced_code_blocks: true, | |
lax_spacing: true, | |
) | |
html_output = markdown_renderer.render(input.read) | |
name = input.is_a?(File) && File.basename(input) || "<stdin>" | |
erb = ERB.new(DATA.read) | |
output.write(erb.result) | |
__END__ | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<title>Rendered <%= name %></title> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<style> | |
<%= gh_markdown_css %> | |
@media (prefers-color-scheme: dark) { | |
body { | |
color: #c9d1d9; | |
background-color: #0d1117; | |
} | |
} | |
@media (prefers-color-scheme: light) { | |
body { | |
color: #24292f; | |
background-color: #ffffff; | |
} | |
} | |
.markdown-body { | |
box-sizing: border-box; | |
min-width: 200px; | |
max-width: 980px; | |
margin: 1 auto; | |
padding: 45px; | |
border: 1px solid #d0d7de; | |
border-radius: 6px; | |
} | |
@media (max-width: 767px) { | |
.markdown-body { | |
padding: 15px; | |
} | |
} | |
</style> | |
</head> | |
<body> | |
<article class="markdown-body"> | |
<%= html_output %> | |
</article> | |
</body> | |
</html> |
- Replace tabs in the HTML template with 8 characters for indent
- Try to inference output file name from input file name (e.g. if only one argument
README.md
is provided, output file is set toREADME.html
)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Revision #7
#!/usr/bin/env ruby
to useruby
installed by user.class="task-list-item"
so theirlist-style-type: disc;
would not be hidden