1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

Clean up namespacing of RI's classes

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@14953 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
drbrain 2008-01-08 10:18:41 +00:00
parent 929faf7906
commit 3e39ade457
20 changed files with 1362 additions and 1402 deletions

View file

@ -1,3 +1,7 @@
Tue Jan 8 19:17:29 2008 Eric Hodel <drbrain@segment7.net>
* lib/rdoc/*: Clean up namespacing of RI's classes.
Tue Jan 8 18:05:35 2008 Eric Hodel <drbrain@segment7.net> Tue Jan 8 18:05:35 2008 Eric Hodel <drbrain@segment7.net>
* bin/ri, lib/rdoc/ri/*: Replace with Ryan Davis' cached ri. * bin/ri, lib/rdoc/ri/*: Replace with Ryan Davis' cached ri.

4
bin/ri
View file

@ -1,6 +1,6 @@
#!/usr//bin/env ruby #!/usr//bin/env ruby
require 'rdoc/ri/ri_driver' require 'rdoc/ri/driver'
RDoc::RI::RiDriver.run ARGV RDoc::RI::Driver.run ARGV

View file

@ -1,10 +1,10 @@
require 'rdoc/generators' require 'rdoc/generators'
require 'rdoc/markup/simple_markup/to_flow' require 'rdoc/markup/simple_markup/to_flow'
require 'rdoc/ri/ri_cache' require 'rdoc/ri/cache'
require 'rdoc/ri/ri_reader' require 'rdoc/ri/reader'
require 'rdoc/ri/ri_writer' require 'rdoc/ri/writer'
require 'rdoc/ri/ri_descriptions' require 'rdoc/ri/descriptions'
class RDoc::Generators::RIGenerator class RDoc::Generators::RIGenerator
@ -25,7 +25,7 @@ class RDoc::Generators::RIGenerator
def initialize(options) #:not-new: def initialize(options) #:not-new:
@options = options @options = options
@ri_writer = RI::RiWriter.new(".") @ri_writer = RDoc::RI::Writer.new "."
@markup = SM::SimpleMarkup.new @markup = SM::SimpleMarkup.new
@to_flow = SM::ToFlow.new @to_flow = SM::ToFlow.new
@ -53,34 +53,35 @@ class RDoc::Generators::RIGenerator
def generate_class_info(cls) def generate_class_info(cls)
if cls === RDoc::NormalModule if cls === RDoc::NormalModule
cls_desc = RI::ModuleDescription.new cls_desc = RDoc::RI::ModuleDescription.new
else else
cls_desc = RI::ClassDescription.new cls_desc = RDoc::RI::ClassDescription.new
cls_desc.superclass = cls.superclass cls_desc.superclass = cls.superclass
end end
cls_desc.name = cls.name cls_desc.name = cls.name
cls_desc.full_name = cls.full_name cls_desc.full_name = cls.full_name
cls_desc.comment = markup(cls.comment) cls_desc.comment = markup(cls.comment)
cls_desc.attributes =cls.attributes.sort.map do |a| cls_desc.attributes = cls.attributes.sort.map do |a|
RI::Attribute.new(a.name, a.rw, markup(a.comment)) RDoc::RI::Attribute.new(a.name, a.rw, markup(a.comment))
end end
cls_desc.constants = cls.constants.map do |c| cls_desc.constants = cls.constants.map do |c|
RI::Constant.new(c.name, c.value, markup(c.comment)) RDoc::RI::Constant.new(c.name, c.value, markup(c.comment))
end end
cls_desc.includes = cls.includes.map do |i| cls_desc.includes = cls.includes.map do |i|
RI::IncludedModule.new(i.name) RDoc::RI::IncludedModule.new(i.name)
end end
class_methods, instance_methods = method_list(cls) class_methods, instance_methods = method_list(cls)
cls_desc.class_methods = class_methods.map do |m| cls_desc.class_methods = class_methods.map do |m|
RI::MethodSummary.new(m.name) RDoc::RI::MethodSummary.new(m.name)
end end
cls_desc.instance_methods = instance_methods.map do |m| cls_desc.instance_methods = instance_methods.map do |m|
RI::MethodSummary.new(m.name) RDoc::RI::MethodSummary.new(m.name)
end end
update_or_replace(cls_desc) update_or_replace(cls_desc)
@ -94,9 +95,8 @@ class RDoc::Generators::RIGenerator
end end
end end
def generate_method_info(cls_desc, method) def generate_method_info(cls_desc, method)
meth_desc = RI::MethodDescription.new meth_desc = RDoc::RI::MethodDescription.new
meth_desc.name = method.name meth_desc.name = method.name
meth_desc.full_name = cls_desc.full_name meth_desc.full_name = cls_desc.full_name
if method.singleton if method.singleton
@ -113,7 +113,7 @@ class RDoc::Generators::RIGenerator
meth_desc.block_params = method.block_params meth_desc.block_params = method.block_params
meth_desc.aliases = method.aliases.map do |a| meth_desc.aliases = method.aliases.map do |a|
RI::AliasName.new(a.name) RDoc::RI::AliasName.new(a.name)
end end
@ri_writer.add_method(cls_desc, meth_desc) @ri_writer.add_method(cls_desc, meth_desc)
@ -190,7 +190,7 @@ class RDoc::Generators::RIGenerator
old_cls = nil old_cls = nil
if @options.merge if @options.merge
rdr = RI::RiReader.new(RI::RiCache.new(@options.op_dir)) rdr = RDoc::RI::Reader.new RDoc::RI::Cache.new(@options.op_dir)
namespace = rdr.top_level_namespace namespace = rdr.top_level_namespace
namespace = rdr.lookup_namespace_in(cls_desc.name, namespace) namespace = rdr.lookup_namespace_in(cls_desc.name, namespace)

View file

@ -1,7 +1,7 @@
# We handle the parsing of options, and subsequently as a singleton # We handle the parsing of options, and subsequently as a singleton
# object to be queried for option values # object to be queried for option values
require "rdoc/ri/ri_paths" require "rdoc/ri/paths"
require 'optparse' require 'optparse'
class RDoc::Options class RDoc::Options
@ -423,7 +423,7 @@ Usage: #{opt.program_name} [options] [names...]
"subsequent --op parameter, so no special", "subsequent --op parameter, so no special",
"privileges are needed.") do |value| "privileges are needed.") do |value|
@generator_name = "ri" @generator_name = "ri"
@op_dir = RI::Paths::HOMEDIR @op_dir = RDoc::RI::Paths::HOMEDIR
setup_generator setup_generator
end end
@ -435,7 +435,7 @@ Usage: #{opt.program_name} [options] [names...]
"making them accessible to others, so", "making them accessible to others, so",
"special privileges are needed.") do |value| "special privileges are needed.") do |value|
@generator_name = "ri" @generator_name = "ri"
@op_dir = RI::Paths::SITEDIR @op_dir = RDoc::RI::Paths::SITEDIR
setup_generator setup_generator
end end
@ -449,7 +449,7 @@ Usage: #{opt.program_name} [options] [names...]
"option is intended to be used during Ruby", "option is intended to be used during Ruby",
"installation.") do |value| "installation.") do |value|
@generator_name = "ri" @generator_name = "ri"
@op_dir = RI::Paths::SYSDIR @op_dir = RDoc::RI::Paths::SYSDIR
setup_generator setup_generator
end end

View file

@ -36,7 +36,7 @@ module RDoc
## ##
# Exception thrown by any rdoc error. # Exception thrown by any rdoc error.
class Error < StandardError; end class Error < RuntimeError; end
RDocError = Error # :nodoc: RDocError = Error # :nodoc:

188
lib/rdoc/ri/cache.rb Normal file
View file

@ -0,0 +1,188 @@
require 'rdoc/ri'
class RDoc::RI::ClassEntry
attr_reader :name
attr_reader :path_names
def initialize(path_name, name, in_class)
@path_names = [ path_name ]
@name = name
@in_class = in_class
@class_methods = []
@instance_methods = []
@inferior_classes = []
end
# We found this class in more tha one place, so add
# in the name from there.
def add_path(path)
@path_names << path
end
# read in our methods and any classes
# and modules in our namespace. Methods are
# stored in files called name-c|i.yaml,
# where the 'name' portion is the external
# form of the method name and the c|i is a class|instance
# flag
def load_from(dir)
Dir.foreach(dir) do |name|
next if name =~ /^\./
# convert from external to internal form, and
# extract the instance/class flag
if name =~ /^(.*?)-(c|i).yaml$/
external_name = $1
is_class_method = $2 == "c"
internal_name = RiWriter.external_to_internal(external_name)
list = is_class_method ? @class_methods : @instance_methods
path = File.join(dir, name)
list << MethodEntry.new(path, internal_name, is_class_method, self)
else
full_name = File.join(dir, name)
if File.directory?(full_name)
inf_class = @inferior_classes.find {|c| c.name == name }
if inf_class
inf_class.add_path(full_name)
else
inf_class = ClassEntry.new(full_name, name, self)
@inferior_classes << inf_class
end
inf_class.load_from(full_name)
end
end
end
end
# Return a list of any classes or modules that we contain
# that match a given string
def contained_modules_matching(name)
@inferior_classes.find_all {|c| c.name[name]}
end
def classes_and_modules
@inferior_classes
end
# Return an exact match to a particular name
def contained_class_named(name)
@inferior_classes.find {|c| c.name == name}
end
# return the list of local methods matching name
# We're split into two because we need distinct behavior
# when called from the _toplevel_
def methods_matching(name, is_class_method)
local_methods_matching(name, is_class_method)
end
# Find methods matching 'name' in ourselves and in
# any classes we contain
def recursively_find_methods_matching(name, is_class_method)
res = local_methods_matching(name, is_class_method)
@inferior_classes.each do |c|
res.concat(c.recursively_find_methods_matching(name, is_class_method))
end
res
end
# Return our full name
def full_name
res = @in_class.full_name
res << "::" unless res.empty?
res << @name
end
# Return a list of all out method names
def all_method_names
res = @class_methods.map {|m| m.full_name }
@instance_methods.each {|m| res << m.full_name}
res
end
private
# Return a list of all our methods matching a given string.
# Is +is_class_methods+ if 'nil', we don't care if the method
# is a class method or not, otherwise we only return
# those methods that match
def local_methods_matching(name, is_class_method)
list = case is_class_method
when nil then @class_methods + @instance_methods
when true then @class_methods
when false then @instance_methods
else fail "Unknown is_class_method: #{is_class_method.inspect}"
end
list.find_all {|m| m.name; m.name[name]}
end
end
##
# A TopLevelEntry is like a class entry, but when asked to search for methods
# searches all classes, not just itself
class RDoc::RI::TopLevelEntry < RDoc::RI::ClassEntry
def methods_matching(name, is_class_method)
res = recursively_find_methods_matching(name, is_class_method)
end
def full_name
""
end
def module_named(name)
end
end
class RDoc::RI::MethodEntry
attr_reader :name
attr_reader :path_name
def initialize(path_name, name, is_class_method, in_class)
@path_name = path_name
@name = name
@is_class_method = is_class_method
@in_class = in_class
end
def full_name
res = @in_class.full_name
unless res.empty?
if @is_class_method
res << "::"
else
res << "#"
end
end
res << @name
end
end
##
# We represent everything know about all 'ri' files accessible to this program
class RDoc::RI::Cache
attr_reader :toplevel
def initialize(dirs)
# At the top level we have a dummy module holding the
# overall namespace
@toplevel = RDoc::RI::TopLevelEntry.new('', '::', nil)
dirs.each do |dir|
@toplevel.load_from(dir)
end
end
end

147
lib/rdoc/ri/descriptions.rb Normal file
View file

@ -0,0 +1,147 @@
require 'yaml'
require 'rdoc/markup/simple_markup/fragments'
require 'rdoc/ri'
#--
# Descriptions are created by RDoc (in ri_generator) and written out in
# serialized form into the documentation tree. ri then reads these to generate
# the documentation
#++
class RDoc::RI::RDoc::RI::NamedThing
attr_reader :name
def initialize(name)
@name = name
end
def <=>(other)
@name <=> other.name
end
def hash
@name.hash
end
def eql?(other)
@name.eql?(other)
end
end
class RDoc::RI::AliasName < RDoc::RI::RDoc::RI::NamedThing; end
class RDoc::RI::Attribute < RDoc::RI::RDoc::RI::NamedThing
attr_reader :rw, :comment
def initialize(name, rw, comment)
super(name)
@rw = rw
@comment = comment
end
end
class RDoc::RI::Constant < RDoc::RI::NamedThing
attr_reader :value, :comment
def initialize(name, value, comment)
super(name)
@value = value
@comment = comment
end
end
class RDoc::RI::IncludedModule < RDoc::RI::NamedThing; end
class RDoc::RI::MethodSummary < RDoc::RI::NamedThing
def initialize(name="")
super
end
end
class RDoc::RI::Description
attr_accessor :name
attr_accessor :full_name
attr_accessor :comment
def serialize
self.to_yaml
end
def self.deserialize(from)
YAML.load(from)
end
def <=>(other)
@name <=> other.name
end
end
class RDoc::RI::ModuleDescription < RDoc::RI::Description
attr_accessor :class_methods
attr_accessor :instance_methods
attr_accessor :attributes
attr_accessor :constants
attr_accessor :includes
# merge in another class desscription into this one
def merge_in(old)
merge(@class_methods, old.class_methods)
merge(@instance_methods, old.instance_methods)
merge(@attributes, old.attributes)
merge(@constants, old.constants)
merge(@includes, old.includes)
if @comment.nil? || @comment.empty?
@comment = old.comment
else
unless old.comment.nil? or old.comment.empty? then
@comment << SM::Flow::RULE.new
@comment.concat old.comment
end
end
end
def display_name
"Module"
end
# the 'ClassDescription' subclass overrides this
# to format up the name of a parent
def superclass_string
nil
end
private
def merge(into, from)
names = {}
into.each {|i| names[i.name] = i }
from.each {|i| names[i.name] = i }
into.replace(names.keys.sort.map {|n| names[n]})
end
end
class RDoc::RI::ClassDescription < RDoc::RI::ModuleDescription
attr_accessor :superclass
def display_name
"Class"
end
def superclass_string
if @superclass && @superclass != "Object"
@superclass
else
nil
end
end
end
class RDoc::RI::MethodDescription < RDoc::RI::Description
attr_accessor :is_class_method
attr_accessor :visibility
attr_accessor :block_params
attr_accessor :is_singleton
attr_accessor :aliases
attr_accessor :is_alias_for
attr_accessor :params
end

View file

@ -1,54 +1,42 @@
# This is a kind of 'flag' module. If you want to write your require 'rdoc/ri'
# own 'ri' display module (perhaps because you'r writing
# an IDE or somesuch beast), you simply write a class ##
# which implements the various 'display' methods in 'DefaultDisplay', # This is a kind of 'flag' module. If you want to write your own 'ri' display
# and include the 'RiDisplay' module in that class. # module (perhaps because you'r writing an IDE or somesuch beast), you simply
# write a class which implements the various 'display' methods in
# 'DefaultDisplay', and include the 'RiDisplay' module in that class.
# #
# To access your class from the command line, you can do # To access your class from the command line, you can do
# #
# ruby -r <your source file> ../ri .... # ruby -r <your source file> ../ri ....
#
# If folks _really_ want to do this from the command line,
# I'll build an option in
module RiDisplay module RDoc::RI::Display
@@display_class = nil @@display_class = nil
def RiDisplay.append_features(display_class) def self.append_features(display_class)
@@display_class = display_class @@display_class = display_class
end end
def RiDisplay.new(*args) def self.new(*args)
@@display_class.new(*args) @@display_class.new(*args)
end end
end end
###################################################################### ##
# # A paging display module. Uses the RDoc::RI::Formatter class to do the actual
# A paging display module. Uses the ri_formatter class to do the # presentation
# actual presentation
#
class DefaultDisplay class RDoc::RI::DefaultDisplay
include RiDisplay include RDoc::RI::Display
def initialize(formatter, width, use_stdout) def initialize(formatter, width, use_stdout)
@use_stdout = use_stdout @use_stdout = use_stdout
@formatter = formatter.new width, " " @formatter = formatter.new width, " "
end end
######################################################################
def display_usage
page do
RI::Options::OptionList.usage(short_form=true)
end
end
######################################################################
def display_method_info(method) def display_method_info(method)
page do page do
@formatter.draw_line(method.full_name) @formatter.draw_line(method.full_name)
@ -65,8 +53,6 @@ class DefaultDisplay
end end
end end
######################################################################
def display_class_info(klass, ri_reader) def display_class_info(klass, ri_reader)
page do page do
superclass = klass.superclass_string superclass = klass.superclass_string
@ -145,8 +131,7 @@ class DefaultDisplay
end end
end end
###################################################################### ##
# Display a list of method names # Display a list of method names
def display_method_list(methods) def display_method_list(methods)
@ -157,8 +142,6 @@ class DefaultDisplay
end end
end end
######################################################################
def display_class_list(namespaces) def display_class_list(namespaces)
page do page do
puts "More than one class or module matched your request. You can refine" puts "More than one class or module matched your request. You can refine"
@ -167,8 +150,6 @@ class DefaultDisplay
end end
end end
######################################################################
def list_known_classes(classes) def list_known_classes(classes)
if classes.empty? if classes.empty?
warn_no_database warn_no_database
@ -181,8 +162,6 @@ class DefaultDisplay
end end
end end
######################################################################
def list_known_names(names) def list_known_names(names)
if names.empty? if names.empty?
warn_no_database warn_no_database
@ -193,12 +172,8 @@ class DefaultDisplay
end end
end end
######################################################################
private private
######################################################################
def page def page
if pager = setup_pager then if pager = setup_pager then
begin begin
@ -215,8 +190,6 @@ class DefaultDisplay
rescue Errno::EPIPE rescue Errno::EPIPE
end end
######################################################################
def setup_pager def setup_pager
unless @use_stdout then unless @use_stdout then
for pager in [ ENV['PAGER'], "less", "more", 'pager' ].compact.uniq for pager in [ ENV['PAGER'], "less", "more", 'pager' ].compact.uniq
@ -227,8 +200,6 @@ class DefaultDisplay
end end
end end
######################################################################
def display_params(method) def display_params(method)
params = method.params params = method.params
@ -248,7 +219,6 @@ class DefaultDisplay
@formatter.wrap("Extension from #{method.source_path}") @formatter.wrap("Extension from #{method.source_path}")
end end
end end
######################################################################
def display_flow(flow) def display_flow(flow)
if !flow || flow.empty? if !flow || flow.empty?
@ -258,8 +228,6 @@ class DefaultDisplay
end end
end end
######################################################################
def warn_no_database def warn_no_database
puts "No ri data found" puts "No ri data found"
puts puts
@ -272,4 +240,5 @@ class DefaultDisplay
puts "If you installed Ruby from a packaging system, then you may need to" puts "If you installed Ruby from a packaging system, then you may need to"
puts "install an additional package, or ask the packager to enable ri generation." puts "install an additional package, or ask the packager to enable ri generation."
end end
end # class RiDisplay end

View file

@ -2,20 +2,20 @@ require 'optparse'
require 'yaml' require 'yaml'
require 'rdoc/ri' require 'rdoc/ri'
require 'rdoc/ri/ri_paths' require 'rdoc/ri/paths'
require 'rdoc/ri/ri_formatter' require 'rdoc/ri/formatter'
require 'rdoc/ri/ri_display' require 'rdoc/ri/display'
require 'fileutils' require 'fileutils'
require 'rdoc/markup/simple_markup' require 'rdoc/markup/simple_markup'
require 'rdoc/markup/simple_markup/to_flow' require 'rdoc/markup/simple_markup/to_flow'
class RDoc::RI::RiDriver class RDoc::RI::Driver
def self.process_args(argv) def self.process_args(argv)
options = {} options = {}
options[:use_stdout] = !$stdout.tty? options[:use_stdout] = !$stdout.tty?
options[:width] = 72 options[:width] = 72
options[:formatter] = RI::TextFormatter.for 'plain' options[:formatter] = RDoc::RI::Formatter.for 'plain'
options[:list_classes] = false options[:list_classes] = false
options[:list_names] = false options[:list_names] = false
@ -33,12 +33,12 @@ class RDoc::RI::RiDriver
opt.summary_indent = ' ' * 4 opt.summary_indent = ' ' * 4
directories = [ directories = [
RI::Paths::SYSDIR, RDoc::RI::Paths::SYSDIR,
RI::Paths::SITEDIR, RDoc::RI::Paths::SITEDIR,
RI::Paths::HOMEDIR RDoc::RI::Paths::HOMEDIR
] ]
if RI::Paths::GEMDIRS then if RDoc::RI::Paths::GEMDIRS then
Gem.path.each do |dir| Gem.path.each do |dir|
directories << "#{dir}/doc/*/ri" directories << "#{dir}/doc/*/ri"
end end
@ -109,19 +109,19 @@ Options may also be set in the 'RI' environment variable.
opt.separator nil opt.separator nil
opt.on("--fmt=FORMAT", "--format=FORMAT", "-f", opt.on("--fmt=FORMAT", "--format=FORMAT", "-f",
RI::TextFormatter.list.split(', '), # HACK RDoc::RI::Formatter::FORMATTERS.keys,
"Format to use when displaying output:", "Format to use when displaying output:",
" #{RI::TextFormatter.list}", " #{RDoc::RI::Formatter.list}",
"Use 'bs' (backspace) with most pager", "Use 'bs' (backspace) with most pager",
"programs. To use ANSI, either disable the", "programs. To use ANSI, either disable the",
"pager or tell the pager to allow control", "pager or tell the pager to allow control",
"characters.") do |value| "characters.") do |value|
options[:formatter] = RI::TextFormatter.for value options[:formatter] = RDoc::RI::Formatter.for value
end end
opt.separator nil opt.separator nil
if RI::Paths::GEMDIRS then unless RDoc::RI::Paths::GEMDIRS.empty? then
opt.on("--[no-]gems", opt.on("--[no-]gems",
"Include documentation from RubyGems.") do |value| "Include documentation from RubyGems.") do |value|
use_gems = value use_gems = value
@ -180,10 +180,10 @@ Options may also be set in the 'RI' environment variable.
options[:names] = argv options[:names] = argv
options[:path] = RI::Paths.path(use_system, use_site, use_home, use_gems, options[:path] = RDoc::RI::Paths.path(use_system, use_site, use_home,
*doc_dirs) use_gems, *doc_dirs)
options[:raw_path] = RI::Paths.raw_path(use_system, use_site, use_home, options[:raw_path] = RDoc::RI::Paths.raw_path(use_system, use_site,
use_gems, *doc_dirs) use_home, use_gems, *doc_dirs)
options options
@ -204,17 +204,18 @@ Options may also be set in the 'RI' environment variable.
@names = options[:names] @names = options[:names]
@class_cache_name = 'classes' @class_cache_name = 'classes'
@all_dirs = RI::Paths.path(true, true, true, true) @all_dirs = RDoc::RI::Paths.path(true, true, true, true)
@homepath = RI::Paths.raw_path(false, false, true, false).first @homepath = RDoc::RI::Paths.raw_path(false, false, true, false).first
@homepath = @homepath.sub(/\.rdoc/, '.ri') @homepath = @homepath.sub(/\.rdoc/, '.ri')
@sys_dirs = RI::Paths.raw_path(true, false, false, false) @sys_dirs = RDoc::RI::Paths.raw_path(true, false, false, false)
FileUtils.mkdir_p cache_file_path unless File.directory? cache_file_path FileUtils.mkdir_p cache_file_path unless File.directory? cache_file_path
@class_cache = nil @class_cache = nil
@display = DefaultDisplay.new(options[:formatter], options[:width], @display = RDoc::RI::DefaultDisplay.new(options[:formatter],
options[:use_stdout]) options[:width],
options[:use_stdout])
end end
def class_cache def class_cache
@ -386,17 +387,6 @@ Options may also be set in the 'RI' environment variable.
cache cache
end end
# Couldn't find documentation in +path+, so tell the user what to do
def report_missing_documentation(path)
STDERR.puts "No ri documentation found in:"
path.each do |d|
STDERR.puts " #{d}"
end
STDERR.puts "\nWas rdoc run to create documentation?\n\n"
RDoc::usage("Installing Documentation")
end
end end
class Hash class Hash

662
lib/rdoc/ri/formatter.rb Normal file
View file

@ -0,0 +1,662 @@
require 'rdoc/ri'
class RDoc::RI::Formatter
attr_reader :indent
def initialize(width, indent)
@width = width
@indent = indent
end
######################################################################
def draw_line(label=nil)
len = @width
len -= (label.size+1) if label
print "-"*len
if label
print(" ")
bold_print(label)
end
puts
end
######################################################################
def wrap(txt, prefix=@indent, linelen=@width)
return unless txt && !txt.empty?
work = conv_markup(txt)
textLen = linelen - prefix.length
patt = Regexp.new("^(.{0,#{textLen}})[ \n]")
next_prefix = prefix.tr("^ ", " ")
res = []
while work.length > textLen
if work =~ patt
res << $1
work.slice!(0, $&.length)
else
res << work.slice!(0, textLen)
end
end
res << work if work.length.nonzero?
puts(prefix + res.join("\n" + next_prefix))
end
######################################################################
def blankline
puts
end
######################################################################
# called when we want to ensure a nbew 'wrap' starts on a newline
# Only needed for HtmlFormatter, because the rest do their
# own line breaking
def break_to_newline
end
######################################################################
def bold_print(txt)
print txt
end
######################################################################
def raw_print_line(txt)
puts txt
end
######################################################################
# convert HTML entities back to ASCII
def conv_html(txt)
txt.
gsub(/&gt;/, '>').
gsub(/&lt;/, '<').
gsub(/&quot;/, '"').
gsub(/&amp;/, '&')
end
# convert markup into display form
def conv_markup(txt)
txt.
gsub(%r{<tt>(.*?)</tt>}) { "+#$1+" } .
gsub(%r{<code>(.*?)</code>}) { "+#$1+" } .
gsub(%r{<b>(.*?)</b>}) { "*#$1*" } .
gsub(%r{<em>(.*?)</em>}) { "_#$1_" }
end
######################################################################
def display_list(list)
case list.type
when SM::ListBase::BULLET
prefixer = proc { |ignored| @indent + "* " }
when SM::ListBase::NUMBER,
SM::ListBase::UPPERALPHA,
SM::ListBase::LOWERALPHA
start = case list.type
when SM::ListBase::NUMBER then 1
when SM::ListBase::UPPERALPHA then 'A'
when SM::ListBase::LOWERALPHA then 'a'
end
prefixer = proc do |ignored|
res = @indent + "#{start}.".ljust(4)
start = start.succ
res
end
when SM::ListBase::LABELED
prefixer = proc do |li|
li.label
end
when SM::ListBase::NOTE
longest = 0
list.contents.each do |item|
if item.kind_of?(SM::Flow::LI) && item.label.length > longest
longest = item.label.length
end
end
prefixer = proc do |li|
@indent + li.label.ljust(longest+1)
end
else
fail "unknown list type"
end
list.contents.each do |item|
if item.kind_of? SM::Flow::LI
prefix = prefixer.call(item)
display_flow_item(item, prefix)
else
display_flow_item(item)
end
end
end
######################################################################
def display_flow_item(item, prefix=@indent)
case item
when SM::Flow::P, SM::Flow::LI
wrap(conv_html(item.body), prefix)
blankline
when SM::Flow::LIST
display_list(item)
when SM::Flow::VERB
display_verbatim_flow_item(item, @indent)
when SM::Flow::H
display_heading(conv_html(item.text), item.level, @indent)
when SM::Flow::RULE
draw_line
else
fail "Unknown flow element: #{item.class}"
end
end
######################################################################
def display_verbatim_flow_item(item, prefix=@indent)
item.body.split(/\n/).each do |line|
print @indent, conv_html(line), "\n"
end
blankline
end
######################################################################
def display_heading(text, level, indent)
text = strip_attributes(text)
case level
when 1
ul = "=" * text.length
puts
puts text.upcase
puts ul
# puts
when 2
ul = "-" * text.length
puts
puts text
puts ul
# puts
else
print indent, text, "\n"
end
end
def display_flow(flow)
flow.each do |f|
display_flow_item(f)
end
end
def strip_attributes(txt)
tokens = txt.split(%r{(</?(?:b|code|em|i|tt)>)})
text = []
attributes = 0
tokens.each do |tok|
case tok
when %r{^</(\w+)>$}, %r{^<(\w+)>$}
;
else
text << tok
end
end
text.join
end
end
##
# Handle text with attributes. We're a base class: there are different
# presentation classes (one, for example, uses overstrikes to handle bold and
# underlining, while another using ANSI escape sequences.
class RDoc::RI::AttributeFormatter < RDoc::RI::Formatter
BOLD = 1
ITALIC = 2
CODE = 4
ATTR_MAP = {
"b" => BOLD,
"code" => CODE,
"em" => ITALIC,
"i" => ITALIC,
"tt" => CODE
}
# TODO: struct?
class AttrChar
attr_reader :char
attr_reader :attr
def initialize(char, attr)
@char = char
@attr = attr
end
end
class AttributeString
attr_reader :txt
def initialize
@txt = []
@optr = 0
end
def <<(char)
@txt << char
end
def empty?
@optr >= @txt.length
end
# accept non space, then all following spaces
def next_word
start = @optr
len = @txt.length
while @optr < len && @txt[@optr].char != " "
@optr += 1
end
while @optr < len && @txt[@optr].char == " "
@optr += 1
end
@txt[start...@optr]
end
end
##
# Overrides base class. Looks for <tt>...</tt> etc sequences
# and generates an array of AttrChars. This array is then used
# as the basis for the split
def wrap(txt, prefix=@indent, linelen=@width)
return unless txt && !txt.empty?
txt = add_attributes_to(txt)
next_prefix = prefix.tr("^ ", " ")
linelen -= prefix.size
line = []
until txt.empty?
word = txt.next_word
if word.size + line.size > linelen
write_attribute_text(prefix, line)
prefix = next_prefix
line = []
end
line.concat(word)
end
write_attribute_text(prefix, line) if line.length > 0
end
protected
##
# overridden in specific formatters
def write_attribute_text(prefix, line)
print prefix
line.each do |achar|
print achar.char
end
puts
end
##
# again, overridden
def bold_print(txt)
print txt
end
private
def add_attributes_to(txt)
tokens = txt.split(%r{(</?(?:b|code|em|i|tt)>)})
text = AttributeString.new
attributes = 0
tokens.each do |tok|
case tok
when %r{^</(\w+)>$} then attributes &= ~(ATTR_MAP[$1]||0)
when %r{^<(\w+)>$} then attributes |= (ATTR_MAP[$1]||0)
else
tok.split(//).each {|ch| text << AttrChar.new(ch, attributes)}
end
end
text
end
end
##
# This formatter generates overstrike-style formatting, which works with
# pagers such as man and less.
class RDoc::RI::OverstrikeFormatter < RDoc::RI::AttributeFormatter
BS = "\C-h"
def write_attribute_text(prefix, line)
print prefix
line.each do |achar|
attr = achar.attr
if (attr & (ITALIC+CODE)) != 0
print "_", BS
end
if (attr & BOLD) != 0
print achar.char, BS
end
print achar.char
end
puts
end
##
# draw a string in bold
def bold_print(text)
text.split(//).each do |ch|
print ch, BS, ch
end
end
end
##
# This formatter uses ANSI escape sequences to colorize stuff works with
# pagers such as man and less.
class RDoc::RI::AnsiFormatter < RDoc::RI::AttributeFormatter
def initialize(*args)
print "\033[0m"
super
end
def write_attribute_text(prefix, line)
print prefix
curr_attr = 0
line.each do |achar|
attr = achar.attr
if achar.attr != curr_attr
update_attributes(achar.attr)
curr_attr = achar.attr
end
print achar.char
end
update_attributes(0) unless curr_attr.zero?
puts
end
def bold_print(txt)
print "\033[1m#{txt}\033[m"
end
HEADINGS = {
1 => [ "\033[1;32m", "\033[m" ] ,
2 => ["\033[4;32m", "\033[m" ],
3 => ["\033[32m", "\033[m" ]
}
def display_heading(text, level, indent)
level = 3 if level > 3
heading = HEADINGS[level]
print indent
print heading[0]
print strip_attributes(text)
puts heading[1]
end
private
ATTR_MAP = {
BOLD => "1",
ITALIC => "33",
CODE => "36"
}
def update_attributes(attr)
str = "\033["
for quality in [ BOLD, ITALIC, CODE]
unless (attr & quality).zero?
str << ATTR_MAP[quality]
end
end
print str, "m"
end
end
##
# This formatter uses HTML.
class RDoc::RI::HtmlFormatter < RDoc::RI::AttributeFormatter
def initialize(*args)
super
end
def write_attribute_text(prefix, line)
curr_attr = 0
line.each do |achar|
attr = achar.attr
if achar.attr != curr_attr
update_attributes(curr_attr, achar.attr)
curr_attr = achar.attr
end
print(escape(achar.char))
end
update_attributes(curr_attr, 0) unless curr_attr.zero?
end
def draw_line(label=nil)
if label != nil
bold_print(label)
end
puts("<hr>")
end
def bold_print(txt)
tag("b") { txt }
end
def blankline()
puts("<p>")
end
def break_to_newline
puts("<br>")
end
def display_heading(text, level, indent)
level = 4 if level > 4
tag("h#{level}") { text }
puts
end
def display_list(list)
case list.type
when SM::ListBase::BULLET
list_type = "ul"
prefixer = proc { |ignored| "<li>" }
when SM::ListBase::NUMBER,
SM::ListBase::UPPERALPHA,
SM::ListBase::LOWERALPHA
list_type = "ol"
prefixer = proc { |ignored| "<li>" }
when SM::ListBase::LABELED
list_type = "dl"
prefixer = proc do |li|
"<dt><b>" + escape(li.label) + "</b><dd>"
end
when SM::ListBase::NOTE
list_type = "table"
prefixer = proc do |li|
%{<tr valign="top"><td>#{li.label.gsub(/ /, '&nbsp;')}</td><td>}
end
else
fail "unknown list type"
end
print "<#{list_type}>"
list.contents.each do |item|
if item.kind_of? SM::Flow::LI
prefix = prefixer.call(item)
print prefix
display_flow_item(item, prefix)
else
display_flow_item(item)
end
end
print "</#{list_type}>"
end
def display_verbatim_flow_item(item, prefix=@indent)
print("<pre>")
item.body.split(/\n/).each do |line|
puts conv_html(line)
end
puts("</pre>")
end
private
ATTR_MAP = {
BOLD => "b>",
ITALIC => "i>",
CODE => "tt>"
}
def update_attributes(current, wanted)
str = ""
# first turn off unwanted ones
off = current & ~wanted
for quality in [ BOLD, ITALIC, CODE]
if (off & quality) > 0
str << "</" + ATTR_MAP[quality]
end
end
# now turn on wanted
for quality in [ BOLD, ITALIC, CODE]
unless (wanted & quality).zero?
str << "<" << ATTR_MAP[quality]
end
end
print str
end
def tag(code)
print("<#{code}>")
print(yield)
print("</#{code}>")
end
def escape(str)
str.
gsub(/&/n, '&amp;').
gsub(/\"/n, '&quot;').
gsub(/>/n, '&gt;').
gsub(/</n, '&lt;')
end
end
##
# This formatter reduces extra lines for a simpler output. It improves way
# output looks for tools like IRC bots.
class RDoc::RI::SimpleFormatter < RDoc::RI::Formatter
##
# No extra blank lines
def blankline
end
##
# Display labels only, no lines
def draw_line(label=nil)
unless label.nil? then
bold_print(label)
puts
end
end
##
# Place heading level indicators inline with heading.
def display_heading(text, level, indent)
text = strip_attributes(text)
case level
when 1
puts "= " + text.upcase
when 2
puts "-- " + text
else
print indent, text, "\n"
end
end
end
##
# Finally, fill in the list of known formatters
class RDoc::RI::Formatter
FORMATTERS = {
"plain" => RDoc::RI::Formatter,
"simple" => RDoc::RI::SimpleFormatter,
"bs" => RDoc::RI::OverstrikeFormatter,
"ansi" => RDoc::RI::AnsiFormatter,
"html" => RDoc::RI::HtmlFormatter,
}
def self.list
FORMATTERS.keys.sort.join(", ")
end
def self.for(name)
FORMATTERS[name.downcase]
end
end

97
lib/rdoc/ri/paths.rb Normal file
View file

@ -0,0 +1,97 @@
require 'rdoc/ri'
##
# Encapsulate all the strangeness to do with finding out where to find RDoc
# files
#
# We basically deal with three directories:
#
# 1. The 'system' documentation directory, which holds the documentation
# distributed with Ruby, and which is managed by the Ruby install process
# 2. The 'site' directory, which contains site-wide documentation added
# locally.
# 3. The 'user' documentation directory, stored under the user's own home
# directory.
#
# There's contention about all this, but for now:
#
# system:: $datadir/ri/<ver>/system/...
# site:: $datadir/ri/<ver>/site/...
# user:: ~/.rdoc
module RDoc::RI::Paths
#:stopdoc:
require 'rbconfig'
DOC_DIR = "doc/rdoc"
version = RbConfig::CONFIG['ruby_version']
base = File.join(RbConfig::CONFIG['datadir'], "ri", version)
SYSDIR = File.join(base, "system")
SITEDIR = File.join(base, "site")
homedir = ENV['HOME'] || ENV['USERPROFILE'] || ENV['HOMEPATH']
if homedir then
HOMEDIR = File.join(homedir, ".rdoc")
else
HOMEDIR = nil
end
# This is the search path for 'ri'
PATH = [ SYSDIR, SITEDIR, HOMEDIR ].find_all {|p| p && File.directory?(p)}
require 'rubygems' unless defined?(Gem) and Gem::Enable
# HACK dup'd from Gem.latest_partials and friends
all_paths = []
all_paths = Gem.path.map do |dir|
Dir[File.join(dir, 'doc', '*', 'ri')]
end.flatten
ri_paths = {}
all_paths.each do |dir|
base = File.basename File.dirname(dir)
if base =~ /(.*)-((\d+\.)*\d+)/ then
name, version = $1, $2
ver = Gem::Version.new version
if ri_paths[name].nil? or ver > ri_paths[name][0] then
ri_paths[name] = [ver, dir]
end
end
end
GEMDIRS = ri_paths.map { |k,v| v.last }.sort
GEMDIRS.each { |dir| PATH << dir }
# Returns the selected documentation directories as an Array, or PATH if no
# overriding directories were given.
def self.path(use_system, use_site, use_home, use_gems, *extra_dirs)
path = raw_path(use_system, use_site, use_home, use_gems, *extra_dirs)
return path.select { |directory| File.directory? directory }
end
# Returns the selected documentation directories including nonexistent
# directories. Used to print out what paths were searched if no ri was
# found.
def self.raw_path(use_system, use_site, use_home, use_gems, *extra_dirs)
return PATH unless use_system or use_site or use_home or use_gems or
not extra_dirs.empty?
path = []
path << extra_dirs unless extra_dirs.empty?
path << SYSDIR if use_system
path << SITEDIR if use_site
path << HOMEDIR if use_home
path << GEMDIRS if use_gems
return path.flatten.compact
end
end

106
lib/rdoc/ri/reader.rb Normal file
View file

@ -0,0 +1,106 @@
require 'rdoc/ri'
require 'rdoc/ri/descriptions'
require 'rdoc/ri/writer'
require 'rdoc/markup/simple_markup/to_flow'
class RDoc::RI::Reader
def initialize(ri_cache)
@cache = ri_cache
end
def top_level_namespace
[ @cache.toplevel ]
end
def lookup_namespace_in(target, namespaces)
result = []
for n in namespaces
result.concat(n.contained_modules_matching(target))
end
result
end
def find_class_by_name(full_name)
names = full_name.split(/::/)
ns = @cache.toplevel
for name in names
ns = ns.contained_class_named(name)
return nil if ns.nil?
end
get_class(ns)
end
def find_methods(name, is_class_method, namespaces)
result = []
namespaces.each do |ns|
result.concat ns.methods_matching(name, is_class_method)
end
result
end
##
# Return the MethodDescription for a given MethodEntry by deserializing the
# YAML
def get_method(method_entry)
path = method_entry.path_name
File.open(path) { |f| RI::Description.deserialize(f) }
end
##
# Return a class description
def get_class(class_entry)
result = nil
for path in class_entry.path_names
path = RiWriter.class_desc_path(path, class_entry)
desc = File.open(path) {|f| RI::Description.deserialize(f) }
if result
result.merge_in(desc)
else
result = desc
end
end
result
end
##
# Return the names of all classes and modules
def full_class_names
res = []
find_classes_in(res, @cache.toplevel)
end
##
# Return a list of all classes, modules, and methods
def all_names
res = []
find_names_in(res, @cache.toplevel)
end
private
def find_classes_in(res, klass)
classes = klass.classes_and_modules
for c in classes
res << c.full_name
find_classes_in(res, c)
end
res
end
def find_names_in(res, klass)
classes = klass.classes_and_modules
for c in classes
res << c.full_name
res.concat c.all_method_names
find_names_in(res, c)
end
res
end
end

View file

@ -1,187 +0,0 @@
module RI
class ClassEntry
attr_reader :name
attr_reader :path_names
def initialize(path_name, name, in_class)
@path_names = [ path_name ]
@name = name
@in_class = in_class
@class_methods = []
@instance_methods = []
@inferior_classes = []
end
# We found this class in more tha one place, so add
# in the name from there.
def add_path(path)
@path_names << path
end
# read in our methods and any classes
# and modules in our namespace. Methods are
# stored in files called name-c|i.yaml,
# where the 'name' portion is the external
# form of the method name and the c|i is a class|instance
# flag
def load_from(dir)
Dir.foreach(dir) do |name|
next if name =~ /^\./
# convert from external to internal form, and
# extract the instance/class flag
if name =~ /^(.*?)-(c|i).yaml$/
external_name = $1
is_class_method = $2 == "c"
internal_name = RiWriter.external_to_internal(external_name)
list = is_class_method ? @class_methods : @instance_methods
path = File.join(dir, name)
list << MethodEntry.new(path, internal_name, is_class_method, self)
else
full_name = File.join(dir, name)
if File.directory?(full_name)
inf_class = @inferior_classes.find {|c| c.name == name }
if inf_class
inf_class.add_path(full_name)
else
inf_class = ClassEntry.new(full_name, name, self)
@inferior_classes << inf_class
end
inf_class.load_from(full_name)
end
end
end
end
# Return a list of any classes or modules that we contain
# that match a given string
def contained_modules_matching(name)
@inferior_classes.find_all {|c| c.name[name]}
end
def classes_and_modules
@inferior_classes
end
# Return an exact match to a particular name
def contained_class_named(name)
@inferior_classes.find {|c| c.name == name}
end
# return the list of local methods matching name
# We're split into two because we need distinct behavior
# when called from the _toplevel_
def methods_matching(name, is_class_method)
local_methods_matching(name, is_class_method)
end
# Find methods matching 'name' in ourselves and in
# any classes we contain
def recursively_find_methods_matching(name, is_class_method)
res = local_methods_matching(name, is_class_method)
@inferior_classes.each do |c|
res.concat(c.recursively_find_methods_matching(name, is_class_method))
end
res
end
# Return our full name
def full_name
res = @in_class.full_name
res << "::" unless res.empty?
res << @name
end
# Return a list of all out method names
def all_method_names
res = @class_methods.map {|m| m.full_name }
@instance_methods.each {|m| res << m.full_name}
res
end
private
# Return a list of all our methods matching a given string.
# Is +is_class_methods+ if 'nil', we don't care if the method
# is a class method or not, otherwise we only return
# those methods that match
def local_methods_matching(name, is_class_method)
list = case is_class_method
when nil then @class_methods + @instance_methods
when true then @class_methods
when false then @instance_methods
else fail "Unknown is_class_method: #{is_class_method.inspect}"
end
list.find_all {|m| m.name; m.name[name]}
end
end
# A TopLevelEntry is like a class entry, but when asked to search
# for methods searches all classes, not just itself
class TopLevelEntry < ClassEntry
def methods_matching(name, is_class_method)
res = recursively_find_methods_matching(name, is_class_method)
end
def full_name
""
end
def module_named(name)
end
end
class MethodEntry
attr_reader :name
attr_reader :path_name
def initialize(path_name, name, is_class_method, in_class)
@path_name = path_name
@name = name
@is_class_method = is_class_method
@in_class = in_class
end
def full_name
res = @in_class.full_name
unless res.empty?
if @is_class_method
res << "::"
else
res << "#"
end
end
res << @name
end
end
# We represent everything know about all 'ri' files
# accessible to this program
class RiCache
attr_reader :toplevel
def initialize(dirs)
# At the top level we have a dummy module holding the
# overall namespace
@toplevel = TopLevelEntry.new('', '::', nil)
dirs.each do |dir|
@toplevel.load_from(dir)
end
end
end
end

View file

@ -1,154 +0,0 @@
require 'yaml'
require 'rdoc/markup/simple_markup/fragments'
# Descriptions are created by RDoc (in ri_generator) and
# written out in serialized form into the documentation
# tree. ri then reads these to generate the documentation
module RI
class NamedThing
attr_reader :name
def initialize(name)
@name = name
end
def <=>(other)
@name <=> other.name
end
def hash
@name.hash
end
def eql?(other)
@name.eql?(other)
end
end
# Alias = Struct.new(:old_name, :new_name)
class AliasName < NamedThing
end
class Attribute < NamedThing
attr_reader :rw, :comment
def initialize(name, rw, comment)
super(name)
@rw = rw
@comment = comment
end
end
class Constant < NamedThing
attr_reader :value, :comment
def initialize(name, value, comment)
super(name)
@value = value
@comment = comment
end
end
class IncludedModule < NamedThing
end
class MethodSummary < NamedThing
def initialize(name="")
super
end
end
class Description
attr_accessor :name
attr_accessor :full_name
attr_accessor :comment
def serialize
self.to_yaml
end
def Description.deserialize(from)
YAML.load(from)
end
def <=>(other)
@name <=> other.name
end
end
class ModuleDescription < Description
attr_accessor :class_methods
attr_accessor :instance_methods
attr_accessor :attributes
attr_accessor :constants
attr_accessor :includes
# merge in another class desscription into this one
def merge_in(old)
merge(@class_methods, old.class_methods)
merge(@instance_methods, old.instance_methods)
merge(@attributes, old.attributes)
merge(@constants, old.constants)
merge(@includes, old.includes)
if @comment.nil? || @comment.empty?
@comment = old.comment
else
unless old.comment.nil? or old.comment.empty? then
@comment << SM::Flow::RULE.new
@comment.concat old.comment
end
end
end
def display_name
"Module"
end
# the 'ClassDescription' subclass overrides this
# to format up the name of a parent
def superclass_string
nil
end
private
def merge(into, from)
names = {}
into.each {|i| names[i.name] = i }
from.each {|i| names[i.name] = i }
into.replace(names.keys.sort.map {|n| names[n]})
end
end
class ClassDescription < ModuleDescription
attr_accessor :superclass
def display_name
"Class"
end
def superclass_string
if @superclass && @superclass != "Object"
@superclass
else
nil
end
end
end
class MethodDescription < Description
attr_accessor :is_class_method
attr_accessor :visibility
attr_accessor :block_params
attr_accessor :is_singleton
attr_accessor :aliases
attr_accessor :is_alias_for
attr_accessor :params
end
end

View file

@ -1,673 +0,0 @@
module RI
class TextFormatter
attr_reader :indent
def initialize(width, indent)
@width = width
@indent = indent
end
######################################################################
def draw_line(label=nil)
len = @width
len -= (label.size+1) if label
print "-"*len
if label
print(" ")
bold_print(label)
end
puts
end
######################################################################
def wrap(txt, prefix=@indent, linelen=@width)
return unless txt && !txt.empty?
work = conv_markup(txt)
textLen = linelen - prefix.length
patt = Regexp.new("^(.{0,#{textLen}})[ \n]")
next_prefix = prefix.tr("^ ", " ")
res = []
while work.length > textLen
if work =~ patt
res << $1
work.slice!(0, $&.length)
else
res << work.slice!(0, textLen)
end
end
res << work if work.length.nonzero?
puts(prefix + res.join("\n" + next_prefix))
end
######################################################################
def blankline
puts
end
######################################################################
# called when we want to ensure a nbew 'wrap' starts on a newline
# Only needed for HtmlFormatter, because the rest do their
# own line breaking
def break_to_newline
end
######################################################################
def bold_print(txt)
print txt
end
######################################################################
def raw_print_line(txt)
puts txt
end
######################################################################
# convert HTML entities back to ASCII
def conv_html(txt)
txt.
gsub(/&gt;/, '>').
gsub(/&lt;/, '<').
gsub(/&quot;/, '"').
gsub(/&amp;/, '&')
end
# convert markup into display form
def conv_markup(txt)
txt.
gsub(%r{<tt>(.*?)</tt>}) { "+#$1+" } .
gsub(%r{<code>(.*?)</code>}) { "+#$1+" } .
gsub(%r{<b>(.*?)</b>}) { "*#$1*" } .
gsub(%r{<em>(.*?)</em>}) { "_#$1_" }
end
######################################################################
def display_list(list)
case list.type
when SM::ListBase::BULLET
prefixer = proc { |ignored| @indent + "* " }
when SM::ListBase::NUMBER,
SM::ListBase::UPPERALPHA,
SM::ListBase::LOWERALPHA
start = case list.type
when SM::ListBase::NUMBER then 1
when SM::ListBase::UPPERALPHA then 'A'
when SM::ListBase::LOWERALPHA then 'a'
end
prefixer = proc do |ignored|
res = @indent + "#{start}.".ljust(4)
start = start.succ
res
end
when SM::ListBase::LABELED
prefixer = proc do |li|
li.label
end
when SM::ListBase::NOTE
longest = 0
list.contents.each do |item|
if item.kind_of?(SM::Flow::LI) && item.label.length > longest
longest = item.label.length
end
end
prefixer = proc do |li|
@indent + li.label.ljust(longest+1)
end
else
fail "unknown list type"
end
list.contents.each do |item|
if item.kind_of? SM::Flow::LI
prefix = prefixer.call(item)
display_flow_item(item, prefix)
else
display_flow_item(item)
end
end
end
######################################################################
def display_flow_item(item, prefix=@indent)
case item
when SM::Flow::P, SM::Flow::LI
wrap(conv_html(item.body), prefix)
blankline
when SM::Flow::LIST
display_list(item)
when SM::Flow::VERB
display_verbatim_flow_item(item, @indent)
when SM::Flow::H
display_heading(conv_html(item.text), item.level, @indent)
when SM::Flow::RULE
draw_line
else
fail "Unknown flow element: #{item.class}"
end
end
######################################################################
def display_verbatim_flow_item(item, prefix=@indent)
item.body.split(/\n/).each do |line|
print @indent, conv_html(line), "\n"
end
blankline
end
######################################################################
def display_heading(text, level, indent)
text = strip_attributes(text)
case level
when 1
ul = "=" * text.length
puts
puts text.upcase
puts ul
# puts
when 2
ul = "-" * text.length
puts
puts text
puts ul
# puts
else
print indent, text, "\n"
end
end
def display_flow(flow)
flow.each do |f|
display_flow_item(f)
end
end
def strip_attributes(txt)
tokens = txt.split(%r{(</?(?:b|code|em|i|tt)>)})
text = []
attributes = 0
tokens.each do |tok|
case tok
when %r{^</(\w+)>$}, %r{^<(\w+)>$}
;
else
text << tok
end
end
text.join
end
end
######################################################################
# Handle text with attributes. We're a base class: there are
# different presentation classes (one, for example, uses overstrikes
# to handle bold and underlining, while another using ANSI escape
# sequences
class AttributeFormatter < TextFormatter
BOLD = 1
ITALIC = 2
CODE = 4
ATTR_MAP = {
"b" => BOLD,
"code" => CODE,
"em" => ITALIC,
"i" => ITALIC,
"tt" => CODE
}
# TODO: struct?
class AttrChar
attr_reader :char
attr_reader :attr
def initialize(char, attr)
@char = char
@attr = attr
end
end
class AttributeString
attr_reader :txt
def initialize
@txt = []
@optr = 0
end
def <<(char)
@txt << char
end
def empty?
@optr >= @txt.length
end
# accept non space, then all following spaces
def next_word
start = @optr
len = @txt.length
while @optr < len && @txt[@optr].char != " "
@optr += 1
end
while @optr < len && @txt[@optr].char == " "
@optr += 1
end
@txt[start...@optr]
end
end
######################################################################
# overrides base class. Looks for <tt>...</tt> etc sequences
# and generates an array of AttrChars. This array is then used
# as the basis for the split
def wrap(txt, prefix=@indent, linelen=@width)
return unless txt && !txt.empty?
txt = add_attributes_to(txt)
next_prefix = prefix.tr("^ ", " ")
linelen -= prefix.size
line = []
until txt.empty?
word = txt.next_word
if word.size + line.size > linelen
write_attribute_text(prefix, line)
prefix = next_prefix
line = []
end
line.concat(word)
end
write_attribute_text(prefix, line) if line.length > 0
end
protected
# overridden in specific formatters
def write_attribute_text(prefix, line)
print prefix
line.each do |achar|
print achar.char
end
puts
end
# again, overridden
def bold_print(txt)
print txt
end
private
def add_attributes_to(txt)
tokens = txt.split(%r{(</?(?:b|code|em|i|tt)>)})
text = AttributeString.new
attributes = 0
tokens.each do |tok|
case tok
when %r{^</(\w+)>$} then attributes &= ~(ATTR_MAP[$1]||0)
when %r{^<(\w+)>$} then attributes |= (ATTR_MAP[$1]||0)
else
tok.split(//).each {|ch| text << AttrChar.new(ch, attributes)}
end
end
text
end
end
##################################################
# This formatter generates overstrike-style formatting, which
# works with pagers such as man and less.
class OverstrikeFormatter < AttributeFormatter
BS = "\C-h"
def write_attribute_text(prefix, line)
print prefix
line.each do |achar|
attr = achar.attr
if (attr & (ITALIC+CODE)) != 0
print "_", BS
end
if (attr & BOLD) != 0
print achar.char, BS
end
print achar.char
end
puts
end
# draw a string in bold
def bold_print(text)
text.split(//).each do |ch|
print ch, BS, ch
end
end
end
##################################################
# This formatter uses ANSI escape sequences
# to colorize stuff
# works with pages such as man and less.
class AnsiFormatter < AttributeFormatter
def initialize(*args)
print "\033[0m"
super
end
def write_attribute_text(prefix, line)
print prefix
curr_attr = 0
line.each do |achar|
attr = achar.attr
if achar.attr != curr_attr
update_attributes(achar.attr)
curr_attr = achar.attr
end
print achar.char
end
update_attributes(0) unless curr_attr.zero?
puts
end
def bold_print(txt)
print "\033[1m#{txt}\033[m"
end
HEADINGS = {
1 => [ "\033[1;32m", "\033[m" ] ,
2 => ["\033[4;32m", "\033[m" ],
3 => ["\033[32m", "\033[m" ]
}
def display_heading(text, level, indent)
level = 3 if level > 3
heading = HEADINGS[level]
print indent
print heading[0]
print strip_attributes(text)
puts heading[1]
end
private
ATTR_MAP = {
BOLD => "1",
ITALIC => "33",
CODE => "36"
}
def update_attributes(attr)
str = "\033["
for quality in [ BOLD, ITALIC, CODE]
unless (attr & quality).zero?
str << ATTR_MAP[quality]
end
end
print str, "m"
end
end
##################################################
# This formatter uses HTML.
class HtmlFormatter < AttributeFormatter
def initialize(*args)
super
end
def write_attribute_text(prefix, line)
curr_attr = 0
line.each do |achar|
attr = achar.attr
if achar.attr != curr_attr
update_attributes(curr_attr, achar.attr)
curr_attr = achar.attr
end
print(escape(achar.char))
end
update_attributes(curr_attr, 0) unless curr_attr.zero?
end
def draw_line(label=nil)
if label != nil
bold_print(label)
end
puts("<hr>")
end
def bold_print(txt)
tag("b") { txt }
end
def blankline()
puts("<p>")
end
def break_to_newline
puts("<br>")
end
def display_heading(text, level, indent)
level = 4 if level > 4
tag("h#{level}") { text }
puts
end
######################################################################
def display_list(list)
case list.type
when SM::ListBase::BULLET
list_type = "ul"
prefixer = proc { |ignored| "<li>" }
when SM::ListBase::NUMBER,
SM::ListBase::UPPERALPHA,
SM::ListBase::LOWERALPHA
list_type = "ol"
prefixer = proc { |ignored| "<li>" }
when SM::ListBase::LABELED
list_type = "dl"
prefixer = proc do |li|
"<dt><b>" + escape(li.label) + "</b><dd>"
end
when SM::ListBase::NOTE
list_type = "table"
prefixer = proc do |li|
%{<tr valign="top"><td>#{li.label.gsub(/ /, '&nbsp;')}</td><td>}
end
else
fail "unknown list type"
end
print "<#{list_type}>"
list.contents.each do |item|
if item.kind_of? SM::Flow::LI
prefix = prefixer.call(item)
print prefix
display_flow_item(item, prefix)
else
display_flow_item(item)
end
end
print "</#{list_type}>"
end
def display_verbatim_flow_item(item, prefix=@indent)
print("<pre>")
item.body.split(/\n/).each do |line|
puts conv_html(line)
end
puts("</pre>")
end
private
ATTR_MAP = {
BOLD => "b>",
ITALIC => "i>",
CODE => "tt>"
}
def update_attributes(current, wanted)
str = ""
# first turn off unwanted ones
off = current & ~wanted
for quality in [ BOLD, ITALIC, CODE]
if (off & quality) > 0
str << "</" + ATTR_MAP[quality]
end
end
# now turn on wanted
for quality in [ BOLD, ITALIC, CODE]
unless (wanted & quality).zero?
str << "<" << ATTR_MAP[quality]
end
end
print str
end
def tag(code)
print("<#{code}>")
print(yield)
print("</#{code}>")
end
def escape(str)
str.
gsub(/&/n, '&amp;').
gsub(/\"/n, '&quot;').
gsub(/>/n, '&gt;').
gsub(/</n, '&lt;')
end
end
##################################################
# This formatter reduces extra lines for a simpler output.
# It improves way output looks for tools like IRC bots.
class SimpleFormatter < TextFormatter
######################################################################
# No extra blank lines
def blankline
end
######################################################################
# Display labels only, no lines
def draw_line(label=nil)
unless label.nil? then
bold_print(label)
puts
end
end
######################################################################
# Place heading level indicators inline with heading.
def display_heading(text, level, indent)
text = strip_attributes(text)
case level
when 1
puts "= " + text.upcase
when 2
puts "-- " + text
else
print indent, text, "\n"
end
end
end
# Finally, fill in the list of known formatters
class TextFormatter
FORMATTERS = {
"ansi" => AnsiFormatter,
"bs" => OverstrikeFormatter,
"html" => HtmlFormatter,
"plain" => TextFormatter,
"simple" => SimpleFormatter,
}
def TextFormatter.list
FORMATTERS.keys.sort.join(", ")
end
def TextFormatter.for(name)
FORMATTERS[name.downcase]
end
end
end

View file

@ -1,97 +0,0 @@
module RI
# Encapsulate all the strangeness to do with finding out
# where to find RDoc files
#
# We basically deal with three directories:
#
# 1. The 'system' documentation directory, which holds
# the documentation distributed with Ruby, and which
# is managed by the Ruby install process
# 2. The 'site' directory, which contains site-wide
# documentation added locally.
# 3. The 'user' documentation directory, stored under the
# user's own home directory.
#
# There's contention about all this, but for now:
#
# system:: $datadir/ri/<ver>/system/...
# site:: $datadir/ri/<ver>/site/...
# user:: ~/.rdoc
module Paths
#:stopdoc:
require 'rbconfig'
DOC_DIR = "doc/rdoc"
version = RbConfig::CONFIG['ruby_version']
base = File.join(RbConfig::CONFIG['datadir'], "ri", version)
SYSDIR = File.join(base, "system")
SITEDIR = File.join(base, "site")
homedir = ENV['HOME'] || ENV['USERPROFILE'] || ENV['HOMEPATH']
if homedir
HOMEDIR = File.join(homedir, ".rdoc")
else
HOMEDIR = nil
end
# This is the search path for 'ri'
PATH = [ SYSDIR, SITEDIR, HOMEDIR ].find_all {|p| p && File.directory?(p)}
require 'rubygems' unless defined?(Gem) and Gem::Enable
# HACK dup'd from Gem.latest_partials and friends
all_paths = []
all_paths = Gem.path.map do |dir|
Dir[File.join(dir, 'doc', '*', 'ri')]
end.flatten
ri_paths = {}
all_paths.each do |dir|
base = File.basename File.dirname(dir)
if base =~ /(.*)-((\d+\.)*\d+)/ then
name, version = $1, $2
ver = Gem::Version.new version
if ri_paths[name].nil? or ver > ri_paths[name][0] then
ri_paths[name] = [ver, dir]
end
end
end
GEMDIRS = ri_paths.map { |k,v| v.last }.sort
GEMDIRS.each { |dir| RI::Paths::PATH << dir }
# Returns the selected documentation directories as an Array, or PATH if no
# overriding directories were given.
def self.path(use_system, use_site, use_home, use_gems, *extra_dirs)
path = raw_path(use_system, use_site, use_home, use_gems, *extra_dirs)
return path.select { |directory| File.directory? directory }
end
# Returns the selected documentation directories including nonexistent
# directories. Used to print out what paths were searched if no ri was
# found.
def self.raw_path(use_system, use_site, use_home, use_gems, *extra_dirs)
return PATH unless use_system or use_site or use_home or use_gems or
not extra_dirs.empty?
path = []
path << extra_dirs unless extra_dirs.empty?
path << RI::Paths::SYSDIR if use_system
path << RI::Paths::SITEDIR if use_site
path << RI::Paths::HOMEDIR if use_home
path << RI::Paths::GEMDIRS if use_gems
return path.flatten.compact
end
end
end

View file

@ -1,100 +0,0 @@
require 'rdoc/ri/ri_descriptions'
require 'rdoc/ri/ri_writer'
require 'rdoc/markup/simple_markup/to_flow'
module RI
class RiReader
def initialize(ri_cache)
@cache = ri_cache
end
def top_level_namespace
[ @cache.toplevel ]
end
def lookup_namespace_in(target, namespaces)
result = []
for n in namespaces
result.concat(n.contained_modules_matching(target))
end
result
end
def find_class_by_name(full_name)
names = full_name.split(/::/)
ns = @cache.toplevel
for name in names
ns = ns.contained_class_named(name)
return nil if ns.nil?
end
get_class(ns)
end
def find_methods(name, is_class_method, namespaces)
result = []
namespaces.each do |ns|
result.concat ns.methods_matching(name, is_class_method)
end
result
end
# return the MethodDescription for a given MethodEntry
# by deserializing the YAML
def get_method(method_entry)
path = method_entry.path_name
File.open(path) { |f| RI::Description.deserialize(f) }
end
# Return a class description
def get_class(class_entry)
result = nil
for path in class_entry.path_names
path = RiWriter.class_desc_path(path, class_entry)
desc = File.open(path) {|f| RI::Description.deserialize(f) }
if result
result.merge_in(desc)
else
result = desc
end
end
result
end
# return the names of all classes and modules
def full_class_names
res = []
find_classes_in(res, @cache.toplevel)
end
# return a list of all classes, modules, and methods
def all_names
res = []
find_names_in(res, @cache.toplevel)
end
# ----
private
# ----
def find_classes_in(res, klass)
classes = klass.classes_and_modules
for c in classes
res << c.full_name
find_classes_in(res, c)
end
res
end
def find_names_in(res, klass)
classes = klass.classes_and_modules
for c in classes
res << c.full_name
res.concat c.all_method_names
find_names_in(res, c)
end
res
end
end
end

View file

@ -1,62 +0,0 @@
require 'fileutils'
module RI
class RiWriter
def RiWriter.class_desc_path(dir, class_desc)
File.join(dir, "cdesc-" + class_desc.name + ".yaml")
end
# Convert a name from internal form (containing punctuation)
# to an external form (where punctuation is replaced
# by %xx)
def RiWriter.internal_to_external(name)
name.gsub(/\W/) { "%%%02x" % $&[0].ord }
end
# And the reverse operation
def RiWriter.external_to_internal(name)
name.gsub(/%([0-9a-f]{2,2})/) { $1.to_i(16).chr }
end
def initialize(base_dir)
@base_dir = base_dir
end
def remove_class(class_desc)
FileUtils.rm_rf(path_to_dir(class_desc.full_name))
end
def add_class(class_desc)
dir = path_to_dir(class_desc.full_name)
FileUtils.mkdir_p(dir)
class_file_name = RiWriter.class_desc_path(dir, class_desc)
File.open(class_file_name, "w") do |f|
f.write(class_desc.serialize)
end
end
def add_method(class_desc, method_desc)
dir = path_to_dir(class_desc.full_name)
file_name = RiWriter.internal_to_external(method_desc.name)
meth_file_name = File.join(dir, file_name)
if method_desc.is_singleton
meth_file_name += "-c.yaml"
else
meth_file_name += "-i.yaml"
end
File.open(meth_file_name, "w") do |f|
f.write(method_desc.serialize)
end
end
private
def path_to_dir(class_name)
File.join(@base_dir, *class_name.split('::'))
end
end
end

View file

@ -1,29 +1,34 @@
###################################################################### require 'rdoc/ri'
class RiError < Exception; end class RDoc::RI::Error < RuntimeError; end
#
##
# Break argument into its constituent class or module names, an # Break argument into its constituent class or module names, an
# optional method type, and a method name # optional method type, and a method name
class NameDescriptor class RDoc::RI::NameDescriptor
attr_reader :class_names attr_reader :class_names
attr_reader :method_name attr_reader :method_name
##
# true and false have the obvious meaning. nil means we don't care # true and false have the obvious meaning. nil means we don't care
attr_reader :is_class_method attr_reader :is_class_method
# arg may be ##
# 1. a class or module name (optionally qualified with other class # +arg+ may be
# or module names (Kernel, File::Stat etc) #
# 2. a method name # 1. A class or module name (optionally qualified with other class or module
# 3. a method name qualified by a optionally fully qualified class # names (Kernel, File::Stat etc)
# or module name # 2. A method name
# 3. A method name qualified by a optionally fully qualified class or module
# name
# #
# We're fairly casual about delimiters: folks can say Kernel::puts, # We're fairly casual about delimiters: folks can say Kernel::puts,
# Kernel.puts, or Kernel\#puts for example. There's one exception: # Kernel.puts, or Kernel\#puts for example. There's one exception: if you
# if you say IO::read, we look for a class method, but if you # say IO::read, we look for a class method, but if you say IO.read, we look
# say IO.read, we look for an instance method # for an instance method
def initialize(arg) def initialize(arg)
@class_names = [] @class_names = []
@ -47,8 +52,7 @@ class NameDescriptor
end end
end end
# Now must have a single token, the method name, or an empty # Now must have a single token, the method name, or an empty array
# array
unless tokens.empty? unless tokens.empty?
@method_name = tokens.shift @method_name = tokens.shift
# We may now have a trailing !, ?, or = to roll into # We may now have a trailing !, ?, or = to roll into
@ -66,10 +70,12 @@ class NameDescriptor
end end
end end
# Return the full class name (with '::' between the components) # Return the full class name (with '::' between the components) or "" if
# or "" if there's no class name # there's no class name
def full_class_name def full_class_name
@class_names.join("::") @class_names.join("::")
end end
end end

64
lib/rdoc/ri/writer.rb Normal file
View file

@ -0,0 +1,64 @@
require 'fileutils'
require 'rdoc/ri'
class RDoc::RI::Writer
def self.class_desc_path(dir, class_desc)
File.join(dir, "cdesc-" + class_desc.name + ".yaml")
end
##
# Convert a name from internal form (containing punctuation) to an external
# form (where punctuation is replaced by %xx)
def self.internal_to_external(name)
name.gsub(/\W/) { "%%%02x" % $&[0].ord }
end
##
# And the reverse operation
def self.external_to_internal(name)
name.gsub(/%([0-9a-f]{2,2})/) { $1.to_i(16).chr }
end
def initialize(base_dir)
@base_dir = base_dir
end
def remove_class(class_desc)
FileUtils.rm_rf(path_to_dir(class_desc.full_name))
end
def add_class(class_desc)
dir = path_to_dir(class_desc.full_name)
FileUtils.mkdir_p(dir)
class_file_name = self.class.class_desc_path(dir, class_desc)
File.open(class_file_name, "w") do |f|
f.write(class_desc.serialize)
end
end
def add_method(class_desc, method_desc)
dir = path_to_dir(class_desc.full_name)
file_name = self.class.internal_to_external(method_desc.name)
meth_file_name = File.join(dir, file_name)
if method_desc.is_singleton
meth_file_name += "-c.yaml"
else
meth_file_name += "-i.yaml"
end
File.open(meth_file_name, "w") do |f|
f.write(method_desc.serialize)
end
end
private
def path_to_dir(class_name)
File.join(@base_dir, *class_name.split('::'))
end
end