diff options
author | Jari Vetoniemi <jari.vetoniemi@indooratlas.com> | 2020-03-16 18:49:26 +0900 |
---|---|---|
committer | Jari Vetoniemi <jari.vetoniemi@indooratlas.com> | 2020-03-30 00:39:06 +0900 |
commit | fcbf63e62c627deae76c1b8cb8c0876c536ed811 (patch) | |
tree | 64cb17de3f41a2b6fef2368028fbd00349946994 /jni/ruby/lib/rdoc/markup |
Fresh start
Diffstat (limited to 'jni/ruby/lib/rdoc/markup')
36 files changed, 5262 insertions, 0 deletions
diff --git a/jni/ruby/lib/rdoc/markup/attr_changer.rb b/jni/ruby/lib/rdoc/markup/attr_changer.rb new file mode 100644 index 0000000..1772f18 --- /dev/null +++ b/jni/ruby/lib/rdoc/markup/attr_changer.rb @@ -0,0 +1,22 @@ +class RDoc::Markup + + AttrChanger = Struct.new :turn_on, :turn_off # :nodoc: + +end + +## +# An AttrChanger records a change in attributes. It contains a bitmap of the +# attributes to turn on, and a bitmap of those to turn off. + +class RDoc::Markup::AttrChanger + + def to_s # :nodoc: + "Attr: +#{turn_on}/-#{turn_off}" + end + + def inspect # :nodoc: + '+%d/-%d' % [turn_on, turn_off] + end + +end + diff --git a/jni/ruby/lib/rdoc/markup/attr_span.rb b/jni/ruby/lib/rdoc/markup/attr_span.rb new file mode 100644 index 0000000..b5c1b3b --- /dev/null +++ b/jni/ruby/lib/rdoc/markup/attr_span.rb @@ -0,0 +1,29 @@ +## +# An array of attributes which parallels the characters in a string. + +class RDoc::Markup::AttrSpan + + ## + # Creates a new AttrSpan for +length+ characters + + def initialize(length) + @attrs = Array.new(length, 0) + end + + ## + # Toggles +bits+ from +start+ to +length+ + def set_attrs(start, length, bits) + for i in start ... (start+length) + @attrs[i] |= bits + end + end + + ## + # Accesses flags for character +n+ + + def [](n) + @attrs[n] + end + +end + diff --git a/jni/ruby/lib/rdoc/markup/attribute_manager.rb b/jni/ruby/lib/rdoc/markup/attribute_manager.rb new file mode 100644 index 0000000..ce4ac76 --- /dev/null +++ b/jni/ruby/lib/rdoc/markup/attribute_manager.rb @@ -0,0 +1,343 @@ +## +# Manages changes of attributes in a block of text + +class RDoc::Markup::AttributeManager + + ## + # The NUL character + + NULL = "\000".freeze + + #-- + # We work by substituting non-printing characters in to the text. For now + # I'm assuming that I can substitute a character in the range 0..8 for a 7 + # bit character without damaging the encoded string, but this might be + # optimistic + #++ + + A_PROTECT = 004 # :nodoc: + + ## + # Special mask character to prevent inline markup handling + + PROTECT_ATTR = A_PROTECT.chr # :nodoc: + + ## + # The attributes enabled for this markup object. + + attr_reader :attributes + + ## + # This maps delimiters that occur around words (such as *bold* or +tt+) + # where the start and end delimiters and the same. This lets us optimize + # the regexp + + attr_reader :matching_word_pairs + + ## + # And this is used when the delimiters aren't the same. In this case the + # hash maps a pattern to the attribute character + + attr_reader :word_pair_map + + ## + # This maps HTML tags to the corresponding attribute char + + attr_reader :html_tags + + ## + # A \ in front of a character that would normally be processed turns off + # processing. We do this by turning \< into <#{PROTECT} + + attr_reader :protectable + + ## + # And this maps _special_ sequences to a name. A special sequence is + # something like a WikiWord + + attr_reader :special + + ## + # Creates a new attribute manager that understands bold, emphasized and + # teletype text. + + def initialize + @html_tags = {} + @matching_word_pairs = {} + @protectable = %w[<] + @special = [] + @word_pair_map = {} + @attributes = RDoc::Markup::Attributes.new + + add_word_pair "*", "*", :BOLD + add_word_pair "_", "_", :EM + add_word_pair "+", "+", :TT + + add_html "em", :EM + add_html "i", :EM + add_html "b", :BOLD + add_html "tt", :TT + add_html "code", :TT + end + + ## + # Return an attribute object with the given turn_on and turn_off bits set + + def attribute(turn_on, turn_off) + RDoc::Markup::AttrChanger.new turn_on, turn_off + end + + ## + # Changes the current attribute from +current+ to +new+ + + def change_attribute current, new + diff = current ^ new + attribute(new & diff, current & diff) + end + + ## + # Used by the tests to change attributes by name from +current_set+ to + # +new_set+ + + def changed_attribute_by_name current_set, new_set + current = new = 0 + current_set.each do |name| + current |= @attributes.bitmap_for(name) + end + + new_set.each do |name| + new |= @attributes.bitmap_for(name) + end + + change_attribute(current, new) + end + + ## + # Copies +start_pos+ to +end_pos+ from the current string + + def copy_string(start_pos, end_pos) + res = @str[start_pos...end_pos] + res.gsub!(/\000/, '') + res + end + + ## + # Map attributes like <b>text</b>to the sequence + # \001\002<char>\001\003<char>, where <char> is a per-attribute specific + # character + + def convert_attrs(str, attrs) + # first do matching ones + tags = @matching_word_pairs.keys.join("") + + re = /(^|\W)([#{tags}])([#\\]?[\w:.\/-]+?\S?)\2(\W|$)/ + + 1 while str.gsub!(re) do + attr = @matching_word_pairs[$2] + attrs.set_attrs($`.length + $1.length + $2.length, $3.length, attr) + $1 + NULL * $2.length + $3 + NULL * $2.length + $4 + end + + # then non-matching + unless @word_pair_map.empty? then + @word_pair_map.each do |regexp, attr| + str.gsub!(regexp) { + attrs.set_attrs($`.length + $1.length, $2.length, attr) + NULL * $1.length + $2 + NULL * $3.length + } + end + end + end + + ## + # Converts HTML tags to RDoc attributes + + def convert_html(str, attrs) + tags = @html_tags.keys.join '|' + + 1 while str.gsub!(/<(#{tags})>(.*?)<\/\1>/i) { + attr = @html_tags[$1.downcase] + html_length = $1.length + 2 + seq = NULL * html_length + attrs.set_attrs($`.length + html_length, $2.length, attr) + seq + $2 + seq + NULL + } + end + + ## + # Converts special sequences to RDoc attributes + + def convert_specials str, attrs + @special.each do |regexp, attribute| + str.scan(regexp) do + capture = $~.size == 1 ? 0 : 1 + + s, e = $~.offset capture + + attrs.set_attrs s, e - s, attribute | @attributes.special + end + end + end + + ## + # Escapes special sequences of text to prevent conversion to RDoc + + def mask_protected_sequences + # protect __send__, __FILE__, etc. + @str.gsub!(/__([a-z]+)__/i, + "_#{PROTECT_ATTR}_#{PROTECT_ATTR}\\1_#{PROTECT_ATTR}_#{PROTECT_ATTR}") + @str.gsub!(/(\A|[^\\])\\([#{Regexp.escape @protectable.join}])/m, + "\\1\\2#{PROTECT_ATTR}") + @str.gsub!(/\\(\\[#{Regexp.escape @protectable.join}])/m, "\\1") + end + + ## + # Unescapes special sequences of text + + def unmask_protected_sequences + @str.gsub!(/(.)#{PROTECT_ATTR}/, "\\1\000") + end + + ## + # Adds a markup class with +name+ for words wrapped in the +start+ and + # +stop+ character. To make words wrapped with "*" bold: + # + # am.add_word_pair '*', '*', :BOLD + + def add_word_pair(start, stop, name) + raise ArgumentError, "Word flags may not start with '<'" if + start[0,1] == '<' + + bitmap = @attributes.bitmap_for name + + if start == stop then + @matching_word_pairs[start] = bitmap + else + pattern = /(#{Regexp.escape start})(\S+)(#{Regexp.escape stop})/ + @word_pair_map[pattern] = bitmap + end + + @protectable << start[0,1] + @protectable.uniq! + end + + ## + # Adds a markup class with +name+ for words surrounded by HTML tag +tag+. + # To process emphasis tags: + # + # am.add_html 'em', :EM + + def add_html(tag, name) + @html_tags[tag.downcase] = @attributes.bitmap_for name + end + + ## + # Adds a special handler for +pattern+ with +name+. A simple URL handler + # would be: + # + # @am.add_special(/((https?:)\S+\w)/, :HYPERLINK) + + def add_special pattern, name + @special << [pattern, @attributes.bitmap_for(name)] + end + + ## + # Processes +str+ converting attributes, HTML and specials + + def flow str + @str = str + + mask_protected_sequences + + @attrs = RDoc::Markup::AttrSpan.new @str.length + + convert_attrs @str, @attrs + convert_html @str, @attrs + convert_specials @str, @attrs + + unmask_protected_sequences + + split_into_flow + end + + ## + # Debug method that prints a string along with its attributes + + def display_attributes + puts + puts @str.tr(NULL, "!") + bit = 1 + 16.times do |bno| + line = "" + @str.length.times do |i| + if (@attrs[i] & bit) == 0 + line << " " + else + if bno.zero? + line << "S" + else + line << ("%d" % (bno+1)) + end + end + end + puts(line) unless line =~ /^ *$/ + bit <<= 1 + end + end + + ## + # Splits the string into chunks by attribute change + + def split_into_flow + res = [] + current_attr = 0 + + str_len = @str.length + + # skip leading invisible text + i = 0 + i += 1 while i < str_len and @str[i].chr == "\0" + start_pos = i + + # then scan the string, chunking it on attribute changes + while i < str_len + new_attr = @attrs[i] + if new_attr != current_attr + if i > start_pos + res << copy_string(start_pos, i) + start_pos = i + end + + res << change_attribute(current_attr, new_attr) + current_attr = new_attr + + if (current_attr & @attributes.special) != 0 then + i += 1 while + i < str_len and (@attrs[i] & @attributes.special) != 0 + + res << RDoc::Markup::Special.new(current_attr, + copy_string(start_pos, i)) + start_pos = i + next + end + end + + # move on, skipping any invisible characters + begin + i += 1 + end while i < str_len and @str[i].chr == "\0" + end + + # tidy up trailing text + if start_pos < str_len + res << copy_string(start_pos, str_len) + end + + # and reset to all attributes off + res << change_attribute(current_attr, 0) if current_attr != 0 + + res + end + +end + diff --git a/jni/ruby/lib/rdoc/markup/attributes.rb b/jni/ruby/lib/rdoc/markup/attributes.rb new file mode 100644 index 0000000..3423f10 --- /dev/null +++ b/jni/ruby/lib/rdoc/markup/attributes.rb @@ -0,0 +1,70 @@ +## +# We manage a set of attributes. Each attribute has a symbol name and a bit +# value. + +class RDoc::Markup::Attributes + + ## + # The special attribute type. See RDoc::Markup#add_special + + attr_reader :special + + ## + # Creates a new attributes set. + + def initialize + @special = 1 + + @name_to_bitmap = [ + [:_SPECIAL_, @special], + ] + + @next_bitmap = @special << 1 + end + + ## + # Returns a unique bit for +name+ + + def bitmap_for name + bitmap = @name_to_bitmap.assoc name + + unless bitmap then + bitmap = @next_bitmap + @next_bitmap <<= 1 + @name_to_bitmap << [name, bitmap] + else + bitmap = bitmap.last + end + + bitmap + end + + ## + # Returns a string representation of +bitmap+ + + def as_string bitmap + return 'none' if bitmap.zero? + res = [] + + @name_to_bitmap.each do |name, bit| + res << name if (bitmap & bit) != 0 + end + + res.join ',' + end + + ## + # yields each attribute name in +bitmap+ + + def each_name_of bitmap + return enum_for __method__, bitmap unless block_given? + + @name_to_bitmap.each do |name, bit| + next if bit == @special + + yield name.to_s if (bitmap & bit) != 0 + end + end + +end + diff --git a/jni/ruby/lib/rdoc/markup/blank_line.rb b/jni/ruby/lib/rdoc/markup/blank_line.rb new file mode 100644 index 0000000..5da0ac8 --- /dev/null +++ b/jni/ruby/lib/rdoc/markup/blank_line.rb @@ -0,0 +1,27 @@ +## +# An empty line. This class is a singleton. + +class RDoc::Markup::BlankLine + + @instance = new + + ## + # RDoc::Markup::BlankLine is a singleton + + def self.new + @instance + end + + ## + # Calls #accept_blank_line on +visitor+ + + def accept visitor + visitor.accept_blank_line self + end + + def pretty_print q # :nodoc: + q.text 'blankline' + end + +end + diff --git a/jni/ruby/lib/rdoc/markup/block_quote.rb b/jni/ruby/lib/rdoc/markup/block_quote.rb new file mode 100644 index 0000000..552f0c4 --- /dev/null +++ b/jni/ruby/lib/rdoc/markup/block_quote.rb @@ -0,0 +1,14 @@ +## +# A quoted section which contains markup items. + +class RDoc::Markup::BlockQuote < RDoc::Markup::Raw + + ## + # Calls #accept_block_quote on +visitor+ + + def accept visitor + visitor.accept_block_quote self + end + +end + diff --git a/jni/ruby/lib/rdoc/markup/document.rb b/jni/ruby/lib/rdoc/markup/document.rb new file mode 100644 index 0000000..be93d80 --- /dev/null +++ b/jni/ruby/lib/rdoc/markup/document.rb @@ -0,0 +1,164 @@ +## +# A Document containing lists, headings, paragraphs, etc. + +class RDoc::Markup::Document + + include Enumerable + + ## + # The file this document was created from. See also + # RDoc::ClassModule#add_comment + + attr_reader :file + + ## + # If a heading is below the given level it will be omitted from the + # table_of_contents + + attr_accessor :omit_headings_below + + ## + # The parts of the Document + + attr_reader :parts + + ## + # Creates a new Document with +parts+ + + def initialize *parts + @parts = [] + @parts.concat parts + + @file = nil + @omit_headings_from_table_of_contents_below = nil + end + + ## + # Appends +part+ to the document + + def << part + case part + when RDoc::Markup::Document then + unless part.empty? then + parts.concat part.parts + parts << RDoc::Markup::BlankLine.new + end + when String then + raise ArgumentError, + "expected RDoc::Markup::Document and friends, got String" unless + part.empty? + else + parts << part + end + end + + def == other # :nodoc: + self.class == other.class and + @file == other.file and + @parts == other.parts + end + + ## + # Runs this document and all its #items through +visitor+ + + def accept visitor + visitor.start_accepting + + visitor.accept_document self + + visitor.end_accepting + end + + ## + # Concatenates the given +parts+ onto the document + + def concat parts + self.parts.concat parts + end + + ## + # Enumerator for the parts of this document + + def each &block + @parts.each(&block) + end + + ## + # Does this document have no parts? + + def empty? + @parts.empty? or (@parts.length == 1 and merged? and @parts.first.empty?) + end + + ## + # The file this Document was created from. + + def file= location + @file = case location + when RDoc::TopLevel then + location.relative_name + else + location + end + end + + ## + # When this is a collection of documents (#file is not set and this document + # contains only other documents as its direct children) #merge replaces + # documents in this class with documents from +other+ when the file matches + # and adds documents from +other+ when the files do not. + # + # The information in +other+ is preferred over the receiver + + def merge other + if empty? then + @parts = other.parts + return self + end + + other.parts.each do |other_part| + self.parts.delete_if do |self_part| + self_part.file and self_part.file == other_part.file + end + + self.parts << other_part + end + + self + end + + ## + # Does this Document contain other Documents? + + def merged? + RDoc::Markup::Document === @parts.first + end + + def pretty_print q # :nodoc: + start = @file ? "[doc (#{@file}): " : '[doc: ' + + q.group 2, start, ']' do + q.seplist @parts do |part| + q.pp part + end + end + end + + ## + # Appends +parts+ to the document + + def push *parts + self.parts.concat parts + end + + ## + # Returns an Array of headings in the document. + # + # Require 'rdoc/markup/formatter' before calling this method. + + def table_of_contents + accept RDoc::Markup::ToTableOfContents.to_toc + end + +end + diff --git a/jni/ruby/lib/rdoc/markup/formatter.rb b/jni/ruby/lib/rdoc/markup/formatter.rb new file mode 100644 index 0000000..7661c95 --- /dev/null +++ b/jni/ruby/lib/rdoc/markup/formatter.rb @@ -0,0 +1,264 @@ +## +# Base class for RDoc markup formatters +# +# Formatters are a visitor that converts an RDoc::Markup tree (from a comment) +# into some kind of output. RDoc ships with formatters for converting back to +# rdoc, ANSI text, HTML, a Table of Contents and other formats. +# +# If you'd like to write your own Formatter use +# RDoc::Markup::FormatterTestCase. If you're writing a text-output formatter +# use RDoc::Markup::TextFormatterTestCase which provides extra test cases. + +class RDoc::Markup::Formatter + + ## + # Tag for inline markup containing a +bit+ for the bitmask and the +on+ and + # +off+ triggers. + + InlineTag = Struct.new(:bit, :on, :off) + + ## + # Converts a target url to one that is relative to a given path + + def self.gen_relative_url path, target + from = File.dirname path + to, to_file = File.split target + + from = from.split "/" + to = to.split "/" + + from.delete '.' + to.delete '.' + + while from.size > 0 and to.size > 0 and from[0] == to[0] do + from.shift + to.shift + end + + from.fill ".." + from.concat to + from << to_file + File.join(*from) + end + + ## + # Creates a new Formatter + + def initialize options, markup = nil + @options = options + + @markup = markup || RDoc::Markup.new + @am = @markup.attribute_manager + @am.add_special(/<br>/, :HARD_BREAK) + + @attributes = @am.attributes + + @attr_tags = [] + + @in_tt = 0 + @tt_bit = @attributes.bitmap_for :TT + + @hard_break = '' + @from_path = '.' + end + + ## + # Adds +document+ to the output + + def accept_document document + document.parts.each do |item| + case item + when RDoc::Markup::Document then # HACK + accept_document item + else + item.accept self + end + end + end + + ## + # Adds a special for links of the form rdoc-...: + + def add_special_RDOCLINK + @markup.add_special(/rdoc-[a-z]+:[^\s\]]+/, :RDOCLINK) + end + + ## + # Adds a special for links of the form {<text>}[<url>] and <word>[<url>] + + def add_special_TIDYLINK + @markup.add_special(/(?: + \{.*?\} | # multi-word label + \b[^\s{}]+? # single-word label + ) + + \[\S+?\] # link target + /x, :TIDYLINK) + end + + ## + # Add a new set of tags for an attribute. We allow separate start and end + # tags for flexibility + + def add_tag(name, start, stop) + attr = @attributes.bitmap_for name + @attr_tags << InlineTag.new(attr, start, stop) + end + + ## + # Allows +tag+ to be decorated with additional information. + + def annotate(tag) + tag + end + + ## + # Marks up +content+ + + def convert content + @markup.convert content, self + end + + ## + # Converts flow items +flow+ + + def convert_flow(flow) + res = [] + + flow.each do |item| + case item + when String then + res << convert_string(item) + when RDoc::Markup::AttrChanger then + off_tags res, item + on_tags res, item + when RDoc::Markup::Special then + res << convert_special(item) + else + raise "Unknown flow element: #{item.inspect}" + end + end + + res.join + end + + ## + # Converts added specials. See RDoc::Markup#add_special + + def convert_special special + return special.text if in_tt? + + handled = false + + @attributes.each_name_of special.type do |name| + method_name = "handle_special_#{name}" + + if respond_to? method_name then + special.text = send method_name, special + handled = true + end + end + + unless handled then + special_name = @attributes.as_string special.type + + raise RDoc::Error, "Unhandled special #{special_name}: #{special}" + end + + special.text + end + + ## + # Converts a string to be fancier if desired + + def convert_string string + string + end + + ## + # Use ignore in your subclass to ignore the content of a node. + # + # ## + # # We don't support raw nodes in ToNoRaw + # + # alias accept_raw ignore + + def ignore *node + end + + ## + # Are we currently inside tt tags? + + def in_tt? + @in_tt > 0 + end + + ## + # Turns on tags for +item+ on +res+ + + def on_tags res, item + attr_mask = item.turn_on + return if attr_mask.zero? + + @attr_tags.each do |tag| + if attr_mask & tag.bit != 0 then + res << annotate(tag.on) + @in_tt += 1 if tt? tag + end + end + end + + ## + # Turns off tags for +item+ on +res+ + + def off_tags res, item + attr_mask = item.turn_off + return if attr_mask.zero? + + @attr_tags.reverse_each do |tag| + if attr_mask & tag.bit != 0 then + @in_tt -= 1 if tt? tag + res << annotate(tag.off) + end + end + end + + ## + # Extracts and a scheme, url and an anchor id from +url+ and returns them. + + def parse_url url + case url + when /^rdoc-label:([^:]*)(?::(.*))?/ then + scheme = 'link' + path = "##{$1}" + id = " id=\"#{$2}\"" if $2 + when /([A-Za-z]+):(.*)/ then + scheme = $1.downcase + path = $2 + when /^#/ then + else + scheme = 'http' + path = url + url = url + end + + if scheme == 'link' then + url = if path[0, 1] == '#' then # is this meaningful? + path + else + self.class.gen_relative_url @from_path, path + end + end + + [scheme, url, id] + end + + ## + # Is +tag+ a tt tag? + + def tt? tag + tag.bit == @tt_bit + end + +end + diff --git a/jni/ruby/lib/rdoc/markup/formatter_test_case.rb b/jni/ruby/lib/rdoc/markup/formatter_test_case.rb new file mode 100644 index 0000000..6616a75 --- /dev/null +++ b/jni/ruby/lib/rdoc/markup/formatter_test_case.rb @@ -0,0 +1,767 @@ +require 'minitest/unit' + +## +# Test case for creating new RDoc::Markup formatters. See +# test/test_rdoc_markup_to_*.rb for examples. +# +# This test case adds a variety of tests to your subclass when +# #add_visitor_tests is called. Most tests set up a scenario then call a +# method you will provide to perform the assertion on the output. +# +# Your subclass must instantiate a visitor and assign it to <tt>@to</tt>. +# +# For example, test_accept_blank_line sets up a RDoc::Markup::BlockLine then +# calls accept_blank_line on your visitor. You are responsible for asserting +# that the output is correct. +# +# Example: +# +# class TestRDocMarkupToNewFormat < RDoc::Markup::FormatterTestCase +# +# add_visitor_tests +# +# def setup +# super +# +# @to = RDoc::Markup::ToNewFormat.new +# end +# +# def accept_blank_line +# assert_equal :junk, @to.res.join +# end +# +# # ... +# +# end + +class RDoc::Markup::FormatterTestCase < RDoc::TestCase + + ## + # Call #setup when inheriting from this test case. + # + # Provides the following instance variables: + # + # +@m+:: RDoc::Markup.new + # +@RM+:: RDoc::Markup # to reduce typing + # +@bullet_list+:: @RM::List.new :BULLET, # ... + # +@label_list+:: @RM::List.new :LABEL, # ... + # +@lalpha_list+:: @RM::List.new :LALPHA, # ... + # +@note_list+:: @RM::List.new :NOTE, # ... + # +@number_list+:: @RM::List.new :NUMBER, # ... + # +@ualpha_list+:: @RM::List.new :UALPHA, # ... + + def setup + super + + @options = RDoc::Options.new + + @m = @RM.new + + @bullet_list = @RM::List.new(:BULLET, + @RM::ListItem.new(nil, @RM::Paragraph.new('l1')), + @RM::ListItem.new(nil, @RM::Paragraph.new('l2'))) + + @label_list = @RM::List.new(:LABEL, + @RM::ListItem.new('cat', @RM::Paragraph.new('cats are cool')), + @RM::ListItem.new('dog', @RM::Paragraph.new('dogs are cool too'))) + + @lalpha_list = @RM::List.new(:LALPHA, + @RM::ListItem.new(nil, @RM::Paragraph.new('l1')), + @RM::ListItem.new(nil, @RM::Paragraph.new('l2'))) + + @note_list = @RM::List.new(:NOTE, + @RM::ListItem.new('cat', @RM::Paragraph.new('cats are cool')), + @RM::ListItem.new('dog', @RM::Paragraph.new('dogs are cool too'))) + + @number_list = @RM::List.new(:NUMBER, + @RM::ListItem.new(nil, @RM::Paragraph.new('l1')), + @RM::ListItem.new(nil, @RM::Paragraph.new('l2'))) + + @ualpha_list = @RM::List.new(:UALPHA, + @RM::ListItem.new(nil, @RM::Paragraph.new('l1')), + @RM::ListItem.new(nil, @RM::Paragraph.new('l2'))) + end + + ## + # Call to add the visitor tests to your test case + + def self.add_visitor_tests + class_eval do + + ## + # Calls start_accepting which needs to verify startup state + + def test_start_accepting + @to.start_accepting + + start_accepting + end + + ## + # Calls end_accepting on your test case which needs to call + # <tt>@to.end_accepting</tt> and verify document generation + + def test_end_accepting + @to.start_accepting + @to.res << 'hi' + + end_accepting + end + + ## + # Calls accept_blank_line + + def test_accept_blank_line + @to.start_accepting + + @to.accept_blank_line @RM::BlankLine.new + + accept_blank_line + end + + ## + # Calls accept_block_quote + + def test_accept_block_quote + @to.start_accepting + + @to.accept_block_quote block para 'quote' + + accept_block_quote + end + ## + # Test case that calls <tt>@to.accept_document</tt> + + def test_accept_document + @to.start_accepting + @to.accept_document @RM::Document.new @RM::Paragraph.new 'hello' + + accept_document + end + + ## + # Calls accept_heading with a level 5 RDoc::Markup::Heading + + def test_accept_heading + @to.start_accepting + + @to.accept_heading @RM::Heading.new(5, 'Hello') + + accept_heading + end + + ## + # Calls accept_heading_1 with a level 1 RDoc::Markup::Heading + + def test_accept_heading_1 + @to.start_accepting + + @to.accept_heading @RM::Heading.new(1, 'Hello') + + accept_heading_1 + end + + ## + # Calls accept_heading_2 with a level 2 RDoc::Markup::Heading + + def test_accept_heading_2 + @to.start_accepting + + @to.accept_heading @RM::Heading.new(2, 'Hello') + + accept_heading_2 + end + + ## + # Calls accept_heading_3 with a level 3 RDoc::Markup::Heading + + def test_accept_heading_3 + # HACK this doesn't belong here + skip "No String#chars, upgrade your ruby" unless ''.respond_to? :chars + + @to.start_accepting + + @to.accept_heading @RM::Heading.new(3, 'Hello') + + accept_heading_3 + end + + ## + # Calls accept_heading_4 with a level 4 RDoc::Markup::Heading + + def test_accept_heading_4 + @to.start_accepting + + @to.accept_heading @RM::Heading.new(4, 'Hello') + + accept_heading_4 + end + + ## + # Calls accept_heading_b with a bold level 1 RDoc::Markup::Heading + + def test_accept_heading_b + @to.start_accepting + + @to.accept_heading @RM::Heading.new(1, '*Hello*') + + accept_heading_b + end + + ## + # Calls accept_heading_suppressed_crossref with a level 1 + # RDoc::Markup::Heading containing a suppressed crossref + + def test_accept_heading_suppressed_crossref # HACK to_html_crossref test + @to.start_accepting + + @to.accept_heading @RM::Heading.new(1, '\\Hello') + + accept_heading_suppressed_crossref + end + + ## + # Calls accept_paragraph + + def test_accept_paragraph + @to.start_accepting + + @to.accept_paragraph @RM::Paragraph.new('hi') + + accept_paragraph + end + + ## + # Calls accept_paragraph_b with a RDoc::Markup::Paragraph containing + # bold words + + def test_accept_paragraph_b + @to.start_accepting + + @to.accept_paragraph @RM::Paragraph.new('reg <b>bold words</b> reg') + + accept_paragraph_b + end + + ## + # Calls accept_paragraph_br with a RDoc::Markup::Paragraph containing + # a \<br> + + def test_accept_paragraph_br + @to.start_accepting + + @to.accept_paragraph para 'one<br>two' + + accept_paragraph_br + end + + ## + # Calls accept_paragraph with a Paragraph containing a hard break + + def test_accept_paragraph_break + @to.start_accepting + + @to.accept_paragraph para('hello', hard_break, 'world') + + accept_paragraph_break + end + + ## + # Calls accept_paragraph_i with a RDoc::Markup::Paragraph containing + # emphasized words + + def test_accept_paragraph_i + @to.start_accepting + + @to.accept_paragraph @RM::Paragraph.new('reg <em>italic words</em> reg') + + accept_paragraph_i + end + + ## + # Calls accept_paragraph_plus with a RDoc::Markup::Paragraph containing + # teletype words + + def test_accept_paragraph_plus + @to.start_accepting + + @to.accept_paragraph @RM::Paragraph.new('reg +teletype+ reg') + + accept_paragraph_plus + end + + ## + # Calls accept_paragraph_star with a RDoc::Markup::Paragraph containing + # bold words + + def test_accept_paragraph_star + @to.start_accepting + + @to.accept_paragraph @RM::Paragraph.new('reg *bold* reg') + + accept_paragraph_star + end + + ## + # Calls accept_paragraph_underscore with a RDoc::Markup::Paragraph + # containing emphasized words + + def test_accept_paragraph_underscore + @to.start_accepting + + @to.accept_paragraph @RM::Paragraph.new('reg _italic_ reg') + + accept_paragraph_underscore + end + + ## + # Calls accept_verbatim with a RDoc::Markup::Verbatim + + def test_accept_verbatim + @to.start_accepting + + @to.accept_verbatim @RM::Verbatim.new("hi\n", " world\n") + + accept_verbatim + end + + ## + # Calls accept_raw with a RDoc::Markup::Raw + + def test_accept_raw + @to.start_accepting + + @to.accept_raw @RM::Raw.new("<table>", + "<tr><th>Name<th>Count", + "<tr><td>a<td>1", + "<tr><td>b<td>2", + "</table>") + + accept_raw + end + + ## + # Calls accept_rule with a RDoc::Markup::Rule + + def test_accept_rule + @to.start_accepting + + @to.accept_rule @RM::Rule.new(4) + + accept_rule + end + + ## + # Calls accept_list_item_start_bullet + + def test_accept_list_item_start_bullet + @to.start_accepting + + @to.accept_list_start @bullet_list + + @to.accept_list_item_start @bullet_list.items.first + + accept_list_item_start_bullet + end + + ## + # Calls accept_list_item_start_label + + def test_accept_list_item_start_label + @to.start_accepting + + @to.accept_list_start @label_list + + @to.accept_list_item_start @label_list.items.first + + accept_list_item_start_label + end + + ## + # Calls accept_list_item_start_lalpha + + def test_accept_list_item_start_lalpha + @to.start_accepting + + @to.accept_list_start @lalpha_list + + @to.accept_list_item_start @lalpha_list.items.first + + accept_list_item_start_lalpha + end + + ## + # Calls accept_list_item_start_note + + def test_accept_list_item_start_note + @to.start_accepting + + @to.accept_list_start @note_list + + @to.accept_list_item_start @note_list.items.first + + accept_list_item_start_note + end + + ## + # Calls accept_list_item_start_note_2 + + def test_accept_list_item_start_note_2 + list = list(:NOTE, + item('<tt>teletype</tt>', + para('teletype description'))) + + @to.start_accepting + + list.accept @to + + @to.end_accepting + + accept_list_item_start_note_2 + end + + ## + # Calls accept_list_item_start_note_multi_description + + def test_accept_list_item_start_note_multi_description + list = list(:NOTE, + item(%w[label], + para('description one')), + item(nil, para('description two'))) + + @to.start_accepting + + list.accept @to + + @to.end_accepting + + accept_list_item_start_note_multi_description + end + + ## + # Calls accept_list_item_start_note_multi_label + + def test_accept_list_item_start_note_multi_label + list = list(:NOTE, + item(%w[one two], + para('two headers'))) + + @to.start_accepting + + list.accept @to + + @to.end_accepting + + accept_list_item_start_note_multi_label + end + + ## + # Calls accept_list_item_start_number + + def test_accept_list_item_start_number + @to.start_accepting + + @to.accept_list_start @number_list + + @to.accept_list_item_start @number_list.items.first + + accept_list_item_start_number + end + + ## + # Calls accept_list_item_start_ualpha + + def test_accept_list_item_start_ualpha + @to.start_accepting + + @to.accept_list_start @ualpha_list + + @to.accept_list_item_start @ualpha_list.items.first + + accept_list_item_start_ualpha + end + + ## + # Calls accept_list_item_end_bullet + + def test_accept_list_item_end_bullet + @to.start_accepting + + @to.accept_list_start @bullet_list + + @to.accept_list_item_start @bullet_list.items.first + + @to.accept_list_item_end @bullet_list.items.first + + accept_list_item_end_bullet + end + + ## + # Calls accept_list_item_end_label + + def test_accept_list_item_end_label + @to.start_accepting + + @to.accept_list_start @label_list + + @to.accept_list_item_start @label_list.items.first + + @to.accept_list_item_end @label_list.items.first + + accept_list_item_end_label + end + + ## + # Calls accept_list_item_end_lalpha + + def test_accept_list_item_end_lalpha + @to.start_accepting + + @to.accept_list_start @lalpha_list + + @to.accept_list_item_start @lalpha_list.items.first + + @to.accept_list_item_end @lalpha_list.items.first + + accept_list_item_end_lalpha + end + + ## + # Calls accept_list_item_end_note + + def test_accept_list_item_end_note + @to.start_accepting + + @to.accept_list_start @note_list + + @to.accept_list_item_start @note_list.items.first + + @to.accept_list_item_end @note_list.items.first + + accept_list_item_end_note + end + + ## + # Calls accept_list_item_end_number + + def test_accept_list_item_end_number + @to.start_accepting + + @to.accept_list_start @number_list + + @to.accept_list_item_start @number_list.items.first + + @to.accept_list_item_end @number_list.items.first + + accept_list_item_end_number + end + + ## + # Calls accept_list_item_end_ualpha + + def test_accept_list_item_end_ualpha + @to.start_accepting + + @to.accept_list_start @ualpha_list + + @to.accept_list_item_start @ualpha_list.items.first + + @to.accept_list_item_end @ualpha_list.items.first + + accept_list_item_end_ualpha + end + + ## + # Calls accept_list_start_bullet + + def test_accept_list_start_bullet + @to.start_accepting + + @to.accept_list_start @bullet_list + + accept_list_start_bullet + end + + ## + # Calls accept_list_start_label + + def test_accept_list_start_label + @to.start_accepting + + @to.accept_list_start @label_list + + accept_list_start_label + end + + ## + # Calls accept_list_start_lalpha + + def test_accept_list_start_lalpha + @to.start_accepting + + @to.accept_list_start @lalpha_list + + accept_list_start_lalpha + end + + ## + # Calls accept_list_start_note + + def test_accept_list_start_note + @to.start_accepting + + @to.accept_list_start @note_list + + accept_list_start_note + end + + ## + # Calls accept_list_start_number + + def test_accept_list_start_number + @to.start_accepting + + @to.accept_list_start @number_list + + accept_list_start_number + end + + ## + # Calls accept_list_start_ualpha + + def test_accept_list_start_ualpha + @to.start_accepting + + @to.accept_list_start @ualpha_list + + accept_list_start_ualpha + end + + ## + # Calls accept_list_end_bullet + + def test_accept_list_end_bullet + @to.start_accepting + + @to.accept_list_start @bullet_list + + @to.accept_list_end @bullet_list + + accept_list_end_bullet + end + + ## + # Calls accept_list_end_label + + def test_accept_list_end_label + @to.start_accepting + + @to.accept_list_start @label_list + + @to.accept_list_end @label_list + + accept_list_end_label + end + + ## + # Calls accept_list_end_lalpha + + def test_accept_list_end_lalpha + @to.start_accepting + + @to.accept_list_start @lalpha_list + + @to.accept_list_end @lalpha_list + + accept_list_end_lalpha + end + + ## + # Calls accept_list_end_number + + def test_accept_list_end_number + @to.start_accepting + + @to.accept_list_start @number_list + + @to.accept_list_end @number_list + + accept_list_end_number + end + + ## + # Calls accept_list_end_note + + def test_accept_list_end_note + @to.start_accepting + + @to.accept_list_start @note_list + + @to.accept_list_end @note_list + + accept_list_end_note + end + + ## + # Calls accept_list_end_ualpha + + def test_accept_list_end_ualpha + @to.start_accepting + + @to.accept_list_start @ualpha_list + + @to.accept_list_end @ualpha_list + + accept_list_end_ualpha + end + + ## + # Calls list_nested with a two-level list + + def test_list_nested + doc = @RM::Document.new( + @RM::List.new(:BULLET, + @RM::ListItem.new(nil, + @RM::Paragraph.new('l1'), + @RM::List.new(:BULLET, + @RM::ListItem.new(nil, + @RM::Paragraph.new('l1.1')))), + @RM::ListItem.new(nil, + @RM::Paragraph.new('l2')))) + + doc.accept @to + + list_nested + end + + ## + # Calls list_verbatim with a list containing a verbatim block + + def test_list_verbatim # HACK overblown + doc = + doc( + list(:BULLET, + item(nil, + para('list stuff'), + blank_line, + verb("* list\n", + " with\n", + "\n", + " second\n", + "\n", + " 1. indented\n", + " 2. numbered\n", + "\n", + " third\n", + "\n", + "* second\n")))) + + doc.accept @to + + list_verbatim + end + end + end + +end + diff --git a/jni/ruby/lib/rdoc/markup/hard_break.rb b/jni/ruby/lib/rdoc/markup/hard_break.rb new file mode 100644 index 0000000..8445ad2 --- /dev/null +++ b/jni/ruby/lib/rdoc/markup/hard_break.rb @@ -0,0 +1,31 @@ +## +# A hard-break in the middle of a paragraph. + +class RDoc::Markup::HardBreak + + @instance = new + + ## + # RDoc::Markup::HardBreak is a singleton + + def self.new + @instance + end + + ## + # Calls #accept_hard_break on +visitor+ + + def accept visitor + visitor.accept_hard_break self + end + + def == other # :nodoc: + self.class === other + end + + def pretty_print q # :nodoc: + q.text "[break]" + end + +end + diff --git a/jni/ruby/lib/rdoc/markup/heading.rb b/jni/ruby/lib/rdoc/markup/heading.rb new file mode 100644 index 0000000..535e310 --- /dev/null +++ b/jni/ruby/lib/rdoc/markup/heading.rb @@ -0,0 +1,78 @@ +## +# A heading with a level (1-6) and text + +RDoc::Markup::Heading = + Struct.new :level, :text do + + @to_html = nil + @to_label = nil + + ## + # A singleton RDoc::Markup::ToLabel formatter for headings. + + def self.to_label + @to_label ||= RDoc::Markup::ToLabel.new + end + + ## + # A singleton plain HTML formatter for headings. Used for creating labels + # for the Table of Contents + + def self.to_html + return @to_html if @to_html + + markup = RDoc::Markup.new + markup.add_special RDoc::CrossReference::CROSSREF_REGEXP, :CROSSREF + + @to_html = RDoc::Markup::ToHtml.new nil + + def @to_html.handle_special_CROSSREF special + special.text.sub(/^\\/, '') + end + + @to_html + end + + ## + # Calls #accept_heading on +visitor+ + + def accept visitor + visitor.accept_heading self + end + + ## + # An HTML-safe anchor reference for this header. + + def aref + "label-#{self.class.to_label.convert text.dup}" + end + + ## + # Creates a fully-qualified label which will include the label from + # +context+. This helps keep ids unique in HTML. + + def label context = nil + label = aref + + label = [context.aref, label].compact.join '-' if + context and context.respond_to? :aref + + label + end + + ## + # HTML markup of the text of this label without the surrounding header + # element. + + def plain_html + self.class.to_html.to_html(text.dup) + end + + def pretty_print q # :nodoc: + q.group 2, "[head: #{level} ", ']' do + q.pp text + end + end + +end + diff --git a/jni/ruby/lib/rdoc/markup/include.rb b/jni/ruby/lib/rdoc/markup/include.rb new file mode 100644 index 0000000..a2e8903 --- /dev/null +++ b/jni/ruby/lib/rdoc/markup/include.rb @@ -0,0 +1,42 @@ +## +# A file included at generation time. Objects of this class are created by +# RDoc::RD for an extension-less include. +# +# This implementation in incomplete. + +class RDoc::Markup::Include + + ## + # The filename to be included, without extension + + attr_reader :file + + ## + # Directories to search for #file + + attr_reader :include_path + + ## + # Creates a new include that will import +file+ from +include_path+ + + def initialize file, include_path + @file = file + @include_path = include_path + end + + def == other # :nodoc: + self.class === other and + @file == other.file and @include_path == other.include_path + end + + def pretty_print q # :nodoc: + q.group 2, '[incl ', ']' do + q.text file + q.breakable + q.text 'from ' + q.pp include_path + end + end + +end + diff --git a/jni/ruby/lib/rdoc/markup/indented_paragraph.rb b/jni/ruby/lib/rdoc/markup/indented_paragraph.rb new file mode 100644 index 0000000..1b8a8c7 --- /dev/null +++ b/jni/ruby/lib/rdoc/markup/indented_paragraph.rb @@ -0,0 +1,47 @@ +## +# An Indented Paragraph of text + +class RDoc::Markup::IndentedParagraph < RDoc::Markup::Raw + + ## + # The indent in number of spaces + + attr_reader :indent + + ## + # Creates a new IndentedParagraph containing +parts+ indented with +indent+ + # spaces + + def initialize indent, *parts + @indent = indent + + super(*parts) + end + + def == other # :nodoc: + super and indent == other.indent + end + + ## + # Calls #accept_indented_paragraph on +visitor+ + + def accept visitor + visitor.accept_indented_paragraph self + end + + ## + # Joins the raw paragraph text and converts inline HardBreaks to the + # +hard_break+ text followed by the indent. + + def text hard_break = nil + @parts.map do |part| + if RDoc::Markup::HardBreak === part then + '%1$s%3$*2$s' % [hard_break, @indent, ' '] if hard_break + else + part + end + end.join + end + +end + diff --git a/jni/ruby/lib/rdoc/markup/inline.rb b/jni/ruby/lib/rdoc/markup/inline.rb new file mode 100644 index 0000000..fb3ab5c --- /dev/null +++ b/jni/ruby/lib/rdoc/markup/inline.rb @@ -0,0 +1 @@ +warn "requiring rdoc/markup/inline is deprecated and will be removed in RDoc 4." if $-w diff --git a/jni/ruby/lib/rdoc/markup/list.rb b/jni/ruby/lib/rdoc/markup/list.rb new file mode 100644 index 0000000..89b7fc2 --- /dev/null +++ b/jni/ruby/lib/rdoc/markup/list.rb @@ -0,0 +1,101 @@ +## +# A List is a homogeneous set of ListItems. +# +# The supported list types include: +# +# :BULLET:: +# An unordered list +# :LABEL:: +# An unordered definition list, but using an alternate RDoc::Markup syntax +# :LALPHA:: +# An ordered list using increasing lowercase English letters +# :NOTE:: +# An unordered definition list +# :NUMBER:: +# An ordered list using increasing Arabic numerals +# :UALPHA:: +# An ordered list using increasing uppercase English letters +# +# Definition lists behave like HTML definition lists. Each list item can +# describe multiple terms. See RDoc::Markup::ListItem for how labels and +# definition are stored as list items. + +class RDoc::Markup::List + + ## + # The list's type + + attr_accessor :type + + ## + # Items in the list + + attr_reader :items + + ## + # Creates a new list of +type+ with +items+. Valid list types are: + # +:BULLET+, +:LABEL+, +:LALPHA+, +:NOTE+, +:NUMBER+, +:UALPHA+ + + def initialize type = nil, *items + @type = type + @items = [] + @items.concat items + end + + ## + # Appends +item+ to the list + + def << item + @items << item + end + + def == other # :nodoc: + self.class == other.class and + @type == other.type and + @items == other.items + end + + ## + # Runs this list and all its #items through +visitor+ + + def accept visitor + visitor.accept_list_start self + + @items.each do |item| + item.accept visitor + end + + visitor.accept_list_end self + end + + ## + # Is the list empty? + + def empty? + @items.empty? + end + + ## + # Returns the last item in the list + + def last + @items.last + end + + def pretty_print q # :nodoc: + q.group 2, "[list: #{@type} ", ']' do + q.seplist @items do |item| + q.pp item + end + end + end + + ## + # Appends +items+ to the list + + def push *items + @items.concat items + end + +end + diff --git a/jni/ruby/lib/rdoc/markup/list_item.rb b/jni/ruby/lib/rdoc/markup/list_item.rb new file mode 100644 index 0000000..c5e59fe --- /dev/null +++ b/jni/ruby/lib/rdoc/markup/list_item.rb @@ -0,0 +1,99 @@ +## +# An item within a List that contains paragraphs, headings, etc. +# +# For BULLET, NUMBER, LALPHA and UALPHA lists, the label will always be nil. +# For NOTE and LABEL lists, the list label may contain: +# +# * a single String for a single label +# * an Array of Strings for a list item with multiple terms +# * nil for an extra description attached to a previously labeled list item + +class RDoc::Markup::ListItem + + ## + # The label for the ListItem + + attr_accessor :label + + ## + # Parts of the ListItem + + attr_reader :parts + + ## + # Creates a new ListItem with an optional +label+ containing +parts+ + + def initialize label = nil, *parts + @label = label + @parts = [] + @parts.concat parts + end + + ## + # Appends +part+ to the ListItem + + def << part + @parts << part + end + + def == other # :nodoc: + self.class == other.class and + @label == other.label and + @parts == other.parts + end + + ## + # Runs this list item and all its #parts through +visitor+ + + def accept visitor + visitor.accept_list_item_start self + + @parts.each do |part| + part.accept visitor + end + + visitor.accept_list_item_end self + end + + ## + # Is the ListItem empty? + + def empty? + @parts.empty? + end + + ## + # Length of parts in the ListItem + + def length + @parts.length + end + + def pretty_print q # :nodoc: + q.group 2, '[item: ', ']' do + case @label + when Array then + q.pp @label + q.text ';' + q.breakable + when String then + q.pp @label + q.text ';' + q.breakable + end + + q.seplist @parts do |part| + q.pp part + end + end + end + + ## + # Adds +parts+ to the ListItem + + def push *parts + @parts.concat parts + end + +end + diff --git a/jni/ruby/lib/rdoc/markup/paragraph.rb b/jni/ruby/lib/rdoc/markup/paragraph.rb new file mode 100644 index 0000000..7180729 --- /dev/null +++ b/jni/ruby/lib/rdoc/markup/paragraph.rb @@ -0,0 +1,28 @@ +## +# A Paragraph of text + +class RDoc::Markup::Paragraph < RDoc::Markup::Raw + + ## + # Calls #accept_paragraph on +visitor+ + + def accept visitor + visitor.accept_paragraph self + end + + ## + # Joins the raw paragraph text and converts inline HardBreaks to the + # +hard_break+ text. + + def text hard_break = '' + @parts.map do |part| + if RDoc::Markup::HardBreak === part then + hard_break + else + part + end + end.join + end + +end + diff --git a/jni/ruby/lib/rdoc/markup/parser.rb b/jni/ruby/lib/rdoc/markup/parser.rb new file mode 100644 index 0000000..cc828a4 --- /dev/null +++ b/jni/ruby/lib/rdoc/markup/parser.rb @@ -0,0 +1,558 @@ +require 'strscan' + +## +# A recursive-descent parser for RDoc markup. +# +# The parser tokenizes an input string then parses the tokens into a Document. +# Documents can be converted into output formats by writing a visitor like +# RDoc::Markup::ToHTML. +# +# The parser only handles the block-level constructs Paragraph, List, +# ListItem, Heading, Verbatim, BlankLine and Rule. Inline markup such as +# <tt>\+blah\+</tt> is handled separately by RDoc::Markup::AttributeManager. +# +# To see what markup the Parser implements read RDoc. To see how to use +# RDoc markup to format text in your program read RDoc::Markup. + +class RDoc::Markup::Parser + + include RDoc::Text + + ## + # List token types + + LIST_TOKENS = [ + :BULLET, + :LABEL, + :LALPHA, + :NOTE, + :NUMBER, + :UALPHA, + ] + + ## + # Parser error subclass + + class Error < RuntimeError; end + + ## + # Raised when the parser is unable to handle the given markup + + class ParseError < Error; end + + ## + # Enables display of debugging information + + attr_accessor :debug + + ## + # Token accessor + + attr_reader :tokens + + ## + # Parses +str+ into a Document. + # + # Use RDoc::Markup#parse instead of this method. + + def self.parse str + parser = new + parser.tokenize str + doc = RDoc::Markup::Document.new + parser.parse doc + end + + ## + # Returns a token stream for +str+, for testing + + def self.tokenize str + parser = new + parser.tokenize str + parser.tokens + end + + ## + # Creates a new Parser. See also ::parse + + def initialize + @binary_input = nil + @current_token = nil + @debug = false + @have_encoding = Object.const_defined? :Encoding + @have_byteslice = ''.respond_to? :byteslice + @input = nil + @input_encoding = nil + @line = 0 + @line_pos = 0 + @s = nil + @tokens = [] + end + + ## + # Builds a Heading of +level+ + + def build_heading level + type, text, = get + + text = case type + when :TEXT then + skip :NEWLINE + text + else + unget + '' + end + + RDoc::Markup::Heading.new level, text + end + + ## + # Builds a List flush to +margin+ + + def build_list margin + p :list_start => margin if @debug + + list = RDoc::Markup::List.new + label = nil + + until @tokens.empty? do + type, data, column, = get + + case type + when *LIST_TOKENS then + if column < margin || (list.type && list.type != type) then + unget + break + end + + list.type = type + peek_type, _, column, = peek_token + + case type + when :NOTE, :LABEL then + label = [] unless label + + if peek_type == :NEWLINE then + # description not on the same line as LABEL/NOTE + # skip the trailing newline & any blank lines below + while peek_type == :NEWLINE + get + peek_type, _, column, = peek_token + end + + # we may be: + # - at end of stream + # - at a column < margin: + # [text] + # blah blah blah + # - at the same column, but with a different type of list item + # [text] + # * blah blah + # - at the same column, with the same type of list item + # [one] + # [two] + # In all cases, we have an empty description. + # In the last case only, we continue. + if peek_type.nil? || column < margin then + empty = true + elsif column == margin then + case peek_type + when type + empty = :continue + when *LIST_TOKENS + empty = true + else + empty = false + end + else + empty = false + end + + if empty then + label << data + next if empty == :continue + break + end + end + else + data = nil + end + + if label then + data = label << data + label = nil + end + + list_item = RDoc::Markup::ListItem.new data + parse list_item, column + list << list_item + + else + unget + break + end + end + + p :list_end => margin if @debug + + if list.empty? then + return nil unless label + return nil unless [:LABEL, :NOTE].include? list.type + + list_item = RDoc::Markup::ListItem.new label, RDoc::Markup::BlankLine.new + list << list_item + end + + list + end + + ## + # Builds a Paragraph that is flush to +margin+ + + def build_paragraph margin + p :paragraph_start => margin if @debug + + paragraph = RDoc::Markup::Paragraph.new + + until @tokens.empty? do + type, data, column, = get + + if type == :TEXT and column == margin then + paragraph << data + + break if peek_token.first == :BREAK + + data << ' ' if skip :NEWLINE + else + unget + break + end + end + + paragraph.parts.last.sub!(/ \z/, '') # cleanup + + p :paragraph_end => margin if @debug + + paragraph + end + + ## + # Builds a Verbatim that is indented from +margin+. + # + # The verbatim block is shifted left (the least indented lines start in + # column 0). Each part of the verbatim is one line of text, always + # terminated by a newline. Blank lines always consist of a single newline + # character, and there is never a single newline at the end of the verbatim. + + def build_verbatim margin + p :verbatim_begin => margin if @debug + verbatim = RDoc::Markup::Verbatim.new + + min_indent = nil + generate_leading_spaces = true + line = '' + + until @tokens.empty? do + type, data, column, = get + + if type == :NEWLINE then + line << data + verbatim << line + line = '' + generate_leading_spaces = true + next + end + + if column <= margin + unget + break + end + + if generate_leading_spaces then + indent = column - margin + line << ' ' * indent + min_indent = indent if min_indent.nil? || indent < min_indent + generate_leading_spaces = false + end + + case type + when :HEADER then + line << '=' * data + _, _, peek_column, = peek_token + peek_column ||= column + data + indent = peek_column - column - data + line << ' ' * indent + when :RULE then + width = 2 + data + line << '-' * width + _, _, peek_column, = peek_token + peek_column ||= column + width + indent = peek_column - column - width + line << ' ' * indent + when :BREAK, :TEXT then + line << data + else # *LIST_TOKENS + list_marker = case type + when :BULLET then data + when :LABEL then "[#{data}]" + when :NOTE then "#{data}::" + else # :LALPHA, :NUMBER, :UALPHA + "#{data}." + end + line << list_marker + peek_type, _, peek_column = peek_token + unless peek_type == :NEWLINE then + peek_column ||= column + list_marker.length + indent = peek_column - column - list_marker.length + line << ' ' * indent + end + end + + end + + verbatim << line << "\n" unless line.empty? + verbatim.parts.each { |p| p.slice!(0, min_indent) unless p == "\n" } if min_indent > 0 + verbatim.normalize + + p :verbatim_end => margin if @debug + + verbatim + end + + ## + # The character offset for the input string at the given +byte_offset+ + + def char_pos byte_offset + if @have_byteslice then + @input.byteslice(0, byte_offset).length + elsif @have_encoding then + matched = @binary_input[0, byte_offset] + matched.force_encoding @input_encoding + matched.length + else + byte_offset + end + end + + ## + # Pulls the next token from the stream. + + def get + @current_token = @tokens.shift + p :get => @current_token if @debug + @current_token + end + + ## + # Parses the tokens into an array of RDoc::Markup::XXX objects, + # and appends them to the passed +parent+ RDoc::Markup::YYY object. + # + # Exits at the end of the token stream, or when it encounters a token + # in a column less than +indent+ (unless it is a NEWLINE). + # + # Returns +parent+. + + def parse parent, indent = 0 + p :parse_start => indent if @debug + + until @tokens.empty? do + type, data, column, = get + + case type + when :BREAK then + parent << RDoc::Markup::BlankLine.new + skip :NEWLINE, false + next + when :NEWLINE then + # trailing newlines are skipped below, so this is a blank line + parent << RDoc::Markup::BlankLine.new + skip :NEWLINE, false + next + end + + # indentation change: break or verbatim + if column < indent then + unget + break + elsif column > indent then + unget + parent << build_verbatim(indent) + next + end + + # indentation is the same + case type + when :HEADER then + parent << build_heading(data) + when :RULE then + parent << RDoc::Markup::Rule.new(data) + skip :NEWLINE + when :TEXT then + unget + parse_text parent, indent + when *LIST_TOKENS then + unget + parent << build_list(indent) + else + type, data, column, line = @current_token + raise ParseError, "Unhandled token #{type} (#{data.inspect}) at #{line}:#{column}" + end + end + + p :parse_end => indent if @debug + + parent + + end + + ## + # Small hook that is overridden by RDoc::TomDoc + + def parse_text parent, indent # :nodoc: + parent << build_paragraph(indent) + end + + ## + # Returns the next token on the stream without modifying the stream + + def peek_token + token = @tokens.first || [] + p :peek => token if @debug + token + end + + ## + # Creates the StringScanner + + def setup_scanner input + @line = 0 + @line_pos = 0 + @input = input.dup + + if @have_encoding and not @have_byteslice then + @input_encoding = @input.encoding + @binary_input = @input.force_encoding Encoding::BINARY + end + + @s = StringScanner.new input + end + + ## + # Skips the next token if its type is +token_type+. + # + # Optionally raises an error if the next token is not of the expected type. + + def skip token_type, error = true + type, = get + return unless type # end of stream + return @current_token if token_type == type + unget + raise ParseError, "expected #{token_type} got #{@current_token.inspect}" if error + end + + ## + # Turns text +input+ into a stream of tokens + + def tokenize input + setup_scanner input + + until @s.eos? do + pos = @s.pos + + # leading spaces will be reflected by the column of the next token + # the only thing we loose are trailing spaces at the end of the file + next if @s.scan(/ +/) + + # note: after BULLET, LABEL, etc., + # indent will be the column of the next non-newline token + + @tokens << case + # [CR]LF => :NEWLINE + when @s.scan(/\r?\n/) then + token = [:NEWLINE, @s.matched, *token_pos(pos)] + @line_pos = char_pos @s.pos + @line += 1 + token + # === text => :HEADER then :TEXT + when @s.scan(/(=+)(\s*)/) then + level = @s[1].length + header = [:HEADER, level, *token_pos(pos)] + + if @s[2] =~ /^\r?\n/ then + @s.pos -= @s[2].length + header + else + pos = @s.pos + @s.scan(/.*/) + @tokens << header + [:TEXT, @s.matched.sub(/\r$/, ''), *token_pos(pos)] + end + # --- (at least 3) and nothing else on the line => :RULE + when @s.scan(/(-{3,}) *\r?$/) then + [:RULE, @s[1].length - 2, *token_pos(pos)] + # * or - followed by white space and text => :BULLET + when @s.scan(/([*-]) +(\S)/) then + @s.pos -= @s[2].bytesize # unget \S + [:BULLET, @s[1], *token_pos(pos)] + # A. text, a. text, 12. text => :UALPHA, :LALPHA, :NUMBER + when @s.scan(/([a-z]|\d+)\. +(\S)/i) then + # FIXME if tab(s), the column will be wrong + # either support tabs everywhere by first expanding them to + # spaces, or assume that they will have been replaced + # before (and provide a check for that at least in debug + # mode) + list_label = @s[1] + @s.pos -= @s[2].bytesize # unget \S + list_type = + case list_label + when /[a-z]/ then :LALPHA + when /[A-Z]/ then :UALPHA + when /\d/ then :NUMBER + else + raise ParseError, "BUG token #{list_label}" + end + [list_type, list_label, *token_pos(pos)] + # [text] followed by spaces or end of line => :LABEL + when @s.scan(/\[(.*?)\]( +|\r?$)/) then + [:LABEL, @s[1], *token_pos(pos)] + # text:: followed by spaces or end of line => :NOTE + when @s.scan(/(.*?)::( +|\r?$)/) then + [:NOTE, @s[1], *token_pos(pos)] + # anything else: :TEXT + else @s.scan(/(.*?)( )?\r?$/) + token = [:TEXT, @s[1], *token_pos(pos)] + + if @s[2] then + @tokens << token + [:BREAK, @s[2], *token_pos(pos + @s[1].length)] + else + token + end + end + end + + self + end + + ## + # Calculates the column (by character) and line of the current token based + # on +byte_offset+. + + def token_pos byte_offset + offset = char_pos byte_offset + + [offset - @line_pos, @line] + end + + ## + # Returns the current token to the token stream + + def unget + token = @current_token + p :unget => token if @debug + raise Error, 'too many #ungets' if token == @tokens.first + @tokens.unshift token if token + end + +end + diff --git a/jni/ruby/lib/rdoc/markup/pre_process.rb b/jni/ruby/lib/rdoc/markup/pre_process.rb new file mode 100644 index 0000000..01fb293 --- /dev/null +++ b/jni/ruby/lib/rdoc/markup/pre_process.rb @@ -0,0 +1,293 @@ +## +# Handle common directives that can occur in a block of text: +# +# \:include: filename +# +# Directives can be escaped by preceding them with a backslash. +# +# RDoc plugin authors can register additional directives to be handled by +# using RDoc::Markup::PreProcess::register. +# +# Any directive that is not built-in to RDoc (including those registered via +# plugins) will be stored in the metadata hash on the CodeObject the comment +# is attached to. See RDoc::Markup@Directives for the list of built-in +# directives. + +class RDoc::Markup::PreProcess + + ## + # An RDoc::Options instance that will be filled in with overrides from + # directives + + attr_accessor :options + + ## + # Adds a post-process handler for directives. The handler will be called + # with the result RDoc::Comment (or text String) and the code object for the + # comment (if any). + + def self.post_process &block + @post_processors << block + end + + ## + # Registered post-processors + + def self.post_processors + @post_processors + end + + ## + # Registers +directive+ as one handled by RDoc. If a block is given the + # directive will be replaced by the result of the block, otherwise the + # directive will be removed from the processed text. + # + # The block will be called with the directive name and the directive + # parameter: + # + # RDoc::Markup::PreProcess.register 'my-directive' do |directive, param| + # # replace text, etc. + # end + + def self.register directive, &block + @registered[directive] = block + end + + ## + # Registered directives + + def self.registered + @registered + end + + ## + # Clears all registered directives and post-processors + + def self.reset + @post_processors = [] + @registered = {} + end + + reset + + ## + # Creates a new pre-processor for +input_file_name+ that will look for + # included files in +include_path+ + + def initialize(input_file_name, include_path) + @input_file_name = input_file_name + @include_path = include_path + @options = nil + end + + ## + # Look for directives in the given +text+. + # + # Options that we don't handle are yielded. If the block returns false the + # directive is restored to the text. If the block returns nil or no block + # was given the directive is handled according to the registered directives. + # If a String was returned the directive is replaced with the string. + # + # If no matching directive was registered the directive is restored to the + # text. + # + # If +code_object+ is given and the directive is unknown then the + # directive's parameter is set as metadata on the +code_object+. See + # RDoc::CodeObject#metadata for details. + + def handle text, code_object = nil, &block + if RDoc::Comment === text then + comment = text + text = text.text + end + + encoding = text.encoding if defined?(Encoding) + + # regexp helper (square brackets for optional) + # $1 $2 $3 $4 $5 + # [prefix][\]:directive:[spaces][param]newline + text.gsub!(/^([ \t]*(?:#|\/?\*)?[ \t]*)(\\?):(\w+):([ \t]*)(.+)?(\r?\n|$)/) do + # skip something like ':toto::' + next $& if $4.empty? and $5 and $5[0, 1] == ':' + + # skip if escaped + next "#$1:#$3:#$4#$5\n" unless $2.empty? + + # This is not in handle_directive because I didn't want to pass another + # argument into it + if comment and $3 == 'markup' then + next "#{$1.strip}\n" unless $5 + comment.format = $5.downcase + next "#{$1.strip}\n" + end + + handle_directive $1, $3, $5, code_object, encoding, &block + end + + comment = text unless comment + + self.class.post_processors.each do |handler| + handler.call comment, code_object + end + + text + end + + ## + # Performs the actions described by +directive+ and its parameter +param+. + # + # +code_object+ is used for directives that operate on a class or module. + # +prefix+ is used to ensure the replacement for handled directives is + # correct. +encoding+ is used for the <tt>include</tt> directive. + # + # For a list of directives in RDoc see RDoc::Markup. + #-- + # When 1.8.7 support is ditched prefix can be defaulted to '' + + def handle_directive prefix, directive, param, code_object = nil, + encoding = nil + blankline = "#{prefix.strip}\n" + directive = directive.downcase + + case directive + when 'arg', 'args' then + return "#{prefix}:#{directive}: #{param}\n" unless code_object + + code_object.params = param + + blankline + when 'category' then + if RDoc::Context === code_object then + section = code_object.add_section param + code_object.temporary_section = section + end + + blankline # ignore category if we're not on an RDoc::Context + when 'doc' then + return blankline unless code_object + code_object.document_self = true + code_object.force_documentation = true + + blankline + when 'enddoc' then + return blankline unless code_object + code_object.done_documenting = true + + blankline + when 'include' then + filename = param.split.first + include_file filename, prefix, encoding + when 'main' then + @options.main_page = param if @options.respond_to? :main_page + + blankline + when 'nodoc' then + return blankline unless code_object + code_object.document_self = nil # notify nodoc + code_object.document_children = param !~ /all/i + + blankline + when 'notnew', 'not_new', 'not-new' then + return blankline unless RDoc::AnyMethod === code_object + + code_object.dont_rename_initialize = true + + blankline + when 'startdoc' then + return blankline unless code_object + + code_object.start_doc + code_object.force_documentation = true + + blankline + when 'stopdoc' then + return blankline unless code_object + + code_object.stop_doc + + blankline + when 'title' then + @options.default_title = param if @options.respond_to? :default_title= + + blankline + when 'yield', 'yields' then + return blankline unless code_object + # remove parameter &block + code_object.params.sub!(/,?\s*&\w+/, '') if code_object.params + + code_object.block_params = param + + blankline + else + result = yield directive, param if block_given? + + case result + when nil then + code_object.metadata[directive] = param if code_object + + if RDoc::Markup::PreProcess.registered.include? directive then + handler = RDoc::Markup::PreProcess.registered[directive] + result = handler.call directive, param if handler + else + result = "#{prefix}:#{directive}: #{param}\n" + end + when false then + result = "#{prefix}:#{directive}: #{param}\n" + end + + result + end + end + + ## + # Handles the <tt>:include: _filename_</tt> directive. + # + # If the first line of the included file starts with '#', and contains + # an encoding information in the form 'coding:' or 'coding=', it is + # removed. + # + # If all lines in the included file start with a '#', this leading '#' + # is removed before inclusion. The included content is indented like + # the <tt>:include:</tt> directive. + #-- + # so all content will be verbatim because of the likely space after '#'? + # TODO shift left the whole file content in that case + # TODO comment stop/start #-- and #++ in included file must be processed here + + def include_file name, indent, encoding + full_name = find_include_file name + + unless full_name then + warn "Couldn't find file to include '#{name}' from #{@input_file_name}" + return '' + end + + content = RDoc::Encoding.read_file full_name, encoding, true + + # strip magic comment + content = content.sub(/\A# .*coding[=:].*$/, '').lstrip + + # strip leading '#'s, but only if all lines start with them + if content =~ /^[^#]/ then + content.gsub(/^/, indent) + else + content.gsub(/^#?/, indent) + end + end + + ## + # Look for the given file in the directory containing the current file, + # and then in each of the directories specified in the RDOC_INCLUDE path + + def find_include_file(name) + to_search = [File.dirname(@input_file_name)].concat @include_path + to_search.each do |dir| + full_name = File.join(dir, name) + stat = File.stat(full_name) rescue next + return full_name if stat.readable? + end + nil + end + +end + diff --git a/jni/ruby/lib/rdoc/markup/raw.rb b/jni/ruby/lib/rdoc/markup/raw.rb new file mode 100644 index 0000000..e11e8ef --- /dev/null +++ b/jni/ruby/lib/rdoc/markup/raw.rb @@ -0,0 +1,69 @@ +## +# A section of text that is added to the output document as-is + +class RDoc::Markup::Raw + + ## + # The component parts of the list + + attr_reader :parts + + ## + # Creates a new Raw containing +parts+ + + def initialize *parts + @parts = [] + @parts.concat parts + end + + ## + # Appends +text+ + + def << text + @parts << text + end + + def == other # :nodoc: + self.class == other.class and @parts == other.parts + end + + ## + # Calls #accept_raw+ on +visitor+ + + def accept visitor + visitor.accept_raw self + end + + ## + # Appends +other+'s parts + + def merge other + @parts.concat other.parts + end + + def pretty_print q # :nodoc: + self.class.name =~ /.*::(\w{1,4})/i + + q.group 2, "[#{$1.downcase}: ", ']' do + q.seplist @parts do |part| + q.pp part + end + end + end + + ## + # Appends +texts+ onto this Paragraph + + def push *texts + self.parts.concat texts + end + + ## + # The raw text + + def text + @parts.join ' ' + end + +end + diff --git a/jni/ruby/lib/rdoc/markup/rule.rb b/jni/ruby/lib/rdoc/markup/rule.rb new file mode 100644 index 0000000..b778f2b --- /dev/null +++ b/jni/ruby/lib/rdoc/markup/rule.rb @@ -0,0 +1,20 @@ +## +# A horizontal rule with a weight + +class RDoc::Markup::Rule < Struct.new :weight + + ## + # Calls #accept_rule on +visitor+ + + def accept visitor + visitor.accept_rule self + end + + def pretty_print q # :nodoc: + q.group 2, '[rule:', ']' do + q.pp weight + end + end + +end + diff --git a/jni/ruby/lib/rdoc/markup/special.rb b/jni/ruby/lib/rdoc/markup/special.rb new file mode 100644 index 0000000..1c0fc03 --- /dev/null +++ b/jni/ruby/lib/rdoc/markup/special.rb @@ -0,0 +1,40 @@ +## +# Hold details of a special sequence + +class RDoc::Markup::Special + + ## + # Special type + + attr_reader :type + + ## + # Special text + + attr_accessor :text + + ## + # Creates a new special sequence of +type+ with +text+ + + def initialize(type, text) + @type, @text = type, text + end + + ## + # Specials are equal when the have the same text and type + + def ==(o) + self.text == o.text && self.type == o.type + end + + def inspect # :nodoc: + "#<RDoc::Markup::Special:0x%x @type=%p, @text=%p>" % [ + object_id, @type, text.dump] + end + + def to_s # :nodoc: + "Special: type=#{type} text=#{text.dump}" + end + +end + diff --git a/jni/ruby/lib/rdoc/markup/text_formatter_test_case.rb b/jni/ruby/lib/rdoc/markup/text_formatter_test_case.rb new file mode 100644 index 0000000..4abf425 --- /dev/null +++ b/jni/ruby/lib/rdoc/markup/text_formatter_test_case.rb @@ -0,0 +1,114 @@ +## +# Test case for creating new plain-text RDoc::Markup formatters. See also +# RDoc::Markup::FormatterTestCase +# +# See test_rdoc_markup_to_rdoc.rb for a complete example. +# +# Example: +# +# class TestRDocMarkupToNewTextFormat < RDoc::Markup::TextFormatterTestCase +# +# add_visitor_tests +# add_text_tests +# +# def setup +# super +# +# @to = RDoc::Markup::ToNewTextFormat.new +# end +# +# def accept_blank_line +# assert_equal :junk, @to.res.join +# end +# +# # ... +# +# end + +class RDoc::Markup::TextFormatterTestCase < RDoc::Markup::FormatterTestCase + + ## + # Adds test cases to the calling TestCase. + + def self.add_text_tests + self.class_eval do + + ## + # Test case that calls <tt>@to.accept_heading</tt> + + def test_accept_heading_indent + @to.start_accepting + @to.indent = 3 + @to.accept_heading @RM::Heading.new(1, 'Hello') + + accept_heading_indent + end + + ## + # Test case that calls <tt>@to.accept_rule</tt> + + def test_accept_rule_indent + @to.start_accepting + @to.indent = 3 + @to.accept_rule @RM::Rule.new(1) + + accept_rule_indent + end + + ## + # Test case that calls <tt>@to.accept_verbatim</tt> + + def test_accept_verbatim_indent + @to.start_accepting + @to.indent = 2 + @to.accept_verbatim @RM::Verbatim.new("hi\n", " world\n") + + accept_verbatim_indent + end + + ## + # Test case that calls <tt>@to.accept_verbatim</tt> with a big indent + + def test_accept_verbatim_big_indent + @to.start_accepting + @to.indent = 2 + @to.accept_verbatim @RM::Verbatim.new("hi\n", "world\n") + + accept_verbatim_big_indent + end + + ## + # Test case that calls <tt>@to.accept_paragraph</tt> with an indent + + def test_accept_paragraph_indent + @to.start_accepting + @to.indent = 3 + @to.accept_paragraph @RM::Paragraph.new(('words ' * 30).strip) + + accept_paragraph_indent + end + + ## + # Test case that calls <tt>@to.accept_paragraph</tt> with a long line + + def test_accept_paragraph_wrap + @to.start_accepting + @to.accept_paragraph @RM::Paragraph.new(('words ' * 30).strip) + + accept_paragraph_wrap + end + + ## + # Test case that calls <tt>@to.attributes</tt> with an escaped + # cross-reference. If this test doesn't pass something may be very + # wrong. + + def test_attributes + assert_equal 'Dog', @to.attributes("\\Dog") + end + + end + end + +end + diff --git a/jni/ruby/lib/rdoc/markup/to_ansi.rb b/jni/ruby/lib/rdoc/markup/to_ansi.rb new file mode 100644 index 0000000..4d439ce --- /dev/null +++ b/jni/ruby/lib/rdoc/markup/to_ansi.rb @@ -0,0 +1,93 @@ +## +# Outputs RDoc markup with vibrant ANSI color! + +class RDoc::Markup::ToAnsi < RDoc::Markup::ToRdoc + + ## + # Creates a new ToAnsi visitor that is ready to output vibrant ANSI color! + + def initialize markup = nil + super + + @headings.clear + @headings[1] = ["\e[1;32m", "\e[m"] # bold + @headings[2] = ["\e[4;32m", "\e[m"] # underline + @headings[3] = ["\e[32m", "\e[m"] # just green + end + + ## + # Maps attributes to ANSI sequences + + def init_tags + add_tag :BOLD, "\e[1m", "\e[m" + add_tag :TT, "\e[7m", "\e[m" + add_tag :EM, "\e[4m", "\e[m" + end + + ## + # Overrides indent width to ensure output lines up correctly. + + def accept_list_item_end list_item + width = case @list_type.last + when :BULLET then + 2 + when :NOTE, :LABEL then + if @prefix then + @res << @prefix.strip + @prefix = nil + end + + @res << "\n" unless res.length == 1 + 2 + else + bullet = @list_index.last.to_s + @list_index[-1] = @list_index.last.succ + bullet.length + 2 + end + + @indent -= width + end + + ## + # Adds coloring to note and label list items + + def accept_list_item_start list_item + bullet = case @list_type.last + when :BULLET then + '*' + when :NOTE, :LABEL then + labels = Array(list_item.label).map do |label| + attributes(label).strip + end.join "\n" + + labels << ":\n" unless labels.empty? + + labels + else + @list_index.last.to_s + '.' + end + + case @list_type.last + when :NOTE, :LABEL then + @indent += 2 + @prefix = bullet + (' ' * @indent) + else + @prefix = (' ' * @indent) + bullet.ljust(bullet.length + 1) + + width = bullet.gsub(/\e\[[\d;]*m/, '').length + 1 + + @indent += width + end + end + + ## + # Starts accepting with a reset screen + + def start_accepting + super + + @res = ["\e[0m"] + end + +end + diff --git a/jni/ruby/lib/rdoc/markup/to_bs.rb b/jni/ruby/lib/rdoc/markup/to_bs.rb new file mode 100644 index 0000000..10c3185 --- /dev/null +++ b/jni/ruby/lib/rdoc/markup/to_bs.rb @@ -0,0 +1,78 @@ +## +# Outputs RDoc markup with hot backspace action! You will probably need a +# pager to use this output format. +# +# This formatter won't work on 1.8.6 because it lacks String#chars. + +class RDoc::Markup::ToBs < RDoc::Markup::ToRdoc + + ## + # Returns a new ToBs that is ready for hot backspace action! + + def initialize markup = nil + super + + @in_b = false + @in_em = false + end + + ## + # Sets a flag that is picked up by #annotate to do the right thing in + # #convert_string + + def init_tags + add_tag :BOLD, '+b', '-b' + add_tag :EM, '+_', '-_' + add_tag :TT, '' , '' # we need in_tt information maintained + end + + ## + # Makes heading text bold. + + def accept_heading heading + use_prefix or @res << ' ' * @indent + @res << @headings[heading.level][0] + @in_b = true + @res << attributes(heading.text) + @in_b = false + @res << @headings[heading.level][1] + @res << "\n" + end + + ## + # Turns on or off special handling for +convert_string+ + + def annotate tag + case tag + when '+b' then @in_b = true + when '-b' then @in_b = false + when '+_' then @in_em = true + when '-_' then @in_em = false + end + '' + end + + ## + # Calls convert_string on the result of convert_special + + def convert_special special + convert_string super + end + + ## + # Adds bold or underline mixed with backspaces + + def convert_string string + return string unless string.respond_to? :chars # your ruby is lame + return string unless @in_b or @in_em + chars = if @in_b then + string.chars.map do |char| "#{char}\b#{char}" end + elsif @in_em then + string.chars.map do |char| "_\b#{char}" end + end + + chars.join + end + +end + diff --git a/jni/ruby/lib/rdoc/markup/to_html.rb b/jni/ruby/lib/rdoc/markup/to_html.rb new file mode 100644 index 0000000..2b1216e --- /dev/null +++ b/jni/ruby/lib/rdoc/markup/to_html.rb @@ -0,0 +1,398 @@ +require 'cgi' + +## +# Outputs RDoc markup as HTML. + +class RDoc::Markup::ToHtml < RDoc::Markup::Formatter + + include RDoc::Text + + # :section: Utilities + + ## + # Maps RDoc::Markup::Parser::LIST_TOKENS types to HTML tags + + LIST_TYPE_TO_HTML = { + :BULLET => ['<ul>', '</ul>'], + :LABEL => ['<dl class="rdoc-list label-list">', '</dl>'], + :LALPHA => ['<ol style="list-style-type: lower-alpha">', '</ol>'], + :NOTE => ['<dl class="rdoc-list note-list">', '</dl>'], + :NUMBER => ['<ol>', '</ol>'], + :UALPHA => ['<ol style="list-style-type: upper-alpha">', '</ol>'], + } + + attr_reader :res # :nodoc: + attr_reader :in_list_entry # :nodoc: + attr_reader :list # :nodoc: + + ## + # The RDoc::CodeObject HTML is being generated for. This is used to + # generate namespaced URI fragments + + attr_accessor :code_object + + ## + # Path to this document for relative links + + attr_accessor :from_path + + # :section: + + ## + # Creates a new formatter that will output HTML + + def initialize options, markup = nil + super + + @code_object = nil + @from_path = '' + @in_list_entry = nil + @list = nil + @th = nil + @hard_break = "<br>\n" + + # external links + @markup.add_special(/(?:link:|https?:|mailto:|ftp:|irc:|www\.)\S+\w/, + :HYPERLINK) + + add_special_RDOCLINK + add_special_TIDYLINK + + init_tags + end + + # :section: Special Handling + # + # These methods handle special markup added by RDoc::Markup#add_special. + + def handle_RDOCLINK url # :nodoc: + case url + when /^rdoc-ref:/ + $' + when /^rdoc-label:/ + text = $' + + text = case text + when /\Alabel-/ then $' + when /\Afootmark-/ then $' + when /\Afoottext-/ then $' + else text + end + + gen_url url, text + when /^rdoc-image:/ + "<img src=\"#{$'}\">" + else + url =~ /\Ardoc-[a-z]+:/ + + $' + end + end + + ## + # +special+ is a <code><br></code> + + def handle_special_HARD_BREAK special + '<br>' + end + + ## + # +special+ is a potential link. The following schemes are handled: + # + # <tt>mailto:</tt>:: + # Inserted as-is. + # <tt>http:</tt>:: + # Links are checked to see if they reference an image. If so, that image + # gets inserted using an <tt><img></tt> tag. Otherwise a conventional + # <tt><a href></tt> is used. + # <tt>link:</tt>:: + # Reference to a local file relative to the output directory. + + def handle_special_HYPERLINK(special) + url = special.text + + gen_url url, url + end + + ## + # +special+ is an rdoc-schemed link that will be converted into a hyperlink. + # + # For the +rdoc-ref+ scheme the named reference will be returned without + # creating a link. + # + # For the +rdoc-label+ scheme the footnote and label prefixes are stripped + # when creating a link. All other contents will be linked verbatim. + + def handle_special_RDOCLINK special + handle_RDOCLINK special.text + end + + ## + # This +special+ is a link where the label is different from the URL + # <tt>label[url]</tt> or <tt>{long label}[url]</tt> + + def handle_special_TIDYLINK(special) + text = special.text + + return text unless + text =~ /^\{(.*)\}\[(.*?)\]$/ or text =~ /^(\S+)\[(.*?)\]$/ + + label = $1 + url = $2 + + label = handle_RDOCLINK label if /^rdoc-image:/ =~ label + + gen_url url, label + end + + # :section: Visitor + # + # These methods implement the HTML visitor. + + ## + # Prepares the visitor for HTML generation + + def start_accepting + @res = [] + @in_list_entry = [] + @list = [] + end + + ## + # Returns the generated output + + def end_accepting + @res.join + end + + ## + # Adds +block_quote+ to the output + + def accept_block_quote block_quote + @res << "\n<blockquote>" + + block_quote.parts.each do |part| + part.accept self + end + + @res << "</blockquote>\n" + end + + ## + # Adds +paragraph+ to the output + + def accept_paragraph paragraph + @res << "\n<p>" + text = paragraph.text @hard_break + text = text.gsub(/\r?\n/, ' ') + @res << wrap(to_html(text)) + @res << "</p>\n" + end + + ## + # Adds +verbatim+ to the output + + def accept_verbatim verbatim + text = verbatim.text.rstrip + + klass = nil + + content = if verbatim.ruby? or parseable? text then + begin + tokens = RDoc::RubyLex.tokenize text, @options + klass = ' class="ruby"' + + RDoc::TokenStream.to_html tokens + rescue RDoc::RubyLex::Error + CGI.escapeHTML text + end + else + CGI.escapeHTML text + end + + if @options.pipe then + @res << "\n<pre><code>#{CGI.escapeHTML text}</code></pre>\n" + else + @res << "\n<pre#{klass}>#{content}</pre>\n" + end + end + + ## + # Adds +rule+ to the output + + def accept_rule rule + @res << "<hr>\n" + end + + ## + # Prepares the visitor for consuming +list+ + + def accept_list_start(list) + @list << list.type + @res << html_list_name(list.type, true) + @in_list_entry.push false + end + + ## + # Finishes consumption of +list+ + + def accept_list_end(list) + @list.pop + if tag = @in_list_entry.pop + @res << tag + end + @res << html_list_name(list.type, false) << "\n" + end + + ## + # Prepares the visitor for consuming +list_item+ + + def accept_list_item_start(list_item) + if tag = @in_list_entry.last + @res << tag + end + + @res << list_item_start(list_item, @list.last) + end + + ## + # Finishes consumption of +list_item+ + + def accept_list_item_end(list_item) + @in_list_entry[-1] = list_end_for(@list.last) + end + + ## + # Adds +blank_line+ to the output + + def accept_blank_line(blank_line) + # @res << annotate("<p />") << "\n" + end + + ## + # Adds +heading+ to the output. The headings greater than 6 are trimmed to + # level 6. + + def accept_heading heading + level = [6, heading.level].min + + label = heading.label @code_object + + @res << if @options.output_decoration + "\n<h#{level} id=\"#{label}\">" + else + "\n<h#{level}>" + end + @res << to_html(heading.text) + unless @options.pipe then + @res << "<span><a href=\"##{label}\">¶</a>" + @res << " <a href=\"#top\">↑</a></span>" + end + @res << "</h#{level}>\n" + end + + ## + # Adds +raw+ to the output + + def accept_raw raw + @res << raw.parts.join("\n") + end + + # :section: Utilities + + ## + # CGI-escapes +text+ + + def convert_string(text) + CGI.escapeHTML text + end + + ## + # Generate a link to +url+ with content +text+. Handles the special cases + # for img: and link: described under handle_special_HYPERLINK + + def gen_url url, text + scheme, url, id = parse_url url + + if %w[http https link].include?(scheme) and + url =~ /\.(gif|png|jpg|jpeg|bmp)$/ then + "<img src=\"#{url}\" />" + else + text = text.sub %r%^#{scheme}:/*%i, '' + text = text.sub %r%^[*\^](\d+)$%, '\1' + + link = "<a#{id} href=\"#{url}\">#{text}</a>" + + link = "<sup>#{link}</sup>" if /"foot/ =~ id + + link + end + end + + ## + # Determines the HTML list element for +list_type+ and +open_tag+ + + def html_list_name(list_type, open_tag) + tags = LIST_TYPE_TO_HTML[list_type] + raise RDoc::Error, "Invalid list type: #{list_type.inspect}" unless tags + tags[open_tag ? 0 : 1] + end + + ## + # Maps attributes to HTML tags + + def init_tags + add_tag :BOLD, "<strong>", "</strong>" + add_tag :TT, "<code>", "</code>" + add_tag :EM, "<em>", "</em>" + end + + ## + # Returns the HTML tag for +list_type+, possible using a label from + # +list_item+ + + def list_item_start(list_item, list_type) + case list_type + when :BULLET, :LALPHA, :NUMBER, :UALPHA then + "<li>" + when :LABEL, :NOTE then + Array(list_item.label).map do |label| + "<dt>#{to_html label}\n" + end.join << "<dd>" + else + raise RDoc::Error, "Invalid list type: #{list_type.inspect}" + end + end + + ## + # Returns the HTML end-tag for +list_type+ + + def list_end_for(list_type) + case list_type + when :BULLET, :LALPHA, :NUMBER, :UALPHA then + "</li>" + when :LABEL, :NOTE then + "</dd>" + else + raise RDoc::Error, "Invalid list type: #{list_type.inspect}" + end + end + + ## + # Returns true if text is valid ruby syntax + + def parseable? text + eval("BEGIN {return true}\n#{text}") + rescue SyntaxError + false + end + + ## + # Converts +item+ to HTML using RDoc::Text#to_html + + def to_html item + super convert_flow @am.flow item + end + +end + diff --git a/jni/ruby/lib/rdoc/markup/to_html_crossref.rb b/jni/ruby/lib/rdoc/markup/to_html_crossref.rb new file mode 100644 index 0000000..d27e0ab --- /dev/null +++ b/jni/ruby/lib/rdoc/markup/to_html_crossref.rb @@ -0,0 +1,160 @@ +## +# Subclass of the RDoc::Markup::ToHtml class that supports looking up method +# names, classes, etc to create links. RDoc::CrossReference is used to +# generate those links based on the current context. + +class RDoc::Markup::ToHtmlCrossref < RDoc::Markup::ToHtml + + # :stopdoc: + ALL_CROSSREF_REGEXP = RDoc::CrossReference::ALL_CROSSREF_REGEXP + CLASS_REGEXP_STR = RDoc::CrossReference::CLASS_REGEXP_STR + CROSSREF_REGEXP = RDoc::CrossReference::CROSSREF_REGEXP + METHOD_REGEXP_STR = RDoc::CrossReference::METHOD_REGEXP_STR + # :startdoc: + + ## + # RDoc::CodeObject for generating references + + attr_accessor :context + + ## + # Should we show '#' characters on method references? + + attr_accessor :show_hash + + ## + # Creates a new crossref resolver that generates links relative to +context+ + # which lives at +from_path+ in the generated files. '#' characters on + # references are removed unless +show_hash+ is true. Only method names + # preceded by '#' or '::' are linked, unless +hyperlink_all+ is true. + + def initialize(options, from_path, context, markup = nil) + raise ArgumentError, 'from_path cannot be nil' if from_path.nil? + + super options, markup + + @context = context + @from_path = from_path + @hyperlink_all = @options.hyperlink_all + @show_hash = @options.show_hash + + crossref_re = @hyperlink_all ? ALL_CROSSREF_REGEXP : CROSSREF_REGEXP + @markup.add_special crossref_re, :CROSSREF + + @cross_reference = RDoc::CrossReference.new @context + end + + ## + # Creates a link to the reference +name+ if the name exists. If +text+ is + # given it is used as the link text, otherwise +name+ is used. + + def cross_reference name, text = nil + lookup = name + + name = name[1..-1] unless @show_hash if name[0, 1] == '#' + + name = "#{CGI.unescape $'} at #{$1}" if name =~ /(.*[^#:])@/ + + text = name unless text + + link lookup, text + end + + ## + # We're invoked when any text matches the CROSSREF pattern. If we find the + # corresponding reference, generate a link. If the name we're looking for + # contains no punctuation, we look for it up the module/class chain. For + # example, ToHtml is found, even without the <tt>RDoc::Markup::</tt> prefix, + # because we look for it in module Markup first. + + def handle_special_CROSSREF(special) + name = special.text + + return name if name =~ /@[\w-]+\.[\w-]/ # labels that look like emails + + unless @hyperlink_all then + # This ensures that words entirely consisting of lowercase letters will + # not have cross-references generated (to suppress lots of erroneous + # cross-references to "new" in text, for instance) + return name if name =~ /\A[a-z]*\z/ + end + + cross_reference name + end + + ## + # Handles <tt>rdoc-ref:</tt> scheme links and allows RDoc::Markup::ToHtml to + # handle other schemes. + + def handle_special_HYPERLINK special + return cross_reference $' if special.text =~ /\Ardoc-ref:/ + + super + end + + ## + # +special+ is an rdoc-schemed link that will be converted into a hyperlink. + # For the rdoc-ref scheme the cross-reference will be looked up and the + # given name will be used. + # + # All other contents are handled by + # {the superclass}[rdoc-ref:RDoc::Markup::ToHtml#handle_special_RDOCLINK] + + def handle_special_RDOCLINK special + url = special.text + + case url + when /\Ardoc-ref:/ then + cross_reference $' + else + super + end + end + + ## + # Generates links for <tt>rdoc-ref:</tt> scheme URLs and allows + # RDoc::Markup::ToHtml to handle other schemes. + + def gen_url url, text + return super unless url =~ /\Ardoc-ref:/ + + cross_reference $', text + end + + ## + # Creates an HTML link to +name+ with the given +text+. + + def link name, text + original_name = name + + if name =~ /(.*[^#:])@/ then + name = $1 + label = $' + end + + ref = @cross_reference.resolve name, text + + text = ref.output_name @context if + RDoc::MethodAttr === ref and text == original_name + + case ref + when String then + ref + else + path = ref.as_href @from_path + + if path =~ /#/ then + path << "-label-#{label}" + elsif ref.sections and + ref.sections.any? { |section| label == section.title } then + path << "##{label}" + else + path << "#label-#{label}" + end if label + + "<a href=\"#{path}\">#{text}</a>" + end + end + +end + diff --git a/jni/ruby/lib/rdoc/markup/to_html_snippet.rb b/jni/ruby/lib/rdoc/markup/to_html_snippet.rb new file mode 100644 index 0000000..4ad0a9a --- /dev/null +++ b/jni/ruby/lib/rdoc/markup/to_html_snippet.rb @@ -0,0 +1,284 @@ +## +# Outputs RDoc markup as paragraphs with inline markup only. + +class RDoc::Markup::ToHtmlSnippet < RDoc::Markup::ToHtml + + ## + # After this many characters the input will be cut off. + + attr_reader :character_limit + + ## + # The number of characters seen so far. + + attr_reader :characters # :nodoc: + + ## + # The attribute bitmask + + attr_reader :mask + + ## + # After this many paragraphs the input will be cut off. + + attr_reader :paragraph_limit + + ## + # Count of paragraphs found + + attr_reader :paragraphs + + ## + # Creates a new ToHtmlSnippet formatter that will cut off the input on the + # next word boundary after the given number of +characters+ or +paragraphs+ + # of text have been encountered. + + def initialize options, characters = 100, paragraphs = 3, markup = nil + super options, markup + + @character_limit = characters + @paragraph_limit = paragraphs + + @characters = 0 + @mask = 0 + @paragraphs = 0 + + @markup.add_special RDoc::CrossReference::CROSSREF_REGEXP, :CROSSREF + end + + ## + # Adds +heading+ to the output as a paragraph + + def accept_heading heading + @res << "<p>#{to_html heading.text}\n" + + add_paragraph + end + + ## + # Raw sections are untrusted and ignored + + alias accept_raw ignore + + ## + # Rules are ignored + + alias accept_rule ignore + + def accept_paragraph paragraph + para = @in_list_entry.last || "<p>" + + text = paragraph.text @hard_break + + @res << "#{para}#{wrap to_html text}\n" + + add_paragraph + end + + ## + # Finishes consumption of +list_item+ + + def accept_list_item_end list_item + end + + ## + # Prepares the visitor for consuming +list_item+ + + def accept_list_item_start list_item + @res << list_item_start(list_item, @list.last) + end + + ## + # Prepares the visitor for consuming +list+ + + def accept_list_start list + @list << list.type + @res << html_list_name(list.type, true) + @in_list_entry.push '' + end + + ## + # Adds +verbatim+ to the output + + def accept_verbatim verbatim + throw :done if @characters >= @character_limit + input = verbatim.text.rstrip + + text = truncate input + text << ' ...' unless text == input + + super RDoc::Markup::Verbatim.new text + + add_paragraph + end + + ## + # Prepares the visitor for HTML snippet generation + + def start_accepting + super + + @characters = 0 + end + + ## + # Removes escaping from the cross-references in +special+ + + def handle_special_CROSSREF special + special.text.sub(/\A\\/, '') + end + + ## + # +special+ is a <code><br></code> + + def handle_special_HARD_BREAK special + @characters -= 4 + '<br>' + end + + ## + # Lists are paragraphs, but notes and labels have a separator + + def list_item_start list_item, list_type + throw :done if @characters >= @character_limit + + case list_type + when :BULLET, :LALPHA, :NUMBER, :UALPHA then + "<p>" + when :LABEL, :NOTE then + labels = Array(list_item.label).map do |label| + to_html label + end.join ', ' + + labels << " — " unless labels.empty? + + start = "<p>#{labels}" + @characters += 1 # try to include the label + start + else + raise RDoc::Error, "Invalid list type: #{list_type.inspect}" + end + end + + ## + # Returns just the text of +link+, +url+ is only used to determine the link + # type. + + def gen_url url, text + if url =~ /^rdoc-label:([^:]*)(?::(.*))?/ then + type = "link" + elsif url =~ /([A-Za-z]+):(.*)/ then + type = $1 + else + type = "http" + end + + if (type == "http" or type == "https" or type == "link") and + url =~ /\.(gif|png|jpg|jpeg|bmp)$/ then + '' + else + text.sub(%r%^#{type}:/*%, '') + end + end + + ## + # In snippets, there are no lists + + def html_list_name list_type, open_tag + '' + end + + ## + # Throws +:done+ when paragraph_limit paragraphs have been encountered + + def add_paragraph + @paragraphs += 1 + + throw :done if @paragraphs >= @paragraph_limit + end + + ## + # Marks up +content+ + + def convert content + catch :done do + return super + end + + end_accepting + end + + ## + # Converts flow items +flow+ + + def convert_flow flow + throw :done if @characters >= @character_limit + + res = [] + @mask = 0 + + flow.each do |item| + case item + when RDoc::Markup::AttrChanger then + off_tags res, item + on_tags res, item + when String then + text = convert_string item + res << truncate(text) + when RDoc::Markup::Special then + text = convert_special item + res << truncate(text) + else + raise "Unknown flow element: #{item.inspect}" + end + + if @characters >= @character_limit then + off_tags res, RDoc::Markup::AttrChanger.new(0, @mask) + break + end + end + + res << ' ...' if @characters >= @character_limit + + res.join + end + + ## + # Maintains a bitmask to allow HTML elements to be closed properly. See + # RDoc::Markup::Formatter. + + def on_tags res, item + @mask ^= item.turn_on + + super + end + + ## + # Maintains a bitmask to allow HTML elements to be closed properly. See + # RDoc::Markup::Formatter. + + def off_tags res, item + @mask ^= item.turn_off + + super + end + + ## + # Truncates +text+ at the end of the first word after the character_limit. + + def truncate text + length = text.length + characters = @characters + @characters += length + + return text if @characters < @character_limit + + remaining = @character_limit - characters + + text =~ /\A(.{#{remaining},}?)(\s|$)/m # TODO word-break instead of \s? + + $1 + end + +end + diff --git a/jni/ruby/lib/rdoc/markup/to_joined_paragraph.rb b/jni/ruby/lib/rdoc/markup/to_joined_paragraph.rb new file mode 100644 index 0000000..8358410 --- /dev/null +++ b/jni/ruby/lib/rdoc/markup/to_joined_paragraph.rb @@ -0,0 +1,71 @@ +## +# Joins the parts of an RDoc::Markup::Paragraph into a single String. +# +# This allows for easier maintenance and testing of Markdown support. +# +# This formatter only works on Paragraph instances. Attempting to process +# other markup syntax items will not work. + +class RDoc::Markup::ToJoinedParagraph < RDoc::Markup::Formatter + + def initialize # :nodoc: + super nil + end + + def start_accepting # :nodoc: + end + + def end_accepting # :nodoc: + end + + ## + # Converts the parts of +paragraph+ to a single entry. + + def accept_paragraph paragraph + parts = [] + string = false + + paragraph.parts.each do |part| + if String === part then + if string then + string << part + else + parts << part + string = part + end + else + parts << part + string = false + end + end + + parts = parts.map do |part| + if String === part then + part.rstrip + else + part + end + end + + # TODO use Enumerable#chunk when Ruby 1.8 support is dropped + #parts = paragraph.parts.chunk do |part| + # String === part + #end.map do |string, chunk| + # string ? chunk.join.rstrip : chunk + #end.flatten + + paragraph.parts.replace parts + end + + alias accept_block_quote ignore + alias accept_heading ignore + alias accept_list_end ignore + alias accept_list_item_end ignore + alias accept_list_item_start ignore + alias accept_list_start ignore + alias accept_raw ignore + alias accept_rule ignore + alias accept_verbatim ignore + +end + diff --git a/jni/ruby/lib/rdoc/markup/to_label.rb b/jni/ruby/lib/rdoc/markup/to_label.rb new file mode 100644 index 0000000..6fbe4a3 --- /dev/null +++ b/jni/ruby/lib/rdoc/markup/to_label.rb @@ -0,0 +1,74 @@ +require 'cgi' + +## +# Creates HTML-safe labels suitable for use in id attributes. Tidylinks are +# converted to their link part and cross-reference links have the suppression +# marks removed (\\SomeClass is converted to SomeClass). + +class RDoc::Markup::ToLabel < RDoc::Markup::Formatter + + attr_reader :res # :nodoc: + + ## + # Creates a new formatter that will output HTML-safe labels + + def initialize markup = nil + super nil, markup + + @markup.add_special RDoc::CrossReference::CROSSREF_REGEXP, :CROSSREF + @markup.add_special(/(((\{.*?\})|\b\S+?)\[\S+?\])/, :TIDYLINK) + + add_tag :BOLD, '', '' + add_tag :TT, '', '' + add_tag :EM, '', '' + + @res = [] + end + + ## + # Converts +text+ to an HTML-safe label + + def convert text + label = convert_flow @am.flow text + + CGI.escape(label).gsub('%', '-').sub(/^-/, '') + end + + ## + # Converts the CROSSREF +special+ to plain text, removing the suppression + # marker, if any + + def handle_special_CROSSREF special + text = special.text + + text.sub(/^\\/, '') + end + + ## + # Converts the TIDYLINK +special+ to just the text part + + def handle_special_TIDYLINK special + text = special.text + + return text unless text =~ /\{(.*?)\}\[(.*?)\]/ or text =~ /(\S+)\[(.*?)\]/ + + $1 + end + + alias accept_blank_line ignore + alias accept_block_quote ignore + alias accept_heading ignore + alias accept_list_end ignore + alias accept_list_item_end ignore + alias accept_list_item_start ignore + alias accept_list_start ignore + alias accept_paragraph ignore + alias accept_raw ignore + alias accept_rule ignore + alias accept_verbatim ignore + alias end_accepting ignore + alias handle_special_HARD_BREAK ignore + alias start_accepting ignore + +end + diff --git a/jni/ruby/lib/rdoc/markup/to_markdown.rb b/jni/ruby/lib/rdoc/markup/to_markdown.rb new file mode 100644 index 0000000..d4b15bf --- /dev/null +++ b/jni/ruby/lib/rdoc/markup/to_markdown.rb @@ -0,0 +1,191 @@ +# :markup: markdown + +## +# Outputs parsed markup as Markdown + +class RDoc::Markup::ToMarkdown < RDoc::Markup::ToRdoc + + ## + # Creates a new formatter that will output Markdown format text + + def initialize markup = nil + super + + @headings[1] = ['# ', ''] + @headings[2] = ['## ', ''] + @headings[3] = ['### ', ''] + @headings[4] = ['#### ', ''] + @headings[5] = ['##### ', ''] + @headings[6] = ['###### ', ''] + + add_special_RDOCLINK + add_special_TIDYLINK + + @hard_break = " \n" + end + + ## + # Maps attributes to HTML sequences + + def init_tags + add_tag :BOLD, '**', '**' + add_tag :EM, '*', '*' + add_tag :TT, '`', '`' + end + + ## + # Adds a newline to the output + + def handle_special_HARD_BREAK special + " \n" + end + + ## + # Finishes consumption of `list` + + def accept_list_end list + @res << "\n" + + super + end + + ## + # Finishes consumption of `list_item` + + def accept_list_item_end list_item + width = case @list_type.last + when :BULLET then + 4 + when :NOTE, :LABEL then + use_prefix + + 4 + else + @list_index[-1] = @list_index.last.succ + 4 + end + + @indent -= width + end + + ## + # Prepares the visitor for consuming `list_item` + + def accept_list_item_start list_item + type = @list_type.last + + case type + when :NOTE, :LABEL then + bullets = Array(list_item.label).map do |label| + attributes(label).strip + end.join "\n" + + bullets << "\n:" + + @prefix = ' ' * @indent + @indent += 4 + @prefix << bullets + (' ' * (@indent - 1)) + else + bullet = type == :BULLET ? '*' : @list_index.last.to_s + '.' + @prefix = (' ' * @indent) + bullet.ljust(4) + + @indent += 4 + end + end + + ## + # Prepares the visitor for consuming `list` + + def accept_list_start list + case list.type + when :BULLET, :LABEL, :NOTE then + @list_index << nil + when :LALPHA, :NUMBER, :UALPHA then + @list_index << 1 + else + raise RDoc::Error, "invalid list type #{list.type}" + end + + @list_width << 4 + @list_type << list.type + end + + ## + # Adds `rule` to the output + + def accept_rule rule + use_prefix or @res << ' ' * @indent + @res << '-' * 3 + @res << "\n" + end + + ## + # Outputs `verbatim` indented 4 columns + + def accept_verbatim verbatim + indent = ' ' * (@indent + 4) + + verbatim.parts.each do |part| + @res << indent unless part == "\n" + @res << part + end + + @res << "\n" unless @res =~ /\n\z/ + end + + ## + # Creates a Markdown-style URL from +url+ with +text+. + + def gen_url url, text + scheme, url, = parse_url url + + "[#{text.sub(%r{^#{scheme}:/*}i, '')}](#{url})" + end + + ## + # Handles <tt>rdoc-</tt> type links for footnotes. + + def handle_rdoc_link url + case url + when /^rdoc-ref:/ then + $' + when /^rdoc-label:footmark-(\d+)/ then + "[^#{$1}]:" + when /^rdoc-label:foottext-(\d+)/ then + "[^#{$1}]" + when /^rdoc-label:label-/ then + gen_url url, $' + when /^rdoc-image:/ then + "![](#{$'})" + when /^rdoc-[a-z]+:/ then + $' + end + end + + ## + # Converts the RDoc markup tidylink into a Markdown.style link. + + def handle_special_TIDYLINK special + text = special.text + + return text unless text =~ /\{(.*?)\}\[(.*?)\]/ or text =~ /(\S+)\[(.*?)\]/ + + label = $1 + url = $2 + + if url =~ /^rdoc-label:foot/ then + handle_rdoc_link url + else + gen_url url, label + end + end + + ## + # Converts the rdoc-...: links into a Markdown.style links. + + def handle_special_RDOCLINK special + handle_rdoc_link special.text + end + +end + diff --git a/jni/ruby/lib/rdoc/markup/to_rdoc.rb b/jni/ruby/lib/rdoc/markup/to_rdoc.rb new file mode 100644 index 0000000..f16b4ed --- /dev/null +++ b/jni/ruby/lib/rdoc/markup/to_rdoc.rb @@ -0,0 +1,333 @@ +## +# Outputs RDoc markup as RDoc markup! (mostly) + +class RDoc::Markup::ToRdoc < RDoc::Markup::Formatter + + ## + # Current indent amount for output in characters + + attr_accessor :indent + + ## + # Output width in characters + + attr_accessor :width + + ## + # Stack of current list indexes for alphabetic and numeric lists + + attr_reader :list_index + + ## + # Stack of list types + + attr_reader :list_type + + ## + # Stack of list widths for indentation + + attr_reader :list_width + + ## + # Prefix for the next list item. See #use_prefix + + attr_reader :prefix + + ## + # Output accumulator + + attr_reader :res + + ## + # Creates a new formatter that will output (mostly) \RDoc markup + + def initialize markup = nil + super nil, markup + + @markup.add_special(/\\\S/, :SUPPRESSED_CROSSREF) + @width = 78 + init_tags + + @headings = {} + @headings.default = [] + + @headings[1] = ['= ', ''] + @headings[2] = ['== ', ''] + @headings[3] = ['=== ', ''] + @headings[4] = ['==== ', ''] + @headings[5] = ['===== ', ''] + @headings[6] = ['====== ', ''] + + @hard_break = "\n" + end + + ## + # Maps attributes to HTML sequences + + def init_tags + add_tag :BOLD, "<b>", "</b>" + add_tag :TT, "<tt>", "</tt>" + add_tag :EM, "<em>", "</em>" + end + + ## + # Adds +blank_line+ to the output + + def accept_blank_line blank_line + @res << "\n" + end + + ## + # Adds +paragraph+ to the output + + def accept_block_quote block_quote + @indent += 2 + + block_quote.parts.each do |part| + @prefix = '> ' + + part.accept self + end + + @indent -= 2 + end + + ## + # Adds +heading+ to the output + + def accept_heading heading + use_prefix or @res << ' ' * @indent + @res << @headings[heading.level][0] + @res << attributes(heading.text) + @res << @headings[heading.level][1] + @res << "\n" + end + + ## + # Finishes consumption of +list+ + + def accept_list_end list + @list_index.pop + @list_type.pop + @list_width.pop + end + + ## + # Finishes consumption of +list_item+ + + def accept_list_item_end list_item + width = case @list_type.last + when :BULLET then + 2 + when :NOTE, :LABEL then + if @prefix then + @res << @prefix.strip + @prefix = nil + end + + @res << "\n" + 2 + else + bullet = @list_index.last.to_s + @list_index[-1] = @list_index.last.succ + bullet.length + 2 + end + + @indent -= width + end + + ## + # Prepares the visitor for consuming +list_item+ + + def accept_list_item_start list_item + type = @list_type.last + + case type + when :NOTE, :LABEL then + bullets = Array(list_item.label).map do |label| + attributes(label).strip + end.join "\n" + + bullets << ":\n" unless bullets.empty? + + @prefix = ' ' * @indent + @indent += 2 + @prefix << bullets + (' ' * @indent) + else + bullet = type == :BULLET ? '*' : @list_index.last.to_s + '.' + @prefix = (' ' * @indent) + bullet.ljust(bullet.length + 1) + width = bullet.length + 1 + @indent += width + end + end + + ## + # Prepares the visitor for consuming +list+ + + def accept_list_start list + case list.type + when :BULLET then + @list_index << nil + @list_width << 1 + when :LABEL, :NOTE then + @list_index << nil + @list_width << 2 + when :LALPHA then + @list_index << 'a' + @list_width << list.items.length.to_s.length + when :NUMBER then + @list_index << 1 + @list_width << list.items.length.to_s.length + when :UALPHA then + @list_index << 'A' + @list_width << list.items.length.to_s.length + else + raise RDoc::Error, "invalid list type #{list.type}" + end + + @list_type << list.type + end + + ## + # Adds +paragraph+ to the output + + def accept_paragraph paragraph + text = paragraph.text @hard_break + wrap attributes text + end + + ## + # Adds +paragraph+ to the output + + def accept_indented_paragraph paragraph + @indent += paragraph.indent + text = paragraph.text @hard_break + wrap attributes text + @indent -= paragraph.indent + end + + ## + # Adds +raw+ to the output + + def accept_raw raw + @res << raw.parts.join("\n") + end + + ## + # Adds +rule+ to the output + + def accept_rule rule + use_prefix or @res << ' ' * @indent + @res << '-' * (@width - @indent) + @res << "\n" + end + + ## + # Outputs +verbatim+ indented 2 columns + + def accept_verbatim verbatim + indent = ' ' * (@indent + 2) + + verbatim.parts.each do |part| + @res << indent unless part == "\n" + @res << part + end + + @res << "\n" unless @res =~ /\n\z/ + end + + ## + # Applies attribute-specific markup to +text+ using RDoc::AttributeManager + + def attributes text + flow = @am.flow text.dup + convert_flow flow + end + + ## + # Returns the generated output + + def end_accepting + @res.join + end + + ## + # Removes preceding \\ from the suppressed crossref +special+ + + def handle_special_SUPPRESSED_CROSSREF special + text = special.text + text = text.sub('\\', '') unless in_tt? + text + end + + ## + # Adds a newline to the output + + def handle_special_HARD_BREAK special + "\n" + end + + ## + # Prepares the visitor for text generation + + def start_accepting + @res = [""] + @indent = 0 + @prefix = nil + + @list_index = [] + @list_type = [] + @list_width = [] + end + + ## + # Adds the stored #prefix to the output and clears it. Lists generate a + # prefix for later consumption. + + def use_prefix + prefix, @prefix = @prefix, nil + @res << prefix if prefix + + prefix + end + + ## + # Wraps +text+ to #width + + def wrap text + return unless text && !text.empty? + + text_len = @width - @indent + + text_len = 20 if text_len < 20 + + re = /^(.{0,#{text_len}})[ \n]/ + next_prefix = ' ' * @indent + + prefix = @prefix || next_prefix + @prefix = nil + + @res << prefix + + while text.length > text_len + if text =~ re then + @res << $1 + text.slice!(0, $&.length) + else + @res << text.slice!(0, text_len) + end + + @res << "\n" << next_prefix + end + + if text.empty? then + @res.pop + @res.pop + else + @res << text + @res << "\n" + end + end + +end + diff --git a/jni/ruby/lib/rdoc/markup/to_table_of_contents.rb b/jni/ruby/lib/rdoc/markup/to_table_of_contents.rb new file mode 100644 index 0000000..2e0f98c --- /dev/null +++ b/jni/ruby/lib/rdoc/markup/to_table_of_contents.rb @@ -0,0 +1,87 @@ +## +# Extracts just the RDoc::Markup::Heading elements from a +# RDoc::Markup::Document to help build a table of contents + +class RDoc::Markup::ToTableOfContents < RDoc::Markup::Formatter + + @to_toc = nil + + ## + # Singleton for table-of-contents generation + + def self.to_toc + @to_toc ||= new + end + + ## + # Output accumulator + + attr_reader :res + + ## + # Omits headings with a level less than the given level. + + attr_accessor :omit_headings_below + + def initialize # :nodoc: + super nil + + @omit_headings_below = nil + end + + ## + # Adds +document+ to the output, using its heading cutoff if present + + def accept_document document + @omit_headings_below = document.omit_headings_below + + super + end + + ## + # Adds +heading+ to the table of contents + + def accept_heading heading + @res << heading unless suppressed? heading + end + + ## + # Returns the table of contents + + def end_accepting + @res + end + + ## + # Prepares the visitor for text generation + + def start_accepting + @omit_headings_below = nil + @res = [] + end + + ## + # Returns true if +heading+ is below the display threshold + + def suppressed? heading + return false unless @omit_headings_below + + heading.level > @omit_headings_below + end + + # :stopdoc: + alias accept_block_quote ignore + alias accept_raw ignore + alias accept_rule ignore + alias accept_blank_line ignore + alias accept_paragraph ignore + alias accept_verbatim ignore + alias accept_list_end ignore + alias accept_list_item_start ignore + alias accept_list_item_end ignore + alias accept_list_end_bullet ignore + alias accept_list_start ignore + # :startdoc: + +end + diff --git a/jni/ruby/lib/rdoc/markup/to_test.rb b/jni/ruby/lib/rdoc/markup/to_test.rb new file mode 100644 index 0000000..c51f64b --- /dev/null +++ b/jni/ruby/lib/rdoc/markup/to_test.rb @@ -0,0 +1,69 @@ +## +# This Markup outputter is used for testing purposes. + +class RDoc::Markup::ToTest < RDoc::Markup::Formatter + + # :stopdoc: + + ## + # :section: Visitor + + def start_accepting + @res = [] + @list = [] + end + + def end_accepting + @res + end + + def accept_paragraph(paragraph) + @res << convert_flow(@am.flow(paragraph.text)) + end + + def accept_raw raw + @res << raw.parts.join + end + + def accept_verbatim(verbatim) + @res << verbatim.text.gsub(/^(\S)/, ' \1') + end + + def accept_list_start(list) + @list << case list.type + when :BULLET then + '*' + when :NUMBER then + '1' + else + list.type + end + end + + def accept_list_end(list) + @list.pop + end + + def accept_list_item_start(list_item) + @res << "#{' ' * (@list.size - 1)}#{@list.last}: " + end + + def accept_list_item_end(list_item) + end + + def accept_blank_line(blank_line) + @res << "\n" + end + + def accept_heading(heading) + @res << "#{'=' * heading.level} #{heading.text}" + end + + def accept_rule(rule) + @res << '-' * rule.weight + end + + # :startdoc: + +end + diff --git a/jni/ruby/lib/rdoc/markup/to_tt_only.rb b/jni/ruby/lib/rdoc/markup/to_tt_only.rb new file mode 100644 index 0000000..e2da20c --- /dev/null +++ b/jni/ruby/lib/rdoc/markup/to_tt_only.rb @@ -0,0 +1,120 @@ +## +# Extracts sections of text enclosed in plus, tt or code. Used to discover +# undocumented parameters. + +class RDoc::Markup::ToTtOnly < RDoc::Markup::Formatter + + ## + # Stack of list types + + attr_reader :list_type + + ## + # Output accumulator + + attr_reader :res + + ## + # Creates a new tt-only formatter. + + def initialize markup = nil + super nil, markup + + add_tag :TT, nil, nil + end + + ## + # Adds tts from +block_quote+ to the output + + def accept_block_quote block_quote + tt_sections block_quote.text + end + + ## + # Pops the list type for +list+ from #list_type + + def accept_list_end list + @list_type.pop + end + + ## + # Pushes the list type for +list+ onto #list_type + + def accept_list_start list + @list_type << list.type + end + + ## + # Prepares the visitor for consuming +list_item+ + + def accept_list_item_start list_item + case @list_type.last + when :NOTE, :LABEL then + Array(list_item.label).map do |label| + tt_sections label + end.flatten + end + end + + ## + # Adds +paragraph+ to the output + + def accept_paragraph paragraph + tt_sections(paragraph.text) + end + + ## + # Does nothing to +markup_item+ because it doesn't have any user-built + # content + + def do_nothing markup_item + end + + alias accept_blank_line do_nothing # :nodoc: + alias accept_heading do_nothing # :nodoc: + alias accept_list_item_end do_nothing # :nodoc: + alias accept_raw do_nothing # :nodoc: + alias accept_rule do_nothing # :nodoc: + alias accept_verbatim do_nothing # :nodoc: + + ## + # Extracts tt sections from +text+ + + def tt_sections text + flow = @am.flow text.dup + + flow.each do |item| + case item + when String then + @res << item if in_tt? + when RDoc::Markup::AttrChanger then + off_tags res, item + on_tags res, item + when RDoc::Markup::Special then + @res << convert_special(item) if in_tt? # TODO can this happen? + else + raise "Unknown flow element: #{item.inspect}" + end + end + + res + end + + ## + # Returns an Array of items that were wrapped in plus, tt or code. + + def end_accepting + @res.compact + end + + ## + # Prepares the visitor for gathering tt sections + + def start_accepting + @res = [] + + @list_type = [] + end + +end + diff --git a/jni/ruby/lib/rdoc/markup/verbatim.rb b/jni/ruby/lib/rdoc/markup/verbatim.rb new file mode 100644 index 0000000..0ddde67 --- /dev/null +++ b/jni/ruby/lib/rdoc/markup/verbatim.rb @@ -0,0 +1,83 @@ +## +# A section of verbatim text + +class RDoc::Markup::Verbatim < RDoc::Markup::Raw + + ## + # Format of this verbatim section + + attr_accessor :format + + def initialize *parts # :nodoc: + super + + @format = nil + end + + def == other # :nodoc: + super and @format == other.format + end + + ## + # Calls #accept_verbatim on +visitor+ + + def accept visitor + visitor.accept_verbatim self + end + + ## + # Collapses 3+ newlines into two newlines + + def normalize + parts = [] + + newlines = 0 + + @parts.each do |part| + case part + when /^\s*\n/ then + newlines += 1 + parts << part if newlines == 1 + else + newlines = 0 + parts << part + end + end + + parts.pop if parts.last =~ /\A\r?\n\z/ + + @parts = parts + end + + def pretty_print q # :nodoc: + self.class.name =~ /.*::(\w{1,4})/i + + q.group 2, "[#{$1.downcase}: ", ']' do + if @format then + q.text "format: #{@format}" + q.breakable + end + + q.seplist @parts do |part| + q.pp part + end + end + end + + ## + # Is this verbatim section Ruby code? + + def ruby? + @format ||= nil # TODO for older ri data, switch the tree to marshal_dump + @format == :ruby + end + + ## + # The text of the section + + def text + @parts.join + end + +end + |