morph-sdoc 0.2.21

Sign up to get free protection for your applications and to get access to all the features.
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