Let macros optionally accept a block of text (#3061).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@10210 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
parent
73aece0baf
commit
fc3a09e49a
|
@ -866,10 +866,11 @@ module ApplicationHelper
|
||||||
(
|
(
|
||||||
\{\{ # opening tag
|
\{\{ # opening tag
|
||||||
([\w]+) # macro name
|
([\w]+) # macro name
|
||||||
(\((.*?)\))? # optional arguments
|
(\(([^\n\r]*?)\))? # optional arguments
|
||||||
|
([\n\r].*[\n\r])? # optional block of text
|
||||||
\}\} # closing tag
|
\}\} # closing tag
|
||||||
)
|
)
|
||||||
)/x unless const_defined?(:MACROS_RE)
|
)/mx unless const_defined?(:MACROS_RE)
|
||||||
|
|
||||||
MACRO_SUB_RE = /(
|
MACRO_SUB_RE = /(
|
||||||
\{\{
|
\{\{
|
||||||
|
@ -899,9 +900,9 @@ module ApplicationHelper
|
||||||
all, index = $1, $2.to_i
|
all, index = $1, $2.to_i
|
||||||
orig = macros.delete(index)
|
orig = macros.delete(index)
|
||||||
if execute && orig && orig =~ MACROS_RE
|
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?
|
if esc.nil?
|
||||||
h(exec_macro(macro, obj, args) || all)
|
h(exec_macro(macro, obj, args, block) || all)
|
||||||
else
|
else
|
||||||
h(all)
|
h(all)
|
||||||
end
|
end
|
||||||
|
|
|
@ -24,7 +24,7 @@ module Redmine
|
||||||
Redmine::WikiFormatting::Macros.available_macros.key?(name.to_sym)
|
Redmine::WikiFormatting::Macros.available_macros.key?(name.to_sym)
|
||||||
end
|
end
|
||||||
|
|
||||||
def exec_macro(name, obj, args)
|
def exec_macro(name, obj, args, text)
|
||||||
macro_options = Redmine::WikiFormatting::Macros.available_macros[name.to_sym]
|
macro_options = Redmine::WikiFormatting::Macros.available_macros[name.to_sym]
|
||||||
return unless macro_options
|
return unless macro_options
|
||||||
|
|
||||||
|
@ -34,7 +34,13 @@ module Redmine
|
||||||
end
|
end
|
||||||
|
|
||||||
begin
|
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
|
rescue => e
|
||||||
"<div class=\"flash error\">Error executing the <strong>#{h name}</strong> macro (#{h e.to_s})</div>".html_safe
|
"<div class=\"flash error\">Error executing the <strong>#{h name}</strong> macro (#{h e.to_s})</div>".html_safe
|
||||||
end
|
end
|
||||||
|
@ -55,9 +61,11 @@ module Redmine
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
# Called with a block to define additional macros.
|
# 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
|
# * obj: the object that is rendered
|
||||||
# * args: macro arguments
|
# * args: macro arguments
|
||||||
|
# * text: a block of text (if the macro accepts
|
||||||
|
# 3 arguments)
|
||||||
#
|
#
|
||||||
# Plugins can use this method to define new macros:
|
# Plugins can use this method to define new macros:
|
||||||
#
|
#
|
||||||
|
@ -66,7 +74,33 @@ module Redmine
|
||||||
# macro :my_macro do |obj, args|
|
# macro :my_macro do |obj, args|
|
||||||
# "My macro output"
|
# "My macro output"
|
||||||
# end
|
# end
|
||||||
|
#
|
||||||
|
# desc "This is my macro that accepts a block of text"
|
||||||
|
# macro :my_macro do |obj, args, text|
|
||||||
|
# "My macro output"
|
||||||
# end
|
# 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)
|
def register(&block)
|
||||||
class_eval(&block) if block_given?
|
class_eval(&block) if block_given?
|
||||||
end
|
end
|
||||||
|
@ -79,7 +113,7 @@ module Redmine
|
||||||
#
|
#
|
||||||
# Examples:
|
# Examples:
|
||||||
# By default, when the macro is invoked, the coma separated list of arguments
|
# 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|
|
# macro :my_macro do |obj, args|
|
||||||
# # args is an array
|
# # args is an array
|
||||||
|
@ -106,8 +140,11 @@ module Redmine
|
||||||
|
|
||||||
# Builtin macros
|
# Builtin macros
|
||||||
desc "Sample macro."
|
desc "Sample macro."
|
||||||
macro :hello_world do |obj, args|
|
macro :hello_world do |obj, args, text|
|
||||||
h("Hello world! Object: #{obj.class.name}, " + (args.empty? ? "Called with no argument." : "Arguments: #{args.join(', ')}"))
|
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
|
end
|
||||||
|
|
||||||
desc "Displays a list of all available macros, including description if available."
|
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()}}")
|
assert_equal '<p>Bar: () (String)</p>', textilizable("{{bar()}}")
|
||||||
end
|
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
|
def test_multiple_macros_on_the_same_line
|
||||||
Redmine::WikiFormatting::Macros.macro :foo do |obj, args|
|
Redmine::WikiFormatting::Macros.macro :foo do |obj, args|
|
||||||
args.any? ? "args: #{args.join(',')}" : "no 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
|
def test_macro_should_receive_the_object_as_argument_when_with_object_and_attribute
|
||||||
issue = Issue.find(1)
|
issue = Issue.find(1)
|
||||||
issue.description = "{{hello_world}}"
|
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
|
end
|
||||||
|
|
||||||
def test_macro_should_receive_the_object_as_argument_when_called_with_object_option
|
def test_macro_should_receive_the_object_as_argument_when_called_with_object_option
|
||||||
text = "{{hello_world}}"
|
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
|
end
|
||||||
|
|
||||||
|
|
||||||
def test_macro_exception_should_be_displayed
|
def test_macro_exception_should_be_displayed
|
||||||
Redmine::WikiFormatting::Macros.macro :exception do |obj, args|
|
Redmine::WikiFormatting::Macros.macro :exception do |obj, args|
|
||||||
raise "My message"
|
raise "My message"
|
||||||
|
@ -237,14 +251,14 @@ class Redmine::WikiFormatting::MacrosTest < ActionView::TestCase
|
||||||
RAW
|
RAW
|
||||||
|
|
||||||
expected = <<-EXPECTED
|
expected = <<-EXPECTED
|
||||||
<p>Hello world! Object: NilClass, Arguments: foo</p>
|
<p>Hello world! Object: NilClass, Arguments: foo and no block of text.</p>
|
||||||
|
|
||||||
<pre>
|
<pre>
|
||||||
{{hello_world(pre)}}
|
{{hello_world(pre)}}
|
||||||
!{{hello_world(pre)}}
|
!{{hello_world(pre)}}
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
<p>Hello world! Object: NilClass, Arguments: bar</p>
|
<p>Hello world! Object: NilClass, Arguments: bar and no block of text.</p>
|
||||||
EXPECTED
|
EXPECTED
|
||||||
|
|
||||||
assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(text).gsub(%r{[\r\n\t]}, '')
|
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
|
def test_macros_should_not_mangle_next_macros_outputs
|
||||||
text = '{{macro(2)}} !{{macro(2)}} {{hello_world(foo)}}'
|
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
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue