starkfish 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +26 -0
  3. data/lib/rdoc/discover.rb +30 -0
  4. data/lib/rdoc/generator/starkfish.rb +523 -0
  5. data/lib/rdoc/generator/template/starkfish/classpage.rhtml +305 -0
  6. data/lib/rdoc/generator/template/starkfish/filepage.rhtml +113 -0
  7. data/lib/rdoc/generator/template/starkfish/images/brick.png +0 -0
  8. data/lib/rdoc/generator/template/starkfish/images/brick_link.png +0 -0
  9. data/lib/rdoc/generator/template/starkfish/images/bug.png +0 -0
  10. data/lib/rdoc/generator/template/starkfish/images/bullet_black.png +0 -0
  11. data/lib/rdoc/generator/template/starkfish/images/bullet_toggle_minus.png +0 -0
  12. data/lib/rdoc/generator/template/starkfish/images/bullet_toggle_plus.png +0 -0
  13. data/lib/rdoc/generator/template/starkfish/images/date.png +0 -0
  14. data/lib/rdoc/generator/template/starkfish/images/find.png +0 -0
  15. data/lib/rdoc/generator/template/starkfish/images/loadingAnimation.gif +0 -0
  16. data/lib/rdoc/generator/template/starkfish/images/macFFBgHack.png +0 -0
  17. data/lib/rdoc/generator/template/starkfish/images/package.png +0 -0
  18. data/lib/rdoc/generator/template/starkfish/images/page_green.png +0 -0
  19. data/lib/rdoc/generator/template/starkfish/images/page_white_text.png +0 -0
  20. data/lib/rdoc/generator/template/starkfish/images/page_white_width.png +0 -0
  21. data/lib/rdoc/generator/template/starkfish/images/plugin.png +0 -0
  22. data/lib/rdoc/generator/template/starkfish/images/ruby.png +0 -0
  23. data/lib/rdoc/generator/template/starkfish/images/tag_green.png +0 -0
  24. data/lib/rdoc/generator/template/starkfish/images/wrench.png +0 -0
  25. data/lib/rdoc/generator/template/starkfish/images/wrench_orange.png +0 -0
  26. data/lib/rdoc/generator/template/starkfish/images/zoom.png +0 -0
  27. data/lib/rdoc/generator/template/starkfish/index.rhtml +57 -0
  28. data/lib/rdoc/generator/template/starkfish/js/darkfish.js +116 -0
  29. data/lib/rdoc/generator/template/starkfish/js/jquery.js +32 -0
  30. data/lib/rdoc/generator/template/starkfish/js/quicksearch.js +114 -0
  31. data/lib/rdoc/generator/template/starkfish/js/thickbox-compressed.js +10 -0
  32. data/lib/rdoc/generator/template/starkfish/rdoc.css +597 -0
  33. data/lib/rdoc/generator/template/starkfish.rhtml +3 -0
  34. metadata +90 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c863cb087e56f1fd6e08386d71a8bb61498a54a5
4
+ data.tar.gz: 8a79e3a89376be3548c65527b570c3a8a8b06a79
5
+ SHA512:
6
+ metadata.gz: 3d65f72e032bafe2a32b215700bd9346b5543a9aa3f610a73d5882fa4f139a9f235693d0d1856a4a7e45f63759adab2f002b3fb2109ab80d44bdf78c104ba694
7
+ data.tar.gz: eb26d3c5cc01b024e19f91403d59b5b879ddd32ccf5dd05754ffbf7e6b65aac5be7f19fb193ee9f4a4ae5775c735aad91a688e9bea47bfd163a33c396e411a1f
data/LICENSE ADDED
@@ -0,0 +1,26 @@
1
+ Copyright (c) 2007-2010, Michael Granger. All rights reserved.
2
+
3
+ Redistribution and use in source and binary forms, with or without
4
+ modification, are permitted provided that the following conditions are met:
5
+
6
+ * Redistributions of source code must retain the above copyright notice,
7
+ this list of conditions and the following disclaimer.
8
+
9
+ * Redistributions in binary form must reproduce the above copyright notice,
10
+ this list of conditions and the following disclaimer in the documentation
11
+ and/or other materials provided with the distribution.
12
+
13
+ * Neither the name of the author/s, nor the names of the project's
14
+ contributors may be used to endorse or promote products derived from this
15
+ software without specific prior written permission.
16
+
17
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
21
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,30 @@
1
+ # Bootstrap file for the Darkfish RDoc generator. Tell RubyGems about Darkfish
2
+ # via autodiscovery.
3
+
4
+ gem 'starkfish'
5
+
6
+ if Gem.respond_to?( :promote_load_path )
7
+ Gem.promote_load_path( 'starkfish', 'rdoc' )
8
+ else
9
+ # move darkfish-rdoc before rdoc in the load path so ours gets picked up first
10
+ # NOTE: This was added as Gem::promote_load_path in RubyGems 1.4
11
+ rdoc = Gem.loaded_specs['rdoc']
12
+ darkfish = Gem.loaded_specs['starkfish']
13
+
14
+ last_darkfish_path = File.join( darkfish.full_gem_path,
15
+ darkfish.require_paths.last )
16
+
17
+ rdoc_paths = rdoc.require_paths.map do |path|
18
+ File.join( rdoc.full_gem_path, path )
19
+ end
20
+
21
+ rdoc_paths.each do |path|
22
+ $LOAD_PATH.delete( path )
23
+ end
24
+
25
+ darkfish_path_index = $LOAD_PATH.index( last_darkfish_path ) + 1
26
+
27
+ $LOAD_PATH.insert( darkfish_path_index, *rdoc_paths )
28
+ end
29
+
30
+ require 'rdoc/generator/starkfish'
@@ -0,0 +1,523 @@
1
+ #!ruby
2
+
3
+ require 'pp'
4
+ require 'pathname'
5
+ require 'fileutils'
6
+ require 'erb'
7
+ require 'yaml'
8
+ require 'enumerator'
9
+
10
+ require 'rdoc/rdoc'
11
+
12
+ DarkfishSuperclass = RDoc::Generator.const_defined?( "XML" ) ? RDoc::Generator::XML : Object
13
+
14
+ #
15
+ # Darkfish RDoc HTML Generator
16
+ #
17
+ # $Id$
18
+ #
19
+ # == Author/s
20
+ # * Michael Granger (ged@FaerieMUD.org)
21
+ #
22
+ # == Contributors
23
+ # * Mahlon E. Smith (mahlon@martini.nu)
24
+ # * Eric Hodel (drbrain@segment7.net)
25
+ #
26
+ # == License
27
+ #
28
+ # :include: LICENSE
29
+ #
30
+ class RDoc::Generator::Darkfish < DarkfishSuperclass
31
+
32
+ RDoc::RDoc.add_generator( self )
33
+
34
+ include ERB::Util
35
+
36
+ # Subversion rev
37
+ SVNRev = %$Rev$
38
+
39
+ # Subversion ID
40
+ SVNId = %$Id$
41
+
42
+ # Path to this file's parent directory. Used to find templates and other
43
+ # resources.
44
+ GENERATOR_DIR = Pathname.new( __FILE__ ).expand_path.dirname
45
+
46
+ # Release Version
47
+ VERSION = '1.1.7'
48
+
49
+ # Directory where generated classes live relative to the root
50
+ CLASS_DIR = nil
51
+
52
+ # Directory where generated files live relative to the root
53
+ FILE_DIR = nil
54
+
55
+ # An array of transforms to run on a method name to derive a suitable
56
+ # anchor name. The pairs are used in pairs as arguments to gsub.
57
+ ANAME_TRANSFORMS = [
58
+ /\?$/, '_p',
59
+ /\!$/, '_bang',
60
+ /=$/, '_eq',
61
+ /^<<$/, '_lshift',
62
+ /^>>$/, '_rshift',
63
+ /\[\]=/, '_aset',
64
+ /\[\]/, '_aref',
65
+ /\*\*/, '_pow',
66
+ /^~$/, '_complement',
67
+ /^!$/, '_bang',
68
+ /^\+@$/, '_uplus',
69
+ /^-@$/, '_uminus',
70
+ /^\+$/, '_add',
71
+ /^-$/, '_sub',
72
+ /^\*$/, '_mult',
73
+ %r{^/$}, '_div',
74
+ /^%$/, '_mod',
75
+ /^<=>$/, '_comp',
76
+ /^==$/, '_equal',
77
+ /^!=$/, '_nequal',
78
+ /^===$/, '_eqq',
79
+ /^>$/, '_gt',
80
+ /^>=$/, '_ge',
81
+ /^<$/, '_lt',
82
+ /^<=$/, '_le',
83
+ /^&$/, '_and',
84
+ /^|$/, '_or',
85
+ /^\^$/, '_xor',
86
+ /^=~$/, '_match',
87
+ /^!~$/, '_notmatch',
88
+ ]
89
+
90
+
91
+ #################################################################
92
+ ### C L A S S M E T H O D S
93
+ #################################################################
94
+
95
+ ### Standard generator factory method
96
+ def self::for( options )
97
+ new( options )
98
+ end
99
+
100
+
101
+ #################################################################
102
+ ### I N S T A N C E M E T H O D S
103
+ #################################################################
104
+
105
+ ### Initialize a few instance variables before we start
106
+ def initialize( options )
107
+ @options = options
108
+ @template = nil
109
+
110
+ template = options.template || 'darkfish'
111
+ @template_dir = (template =~ /\A\//) ?
112
+ Pathname.new( template ) :
113
+ GENERATOR_DIR + 'template/' + template
114
+
115
+ configfile = @template_dir + 'config.yml'
116
+ @config = (configfile.file?) ? YAML.load_file( configfile.to_s ) : {}
117
+
118
+ @files = []
119
+ @classes = []
120
+ @hyperlinks = {}
121
+
122
+ @basedir = Pathname.pwd.expand_path
123
+
124
+ options.diagram = false
125
+
126
+ super()
127
+ end
128
+
129
+
130
+ ######
131
+ public
132
+ ######
133
+
134
+ # The output directory
135
+ attr_reader :outputdir
136
+
137
+
138
+ ### Read the spcified To be called from ERB template to import (embed)
139
+ ### another template
140
+ def import( erbfile )
141
+ erb = File.open( erbfile ) {|fp| ERb.new(fp.read) }
142
+ return erb.run( binding() )
143
+ end
144
+
145
+
146
+ ### Return the data section from the config file (if any)
147
+ def data
148
+ return @config['data']
149
+ end
150
+
151
+
152
+ ### Output progress information if debugging is enabled
153
+ def debug_msg( *msg )
154
+ return unless $DEBUG
155
+ $stderr.puts( *msg )
156
+ end
157
+
158
+
159
+ ### Create the directories the generated docs will live in if
160
+ ### they don't already exist.
161
+ def gen_sub_directories
162
+ @outputdir.mkpath
163
+ end
164
+
165
+
166
+ ### Copy over the stylesheet into the appropriate place in the
167
+ ### output directory.
168
+ def write_style_sheet
169
+ debug_msg "Copying over static files"
170
+ staticfiles = @config['static'] || %w[rdoc.css js images]
171
+ staticfiles = staticfiles.split( /\s+/ ) if staticfiles.is_a?( String )
172
+ staticfiles.each do |path|
173
+ FileUtils.cp_r( @template_dir + path, '.', :verbose => $DEBUG, :noop => $dryrun )
174
+ end
175
+ end
176
+
177
+
178
+
179
+ ### Build the initial indices and output objects
180
+ ### based on an array of TopLevel objects containing
181
+ ### the extracted information.
182
+ def generate( toplevels )
183
+ @outputdir = Pathname.new( @options.op_dir ).expand_path( @basedir )
184
+ if RDoc::Generator::Context.respond_to?( :build_indicies)
185
+ @files, @classes = RDoc::Generator::Context.build_indicies( toplevels, @options )
186
+ else
187
+ @files, @classes = RDoc::Generator::Context.build_indices( toplevels, @options )
188
+ end
189
+
190
+ # Now actually write the output
191
+ generate_xhtml( @options, @files, @classes )
192
+
193
+ rescue StandardError => err
194
+ debug_msg "%s: %s\n %s" % [ err.class.name, err.message, err.backtrace.join("\n ") ]
195
+ raise
196
+ end
197
+
198
+
199
+ ### No-opped
200
+ def load_html_template # :nodoc:
201
+ end
202
+
203
+
204
+ ### Generate output
205
+ def generate_xhtml( options, files, classes )
206
+ files = gen_into( @files )
207
+ classes = gen_into( @classes )
208
+
209
+ # Make a hash of class info keyed by class name
210
+ classes_by_classname = classes.inject({}) {|hash, classinfo|
211
+ hash[ classinfo[:full_name] ] = classinfo
212
+ hash[ classinfo[:full_name] ][:outfile] =
213
+ classinfo[:full_name].gsub( /::/, '/' ) + '.html'
214
+ hash
215
+ }
216
+
217
+ # Make a hash of file info keyed by path
218
+ files_by_path = files.inject({}) {|hash, fileinfo|
219
+ hash[ fileinfo[:full_path] ] = fileinfo
220
+ hash[ fileinfo[:full_path] ][:outfile] = fileinfo[:full_path] + '.html'
221
+ hash
222
+ }
223
+
224
+ self.write_style_sheet
225
+ self.generate_index( options, files_by_path, classes_by_classname )
226
+ self.generate_class_files( options, files_by_path, classes_by_classname )
227
+ self.generate_file_files( options, files_by_path, classes_by_classname )
228
+ end
229
+
230
+
231
+
232
+ #########
233
+ protected
234
+ #########
235
+
236
+ ### Return a list of the documented modules sorted by salience first, then by name.
237
+ def get_sorted_module_list( classes )
238
+ nscounts = classes.keys.inject({}) do |counthash, name|
239
+ toplevel = name.gsub( /::.*/, '' )
240
+ counthash[toplevel] ||= 0
241
+ counthash[toplevel] += 1
242
+
243
+ counthash
244
+ end
245
+
246
+ # Sort based on how often the toplevel namespace occurs, and then on the name
247
+ # of the module -- this works for projects that put their stuff into a
248
+ # namespace, of course, but doesn't hurt if they don't.
249
+ return classes.keys.sort_by do |name|
250
+ toplevel = name.gsub( /::.*/, '' )
251
+ [
252
+ nscounts[ toplevel ] * -1,
253
+ name
254
+ ]
255
+ end
256
+ end
257
+
258
+
259
+ ### Generate an index page which lists all the classes which
260
+ ### are documented.
261
+ def generate_index( options, files, classes )
262
+ debug_msg "Rendering the index page..."
263
+
264
+ templatefile = @template_dir + 'index.rhtml'
265
+ modsort = self.get_sorted_module_list( classes )
266
+ outfile = @basedir + @options.op_dir + 'index.html'
267
+
268
+ self.render_template( templatefile, binding(), outfile )
269
+ end
270
+
271
+
272
+ ### Generate a documentation file for each class present in the
273
+ ### given hash of +classes+.
274
+ def generate_class_files( options, files, classes )
275
+ debug_msg "Generating class documentation in #@outputdir"
276
+ templatefile = @template_dir + 'classpage.rhtml'
277
+ outputdir = @outputdir
278
+
279
+ modsort = self.get_sorted_module_list( classes )
280
+
281
+ classes.sort_by {|k,v| k }.each do |classname, classinfo|
282
+ debug_msg " working on %s (%s)" % [ classname, classinfo[:outfile] ]
283
+ outfile = outputdir + classinfo[:outfile]
284
+ rel_prefix = outputdir.relative_path_from( outfile.dirname )
285
+ svninfo = self.get_svninfo( classinfo )
286
+
287
+ debug_msg " rendering #{outfile}"
288
+ self.render_template( templatefile, binding(), outfile )
289
+ end
290
+ end
291
+
292
+
293
+ ### Generate a documentation file for each file present in the
294
+ ### given hash of +files+.
295
+ def generate_file_files( options, files, classes )
296
+ debug_msg "Generating file documentation in #@outputdir"
297
+ templatefile = @template_dir + 'filepage.rhtml'
298
+
299
+ modsort = self.get_sorted_module_list( classes )
300
+
301
+ files.sort_by {|k,v| k }.each do |path, fileinfo|
302
+ outfile = @outputdir + fileinfo[:outfile]
303
+ debug_msg " working on %s (%s)" % [ path, outfile ]
304
+ rel_prefix = @outputdir.relative_path_from( outfile.dirname )
305
+ context = binding()
306
+
307
+ debug_msg " rendering #{outfile}"
308
+ self.render_template( templatefile, binding(), outfile )
309
+ end
310
+ end
311
+
312
+
313
+ ### Return a string describing the amount of time in the given number of
314
+ ### seconds in terms a human can understand easily.
315
+ def time_delta_string( seconds )
316
+ return 'less than a minute' if seconds < 1.minute
317
+ return (seconds / 1.minute).to_s + ' minute' + (seconds/60 == 1 ? '' : 's') if seconds < 50.minutes
318
+ return 'about one hour' if seconds < 90.minutes
319
+ return (seconds / 1.hour).to_s + ' hours' if seconds < 18.hours
320
+ return 'one day' if seconds < 1.day
321
+ return 'about one day' if seconds < 2.days
322
+ return (seconds / 1.day).to_s + ' days' if seconds < 1.week
323
+ return 'about one week' if seconds < 2.week
324
+ return (seconds / 1.week).to_s + ' weeks' if seconds < 3.months
325
+ return (seconds / 1.month).to_s + ' months' if seconds < 1.year
326
+ return (seconds / 1.year).to_s + ' years'
327
+ end
328
+
329
+
330
+ # %q$Id$"
331
+ SVNID_PATTERN = /
332
+ \$Id:\s
333
+ (\S+)\s # filename
334
+ (\d+)\s # rev
335
+ (\d{4}-\d{2}-\d{2})\s # Date (YYYY-MM-DD)
336
+ (\d{2}:\d{2}:\d{2}Z)\s # Time (HH:MM:SSZ)
337
+ (\w+)\s # committer
338
+ \$$
339
+ /x
340
+
341
+ ### Try to extract Subversion information out of the first constant whose value looks like
342
+ ### a subversion Id tag. If no matching constant is found, and empty hash is returned.
343
+ def get_svninfo( classinfo )
344
+ return {} unless classinfo[:sections]
345
+ constants = classinfo[:sections].first[:constants] or return {}
346
+
347
+ constants.find {|c| c[:value] =~ SVNID_PATTERN } or return {}
348
+
349
+ filename, rev, date, time, committer = $~.captures
350
+ commitdate = Time.parse( date + ' ' + time )
351
+
352
+ return {
353
+ :filename => filename,
354
+ :rev => Integer( rev ),
355
+ :commitdate => commitdate,
356
+ :commitdelta => time_delta_string( Time.now.to_i - commitdate.to_i ),
357
+ :committer => committer,
358
+ }
359
+ end
360
+
361
+
362
+ ### Load and render the erb template in the given +templatefile+ within the specified
363
+ ### +context+ (a Binding object) and write it out to +outfile+. Both +templatefile+ and
364
+ ### +outfile+ should be Pathname-like objects.
365
+ def render_template( templatefile, context, outfile )
366
+ template_src = templatefile.read
367
+ template = ERB.new( template_src, nil, '<>' )
368
+ template.filename = templatefile.to_s
369
+
370
+ output = begin
371
+ template.result( context )
372
+ rescue NoMethodError => err
373
+ raise "Error while evaluating %s: %s (at %p)" % [
374
+ templatefile.to_s,
375
+ err.message,
376
+ eval( "_erbout[-50,50]", context )
377
+ ]
378
+ end
379
+
380
+ output = self.wrap_content( output, context )
381
+
382
+ unless $dryrun
383
+ outfile.dirname.mkpath
384
+ outfile.open( 'w', 0644 ) do |ofh|
385
+ ofh.print( output )
386
+ end
387
+ else
388
+ debug_msg " would have written %d bytes to %s" %
389
+ [ output.length, outfile ]
390
+ end
391
+ end
392
+
393
+
394
+ ### Load the configured wrapper file and wrap it around the given +content+.
395
+ def wrap_content( output, context )
396
+ wrapper = @options.wrapper || @config['wrapper'] || 'wrapper.rhtml'
397
+ wrapperfile = (wrapper =~ /\A\//) ?
398
+ Pathname.new( wrapper ) :
399
+ @template_dir + wrapper
400
+
401
+ if wrapperfile.file?
402
+ # Add 'content' to the context binding for the wrapper template
403
+ eval( "content = %p" % [output], context )
404
+
405
+ template_src = wrapperfile.read
406
+ template = ERB.new( template_src, nil, '<>' )
407
+ template.filename = templatefile.to_s
408
+
409
+ begin
410
+ return template.result( context )
411
+ rescue NoMethodError => err
412
+ raise "Error while evaluating %s: %s (at %p)" % [
413
+ templatefile.to_s,
414
+ err.message,
415
+ eval( "_erbout[-50,50]", context )
416
+ ]
417
+ end
418
+ end
419
+
420
+ end
421
+
422
+
423
+ #######
424
+ private
425
+ #######
426
+
427
+ ### Given the name of a Ruby method, return a name suitable for use as target names in
428
+ ### A tags.
429
+ def aname_from_method( methodname )
430
+ return ANAME_TRANSFORMS.enum_slice( 2 ).inject( methodname.to_s ) do |name, xform|
431
+ name.gsub( *xform )
432
+ end
433
+ end
434
+
435
+
436
+ end # Roc::Generator::Darkfish
437
+
438
+ # :stopdoc:
439
+
440
+ ### Time constants
441
+ module TimeConstantMethods # :nodoc:
442
+
443
+ ### Number of seconds (returns receiver unmodified)
444
+ def seconds
445
+ return self
446
+ end
447
+ alias_method :second, :seconds
448
+
449
+ ### Returns number of seconds in <receiver> minutes
450
+ def minutes
451
+ return self * 60
452
+ end
453
+ alias_method :minute, :minutes
454
+
455
+ ### Returns the number of seconds in <receiver> hours
456
+ def hours
457
+ return self * 60.minutes
458
+ end
459
+ alias_method :hour, :hours
460
+
461
+ ### Returns the number of seconds in <receiver> days
462
+ def days
463
+ return self * 24.hours
464
+ end
465
+ alias_method :day, :days
466
+
467
+ ### Return the number of seconds in <receiver> weeks
468
+ def weeks
469
+ return self * 7.days
470
+ end
471
+ alias_method :week, :weeks
472
+
473
+ ### Returns the number of seconds in <receiver> fortnights
474
+ def fortnights
475
+ return self * 2.weeks
476
+ end
477
+ alias_method :fortnight, :fortnights
478
+
479
+ ### Returns the number of seconds in <receiver> months (approximate)
480
+ def months
481
+ return self * 30.days
482
+ end
483
+ alias_method :month, :months
484
+
485
+ ### Returns the number of seconds in <receiver> years (approximate)
486
+ def years
487
+ return (self * 365.25.days).to_i
488
+ end
489
+ alias_method :year, :years
490
+
491
+
492
+ ### Returns the Time <receiver> number of seconds before the
493
+ ### specified +time+. E.g., 2.hours.before( header.expiration )
494
+ def before( time )
495
+ return time - self
496
+ end
497
+
498
+
499
+ ### Returns the Time <receiver> number of seconds ago. (e.g.,
500
+ ### expiration > 2.hours.ago )
501
+ def ago
502
+ return self.before( ::Time.now )
503
+ end
504
+
505
+
506
+ ### Returns the Time <receiver> number of seconds after the given +time+.
507
+ ### E.g., 10.minutes.after( header.expiration )
508
+ def after( time )
509
+ return time + self
510
+ end
511
+
512
+ # Reads best without arguments: 10.minutes.from_now
513
+ def from_now
514
+ return self.after( ::Time.now )
515
+ end
516
+ end # module TimeConstantMethods
517
+
518
+
519
+ # Extend Numeric with time constants
520
+ class Numeric # :nodoc:
521
+ include TimeConstantMethods
522
+ end
523
+