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:
parent
929faf7906
commit
3e39ade457
20 changed files with 1362 additions and 1402 deletions
|
@ -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
4
bin/ri
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
188
lib/rdoc/ri/cache.rb
Normal 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
147
lib/rdoc/ri/descriptions.rb
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
662
lib/rdoc/ri/formatter.rb
Normal 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(/>/, '>').
|
||||
gsub(/</, '<').
|
||||
gsub(/"/, '"').
|
||||
gsub(/&/, '&')
|
||||
|
||||
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(/ /, ' ')}</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, '&').
|
||||
gsub(/\"/n, '"').
|
||||
gsub(/>/n, '>').
|
||||
gsub(/</n, '<')
|
||||
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
97
lib/rdoc/ri/paths.rb
Normal 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
106
lib/rdoc/ri/reader.rb
Normal 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
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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(/>/, '>').
|
||||
gsub(/</, '<').
|
||||
gsub(/"/, '"').
|
||||
gsub(/&/, '&')
|
||||
|
||||
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(/ /, ' ')}</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, '&').
|
||||
gsub(/\"/n, '"').
|
||||
gsub(/>/n, '>').
|
||||
gsub(/</n, '<')
|
||||
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
|
||||
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
64
lib/rdoc/ri/writer.rb
Normal 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
|
||||
|
Loading…
Reference in a new issue