~ubuntu-branches/ubuntu/quantal/ruby1.9.1/quantal

« back to all changes in this revision

Viewing changes to lib/rdoc/generator/darkfish.rb

  • Committer: Bazaar Package Importer
  • Author(s): Lucas Nussbaum
  • Date: 2011-09-24 19:16:17 UTC
  • mfrom: (1.1.8 upstream) (13.1.7 experimental)
  • Revision ID: james.westby@ubuntu.com-20110924191617-o1qz4rcmqjot8zuy
Tags: 1.9.3~rc1-1
* New upstream release: 1.9.3 RC1.
  + Includes load.c fixes. Closes: #639959.
* Upload to unstable.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# -*- mode: ruby; ruby-indent-level: 2; tab-width: 2 -*-
2
 
# vim: noet ts=2 sts=8 sw=2
3
2
 
4
3
require 'pathname'
5
4
require 'fileutils'
6
 
require 'erb'
 
5
require 'rdoc/erbio'
7
6
 
8
7
require 'rdoc/generator/markup'
9
8
 
10
 
$DARKFISH_DRYRUN = false # TODO make me non-global
11
 
 
12
 
#
 
9
##
13
10
# Darkfish RDoc HTML Generator
14
11
#
15
12
# $Id: darkfish.rb 52 2009-01-07 02:08:11Z deveiant $
49
46
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
50
47
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
51
48
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
52
 
#
 
49
 
53
50
class RDoc::Generator::Darkfish
54
51
 
55
 
        RDoc::RDoc.add_generator( self )
56
 
 
57
 
        include ERB::Util
58
 
 
59
 
        # Subversion rev
60
 
        SVNRev = %$Rev: 52 $
61
 
 
62
 
        # Subversion ID
63
 
        SVNId = %$Id: darkfish.rb 52 2009-01-07 02:08:11Z deveiant $
64
 
 
65
 
        # Path to this file's parent directory. Used to find templates and other
66
 
        # resources.
67
 
        GENERATOR_DIR = File.join 'rdoc', 'generator'
68
 
 
69
 
        # Release Version
70
 
        VERSION = '1.1.6'
71
 
 
72
 
        # Directory where generated classes live relative to the root
73
 
        CLASS_DIR = nil
74
 
 
75
 
        # Directory where generated files live relative to the root
76
 
        FILE_DIR = nil
77
 
 
78
 
 
79
 
        #################################################################
80
 
        ###     C L A S S   M E T H O D S
81
 
        #################################################################
82
 
 
83
 
        ### Standard generator factory method
84
 
        def self::for( options )
85
 
                new( options )
86
 
        end
87
 
 
88
 
 
89
 
        #################################################################
90
 
        ###     I N S T A N C E   M E T H O D S
91
 
        #################################################################
92
 
 
93
 
        ### Initialize a few instance variables before we start
94
 
        def initialize( options )
95
 
                @options = options
96
 
 
97
 
                template = @options.template || 'darkfish'
98
 
 
99
 
                template_dir = $LOAD_PATH.map do |path|
100
 
                        File.join File.expand_path(path), GENERATOR_DIR, 'template', template
101
 
                end.find do |dir|
102
 
                        File.directory? dir
103
 
                end
104
 
 
105
 
                raise RDoc::Error, "could not find template #{template.inspect}" unless
106
 
                        template_dir
107
 
 
108
 
                @template_dir = Pathname.new File.expand_path(template_dir)
109
 
 
110
 
                @files      = nil
111
 
                @classes    = nil
112
 
 
113
 
                @basedir = Pathname.pwd.expand_path
114
 
        end
115
 
 
116
 
        ######
117
 
        public
118
 
        ######
119
 
 
120
 
        # The output directory
121
 
        attr_reader :outputdir
122
 
 
123
 
 
124
 
        ### Output progress information if debugging is enabled
125
 
        def debug_msg( *msg )
126
 
                return unless $DEBUG_RDOC
127
 
                $stderr.puts( *msg )
128
 
        end
129
 
 
130
 
        def class_dir
131
 
                CLASS_DIR
132
 
        end
133
 
 
134
 
        def file_dir
135
 
                FILE_DIR
136
 
        end
137
 
 
138
 
        ### Create the directories the generated docs will live in if
139
 
        ### they don't already exist.
140
 
        def gen_sub_directories
141
 
                @outputdir.mkpath
142
 
        end
143
 
 
144
 
        ### Copy over the stylesheet into the appropriate place in the output
145
 
        ### directory.
146
 
        def write_style_sheet
147
 
                debug_msg "Copying static files"
148
 
                options = { :verbose => $DEBUG_RDOC, :noop => $DARKFISH_DRYRUN }
149
 
 
150
 
                FileUtils.cp @template_dir + 'rdoc.css', '.', options
151
 
 
152
 
                Dir[(@template_dir + "{js,images}/**/*").to_s].each do |path|
153
 
                        next if File.directory? path
154
 
                        next if path =~ /#{File::SEPARATOR}\./
155
 
 
156
 
                        dst = Pathname.new(path).relative_path_from @template_dir
157
 
 
158
 
                        # I suck at glob
159
 
                        dst_dir = dst.dirname
160
 
                        FileUtils.mkdir_p dst_dir, options unless File.exist? dst_dir
161
 
 
162
 
                        FileUtils.cp @template_dir + path, dst, options
163
 
                end
164
 
        end
165
 
 
166
 
        ### Build the initial indices and output objects
167
 
        ### based on an array of TopLevel objects containing
168
 
        ### the extracted information.
169
 
        def generate( top_levels )
170
 
                @outputdir = Pathname.new( @options.op_dir ).expand_path( @basedir )
171
 
 
172
 
                @files = top_levels.sort
173
 
                @classes = RDoc::TopLevel.all_classes_and_modules.sort
174
 
                @methods = @classes.map { |m| m.method_list }.flatten.sort
175
 
                @modsort = get_sorted_module_list( @classes )
176
 
 
177
 
                # Now actually write the output
178
 
                write_style_sheet
179
 
                generate_index
180
 
                generate_class_files
181
 
                generate_file_files
182
 
 
183
 
        rescue StandardError => err
184
 
                debug_msg "%s: %s\n  %s" % [ err.class.name, err.message, err.backtrace.join("\n  ") ]
185
 
                raise
186
 
        end
187
 
 
188
 
        #########
189
 
        protected
190
 
        #########
191
 
 
192
 
        ### Return a list of the documented modules sorted by salience first, then
193
 
        ### by name.
194
 
        def get_sorted_module_list( classes )
195
 
                nscounts = classes.inject({}) do |counthash, klass|
196
 
                        top_level = klass.full_name.gsub( /::.*/, '' )
197
 
                        counthash[top_level] ||= 0
198
 
                        counthash[top_level] += 1
199
 
 
200
 
                        counthash
201
 
                end
202
 
 
203
 
                # Sort based on how often the top level namespace occurs, and then on the
204
 
                # name of the module -- this works for projects that put their stuff into
205
 
                # a namespace, of course, but doesn't hurt if they don't.
206
 
                classes.sort_by do |klass|
207
 
                        top_level = klass.full_name.gsub( /::.*/, '' )
208
 
                        [
209
 
                                nscounts[ top_level ] * -1,
210
 
                                klass.full_name
211
 
                        ]
212
 
                end.select do |klass|
213
 
                        klass.document_self
214
 
                end
215
 
        end
216
 
 
217
 
        ### Generate an index page which lists all the classes which
218
 
        ### are documented.
219
 
        def generate_index
220
 
                template_file = @template_dir + 'index.rhtml'
221
 
                return unless template_file.exist?
222
 
 
223
 
                debug_msg "Rendering the index page..."
224
 
 
225
 
                template_src = template_file.read
226
 
                template = ERB.new( template_src, nil, '<>' )
227
 
                template.filename = template_file.to_s
228
 
                context = binding()
229
 
 
230
 
                output = nil
231
 
 
232
 
                begin
233
 
                        output = template.result( context )
234
 
                rescue NoMethodError => err
235
 
                        raise RDoc::Error, "Error while evaluating %s: %s (at %p)" % [
236
 
                                template_file,
237
 
                                err.message,
238
 
                                eval( "_erbout[-50,50]", context )
239
 
                        ], err.backtrace
240
 
                end
241
 
 
242
 
                outfile = @basedir + @options.op_dir + 'index.html'
243
 
                unless $DARKFISH_DRYRUN
244
 
                        debug_msg "Outputting to %s" % [outfile.expand_path]
245
 
                        outfile.open( 'w', 0644 ) do |fh|
246
 
                                fh.print( output )
247
 
                        end
248
 
                else
249
 
                        debug_msg "Would have output to %s" % [outfile.expand_path]
250
 
                end
251
 
        end
252
 
 
253
 
        ### Generate a documentation file for each class
254
 
        def generate_class_files
255
 
                template_file = @template_dir + 'classpage.rhtml'
256
 
                return unless template_file.exist?
257
 
                debug_msg "Generating class documentation in #@outputdir"
258
 
 
259
 
                @classes.each do |klass|
260
 
                        debug_msg "  working on %s (%s)" % [ klass.full_name, klass.path ]
261
 
                        outfile    = @outputdir + klass.path
262
 
                        rel_prefix = @outputdir.relative_path_from( outfile.dirname )
263
 
                        svninfo    = self.get_svninfo( klass )
264
 
 
265
 
                        debug_msg "  rendering #{outfile}"
266
 
                        self.render_template( template_file, binding(), outfile )
267
 
                end
268
 
        end
269
 
 
270
 
        ### Generate a documentation file for each file
271
 
        def generate_file_files
272
 
                template_file = @template_dir + 'filepage.rhtml'
273
 
                return unless template_file.exist?
274
 
                debug_msg "Generating file documentation in #@outputdir"
275
 
 
276
 
                @files.each do |file|
277
 
                        outfile     = @outputdir + file.path
278
 
                        debug_msg "  working on %s (%s)" % [ file.full_name, outfile ]
279
 
                        rel_prefix  = @outputdir.relative_path_from( outfile.dirname )
280
 
                        context     = binding()
281
 
 
282
 
                        debug_msg "  rendering #{outfile}"
283
 
                        self.render_template( template_file, binding(), outfile )
284
 
                end
285
 
        end
286
 
 
287
 
 
288
 
        ### Return a string describing the amount of time in the given number of
289
 
        ### seconds in terms a human can understand easily.
290
 
        def time_delta_string( seconds )
291
 
                return 'less than a minute' if seconds < 1.minute
292
 
                return (seconds / 1.minute).to_s + ' minute' + (seconds/60 == 1 ? '' : 's') if seconds < 50.minutes
293
 
                return 'about one hour' if seconds < 90.minutes
294
 
                return (seconds / 1.hour).to_s + ' hours' if seconds < 18.hours
295
 
                return 'one day' if seconds < 1.day
296
 
                return 'about one day' if seconds < 2.days
297
 
                return (seconds / 1.day).to_s + ' days' if seconds < 1.week
298
 
                return 'about one week' if seconds < 2.week
299
 
                return (seconds / 1.week).to_s + ' weeks' if seconds < 3.months
300
 
                return (seconds / 1.month).to_s + ' months' if seconds < 1.year
301
 
                return (seconds / 1.year).to_s + ' years'
302
 
        end
303
 
 
304
 
 
305
 
        # %q$Id: darkfish.rb 52 2009-01-07 02:08:11Z deveiant $"
306
 
        SVNID_PATTERN = /
307
 
                \$Id:\s
308
 
                        (\S+)\s                                 # filename
309
 
                        (\d+)\s                                 # rev
310
 
                        (\d{4}-\d{2}-\d{2})\s   # Date (YYYY-MM-DD)
311
 
                        (\d{2}:\d{2}:\d{2}Z)\s  # Time (HH:MM:SSZ)
312
 
                        (\w+)\s                                 # committer
313
 
                \$$
314
 
        /x
315
 
 
316
 
        ### Try to extract Subversion information out of the first constant whose value looks like
317
 
        ### a subversion Id tag. If no matching constant is found, and empty hash is returned.
318
 
        def get_svninfo( klass )
319
 
                constants = klass.constants or return {}
320
 
 
321
 
                constants.find {|c| c.value =~ SVNID_PATTERN } or return {}
322
 
 
323
 
                filename, rev, date, time, committer = $~.captures
324
 
                commitdate = Time.parse( date + ' ' + time )
325
 
 
326
 
                return {
327
 
                        :filename    => filename,
328
 
                        :rev         => Integer( rev ),
329
 
                        :commitdate  => commitdate,
330
 
                        :commitdelta => time_delta_string( Time.now.to_i - commitdate.to_i ),
331
 
                        :committer   => committer,
332
 
                }
333
 
        end
334
 
 
335
 
 
336
 
        ### Load and render the erb template in the given +template_file+ within the
337
 
        ### specified +context+ (a Binding object) and write it out to +outfile+.
338
 
        ### Both +template_file+ and +outfile+ should be Pathname-like objects.
339
 
 
340
 
        def render_template( template_file, context, outfile )
341
 
                template_src = template_file.read
342
 
                template = ERB.new( template_src, nil, '<>' )
343
 
                template.filename = template_file.to_s
344
 
 
345
 
                output = begin
346
 
                                                         template.result( context )
347
 
                                                 rescue NoMethodError => err
348
 
                                                         raise RDoc::Error, "Error while evaluating %s: %s (at %p)" % [
349
 
                                                                 template_file.to_s,
350
 
                                                                 err.message,
351
 
                                                                 eval( "_erbout[-50,50]", context )
352
 
                                                         ], err.backtrace
353
 
                                                 end
354
 
 
355
 
                unless $DARKFISH_DRYRUN
356
 
                        outfile.dirname.mkpath
357
 
                        outfile.open( 'w', 0644 ) do |ofh|
358
 
                                ofh.print( output )
359
 
                        end
360
 
                else
361
 
                        debug_msg "  would have written %d bytes to %s" %
362
 
                        [ output.length, outfile ]
363
 
                end
364
 
        end
365
 
 
366
 
end # Roc::Generator::Darkfish
367
 
 
368
 
# :stopdoc:
369
 
 
370
 
### Time constants
371
 
module TimeConstantMethods # :nodoc:
372
 
 
373
 
        ### Number of seconds (returns receiver unmodified)
374
 
        def seconds
375
 
                return self
376
 
        end
377
 
        alias_method :second, :seconds
378
 
 
379
 
        ### Returns number of seconds in <receiver> minutes
380
 
        def minutes
381
 
                return self * 60
382
 
        end
383
 
        alias_method :minute, :minutes
384
 
 
385
 
        ### Returns the number of seconds in <receiver> hours
386
 
        def hours
387
 
                return self * 60.minutes
388
 
        end
389
 
        alias_method :hour, :hours
390
 
 
391
 
        ### Returns the number of seconds in <receiver> days
392
 
        def days
393
 
                return self * 24.hours
394
 
        end
395
 
        alias_method :day, :days
396
 
 
397
 
        ### Return the number of seconds in <receiver> weeks
398
 
        def weeks
399
 
                return self * 7.days
400
 
        end
401
 
        alias_method :week, :weeks
402
 
 
403
 
        ### Returns the number of seconds in <receiver> fortnights
404
 
        def fortnights
405
 
                return self * 2.weeks
406
 
        end
407
 
        alias_method :fortnight, :fortnights
408
 
 
409
 
        ### Returns the number of seconds in <receiver> months (approximate)
410
 
        def months
411
 
                return self * 30.days
412
 
        end
413
 
        alias_method :month, :months
414
 
 
415
 
        ### Returns the number of seconds in <receiver> years (approximate)
416
 
        def years
417
 
                return (self * 365.25.days).to_i
418
 
        end
419
 
        alias_method :year, :years
420
 
 
421
 
 
422
 
        ### Returns the Time <receiver> number of seconds before the
423
 
        ### specified +time+. E.g., 2.hours.before( header.expiration )
424
 
        def before( time )
425
 
                return time - self
426
 
        end
427
 
 
428
 
 
429
 
        ### Returns the Time <receiver> number of seconds ago. (e.g.,
430
 
        ### expiration > 2.hours.ago )
431
 
        def ago
432
 
                return self.before( ::Time.now )
433
 
        end
434
 
 
435
 
 
436
 
        ### Returns the Time <receiver> number of seconds after the given +time+.
437
 
        ### E.g., 10.minutes.after( header.expiration )
438
 
        def after( time )
439
 
                return time + self
440
 
        end
441
 
 
442
 
        # Reads best without arguments:  10.minutes.from_now
443
 
        def from_now
444
 
                return self.after( ::Time.now )
445
 
        end
446
 
end # module TimeConstantMethods
447
 
 
448
 
 
449
 
# Extend Numeric with time constants
450
 
class Numeric # :nodoc:
451
 
        include TimeConstantMethods
 
52
  RDoc::RDoc.add_generator self
 
53
 
 
54
  include ERB::Util
 
55
 
 
56
  # Path to this file's parent directory. Used to find templates and other
 
57
  # resources.
 
58
 
 
59
  GENERATOR_DIR = File.join 'rdoc', 'generator'
 
60
 
 
61
  ##
 
62
  # Release Version
 
63
 
 
64
  VERSION = '2'
 
65
 
 
66
  ##
 
67
  # Description of this generator
 
68
 
 
69
  DESCRIPTION = 'HTML generator, written by Michael Granger'
 
70
 
 
71
  ##
 
72
  # Initialize a few instance variables before we start
 
73
 
 
74
  def initialize options
 
75
    @options = options
 
76
 
 
77
    @template_dir = Pathname.new options.template_dir
 
78
    @template_cache = {}
 
79
 
 
80
    @files      = nil
 
81
    @classes    = nil
 
82
 
 
83
    @basedir = Pathname.pwd.expand_path
 
84
  end
 
85
 
 
86
  ##
 
87
  # The output directory
 
88
 
 
89
  attr_reader :outputdir
 
90
 
 
91
  ##
 
92
  # Output progress information if debugging is enabled
 
93
 
 
94
  def debug_msg *msg
 
95
    return unless $DEBUG_RDOC
 
96
    $stderr.puts(*msg)
 
97
  end
 
98
 
 
99
  ##
 
100
  # Directory where generated class HTML files live relative to the output
 
101
  # dir.
 
102
 
 
103
  def class_dir
 
104
    nil
 
105
  end
 
106
 
 
107
  ##
 
108
  # Directory where generated class HTML files live relative to the output
 
109
  # dir.
 
110
 
 
111
  def file_dir
 
112
    nil
 
113
  end
 
114
 
 
115
  ##
 
116
  # Create the directories the generated docs will live in if they don't
 
117
  # already exist.
 
118
 
 
119
  def gen_sub_directories
 
120
    @outputdir.mkpath
 
121
  end
 
122
 
 
123
  ##
 
124
  # Copy over the stylesheet into the appropriate place in the output
 
125
  # directory.
 
126
 
 
127
  def write_style_sheet
 
128
    debug_msg "Copying static files"
 
129
    options = { :verbose => $DEBUG_RDOC, :noop => @options.dry_run }
 
130
 
 
131
    FileUtils.cp @template_dir + 'rdoc.css', '.', options
 
132
 
 
133
    Dir[(@template_dir + "{js,images}/**/*").to_s].each do |path|
 
134
      next if File.directory? path
 
135
      next if File.basename(path) =~ /^\./
 
136
 
 
137
        dst = Pathname.new(path).relative_path_from @template_dir
 
138
 
 
139
      # I suck at glob
 
140
      dst_dir = dst.dirname
 
141
      FileUtils.mkdir_p dst_dir, options unless File.exist? dst_dir
 
142
 
 
143
      FileUtils.cp @template_dir + path, dst, options
 
144
    end
 
145
  end
 
146
 
 
147
  ##
 
148
  # Build the initial indices and output objects based on an array of TopLevel
 
149
  # objects containing the extracted information.
 
150
 
 
151
  def generate top_levels
 
152
    @outputdir = Pathname.new(@options.op_dir).expand_path(@basedir)
 
153
 
 
154
    @files = top_levels.sort
 
155
    @classes = RDoc::TopLevel.all_classes_and_modules.sort
 
156
    @methods = @classes.map { |m| m.method_list }.flatten.sort
 
157
    @modsort = get_sorted_module_list(@classes)
 
158
 
 
159
    # Now actually write the output
 
160
    write_style_sheet
 
161
    generate_index
 
162
    generate_class_files
 
163
    generate_file_files
 
164
 
 
165
  rescue => e
 
166
    debug_msg "%s: %s\n  %s" % [
 
167
      e.class.name, e.message, e.backtrace.join("\n  ")
 
168
    ]
 
169
 
 
170
    raise
 
171
  end
 
172
 
 
173
  protected
 
174
 
 
175
  ##
 
176
  # Return a list of the documented modules sorted by salience first, then
 
177
  # by name.
 
178
 
 
179
  def get_sorted_module_list(classes)
 
180
    nscounts = classes.inject({}) do |counthash, klass|
 
181
      top_level = klass.full_name.gsub(/::.*/, '')
 
182
      counthash[top_level] ||= 0
 
183
      counthash[top_level] += 1
 
184
 
 
185
      counthash
 
186
    end
 
187
 
 
188
    # Sort based on how often the top level namespace occurs, and then on the
 
189
    # name of the module -- this works for projects that put their stuff into
 
190
    # a namespace, of course, but doesn't hurt if they don't.
 
191
    classes.sort_by do |klass|
 
192
      top_level = klass.full_name.gsub( /::.*/, '' )
 
193
      [nscounts[top_level] * -1, klass.full_name]
 
194
    end.select do |klass|
 
195
      klass.display?
 
196
    end
 
197
  end
 
198
 
 
199
  ##
 
200
  # Generate an index page which lists all the classes which are documented.
 
201
 
 
202
  def generate_index
 
203
    template_file = @template_dir + 'index.rhtml'
 
204
    return unless template_file.exist?
 
205
 
 
206
    debug_msg "Rendering the index page..."
 
207
 
 
208
    out_file = @basedir + @options.op_dir + 'index.html'
 
209
 
 
210
    render_template template_file, out_file do |io| binding end
 
211
  rescue => e
 
212
    error = RDoc::Error.new \
 
213
      "error generating index.html: #{e.message} (#{e.class})"
 
214
    error.set_backtrace e.backtrace
 
215
 
 
216
    raise error
 
217
  end
 
218
 
 
219
  ##
 
220
  # Generate a documentation file for each class
 
221
 
 
222
  def generate_class_files
 
223
    template_file = @template_dir + 'classpage.rhtml'
 
224
    return unless template_file.exist?
 
225
    debug_msg "Generating class documentation in #{@outputdir}"
 
226
 
 
227
    current = nil
 
228
 
 
229
    @classes.each do |klass|
 
230
      current = klass
 
231
      debug_msg "  working on %s (%s)" % [klass.full_name, klass.path]
 
232
      out_file   = @outputdir + klass.path
 
233
      # suppress 1.9.3 warning
 
234
      rel_prefix = rel_prefix = @outputdir.relative_path_from(out_file.dirname)
 
235
      svninfo    = svninfo    = self.get_svninfo(klass)
 
236
 
 
237
      debug_msg "  rendering #{out_file}"
 
238
      render_template template_file, out_file do |io| binding end
 
239
    end
 
240
  rescue => e
 
241
    error = RDoc::Error.new \
 
242
      "error generating #{current.path}: #{e.message} (#{e.class})"
 
243
    error.set_backtrace e.backtrace
 
244
 
 
245
    raise error
 
246
  end
 
247
 
 
248
  ##
 
249
  # Generate a documentation file for each file
 
250
 
 
251
  def generate_file_files
 
252
    template_file = @template_dir + 'filepage.rhtml'
 
253
    return unless template_file.exist?
 
254
    debug_msg "Generating file documentation in #{@outputdir}"
 
255
 
 
256
    out_file = nil
 
257
 
 
258
    @files.each do |file|
 
259
      out_file     = @outputdir + file.path
 
260
      debug_msg "  working on %s (%s)" % [file.full_name, out_file]
 
261
      # suppress 1.9.3 warning
 
262
      rel_prefix = rel_prefix  = @outputdir.relative_path_from(out_file.dirname)
 
263
 
 
264
      debug_msg "  rendering #{out_file}"
 
265
      render_template template_file, out_file do |io| binding end
 
266
    end
 
267
  rescue => e
 
268
    error =
 
269
      RDoc::Error.new "error generating #{out_file}: #{e.message} (#{e.class})"
 
270
    error.set_backtrace e.backtrace
 
271
 
 
272
    raise error
 
273
  end
 
274
 
 
275
  ##
 
276
  # Return a string describing the amount of time in the given number of
 
277
  # seconds in terms a human can understand easily.
 
278
 
 
279
  def time_delta_string seconds
 
280
    return 'less than a minute'          if seconds < 60
 
281
    return "#{seconds / 60} minute#{seconds / 60 == 1 ? '' : 's'}" if
 
282
                                            seconds < 3000     # 50 minutes
 
283
    return 'about one hour'              if seconds < 5400     # 90 minutes
 
284
    return "#{seconds / 3600} hours"     if seconds < 64800    # 18 hours
 
285
    return 'one day'                     if seconds < 86400    #  1 day
 
286
    return 'about one day'               if seconds < 172800   #  2 days
 
287
    return "#{seconds / 86400} days"     if seconds < 604800   #  1 week
 
288
    return 'about one week'              if seconds < 1209600  #  2 week
 
289
    return "#{seconds / 604800} weeks"   if seconds < 7257600  #  3 months
 
290
    return "#{seconds / 2419200} months" if seconds < 31536000 #  1 year
 
291
    return "#{seconds / 31536000} years"
 
292
  end
 
293
 
 
294
  # %q$Id: darkfish.rb 52 2009-01-07 02:08:11Z deveiant $"
 
295
  SVNID_PATTERN = /
 
296
    \$Id:\s
 
297
    (\S+)\s                # filename
 
298
    (\d+)\s                # rev
 
299
    (\d{4}-\d{2}-\d{2})\s  # Date (YYYY-MM-DD)
 
300
    (\d{2}:\d{2}:\d{2}Z)\s # Time (HH:MM:SSZ)
 
301
    (\w+)\s                # committer
 
302
    \$$
 
303
  /x
 
304
 
 
305
  ##
 
306
  # Try to extract Subversion information out of the first constant whose
 
307
  # value looks like a subversion Id tag. If no matching constant is found,
 
308
  # and empty hash is returned.
 
309
 
 
310
  def get_svninfo klass
 
311
    constants = klass.constants or return {}
 
312
 
 
313
    constants.find { |c| c.value =~ SVNID_PATTERN } or return {}
 
314
 
 
315
    filename, rev, date, time, committer = $~.captures
 
316
    commitdate = Time.parse "#{date} #{time}"
 
317
 
 
318
    return {
 
319
      :filename    => filename,
 
320
      :rev         => Integer(rev),
 
321
      :commitdate  => commitdate,
 
322
      :commitdelta => time_delta_string(Time.now - commitdate),
 
323
      :committer   => committer,
 
324
    }
 
325
  end
 
326
 
 
327
  ##
 
328
  # Load and render the erb template in the given +template_file+ and write
 
329
  # it out to +out_file+.
 
330
  #
 
331
  # Both +template_file+ and +out_file+ should be Pathname-like objects.
 
332
  #
 
333
  # An io will be yielded which must be captured by binding in the caller.
 
334
 
 
335
  def render_template template_file, out_file # :yield: io
 
336
    template = template_for template_file
 
337
 
 
338
    unless @options.dry_run then
 
339
      debug_msg "Outputting to %s" % [out_file.expand_path]
 
340
 
 
341
      out_file.dirname.mkpath
 
342
      out_file.open 'w', 0644 do |io|
 
343
        io.set_encoding @options.encoding if Object.const_defined? :Encoding
 
344
 
 
345
        context = yield io
 
346
 
 
347
        template_result template, context, template_file
 
348
      end
 
349
    else
 
350
      context = yield nil
 
351
 
 
352
      output = template_result template, context, template_file
 
353
 
 
354
      debug_msg "  would have written %d characters to %s" % [
 
355
        output.length, out_file.expand_path
 
356
      ]
 
357
    end
 
358
  end
 
359
 
 
360
  ##
 
361
  # Creates the result for +template+ with +context+.  If an error is raised a
 
362
  # Pathname +template_file+ will indicate the file where the error occurred.
 
363
 
 
364
  def template_result template, context, template_file
 
365
    template.filename = template_file.to_s
 
366
    template.result context
 
367
  rescue NoMethodError => e
 
368
    raise RDoc::Error, "Error while evaluating %s: %s" % [
 
369
      template_file.expand_path,
 
370
      e.message,
 
371
    ], e.backtrace
 
372
  end
 
373
 
 
374
  ##
 
375
  # Retrieves a cache template for +file+, if present, or fills the cache.
 
376
 
 
377
  def template_for file
 
378
    template = @template_cache[file]
 
379
 
 
380
    return template if template
 
381
 
 
382
    klass = @options.dry_run ? ERB : RDoc::ERBIO
 
383
 
 
384
    template = klass.new file.read, nil, '<>'
 
385
    @template_cache[file] = template
 
386
    template
 
387
  end
 
388
 
452
389
end
453
390