diff options
Diffstat (limited to 'lib/rdoc/markup')
-rw-r--r-- | lib/rdoc/markup/attribute_manager.rb | 33 | ||||
-rw-r--r-- | lib/rdoc/markup/blank_line.rb | 14 | ||||
-rw-r--r-- | lib/rdoc/markup/document.rb | 6 | ||||
-rw-r--r-- | lib/rdoc/markup/formatter.rb | 10 | ||||
-rw-r--r-- | lib/rdoc/markup/formatter_test_case.rb | 342 | ||||
-rw-r--r-- | lib/rdoc/markup/heading.rb | 3 | ||||
-rw-r--r-- | lib/rdoc/markup/inline.rb | 12 | ||||
-rw-r--r-- | lib/rdoc/markup/list.rb | 3 | ||||
-rw-r--r-- | lib/rdoc/markup/list_item.rb | 3 | ||||
-rw-r--r-- | lib/rdoc/markup/paragraph.rb | 3 | ||||
-rw-r--r-- | lib/rdoc/markup/parser.rb | 426 | ||||
-rw-r--r-- | lib/rdoc/markup/pre_process.rb (renamed from lib/rdoc/markup/preprocess.rb) | 79 | ||||
-rw-r--r-- | lib/rdoc/markup/raw.rb | 4 | ||||
-rw-r--r-- | lib/rdoc/markup/rule.rb | 3 | ||||
-rw-r--r-- | lib/rdoc/markup/text_formatter_test_case.rb | 116 | ||||
-rw-r--r-- | lib/rdoc/markup/to_ansi.rb | 16 | ||||
-rw-r--r-- | lib/rdoc/markup/to_bs.rb | 10 | ||||
-rw-r--r-- | lib/rdoc/markup/to_html.rb | 175 | ||||
-rw-r--r-- | lib/rdoc/markup/to_html_crossref.rb | 109 | ||||
-rw-r--r-- | lib/rdoc/markup/to_rdoc.rb | 143 | ||||
-rw-r--r-- | lib/rdoc/markup/to_test.rb | 10 | ||||
-rw-r--r-- | lib/rdoc/markup/verbatim.rb | 9 |
22 files changed, 1075 insertions, 454 deletions
diff --git a/lib/rdoc/markup/attribute_manager.rb b/lib/rdoc/markup/attribute_manager.rb index e86e7f6812..2ee243ab0b 100644 --- a/lib/rdoc/markup/attribute_manager.rb +++ b/lib/rdoc/markup/attribute_manager.rb @@ -15,9 +15,12 @@ class RDoc::Markup::AttributeManager # optimistic #++ - A_PROTECT = 004 # :nodoc: + A_PROTECT = 004 # :nodoc: - PROTECT_ATTR = A_PROTECT.chr # :nodoc: + ## + # Special mask character to prevent inline markup handling + + PROTECT_ATTR = A_PROTECT.chr # :nodoc: ## # This maps delimiters that occur around words (such as *bold* or +tt+) @@ -56,7 +59,7 @@ class RDoc::Markup::AttributeManager def initialize @html_tags = {} @matching_word_pairs = {} - @protectable = %w[<\\] + @protectable = %w[<] @special = {} @word_pair_map = {} @@ -79,12 +82,19 @@ class RDoc::Markup::AttributeManager RDoc::Markup::AttrChanger.new turn_on, turn_off end - def change_attribute(current, new) + ## + # Changes the current attribute from +current+ to +new+ + + def change_attribute current, new diff = current ^ new attribute(new & diff, current & diff) end - def changed_attribute_by_name(current_set, new_set) + ## + # 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 |= RDoc::Markup::Attribute.bitmap_for(name) @@ -97,6 +107,9 @@ class RDoc::Markup::AttributeManager 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/, '') @@ -112,7 +125,7 @@ class RDoc::Markup::AttributeManager # first do matching ones tags = @matching_word_pairs.keys.join("") - re = /(^|[^\w#{NULL}])([#{tags}])([#:\\]?[\w.\/-]+?\S?)\2(\W|$)/ + re = /(^|\W)([#{tags}])([#:\\]?[\w.\/-]+?\S?)\2(\W|$)/ 1 while str.gsub!(re) do attr = @matching_word_pairs[$2] @@ -164,6 +177,9 @@ class RDoc::Markup::AttributeManager # 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!(/\\([#{Regexp.escape @protectable.join('')}])/, "\\1#{PROTECT_ATTR}") end @@ -228,8 +244,8 @@ class RDoc::Markup::AttributeManager @attrs = RDoc::Markup::AttrSpan.new @str.length - convert_html @str, @attrs convert_attrs @str, @attrs + convert_html @str, @attrs convert_specials @str, @attrs unmask_protected_sequences @@ -262,6 +278,9 @@ class RDoc::Markup::AttributeManager end end + ## + # Splits the string into chunks by attribute change + def split_into_flow res = [] current_attr = 0 diff --git a/lib/rdoc/markup/blank_line.rb b/lib/rdoc/markup/blank_line.rb index a8c07c8e57..5da0ac8d81 100644 --- a/lib/rdoc/markup/blank_line.rb +++ b/lib/rdoc/markup/blank_line.rb @@ -1,12 +1,20 @@ ## -# An empty line +# An empty line. This class is a singleton. class RDoc::Markup::BlankLine - def == other # :nodoc: - self.class == other.class + @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 diff --git a/lib/rdoc/markup/document.rb b/lib/rdoc/markup/document.rb index 7963e9afe1..688e8e822e 100644 --- a/lib/rdoc/markup/document.rb +++ b/lib/rdoc/markup/document.rb @@ -39,6 +39,9 @@ class RDoc::Markup::Document self.class == other.class and @parts == other.parts end + ## + # Runs this document and all its #items through +visitor+ + def accept visitor visitor.start_accepting @@ -49,6 +52,9 @@ class RDoc::Markup::Document visitor.end_accepting end + ## + # Does this document have no parts? + def empty? @parts.empty? end diff --git a/lib/rdoc/markup/formatter.rb b/lib/rdoc/markup/formatter.rb index 993e523f0c..9308954de1 100644 --- a/lib/rdoc/markup/formatter.rb +++ b/lib/rdoc/markup/formatter.rb @@ -7,6 +7,10 @@ require 'rdoc/markup' 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) ## @@ -101,6 +105,9 @@ class RDoc::Markup::Formatter @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? @@ -113,6 +120,9 @@ class RDoc::Markup::Formatter end end + ## + # Turns off tags for +item+ on +res+ + def off_tags res, item attr_mask = item.turn_off return if attr_mask.zero? diff --git a/lib/rdoc/markup/formatter_test_case.rb b/lib/rdoc/markup/formatter_test_case.rb index 26c8d63332..dd755c55d1 100644 --- a/lib/rdoc/markup/formatter_test_case.rb +++ b/lib/rdoc/markup/formatter_test_case.rb @@ -4,14 +4,57 @@ require 'rdoc/markup/formatter' ## # 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 < MiniTest::Unit::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 @m = RDoc::Markup.new - @am = RDoc::Markup::AttributeManager.new @RM = RDoc::Markup @bullet_list = @RM::List.new(:BULLET, @@ -39,14 +82,25 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase @RM::ListItem.new(nil, @RM::Paragraph.new('l2'))) end + ## + # Call to add the visitor tests to your test case + def self.add_visitor_tests self.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' @@ -54,6 +108,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase end_accepting end + ## + # Calls accept_blank_line + def test_accept_blank_line @to.start_accepting @@ -62,6 +119,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_blank_line end + ## + # Calls accept_heading with a level 5 RDoc::Markup::Heading + def test_accept_heading @to.start_accepting @@ -70,6 +130,79 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase 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 @@ -78,15 +211,80 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase 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_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") + @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 @@ -99,6 +297,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_raw end + ## + # Calls accept_rule with a RDoc::Markup::Rule + def test_accept_rule @to.start_accepting @@ -107,6 +308,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_rule end + ## + # Calls accept_list_item_start_bullet + def test_accept_list_item_start_bullet @to.start_accepting @@ -117,6 +321,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_item_start_bullet end + ## + # Calls accept_list_item_start_label + def test_accept_list_item_start_label @to.start_accepting @@ -127,6 +334,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_item_start_label end + ## + # Calls accept_list_item_start_lalpha + def test_accept_list_item_start_lalpha @to.start_accepting @@ -137,6 +347,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_item_start_lalpha end + ## + # Calls accept_list_item_start_note + def test_accept_list_item_start_note @to.start_accepting @@ -147,6 +360,26 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_item_start_note end + ## + # Calls accept_list_item_start_note_2 + + def test_accept_list_item_start_note_2 + list = @RM::List.new(:NOTE, + @RM::ListItem.new('<tt>teletype</tt>', + @RM::Paragraph.new('teletype description'))) + + @to.start_accepting + + list.accept @to + + @to.end_accepting + + accept_list_item_start_note_2 + end + + ## + # Calls accept_list_item_start_number + def test_accept_list_item_start_number @to.start_accepting @@ -157,6 +390,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_item_start_number end + ## + # Calls accept_list_item_start_ualpha + def test_accept_list_item_start_ualpha @to.start_accepting @@ -167,6 +403,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_item_start_ualpha end + ## + # Calls accept_list_item_end_bullet + def test_accept_list_item_end_bullet @to.start_accepting @@ -179,6 +418,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_item_end_bullet end + ## + # Calls accept_list_item_end_label + def test_accept_list_item_end_label @to.start_accepting @@ -191,6 +433,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_item_end_label end + ## + # Calls accept_list_item_end_lalpha + def test_accept_list_item_end_lalpha @to.start_accepting @@ -203,6 +448,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_item_end_lalpha end + ## + # Calls accept_list_item_end_note + def test_accept_list_item_end_note @to.start_accepting @@ -215,6 +463,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_item_end_note end + ## + # Calls accept_list_item_end_number + def test_accept_list_item_end_number @to.start_accepting @@ -227,6 +478,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_item_end_number end + ## + # Calls accept_list_item_end_ualpha + def test_accept_list_item_end_ualpha @to.start_accepting @@ -239,6 +493,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_item_end_ualpha end + ## + # Calls accept_list_start_bullet + def test_accept_list_start_bullet @to.start_accepting @@ -247,6 +504,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_start_bullet end + ## + # Calls accept_list_start_label + def test_accept_list_start_label @to.start_accepting @@ -255,6 +515,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_start_label end + ## + # Calls accept_list_start_lalpha + def test_accept_list_start_lalpha @to.start_accepting @@ -263,6 +526,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_start_lalpha end + ## + # Calls accept_list_start_note + def test_accept_list_start_note @to.start_accepting @@ -271,6 +537,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_start_note end + ## + # Calls accept_list_start_number + def test_accept_list_start_number @to.start_accepting @@ -279,6 +548,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_start_number end + ## + # Calls accept_list_start_ualpha + def test_accept_list_start_ualpha @to.start_accepting @@ -287,6 +559,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_start_ualpha end + ## + # Calls accept_list_end_bullet + def test_accept_list_end_bullet @to.start_accepting @@ -297,6 +572,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_end_bullet end + ## + # Calls accept_list_end_label + def test_accept_list_end_label @to.start_accepting @@ -307,6 +585,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_end_label end + ## + # Calls accept_list_end_lalpha + def test_accept_list_end_lalpha @to.start_accepting @@ -317,6 +598,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_end_lalpha end + ## + # Calls accept_list_end_number + def test_accept_list_end_number @to.start_accepting @@ -327,6 +611,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_end_number end + ## + # Calls accept_list_end_note + def test_accept_list_end_note @to.start_accepting @@ -337,6 +624,9 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase accept_list_end_note end + ## + # Calls accept_list_end_ulpha + def test_accept_list_end_ualpha @to.start_accepting @@ -346,6 +636,52 @@ class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase 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 = @RM::Document.new( + @RM::List.new(:BULLET, + @RM::ListItem.new(nil, + @RM::Paragraph.new('list', 'stuff'), + @RM::BlankLine.new, + @RM::Verbatim.new("* 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 diff --git a/lib/rdoc/markup/heading.rb b/lib/rdoc/markup/heading.rb index 21e2574d68..081d637729 100644 --- a/lib/rdoc/markup/heading.rb +++ b/lib/rdoc/markup/heading.rb @@ -3,6 +3,9 @@ class RDoc::Markup::Heading < Struct.new :level, :text + ## + # Calls #accept_heading on +wisitor+ + def accept visitor visitor.accept_heading self end diff --git a/lib/rdoc/markup/inline.rb b/lib/rdoc/markup/inline.rb index 1b5eac45ae..f5bf98a071 100644 --- a/lib/rdoc/markup/inline.rb +++ b/lib/rdoc/markup/inline.rb @@ -1,3 +1,4 @@ +require 'rdoc' class RDoc::Markup ## @@ -14,6 +15,9 @@ class RDoc::Markup @@name_to_bitmap = { :_SPECIAL_ => SPECIAL } @@next_bitmap = 2 + ## + # Returns a unique bit for +name+ + def self.bitmap_for(name) bitmap = @@name_to_bitmap[name] unless bitmap then @@ -24,6 +28,9 @@ class RDoc::Markup bitmap end + ## + # Returns a string reperesentation of +bitmap+ + def self.as_string(bitmap) return "none" if bitmap.zero? res = [] @@ -33,6 +40,9 @@ class RDoc::Markup res.join(",") end + ## + # yields each attribute name in +bitmap+ + def self.each_name_of(bitmap) @@name_to_bitmap.each do |name, bit| next if bit == SPECIAL @@ -75,7 +85,7 @@ class RDoc::Markup end ## - # Acccesses flags for character +n+ + # Accesses flags for character +n+ def [](n) @attrs[n] diff --git a/lib/rdoc/markup/list.rb b/lib/rdoc/markup/list.rb index 75326ed836..820b4c9645 100644 --- a/lib/rdoc/markup/list.rb +++ b/lib/rdoc/markup/list.rb @@ -35,6 +35,9 @@ class RDoc::Markup::List @items == other.items end + ## + # Runs this list and all its #items through +visitor+ + def accept visitor visitor.accept_list_start self diff --git a/lib/rdoc/markup/list_item.rb b/lib/rdoc/markup/list_item.rb index 500e814fe1..d719c352ec 100644 --- a/lib/rdoc/markup/list_item.rb +++ b/lib/rdoc/markup/list_item.rb @@ -35,6 +35,9 @@ class RDoc::Markup::ListItem @parts == other.parts end + ## + # Runs this list item and all its #parts through +visitor+ + def accept visitor visitor.accept_list_item_start self diff --git a/lib/rdoc/markup/paragraph.rb b/lib/rdoc/markup/paragraph.rb index a9923ed24d..808430d576 100644 --- a/lib/rdoc/markup/paragraph.rb +++ b/lib/rdoc/markup/paragraph.rb @@ -3,6 +3,9 @@ class RDoc::Markup::Paragraph < RDoc::Markup::Raw + ## + # Calls #accept_paragraph on +visitor+ + def accept visitor visitor.accept_paragraph self end diff --git a/lib/rdoc/markup/parser.rb b/lib/rdoc/markup/parser.rb index 9fba69dc29..ea02ee3c5b 100644 --- a/lib/rdoc/markup/parser.rb +++ b/lib/rdoc/markup/parser.rb @@ -52,13 +52,13 @@ class RDoc::Markup::Parser attr_reader :tokens ## - # Parsers +str+ into a Document + # Parses +str+ into a Document def self.parse str parser = new - #parser.debug = true parser.tokenize str - RDoc::Markup::Document.new(*parser.parse) + doc = RDoc::Markup::Document.new + parser.parse doc end ## @@ -86,6 +86,7 @@ class RDoc::Markup::Parser # Builds a Heading of +level+ def build_heading level + _, text, = get # TEXT heading = RDoc::Markup::Heading.new level, text skip :NEWLINE @@ -105,38 +106,69 @@ class RDoc::Markup::Parser case type when :BULLET, :LABEL, :LALPHA, :NOTE, :NUMBER, :UALPHA then - list_type = type - if column < margin then + if column < margin || (list.type && list.type != type) then unget break end - if list.type and list.type != list_type then - unget - break - end - - list.type = list_type + list.type = type + peek_type, _, column, = peek_token case type when :NOTE, :LABEL then - _, indent, = get # SPACE - if :NEWLINE == peek_token.first then - get - peek_type, new_indent, peek_column, = peek_token - indent = new_indent if - peek_type == :INDENT and peek_column >= column - unget + 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 = 1 + elsif column == margin then + case peek_type + when type + empty = 2 # continue + when *LIST_TOKENS + empty = 1 + else + empty = 0 + end + else + empty = 0 + end + + if empty > 0 then + item = RDoc::Markup::ListItem.new(data) + item << RDoc::Markup::BlankLine.new + list << item + break if empty == 1 + next + end end else data = nil - _, indent, = get end - list_item = build_list_item(margin + indent, data) + list_item = RDoc::Markup::ListItem.new data + parse list_item, column + list << list_item - list << list_item if list_item else unget break @@ -151,54 +183,6 @@ class RDoc::Markup::Parser end ## - # Builds a ListItem that is flush to +indent+ with type +item_type+ - - def build_list_item indent, item_type = nil - p :list_item_start => [indent, item_type] if @debug - - list_item = RDoc::Markup::ListItem.new item_type - - until @tokens.empty? do - type, data, column = get - - if column < indent and - not type == :NEWLINE and - (type != :INDENT or data < indent) then - unget - break - end - - case type - when :INDENT then - unget - list_item.push(*parse(indent)) - when :TEXT then - unget - list_item << build_paragraph(indent) - when :HEADER then - list_item << build_heading(data) - when :NEWLINE then - list_item << RDoc::Markup::BlankLine.new - when *LIST_TOKENS then - unget - list_item << build_list(column) - else - raise ParseError, "Unhandled token #{@current_token.inspect}" - end - end - - p :list_item_end => [indent, item_type] if @debug - - return nil if list_item.empty? - - list_item.parts.shift if - RDoc::Markup::BlankLine === list_item.parts.first and - list_item.length > 1 - - list_item - end - - ## # Builds a Paragraph that is flush to +margin+ def build_paragraph margin @@ -209,18 +193,7 @@ class RDoc::Markup::Parser until @tokens.empty? do type, data, column, = get - case type - when :INDENT then - next if data == margin and peek_token[0] == :TEXT - - unget - break - when :TEXT then - if column != margin then - unget - break - end - + if type == :TEXT && column == margin then paragraph << data skip :NEWLINE else @@ -235,67 +208,81 @@ class RDoc::Markup::Parser end ## - # Builds a Verbatim that is flush to +margin+ + # 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 - case type - when :INDENT then - if margin >= data then - unget - break - end + if type == :NEWLINE then + line << data + verbatim << line + line = '' + generate_leading_spaces = true + next + end - indent = data - margin + if column <= margin + unget + break + end - verbatim << ' ' * indent - when :HEADER then - verbatim << '=' * data + 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 - verbatim << ' ' * (peek_column - column - data) + indent = peek_column - column - data + line << ' ' * indent when :RULE then width = 2 + data - verbatim << '-' * width - + line << '-' * width _, _, peek_column, = peek_token - peek_column ||= column + data + 2 - verbatim << ' ' * (peek_column - column - width) + peek_column ||= column + width + indent = peek_column - column - width + line << ' ' * indent when :TEXT then - verbatim << data - when *LIST_TOKENS then - if column <= margin then - unget - break - end - + line << data + else # *LIST_TOKENS list_marker = case type - when :BULLET then '*' - when :LABEL then "[#{data}]" - when :LALPHA, :NUMBER, :UALPHA then "#{data}." - when :NOTE then "#{data}::" + when :BULLET then data + when :LABEL then "[#{data}]" + when :NOTE then "#{data}::" + else # :LALPHA, :NUMBER, :UALPHA + "#{data}." end - - verbatim << list_marker - - _, data, = get - - verbatim << ' ' * (data - list_marker.length) - when :NEWLINE then - verbatim << data - break unless [:INDENT, :NEWLINE].include? peek_token[0] - else - unget - break + 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 @@ -313,65 +300,60 @@ class RDoc::Markup::Parser end ## - # Parses the tokens into a Document - - def parse indent = 0 + # 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 - document = [] - until @tokens.empty? do type, data, column, = get - if type != :INDENT and column < indent then + if type == :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 verbattim + 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 - document << build_heading(data) - when :INDENT then - if indent > data then - unget - break - elsif indent == data then - next - end - - unget - document << build_verbatim(indent) - when :NEWLINE then - document << RDoc::Markup::BlankLine.new - skip :NEWLINE, false + parent << build_heading(data) when :RULE then - document << RDoc::Markup::Rule.new(data) + parent << RDoc::Markup::Rule.new(data) skip :NEWLINE when :TEXT then unget - document << build_paragraph(indent) - - # we're done with this paragraph (indent mismatch) - break if peek_token[0] == :TEXT + parent << build_paragraph(indent) when *LIST_TOKENS then unget - - list = build_list(indent) - - document << list if list - - # we're done with this list (indent mismatch) - break if LIST_TOKENS.include? peek_token.first and indent > 0 + parent << build_list(indent) else type, data, column, line = @current_token - raise ParseError, - "Unhandled token #{type} (#{data.inspect}) at #{line}:#{column}" + raise ParseError, "Unhandled token #{type} (#{data.inspect}) at #{line}:#{column}" end end p :parse_end => indent if @debug - document + parent + end ## @@ -384,63 +366,16 @@ class RDoc::Markup::Parser end ## - # Skips a token of +token_type+, optionally raising an error. + # 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 - - ## - # Consumes tokens until NEWLINE and turns them back into text - - def text - text = '' - - loop do - type, data, = get - - text << case type - when :BULLET then - _, space, = get # SPACE - "*#{' ' * (space - 1)}" - when :LABEL then - _, space, = get # SPACE - "[#{data}]#{' ' * (space - data.length - 2)}" - when :LALPHA, :NUMBER, :UALPHA then - _, space, = get # SPACE - "#{data}.#{' ' * (space - 2)}" - when :NOTE then - _, space = get # SPACE - "#{data}::#{' ' * (space - data.length - 2)}" - when :TEXT then - data - when :NEWLINE then - unget - break - when nil then - break - else - raise ParseError, "unhandled token #{@current_token.inspect}" - end - end - - text - end - - ## - # Calculates the column and line of the current token based on +offset+. - - def token_pos offset - [offset - @line_pos, @line] + raise ParseError, "expected #{token_type} got #{@current_token.inspect}" if error end ## @@ -455,51 +390,62 @@ class RDoc::Markup::Parser 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 = s.pos @line += 1 token - when s.scan(/ +/) then - [:INDENT, s.matched_size, *token_pos(pos)] + # === text => :HEADER then :TEXT when s.scan(/(=+)\s*/) then level = s[1].length level = 6 if level > 6 @tokens << [:HEADER, level, *token_pos(pos)] - pos = s.pos s.scan(/.*/) - [:TEXT, s.matched, *token_pos(pos)] - when s.scan(/^(-{3,}) *$/) then + [:TEXT, s.matched.sub(/\r$/, ''), *token_pos(pos)] + # --- (at least 3) and nothing else on the line => :RULE + when s.scan(/(-{3,}) *$/) then [:RULE, s[1].length - 2, *token_pos(pos)] - when s.scan(/([*-])\s+/) then - @tokens << [:BULLET, :BULLET, *token_pos(pos)] - [:SPACE, s.matched_size, *token_pos(pos)] - when s.scan(/([a-z]|\d+)\.[ \t]+\S/i) then + # * 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] - width = s.matched_size - 1 - - s.pos -= 1 # 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 - - @tokens << [list_type, list_label, *token_pos(pos)] - [:SPACE, width, *token_pos(pos)] + 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(/\[(.*?)\]( +|$)/) then - @tokens << [:LABEL, s[1], *token_pos(pos)] - [:SPACE, s.matched_size, *token_pos(pos)] + [:LABEL, s[1], *token_pos(pos)] + # text:: followed by spaces or end of line => :NOTE when s.scan(/(.*?)::( +|$)/) then - @tokens << [:NOTE, s[1], *token_pos(pos)] - [:SPACE, s.matched_size, *token_pos(pos)] + [:NOTE, s[1], *token_pos(pos)] + # anything else: :TEXT else s.scan(/.*/) - [:TEXT, s.matched, *token_pos(pos)] + [:TEXT, s.matched.sub(/\r$/, ''), *token_pos(pos)] end end @@ -507,9 +453,17 @@ class RDoc::Markup::Parser end ## - # Returns the current token or +token+ to the token stream + # Calculates the column and line of the current token based on +offset+. + + def token_pos offset + [offset - @line_pos, @line] + end + + ## + # Returns the current token to the token stream - def unget token = @current_token + def unget + token = @current_token p :unget => token if @debug raise Error, 'too many #ungets' if token == @tokens.first @tokens.unshift token if token diff --git a/lib/rdoc/markup/preprocess.rb b/lib/rdoc/markup/pre_process.rb index cefb498916..e59bd227b7 100644 --- a/lib/rdoc/markup/preprocess.rb +++ b/lib/rdoc/markup/pre_process.rb @@ -1,12 +1,15 @@ require 'rdoc/markup' +require 'rdoc/encoding' ## # Handle common directives that can occur in a block of text: # -# : include : filename +# \:include: filename # -# RDoc plugin authors can register additional directives to be handled through -# RDoc::Markup::PreProcess::register +# 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 class RDoc::Markup::PreProcess @@ -52,18 +55,25 @@ class RDoc::Markup::PreProcess # +code_object+. See RDoc::CodeObject#metadata def handle text, code_object = nil - text.gsub!(/^([ \t]*#?[ \t]*):(\w+):([ \t]*)(.+)?\n/) do - next $& if $3.empty? and $4 and $4[0, 1] == ':' + # regexp helper (square brackets for optional) + # $1 $2 $3 $4 $5 + # [prefix][\]:directive:[spaces][param]newline + text.gsub!(/^([ \t]*#?[ \t]*)(\\?):(\w+):([ \t]*)(.+)?\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? prefix = $1 - directive = $2.downcase - param = $4 + directive = $3.downcase + param = $5 case directive when 'include' then filename = param.split[0] - include_file filename, prefix - + encoding = if defined?(Encoding) then text.encoding else nil end + include_file filename, prefix, encoding else result = yield directive, param if block_given? @@ -88,27 +98,38 @@ class RDoc::Markup::PreProcess end ## - # Include a file, indenting it correctly. - - def include_file(name, indent) - if full_name = find_include_file(name) then - content = if defined?(Encoding) then - File.binread full_name - else - File.read full_name - end - # HACK determine content type and force encoding - 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 - else + # 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 + + # 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 diff --git a/lib/rdoc/markup/raw.rb b/lib/rdoc/markup/raw.rb index 1124be7cc8..ca877c79af 100644 --- a/lib/rdoc/markup/raw.rb +++ b/lib/rdoc/markup/raw.rb @@ -27,6 +27,9 @@ class RDoc::Markup::Raw self.class == other.class and text == other.text end + ## + # Calls #accept_raw+ on +visitor+ + def accept visitor visitor.accept_raw self end @@ -63,3 +66,4 @@ class RDoc::Markup::Raw end end + diff --git a/lib/rdoc/markup/rule.rb b/lib/rdoc/markup/rule.rb index 4fcd040d2b..b778f2bc09 100644 --- a/lib/rdoc/markup/rule.rb +++ b/lib/rdoc/markup/rule.rb @@ -3,6 +3,9 @@ class RDoc::Markup::Rule < Struct.new :weight + ## + # Calls #accept_rule on +visitor+ + def accept visitor visitor.accept_rule self end diff --git a/lib/rdoc/markup/text_formatter_test_case.rb b/lib/rdoc/markup/text_formatter_test_case.rb new file mode 100644 index 0000000000..ba9e7c6187 --- /dev/null +++ b/lib/rdoc/markup/text_formatter_test_case.rb @@ -0,0 +1,116 @@ +require 'rdoc/markup/formatter_test_case' + +## +# 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/lib/rdoc/markup/to_ansi.rb b/lib/rdoc/markup/to_ansi.rb index 9a5be8babb..c9f874ea3c 100644 --- a/lib/rdoc/markup/to_ansi.rb +++ b/lib/rdoc/markup/to_ansi.rb @@ -1,10 +1,13 @@ -require 'rdoc/markup/inline' +require 'rdoc/markup/to_rdoc' ## # 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 super @@ -23,12 +26,15 @@ class RDoc::Markup::ToAnsi < RDoc::Markup::ToRdoc 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 - @res << "\n" + @res << "\n" unless res.length == 1 2 else bullet = @list_index.last.to_s @@ -39,6 +45,9 @@ class RDoc::Markup::ToAnsi < RDoc::Markup::ToRdoc @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 @@ -62,6 +71,9 @@ class RDoc::Markup::ToAnsi < RDoc::Markup::ToRdoc end end + ## + # Starts accepting with a reset screen + def start_accepting super diff --git a/lib/rdoc/markup/to_bs.rb b/lib/rdoc/markup/to_bs.rb index e7af129824..931edd81ea 100644 --- a/lib/rdoc/markup/to_bs.rb +++ b/lib/rdoc/markup/to_bs.rb @@ -1,4 +1,4 @@ -require 'rdoc/markup/inline' +require 'rdoc/markup/to_rdoc' ## # Outputs RDoc markup with hot backspace action! You will probably need a @@ -8,6 +8,9 @@ require 'rdoc/markup/inline' class RDoc::Markup::ToBs < RDoc::Markup::ToRdoc + ## + # Returns a new ToBs that is ready for hot backspace action! + def initialize super @@ -22,8 +25,12 @@ class RDoc::Markup::ToBs < RDoc::Markup::ToRdoc 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] @@ -44,7 +51,6 @@ class RDoc::Markup::ToBs < RDoc::Markup::ToRdoc when '+_' then @in_em = true when '-_' then @in_em = false end - '' end diff --git a/lib/rdoc/markup/to_html.rb b/lib/rdoc/markup/to_html.rb index 74e3137eb2..de723921e9 100644 --- a/lib/rdoc/markup/to_html.rb +++ b/lib/rdoc/markup/to_html.rb @@ -8,6 +8,8 @@ require 'cgi' class RDoc::Markup::ToHtml < RDoc::Markup::Formatter + include RDoc::Text + ## # Maps RDoc::Markup::Parser::LIST_TOKENS types to HTML tags @@ -15,7 +17,7 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter :BULLET => ['<ul>', '</ul>'], :LABEL => ['<dl>', '</dl>'], :LALPHA => ['<ol style="display: lower-alpha">', '</ol>'], - :NOTE => ['<table>', '</table>'], + :NOTE => ['<table class="rdoc-list">', '</table>'], :NUMBER => ['<ol>', '</ol>'], :UALPHA => ['<ol style="display: upper-alpha">', '</ol>'], } @@ -48,6 +50,9 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter File.join(*from) end + ## + # Creates a new formatter that will output HTML + def initialize super @@ -103,13 +108,15 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter end end + # :section: Special handling + ## - # And we're invoked with a potential external hyperlink mailto: - # just gets inserted. http: links are checked to see if they + # And we're invoked with a potential external hyperlink. <tt>mailto:</tt> + # just gets inserted. <tt>http:</tt> links are checked to see if they # reference an image. If so, that image gets inserted using an - # <img> tag. Otherwise a conventional <a href> is used. We also - # support a special type of hyperlink, link:, which is a reference - # to a local file whose path is relative to the --op directory. + # <tt><img></tt> tag. Otherwise a conventional <tt><a href></tt> is used. + # We also support a special type of hyperlink, <tt>link:</tt>, which is a + # reference to a local file whose path is relative to the --op directory. def handle_special_HYPERLINK(special) url = special.text @@ -118,7 +125,7 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter ## # Here's a hypedlink where the label is different to the URL - # <label>[url] or {long label}[url] + # <label>[url] or {long label}[url] def handle_special_TIDYLINK(special) text = special.text @@ -130,8 +137,10 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter gen_url url, label end + # :section: Utilities + ## - # This is a higher speed (if messier) version of wrap + # Wraps +txt+ to +line_len+ def wrap(txt, line_len = 76) res = [] @@ -159,173 +168,150 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter sp += 1 while sp < ep and txt[sp] == ?\s end - res.join + res.join.strip end - ## # :section: 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 +paragraph+ to the output + def accept_paragraph(paragraph) - @res << annotate("<p>") + "\n" - @res << wrap(convert_flow(@am.flow(paragraph.text))) - @res << annotate("</p>") + "\n" + @res << "\n<p>" + @res << wrap(to_html(paragraph.text)) + @res << "</p>\n" end + ## + # Adds +verbatim+ to the output + def accept_verbatim(verbatim) - @res << annotate("<pre>") << "\n" - @res << CGI.escapeHTML(verbatim.text) - @res << annotate("</pre>") << "\n" + @res << "\n<pre>" + @res << CGI.escapeHTML(verbatim.text.rstrip) + @res << "</pre>\n" end + ## + # Adds +rule+ to the output + def accept_rule(rule) size = rule.weight size = 10 if size > 10 - @res << "<hr style=\"height: #{size}px\"></hr>" + @res << "<hr style=\"height: #{size}px\">\n" end + ## + # Prepares the visitor for consuming +list+ + def accept_list_start(list) @list << list.type - @res << html_list_name(list.type, true) << "\n" + @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 << annotate(tag) << "\n" + @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 << annotate(tag) << "\n" + @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 - def accept_blank_line(blank_line) - # @res << annotate("<p />") << "\n" - end - - def accept_heading(heading) - @res << convert_heading(heading.level, @am.flow(heading.text)) - end - - def accept_raw raw - @res << raw.parts.join("\n") - end - - private - ## - # Converts string +item+ + # Adds +blank_line+ to the output - def convert_string(item) - in_tt? ? convert_string_simple(item) : convert_string_fancy(item) + def accept_blank_line(blank_line) + # @res << annotate("<p />") << "\n" end ## - # Escapes HTML in +item+ + # Adds +heading+ to the output - def convert_string_simple(item) - CGI.escapeHTML item + def accept_heading(heading) + @res << "\n<h#{heading.level}>" + @res << to_html(heading.text) + @res << "</h#{heading.level}>\n" end ## - # Converts ampersand, dashes, elipsis, quotes, copyright and registered - # trademark symbols to HTML escaped Unicode. - - def convert_string_fancy(item) - # convert ampersand before doing anything else - item.gsub(/&/, '&'). - - # convert -- to em-dash, (-- to en-dash) - gsub(/---?/, '—'). #gsub(/--/, '–'). - - # convert ... to elipsis (and make sure .... becomes .<elipsis>) - gsub(/\.\.\.\./, '.…').gsub(/\.\.\./, '…'). + # Adds +raw+ to the output - # convert single closing quote - gsub(%r{([^ \t\r\n\[\{\(])\'}, '\1’'). # } - gsub(%r{\'(?=\W|s\b)}, '’'). - - # convert single opening quote - gsub(/'/, '‘'). - - # convert double closing quote - gsub(%r{([^ \t\r\n\[\{\(])\"(?=\W)}, '\1”'). # } - - # convert double opening quote - gsub(/"/, '“'). - - # convert copyright - gsub(/\(c\)/, '©'). - - # convert registered trademark - gsub(/\(r\)/, '®') + def accept_raw raw + @res << raw.parts.join("\n") end ## - # Converts headings to hN elements + # CGI escapes +text+ - def convert_heading(level, flow) - [annotate("<h#{level}>"), - convert_flow(flow), - annotate("</h#{level}>\n")].join + def convert_string(text) + CGI.escapeHTML text end ## - # Determins the HTML list element for +list_type+ and +open_tag+ + # 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 - annotate tags[open_tag ? 0 : 1] + tags[open_tag ? 0 : 1] end ## - # Starts a list item + # 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 - annotate("<li>") - + "<li>" when :LABEL then - annotate("<dt>") + - convert_flow(@am.flow(list_item.label)) + - annotate("</dt>") + - annotate("<dd>") - + "<dt>#{to_html list_item.label}</dt>\n<dd>" when :NOTE then - annotate("<tr>") + - annotate("<td valign=\"top\">") + - convert_flow(@am.flow(list_item.label)) + - annotate("</td>") + - annotate("<td>") + "<tr><td class=\"rdoc-term\"><p>#{to_html list_item.label}</p></td>\n<td>" else raise RDoc::Error, "Invalid list type: #{list_type.inspect}" end end ## - # Ends a list item + # Returns the HTML end-tag for +list_type+ def list_end_for(list_type) case list_type @@ -340,5 +326,12 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter end 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/lib/rdoc/markup/to_html_crossref.rb b/lib/rdoc/markup/to_html_crossref.rb index 44e71486fb..a3feb848a2 100644 --- a/lib/rdoc/markup/to_html_crossref.rb +++ b/lib/rdoc/markup/to_html_crossref.rb @@ -9,10 +9,10 @@ class RDoc::Markup::ToHtmlCrossref < RDoc::Markup::ToHtml ## # Regular expression to match class references # - # 1) There can be a '\' in front of text to suppress any cross-references - # 2) There can be a '::' in front of class names to reference from the + # 1. There can be a '\\' in front of text to suppress the cross-reference + # 2. There can be a '::' in front of class names to reference from the # top-level namespace. - # 3) The method can be followed by parenthesis + # 3. The method can be followed by parenthesis (not recommended) CLASS_REGEXP_STR = '\\\\?((?:\:{2})?[A-Z]\w*(?:\:\:\w+)*)' @@ -34,10 +34,10 @@ class RDoc::Markup::ToHtmlCrossref < RDoc::Markup::ToHtml # A::B::C.meth #{CLASS_REGEXP_STR}(?:[.#]|::)#{METHOD_REGEXP_STR} - # Stand-alone method (proceeded by a #) + # Stand-alone method (preceeded by a #) | \\?\##{METHOD_REGEXP_STR} - # Stand-alone method (proceeded by ::) + # Stand-alone method (preceeded by ::) | ::#{METHOD_REGEXP_STR} # A::B::C @@ -51,9 +51,10 @@ class RDoc::Markup::ToHtmlCrossref < RDoc::Markup::ToHtml # In order that words like "can't" not # be flagged as potential cross-references, only # flag potential class cross-references if the character - # after the cross-referece is a space or sentence - # punctuation. - | #{CLASS_REGEXP_STR}(?=[\s\)\.\?\!\,\;]|\z) + # after the cross-referece is a space, sentence + # punctuation, tag start character, or attribute + # marker. + | #{CLASS_REGEXP_STR}(?=[\s\)\.\?\!\,\;<\000]|\z) # Things that look like filenames # The key thing is that there must be at least @@ -62,7 +63,29 @@ class RDoc::Markup::ToHtmlCrossref < RDoc::Markup::ToHtml | (?:\.\.\/)*[-\/\w]+[_\/\.][-\w\/\.]+ # Things that have markup suppressed - | \\[^\s] + # Don't process things like '\<' in \<tt>, though. + # TODO: including < is a hack, not very satisfying. + | \\[^\s<] + )/x + + ## + # Version of CROSSREF_REGEXP used when <tt>--hyperlink-all</tt> is specified. + + ALL_CROSSREF_REGEXP = /( + # A::B::C.meth + #{CLASS_REGEXP_STR}(?:[.#]|::)#{METHOD_REGEXP_STR} + + # Stand-alone method + | \\?#{METHOD_REGEXP_STR} + + # A::B::C + | #{CLASS_REGEXP_STR}(?=[\s\)\.\?\!\,\;<\000]|\z) + + # Things that look like filenames + | (?:\.\.\/)*[-\/\w]+[_\/\.][-\w\/\.]+ + + # Things that have markup suppressed + | \\[^\s<] )/x ## @@ -71,19 +94,28 @@ class RDoc::Markup::ToHtmlCrossref < RDoc::Markup::ToHtml 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. + # references are removed unless +show_hash+ is true. Only method names + # preceded by '#' or '::' are hyperlinked, unless +hyperlink_all+ is true. - def initialize(from_path, context, show_hash) + def initialize(from_path, context, show_hash, hyperlink_all = false) raise ArgumentError, 'from_path cannot be nil' if from_path.nil? super() - @markup.add_special(CROSSREF_REGEXP, :CROSSREF) + crossref_re = hyperlink_all ? ALL_CROSSREF_REGEXP : CROSSREF_REGEXP + + @markup.add_special crossref_re, :CROSSREF @from_path = from_path @context = context @show_hash = show_hash + @hyperlink_all = hyperlink_all @seen = {} end @@ -92,22 +124,24 @@ class RDoc::Markup::ToHtmlCrossref < RDoc::Markup::ToHtml # We're invoked when any text matches the CROSSREF pattern. If we find the # corresponding reference, generate a hyperlink. If the name we're looking # for contains no punctuation, we look for it up the module/class chain. - # For example, HyperlinkHtml is found, even without the Generator:: prefix, - # because we look for it in module Generator first. + # 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 - # 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/ + 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 return @seen[name] if @seen.include? name lookup = name - name = name[0, 1] unless @show_hash if name[0, 1] == '#' + name = name[1..-1] unless @show_hash if name[0, 1] == '#' # Find class, module, or method in class or module. # @@ -120,26 +154,47 @@ class RDoc::Markup::ToHtmlCrossref < RDoc::Markup::ToHtml # whether the string as a whole is a known symbol). if /#{CLASS_REGEXP_STR}([.#]|::)#{METHOD_REGEXP_STR}/ =~ lookup then - container = $1 type = $2 - type = '#' if type == '.' + type = '' if type == '.' # will find either #method or ::method method = "#{type}#{$3}" - ref = @context.find_symbol container, method + container = @context.find_symbol_module($1) + elsif /^([.#]|::)#{METHOD_REGEXP_STR}/ =~ lookup then + type = $1 + type = '' if type == '.' + method = "#{type}#{$2}" + container = @context + else + container = nil + end + + if container then + ref = container.find_local_symbol method + + unless ref || RDoc::TopLevel === container then + ref = container.find_ancestor_local_symbol method + end end ref = @context.find_symbol lookup unless ref + ref = nil if RDoc::Alias === ref # external alias: can't link to it out = if lookup == '\\' then lookup elsif lookup =~ /^\\/ then - $' - elsif ref and ref.document_self then - "<a href=\"#{ref.as_href @from_path}\">#{name}</a>" + # we remove the \ only in front of what we know: + # other backslashes are treated later, only outside of <tt> + ref ? $' : lookup + elsif ref then + if ref.document_self then + "<a href=\"#{ref.as_href @from_path}\">#{name}</a>" + else + name + end else - name + lookup end - @seen[name] = out + @seen[lookup] = out out end diff --git a/lib/rdoc/markup/to_rdoc.rb b/lib/rdoc/markup/to_rdoc.rb index 867715bb1e..b1ac59e5b0 100644 --- a/lib/rdoc/markup/to_rdoc.rb +++ b/lib/rdoc/markup/to_rdoc.rb @@ -1,3 +1,4 @@ +require 'rdoc/markup/formatter' require 'rdoc/markup/inline' ## @@ -5,21 +6,49 @@ require 'rdoc/markup/inline' 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 super - @markup.add_special(/\\[^\s]/, :SUPPRESSED_CROSSREF) - + @markup.add_special(/\\\S/, :SUPPRESSED_CROSSREF) @width = 78 - @prefix = '' - init_tags @headings = {} @@ -34,7 +63,7 @@ class RDoc::Markup::ToRdoc < RDoc::Markup::Formatter end ## - # Maps attributes to ANSI sequences + # Maps attributes to HTML sequences def init_tags add_tag :BOLD, "<b>", "</b>" @@ -42,10 +71,16 @@ class RDoc::Markup::ToRdoc < RDoc::Markup::Formatter add_tag :EM, "<em>", "</em>" end + ## + # Adds +blank_line+ to the output + def accept_blank_line blank_line @res << "\n" end + ## + # Adds +heading+ to the output + def accept_heading heading use_prefix or @res << ' ' * @indent @res << @headings[heading.level][0] @@ -54,12 +89,18 @@ class RDoc::Markup::ToRdoc < RDoc::Markup::Formatter @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 @@ -76,29 +117,29 @@ class RDoc::Markup::ToRdoc < RDoc::Markup::Formatter @indent -= width end + ## + # Prepares the visitor for consuming +list_item+ + def accept_list_item_start list_item - bullet = case @list_type.last - when :BULLET then - '*' - when :NOTE, :LABEL then - attributes(list_item.label) + ":\n" - else - @list_index.last.to_s + '.' - end - - case @list_type.last + type = @list_type.last + + case type when :NOTE, :LABEL then + bullet = attributes(list_item.label) + ":\n" + @prefix = ' ' * @indent @indent += 2 - @prefix = bullet + (' ' * @indent) + @prefix << bullet + (' ' * @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 @@ -123,14 +164,23 @@ class RDoc::Markup::ToRdoc < RDoc::Markup::Formatter @list_type << list.type end + ## + # Adds +paragraph+ to the output + def accept_paragraph paragraph wrap attributes(paragraph.text) 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) @@ -138,58 +188,46 @@ class RDoc::Markup::ToRdoc < RDoc::Markup::Formatter end ## - # Outputs +verbatim+ flush left and indented 2 columns + # Outputs +verbatim+ indented 2 columns def accept_verbatim verbatim indent = ' ' * (@indent + 2) - lines = [] - current_line = [] - - # split into lines verbatim.parts.each do |part| - current_line << part - - if part == "\n" then - lines << current_line - current_line = [] - end + @res << indent unless part == "\n" + @res << part end - lines << current_line unless current_line.empty? - - # calculate margin - indented = lines.select { |line| line != ["\n"] } - margin = indented.map { |line| line.first.length }.min - - # flush left - indented.each { |line| line[0][0...margin] = '' } - - # output - use_prefix or @res << indent # verbatim is unlikely to have prefix - @res << lines.shift.join - - lines.each do |line| - @res << indent unless line == ["\n"] - @res << line.join - end - - @res << "\n" + @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 preceeding \\ from the suppressed crossref +special+ + def handle_special_SUPPRESSED_CROSSREF special - special.text.sub(/\\/, '') + text = special.text + text = text.sub('\\', '') unless in_tt? + text end + ## + # Prepares the visitor for text generation + def start_accepting @res = [""] @indent = 0 @@ -200,6 +238,10 @@ class RDoc::Markup::ToRdoc < RDoc::Markup::Formatter @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 @@ -208,6 +250,9 @@ class RDoc::Markup::ToRdoc < RDoc::Markup::Formatter prefix end + ## + # Wraps +text+ to #width + def wrap text return unless text && !text.empty? diff --git a/lib/rdoc/markup/to_test.rb b/lib/rdoc/markup/to_test.rb index 0afdb96a18..f79f9475f1 100644 --- a/lib/rdoc/markup/to_test.rb +++ b/lib/rdoc/markup/to_test.rb @@ -6,6 +6,8 @@ require 'rdoc/markup/formatter' class RDoc::Markup::ToTest < RDoc::Markup::Formatter + # :stopdoc: + ## # :section: Visitor @@ -22,8 +24,12 @@ class RDoc::Markup::ToTest < RDoc::Markup::Formatter @res << paragraph.text end + def accept_raw raw + @res << raw.parts.join + end + def accept_verbatim(verbatim) - @res << verbatim.text + @res << verbatim.text.gsub(/^(\S)/, ' \1') end def accept_list_start(list) @@ -60,5 +66,7 @@ class RDoc::Markup::ToTest < RDoc::Markup::Formatter @res << '-' * rule.weight end + # :startdoc: + end diff --git a/lib/rdoc/markup/verbatim.rb b/lib/rdoc/markup/verbatim.rb index c684d78765..8fe2184699 100644 --- a/lib/rdoc/markup/verbatim.rb +++ b/lib/rdoc/markup/verbatim.rb @@ -3,6 +3,9 @@ class RDoc::Markup::Verbatim < RDoc::Markup::Raw + ## + # Calls #accept_verbatim on +visitor+ + def accept visitor visitor.accept_verbatim self end @@ -17,16 +20,16 @@ class RDoc::Markup::Verbatim < RDoc::Markup::Raw @parts.each do |part| case part - when /\n/ then + when /^\s*\n/ then newlines += 1 - parts << part if newlines <= 2 + parts << part if newlines == 1 else newlines = 0 parts << part end end - parts.slice!(-1) if parts[-2..-1] == ["\n", "\n"] + parts.pop if parts.last =~ /\A\r?\n\z/ @parts = parts end |