More lenient parsing of the meta line
[matthijs/upstream/blosxom-plugins.git] / gavinc / mason_blocks
1 # Blosxom Plugin: mason_blocks
2 # Author(s): Gavin Carr <gavin@openfusion.com.au>
3 # Version: 0.002000
4
5 package mason_blocks;
6
7 use strict;
8
9 # --- Configurable variables -----
10
11 # Debug verbosity
12 my $debug_level = 0;
13
14 # --------------------------------
15
16 sub start { 1 };
17
18 sub head {
19   my($pkg, $currentdir, $head_ref) = @_;
20   munge_blocks($head_ref);
21   return 1;
22 }
23
24 sub story {
25   my ($pkg, $path, $filename, $story_ref, $title_ref, $body_ref) = @_;
26   munge_blocks($story_ref);
27   return 1;
28 }
29
30 sub foot {
31   my($pkg, $currentdir, $foot_ref) = @_;
32   munge_blocks($foot_ref);
33   return 1;
34 }
35
36 sub munge_blocks {
37   my ($flavour_ref) = @_;
38
39   my @flavour = ();
40   my ($doc, @if, @else, @if_blocks, @else_blocks);
41   @if = @else = @if_blocks = @else_blocks = ();
42   for (split /\n/, $$flavour_ref) {
43     # Ignore lines beginning with '%#'
44     next if m/^%\s*#/;
45
46     # Ignore if in doc block <%doc> .... </%doc> (at beginning of lines)
47     if (m!^</%doc>! && $doc) {
48       $doc = 0;
49       next;
50     } elsif ($doc) {
51       next;
52     } elsif (m!^<%doc>!) {
53       $doc = 1;
54       next;
55     }
56
57     # Minimalist version - if, } else {, } only, nesting supported
58     if (m/^%\s*if\b/) {
59       my $if = $_;
60       $if =~ s/^%\s*if\s*//;
61       $if =~ s/\s*{\s*$//;
62       # New block, record condition, and add new entries to other arrays
63       push @if, $if;
64       push @else, 0;
65       push @if_blocks,   [];
66       push @else_blocks, [];
67       next;
68     }
69     elsif (m/^%\s*\}\s*else\s*\{/) {
70       # Else at same level - set current else flag to true
71       $else[$#else] = 1;
72       next;
73     }
74     elsif (m/^%\s*\}/) {
75       # End of block - pull latest entries from all arrays
76       my $if = pop @if;
77       my $else = pop @else;
78       my $if_block = pop @if_blocks;
79       my $else_block = pop @else_blocks;
80       warn "end_block: if '$if', if block " . 
81         scalar(@$if_block) . " lines, else block " . 
82         scalar(@$else_block) . " lines\n" 
83           if $debug_level;
84       # Check condition, and replace current line with appropriate flattened block
85       if (eval "$if") {
86         $_ = join "\n", @$if_block;
87       } else {
88         $_ = join "\n", @$else_block;
89       }
90     }
91     
92     # Regular line - add to @else_blocks, @if_blocks, or @flavour
93     if (@else && $else[$#else]) {
94       push @{$else_blocks[$#else_blocks]}, $_;
95     }
96     elsif (@if) {
97       push @{$if_blocks[$#if_blocks]}, $_;
98     }
99     else {
100       push @flavour, $_;
101     }
102   }
103
104   # Join flavour lines and update $$flavour_ref
105   $$flavour_ref = join "\n", @flavour;
106
107   # Support mason-style end-of-line newline escapes
108   $$flavour_ref =~ s/\\\r?\n//g;
109 }
110
111 1;
112
113 __END__
114
115 =head1 NAME
116
117 mason_blocks - blosxom plugin to support simple mason-style if-blocks
118 in blosxom flavours/templates
119
120 =head1 SYNOPSIS
121
122     # In a flavour or template file ...
123
124     # Mason-style conditionals
125     % if ($pagetype::pagetype ne 'story') {
126     <a href="$permalink::story#comments">Comments ($feedback::count) &raquo;</a>
127     % } else {
128     <a href="$permalink::story#leave_comment">Leave a comment &raquo;</a>
129     % }
130
131     # Mason-style comments
132     %# Only show a comments section if there are comments
133     % if ($feedback::count > 0) {
134     $feedback::comments
135     % }
136
137     # Mason-style block comments
138     <%doc>
139     This is a great big
140     multi-line, extremely important
141     comment.
142     </%doc>
143
144     # Mason-style newline escaping, if last character on line is a backslash e.g.
145     <p>\
146     Foo bar\
147     </p>
148     # is rendered as: <p>Foo bar</p>
149     
150
151 =head1 DESCRIPTION
152
153 mason_blocks is a blosxom plugin implementing simple conditional and
154 commment blocks using mason-style syntax (as in the HTML::Mason perl
155 templating system), for use in blosxom flavour and template files.
156
157 =head2 CONDITIONALS
158
159 mason_blocks supports simple if and if-else blocks using lines beginning
160 with the '%' character (which must be the first character on the line)
161 e.g.
162
163     % if ($pagetype::pagetype eq 'story') {
164     <a href="$permalink::story#leave_comment">Leave a comment &raquo;</a>
165     % }
166
167 and
168
169     % if ($pagetype::pagetype ne 'story') {
170     <a href="$permalink::story#comments">Comments ($feedback::count) &raquo;</a>
171     % } else {
172     <a href="$permalink::story#leave_comment">Leave a comment &raquo;</a>
173     % }
174
175 Whitespace is not significant, but braces are required and should match, 
176 just as in perl. The if-conditions can comprise any valid perl condition.
177
178 =head2 COMMENTS
179
180 Two comment styles are also supported. Single line comments must begin with
181 a '%' character, followed by optional whitespace, follwed by a '#' character,
182 and continue only to the end of the line e.g.
183
184     %# This is a completely profound and illuminating comment
185     % # As is this
186
187 Block comments must begin with a <%doc> token (at the beginning of a line)
188 and end with a </%doc> token (also at the beginning of a line). All text
189 between these two tokens is treated as a comment and not included in output.
190 For example:
191
192     <%doc>
193     More enlightening profundities from your template author
194     Explaining why this stuff is as ugly as it is ...
195     </%doc>
196
197 Block comments cannot be nested.
198
199 =head2 NEWLINE ESCAPING
200
201 mason_blocks also supports mason-style newline escaping i.e. if the last
202 character on a line is a backslash, mason_blocks escapes the line break,
203 deleting both the backslash and the following newline character(s). This 
204 is useful in conjunction with conditionals where you'd prefer the 
205 conditional content to appear inline within the enclosing content e.g.
206
207   <a href="$permalink::story#comments">\
208   % if ($feedback::count == 0) {
209   No Comments \
210   % }
211   &raquo;</a>
212
213 would be rendered as:
214
215   <a href="$permalink::story#comments">No Comments &raquo;</a>
216
217 This is not just a prettiness issue - some browsers treat embedded
218 whitespace (including newlines) as significant, even when they 
219 shouldn't.
220
221 =head2 VS. INTERPOLATE_FANCY
222
223 mason_blocks was initially born out of my frustration with older versions 
224 of interpolate_fancy not supporting nested constructs properly (though I'd
225 also been frustrated with the syntax and the limits on the conditionals
226 available). 
227
228 I thought for an experiment I'd see how hard simple non-nested 
229 conditionals using a mason-style syntax would be, and it turned out to be
230 not very difficult at all. I no longer use interpolate_fancy at all - my
231 limited use cases seem better met using mason_blocks.
232
233 As I see it, mason_blocks has the following advantages over 
234 interpolate_fancy:
235
236 =over 4
237
238 =item Nesting support
239
240 Earlier versions of interpolate_fancy had problems with nested 
241 constructs. I believe that this has been fixed in the later versions
242 updated by Matthijs Kooijman (>= version 20060111). 
243
244 mason_blocks fully supports nested conditionals.
245
246 =item If-Else Constructs
247
248 mason_blocks supports simple if-else blocks, instead of making you
249 use 'if x eq 1; if x ne 1' pairs. It does not currently support 
250 elsif, however.
251
252 =item Full perl conditions
253
254 mason_blocks allows you to use full perl conditions (including composite
255 conditions) instead of being limited to simple conditions using only the
256 most common operators. For instance, all of the following require hoop
257 jumping with interpolate_fancy:
258
259 =over 4
260
261 =item if ($foo >= 3)
262
263 =item if (substr($foo, 3, 1) eq 'X')
264
265 =item if ($pagetype::pagetype eq 'story' && $feedback::comments > 0)
266
267 =back
268
269 =back
270
271 mason_blocks does not provide any of interpolate_fancy's interpolation 
272 or action functionality, however.
273
274
275 =head1 USAGE
276
277 mason_blocks should probably be loaded relatively late, since you'll
278 often want to use various plugin package variables in your conditionals.
279
280 It uses the 'head', 'story', and 'footer' hooks, rather than 'interpolate',
281 so it should be able to be used alongside any of the interpolate plugins
282 if you wish.
283
284
285 =head1 SEE ALSO
286
287 HTML::Mason, for the block syntax (the module is not used or required by 
288 this plugin, however).
289
290 Blosxom: http://blosxom.sourceforge.net/
291
292
293 =head1 BUGS
294
295 Please report bugs either directly to the author or to the blosxom 
296 development mailing list: <blosxom-devel@lists.sourceforge.net>.
297
298
299 =head1 TODO
300
301 - add elsif support
302
303
304 =head1 AUTHOR
305
306 Gavin Carr <gavin@openfusion.com.au>, http://www.openfusion.net/
307
308 =head1 LICENSE
309
310 Copyright 2007, Gavin Carr.
311
312 This plugin is licensed under the same terms as blosxom itself i.e.
313
314 Permission is hereby granted, free of charge, to any person obtaining a
315 copy of this software and associated documentation files (the "Software"),
316 to deal in the Software without restriction, including without limitation
317 the rights to use, copy, modify, merge, publish, distribute, sublicense,
318 and/or sell copies of the Software, and to permit persons to whom the
319 Software is furnished to do so, subject to the following conditions:
320
321 The above copyright notice and this permission notice shall be included
322 in all copies or substantial portions of the Software.
323
324 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
325 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
326 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
327 THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
328 OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
329 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
330 OTHER DEALINGS IN THE SOFTWARE.
331
332 =cut
333
334 # vim:ft=perl