##// END OF EJS Templates
Updates macro description (#10789)....
Jean-Philippe Lang -
r10219:34b64d646fc1
parent child
Show More
@@ -1,233 +1,234
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 module Redmine
18 module Redmine
19 module WikiFormatting
19 module WikiFormatting
20 module Macros
20 module Macros
21 module Definitions
21 module Definitions
22 # Returns true if +name+ is the name of an existing macro
22 # Returns true if +name+ is the name of an existing macro
23 def macro_exists?(name)
23 def macro_exists?(name)
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, text)
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
31 method_name = "macro_#{name}"
31 method_name = "macro_#{name}"
32 unless macro_options[:parse_args] == false
32 unless macro_options[:parse_args] == false
33 args = args.split(',').map(&:strip)
33 args = args.split(',').map(&:strip)
34 end
34 end
35
35
36 begin
36 begin
37 if self.class.instance_method(method_name).arity == 3
37 if self.class.instance_method(method_name).arity == 3
38 send(method_name, obj, args, text)
38 send(method_name, obj, args, text)
39 elsif text
39 elsif text
40 raise "This macro does not accept a block of text"
40 raise "This macro does not accept a block of text"
41 else
41 else
42 send(method_name, obj, args)
42 send(method_name, obj, args)
43 end
43 end
44 rescue => e
44 rescue => e
45 "<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
46 end
46 end
47 end
47 end
48
48
49 def extract_macro_options(args, *keys)
49 def extract_macro_options(args, *keys)
50 options = {}
50 options = {}
51 while args.last.to_s.strip =~ %r{^(.+)\=(.+)$} && keys.include?($1.downcase.to_sym)
51 while args.last.to_s.strip =~ %r{^(.+)\=(.+)$} && keys.include?($1.downcase.to_sym)
52 options[$1.downcase.to_sym] = $2
52 options[$1.downcase.to_sym] = $2
53 args.pop
53 args.pop
54 end
54 end
55 return [args, options]
55 return [args, options]
56 end
56 end
57 end
57 end
58
58
59 @@available_macros = {}
59 @@available_macros = {}
60 mattr_accessor :available_macros
60 mattr_accessor :available_macros
61
61
62 class << self
62 class << self
63 # Plugins can use this method to define new macros:
63 # Plugins can use this method to define new macros:
64 #
64 #
65 # Redmine::WikiFormatting::Macros.register do
65 # Redmine::WikiFormatting::Macros.register do
66 # desc "This is my macro"
66 # desc "This is my macro"
67 # macro :my_macro do |obj, args|
67 # macro :my_macro do |obj, args|
68 # "My macro output"
68 # "My macro output"
69 # end
69 # end
70 #
70 #
71 # desc "This is my macro that accepts a block of text"
71 # desc "This is my macro that accepts a block of text"
72 # macro :my_macro do |obj, args, text|
72 # macro :my_macro do |obj, args, text|
73 # "My macro output"
73 # "My macro output"
74 # end
74 # end
75 # end
75 # end
76 def register(&block)
76 def register(&block)
77 class_eval(&block) if block_given?
77 class_eval(&block) if block_given?
78 end
78 end
79
79
80 # Defines a new macro with the given name, options and block.
80 # Defines a new macro with the given name, options and block.
81 #
81 #
82 # Options:
82 # Options:
83 # * :desc - A description of the macro
83 # * :desc - A description of the macro
84 # * :parse_args => false - Disables arguments parsing (the whole arguments
84 # * :parse_args => false - Disables arguments parsing (the whole arguments
85 # string is passed to the macro)
85 # string is passed to the macro)
86 #
86 #
87 # Macro blocks accept 2 or 3 arguments:
87 # Macro blocks accept 2 or 3 arguments:
88 # * obj: the object that is rendered (eg. an Issue, a WikiContent...)
88 # * obj: the object that is rendered (eg. an Issue, a WikiContent...)
89 # * args: macro arguments
89 # * args: macro arguments
90 # * text: the block of text given to the macro (should be present only if the
90 # * text: the block of text given to the macro (should be present only if the
91 # macro accepts a block of text). text is a String or nil if the macro is
91 # macro accepts a block of text). text is a String or nil if the macro is
92 # invoked without a block of text.
92 # invoked without a block of text.
93 #
93 #
94 # Examples:
94 # Examples:
95 # By default, when the macro is invoked, the coma separated list of arguments
95 # By default, when the macro is invoked, the coma separated list of arguments
96 # is split and passed to the macro block as an array. If no argument is given
96 # is split and passed to the macro block as an array. If no argument is given
97 # the macro will be invoked with an empty array:
97 # the macro will be invoked with an empty array:
98 #
98 #
99 # macro :my_macro do |obj, args|
99 # macro :my_macro do |obj, args|
100 # # args is an array
100 # # args is an array
101 # # and this macro do not accept a block of text
101 # # and this macro do not accept a block of text
102 # end
102 # end
103 #
103 #
104 # You can disable arguments spliting with the :parse_args => false option. In
104 # You can disable arguments spliting with the :parse_args => false option. In
105 # this case, the full string of arguments is passed to the macro:
105 # this case, the full string of arguments is passed to the macro:
106 #
106 #
107 # macro :my_macro, :parse_args => false do |obj, args|
107 # macro :my_macro, :parse_args => false do |obj, args|
108 # # args is a string
108 # # args is a string
109 # end
109 # end
110 #
110 #
111 # Macro can optionally accept a block of text:
111 # Macro can optionally accept a block of text:
112 #
112 #
113 # macro :my_macro do |obj, args, text|
113 # macro :my_macro do |obj, args, text|
114 # # this macro accepts a block of text
114 # # this macro accepts a block of text
115 # end
115 # end
116 #
116 #
117 # Macros are invoked in formatted text using double curly brackets. Arguments
117 # Macros are invoked in formatted text using double curly brackets. Arguments
118 # must be enclosed in parenthesis if any. A new line after the macro name or the
118 # must be enclosed in parenthesis if any. A new line after the macro name or the
119 # arguments starts the block of text that will be passe to the macro (invoking
119 # arguments starts the block of text that will be passe to the macro (invoking
120 # a macro that do not accept a block of text with some text will fail).
120 # a macro that do not accept a block of text with some text will fail).
121 # Examples:
121 # Examples:
122 #
122 #
123 # No arguments:
123 # No arguments:
124 # {{my_macro}}
124 # {{my_macro}}
125 #
125 #
126 # With arguments:
126 # With arguments:
127 # {{my_macro(arg1, arg2)}}
127 # {{my_macro(arg1, arg2)}}
128 #
128 #
129 # With a block of text:
129 # With a block of text:
130 # {{my_macro
130 # {{my_macro
131 # multiple lines
131 # multiple lines
132 # of text
132 # of text
133 # }}
133 # }}
134 #
134 #
135 # With arguments and a block of text
135 # With arguments and a block of text
136 # {{my_macro(arg1, arg2)
136 # {{my_macro(arg1, arg2)
137 # multiple lines
137 # multiple lines
138 # of text
138 # of text
139 # }}
139 # }}
140 #
140 #
141 # If a block of text is given, the closing tag }} must be at the start of a new line.
141 # If a block of text is given, the closing tag }} must be at the start of a new line.
142 def macro(name, options={}, &block)
142 def macro(name, options={}, &block)
143 options.assert_valid_keys(:desc, :parse_args)
143 options.assert_valid_keys(:desc, :parse_args)
144 unless name.to_s.match(/\A\w+\z/)
144 unless name.to_s.match(/\A\w+\z/)
145 raise "Invalid macro name: #{name} (only 0-9, A-Z, a-z and _ characters are accepted)"
145 raise "Invalid macro name: #{name} (only 0-9, A-Z, a-z and _ characters are accepted)"
146 end
146 end
147 unless block_given?
147 unless block_given?
148 raise "Can not create a macro without a block!"
148 raise "Can not create a macro without a block!"
149 end
149 end
150 name = name.to_sym if name.is_a?(String)
150 name = name.to_sym if name.is_a?(String)
151 available_macros[name] = {:desc => @@desc || ''}.merge(options)
151 available_macros[name] = {:desc => @@desc || ''}.merge(options)
152 @@desc = nil
152 @@desc = nil
153 Definitions.send :define_method, "macro_#{name}".downcase, &block
153 Definitions.send :define_method, "macro_#{name}".downcase, &block
154 end
154 end
155
155
156 # Sets description for the next macro to be defined
156 # Sets description for the next macro to be defined
157 def desc(txt)
157 def desc(txt)
158 @@desc = txt
158 @@desc = txt
159 end
159 end
160 end
160 end
161
161
162 # Builtin macros
162 # Builtin macros
163 desc "Sample macro."
163 desc "Sample macro."
164 macro :hello_world do |obj, args, text|
164 macro :hello_world do |obj, args, text|
165 h("Hello world! Object: #{obj.class.name}, " +
165 h("Hello world! Object: #{obj.class.name}, " +
166 (args.empty? ? "Called with no argument" : "Arguments: #{args.join(', ')}") +
166 (args.empty? ? "Called with no argument" : "Arguments: #{args.join(', ')}") +
167 " and " + (text.present? ? "a #{text.size} bytes long block of text." : "no block of text.")
167 " and " + (text.present? ? "a #{text.size} bytes long block of text." : "no block of text.")
168 )
168 )
169 end
169 end
170
170
171 desc "Displays a list of all available macros, including description if available."
171 desc "Displays a list of all available macros, including description if available."
172 macro :macro_list do |obj, args|
172 macro :macro_list do |obj, args|
173 out = ''.html_safe
173 out = ''.html_safe
174 @@available_macros.each do |macro, options|
174 @@available_macros.each do |macro, options|
175 out << content_tag('dt', content_tag('code', macro.to_s))
175 out << content_tag('dt', content_tag('code', macro.to_s))
176 out << content_tag('dd', textilizable(options[:desc]))
176 out << content_tag('dd', textilizable(options[:desc]))
177 end
177 end
178 content_tag('dl', out)
178 content_tag('dl', out)
179 end
179 end
180
180
181 desc "Displays a list of child pages. With no argument, it displays the child pages of the current wiki page. Examples:\n\n" +
181 desc "Displays a list of child pages. With no argument, it displays the child pages of the current wiki page. Examples:\n\n" +
182 " !{{child_pages}} -- can be used from a wiki page only\n" +
182 " !{{child_pages}} -- can be used from a wiki page only\n" +
183 " !{{child_pages(depth=2)}} -- display 2 levels nesting only\n"
183 " !{{child_pages(Foo)}} -- lists all children of page Foo\n" +
184 " !{{child_pages(Foo)}} -- lists all children of page Foo\n" +
184 " !{{child_pages(Foo, parent=1)}} -- same as above with a link to page Foo"
185 " !{{child_pages(Foo, parent=1)}} -- same as above with a link to page Foo"
185 macro :child_pages do |obj, args|
186 macro :child_pages do |obj, args|
186 args, options = extract_macro_options(args, :parent, :depth)
187 args, options = extract_macro_options(args, :parent, :depth)
187 options[:depth] = options[:depth].to_i if options[:depth].present?
188 options[:depth] = options[:depth].to_i if options[:depth].present?
188
189
189 page = nil
190 page = nil
190 if args.size > 0
191 if args.size > 0
191 page = Wiki.find_page(args.first.to_s, :project => @project)
192 page = Wiki.find_page(args.first.to_s, :project => @project)
192 elsif obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)
193 elsif obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)
193 page = obj.page
194 page = obj.page
194 else
195 else
195 raise 'With no argument, this macro can be called from wiki pages only.'
196 raise 'With no argument, this macro can be called from wiki pages only.'
196 end
197 end
197 raise 'Page not found' if page.nil? || !User.current.allowed_to?(:view_wiki_pages, page.wiki.project)
198 raise 'Page not found' if page.nil? || !User.current.allowed_to?(:view_wiki_pages, page.wiki.project)
198 pages = page.self_and_descendants(options[:depth]).group_by(&:parent_id)
199 pages = page.self_and_descendants(options[:depth]).group_by(&:parent_id)
199 render_page_hierarchy(pages, options[:parent] ? page.parent_id : page.id)
200 render_page_hierarchy(pages, options[:parent] ? page.parent_id : page.id)
200 end
201 end
201
202
202 desc "Include a wiki page. Example:\n\n !{{include(Foo)}}\n\nor to include a page of a specific project wiki:\n\n !{{include(projectname:Foo)}}"
203 desc "Include a wiki page. Example:\n\n !{{include(Foo)}}\n\nor to include a page of a specific project wiki:\n\n !{{include(projectname:Foo)}}"
203 macro :include do |obj, args|
204 macro :include do |obj, args|
204 page = Wiki.find_page(args.first.to_s, :project => @project)
205 page = Wiki.find_page(args.first.to_s, :project => @project)
205 raise 'Page not found' if page.nil? || !User.current.allowed_to?(:view_wiki_pages, page.wiki.project)
206 raise 'Page not found' if page.nil? || !User.current.allowed_to?(:view_wiki_pages, page.wiki.project)
206 @included_wiki_pages ||= []
207 @included_wiki_pages ||= []
207 raise 'Circular inclusion detected' if @included_wiki_pages.include?(page.title)
208 raise 'Circular inclusion detected' if @included_wiki_pages.include?(page.title)
208 @included_wiki_pages << page.title
209 @included_wiki_pages << page.title
209 out = textilizable(page.content, :text, :attachments => page.attachments, :headings => false)
210 out = textilizable(page.content, :text, :attachments => page.attachments, :headings => false)
210 @included_wiki_pages.pop
211 @included_wiki_pages.pop
211 out
212 out
212 end
213 end
213
214
214 desc "Displays a clickable thumbnail of an attached image. Examples:\n\n<pre>{{thumbnail(image.png)}}\n{{thumbnail(image.png, size=300, title=Thumbnail)}}</pre>"
215 desc "Displays a clickable thumbnail of an attached image. Examples:\n\n<pre>{{thumbnail(image.png)}}\n{{thumbnail(image.png, size=300, title=Thumbnail)}}</pre>"
215 macro :thumbnail do |obj, args|
216 macro :thumbnail do |obj, args|
216 args, options = extract_macro_options(args, :size, :title)
217 args, options = extract_macro_options(args, :size, :title)
217 filename = args.first
218 filename = args.first
218 raise 'Filename required' unless filename.present?
219 raise 'Filename required' unless filename.present?
219 size = options[:size]
220 size = options[:size]
220 raise 'Invalid size parameter' unless size.nil? || size.match(/^\d+$/)
221 raise 'Invalid size parameter' unless size.nil? || size.match(/^\d+$/)
221 size = size.to_i
222 size = size.to_i
222 size = nil unless size > 0
223 size = nil unless size > 0
223 if obj && obj.respond_to?(:attachments) && attachment = Attachment.latest_attach(obj.attachments, filename)
224 if obj && obj.respond_to?(:attachments) && attachment = Attachment.latest_attach(obj.attachments, filename)
224 title = options[:title] || attachment.title
225 title = options[:title] || attachment.title
225 img = image_tag(url_for(:controller => 'attachments', :action => 'thumbnail', :id => attachment, :size => size), :alt => attachment.filename)
226 img = image_tag(url_for(:controller => 'attachments', :action => 'thumbnail', :id => attachment, :size => size), :alt => attachment.filename)
226 link_to(img, url_for(:controller => 'attachments', :action => 'show', :id => attachment), :class => 'thumbnail', :title => title)
227 link_to(img, url_for(:controller => 'attachments', :action => 'show', :id => attachment), :class => 'thumbnail', :title => title)
227 else
228 else
228 raise "Attachment #{filename} not found"
229 raise "Attachment #{filename} not found"
229 end
230 end
230 end
231 end
231 end
232 end
232 end
233 end
233 end
234 end
General Comments 0
You need to be logged in to leave comments. Login now