rdoc-generator-solarfish 0.0.2

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 (43) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.rubocop.yml +26 -0
  4. data/.travis.yml +9 -0
  5. data/Gemfile +2 -0
  6. data/LICENSE +21 -0
  7. data/README.md +175 -0
  8. data/Rakefile +28 -0
  9. data/data/rdoc-generator-solarfish/templates/onepage.slim +110 -0
  10. data/data/rdoc-generator-solarfish/themes/common/fonts/LICENSE +7 -0
  11. data/data/rdoc-generator-solarfish/themes/common/fonts/LICENSE.Raleway +95 -0
  12. data/data/rdoc-generator-solarfish/themes/common/fonts/LICENSE.Roboto +201 -0
  13. data/data/rdoc-generator-solarfish/themes/common/fonts/Raleway-Regular.ttf +0 -0
  14. data/data/rdoc-generator-solarfish/themes/common/fonts/Raleway-SemiBold.ttf +0 -0
  15. data/data/rdoc-generator-solarfish/themes/common/fonts/Roboto-Regular.ttf +0 -0
  16. data/data/rdoc-generator-solarfish/themes/common/syntax/LICENSE +3 -0
  17. data/data/rdoc-generator-solarfish/themes/common/syntax/LICENSE.onelight +20 -0
  18. data/data/rdoc-generator-solarfish/themes/common/syntax/onelight.sass +47 -0
  19. data/data/rdoc-generator-solarfish/themes/light/main.sass +215 -0
  20. data/data/rdoc-generator-solarfish/themes/light.yml +16 -0
  21. data/docs/example.rb +142 -0
  22. data/docs/example_output/Raleway-Regular.ttf +0 -0
  23. data/docs/example_output/Raleway-SemiBold.ttf +0 -0
  24. data/docs/example_output/Roboto-Regular.ttf +0 -0
  25. data/docs/example_output/created.rid +2 -0
  26. data/docs/example_output/index.html +404 -0
  27. data/docs/example_output/index.json +428 -0
  28. data/docs/example_output/main.css +210 -0
  29. data/docs/example_output/onelight.css +22 -0
  30. data/lib/rdoc/discover.rb +1 -0
  31. data/lib/rdoc/generator/doc_loader.rb +315 -0
  32. data/lib/rdoc/generator/html_writer.rb +35 -0
  33. data/lib/rdoc/generator/json_writer.rb +16 -0
  34. data/lib/rdoc/generator/settings.rb +57 -0
  35. data/lib/rdoc/generator/solarfish.rb +135 -0
  36. data/lib/rdoc/generator/template_loader.rb +42 -0
  37. data/lib/rdoc/generator/theme_loader.rb +160 -0
  38. data/lib/solarfish.rb +2 -0
  39. data/rdoc-generator-solarfish.gemspec +31 -0
  40. data/test/test_generator.rb +95 -0
  41. data/test/test_theme_loader.rb +89 -0
  42. data/test/themes.rb +213 -0
  43. metadata +216 -0
@@ -0,0 +1,315 @@
1
+ # DocLoader reads RDoc documentation from RDoc store and builds a hash that
2
+ # will be passed to HTML template or written to JSON file.
3
+ class DocLoader
4
+ def initialize(options, store)
5
+ @options = options
6
+ @store = store
7
+ end
8
+
9
+ def load
10
+ build_classes
11
+ end
12
+
13
+ private
14
+
15
+ def build_classes
16
+ classes = @store.all_classes_and_modules
17
+
18
+ classes = classes.reject do |klass|
19
+ skip_class? klass.full_name
20
+ end
21
+
22
+ class_list = classes.map do |klass|
23
+ {
24
+ id: klass.full_name.strip,
25
+ title: klass.full_name,
26
+ kind: get_class_kind(klass.full_name),
27
+ comment: get_comment(klass),
28
+ groups: build_groups(klass)
29
+ }
30
+ end
31
+
32
+ stable_sort_by! class_list do |klass|
33
+ klass[:id]
34
+ end
35
+
36
+ with_labels class_list
37
+ end
38
+
39
+ def build_groups(klass)
40
+ members = build_members(klass)
41
+ groups = {}
42
+
43
+ members.each do |member|
44
+ group = get_builtin_group(member)
45
+ next unless group
46
+
47
+ group_id = make_id(klass, group[:type].to_s)
48
+
49
+ unless groups.include? group_id
50
+ groups[group_id] = group.merge(
51
+ id: group_id,
52
+ members: []
53
+ )
54
+ end
55
+
56
+ groups[group_id][:members] << member
57
+ end
58
+
59
+ group_list = groups.values
60
+
61
+ group_list.each do |group|
62
+ stable_sort_by! group[:members] do |member|
63
+ member[:id]
64
+ end
65
+ end
66
+
67
+ stable_sort_by! group_list do |group|
68
+ builtin_group_order.index group[:type]
69
+ end
70
+
71
+ group_list
72
+ end
73
+
74
+ def build_members(klass)
75
+ members = []
76
+
77
+ method_members = build_members_from_list klass, klass.method_list do |member|
78
+ member[:kind] = :method
79
+ end
80
+
81
+ attr_members = build_members_from_list klass, klass.attributes do |member|
82
+ member[:kind] = :attribute
83
+ end
84
+
85
+ const_members = build_members_from_list klass, klass.constants do |member|
86
+ member[:kind] = :constant
87
+ end
88
+
89
+ extends_members = build_members_from_list klass, klass.extends do |member|
90
+ member[:kind] = :extended
91
+ end
92
+
93
+ include_members = build_members_from_list klass, klass.includes do |member|
94
+ member[:kind] = :included
95
+ end
96
+
97
+ members.push(*method_members)
98
+ members.push(*attr_members)
99
+ members.push(*const_members)
100
+ members.push(*extends_members)
101
+ members.push(*include_members)
102
+
103
+ with_labels members
104
+ end
105
+
106
+ def build_members_from_list(klass, member_list)
107
+ members = []
108
+
109
+ member_list.each do |m|
110
+ next if skip_member? m.name
111
+
112
+ member = {}
113
+
114
+ member[:id] = make_id(klass, m.name)
115
+ member[:title] = get_title(m)
116
+ member[:signature] = get_signature(m)
117
+ member[:comment] = get_comment(m)
118
+
119
+ if m.respond_to? :markup_code
120
+ member[:code] = m.markup_code if m.markup_code && m.markup_code != ''
121
+ end
122
+
123
+ if m.respond_to? :type
124
+ member[:level] = m.type.to_sym if m.type
125
+ end
126
+
127
+ if m.respond_to? :visibility
128
+ member[:visibility] = m.visibility.to_sym if m.visibility
129
+ end
130
+
131
+ yield member
132
+
133
+ members << member
134
+ end
135
+
136
+ members
137
+ end
138
+
139
+ def build_labels(object)
140
+ labels = []
141
+
142
+ case object[:kind]
143
+ when :module, :class, :constant, :included, :extended
144
+ labels << {
145
+ id: object[:kind].capitalize,
146
+ title: object[:kind].to_s
147
+ }
148
+
149
+ when :method
150
+ labels << if object[:level] == :class
151
+ {
152
+ id: 'ClassMethod',
153
+ title: 'class method'
154
+ }
155
+ else
156
+ {
157
+ id: 'InstanceMethod',
158
+ title: 'instance method'
159
+ }
160
+ end
161
+
162
+ when :attribute
163
+ labels << if object[:level] == :class
164
+ {
165
+ id: 'ClassAttribute',
166
+ title: 'class attribute'
167
+ }
168
+ else
169
+ {
170
+ id: 'InstanceAttribute',
171
+ title: 'instance attribute'
172
+ }
173
+ end
174
+ end
175
+
176
+ if object[:visibility]
177
+ labels << {
178
+ id: object[:visibility].capitalize,
179
+ title: object[:visibility].to_s
180
+ }
181
+ end
182
+
183
+ labels
184
+ end
185
+
186
+ def with_labels(array)
187
+ array.each do |object|
188
+ object[:labels] = build_labels(object)
189
+ end
190
+ array
191
+ end
192
+
193
+ def make_id(klass, name)
194
+ klass.full_name.strip + '::' + name
195
+ end
196
+
197
+ def get_title(object)
198
+ object.name
199
+ end
200
+
201
+ def get_signature(object)
202
+ if object.respond_to? :arglists
203
+ return object.arglists if object.arglists
204
+ end
205
+ ''
206
+ end
207
+
208
+ def get_comment(object)
209
+ if object.comment.respond_to? :text
210
+ object.description.strip
211
+ else
212
+ object.comment
213
+ end
214
+ end
215
+
216
+ def get_class_kind(class_name)
217
+ if @store.all_modules.select { |m| m.full_name == class_name }.size == 1
218
+ :module
219
+ else
220
+ :class
221
+ end
222
+ end
223
+
224
+ def builtin_group_order
225
+ %i[
226
+ ExtendedClasses
227
+ IncludedModules
228
+ Constants
229
+ ClassAttributes
230
+ ClassMethods
231
+ InstanceAttributes
232
+ InstanceMethods
233
+ ]
234
+ end
235
+
236
+ def get_builtin_group(member)
237
+ case member[:kind]
238
+ when :method
239
+ case member[:level]
240
+ when :instance
241
+ {
242
+ title: 'Instance Methods',
243
+ type: :InstanceMethods,
244
+ kind: :method,
245
+ level: :instance
246
+ }
247
+ when :class
248
+ {
249
+ title: 'Class Methods',
250
+ type: :ClassMethods,
251
+ kind: :method,
252
+ level: :class
253
+ }
254
+ end
255
+ when :attribute
256
+ case member[:level]
257
+ when :instance
258
+ {
259
+ title: 'Instance Attributes',
260
+ type: :InstanceAttributes,
261
+ kind: :attribute,
262
+ level: :instance
263
+ }
264
+ when :class
265
+ {
266
+ title: 'Class Attributes',
267
+ type: :ClassAttributes,
268
+ kind: :attribute,
269
+ level: :class
270
+ }
271
+ end
272
+ when :constant
273
+ {
274
+ title: 'Constants',
275
+ type: :Constants,
276
+ kind: :constant
277
+ }
278
+ when :extended
279
+ {
280
+ title: 'Extended Classes',
281
+ type: :ExtendedClasses,
282
+ kind: :extended
283
+ }
284
+ when :included
285
+ {
286
+ title: 'Included Modules',
287
+ type: :IncludedModules,
288
+ kind: :included
289
+ }
290
+ end
291
+ end
292
+
293
+ def stable_sort_by!(array)
294
+ sorted = array.each_with_index.sort_by do |e, n|
295
+ [yield(e), n]
296
+ end
297
+ array.replace(sorted.map(&:first))
298
+ end
299
+
300
+ def skip_class?(class_name)
301
+ if @options.sf_filter_classes
302
+ @options.sf_filter_classes.match(class_name).nil?
303
+ else
304
+ false
305
+ end
306
+ end
307
+
308
+ def skip_member?(member_name)
309
+ if @options.sf_filter_members
310
+ @options.sf_filter_members.match(member_name).nil?
311
+ else
312
+ false
313
+ end
314
+ end
315
+ end
@@ -0,0 +1,35 @@
1
+ require 'fileutils'
2
+
3
+ # HTMLWriter builds HTML file from a template and copies HTML and
4
+ # theme files to the output directory.
5
+ class HTMLWriter
6
+ def initialize(options)
7
+ @options = options
8
+ end
9
+
10
+ def write(data, theme_files, template)
11
+ html = template.render(data)
12
+ install_theme(theme_files)
13
+ install_html(html)
14
+ end
15
+
16
+ private
17
+
18
+ def install_theme(theme_files)
19
+ theme_files.each do |file|
20
+ if file[:dst_name]
21
+ if file[:src_path]
22
+ FileUtils.copy_file(file[:src_path], file[:dst_name])
23
+ elsif file[:data]
24
+ File.write(file[:dst_name], file[:data])
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ def install_html(html)
31
+ File.open(@options.sf_htmlfile, 'w') do |file|
32
+ file.write(html)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,16 @@
1
+ require 'json'
2
+
3
+ # JSONWriter builds JSON file and copies it to the output directory.
4
+ class JSONWriter
5
+ def initialize(options)
6
+ @options = options
7
+ end
8
+
9
+ def write(data)
10
+ json = JSON.pretty_generate(data)
11
+
12
+ File.open(@options.sf_jsonfile, 'w') do |file|
13
+ file.write(json)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,57 @@
1
+ require 'pathname'
2
+ require 'rubygems'
3
+
4
+ # Predefined configuration.
5
+ module Settings
6
+ DEFAULT_HTMLFILE = 'index.html'.freeze
7
+ DEFAULT_TEMPLATE = 'onepage'.freeze
8
+ DEFAULT_THEME = 'light'.freeze
9
+
10
+ def self.list_file_names(dir, ext)
11
+ data_files(dir, "*#{ext}").map do |file|
12
+ File.basename(file, ext)
13
+ end
14
+ end
15
+
16
+ def self.find_file(dir, ext, name)
17
+ if name.include? '/'
18
+ File.absolute_path name
19
+ else
20
+ data_files(dir, "*#{ext}").each do |file|
21
+ return file if File.basename(file, ext) == name
22
+ end
23
+ end
24
+ end
25
+
26
+ def self.data_files(subdir, pattern)
27
+ files = []
28
+
29
+ data_dirs(subdir).each do |dir|
30
+ pattern = File.join dir, pattern
31
+
32
+ Dir[pattern].sort.map do |file|
33
+ files << file
34
+ end
35
+ end
36
+
37
+ files.uniq
38
+ end
39
+
40
+ def self.data_dirs(subdir)
41
+ gemdirs = [
42
+ Pathname.new(File.join(File.dirname(__FILE__), '../../..')).cleanpath.to_s
43
+ ]
44
+ Gem::Specification.each do |spec|
45
+ gemdirs << spec.full_gem_path
46
+ end
47
+
48
+ datadirs = gemdirs.map do |dir|
49
+ File.join dir, 'data', 'rdoc-generator-solarfish', subdir
50
+ end
51
+ datadirs = datadirs.select do |dir|
52
+ File.exist? dir
53
+ end
54
+
55
+ datadirs.uniq
56
+ end
57
+ end
@@ -0,0 +1,135 @@
1
+ require 'rdoc/rdoc'
2
+
3
+ require_relative 'settings'
4
+ require_relative 'doc_loader'
5
+ require_relative 'theme_loader'
6
+ require_relative 'template_loader'
7
+ require_relative 'json_writer'
8
+ require_relative 'html_writer'
9
+
10
+ # SolarFish generator options.
11
+ class RDoc::Options
12
+ attr_accessor :sf_htmlfile
13
+ attr_accessor :sf_jsonfile
14
+ attr_accessor :sf_prefix
15
+ attr_accessor :sf_template
16
+ attr_accessor :sf_themes
17
+ attr_accessor :sf_filter_classes
18
+ attr_accessor :sf_filter_members
19
+ end
20
+
21
+ # SolarFish generator.
22
+ # Registers command line options and generates HTML and/or JSON output for given
23
+ # RDoc documentation and options.
24
+ class RDoc::Generator::SolarFish
25
+ RDoc::RDoc.add_generator(self)
26
+
27
+ def self.setup_options(rdoc_options)
28
+ rdoc_options.sf_htmlfile = Settings::DEFAULT_HTMLFILE
29
+
30
+ opt = rdoc_options.option_parser
31
+ opt.separator 'SolarFish generator options:'
32
+
33
+ opt.separator nil
34
+ opt.on('--sf-htmlfile=FILE', String,
35
+ 'Set output HTML file name.',
36
+ "Defaults to '#{Settings::DEFAULT_HTMLFILE}'.") do |value|
37
+ rdoc_options.sf_htmlfile = value
38
+ end
39
+
40
+ opt.separator nil
41
+ opt.on('--sf-jsonfile=FILE', String,
42
+ 'Set output JSON file name.',
43
+ 'Empty by default.') do |value|
44
+ rdoc_options.sf_jsonfile = value
45
+ end
46
+
47
+ opt.separator nil
48
+ opt.on('--sf-template=NAME', String,
49
+ "Set template. Defaults to '#{Settings::DEFAULT_TEMPLATE}'.",
50
+ "If name contains slash, it's a path, and",
51
+ "otherwise it's a name of installed template.",
52
+ 'Installed templates:',
53
+ *(TemplateLoader.templates_list
54
+ .map { |s| " - #{s}" })) do |value|
55
+ rdoc_options.sf_template = TemplateLoader.template_path(value)
56
+ end
57
+
58
+ opt.separator nil
59
+ opt.on('--sf-theme=NAME', String,
60
+ "Set theme. Defaults to '#{Settings::DEFAULT_THEME}'. Specify",
61
+ 'multiple times to merge several themes. Every',
62
+ 'next theme overwrites options set by previous',
63
+ "themes. If name contains slash, it's a path,",
64
+ "and otherwise it's a name of installed theme.",
65
+ 'Installed themes:',
66
+ *(ThemeLoader.themes_list
67
+ .map { |s| " - #{s}" })) do |value|
68
+ rdoc_options.sf_themes ||= []
69
+ rdoc_options.sf_themes << ThemeLoader.theme_path(value)
70
+ end
71
+
72
+ opt.separator nil
73
+ opt.on('--sf-prefix=PREFIX', String,
74
+ 'Set URL prefix for links to stylesheets and',
75
+ 'scripts in generated HTML. Empty by default.') do |value|
76
+ rdoc_options.sf_prefix = value
77
+ end
78
+
79
+ opt.separator nil
80
+ opt.on('--sf-filter-classes=REGEX', String,
81
+ 'Include only classes and modules that',
82
+ 'match regex.') do |value|
83
+ rdoc_options.sf_filter_classes = Regexp.new(value)
84
+ end
85
+
86
+ opt.separator nil
87
+ opt.on('--sf-filter-members=REGEX', String,
88
+ 'Include only members that match regex.') do |value|
89
+ rdoc_options.sf_filter_members = Regexp.new(value)
90
+ end
91
+ end
92
+
93
+ def initialize(store, options)
94
+ @store = store
95
+ @options = options
96
+ end
97
+
98
+ def class_dir
99
+ nil
100
+ end
101
+
102
+ def file_dir
103
+ nil
104
+ end
105
+
106
+ def generate
107
+ @options.sf_themes ||= [ThemeLoader.theme_path(Settings::DEFAULT_THEME)]
108
+ @options.sf_template ||= TemplateLoader.template_path(Settings::DEFAULT_TEMPLATE)
109
+
110
+ doc_loader = DocLoader.new(@options, @store)
111
+ classes = doc_loader.load
112
+
113
+ theme_loader = ThemeLoader.new(@options)
114
+ theme, theme_files = theme_loader.load
115
+
116
+ data = {
117
+ title: @options.title,
118
+ theme: theme,
119
+ classes: classes
120
+ }
121
+
122
+ if @options.sf_jsonfile
123
+ json_writer = JSONWriter.new(@options)
124
+ json_writer.write(data)
125
+ end
126
+
127
+ if @options.sf_htmlfile
128
+ template_loader = TemplateLoader.new(@options)
129
+ template = template_loader.load
130
+
131
+ html_writer = HTMLWriter.new(@options)
132
+ html_writer.write(data, theme_files, template)
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,42 @@
1
+ require 'slim'
2
+ require 'recursive-open-struct'
3
+
4
+ require_relative 'settings'
5
+
6
+ # TemplateLoader reads a template from `.slim' file and builds a Template object
7
+ # that may render HTML given a hash with documentation and theme.
8
+ class TemplateLoader
9
+ def self.templates_list
10
+ Settings.list_file_names 'templates', '.slim'
11
+ end
12
+
13
+ def self.template_path(name)
14
+ Settings.find_file 'templates', '.slim', name
15
+ end
16
+
17
+ def initialize(options)
18
+ @options = options
19
+ end
20
+
21
+ def load
22
+ Template.new @options.sf_template
23
+ end
24
+ end
25
+
26
+ # Template allows to render HTML from a given hash with documentation and theme.
27
+ class Template
28
+ def initialize(path)
29
+ @path = path
30
+ end
31
+
32
+ def render(data)
33
+ opts = {
34
+ pretty: true
35
+ }
36
+
37
+ vars = RecursiveOpenStruct.new(data, recurse_over_arrays: true)
38
+
39
+ template = Slim::Template.new(@path, opts)
40
+ template.render(vars)
41
+ end
42
+ end