@@ -866,10 +866,11 module ApplicationHelper | |||
|
866 | 866 | ( |
|
867 | 867 | \{\{ # opening tag |
|
868 | 868 | ([\w]+) # macro name |
|
869 |
(\(( |
|
|
869 | (\(([^\n\r]*?)\))? # optional arguments | |
|
870 | ([\n\r].*[\n\r])? # optional block of text | |
|
870 | 871 | \}\} # closing tag |
|
871 | 872 | ) |
|
872 | )/x unless const_defined?(:MACROS_RE) | |
|
873 | )/mx unless const_defined?(:MACROS_RE) | |
|
873 | 874 | |
|
874 | 875 | MACRO_SUB_RE = /( |
|
875 | 876 | \{\{ |
@@ -899,9 +900,9 module ApplicationHelper | |||
|
899 | 900 | all, index = $1, $2.to_i |
|
900 | 901 | orig = macros.delete(index) |
|
901 | 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 | 904 | if esc.nil? |
|
904 | h(exec_macro(macro, obj, args) || all) | |
|
905 | h(exec_macro(macro, obj, args, block) || all) | |
|
905 | 906 | else |
|
906 | 907 | h(all) |
|
907 | 908 | end |
@@ -24,7 +24,7 module Redmine | |||
|
24 | 24 | Redmine::WikiFormatting::Macros.available_macros.key?(name.to_sym) |
|
25 | 25 | end |
|
26 | 26 | |
|
27 | def exec_macro(name, obj, args) | |
|
27 | def exec_macro(name, obj, args, text) | |
|
28 | 28 | macro_options = Redmine::WikiFormatting::Macros.available_macros[name.to_sym] |
|
29 | 29 | return unless macro_options |
|
30 | 30 | |
@@ -34,7 +34,13 module Redmine | |||
|
34 | 34 | end |
|
35 | 35 | |
|
36 | 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 | 44 | rescue => e |
|
39 | 45 | "<div class=\"flash error\">Error executing the <strong>#{h name}</strong> macro (#{h e.to_s})</div>".html_safe |
|
40 | 46 | end |
@@ -55,9 +61,11 module Redmine | |||
|
55 | 61 | |
|
56 | 62 | class << self |
|
57 | 63 | # Called with a block to define additional macros. |
|
58 | # Macro blocks accept 2 arguments: | |
|
64 | # Macro blocks accept 2 or 3 arguments: | |
|
59 | 65 | # * obj: the object that is rendered |
|
60 | 66 | # * args: macro arguments |
|
67 | # * text: a block of text (if the macro accepts | |
|
68 | # 3 arguments) | |
|
61 | 69 | # |
|
62 | 70 | # Plugins can use this method to define new macros: |
|
63 | 71 | # |
@@ -66,7 +74,33 module Redmine | |||
|
66 | 74 | # macro :my_macro do |obj, args| |
|
67 | 75 | # "My macro output" |
|
68 | 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 | 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 | 104 | def register(&block) |
|
71 | 105 | class_eval(&block) if block_given? |
|
72 | 106 | end |
@@ -79,7 +113,7 module Redmine | |||
|
79 | 113 | # |
|
80 | 114 | # Examples: |
|
81 | 115 | # By default, when the macro is invoked, the coma separated list of arguments |
|
82 |
# is |
|
|
116 | # is split and passed to the macro block as an array: | |
|
83 | 117 | # |
|
84 | 118 | # macro :my_macro do |obj, args| |
|
85 | 119 | # # args is an array |
@@ -106,8 +140,11 module Redmine | |||
|
106 | 140 | |
|
107 | 141 | # Builtin macros |
|
108 | 142 | desc "Sample macro." |
|
109 | macro :hello_world do |obj, args| | |
|
110 | h("Hello world! Object: #{obj.class.name}, " + (args.empty? ? "Called with no argument." : "Arguments: #{args.join(', ')}")) | |
|
143 | macro :hello_world do |obj, args, text| | |
|
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 | 148 | end |
|
112 | 149 | |
|
113 | 150 | desc "Displays a list of all available macros, including description if available." |
@@ -65,6 +65,19 class Redmine::WikiFormatting::MacrosTest < ActionView::TestCase | |||
|
65 | 65 | assert_equal '<p>Bar: () (String)</p>', textilizable("{{bar()}}") |
|
66 | 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 | 81 | def test_multiple_macros_on_the_same_line |
|
69 | 82 | Redmine::WikiFormatting::Macros.macro :foo do |obj, args| |
|
70 | 83 | args.any? ? "args: #{args.join(',')}" : "no args" |
@@ -79,14 +92,15 class Redmine::WikiFormatting::MacrosTest < ActionView::TestCase | |||
|
79 | 92 | def test_macro_should_receive_the_object_as_argument_when_with_object_and_attribute |
|
80 | 93 | issue = Issue.find(1) |
|
81 | 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 | 96 | end |
|
84 | 97 | |
|
85 | 98 | def test_macro_should_receive_the_object_as_argument_when_called_with_object_option |
|
86 | 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 | 101 | end |
|
89 | 102 | |
|
103 | ||
|
90 | 104 | def test_macro_exception_should_be_displayed |
|
91 | 105 | Redmine::WikiFormatting::Macros.macro :exception do |obj, args| |
|
92 | 106 | raise "My message" |
@@ -237,14 +251,14 class Redmine::WikiFormatting::MacrosTest < ActionView::TestCase | |||
|
237 | 251 | RAW |
|
238 | 252 | |
|
239 | 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 | 256 | <pre> |
|
243 | 257 | {{hello_world(pre)}} |
|
244 | 258 | !{{hello_world(pre)}} |
|
245 | 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 | 262 | EXPECTED |
|
249 | 263 | |
|
250 | 264 | assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(text).gsub(%r{[\r\n\t]}, '') |
@@ -257,6 +271,6 EXPECTED | |||
|
257 | 271 | |
|
258 | 272 | def test_macros_should_not_mangle_next_macros_outputs |
|
259 | 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 | 275 | end |
|
262 | 276 | end |
General Comments 0
You need to be logged in to leave comments.
Login now