# RDoc creates a namespace conflict with IRB within 'rdoc/parsers/parse_rb'
# In that file, RubyToken and RubyLex get defined in the Object namespace,
# which will conflict with prior definitions from, for instance, IRB.
#
# This code redefines the RDoc RubyToken and RubyLex within the RDoc
# namespace. RDoc is not affected because all includes and uses of
# RubyToken and RubyLex are set when RDoc is loaded. The single exception
# I know of are several calls to class methods of RubyLex (ex RubyLex.debug?).
# These calls will be routed to the existing RubyLex.
#
# Uses of the existing RubyToken and RubyLex (as by irb) should be
# unaffected as the constants are reset after RDoc loads.
#
if Object.const_defined?(:RubyToken) || Object.const_defined?(:RubyLex)
class Object # :nodoc:
old_ruby_token = const_defined?(:RubyToken) ? remove_const(:RubyToken) : nil
old_ruby_lex = const_defined?(:RubyLex) ? remove_const(:RubyLex) : nil
require 'rdoc/rdoc'
# if by chance rdoc has ALREADY been loaded then requiring
# rdoc will not reset RubyToken and RubyLex... in this case
# the old constants are what you want.
new_ruby_token = const_defined?(:RubyToken) ? remove_const(:RubyToken) : old_ruby_token
new_ruby_lex = const_defined?(:RubyLex) ? remove_const(:RubyLex) : old_ruby_lex
RDoc.const_set(:RubyToken, new_ruby_token)
RDoc.const_set(:RubyLex, new_ruby_lex)
const_set(:RubyToken, old_ruby_token) unless old_ruby_token == nil
const_set(:RubyLex, old_ruby_lex) unless old_ruby_lex == nil
end
else
require 'rdoc/rdoc'
if Object.const_defined?(:RubyToken) && !RDoc.const_defined?(:RubyToken)
class Object # :nodoc:
RDoc.const_set(:RubyToken, remove_const(:RubyToken))
end
end
if Object.const_defined?(:RubyLex) && !RDoc.const_defined?(:RubyLex)
class Object # :nodoc:
RDoc.const_set(:RubyLex, remove_const(:RubyLex))
RDoc::RubyLex.const_set(:RubyLex, RDoc::RubyLex)
end
end
end
unless Object.const_defined?(:TokenStream)
TokenStream = RDoc::TokenStream
Options = RDoc::Options
end
# CDoc hooks into and extends RDoc to make Configurable documentation available
# as attributes. CDoc provides an extension to the standard RDoc HTMLGenerator
# and template.
#
# === Usage
# To generate task documentation with configuration information, CDoc must be
# loaded and the appropriate flags passed to rdoc . Essentially what you want
# is:
#
# % rdoc --fmt cdoc --template cdoc/cdoc_html_template [file_names....]
#
# Unfortunately, there is no way to load or require a file into the rdoc
# utility directly; the above code causes an 'Invalid output formatter' error.
# However, CDoc is easy to utilize from a Rake::RDocTask:
#
# require 'rake'
# require 'rake/rdoctask'
#
# desc 'Generate documentation.'
# Rake::RDocTask.new(:rdoc) do |rdoc|
# require 'cdoc'
# rdoc.template = 'cdoc/cdoc_html_template'
# rdoc.options << '--fmt' << 'cdoc'
#
# # specify whatever else you need
# # rdoc.rdoc_files.include(...)
# end
#
# Now execute the rake task like:
#
# % rake rdoc
#
# === Implementation
#
# RDoc is a beast to utilize in a non-standard way. One way to make RDoc parse
# unexpected flags like 'config' or 'config_attr' is to use the '--accessor'
# option (see 'rdoc --help' or the RDoc documentation for more details).
#
# CDoc hooks into the '--accessor' parsing process to pull out configuration
# attributes and format them into their own Configuration section on an RDoc
# html page. When 'cdoc' is specified as an rdoc option, CDoc in effect sets
# accessor flags for all the standard Task configuration methods, and then
# extends the RDoc::RubyParser handle these specially.
#
# If cdoc is not specified as the rdoc format, CDoc does not affect the RDoc
# output. Similarly, the configuration attributes will not appear in the
# output unless you specify a template that utilizes them.
#
# ==== Namespace conflicts
#
# RDoc creates a namespace conflict with other libraries that define RubyToken
# and RubyLex in the Object namespace (the prime example being IRB). CDoc checks
# for such a conflict and redfines the RDoc versions of RubyToken and RubyLex
# within the RDoc namespace. Essentially:
#
# original constant redefined constant
# RubyToken RDoc::RubyToken
# RubyLex RDoc::RubyLex
#
# The redefinition should not affect the existing (non RDoc) RubyToken and
# RubyLex constants, but if you directly use the RDoc versions after loading
# CDoc, you should be aware that they must be accessed through the new
# constants. Unfortunatley the trick is not seamless. The RDoc RubyLex makes
# a few calls to the RubyLex class method 'debug?'... these will be issued to
# the existing (non RDoc) RubyLex method and not the redefined
# RDoc::RubyLex.debug?
#
# In addition, because of the RubyLex calls, the RDoc::RubyLex cannot be fully
# hidden when CDoc is loaded before the conflicting RubyLex; you cannot load
# CDoc before loading IRB without raising warnings.
#
# Luckily all these troubles can be avoided very easily by not loading CDoc or
# RDoc when you're in irb. On the plus side, going against what I just said,
# you can now access/use RDoc within irb by requiring 'cdoc'.
#
#--
# Note that tap-0.10.0 heavily refactored CDoc functionality out of the old CDoc
# and into Lazydoc, and changed the declaration syntax for configurations. These
# changes also affected the implementation of CDoc. Mostly the changes are hacks
# to get the old system to work in the new system... as hacky as the old CDoc was,
# now this CDoc is hacky AND may have cruft. Until it breaks completely, I leave
# it as is... ugly and hard to fathom.
#
module CDoc
# Encasulates information about the configuration. Designed to be utilized
# by the CDocHTMLGenerator as similarly as possible to standard attributes.
class ConfigAttr < RDoc::Attr # :nodoc:
# Contains the actual declaration for the config attribute. ex: "c [:key, 'value'] # comment"
attr_accessor :config_declaration, :default
def initialize(*args)
@comment = nil # suppress a warning in Ruby 1.9
super
end
alias original_comment comment
def desc
case text.to_s
when /^#--(.*)/ then $1.strip
when /^#(.*)/ then $1.strip
else
nil
end
end
# The description for the config. Comment is formed from the standard
# attribute comment and the text following the attribute, which is slightly
# different than normal:
#
# # standard comment
# attr_accessor :attribute
#
# # standard comment
# config_accessor :config # ...added to standard comment
#
# c [:key, 'value'] # hence you can comment inline like this.
#
# The comments for each of these will be:
# attribute:: standard comment
# config:: standard comment ...added to standard comment
# key:: hence you can comment inline like this.
#
def comment(add_default=true)
# this would include the trailing comment...
# text_comment = text.to_s.sub(/^#--.*/m, '')
#original_comment.to_s + text_comment + (default && add_default ? " (#{default})" : "")
comment = original_comment.to_s.strip
comment = desc.to_s if comment.empty?
comment + (default && add_default ? " (#{default})" : "")
end
end
module CodeObjectAccess # :nodoc:
def comment_sections(section_regexp=//, normalize_comments=false)
res = {}
section = nil
lines = []
comment_lines = comment.split(/\r?\n/)
comment_lines << nil
comment_lines.each do |line|
case line
when nil, /^\s*#\s*=+(.*)/
next_section = (line == nil ? nil : $1.to_s.strip)
if section =~ section_regexp
lines << "" unless normalize_comments
res[section] = lines.join("\n") unless section == nil
end
section = next_section
lines = []
else
if normalize_comments
line =~ /^\s*#\s?(.*)/
line = $1.to_s
end
lines << line
end
end
res
end
end
module ClassModuleAccess # :nodoc:
def find_class_or_module_named(name)
return self if full_name == name
(@classes.values + @modules.values).each do |c|
res = c.find_class_or_module_named(name)
return res if res
end
nil
end
def configurations
@attributes.select do |attribute|
attribute.kind_of?(CDoc::ConfigAttr)
end
end
def find_configuration_named(name)
@attributes.each do |attribute|
next unless attribute.kind_of?(CDoc::ConfigAttr)
return attribute if attribute.name == name
end
nil
end
end
# Overrides the new method automatically extend the new object with
# ConfigParser. Intended to be used like:
# RDoc::RubyParser.extend InitializeConfigParser
module InitializeConfigParser # :nodoc:
def new(*args)
parser = super
parser.extend ConfigParser
#parser.config_mode = 'config_accessor'
parser
end
end
# Provides methods extending an RDoc::RubyParser such that the parser will produce
# CDoc::ConfigAttr instances in the place of RDoc::Attr instances during attribute
# parsing.
module ConfigParser # :nodoc:
include RDoc::RubyToken
include TokenStream
CONFIG_ACCESSORS = ['config', 'config_attr']
# Gets tokens until the next TkNL
def get_tk_to_nl
tokens = []
while !(tk = get_tk).kind_of?(TkNL)
tokens.push tk
end
unget_tk(tk)
tokens
end
# Works like the original parse_attr_accessor, except that the arg
# name is parsed from the config syntax and added attribute will
# be a CDoc::ConfigAttr. For example:
#
# class ConfigClass
# include Configurable
# config :key, 'value' # comment
# end
#
# produces an attribute named :key in the current config_rw mode.
#
# (see 'rdoc/parsers/parse_rb' line 2509)
def parse_config(context, single, tk, comment)
tks = get_tk_to_nl
key_tk = nil
value_tk = nil
tks.each do |token|
next if token.kind_of?(TkSPACE)
if key_tk == nil
case token
when TkSYMBOL then key_tk = token
when TkLPAREN then next
else break
end
else
case token
when TkCOMMA then value_tk = token
else
value_tk = token if value_tk.kind_of?(TkCOMMA)
break
end
end
end
text = ""
if tks.last.kind_of?(TkCOMMENT)
text = tks.last.text.chomp("\n").chomp("\r")
unget_tk(tks.last)
# If nodoc is given, don't document
tmp = RDoc::CodeObject.new
read_documentation_modifiers(tmp, RDoc::ATTR_MODIFIERS)
text = nil unless tmp.document_self
end
tks.reverse_each {|token| unget_tk(token) }
return if key_tk == nil || text == nil
arg = key_tk.text[1..-1]
default = nil
if value_tk
if text =~ /(.*):no_default:(.*)/
text = $1 + $2
else
default = value_tk.text
end
end
att = CDoc::ConfigAttr.new(text, arg, "RW", comment)
att.config_declaration = get_tkread
att.default = default
context.add_attribute(att)
end
# Overrides the standard parse_attr_accessor method to hook in parsing
# of the config accessors. If the input token is not named as one of the
# CONFIG_ACCESSORS, it will be processed normally.
def parse_attr_accessor(context, single, tk, comment)
case tk.name
when 'config', 'config_attr'
parse_config(context, single, tk, comment)
else
super
end
end
end
end
# Register the CDoc generator (in case you want to actually use it).
# method echos RDoc generator registration (see 'rdoc/rdoc' line 76)
Generator = Struct.new(:file_name, :class_name, :key)
RDoc::RDoc::GENERATORS['cdoc'] = Generator.new(
"cdoc/cdoc_html_generator.rb",
"CDocHTMLGenerator".intern,
"cdoc")
# Add the extended accessors to context classes.
module RDoc # :nodoc:
class CodeObject # :nodoc:
include CDoc::CodeObjectAccess
end
class ClassModule # :nodoc:
include CDoc::ClassModuleAccess
end
end
# Override methods in Options to in effect incorporate the accessor
# flags for CDoc parsing. (see 'rdoc/options') Raise an error if an
# accessor flag has already been specified.
class Options # :nodoc:
alias cdoc_original_parse parse
def parse(argv, generators)
cdoc_original_parse(argv, generators)
return unless @generator_name == 'cdoc'
accessors = CDoc::ConfigParser::CONFIG_ACCESSORS
# check the config_accessor_flags for accessor conflicts
extra_accessor_flags.each_pair do |accessor, flag|
if accessors.include?(accessor)
raise OptionList.error("cdoc format already handles the accessor '#{accessor}'")
end
end
# extra_accessors will be nil if no extra accessors were
# specifed, otherwise it'll be a regexp like /^(...)$/
# the string subset assumes
# regexp.to_s # => /(?-mix:^(...)$)/
@extra_accessors ||= /^()$/
current_accessors_str = @extra_accessors.to_s[9..-4]
# echos the Regexp production code in rdoc/options.rb
# (see the parse method, line 501)
re = '^(' + current_accessors_str + accessors.map{|a| Regexp.quote(a)}.join('|') + ')$'
@extra_accessors = Regexp.new(re)
RDoc::RubyParser.extend CDoc::InitializeConfigParser
end
end