morph-sdoc 0.2.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. data/.rvmrc +2 -0
  2. data/LICENSE +21 -0
  3. data/README.rdoc +37 -0
  4. data/Rakefile +51 -0
  5. data/VERSION.yml +5 -0
  6. data/bin/sdoc +11 -0
  7. data/bin/sdoc-merge +12 -0
  8. data/lib/rdoc/discover.rb +1 -0
  9. data/lib/sdoc.rb +16 -0
  10. data/lib/sdoc/c_parser_fix.rb +31 -0
  11. data/lib/sdoc/generator/shtml.rb +352 -0
  12. data/lib/sdoc/generator/template/direct/_context.rhtml +172 -0
  13. data/lib/sdoc/generator/template/direct/class.rhtml +40 -0
  14. data/lib/sdoc/generator/template/direct/file.rhtml +30 -0
  15. data/lib/sdoc/generator/template/direct/index.rhtml +14 -0
  16. data/lib/sdoc/generator/template/direct/resources/apple-touch-icon.png +0 -0
  17. data/lib/sdoc/generator/template/direct/resources/css/main.css +278 -0
  18. data/lib/sdoc/generator/template/direct/resources/css/panel.css +383 -0
  19. data/lib/sdoc/generator/template/direct/resources/css/reset.css +53 -0
  20. data/lib/sdoc/generator/template/direct/resources/favicon.ico +0 -0
  21. data/lib/sdoc/generator/template/direct/resources/i/arrows.png +0 -0
  22. data/lib/sdoc/generator/template/direct/resources/i/results_bg.png +0 -0
  23. data/lib/sdoc/generator/template/direct/resources/i/tree_bg.png +0 -0
  24. data/lib/sdoc/generator/template/direct/resources/js/jquery-1.3.2.min.js +19 -0
  25. data/lib/sdoc/generator/template/direct/resources/js/jquery-effect.js +593 -0
  26. data/lib/sdoc/generator/template/direct/resources/js/main.js +22 -0
  27. data/lib/sdoc/generator/template/direct/resources/js/searchdoc.js +628 -0
  28. data/lib/sdoc/generator/template/direct/resources/panel/index.html +71 -0
  29. data/lib/sdoc/generator/template/merge/index.rhtml +14 -0
  30. data/lib/sdoc/generator/template/shtml/_context.rhtml +164 -0
  31. data/lib/sdoc/generator/template/shtml/class.rhtml +46 -0
  32. data/lib/sdoc/generator/template/shtml/file.rhtml +37 -0
  33. data/lib/sdoc/generator/template/shtml/index.rhtml +14 -0
  34. data/lib/sdoc/generator/template/shtml/resources/apple-touch-icon.png +0 -0
  35. data/lib/sdoc/generator/template/shtml/resources/css/main.css +191 -0
  36. data/lib/sdoc/generator/template/shtml/resources/css/panel.css +383 -0
  37. data/lib/sdoc/generator/template/shtml/resources/css/reset.css +53 -0
  38. data/lib/sdoc/generator/template/shtml/resources/favicon.ico +0 -0
  39. data/lib/sdoc/generator/template/shtml/resources/i/arrows.png +0 -0
  40. data/lib/sdoc/generator/template/shtml/resources/i/results_bg.png +0 -0
  41. data/lib/sdoc/generator/template/shtml/resources/i/tree_bg.png +0 -0
  42. data/lib/sdoc/generator/template/shtml/resources/js/jquery-1.3.2.min.js +19 -0
  43. data/lib/sdoc/generator/template/shtml/resources/js/main.js +34 -0
  44. data/lib/sdoc/generator/template/shtml/resources/js/searchdoc.js +628 -0
  45. data/lib/sdoc/generator/template/shtml/resources/panel/index.html +71 -0
  46. data/lib/sdoc/github.rb +61 -0
  47. data/lib/sdoc/helpers.rb +26 -0
  48. data/lib/sdoc/json_backend.rb +15 -0
  49. data/lib/sdoc/merge.rb +217 -0
  50. data/lib/sdoc/options.rb +329 -0
  51. data/lib/sdoc/templatable.rb +58 -0
  52. data/sdoc.gemspec +93 -0
  53. metadata +151 -0
data/.rvmrc ADDED
@@ -0,0 +1,2 @@
1
+ rvm use --create 1.8.7@sdoc
2
+
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2009 Vladimir Kolesnikov
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
data/README.rdoc ADDED
@@ -0,0 +1,37 @@
1
+ = SDoc
2
+ == What's in?
3
+ - shtml - RDoc's generator to build searchable documentation
4
+ - <tt>sdoc-merge</tt> - comand line tool to build merge multiple sdoc documentations
5
+ packages into a single one
6
+ - <tt>sdoc</tt> - command line tool to run rdoc with generator=shtml
7
+
8
+ == Getting Started
9
+ sudo gem install sdoc
10
+ sdoc -N projectdir
11
+
12
+ == Command line sdoc
13
+ sdoc is simply a wrapper to rdoc command line tool. see <tt>sdoc --help </tt>
14
+ for more details. <tt>--fmt</tt> is set to shtml by default.
15
+ Default template <tt>-T</tt> is shtml. You can also use 'direct' template.
16
+ Example:
17
+ <tt>sdoc -o doc/rails -T direct rails</tt>
18
+
19
+ == Rake
20
+ # Rakefile
21
+ require 'sdoc' # and use your RDoc task the same way you used it before
22
+
23
+ Rake::RDocTask.new do |rdoc|
24
+ rdoc.rdoc_dir = 'doc/rdoc'
25
+ rdoc.options << '--fmt' << 'shtml' # explictly set shtml generator
26
+ rdoc.template = 'direct' # lighter template used on railsapi.com
27
+ ...
28
+ end
29
+
30
+ == sdoc-merge
31
+ Usage: sdoc-merge [options] directories
32
+ -n, --names [NAMES] Names of merged repositories. Comma separated
33
+ -o, --op [DIRECTORY] Set the output directory
34
+ -t, --title [TITLE] Set the title of merged file
35
+
36
+ Example:
37
+ <tt>sdoc-merge --title "Ruby v1.9, Rails v2.3.2.1" --op merged --names "Ruby,Rails" ruby-v1.9 rails-v2.3.2.1</tt>
data/Rakefile ADDED
@@ -0,0 +1,51 @@
1
+ require 'rake/testtask'
2
+ require 'rake/gempackagetask'
3
+
4
+ task :default => :test
5
+
6
+ Rake::TestTask.new("test") do |t|
7
+ t.libs << 'test'
8
+ t.pattern = 'test/**/*_test.rb'
9
+ t.warning = true
10
+ t.verbose = true
11
+ end
12
+
13
+ desc "Generate file list for .gemspec"
14
+ task :gem_file_list do
15
+ f = FileList.new
16
+ f.include('lib/**/**')
17
+ f.include('rdoc/**/**')
18
+ f.exclude('rdoc/test/**/**')
19
+ print "%w(" + f.to_a.select{|file| !File.directory? file }.join(' ') + ")\n"
20
+ end
21
+
22
+ begin
23
+ require 'jeweler'
24
+
25
+ spec = Gem::Specification.new do |gem|
26
+ gem.name = "morph-sdoc"
27
+ gem.summary = "rdoc html with javascript search index."
28
+ gem.email = "voloko@gmail.com"
29
+ gem.homepage = "http://github.com/voloko/sdoc"
30
+ gem.authors = ["Volodya Kolesnikov"]
31
+ gem.add_dependency("rdoc", "= 2.4.3")
32
+
33
+ if defined?(JRUBY_VERSION)
34
+ gem.platform = Gem::Platform.new(['universal', 'java', nil])
35
+ gem.add_dependency("json_pure", ">= 1.1.3")
36
+ else
37
+ gem.add_dependency("json", ">= 1.1.3")
38
+ end
39
+ end
40
+
41
+ jewler = Jeweler::Tasks.new(spec)
42
+
43
+ desc "Replace system gem with symlink to this folder"
44
+ task 'ghost' do
45
+ path = Gem.searcher.find(jewler.gemspec.name).full_gem_path
46
+ system 'sudo', 'rm', '-r', path
47
+ symlink File.expand_path('.'), path
48
+ end
49
+ rescue LoadError
50
+ puts "Jeweler not available. Install it with: (sudo) gem install jeweler"
51
+ end
data/VERSION.yml ADDED
@@ -0,0 +1,5 @@
1
+ ---
2
+ :patch: 21
3
+ :build:
4
+ :major: 0
5
+ :minor: 2
data/bin/sdoc ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby -KU
2
+
3
+ require File.dirname(__FILE__) + '/../lib/sdoc' # add extensions
4
+
5
+ begin
6
+ r = RDoc::RDoc.new
7
+ r.document(ARGV)
8
+ rescue RDoc::RDocError => e
9
+ $stderr.puts e.message
10
+ exit(1)
11
+ end
data/bin/sdoc-merge ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby -KU
2
+
3
+ require File.dirname(__FILE__) + '/../lib/sdoc' # add extensions
4
+ require 'sdoc/merge'
5
+
6
+ begin
7
+ m = SDoc::Merge.new
8
+ m.merge(ARGV)
9
+ rescue RDoc::RDocError => e
10
+ $stderr.puts e.message
11
+ exit(1)
12
+ end
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__), '/../sdoc')
data/lib/sdoc.rb ADDED
@@ -0,0 +1,16 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+ require "rubygems"
3
+ gem "rdoc", "= 2.4.3"
4
+
5
+ require "rdoc/rdoc"
6
+
7
+ module SDoc
8
+ end
9
+
10
+ require "sdoc/generator/shtml"
11
+ require "sdoc/c_parser_fix"
12
+
13
+ unless defined? SDOC_FIXED_RDOC_OPTIONS
14
+ SDOC_FIXED_RDOC_OPTIONS = 1
15
+ require "sdoc/options.rb"
16
+ end
@@ -0,0 +1,31 @@
1
+ require "rdoc/parser/c"
2
+
3
+ # New RDoc somehow misses class comments.
4
+ # copied this function from "2.2.2"
5
+ if ['2.4.2', '2.4.3'].include? RDoc::VERSION
6
+
7
+ class RDoc::Parser::C
8
+ def find_class_comment(class_name, class_meth)
9
+ comment = nil
10
+ if @content =~ %r{((?>/\*.*?\*/\s+))
11
+ (static\s+)?void\s+Init_#{class_name}\s*(?:_\(\s*)?\(\s*(?:void\s*)\)}xmi then
12
+ comment = $1
13
+ elsif @content =~ %r{Document-(?:class|module):\s#{class_name}\s*?(?:<\s+[:,\w]+)?\n((?>.*?\*/))}m
14
+ comment = $1
15
+ else
16
+ if @content =~ /rb_define_(class|module)/m then
17
+ class_name = class_name.split("::").last
18
+ comments = []
19
+ @content.split(/(\/\*.*?\*\/)\s*?\n/m).each_with_index do |chunk, index|
20
+ comments[index] = chunk
21
+ if chunk =~ /rb_define_(class|module).*?"(#{class_name})"/m then
22
+ comment = comments[index-1]
23
+ break
24
+ end
25
+ end
26
+ end
27
+ end
28
+ class_meth.comment = mangle_comment(comment) if comment
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,352 @@
1
+ require 'sdoc/json_backend'
2
+ require 'iconv'
3
+ require 'pathname'
4
+ require 'fileutils'
5
+ require 'erb'
6
+
7
+ gem 'rdoc', '>= 2.4.2'
8
+ require 'rdoc/rdoc'
9
+ require 'rdoc/generator'
10
+ require 'rdoc/generator/markup'
11
+
12
+ require 'sdoc/github'
13
+ require 'sdoc/templatable'
14
+ require 'sdoc/helpers'
15
+
16
+ class RDoc::ClassModule
17
+ def document_self_or_methods
18
+ document_self || method_list.any?{ |m| m.document_self }
19
+ end
20
+
21
+ def with_documentation?
22
+ document_self_or_methods || classes_and_modules.any?{ |c| c.with_documentation? }
23
+ end
24
+ end
25
+
26
+ class RDoc::Generator::SHtml
27
+ RDoc::RDoc.add_generator( self )
28
+ include ERB::Util
29
+ include SDoc::GitHub
30
+ include SDoc::Templatable
31
+ include SDoc::Helpers
32
+
33
+ GENERATOR_DIRS = [File.join('sdoc', 'generator'), File.join('rdoc', 'generator')]
34
+
35
+ # Used in js to reduce index sizes
36
+ TYPE_CLASS = 1
37
+ TYPE_METHOD = 2
38
+ TYPE_FILE = 3
39
+
40
+ TREE_FILE = File.join 'panel', 'tree.js'
41
+ SEARCH_INDEX_FILE = File.join 'panel', 'search_index.js'
42
+
43
+ FILE_DIR = 'files'
44
+ CLASS_DIR = 'classes'
45
+
46
+ RESOURCES_DIR = File.join('resources', '.')
47
+
48
+ attr_reader :basedir
49
+
50
+ def self.for(options)
51
+ self.new(options)
52
+ end
53
+
54
+ def self.template_dir template
55
+ $LOAD_PATH.map do |path|
56
+ GENERATOR_DIRS.map do |dir|
57
+ File.join path, dir, 'template', template
58
+ end
59
+ end.flatten.find do |dir|
60
+ File.directory? dir
61
+ end
62
+ end
63
+
64
+ def initialize(options)
65
+ @options = options
66
+ if @options.respond_to?('diagram=')
67
+ @options.diagram = false
68
+ end
69
+ @github_url_cache = {}
70
+
71
+ template = @options.template || 'direct'
72
+
73
+ templ_dir = self.class.template_dir template
74
+
75
+ raise RDoc::Error, "could not find template #{template.inspect}" unless
76
+ templ_dir
77
+
78
+ @template_dir = Pathname.new File.expand_path(templ_dir)
79
+ @basedir = Pathname.pwd.expand_path
80
+ end
81
+
82
+ def generate( top_levels )
83
+ @outputdir = Pathname.new( @options.op_dir ).expand_path( @basedir )
84
+ @files = top_levels.sort
85
+ @classes = RDoc::TopLevel.all_classes_and_modules.sort
86
+
87
+ # Now actually write the output
88
+ copy_resources
89
+ generate_class_tree
90
+ generate_search_index
91
+ generate_file_files
92
+ generate_class_files
93
+ generate_index_file
94
+ end
95
+
96
+ def class_dir
97
+ CLASS_DIR
98
+ end
99
+
100
+ def file_dir
101
+ FILE_DIR
102
+ end
103
+
104
+
105
+ protected
106
+ ### Output progress information if debugging is enabled
107
+ def debug_msg( *msg )
108
+ return unless $DEBUG_RDOC
109
+ $stderr.puts( *msg )
110
+ end
111
+
112
+ ### Create class tree structure and write it as json
113
+ def generate_class_tree
114
+ debug_msg "Generating class tree"
115
+ topclasses = @classes.select {|klass| !(RDoc::ClassModule === klass.parent) }
116
+ tree = generate_file_tree + generate_class_tree_level(topclasses)
117
+ debug_msg " writing class tree to %s" % TREE_FILE
118
+ File.open(TREE_FILE, "w", 0644) do |f|
119
+ f.write('var tree = '); f.write(tree.to_json(:max_nesting => 0))
120
+ end unless $dryrun
121
+ end
122
+
123
+ ### Recursivly build class tree structure
124
+ def generate_class_tree_level(classes)
125
+ tree = []
126
+ classes.select{|c| c.with_documentation? }.sort.each do |klass|
127
+ item = [
128
+ klass.name,
129
+ klass.document_self_or_methods ? klass.path : '',
130
+ klass.module? ? '' : (klass.superclass ? " < #{String === klass.superclass ? klass.superclass : klass.superclass.full_name}" : ''),
131
+ generate_class_tree_level(klass.classes_and_modules)
132
+ ]
133
+ tree << item
134
+ end
135
+ tree
136
+ end
137
+
138
+ ### Create search index for all classes, methods and files
139
+ ### Wite it as json
140
+ def generate_search_index
141
+ debug_msg "Generating search index"
142
+
143
+ index = {
144
+ :searchIndex => [],
145
+ :longSearchIndex => [],
146
+ :info => []
147
+ }
148
+
149
+ add_class_search_index(index)
150
+ add_method_search_index(index)
151
+ add_file_search_index(index)
152
+
153
+ debug_msg " writing search index to %s" % SEARCH_INDEX_FILE
154
+ data = {
155
+ :index => index
156
+ }
157
+ File.open(SEARCH_INDEX_FILE, "w", 0644) do |f|
158
+ f.write('var search_data = '); f.write(data.to_json(:max_nesting => 0))
159
+ end unless $dryrun
160
+ end
161
+
162
+ ### Add files to search +index+ array
163
+ def add_file_search_index(index)
164
+ debug_msg " generating file search index"
165
+
166
+ @files.select { |file|
167
+ file.document_self
168
+ }.sort.each do |file|
169
+ index[:searchIndex].push( search_string(file.name) )
170
+ index[:longSearchIndex].push( search_string(file.path) )
171
+ index[:info].push([
172
+ file.name,
173
+ file.path,
174
+ file.path,
175
+ '',
176
+ snippet(file.comment),
177
+ TYPE_FILE
178
+ ])
179
+ end
180
+ end
181
+
182
+ ### Add classes to search +index+ array
183
+ def add_class_search_index(index)
184
+ debug_msg " generating class search index"
185
+
186
+ @classes.select { |klass|
187
+ klass.document_self_or_methods
188
+ }.sort.each do |klass|
189
+ modulename = klass.module? ? '' : (klass.superclass ? (String === klass.superclass ? klass.superclass : klass.superclass.full_name) : '')
190
+ index[:searchIndex].push( search_string(klass.name) )
191
+ index[:longSearchIndex].push( search_string(klass.parent.full_name) )
192
+ files = klass.in_files.map{ |file| file.absolute_name }
193
+ index[:info].push([
194
+ klass.name,
195
+ files.include?(klass.parent.full_name) ? files.first : klass.parent.full_name,
196
+ klass.path,
197
+ modulename ? " < #{modulename}" : '',
198
+ snippet(klass.comment),
199
+ TYPE_CLASS
200
+ ])
201
+ end
202
+ end
203
+
204
+ ### Add methods to search +index+ array
205
+ def add_method_search_index(index)
206
+ debug_msg " generating method search index"
207
+
208
+ list = @classes.map { |klass|
209
+ klass.method_list
210
+ }.flatten.sort{ |a, b| a.name == b.name ? a.parent.full_name <=> b.parent.full_name : a.name <=> b.name }.select { |method|
211
+ method.document_self
212
+ }
213
+ unless @options.show_all
214
+ list = list.find_all {|m| m.visibility == :public || m.visibility == :protected || m.force_documentation }
215
+ end
216
+
217
+ list.each do |method|
218
+ index[:searchIndex].push( search_string(method.name) + '()' )
219
+ index[:longSearchIndex].push( search_string(method.parent.full_name) )
220
+ index[:info].push([
221
+ method.name,
222
+ method.parent.full_name,
223
+ method.path,
224
+ method.params,
225
+ snippet(method.comment),
226
+ TYPE_METHOD
227
+ ])
228
+ end
229
+ end
230
+
231
+ ### Generate a documentation file for each class
232
+ def generate_class_files
233
+ debug_msg "Generating class documentation in #@outputdir"
234
+ templatefile = @template_dir + 'class.rhtml'
235
+
236
+ @classes.each do |klass|
237
+ debug_msg " working on %s (%s)" % [ klass.full_name, klass.path ]
238
+ outfile = @outputdir + klass.path
239
+ rel_prefix = @outputdir.relative_path_from( outfile.dirname )
240
+
241
+ debug_msg " rendering #{outfile}"
242
+ self.render_template( templatefile, binding(), outfile )
243
+ end
244
+ end
245
+
246
+ ### Generate a documentation file for each file
247
+ def generate_file_files
248
+ debug_msg "Generating file documentation in #@outputdir"
249
+ templatefile = @template_dir + 'file.rhtml'
250
+
251
+ @files.each do |file|
252
+ outfile = @outputdir + file.path
253
+ debug_msg " working on %s (%s)" % [ file.full_name, outfile ]
254
+ rel_prefix = @outputdir.relative_path_from( outfile.dirname )
255
+
256
+ debug_msg " rendering #{outfile}"
257
+ self.render_template( templatefile, binding(), outfile )
258
+ end
259
+ end
260
+
261
+ def index_file
262
+ if @options.main_page && file = @files.find { |f| f.full_name == @options.main_page }
263
+ file
264
+ else
265
+ @files.first
266
+ end
267
+ end
268
+
269
+ ### Create index.html with frameset
270
+ def generate_index_file
271
+ debug_msg "Generating index file in #@outputdir"
272
+ templatefile = @template_dir + 'index.rhtml'
273
+ outfile = @outputdir + 'index.html'
274
+ index_path = index_file.path
275
+
276
+ self.render_template( templatefile, binding(), outfile )
277
+ end
278
+
279
+ ### Strip comments on a space after 100 chars
280
+ def snippet(str)
281
+ str ||= ''
282
+ if str =~ /^(?>\s*)[^\#]/
283
+ content = str
284
+ else
285
+ content = str.gsub(/^\s*(#+)\s*/, '')
286
+ end
287
+
288
+ content = content.sub(/^(.{100,}?)\s.*/m, "\\1").gsub(/\r?\n/m, ' ')
289
+
290
+ begin
291
+ content.to_json(:max_nesting => 0)
292
+ rescue # might fail on non-unicode string
293
+ begin
294
+ content = Iconv.conv('latin1//ignore', "UTF8", content) # remove all non-unicode chars
295
+ content.to_json(:max_nesting => 0)
296
+ rescue
297
+ content = '' # something hugely wrong happend
298
+ end
299
+ end
300
+ content
301
+ end
302
+
303
+ ### Build search index key
304
+ def search_string(string)
305
+ string ||= ''
306
+ string.downcase.gsub(/\s/,'')
307
+ end
308
+
309
+ ### Copy all the resource files to output dir
310
+ def copy_resources
311
+ resoureces_path = @template_dir + RESOURCES_DIR
312
+ debug_msg "Copying #{resoureces_path}/** to #{@outputdir}/**"
313
+ FileUtils.cp_r resoureces_path.to_s, @outputdir.to_s, :preserve => true unless $dryrun
314
+ end
315
+
316
+ class FilesTree
317
+ attr_reader :children
318
+ def add(path, url)
319
+ path = path.split(File::SEPARATOR) unless Array === path
320
+ @children ||= {}
321
+ if path.length == 1
322
+ @children[path.first] = url
323
+ else
324
+ @children[path.first] ||= FilesTree.new
325
+ @children[path.first].add(path[1, path.length], url)
326
+ end
327
+ end
328
+ end
329
+
330
+ def generate_file_tree
331
+ if @files.length > 1
332
+ @files_tree = FilesTree.new
333
+ @files.each do |file|
334
+ @files_tree.add(file.relative_name, file.path)
335
+ end
336
+ [['', '', 'files', generate_file_tree_level(@files_tree)]]
337
+ else
338
+ []
339
+ end
340
+ end
341
+
342
+ def generate_file_tree_level(tree)
343
+ tree.children.keys.sort.map do |name|
344
+ child = tree.children[name]
345
+ if String === child
346
+ [name, child, '', []]
347
+ else
348
+ ['', '', name, generate_file_tree_level(child)]
349
+ end
350
+ end
351
+ end
352
+ end