]> Shamusworld >> Repos - ardour-manual-diverged/blob - build.rb
Changing Editor's titles
[ardour-manual-diverged] / build.rb
1 #!/usr/bin/env ruby
2
3 require 'pathname'
4 require 'yaml'
5 require 'optparse'
6
7 begin require 'liquid'
8 rescue LoadError
9     puts "Please install the 'liquid' Ruby gem (available in Debian/Ubuntu as 'ruby-liquid')"
10     exit 1
11 end
12
13 CONFIG = {
14     pages_dir: '_manual',
15     layouts_dir: '_layouts',
16     static_dir: 'source',
17     output_dir: '_site'
18 }
19
20 def child_url?(a, b)
21     a.start_with?(b) && b.count('/') + 1 == a.count('/')
22 end
23
24 class Site
25     attr_reader :pages, :layouts
26
27     def initialize()
28         @pages = []
29         @layouts = {}
30     end
31     
32     def build()
33         print "Building... "
34
35         read_layouts()
36         read_pages()
37         copy_static()
38         process_pages()
39
40         puts "done."
41     end
42
43     def read_layouts()
44         Pathname.glob(layouts_dir + Pathname('*.html')) do |path|
45             next if !path.file?
46             layout = Layout.new(self, path)
47             layout.read
48             @layouts[path.basename('.html').to_s] = layout
49         end
50     end
51     
52     def read_pages()
53         pages_dir.find do |path|
54             if path.file? && path.extname == '.html'
55                 page = Page.new(self, path)
56                 page.read
57                 @pages << page
58             end
59         end
60     end
61
62     def process_pages()
63         @pages.each {|page| page.process}
64     end
65     
66     def copy_static()
67         unless system("rsync -a --delete --exclude='*~' #{static_dir}/. #{output_dir}")
68             puts "Couldn't copy static files, is rsync installed?"
69         end
70     end
71     
72     def find_children(url)
73         sorted_pages.select { |p| child_url?(p.url, url) }
74     end
75     
76     def toplevel() @toplevel_memo ||= find_children('/') end
77     def sorted_pages() @sorted_pages_memo ||= @pages.sort_by{ |p| p.sort_url } end
78
79     def pages_dir() @pages_dir_memo ||= Pathname(CONFIG[:pages_dir]) end
80     def layouts_dir() @layouts_dir_memo ||= Pathname(CONFIG[:layouts_dir]) end
81     def static_dir() @static_dir_memo ||= Pathname(CONFIG[:static_dir]) end
82     def output_dir() @output_dir_memo ||= Pathname(CONFIG[:output_dir]) end
83 end
84
85 class Page
86     attr_reader :path, :out_path, :url, :sort_url
87
88     def initialize(site, path)
89         @site = site
90         @path = path
91
92         relative_path = @path.relative_path_from(@site.pages_dir);
93         a = relative_path.each_filename.map do |x|
94             x.sub(/^[0-9]*[-_]/, '')
95         end
96         a[-1].sub!(/\.html$/, '')
97         s = a.join('/')
98
99         @out_path = @site.output_dir + Pathname(s) + Pathname("index.html")
100         @url = "/#{s}/"
101         @sort_url = @path.to_s.sub(/\.html$/, '')
102     end
103
104     def related_to?(p)
105         # should we show p in the index on selfs page?
106         url.start_with?(p.url) || child_url?(url, p.url)
107     end
108
109     def title()
110         @page_context['title'] || ""
111     end
112
113     def menu_title()
114         @page_context['menu_title'] || title
115     end
116
117     def read()
118         content = @path.read
119         frontmatter, @content = split_frontmatter(content) || abort("File not well-formatted: #{@path}") 
120         @page_context = YAML.load(frontmatter)
121         @template = Liquid::Template.parse(@content)
122     end        
123
124     def split_frontmatter(txt)
125         @split_regex ||= /\A---[ \t\r]*\n(?<frontmatter>.*?)^---[ \t\r]*\n(?<content>.*)\z/m
126         match = @split_regex.match txt 
127         match ? [match['frontmatter'], match['content']] : nil
128     end
129     
130     def find_layout()
131         @site.layouts[@page_context['layout'] || 'default']
132     end
133
134     def children()
135         @children ||= @site.find_children(@url)
136     end
137     
138     def render()
139         registers = {page: self, site: @site}
140         context = {'page' => @page_context}
141         content = @template.render!(context, registers: registers)
142         find_layout.render(context.merge({'content' => content}), registers)
143     end
144     
145     def process()
146         path = out_path
147         path.dirname.mkpath
148         path.open('w') { |f| f.write(render) }
149     end
150 end
151
152 class Layout < Page
153     def render(context, registers)
154         context = context.dup
155         context['page'] = @page_context.merge(context['page'])
156         content = @template.render!(context, registers: registers)
157         if @page_context.has_key?('layout')
158             find_layout.render(context.merge({'content' => content}), registers)
159         else
160             content
161         end
162     end
163 end
164
165 class Tag_tree < Liquid::Tag
166     def join(children_html)
167         children_html.empty? ? "" : "<dl>\n" + children_html.join + "</dl>\n"
168     end
169
170     def render(context)
171         current = context.registers[:page]
172         site = context.registers[:site]
173
174         format_entry = lambda do |page|
175             children = page.children
176             
177             css = (page == current) ? ' class="active"' : ""
178             children_html = current.related_to?(page) ? join(children.map(&format_entry)) : ""
179             
180             %{
181           <dt#{css}>
182             <a href='#{page.url}'>#{page.menu_title}</a>
183           </dt>
184           <dd#{css}>
185             #{children_html}
186           </dd>
187         }
188         end
189
190         join(site.toplevel.map(&format_entry))
191     end
192 end
193
194 class Tag_children < Liquid::Tag
195     def render(context)
196         children = context.registers[:page].children
197         entries = children.map {|p| "<li><a href='#{p.url}'>#{p.title}</a></li>" }
198         
199         "<div id='subtopics'>
200         <h2>This chapter covers the following topics:</h2>
201         <ul>
202           #{entries.join}
203         </ul>
204         </div>
205       "
206     end
207 end
208
209 class Tag_prevnext < Liquid::Tag
210     def render(context)
211         current = context.registers[:page]
212         pages = context.registers[:site].sorted_pages
213         
214         index = pages.index { |page| page == current }
215         return '' if !index
216         
217         link = lambda do |p, cls, txt| 
218             "<li><a title='#{p.title}' href='#{p.url}' class='#{cls}'>#{txt}</a></li>"
219         end
220         prev_link = index > 0 ? link.call(pages[index-1], "previous", " &lt; Previous ") : ""
221         next_link = index < pages.length-1 ? link.call(pages[index+1], "next", " Next &gt; ") : ""
222         
223         "<ul class='pager'>#{prev_link}#{next_link}</ul>"
224     end
225 end
226
227 class Server
228     def start_watcher()
229         begin require 'listen'
230         rescue LoadError
231             puts "To use the --watch function, please install the 'listen' Ruby gem"
232             puts "(available in Debian/Ubuntu as 'ruby-listen')"
233             return nil
234         end
235
236         listener = Listen.to(CONFIG[:pages_dir], wait_for_delay: 1.0, only: /.html$/) do |modified, added, removed|
237             Site.new.build
238         end
239         listener.start
240         listener
241     end
242
243     def run(options)
244         require 'webrick'
245         listener = options[:watch] && start_watcher
246         port = options[:port] || 8000
247             
248         puts "Serving at http://localhost:#{port}/ ..."
249         server = WEBrick::HTTPServer.new :Port => port, :DocumentRoot => CONFIG[:output_dir]
250         trap 'INT' do 
251             server.shutdown 
252         end
253         server.start
254         listener.stop if listener
255     end  
256 end
257
258 def main
259     Liquid::Template.register_tag('tree', Tag_tree)
260     Liquid::Template.register_tag('children', Tag_children)
261     Liquid::Template.register_tag('prevnext', Tag_prevnext)
262
263     if defined? Liquid::Template.error_mode
264         Liquid::Template.error_mode = :strict
265     end
266
267     options = {}
268     OptionParser.new do |opts| 
269         opts.banner = %{Usage: build.rb <command> [options]
270
271 Use 'build.rb' to build the manual.  Use 'build.rb serve' to also
272 start a web server; setting any web server options implies "serve".
273 }
274         opts.on("-w", "--watch", "Watch for changes") { options[:watch] = true }
275         opts.on("-p", "--port N", Integer, "Specify port for web server") { |p| options[:port] = p }
276     end.parse!
277
278     Site.new.build
279
280     if options[:watch] || options[:port] || (ARGV.length > 0 && "serve".start_with?(ARGV[0]))
281         Server.new.run(options)
282     end
283 end
284
285 main