##// END OF EJS Templates
Let macros optionally accept a block of text (#3061)....
Jean-Philippe Lang -
r10027:fc3a09e49a69
parent child
Show More
@@ -866,10 +866,11 module ApplicationHelper
866 (
866 (
867 \{\{ # opening tag
867 \{\{ # opening tag
868 ([\w]+) # macro name
868 ([\w]+) # macro name
869 (\((.*?)\))? # optional arguments
869 (\(([^\n\r]*?)\))? # optional arguments
870 ([\n\r].*[\n\r])? # optional block of text
870 \}\} # closing tag
871 \}\} # closing tag
871 )
872 )
872 )/x unless const_defined?(:MACROS_RE)
873 )/mx unless const_defined?(:MACROS_RE)
873
874
874 MACRO_SUB_RE = /(
875 MACRO_SUB_RE = /(
875 \{\{
876 \{\{
@@ -899,9 +900,9 module ApplicationHelper
899 all, index = $1, $2.to_i
900 all, index = $1, $2.to_i
900 orig = macros.delete(index)
901 orig = macros.delete(index)
901 if execute && orig && orig =~ MACROS_RE
902 if execute && orig && orig =~ MACROS_RE
902 esc, all, macro, args = $2, $3, $4.downcase, $6.to_s
903 esc, all, macro, args, block = $2, $3, $4.downcase, $6.to_s, $7.try(:strip)
903 if esc.nil?
904 if esc.nil?
904 h(exec_macro(macro, obj, args) || all)
905 h(exec_macro(macro, obj, args, block) || all)
905 else
906 else
906 h(all)
907 h(all)
907 end
908 end
@@ -24,7 +24,7 module Redmine
24 Redmine::WikiFormatting::Macros.available_macros.key?(name.to_sym)
24 Redmine::WikiFormatting::Macros.available_macros.key?(name.to_sym)
25 end
25 end
26
26
27 def exec_macro(name, obj, args)
27 def exec_macro(name, obj, args, text)
28 macro_options = Redmine::WikiFormatting::Macros.available_macros[name.to_sym]
28 macro_options = Redmine::WikiFormatting::Macros.available_macros[name.to_sym]
29 return unless macro_options
29 return unless macro_options
30
30
@@ -34,7 +34,13 module Redmine
34 end
34 end
35
35
36 begin
36 begin
37 send(method_name, obj, args) if respond_to?(method_name)
37 if self.class.instance_method(method_name).arity == 3
38 send(method_name, obj, args, text)
39 elsif text
40 raise "This macro does not accept a block of text"
41 else
42 send(method_name, obj, args)
43 end
38 rescue => e
44 rescue => e
39 "<div class=\"flash error\">Error executing the <strong>#{h name}</strong> macro (#{h e.to_s})</div>".html_safe
45 "<div class=\"flash error\">Error executing the <strong>#{h name}</strong> macro (#{h e.to_s})</div>".html_safe
40 end
46 end
@@ -55,9 +61,11 module Redmine
55
61
56 class << self
62 class << self
57 # Called with a block to define additional macros.
63 # Called with a block to define additional macros.
58 # Macro blocks accept 2 arguments:
64 # Macro blocks accept 2 or 3 arguments:
59 # * obj: the object that is rendered
65 # * obj: the object that is rendered
60 # * args: macro arguments
66 # * args: macro arguments
67 # * text: a block of text (if the macro accepts
68 # 3 arguments)
61 #
69 #
62 # Plugins can use this method to define new macros:
70 # Plugins can use this method to define new macros:
63 #
71 #
@@ -66,7 +74,33 module Redmine
66 # macro :my_macro do |obj, args|
74 # macro :my_macro do |obj, args|
67 # "My macro output"
75 # "My macro output"
68 # end
76 # end
77 #
78 # desc "This is my macro that accepts a block of text"
79 # macro :my_macro do |obj, args, text|
80 # "My macro output"
81 # end
69 # end
82 # end
83 #
84 # Macros are invoked in formatted text using the following
85 # syntax:
86 #
87 # No arguments:
88 # {{my_macro}}
89 #
90 # With arguments:
91 # {{my_macro(arg1, arg2)}}
92 #
93 # With a block of text:
94 # {{my_macro
95 # multiple lines
96 # of text
97 # }}
98 #
99 # With arguments and a block of text
100 # {{my_macro(arg1, arg2)
101 # multiple lines
102 # of text
103 # }}
70 def register(&block)
104 def register(&block)
71 class_eval(&block) if block_given?
105 class_eval(&block) if block_given?
72 end
106 end
@@ -79,7 +113,7 module Redmine
79 #
113 #
80 # Examples:
114 # Examples:
81 # By default, when the macro is invoked, the coma separated list of arguments
115 # By default, when the macro is invoked, the coma separated list of arguments
82 # is parsed and passed to the macro block as an array:
116 # is split and passed to the macro block as an array:
83 #
117 #
84 # macro :my_macro do |obj, args|
118 # macro :my_macro do |obj, args|
85 # # args is an array
119 # # args is an array
@@ -106,8 +140,11 module Redmine
106
140
107 # Builtin macros
141 # Builtin macros
108 desc "Sample macro."
142 desc "Sample macro."
109 macro :hello_world do |obj, args|
143 macro :hello_world do |obj, args, text|
110 h("Hello world! Object: #{obj.class.name}, " + (args.empty? ? "Called with no argument." : "Arguments: #{args.join(', ')}"))
144 h("Hello world! Object: #{obj.class.name}, " +
145 (args.empty? ? "Called with no argument" : "Arguments: #{args.join(', ')}") +
146 " and " + (text.present? ? "a #{text.size} bytes long block of text." : "no block of text.")
147 )
111 end
148 end
112
149
113 desc "Displays a list of all available macros, including description if available."
150 desc "Displays a list of all available macros, including description if available."
@@ -65,6 +65,19 class Redmine::WikiFormatting::MacrosTest < ActionView::TestCase
65 assert_equal '<p>Bar: () (String)</p>', textilizable("{{bar()}}")
65 assert_equal '<p>Bar: () (String)</p>', textilizable("{{bar()}}")
66 end
66 end
67
67
68 def test_macro_registration_with_3_args_should_receive_text_argument
69 Redmine::WikiFormatting::Macros.register do
70 macro :baz do |obj, args, text|
71 "Baz: (#{args.join(',')}) (#{text.class.name}) (#{text})"
72 end
73 end
74
75 assert_equal "<p>Baz: () (NilClass) ()</p>", textilizable("{{baz}}")
76 assert_equal "<p>Baz: () (NilClass) ()</p>", textilizable("{{baz()}}")
77 assert_equal "<p>Baz: () (String) (line1\nline2)</p>", textilizable("{{baz()\nline1\nline2\n}}")
78 assert_equal "<p>Baz: (arg1,arg2) (String) (line1\nline2)</p>", textilizable("{{baz(arg1, arg2)\nline1\nline2\n}}")
79 end
80
68 def test_multiple_macros_on_the_same_line
81 def test_multiple_macros_on_the_same_line
69 Redmine::WikiFormatting::Macros.macro :foo do |obj, args|
82 Redmine::WikiFormatting::Macros.macro :foo do |obj, args|
70 args.any? ? "args: #{args.join(',')}" : "no args"
83 args.any? ? "args: #{args.join(',')}" : "no args"
@@ -79,14 +92,15 class Redmine::WikiFormatting::MacrosTest < ActionView::TestCase
79 def test_macro_should_receive_the_object_as_argument_when_with_object_and_attribute
92 def test_macro_should_receive_the_object_as_argument_when_with_object_and_attribute
80 issue = Issue.find(1)
93 issue = Issue.find(1)
81 issue.description = "{{hello_world}}"
94 issue.description = "{{hello_world}}"
82 assert_equal '<p>Hello world! Object: Issue, Called with no argument.</p>', textilizable(issue, :description)
95 assert_equal '<p>Hello world! Object: Issue, Called with no argument and no block of text.</p>', textilizable(issue, :description)
83 end
96 end
84
97
85 def test_macro_should_receive_the_object_as_argument_when_called_with_object_option
98 def test_macro_should_receive_the_object_as_argument_when_called_with_object_option
86 text = "{{hello_world}}"
99 text = "{{hello_world}}"
87 assert_equal '<p>Hello world! Object: Issue, Called with no argument.</p>', textilizable(text, :object => Issue.find(1))
100 assert_equal '<p>Hello world! Object: Issue, Called with no argument and no block of text.</p>', textilizable(text, :object => Issue.find(1))
88 end
101 end
89
102
103
90 def test_macro_exception_should_be_displayed
104 def test_macro_exception_should_be_displayed
91 Redmine::WikiFormatting::Macros.macro :exception do |obj, args|
105 Redmine::WikiFormatting::Macros.macro :exception do |obj, args|
92 raise "My message"
106 raise "My message"
@@ -237,14 +251,14 class Redmine::WikiFormatting::MacrosTest < ActionView::TestCase
237 RAW
251 RAW
238
252
239 expected = <<-EXPECTED
253 expected = <<-EXPECTED
240 <p>Hello world! Object: NilClass, Arguments: foo</p>
254 <p>Hello world! Object: NilClass, Arguments: foo and no block of text.</p>
241
255
242 <pre>
256 <pre>
243 {{hello_world(pre)}}
257 {{hello_world(pre)}}
244 !{{hello_world(pre)}}
258 !{{hello_world(pre)}}
245 </pre>
259 </pre>
246
260
247 <p>Hello world! Object: NilClass, Arguments: bar</p>
261 <p>Hello world! Object: NilClass, Arguments: bar and no block of text.</p>
248 EXPECTED
262 EXPECTED
249
263
250 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(text).gsub(%r{[\r\n\t]}, '')
264 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(text).gsub(%r{[\r\n\t]}, '')
@@ -257,6 +271,6 EXPECTED
257
271
258 def test_macros_should_not_mangle_next_macros_outputs
272 def test_macros_should_not_mangle_next_macros_outputs
259 text = '{{macro(2)}} !{{macro(2)}} {{hello_world(foo)}}'
273 text = '{{macro(2)}} !{{macro(2)}} {{hello_world(foo)}}'
260 assert_equal '<p>{{macro(2)}} {{macro(2)}} Hello world! Object: NilClass, Arguments: foo</p>', textilizable(text)
274 assert_equal '<p>{{macro(2)}} {{macro(2)}} Hello world! Object: NilClass, Arguments: foo and no block of text.</p>', textilizable(text)
261 end
275 end
262 end
276 end
General Comments 0
You need to be logged in to leave comments. Login now