erb static site builder
source link: https://willschenk.com/labnotes/2023/erb_static_site_builder/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Sometimes you need something simple. I wanted to build a quick script
that will let me take a directory of html
and md
files, and statically
render them with an optional layout. Lets walk through how to put
that together with ruby.
Simple erb render
simple.erb
:
<ul>
<% 5.times do |i| %>
<li><%= i %> item</li>
<% end %>
</ul>
And then the test script
simple.rb
:
require 'erb'
template = ERB.new File.read( "simple.erb" ), trim_mode: "<>"
puts template.run(binding)
This gives us:
<ul>
<li>0 item</li>
<li>1 item</li>
<li>2 item</li>
<li>3 item</li>
<li>4 item</li>
</ul>
Nested erb render
We're going to use tilt
here to give us the nifty yield
functionality,
and it also makes it easy for us to include other templating engines.
layout.erb
:
<html>
<head>
<title><%= title || "Title" %></title>
</head>
<body>
<%= yield %>
</body>
</html>
nested.rb
:
require 'erb'
require 'tilt'
require 'tilt/erb'
outer = Tilt::ERBTemplate.new('layout.erb')
html = outer.render( binding, title: nil ) do
Tilt::ERBTemplate.new( "simple.erb" ).render
end
puts html
Gives us:
<html>
<head>
<title>Title</title>
<body>
<ul>
<li>0 item</li>
<li>1 item</li>
<li>2 item</li>
<li>3 item</li>
<li>4 item</li>
</ul>
</body>
</html>
So that works great.
Front Matter
Now lets look at front matter, in both an html
file as well as a
markdown
file.
front_matter.html
:
<!--
---
title: this is an html file
---
-->
<h1>Hello World</h1>
<p>Some really great text, its so good and I love it.</p>
front_matter.md
:
---
title: This is a markdown file
---
# Hello world
This text is also great.
Now lets parse it up and spit it out:
front_matter.rb
:
require 'bundler/inline'
gemfile do
source 'https://rubygems.org'
gem 'front_matter_parser', "1.0.1"
gem 'tilt'
gem 'kramdown'
end
require 'tilt/erb'
require 'tilt/kramdown'
def process( file )
puts "\nProcessing #{file}"
parsed = FrontMatterParser::Parser.parse_file(file)
title = parsed.front_matter["title"] ||= "Default Title"
outer = Tilt::ERBTemplate.new('layout.erb', default_encoding: 'UTF-8' )
res = outer.render( binding, title: title ) do
results = ERB.new( parsed.content ).result( binding )
if file =~ /.md$/
Kramdown::Document.new(results).to_html
else
results
end
end
puts res
end
process "front_matter.html"
process "front_matter.md"
And we get:
Processing front_matter.html
<html>
<head>
<title>this is an html file</title>
</head>
<body>
<h1>Hello World</h1>
<p>Some really great text, its so good and I love it.</p>
</body>
</html>
Processing front_matter.md
<html>
<head>
<title>This is a markdown file</title>
</head>
<body>
<h1 id="hello-world">Hello world</h1>
<p>This text is also great.</p>
</body>
</html>
Putting it all together
OK, so lets put this together. We'll have all of our files in input/
and it will spit everything out into output/
. If we find a file
called input/_layout.html
we will use that as our layout, otherwise
we'll hardcode something in the script that will use watercss. We
will add access to the site
so we can loop over pages.
simple_gen.rb
:
#!/usr/bin/env ruby
require 'bundler/inline'
gemfile do
source 'https://rubygems.org'
gem 'front_matter_parser', "1.0.1"
gem 'tilt'
gem 'kramdown'
end
require 'tilt/erb'
require 'tilt/kramdown'
DEFAULT_LAYOUT = <<-HTML
<html>
<head>
<title><%= title %></title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.css"></link>
</head>
<body>
<nav>
<ul>
<% site.pages.each do |page| %>
<li><a href="<%= page.link %>"><%= page.title %></a></li>
<% end %>
</ul>
</nav>
<%= yield %>
</body>
</html>
HTML
class InputFile
attr_accessor :file, :parsed, :front_matter, :content
def initialize( file, prefix )
@file = file[prefix.length+1..]
@parsed = FrontMatterParser::Parser.parse_file(file)
@front_matter = @parsed.front_matter
@content = @parsed.content
end
def title
return @file if front_matter.nil?
front_matter["title"] ||= "Title"
end
def link
if File.extname(file) == ".md"
"/" + file.gsub( /.md/, ".html" )
else
"/" + file
end
end
end
class Site
attr_accessor :dir
def initialize( dir = 'input' )
@dir = dir
@files = {}
Dir.glob( "#{dir}/**/*" ).each do |file|
add_file file
end
end
def add_file file
if File.file? file
f = InputFile.new( file, @dir )
printf "%-15s %s\n", f.file, f.title
if f.file == "_layout.html"
@layout = f
else
@files[f.file] = f
end
else
puts "Skipping #{file}"
end
end
def layout
Tilt::ERBTemplate.new do
@layout.nil? ? DEFAULT_LAYOUT : @layout.content
end
end
def pages
@files.values
end
def generate
FileUtils.mkdir_p "output"
outer = layout
@files.each do |key,file|
res = outer.render( binding, title: file.title, site: self ) do
results = ERB.new( file.content ).result_with_hash( page: file, title: file.title, site: self )
if key =~ /.md$/
Kramdown::Document.new(results).to_html
else
results
end
end
output_file = "output#{file.link}"
FileUtils.mkdir_p File.dirname( output_file )
puts "Writing #{output_file}"
File.open( output_file, "w" ) { |out| out << res }
end
end
end
s = Site.new
s.generate
Here's some sample output:
_layout.html Title
index.md Title
Skipping input/sub
sub/1.md First
sub/2.md Second
Writing output/index.html
Writing output/sub/1.html
Writing output/sub/2.html
And we could add a custom layout:
input/_layout.html
:
<!doctype html>
<html>
<head>
<title><%= title || "Title" %></title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.tailwindcss.com?plugins=forms,typography,aspect-ratio,line-clamp"></script>
</head>
<body>
<nav>
<ul class="flex justify-between">
<% site.pages.each do |page| %>
<li class="inline-block mx-2"><a href="<%= page.link %>"><%= page.title %></a></li>
<% end %>
</ul>
</nav>
<div class="prose">
<%= yield %>
</div>
</body>
</html>
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK