diff options
author | drbrain <drbrain@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2010-04-01 07:45:16 +0000 |
---|---|---|
committer | drbrain <drbrain@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2010-04-01 07:45:16 +0000 |
commit | 46580b51477355fece514573c88cb67030f4a502 (patch) | |
tree | 779c1a64466643461b3daa4cd9a3548b84f0fd55 /lib/rdoc/generator/darkfish.rb | |
parent | 9b40cdfe8c973a061c5683ad78c283b9ddb8b2e9 (diff) | |
download | ruby-46580b51477355fece514573c88cb67030f4a502.tar.gz |
Import RDoc 2.5
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@27147 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'lib/rdoc/generator/darkfish.rb')
-rw-r--r-- | lib/rdoc/generator/darkfish.rb | 459 |
1 files changed, 459 insertions, 0 deletions
diff --git a/lib/rdoc/generator/darkfish.rb b/lib/rdoc/generator/darkfish.rb new file mode 100644 index 0000000000..a25475d39f --- /dev/null +++ b/lib/rdoc/generator/darkfish.rb @@ -0,0 +1,459 @@ +#!ruby +# vim: noet ts=2 sts=8 sw=2 + +unless File.exist? File.expand_path('../.svn', __FILE__) then + require 'rubygems' + gem 'rdoc', '>= 2.4' +end + +require 'pp' +require 'pathname' +require 'fileutils' +require 'erb' + +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 $ +# +# == Author/s +# * Michael Granger (ged@FaerieMUD.org) +# +# == Contributors +# * Mahlon E. Smith (mahlon@martini.nu) +# * Eric Hodel (drbrain@segment7.net) +# +# == License +# +# Copyright (c) 2007, 2008, Michael Granger. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# * Neither the name of the author/s, nor the names of the project's +# contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +class RDoc::Generator::Darkfish + + RDoc::RDoc.add_generator( self ) + + include ERB::Util + + # Subversion rev + SVNRev = %$Rev: 52 $ + + # Subversion ID + SVNId = %$Id: darkfish.rb 52 2009-01-07 02:08:11Z deveiant $ + + # Path to this file's parent directory. Used to find templates and other + # resources. + GENERATOR_DIR = File.join 'rdoc', 'generator' + + # Release Version + VERSION = '1.1.6' + + # Directory where generated classes live relative to the root + CLASS_DIR = nil + + # Directory where generated files live relative to the root + FILE_DIR = nil + + + ################################################################# + ### C L A S S M E T H O D S + ################################################################# + + ### Standard generator factory method + def self::for( options ) + new( options ) + end + + + ################################################################# + ### I N S T A N C E M E T H O D S + ################################################################# + + ### Initialize a few instance variables before we start + def initialize( options ) + @options = options + + template = @options.template || 'darkfish' + + 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 + + raise RDoc::Error, "could not find template #{template.inspect}" unless + template_dir + + @template_dir = Pathname.new File.expand_path(template_dir) + + @files = nil + @classes = nil + + @basedir = Pathname.pwd.expand_path + end + + ###### + public + ###### + + # The output directory + attr_reader :outputdir + + + ### Output progress information if debugging is enabled + def debug_msg( *msg ) + return unless $DEBUG_RDOC + $stderr.puts( *msg ) + end + + def class_dir + CLASS_DIR + end + + def file_dir + FILE_DIR + end + + ### Create the directories the generated docs will live in if + ### they don't already exist. + def gen_sub_directories + @outputdir.mkpath + 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 ) + context = binding() + + 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 + |