Daily Code Reading #34 – Redmine WikiFormatting#execute_macros

Today I’m digging back into the WikiFormatting to read through #execute_macros.

The Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module Redmine
  module WikiFormatting
    class < e
              "<div class="flash error">Error executing the <strong>#{macro}</strong> macro (#{e})</div>"
            end || all
          else
            all
          end
        end
      end
 
 
    end
  end
end

Review

It will be best to use an example macro in order to trace the flow of this method. Redmine has a macro called include which can be used to include another wiki page into the current one. Lets use the following text as our page content:

This is a page that will include Design

{{include(Design)}}

Tracing the flow of this macro, the call stack looks like:

  1. ApplicationHelper#textilizable calls Redmine::WikiFormatting.to_html with a block { |macro, args| exec_macro(macro, obj, args) }
  2. Redmine::WikiFormatting.to_html uses the configured formatters to format the plain text
  3. Since Redmine::WikiFormatting.to_html was passed a block, it runs execute_macros(text, block) which passes the block from #textilizable

Now the program ends up in the method above with the converted text and the block from #textilizable. At this point it runs the MACROS_RE regular expression through gsub! so it can replace all of the macros with their content.

MACROS_RE regular expression

1
2
3
4
5
6
7
8
9
      MACROS_RE = /
                    (!)?                        # escaping
                    (
                    \{\{                        # opening tag
                    ([\w]+)                     # macro name
                    (\(([^\}]*)\))?             # optional arguments
                    \}\}                        # closing tag
                    )
                  /x unless const_defined?(:MACROS_RE)

The first thing to notice is that the expression uses the /x modifier. This tells it to ignore whitespace between each regular expression token and lets the expression be written on multiple lines with inline comments. Without it, the expression would be much harder to read:

1
/(!)(\{\{([\w]+)(\(([^\}]*)\))?\}\})/

I think the comments explain what parts match but here are the different match sets:

  1. the “!” prefix to escape
  2. the entire macro after the escape
  3. the macro name, without the {}
  4. all of the arguments enclosed with ()
  5. all of the arguments

gsub

Having done a lot of regular expression work on my book, I’ve become very familiar with how regular expressions are used with #gsub! and a block. First #gsub! checks if the text matches and for every match it yields to the block, setting up the standard $1, $2, $n variables from the regular expression result. Whatever is returned from #gsub! is used as the replacement.

For example:

1
2
3
4
5
"Little Stream Software writes Rails code".gsub!(/(Rails)/) do
  "Redmine"
end
 
# returns =&gt; "Little Stream Software writes Redmine Code"

So when the MACROS_RE matches, the regular expression results are setup and passed into the block.

Macro Arguments

1
args = ($5 || '').split(',').each(&amp;:strip)

MACROS_RE matches the arguments and passes them down as a comma separated string; ‘Design’ from the example. || is used in case the macro wasn’t passed any arguments. Then split is used to convert the string to an array of arguments, separated by the commas. Finally, strip is called on them to handle any extra whitespace.

Escaped macro

1
2
3
4
5
          if esc.nil?
            # ...
          else
            all
          end

It took me a minute to understand what esc was doing but then I remembered that it’s only set when the macro is escaped so it can be printed to the page. So when the macro is escaped, the full macro content is rendered using the second match ($2 or all).

Calling the macro

1
2
3
4
5
            begin
              macros_runner.call(macro, args)
            rescue =&gt; e
              "<div class="flash error">Error executing the <strong>#{macro}</strong> macro (#{e})</div>"
            end || all

At this point we have a macro (include) and some args (['Design']). So the block passed all the way down from textilizable is run and the output is passed back up to #to_html.

There is still one more level of code that I need to dig though before we get to the actual running macro. This is in the exec_macro method that textilizable uses.