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 +4 -4
- data/README.md +356 -1
- data/lib/html/template/version.rb +2 -2
- data/lib/html/template.rb +120 -28
- data/test/templates/opml.xml.erb +7 -5
- data/test/templates/opml.xml.tmpl +6 -4
- data/test/templates/planet/atom.xml.tmpl +31 -31
- data/test/templates/planet/foafroll.xml.tmpl +21 -21
- data/test/templates/planet/opml.xml.tmpl +12 -12
- data/test/templates/planet/rss10.xml.tmpl +22 -22
- data/test/templates/planet/rss20.xml.tmpl +18 -18
- data/test/test_merge.rb +2 -4
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3e35f465439469ba0e64f58a6ece96b2b5aa8358
|
4
|
+
data.tar.gz: 94054c9cf1dd94bee198b2e76e296542698b2c49
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
|
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
|
-
|
20
|
-
|
21
|
-
|
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-
|
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-
|
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-
|
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
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
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
|
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
|
-
|
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}.
|
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
|
-
|
183
|
+
elsif tag == 'UNLESS' && tag_open
|
184
|
+
"<% unless #{ctx}#{ident} %>"
|
185
|
+
elsif (tag == 'IF' || tag == 'UNLESS') && tag_close
|
114
186
|
"<% end %>"
|
115
|
-
elsif
|
116
|
-
|
117
|
-
|
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
|
data/test/templates/opml.xml.erb
CHANGED
@@ -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.
|
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
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
-
|
20
|
+
<name><TMPL_VAR author_name ESCAPE="HTML"></name>
|
21
21
|
<TMPL_IF author_email>
|
22
|
-
|
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
|
-
|
26
|
+
<name><TMPL_VAR channel_author_name ESCAPE="HTML"></name>
|
27
27
|
<TMPL_IF channel_author_email>
|
28
|
-
|
28
|
+
<email><TMPL_VAR channel_author_email ESCAPE="HTML"></email>
|
29
29
|
</TMPL_IF channel_author_email>
|
30
30
|
<TMPL_ELSE>
|
31
|
-
|
31
|
+
<name><TMPL_VAR channel_name ESCAPE="HTML"></name>
|
32
32
|
</TMPL_IF>
|
33
33
|
</TMPL_IF>
|
34
|
-
|
35
|
-
|
36
|
-
|
34
|
+
<uri><TMPL_VAR channel_link ESCAPE="HTML"></uri>
|
35
|
+
</author>
|
36
|
+
<source>
|
37
37
|
<TMPL_IF channel_title>
|
38
|
-
|
38
|
+
<title type="html"><TMPL_VAR channel_title ESCAPE="HTML"></title>
|
39
39
|
<TMPL_ELSE>
|
40
|
-
|
40
|
+
<title type="html"><TMPL_VAR channel_name ESCAPE="HTML"></title>
|
41
41
|
</TMPL_IF>
|
42
42
|
<TMPL_IF channel_subtitle>
|
43
|
-
|
43
|
+
<subtitle type="html"><TMPL_VAR channel_subtitle ESCAPE="HTML"></subtitle>
|
44
44
|
</TMPL_IF>
|
45
|
-
|
45
|
+
<link rel="self" href="<TMPL_VAR channel_url ESCAPE="HTML">"/>
|
46
46
|
<TMPL_IF channel_id>
|
47
|
-
|
47
|
+
<id><TMPL_VAR channel_id ESCAPE="HTML"></id>
|
48
48
|
<TMPL_ELSE>
|
49
|
-
|
49
|
+
<id><TMPL_VAR channel_url ESCAPE="HTML"></id>
|
50
50
|
</TMPL_IF>
|
51
51
|
<TMPL_IF channel_updated_iso>
|
52
|
-
|
52
|
+
<updated><TMPL_VAR channel_updated_iso></updated>
|
53
53
|
</TMPL_IF>
|
54
54
|
<TMPL_IF channel_rights>
|
55
|
-
|
55
|
+
<rights type="html"><TMPL_VAR channel_rights ESCAPE="HTML"></rights>
|
56
56
|
</TMPL_IF>
|
57
|
-
|
58
|
-
|
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
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
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
|
-
|
11
|
-
|
12
|
-
|
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
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
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
|
-
|
11
|
-
|
12
|
-
|
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
|
-
|
15
|
-
|
14
|
+
<items>
|
15
|
+
<rdf:Seq>
|
16
16
|
<TMPL_LOOP Items>
|
17
|
-
|
17
|
+
<rdf:li rdf:resource="<TMPL_VAR id ESCAPE="HTML">" />
|
18
18
|
</TMPL_LOOP>
|
19
|
-
|
20
|
-
|
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
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
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
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
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
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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: [
|
97
|
-
|
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
|
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-
|
11
|
+
date: 2020-02-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rdoc
|