git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@10210 e93f8b46-1217-0410-a6f0-8f06a7374b81tags/2.1.0
@@ -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 |
@@ -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." |
@@ -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 |