html-template 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d7ee8a92f5791e4ac9a898027d3815cb0f4c3619
4
- data.tar.gz: f20c60078147da549da1495b13abb55f7aef81f8
3
+ metadata.gz: 3e35f465439469ba0e64f58a6ece96b2b5aa8358
4
+ data.tar.gz: 94054c9cf1dd94bee198b2e76e296542698b2c49
5
5
  SHA512:
6
- metadata.gz: 49601653c67d7e9eb88813fe399e6aa738a8bb232d4048b98aaaa918335e9d22261d24f084243d545c5f6f9ae4849809ac49a30d3b210396e1ca138a47ec8455
7
- data.tar.gz: cdbc81e61aabbac50bfd499d3ffa9efa3417d3652fe2610a32c66b39d597a17bc604431b78f1684031a055527f3618de78770e7f0394b84e5f3669972bb73bed
6
+ metadata.gz: 3ffb1e09a7d8d3ddea7bc13789ed8037813750ae8dcad89e7f0d778808ef9c1797e86545031a734efe4ac9a0b50ef1ad9b613cd15bf743d9c2276fc57e1dd4e5
7
+ data.tar.gz: 3c5cf4655de62ee201567fb5650b8d8219b13fc3b6be6a534a1a8db8980f96fe9080d65c14fe04246793b1c891da972a72f235d7298c3b7cc7526db0025c9288
data/README.md CHANGED
@@ -9,9 +9,364 @@
9
9
 
10
10
 
11
11
 
12
+
13
+ ## Intro
14
+
15
+ Note: The original [`HTML::Template`](https://metacpan.org/pod/HTML::Template) package was written by Sam Tregar et al
16
+ (in Perl with a first 1.0 release in 1999!)
17
+ and the documentation here
18
+ is mostly a copy from the original
19
+ with some changes / adoptions for
20
+ the ruby version.
21
+
22
+
23
+
24
+ First you make a template - this is just a normal HTML file with a few extra tags, the simplest being `<TMPL_VAR>`
25
+
26
+ For example, `students.html.tmpl`:
27
+
28
+ ```
29
+ <TMPL_LOOP students>
30
+ <p>
31
+ Name: <TMPL_VAR name><br>
32
+ GPA: <TMPL_VAR gpa>
33
+ </p>
34
+ </TMPL_LOOP>
35
+ ```
36
+
37
+ Now you can use it in your script:
38
+
39
+ ``` ruby
40
+ require 'html/template'
41
+
42
+ template = HtmlTemplate.new( filename: 'students.html.tmpl' )
43
+
44
+ puts template.render( students: [ { name: 'Bluto Blutarsky', gpa: 0.0 },
45
+ { name: 'Tracey Flick', gpa: 4.0 }
46
+ ]
47
+ )
48
+ ```
49
+
50
+ If all is well in the universe this should output something like this:
51
+
52
+ ```
53
+ <p>
54
+ Name: Bluto Blutarsky<br>
55
+ GPA: 0.0
56
+ </p>
57
+
58
+ <p>
59
+ Name: Tracey Flick<br>
60
+ GPA: 4.0
61
+ </p>
62
+ ```
63
+
64
+
12
65
  ## Usage
13
66
 
14
- To be done
67
+
68
+ This library attempts to make using HTML templates simple and natural.
69
+ It extends standard HTML with a few new HTML-esque tags - `<TMPL_VAR>` `<TMPL_LOOP>`, `<TMPL_IF>`, `<TMPL_ELSE>` and `<TMPL_UNLESS>`. The file written with HTML and these new tags is called a template. It is usually saved separate from your script - possibly even created by someone else! Using this module you fill in the values for the variables, loops and branches declared in the template. This allows you to separate design - the HTML - from the data, which you generate in the ~~Perl~~ Ruby script.
70
+
71
+ ### The Tags
72
+
73
+ `TMPL_VAR` • `TMPL_LOOP` • `TMPL_IF` • `TMPL_ELSE` • `TMPL_UNLESS`
74
+
75
+
76
+ #### `TMPL_VAR`
77
+
78
+ <TMPL_VAR name>
79
+
80
+ The `<TMPL_VAR>` tag is very simple.
81
+ When the template is output - for each `<TMPL_VAR>` tag you
82
+ use in the template the `<TMPL_VAR>` is replaced with the value text you specified.
83
+ If you don't set a value it just gets skipped in the output.
84
+
85
+ **Attributes**
86
+
87
+ The following "attributes" can also be specified in template var tags:
88
+
89
+ `ESCAPE`
90
+
91
+ This allows you to escape the value before it's put into the output.
92
+
93
+ This is useful when you want to use a `TMPL_VAR` in a context where those characters would cause trouble. For example:
94
+
95
+ <input name=param type=text value="<TMPL_VAR name>">
96
+
97
+ If you use a value like `sam"my` you'll get in trouble with HTML's
98
+ idea of a double-quote. On the other hand,
99
+ if you use `ESCAPE=HTML`, like this:
100
+
101
+ <input name=param type=text value="<TMPL_VAR name ESCAPE=HTML>">
102
+
103
+ You'll get what you wanted no matter what value happens
104
+ to be passed in.
105
+
106
+ The following escape values are supported:
107
+
108
+ `HTML`
109
+
110
+ Replaces the following characters with their
111
+ HTML entity equivalent: `&`, `"`, `'`, `<`, `>`
112
+
113
+ `NONE`
114
+
115
+ Performs no escaping. This is the default.
116
+
117
+
118
+
119
+
120
+ #### `TMPL_LOOP`
121
+
122
+ <TMPL_LOOP name> ... </TMPL_LOOP>
123
+
124
+ The `<TMPL_LOOP>` tag is a bit more complicated than `<TMPL_VAR>`. The `<TMPL_LOOP>` tag allows you to delimit a section of text and give it a name. Inside this named loop you place `<TMPL_VAR>`s. Now you pass in a list (an array) of values for this loop. The loop iterates over the list and produces output from the text block for each pass. Here's an example:
125
+
126
+ In the template:
127
+
128
+ <TMPL_LOOP employees>
129
+ <p>
130
+ Name: <TMPL_VAR name><br>
131
+ Job: <TMPL_VAR job>
132
+ </p>
133
+ </TMPL_LOOP>
134
+
135
+ In your Ruby code:
136
+
137
+ ``` ruby
138
+ puts template.result( employees: [ { name: 'Sam', job: 'programmer' },
139
+ { name: 'Steve', job: 'soda jerk' }
140
+ ]
141
+ )
142
+ ```
143
+
144
+ The output is:
145
+
146
+ ```
147
+ <p>
148
+ Name: Sam<br>
149
+ Job: programmer
150
+ </p>
151
+
152
+ <p>
153
+ Name: Steve<br>
154
+ Job: soda jerk
155
+ </p>
156
+ ```
157
+
158
+ As you can see above the `<TMPL_LOOP>` takes a list of values
159
+ and then iterates over the loop body producing output.
160
+
161
+
162
+ **Loop Context Variables**
163
+
164
+ Inside a loop extra variables that depend on the loop's context
165
+ are made available. These are:
166
+
167
+ `__FIRST__`
168
+
169
+ Value that is true for the first iteration of the loop
170
+ and false every other time.
171
+
172
+ `__LAST__`
173
+
174
+ Value that is true for the last iteration of the loop and false every other time.
175
+
176
+ `__INNER__`
177
+
178
+ Value that is true for the every iteration of the loop except for the first and last.
179
+
180
+ `__OUTER__`
181
+
182
+ Value that is true for the first and last iterations of the loop.
183
+
184
+ `__ODD__`
185
+
186
+ Value that is true for the every odd iteration of the loop.
187
+
188
+ `__EVEN__`
189
+
190
+ Value that is true for the every even iteration of the loop.
191
+
192
+ `__COUNTER__`
193
+
194
+ An integer (starting from 1) whose value increments for each iteration of the loop.
195
+
196
+ `__INDEX__`
197
+
198
+ An integer (starting from 0) whose value increments
199
+ for each iteration of the loop.
200
+
201
+
202
+ Just like any other `TMPL_VAR`s these variables can be used in `<TMPL_IF>`, `<TMPL_UNLESS>` and `<TMPL_ELSE>` to control how a loop is output.
203
+
204
+ Example:
205
+
206
+ ```
207
+ <TMPL_LOOP foos>
208
+ <TMPL_IF __FIRST__>
209
+ This only outputs on the first pass.
210
+ </TMPL_IF>
211
+
212
+ <TMPL_IF __ODD__>
213
+ This outputs every other pass, on the odd passes.
214
+ </TMPL_IF>
215
+
216
+ <TMPL_UNLESS __ODD__>
217
+ This outputs every other pass, on the even passes.
218
+ </TMPL_UNLESS>
219
+
220
+ <TMPL_IF __INNER__>
221
+ This outputs on passes that are neither first nor last.
222
+ </TMPL_IF>
223
+
224
+ This is pass number <TMPL_VAR __COUNTER__>.
225
+
226
+ <TMPL_IF __LAST__>
227
+ This only outputs on the last pass.
228
+ </TMPL_IF>
229
+ </TMPL_LOOP>
230
+ ```
231
+
232
+ One use of this feature is to provide a "separator" similar in effect to the ruby method `join()`. Example:
233
+
234
+ ```
235
+ <TMPL_LOOP fruits>
236
+ <TMPL_IF __LAST__> and </TMPL_IF>
237
+ <TMPL_VAR name><TMPL_UNLESS __LAST__>, <TMPL_ELSE>.</TMPL_UNLESS>
238
+ </TMPL_LOOP>
239
+ ```
240
+
241
+ Would output something like:
242
+
243
+ Apples, Oranges, Brains, Toes, and Kiwi.
244
+
245
+
246
+ NOTE: A loop with only a single pass will get both `__FIRST__` and `__LAST__`
247
+ set to true, but not `__INNER__`.
248
+
249
+
250
+
251
+ #### `TMPL_IF`
252
+
253
+ <TMPL_IF name> ... </TMPL_IF>
254
+
255
+ The `<TMPL_IF>` tag allows you to include or not include a block of the template based on the value of a given name. If the name is given a value that is true for Ruby - like `true` - then the block is included in the output. If it is not defined, or given a false value - like `false` or `nil` - then it is skipped. The names are specified the same way as with `<TMPL_VAR>`.
256
+
257
+ Example Template:
258
+
259
+ <TMPL_IF bool>
260
+ Some text that is ouptut only if bool is true!
261
+ </TMPL_IF>
262
+
263
+ Now if you call `template.result( bool: true )` then the above block will be included by output.
264
+
265
+ `<TMPL_IF> </TMPL_IF>` blocks can include any valid HTML template construct - `VAR`s and `LOOP`s and other `IF/ELSE` blocks.
266
+
267
+
268
+
269
+ #### `TMPL_ELSE`
270
+
271
+ <TMPL_IF name> ... <TMPL_ELSE> ... </TMPL_IF>
272
+
273
+ You can include an alternate block in your `<TMPL_IF>` block by using `<TMPL_ELSE>`. NOTE: You still end the block with `</TMPL_IF>`, not `</TMPL_ELSE>`!
274
+
275
+ Example:
276
+
277
+ <TMPL_IF bool>
278
+ Some text that is output only if bool is true.
279
+ <TMPL_ELSE>
280
+ Some text that is output only if bool is false.
281
+ </TMPL_IF>
282
+
283
+
284
+ #### `TMPL_UNLESS`
285
+
286
+ <TMPL_UNLESS name> ... </TMPL_UNLESS>
287
+
288
+ This tag is the opposite of `<TMPL_IF>`. The block is output if the name is set false or not defined. You can use `<TMPL_ELSE>` with `<TMPL_UNLESS>` just as you can with `<TMPL_IF>`.
289
+
290
+ Example:
291
+
292
+ <TMPL_UNLESS bool>
293
+ Some text that is output only if bool is false.
294
+ <TMPL_ELSE>
295
+ Some text that is output only if bool is true.
296
+ </TMPL_UNLESS>
297
+
298
+
299
+
300
+
301
+
302
+ ## Back to the Future - Convert HTML Templates to Embedded Ruby (ERB)
303
+
304
+ The HTML library always converts classic
305
+ HTML Template to Embedded Ruby (ERB) style.
306
+ Use the `text` attribute to get the converted template source.
307
+
308
+ Example:
309
+
310
+ ``` ruby
311
+ puts HtmlTemplate.new( <<TXT ).text
312
+ <opml version="1.1">
313
+ <head>
314
+ <title><TMPL_VAR name ESCAPE="HTML"></title>
315
+ <dateModified><TMPL_VAR date_822></dateModified>
316
+ <ownerName><TMPL_VAR owner_name></ownerName>
317
+ <ownerEmail><TMPL_VAR owner_email></ownerEmail>
318
+ </head>
319
+
320
+ <body>
321
+ <TMPL_LOOP Channels>
322
+ <outline type="rss"
323
+ text="<TMPL_VAR name ESCAPE="HTML">"
324
+ xmlUrl="<TMPL_VAR url ESCAPE="HTML">"
325
+ <TMPL_IF channel_link> htmlUrl="<TMPL_VAR channel_link ESCAPE="HTML">"</TMPL_IF> />
326
+ </TMPL_LOOP>
327
+ </body>
328
+ </opml>
329
+ TXT
330
+ ```
331
+
332
+ will print if debugging is turned on:
333
+
334
+ ```
335
+ line 4 - match <TMPL_VAR name ESCAPE="HTML"> replacing with: <%=h name %>
336
+ line 5 - match <TMPL_VAR date_822> replacing with: <%= date_822 %>
337
+ line 6 - match <TMPL_VAR owner_name> replacing with: <%= owner_name %>
338
+ line 7 - match <TMPL_VAR owner_email> replacing with: <%= owner_email %>
339
+ line 11 - match <TMPL_LOOP Channels> replacing with: <% Channels.each_with_loop do |channel, channel_loop| %>
340
+ line 13 - match <TMPL_VAR name ESCAPE="HTML"> replacing with: <%=h channel.name %>
341
+ line 14 - match <TMPL_VAR url ESCAPE="HTML"> replacing with: <%=h channel.url %>
342
+ line 15 - match <TMPL_IF channel_link> replacing with: <% if channel.channel_link %>
343
+ line 15 - match <TMPL_VAR channel_link ESCAPE="HTML"> replacing with: <%=h channel.channel_link %>
344
+ line 15 - match </TMPL_IF> replacing with: <% end %>
345
+ line 16 - match </TMPL_LOOP> replacing with: <% end %>
346
+ ```
347
+
348
+ and result in:
349
+
350
+ ``` erb
351
+ <opml version="1.1">
352
+ <head>
353
+ <title><%=h name %></title>
354
+ <dateModified><%= date_822 %></dateModified>
355
+ <ownerName><%= owner_name %></ownerName>
356
+ </head>
357
+
358
+ <body>
359
+ <% Channels.each_with_loop do |channel, channel_loop| %>
360
+ <outline type="rss"
361
+ text="<%=h channel.name %>"
362
+ xmlUrl="<%=h channel.url %>"
363
+ <% if channel.channel_link %> htmlUrl="<%=h channel.channel_link %>"<% end %> />
364
+ <% end %>
365
+ </body>
366
+ </opml>
367
+ ```
368
+
369
+
15
370
 
16
371
  ## License
17
372
 
@@ -3,8 +3,8 @@
3
3
 
4
4
  class HtmlTemplate
5
5
  MAJOR = 0
6
- MINOR = 0
7
- PATCH = 1
6
+ MINOR = 1
7
+ PATCH = 0
8
8
  VERSION = [MAJOR,MINOR,PATCH].join('.')
9
9
 
10
10
  def self.version
data/lib/html/template.rb CHANGED
@@ -7,6 +7,27 @@ require 'ostruct'
7
7
  require 'fileutils'
8
8
 
9
9
 
10
+
11
+ module Enumerable
12
+ class LoopMeta
13
+ def initialize( total )
14
+ @total = total
15
+ @index = 0
16
+ end
17
+ def index=(value) @index=value; end
18
+ end
19
+
20
+ def each_with_loop( &blk )
21
+ loop_meta = LoopMeta.new( size )
22
+ each_with_index do |item, index|
23
+ loop_meta.index = index
24
+ blk.call( item, loop_meta )
25
+ end
26
+ end
27
+ end
28
+
29
+
30
+
10
31
  # our own code
11
32
  require 'html/template/version' # note: let version always get first
12
33
 
@@ -15,29 +36,54 @@ require 'html/template/version' # note: let version always get first
15
36
  class HtmlTemplate
16
37
 
17
38
  attr_reader :text ## returns converted template text (with "breaking" comments!!!)
18
-
19
- def initialize( text )
20
- @text = convert( text ) ## note: keep a copy of the converted template text
21
- @template = ERB.new( strip_comments( @text ) )
39
+ attr_reader :template ## return "inner" (erb) template object
40
+ attr_reader :errors
41
+
42
+ def initialize( text=nil, filename: nil )
43
+ if text.nil? ## try to read file (by filename)
44
+ text = File.open( filename, 'r:utf-8' ) { |f| f.read }
45
+ end
46
+
47
+ ## todo/fix: add filename to ERB too (for better error reporting)
48
+ @text, @errors = convert( text ) ## note: keep a copy of the converted template text
49
+
50
+ if @errors.size > 0
51
+ puts "!! ERROR - #{@errors.size} conversion / syntax error(s):"
52
+ pp @errors
53
+ raise ## todo - find a good Error - StandardError - why? why not?
54
+ end
55
+
56
+ @template = ERB.new( @text, nil, '%<>' )
22
57
  end
23
58
 
24
59
 
25
60
  VAR_RE = %r{<TMPL_(?<tag>VAR)
26
61
  \s
27
- (?<ident>[a-zA-Z_0-9]+)
62
+ (?<ident>[a-zA-Z_][a-zA-Z0-9_]*)
63
+ (\s
64
+ (?<escape>ESCAPE)
65
+ =
66
+ "(?<format>HTML|NONE)"
67
+ )?
28
68
  >}x
29
69
 
30
- IF_OPEN_RE = %r{(?<open><)TMPL_(?<tag>IF)
70
+ IF_OPEN_RE = %r{(?<open><)TMPL_(?<tag>IF|UNLESS)
31
71
  \s
32
- (?<ident>[a-zA-Z_0-9]+)
72
+ (?<ident>[a-zA-Z_][a-zA-Z0-9_]*)
33
73
  >}x
34
74
 
35
- IF_CLOSE_RE = %r{(?<close></)TMPL_(?<tag>IF)
75
+ IF_CLOSE_RE = %r{(?<close></)TMPL_(?<tag>IF|UNLESS)
76
+ (\s
77
+ (?<ident>[a-zA-Z_][a-zA-Z0-9_]*)
78
+ )? # note: allow optional identifier
36
79
  >}x
80
+
81
+ ELSE_RE = %r{<TMPL_(?<tag>ELSE)
82
+ >}x
37
83
 
38
84
  LOOP_OPEN_RE = %r{(?<open><)TMPL_(?<tag>LOOP)
39
85
  \s
40
- (?<ident>[a-zA-Z_0-9]+)
86
+ (?<ident>[a-zA-Z_][a-zA-Z0-9_]*)
41
87
  >}x
42
88
 
43
89
  LOOP_CLOSE_RE = %r{(?<close></)TMPL_(?<tag>LOOP)
@@ -45,27 +91,43 @@ class HtmlTemplate
45
91
 
46
92
  CATCH_OPEN_RE = %r{(?<open><)TMPL_(?<unknown>[^>]+?)
47
93
  >}x
94
+
95
+ CATCH_CLOSE_RE = %r{(?<close></)TMPL_(?<unknown>[^>]+?)
96
+ >}x
48
97
 
49
98
 
50
99
  ALL_RE = Regexp.union( VAR_RE,
51
100
  IF_OPEN_RE,
52
101
  IF_CLOSE_RE,
102
+ ELSE_RE,
53
103
  LOOP_OPEN_RE,
54
104
  LOOP_CLOSE_RE,
55
- CATCH_OPEN_RE )
56
-
57
-
58
- def strip_comments( text )
59
- ## strip/remove comments lines starting with #
60
- buf = String.new('') ## note: '' required for getting source encoding AND not ASCII-8BIT!!!
61
- text.each_line do |line|
62
- next if line.lstrip.start_with?( '#' )
63
- buf << line
64
- end
65
- buf
105
+ CATCH_OPEN_RE,
106
+ CATCH_CLOSE_RE )
107
+
108
+
109
+ def to_recursive_ostruct( o )
110
+ if o.is_a?( Array )
111
+ o.reduce( [] ) do |ary, item|
112
+ ary << to_recursive_ostruct( item )
113
+ ary
114
+ end
115
+ elsif o.is_a?( Hash )
116
+ ## puts 'to_recursive_ostruct (hash):'
117
+ OpenStruct.new( o.reduce( {} ) do |hash, (key, val)|
118
+ ## puts "#{key} => #{val}:#{val.class.name}"
119
+ hash[key] = to_recursive_ostruct( val )
120
+ hash
121
+ end )
122
+ else ## assume regular "primitive" value - pass along as is
123
+ o
124
+ end
66
125
  end
67
126
 
127
+
68
128
  def convert( text )
129
+ errors = [] # note: reset global errros list
130
+
69
131
  stack = []
70
132
 
71
133
  ## note: convert line-by-line
@@ -76,7 +138,7 @@ class HtmlTemplate
76
138
  lineno += 1
77
139
 
78
140
  if line.lstrip.start_with?( '#' ) ## or make it tripple ### - why? why not?
79
- buf << line ## pass along as is for now!!
141
+ buf << "%#{line.lstrip}"
80
142
  elsif line.strip.empty?
81
143
  buf << line
82
144
  else
@@ -90,6 +152,9 @@ class HtmlTemplate
90
152
  ident = m[:ident]
91
153
  unknown = m[:unknown] # catch all for unknown / unmatched tags
92
154
 
155
+ escape = m[:escape]
156
+ format = m[:format]
157
+
93
158
  ## todo/fix: rename ctx to scope or __ - why? why not?
94
159
  ## note: peek; get top stack item
95
160
  ## if top level (stack empty) => nothing
@@ -97,24 +162,40 @@ class HtmlTemplate
97
162
  ctx = stack.empty? ? '' : "#{stack[-1]}."
98
163
 
99
164
  code = if tag == 'VAR'
100
- "<%= #{ctx}#{ident} %>"
165
+ if escape && format == 'HTML'
166
+ ## check or use long form e.g. CGI.escapeHTML - why? why not?
167
+ "<%=h #{ctx}#{ident} %>"
168
+ else
169
+ "<%= #{ctx}#{ident} %>"
170
+ end
101
171
  elsif tag == 'LOOP' && tag_open
102
172
  ## assume plural ident e.g. channels
103
173
  ## cut-off last char, that is, the plural s channels => channel
104
174
  ## note: ALWAYS downcase (auto-generated) loop iterator/pass name
105
175
  it = ident[0..-2].downcase
106
176
  stack.push( it )
107
- "<% #{ctx}#{ident}.each do |#{it}| %>"
177
+ "<% #{ctx}#{ident}.each_with_loop do |#{it}, #{it}_loop| %>"
108
178
  elsif tag == 'LOOP' && tag_close
109
179
  stack.pop
110
180
  "<% end %>"
111
181
  elsif tag == 'IF' && tag_open
112
182
  "<% if #{ctx}#{ident} %>"
113
- elsif tag == 'IF' && tag_close
183
+ elsif tag == 'UNLESS' && tag_open
184
+ "<% unless #{ctx}#{ident} %>"
185
+ elsif (tag == 'IF' || tag == 'UNLESS') && tag_close
114
186
  "<% end %>"
115
- elsif unknown && tag_open
116
- puts "!! ERROR"
117
- "<%# !!error - unknown open tag: #{unknown} %>"
187
+ elsif tag == 'ELSE'
188
+ "<% else %>"
189
+ elsif unknown
190
+ errors << if tag_open
191
+ "line #{lineno} - unknown open tag: #{unknown}"
192
+ else ## assume tag_close
193
+ "line #{lineno} - unknown close tag: #{unknown}"
194
+ end
195
+
196
+ puts "!! ERROR in line #{lineno} - #{errors[-1]}:"
197
+ puts line
198
+ "<%# !!error - #{errors[-1]} %>"
118
199
  else
119
200
  raise ArgumentError ## unknown tag #{tag}
120
201
  end
@@ -125,7 +206,7 @@ class HtmlTemplate
125
206
  end
126
207
  end
127
208
  end # each_line
128
- buf
209
+ [buf, errors]
129
210
  end # method convert
130
211
 
131
212
 
@@ -142,6 +223,17 @@ class HtmlTemplate
142
223
  ## todo: use locals / assigns or something instead of **kwargs - why? why not?
143
224
  ## allow/support (extra) locals / assigns - why? why not?
144
225
  ## note: Ruby >= 2.5 has ERB#result_with_hash - use later - why? why not?
226
+
227
+ kwargs = kwargs.reduce( {} ) do |hash, (key, val)|
228
+ ## puts "#{key} => #{val}:#{val.class.name}"
229
+ hash[key] = to_recursive_ostruct( val )
230
+ hash
231
+ end
232
+
233
+ ## (auto-)convert array and hash values to ostruct
234
+ ## for easy dot (.) access
235
+ ## e.g. student.name instead of student[:name]
236
+
145
237
  @template.result( Context.new( **kwargs ).get_binding )
146
238
  end
147
239
  end
@@ -1,17 +1,19 @@
1
+ %### Planet OPML template.
2
+ %###
1
3
  <?xml version="1.0"?>
2
4
  <opml version="1.1">
3
5
  <head>
4
- <title><%= name %></title>
6
+ <title><%=h name %></title>
5
7
  <dateModified><%= date_822 %></dateModified>
6
8
  <ownerName><%= owner_name %></ownerName>
7
9
  </head>
8
10
 
9
11
  <body>
10
- <% channels.each do |channel| %>
12
+ <% channels.each_with_loop do |channel, channel_loop| %>
11
13
  <outline type="rss"
12
- text="<%= channel.name %>"
13
- xmlUrl="<%= channel.url %>"
14
- <% if channel.channel_link %> htmlUrl="<%= channel.channel_link %>"<% end %> />
14
+ text="<%=h channel.name %>"
15
+ xmlUrl="<%=h channel.url %>"
16
+ <% if channel.channel_link %> htmlUrl="<%=h channel.channel_link %>"<% end %> />
15
17
  <% end %>
16
18
  </body>
17
19
  </opml>
@@ -1,7 +1,9 @@
1
+ ### Planet OPML template.
2
+ ###
1
3
  <?xml version="1.0"?>
2
4
  <opml version="1.1">
3
5
  <head>
4
- <title><TMPL_VAR name></title>
6
+ <title><TMPL_VAR name ESCAPE="HTML"></title>
5
7
  <dateModified><TMPL_VAR date_822></dateModified>
6
8
  <ownerName><TMPL_VAR owner_name></ownerName>
7
9
  </head>
@@ -9,9 +11,9 @@
9
11
  <body>
10
12
  <TMPL_LOOP channels>
11
13
  <outline type="rss"
12
- text="<TMPL_VAR name>"
13
- xmlUrl="<TMPL_VAR url>"
14
- <TMPL_IF channel_link> htmlUrl="<TMPL_VAR channel_link>"</TMPL_IF> />
14
+ text="<TMPL_VAR name ESCAPE="HTML">"
15
+ xmlUrl="<TMPL_VAR url ESCAPE="HTML">"
16
+ <TMPL_IF channel_link> htmlUrl="<TMPL_VAR channel_link ESCAPE="HTML">"</TMPL_IF> />
15
17
  </TMPL_LOOP>
16
18
  </body>
17
19
  </opml>
@@ -1,61 +1,61 @@
1
1
  <?xml version="1.0" encoding="utf-8" standalone="yes" ?>
2
2
  <feed xmlns="http://www.w3.org/2005/Atom">
3
3
 
4
- <title><TMPL_VAR name></title>
5
- <link rel="self" href="<TMPL_VAR feed ESCAPE="HTML">"/>
6
- <link href="<TMPL_VAR link ESCAPE="HTML">"/>
7
- <id><TMPL_VAR feed ESCAPE="HTML"></id>
8
- <updated><TMPL_VAR date_iso></updated>
9
- <generator uri="http://www.planetplanet.org/"><TMPL_VAR generator ESCAPE="HTML"></generator>
4
+ <title><TMPL_VAR name></title>
5
+ <link rel="self" href="<TMPL_VAR feed ESCAPE="HTML">"/>
6
+ <link href="<TMPL_VAR link ESCAPE="HTML">"/>
7
+ <id><TMPL_VAR feed ESCAPE="HTML"></id>
8
+ <updated><TMPL_VAR date_iso></updated>
9
+ <generator uri="http://www.planetplanet.org/"><TMPL_VAR generator ESCAPE="HTML"></generator>
10
10
 
11
11
  <TMPL_LOOP Items>
12
- <entry<TMPL_IF channel_language> xml:lang="<TMPL_VAR channel_language>"</TMPL_IF>>
13
- <title type="html"<TMPL_IF title_language> xml:lang="<TMPL_VAR title_language>"</TMPL_IF>><TMPL_VAR title ESCAPE="HTML"></title>
14
- <link href="<TMPL_VAR link ESCAPE="HTML">"/>
15
- <id><TMPL_VAR id ESCAPE="HTML"></id>
16
- <updated><TMPL_VAR date_iso></updated>
17
- <content type="html"<TMPL_IF content_language> xml:lang="<TMPL_VAR content_language>"</TMPL_IF>><TMPL_VAR content ESCAPE="HTML"></content>
18
- <author>
12
+ <entry<TMPL_IF channel_language> xml:lang="<TMPL_VAR channel_language>"</TMPL_IF>>
13
+ <title type="html"<TMPL_IF title_language> xml:lang="<TMPL_VAR title_language>"</TMPL_IF>><TMPL_VAR title ESCAPE="HTML"></title>
14
+ <link href="<TMPL_VAR link ESCAPE="HTML">"/>
15
+ <id><TMPL_VAR id ESCAPE="HTML"></id>
16
+ <updated><TMPL_VAR date_iso></updated>
17
+ <content type="html"<TMPL_IF content_language> xml:lang="<TMPL_VAR content_language>"</TMPL_IF>><TMPL_VAR content ESCAPE="HTML"></content>
18
+ <author>
19
19
  <TMPL_IF author_name>
20
- <name><TMPL_VAR author_name ESCAPE="HTML"></name>
20
+ <name><TMPL_VAR author_name ESCAPE="HTML"></name>
21
21
  <TMPL_IF author_email>
22
- <email><TMPL_VAR author_email ESCAPE="HTML"></email>
22
+ <email><TMPL_VAR author_email ESCAPE="HTML"></email>
23
23
  </TMPL_IF author_email>
24
24
  <TMPL_ELSE>
25
25
  <TMPL_IF channel_author_name>
26
- <name><TMPL_VAR channel_author_name ESCAPE="HTML"></name>
26
+ <name><TMPL_VAR channel_author_name ESCAPE="HTML"></name>
27
27
  <TMPL_IF channel_author_email>
28
- <email><TMPL_VAR channel_author_email ESCAPE="HTML"></email>
28
+ <email><TMPL_VAR channel_author_email ESCAPE="HTML"></email>
29
29
  </TMPL_IF channel_author_email>
30
30
  <TMPL_ELSE>
31
- <name><TMPL_VAR channel_name ESCAPE="HTML"></name>
31
+ <name><TMPL_VAR channel_name ESCAPE="HTML"></name>
32
32
  </TMPL_IF>
33
33
  </TMPL_IF>
34
- <uri><TMPL_VAR channel_link ESCAPE="HTML"></uri>
35
- </author>
36
- <source>
34
+ <uri><TMPL_VAR channel_link ESCAPE="HTML"></uri>
35
+ </author>
36
+ <source>
37
37
  <TMPL_IF channel_title>
38
- <title type="html"><TMPL_VAR channel_title ESCAPE="HTML"></title>
38
+ <title type="html"><TMPL_VAR channel_title ESCAPE="HTML"></title>
39
39
  <TMPL_ELSE>
40
- <title type="html"><TMPL_VAR channel_name ESCAPE="HTML"></title>
40
+ <title type="html"><TMPL_VAR channel_name ESCAPE="HTML"></title>
41
41
  </TMPL_IF>
42
42
  <TMPL_IF channel_subtitle>
43
- <subtitle type="html"><TMPL_VAR channel_subtitle ESCAPE="HTML"></subtitle>
43
+ <subtitle type="html"><TMPL_VAR channel_subtitle ESCAPE="HTML"></subtitle>
44
44
  </TMPL_IF>
45
- <link rel="self" href="<TMPL_VAR channel_url ESCAPE="HTML">"/>
45
+ <link rel="self" href="<TMPL_VAR channel_url ESCAPE="HTML">"/>
46
46
  <TMPL_IF channel_id>
47
- <id><TMPL_VAR channel_id ESCAPE="HTML"></id>
47
+ <id><TMPL_VAR channel_id ESCAPE="HTML"></id>
48
48
  <TMPL_ELSE>
49
- <id><TMPL_VAR channel_url ESCAPE="HTML"></id>
49
+ <id><TMPL_VAR channel_url ESCAPE="HTML"></id>
50
50
  </TMPL_IF>
51
51
  <TMPL_IF channel_updated_iso>
52
- <updated><TMPL_VAR channel_updated_iso></updated>
52
+ <updated><TMPL_VAR channel_updated_iso></updated>
53
53
  </TMPL_IF>
54
54
  <TMPL_IF channel_rights>
55
- <rights type="html"><TMPL_VAR channel_rights ESCAPE="HTML"></rights>
55
+ <rights type="html"><TMPL_VAR channel_rights ESCAPE="HTML"></rights>
56
56
  </TMPL_IF>
57
- </source>
58
- </entry>
57
+ </source>
58
+ </entry>
59
59
 
60
60
  </TMPL_LOOP>
61
61
  </feed>
@@ -1,30 +1,30 @@
1
1
  <?xml version="1.0"?>
2
2
  <rdf:RDF
3
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
4
- xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
5
- xmlns:foaf="http://xmlns.com/foaf/0.1/"
6
- xmlns:rss="http://purl.org/rss/1.0/"
7
- xmlns:dc="http://purl.org/dc/elements/1.1/"
3
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
4
+ xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
5
+ xmlns:foaf="http://xmlns.com/foaf/0.1/"
6
+ xmlns:rss="http://purl.org/rss/1.0/"
7
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
8
8
  >
9
9
  <foaf:Group>
10
- <foaf:name><TMPL_VAR name ESCAPE="HTML"></foaf:name>
11
- <foaf:homepage><TMPL_VAR link ESCAPE="HTML"></foaf:homepage>
12
- <rdfs:seeAlso rdf:resource="<TMPL_VAR url ESCAPE="HTML">" />
10
+ <foaf:name><TMPL_VAR name ESCAPE="HTML"></foaf:name>
11
+ <foaf:homepage><TMPL_VAR link ESCAPE="HTML"></foaf:homepage>
12
+ <rdfs:seeAlso rdf:resource="<TMPL_VAR url ESCAPE="HTML">" />
13
13
 
14
14
  <TMPL_LOOP Channels>
15
- <foaf:member>
16
- <foaf:Agent>
17
- <foaf:name><TMPL_VAR name ESCAPE="HTML"></foaf:name>
18
- <foaf:weblog>
19
- <foaf:Document rdf:about="<TMPL_VAR link ESCAPE="HTML">">
20
- <dc:title><TMPL_VAR title_plain ESCAPE="HTML"></dc:title>
21
- <rdfs:seeAlso>
22
- <rss:channel rdf:about="<TMPL_VAR url ESCAPE="HTML">" />
23
- </rdfs:seeAlso>
24
- </foaf:Document>
25
- </foaf:weblog>
26
- </foaf:Agent>
27
- </foaf:member>
15
+ <foaf:member>
16
+ <foaf:Agent>
17
+ <foaf:name><TMPL_VAR name ESCAPE="HTML"></foaf:name>
18
+ <foaf:weblog>
19
+ <foaf:Document rdf:about="<TMPL_VAR link ESCAPE="HTML">">
20
+ <dc:title><TMPL_VAR title_plain ESCAPE="HTML"></dc:title>
21
+ <rdfs:seeAlso>
22
+ <rss:channel rdf:about="<TMPL_VAR url ESCAPE="HTML">" />
23
+ </rdfs:seeAlso>
24
+ </foaf:Document>
25
+ </foaf:weblog>
26
+ </foaf:Agent>
27
+ </foaf:member>
28
28
  </TMPL_LOOP>
29
29
 
30
30
  </foaf:Group>
@@ -1,15 +1,15 @@
1
1
  <?xml version="1.0"?>
2
2
  <opml version="1.1">
3
- <head>
4
- <title><TMPL_VAR name ESCAPE="HTML"></title>
5
- <dateModified><TMPL_VAR date_822></dateModified>
6
- <ownerName><TMPL_VAR owner_name></ownerName>
7
- <ownerEmail><TMPL_VAR owner_email></ownerEmail>
8
- </head>
9
-
10
- <body>
11
- <TMPL_LOOP Channels>
12
- <outline type="rss" text="<TMPL_VAR name ESCAPE="HTML">" xmlUrl="<TMPL_VAR url ESCAPE="HTML">" title="<TMPL_IF title><TMPL_VAR title ESCAPE="HTML"></TMPL_IF><TMPL_UNLESS title><TMPL_VAR name ESCAPE="HTML"></TMPL_UNLESS>"<TMPL_IF channel_link> htmlUrl="<TMPL_VAR channel_link ESCAPE="HTML">"</TMPL_IF> />
13
- </TMPL_LOOP>
14
- </body>
3
+ <head>
4
+ <title><TMPL_VAR name ESCAPE="HTML"></title>
5
+ <dateModified><TMPL_VAR date_822></dateModified>
6
+ <ownerName><TMPL_VAR owner_name></ownerName>
7
+ <ownerEmail><TMPL_VAR owner_email></ownerEmail>
8
+ </head>
9
+
10
+ <body>
11
+ <TMPL_LOOP Channels>
12
+ <outline type="rss" text="<TMPL_VAR name ESCAPE="HTML">" xmlUrl="<TMPL_VAR url ESCAPE="HTML">" title="<TMPL_IF title><TMPL_VAR title ESCAPE="HTML"></TMPL_IF><TMPL_UNLESS title><TMPL_VAR name ESCAPE="HTML"></TMPL_UNLESS>"<TMPL_IF channel_link> htmlUrl="<TMPL_VAR channel_link ESCAPE="HTML">"</TMPL_IF> />
13
+ </TMPL_LOOP>
14
+ </body>
15
15
  </opml>
@@ -1,36 +1,36 @@
1
1
  <?xml version="1.0"?>
2
2
  <rdf:RDF
3
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
4
- xmlns:dc="http://purl.org/dc/elements/1.1/"
5
- xmlns:foaf="http://xmlns.com/foaf/0.1/"
6
- xmlns:content="http://purl.org/rss/1.0/modules/content/"
7
- xmlns="http://purl.org/rss/1.0/"
3
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
4
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
5
+ xmlns:foaf="http://xmlns.com/foaf/0.1/"
6
+ xmlns:content="http://purl.org/rss/1.0/modules/content/"
7
+ xmlns="http://purl.org/rss/1.0/"
8
8
  >
9
9
  <channel rdf:about="<TMPL_VAR link ESCAPE="HTML">">
10
- <title><TMPL_VAR name ESCAPE="HTML"></title>
11
- <link><TMPL_VAR link ESCAPE="HTML"></link>
12
- <description><TMPL_VAR name ESCAPE="HTML"> - <TMPL_VAR link ESCAPE="HTML"></description>
10
+ <title><TMPL_VAR name ESCAPE="HTML"></title>
11
+ <link><TMPL_VAR link ESCAPE="HTML"></link>
12
+ <description><TMPL_VAR name ESCAPE="HTML"> - <TMPL_VAR link ESCAPE="HTML"></description>
13
13
 
14
- <items>
15
- <rdf:Seq>
14
+ <items>
15
+ <rdf:Seq>
16
16
  <TMPL_LOOP Items>
17
- <rdf:li rdf:resource="<TMPL_VAR id ESCAPE="HTML">" />
17
+ <rdf:li rdf:resource="<TMPL_VAR id ESCAPE="HTML">" />
18
18
  </TMPL_LOOP>
19
- </rdf:Seq>
20
- </items>
19
+ </rdf:Seq>
20
+ </items>
21
21
  </channel>
22
22
 
23
23
  <TMPL_LOOP Items>
24
24
  <item rdf:about="<TMPL_VAR id ESCAPE="HTML">">
25
- <title><TMPL_VAR channel_name ESCAPE="HTML"><TMPL_IF title>: <TMPL_VAR title_plain ESCAPE="HTML"></TMPL_IF></title>
26
- <link><TMPL_VAR link ESCAPE="HTML"></link>
27
- <TMPL_IF content>
28
- <content:encoded><TMPL_VAR content ESCAPE="HTML"></content:encoded>
29
- </TMPL_IF>
30
- <dc:date><TMPL_VAR date_iso></dc:date>
31
- <TMPL_IF author_name>
32
- <dc:creator><TMPL_VAR author_name></dc:creator>
33
- </TMPL_IF>
25
+ <title><TMPL_VAR channel_name ESCAPE="HTML"><TMPL_IF title>: <TMPL_VAR title_plain ESCAPE="HTML"></TMPL_IF></title>
26
+ <link><TMPL_VAR link ESCAPE="HTML"></link>
27
+ <TMPL_IF content>
28
+ <content:encoded><TMPL_VAR content ESCAPE="HTML"></content:encoded>
29
+ </TMPL_IF>
30
+ <dc:date><TMPL_VAR date_iso></dc:date>
31
+ <TMPL_IF author_name>
32
+ <dc:creator><TMPL_VAR author_name></dc:creator>
33
+ </TMPL_IF>
34
34
  </item>
35
35
  </TMPL_LOOP>
36
36
 
@@ -2,27 +2,27 @@
2
2
  <rss version="2.0">
3
3
 
4
4
  <channel>
5
- <title><TMPL_VAR name></title>
6
- <link><TMPL_VAR link ESCAPE="HTML"></link>
7
- <language>en</language>
8
- <description><TMPL_VAR name ESCAPE="HTML"> - <TMPL_VAR link ESCAPE="HTML"></description>
5
+ <title><TMPL_VAR name></title>
6
+ <link><TMPL_VAR link ESCAPE="HTML"></link>
7
+ <language>en</language>
8
+ <description><TMPL_VAR name ESCAPE="HTML"> - <TMPL_VAR link ESCAPE="HTML"></description>
9
9
 
10
10
  <TMPL_LOOP Items>
11
11
  <item>
12
- <title><TMPL_VAR channel_name ESCAPE="HTML"><TMPL_IF title>: <TMPL_VAR title_plain ESCAPE="HTML"></TMPL_IF></title>
13
- <guid><TMPL_VAR id ESCAPE="HTML"></guid>
14
- <link><TMPL_VAR link ESCAPE="HTML"></link>
15
- <TMPL_IF content>
16
- <description><TMPL_VAR content ESCAPE="HTML"></description>
17
- </TMPL_IF>
18
- <pubDate><TMPL_VAR date_822></pubDate>
19
- <TMPL_IF author_email>
20
- <TMPL_IF author_name>
21
- <author><TMPL_VAR author_email> (<TMPL_VAR author_name>)</author>
22
- <TMPL_ELSE>
23
- <author><TMPL_VAR author_email></author>
24
- </TMPL_IF>
25
- </TMPL_IF>
12
+ <title><TMPL_VAR channel_name ESCAPE="HTML"><TMPL_IF title>: <TMPL_VAR title_plain ESCAPE="HTML"></TMPL_IF></title>
13
+ <guid><TMPL_VAR id ESCAPE="HTML"></guid>
14
+ <link><TMPL_VAR link ESCAPE="HTML"></link>
15
+ <TMPL_IF content>
16
+ <description><TMPL_VAR content ESCAPE="HTML"></description>
17
+ </TMPL_IF>
18
+ <pubDate><TMPL_VAR date_822></pubDate>
19
+ <TMPL_IF author_email>
20
+ <TMPL_IF author_name>
21
+ <author><TMPL_VAR author_email> (<TMPL_VAR author_name>)</author>
22
+ <TMPL_ELSE>
23
+ <author><TMPL_VAR author_email></author>
24
+ </TMPL_IF>
25
+ </TMPL_IF>
26
26
  </item>
27
27
  </TMPL_LOOP>
28
28
 
data/test/test_merge.rb CHANGED
@@ -77,8 +77,6 @@ $template->param(
77
77
  print $template->output;
78
78
  =end
79
79
 
80
- Student = Struct.new( :name, :gpa )
81
-
82
80
  def test_students_example
83
81
 
84
82
  tmpl =<<TXT
@@ -93,8 +91,8 @@ TXT
93
91
  t = HtmlTemplate.new( tmpl )
94
92
  puts t.text
95
93
  puts "---"
96
- puts t.render( students: [ Student.new( 'Bluto Blutarsky', 0.0 ),
97
- Student.new( 'Tracey Flick', 4.0 ) ])
94
+ puts t.render( students: [ { name: 'Bluto Blutarsky', gpa: 0.0 },
95
+ { name: 'Tracey Flick', gpa: 4.0 } ])
98
96
 
99
97
 
100
98
  assert true # assume it's alright if we get here
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: html-template
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gerald Bauer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-02-09 00:00:00.000000000 Z
11
+ date: 2020-02-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rdoc