summaryrefslogtreecommitdiff
path: root/lib/rdoc/markup
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rdoc/markup')
-rw-r--r--lib/rdoc/markup/attribute_manager.rb141
-rw-r--r--lib/rdoc/markup/blank_line.rb19
-rw-r--r--lib/rdoc/markup/document.rb72
-rw-r--r--lib/rdoc/markup/formatter.rb129
-rw-r--r--lib/rdoc/markup/formatter_test_case.rb341
-rw-r--r--lib/rdoc/markup/fragments.rb337
-rw-r--r--lib/rdoc/markup/heading.rb17
-rw-r--r--lib/rdoc/markup/inline.rb42
-rw-r--r--lib/rdoc/markup/lines.rb152
-rw-r--r--lib/rdoc/markup/list.rb78
-rw-r--r--lib/rdoc/markup/list_item.rb83
-rw-r--r--lib/rdoc/markup/paragraph.rb66
-rw-r--r--lib/rdoc/markup/parser.rb528
-rw-r--r--lib/rdoc/markup/preprocess.rb11
-rw-r--r--lib/rdoc/markup/rule.rb17
-rw-r--r--lib/rdoc/markup/to_ansi.rb72
-rw-r--r--lib/rdoc/markup/to_bs.rb74
-rw-r--r--lib/rdoc/markup/to_flow.rb185
-rw-r--r--lib/rdoc/markup/to_html.rb327
-rw-r--r--lib/rdoc/markup/to_html_crossref.rb84
-rw-r--r--lib/rdoc/markup/to_latex.rb328
-rw-r--r--lib/rdoc/markup/to_rdoc.rb243
-rw-r--r--lib/rdoc/markup/to_test.rb46
-rw-r--r--lib/rdoc/markup/to_texinfo.rb69
-rw-r--r--lib/rdoc/markup/verbatim.rb42
25 files changed, 2123 insertions, 1380 deletions
diff --git a/lib/rdoc/markup/attribute_manager.rb b/lib/rdoc/markup/attribute_manager.rb
index d13b79376c..5b9e070efb 100644
--- a/lib/rdoc/markup/attribute_manager.rb
+++ b/lib/rdoc/markup/attribute_manager.rb
@@ -1,41 +1,76 @@
-require 'rdoc/markup/inline'
+##
+# 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
- PROTECT_ATTR = A_PROTECT.chr
+ A_PROTECT = 004 # :nodoc:
+
+ PROTECT_ATTR = A_PROTECT.chr # :nodoc:
##
# 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
- MATCHING_WORD_PAIRS = {}
+ 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
- WORD_PAIR_MAP = {}
+ attr_reader :word_pair_map
##
# This maps HTML tags to the corresponding attribute char
- HTML_TAGS = {}
+ 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
- SPECIAL = {}
+ 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 = {}
+
+ 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
@@ -75,19 +110,19 @@ class RDoc::Markup::AttributeManager
def convert_attrs(str, attrs)
# first do matching ones
- tags = MATCHING_WORD_PAIRS.keys.join("")
+ tags = @matching_word_pairs.keys.join("")
re = /(^|\W)([#{tags}])([#:\\]?[\w.\/-]+?\S?)\2(\W|$)/
1 while str.gsub!(re) do
- attr = MATCHING_WORD_PAIRS[$2]
+ 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|
+ 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
@@ -96,11 +131,14 @@ class RDoc::Markup::AttributeManager
end
end
+ ##
+ # Converts HTML tags to RDoc attributes
+
def convert_html(str, attrs)
- tags = HTML_TAGS.keys.join '|'
+ tags = @html_tags.keys.join '|'
1 while str.gsub!(/<(#{tags})>(.*?)<\/\1>/i) {
- attr = HTML_TAGS[$1.downcase]
+ attr = @html_tags[$1.downcase]
html_length = $1.length + 2
seq = NULL * html_length
attrs.set_attrs($`.length + html_length, $2.length, attr)
@@ -108,9 +146,12 @@ class RDoc::Markup::AttributeManager
}
end
+ ##
+ # Converts special sequences to RDoc attributes
+
def convert_specials(str, attrs)
- unless SPECIAL.empty?
- SPECIAL.each do |regexp, attr|
+ unless @special.empty?
+ @special.each do |regexp, attr|
str.scan(regexp) do
attrs.set_attrs($`.length, $&.length,
attr | RDoc::Markup::Attribute::SPECIAL)
@@ -120,31 +161,25 @@ class RDoc::Markup::AttributeManager
end
##
- # A \ in front of a character that would normally be processed turns off
- # processing. We do this by turning \< into <#{PROTECT}
-
- PROTECTABLE = %w[<\\]
+ # Escapes special sequences of text to prevent conversion to RDoc
def mask_protected_sequences
- protect_pattern = Regexp.new("\\\\([#{Regexp.escape(PROTECTABLE.join(''))}])")
- @str.gsub!(protect_pattern, "\\1#{PROTECT_ATTR}")
+ @str.gsub!(/\\([#{Regexp.escape @protectable.join('')}])/,
+ "\\1#{PROTECT_ATTR}")
end
+ ##
+ # Unescapes special sequences of text
+
def unmask_protected_sequences
@str.gsub!(/(.)#{PROTECT_ATTR}/, "\\1\000")
end
- def initialize
- 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
+ ##
+ # 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
@@ -153,24 +188,39 @@ class RDoc::Markup::AttributeManager
bitmap = RDoc::Markup::Attribute.bitmap_for name
if start == stop then
- MATCHING_WORD_PAIRS[start] = bitmap
+ @matching_word_pairs[start] = bitmap
else
pattern = /(#{Regexp.escape start})(\S+)(#{Regexp.escape stop})/
- WORD_PAIR_MAP[pattern] = bitmap
+ @word_pair_map[pattern] = bitmap
end
- PROTECTABLE << start[0,1]
- PROTECTABLE.uniq!
+ @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] = RDoc::Markup::Attribute.bitmap_for name
+ @html_tags[tag.downcase] = RDoc::Markup::Attribute.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] = RDoc::Markup::Attribute.bitmap_for name
+ @special[pattern] = RDoc::Markup::Attribute.bitmap_for name
end
+ ##
+ # Processes +str+ converting attributes, HTML and specials
+
def flow(str)
@str = str
@@ -178,15 +228,18 @@ class RDoc::Markup::AttributeManager
@attrs = RDoc::Markup::AttrSpan.new @str.length
- convert_attrs(@str, @attrs)
- convert_html(@str, @attrs)
- convert_specials(str, @attrs)
+ convert_attrs @str, @attrs
+ convert_html @str, @attrs
+ convert_specials @str, @attrs
unmask_protected_sequences
- return split_into_flow
+ split_into_flow
end
+ ##
+ # Debug method that prints a string along with its attributes
+
def display_attributes
puts
puts @str.tr(NULL, "!")
@@ -258,7 +311,7 @@ class RDoc::Markup::AttributeManager
# and reset to all attributes off
res << change_attribute(current_attr, 0) if current_attr != 0
- return res
+ res
end
end
diff --git a/lib/rdoc/markup/blank_line.rb b/lib/rdoc/markup/blank_line.rb
new file mode 100644
index 0000000000..a8c07c8e57
--- /dev/null
+++ b/lib/rdoc/markup/blank_line.rb
@@ -0,0 +1,19 @@
+##
+# An empty line
+
+class RDoc::Markup::BlankLine
+
+ def == other # :nodoc:
+ self.class == other.class
+ end
+
+ def accept visitor
+ visitor.accept_blank_line self
+ end
+
+ def pretty_print q # :nodoc:
+ q.text 'blankline'
+ end
+
+end
+
diff --git a/lib/rdoc/markup/document.rb b/lib/rdoc/markup/document.rb
new file mode 100644
index 0000000000..7963e9afe1
--- /dev/null
+++ b/lib/rdoc/markup/document.rb
@@ -0,0 +1,72 @@
+##
+# A Document containing lists, headings, paragraphs, etc.
+
+class RDoc::Markup::Document
+
+ ##
+ # The parts of the Document
+
+ attr_reader :parts
+
+ ##
+ # Creates a new Document with +parts+
+
+ def initialize *parts
+ @parts = []
+ @parts.push(*parts)
+ end
+
+ ##
+ # Appends +part+ to the document
+
+ def << part
+ case part
+ when RDoc::Markup::Document then
+ unless part.empty? then
+ parts.push(*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 @parts == other.parts
+ end
+
+ def accept visitor
+ visitor.start_accepting
+
+ @parts.each do |item|
+ item.accept visitor
+ end
+
+ visitor.end_accepting
+ end
+
+ def empty?
+ @parts.empty?
+ end
+
+ def pretty_print q # :nodoc:
+ q.group 2, '[doc: ', ']' do
+ q.seplist @parts do |part|
+ q.pp part
+ end
+ end
+ end
+
+ ##
+ # Appends +parts+ to the document
+
+ def push *parts
+ self.parts.push(*parts)
+ end
+
+end
+
diff --git a/lib/rdoc/markup/formatter.rb b/lib/rdoc/markup/formatter.rb
index 14cbae59f9..993e523f0c 100644
--- a/lib/rdoc/markup/formatter.rb
+++ b/lib/rdoc/markup/formatter.rb
@@ -1,14 +1,143 @@
require 'rdoc/markup'
+##
+# Base class for RDoc markup formatters
+#
+# Formatters use a visitor pattern to convert content into output.
+
class RDoc::Markup::Formatter
+ InlineTag = Struct.new(:bit, :on, :off)
+
+ ##
+ # Creates a new Formatter
+
def initialize
@markup = RDoc::Markup.new
+ @am = @markup.attribute_manager
+ @attr_tags = []
+
+ @in_tt = 0
+ @tt_bit = RDoc::Markup::Attribute.bitmap_for :TT
+ 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 = RDoc::Markup::Attribute.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)
+ handled = false
+
+ RDoc::Markup::Attribute.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
+
+ raise "Unhandled special: #{special}" unless handled
+
+ special.text
+ end
+
+ ##
+ # Converts a string to be fancier if desired
+
+ def convert_string string
+ string
+ end
+
+ ##
+ # Are we currently inside tt tags?
+
+ def in_tt?
+ @in_tt > 0
+ end
+
+ 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
+
+ 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
+
+ ##
+ # Is +tag+ a tt tag?
+
+ def tt? tag
+ tag.bit == @tt_bit
+ end
+
end
+class RDoc::Markup
+ autoload :ToAnsi, 'rdoc/markup/to_ansi'
+ autoload :ToBs, 'rdoc/markup/to_bs'
+ autoload :ToHtml, 'rdoc/markup/to_html'
+ autoload :ToHtmlCrossref, 'rdoc/markup/to_html_crossref'
+ autoload :ToRdoc, 'rdoc/markup/to_rdoc'
+end
diff --git a/lib/rdoc/markup/formatter_test_case.rb b/lib/rdoc/markup/formatter_test_case.rb
new file mode 100644
index 0000000000..9b9d7cf000
--- /dev/null
+++ b/lib/rdoc/markup/formatter_test_case.rb
@@ -0,0 +1,341 @@
+require 'minitest/unit'
+require 'rdoc/markup/formatter'
+
+##
+# Test case for creating new RDoc::Markup formatters. See
+# test/test_rdoc_markup_to_*.rb for examples.
+
+class RDoc::Markup::FormatterTestCase < MiniTest::Unit::TestCase
+
+ def setup
+ super
+
+ @m = RDoc::Markup.new
+ @am = RDoc::Markup::AttributeManager.new
+ @RM = RDoc::Markup
+
+ @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
+
+ def self.add_visitor_tests
+ self.class_eval do
+ def test_start_accepting
+ @to.start_accepting
+
+ start_accepting
+ end
+
+ def test_end_accepting
+ @to.start_accepting
+ @to.res << 'hi'
+
+ end_accepting
+ end
+
+ def test_accept_blank_line
+ @to.start_accepting
+
+ @to.accept_blank_line @RM::BlankLine.new
+
+ accept_blank_line
+ end
+
+ def test_accept_heading
+ @to.start_accepting
+
+ @to.accept_heading @RM::Heading.new(5, 'Hello')
+
+ accept_heading
+ end
+
+ def test_accept_paragraph
+ @to.start_accepting
+
+ @to.accept_paragraph @RM::Paragraph.new('hi')
+
+ accept_paragraph
+ end
+
+ def test_accept_verbatim
+ @to.start_accepting
+
+ @to.accept_verbatim @RM::Verbatim.new(' ', 'hi', "\n",
+ ' ', 'world', "\n")
+
+ accept_verbatim
+ end
+
+ def test_accept_rule
+ @to.start_accepting
+
+ @to.accept_rule @RM::Rule.new(4)
+
+ accept_rule
+ end
+
+ 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
+
+ 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
+
+ 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
+
+ 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
+
+ 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
+
+ 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
+
+ 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
+
+ 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
+
+ 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
+
+ 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
+
+ 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
+
+ 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
+
+ def test_accept_list_start_bullet
+ @to.start_accepting
+
+ @to.accept_list_start @bullet_list
+
+ accept_list_start_bullet
+ end
+
+ def test_accept_list_start_label
+ @to.start_accepting
+
+ @to.accept_list_start @label_list
+
+ accept_list_start_label
+ end
+
+ def test_accept_list_start_lalpha
+ @to.start_accepting
+
+ @to.accept_list_start @lalpha_list
+
+ accept_list_start_lalpha
+ end
+
+ def test_accept_list_start_note
+ @to.start_accepting
+
+ @to.accept_list_start @note_list
+
+ accept_list_start_note
+ end
+
+ def test_accept_list_start_number
+ @to.start_accepting
+
+ @to.accept_list_start @number_list
+
+ accept_list_start_number
+ end
+
+ def test_accept_list_start_ualpha
+ @to.start_accepting
+
+ @to.accept_list_start @ualpha_list
+
+ accept_list_start_ualpha
+ end
+
+ 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
+
+ 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
+
+ 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
+
+ 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
+
+ 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
+
+ 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
+ end
+ end
+
+end
+
diff --git a/lib/rdoc/markup/fragments.rb b/lib/rdoc/markup/fragments.rb
deleted file mode 100644
index 0031b809b4..0000000000
--- a/lib/rdoc/markup/fragments.rb
+++ /dev/null
@@ -1,337 +0,0 @@
-require 'rdoc/markup'
-require 'rdoc/markup/lines'
-
-class RDoc::Markup
-
- ##
- # A Fragment is a chunk of text, subclassed as a paragraph, a list
- # entry, or verbatim text.
-
- class Fragment
- attr_reader :level, :param, :txt
- attr_accessor :type
-
- ##
- # This is a simple factory system that lets us associate fragement
- # types (a string) with a subclass of fragment
-
- TYPE_MAP = {}
-
- def self.type_name(name)
- TYPE_MAP[name] = self
- end
-
- def self.for(line)
- klass = TYPE_MAP[line.type] ||
- raise("Unknown line type: '#{line.type.inspect}:' '#{line.text}'")
- return klass.new(line.level, line.param, line.flag, line.text)
- end
-
- def initialize(level, param, type, txt)
- @level = level
- @param = param
- @type = type
- @txt = ""
- add_text(txt) if txt
- end
-
- def add_text(txt)
- @txt << " " if @txt.length > 0
- @txt << txt.tr_s("\n ", " ").strip
- end
-
- def to_s
- "L#@level: #{self.class.name.split('::')[-1]}\n#@txt"
- end
-
- end
-
- ##
- # A paragraph is a fragment which gets wrapped to fit. We remove all
- # newlines when we're created, and have them put back on output.
-
- class Paragraph < Fragment
- type_name :PARAGRAPH
- end
-
- class BlankLine < Paragraph
- type_name :BLANK
- end
-
- class Heading < Paragraph
- type_name :HEADING
-
- def head_level
- @param.to_i
- end
- end
-
- ##
- # A List is a fragment with some kind of label
-
- class ListBase < Paragraph
- LIST_TYPES = [
- :BULLET,
- :NUMBER,
- :UPPERALPHA,
- :LOWERALPHA,
- :LABELED,
- :NOTE,
- ]
- end
-
- class ListItem < ListBase
- type_name :LIST
-
- def to_s
- text = if [:NOTE, :LABELED].include? type then
- "#{@param}: #{@txt}"
- else
- @txt
- end
-
- "L#@level: #{type} #{self.class.name.split('::')[-1]}\n#{text}"
- end
-
- end
-
- class ListStart < ListBase
- def initialize(level, param, type)
- super(level, param, type, nil)
- end
- end
-
- class ListEnd < ListBase
- def initialize(level, type)
- super(level, "", type, nil)
- end
- end
-
- ##
- # Verbatim code contains lines that don't get wrapped.
-
- class Verbatim < Fragment
- type_name :VERBATIM
-
- def add_text(txt)
- @txt << txt.chomp << "\n"
- end
-
- end
-
- ##
- # A horizontal rule
-
- class Rule < Fragment
- type_name :RULE
- end
-
- ##
- # Collect groups of lines together. Each group will end up containing a flow
- # of text.
-
- class LineCollection
-
- def initialize
- @fragments = []
- end
-
- def add(fragment)
- @fragments << fragment
- end
-
- def each(&b)
- @fragments.each(&b)
- end
-
- def to_a # :nodoc:
- @fragments.map {|fragment| fragment.to_s}
- end
-
- ##
- # Factory for different fragment types
-
- def fragment_for(*args)
- Fragment.for(*args)
- end
-
- ##
- # Tidy up at the end
-
- def normalize
- change_verbatim_blank_lines
- add_list_start_and_ends
- add_list_breaks
- tidy_blank_lines
- end
-
- def to_s
- @fragments.join("\n----\n")
- end
-
- def accept(am, visitor)
- visitor.start_accepting
-
- @fragments.each do |fragment|
- case fragment
- when Verbatim
- visitor.accept_verbatim(am, fragment)
- when Rule
- visitor.accept_rule(am, fragment)
- when ListStart
- visitor.accept_list_start(am, fragment)
- when ListEnd
- visitor.accept_list_end(am, fragment)
- when ListItem
- visitor.accept_list_item(am, fragment)
- when BlankLine
- visitor.accept_blank_line(am, fragment)
- when Heading
- visitor.accept_heading(am, fragment)
- when Paragraph
- visitor.accept_paragraph(am, fragment)
- end
- end
-
- visitor.end_accepting
- end
-
- private
-
- # If you have:
- #
- # normal paragraph text.
- #
- # this is code
- #
- # and more code
- #
- # You'll end up with the fragments Paragraph, BlankLine, Verbatim,
- # BlankLine, Verbatim, BlankLine, etc.
- #
- # The BlankLine in the middle of the verbatim chunk needs to be changed to
- # a real verbatim newline, and the two verbatim blocks merged
-
- def change_verbatim_blank_lines
- frag_block = nil
- blank_count = 0
- @fragments.each_with_index do |frag, i|
- if frag_block.nil?
- frag_block = frag if Verbatim === frag
- else
- case frag
- when Verbatim
- blank_count.times { frag_block.add_text("\n") }
- blank_count = 0
- frag_block.add_text(frag.txt)
- @fragments[i] = nil # remove out current fragment
- when BlankLine
- if frag_block
- blank_count += 1
- @fragments[i] = nil
- end
- else
- frag_block = nil
- blank_count = 0
- end
- end
- end
- @fragments.compact!
- end
-
- ##
- # List nesting is implicit given the level of indentation. Make it
- # explicit, just to make life a tad easier for the output processors
-
- def add_list_start_and_ends
- level = 0
- res = []
- type_stack = []
-
- @fragments.each do |fragment|
- # $stderr.puts "#{level} : #{fragment.class.name} : #{fragment.level}"
- new_level = fragment.level
- while (level < new_level)
- level += 1
- type = fragment.type
- res << ListStart.new(level, fragment.param, type) if type
- type_stack.push type
- # $stderr.puts "Start: #{level}"
- end
-
- while level > new_level
- type = type_stack.pop
- res << ListEnd.new(level, type) if type
- level -= 1
- # $stderr.puts "End: #{level}, #{type}"
- end
-
- res << fragment
- level = fragment.level
- end
- level.downto(1) do |i|
- type = type_stack.pop
- res << ListEnd.new(i, type) if type
- end
-
- @fragments = res
- end
-
- ##
- # Inserts start/ends between list entries at the same level that have
- # different element types
-
- def add_list_breaks
- res = @fragments
-
- @fragments = []
- list_stack = []
-
- res.each do |fragment|
- case fragment
- when ListStart
- list_stack.push fragment
- when ListEnd
- start = list_stack.pop
- fragment.type = start.type
- when ListItem
- l = list_stack.last
- if fragment.type != l.type
- @fragments << ListEnd.new(l.level, l.type)
- start = ListStart.new(l.level, fragment.param, fragment.type)
- @fragments << start
- list_stack.pop
- list_stack.push start
- end
- else
- ;
- end
- @fragments << fragment
- end
- end
-
- ##
- # Tidy up the blank lines:
- # * change Blank/ListEnd into ListEnd/Blank
- # * remove blank lines at the front
-
- def tidy_blank_lines
- (@fragments.size - 1).times do |i|
- if BlankLine === @fragments[i] and ListEnd === @fragments[i+1] then
- @fragments[i], @fragments[i+1] = @fragments[i+1], @fragments[i]
- end
- end
-
- # remove leading blanks
- @fragments.each_with_index do |f, i|
- break unless f.kind_of? BlankLine
- @fragments[i] = nil
- end
-
- @fragments.compact!
- end
-
- end
-
-end
-
diff --git a/lib/rdoc/markup/heading.rb b/lib/rdoc/markup/heading.rb
new file mode 100644
index 0000000000..21e2574d68
--- /dev/null
+++ b/lib/rdoc/markup/heading.rb
@@ -0,0 +1,17 @@
+##
+# A heading with a level (1-6) and text
+
+class RDoc::Markup::Heading < Struct.new :level, :text
+
+ def accept visitor
+ visitor.accept_heading self
+ end
+
+ def pretty_print q # :nodoc:
+ q.group 2, "[head: #{level} ", ']' do
+ q.pp text
+ end
+ end
+
+end
+
diff --git a/lib/rdoc/markup/inline.rb b/lib/rdoc/markup/inline.rb
index 46c9b5822c..1b5eac45ae 100644
--- a/lib/rdoc/markup/inline.rb
+++ b/lib/rdoc/markup/inline.rb
@@ -1,5 +1,3 @@
-require 'rdoc/markup'
-
class RDoc::Markup
##
@@ -7,6 +5,10 @@ class RDoc::Markup
# value.
class Attribute
+
+ ##
+ # Special attribute type. See RDoc::Markup#add_special
+
SPECIAL = 1
@@name_to_bitmap = { :_SPECIAL_ => SPECIAL }
@@ -37,17 +39,18 @@ class RDoc::Markup
yield name.to_s if (bitmap & bit) != 0
end
end
+
end
- AttrChanger = Struct.new(:turn_on, :turn_off)
+ AttrChanger = Struct.new :turn_on, :turn_off # :nodoc:
##
# 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 AttrChanger
- def to_s
- "Attr: +#{Attribute.as_string(turn_on)}/-#{Attribute.as_string(turn_on)}"
+ def to_s # :nodoc:
+ "Attr: +#{Attribute.as_string turn_on}/-#{Attribute.as_string turn_on}"
end
end
@@ -55,42 +58,66 @@ class RDoc::Markup
# An array of attributes which parallels the characters in a string.
class 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
+ ##
+ # Acccesses flags for character +n+
+
def [](n)
@attrs[n]
end
+
end
##
# Hold details of a special sequence
class 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
+ def inspect # :nodoc:
"#<RDoc::Markup::Special:0x%x @type=%p, name=%p @text=%p>" % [
object_id, @type, RDoc::Markup::Attribute.as_string(type), text.dump]
end
- def to_s
+ def to_s # :nodoc:
"Special: type=#{type}, name=#{RDoc::Markup::Attribute.as_string type}, text=#{text.dump}"
end
@@ -98,4 +125,3 @@ class RDoc::Markup
end
-require 'rdoc/markup/attribute_manager'
diff --git a/lib/rdoc/markup/lines.rb b/lib/rdoc/markup/lines.rb
deleted file mode 100644
index 069492122f..0000000000
--- a/lib/rdoc/markup/lines.rb
+++ /dev/null
@@ -1,152 +0,0 @@
-class RDoc::Markup
-
- ##
- # We store the lines we're working on as objects of class Line. These
- # contain the text of the line, along with a flag indicating the line type,
- # and an indentation level.
-
- class Line
- INFINITY = 9999
-
- LINE_TYPES = [
- :BLANK,
- :HEADING,
- :LIST,
- :PARAGRAPH,
- :RULE,
- :VERBATIM,
- ]
-
- # line type
- attr_accessor :type
-
- # The indentation nesting level
- attr_accessor :level
-
- # The contents
- attr_accessor :text
-
- # A prefix or parameter. For LIST lines, this is
- # the text that introduced the list item (the label)
- attr_accessor :param
-
- # A flag. For list lines, this is the type of the list
- attr_accessor :flag
-
- # the number of leading spaces
- attr_accessor :leading_spaces
-
- # true if this line has been deleted from the list of lines
- attr_accessor :deleted
-
- def initialize(text)
- @text = text.dup
- @deleted = false
-
- # expand tabs
- 1 while @text.gsub!(/\t+/) { ' ' * (8*$&.length - $`.length % 8)} && $~ #`
-
- # Strip trailing whitespace
- @text.sub!(/\s+$/, '')
-
- # and look for leading whitespace
- if @text.length > 0
- @text =~ /^(\s*)/
- @leading_spaces = $1.length
- else
- @leading_spaces = INFINITY
- end
- end
-
- # Return true if this line is blank
- def blank?
- @text.empty?
- end
-
- # stamp a line with a type, a level, a prefix, and a flag
- def stamp(type, level, param="", flag=nil)
- @type, @level, @param, @flag = type, level, param, flag
- end
-
- ##
- # Strip off the leading margin
-
- def strip_leading(size)
- if @text.size > size
- @text[0,size] = ""
- else
- @text = ""
- end
- end
-
- def to_s
- "#@type#@level: #@text"
- end
- end
-
- ##
- # A container for all the lines.
-
- class Lines
-
- include Enumerable
-
- attr_reader :lines # :nodoc:
-
- def initialize(lines)
- @lines = lines
- rewind
- end
-
- def empty?
- @lines.size.zero?
- end
-
- def each
- @lines.each do |line|
- yield line unless line.deleted
- end
- end
-
-# def [](index)
-# @lines[index]
-# end
-
- def rewind
- @nextline = 0
- end
-
- def next
- begin
- res = @lines[@nextline]
- @nextline += 1 if @nextline < @lines.size
- end while res and res.deleted and @nextline < @lines.size
- res
- end
-
- def unget
- @nextline -= 1
- end
-
- def delete(a_line)
- a_line.deleted = true
- end
-
- def normalize
- margin = @lines.collect{|l| l.leading_spaces}.min
- margin = 0 if margin == :INFINITY
- @lines.each {|line| line.strip_leading(margin) } if margin > 0
- end
-
- def as_text
- @lines.map {|l| l.text}.join("\n")
- end
-
- def line_types
- @lines.map {|l| l.type }
- end
-
- end
-
-end
-
diff --git a/lib/rdoc/markup/list.rb b/lib/rdoc/markup/list.rb
new file mode 100644
index 0000000000..75326ed836
--- /dev/null
+++ b/lib/rdoc/markup/list.rb
@@ -0,0 +1,78 @@
+##
+# A List of ListItems
+
+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+
+
+ def initialize type = nil, *items
+ @type = type
+ @items = []
+ @items.push(*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
+
+ 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.push(*items)
+ end
+
+end
+
diff --git a/lib/rdoc/markup/list_item.rb b/lib/rdoc/markup/list_item.rb
new file mode 100644
index 0000000000..500e814fe1
--- /dev/null
+++ b/lib/rdoc/markup/list_item.rb
@@ -0,0 +1,83 @@
+##
+# An item within a List that contains paragraphs, headings, etc.
+
+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.push(*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
+
+ 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
+ if @label then
+ q.text @label
+ q.breakable
+ end
+
+ q.seplist @parts do |part|
+ q.pp part
+ end
+ end
+ end
+
+ ##
+ # Adds +parts+ to the ListItem
+
+ def push *parts
+ @parts.push(*parts)
+ end
+
+end
+
diff --git a/lib/rdoc/markup/paragraph.rb b/lib/rdoc/markup/paragraph.rb
new file mode 100644
index 0000000000..bc23423dfc
--- /dev/null
+++ b/lib/rdoc/markup/paragraph.rb
@@ -0,0 +1,66 @@
+##
+# A Paragraph of text
+
+class RDoc::Markup::Paragraph
+
+ ##
+ # The component parts of the list
+
+ attr_reader :parts
+
+ ##
+ # Creates a new Paragraph containing +parts+
+
+ def initialize *parts
+ @parts = []
+ @parts.push(*parts)
+ end
+
+ ##
+ # Appends +text+ to the Paragraph
+
+ def << text
+ @parts << text
+ end
+
+ def == other # :nodoc:
+ self.class == other.class and text == other.text
+ end
+
+ def accept visitor
+ visitor.accept_paragraph self
+ end
+
+ ##
+ # Appends +other+'s parts into this Paragraph
+
+ def merge other
+ @parts.push(*other.parts)
+ end
+
+ def pretty_print q # :nodoc:
+ self.class.name =~ /.*::(\w{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.push(*texts)
+ end
+
+ ##
+ # The text of this paragraph
+
+ def text
+ @parts.join ' '
+ end
+
+end
+
diff --git a/lib/rdoc/markup/parser.rb b/lib/rdoc/markup/parser.rb
new file mode 100644
index 0000000000..c0d6519fd5
--- /dev/null
+++ b/lib/rdoc/markup/parser.rb
@@ -0,0 +1,528 @@
+require 'strscan'
+require 'rdoc/text'
+
+##
+# 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
+
+ ##
+ # Parsers +str+ into a Document
+
+ def self.parse str
+ parser = new
+ #parser.debug = true
+ parser.tokenize str
+ RDoc::Markup::Document.new(*parser.parse)
+ 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
+ @tokens = []
+ @current_token = nil
+ @debug = false
+
+ @line = 0
+ @line_pos = 0
+ end
+
+ ##
+ # Builds a Heading of +level+
+
+ def build_heading level
+ heading = RDoc::Markup::Heading.new level, text
+ skip :NEWLINE
+
+ heading
+ end
+
+ ##
+ # Builds a List flush to +margin+
+
+ def build_list margin
+ p :list_start => margin if @debug
+
+ list = RDoc::Markup::List.new
+
+ until @tokens.empty? do
+ type, data, column, = get
+
+ case type
+ when :BULLET, :LABEL, :LALPHA, :NOTE, :NUMBER, :UALPHA then
+ list_type = type
+
+ if column < margin then
+ unget
+ break
+ end
+
+ if list.type and list.type != list_type then
+ unget
+ break
+ end
+
+ list.type = list_type
+
+ 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
+ end
+ else
+ data = nil
+ _, indent, = get
+ end
+
+ list_item = build_list_item(margin + indent, data)
+
+ list << list_item if list_item
+ else
+ unget
+ break
+ end
+ end
+
+ p :list_end => margin if @debug
+
+ return nil if list.empty?
+
+ list
+ 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
+ p :paragraph_start => margin if @debug
+
+ paragraph = RDoc::Markup::Paragraph.new
+
+ 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
+
+ paragraph << data
+ skip :NEWLINE
+ else
+ unget
+ break
+ end
+ end
+
+ p :paragraph_end => margin if @debug
+
+ paragraph
+ end
+
+ ##
+ # Builds a Verbatim that is flush to +margin+
+
+ def build_verbatim margin
+ p :verbatim_begin => margin if @debug
+ verbatim = RDoc::Markup::Verbatim.new
+
+ until @tokens.empty? do
+ type, data, column, = get
+
+ case type
+ when :INDENT then
+ if margin >= data then
+ unget
+ break
+ end
+
+ indent = data - margin
+
+ verbatim << ' ' * indent
+ when :HEADER then
+ verbatim << '=' * data
+
+ _, _, peek_column, = peek_token
+ peek_column ||= column + data
+ verbatim << ' ' * (peek_column - column - data)
+ when :RULE then
+ width = 2 + data
+ verbatim << '-' * width
+
+ _, _, peek_column, = peek_token
+ peek_column ||= column + data + 2
+ verbatim << ' ' * (peek_column - column - width)
+ when :TEXT then
+ verbatim << data
+ when *LIST_TOKENS then
+ if column <= margin then
+ unget
+ break
+ end
+
+ list_marker = case type
+ when :BULLET then '*'
+ when :LABEL then "[#{data}]"
+ when :LALPHA, :NUMBER, :UALPHA then "#{data}."
+ when :NOTE then "#{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
+ end
+ end
+
+ verbatim.normalize
+
+ p :verbatim_end => margin if @debug
+
+ verbatim
+ 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 a Document
+
+ def parse indent = 0
+ p :parse_start => indent if @debug
+
+ document = []
+
+ until @tokens.empty? do
+ type, data, column, = get
+
+ if type != :INDENT and column < indent then
+ unget
+ break
+ end
+
+ 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
+ when :RULE then
+ document << 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
+ 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
+ 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
+
+ document
+ 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
+
+ ##
+ # Skips a token of +token_type+, optionally raising an error.
+
+ def skip token_type, error = true
+ type, data, = 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]
+ end
+
+ ##
+ # Turns text +input+ into a stream of tokens
+
+ def tokenize input
+ s = StringScanner.new input
+
+ @line = 0
+ @line_pos = 0
+
+ until s.eos? do
+ pos = s.pos
+
+ @tokens << case
+ 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)]
+ 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
+ [: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
+ 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)]
+ when s.scan(/\[(.*?)\]( +|$)/) then
+ @tokens << [:LABEL, s[1], *token_pos(pos)]
+ [:SPACE, s.matched_size, *token_pos(pos)]
+ when s.scan(/(.*?)::( +|$)/) then
+ @tokens << [:NOTE, s[1], *token_pos(pos)]
+ [:SPACE, s.matched_size, *token_pos(pos)]
+ else s.scan(/.*/)
+ [:TEXT, s.matched, *token_pos(pos)]
+ end
+ end
+
+ self
+ end
+
+ ##
+ # Returns the current token or +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
+
+require 'rdoc/markup/blank_line'
+require 'rdoc/markup/document'
+require 'rdoc/markup/heading'
+require 'rdoc/markup/list'
+require 'rdoc/markup/list_item'
+require 'rdoc/markup/paragraph'
+require 'rdoc/markup/rule'
+require 'rdoc/markup/verbatim'
+
diff --git a/lib/rdoc/markup/preprocess.rb b/lib/rdoc/markup/preprocess.rb
index 00dd4be4ad..a175d179cf 100644
--- a/lib/rdoc/markup/preprocess.rb
+++ b/lib/rdoc/markup/preprocess.rb
@@ -7,6 +7,10 @@ require 'rdoc/markup'
class RDoc::Markup::PreProcess
+ ##
+ # 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
@@ -44,15 +48,16 @@ class RDoc::Markup::PreProcess
def include_file(name, indent)
if full_name = find_include_file(name) then
- content = File.open(full_name) {|f| f.read}
+ content = File.read full_name
+
# strip leading '#'s, but only if all lines start with them
- if content =~ /^[^#]/
+ if content =~ /^[^#]/ then
content.gsub(/^/, indent)
else
content.gsub(/^#?/, indent)
end
else
- $stderr.puts "Couldn't find file to include: '#{name}'"
+ $stderr.puts "Couldn't find file to include '#{name}' from #{@input_file_name}"
''
end
end
diff --git a/lib/rdoc/markup/rule.rb b/lib/rdoc/markup/rule.rb
new file mode 100644
index 0000000000..4fcd040d2b
--- /dev/null
+++ b/lib/rdoc/markup/rule.rb
@@ -0,0 +1,17 @@
+##
+# A horizontal rule with a weight
+
+class RDoc::Markup::Rule < Struct.new :weight
+
+ 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/lib/rdoc/markup/to_ansi.rb b/lib/rdoc/markup/to_ansi.rb
new file mode 100644
index 0000000000..9a5be8babb
--- /dev/null
+++ b/lib/rdoc/markup/to_ansi.rb
@@ -0,0 +1,72 @@
+require 'rdoc/markup/inline'
+
+##
+# Outputs RDoc markup with vibrant ANSI color!
+
+class RDoc::Markup::ToAnsi < RDoc::Markup::ToRdoc
+
+ def initialize
+ super
+
+ @headings.clear
+ @headings[1] = ["\e[1;32m", "\e[m"]
+ @headings[2] = ["\e[4;32m", "\e[m"]
+ @headings[3] = ["\e[32m", "\e[m"]
+ 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
+
+ def accept_list_item_end list_item
+ width = case @list_type.last
+ when :BULLET then
+ 2
+ when :NOTE, :LABEL then
+ @res << "\n"
+ 2
+ else
+ bullet = @list_index.last.to_s
+ @list_index[-1] = @list_index.last.succ
+ bullet.length + 2
+ end
+
+ @indent -= width
+ end
+
+ 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
+ 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
+
+ def start_accepting
+ super
+
+ @res = ["\e[0m"]
+ end
+
+end
+
diff --git a/lib/rdoc/markup/to_bs.rb b/lib/rdoc/markup/to_bs.rb
new file mode 100644
index 0000000000..e7af129824
--- /dev/null
+++ b/lib/rdoc/markup/to_bs.rb
@@ -0,0 +1,74 @@
+require 'rdoc/markup/inline'
+
+##
+# 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
+
+ def initialize
+ 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, '+_', '-_'
+ end
+
+ 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/lib/rdoc/markup/to_flow.rb b/lib/rdoc/markup/to_flow.rb
deleted file mode 100644
index 3d87b3e9c3..0000000000
--- a/lib/rdoc/markup/to_flow.rb
+++ /dev/null
@@ -1,185 +0,0 @@
-require 'rdoc/markup/formatter'
-require 'rdoc/markup/fragments'
-require 'rdoc/markup/inline'
-require 'cgi'
-
-class RDoc::Markup
-
- module Flow
- P = Struct.new(:body)
- VERB = Struct.new(:body)
- RULE = Struct.new(:width)
- class LIST
- attr_reader :type, :contents
- def initialize(type)
- @type = type
- @contents = []
- end
- def <<(stuff)
- @contents << stuff
- end
- end
- LI = Struct.new(:label, :body)
- H = Struct.new(:level, :text)
- end
-
- class ToFlow < RDoc::Markup::Formatter
- LIST_TYPE_TO_HTML = {
- :BULLET => [ "<ul>", "</ul>" ],
- :NUMBER => [ "<ol>", "</ol>" ],
- :UPPERALPHA => [ "<ol>", "</ol>" ],
- :LOWERALPHA => [ "<ol>", "</ol>" ],
- :LABELED => [ "<dl>", "</dl>" ],
- :NOTE => [ "<table>", "</table>" ],
- }
-
- InlineTag = Struct.new(:bit, :on, :off)
-
- def initialize
- super
-
- init_tags
- end
-
- ##
- # Set up the standard mapping of attributes to HTML tags
-
- def init_tags
- @attr_tags = [
- InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:BOLD), "<b>", "</b>"),
- InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:TT), "<tt>", "</tt>"),
- InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:EM), "<em>", "</em>"),
- ]
- end
-
- ##
- # Add a new set of HTML tags for an attribute. We allow separate start and
- # end tags for flexibility
-
- def add_tag(name, start, stop)
- @attr_tags << InlineTag.new(RDoc::Markup::Attribute.bitmap_for(name), start, stop)
- end
-
- ##
- # Given an HTML tag, decorate it with class information and the like if
- # required. This is a no-op in the base class, but is overridden in HTML
- # output classes that implement style sheets
-
- def annotate(tag)
- tag
- end
-
- ##
- # Here's the client side of the visitor pattern
-
- def start_accepting
- @res = []
- @list_stack = []
- end
-
- def end_accepting
- @res
- end
-
- def accept_paragraph(am, fragment)
- @res << Flow::P.new((convert_flow(am.flow(fragment.txt))))
- end
-
- def accept_verbatim(am, fragment)
- @res << Flow::VERB.new((convert_flow(am.flow(fragment.txt))))
- end
-
- def accept_rule(am, fragment)
- size = fragment.param
- size = 10 if size > 10
- @res << Flow::RULE.new(size)
- end
-
- def accept_list_start(am, fragment)
- @list_stack.push(@res)
- list = Flow::LIST.new(fragment.type)
- @res << list
- @res = list
- end
-
- def accept_list_end(am, fragment)
- @res = @list_stack.pop
- end
-
- def accept_list_item(am, fragment)
- @res << Flow::LI.new(fragment.param, convert_flow(am.flow(fragment.txt)))
- end
-
- def accept_blank_line(am, fragment)
- # @res << annotate("<p />") << "\n"
- end
-
- def accept_heading(am, fragment)
- @res << Flow::H.new(fragment.head_level, convert_flow(am.flow(fragment.txt)))
- end
-
- private
-
- 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
- res << annotate(tag.on)
- end
- end
- end
-
- 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
- res << annotate(tag.off)
- end
- end
- end
-
- def convert_flow(flow)
- res = ""
- flow.each do |item|
- case item
- when String
- res << convert_string(item)
- when AttrChanger
- off_tags(res, item)
- on_tags(res, item)
- when Special
- res << convert_special(item)
- else
- raise "Unknown flow element: #{item.inspect}"
- end
- end
- res
- end
-
- def convert_string(item)
- CGI.escapeHTML(item)
- end
-
- def convert_special(special)
- handled = false
- Attribute.each_name_of(special.type) do |name|
- method_name = "handle_special_#{name}"
- if self.respond_to? method_name
- special.text = send(method_name, special)
- handled = true
- end
- end
-
- raise "Unhandled special: #{special}" unless handled
-
- special.text
- end
-
- end
-
-end
-
diff --git a/lib/rdoc/markup/to_html.rb b/lib/rdoc/markup/to_html.rb
index 0165042d84..f060fd1c72 100644
--- a/lib/rdoc/markup/to_html.rb
+++ b/lib/rdoc/markup/to_html.rb
@@ -1,38 +1,28 @@
require 'rdoc/markup/formatter'
-require 'rdoc/markup/fragments'
require 'rdoc/markup/inline'
require 'cgi'
+##
+# Outputs RDoc markup as HTML
+
class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
+ ##
+ # Maps RDoc::Markup::Parser::LIST_TOKENS types to HTML tags
+
LIST_TYPE_TO_HTML = {
- :BULLET => %w[<ul> </ul>],
- :NUMBER => %w[<ol> </ol>],
- :UPPERALPHA => %w[<ol> </ol>],
- :LOWERALPHA => %w[<ol> </ol>],
- :LABELED => %w[<dl> </dl>],
- :NOTE => %w[<table> </table>],
+ :BULLET => ['<ul>', '</ul>'],
+ :LABEL => ['<dl>', '</dl>'],
+ :LALPHA => ['<ol style="display: lower-alpha">', '</ol>'],
+ :NOTE => ['<table>', '</table>'],
+ :NUMBER => ['<ol>', '</ol>'],
+ :UALPHA => ['<ol style="display: upper-alpha">', '</ol>'],
}
- InlineTag = Struct.new(:bit, :on, :off)
-
- def initialize
- super
-
- # @in_tt - tt nested levels count
- # @tt_bit - cache
- @in_tt = 0
- @tt_bit = RDoc::Markup::Attribute.bitmap_for :TT
-
- # external hyperlinks
- @markup.add_special(/((link:|https?:|mailto:|ftp:|www\.)\S+\w)/, :HYPERLINK)
-
- # and links of the form <text>[<url>]
- @markup.add_special(/(((\{.*?\})|\b\S+?)\[\S+?\.\S+?\])/, :TIDYLINK)
-
- init_tags
- end
+ attr_reader :res # :nodoc:
+ attr_reader :in_list_entry # :nodoc:
+ attr_reader :list # :nodoc:
##
# Converts a target url to one that is relative to a given path
@@ -44,6 +34,9 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
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
@@ -55,6 +48,31 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
File.join(*from)
end
+ def initialize
+ super
+
+ @th = nil
+ @in_list_entry = nil
+ @list = nil
+
+ # external hyperlinks
+ @markup.add_special(/((link:|https?:|mailto:|ftp:|www\.)\S+\w)/, :HYPERLINK)
+
+ # and links of the form <text>[<url>]
+ @markup.add_special(/(((\{.*?\})|\b\S+?)\[\S+?\.\S+?\])/, :TIDYLINK)
+
+ init_tags
+ end
+
+ ##
+ # Maps attributes to HTML tags
+
+ def init_tags
+ add_tag :BOLD, "<b>", "</b>"
+ add_tag :TT, "<tt>", "</tt>"
+ add_tag :EM, "<em>", "</em>"
+ end
+
##
# Generate a hyperlink for url, labeled with text. Handle the
# special cases for img: and link: described under handle_special_HYPERLINK
@@ -113,195 +131,121 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
end
##
- # are we currently inside <tt> tags?
-
- def in_tt?
- @in_tt > 0
- end
-
- ##
- # is +tag+ a <tt> tag?
-
- def tt?(tag)
- tag.bit == @tt_bit
- end
-
- ##
- # Set up the standard mapping of attributes to HTML tags
-
- def init_tags
- @attr_tags = [
- InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:BOLD), "<b>", "</b>"),
- InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:TT), "<tt>", "</tt>"),
- InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:EM), "<em>", "</em>"),
- ]
- end
-
- ##
- # Add a new set of HTML tags for an attribute. We allow separate start and
- # end tags for flexibility.
+ # This is a higher speed (if messier) version of wrap
- def add_tag(name, start, stop)
- @attr_tags << InlineTag.new(RDoc::Markup::Attribute.bitmap_for(name), start, stop)
- end
+ def wrap(txt, line_len = 76)
+ res = []
+ sp = 0
+ ep = txt.length
- ##
- # Given an HTML tag, decorate it with class information and the like if
- # required. This is a no-op in the base class, but is overridden in HTML
- # output classes that implement style sheets.
+ while sp < ep
+ # scan back for a space
+ p = sp + line_len - 1
+ if p >= ep
+ p = ep
+ else
+ while p > sp and txt[p] != ?\s
+ p -= 1
+ end
+ if p <= sp
+ p = sp + line_len
+ while p < ep and txt[p] != ?\s
+ p += 1
+ end
+ end
+ end
+ res << txt[sp...p] << "\n"
+ sp = p
+ sp += 1 while sp < ep and txt[sp] == ?\s
+ end
- def annotate(tag)
- tag
+ res.join
end
##
- # Here's the client side of the visitor pattern
+ # :section: Visitor
def start_accepting
- @res = ""
+ @res = []
@in_list_entry = []
+ @list = []
end
def end_accepting
- @res
+ @res.join
end
- def accept_paragraph(am, fragment)
+ def accept_paragraph(paragraph)
@res << annotate("<p>") + "\n"
- @res << wrap(convert_flow(am.flow(fragment.txt)))
+ @res << wrap(convert_flow(@am.flow(paragraph.text)))
@res << annotate("</p>") + "\n"
end
- def accept_verbatim(am, fragment)
- @res << annotate("<pre>") + "\n"
- @res << CGI.escapeHTML(fragment.txt)
+ def accept_verbatim(verbatim)
+ @res << annotate("<pre>") << "\n"
+ @res << CGI.escapeHTML(verbatim.text)
@res << annotate("</pre>") << "\n"
end
- def accept_rule(am, fragment)
- size = fragment.param
+ def accept_rule(rule)
+ size = rule.weight
size = 10 if size > 10
- @res << "<hr size=\"#{size}\"></hr>"
+ @res << "<hr style=\"height: #{size}px\"></hr>"
end
- def accept_list_start(am, fragment)
- @res << html_list_name(fragment.type, true) << "\n"
+ def accept_list_start(list)
+ @list << list.type
+ @res << html_list_name(list.type, true) << "\n"
@in_list_entry.push false
end
- def accept_list_end(am, fragment)
+ def accept_list_end(list)
+ @list.pop
if tag = @in_list_entry.pop
@res << annotate(tag) << "\n"
end
- @res << html_list_name(fragment.type, false) << "\n"
+ @res << html_list_name(list.type, false) << "\n"
end
- def accept_list_item(am, fragment)
+ def accept_list_item_start(list_item)
if tag = @in_list_entry.last
@res << annotate(tag) << "\n"
end
- @res << list_item_start(am, fragment)
-
- @res << wrap(convert_flow(am.flow(fragment.txt))) << "\n"
+ @res << list_item_start(list_item, @list.last)
+ end
- @in_list_entry[-1] = list_end_for(fragment.type)
+ def accept_list_item_end(list_item)
+ @in_list_entry[-1] = list_end_for(@list.last)
end
- def accept_blank_line(am, fragment)
+ def accept_blank_line(blank_line)
# @res << annotate("<p />") << "\n"
end
- def accept_heading(am, fragment)
- @res << convert_heading(fragment.head_level, am.flow(fragment.txt))
- end
-
- ##
- # This is a higher speed (if messier) version of wrap
-
- def wrap(txt, line_len = 76)
- res = ""
- sp = 0
- ep = txt.length
- while sp < ep
- # scan back for a space
- p = sp + line_len - 1
- if p >= ep
- p = ep
- else
- while p > sp and txt[p] != ?\s
- p -= 1
- end
- if p <= sp
- p = sp + line_len
- while p < ep and txt[p] != ?\s
- p += 1
- end
- end
- end
- res << txt[sp...p] << "\n"
- sp = p
- sp += 1 while sp < ep and txt[sp] == ?\s
- end
- res
+ def accept_heading(heading)
+ @res << convert_heading(heading.level, @am.flow(heading.text))
end
private
- 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
- res << annotate(tag.on)
- @in_tt += 1 if tt?(tag)
- end
- end
- end
-
- 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
- @in_tt -= 1 if tt?(tag)
- res << annotate(tag.off)
- end
- end
- end
-
- def convert_flow(flow)
- res = ""
-
- flow.each do |item|
- case item
- when String
- res << convert_string(item)
- when RDoc::Markup::AttrChanger
- off_tags(res, item)
- on_tags(res, item)
- when RDoc::Markup::Special
- res << convert_special(item)
- else
- raise "Unknown flow element: #{item.inspect}"
- end
- end
-
- res
- end
+ ##
+ # Converts string +item+
def convert_string(item)
in_tt? ? convert_string_simple(item) : convert_string_fancy(item)
end
+ ##
+ # Escapes HTML in +item+
+
def convert_string_simple(item)
CGI.escapeHTML item
end
##
- # some of these patterns are taken from SmartyPants...
+ # 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
@@ -333,69 +277,62 @@ class RDoc::Markup::ToHtml < RDoc::Markup::Formatter
gsub(/\(r\)/, '&#174;')
end
- def convert_special(special)
- handled = false
- RDoc::Markup::Attribute.each_name_of(special.type) do |name|
- method_name = "handle_special_#{name}"
- if self.respond_to? method_name
- special.text = send(method_name, special)
- handled = true
- end
- end
- raise "Unhandled special: #{special}" unless handled
- special.text
- end
+ ##
+ # Converts headings to hN elements
def convert_heading(level, flow)
- res =
- annotate("<h#{level}>") +
- convert_flow(flow) +
- annotate("</h#{level}>\n")
+ [annotate("<h#{level}>"),
+ convert_flow(flow),
+ annotate("</h#{level}>\n")].join
end
- def html_list_name(list_type, is_open_tag)
- tags = LIST_TYPE_TO_HTML[list_type] || raise("Invalid list type: #{list_type.inspect}")
- annotate(tags[ is_open_tag ? 0 : 1])
- end
+ ##
+ # Determins the HTML list element for +list_type+ and +open_tag+
- def list_item_start(am, fragment)
- case fragment.type
- when :BULLET, :NUMBER then
- annotate("<li>")
+ 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]
+ end
- when :UPPERALPHA then
- annotate("<li type=\"A\">")
+ ##
+ # Starts a list item
- when :LOWERALPHA then
- annotate("<li type=\"a\">")
+ def list_item_start(list_item, list_type)
+ case list_type
+ when :BULLET, :LALPHA, :NUMBER, :UALPHA then
+ annotate("<li>")
- when :LABELED then
+ when :LABEL then
annotate("<dt>") +
- convert_flow(am.flow(fragment.param)) +
+ convert_flow(@am.flow(list_item.label)) +
annotate("</dt>") +
annotate("<dd>")
when :NOTE then
annotate("<tr>") +
annotate("<td valign=\"top\">") +
- convert_flow(am.flow(fragment.param)) +
+ convert_flow(@am.flow(list_item.label)) +
annotate("</td>") +
annotate("<td>")
else
- raise "Invalid list type"
+ raise RDoc::Error, "Invalid list type: #{list_type.inspect}"
end
end
- def list_end_for(fragment_type)
- case fragment_type
- when :BULLET, :NUMBER, :UPPERALPHA, :LOWERALPHA then
+ ##
+ # Ends a list item
+
+ def list_end_for(list_type)
+ case list_type
+ when :BULLET, :LALPHA, :NUMBER, :UALPHA then
"</li>"
- when :LABELED then
+ when :LABEL then
"</dd>"
when :NOTE then
"</td></tr>"
else
- raise "Invalid list type"
+ raise RDoc::Error, "Invalid list type: #{list_type.inspect}"
end
end
diff --git a/lib/rdoc/markup/to_html_crossref.rb b/lib/rdoc/markup/to_html_crossref.rb
index 930218bb7e..1f62ee04f9 100644
--- a/lib/rdoc/markup/to_html_crossref.rb
+++ b/lib/rdoc/markup/to_html_crossref.rb
@@ -1,44 +1,35 @@
require 'rdoc/markup/to_html'
##
-# Subclass of the RDoc::Markup::ToHtml class that supports looking up words in
-# the AllReferences list. Those that are found (like AllReferences in this
-# comment) will be hyperlinked
+# Subclass of the RDoc::Markup::ToHtml class that supports looking up words
+# from a context. Those that are found will be hyperlinked.
class RDoc::Markup::ToHtmlCrossref < RDoc::Markup::ToHtml
- attr_accessor :context
-
- # Regular expressions to match class and method references.
- #
- # 1.) There can be a '\' in front of text to suppress
- # any cross-references (note, however, that the single '\'
- # is written as '\\\\' in order to escape it twice, once
- # in the Ruby String literal and once in the regexp).
- # 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,
- # which may or may not have things inside (this
- # apparently is allowed for Fortran 95, but I also think that this
- # is a good idea for Ruby, as it is very reasonable to want to
- # reference a call with arguments).
- #
- # NOTE: In order to support Fortran 95 properly, the [A-Z] below
- # should be changed to [A-Za-z]. This slows down rdoc significantly,
- # however, and the Fortran 95 support is broken in any case due to
- # the return in handle_special_CROSSREF if the token consists
- # entirely of lowercase letters.
+ ##
+ # Regular expression to match class references
#
- # The markup/cross-referencing engine needs a rewrite for
- # Fortran 95 to be supported properly.
+ # 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
+ # top-level namespace.
+ # 3) The method can be followed by parenthesis
+
CLASS_REGEXP_STR = '\\\\?((?:\:{2})?[A-Z]\w*(?:\:\:\w+)*)'
- METHOD_REGEXP_STR = '(\w+[!?=]?)(?:\([\.\w+\*\/\+\-\=\<\>]*\))?'
+ ##
+ # Regular expression to match method references.
+ #
+ # See CLASS_REGEXP_STR
+
+ METHOD_REGEXP_STR = '(\w+[!?=]?)(?:\([\w.+*/=<>-]*\))?'
+
+ ##
# Regular expressions matching text that should potentially have
- # cross-reference links generated are passed to add_special.
- # Note that these expressions are meant to pick up text for which
- # cross-references have been suppressed, since the suppression
- # characters are removed by the code that is triggered.
+ # cross-reference links generated are passed to add_special. Note that
+ # these expressions are meant to pick up text for which cross-references
+ # have been suppressed, since the suppression characters are removed by the
+ # code that is triggered.
+
CROSSREF_REGEXP = /(
# A::B::C.meth
#{CLASS_REGEXP_STR}[\.\#]#{METHOD_REGEXP_STR}
@@ -72,8 +63,14 @@ class RDoc::Markup::ToHtmlCrossref < RDoc::Markup::ToHtml
)/x
##
- # We need to record the html path of our caller so we can generate
- # correct relative paths for any hyperlinks that we find
+ # RDoc::CodeObject for generating references
+
+ attr_accessor :context
+
+ ##
+ # 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.
def initialize(from_path, context, show_hash)
raise ArgumentError, 'from_path cannot be nil' if from_path.nil?
@@ -89,19 +86,18 @@ class RDoc::Markup::ToHtmlCrossref < RDoc::Markup::ToHtml
end
##
- # We're invoked when any text matches the CROSSREF pattern
- # (defined in MarkUp). If we fine 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.
+ # We're invoked when any text matches the CROSSREF pattern (defined in
+ # MarkUp). 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.
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)
+ # 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/
return @seen[name] if @seen.include? name
@@ -113,7 +109,6 @@ class RDoc::Markup::ToHtmlCrossref < RDoc::Markup::ToHtml
lookup = name
end
-
# Find class, module, or method in class or module.
#
# Do not, however, use an if/elsif/else chain to do so. Instead, test
@@ -132,7 +127,9 @@ class RDoc::Markup::ToHtmlCrossref < RDoc::Markup::ToHtml
ref = @context.find_symbol lookup unless ref
- out = if lookup =~ /^\\/ then
+ out = if lookup == '\\' then
+ lookup
+ elsif lookup =~ /^\\/ then
$'
elsif ref and ref.document_self then
"<a href=\"#{ref.as_href(@from_path)}\">#{name}</a>"
@@ -146,3 +143,4 @@ class RDoc::Markup::ToHtmlCrossref < RDoc::Markup::ToHtml
end
end
+
diff --git a/lib/rdoc/markup/to_latex.rb b/lib/rdoc/markup/to_latex.rb
deleted file mode 100644
index bbf958f2ed..0000000000
--- a/lib/rdoc/markup/to_latex.rb
+++ /dev/null
@@ -1,328 +0,0 @@
-require 'rdoc/markup/formatter'
-require 'rdoc/markup/fragments'
-require 'rdoc/markup/inline'
-
-require 'cgi'
-
-##
-# Convert SimpleMarkup to basic LaTeX report format.
-
-class RDoc::Markup::ToLaTeX < RDoc::Markup::Formatter
-
- BS = "\020" # \
- OB = "\021" # {
- CB = "\022" # }
- DL = "\023" # Dollar
-
- BACKSLASH = "#{BS}symbol#{OB}92#{CB}"
- HAT = "#{BS}symbol#{OB}94#{CB}"
- BACKQUOTE = "#{BS}symbol#{OB}0#{CB}"
- TILDE = "#{DL}#{BS}sim#{DL}"
- LESSTHAN = "#{DL}<#{DL}"
- GREATERTHAN = "#{DL}>#{DL}"
-
- def self.l(str)
- str.tr('\\', BS).tr('{', OB).tr('}', CB).tr('$', DL)
- end
-
- def l(arg)
- RDoc::Markup::ToLaTeX.l(arg)
- end
-
- LIST_TYPE_TO_LATEX = {
- :BULLET => [ l("\\begin{itemize}"), l("\\end{itemize}") ],
- :NUMBER => [ l("\\begin{enumerate}"), l("\\end{enumerate}"), "\\arabic" ],
- :UPPERALPHA => [ l("\\begin{enumerate}"), l("\\end{enumerate}"), "\\Alph" ],
- :LOWERALPHA => [ l("\\begin{enumerate}"), l("\\end{enumerate}"), "\\alph" ],
- :LABELED => [ l("\\begin{description}"), l("\\end{description}") ],
- :NOTE => [
- l("\\begin{tabularx}{\\linewidth}{@{} l X @{}}"),
- l("\\end{tabularx}") ],
- }
-
- InlineTag = Struct.new(:bit, :on, :off)
-
- def initialize
- init_tags
- @list_depth = 0
- @prev_list_types = []
- end
-
- ##
- # Set up the standard mapping of attributes to LaTeX
-
- def init_tags
- @attr_tags = [
- InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:BOLD), l("\\textbf{"), l("}")),
- InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:TT), l("\\texttt{"), l("}")),
- InlineTag.new(RDoc::Markup::Attribute.bitmap_for(:EM), l("\\emph{"), l("}")),
- ]
- end
-
- ##
- # Escape a LaTeX string
-
- def escape(str)
- $stderr.print "FE: ", str if $DEBUG_RDOC
- s = str.
- sub(/\s+$/, '').
- gsub(/([_\${}&%#])/, "#{BS}\\1").
- gsub(/\\/, BACKSLASH).
- gsub(/\^/, HAT).
- gsub(/~/, TILDE).
- gsub(/</, LESSTHAN).
- gsub(/>/, GREATERTHAN).
- gsub(/,,/, ",{},").
- gsub(/\`/, BACKQUOTE)
- $stderr.print "-> ", s, "\n" if $DEBUG_RDOC
- s
- end
-
- ##
- # Add a new set of LaTeX tags for an attribute. We allow
- # separate start and end tags for flexibility
-
- def add_tag(name, start, stop)
- @attr_tags << InlineTag.new(RDoc::Markup::Attribute.bitmap_for(name), start, stop)
- end
-
- ##
- # Here's the client side of the visitor pattern
-
- def start_accepting
- @res = ""
- @in_list_entry = []
- end
-
- def end_accepting
- @res.tr(BS, '\\').tr(OB, '{').tr(CB, '}').tr(DL, '$')
- end
-
- def accept_paragraph(am, fragment)
- @res << wrap(convert_flow(am.flow(fragment.txt)))
- @res << "\n"
- end
-
- def accept_verbatim(am, fragment)
- @res << "\n\\begin{code}\n"
- @res << fragment.txt.sub(/[\n\s]+\Z/, '')
- @res << "\n\\end{code}\n\n"
- end
-
- def accept_rule(am, fragment)
- size = fragment.param
- size = 10 if size > 10
- @res << "\n\n\\rule{\\linewidth}{#{size}pt}\n\n"
- end
-
- def accept_list_start(am, fragment)
- @res << list_name(fragment.type, true) << "\n"
- @in_list_entry.push false
- end
-
- def accept_list_end(am, fragment)
- if tag = @in_list_entry.pop
- @res << tag << "\n"
- end
- @res << list_name(fragment.type, false) << "\n"
- end
-
- def accept_list_item(am, fragment)
- if tag = @in_list_entry.last
- @res << tag << "\n"
- end
- @res << list_item_start(am, fragment)
- @res << wrap(convert_flow(am.flow(fragment.txt))) << "\n"
- @in_list_entry[-1] = list_end_for(fragment.type)
- end
-
- def accept_blank_line(am, fragment)
- # @res << "\n"
- end
-
- def accept_heading(am, fragment)
- @res << convert_heading(fragment.head_level, am.flow(fragment.txt))
- end
-
- ##
- # This is a higher speed (if messier) version of wrap
-
- def wrap(txt, line_len = 76)
- res = ""
- sp = 0
- ep = txt.length
- while sp < ep
- # scan back for a space
- p = sp + line_len - 1
- if p >= ep
- p = ep
- else
- while p > sp and txt[p] != ?\s
- p -= 1
- end
- if p <= sp
- p = sp + line_len
- while p < ep and txt[p] != ?\s
- p += 1
- end
- end
- end
- res << txt[sp...p] << "\n"
- sp = p
- sp += 1 while sp < ep and txt[sp] == ?\s
- end
- res
- end
-
- private
-
- 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
- res << tag.on
- end
- end
- end
-
- 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
- res << tag.off
- end
- end
- end
-
- def convert_flow(flow)
- res = ""
- flow.each do |item|
- case item
- when String
- $stderr.puts "Converting '#{item}'" if $DEBUG_RDOC
- res << convert_string(item)
- when AttrChanger
- off_tags(res, item)
- on_tags(res, item)
- when Special
- res << convert_special(item)
- else
- raise "Unknown flow element: #{item.inspect}"
- end
- end
- res
- end
-
- ##
- # some of these patterns are taken from SmartyPants...
-
- def convert_string(item)
- escape(item).
-
- # convert ... to elipsis (and make sure .... becomes .<elipsis>)
- gsub(/\.\.\.\./, '.\ldots{}').gsub(/\.\.\./, '\ldots{}').
-
- # 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\)/, '\copyright{}')
-
- end
-
- def convert_special(special)
- handled = false
- Attribute.each_name_of(special.type) do |name|
- method_name = "handle_special_#{name}"
- if self.respond_to? method_name
- special.text = send(method_name, special)
- handled = true
- end
- end
- raise "Unhandled special: #{special}" unless handled
- special.text
- end
-
- def convert_heading(level, flow)
- res =
- case level
- when 1 then "\\chapter{"
- when 2 then "\\section{"
- when 3 then "\\subsection{"
- when 4 then "\\subsubsection{"
- else "\\paragraph{"
- end +
- convert_flow(flow) +
- "}\n"
- end
-
- def list_name(list_type, is_open_tag)
- tags = LIST_TYPE_TO_LATEX[list_type] || raise("Invalid list type: #{list_type.inspect}")
- if tags[2] # enumerate
- if is_open_tag
- @list_depth += 1
- if @prev_list_types[@list_depth] != tags[2]
- case @list_depth
- when 1
- roman = "i"
- when 2
- roman = "ii"
- when 3
- roman = "iii"
- when 4
- roman = "iv"
- else
- raise("Too deep list: level #{@list_depth}")
- end
- @prev_list_types[@list_depth] = tags[2]
- return l("\\renewcommand{\\labelenum#{roman}}{#{tags[2]}{enum#{roman}}}") + "\n" + tags[0]
- end
- else
- @list_depth -= 1
- end
- end
- tags[ is_open_tag ? 0 : 1]
- end
-
- def list_item_start(am, fragment)
- case fragment.type
- when :BULLET, :NUMBER, :UPPERALPHA, :LOWERALPHA then
- "\\item "
-
- when :LABELED then
- "\\item[" + convert_flow(am.flow(fragment.param)) + "] "
-
- when :NOTE then
- convert_flow(am.flow(fragment.param)) + " & "
- else
- raise "Invalid list type"
- end
- end
-
- def list_end_for(fragment_type)
- case fragment_type
- when :BULLET, :NUMBER, :UPPERALPHA, :LOWERALPHA, :LABELED then
- ""
- when :NOTE
- "\\\\\n"
- else
- raise "Invalid list type"
- end
- end
-
-end
-
diff --git a/lib/rdoc/markup/to_rdoc.rb b/lib/rdoc/markup/to_rdoc.rb
new file mode 100644
index 0000000000..97c7d0c984
--- /dev/null
+++ b/lib/rdoc/markup/to_rdoc.rb
@@ -0,0 +1,243 @@
+require 'rdoc/markup/inline'
+
+##
+# Outputs RDoc markup as RDoc markup! (mostly)
+
+class RDoc::Markup::ToRdoc < RDoc::Markup::Formatter
+
+ attr_accessor :indent
+ attr_reader :list_index
+ attr_reader :list_type
+ attr_reader :list_width
+ attr_reader :prefix
+ attr_reader :res
+
+ def initialize
+ super
+
+ @markup.add_special(/\\[^\s]/, :SUPPRESSED_CROSSREF)
+
+ @width = 78
+ @prefix = ''
+
+ init_tags
+
+ @headings = {}
+ @headings.default = []
+
+ @headings[1] = ['= ', '']
+ @headings[2] = ['== ', '']
+ @headings[3] = ['=== ', '']
+ @headings[4] = ['==== ', '']
+ @headings[5] = ['===== ', '']
+ @headings[6] = ['====== ', '']
+ end
+
+ ##
+ # Maps attributes to ANSI sequences
+
+ def init_tags
+ add_tag :BOLD, "<b>", "</b>"
+ add_tag :TT, "<tt>", "</tt>"
+ add_tag :EM, "<em>", "</em>"
+ end
+
+ def accept_blank_line blank_line
+ @res << "\n"
+ end
+
+ 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
+
+ def accept_list_end list
+ @list_index.pop
+ @list_type.pop
+ @list_width.pop
+ end
+
+ def accept_list_item_end list_item
+ width = case @list_type.last
+ when :BULLET then
+ 2
+ when :NOTE, :LABEL then
+ @res << "\n"
+ 2
+ else
+ bullet = @list_index.last.to_s
+ @list_index[-1] = @list_index.last.succ
+ bullet.length + 2
+ end
+
+ @indent -= width
+ end
+
+ 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
+ when :NOTE, :LABEL then
+ @indent += 2
+ @prefix = bullet + (' ' * @indent)
+ else
+ @prefix = (' ' * @indent) + bullet.ljust(bullet.length + 1)
+
+ width = bullet.length + 1
+
+ @indent += width
+ end
+ end
+
+ 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
+
+ def accept_paragraph paragraph
+ wrap attributes(paragraph.text)
+ end
+
+ def accept_rule rule
+ use_prefix or @res << ' ' * @indent
+ @res << '-' * (@width - @indent)
+ @res << "\n"
+ end
+
+ ##
+ # Outputs +verbatim+ flush left and 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
+ 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"
+ end
+
+ def attributes text
+ flow = @am.flow text.dup
+ convert_flow flow
+ end
+
+ def end_accepting
+ @res.join
+ end
+
+ def handle_special_SUPPRESSED_CROSSREF special
+ special.text.sub(/\\/, '')
+ end
+
+ def start_accepting
+ @res = [""]
+ @indent = 0
+ @prefix = nil
+
+ @list_index = []
+ @list_type = []
+ @list_width = []
+ end
+
+ def use_prefix
+ prefix = @prefix
+ @prefix = nil
+ @res << prefix if prefix
+
+ prefix
+ end
+
+ 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/lib/rdoc/markup/to_test.rb b/lib/rdoc/markup/to_test.rb
index ce6aff6e9a..0afdb96a18 100644
--- a/lib/rdoc/markup/to_test.rb
+++ b/lib/rdoc/markup/to_test.rb
@@ -6,44 +6,58 @@ require 'rdoc/markup/formatter'
class RDoc::Markup::ToTest < RDoc::Markup::Formatter
+ ##
+ # :section: Visitor
+
def start_accepting
@res = []
+ @list = []
end
def end_accepting
@res
end
- def accept_paragraph(am, fragment)
- @res << fragment.to_s
+ def accept_paragraph(paragraph)
+ @res << paragraph.text
+ end
+
+ def accept_verbatim(verbatim)
+ @res << verbatim.text
end
- def accept_verbatim(am, fragment)
- @res << fragment.to_s
+ def accept_list_start(list)
+ @list << case list.type
+ when :BULLET then
+ '*'
+ when :NUMBER then
+ '1'
+ else
+ list.type
+ end
end
- def accept_list_start(am, fragment)
- @res << fragment.to_s
+ def accept_list_end(list)
+ @list.pop
end
- def accept_list_end(am, fragment)
- @res << fragment.to_s
+ def accept_list_item_start(list_item)
+ @res << "#{' ' * (@list.size - 1)}#{@list.last}: "
end
- def accept_list_item(am, fragment)
- @res << fragment.to_s
+ def accept_list_item_end(list_item)
end
- def accept_blank_line(am, fragment)
- @res << fragment.to_s
+ def accept_blank_line(blank_line)
+ @res << "\n"
end
- def accept_heading(am, fragment)
- @res << fragment.to_s
+ def accept_heading(heading)
+ @res << "#{'=' * heading.level} #{heading.text}"
end
- def accept_rule(am, fragment)
- @res << fragment.to_s
+ def accept_rule(rule)
+ @res << '-' * rule.weight
end
end
diff --git a/lib/rdoc/markup/to_texinfo.rb b/lib/rdoc/markup/to_texinfo.rb
deleted file mode 100644
index 65a1608c4d..0000000000
--- a/lib/rdoc/markup/to_texinfo.rb
+++ /dev/null
@@ -1,69 +0,0 @@
-require 'rdoc/markup/formatter'
-require 'rdoc/markup/fragments'
-require 'rdoc/markup/inline'
-
-require 'rdoc/markup'
-require 'rdoc/markup/formatter'
-
-##
-# Convert SimpleMarkup to basic TexInfo format
-#
-# TODO: WTF is AttributeManager for?
-#
-class RDoc::Markup::ToTexInfo < RDoc::Markup::Formatter
-
- def start_accepting
- @text = []
- end
-
- def end_accepting
- @text.join("\n")
- end
-
- def accept_paragraph(attributes, text)
- @text << format(text)
- end
-
- def accept_verbatim(attributes, text)
- @text << "@verb{|#{format(text)}|}"
- end
-
- def accept_heading(attributes, text)
- heading = ['@majorheading', '@chapheading'][text.head_level - 1] || '@heading'
- @text << "#{heading} #{format(text)}"
- end
-
- def accept_list_start(attributes, text)
- @text << '@itemize @bullet'
- end
-
- def accept_list_end(attributes, text)
- @text << '@end itemize'
- end
-
- def accept_list_item(attributes, text)
- @text << "@item\n#{format(text)}"
- end
-
- def accept_blank_line(attributes, text)
- @text << "\n"
- end
-
- def accept_rule(attributes, text)
- @text << '-----'
- end
-
- def format(text)
- text.txt.
- gsub(/@/, "@@").
- gsub(/\{/, "@{").
- gsub(/\}/, "@}").
- # gsub(/,/, "@,"). # technically only required in cross-refs
- gsub(/\+([\w]+)\+/, "@code{\\1}").
- gsub(/\<tt\>([^<]+)\<\/tt\>/, "@code{\\1}").
- gsub(/\*([\w]+)\*/, "@strong{\\1}").
- gsub(/\<b\>([^<]+)\<\/b\>/, "@strong{\\1}").
- gsub(/_([\w]+)_/, "@emph{\\1}").
- gsub(/\<em\>([^<]+)\<\/em\>/, "@emph{\\1}")
- end
-end
diff --git a/lib/rdoc/markup/verbatim.rb b/lib/rdoc/markup/verbatim.rb
new file mode 100644
index 0000000000..faf539a723
--- /dev/null
+++ b/lib/rdoc/markup/verbatim.rb
@@ -0,0 +1,42 @@
+##
+# A section of verbatim text
+
+class RDoc::Markup::Verbatim < RDoc::Markup::Paragraph
+
+ 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 /\n/ then
+ newlines += 1
+ parts << part if newlines <= 2
+ else
+ newlines = 0
+ parts << part
+ end
+ end
+
+ parts.slice!(-1) if parts[-2..-1] == ["\n", "\n"]
+
+ @parts = parts
+ end
+
+ ##
+ # The text of the section
+
+ def text
+ @parts.join
+ end
+
+end
+