diff options
Diffstat (limited to 'ruby_1_8_6/lib/rdoc/ri')
-rw-r--r-- | ruby_1_8_6/lib/rdoc/ri/ri_cache.rb | 187 | ||||
-rw-r--r-- | ruby_1_8_6/lib/rdoc/ri/ri_descriptions.rb | 154 | ||||
-rw-r--r-- | ruby_1_8_6/lib/rdoc/ri/ri_display.rb | 255 | ||||
-rw-r--r-- | ruby_1_8_6/lib/rdoc/ri/ri_driver.rb | 143 | ||||
-rw-r--r-- | ruby_1_8_6/lib/rdoc/ri/ri_formatter.rb | 672 | ||||
-rw-r--r-- | ruby_1_8_6/lib/rdoc/ri/ri_options.rb | 313 | ||||
-rw-r--r-- | ruby_1_8_6/lib/rdoc/ri/ri_paths.rb | 80 | ||||
-rw-r--r-- | ruby_1_8_6/lib/rdoc/ri/ri_reader.rb | 100 | ||||
-rw-r--r-- | ruby_1_8_6/lib/rdoc/ri/ri_util.rb | 75 | ||||
-rw-r--r-- | ruby_1_8_6/lib/rdoc/ri/ri_writer.rb | 62 |
10 files changed, 2041 insertions, 0 deletions
diff --git a/ruby_1_8_6/lib/rdoc/ri/ri_cache.rb b/ruby_1_8_6/lib/rdoc/ri/ri_cache.rb new file mode 100644 index 0000000000..1844ac969e --- /dev/null +++ b/ruby_1_8_6/lib/rdoc/ri/ri_cache.rb @@ -0,0 +1,187 @@ +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 diff --git a/ruby_1_8_6/lib/rdoc/ri/ri_descriptions.rb b/ruby_1_8_6/lib/rdoc/ri/ri_descriptions.rb new file mode 100644 index 0000000000..e5ea9f2fbf --- /dev/null +++ b/ruby_1_8_6/lib/rdoc/ri/ri_descriptions.rb @@ -0,0 +1,154 @@ +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 diff --git a/ruby_1_8_6/lib/rdoc/ri/ri_display.rb b/ruby_1_8_6/lib/rdoc/ri/ri_display.rb new file mode 100644 index 0000000000..67962fc2c1 --- /dev/null +++ b/ruby_1_8_6/lib/rdoc/ri/ri_display.rb @@ -0,0 +1,255 @@ +require 'rdoc/ri/ri_util' +require 'rdoc/ri/ri_formatter' +require 'rdoc/ri/ri_options' + + +# 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 + @@display_class = nil + + def RiDisplay.append_features(display_class) + @@display_class = display_class + end + + def RiDisplay.new(*args) + @@display_class.new(*args) + end +end + +###################################################################### +# +# A paging display module. Uses the ri_formatter class to do the +# actual presentation +# + +class DefaultDisplay + + include RiDisplay + + def initialize(options) + @options = options + @formatter = @options.formatter.new(@options, " ") + 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) + display_params(method) + @formatter.draw_line + display_flow(method.comment) + if method.aliases && !method.aliases.empty? + @formatter.blankline + aka = "(also known as " + aka << method.aliases.map {|a| a.name }.join(", ") + aka << ")" + @formatter.wrap(aka) + end + end + end + + ###################################################################### + + def display_class_info(klass, ri_reader) + page do + superclass = klass.superclass_string + + if superclass + superclass = " < " + superclass + else + superclass = "" + end + + @formatter.draw_line(klass.display_name + ": " + + klass.full_name + superclass) + + display_flow(klass.comment) + @formatter.draw_line + + unless klass.includes.empty? + @formatter.blankline + @formatter.display_heading("Includes:", 2, "") + incs = [] + klass.includes.each do |inc| + inc_desc = ri_reader.find_class_by_name(inc.name) + if inc_desc + str = inc.name + "(" + str << inc_desc.instance_methods.map{|m| m.name}.join(", ") + str << ")" + incs << str + else + incs << inc.name + end + end + @formatter.wrap(incs.sort.join(', ')) + end + + unless klass.constants.empty? + @formatter.blankline + @formatter.display_heading("Constants:", 2, "") + len = 0 + klass.constants.each { |c| len = c.name.length if c.name.length > len } + len += 2 + klass.constants.each do |c| + @formatter.wrap(c.value, + @formatter.indent+((c.name+":").ljust(len))) + end + end + + unless klass.class_methods.empty? + @formatter.blankline + @formatter.display_heading("Class methods:", 2, "") + @formatter.wrap(klass.class_methods.map{|m| m.name}.sort.join(', ')) + end + + unless klass.instance_methods.empty? + @formatter.blankline + @formatter.display_heading("Instance methods:", 2, "") + @formatter.wrap(klass.instance_methods.map{|m| m.name}.sort.join(', ')) + end + + unless klass.attributes.empty? + @formatter.blankline + @formatter.wrap("Attributes:", "") + @formatter.wrap(klass.attributes.map{|a| a.name}.sort.join(', ')) + end + end + end + + ###################################################################### + + # Display a list of method names + + def display_method_list(methods) + page do + puts "More than one method matched your request. You can refine" + puts "your search by asking for information on one of:\n\n" + @formatter.wrap(methods.map {|m| m.full_name} .join(", ")) + end + end + + ###################################################################### + + def display_class_list(namespaces) + page do + puts "More than one class or module matched your request. You can refine" + puts "your search by asking for information on one of:\n\n" + @formatter.wrap(namespaces.map {|m| m.full_name}.join(", ")) + end + end + + ###################################################################### + + def list_known_classes(classes) + if classes.empty? + warn_no_database + else + page do + @formatter.draw_line("Known classes and modules") + @formatter.blankline + @formatter.wrap(classes.sort.join(", ")) + end + end + end + + ###################################################################### + + def list_known_names(names) + if names.empty? + warn_no_database + else + page do + names.each {|n| @formatter.raw_print_line(n)} + end + end + end + + ###################################################################### + + private + + ###################################################################### + + def page + return yield unless pager = setup_pager + begin + save_stdout = STDOUT.clone + STDOUT.reopen(pager) + yield + ensure + STDOUT.reopen(save_stdout) + save_stdout.close + pager.close + end + end + + ###################################################################### + + def setup_pager + unless @options.use_stdout + for pager in [ ENV['PAGER'], "less", "more", 'pager' ].compact.uniq + return IO.popen(pager, "w") rescue nil + end + @options.use_stdout = true + nil + end + end + + ###################################################################### + + def display_params(method) + + params = method.params + + if params[0,1] == "(" + if method.is_singleton + params = method.full_name + params + else + params = method.name + params + end + end + params.split(/\n/).each do |p| + @formatter.wrap(p) + @formatter.break_to_newline + end + end + ###################################################################### + + def display_flow(flow) + if !flow || flow.empty? + @formatter.wrap("(no description...)") + else + @formatter.display_flow(flow) + end + end + + ###################################################################### + + def warn_no_database + puts "Before using ri, you need to generate documentation" + puts "using 'rdoc' with the --ri option" + end +end # class RiDisplay diff --git a/ruby_1_8_6/lib/rdoc/ri/ri_driver.rb b/ruby_1_8_6/lib/rdoc/ri/ri_driver.rb new file mode 100644 index 0000000000..a00f20ee3b --- /dev/null +++ b/ruby_1_8_6/lib/rdoc/ri/ri_driver.rb @@ -0,0 +1,143 @@ +require 'rdoc/ri/ri_paths' +require 'rdoc/usage' +require 'rdoc/ri/ri_cache' +require 'rdoc/ri/ri_util' +require 'rdoc/ri/ri_reader' +require 'rdoc/ri/ri_formatter' +require 'rdoc/ri/ri_options' + + +###################################################################### + +class RiDriver + + def initialize + @options = RI::Options.instance + + args = ARGV + if ENV["RI"] + args = ENV["RI"].split.concat(ARGV) + end + + @options.parse(args) + + path = @options.path + report_missing_documentation @options.raw_path if path.empty? + + @ri_reader = RI::RiReader.new(RI::RiCache.new(path)) + @display = @options.displayer + 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 + + ###################################################################### + + # If the list of matching methods contains exactly one entry, or + # if it contains an entry that exactly matches the requested method, + # then display that entry, otherwise display the list of + # matching method names + + def report_method_stuff(requested_method_name, methods) + if methods.size == 1 + method = @ri_reader.get_method(methods[0]) + @display.display_method_info(method) + else + entries = methods.find_all {|m| m.name == requested_method_name} + if entries.size == 1 + method = @ri_reader.get_method(entries[0]) + @display.display_method_info(method) + else + @display.display_method_list(methods) + end + end + end + + ###################################################################### + + def report_class_stuff(namespaces) + if namespaces.size == 1 + klass = @ri_reader.get_class(namespaces[0]) + @display.display_class_info(klass, @ri_reader) + else +# entries = namespaces.find_all {|m| m.full_name == requested_class_name} +# if entries.size == 1 +# klass = @ri_reader.get_class(entries[0]) +# @display.display_class_info(klass, @ri_reader) +# else + @display.display_class_list(namespaces) +# end + end + end + + ###################################################################### + + + def get_info_for(arg) + desc = NameDescriptor.new(arg) + + namespaces = @ri_reader.top_level_namespace + + for class_name in desc.class_names + namespaces = @ri_reader.lookup_namespace_in(class_name, namespaces) + if namespaces.empty? + raise RiError.new("Nothing known about #{arg}") + end + end + + # at this point, if we have multiple possible namespaces, but one + # is an exact match for our requested class, prune down to just it + + full_class_name = desc.full_class_name + entries = namespaces.find_all {|m| m.full_name == full_class_name} + namespaces = entries if entries.size == 1 + + if desc.method_name.nil? + report_class_stuff(namespaces) + else + methods = @ri_reader.find_methods(desc.method_name, + desc.is_class_method, + namespaces) + + if methods.empty? + raise RiError.new("Nothing known about #{arg}") + else + report_method_stuff(desc.method_name, methods) + end + end + end + + ###################################################################### + + def process_args + if @options.list_classes + classes = @ri_reader.full_class_names + @display.list_known_classes(classes) + elsif @options.list_names + names = @ri_reader.all_names + @display.list_known_names(names) + else + if ARGV.size.zero? + @display.display_usage + else + begin + ARGV.each do |arg| + get_info_for(arg) + end + rescue RiError => e + STDERR.puts(e.message) + exit(1) + end + end + end + end + +end # class RiDriver diff --git a/ruby_1_8_6/lib/rdoc/ri/ri_formatter.rb b/ruby_1_8_6/lib/rdoc/ri/ri_formatter.rb new file mode 100644 index 0000000000..34eb561ca3 --- /dev/null +++ b/ruby_1_8_6/lib/rdoc/ri/ri_formatter.rb @@ -0,0 +1,672 @@ +module RI + class TextFormatter + + attr_reader :indent + + def initialize(options, indent) + @options = options + @width = options.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>") + puts item.body + 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 + + diff --git a/ruby_1_8_6/lib/rdoc/ri/ri_options.rb b/ruby_1_8_6/lib/rdoc/ri/ri_options.rb new file mode 100644 index 0000000000..db9f4afecf --- /dev/null +++ b/ruby_1_8_6/lib/rdoc/ri/ri_options.rb @@ -0,0 +1,313 @@ +# We handle the parsing of options, and subsequently as a singleton +# object to be queried for option values + +module RI + + require 'rdoc/ri/ri_paths' + require 'rdoc/ri/ri_display' + + VERSION_STRING = "ri v1.0.1 - 20041108" + + class Options + + require 'singleton' + require 'getoptlong' + + include Singleton + + # No not use a pager. Writable, because ri sets it if it + # can't find a pager + attr_accessor :use_stdout + + # should we just display a class list and exit + attr_reader :list_classes + + # should we display a list of all names + attr_reader :list_names + + # The width of the output line + attr_reader :width + + # the formatting we apply to the output + attr_reader :formatter + + # the directory we search for original documentation + attr_reader :doc_dir + + module OptionList + + OPTION_LIST = [ + [ "--help", "-h", nil, + "you're looking at it" ], + + [ "--classes", "-c", nil, + "Display the names of classes and modules we\n" + + "know about"], + + [ "--doc-dir", "-d", "<dirname>", + "A directory to search for documentation. If not\n" + + "specified, we search the standard rdoc/ri directories.\n" + + "May be repeated."], + + [ "--system", nil, nil, + "Include documentation from Ruby's standard library:\n " + + RI::Paths::SYSDIR ], + + [ "--site", nil, nil, + "Include documentation from libraries installed in site_lib:\n " + + RI::Paths::SITEDIR ], + + [ "--home", nil, nil, + "Include documentation stored in ~/.rdoc:\n " + + (RI::Paths::HOMEDIR || "No ~/.rdoc found") ], + + [ "--gems", nil, nil, + "Include documentation from Rubygems:\n " + + (RI::Paths::GEMDIRS ? "#{Gem.path}/doc/*/ri" : + "No Rubygems ri found.") ], + + [ "--format", "-f", "<name>", + "Format to use when displaying output:\n" + + " " + RI::TextFormatter.list + "\n" + + "Use 'bs' (backspace) with most pager programs.\n" + + "To use ANSI, either also use the -T option, or\n" + + "tell your pager to allow control characters\n" + + "(for example using the -R option to less)"], + + [ "--list-names", "-l", nil, + "List all the names known to RDoc, one per line" + ], + + [ "--no-pager", "-T", nil, + "Send output directly to stdout." + ], + + [ "--width", "-w", "output width", + "Set the width of the output" ], + + [ "--version", "-v", nil, + "Display the version of ri" + ], + + ] + + def OptionList.options + OPTION_LIST.map do |long, short, arg,| + option = [] + option << long + option << short unless short.nil? + option << (arg ? GetoptLong::REQUIRED_ARGUMENT : + GetoptLong::NO_ARGUMENT) + option + end + end + + + def OptionList.strip_output(text) + text =~ /^\s+/ + leading_spaces = $& + text.gsub!(/^#{leading_spaces}/, '') + $stdout.puts text + end + + + # Show an error and exit + + def OptionList.error(msg) + $stderr.puts + $stderr.puts msg + $stderr.puts "\nFor help on options, try 'ri --help'\n\n" + exit 1 + end + + # Show usage and exit + + def OptionList.usage(short_form=false) + + puts + puts(RI::VERSION_STRING) + puts + + name = File.basename($0) + + directories = [ + RI::Paths::SYSDIR, + RI::Paths::SITEDIR, + RI::Paths::HOMEDIR + ] + + directories << "#{Gem.path}/doc/*/ri" if RI::Paths::GEMDIRS + + directories = directories.join("\n ") + + OptionList.strip_output(<<-EOT) + Usage: + + #{name} [options] [names...] + + Display information on Ruby classes, modules, and methods. + Give the names of classes or methods to see their documentation. + Partial names may be given: if the names match more than + one entity, a list will be shown, otherwise details on + that entity will be displayed. + + Nested classes and modules can be specified using the normal + Name::Name notation, and instance methods can be distinguished + from class methods using "." (or "#") instead of "::". + + For example: + + ri File + ri File.new + ri F.n + ri zip + + Note that shell quoting may be required for method names + containing punctuation: + + ri 'Array.[]' + ri compact\\! + + By default ri searches for documentation in the following + directories: + + #{directories} + + Specifying the --system, --site, --home, --gems or --doc-dir + options will limit ri to searching only the specified + directories. + + EOT + + if short_form + puts "For help on options, type 'ri -h'" + puts "For a list of classes I know about, type 'ri -c'" + else + puts "Options:\n\n" + OPTION_LIST.each do|long, short, arg, desc| + opt = '' + opt << (short ? sprintf("%15s", "#{long}, #{short}") : + sprintf("%15s", long)) + if arg + opt << " " << arg + end + print opt + desc = desc.split("\n") + if opt.size < 17 + print " "*(18-opt.size) + puts desc.shift + else + puts + end + desc.each do |line| + puts(" "*18 + line) + end + puts + end + puts "Options may also be passed in the 'RI' environment variable" + exit 0 + end + end + end + + # Show the version and exit + def show_version + puts VERSION_STRING + exit(0) + end + + def initialize + @use_stdout = !STDOUT.tty? + @width = 72 + @formatter = RI::TextFormatter.for("plain") + @list_classes = false + @list_names = false + + # By default all paths are used. If any of these are true, only those + # directories are used. + @use_system = false + @use_site = false + @use_home = false + @use_gems = false + @doc_dirs = [] + end + + # Parse command line options. + + def parse(args) + + old_argv = ARGV.dup + + ARGV.replace(args) + + begin + + go = GetoptLong.new(*OptionList.options) + go.quiet = true + + go.each do |opt, arg| + case opt + when "--help" then OptionList.usage + when "--version" then show_version + when "--list-names" then @list_names = true + when "--no-pager" then @use_stdout = true + when "--classes" then @list_classes = true + + when "--system" then @use_system = true + when "--site" then @use_site = true + when "--home" then @use_home = true + when "--gems" then @use_gems = true + + when "--doc-dir" + if File.directory?(arg) + @doc_dirs << arg + else + $stderr.puts "Invalid directory: #{arg}" + exit 1 + end + + when "--format" + @formatter = RI::TextFormatter.for(arg) + unless @formatter + $stderr.print "Invalid formatter (should be one of " + $stderr.puts RI::TextFormatter.list + ")" + exit 1 + end + when "--width" + begin + @width = Integer(arg) + rescue + $stderr.puts "Invalid width: '#{arg}'" + exit 1 + end + end + end + + rescue GetoptLong::InvalidOption, GetoptLong::MissingArgument => error + OptionList.error(error.message) + + end + end + + # Return the selected documentation directories. + + def path + RI::Paths.path(@use_system, @use_site, @use_home, @use_gems, *@doc_dirs) + end + + def raw_path + RI::Paths.raw_path(@use_system, @use_site, @use_home, @use_gems, + *@doc_dirs) + end + + # Return an instance of the displayer (the thing that actually writes + # the information). This allows us to load in new displayer classes + # at runtime (for example to help with IDE integration) + + def displayer + ::RiDisplay.new(self) + end + end + +end + diff --git a/ruby_1_8_6/lib/rdoc/ri/ri_paths.rb b/ruby_1_8_6/lib/rdoc/ri/ri_paths.rb new file mode 100644 index 0000000000..32363bf70a --- /dev/null +++ b/ruby_1_8_6/lib/rdoc/ri/ri_paths.rb @@ -0,0 +1,80 @@ +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 = Config::CONFIG['ruby_version'] + + base = File.join(Config::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)} + + begin + require 'rubygems' + GEMDIRS = Dir["#{Gem.path}/doc/*/ri"] + GEMDIRS.each { |path| RI::Paths::PATH << path } + rescue LoadError + GEMDIRS = nil + end + + # 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 { |path| File.directory? path } + 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 diff --git a/ruby_1_8_6/lib/rdoc/ri/ri_reader.rb b/ruby_1_8_6/lib/rdoc/ri/ri_reader.rb new file mode 100644 index 0000000000..fb2c373e38 --- /dev/null +++ b/ruby_1_8_6/lib/rdoc/ri/ri_reader.rb @@ -0,0 +1,100 @@ +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 diff --git a/ruby_1_8_6/lib/rdoc/ri/ri_util.rb b/ruby_1_8_6/lib/rdoc/ri/ri_util.rb new file mode 100644 index 0000000000..8a01255897 --- /dev/null +++ b/ruby_1_8_6/lib/rdoc/ri/ri_util.rb @@ -0,0 +1,75 @@ +###################################################################### + +class RiError < Exception; end +# +# Break argument into its constituent class or module names, an +# optional method type, and a method name + +class 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 + # + # 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 + + def initialize(arg) + @class_names = [] + separator = nil + + tokens = arg.split(/(\.|::|#)/) + + # Skip leading '::', '#' or '.', but remember it might + # be a method name qualifier + separator = tokens.shift if tokens[0] =~ /^(\.|::|#)/ + + # 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? + separator = tokens.shift + break unless separator == "::" + end + end + + # 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 + # the method name + if !tokens.empty? && tokens[0] =~ /^[!?=]$/ + @method_name << tokens.shift + end + + if @method_name =~ /::|\.|#/ or !tokens.empty? + raise RiError.new("Bad argument: #{arg}") + end + if separator && separator != '.' + @is_class_method = separator == "::" + end + end + end + + # 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 diff --git a/ruby_1_8_6/lib/rdoc/ri/ri_writer.rb b/ruby_1_8_6/lib/rdoc/ri/ri_writer.rb new file mode 100644 index 0000000000..78c68e8409 --- /dev/null +++ b/ruby_1_8_6/lib/rdoc/ri/ri_writer.rb @@ -0,0 +1,62 @@ +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/) { sprintf("%%%02x", $&[0]) } + 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 |