A static site generator for GemText and HTML written in Ruby
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

276 lines
6.9 KiB

#!/bin/ruby
require 'erb'
require 'date'
require 'fileutils'
require 'yaml'
# Helper to convert Gregorian Dates to Asgardian
class Time
def to_asgardian
year = self.year - 2016
month = self.yday / 28
mday = self.yday % 28
# Adjust for last day of a month
if mday == 0
month -= 1
mday = 28
end
if self.yday == 365
# Extra Day
month = 13
mday = 29
elsif self.yday == 366
# Extra Day Duplicate (Leap Year)
month = 13
mday = 30
end
month_name = %w(Aquarius Pisces Aries Taurus Gemini Cancer Leo Virgo Libra Scorpio Ophiuchus Sagittarius Capricornus)[month]
"#{mday} #{month_name} #{year.positive? ? year.to_s.rjust(4, '0') : year.abs.to_s.rjust(4, '0') + ' BA'}"
end
end
# Gemtext generation
def generate_gemini(path, posts)
target_path = "../public_gemini/#{path}"
target_folder = target_path.split('/')[0...-1].join('/')
target = ""
# Parse the file for metadata and content
parsed_metadata = nil
content = ""
File.open path do |file|
text = file.read
pieces = text.split("---\n")
if pieces.size >= 3
metadata = YAML.load(pieces[1])
content = pieces.drop(2).join("---\n")
else
content = text
end
end
# Gemini runs last so find metadata instead of setting it
metadata = posts.select { |p| p['url'].start_with? path.gsub('.erb', '').gsub('.gmi', '') }.first
metadata ||= parsed_metadata
metadata['gemini_url'] = path.gsub '.erb', '' if metadata
# Optional header
folder = path.split('/')[0...-1]
header = (folder + %w[header.gmi]).join '/'
if File.exists? header
File.open header do |file|
target << file.read
end
end
header = (folder + %w[header.gmi.erb]).join '/'
if File.exists? header
template = File.read header
renderer = ERB.new template, nil, '<>'
target << renderer.result_with_hash(metadata)
end
# Place the content in the post
if path.end_with? '.erb'
target_path.gsub! '.erb', ''
renderer = ERB.new content, nil, '<>'
metadata ||= {}
metadata['posts'] = posts.map { |p| p['url'] = p['gemini_url']; p }
target << renderer.result_with_hash(metadata)
else
target << content
end
# Optional footer
footer = (folder + %w[footer.gmi]).join '/'
if File.exists? footer
File.open footer do |file|
target << file.read
end
end
footer = (folder + %w[footer.gmi.erb]).join '/'
if File.exists? footer
template = File.read footer
renderer = ERB.new template, nil, '<>'
target << renderer.result_with_hash(metadata)
end
# Ensure the folder exists
FileUtils.mkdir_p target_folder
# Write the file
File.open target_path, 'w' do |file|
file.write target
end
end
def gemini_to_html(gemtext)
in_pre = false
in_list = false
in_quote = false
unlist = ->(line) {
if in_list && !line.start_with?('* ')
in_list = false
"</ul>\n"
else
""
end
}
unquote = ->(line) {
if in_quote && !line.start_with?('>')
in_quote = false
"</blockquote>\n"
else
""
end
}
gemtext.split("\n").map do |line|
if in_pre
if line.strip == '```'
in_pre = false
"</pre>"
else
line
end
else
case line
when /^# (.*)/
"#{unquote.call line}#{unlist.call line}<h1>#{$1}</h1>"
when /^## (.*)/
"#{unquote.call line}#{unlist.call line}<h2>#{$1}</h2>"
when /^### (.*)/
"#{unquote.call line}#{unlist.call line}<h3>#{$1}</h3>"
when /^```/
in_pre = true
"#{unquote.call line}#{unlist.call line}<pre>"
when /^\* (.*)/
if !in_list
in_list = true
"#{unquote.call line}<ul>\n<li>#{$1}</li>"
else
"<li>#{$1}</li>"
end
when /^>\s?(.*)/
if !in_quote
in_quote = true
"#{unlist.call line}<blockquote>\n#{$1}"
else
$1
end
when /^=>\s*([^\s]+)\s?(.*)/
"#{unquote.call line}#{unlist.call line}<p><a href='#{$1}'>#{$2 || $1}</a></p>"
else
if line.strip == ''
"#{unquote.call line}#{unlist.call line}"
else
"#{unquote.call line}#{unlist.call line}<p>#{line}</p>"
end
end
end
end.join "\n"
end
# HTML Generation
def generate_html(path, posts)
target_path = "../public_html/#{path}".gsub '.gmi', '.html'
target_folder = target_path.split('/')[0...-1].join('/')
target = ""
# Parse the file for metadata and content
metadata = nil
content = ""
File.open path do |file|
text = file.read
pieces = text.split("---\n")
if pieces.size >= 3
metadata = YAML.load(pieces[1])
content = pieces.drop(2).join("---\n")
else
content = text
end
end
if metadata
metadata['updated'] = File.mtime path
metadata['url'] = path.gsub('.erb', '').gsub '.gmi', '.html'
posts << metadata
end
# Optional header
folder = path.split('/')[0...-1]
header = (folder + %w[header.html]).join '/'
if File.exists? header
File.open header do |file|
target << file.read
end
end
header = (folder + %w[header.html.erb]).join '/'
if File.exists? header
template = File.read header
renderer = ERB.new template, nil, '<>'
target << renderer.result_with_hash(metadata)
end
# Place the content in the post
if path.end_with? '.erb'
target_path.gsub! '.erb', ''
renderer = ERB.new content, nil, '<>'
metadata ||= {}
metadata['posts'] = posts
gemini = renderer.result_with_hash(metadata)
target << gemini_to_html(gemini)
else
target << gemini_to_html(content)
end
# Optional footer
footer = (folder + %w[footer.html]).join '/'
if File.exists? footer
File.open footer do |file|
target << file.read
end
end
footer = (folder + %w[footer.html.erb]).join '/'
if File.exists? footer
template = File.read footer
renderer = ERB.new template, nil, '<>'
target << renderer.result_with_hash(metadata)
end
# Ensure the folder exists
FileUtils.mkdir_p target_folder
# Write the file
File.open target_path, 'w' do |file|
file.write target
end
end
# Store all posts for the feeds
posts = []
# Find all Gemini content
Dir['**/*.gmi.erb', '**/*.gmi', 'index.gmi.erb'].each do |path|
next if File.basename(path) == 'header.gmi'
next if File.basename(path) == 'header.gmi.erb'
next if File.basename(path) == 'footer.gmi'
next if File.basename(path) == 'footer.gmi.erb'
puts "Processing #{path}..."
generate_html path, posts
generate_gemini path, posts
end
# Process Atom feed
if File.exists? 'atom.xml.erb'
template = File.read 'atom.xml.erb'
renderer = ERB.new template, nil, '<>'
File.open '../public_gemini/atom.xml', 'w' do |file|
file.write renderer.result_with_hash(posts: posts, scheme: 'gemini')
end
File.open '../public_html/atom.xml', 'w' do |file|
file.write renderer.result_with_hash(posts: posts, scheme: 'https')
end
end