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.
@@ -0,0 +1,7 @@
1
+ # -*- ruby -*-
2
+
3
+ $stderr.puts "Discovered Darkfish!" if $DEBUG
4
+
5
+ # RDoc plugin hook
6
+ require 'sixfish'
7
+
@@ -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
+