]> git.wincent.com - wikitext.git/commitdiff
Implement "link_proc" option ("red link" support)
authorWincent Colaiuta <win@wincent.com>
Thu, 23 Jul 2009 20:41:27 +0000 (22:41 +0200)
committerWincent Colaiuta <win@wincent.com>
Fri, 24 Jul 2009 09:57:12 +0000 (11:57 +0200)
Signed-off-by: Wincent Colaiuta <win@wincent.com>
doc/RELEASE-NOTES
ext/parser.c
spec/internal_link_spec.rb

index 2c29c10fa65733a9548043ada707db3bae116306..cfa25989bb845c541a1e91582298f88aaac7a615 100644 (file)
@@ -9,6 +9,10 @@ the source code repository at: http://git.wincent.com.
 = Changes in 1.9
 
 * NilClass#w method now accepts and optional parameters hash
+* new +link_proc+ option to Wikitext::Parser#parse allows you to
+  dynamically apply a custom CSS class based on the link target;
+  this can be used, for example, to provide "red links" for
+  articles which do not exist yet
 
 = Changes in 1.8
 
index fd282709bd863bdb48ac41ad62a18f9cc29da213..4728ad8a77fadd5c03a61deda9d448ca50c5fc08 100644 (file)
@@ -1042,6 +1042,7 @@ VALUE Wikitext_parser_parse(int argc, VALUE *argv, VALUE self)
     // process options hash
     int base_indent = 0;
     int base_heading_level = NUM2INT(rb_iv_get(self, "@base_heading_level"));
+    VALUE link_proc = Qnil;
     if (!NIL_P(options) && TYPE(options) == T_HASH)
     {
         // :indent => 0 (or more)
@@ -1061,6 +1062,11 @@ VALUE Wikitext_parser_parse(int argc, VALUE *argv, VALUE self)
         // :base_heading_level => 0/1/2/3/4/5/6
         if (rb_funcall(options, rb_intern("has_key?"), 1, ID2SYM(rb_intern("base_heading_level"))) == Qtrue)
             base_heading_level = NUM2INT(rb_hash_aref(options, ID2SYM(rb_intern("base_heading_level"))));
+
+        // :link_proc => lambda { |link_target| ... }
+        // TODO: refactor to avoid some repeated calls to ID2SYM and rb_intern
+        if (rb_funcall(options, rb_intern("has_key?"), 1, ID2SYM(rb_intern("link_proc"))) == Qtrue)
+            link_proc = rb_hash_aref(options, ID2SYM(rb_intern("link_proc")));
     }
 
     // normalize, regardless of whether this came from instance variable or override
@@ -2144,10 +2150,20 @@ VALUE Wikitext_parser_parse(int argc, VALUE *argv, VALUE self)
                     }
                     else
                         wiki_trim_link_text(parser);
+
+                    // perform "redlink" check before manipulating link_target
+                    if (NIL_P(link_proc))
+                        j = Qnil;
+                    else
+                    {
+                        j = rb_funcall(link_proc, rb_intern("call"), 1, string_from_str(parser->link_target));
+                        if (!NIL_P(j))
+                            j = StringValue(j);
+                    }
                     wiki_encode_link_target(parser);
                     wiki_pop_from_stack_up_to(parser, output, LINK_START, true);
                     parser->capture = NULL;
-                    wiki_append_hyperlink(parser, prefix, parser->link_target, parser->link_text, Qnil, false);
+                    wiki_append_hyperlink(parser, prefix, parser->link_target, parser->link_text, j, false);
                     str_clear(parser->link_target);
                     str_clear(parser->link_text);
                 }
index 5e4d5886e12276ca4289f24204944c59c6a9942b..a00dcd8cc291ec4cd7100ea1365bffedf958fe1d 100755 (executable)
@@ -114,6 +114,119 @@ describe Wikitext::Parser, 'internal links (space to underscore off)' do
     @parser.parse('foo [[bar]] baz').should == expected # was a bug
   end
 
+  describe '"red link" support' do
+    it 'should accept a Proc object via the optional "link_proc" parameter' do
+      @parser.parse('foo', :link_proc => Proc.new { }).should == %Q{<p>foo</p>\n}
+    end
+
+    it 'should accept a lambda via the optional "link_proc" parameter' do
+      @parser.parse('foo', :link_proc => lambda { }).should == %Q{<p>foo</p>\n}
+    end
+
+    it 'should apply custom link CSS when supplied (Proc object version)' do
+      link_proc = Proc.new { |target| target == 'bar' ? 'redlink' : nil }
+      expected = %Q{<p><a href="/wiki/foo">foo</a> <a href="/wiki/bar" class="redlink">bar</a></p>\n}
+      @parser.parse('[[foo]] [[bar]]', :link_proc => link_proc).should == expected
+    end
+
+    it 'should apply custom link CSS when supplied (lambda version)' do
+      link_proc = lambda { |target| target == 'bar' ? 'redlink' : nil }
+      expected = %Q{<p><a href="/wiki/foo">foo</a> <a href="/wiki/bar" class="redlink">bar</a></p>\n}
+      @parser.parse('[[foo]] [[bar]]', :link_proc => link_proc).should == expected
+    end
+
+    it 'should apply no custom link CSS when supplied nil (Proc object version)' do
+      expected = %Q{<p><a href="/wiki/foo">foo</a></p>\n}
+      @parser.parse('[[foo]]', :link_proc => Proc.new { |target| nil }).should == expected
+    end
+
+    it 'should apply no custom link CSS when supplied nil (lambda version)' do
+      expected = %Q{<p><a href="/wiki/foo">foo</a></p>\n}
+      @parser.parse('[[foo]]', :link_proc => lambda { |target| nil }).should == expected
+    end
+
+    it 'should let exceptions bubble up from the link proc (Proc object version)' do
+      lambda { @parser.parse('[[foo]]', :link_proc => Proc.new { |target| raise 'bar' }) }.should raise_error(RuntimeError, /bar/)
+    end
+
+    it 'should let exceptions bubble up from the link proc (lambda version)' do
+      lambda { @parser.parse('[[foo]]', :link_proc => lambda { |target| raise 'bar' }) }.should raise_error(RuntimeError, /bar/)
+    end
+
+    it 'should complain if the link proc returns a non-stringy object (Proc object version)' do
+      lambda {
+        @parser.parse '[[foo]]', :link_proc => Proc.new { 1 }
+      }.should raise_error(TypeError, /can't convert/)
+    end
+
+    it 'should complain if the link proc returns a non-stringy object (lambda version)' do
+      lambda {
+        @parser.parse '[[foo]]', :link_proc => lambda { 1 }
+      }.should raise_error(TypeError, /can't convert/)
+    end
+
+    # a couple of Ruby's idiosynchrasies: different behaviour of lambdas and Procs
+    it 'should not complain if the Proc object accepts too many arguments' do
+      lambda {
+        @parser.parse '[[foo]]', :link_proc => Proc.new { |a,b| }
+      }.should_not raise_error(ArgumentError, /wrong number/)
+    end
+
+    it 'should complain if the lambda accepts too many arguments' do
+      lambda {
+        @parser.parse '[[foo]]', :link_proc => lambda { |a,b| }
+      }.should raise_error(ArgumentError, /wrong number/)
+    end
+
+    it 'should complain when "return" is used inside a "Proc.new" block' do
+      lambda {
+        @parser.parse '[[foo]]', :link_proc => Proc.new { return 'bar' }
+      }.should raise_error(LocalJumpError)
+    end
+
+    it 'should not complain when "return" is used inside a lambda' do
+      lambda {
+        @parser.parse '[[foo]]', :link_proc => lambda { return 'bar' }
+      }.should_not raise_error(LocalJumpError)
+    end
+
+    it 'should interact correctly with spaces in link targets (Proc object version)' do
+      link_proc = Proc.new { |target| target == 'bar b' ? 'redlink' : nil }
+      expected = %Q{<p><a href="/wiki/foo%20a">foo a</a> <a href="/wiki/bar%20b" class="redlink">bar b</a></p>\n}
+      @parser.parse('[[foo a]] [[bar b]]', :link_proc => link_proc).should == expected
+    end
+
+    it 'should interact correctly with spaces in link targets (lambda version)' do
+      link_proc = lambda { |target| target == 'bar b' ? 'redlink' : nil }
+      expected = %Q{<p><a href="/wiki/foo%20a">foo a</a> <a href="/wiki/bar%20b" class="redlink">bar b</a></p>\n}
+      @parser.parse('[[foo a]] [[bar b]]', :link_proc => link_proc).should == expected
+    end
+
+    it 'should interact correctly with explicit link text (Proc object version)' do
+      link_proc = Proc.new { |target| target == 'bar b' ? 'redlink' : nil }
+      expected = %Q{<p><a href="/wiki/foo%20a">hello</a> <a href="/wiki/bar%20b" class="redlink">world</a></p>\n}
+      @parser.parse('[[foo a|hello]] [[bar b|world]]', :link_proc => link_proc).should == expected
+    end
+
+    it 'should interact correctly with explicit link text (lambda version)' do
+      link_proc = lambda { |target| target == 'bar b' ? 'redlink' : nil }
+      expected = %Q{<p><a href="/wiki/foo%20a">hello</a> <a href="/wiki/bar%20b" class="redlink">world</a></p>\n}
+      @parser.parse('[[foo a|hello]] [[bar b|world]]', :link_proc => link_proc).should == expected
+    end
+
+    it 'should handle link targets with encoded parts (Proc object version)' do
+      link_proc = Proc.new { |target| target == 'información' ? 'redlink' : nil }
+      expected = %Q{<p><a href="/wiki/informaci%c3%b3n" class="redlink">informaci&#x00f3;n</a> <a href="/wiki/bar">bar</a></p>\n}
+      @parser.parse('[[información]] [[bar]]', :link_proc => link_proc).should == expected
+    end
+
+    it 'should handle link targets with encoded parts (lambda version)' do
+      link_proc = lambda { |target| target == 'información' ? 'redlink' : nil }
+      expected = %Q{<p><a href="/wiki/informaci%c3%b3n" class="redlink">informaci&#x00f3;n</a> <a href="/wiki/bar">bar</a></p>\n}
+      @parser.parse('[[información]] [[bar]]', :link_proc => link_proc).should == expected
+    end
+  end
+
   describe 'custom link text' do
     it 'should recognize link text placed after the separator' do
       @parser.parse('[[foo|bar]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}
@@ -541,6 +654,119 @@ describe Wikitext::Parser, 'internal links (space to underscore on)' do
     @parser.parse('foo [[bar]] baz').should == expected # was a bug
   end
 
+  describe '"red link" support' do
+    it 'should accept a Proc object via the optional "link_proc" parameter' do
+      @parser.parse('foo', :link_proc => Proc.new { }).should == %Q{<p>foo</p>\n}
+    end
+
+    it 'should accept a lambda via the optional "link_proc" parameter' do
+      @parser.parse('foo', :link_proc => lambda { }).should == %Q{<p>foo</p>\n}
+    end
+
+    it 'should apply custom link CSS when supplied (Proc object version)' do
+      link_proc = Proc.new { |target| target == 'bar' ? 'redlink' : nil }
+      expected = %Q{<p><a href="/wiki/foo">foo</a> <a href="/wiki/bar" class="redlink">bar</a></p>\n}
+      @parser.parse('[[foo]] [[bar]]', :link_proc => link_proc).should == expected
+    end
+
+    it 'should apply custom link CSS when supplied (lambda version)' do
+      link_proc = lambda { |target| target == 'bar' ? 'redlink' : nil }
+      expected = %Q{<p><a href="/wiki/foo">foo</a> <a href="/wiki/bar" class="redlink">bar</a></p>\n}
+      @parser.parse('[[foo]] [[bar]]', :link_proc => link_proc).should == expected
+    end
+
+    it 'should apply no custom link CSS when supplied nil (Proc object version)' do
+      expected = %Q{<p><a href="/wiki/foo">foo</a></p>\n}
+      @parser.parse('[[foo]]', :link_proc => Proc.new { |target| nil }).should == expected
+    end
+
+    it 'should apply no custom link CSS when supplied nil (lambda version)' do
+      expected = %Q{<p><a href="/wiki/foo">foo</a></p>\n}
+      @parser.parse('[[foo]]', :link_proc => lambda { |target| nil }).should == expected
+    end
+
+    it 'should let exceptions bubble up from the link proc (Proc object version)' do
+      lambda { @parser.parse('[[foo]]', :link_proc => Proc.new { |target| raise 'bar' }) }.should raise_error(RuntimeError, /bar/)
+    end
+
+    it 'should let exceptions bubble up from the link proc (lambda version)' do
+      lambda { @parser.parse('[[foo]]', :link_proc => lambda { |target| raise 'bar' }) }.should raise_error(RuntimeError, /bar/)
+    end
+
+    it 'should complain if the link proc returns a non-stringy object (Proc object version)' do
+      lambda {
+        @parser.parse '[[foo]]', :link_proc => Proc.new { 1 }
+      }.should raise_error(TypeError, /can't convert/)
+    end
+
+    it 'should complain if the link proc returns a non-stringy object (lambda version)' do
+      lambda {
+        @parser.parse '[[foo]]', :link_proc => lambda { 1 }
+      }.should raise_error(TypeError, /can't convert/)
+    end
+
+    # a couple of Ruby's idiosynchrasies: different behaviour of lambdas and Procs
+    it 'should not complain if the Proc object accepts too many arguments' do
+      lambda {
+        @parser.parse '[[foo]]', :link_proc => Proc.new { |a,b| }
+      }.should_not raise_error(ArgumentError, /wrong number/)
+    end
+
+    it 'should complain if the lambda accepts too many arguments' do
+      lambda {
+        @parser.parse '[[foo]]', :link_proc => lambda { |a,b| }
+      }.should raise_error(ArgumentError, /wrong number/)
+    end
+
+    it 'should complain when "return" is used inside a "Proc.new" block' do
+      lambda {
+        @parser.parse '[[foo]]', :link_proc => Proc.new { return 'bar' }
+      }.should raise_error(LocalJumpError)
+    end
+
+    it 'should not complain when "return" is used inside a lambda' do
+      lambda {
+        @parser.parse '[[foo]]', :link_proc => lambda { return 'bar' }
+      }.should_not raise_error(LocalJumpError)
+    end
+
+    it 'should interact correctly with spaces in link targets (Proc object version)' do
+      link_proc = Proc.new { |target| target == 'bar b' ? 'redlink' : nil }
+      expected = %Q{<p><a href="/wiki/foo_a">foo a</a> <a href="/wiki/bar_b" class="redlink">bar b</a></p>\n}
+      @parser.parse('[[foo a]] [[bar b]]', :link_proc => link_proc).should == expected
+    end
+
+    it 'should interact correctly with spaces in link targets (lambda version)' do
+      link_proc = lambda { |target| target == 'bar b' ? 'redlink' : nil }
+      expected = %Q{<p><a href="/wiki/foo_a">foo a</a> <a href="/wiki/bar_b" class="redlink">bar b</a></p>\n}
+      @parser.parse('[[foo a]] [[bar b]]', :link_proc => link_proc).should == expected
+    end
+
+    it 'should interact correctly with explicit link text (Proc object version)' do
+      link_proc = Proc.new { |target| target == 'bar b' ? 'redlink' : nil }
+      expected = %Q{<p><a href="/wiki/foo_a">hello</a> <a href="/wiki/bar_b" class="redlink">world</a></p>\n}
+      @parser.parse('[[foo a|hello]] [[bar b|world]]', :link_proc => link_proc).should == expected
+    end
+
+    it 'should interact correctly with explicit link text (lambda version)' do
+      link_proc = lambda { |target| target == 'bar b' ? 'redlink' : nil }
+      expected = %Q{<p><a href="/wiki/foo_a">hello</a> <a href="/wiki/bar_b" class="redlink">world</a></p>\n}
+      @parser.parse('[[foo a|hello]] [[bar b|world]]', :link_proc => link_proc).should == expected
+    end
+
+    it 'should handle link targets with encoded parts (Proc object version)' do
+      link_proc = Proc.new { |target| target == 'información' ? 'redlink' : nil }
+      expected = %Q{<p><a href="/wiki/informaci%c3%b3n" class="redlink">informaci&#x00f3;n</a> <a href="/wiki/bar">bar</a></p>\n}
+      @parser.parse('[[información]] [[bar]]', :link_proc => link_proc).should == expected
+    end
+
+    it 'should handle link targets with encoded parts (lambda version)' do
+      link_proc = lambda { |target| target == 'información' ? 'redlink' : nil }
+      expected = %Q{<p><a href="/wiki/informaci%c3%b3n" class="redlink">informaci&#x00f3;n</a> <a href="/wiki/bar">bar</a></p>\n}
+      @parser.parse('[[información]] [[bar]]', :link_proc => link_proc).should == expected
+    end
+  end
+
   describe 'custom link text' do
     it 'should recognize link text placed after the separator' do
       @parser.parse('[[foo|bar]]').should == %Q{<p><a href="/wiki/foo">bar</a></p>\n}