darkfish-rdoc 1.1.1

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