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