luigi-template 0.5.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 +7 -0
- data/README.mkd +75 -0
- data/Rakefile +16 -0
- data/lib/luigi-template.rb +914 -0
- data/test/test_cache.rb +28 -0
- data/test/test_default_filters.rb +86 -0
- data/test/test_errors.rb +32 -0
- data/test/test_filters.rb +51 -0
- data/test/test_template.rb +65 -0
- metadata +51 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a2d52d440f44dd02a4bfef5a305bef2bae1a3c25e2cbe8874e42a34ac53ce862
|
4
|
+
data.tar.gz: cb991f02739dee48a8f752e68fc2975e936269422e7fd937115824f2335a43de
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2f977f467899c187c3bf05fa76e9f8f8170bd7edeed8a57452e2ce448f3930456cd80fbee1425d6e5c68121034b0ec3dd0af2c6f6e63f463718e387ee0cb9fc8
|
7
|
+
data.tar.gz: b4595175ca66f28c6bfec2ab31d4d4441dae8877ed12b23e69b3422ffa7bd95c6f41a023a776ee1ee04f7176ce71241ca626d0ba0b254bc2782cca5d49e4a688
|
data/README.mkd
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
Luigi Template
|
2
|
+
==============
|
3
|
+
|
4
|
+
Overview
|
5
|
+
--------
|
6
|
+
Luigi Template is a string templating library for JavaScript, Java,
|
7
|
+
Ruby, and PHP.
|
8
|
+
|
9
|
+
Here's an example:
|
10
|
+
|
11
|
+
*TODO*
|
12
|
+
|
13
|
+
Features:
|
14
|
+
|
15
|
+
*TODO*
|
16
|
+
|
17
|
+
Documentation
|
18
|
+
-------------
|
19
|
+
The API documentation is available online at the following URL:
|
20
|
+
|
21
|
+
https://pablotron.github.io/luigi-template/ruby/
|
22
|
+
|
23
|
+
You can generate the API documentation in the `docs/` directory via
|
24
|
+
[RDoc][], like so:
|
25
|
+
|
26
|
+
# generate API documentation in docs/ directory
|
27
|
+
rake docs
|
28
|
+
|
29
|
+
Tests
|
30
|
+
-----
|
31
|
+
You can run the [minitest][] test suite via [Rake][], like so:
|
32
|
+
|
33
|
+
# run the test suite
|
34
|
+
rake test
|
35
|
+
|
36
|
+
To generate a [JUnit][]-compatible XML report, install the
|
37
|
+
[minitest-junit][] gem and then do the following:
|
38
|
+
|
39
|
+
# run the test suite and generate a junit-compatible report.xml
|
40
|
+
rake test TESTOPTS=--junit
|
41
|
+
|
42
|
+
Author
|
43
|
+
------
|
44
|
+
Paul Duncan ([pabs@pablotron.org][me])<br/>
|
45
|
+
https://pablotron.org/
|
46
|
+
|
47
|
+
License
|
48
|
+
-------
|
49
|
+
Copyright 2010-2018 Paul Duncan ([pabs@pablotron.org][me])
|
50
|
+
|
51
|
+
Permission is hereby granted, free of charge, to any person obtaining a
|
52
|
+
copy of this software and associated documentation files (the
|
53
|
+
"Software"), to deal in the Software without restriction, including
|
54
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
55
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
56
|
+
permit persons to whom the Software is furnished to do so, subject to
|
57
|
+
the following conditions:
|
58
|
+
|
59
|
+
The above copyright notice and this permission notice shall be included
|
60
|
+
in all copies or substantial portions of the Software.
|
61
|
+
|
62
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
63
|
+
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
64
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
65
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
66
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
67
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
68
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
69
|
+
|
70
|
+
[JUnit]: https://junit.org/
|
71
|
+
[me]: mailto:pabs@pablotron.org
|
72
|
+
[minitest]: https://github.com/seattlerb/minitest
|
73
|
+
[minitest-junit]: https://github.com/aespinosa/minitest-junit
|
74
|
+
[RDoc]: https://github.com/ruby/rdoc
|
75
|
+
[Rake]: https://github.com/ruby/rake
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'rake/testtask'
|
2
|
+
require 'rdoc/task'
|
3
|
+
|
4
|
+
Rake::TestTask.new do |t|
|
5
|
+
t.libs << 'test'
|
6
|
+
end
|
7
|
+
|
8
|
+
RDoc::Task.new :docs do |t|
|
9
|
+
t.main = "lib/luigi-template.rb"
|
10
|
+
t.rdoc_files.include('lib/*.rb')
|
11
|
+
t.rdoc_dir = 'docs'
|
12
|
+
# t.options << "--all"
|
13
|
+
end
|
14
|
+
|
15
|
+
desc "Run tests"
|
16
|
+
task :default => :test
|
@@ -0,0 +1,914 @@
|
|
1
|
+
#
|
2
|
+
# = Luigi Template
|
3
|
+
#
|
4
|
+
# == Overview
|
5
|
+
#
|
6
|
+
# Luigi Template is a string template library.
|
7
|
+
#
|
8
|
+
# == Examples
|
9
|
+
#
|
10
|
+
# Simple usage example:
|
11
|
+
#
|
12
|
+
# # load luigi template
|
13
|
+
# require 'luigi-template'
|
14
|
+
#
|
15
|
+
# # create template
|
16
|
+
# tmpl = Luigi::Template.new('hello %{name}')
|
17
|
+
#
|
18
|
+
# # run template and print result
|
19
|
+
# puts tmpl.run({
|
20
|
+
# name: 'Paul'
|
21
|
+
# })
|
22
|
+
#
|
23
|
+
# # prints "hello Paul"
|
24
|
+
#
|
25
|
+
# You can also filter values in templates, using the pipe symbol:
|
26
|
+
#
|
27
|
+
# # create template that converts name to upper-case
|
28
|
+
# tmpl = Luigi::Template.new('hello %{name | uc}')
|
29
|
+
#
|
30
|
+
# # run template and print result
|
31
|
+
# puts tmpl.run({
|
32
|
+
# name: 'Paul'
|
33
|
+
# })
|
34
|
+
#
|
35
|
+
# # prints "hello PAUL"
|
36
|
+
#
|
37
|
+
# Filters can be chained:
|
38
|
+
#
|
39
|
+
# # create template that converts name to upper-case and then
|
40
|
+
# # strips leading and trailing whitespace
|
41
|
+
# tmpl = Luigi::Template.new('hello %{name | uc | trim}')
|
42
|
+
#
|
43
|
+
# # run template and print result
|
44
|
+
# puts tmpl.run({
|
45
|
+
# name: ' Paul '
|
46
|
+
# })
|
47
|
+
#
|
48
|
+
# # prints "hello PAUL"
|
49
|
+
#
|
50
|
+
# Filters can take arguments:
|
51
|
+
#
|
52
|
+
# # create template that converts name to lowercase and then
|
53
|
+
# # calculates the SHA-1 digest of the result
|
54
|
+
# tmpl = Luigi::Template.new('hello %{name | lc | hash sha1}')
|
55
|
+
#
|
56
|
+
# # run template and print result
|
57
|
+
# puts tmpl.run({
|
58
|
+
# name: 'Paul',
|
59
|
+
# })
|
60
|
+
#
|
61
|
+
# # prints "hello a027184a55211cd23e3f3094f1fdc728df5e0500"
|
62
|
+
#
|
63
|
+
# You can define custom global filters:
|
64
|
+
#
|
65
|
+
# # create custom global filter named 'foobarify'
|
66
|
+
# Luigi::FILTERS[:foobarify] = proc { |s| "foo-#{s}-bar" }
|
67
|
+
#
|
68
|
+
# # create template which uses custom "foobarify" filter
|
69
|
+
# tmpl = Luigi::Template.new('hello %{name | foobarify}')
|
70
|
+
#
|
71
|
+
# # run template and print result
|
72
|
+
# puts tmpl.run({
|
73
|
+
# name: 'Paul'
|
74
|
+
# })
|
75
|
+
#
|
76
|
+
# # prints "hello foo-Paul-bar"
|
77
|
+
#
|
78
|
+
# Or define custom filters for a template:
|
79
|
+
#
|
80
|
+
# # create template with custom filters rather than global filters
|
81
|
+
# tmpl = Luigi::Template.new('hello %{name | reverse}', {
|
82
|
+
# reverse: proc { |s| s.reverse }
|
83
|
+
# })
|
84
|
+
#
|
85
|
+
# # run template and print result
|
86
|
+
# puts tmpl.run({
|
87
|
+
# name: 'Paul',
|
88
|
+
# })
|
89
|
+
#
|
90
|
+
# # prints "hello luaP"
|
91
|
+
#
|
92
|
+
# Your custom filters can accept arguments, too:
|
93
|
+
#
|
94
|
+
# # create custom global filter named 'foobarify'
|
95
|
+
# Luigi::FILTERS[:wrap] = proc { |s, args|
|
96
|
+
# case args.length
|
97
|
+
# when 2
|
98
|
+
# '(%s, %s, %s)' % [args[0], s, args[1]]
|
99
|
+
# when 1
|
100
|
+
# '(%s in %s)' % [s, args[0]]
|
101
|
+
# when 0
|
102
|
+
# s
|
103
|
+
# else
|
104
|
+
# raise 'invalid argument count'
|
105
|
+
# end
|
106
|
+
# }
|
107
|
+
#
|
108
|
+
# # create template that uses custom "wrap" filter
|
109
|
+
# tmpl = Luigi::Template.new('sandwich: %{meat | wrap slice heel}, taco: %{meat | wrap shell}')
|
110
|
+
#
|
111
|
+
# # run template and print result
|
112
|
+
# puts tmpl.run({
|
113
|
+
# meat: 'chicken'
|
114
|
+
# })
|
115
|
+
#
|
116
|
+
# # prints "sandwich: (slice, chicken, heel), taco: (chicken in shell)"
|
117
|
+
#
|
118
|
+
# == Filters
|
119
|
+
#
|
120
|
+
# The following filters are built-in:
|
121
|
+
#
|
122
|
+
# * +uc+: Convert string to upper-case.
|
123
|
+
# * +lc+: Convert string to lower-case.
|
124
|
+
# * +h+: HTML-escape string.
|
125
|
+
# * +u+: URL-escape string.
|
126
|
+
# * +json+: JSON-encode value.
|
127
|
+
# * +trim+: Strip leading and trailing whitespace from string.
|
128
|
+
# * +base64+: Base64-encode value.
|
129
|
+
# * +hash+: Hash value. Requires hash algorithm parameter (ex:
|
130
|
+
# "sha1", "md5", etc).
|
131
|
+
#
|
132
|
+
# You can add your own global filters, like so:
|
133
|
+
#
|
134
|
+
# # create custom global filter named 'foobarify'
|
135
|
+
# Luigi::FILTERS[:foobarify] = proc { |s| "foo-#{s}-bar" }
|
136
|
+
#
|
137
|
+
# # create template which uses custom "foobarify" filter
|
138
|
+
# tmpl = Luigi::Template.new('hello %{name | foobarify}')
|
139
|
+
#
|
140
|
+
# # run template and print result
|
141
|
+
# puts tmpl.run({
|
142
|
+
# name: 'Paul'
|
143
|
+
# })
|
144
|
+
#
|
145
|
+
# # prints "hello foo-Paul-bar"
|
146
|
+
#
|
147
|
+
# == License
|
148
|
+
#
|
149
|
+
# Copyright 2007-2018 Paul Duncan ({pabs@pablotron.org}[mailto:pabs@pablotron.org])
|
150
|
+
#
|
151
|
+
# Permission is hereby granted, free of charge, to any person obtaining a
|
152
|
+
# copy of this software and associated documentation files (the
|
153
|
+
# "Software"), to deal in the Software without restriction, including
|
154
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
155
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
156
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
157
|
+
# the following conditions:
|
158
|
+
#
|
159
|
+
# The above copyright notice and this permission notice shall be included
|
160
|
+
# in all copies or substantial portions of the Software.
|
161
|
+
#
|
162
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
163
|
+
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
164
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
165
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
166
|
+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
167
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
168
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
169
|
+
#
|
170
|
+
|
171
|
+
require 'uri'
|
172
|
+
require 'json'
|
173
|
+
require 'openssl'
|
174
|
+
# require 'pp'
|
175
|
+
|
176
|
+
#
|
177
|
+
# Top-level Luigi namespace. See Luigi::Template for details.
|
178
|
+
#
|
179
|
+
# Example:
|
180
|
+
#
|
181
|
+
# # load luigi template
|
182
|
+
# require 'luigi-template'
|
183
|
+
#
|
184
|
+
# # create template
|
185
|
+
# tmpl = Luigi::Template.new('hello %{name}')
|
186
|
+
#
|
187
|
+
# # run template and print result
|
188
|
+
# puts tmpl.run({
|
189
|
+
# name: 'Paul'
|
190
|
+
# })
|
191
|
+
#
|
192
|
+
# # prints "hello Paul"
|
193
|
+
#
|
194
|
+
# You can also filter values in templates, using the pipe symbol:
|
195
|
+
#
|
196
|
+
# # create template that converts name to upper-case
|
197
|
+
# tmpl = Luigi::Template.new('hello %{name | uc}')
|
198
|
+
#
|
199
|
+
# # run template and print result
|
200
|
+
# puts tmpl.run({
|
201
|
+
# name: 'Paul'
|
202
|
+
# })
|
203
|
+
#
|
204
|
+
# # prints "hello PAUL"
|
205
|
+
#
|
206
|
+
# Filters can be chained:
|
207
|
+
#
|
208
|
+
# # create template that converts name to upper-case and then
|
209
|
+
# # strips leading and trailing whitespace
|
210
|
+
# tmpl = Luigi::Template.new('hello %{name | uc | trim}')
|
211
|
+
#
|
212
|
+
# # run template and print result
|
213
|
+
# puts tmpl.run({
|
214
|
+
# name: ' Paul '
|
215
|
+
# })
|
216
|
+
#
|
217
|
+
# # prints "hello PAUL"
|
218
|
+
#
|
219
|
+
# Filters can take arguments:
|
220
|
+
#
|
221
|
+
# # create template that converts name to lowercase and then
|
222
|
+
# # calculates the SHA-1 digest of the result
|
223
|
+
# tmpl = Luigi::Template.new('hello %{name | lc | hash sha1}')
|
224
|
+
#
|
225
|
+
# # run template and print result
|
226
|
+
# puts tmpl.run({
|
227
|
+
# name: 'Paul',
|
228
|
+
# })
|
229
|
+
#
|
230
|
+
# # prints "hello a027184a55211cd23e3f3094f1fdc728df5e0500"
|
231
|
+
#
|
232
|
+
# You can define custom global filters:
|
233
|
+
#
|
234
|
+
# # create custom global filter named 'foobarify'
|
235
|
+
# Luigi::FILTERS[:foobarify] = proc { |s| "foo-#{s}-bar" }
|
236
|
+
#
|
237
|
+
# # create template which uses custom "foobarify" filter
|
238
|
+
# tmpl = Luigi::Template.new('hello %{name | foobarify}')
|
239
|
+
#
|
240
|
+
# # run template and print result
|
241
|
+
# puts tmpl.run({
|
242
|
+
# name: 'Paul'
|
243
|
+
# })
|
244
|
+
#
|
245
|
+
# # prints "hello foo-Paul-bar"
|
246
|
+
#
|
247
|
+
# Or define custom filters for a template:
|
248
|
+
#
|
249
|
+
# # create template with custom filters rather than global filters
|
250
|
+
# tmpl = Luigi::Template.new('hello %{name | reverse}', {
|
251
|
+
# reverse: proc { |s| s.reverse }
|
252
|
+
# })
|
253
|
+
#
|
254
|
+
# # run template and print result
|
255
|
+
# puts tmpl.run({
|
256
|
+
# name: 'Paul',
|
257
|
+
# })
|
258
|
+
#
|
259
|
+
# # prints "hello luaP"
|
260
|
+
#
|
261
|
+
# Your custom filters can accept arguments, too:
|
262
|
+
#
|
263
|
+
# # create custom global filter named 'foobarify'
|
264
|
+
# Luigi::FILTERS[:wrap] = proc { |s, args|
|
265
|
+
# case args.length
|
266
|
+
# when 2
|
267
|
+
# '(%s, %s, %s)' % [args[0], s, args[1]]
|
268
|
+
# when 1
|
269
|
+
# '(%s in %s)' % [s, args[0]]
|
270
|
+
# when 0
|
271
|
+
# s
|
272
|
+
# else
|
273
|
+
# raise 'invalid argument count'
|
274
|
+
# end
|
275
|
+
# }
|
276
|
+
#
|
277
|
+
# # create template that uses custom "wrap" filter
|
278
|
+
# tmpl = Luigi::Template.new('sandwich: %{meat | wrap slice heel}, taco: %{meat | wrap shell}')
|
279
|
+
#
|
280
|
+
# # run template and print result
|
281
|
+
# puts tmpl.run({
|
282
|
+
# meat: 'chicken'
|
283
|
+
# })
|
284
|
+
#
|
285
|
+
# # prints "sandwich: (slice, chicken, heel), taco: (chicken in shell)"
|
286
|
+
#
|
287
|
+
module Luigi
|
288
|
+
#
|
289
|
+
# Version of Luigi Template.
|
290
|
+
#
|
291
|
+
VERSION = '0.5.0'
|
292
|
+
|
293
|
+
#
|
294
|
+
# Base class for all errors raised by Luigi Template.
|
295
|
+
#
|
296
|
+
class LuigiError < Exception
|
297
|
+
end
|
298
|
+
|
299
|
+
#
|
300
|
+
# Base class for unknown entry errors raised by Luigi Template.
|
301
|
+
#
|
302
|
+
class BaseUnknownError < LuigiError
|
303
|
+
#
|
304
|
+
# Type of unknown entry (Symbol).
|
305
|
+
#
|
306
|
+
attr_reader :type
|
307
|
+
|
308
|
+
#
|
309
|
+
# Name of unknown entry (String).
|
310
|
+
#
|
311
|
+
attr_reader :name
|
312
|
+
|
313
|
+
#
|
314
|
+
# Create a new BaseUnknownError instance.
|
315
|
+
#
|
316
|
+
# Parameters:
|
317
|
+
#
|
318
|
+
# * +type+: Type name (ex: "template", "filter", or "key").
|
319
|
+
# * +name+: Item name.
|
320
|
+
#
|
321
|
+
def initialize(type, name)
|
322
|
+
@type, @name = type, name
|
323
|
+
super("unknown #{type}: #{name}")
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
#
|
328
|
+
# Thrown by Luigi::Template#run when an unknown key is encountered.
|
329
|
+
#
|
330
|
+
# The key is available in the +name+ attribute.
|
331
|
+
#
|
332
|
+
class UnknownKeyError < BaseUnknownError
|
333
|
+
#
|
334
|
+
# Create a new UnknownFilterError instance.
|
335
|
+
#
|
336
|
+
# Parameters:
|
337
|
+
#
|
338
|
+
# * +name+: Unknown key.
|
339
|
+
#
|
340
|
+
def initialize(name)
|
341
|
+
super(:key, name)
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
#
|
346
|
+
# Thrown by Luigi::Template#run when an unknown filter is encountered.
|
347
|
+
#
|
348
|
+
# The unknown filter name is available in the +name+ attribute.
|
349
|
+
#
|
350
|
+
class UnknownFilterError < BaseUnknownError
|
351
|
+
#
|
352
|
+
# Create a new UnknownFilterError instance.
|
353
|
+
#
|
354
|
+
# Parameters:
|
355
|
+
#
|
356
|
+
# * +name+: Name of the unknown filter.
|
357
|
+
#
|
358
|
+
def initialize(name)
|
359
|
+
super(:filter, name)
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
|
364
|
+
#
|
365
|
+
# Thrown by Luigi::Cache#run when an unknown template is encountered.
|
366
|
+
#
|
367
|
+
# The unknown template name is available in the +name+ attribute.
|
368
|
+
#
|
369
|
+
class UnknownTemplateError < BaseUnknownError
|
370
|
+
#
|
371
|
+
# Create a new UnknownTemplateError instance.
|
372
|
+
#
|
373
|
+
# Parameters:
|
374
|
+
#
|
375
|
+
# * +name+: Unknown template name.
|
376
|
+
#
|
377
|
+
def initialize(name)
|
378
|
+
super(:template, name);
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
#
|
383
|
+
# HTML entity map.
|
384
|
+
#
|
385
|
+
# Used by built-in +h+ filter.
|
386
|
+
#
|
387
|
+
HTML_ENTITIES = {
|
388
|
+
38 => '&',
|
389
|
+
60 => '<',
|
390
|
+
62 => '>',
|
391
|
+
34 => '"',
|
392
|
+
39 => ''',
|
393
|
+
}
|
394
|
+
|
395
|
+
#
|
396
|
+
# Map of built-in global filters.
|
397
|
+
#
|
398
|
+
# Default Filters:
|
399
|
+
#
|
400
|
+
# * +uc+: Convert string to upper-case.
|
401
|
+
# * +lc+: Convert string to lower-case.
|
402
|
+
# * +h+: HTML-escape string.
|
403
|
+
# * +u+: URL-escape string.
|
404
|
+
# * +json+: JSON-encode value.
|
405
|
+
# * +trim+: Strip leading and trailing whitespace from string.
|
406
|
+
# * +base64+: Base64-encode value.
|
407
|
+
# * +hash+: Hash value. Requires hash algorithm parameter (ex:
|
408
|
+
# "sha1", "md5", etc).
|
409
|
+
#
|
410
|
+
# You can add your own global filters, like so:
|
411
|
+
#
|
412
|
+
# # create custom global filter named 'foobarify'
|
413
|
+
# Luigi::FILTERS[:foobarify] = proc { |s| "foo-#{s}-bar" }
|
414
|
+
#
|
415
|
+
# # create template which uses custom "foobarify" filter
|
416
|
+
# tmpl = Luigi::Template.new('hello %{name | foobarify}')
|
417
|
+
#
|
418
|
+
# # run template and print result
|
419
|
+
# puts tmpl.run({
|
420
|
+
# name: 'Paul'
|
421
|
+
# })
|
422
|
+
#
|
423
|
+
# # prints "hello foo-Paul-bar"
|
424
|
+
#
|
425
|
+
FILTERS = {
|
426
|
+
# upper-case string
|
427
|
+
uc: proc { |v|
|
428
|
+
(v || '').to_s.upcase
|
429
|
+
},
|
430
|
+
|
431
|
+
# lower-case string
|
432
|
+
lc: proc { |v|
|
433
|
+
(v || '').to_s.downcase
|
434
|
+
},
|
435
|
+
|
436
|
+
# html-escape string
|
437
|
+
h: proc { |v|
|
438
|
+
(v || '').to_s.bytes.map { |b|
|
439
|
+
if b < 32 || b > 126
|
440
|
+
"&##{b};"
|
441
|
+
elsif HTML_ENTITIES.key?(b)
|
442
|
+
HTML_ENTITIES[b]
|
443
|
+
else
|
444
|
+
b.chr
|
445
|
+
end
|
446
|
+
}.join
|
447
|
+
},
|
448
|
+
|
449
|
+
# uri-escape string
|
450
|
+
u: proc { |v|
|
451
|
+
URI.encode_www_form_component((v || '').to_s)
|
452
|
+
},
|
453
|
+
|
454
|
+
# json-encode value
|
455
|
+
json: proc { |v|
|
456
|
+
JSON.unparse(v)
|
457
|
+
},
|
458
|
+
|
459
|
+
# trim leading and trailing whitespace from string
|
460
|
+
trim: proc { |v, args, row, t|
|
461
|
+
(v || '').to_s.strip
|
462
|
+
},
|
463
|
+
|
464
|
+
# base64-encode string
|
465
|
+
base64: proc { |v, args, row, t|
|
466
|
+
[(v || '').to_s].pack('m').strip
|
467
|
+
},
|
468
|
+
|
469
|
+
# hash string
|
470
|
+
hash: proc { |v, args, row, t|
|
471
|
+
OpenSSL::Digest.new(args[0]).hexdigest((v || '').to_s)
|
472
|
+
},
|
473
|
+
}
|
474
|
+
|
475
|
+
#
|
476
|
+
# Template parser.
|
477
|
+
#
|
478
|
+
module Parser # :nodoc: all
|
479
|
+
RES = {
|
480
|
+
action: %r{
|
481
|
+
# match opening brace
|
482
|
+
%\{
|
483
|
+
|
484
|
+
# match optional whitespace
|
485
|
+
\s*
|
486
|
+
|
487
|
+
# match key
|
488
|
+
(?<key>[^\s\|\}]+)
|
489
|
+
|
490
|
+
# match filter(s)
|
491
|
+
(?<filters>(\s*\|(\s*[^\s\|\}]+)+)*)
|
492
|
+
|
493
|
+
# match optional whitespace
|
494
|
+
\s*
|
495
|
+
|
496
|
+
# match closing brace
|
497
|
+
\}
|
498
|
+
|
499
|
+
# or match up all non-% chars or a single % char
|
500
|
+
| (?<text>[^%]* | %)
|
501
|
+
}mx,
|
502
|
+
|
503
|
+
filter: %r{
|
504
|
+
# match filter name
|
505
|
+
(?<name>\S+)
|
506
|
+
|
507
|
+
# match filter arguments (optional)
|
508
|
+
(?<args>(\s*\S+)*)
|
509
|
+
|
510
|
+
# optional trailing whitespace
|
511
|
+
\s*
|
512
|
+
}mx,
|
513
|
+
|
514
|
+
delim_filters: %r{
|
515
|
+
\s*\|\s*
|
516
|
+
}mx,
|
517
|
+
|
518
|
+
delim_args: %r{
|
519
|
+
\s+
|
520
|
+
},
|
521
|
+
}.reduce({}) do |r, row|
|
522
|
+
r[row[0]] = row[1].freeze
|
523
|
+
r
|
524
|
+
end.freeze
|
525
|
+
|
526
|
+
#
|
527
|
+
# Parse a (possibly empty) string into an array of actions.
|
528
|
+
#
|
529
|
+
def self.parse_template(str)
|
530
|
+
str.scan(RES[:action]).map { |m|
|
531
|
+
if m[0] && m[0].length > 0
|
532
|
+
fs = parse_filters(m[1]).freeze
|
533
|
+
{ type: :action, key: m[0].intern, filters: fs }
|
534
|
+
else
|
535
|
+
# literal text
|
536
|
+
{ type: :text, text: m[2].freeze }
|
537
|
+
end.freeze
|
538
|
+
}.freeze
|
539
|
+
end
|
540
|
+
|
541
|
+
#
|
542
|
+
# Parse a (possibly empty) string into an array of filters.
|
543
|
+
#
|
544
|
+
def self.parse_filters(str)
|
545
|
+
# strip leading and trailing whitespace
|
546
|
+
str = (str || '').strip
|
547
|
+
|
548
|
+
if str.length > 0
|
549
|
+
str.strip.split(RES[:delim_filters]).inject([]) do |r, f|
|
550
|
+
# strip whitespace
|
551
|
+
f = f.strip
|
552
|
+
|
553
|
+
if f.length > 0
|
554
|
+
md = f.match(RES[:filter])
|
555
|
+
raise "invalid filter: #{f}" unless md
|
556
|
+
# pp md
|
557
|
+
|
558
|
+
# get args
|
559
|
+
args = md[:args].strip
|
560
|
+
|
561
|
+
# add to result
|
562
|
+
r << {
|
563
|
+
name: md[:name].intern,
|
564
|
+
args: args.length > 0 ? args.split(RES[:delim_args]) : [],
|
565
|
+
}
|
566
|
+
end
|
567
|
+
|
568
|
+
# return result
|
569
|
+
r
|
570
|
+
end
|
571
|
+
else
|
572
|
+
# return empty filter set
|
573
|
+
[]
|
574
|
+
end
|
575
|
+
end
|
576
|
+
end
|
577
|
+
|
578
|
+
#
|
579
|
+
# Template class.
|
580
|
+
#
|
581
|
+
# Parse a template string into a Luigi::Template instance, and then
|
582
|
+
# apply the Luigi::Template via the Luigi::Template#run() method.
|
583
|
+
#
|
584
|
+
# Example:
|
585
|
+
#
|
586
|
+
# # load luigi template
|
587
|
+
# require 'luigi-template'
|
588
|
+
#
|
589
|
+
# # create template
|
590
|
+
# tmpl = Luigi::Template.new('hello %{name}')
|
591
|
+
#
|
592
|
+
# # run template and print result
|
593
|
+
# puts tmpl.run({
|
594
|
+
# name: 'Paul'
|
595
|
+
# })
|
596
|
+
#
|
597
|
+
# # prints "hello Paul"
|
598
|
+
#
|
599
|
+
# You can also filter values in templates, using the pipe symbol:
|
600
|
+
#
|
601
|
+
# # create template that converts name to upper-case
|
602
|
+
# tmpl = Luigi::Template.new('hello %{name | uc}')
|
603
|
+
#
|
604
|
+
# # run template and print result
|
605
|
+
# puts tmpl.run({
|
606
|
+
# name: 'Paul'
|
607
|
+
# })
|
608
|
+
#
|
609
|
+
# # prints "hello PAUL"
|
610
|
+
#
|
611
|
+
# Filters can be chained:
|
612
|
+
#
|
613
|
+
# # create template that converts name to upper-case and then
|
614
|
+
# # strips leading and trailing whitespace
|
615
|
+
# tmpl = Luigi::Template.new('hello %{name | uc | trim}')
|
616
|
+
#
|
617
|
+
# # run template and print result
|
618
|
+
# puts tmpl.run({
|
619
|
+
# name: ' Paul '
|
620
|
+
# })
|
621
|
+
#
|
622
|
+
# # prints "hello PAUL"
|
623
|
+
#
|
624
|
+
# Filters can take arguments:
|
625
|
+
#
|
626
|
+
# # create template that converts name to lowercase and then
|
627
|
+
# # calculates the SHA-1 digest of the result
|
628
|
+
# tmpl = Luigi::Template.new('hello %{name | lc | hash sha1}')
|
629
|
+
#
|
630
|
+
# # run template and print result
|
631
|
+
# puts tmpl.run({
|
632
|
+
# name: 'Paul',
|
633
|
+
# })
|
634
|
+
#
|
635
|
+
# # prints "hello a027184a55211cd23e3f3094f1fdc728df5e0500"
|
636
|
+
#
|
637
|
+
# You can define custom global filters:
|
638
|
+
#
|
639
|
+
# # create custom global filter named 'foobarify'
|
640
|
+
# Luigi::FILTERS[:foobarify] = proc { |s| "foo-#{s}-bar" }
|
641
|
+
#
|
642
|
+
# # create template which uses custom "foobarify" filter
|
643
|
+
# tmpl = Luigi::Template.new('hello %{name | foobarify}')
|
644
|
+
#
|
645
|
+
# # run template and print result
|
646
|
+
# puts tmpl.run({
|
647
|
+
# name: 'Paul'
|
648
|
+
# })
|
649
|
+
#
|
650
|
+
# # prints "hello foo-Paul-bar"
|
651
|
+
#
|
652
|
+
# Or define custom filters for a template:
|
653
|
+
#
|
654
|
+
# # create template with custom filters rather than global filters
|
655
|
+
# tmpl = Luigi::Template.new('hello %{name | reverse}', {
|
656
|
+
# reverse: proc { |s| s.reverse }
|
657
|
+
# })
|
658
|
+
#
|
659
|
+
# # run template and print result
|
660
|
+
# puts tmpl.run({
|
661
|
+
# name: 'Paul',
|
662
|
+
# })
|
663
|
+
#
|
664
|
+
# # prints "hello luaP"
|
665
|
+
#
|
666
|
+
# Your custom filters can accept arguments, too:
|
667
|
+
#
|
668
|
+
# # create custom global filter named 'foobarify'
|
669
|
+
# Luigi::FILTERS[:wrap] = proc { |s, args|
|
670
|
+
# case args.length
|
671
|
+
# when 2
|
672
|
+
# '(%s, %s, %s)' % [args[0], s, args[1]]
|
673
|
+
# when 1
|
674
|
+
# '(%s in %s)' % [s, args[0]]
|
675
|
+
# when 0
|
676
|
+
# s
|
677
|
+
# else
|
678
|
+
# raise 'invalid argument count'
|
679
|
+
# end
|
680
|
+
# }
|
681
|
+
#
|
682
|
+
# # create template that uses custom "wrap" filter
|
683
|
+
# tmpl = Luigi::Template.new('sandwich: %{meat | wrap slice heel}, taco: %{meat | wrap shell}')
|
684
|
+
#
|
685
|
+
# # run template and print result
|
686
|
+
# puts tmpl.run({
|
687
|
+
# meat: 'chicken'
|
688
|
+
# })
|
689
|
+
#
|
690
|
+
# # prints "sandwich: (slice, chicken, heel), taco: (chicken in shell)"
|
691
|
+
#
|
692
|
+
class Template
|
693
|
+
#
|
694
|
+
# Original template string.
|
695
|
+
#
|
696
|
+
attr_reader :str
|
697
|
+
|
698
|
+
#
|
699
|
+
# Create a new template, expand it with the given arguments and
|
700
|
+
# filters, and print the result.
|
701
|
+
#
|
702
|
+
# Parameters:
|
703
|
+
#
|
704
|
+
# * +str+: Template string.
|
705
|
+
# * +args+: Argument key/value map.
|
706
|
+
# * +filters+: Hash of filters. Defaults to Luigi::FILTERS if
|
707
|
+
# unspecified.
|
708
|
+
#
|
709
|
+
# Example:
|
710
|
+
#
|
711
|
+
# # create a template object, expand it, and print the result
|
712
|
+
# puts Luigi::Template.run('hello %{name}', {
|
713
|
+
# name: 'Paul'
|
714
|
+
# })
|
715
|
+
#
|
716
|
+
# # prints "hello Paul"
|
717
|
+
#
|
718
|
+
def self.run(str, args = {}, filters = FILTERS)
|
719
|
+
Template.new(str, filters).run(args)
|
720
|
+
end
|
721
|
+
|
722
|
+
#
|
723
|
+
# Create a new Template from the given string.
|
724
|
+
#
|
725
|
+
def initialize(str, filters = FILTERS)
|
726
|
+
@str, @filters = str.freeze, filters
|
727
|
+
@actions = Parser.parse_template(str).freeze
|
728
|
+
end
|
729
|
+
|
730
|
+
#
|
731
|
+
# Expand template with the given arguments and return the result.
|
732
|
+
#
|
733
|
+
# Parameters:
|
734
|
+
#
|
735
|
+
# * +args+: Argument key/value map.
|
736
|
+
#
|
737
|
+
# Example:
|
738
|
+
#
|
739
|
+
# # create a template object
|
740
|
+
# tmpl = Luigi::Template.new('hello %{name}')
|
741
|
+
#
|
742
|
+
# # apply template, print result
|
743
|
+
# puts tmpl.run({ name: 'Paul'})
|
744
|
+
#
|
745
|
+
# # prints "hello Paul"
|
746
|
+
#
|
747
|
+
# This method is aliased as "%", so you can do this:
|
748
|
+
#
|
749
|
+
# # create template
|
750
|
+
# tmpl = Luigi::Template.new('hello %{name | uc}')
|
751
|
+
#
|
752
|
+
# # run template and print result
|
753
|
+
# puts tmpl % { name: 'Paul' }
|
754
|
+
#
|
755
|
+
# # prints "hello PAUL"
|
756
|
+
#
|
757
|
+
def run(args)
|
758
|
+
@actions.map { |a|
|
759
|
+
# pp a
|
760
|
+
|
761
|
+
case a[:type]
|
762
|
+
when :action
|
763
|
+
# check key and get value
|
764
|
+
val = if args.key?(a[:key])
|
765
|
+
args[a[:key]]
|
766
|
+
elsif args.key?(a[:key].to_s)
|
767
|
+
args[a[:key].to_s]
|
768
|
+
else
|
769
|
+
# invalid key
|
770
|
+
raise UnknownKeyError.new(a[:key])
|
771
|
+
end
|
772
|
+
|
773
|
+
# filter value
|
774
|
+
a[:filters].inject(val) do |r, f|
|
775
|
+
# check filter name
|
776
|
+
unless @filters.key?(f[:name])
|
777
|
+
raise UnknownFilterError.new(f[:name])
|
778
|
+
end
|
779
|
+
|
780
|
+
# call filter, return result
|
781
|
+
@filters[f[:name]].call(r, f[:args], args, self)
|
782
|
+
end
|
783
|
+
when :text
|
784
|
+
# literal text
|
785
|
+
a[:text]
|
786
|
+
else
|
787
|
+
# never reached
|
788
|
+
raise "unknown action type: #{a[:type]}"
|
789
|
+
end
|
790
|
+
}.join
|
791
|
+
end
|
792
|
+
|
793
|
+
alias :'%' :run
|
794
|
+
|
795
|
+
#
|
796
|
+
# Return the input template string.
|
797
|
+
#
|
798
|
+
# Example:
|
799
|
+
#
|
800
|
+
# # create a template object
|
801
|
+
# tmpl = Luigi::Template.new('hello %{name}')
|
802
|
+
#
|
803
|
+
# # create a template object
|
804
|
+
# puts tmpl.to_s
|
805
|
+
#
|
806
|
+
# # prints "hello %{name}"
|
807
|
+
#
|
808
|
+
def to_s
|
809
|
+
@str
|
810
|
+
end
|
811
|
+
end
|
812
|
+
|
813
|
+
#
|
814
|
+
# Minimal lazy-loading template cache.
|
815
|
+
#
|
816
|
+
# Group a set of templates together and only parse them on an
|
817
|
+
# as-needed basis.
|
818
|
+
#
|
819
|
+
class Cache
|
820
|
+
#
|
821
|
+
# Create a new template cache with the given templates.
|
822
|
+
#
|
823
|
+
# Parameters:
|
824
|
+
#
|
825
|
+
# * +strings+: Map of template names to template strings.
|
826
|
+
# * +filters+: Hash of filters. Defaults to Luigi::FILTERS if
|
827
|
+
# unspecified.
|
828
|
+
#
|
829
|
+
# Example:
|
830
|
+
#
|
831
|
+
# # create template cache
|
832
|
+
# cache = Luigi::Cache.new({
|
833
|
+
# hi: 'hi %{name}!'
|
834
|
+
# })
|
835
|
+
#
|
836
|
+
# # run template from cache
|
837
|
+
# puts cache.run(:hi, {
|
838
|
+
# name: 'Paul'
|
839
|
+
# })
|
840
|
+
#
|
841
|
+
# # prints "hi paul!"
|
842
|
+
#
|
843
|
+
def initialize(strings, filters = FILTERS)
|
844
|
+
# work with frozen copy of strings hash
|
845
|
+
strings = strings.freeze
|
846
|
+
|
847
|
+
@templates = Hash.new do |h, k|
|
848
|
+
# always deal with symbols
|
849
|
+
k = k.intern
|
850
|
+
|
851
|
+
# make sure template exists
|
852
|
+
raise UnknownTemplateError.new(k) unless strings.key?(k)
|
853
|
+
|
854
|
+
# create template
|
855
|
+
h[k] = Template.new(strings[k], filters)
|
856
|
+
end
|
857
|
+
end
|
858
|
+
|
859
|
+
#
|
860
|
+
# Get given template, or raise an UnknownTemplateError if the given
|
861
|
+
# template does not exist.
|
862
|
+
#
|
863
|
+
# Example:
|
864
|
+
#
|
865
|
+
# # create template cache
|
866
|
+
# cache = Luigi::Cache.new({
|
867
|
+
# hi: 'hi %{name}!'
|
868
|
+
# })
|
869
|
+
#
|
870
|
+
# # get template from cache
|
871
|
+
# tmpl = cache[:hi]
|
872
|
+
#
|
873
|
+
# # run template, print result
|
874
|
+
# puts tmpl.run(:hi, {
|
875
|
+
# name: 'Paul'
|
876
|
+
# })
|
877
|
+
#
|
878
|
+
# # prints "hi Paul"
|
879
|
+
#
|
880
|
+
def [](key)
|
881
|
+
@templates[key]
|
882
|
+
end
|
883
|
+
|
884
|
+
#
|
885
|
+
# Run specified template from cache with the given templates.
|
886
|
+
#
|
887
|
+
# Raises an UnknownTemplateError if the given template key does not
|
888
|
+
# exist.
|
889
|
+
#
|
890
|
+
# Parameters:
|
891
|
+
#
|
892
|
+
# * +key+: Template key.
|
893
|
+
# * +args+: Argument key/value map.
|
894
|
+
#
|
895
|
+
# Example:
|
896
|
+
#
|
897
|
+
# # create template cache
|
898
|
+
# cache = Luigi::Cache.new({
|
899
|
+
# hi: 'hi %{name}!'
|
900
|
+
# })
|
901
|
+
#
|
902
|
+
# # run template from cache
|
903
|
+
# puts cache.run(:hi, {
|
904
|
+
# name: 'Paul'
|
905
|
+
# })
|
906
|
+
#
|
907
|
+
# # prints "hi paul!"
|
908
|
+
#
|
909
|
+
def run(key, args)
|
910
|
+
# run template with args and return result
|
911
|
+
@templates[key].run(args)
|
912
|
+
end
|
913
|
+
end
|
914
|
+
end
|
data/test/test_cache.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'luigi-template'
|
3
|
+
|
4
|
+
class CacheTest < MiniTest::Test
|
5
|
+
def test_cache
|
6
|
+
cache = Luigi::Cache.new({
|
7
|
+
foo: 'foo%{bar}',
|
8
|
+
})
|
9
|
+
|
10
|
+
r = cache.run(:foo, bar: 'foo')
|
11
|
+
|
12
|
+
assert_equal 'foofoo', r
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_cache_with_custom_filters
|
16
|
+
cache = Luigi::Cache.new({
|
17
|
+
foo: 'foo%{bar | barify}',
|
18
|
+
}, {
|
19
|
+
barify: proc { |v|
|
20
|
+
"bar-#{v}-bar"
|
21
|
+
},
|
22
|
+
})
|
23
|
+
|
24
|
+
r = cache.run(:foo, bar: 'foo')
|
25
|
+
|
26
|
+
assert_equal 'foobar-foo-bar', r
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'luigi-template'
|
3
|
+
|
4
|
+
class FiltersTest < MiniTest::Test
|
5
|
+
def test_uc
|
6
|
+
r = Luigi::Template.run('foo%{bar|uc}', {
|
7
|
+
bar: 'bar',
|
8
|
+
})
|
9
|
+
|
10
|
+
assert_equal 'fooBAR', r
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_lc
|
14
|
+
r = Luigi::Template.run('foo%{bar|lc}', {
|
15
|
+
bar: 'BAR',
|
16
|
+
})
|
17
|
+
|
18
|
+
assert_equal 'foobar', r
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_h
|
22
|
+
r = Luigi::Template.run('%{bar|h}', {
|
23
|
+
bar: "<>&\"'\x0f",
|
24
|
+
})
|
25
|
+
|
26
|
+
assert_equal '<>&"'', r
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_u
|
30
|
+
r = Luigi::Template.run('%{bar|u}', {
|
31
|
+
bar: "asdf<>&\"' \x0f",
|
32
|
+
})
|
33
|
+
|
34
|
+
assert_equal 'asdf%3C%3E%26%22%27+%0F', r
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_json
|
38
|
+
want = '{"true":true,"false":false,"null":null,"number":5,"string":"foo","hash":{"foo":"bar"},"array":[0,1]}';
|
39
|
+
|
40
|
+
r = Luigi::Template.run('%{bar|json}', {
|
41
|
+
bar: {
|
42
|
+
true: true,
|
43
|
+
false: false,
|
44
|
+
null: nil,
|
45
|
+
number: 5,
|
46
|
+
string: 'foo',
|
47
|
+
hash: { foo: 'bar' },
|
48
|
+
array: [0, 1],
|
49
|
+
},
|
50
|
+
})
|
51
|
+
|
52
|
+
assert_equal want, r
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_trim
|
56
|
+
r = Luigi::Template.run('foo%{bar|trim}', {
|
57
|
+
bar: "\r\n\t\v foo \r\n\t\v",
|
58
|
+
})
|
59
|
+
|
60
|
+
assert_equal 'foofoo', r
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_base64
|
64
|
+
r = Luigi::Template.run('%{bar|base64}', {
|
65
|
+
bar: "foo",
|
66
|
+
})
|
67
|
+
|
68
|
+
assert_equal 'Zm9v', r
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_hash_md5
|
72
|
+
r = Luigi::Template.run('%{bar|hash md5}', {
|
73
|
+
bar: "foo",
|
74
|
+
})
|
75
|
+
|
76
|
+
assert_equal 'acbd18db4cc2f85cedef654fccc4a4d8', r
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_hash_sha1
|
80
|
+
r = Luigi::Template.run('%{bar|hash sha1}', {
|
81
|
+
bar: "foo",
|
82
|
+
})
|
83
|
+
|
84
|
+
assert_equal '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33', r
|
85
|
+
end
|
86
|
+
end
|
data/test/test_errors.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'luigi-template'
|
3
|
+
|
4
|
+
class ErrorsTest < MiniTest::Test
|
5
|
+
def test_unknown_key_error
|
6
|
+
assert_raises(Luigi::UnknownKeyError) do
|
7
|
+
Luigi::Template.run('foo%{unknown-key}', {
|
8
|
+
bar: 'foo',
|
9
|
+
})
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_unknown_filter_error
|
14
|
+
assert_raises(Luigi::UnknownFilterError) do
|
15
|
+
Luigi::Template.run('foo%{bar | unknown-filter}', {
|
16
|
+
bar: 'foo',
|
17
|
+
})
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_unknown_template_error
|
22
|
+
assert_raises(Luigi::UnknownTemplateError) do
|
23
|
+
cache = Luigi::Cache.new({
|
24
|
+
foo: 'foo%{bar}',
|
25
|
+
})
|
26
|
+
|
27
|
+
cache.run('unknown-template', {
|
28
|
+
bar: 'foo'
|
29
|
+
})
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'luigi-template'
|
3
|
+
|
4
|
+
class FiltersTest < MiniTest::Test
|
5
|
+
def test_filter
|
6
|
+
r = Luigi::Template.run('foo%{bar|h}', {
|
7
|
+
bar: '<',
|
8
|
+
})
|
9
|
+
|
10
|
+
assert_equal 'foo<', r
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_filter_chain
|
14
|
+
want = 'foofeab40e1fca77c7360ccca1481bb8ba5f919ce3a'
|
15
|
+
r = Luigi::Template.run('foo%{bar | uc | hash sha1}', {
|
16
|
+
bar: 'foo',
|
17
|
+
})
|
18
|
+
|
19
|
+
assert_equal want, r
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_custom_global_filter
|
23
|
+
Luigi::FILTERS[:barify] = proc { |v| 'BAR' }
|
24
|
+
|
25
|
+
r = Luigi::Template.run('foo%{bar | barify}', {
|
26
|
+
bar: 'foo',
|
27
|
+
})
|
28
|
+
|
29
|
+
assert_equal 'fooBAR', r
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_custom_template_filter
|
33
|
+
r = Luigi::Template.run('foo%{bar | barify}', {
|
34
|
+
bar: 'foo',
|
35
|
+
}, {
|
36
|
+
barify: proc { |v| 'BAR' }
|
37
|
+
})
|
38
|
+
|
39
|
+
assert_equal 'fooBAR', r
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_filter_args
|
43
|
+
want = 'foo0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
|
44
|
+
|
45
|
+
r = Luigi::Template.run('foo%{bar | hash sha1}', {
|
46
|
+
bar: 'foo'
|
47
|
+
})
|
48
|
+
|
49
|
+
assert_equal want, r
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'luigi-template'
|
3
|
+
|
4
|
+
class TemplateTest < MiniTest::Test
|
5
|
+
def test_new
|
6
|
+
t = Luigi::Template.new('foo%{bar}')
|
7
|
+
assert_instance_of Luigi::Template, t
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_run
|
11
|
+
t = Luigi::Template.new('foo%{bar}')
|
12
|
+
r = t.run(bar: 'foo')
|
13
|
+
|
14
|
+
assert_equal 'foofoo', r
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_run_with_string_key
|
18
|
+
t = Luigi::Template.new('foo%{bar}')
|
19
|
+
r = t.run('bar' => 'foo')
|
20
|
+
|
21
|
+
assert_equal 'foofoo', r
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_self_run
|
25
|
+
r = Luigi::Template.run('foo%{bar}', bar: 'foo')
|
26
|
+
assert_equal 'foofoo', r
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_multiple_keys
|
30
|
+
r = Luigi::Template.run('foo%{bar}%{baz}', {
|
31
|
+
bar: 'foo',
|
32
|
+
baz: 'bar',
|
33
|
+
})
|
34
|
+
|
35
|
+
assert_equal 'foofoobar', r
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_whitespace
|
39
|
+
r = Luigi::Template.run('%{ bar}%{ bar }%{ bar}', {
|
40
|
+
bar: 'foo',
|
41
|
+
})
|
42
|
+
|
43
|
+
assert_equal 'foofoofoo', r
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_newlines
|
47
|
+
r = Luigi::Template.run('%{
|
48
|
+
bar}%{
|
49
|
+
bar
|
50
|
+
|
51
|
+
}%{
|
52
|
+
bar}', {
|
53
|
+
bar: 'foo',
|
54
|
+
})
|
55
|
+
|
56
|
+
assert_equal 'foofoofoo', r
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_to_s
|
60
|
+
want = '%{val | h}'
|
61
|
+
t = Luigi::Template.new(want)
|
62
|
+
|
63
|
+
assert_equal want, t.to_s
|
64
|
+
end
|
65
|
+
end
|
metadata
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: luigi-template
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.5.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Paul Duncan
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-09-05 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Simple string templating library.
|
14
|
+
email: pabs@pablotron.org
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- README.mkd
|
20
|
+
- Rakefile
|
21
|
+
- lib/luigi-template.rb
|
22
|
+
- test/test_cache.rb
|
23
|
+
- test/test_default_filters.rb
|
24
|
+
- test/test_errors.rb
|
25
|
+
- test/test_filters.rb
|
26
|
+
- test/test_template.rb
|
27
|
+
homepage: https://github.com/pablotron/luigi-template
|
28
|
+
licenses:
|
29
|
+
- MIT
|
30
|
+
metadata: {}
|
31
|
+
post_install_message:
|
32
|
+
rdoc_options: []
|
33
|
+
require_paths:
|
34
|
+
- lib
|
35
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
requirements: []
|
46
|
+
rubyforge_project:
|
47
|
+
rubygems_version: 2.7.6
|
48
|
+
signing_key:
|
49
|
+
specification_version: 4
|
50
|
+
summary: Simple string templating library.
|
51
|
+
test_files: []
|