diff options
author | drbrain <drbrain@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2008-01-08 10:18:41 +0000 |
---|---|---|
committer | drbrain <drbrain@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2008-01-08 10:18:41 +0000 |
commit | 3e39ade457b432a57d63e00d5a9898ca202d26f1 (patch) | |
tree | e82f30dd15c6f0e3d89fbe45c573ad755750e769 /lib/rdoc/ri | |
parent | 929faf7906a2f2ca7dbf5eda5670e666edddfa88 (diff) | |
download | ruby-3e39ade457b432a57d63e00d5a9898ca202d26f1.tar.gz |
Clean up namespacing of RI's classes
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@14953 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'lib/rdoc/ri')
-rw-r--r-- | lib/rdoc/ri/cache.rb | 188 | ||||
-rw-r--r-- | lib/rdoc/ri/descriptions.rb | 147 | ||||
-rw-r--r-- | lib/rdoc/ri/display.rb (renamed from lib/rdoc/ri/ri_display.rb) | 71 | ||||
-rw-r--r-- | lib/rdoc/ri/driver.rb (renamed from lib/rdoc/ri/ri_driver.rb) | 60 | ||||
-rw-r--r-- | lib/rdoc/ri/formatter.rb | 662 | ||||
-rw-r--r-- | lib/rdoc/ri/paths.rb | 97 | ||||
-rw-r--r-- | lib/rdoc/ri/reader.rb | 106 | ||||
-rw-r--r-- | lib/rdoc/ri/ri_cache.rb | 187 | ||||
-rw-r--r-- | lib/rdoc/ri/ri_descriptions.rb | 154 | ||||
-rw-r--r-- | lib/rdoc/ri/ri_formatter.rb | 673 | ||||
-rw-r--r-- | lib/rdoc/ri/ri_paths.rb | 97 | ||||
-rw-r--r-- | lib/rdoc/ri/ri_reader.rb | 100 | ||||
-rw-r--r-- | lib/rdoc/ri/ri_writer.rb | 62 | ||||
-rw-r--r-- | lib/rdoc/ri/util.rb (renamed from lib/rdoc/ri/ri_util.rb) | 44 | ||||
-rw-r--r-- | lib/rdoc/ri/writer.rb | 64 |
15 files changed, 1334 insertions, 1378 deletions
diff --git a/lib/rdoc/ri/cache.rb b/lib/rdoc/ri/cache.rb new file mode 100644 index 0000000000..2e267d95fb --- /dev/null +++ b/lib/rdoc/ri/cache.rb @@ -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 + diff --git a/lib/rdoc/ri/descriptions.rb b/lib/rdoc/ri/descriptions.rb new file mode 100644 index 0000000000..c9b7c9ba77 --- /dev/null +++ b/lib/rdoc/ri/descriptions.rb @@ -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 + diff --git a/lib/rdoc/ri/ri_display.rb b/lib/rdoc/ri/display.rb index efc8928765..21fd488bb3 100644 --- a/lib/rdoc/ri/ri_display.rb +++ b/lib/rdoc/ri/display.rb @@ -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 + diff --git a/lib/rdoc/ri/ri_driver.rb b/lib/rdoc/ri/driver.rb index 9f07db82c2..9b1e3bea30 100644 --- a/lib/rdoc/ri/ri_driver.rb +++ b/lib/rdoc/ri/driver.rb @@ -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 diff --git a/lib/rdoc/ri/formatter.rb b/lib/rdoc/ri/formatter.rb new file mode 100644 index 0000000000..0a98508dde --- /dev/null +++ b/lib/rdoc/ri/formatter.rb @@ -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 + diff --git a/lib/rdoc/ri/paths.rb b/lib/rdoc/ri/paths.rb new file mode 100644 index 0000000000..aba9b6f69e --- /dev/null +++ b/lib/rdoc/ri/paths.rb @@ -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 + diff --git a/lib/rdoc/ri/reader.rb b/lib/rdoc/ri/reader.rb new file mode 100644 index 0000000000..e56c9fb76e --- /dev/null +++ b/lib/rdoc/ri/reader.rb @@ -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 + diff --git a/lib/rdoc/ri/ri_cache.rb b/lib/rdoc/ri/ri_cache.rb deleted file mode 100644 index 1844ac969e..0000000000 --- a/lib/rdoc/ri/ri_cache.rb +++ /dev/null @@ -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 diff --git a/lib/rdoc/ri/ri_descriptions.rb b/lib/rdoc/ri/ri_descriptions.rb deleted file mode 100644 index e5ea9f2fbf..0000000000 --- a/lib/rdoc/ri/ri_descriptions.rb +++ /dev/null @@ -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 diff --git a/lib/rdoc/ri/ri_formatter.rb b/lib/rdoc/ri/ri_formatter.rb deleted file mode 100644 index d317170b7c..0000000000 --- a/lib/rdoc/ri/ri_formatter.rb +++ /dev/null @@ -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 - - diff --git a/lib/rdoc/ri/ri_paths.rb b/lib/rdoc/ri/ri_paths.rb deleted file mode 100644 index 0957216794..0000000000 --- a/lib/rdoc/ri/ri_paths.rb +++ /dev/null @@ -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 diff --git a/lib/rdoc/ri/ri_reader.rb b/lib/rdoc/ri/ri_reader.rb deleted file mode 100644 index fb2c373e38..0000000000 --- a/lib/rdoc/ri/ri_reader.rb +++ /dev/null @@ -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 diff --git a/lib/rdoc/ri/ri_writer.rb b/lib/rdoc/ri/ri_writer.rb deleted file mode 100644 index 032558ed1c..0000000000 --- a/lib/rdoc/ri/ri_writer.rb +++ /dev/null @@ -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 diff --git a/lib/rdoc/ri/ri_util.rb b/lib/rdoc/ri/util.rb index 8a01255897..c4e6af47f0 100644 --- a/lib/rdoc/ri/ri_util.rb +++ b/lib/rdoc/ri/util.rb @@ -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 + diff --git a/lib/rdoc/ri/writer.rb b/lib/rdoc/ri/writer.rb new file mode 100644 index 0000000000..2d14942bdd --- /dev/null +++ b/lib/rdoc/ri/writer.rb @@ -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 + |