]> Shamusworld >> Repos - ardour-manual/blob - _plugins/manual.rb
copy-editing chapter 8.
[ardour-manual] / _plugins / manual.rb
1 require 'erb'
2 require 'fileutils'
3 require 'tmpdir'
4 require 'pp'
5
6 module Manual
7
8   DIRECTORY_ENTRIES = {}
9
10   def self.traverse_data(entries, directory_sort = false, paths = [], key_paths = [], &block)
11
12     entries.map do |entry|
13
14       entry = entry.dup
15
16       if entry[:type] == 'directory'
17         entry[:children] = traverse_data(entry[:children], directory_sort, paths + [entry[:name]], key_paths + [entry[:key]], &block)
18       end
19       block_given? ? block.call(entry) : entry
20     end
21   end
22
23   def self.traverse(path, directory_sort = false, paths = [], key_paths = [], &block)
24
25       entries = Dir.glob(File.join(path,'*')).sort
26
27       entries.sort_by! { |e| File.directory?(e) ? 1 : 0  } if directory_sort
28
29       entries.map do |entry|
30           is_dir = File.directory?(entry)
31
32           data = extract_data(is_dir ? "#{entry}.html" : entry)
33
34           short_title = data['menu_title'] || data['title']
35
36           name = entry[/[^\/]+$/] # filename
37           key = name.sub(/^[0-9]+(\-|_)/,'').sub(/\.[^\.]+$/,'') # slug
38           my_paths = paths + [name]
39           my_key_paths = key_paths + [key]
40           url = '/' + my_key_paths.join('/') + '/'
41
42           without_extension = entry.sub(/\.[^\/\/]+$/,'')
43
44           h = {
45               name: name,
46               title: data['title'] || key,
47               menu_title: short_title || key,
48               key: key,
49               filename: entry,
50               type: is_dir ? 'directory' : 'file',
51               url: url
52           }
53
54           if is_dir
55               h.update \
56                   children: traverse(entry, directory_sort, my_paths, my_key_paths, &block)
57           else
58               h.update extension: File.extname(name), has_dir: File.directory?(without_extension)
59           end
60
61           if is_dir
62             DIRECTORY_ENTRIES[url] = h
63           end
64
65           block_given? ? block.call(h) : h
66       end.compact
67     end
68
69     def self.extract_data(filename)
70       if File.exists?(filename) and !File.directory?(filename) and first3 = File.open(filename) { |fd| fd.read(3) } and first3 == '---'
71         blah = filename.sub(/^_manual\//,'')
72         page = Jekyll::Page.new(@site, '_manual', File.dirname(blah), File.basename(blah))
73         page.data
74       else
75         {}
76       end
77     end
78
79   class ManualPage < Jekyll::Page
80     def initialize(*args)
81       super
82     end
83   end
84
85   class ManualGenerator < Jekyll::Generator
86
87     safe true
88     
89     def generate(site)
90       source = site.config['source']
91       destination = site.config['destination']
92
93       manual_dir = '_manual'
94
95       # now we need to turn our raw input files into something for jekyll to process
96       # everything is in a directory with it's name and all content is in index.html files
97       # the tmpdir gets removed at the end of this block automatically
98
99       Dir.mktmpdir do |tmpdir|
100
101         Manual.traverse manual_dir, true do |entry|
102           output_filename = File.join(tmpdir, entry[:url], "index#{entry[:extension]}")
103
104           FileUtils.mkdir_p File.dirname(output_filename)
105           
106           next unless entry[:type] == 'file'
107
108           File.open(output_filename, 'w+') do |f| 
109             f << File.read(entry[:filename])
110           end
111
112           relative_filename = File.join(entry[:url], "index#{entry[:extension]}")
113
114           site.pages << Jekyll::Page.new(site, tmpdir, File.dirname(relative_filename), File.basename(relative_filename))
115         end
116
117       end
118     end
119
120   end
121
122   class ManualChildPageTag < Liquid::Tag
123     def render(context)
124       current = context['page.url'].sub(/[^\/]+$/,'')
125
126       if entry = DIRECTORY_ENTRIES[current]
127
128         path = File.join(entry[:filename], '*')
129
130         entries = entry[:children].map do |child|
131           "<li><a href='#{child[:url]}'>#{child[:title]}</a></li>"
132         end.uniq
133
134         "<div id='subtopics'>
135         <h2>This chapter covers the following topics:</h2>
136         <ul>
137           #{entries.join}
138         </ul>
139         </div>
140         "
141       end
142     end
143   end
144
145   # generates a big <dl> list of the manual page stucture
146
147   class ManualTOCTag < Liquid::Tag
148
149     def process_hierarchy(items_a, items_b)
150       current = true
151       position = nil
152       level = -1
153
154       [items_a.length,items_b.length].max.times do |i|
155         a = items_a[i]
156         b = items_b[i]
157
158         current = false if a != b
159
160         # start incrementing this when we don't have either a or b
161         level += 1 if !a || !b
162
163         if a && b
164           return [false] if a != b
165         elsif a
166           position = :parent
167         elsif b
168           position = :child
169         end
170       end
171       position ? [current, position, level + 1] : [current]
172     end
173
174     def render(context)
175
176       @source = '_manual' #context.registers[:site].source
177
178       @@data_tree ||= Manual.traverse(@source)
179
180       @site = context.registers[:site]
181       current = context['page.url'].sub(/[^\/]+$/,'')
182
183       current_a = current.split('/').reject(&:empty?)
184
185       tree = Manual.traverse_data(@@data_tree) do |entry|
186       
187           url = entry[:url]
188
189           url_a = url.split('/').reject(&:empty?)
190
191           depth = url_a.length
192           is_current, position, level = *process_hierarchy(current_a, url_a)
193           
194           # this massively speeds up build time by not including the whole menu tree for each page
195           next if depth > 1 && current_a[0] != url_a[0]
196
197           css_classes = []
198           css_classes << 'active' if is_current
199           css_classes << position.to_s if position
200           css_classes << "#{position}-#{level}" if position && level
201           css_classes << 'other' unless is_current || position || level
202
203           css_classes << "level-#{depth}"
204           css_classes = css_classes.join(' ')
205
206           if entry[:type] == 'directory'
207
208               erb = ::ERB.new <<-HTML
209                   <dt class="<%= css_classes %>">
210                       <a href="<%= entry[:url] %>"><%= entry[:menu_title] %></a>
211                   </dt>
212                   <dd class="<%= css_classes %>">
213                       <% if entry[:children].any? %>
214                         <dl>
215                             <%= entry[:children].join %> 
216                         </dl>
217                       <% end %>
218                   </dd>
219               HTML
220
221               erb.result(binding)
222           else
223
224               directory_filename = entry[:filename].sub(/\.[^\/\.]+$/,'')
225
226               unless entry[:has_dir]
227
228                 erb = ::ERB.new <<-HTML
229                     <dt class="<%= css_classes %>">
230                         <a href="<%= entry[:url] %>"><%= entry[:menu_title] %></a>
231                     </dt>
232                     <dd class="<%= css_classes %>">
233                     </dd>
234                 HTML
235
236                 erb.result(binding)
237              end
238           end
239           
240          
241       end
242
243       "<dl>#{tree.join}</dl>
244       <script type='text/javascript'>
245       //<![CDATA[
246         offset = document.getElementsByClassName('active')[0].offsetTop;
247         height = document.getElementById('tree').clientHeight;
248         if (offset > (height * .7)) {
249           tree.scrollTop = offset - height * .3;
250         }
251       //]]>
252       </script>"
253
254     end
255
256
257   end
258
259 end
260
261 Liquid::Template.register_tag('tree', Manual::ManualTOCTag) 
262 Liquid::Template.register_tag('children', Manual::ManualChildPageTag)