Решение на Пета задача от Нено Ганчев

Обратно към всички решения

Към профила на Нено Ганчев

Резултати

  • 8 точки от тестове
  • 0 бонус точки
  • 8 точки общо
  • 56 успешни тест(а)
  • 1 неуспешни тест(а)

Код

class Formatter
def initialize(markdown_text)
@markdown = markdown_text
@html = nil
end
def to_html
if @html == nil
formatted_lines = @markdown.lines.map { |line| Line.recognize_type(line) }
merge_adjacent_lines_of_the_same_type(formatted_lines)
@html = formatted_lines.map { |line| line.to_html }.inject(:+).strip
end
@html
end
alias :to_s :to_html
def inspect
@markdown
end
private
def merge_adjacent_lines_of_the_same_type(formatted_lines)
prev_line = nil
formatted_lines.keep_if do |line|
prev_line_copy, prev_line = prev_line, line
merged = (prev_line_copy and prev_line_copy.merge(line))
prev_line = prev_line_copy if merged # the current line will be removed fr,SKEPTIC,!
# so restore the reference to the lin,SKEPTIC,!
not merged
end
end
#
# The apply_html_formatting function in the HtmlFormatting module is responsib,SKEPTIC,!
# symbols and sequences with their corresponding html tags within the confines,SKEPTIC,!
#
module HtmlFormatting
def apply_html_formatting(text, *options)
return "" if text.empty?
match_data = SpecialSequence.match(text, options)
return match_data.pre_match +
match_data.matched_sequence.to_html +
apply_html_formatting(match_data.post_match)
end
class SequenceMatchData
def initialize(pre_match, sequence, post_match)
@pre_match, @matched_sequence, @post_match = pre_match, sequence, post_match
end
def match_offset
@pre_match.length
end
attr_reader :pre_match, :matched_sequence, :post_match
end
class SpecialSequence
include HtmlFormatting
@children = []
def self.inherited(child)
@children << child
end
# Finds the first special sequence in the text and splits the text in 3 pieces:
# 1. the processed text before the special sequence, which can safely be p,SKEPTIC,!
# 2. the special sequence itself
# 3. the unprocessed text after the special sequence, which must still be ,SKEPTIC,!
def self.match(text, options)
matched_sequences = @children.map { |seq| seq.match(text, options) }
matched_sequences << SequenceMatchData.new(text, NoSequence.new, "") # i,SKEPTIC,!
matched_sequences.reject { |match| match == nil }.min_by(&:match_offset)
end
end
# The safety net: this sequence always matches
class NoSequence < SpecialSequence
def self.match(text, options)
SequenceMatchData.new(text, self.new, "")
end
def to_html
""
end
end
class LinkSequence < SpecialSequence
@@LinkPattern = /\[(?<name>[^\]]+?)\]\((?<url>[^\)]+?)\)/
def initialize(name, url)
@name, @url = name, url
end
def self.match(text, options)
return nil if options.include? :ignore_link
match = @@LinkPattern.match(text)
if match
SequenceMatchData.new(match.pre_match,
self.new(match[:name], match[:url]),
match.post_match)
else
nil
end
end
def to_html
"<a href=\"#{apply_html_formatting @url}\">#{apply_html_formatting @name}</a>"
end
end
class BoldSequence < SpecialSequence
def initialize(bold_text)
@bold_text = bold_text
end
def self.match(text, options)
return nil if options.include? :ignore_bold
match = /\*\*(?<bold>.+?)\*\*/.match(text)
if match
SequenceMatchData.new(match.pre_match, self.new(match[:bold]), match.post_match)
else
nil
end
end
def to_html
"<strong>#{apply_html_formatting @bold_text}</strong>"
end
end
class ItalicSequence < SpecialSequence
def initialize(italic_text)
@italic_text = italic_text
end
def self.match(text, options)
return nil if options.include? :ignore_italic
m = /_(?<italic>.+?)_/.match(text)
if m
SequenceMatchData.new(m.pre_match, self.new(m[:italic]), m.post_match)
else
nil
end
end
def to_html
"<em>#{apply_html_formatting @italic_text}</em>"
end
end
class AmpersandSequence < SpecialSequence
def self.match(text, options)
match = /&/.match text
if match
SequenceMatchData.new(match.pre_match, self.new, match.post_match)
else
nil
end
end
def to_html
"&amp;"
end
end
class LessThanSequence < SpecialSequence
def self.match(text, options)
match = /</.match text
if match
SequenceMatchData.new(match.pre_match, self.new, match.post_match)
else
nil
end
end
def to_html
"&lt;"
end
end
class GreaterThanSequence < SpecialSequence
def self.match(text, options)
match = />/.match text
if match
SequenceMatchData.new(match.pre_match, self.new, match.post_match)
else
nil
end
end
def to_html
"&gt;"
end
end
class QuotesSequence < SpecialSequence
def self.match(text, options)
match = /"/.match text
if match
SequenceMatchData.new(match.pre_match, self.new, match.post_match)
else
nil
end
end
def to_html
"&quot;"
end
end
end
#
# The Line hierarchy of classes is responsible for recognizing the types of wh,SKEPTIC,!
#
class Line
include HtmlFormatting
@children = []
def self.inherited(child)
@children << child
end
def self.recognize_type(line)
@children.each { |child| return child.new(line) if child.recognize?(line) }
raise "No line type recognizes line '#{line}'"
end
end
class HeaderLine < Line
@@HeaderPattern = /^\s{0,3}\#{1,4}\s+/
def initialize(line)
@header_text = apply_html_formatting @@HeaderPattern.match(line).post_match
@header_level = 4.downto(1) { |n| break n if line.lstrip.start_with?("#" * n) }
end
def self.recognize?(line)
match = @@HeaderPattern.match(line)
match != nil and not match.post_match.empty?
end
def to_html
"<h#{@header_level}>#{@header_text.strip}</h#{@header_level}>\n"
end
def merge(another_line)
false # adjacent header lines are not merged
end
end
class CodeBlockLine < Line
@@CodeBlockPattern = /^ /
def initialize(line)
@code_text = apply_html_formatting @@CodeBlockPattern.match(line).post_match,
:ignore_link, :ignore_bold, :ignore_italic
end
def self.recognize?(line)
@@CodeBlockPattern.match(line)
end
def to_html
"<pre><code>#{@code_text.chomp}</code></pre>\n"
end
def merge(another_line)
return false if another_line.class != self.class
@code_text << another_line.code_text
end
protected
attr_reader :code_text
end
class QuoteLine < Line
@@QuotePattern = /^\s{0,3}>\s/
def initialize(line)
@quoted_text = @@QuotePattern.match(line).post_match
end
def self.recognize?(line)
@@QuotePattern.match(line)
end
def to_html
formatted_quote = Formatter.new(@quoted_text).to_html
"<blockquote>#{formatted_quote.chomp}</blockquote>\n"
end
def merge(another_line)
return false if another_line.class != self.class
@quoted_text << another_line.quoted_text
end
protected
attr_reader :quoted_text
end
class OrderedListLine < Line
@@OrderedListPattern = /^\s{0,3}\d+\. /
def initialize(line)
@items = []
@items << apply_html_formatting(@@OrderedListPattern.match(line).post_match)
end
def self.recognize?(line)
match = @@OrderedListPattern.match(line)
match != nil and not match.post_match.empty?
end
def to_html
html = "<ol>\n"
@items.each { |item| html << " <li>#{item.chomp}</li>\n" }
html << "</ol>\n"
end
def merge(another_line)
return false if another_line.class != self.class
@items.concat another_line.items
end
protected
attr_reader :items
end
class UnorderedListLine < Line
@@UnorderedListPattern = /^\s{0,3}\* /
def initialize(line)
@items = []
@items << apply_html_formatting(@@UnorderedListPattern.match(line).post_match)
end
def self.recognize?(line)
match = @@UnorderedListPattern.match(line)
match != nil and not match.post_match.empty?
end
def to_html
html = "<ul>\n"
@items.each { |item| html << " <li>#{item.chomp}</li>\n" }
html << "</ul>\n"
end
def merge(another_line)
return false if another_line.class != self.class
@items.concat another_line.items
end
protected
attr_reader :items
end
# N.B.: EmptyLine must be defined after CodeBlock, because it would recognize ,SKEPTIC,!
class EmptyLine < Line
@@EmptyPattern = /^\s*$/
def initialize(line)
@empty_lines = "\n"
end
def self.recognize?(line)
@@EmptyPattern.match(line)
end
def to_html
@empty_lines
end
def merge(another_line)
return false if another_line.class != self.class
@empty_lines << another_line.empty_lines
end
protected
attr_reader :empty_lines
end
# N.B.: ParagraphLine must be defined last because it matches every line so it,SKEPTIC,!
class ParagraphLine < Line
def initialize(line)
@paragraph_text = apply_html_formatting line
end
def self.recognize?(line)
true # if the other line types didn't recognize this line, it must be a paragraph
end
def to_html
"<p>#{@paragraph_text.strip}</p>\n"
end
def merge(another_line)
return false if another_line.class != self.class
@paragraph_text << another_line.paragraph_text
end
protected
attr_reader :paragraph_text
end
end

Лог от изпълнението

...F.....................................................

Failures:

  1) Formatter paragraphs does not render empty paragraphs
     Failure/Error: Formatter.new(plain).to_html.should eq formatted.strip
     NoMethodError:
       undefined method `strip' for nil:NilClass
     # /tmp/d20111129-16859-16ldrs6/solution.rb:11:in `to_html'
     # /tmp/d20111129-16859-16ldrs6/spec.rb:660:in `expect_transformation'
     # /tmp/d20111129-16859-16ldrs6/spec.rb:49:in `block (3 levels) in <top (required)>'
     # ./lib/homework/run_with_timeout.rb:5:in `block (3 levels) in <top (required)>'
     # ./lib/homework/run_with_timeout.rb:5:in `block (2 levels) in <top (required)>'

Finished in 0.61684 seconds
57 examples, 1 failure

Failed examples:

rspec /tmp/d20111129-16859-16ldrs6/spec.rb:48 # Formatter paragraphs does not render empty paragraphs

История (1 версия и 0 коментара)

Нено обнови решението на 21.11.2011 03:30 (преди около 13 години)

+class Formatter
+ def initialize(markdown_text)
+ @markdown = markdown_text
+ @html = nil
+ end
+
+ def to_html
+ if @html == nil
+ formatted_lines = @markdown.lines.map { |line| Line.recognize_type(line) }
+ merge_adjacent_lines_of_the_same_type(formatted_lines)
+ @html = formatted_lines.map { |line| line.to_html }.inject(:+).strip
+ end
+ @html
+ end
+
+ alias :to_s :to_html
+
+ def inspect
+ @markdown
+ end
+
+ private
+
+ def merge_adjacent_lines_of_the_same_type(formatted_lines)
+ prev_line = nil
+ formatted_lines.keep_if do |line|
+ prev_line_copy, prev_line = prev_line, line
+ merged = (prev_line_copy and prev_line_copy.merge(line))
+ prev_line = prev_line_copy if merged # the current line will be removed fr,SKEPTIC,!
+ # so restore the reference to the lin,SKEPTIC,!
+ not merged
+ end
+ end
+
+ #
+ # The apply_html_formatting function in the HtmlFormatting module is responsib,SKEPTIC,!
+ # symbols and sequences with their corresponding html tags within the confines,SKEPTIC,!
+ #
+
+ module HtmlFormatting
+ def apply_html_formatting(text, *options)
+ return "" if text.empty?
+ match_data = SpecialSequence.match(text, options)
+ return match_data.pre_match +
+ match_data.matched_sequence.to_html +
+ apply_html_formatting(match_data.post_match)
+ end
+
+ class SequenceMatchData
+ def initialize(pre_match, sequence, post_match)
+ @pre_match, @matched_sequence, @post_match = pre_match, sequence, post_match
+ end
+
+ def match_offset
+ @pre_match.length
+ end
+
+ attr_reader :pre_match, :matched_sequence, :post_match
+ end
+
+ class SpecialSequence
+ include HtmlFormatting
+
+ @children = []
+
+ def self.inherited(child)
+ @children << child
+ end
+
+ # Finds the first special sequence in the text and splits the text in 3 pieces:
+ # 1. the processed text before the special sequence, which can safely be p,SKEPTIC,!
+ # 2. the special sequence itself
+ # 3. the unprocessed text after the special sequence, which must still be ,SKEPTIC,!
+ def self.match(text, options)
+ matched_sequences = @children.map { |seq| seq.match(text, options) }
+ matched_sequences << SequenceMatchData.new(text, NoSequence.new, "") # i,SKEPTIC,!
+ matched_sequences.reject { |match| match == nil }.min_by(&:match_offset)
+ end
+ end
+
+ # The safety net: this sequence always matches
+ class NoSequence < SpecialSequence
+ def self.match(text, options)
+ SequenceMatchData.new(text, self.new, "")
+ end
+
+ def to_html
+ ""
+ end
+ end
+
+ class LinkSequence < SpecialSequence
+ @@LinkPattern = /\[(?<name>[^\]]+?)\]\((?<url>[^\)]+?)\)/
+
+ def initialize(name, url)
+ @name, @url = name, url
+ end
+
+ def self.match(text, options)
+ return nil if options.include? :ignore_link
+
+ match = @@LinkPattern.match(text)
+ if match
+ SequenceMatchData.new(match.pre_match,
+ self.new(match[:name], match[:url]),
+ match.post_match)
+ else
+ nil
+ end
+ end
+
+ def to_html
+ "<a href=\"#{apply_html_formatting @url}\">#{apply_html_formatting @name}</a>"
+ end
+ end
+
+ class BoldSequence < SpecialSequence
+ def initialize(bold_text)
+ @bold_text = bold_text
+ end
+
+ def self.match(text, options)
+ return nil if options.include? :ignore_bold
+
+ match = /\*\*(?<bold>.+?)\*\*/.match(text)
+ if match
+ SequenceMatchData.new(match.pre_match, self.new(match[:bold]), match.post_match)
+ else
+ nil
+ end
+ end
+
+ def to_html
+ "<strong>#{apply_html_formatting @bold_text}</strong>"
+ end
+ end
+
+ class ItalicSequence < SpecialSequence
+ def initialize(italic_text)
+ @italic_text = italic_text
+ end
+
+ def self.match(text, options)
+ return nil if options.include? :ignore_italic
+
+ m = /_(?<italic>.+?)_/.match(text)
+ if m
+ SequenceMatchData.new(m.pre_match, self.new(m[:italic]), m.post_match)
+ else
+ nil
+ end
+ end
+
+ def to_html
+ "<em>#{apply_html_formatting @italic_text}</em>"
+ end
+ end
+
+ class AmpersandSequence < SpecialSequence
+ def self.match(text, options)
+ match = /&/.match text
+ if match
+ SequenceMatchData.new(match.pre_match, self.new, match.post_match)
+ else
+ nil
+ end
+ end
+
+ def to_html
+ "&amp;"
+ end
+ end
+
+ class LessThanSequence < SpecialSequence
+ def self.match(text, options)
+ match = /</.match text
+ if match
+ SequenceMatchData.new(match.pre_match, self.new, match.post_match)
+ else
+ nil
+ end
+ end
+
+ def to_html
+ "&lt;"
+ end
+ end
+
+ class GreaterThanSequence < SpecialSequence
+ def self.match(text, options)
+ match = />/.match text
+ if match
+ SequenceMatchData.new(match.pre_match, self.new, match.post_match)
+ else
+ nil
+ end
+ end
+
+ def to_html
+ "&gt;"
+ end
+ end
+
+ class QuotesSequence < SpecialSequence
+ def self.match(text, options)
+ match = /"/.match text
+ if match
+ SequenceMatchData.new(match.pre_match, self.new, match.post_match)
+ else
+ nil
+ end
+ end
+
+ def to_html
+ "&quot;"
+ end
+ end
+ end
+
+ #
+ # The Line hierarchy of classes is responsible for recognizing the types of wh,SKEPTIC,!
+ #
+
+ class Line
+ include HtmlFormatting
+
+ @children = []
+
+ def self.inherited(child)
+ @children << child
+ end
+
+ def self.recognize_type(line)
+ @children.each { |child| return child.new(line) if child.recognize?(line) }
+ raise "No line type recognizes line '#{line}'"
+ end
+ end
+
+ class HeaderLine < Line
+ @@HeaderPattern = /^\s{0,3}\#{1,4}\s+/
+
+ def initialize(line)
+ @header_text = apply_html_formatting @@HeaderPattern.match(line).post_match
+ @header_level = 4.downto(1) { |n| break n if line.lstrip.start_with?("#" * n) }
+ end
+
+ def self.recognize?(line)
+ match = @@HeaderPattern.match(line)
+ match != nil and not match.post_match.empty?
+ end
+
+ def to_html
+ "<h#{@header_level}>#{@header_text.strip}</h#{@header_level}>\n"
+ end
+
+ def merge(another_line)
+ false # adjacent header lines are not merged
+ end
+ end
+
+ class CodeBlockLine < Line
+ @@CodeBlockPattern = /^ /
+
+ def initialize(line)
+ @code_text = apply_html_formatting @@CodeBlockPattern.match(line).post_match,
+ :ignore_link, :ignore_bold, :ignore_italic
+ end
+
+ def self.recognize?(line)
+ @@CodeBlockPattern.match(line)
+ end
+
+ def to_html
+ "<pre><code>#{@code_text.chomp}</code></pre>\n"
+ end
+
+ def merge(another_line)
+ return false if another_line.class != self.class
+ @code_text << another_line.code_text
+ end
+
+ protected
+
+ attr_reader :code_text
+ end
+
+ class QuoteLine < Line
+ @@QuotePattern = /^\s{0,3}>\s/
+
+ def initialize(line)
+ @quoted_text = @@QuotePattern.match(line).post_match
+ end
+
+ def self.recognize?(line)
+ @@QuotePattern.match(line)
+ end
+
+ def to_html
+ formatted_quote = Formatter.new(@quoted_text).to_html
+ "<blockquote>#{formatted_quote.chomp}</blockquote>\n"
+ end
+
+ def merge(another_line)
+ return false if another_line.class != self.class
+ @quoted_text << another_line.quoted_text
+ end
+
+ protected
+
+ attr_reader :quoted_text
+ end
+
+ class OrderedListLine < Line
+ @@OrderedListPattern = /^\s{0,3}\d+\. /
+
+ def initialize(line)
+ @items = []
+ @items << apply_html_formatting(@@OrderedListPattern.match(line).post_match)
+ end
+
+ def self.recognize?(line)
+ match = @@OrderedListPattern.match(line)
+ match != nil and not match.post_match.empty?
+ end
+
+ def to_html
+ html = "<ol>\n"
+ @items.each { |item| html << " <li>#{item.chomp}</li>\n" }
+ html << "</ol>\n"
+ end
+
+ def merge(another_line)
+ return false if another_line.class != self.class
+ @items.concat another_line.items
+ end
+
+ protected
+
+ attr_reader :items
+ end
+
+ class UnorderedListLine < Line
+ @@UnorderedListPattern = /^\s{0,3}\* /
+
+ def initialize(line)
+ @items = []
+ @items << apply_html_formatting(@@UnorderedListPattern.match(line).post_match)
+ end
+
+ def self.recognize?(line)
+ match = @@UnorderedListPattern.match(line)
+ match != nil and not match.post_match.empty?
+ end
+
+ def to_html
+ html = "<ul>\n"
+ @items.each { |item| html << " <li>#{item.chomp}</li>\n" }
+ html << "</ul>\n"
+ end
+
+ def merge(another_line)
+ return false if another_line.class != self.class
+ @items.concat another_line.items
+ end
+
+ protected
+
+ attr_reader :items
+ end
+
+ # N.B.: EmptyLine must be defined after CodeBlock, because it would recognize ,SKEPTIC,!
+ class EmptyLine < Line
+ @@EmptyPattern = /^\s*$/
+
+ def initialize(line)
+ @empty_lines = "\n"
+ end
+
+ def self.recognize?(line)
+ @@EmptyPattern.match(line)
+ end
+
+ def to_html
+ @empty_lines
+ end
+
+ def merge(another_line)
+ return false if another_line.class != self.class
+ @empty_lines << another_line.empty_lines
+ end
+
+ protected
+
+ attr_reader :empty_lines
+ end
+
+ # N.B.: ParagraphLine must be defined last because it matches every line so it,SKEPTIC,!
+ class ParagraphLine < Line
+ def initialize(line)
+ @paragraph_text = apply_html_formatting line
+ end
+
+ def self.recognize?(line)
+ true # if the other line types didn't recognize this line, it must be a paragraph
+ end
+
+ def to_html
+ "<p>#{@paragraph_text.strip}</p>\n"
+ end
+
+ def merge(another_line)
+ return false if another_line.class != self.class
+ @paragraph_text << another_line.paragraph_text
+ end
+
+ protected
+
+ attr_reader :paragraph_text
+ end
+end