diff --git a/Gemfile b/Gemfile
index 594c90b..d6432c8 100644
--- a/Gemfile
+++ b/Gemfile
@@ -6,3 +6,6 @@ gem "slim"
gem "bb-ruby"
gem "ruby-bbcode"
gem "rspec", "~> 3.0"
+gem "parslet"
+gem "guard"
+gem "guard-rspec"
diff --git a/Gemfile.lock b/Gemfile.lock
index 9244ec2..0a1c754 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -8,15 +8,45 @@ GEM
thread_safe (~> 0.3, >= 0.3.4)
tzinfo (~> 1.1)
bb-ruby (1.1.0)
+ blankslate (3.1.3)
+ celluloid (0.15.2)
+ timers (~> 1.1.0)
+ coderay (1.1.0)
daemons (1.2.3)
diff-lcs (1.2.5)
eventmachine (1.0.8)
+ ffi (1.9.3)
+ formatador (0.2.5)
+ guard (2.6.1)
+ formatador (>= 0.2.4)
+ listen (~> 2.7)
+ lumberjack (~> 1.0)
+ pry (>= 0.9.12)
+ thor (>= 0.18.1)
+ guard-rspec (4.3.1)
+ guard (~> 2.1)
+ rspec (>= 2.14, < 4.0)
i18n (0.7.0)
json (1.8.3)
+ listen (2.7.9)
+ celluloid (>= 0.15.2)
+ rb-fsevent (>= 0.9.3)
+ rb-inotify (>= 0.9)
+ lumberjack (1.0.9)
+ method_source (0.8.2)
minitest (5.8.2)
+ parslet (1.7.1)
+ blankslate (>= 2.0, <= 4.0)
+ pry (0.10.0)
+ coderay (~> 1.1.0)
+ method_source (~> 0.8.1)
+ slop (~> 3.4)
rack (1.6.4)
rack-protection (1.5.3)
rack
+ rb-fsevent (0.9.4)
+ rb-inotify (0.9.5)
+ ffi (>= 0.5.0)
rspec (3.1.0)
rspec-core (~> 3.1.0)
rspec-expectations (~> 3.1.0)
@@ -38,13 +68,16 @@ GEM
slim (3.0.6)
temple (~> 0.7.3)
tilt (>= 1.3.3, < 2.1)
+ slop (3.5.0)
temple (0.7.6)
thin (1.6.4)
daemons (~> 1.0, >= 1.0.9)
eventmachine (~> 1.0, >= 1.0.4)
rack (~> 1.0)
+ thor (0.19.1)
thread_safe (0.3.5)
tilt (2.0.1)
+ timers (1.1.0)
tzinfo (1.2.2)
thread_safe (~> 0.1)
@@ -53,6 +86,9 @@ PLATFORMS
DEPENDENCIES
bb-ruby
+ guard
+ guard-rspec
+ parslet
rspec (~> 3.0)
ruby-bbcode
sinatra
diff --git a/Guardfile b/Guardfile
new file mode 100644
index 0000000..e3ada70
--- /dev/null
+++ b/Guardfile
@@ -0,0 +1,4 @@
+guard :rspec, cmd: 'bundle exec rspec' do
+ watch('bristlecode.rb') { "spec" }
+ watch(%r{^spec/.+(_spec\.rb)$}) { "spec" }
+end
diff --git a/bristlecode.rb b/bristlecode.rb
index 34a7b0a..b8f8a43 100644
--- a/bristlecode.rb
+++ b/bristlecode.rb
@@ -21,14 +21,20 @@ module Bristlecode
rule(:italic_close) { str('[/i]') | str('[/I]') | eof }
rule(:italic) { italic_open >> children.as(:italic) >> italic_close }
+ rule(:url_open) { str('[url]') }
+ rule(:url_close) { str('[/url]') | eof }
+ rule(:simple_href) { (url_close.absent? >> any).repeat }
+ rule(:simple_url) { url_open >> simple_href.as(:href) >> url_close }
+ rule(:url) { simple_url.as(:url) }
+
rule(:eof) { any.absent? }
- rule(:tag) { bold | italic }
- rule(:elem) { text | tag }
- rule(:tag_open) { bold_open | italic_open }
- rule(:tag_close) { bold_close | italic_close }
+ rule(:tag) { bold | italic | url }
+ rule(:elem) { text.as(:text) | tag }
+ rule(:tag_open) { bold_open | italic_open | url_open }
+ rule(:tag_close) { bold_close | italic_close | url_close }
rule(:tag_delim) { tag_open | tag_close }
- rule(:text) { (tag_delim.absent? >> any).repeat(1).as(:text) }
+ rule(:text) { (tag_delim.absent? >> any).repeat(1) }
rule(:children) { space? >> elem.repeat }
rule(:doc) { space? >> elem.repeat.as(:doc) }
root(:doc)
@@ -39,55 +45,77 @@ module Bristlecode
rule(italic: sequence(:children)) { Italic.new(children) }
rule(text: simple(:text)) { Text.new(text) }
rule(doc: subtree(:doc)) { Doc.new(doc) }
+ rule(url: subtree(:url)) { Url.new(url) }
end
class Doc
+ attr_accessor :children
+
def initialize(children)
- @children = children
+ self.children = children
end
def to_html
s = StringIO.new
- @children.each{|child| s << child.to_html }
+ children.each{|child| s << child.to_html }
s.string
end
end
class Text
+ attr_accessor :text
+
def initialize(text)
- @text = text.to_str.strip
+ self.text = text.to_str.strip
end
def to_html
- @text
+ text
end
end
class Bold
+ attr_accessor :children
+
def initialize(children)
- @children = children
+ self.children = children
end
def to_html
s = StringIO.new
s << ""
- @children.each{|child| s << child.to_html }
+ children.each{|child| s << child.to_html }
s << ""
s.string
end
end
class Italic
+ attr_accessor :children
+
def initialize(children)
- @children = children
+ self.children = children
end
def to_html
s = StringIO.new
s << ""
- @children.each{|child| s << child.to_html }
+ children.each{|child| s << child.to_html }
s << ""
s.string
end
end
+
+ class Url
+ attr_accessor :href, :title
+
+ def initialize(args)
+ self.href = args[:href].to_str.strip
+ self.title = args[:title] || href
+ end
+
+ def to_html
+ "#{title}"
+ end
+ end
end
diff --git a/spec/bristlecode/parser_spec.rb b/spec/bristlecode/parser_spec.rb
index 0ddccc9..5e2199d 100644
--- a/spec/bristlecode/parser_spec.rb
+++ b/spec/bristlecode/parser_spec.rb
@@ -45,6 +45,22 @@ module Bristlecode
expect(to_html("[b]bold")).to eq("bold")
expect(to_html("[i]italic")).to eq("italic")
end
+
+ it 'can render simple links' do
+ input = '[url]example.com[/url]'
+ output = 'example.com'
+ expect(to_html(input)).to eq(output)
+
+ input = '[url] example.com [/url]'
+ output = 'example.com'
+ expect(to_html(input)).to eq(output)
+ end
+
+ it 'passes simple url contents opaquely' do
+ input = '[url]x[b]y[/b]z[/url]'
+ output = 'x[b]y[/b]z'
+ expect(to_html(input)).to eq(output)
+ end
end
describe Parser do
@@ -133,5 +149,19 @@ module Bristlecode
expect(parser.italic).not_to parse('[italic]fake content[/italic]')
end
end
+
+ describe '#url' do
+ it 'can parse correct urls' do
+ expect(parser.url).to parse('[url]google.com[/url]')
+ end
+
+ it "doesn't die on elements nested in simple urls" do
+ expect(parser.url).to parse('[url]goog[b]le.c[/b]om[/url]')
+ end
+
+ it 'fails nested [url] tags' do
+ expect(parser.url).not_to parse('[url]x[url]y[/url]z[/url]')
+ end
+ end
end
end