# = Trivium -- a minimalist blogging engine # # Copyright (C) 2008 Christian Neukirchen # Licensed under the terms of the MIT license. require 'time'; require 'cgi' $: << "vendor" require 'bluecloth'; require 'rubypants'; require 'htemplate' BlueCloth::EMPTY_ELEMENT_SUFFIX.replace(">") Dir.mkdir("html") rescue true def File.write(name, content) File.open(name, "wb") { |out| out << content } puts name end def dep(dst, *srcs) yield dst unless srcs.all? { |src| (File.mtime(src) < File.mtime(dst) rescue false) } end def parse(f) head, body = File.read(f).split("\n\n", 2) rescue (return nil) entry = {:body => body, :id => File.basename(f, ".entry"), :file => f} head.scan(/(\w+): *(.*)/) { entry[$1.downcase.to_sym] = $2 } entry[:date] = Time.parse(entry[:date]) if entry[:date] entry[:updated] = entry[:updated] ? Time.parse(entry[:updated]) : entry[:date] entry[:title] = entry[:date].strftime("%d%b%Y").downcase if entry[:date] entry end Entry = Hash.new { |h, k| h[k] = parse k } ENTRIES = Dir.glob("entries/*.entry").map { |x| Entry[x] }. sort_by { |f| f[:date] }.reverse Entry.values.each { |v| Entry[v[:id]] = v } class SpanBlueCloth < BlueCloth # Don't do block transforms. alias apply_block_transforms apply_span_transforms def to_html super.rstrip end end class InlineMath < String MATH_TEX = 'http://vuxu.org/~chris/mathtex/mathtex.cgi?' + CGI.escape('\textstyle{}\usepackage{color}' + '\color{white}\rule[-0.333em]{0.01pt}{1.2em}\color{black}') def to_html gsub(/\$(.*?)\$/) { html = CGI.escapeHTML($1) formula = CGI.escape($1).gsub('+', '%20') %{#{html}} } end end class Dots < String MATH_TEX = "http://vuxu.org/~chris/mathtex/mathtex.cgi?" def to_html gsub(/^\.(\w+)([^\n]*?)\n(.*?)^\.\1\.$/m) { name, args, body = $1, $2, $3 case name when "link" title, desc = body.split("|", 2) warn "forgot link: #{title.strip}" if args.strip.empty? %{} when "quote" if args.strip.empty? src = "" else src = %{— #{args.strip}} end text = (body + src).gsub(/^ +/) { " " * $&.size }. gsub(/^.*$/, '> \& ') %{
#{BlueCloth.new(text).to_html}
} when "math" body << "\\eqno{#{args.strip}}" unless args.strip.empty? %{
#{CGI.escapeHTML body}
} when "thumb" '
' + body.split(/\n{2,}/).map { |para| alt, thumb, img = para.strip.split("\n", 3) %{#{alt}} }.join("\n") + '
' else %{
#{ BlueCloth.new(Dots.new(body).to_html).to_html}
} end } end end def format(e) [InlineMath, Dots, BlueCloth, RubyPants].inject(e[:body]) { |a,e| e.new(a).to_html } end def template(template, data) HTemplate.new(File.read(template), template).expand(data) end def group(entries, &block) r = {}; entries.each { |e| (r[block[e]] ||= []) << e }; r end def inner_sort(group, &block) group.each { |key, entries| entries.sort_by(&block) } end def outer_sort(group, &block) group.sort_by(&block) end def chain(group, name) group.each_with_index { |(key, entries), i| entries.each { |e| e[:"next_by_#{name}"] = group[i+1][0] if group[i+1] e[:"prev_by_#{name}"] = group[i-1][0] if i > 0 } } end def deps(e) [e[:id], e[:next_by_date], e[:prev_by_date]].compact.map {|z| Entry[z][:file] } end single_by_date = group(ENTRIES) { |e| e[:id] } single_by_date = outer_sort(single_by_date) { |k,e| e.first[:date] } chain(single_by_date, "date") single_by_date.each { |date, entries| entry = entries.first dep "html/#{entry[:id]}.html", "template/single.ht", *deps(entry) do |dst| File.write(dst, template("template/single.ht", entry)) end } monthly = group(ENTRIES) { |e| e[:date].strftime("%Y-%m") } inner_sort(monthly) { |e| e[:date] } monthly = outer_sort(monthly) { |k,e| e.first[:date] } chain(monthly, "month") monthly.each { |month, entries| entry = entries.first dep "html/#{month}.html", "template/monthly.ht", *deps(entry) do |dst| File.write(dst, template("template/monthly.ht", :entries => entries, :month => month)) end } front = ENTRIES.first(10) d = front.map { |e| e[:file] } dep "html/index.html", "template/front.ht", *d do |dst| File.write(dst, template("template/front.ht", :entries => front, :next => monthly.last)) end feed = ENTRIES.first(20) d = feed.map { |e| e[:file] } dep "html/index.atom", "template/atom.ht", *d do |dst| File.write(dst, template("template/atom.ht", :entries => feed, :time => Time.now)) end dep "html/index.json", "template/json.ht", *d do |dst| File.write(dst, template("template/json.ht", :entries => feed, :time => Time.now)) end system "rsync -r data/ html"