]> source.dussan.org Git - redmine.git/commitdiff
Let macros optionally accept a block of text (#3061).
authorJean-Philippe Lang <jp_lang@yahoo.fr>
Sat, 18 Aug 2012 08:25:22 +0000 (08:25 +0000)
committerJean-Philippe Lang <jp_lang@yahoo.fr>
Sat, 18 Aug 2012 08:25:22 +0000 (08:25 +0000)
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@10210 e93f8b46-1217-0410-a6f0-8f06a7374b81

app/helpers/application_helper.rb
lib/redmine/wiki_formatting/macros.rb
test/unit/lib/redmine/wiki_formatting/macros_test.rb

index 74032496f6b40af4331ec7b825382309e3aa9ee6..cfa9cb5ab0c428b19256def0af5bc3ec60a7e845 100644 (file)
@@ -866,10 +866,11 @@ module ApplicationHelper
                 (
                 \{\{                        # opening tag
                 ([\w]+)                     # macro name
-                (\((.*?)\))?                # optional arguments
+                (\(([^\n\r]*?)\))?          # optional arguments
+                ([\n\r].*[\n\r])?           # optional block of text
                 \}\}                        # closing tag
                 )
-               )/x unless const_defined?(:MACROS_RE)
+               )/mx unless const_defined?(:MACROS_RE)
 
   MACRO_SUB_RE = /(
                   \{\{
@@ -899,9 +900,9 @@ module ApplicationHelper
       all, index = $1, $2.to_i
       orig = macros.delete(index)
       if execute && orig && orig =~ MACROS_RE
-        esc, all, macro, args = $2, $3, $4.downcase, $6.to_s
+        esc, all, macro, args, block = $2, $3, $4.downcase, $6.to_s, $7.try(:strip)
         if esc.nil?
-          h(exec_macro(macro, obj, args) || all)
+          h(exec_macro(macro, obj, args, block) || all)
         else
           h(all)
         end
index 55bde5e294dc6403247524d40ffa1067224dcc71..16a3239ee97d19f337ad0a6dd226ccd9f931b45f 100644 (file)
@@ -24,7 +24,7 @@ module Redmine
           Redmine::WikiFormatting::Macros.available_macros.key?(name.to_sym)
         end
 
-        def exec_macro(name, obj, args)
+        def exec_macro(name, obj, args, text)
           macro_options = Redmine::WikiFormatting::Macros.available_macros[name.to_sym]
           return unless macro_options
 
@@ -34,7 +34,13 @@ module Redmine
           end
 
           begin
-            send(method_name, obj, args) if respond_to?(method_name)
+            if self.class.instance_method(method_name).arity == 3
+              send(method_name, obj, args, text)
+            elsif text
+              raise "This macro does not accept a block of text"
+            else
+              send(method_name, obj, args)
+            end
           rescue => e
             "<div class=\"flash error\">Error executing the <strong>#{h name}</strong> macro (#{h e.to_s})</div>".html_safe
           end
@@ -55,9 +61,11 @@ module Redmine
 
       class << self
         # Called with a block to define additional macros.
-        # Macro blocks accept 2 arguments:
+        # Macro blocks accept 2 or 3 arguments:
         # * obj: the object that is rendered
         # * args: macro arguments
+        # * text: a block of text (if the macro accepts
+        #   3 arguments)
         #
         # Plugins can use this method to define new macros:
         #
@@ -66,7 +74,33 @@ module Redmine
         #     macro :my_macro do |obj, args|
         #       "My macro output"
         #     end
+        #
+        #     desc "This is my macro that accepts a block of text"
+        #     macro :my_macro do |obj, args, text|
+        #       "My macro output"
+        #     end
         #   end
+        #
+        # Macros are invoked in formatted text using the following
+        # syntax:
+        #
+        #   No arguments:
+        #   {{my_macro}}
+        #
+        #   With arguments:
+        #   {{my_macro(arg1, arg2)}}
+        #
+        #   With a block of text:
+        #   {{my_macro
+        #   multiple lines
+        #   of text
+        #   }}
+        #
+        #   With arguments and a block of text
+        #   {{my_macro(arg1, arg2)
+        #   multiple lines
+        #   of text
+        #   }}
         def register(&block)
           class_eval(&block) if block_given?
         end
@@ -79,7 +113,7 @@ module Redmine
         #
         # Examples:
         # By default, when the macro is invoked, the coma separated list of arguments
-        # is parsed and passed to the macro block as an array:
+        # is split and passed to the macro block as an array:
         #
         #   macro :my_macro do |obj, args|
         #     # args is an array
@@ -106,8 +140,11 @@ module Redmine
 
       # Builtin macros
       desc "Sample macro."
-      macro :hello_world do |obj, args|
-        h("Hello world! Object: #{obj.class.name}, " + (args.empty? ? "Called with no argument." : "Arguments: #{args.join(', ')}"))
+      macro :hello_world do |obj, args, text|
+        h("Hello world! Object: #{obj.class.name}, " + 
+          (args.empty? ? "Called with no argument" : "Arguments: #{args.join(', ')}") +
+          " and " + (text.present? ? "a #{text.size} bytes long block of text." : "no block of text.")
+        )
       end
 
       desc "Displays a list of all available macros, including description if available."
index b313e8b802936d66c84e5eda1948d8d3f345118c..90118490918f5ce9f5747d8b7ae92eecc291c4d7 100644 (file)
@@ -65,6 +65,19 @@ class Redmine::WikiFormatting::MacrosTest < ActionView::TestCase
     assert_equal '<p>Bar: () (String)</p>', textilizable("{{bar()}}")
   end
 
+  def test_macro_registration_with_3_args_should_receive_text_argument
+    Redmine::WikiFormatting::Macros.register do
+      macro :baz do |obj, args, text|
+        "Baz: (#{args.join(',')}) (#{text.class.name}) (#{text})"
+      end
+    end
+
+    assert_equal "<p>Baz: () (NilClass) ()</p>", textilizable("{{baz}}")
+    assert_equal "<p>Baz: () (NilClass) ()</p>", textilizable("{{baz()}}")
+    assert_equal "<p>Baz: () (String) (line1\nline2)</p>", textilizable("{{baz()\nline1\nline2\n}}")
+    assert_equal "<p>Baz: (arg1,arg2) (String) (line1\nline2)</p>", textilizable("{{baz(arg1, arg2)\nline1\nline2\n}}")
+  end
+
   def test_multiple_macros_on_the_same_line
     Redmine::WikiFormatting::Macros.macro :foo do |obj, args|
       args.any? ? "args: #{args.join(',')}" : "no args" 
@@ -79,14 +92,15 @@ class Redmine::WikiFormatting::MacrosTest < ActionView::TestCase
   def test_macro_should_receive_the_object_as_argument_when_with_object_and_attribute
     issue = Issue.find(1)
     issue.description = "{{hello_world}}"
-    assert_equal '<p>Hello world! Object: Issue, Called with no argument.</p>', textilizable(issue, :description)
+    assert_equal '<p>Hello world! Object: Issue, Called with no argument and no block of text.</p>', textilizable(issue, :description)
   end
 
   def test_macro_should_receive_the_object_as_argument_when_called_with_object_option
     text = "{{hello_world}}"
-    assert_equal '<p>Hello world! Object: Issue, Called with no argument.</p>', textilizable(text, :object => Issue.find(1))
+    assert_equal '<p>Hello world! Object: Issue, Called with no argument and no block of text.</p>', textilizable(text, :object => Issue.find(1))
   end
 
+
   def test_macro_exception_should_be_displayed
     Redmine::WikiFormatting::Macros.macro :exception do |obj, args|
       raise "My message"
@@ -237,14 +251,14 @@ class Redmine::WikiFormatting::MacrosTest < ActionView::TestCase
 RAW
 
     expected = <<-EXPECTED
-<p>Hello world! Object: NilClass, Arguments: foo</p>
+<p>Hello world! Object: NilClass, Arguments: foo and no block of text.</p>
 
 <pre>
 {{hello_world(pre)}}
 !{{hello_world(pre)}}
 </pre>
 
-<p>Hello world! Object: NilClass, Arguments: bar</p>
+<p>Hello world! Object: NilClass, Arguments: bar and no block of text.</p>
 EXPECTED
 
     assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(text).gsub(%r{[\r\n\t]}, '')
@@ -257,6 +271,6 @@ EXPECTED
 
   def test_macros_should_not_mangle_next_macros_outputs
     text = '{{macro(2)}} !{{macro(2)}} {{hello_world(foo)}}'
-    assert_equal '<p>{{macro(2)}} {{macro(2)}} Hello world! Object: NilClass, Arguments: foo</p>', textilizable(text)
+    assert_equal '<p>{{macro(2)}} {{macro(2)}} Hello world! Object: NilClass, Arguments: foo and no block of text.</p>', textilizable(text)
   end
 end