diff options
author | drbrain <drbrain@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2010-12-20 03:22:49 +0000 |
---|---|---|
committer | drbrain <drbrain@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2010-12-20 03:22:49 +0000 |
commit | 2ef9c50c6e405717d06362787c4549ca4f1c6485 (patch) | |
tree | ee99486567461dd5796f3d6edcc9e204187f2666 /lib/rdoc/generator/darkfish.rb | |
parent | d7effd506f5b91a636f2e6452ef1946b923007c7 (diff) |
Import RDoc 3
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@30249 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'lib/rdoc/generator/darkfish.rb')
-rw-r--r-- | lib/rdoc/generator/darkfish.rb | 658 |
1 files changed, 284 insertions, 374 deletions
diff --git a/lib/rdoc/generator/darkfish.rb b/lib/rdoc/generator/darkfish.rb index f64641873e..e5a6e57424 100644 --- a/lib/rdoc/generator/darkfish.rb +++ b/lib/rdoc/generator/darkfish.rb @@ -1,15 +1,12 @@ # -*- mode: ruby; ruby-indent-level: 2; tab-width: 2 -*- -# vim: noet ts=2 sts=8 sw=2 require 'pathname' require 'fileutils' -require 'erb' +require 'rdoc/erbio' require 'rdoc/generator/markup' -$DARKFISH_DRYRUN = false # TODO make me non-global - -# +## # Darkfish RDoc HTML Generator # # $Id: darkfish.rb 52 2009-01-07 02:08:11Z deveiant $ @@ -52,401 +49,314 @@ $DARKFISH_DRYRUN = false # TODO make me non-global # class RDoc::Generator::Darkfish - RDoc::RDoc.add_generator( self ) + RDoc::RDoc.add_generator self + + include ERB::Util + + # Path to this file's parent directory. Used to find templates and other + # resources. + + GENERATOR_DIR = File.join 'rdoc', 'generator' + + ## + # Release Version + + VERSION = '2' + + ## + # Initialize a few instance variables before we start - include ERB::Util + def initialize options + @options = options - # Subversion rev - SVNRev = %$Rev: 52 $ + @template_dir = Pathname.new options.template_dir + @template_cache = {} - # Subversion ID - SVNId = %$Id: darkfish.rb 52 2009-01-07 02:08:11Z deveiant $ + @files = nil + @classes = nil - # Path to this file's parent directory. Used to find templates and other - # resources. - GENERATOR_DIR = File.join 'rdoc', 'generator' + @basedir = Pathname.pwd.expand_path + end - # Release Version - VERSION = '1.1.6' + ## + # The output directory - # Directory where generated classes live relative to the root - CLASS_DIR = nil + attr_reader :outputdir - # Directory where generated files live relative to the root - FILE_DIR = nil + ## + # Output progress information if debugging is enabled + def debug_msg *msg + return unless $DEBUG_RDOC + $stderr.puts(*msg) + end - ################################################################# - ### C L A S S M E T H O D S - ################################################################# + ## + # Directory where generated class HTML files live relative to the output + # dir. - ### Standard generator factory method - def self::for( options ) - new( options ) - end + def class_dir + nil + end + ## + # Directory where generated class HTML files live relative to the output + # dir. - ################################################################# - ### I N S T A N C E M E T H O D S - ################################################################# + def file_dir + nil + end - ### Initialize a few instance variables before we start - def initialize( options ) - @options = options + ## + # Create the directories the generated docs will live in if they don't + # already exist. - template = @options.template || 'darkfish' + def gen_sub_directories + @outputdir.mkpath + end - template_dir = $LOAD_PATH.map do |path| - File.join File.expand_path(path), GENERATOR_DIR, 'template', template - end.find do |dir| - File.directory? dir - end + ## + # Copy over the stylesheet into the appropriate place in the output + # directory. - raise RDoc::Error, "could not find template #{template.inspect}" unless - template_dir + def write_style_sheet + debug_msg "Copying static files" + options = { :verbose => $DEBUG_RDOC, :noop => @options.dry_run } - @template_dir = Pathname.new File.expand_path(template_dir) + FileUtils.cp @template_dir + 'rdoc.css', '.', options - @files = nil - @classes = nil + Dir[(@template_dir + "{js,images}/**/*").to_s].each do |path| + next if File.directory? path + next if File.basename(path) =~ /^\./ - @basedir = Pathname.pwd.expand_path - end + dst = Pathname.new(path).relative_path_from @template_dir - ###### - public - ###### + # I suck at glob + dst_dir = dst.dirname + FileUtils.mkdir_p dst_dir, options unless File.exist? dst_dir - # The output directory - attr_reader :outputdir + FileUtils.cp @template_dir + path, dst, options + end + end + ## + # Build the initial indices and output objects based on an array of TopLevel + # objects containing the extracted information. - ### Output progress information if debugging is enabled - def debug_msg( *msg ) - return unless $DEBUG_RDOC - $stderr.puts( *msg ) - end + def generate top_levels + @outputdir = Pathname.new(@options.op_dir).expand_path(@basedir) - def class_dir - CLASS_DIR - end + @files = top_levels.sort + @classes = RDoc::TopLevel.all_classes_and_modules.sort + @methods = @classes.map { |m| m.method_list }.flatten.sort + @modsort = get_sorted_module_list(@classes) - def file_dir - FILE_DIR - end + # Now actually write the output + write_style_sheet + generate_index + generate_class_files + generate_file_files - ### Create the directories the generated docs will live in if - ### they don't already exist. - def gen_sub_directories - @outputdir.mkpath - end + rescue StandardError => err + debug_msg "%s: %s\n %s" % [ + err.class.name, err.message, err.backtrace.join("\n ") + ] + + raise + end + + protected + + ## + # Return a list of the documented modules sorted by salience first, then + # by name. + + def get_sorted_module_list(classes) + nscounts = classes.inject({}) do |counthash, klass| + top_level = klass.full_name.gsub(/::.*/, '') + counthash[top_level] ||= 0 + counthash[top_level] += 1 + + counthash + end + + # Sort based on how often the top level namespace occurs, and then on the + # name of the module -- this works for projects that put their stuff into + # a namespace, of course, but doesn't hurt if they don't. + classes.sort_by do |klass| + top_level = klass.full_name.gsub( /::.*/, '' ) + [nscounts[top_level] * -1, klass.full_name] + end.select do |klass| + klass.document_self + end + end + + ## + # Generate an index page which lists all the classes which are documented. + + def generate_index + template_file = @template_dir + 'index.rhtml' + return unless template_file.exist? + + debug_msg "Rendering the index page..." + + out_file = @basedir + @options.op_dir + 'index.html' + + render_template template_file, out_file do |io| binding end + end + + ## + # Generate a documentation file for each class + + def generate_class_files + template_file = @template_dir + 'classpage.rhtml' + return unless template_file.exist? + debug_msg "Generating class documentation in #@outputdir" + + @classes.each do |klass| + debug_msg " working on %s (%s)" % [klass.full_name, klass.path] + out_file = @outputdir + klass.path + # suppress 1.9.3 warning + rel_prefix = rel_prefix = @outputdir.relative_path_from(out_file.dirname) + svninfo = svninfo = self.get_svninfo(klass) + + debug_msg " rendering #{out_file}" + render_template template_file, out_file do |io| binding end + end + end + + ## + # Generate a documentation file for each file + + def generate_file_files + template_file = @template_dir + 'filepage.rhtml' + return unless template_file.exist? + debug_msg "Generating file documentation in #@outputdir" + + @files.each do |file| + out_file = @outputdir + file.path + debug_msg " working on %s (%s)" % [ file.full_name, out_file ] + # suppress 1.9.3 warning + rel_prefix = rel_prefix = @outputdir.relative_path_from(out_file.dirname) + + debug_msg " rendering #{out_file}" + render_template template_file, out_file do |io| binding end + end + end + + ## + # Return a string describing the amount of time in the given number of + # seconds in terms a human can understand easily. + + def time_delta_string seconds + return 'less than a minute' if seconds < 60 + return "#{seconds / 60} minute#{seconds / 60 == 1 ? '' : 's'}" if + seconds < 3000 # 50 minutes + return 'about one hour' if seconds < 5400 # 90 minutes + return "#{seconds / 3600} hours" if seconds < 64800 # 18 hours + return 'one day' if seconds < 86400 # 1 day + return 'about one day' if seconds < 172800 # 2 days + return "#{seconds / 86400} days" if seconds < 604800 # 1 week + return 'about one week' if seconds < 1209600 # 2 week + return "#{seconds / 604800} weeks" if seconds < 7257600 # 3 months + return "#{seconds / 2419200} months" if seconds < 31536000 # 1 year + return "#{seconds / 31536000} years" + end + + # %q$Id: darkfish.rb 52 2009-01-07 02:08:11Z deveiant $" + SVNID_PATTERN = / + \$Id:\s + (\S+)\s # filename + (\d+)\s # rev + (\d{4}-\d{2}-\d{2})\s # Date (YYYY-MM-DD) + (\d{2}:\d{2}:\d{2}Z)\s # Time (HH:MM:SSZ) + (\w+)\s # committer + \$$ + /x + + ## + # Try to extract Subversion information out of the first constant whose + # value looks like a subversion Id tag. If no matching constant is found, + # and empty hash is returned. + + def get_svninfo klass + constants = klass.constants or return {} + + constants.find { |c| c.value =~ SVNID_PATTERN } or return {} + + filename, rev, date, time, committer = $~.captures + commitdate = Time.parse "#{date} #{time}" + + return { + :filename => filename, + :rev => Integer(rev), + :commitdate => commitdate, + :commitdelta => time_delta_string(Time.now - commitdate), + :committer => committer, + } + end + + ## + # Load and render the erb template in the given +template_file+ and write + # it out to +out_file+. + # + # Both +template_file+ and +out_file+ should be Pathname-like objects. + # + # An io will be yielded which must be captured by binding in the caller. + + def render_template template_file, out_file # :yield: io + template = template_for template_file + + unless @options.dry_run then + debug_msg "Outputting to %s" % [out_file.expand_path] + + out_file.dirname.mkpath + out_file.open 'w', 0644 do |io| + io.set_encoding @options.encoding if Object.const_defined? :Encoding + + context = yield io + + template_result template, context, template_file + end + else + context = yield nil + + output = template_result template, context, template_file + + debug_msg " would have written %d characters to %s" % [ + output.length, out_file.expand_path + ] + end + end + + ## + # Creates the result for +template+ with +context+. If an error is raised a + # Pathname +template_file+ will indicate the file where the error occurred. + + def template_result template, context, template_file + template.filename = template_file.to_s + template.result context + rescue NoMethodError => e + raise RDoc::Error, "Error while evaluating %s: %s" % [ + template_file.expand_path, + e.message, + ], e.backtrace + end + + ## + # Retrieves a cache template for +file+, if present, or fills the cache. + + def template_for file + template = @template_cache[file] + + return template if template + + klass = @options.dry_run ? ERB : RDoc::ERBIO + + template = klass.new file.read, nil, '<>' + @template_cache[file] = template + template + end - ### Copy over the stylesheet into the appropriate place in the output - ### directory. - def write_style_sheet - debug_msg "Copying static files" - options = { :verbose => $DEBUG_RDOC, :noop => $DARKFISH_DRYRUN } - - FileUtils.cp @template_dir + 'rdoc.css', '.', options - - Dir[(@template_dir + "{js,images}/**/*").to_s].each do |path| - next if File.directory? path - next if path =~ /#{File::SEPARATOR}\./ - - dst = Pathname.new(path).relative_path_from @template_dir - - # I suck at glob - dst_dir = dst.dirname - FileUtils.mkdir_p dst_dir, options unless File.exist? dst_dir - - FileUtils.cp @template_dir + path, dst, options - end - end - - ### Build the initial indices and output objects - ### based on an array of TopLevel objects containing - ### the extracted information. - def generate( top_levels ) - @outputdir = Pathname.new( @options.op_dir ).expand_path( @basedir ) - - @files = top_levels.sort - @classes = RDoc::TopLevel.all_classes_and_modules.sort - @methods = @classes.map { |m| m.method_list }.flatten.sort - @modsort = get_sorted_module_list( @classes ) - - # Now actually write the output - write_style_sheet - generate_index - generate_class_files - generate_file_files - - rescue StandardError => err - debug_msg "%s: %s\n %s" % [ err.class.name, err.message, err.backtrace.join("\n ") ] - raise - end - - ######### - protected - ######### - - ### Return a list of the documented modules sorted by salience first, then - ### by name. - def get_sorted_module_list( classes ) - nscounts = classes.inject({}) do |counthash, klass| - top_level = klass.full_name.gsub( /::.*/, '' ) - counthash[top_level] ||= 0 - counthash[top_level] += 1 - - counthash - end - - # Sort based on how often the top level namespace occurs, and then on the - # name of the module -- this works for projects that put their stuff into - # a namespace, of course, but doesn't hurt if they don't. - classes.sort_by do |klass| - top_level = klass.full_name.gsub( /::.*/, '' ) - [ - nscounts[ top_level ] * -1, - klass.full_name - ] - end.select do |klass| - klass.document_self - end - end - - ### Generate an index page which lists all the classes which - ### are documented. - def generate_index - template_file = @template_dir + 'index.rhtml' - return unless template_file.exist? - - debug_msg "Rendering the index page..." - - template_src = template_file.read - template = ERB.new( template_src, nil, '<>' ) - template.filename = template_file.to_s - context = binding() - - output = nil - - begin - output = template.result( context ) - rescue NoMethodError => err - raise RDoc::Error, "Error while evaluating %s: %s (at %p)" % [ - template_file, - err.message, - eval( "_erbout[-50,50]", context ) - ], err.backtrace - end - - outfile = @basedir + @options.op_dir + 'index.html' - unless $DARKFISH_DRYRUN - debug_msg "Outputting to %s" % [outfile.expand_path] - outfile.open( 'w', 0644 ) do |fh| - fh.print( output ) - end - else - debug_msg "Would have output to %s" % [outfile.expand_path] - end - end - - ### Generate a documentation file for each class - def generate_class_files - template_file = @template_dir + 'classpage.rhtml' - return unless template_file.exist? - debug_msg "Generating class documentation in #@outputdir" - - @classes.each do |klass| - debug_msg " working on %s (%s)" % [ klass.full_name, klass.path ] - outfile = @outputdir + klass.path - rel_prefix = @outputdir.relative_path_from( outfile.dirname ) - svninfo = self.get_svninfo( klass ) - - debug_msg " rendering #{outfile}" - self.render_template( template_file, binding(), outfile ) - end - end - - ### Generate a documentation file for each file - def generate_file_files - template_file = @template_dir + 'filepage.rhtml' - return unless template_file.exist? - debug_msg "Generating file documentation in #@outputdir" - - @files.each do |file| - outfile = @outputdir + file.path - debug_msg " working on %s (%s)" % [ file.full_name, outfile ] - rel_prefix = @outputdir.relative_path_from( outfile.dirname ) - - debug_msg " rendering #{outfile}" - self.render_template( template_file, binding(), outfile ) - end - end - - - ### Return a string describing the amount of time in the given number of - ### seconds in terms a human can understand easily. - def time_delta_string( seconds ) - return 'less than a minute' if seconds < 1.minute - return (seconds / 1.minute).to_s + ' minute' + (seconds/60 == 1 ? '' : 's') if seconds < 50.minutes - return 'about one hour' if seconds < 90.minutes - return (seconds / 1.hour).to_s + ' hours' if seconds < 18.hours - return 'one day' if seconds < 1.day - return 'about one day' if seconds < 2.days - return (seconds / 1.day).to_s + ' days' if seconds < 1.week - return 'about one week' if seconds < 2.week - return (seconds / 1.week).to_s + ' weeks' if seconds < 3.months - return (seconds / 1.month).to_s + ' months' if seconds < 1.year - return (seconds / 1.year).to_s + ' years' - end - - - # %q$Id: darkfish.rb 52 2009-01-07 02:08:11Z deveiant $" - SVNID_PATTERN = / - \$Id:\s - (\S+)\s # filename - (\d+)\s # rev - (\d{4}-\d{2}-\d{2})\s # Date (YYYY-MM-DD) - (\d{2}:\d{2}:\d{2}Z)\s # Time (HH:MM:SSZ) - (\w+)\s # committer - \$$ - /x - - ### Try to extract Subversion information out of the first constant whose value looks like - ### a subversion Id tag. If no matching constant is found, and empty hash is returned. - def get_svninfo( klass ) - constants = klass.constants or return {} - - constants.find {|c| c.value =~ SVNID_PATTERN } or return {} - - filename, rev, date, time, committer = $~.captures - commitdate = Time.parse( date + ' ' + time ) - - return { - :filename => filename, - :rev => Integer( rev ), - :commitdate => commitdate, - :commitdelta => time_delta_string( Time.now.to_i - commitdate.to_i ), - :committer => committer, - } - end - - - ### Load and render the erb template in the given +template_file+ within the - ### specified +context+ (a Binding object) and write it out to +outfile+. - ### Both +template_file+ and +outfile+ should be Pathname-like objects. - - def render_template( template_file, context, outfile ) - template_src = template_file.read - template = ERB.new( template_src, nil, '<>' ) - template.filename = template_file.to_s - - output = begin - template.result( context ) - rescue NoMethodError => err - raise RDoc::Error, "Error while evaluating %s: %s (at %p)" % [ - template_file.to_s, - err.message, - eval( "_erbout[-50,50]", context ) - ], err.backtrace - end - - unless $DARKFISH_DRYRUN - outfile.dirname.mkpath - outfile.open( 'w', 0644 ) do |ofh| - ofh.print( output ) - end - else - debug_msg " would have written %d bytes to %s" % - [ output.length, outfile ] - end - end - -end # Roc::Generator::Darkfish - -# :stopdoc: - -### Time constants -module TimeConstantMethods # :nodoc: - - ### Number of seconds (returns receiver unmodified) - def seconds - return self - end - alias_method :second, :seconds - - ### Returns number of seconds in <receiver> minutes - def minutes - return self * 60 - end - alias_method :minute, :minutes - - ### Returns the number of seconds in <receiver> hours - def hours - return self * 60.minutes - end - alias_method :hour, :hours - - ### Returns the number of seconds in <receiver> days - def days - return self * 24.hours - end - alias_method :day, :days - - ### Return the number of seconds in <receiver> weeks - def weeks - return self * 7.days - end - alias_method :week, :weeks - - ### Returns the number of seconds in <receiver> fortnights - def fortnights - return self * 2.weeks - end - alias_method :fortnight, :fortnights - - ### Returns the number of seconds in <receiver> months (approximate) - def months - return self * 30.days - end - alias_method :month, :months - - ### Returns the number of seconds in <receiver> years (approximate) - def years - return (self * 365.25.days).to_i - end - alias_method :year, :years - - - ### Returns the Time <receiver> number of seconds before the - ### specified +time+. E.g., 2.hours.before( header.expiration ) - def before( time ) - return time - self - end - - - ### Returns the Time <receiver> number of seconds ago. (e.g., - ### expiration > 2.hours.ago ) - def ago - return self.before( ::Time.now ) - end - - - ### Returns the Time <receiver> number of seconds after the given +time+. - ### E.g., 10.minutes.after( header.expiration ) - def after( time ) - return time + self - end - - # Reads best without arguments: 10.minutes.from_now - def from_now - return self.after( ::Time.now ) - end -end # module TimeConstantMethods - - -# Extend Numeric with time constants -class Numeric # :nodoc: - include TimeConstantMethods end |