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>
* bin/ri, lib/rdoc/ri/*: Replace with Ryan Davis' cached ri.

4
bin/ri
View file

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

View file

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

View file

@ -36,7 +36,7 @@ module RDoc
##
# Exception thrown by any rdoc error.
class Error < StandardError; end
class Error < RuntimeError; end
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
# 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',
# and include the 'RiDisplay' module in that class.
require 'rdoc/ri'
##
# This is a kind of 'flag' module. If you want to write your 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', and include the 'RiDisplay' module in that class.
#
# To access your class from the command line, you can do
#
# 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
def RiDisplay.append_features(display_class)
def self.append_features(display_class)
@@display_class = display_class
end
def RiDisplay.new(*args)
def self.new(*args)
@@display_class.new(*args)
end
end
######################################################################
#
# A paging display module. Uses the ri_formatter class to do the
# actual presentation
#
##
# A paging display module. Uses the RDoc::RI::Formatter class to do the actual
# presentation
class DefaultDisplay
class RDoc::RI::DefaultDisplay
include RiDisplay
include RDoc::RI::Display
def initialize(formatter, width, use_stdout)
@use_stdout = use_stdout
@formatter = formatter.new width, " "
end
######################################################################
def display_usage
page do
RI::Options::OptionList.usage(short_form=true)
end
end
######################################################################
def display_method_info(method)
page do
@formatter.draw_line(method.full_name)
@ -65,8 +53,6 @@ class DefaultDisplay
end
end
######################################################################
def display_class_info(klass, ri_reader)
page do
superclass = klass.superclass_string
@ -145,8 +131,7 @@ class DefaultDisplay
end
end
######################################################################
##
# Display a list of method names
def display_method_list(methods)
@ -157,8 +142,6 @@ class DefaultDisplay
end
end
######################################################################
def display_class_list(namespaces)
page do
puts "More than one class or module matched your request. You can refine"
@ -167,8 +150,6 @@ class DefaultDisplay
end
end
######################################################################
def list_known_classes(classes)
if classes.empty?
warn_no_database
@ -181,8 +162,6 @@ class DefaultDisplay
end
end
######################################################################
def list_known_names(names)
if names.empty?
warn_no_database
@ -193,12 +172,8 @@ class DefaultDisplay
end
end
######################################################################
private
######################################################################
def page
if pager = setup_pager then
begin
@ -215,8 +190,6 @@ class DefaultDisplay
rescue Errno::EPIPE
end
######################################################################
def setup_pager
unless @use_stdout then
for pager in [ ENV['PAGER'], "less", "more", 'pager' ].compact.uniq
@ -227,8 +200,6 @@ class DefaultDisplay
end
end
######################################################################
def display_params(method)
params = method.params
@ -248,7 +219,6 @@ class DefaultDisplay
@formatter.wrap("Extension from #{method.source_path}")
end
end
######################################################################
def display_flow(flow)
if !flow || flow.empty?
@ -258,8 +228,6 @@ class DefaultDisplay
end
end
######################################################################
def warn_no_database
puts "No ri data found"
puts
@ -272,4 +240,5 @@ class DefaultDisplay
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."
end
end # class RiDisplay
end

View file

@ -2,20 +2,20 @@ require 'optparse'
require 'yaml'
require 'rdoc/ri'
require 'rdoc/ri/ri_paths'
require 'rdoc/ri/ri_formatter'
require 'rdoc/ri/ri_display'
require 'rdoc/ri/paths'
require 'rdoc/ri/formatter'
require 'rdoc/ri/display'
require 'fileutils'
require 'rdoc/markup/simple_markup'
require 'rdoc/markup/simple_markup/to_flow'
class RDoc::RI::RiDriver
class RDoc::RI::Driver
def self.process_args(argv)
options = {}
options[:use_stdout] = !$stdout.tty?
options[:width] = 72
options[:formatter] = RI::TextFormatter.for 'plain'
options[:formatter] = RDoc::RI::Formatter.for 'plain'
options[:list_classes] = false
options[:list_names] = false
@ -33,12 +33,12 @@ class RDoc::RI::RiDriver
opt.summary_indent = ' ' * 4
directories = [
RI::Paths::SYSDIR,
RI::Paths::SITEDIR,
RI::Paths::HOMEDIR
RDoc::RI::Paths::SYSDIR,
RDoc::RI::Paths::SITEDIR,
RDoc::RI::Paths::HOMEDIR
]
if RI::Paths::GEMDIRS then
if RDoc::RI::Paths::GEMDIRS then
Gem.path.each do |dir|
directories << "#{dir}/doc/*/ri"
end
@ -109,19 +109,19 @@ Options may also be set in the 'RI' environment variable.
opt.separator nil
opt.on("--fmt=FORMAT", "--format=FORMAT", "-f",
RI::TextFormatter.list.split(', '), # HACK
RDoc::RI::Formatter::FORMATTERS.keys,
"Format to use when displaying output:",
" #{RI::TextFormatter.list}",
" #{RDoc::RI::Formatter.list}",
"Use 'bs' (backspace) with most pager",
"programs. To use ANSI, either disable the",
"pager or tell the pager to allow control",
"characters.") do |value|
options[:formatter] = RI::TextFormatter.for value
options[:formatter] = RDoc::RI::Formatter.for value
end
opt.separator nil
if RI::Paths::GEMDIRS then
unless RDoc::RI::Paths::GEMDIRS.empty? then
opt.on("--[no-]gems",
"Include documentation from RubyGems.") do |value|
use_gems = value
@ -180,10 +180,10 @@ Options may also be set in the 'RI' environment variable.
options[:names] = argv
options[:path] = RI::Paths.path(use_system, use_site, use_home, use_gems,
*doc_dirs)
options[:raw_path] = RI::Paths.raw_path(use_system, use_site, use_home,
use_gems, *doc_dirs)
options[:path] = RDoc::RI::Paths.path(use_system, use_site, use_home,
use_gems, *doc_dirs)
options[:raw_path] = RDoc::RI::Paths.raw_path(use_system, use_site,
use_home, use_gems, *doc_dirs)
options
@ -204,17 +204,18 @@ Options may also be set in the 'RI' environment variable.
@names = options[:names]
@class_cache_name = 'classes'
@all_dirs = RI::Paths.path(true, true, true, true)
@homepath = RI::Paths.raw_path(false, false, true, false).first
@all_dirs = RDoc::RI::Paths.path(true, true, true, true)
@homepath = RDoc::RI::Paths.raw_path(false, false, true, false).first
@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
@class_cache = nil
@display = DefaultDisplay.new(options[:formatter], options[:width],
options[:use_stdout])
@display = RDoc::RI::DefaultDisplay.new(options[:formatter],
options[:width],
options[:use_stdout])
end
def class_cache
@ -371,7 +372,7 @@ Options may also be set in the 'RI' environment variable.
end
end
end
def select_classes(pattern = nil)
classes = class_cache.keys.sort
classes = classes.grep pattern if pattern
@ -386,17 +387,6 @@ Options may also be set in the 'RI' environment variable.
cache
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
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
# optional method type, and a method name
class NameDescriptor
class RDoc::RI::NameDescriptor
attr_reader :class_names
attr_reader :method_name
##
# true and false have the obvious meaning. nil means we don't care
attr_reader :is_class_method
# arg may be
# 1. a class or module name (optionally qualified with other class
# or module names (Kernel, File::Stat etc)
# 2. a method name
# 3. a method name qualified by a optionally fully qualified class
# or module name
##
# +arg+ may be
#
# 1. A class or module name (optionally qualified with other class or module
# names (Kernel, File::Stat etc)
# 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,
# Kernel.puts, or Kernel\#puts for example. There's one exception:
# if you say IO::read, we look for a class method, but if you
# say IO.read, we look for an instance method
# Kernel.puts, or Kernel\#puts for example. There's one exception: if you
# say IO::read, we look for a class method, but if you say IO.read, we look
# for an instance method
def initialize(arg)
@class_names = []
@ -38,7 +43,7 @@ class NameDescriptor
# Skip leading '::', but remember we potentially have an inst
# leading stuff must be class names
while tokens[0] =~ /^[A-Z]/
@class_names << tokens.shift
unless tokens.empty?
@ -46,9 +51,8 @@ class NameDescriptor
break unless separator == "::"
end
end
# Now must have a single token, the method name, or an empty
# array
# Now must have a single token, the method name, or an empty array
unless tokens.empty?
@method_name = tokens.shift
# We may now have a trailing !, ?, or = to roll into
@ -66,10 +70,12 @@ class NameDescriptor
end
end
# Return the full class name (with '::' between the components)
# or "" if there's no class name
# Return the full class name (with '::' between the components) or "" if
# there's no class name
def full_class_name
@class_names.join("::")
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