Last active
July 15, 2025 18:40
-
-
Save bitti/016ea088f1e9dfc7120eb5f8c937c2da 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/ruby | |
require 'redcarpet' | |
require 'pygments' | |
require 'erb' | |
input = ARGV[0] && ARGV[0] != "-" && File.open(ARGV[0]) || STDIN | |
output = ARGV[1] && File.open(ARGV[1], "w") || STDOUT | |
cache_dir = File.join(ENV["XDG_CACHE_HOME"] || "#{ENV['HOME']}/.cache", "gfm-render") | |
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">) | |
elsif text.start_with?("[ ]") | |
text[0..2] = %(<input type="checkbox" class="task-list-item-checkbox" disabled="">) | |
end | |
%(<li class="task-list-item">#{text}</li>) | |
end | |
def block_code( code, language ) | |
Pygments.highlight( code, lexer: language ) | |
end | |
end | |
markdown_renderer = Redcarpet::Markdown.new( | |
RenderWithTaskLists, | |
no_intra_emphasis: true, | |
autolink: true, | |
tables: true, | |
fenced_code_blocks: true, | |
lax_spacing: true, | |
# Other options to consider: | |
# | |
# with_toc_data: true, | |
# strikethrough: true, | |
# superscript: true, | |
# underline: true, | |
# highlight: true | |
) | |
html_output = markdown_renderer.render(input.read) | |
name = input.is_a?(File) && File.basename(input) || "<stdin>" | |
gh_markdown_css_filename = File.join(cache_dir, "github-markdown.css") | |
unless File.exist?(gh_markdown_css_filename) | |
require 'net/http' | |
require 'pathname' | |
CSS_URL = 'https://raw.githubusercontent.com/sindresorhus/github-markdown-css/main/github-markdown.css' | |
STDERR.puts "Download #{CSS_URL} and cache to #{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 | |
gh_markdown_css = File.read(gh_markdown_css_filename) | |
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 %> | |
<%= Pygments.css('.highlight', style: 'gruvbox-dark') %> | |
@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> |
Thanks @Pysis868, I'd like to integrate your changes in my version. But I also extended it in a different way by integrating source code rendering via pygments.rb (which is the old way GitHub used to render source code, before they replaced it with a proprietary way).
To make it work this command needs to be added to step 2 above:
$ gem i --user-install pygments.rb -v 3.0.0
(Using the old 3.0.0 version is necessary if you use the default Ruby 2.6 currently included in macOS. If you use a newer Ruby version, you probably can also use the newest pygments.rb gem.)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Pysis868/gfm-render - Revision #5
Moved CSS download check to the beginning so that can completed more quickly with a quick, initial script execution without providing any parameters/files.
Added related CLI option to help facilitate this with a simple program exit to not keep the user's terminal busy, possibly unexpectedly, and another to possibly re-download it later.
Added help text.