9 layouts_dir: '_layouts',
11 output_dir: '_site' # will get wiped!
15 a.start_with?(b) && b.count('/') + 1 == a.count('/')
19 attr_reader :pages, :layouts
38 Pathname.glob(layouts_dir + Pathname('*.html')) do |path|
40 layout = Layout.new(self, path)
42 @layouts[path.basename('.html').to_s] = layout
47 pages_dir.find do |path|
48 if path.file? && path.extname == '.html'
49 page = Page.new(self, path)
57 @pages.each {|page| page.process}
61 `rsync -a --delete --exclude='*~' #{static_dir}/. #{output_dir}`
64 def find_children(url)
65 sorted_pages.select { |p| child_url?(p.url, url) }
68 def toplevel() @toplevel_memo ||= find_children('/') end
69 def sorted_pages() @sorted_pages_memo ||= @pages.sort_by{ |p| p.sort_url } end
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
78 attr_reader :path, :out_path, :url, :sort_url
80 def initialize(site, path)
85 @out_path = @site.output_dir + canon + Pathname("index.html")
86 @url = '/' + canon + '/'
87 @sort_url = @path.to_s.sub(/\.html$/, '')
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$/, '')
99 # should we show p in the index on selfs page?
100 url.start_with?(p.url) || child_url?(url, p.url)
104 @page_context['title'] || ""
108 @page_context['menu_title'] || title
113 frontmatter, @content = split_frontmatter(content) || abort("File not well-formatted: #{@path}")
114 @page_context = YAML.load(frontmatter)
115 @template = Liquid::Template.parse(@content)
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
125 @site.layouts[@page_context['layout'] || 'default']
129 @children ||= @site.find_children(@url)
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)
142 path.open('w') { |f| f.write(render) }
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)
159 class Tag_tree < Liquid::Tag
160 def join(children_html)
161 children_html.empty? ? "" : "<dl>\n" + children_html.join + "</dl>\n"
165 current = context.registers[:page]
166 site = context.registers[:site]
168 format_entry = lambda do |page|
169 children = page.children
171 css = (page == current) ? ' class="active"' : ""
172 children_html = current.related_to?(page) ? join(children.map(&format_entry)) : ""
176 <a href='#{page.url}'>#{page.menu_title}</a>
184 join(site.toplevel.map(&format_entry))
188 class Tag_children < Liquid::Tag
190 children = context.registers[:page].children
191 entries = children.map {|p| "<li><a href='#{p.url}'>#{p.title}</a></li>" }
193 "<div id='subtopics'>
194 <h2>This chapter covers the following topics:</h2>
203 class Tag_prevnext < Liquid::Tag
205 current = context.registers[:page]
206 pages = context.registers[:site].sorted_pages
208 index = pages.index { |page| page == current }
211 link = lambda do |p, cls, txt|
212 "<li><a title='#{p.title}' href='#{p.url}' class='#{cls}'>#{txt}</a></li>"
214 prev_link = index > 0 ? link.call(pages[index-1], "previous", " < Previous ") : ""
215 next_link = index < pages.length-1 ? link.call(pages[index+1], "next", " Next > ") : ""
217 "<ul class='pager'>#{prev_link}#{next_link}</ul>"
225 listener = Listen.to(CONFIG[:pages_dir], wait_for_delay: 1.0, only: /.html$/) do |modified, added, removed|
235 listener = options[:watch] && start_watcher
237 puts "Serving at http://localhost:8000/ ..."
238 server = WEBrick::HTTPServer.new :Port => 8000, :DocumentRoot => CONFIG[:output_dir]
243 listener.stop if listener
248 Liquid::Template.register_tag('tree', Tag_tree)
249 Liquid::Template.register_tag('children', Tag_children)
250 Liquid::Template.register_tag('prevnext', Tag_prevnext)
252 if defined? Liquid::Template.error_mode
253 Liquid::Template.error_mode = :strict
257 OptionParser.new do |opts|
258 opts.on("--watch", "Watch for changes") { options[:watch] = true }
264 Server.new.run(options)