rdoc-generator-sixfish 0.5.0.pre20180710102525
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.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +1 -0
- data.tar.gz.sig +3 -0
- data/.editorconfig +16 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/ChangeLog +675 -0
- data/History.md +39 -0
- data/Manifest.txt +29 -0
- data/README.md +78 -0
- data/data/rdoc-generator-sixfish/css/sixfish.css +1184 -0
- data/data/rdoc-generator-sixfish/css/sixfish.css.map +7 -0
- data/data/rdoc-generator-sixfish/fa/light.js +0 -0
- data/data/rdoc-generator-sixfish/fa/light.svg +0 -0
- data/data/rdoc-generator-sixfish/fa/loader.js +1 -0
- data/data/rdoc-generator-sixfish/fa/regular.js +1 -0
- data/data/rdoc-generator-sixfish/fa/regular.svg +662 -0
- data/data/rdoc-generator-sixfish/fa/solid.js +1 -0
- data/data/rdoc-generator-sixfish/fa/solid.svg +662 -0
- data/data/rdoc-generator-sixfish/images/glyphicons-28-search.png +0 -0
- data/data/rdoc-generator-sixfish/js/jquery-3.1.1.js +10220 -0
- data/data/rdoc-generator-sixfish/js/sixfish.min.js +99 -0
- data/data/rdoc-generator-sixfish/templates/class.tmpl +212 -0
- data/data/rdoc-generator-sixfish/templates/file.tmpl +20 -0
- data/data/rdoc-generator-sixfish/templates/index.tmpl +54 -0
- data/data/rdoc-generator-sixfish/templates/layout.tmpl +77 -0
- data/lib/rdoc/discover.rb +7 -0
- data/lib/rdoc/generator/sixfish.rb +401 -0
- data/lib/sixfish.rb +30 -0
- data/spec/helpers.rb +45 -0
- data/spec/rdoc/generator/sixfish_spec.rb +250 -0
- data/spec/sixfish_spec.rb +23 -0
- metadata +265 -0
- metadata.gz.sig +0 -0
@@ -0,0 +1,401 @@
|
|
1
|
+
# -*- mode: ruby; ruby-indent-level: 4; tab-width: 4 -*-
|
2
|
+
|
3
|
+
gem 'rdoc'
|
4
|
+
|
5
|
+
require 'uri'
|
6
|
+
require 'yajl'
|
7
|
+
require 'inversion'
|
8
|
+
require 'loggability'
|
9
|
+
require 'fileutils'
|
10
|
+
require 'pathname'
|
11
|
+
require 'rdoc/rdoc'
|
12
|
+
require 'rdoc/generator/json_index'
|
13
|
+
|
14
|
+
# The Sixfish generator class.
|
15
|
+
class RDoc::Generator::Sixfish
|
16
|
+
extend Loggability
|
17
|
+
include FileUtils
|
18
|
+
|
19
|
+
|
20
|
+
# Loggability API -- set up a Logger for Sixfish
|
21
|
+
log_as :sixfish
|
22
|
+
|
23
|
+
|
24
|
+
# The data directory in the project if that exists, otherwise the gem datadir
|
25
|
+
DATADIR = if ENV['SIXFISH_DATADIR']
|
26
|
+
Pathname( ENV['SIXFISH_DATADIR'] ).expand_path( Pathname.pwd )
|
27
|
+
elsif File.directory?( 'data/rdoc-generator-sixfish' )
|
28
|
+
Pathname( 'data/rdoc-generator-sixfish' ).expand_path( Pathname.pwd )
|
29
|
+
elsif path = Gem.latest_spec_for( 'rdoc-generator-sixfish' )&.datadir
|
30
|
+
Pathname( path )
|
31
|
+
else
|
32
|
+
raise ScriptError, "can't find the data directory!"
|
33
|
+
end
|
34
|
+
|
35
|
+
# Register with RDoc as an alternative generator
|
36
|
+
RDoc::RDoc.add_generator( self )
|
37
|
+
|
38
|
+
|
39
|
+
### Add generator-specific options to the option-parser
|
40
|
+
def self::setup_options( rdoc_options )
|
41
|
+
op = rdoc_options.option_parser
|
42
|
+
|
43
|
+
op.accept( URI ) do |string|
|
44
|
+
uri = URI.parse( string ) rescue nil
|
45
|
+
raise OptionParser::InvalidArgument unless uri
|
46
|
+
uri
|
47
|
+
end
|
48
|
+
op.on( '--additional-stylesheet=URL', URI,
|
49
|
+
"Add an additional (preferred) stylesheet",
|
50
|
+
"link to each generated page. This allows",
|
51
|
+
"the output style to be overridden." ) do |url|
|
52
|
+
rdoc_options.additional_stylesheet = url
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
### Set up some instance variables
|
58
|
+
def initialize( store, options )
|
59
|
+
@store = store
|
60
|
+
@options = options
|
61
|
+
$DEBUG_RDOC = $VERBOSE || $DEBUG
|
62
|
+
|
63
|
+
self.log.debug "Setting up generator for %p with options: %p" % [ @store, @options ]
|
64
|
+
|
65
|
+
extend( FileUtils::Verbose ) if $DEBUG_RDOC
|
66
|
+
extend( FileUtils::DryRun ) if options.dry_run
|
67
|
+
|
68
|
+
@base_dir = Pathname.pwd.expand_path
|
69
|
+
@template_dir = DATADIR
|
70
|
+
@output_dir = Pathname( @options.op_dir ).expand_path( @base_dir )
|
71
|
+
|
72
|
+
@template_cache = {}
|
73
|
+
@files = nil
|
74
|
+
@classes = nil
|
75
|
+
@search_index = {}
|
76
|
+
|
77
|
+
Inversion::Template.configure( :template_paths => [self.template_dir + 'templates'] )
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
######
|
82
|
+
public
|
83
|
+
######
|
84
|
+
|
85
|
+
# The base directory (current working directory) as a Pathname
|
86
|
+
attr_reader :base_dir
|
87
|
+
|
88
|
+
# The directory containing templates as a Pathname
|
89
|
+
attr_reader :template_dir
|
90
|
+
|
91
|
+
# The output directory as a Pathname
|
92
|
+
attr_reader :output_dir
|
93
|
+
|
94
|
+
# The command-line options given to the rdoc command
|
95
|
+
attr_reader :options
|
96
|
+
|
97
|
+
# The RDoc::Store that contains the parsed CodeObjects
|
98
|
+
attr_reader :store
|
99
|
+
|
100
|
+
|
101
|
+
### Output progress information if debugging is enabled
|
102
|
+
def debug_msg( *msg )
|
103
|
+
return unless $DEBUG_RDOC
|
104
|
+
$stderr.puts( *msg )
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
### Backward-compatible (no-op) method.
|
109
|
+
def class_dir # :nodoc:
|
110
|
+
nil
|
111
|
+
end
|
112
|
+
alias_method :file_dir, :class_dir
|
113
|
+
|
114
|
+
|
115
|
+
### Create the directories the generated docs will live in if they don't
|
116
|
+
### already exist.
|
117
|
+
def gen_sub_directories
|
118
|
+
self.output_dir.mkpath
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
### Build the initial indices and output objects based on the files in the generator's store.
|
123
|
+
def generate
|
124
|
+
self.populate_data_objects
|
125
|
+
|
126
|
+
self.generate_index_page
|
127
|
+
self.generate_class_files
|
128
|
+
self.generate_file_files
|
129
|
+
|
130
|
+
self.generate_search_index
|
131
|
+
|
132
|
+
self.copy_static_assets
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
### Populate the data objects necessary to generate documentation from the generator's
|
137
|
+
### store.
|
138
|
+
def populate_data_objects
|
139
|
+
@files = self.store.all_files.sort
|
140
|
+
@classes = self.store.all_classes_and_modules.sort
|
141
|
+
@methods = @classes.map {|m| m.method_list }.flatten.sort
|
142
|
+
@modsort = self.get_sorted_module_list( @classes )
|
143
|
+
end
|
144
|
+
|
145
|
+
|
146
|
+
### Generate an index page which lists all the classes which are documented.
|
147
|
+
def generate_index_page
|
148
|
+
self.log.debug "Generating index page"
|
149
|
+
layout = self.load_layout_template
|
150
|
+
template = self.load_template( 'index.tmpl' )
|
151
|
+
out_file = self.output_dir + 'index.html'
|
152
|
+
out_file.dirname.mkpath
|
153
|
+
|
154
|
+
mainpage = nil
|
155
|
+
if mpname = self.options.main_page
|
156
|
+
mainpage = @files.find {|f| f.full_name == mpname }
|
157
|
+
else
|
158
|
+
mainpage = @files.find {|f| f.full_name =~ /\breadme\b/i }
|
159
|
+
end
|
160
|
+
self.log.debug " using main_page (%s)" % [ mainpage ]
|
161
|
+
|
162
|
+
if mainpage
|
163
|
+
template.mainpage = mainpage
|
164
|
+
template.synopsis = self.extract_synopsis( mainpage )
|
165
|
+
end
|
166
|
+
|
167
|
+
layout.rel_prefix = self.output_dir.relative_path_from( out_file.dirname )
|
168
|
+
layout.contents = template
|
169
|
+
layout.pageclass = 'index-page'
|
170
|
+
|
171
|
+
out_file.open( 'w', 0644 ) {|io| io.print(layout.render) }
|
172
|
+
end
|
173
|
+
|
174
|
+
|
175
|
+
### Generate a documentation file for each class and module
|
176
|
+
def generate_class_files
|
177
|
+
layout = self.load_layout_template
|
178
|
+
template = self.load_template( 'class.tmpl' )
|
179
|
+
|
180
|
+
self.log.debug "Generating class documentation in #{self.output_dir}"
|
181
|
+
|
182
|
+
@classes.each do |klass|
|
183
|
+
self.log.debug " working on %s (%s)" % [klass.full_name, klass.path]
|
184
|
+
|
185
|
+
out_file = self.output_dir + klass.path
|
186
|
+
out_file.dirname.mkpath
|
187
|
+
|
188
|
+
template.klass = klass
|
189
|
+
|
190
|
+
layout.contents = template
|
191
|
+
layout.rel_prefix = self.output_dir.relative_path_from( out_file.dirname )
|
192
|
+
layout.pageclass = 'class-page'
|
193
|
+
|
194
|
+
out_file.open( 'w', 0644 ) {|io| io.print(layout.render) }
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
|
199
|
+
### Generate a documentation file for each file
|
200
|
+
def generate_file_files
|
201
|
+
layout = self.load_layout_template
|
202
|
+
template = self.load_template( 'file.tmpl' )
|
203
|
+
|
204
|
+
self.log.debug "Generating file documentation in #{self.output_dir}"
|
205
|
+
|
206
|
+
@files.select {|f| f.text? }.each do |file|
|
207
|
+
out_file = self.output_dir + file.path
|
208
|
+
out_file.dirname.mkpath
|
209
|
+
|
210
|
+
self.log.debug " working on %s (%s)" % [file.full_name, out_file]
|
211
|
+
|
212
|
+
template.file = file
|
213
|
+
|
214
|
+
# If the page itself has an H1, use it for the header, otherwise make one
|
215
|
+
# out of the name of the file
|
216
|
+
if md = file.description.match( %r{<h1.*?>.*?</h1>}i )
|
217
|
+
template.header = md[ 0 ]
|
218
|
+
template.description = file.description[ md.offset(0)[1] + 1 .. -1 ]
|
219
|
+
else
|
220
|
+
template.header = File.basename( file.full_name, File.extname(file.full_name) )
|
221
|
+
template.description = file.description
|
222
|
+
end
|
223
|
+
|
224
|
+
layout.contents = template
|
225
|
+
layout.rel_prefix = self.output_dir.relative_path_from(out_file.dirname)
|
226
|
+
layout.pageclass = 'file-page'
|
227
|
+
|
228
|
+
out_file.open( 'w', 0644 ) {|io| io.print(layout.render) }
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
|
233
|
+
### Generate a JSON search index for the quicksearch blank.
|
234
|
+
def generate_search_index
|
235
|
+
out_file = self.output_dir + 'js/searchindex.json'
|
236
|
+
|
237
|
+
self.log.debug "Generating search index (%s)." % [ out_file ]
|
238
|
+
index = []
|
239
|
+
|
240
|
+
objs = self.get_indexable_objects
|
241
|
+
objs.each do |codeobj|
|
242
|
+
self.log.debug " #{codeobj.name}..."
|
243
|
+
record = codeobj.search_record
|
244
|
+
index << {
|
245
|
+
name: record[2],
|
246
|
+
link: record[4],
|
247
|
+
snippet: record[6],
|
248
|
+
type: codeobj.class.name.downcase.sub( /.*::/, '' )
|
249
|
+
}
|
250
|
+
end
|
251
|
+
|
252
|
+
self.log.debug " dumping JSON..."
|
253
|
+
out_file.dirname.mkpath
|
254
|
+
ofh = out_file.open( 'w:utf-8', 0644 )
|
255
|
+
|
256
|
+
json = Yajl.dump( index, pretty: true, indent: "\t" )
|
257
|
+
|
258
|
+
ofh.puts( 'var SearchIndex = ', json, ';' )
|
259
|
+
end
|
260
|
+
|
261
|
+
|
262
|
+
### Copies static files from the static_path into the output directory
|
263
|
+
def copy_static_assets
|
264
|
+
asset_paths = self.find_static_assets
|
265
|
+
|
266
|
+
self.log.debug "Copying assets from paths: %s" % [ asset_paths.join(', ') ]
|
267
|
+
|
268
|
+
asset_paths.each do |path|
|
269
|
+
|
270
|
+
# For plain files, just install them
|
271
|
+
if path.file?
|
272
|
+
self.log.debug " plain file; installing as-is"
|
273
|
+
install( path, self.output_dir, :mode => 0644 )
|
274
|
+
|
275
|
+
# Glob all the files out of subdirectories and install them
|
276
|
+
elsif path.directory?
|
277
|
+
self.log.debug " directory %p; copying contents" % [ path ]
|
278
|
+
|
279
|
+
Pathname.glob( path + '{css,fa,fonts,img,js}/**/*'.to_s ).each do |asset|
|
280
|
+
next if asset.directory? || asset.basename.to_s.start_with?( '.' )
|
281
|
+
|
282
|
+
dst = asset.relative_path_from( path )
|
283
|
+
dst.dirname.mkpath
|
284
|
+
|
285
|
+
self.log.debug " %p -> %p" % [ asset, dst ]
|
286
|
+
install asset, dst, :mode => 0644
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
|
293
|
+
#########
|
294
|
+
protected
|
295
|
+
#########
|
296
|
+
|
297
|
+
### Return an Array of Pathname objects for each file/directory in the
|
298
|
+
### list of static assets that should be copied into the output directory.
|
299
|
+
def find_static_assets
|
300
|
+
paths = self.options.static_path || []
|
301
|
+
self.log.debug "Finding asset paths. Static paths: %p" % [ paths ]
|
302
|
+
|
303
|
+
# Add each subdirectory of the template dir
|
304
|
+
self.log.debug " adding directories under %s" % [ self.template_dir ]
|
305
|
+
paths << self.template_dir
|
306
|
+
self.log.debug " paths are now: %p" % [ paths ]
|
307
|
+
|
308
|
+
return paths.flatten.compact.uniq
|
309
|
+
end
|
310
|
+
|
311
|
+
|
312
|
+
### Return a list of the documented modules sorted by salience first, then
|
313
|
+
### by name.
|
314
|
+
def get_sorted_module_list(classes)
|
315
|
+
nscounts = classes.inject({}) do |counthash, klass|
|
316
|
+
top_level = klass.full_name.gsub( /::.*/, '' )
|
317
|
+
counthash[top_level] ||= 0
|
318
|
+
counthash[top_level] += 1
|
319
|
+
|
320
|
+
counthash
|
321
|
+
end
|
322
|
+
|
323
|
+
# Sort based on how often the top level namespace occurs, and then on the
|
324
|
+
# name of the module -- this works for projects that put their stuff into
|
325
|
+
# a namespace, of course, but doesn't hurt if they don't.
|
326
|
+
classes.sort_by do |klass|
|
327
|
+
top_level = klass.full_name.gsub( /::.*/, '' )
|
328
|
+
[nscounts[top_level] * -1, klass.full_name]
|
329
|
+
end.select do |klass|
|
330
|
+
klass.display?
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
|
335
|
+
### Fetch the template with the specified +name+ from the cache or load it
|
336
|
+
### and cache it.
|
337
|
+
def load_template( name )
|
338
|
+
unless @template_cache.key?( name )
|
339
|
+
@template_cache[ name ] = Inversion::Template.load( name, encoding:'utf-8' )
|
340
|
+
end
|
341
|
+
|
342
|
+
return @template_cache[ name ].dup
|
343
|
+
end
|
344
|
+
|
345
|
+
|
346
|
+
### Load the layout template and return it after setting any values it needs.
|
347
|
+
def load_layout_template
|
348
|
+
template = self.load_template( 'layout.tmpl' )
|
349
|
+
|
350
|
+
template.files = @files
|
351
|
+
template.classes = @classes
|
352
|
+
template.methods = @methods
|
353
|
+
template.modsort = @modsort
|
354
|
+
template.rdoc_options = @options
|
355
|
+
|
356
|
+
template.rdoc_version = RDoc::VERSION
|
357
|
+
template.sixfish_version = Sixfish.version_string
|
358
|
+
|
359
|
+
return template
|
360
|
+
end
|
361
|
+
|
362
|
+
|
363
|
+
### Return a list of CodeObjects that belong in the index.
|
364
|
+
def get_indexable_objects
|
365
|
+
objs = []
|
366
|
+
|
367
|
+
objs += @classes.select( &:document_self_or_methods ).uniq( &:path )
|
368
|
+
objs += @classes.map( &:method_list ).flatten.uniq( &:path )
|
369
|
+
objs += @files.select( &:text? )
|
370
|
+
|
371
|
+
return objs
|
372
|
+
end
|
373
|
+
|
374
|
+
|
375
|
+
### Extract a synopsis for the project from the specified +mainpage+ and
|
376
|
+
### return it as a String.
|
377
|
+
def extract_synopsis( mainpage )
|
378
|
+
desc = mainpage.description
|
379
|
+
heading = desc[ %r{(<h1.*?/h1>)}im ] || ''
|
380
|
+
paras = desc.scan( %r{<p\b.*?/p>}im )
|
381
|
+
|
382
|
+
first_para = paras.map( &:strip ).find do |para|
|
383
|
+
# Discard paragraphs consisting only of a link
|
384
|
+
!( para.start_with?('<p><a') && para.end_with?('/a></p>') )
|
385
|
+
end
|
386
|
+
|
387
|
+
return heading + first_para
|
388
|
+
end
|
389
|
+
|
390
|
+
end # class RDoc::Generator::Sixfish
|
391
|
+
|
392
|
+
|
393
|
+
# Reopen to add custom option attrs.
|
394
|
+
class RDoc::Options
|
395
|
+
|
396
|
+
##
|
397
|
+
# Allow setting a custom stylesheet
|
398
|
+
attr_accessor :additional_stylesheet
|
399
|
+
|
400
|
+
end
|
401
|
+
|
data/lib/sixfish.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- mode: ruby; ruby-indent-level: 4; tab-width: 4 -*-
|
2
|
+
|
3
|
+
require 'rdoc/rdoc'
|
4
|
+
require 'rdoc/generator/sixfish'
|
5
|
+
|
6
|
+
# :title: Sixfish RDoc
|
7
|
+
#
|
8
|
+
# Toplevel namespace for Sixfish. The main goods are in RDoc::Generator::Sixfish.
|
9
|
+
module Sixfish
|
10
|
+
|
11
|
+
# Library version constant
|
12
|
+
VERSION = '0.4.0'
|
13
|
+
|
14
|
+
# Version-control revision constant
|
15
|
+
REVISION = %q$Revision: bd4700e71546 $
|
16
|
+
|
17
|
+
# Fivefish project URL
|
18
|
+
PROJECT_URL = 'https://bitbucket.com/ged/fivefish'
|
19
|
+
|
20
|
+
|
21
|
+
### Get the library version. If +include_buildnum+ is true, the version string will
|
22
|
+
### include the VCS rev ID.
|
23
|
+
def self::version_string( include_buildnum=false )
|
24
|
+
vstring = "Sixfish RDoc %s" % [ VERSION ]
|
25
|
+
vstring << " (build %s)" % [ REVISION[/: ([[:xdigit:]]+)/, 1] || '0' ] if include_buildnum
|
26
|
+
return vstring
|
27
|
+
end
|
28
|
+
|
29
|
+
end # module Sixfish
|
30
|
+
|
data/spec/helpers.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
# SimpleCov test coverage reporting; enable this using the :coverage rake task
|
5
|
+
if ENV['COVERAGE']
|
6
|
+
$stderr.puts "\n\n>>> Enabling coverage report.\n\n"
|
7
|
+
require 'simplecov'
|
8
|
+
SimpleCov.start do
|
9
|
+
add_filter 'spec'
|
10
|
+
add_group "Needing tests" do |file|
|
11
|
+
file.covered_percent < 90
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
require 'loggability'
|
18
|
+
require 'loggability/spechelpers'
|
19
|
+
|
20
|
+
require 'rspec'
|
21
|
+
require 'sixfish'
|
22
|
+
|
23
|
+
Loggability.format_with( :color ) if $stdout.tty?
|
24
|
+
|
25
|
+
|
26
|
+
### RSpec helper functions.
|
27
|
+
module Sixfish::SpecHelpers
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
### Mock with RSpec
|
32
|
+
RSpec.configure do |config|
|
33
|
+
config.run_all_when_everything_filtered = true
|
34
|
+
config.filter_run :focus
|
35
|
+
config.order = 'random'
|
36
|
+
config.mock_with( :rspec ) do |mock|
|
37
|
+
mock.syntax = :expect
|
38
|
+
end
|
39
|
+
|
40
|
+
config.include( Loggability::SpecHelpers )
|
41
|
+
config.include( Sixfish::SpecHelpers )
|
42
|
+
end
|
43
|
+
|
44
|
+
# vim: set nosta noet ts=4 sw=4:
|
45
|
+
|