darkfish-rdoc 1.1.1

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,4 @@
1
+
2
+ task :coverage do
3
+ trace "No coverage generated"
4
+ end
@@ -0,0 +1,26 @@
1
+ # Bootstrap file for the Darkfish RDoc generator -- since RDoc doesn't
2
+ # know about gems, this file is necessary to get things loaded before RDoc
3
+ # checks to see what generators it knows about.
4
+
5
+ gem 'rdoc', '>= 2.0.0'
6
+
7
+ require 'pathname'
8
+ require 'rdoc/rdoc' unless defined?( RDoc ) && defined?( RDoc::RDoc )
9
+
10
+ ### Version for the Rakefile (update this in rdoc/generator/darkfish.rb too)
11
+ # VERSION = '1.1.1'
12
+
13
+ begin
14
+ generator_dir = Pathname.new( __FILE__ ).dirname + 'rdoc/generator'
15
+
16
+ # Add the darkfish generator to the ones RDoc knows about
17
+ generator = RDoc::RDoc::Generator.new(
18
+ generator_dir + 'darkfish.rb',
19
+ :Darkfish,
20
+ 'darkfish'
21
+ )
22
+
23
+ $stderr.puts "Adding 'darkfish' as an RDoc generator type" if $DEBUG
24
+ RDoc::RDoc::GENERATORS[ 'darkfish' ] = generator
25
+ end
26
+
@@ -0,0 +1,456 @@
1
+ #!ruby
2
+ #
3
+ # Darkfish RDoc HTML Generator
4
+ # $Id: darkfish.rb 18 2008-08-07 06:56:40Z deveiant $
5
+ #
6
+ # Author: Michael Granger <ged@FaerieMUD.org>
7
+ #
8
+ # == License
9
+ #
10
+ # Copyright (c) 2007, 2008, The FaerieMUD Consortium
11
+ # All rights reserved.
12
+ #
13
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
14
+ # of this software and associated documentation files (the "Software"), to deal
15
+ # in the Software without restriction, including without limitation the rights
16
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ # copies of the Software, and to permit persons to whom the Software is
18
+ # furnished to do so, subject to the following conditions:
19
+ #
20
+ # The above copyright notice and this permission notice shall be included in
21
+ # all copies or substantial portions of the Software.
22
+ #
23
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
29
+ # THE SOFTWARE.
30
+ #
31
+
32
+
33
+ require 'rubygems'
34
+ gem 'rdoc', '>= 2.0.0'
35
+
36
+ require 'pp'
37
+ require 'pathname'
38
+ require 'fileutils'
39
+ require 'erb'
40
+ require 'yaml'
41
+
42
+ require 'rdoc/rdoc'
43
+ require 'rdoc/generator/xml'
44
+ require 'rdoc/generator/html'
45
+
46
+ ### A erb-based RDoc HTML generator
47
+ class RDoc::Generator::Darkfish < RDoc::Generator::XML
48
+ include ERB::Util
49
+
50
+ # Subversion rev
51
+ SVNRev = %$Rev: 18 $
52
+
53
+ # Subversion ID
54
+ SVNId = %$Id: darkfish.rb 18 2008-08-07 06:56:40Z deveiant $
55
+
56
+ # Path to this file's parent directory. Used to find templates and other
57
+ # resources.
58
+ GENERATOR_DIR = Pathname.new( __FILE__ ).expand_path.dirname
59
+
60
+ # Darkfish Version (update this in )
61
+ VERSION = '1.1.1'
62
+
63
+
64
+ ### Standard generator factory method
65
+ def self::for( options )
66
+ new( options )
67
+ end
68
+
69
+
70
+ ### Initialize a few instance variables before we start
71
+ def initialize( *args )
72
+ @template = nil
73
+ @template_dir = GENERATOR_DIR + 'template/darkfish'
74
+
75
+ @files = []
76
+ @classes = []
77
+ @hyperlinks = {}
78
+
79
+ @basedir = Pathname.pwd.expand_path
80
+
81
+ super
82
+ end
83
+
84
+
85
+ ### Output progress information if debugging is enabled
86
+ def debug_msg( *msg )
87
+ return unless $DEBUG
88
+ $stderr.puts( *msg )
89
+ end
90
+
91
+
92
+ ### Create the directories the generated docs will live in if
93
+ ### they don't already exist.
94
+ def gen_sub_directories
95
+ @outputdir.mkpath
96
+ end
97
+
98
+
99
+ ### Copy over the stylesheet into the appropriate place in the
100
+ ### output directory.
101
+ def write_style_sheet
102
+ debug_msg "Copying over static files"
103
+ staticfiles = %w[rdoc.css js images]
104
+ staticfiles.each do |path|
105
+ FileUtils.cp_r( @template_dir + path, '.', :verbose => $DEBUG )
106
+ end
107
+ end
108
+
109
+
110
+
111
+ ### Build the initial indices and output objects
112
+ ### based on an array of TopLevel objects containing
113
+ ### the extracted information.
114
+ def generate( toplevels )
115
+ @outputdir = Pathname.new( @options.op_dir ).expand_path( @basedir )
116
+ @files, @classes = RDoc::Generator::Context.build_indicies( toplevels, @options )
117
+
118
+ # Now actually write the output
119
+ generate_xhtml( @options, @files, @classes )
120
+
121
+ rescue StandardError => err
122
+ debug_msg "%s: %s\n %s" % [ err.class.name, err.message, err.backtrace.join("\n ") ]
123
+ end
124
+
125
+
126
+ ### No-opped
127
+ def load_html_template # :nodoc:
128
+ end
129
+
130
+
131
+ ### Generate output
132
+ def generate_xhtml( options, files, classes )
133
+ files = gen_into( @files )
134
+ classes = gen_into( @classes )
135
+
136
+ # Make a hash of class info keyed by class name
137
+ classes_by_classname = classes.inject({}) {|hash, classinfo|
138
+ hash[ classinfo['full_name'] ] = classinfo
139
+ hash[ classinfo['full_name'] ][:outfile] =
140
+ classinfo['full_name'].gsub( /::/, '/' ) + '.html'
141
+ hash
142
+ }
143
+
144
+ # Make a hash of
145
+ files_by_path = files.inject({}) {|hash, fileinfo|
146
+ hash[ fileinfo['full_path'] ] = fileinfo
147
+ hash[ fileinfo['full_path'] ][:outfile] =
148
+ fileinfo['full_path'] + '.html'
149
+ hash
150
+ }
151
+
152
+ self.write_style_sheet
153
+ self.generate_index( options, files_by_path, classes_by_classname )
154
+ self.generate_class_files( options, files_by_path, classes_by_classname )
155
+ self.generate_file_files( options, files_by_path, classes_by_classname )
156
+ end
157
+
158
+
159
+
160
+ #########
161
+ protected
162
+ #########
163
+
164
+ ### Return a list of the documented modules sorted by salience first, then by name.
165
+ def get_sorted_module_list( classes )
166
+ nscounts = classes.keys.inject({}) do |counthash, name|
167
+ toplevel = name.gsub( /::.*/, '' )
168
+ counthash[toplevel] ||= 0
169
+ counthash[toplevel] += 1
170
+
171
+ counthash
172
+ end
173
+
174
+ # Sort based on how often the toplevel namespace occurs, and then on the name
175
+ # of the module -- this works for projects that put their stuff into a
176
+ # namespace, of course, but doesn't hurt if they don't.
177
+ return classes.keys.sort_by do |name|
178
+ toplevel = name.gsub( /::.*/, '' )
179
+ [
180
+ nscounts[ toplevel ] * -1,
181
+ name
182
+ ]
183
+ end
184
+ end
185
+
186
+
187
+ ### Generate an index page which lists all the classes which
188
+ ### are documented.
189
+ def generate_index( options, files, classes )
190
+ debug_msg "Rendering the index page..."
191
+
192
+ templatefile = @template_dir + 'index.rhtml'
193
+ template_src = templatefile.read
194
+ template = ERB.new( template_src, nil, '<>' )
195
+ template.filename = templatefile.to_s
196
+ context = binding()
197
+
198
+ modsort = self.get_sorted_module_list( classes )
199
+ output = nil
200
+ begin
201
+ output = template.result( context )
202
+ rescue NoMethodError => err
203
+ raise "Error while evaluating %s: %s (at %p)" % [
204
+ templatefile,
205
+ err.message,
206
+ eval( "_erbout[-50,50]", context )
207
+ ]
208
+ end
209
+
210
+ outfile = @basedir + @options.op_dir + 'index.html'
211
+ unless $dryrun
212
+ debug_msg "Outputting to %s" % [outfile.expand_path]
213
+ outfile.open( 'w', 0644 ) do |fh|
214
+ fh.print( output )
215
+ end
216
+ else
217
+ debug_msg "Would have output to %s" % [outfile.expand_path]
218
+ end
219
+ end
220
+
221
+
222
+
223
+ ### Generate a documentation file for each class present in the
224
+ ### given hash of +classes+.
225
+ def generate_class_files( options, files, classes )
226
+ debug_msg "Generating class documentation in #@outputdir"
227
+ templatefile = @template_dir + 'classpage.rhtml'
228
+ outputdir = @outputdir
229
+
230
+ modsort = self.get_sorted_module_list( classes )
231
+
232
+ classes.sort_by {|k,v| k }.each do |classname, classinfo|
233
+ debug_msg " working on %s (%s)" % [ classname, classinfo[:outfile] ]
234
+ outfile = outputdir + classinfo[:outfile]
235
+ rel_prefix = outputdir.relative_path_from( outfile.dirname )
236
+ svninfo = self.get_svninfo( classinfo )
237
+
238
+ self.render_template( templatefile, binding(), outfile )
239
+ end
240
+ end
241
+
242
+
243
+ ### Generate a documentation file for each file present in the
244
+ ### given hash of +files+.
245
+ def generate_file_files( options, files, classes )
246
+ debug_msg "Generating file documentation in #@outputdir"
247
+ templatefile = @template_dir + 'filepage.rhtml'
248
+
249
+ files.sort_by {|k,v| k }.each do |path, fileinfo|
250
+ outfile = @outputdir + fileinfo[:outfile]
251
+ debug_msg " working on %s (%s)" % [ path, outfile ]
252
+ rel_prefix = @outputdir.relative_path_from( outfile.dirname )
253
+ context = binding()
254
+
255
+ debug_msg " rending #{outfile}"
256
+ self.render_template( templatefile, binding(), outfile )
257
+ end
258
+ end
259
+
260
+
261
+ ### Return a string describing the amount of time in the given number of
262
+ ### seconds in terms a human can understand easily.
263
+ def time_delta_string( seconds )
264
+ return 'less than a minute' if seconds < 1.minute
265
+ return (seconds / 1.minute).to_s + ' minute' + (seconds/60 == 1 ? '' : 's') if seconds < 50.minutes
266
+ return 'about one hour' if seconds < 90.minutes
267
+ return (seconds / 1.hour).to_s + ' hours' if seconds < 18.hours
268
+ return 'one day' if seconds < 1.day
269
+ return 'about one day' if seconds < 2.days
270
+ return (seconds / 1.day).to_s + ' days' if seconds < 1.week
271
+ return 'about one week' if seconds < 2.week
272
+ return (seconds / 1.week).to_s + ' weeks' if seconds < 3.months
273
+ return (seconds / 1.month).to_s + ' months' if seconds < 1.year
274
+ return (seconds / 1.year).to_s + ' years'
275
+ end
276
+
277
+
278
+ # %q$Id: darkfish.rb 18 2008-08-07 06:56:40Z deveiant $"
279
+ SVNID_PATTERN = /
280
+ \$Id:\s
281
+ (\S+)\s # filename
282
+ (\d+)\s # rev
283
+ (\d{4}-\d{2}-\d{2})\s # Date (YYYY-MM-DD)
284
+ (\d{2}:\d{2}:\d{2}Z)\s # Time (HH:MM:SSZ)
285
+ (\w+)\s # committer
286
+ \$$
287
+ /x
288
+
289
+ ### Try to extract Subversion information out of the first constant whose value looks like
290
+ ### a subversion Id tag. If no matching constant is found, and empty hash is returned.
291
+ def get_svninfo( classinfo )
292
+ return {} unless classinfo['sections']
293
+ constants = classinfo['sections'].first['constants'] or return {}
294
+
295
+ constants.find {|c| c['value'] =~ SVNID_PATTERN } or return {}
296
+
297
+ filename, rev, date, time, committer = $~.captures
298
+ commitdate = Time.parse( date + ' ' + time )
299
+
300
+ return {
301
+ :filename => filename,
302
+ :rev => Integer( rev ),
303
+ :commitdate => commitdate,
304
+ :commitdelta => time_delta_string( Time.now.to_i - commitdate.to_i ),
305
+ :committer => committer,
306
+ }
307
+ end
308
+
309
+
310
+ ### Load and render the erb template in the given +templatefile+ within the specified
311
+ ### +context+ (a Binding object) and write it out to +outfile+. Both +templatefile+ and
312
+ ### +outfile+ should be Pathname-like objects.
313
+ def render_template( templatefile, context, outfile )
314
+ template_src = templatefile.read
315
+ template = ERB.new( template_src, nil, '<>' )
316
+ template.filename = templatefile.to_s
317
+
318
+ output = begin
319
+ template.result( context )
320
+ rescue NoMethodError => err
321
+ raise "Error while evaluating %s: %s (at %p)" % [
322
+ templatefile.to_s,
323
+ err.message,
324
+ eval( "_erbout[-50,50]", context )
325
+ ]
326
+ end
327
+
328
+ unless $dryrun
329
+ outfile.dirname.mkpath
330
+ outfile.open( 'w', 0644 ) do |ofh|
331
+ ofh.print( output )
332
+ end
333
+ else
334
+ debug_msg " would have written %d bytes to %s" %
335
+ [ output.length, outfile ]
336
+ end
337
+ end
338
+
339
+ end # Roc::Generator::Darkfish
340
+
341
+ # Silly alias for RDoc's silly upcased 'class_name'
342
+ RDoc::Generator::DARKFISH = RDoc::Generator::Darkfish
343
+
344
+
345
+ # :stopdoc:
346
+
347
+ ### Time constants
348
+ module TimeConstantMethods # :nodoc:
349
+
350
+ ### Number of seconds (returns receiver unmodified)
351
+ def seconds
352
+ return self
353
+ end
354
+ alias_method :second, :seconds
355
+
356
+ ### Returns number of seconds in <receiver> minutes
357
+ def minutes
358
+ return self * 60
359
+ end
360
+ alias_method :minute, :minutes
361
+
362
+ ### Returns the number of seconds in <receiver> hours
363
+ def hours
364
+ return self * 60.minutes
365
+ end
366
+ alias_method :hour, :hours
367
+
368
+ ### Returns the number of seconds in <receiver> days
369
+ def days
370
+ return self * 24.hours
371
+ end
372
+ alias_method :day, :days
373
+
374
+ ### Return the number of seconds in <receiver> weeks
375
+ def weeks
376
+ return self * 7.days
377
+ end
378
+ alias_method :week, :weeks
379
+
380
+ ### Returns the number of seconds in <receiver> fortnights
381
+ def fortnights
382
+ return self * 2.weeks
383
+ end
384
+ alias_method :fortnight, :fortnights
385
+
386
+ ### Returns the number of seconds in <receiver> months (approximate)
387
+ def months
388
+ return self * 30.days
389
+ end
390
+ alias_method :month, :months
391
+
392
+ ### Returns the number of seconds in <receiver> years (approximate)
393
+ def years
394
+ return (self * 365.25.days).to_i
395
+ end
396
+ alias_method :year, :years
397
+
398
+
399
+ ### Returns the Time <receiver> number of seconds before the
400
+ ### specified +time+. E.g., 2.hours.before( header.expiration )
401
+ def before( time )
402
+ return time - self
403
+ end
404
+
405
+
406
+ ### Returns the Time <receiver> number of seconds ago. (e.g.,
407
+ ### expiration > 2.hours.ago )
408
+ def ago
409
+ return self.before( ::Time.now )
410
+ end
411
+
412
+
413
+ ### Returns the Time <receiver> number of seconds after the given +time+.
414
+ ### E.g., 10.minutes.after( header.expiration )
415
+ def after( time )
416
+ return time + self
417
+ end
418
+
419
+ # Reads best without arguments: 10.minutes.from_now
420
+ def from_now
421
+ return self.after( ::Time.now )
422
+ end
423
+ end # module TimeConstantMethods
424
+
425
+
426
+ # Extend Numeric with time constants
427
+ class Numeric # :nodoc:
428
+ include TimeConstantMethods
429
+ end
430
+
431
+
432
+ ### Monkeypatch RDoc::Generator::Method so it works with line numbers
433
+ ### turned on and $DEBUG = true. Also make it use a conditional instead
434
+ ### of a side-effect to get the initial blank line.
435
+ class RDoc::Generator::Method # :nodoc:
436
+ def add_line_numbers(src)
437
+ if src =~ /\A.*, line (\d+)/
438
+ first = $1.to_i - 1
439
+ last = first + src.count("\n")
440
+ size = last.to_s.length
441
+
442
+ line = first
443
+ src.gsub!(/^/) do
444
+ if line == first
445
+ res = " " * ( size + 2 )
446
+ else
447
+ res = sprintf( "%#{size}d: ", line )
448
+ end
449
+
450
+ line += 1
451
+ res
452
+ end
453
+ end
454
+ end
455
+ end
456
+