Merge branch 'ready/perf'
[ikiwiki] / doc / todo / allow_TMPL__95__LOOP_in_template_directives.mdwn
1 [[!tag patch todo]]
2
3 [[!template id="note" text="""
4 Simply copied this from my website
5 [[http://www.camco.ie/code/ikiwiki,3.20120202,20120313a/]]
6 feel free to reformat / delete"""]]
7
8 The following re-write allows for multiple definitions of the
9 same tag value in a [[plugins/template]] definition.  This, in turn, allows
10 us to use TMPL_LOOPS in our [[ikiwiki/directive/template]] directives; all-be-it in a
11 rather limited way.
12
13 > I'm willing to consider such a feature, but it needs to be presented in
14 > the form of a patch that is reviewable, not a gratuitous rewrite.
15 > --[[Joey]] 
16
17 >> Yes, my apologies for that.  The two worker functions `mktmpl_hash`
18 and `proc_tmpl_hash` are new.  The `preprocess` function then starts
19 by arranging the parameters into an array.  This array is passed to the
20 `mktmpl_hash` and it creates a hash, suitable for passing into the
21 HTML::Template directly.  The `proc_tmpl_hash` then walks the hash
22 structure and processes the parameters.
23
24 >> I know ... you weren't looking for an explanation, just a patch
25 ... totally understand.  Point I'm trying to make, it's a 90% re-write
26 anyway (and my `style(8)` will probably piss most people off).
27
28 >> Anyway, would love to contribute so will try to get to doing this
29 "correctly" and post as a patch.
30
31 I would, personally, only use this feature for very basic loops
32 and, although nested loops *might* be possible (with a little
33 more tinkering) it think any attempt would be better served by
34 [[Kathyrn Anderson's|http://www.katspace.org/]] [[field et
35 al.|http://ikiwiki.info/plugins/contrib/field/]] plugin.
36
37 It *is* (primarily) intended to allow insertion of organised CSS
38 blocks (i.e. `<div>`) through template directives (since i can't
39 seem to get HTML and Markup to mix the way I want).
40
41 [[!template id="note" text="""
42 Apologies for the re-write.  I struggle reading perl code that
43 I didn't write and (probably too often) re-format to reduce my
44 head-aches.  Anyway it didn't make sense to post the patch since
45 everything's changed now.
46 """]]
47
48 NB: this *should* be 100% backwards compatible.
49
50 # `lib/perl5/IkiWiki/Plugin/template.pm`
51
52 [[!format perl """
53
54         #!/usr/bin/perl
55         # Structured template plugin.
56         package IkiWiki::Plugin::template ;
57
58         use warnings ;
59         use strict ;
60         use IkiWiki 3.00 ;
61         use Encode ;
62
63         sub mktmpl_hash( $ ; $ ; @ ) ;
64                                 # declare to supress warning in recursive call
65         sub mktmpl_hash( $ ; $ ; @ )
66                                 # make hash for the template, filling
67                                 # values from the supplied params
68         {
69                 my $template = shift( @_ )
70                                 || error( "mktmpl_hash: no template provided" ) ;
71                 my $param_src = shift( @_ )
72                                 || error( "mktmpl_hash: no parameters" ) ;
73
74                 my $path ;
75                 if( $#_ > 0 )
76                 {
77                         $path = [ @_ ] ;
78                 } else {
79                         $path = shift(@_) || [] ;
80                 } ;
81
82                 my %params ;
83
84                 my @path_vars ;
85                 if( $#{$path} < 0 )
86                 {
87                         @path_vars = $template->query() ;
88                 } else {
89                         @path_vars = $template->query( loop => $path ) ;
90                 } ;
91
92                 foreach my $var ( @path_vars )
93                 {
94                         push( @{$path}, $var ) ;
95                         my $param_type = $template->query( name => $path ) ;
96                         if( $param_type eq 'VAR' )
97                         {
98                                 my @var_path = split( /_/, $var ) ;
99                                 if( $var_path[0] ne '' )
100                                 {
101                                         $path->[-1] = join( '_', @var_path[1..$#var_path] )
102                                                 if( $var_path[0] eq 'raw' ) ;
103                                         $params{$var} = shift( @{$param_src->{$path->[-1]}} )
104                                                         || return(undef) ;
105                                 } ;
106                         } elsif( $param_type eq 'LOOP' )
107                         {
108                                 $params{$var} = [] ;
109                                 push( @{$params{$var}}, $_ )
110                                         while( $_ = mktmpl_hash($template,$param_src,$path) ) ;
111                         } ;
112                         pop( @{$path} ) ;
113                 } ; 
114                 return( \%params ) ;
115         } ;
116
117         sub proc_tmpl_hash( $ ; $ ; $ ; $ ) ;
118                                 # declare to supress warning in recursive call
119         sub proc_tmpl_hash( $ ; $ ; $ ; $ )
120                                 # walk the hash, preprocess and
121                                 # convert to html
122         {
123                 my $tmpl_hash = shift( @_ ) ;
124                 my $page = shift( @_ ) ;
125                 my $destpage = shift( @_ ) ;
126                 my $scan = shift( @_ ) ;
127                 foreach my $key ( keys(%{$tmpl_hash}) )
128                 {
129                         unless( ref($tmpl_hash->{$key}) )
130                                                 # here we assume that
131                                                 # any reference is an
132                                                 # array and allow it to
133                                                 # fail if that's false
134                         {
135                                 $tmpl_hash->{$key} =
136                                                 IkiWiki::preprocess(
137                                                                 $page,
138                                                                 $destpage,
139                                                                 $tmpl_hash->{$key},
140                                                                 $scan ) ;
141                                 my @key_path = split( /_/, $key ) ;
142                                 $tmpl_hash->{$key} =
143                                                 IkiWiki::htmlize(
144                                                                 $page,
145                                                                 $destpage,
146                                                                 pagetype($pagesources{$page}),
147                                                                 $tmpl_hash->{$key}, )
148                                         unless( $key_path[0] eq 'raw' ) ;
149                         } else {
150                                 proc_tmpl_hash( $_, $page, $destpage, $scan )
151                                         foreach( @{$tmpl_hash->{$key}} ) ;
152                         } ;
153                 } ;
154         } ;
155
156         # "standard" ikiwiki definitions / hooks
157
158         sub import
159         {
160                 hook( type => "getsetup",
161                                 id => "template",
162                                 call => \&getsetup ) ;
163                 hook( type => "preprocess",
164                                 id => "template",
165                                 call => \&preprocess,
166                                 scan => 1 ) ;
167         } ;
168
169         sub getsetup()
170         {
171                 return(
172                                 plugin => {
173                                         safe => 1,
174                                         rebuild => undef,
175                                         section => "widget",
176                                 }, ) ;
177         } ;
178
179         sub preprocess( @ )
180         {
181         # first process arguments into arrays of values
182                 my %params ;
183
184                 my( $key, $value ) ;
185                 while( ($key,$value)=splice(@_,0,2) )
186                 {
187                         if( exists($params{$key}) )
188                         {
189                                 push( @{$params{$key}}, $value ) ;
190                         } else {
191                                 $params{$key} = [ $value ] ;
192                         } ;
193                 } ;
194
195         # set context
196                 my $scan = ! defined( wantarray() ) ;
197                                         # This needs to run even in scan
198                                         # mode, in order to process links
199                                         # and other metadata included via
200                                         # the template.
201
202         # check for critical values
203                 if( ! exists($params{id}) )
204                 {
205                         error( gettext("missing id parameter") ) ;
206                 } ;
207
208         # set some convenience variables
209                 my $id = $params{id}->[$#{$params{id}}] ;
210                 my $page = $params{page}->[$#{$params{page}}] ;
211                 my $destpage = $params{destpage}->[$#{$params{destpage}}] ;
212         # ... and an essential one for the production pass
213                 $params{basename} = [ IkiWiki::basename($page) ] ;
214
215         # load the template
216                 my $template ;
217                 eval {
218                         $template =
219                                         template_depends( $id, $page,
220                                                         blind_cache=>1 ) ;
221                                                 # The bare id is used, so
222                                                 # a page templates/$id can
223                                                 # be used as the template.
224                 } ;
225                 if( $@ )
226                 {
227                         error(
228                                         sprintf(
229                                                         gettext("failed to process template %s"),
230                                                         htmllink(
231                                                                         $page,
232                                                                         $destpage,
233                                                                         "/templates/$id")
234                                                         )." $@"
235                                         ) ;
236                 } ;
237
238         # create and process the parameters
239                 my $tmpl_hash = mktmpl_hash( $template, \%params ) ;
240                 proc_tmpl_hash( $tmpl_hash, $page, $destpage, $scan ) ;
241         # ... and load the template with the values
242                 $template->param( $tmpl_hash ) ;
243
244         # return the processed page chunk
245                 return( IkiWiki::preprocess($page,
246                                                 $destpage,
247                                                 $template->output(),$scan)
248                                 ) ;
249         } ;
250
251         1 ;
252
253 """]]
254
255 ## sample template
256
257         # <TMPL_VAR HEADER0>
258
259         <table>
260         <TMPL_LOOP TEST0>
261         <tr>
262                 <td><TMPL_VAR DATA0></td>
263                 <td><TMPL_VAR DATA1></td>
264         </tr>
265         </TMPL_LOOP>
266         </table>
267
268 ## sample iki page
269
270         \[[!meta title="this is my loops page"]]
271
272         \[[!template id="loops"
273         header0="this is a table"
274         data0="cell0:0"
275         data1="cell0:1"
276         data0="cell1:0"
277         data1="cell1:1"
278         ]]