starkfish 0.2.0

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.
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
+