+#!/usr/bin/env ruby
+
require 'pathname'
-require 'fileutils'
require 'yaml'
-require 'liquid'
+require 'optparse'
-def split_frontmatter(txt)
- re = /\A---[ \t\r]*\n(?<frontmatter>.*?)^---[ \t\r]*\n(?<content>.*)\z/m
- match = re.match txt
- match ? [match['frontmatter'], match['content']] : nil
+begin require 'liquid'
+rescue LoadError
+ puts "Please install the 'liquid' Ruby gem (available in Debian/Ubuntu as 'ruby-liquid')"
+ exit 1
end
+CONFIG = {
+ pages_dir: '_manual',
+ layouts_dir: '_layouts',
+ static_dir: 'source',
+ output_dir: '_site'
+}
+
def child_url?(a, b)
a.start_with?(b) && b.count('/') + 1 == a.count('/')
end
class Site
- attr_reader :pages
+ attr_reader :pages, :layouts
def initialize()
@pages = []
- @config = {
- 'pages_dir' => '_manual',
- 'layouts_dir' => '_layouts',
- 'static_dir' => 'source',
- 'output_dir' => '_site'
- }
@layouts = {}
end
+ def build()
+ print "Building... "
+
+ read_layouts()
+ read_pages()
+ copy_static()
+ process_pages()
+
+ puts "done."
+ end
+
def read_layouts()
- layouts_dir = Pathname(@config['layouts_dir'])
Pathname.glob(layouts_dir + Pathname('*.html')) do |path|
next if !path.file?
layout = Layout.new(self, path)
end
end
- def find_layout(name)
- @layouts[name]
- end
-
def read_pages()
- pages_dir = Pathname.new(@config['pages_dir'])
pages_dir.find do |path|
if path.file? && path.extname == '.html'
page = Page.new(self, path)
end
end
- def find_children(url)
- @pages.select{ |p| child_url?(p.url, url) }.sort_by{ |p| p.path.basename }
- end
-
def process_pages()
@pages.each {|page| page.process}
end
def copy_static()
- # http://ruby-doc.org/stdlib-2.2.1/libdoc/fileutils/rdoc/index.html
+ unless system("rsync -a --delete --exclude='*~' #{static_dir}/. #{output_dir}")
+ puts "Couldn't copy static files, is rsync installed?"
+ end
end
- def pages_dir()
- Pathname(@config['pages_dir'])
+ def find_children(url)
+ sorted_pages.select { |p| child_url?(p.url, url) }
end
- def output_dir()
- Pathname(@config['output_dir'])
- end
+ def toplevel() @toplevel_memo ||= find_children('/') end
+ def sorted_pages() @sorted_pages_memo ||= @pages.sort_by{ |p| p.sort_url } end
- def run()
- #read_config()
- read_layouts()
- read_pages()
- copy_static()
- process_pages()
- end
+ def pages_dir() @pages_dir_memo ||= Pathname(CONFIG[:pages_dir]) end
+ def layouts_dir() @layouts_dir_memo ||= Pathname(CONFIG[:layouts_dir]) end
+ def static_dir() @static_dir_memo ||= Pathname(CONFIG[:static_dir]) end
+ def output_dir() @output_dir_memo ||= Pathname(CONFIG[:output_dir]) end
end
class Page
@site = site
@path = path
- canon = canonical
- @out_path = @site.output_dir + canon + Pathname("index.html")
- @url = '/' + canon + '/'
- @sort_url = @path.to_s.sub(/\.html$/, '')
- end
+ relative_path = @path.relative_path_from(@site.pages_dir);
+ a = relative_path.each_filename.map do |x|
+ x.sub(/^[0-9]*[-_]/, '')
+ end
+ a[-1].sub!(/\.html$/, '')
+ s = a.join('/')
- def canonical()
- remove_numbers = lambda {|x| x.sub(/^[0-9]*[-_]/, '') }
- path = @path.relative_path_from(@site.pages_dir)
- a = path.each_filename.map(&remove_numbers)
- a[-1] = a[-1].sub(/\.html$/, '')
- a.join('/')
+ @out_path = @site.output_dir + Pathname(s) + Pathname("index.html")
+ @url = "/#{s}/"
+ @sort_url = @path.to_s.sub(/\.html$/, '')
end
def related_to?(p)
end
def title()
- if !@page_context
- puts 'nil page context: ' + @path.to_s
- end
@page_context['title'] || ""
end
+ def menu_title()
+ @page_context['menu_title'] || title
+ end
+
def read()
content = @path.read
- split = split_frontmatter content
- split || abort("Not a Jekyll-formatted file: #{@path}")
- frontmatter, @content = split
+ frontmatter, @content = split_frontmatter(content) || abort("File not well-formatted: #{@path}")
@page_context = YAML.load(frontmatter)
@template = Liquid::Template.parse(@content)
end
+
+ def split_frontmatter(txt)
+ @split_regex ||= /\A---[ \t\r]*\n(?<frontmatter>.*?)^---[ \t\r]*\n(?<content>.*)\z/m
+ match = @split_regex.match txt
+ match ? [match['frontmatter'], match['content']] : nil
+ end
def find_layout()
- @site.find_layout(@page_context['layout'] || 'default')
+ @site.layouts[@page_context['layout'] || 'default']
end
def children()
- @site.find_children(@url)
+ @children ||= @site.find_children(@url)
end
def render()
%{
<dt#{css}>
- <a href='#{page.url}'>#{page.title}</a>
+ <a href='#{page.url}'>#{page.menu_title}</a>
</dt>
<dd#{css}>
#{children_html}
}
end
- join(site.find_children('/').map(&format_entry))
+ join(site.toplevel.map(&format_entry))
end
end
class Tag_prevnext < Liquid::Tag
def render(context)
- site = context.registers[:site]
current = context.registers[:page]
+ pages = context.registers[:site].sorted_pages
- pages = site.pages.sort_by{ |p| p.sort_url }
+ index = pages.index { |page| page == current }
+ return '' if !index
- ind = pages.index { |page| page == current }
- return '' if !ind
-
- lnk = lambda do |p, cls, txt|
+ link = lambda do |p, cls, txt|
"<li><a title='#{p.title}' href='#{p.url}' class='#{cls}'>#{txt}</a></li>"
end
- prev_link = ind > 0 ? lnk.call(pages[ind-1], "previous", " < Previous ") : ""
- next_link = ind < pages.length-1 ? lnk.call(pages[ind+1], "next", " Next > ") : ""
+ prev_link = index > 0 ? link.call(pages[index-1], "previous", " < Previous ") : ""
+ next_link = index < pages.length-1 ? link.call(pages[index+1], "next", " Next > ") : ""
"<ul class='pager'>#{prev_link}#{next_link}</ul>"
end
end
-Liquid::Template.register_tag('tree', Tag_tree)
-Liquid::Template.register_tag('children', Tag_children)
-Liquid::Template.register_tag('prevnext', Tag_prevnext)
+class Server
+ def start_watcher()
+ begin require 'listen'
+ rescue LoadError
+ puts "To use the --watch function, please install the 'listen' Ruby gem"
+ puts "(available in Debian/Ubuntu as 'ruby-listen')"
+ return nil
+ end
+
+ listener = Listen.to(CONFIG[:pages_dir], wait_for_delay: 1.0, only: /.html$/) do |modified, added, removed|
+ Site.new.build
+ end
+ listener.start
+ listener
+ end
+
+ def run(options)
+ require 'webrick'
+ listener = options[:watch] && start_watcher
+ port = options[:port] || 8000
+
+ puts "Serving at http://localhost:#{port}/ ..."
+ server = WEBrick::HTTPServer.new :Port => port, :DocumentRoot => CONFIG[:output_dir]
+ trap 'INT' do
+ server.shutdown
+ end
+ server.start
+ listener.stop if listener
+ end
+end
+
+def main
+ Liquid::Template.register_tag('tree', Tag_tree)
+ Liquid::Template.register_tag('children', Tag_children)
+ Liquid::Template.register_tag('prevnext', Tag_prevnext)
-if defined? Liquid::Template.error_mode
- Liquid::Template.error_mode = :strict
+ if defined? Liquid::Template.error_mode
+ Liquid::Template.error_mode = :strict
+ end
+
+ options = {}
+ OptionParser.new do |opts|
+ opts.banner = %{Usage: build.rb <command> [options]
+
+Use 'build.rb' to build the manual. Use 'build.rb serve' to also
+start a web server; setting any web server options implies "serve".
+}
+ opts.on("-w", "--watch", "Watch for changes") { options[:watch] = true }
+ opts.on("-p", "--port N", Integer, "Specify port for web server") { |p| options[:port] = p }
+ end.parse!
+
+ Site.new.build
+
+ if options[:watch] || options[:port] || (ARGV.length > 0 && "serve".start_with?(ARGV[0]))
+ Server.new.run(options)
+ end
end
-Site.new.run
+main