ltdtemplate 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.
@@ -0,0 +1,451 @@
1
+ # LtdTemplate - Ltd (limited) Template
2
+ #
3
+ # A template system with limitable resource usage.
4
+ #
5
+ # @author Brian Katzung <briank@kappacs.com>, Kappa Computer Solutions, LLC
6
+ # @copyright 2013 Brian Katzung and Kappa Computer Solutions, LLC
7
+ # @license MIT License
8
+
9
+ class LtdTemplate
10
+
11
+ TOKEN_MAP = {
12
+ ?. => :dot, # method separator
13
+ '..' => :dotdot, # begin named values
14
+ ?( => :lparen, # begin call parameters
15
+ ?, => :comma, # next call parameter
16
+ ?) => :rparen, # end call parameters
17
+ ?[ => :lbrack, # begin array subscripts
18
+ ?] => :rbrack, # end array subscripts
19
+ ?{ => :lbrace, # begin code block
20
+ ?} => :rbrace # end code block
21
+ }
22
+
23
+ # @!attribute [r] exceeded
24
+ # @return [Symbol, nil]
25
+ # The resource whose limit was being exceeded when an
26
+ # LtdTemplate::ResourceLimitExceeded exception was raised.
27
+ attr_reader :exceeded
28
+
29
+ # @!attribute [r] factory_singletons
30
+ # @return [Hash]
31
+ # A hash of factory singletons (e.g. nil, true, and false values)
32
+ # for this template.
33
+ attr_reader :factory_singletons
34
+
35
+ # @!attribute [r] limits
36
+ # @return [Hash]
37
+ # A hash of resource limits to enforce during parsing and rendering.
38
+ attr_reader :limits
39
+
40
+ # @!attribute [r] namespace
41
+ # @return [LtdTemplate::Value::Namespace, nil]
42
+ # The current namespace (at the bottom of the rendering namespace stack).
43
+ attr_reader :namespace
44
+
45
+ # @!attribute [r] options
46
+ # @return [Hash]
47
+ # Instance initialization options
48
+ attr_reader :options
49
+
50
+ # @!attribute [r] usage
51
+ # @return [Hash]
52
+ # A hash of resource usage. It is updated after calls to #parse and
53
+ # #render.
54
+ attr_reader :usage
55
+
56
+ # @!attribute [r] used
57
+ # @return [Hash]
58
+ # A hash of used resources for this template
59
+ attr_reader :used
60
+
61
+ # @@classes contains the default factory classes. These can be overridden
62
+ # globally using the #set_classes class method or per-template using the
63
+ # #set_classes instance method.
64
+ @@classes = {
65
+ #
66
+ # These represent storable values. Some may also occur as
67
+ # literals in code blocks.
68
+ #
69
+ :array => 'LtdTemplate::Value::Array',
70
+ :boolean => 'LtdTemplate::Value::Boolean',
71
+ :explicit_block => 'LtdTemplate::Value::Code_Block',
72
+ :namespace => 'LtdTemplate::Value::Namespace',
73
+ :nil => 'LtdTemplate::Value::Nil',
74
+ :number => 'LtdTemplate::Value::Number',
75
+ :string => 'LtdTemplate::Value::String',
76
+
77
+ #
78
+ # These only occur as part of code blocks.
79
+ #
80
+ :call => 'LtdTemplate::Code::Call',
81
+ :implied_block => 'LtdTemplate::Code::Code_Block',
82
+ :parameters => 'LtdTemplate::Code::Parameters',
83
+ :subscript => 'LtdTemplate::Code::Subscript',
84
+ :variable => 'LtdTemplate::Code::Variable',
85
+ }
86
+
87
+ # Change default factory classes globally
88
+ #
89
+ # @param classes [Hash] A hash of factory symbols and corresponding
90
+ # classes to be instantiated.
91
+ # @return [Hash] Returns the current class mapping.
92
+ def self.set_classes (classes)
93
+ @@classes.merge! classes
94
+ end
95
+
96
+ def initialize (options = {})
97
+ @classes = @@classes
98
+ @code = nil
99
+ @factory_singletons = {}
100
+ @limits = {}
101
+ @namespace = nil
102
+ @options = options
103
+ @usage = {}
104
+ @used = {}
105
+ end
106
+
107
+ # Parse a template from a string.
108
+ #
109
+ # @param template [String] A template string. Templates look
110
+ # like <tt>'literal<<template code>>literal...'</tt>.
111
+ # @return [LtdTemplate]
112
+ def parse (template)
113
+ @usage = {}
114
+ tokens = []
115
+ literal = true
116
+ trim_next = false
117
+
118
+ # Template "text<<code>>text<<code>>..." =>
119
+ # (text, code, text, code, ...)
120
+ template.split(/<<(?!<)((?:[^<>]|<[^<]|>[^>])*)>>/s).each do |part|
121
+ if part.length > 0
122
+ if literal
123
+ part.sub!(/^\s+/s, '') if trim_next
124
+ tokens.push [ :ext_string, part ]
125
+ else
126
+ if part[0] == '.'
127
+ tokens[-1][1].sub!(/\s+$/s, '') if
128
+ tokens[0] and tokens[-1][0] == :ext_string
129
+ part = part[1..-1] if part.length > 1
130
+ end
131
+ part = part[0..-2] if trim_next = (part[-1] == '.')
132
+ tokens += get_tokens part
133
+ end
134
+ else
135
+ trim_next = false
136
+ end
137
+ literal = !literal
138
+ end
139
+
140
+ @code = parse_template tokens
141
+ self
142
+ end
143
+
144
+ # Change default factory classes for this template.
145
+ #
146
+ # @param classes [Hash] A hash of factory symbols and corresponding
147
+ # classes to be instantiated.
148
+ # @return [LtdTemplate]
149
+ def set_classes (classes)
150
+ @classes.merge! classes
151
+ self
152
+ end
153
+
154
+ # Generate new code/value objects.
155
+ #
156
+ # @param type [Symbol] The symbol for the type of object to generate,
157
+ # e.g. :number, :string, :implicit_block, etc.
158
+ # @param args [Array] Type-specific initialization parameters.
159
+ # @return Returns the new code/value object.
160
+ def factory (type, *args)
161
+ use :factory
162
+ type = @classes[type]
163
+ file = type.downcase.gsub '::', '/'
164
+ require file
165
+ eval(type).instance(self, *args)
166
+ end
167
+
168
+ # Render the template.
169
+ #
170
+ # The options hash may include :parameters, which may be an array or
171
+ # hash. These values will form the parameter array "_" in the root
172
+ # namespace.
173
+ #
174
+ # @param options [Hash] Rendering options.
175
+ # @return [String] The result of rendering the template.
176
+ def render (options = {})
177
+ @exceeded = nil # No limit exceeded yet
178
+ @namespace = nil # Reset the namespace stack between runs
179
+ @usage = {} # Reset resource usage
180
+ @used = {} # No libraries used yet
181
+
182
+ #
183
+ # Accept template parameters from an array or hash.
184
+ #
185
+ parameters = factory :array
186
+ parameters.set_from_array options[:parameters] if
187
+ options[:parameters].is_a? Array
188
+ parameters.set_from_hash options[:parameters] if
189
+ options[:parameters].is_a? Hash
190
+
191
+ #
192
+ # Create the root namespace and evaluate the template code.
193
+ #
194
+ push_namespace 'render', parameters
195
+ @code ? @code.get_value.to_text : ''
196
+ end
197
+
198
+ # Push a new namespace onto the namespace stack.
199
+ #
200
+ # @param method [String] The (native) method string. This will be
201
+ # available as <tt>$.method</tt> within the template.
202
+ # @param parameters [Array<LtdTemplate::Code>] These are code blocks
203
+ # to set the namespace parameters (available as the "_" array in the
204
+ # template).
205
+ # @param opts [Hash] Options hash.
206
+ # @option opts [LtdTemplate::Value] :target The target of the method
207
+ # call. This will be available as <tt>$.target</tt> within the template.
208
+ def push_namespace (method, parameters = nil, opts = {})
209
+ use :namespaces
210
+ use :namespace_depth
211
+ @namespace = factory :namespace, method, parameters, @namespace
212
+ @namespace.target = opts[:target] if opts[:target]
213
+ end
214
+
215
+ # Pop the current namespace from the stack.
216
+ def pop_namespace
217
+ if @namespace.parent
218
+ @namespace = @namespace.parent
219
+ use :namespace_depth, -1
220
+ end
221
+ end
222
+
223
+ # Track incremental usage of a resource.
224
+ #
225
+ # @param resource [Symbol] The resource being used.
226
+ # @param amount [Integer] The additional amount of the resource being
227
+ # used (or released, if negative).
228
+ def use (resource, amount = 1)
229
+ @usage[resource] ||= 0
230
+ @usage[resource] += amount
231
+ check_limit resource
232
+ end
233
+
234
+ # Track peak usage of a resource.
235
+ #
236
+ # @param resource [Symbol] The resource being used.
237
+ # @param amount [Integer] The total amount of the resource currently
238
+ # being used.
239
+ def using (resource, amount)
240
+ @usage[resource] ||= 0
241
+ @usage[resource] = amount if amount > @usage[resource]
242
+ check_limit resource
243
+ end
244
+
245
+ # Throw an exception if a resource limit has been exceeded.
246
+ #
247
+ # @param resource [Symbol] The resource limit to be checked.
248
+ def check_limit (resource)
249
+ if @limits[resource] and @usage[resource] and
250
+ @usage[resource] > @limits[resource]
251
+ @exceeded = resource
252
+ raise LtdTemplate::ResourceLimitExceeded
253
+ end
254
+ end
255
+
256
+ # Convert a string into an array of parsing tokens.
257
+ #
258
+ # @param str [String] The string to split into parsing tokens.
259
+ # @return [Array<Array>]
260
+ def get_tokens (str)
261
+ tokens = []
262
+ str.split(%r{(
263
+ /\*.*?\*/ # /* comment */
264
+ |
265
+ '(?:\\.|[^\.,\[\](){}\s])+# 'string
266
+ |
267
+ "(?:\\"|[^"])*" # "string"
268
+ |
269
+ [@^][a-zA-Z0-9_]+ # root or parent identifier
270
+ |
271
+ [a-zA-Z_][a-zA-Z0-9_]*# alphanumeric identifier
272
+ |
273
+ -?\d+(?:\.\d+)? # integer or real numeric literal
274
+ |
275
+ \.\. # begin keyed values
276
+ |
277
+ [\.(,)\[\]{}] # methods, calls, elements, blocks
278
+ |
279
+ \s+
280
+ )}mx).grep(/\S/).each do |token|
281
+ if token =~ %r{^/\*.*\*/$}s
282
+ # Ignore comment
283
+ elsif token =~ /^'(.*)/s or token =~ /^"(.*)"$/s
284
+ # String literal
285
+ #tokens.push [ :string, $1.gsub(/\\(.)/, '\1') ]
286
+ tokens.push [ :string, parse_strlit($1) ]
287
+ elsif token =~ /^-?\d+(?:\.\d+)?/
288
+ # Numeric literal
289
+ tokens.push [ :number, token ]
290
+ elsif TOKEN_MAP[token]
291
+ # Delimiter
292
+ tokens.push [ TOKEN_MAP[token] ]
293
+ elsif token =~ /^[@^][a-z0-9_]|^[a-z_]|^\$$/i
294
+ # Variable or alphanumeric method name
295
+ tokens.push [ :name, token ]
296
+ else
297
+ # Punctuated method name
298
+ tokens.push [ :method, token ]
299
+ end
300
+ end
301
+
302
+ tokens
303
+ end
304
+
305
+ # This is the top-level token parser.
306
+ #
307
+ # @param tokens [Array<Array>] The tokens to be parsed.
308
+ # @return [LtdTemplate::Code] The implementation code.
309
+ def parse_template (tokens); parse_block tokens; end
310
+
311
+ # Parse a code block, stopping at any stop token.
312
+ #
313
+ # @param tokens [Array<Array>] The raw token stream.
314
+ # @param stops [Array<Symbol>] An optional list of stop tokens, such
315
+ # as :comma (comma) or :rparen (right parenthesis).
316
+ # @return [LtdTemplate::Code]
317
+ def parse_block (tokens, stops = [])
318
+ code = []
319
+ while tokens[0]
320
+ break if stops.include? tokens[0][0]
321
+
322
+ token = tokens.shift# Consume the current token
323
+ case token[0]
324
+ when :string, :ext_string # string literal
325
+ code.push factory(:string, token[1])
326
+ when :number # numeric literal
327
+ code.push factory(:number, token[1])
328
+ when :name # variable
329
+ code.push factory(:variable, token[1])
330
+ when :lbrack # variable element subscripts
331
+ subs = parse_subscripts tokens
332
+ code.push factory(:subscript, code.pop, subs) if code[0]
333
+ when :dot # method call w/ or w/out parameters
334
+ case tokens[0][0]
335
+ when :name, :method, :string
336
+ method = tokens.shift
337
+ if tokens[0] and tokens[0][0] == :lparen
338
+ tokens.shift # Consume (
339
+ params = parse_parameters tokens
340
+ else
341
+ params = factory :parameters
342
+ end
343
+ code.push factory(:call, code.pop, method[1], params) if
344
+ code[0]
345
+ end if tokens[0]
346
+ when :method # punctuated method call
347
+ # Insert the implied dot and re-parse
348
+ tokens.unshift [ :dot ], token
349
+ when :lparen # call
350
+ params = parse_parameters tokens
351
+ code.push factory(:call, code.pop, 'call', params) if code[0]
352
+ when :lbrace # explicit code block
353
+ code.push factory(:explicit_block,
354
+ parse_block(tokens, [ :rbrace ]))
355
+ tokens.shift if tokens[0] # Consume }
356
+ end
357
+ end
358
+
359
+ (code.size == 1) ? code[0] : factory(:implied_block, code)
360
+ end
361
+
362
+ # Parse subscripts after the opening left bracket
363
+ #
364
+ # @param tokens [Array<Array>]] The token stream.
365
+ # @return [Array<LtdTemplate::Code>]
366
+ def parse_subscripts (tokens)
367
+ subs = parse_list tokens, [ :rbrack ], [ :lbrack ]
368
+ tokens.shift # Consume ]
369
+ subs
370
+ end
371
+
372
+ # Parse a positional and/or named parameter list
373
+ #
374
+ # @param tokens [Array<Array>] The token stream.
375
+ # @return [LtdTemplate::Code::Parameters]
376
+ def parse_parameters (tokens)
377
+ positional = parse_list tokens, [ :dotdot, :rparen ]
378
+
379
+ if tokens[0] and tokens[0][0] == :dotdot
380
+ tokens.shift # Consume ..
381
+ named = parse_list tokens, [ :rparen ]
382
+ else
383
+ named = nil
384
+ end
385
+
386
+ tokens.shift # Consume )
387
+ factory :parameters, positional, named
388
+ end
389
+
390
+ # Common code for parsing various lists.
391
+ #
392
+ # @param tokens [Array<Array>] The remaining unparsed tokens.
393
+ # @param stops [Array<Symbol>] The list of tokens that stop parsing
394
+ # of the list.
395
+ # @param resume [Array<Symbol>] The list of tokens that will resume
396
+ # parsing if they occur after a stop token (e.g. subscript parsing
397
+ # stops at ']' but resumes if followed by '[').
398
+ # @return [Array<LtdTemplate::Code>]
399
+ def parse_list (tokens, stops, resume = [])
400
+ list = []
401
+ block_stops = stops + [ :comma ]
402
+ while tokens[0]
403
+ if !stops.include? tokens[0][0]
404
+ block = parse_block tokens, block_stops
405
+ list.push block if block
406
+ tokens.shift if tokens[0] and tokens[0][0] == :comma
407
+ elsif tokens[1] and resume.include? tokens[1][0]
408
+ tokens.shift 2 # Consume stop and resume tokens
409
+ else
410
+ break
411
+ end
412
+ end
413
+
414
+ list
415
+ end
416
+
417
+ # Parse escape sequences in string literals.
418
+ #
419
+ # These are the same as in Ruby double-quoted strings:
420
+ # \M-\C-x meta-control-x
421
+ # \M-x meta-x
422
+ # \C-x, \cx control-x
423
+ # \udddd Unicode four-digit, 16-bit hexadecimal code point dddd
424
+ # \xdd two-digit, 8-bit hexadecimal dd
425
+ # \ddd one-, two-, or three-digit, 8-bit octal ddd
426
+ # \c any other character c is just itself
427
+ #
428
+ # @param raw [String] The original (raw) string.
429
+ # @return [String] The string after escape processing.
430
+ def parse_strlit (raw)
431
+ raw.split(%r{(\\M-\\C-.|\\M-.|\\C-.|\\c.|
432
+ \\u[0-9a-fA-F]{4}|\\x[0-9a-fA-f]{2}|\\[0-7]{1,3}|\\.)}x).
433
+ map do |part|
434
+ case part
435
+ when /\\M-\\C-(.)/ then ($1.ord & 31 | 128).chr
436
+ when /\\M-(.)/ then ($1.ord | 128).chr
437
+ when /\\C-(.)/, /\\c(.)/ then ($1.ord & 31).chr
438
+ when /\\u(.*)/ then $1.to_i(16).chr(Encoding::UTF_16BE)
439
+ when /\\x(..)/ then $1.to_i(16).chr
440
+ when /\\([0-7]+)/ then $1.to_i(8).chr
441
+ when /\\(.)/ then "\a\b\e\f\n\r\s\t\v"["abefnrstv".
442
+ index($1) || 9] || $1
443
+ else part
444
+ end
445
+ end.join ''
446
+ end
447
+
448
+ end
449
+
450
+ # This exception is raised when a resource limit is exceeded.
451
+ class LtdTemplate::ResourceLimitExceeded < RuntimeError; end
@@ -0,0 +1,16 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "ltdtemplate"
3
+ s.version = "0.1.0"
4
+ s.date = "2013-07-12"
5
+ s.authors = ["Brian Katzung"]
6
+ s.email = ["briank@kappacs.com"]
7
+ s.homepage = "http://rubygems.org/gems/ltdtemplate"
8
+ s.summary = "A template system with limitable resource usage"
9
+ s.description = "A template system with limitable resource usage, e.g. for administrator-editable message localization"
10
+ s.license = "MIT"
11
+
12
+ s.files = Dir.glob("lib/**/*") +
13
+ %w{ltdtemplate.gemspec HISTORY.txt TEMPLATE_MANUAL.html}
14
+ s.test_files = Dir.glob("test/**/[0-9]*.rb")
15
+ s.require_path = 'lib'
16
+ end
data/test/00class.rb ADDED
@@ -0,0 +1,20 @@
1
+ require 'minitest/autorun'
2
+ require 'ltdtemplate'
3
+
4
+ class TestLtdTemplate_00 < MiniTest::Unit::TestCase
5
+
6
+ def test_cmethod_new
7
+ assert_respond_to LtdTemplate, :new
8
+ end
9
+
10
+ def test_cmethod_set_classes
11
+ assert_respond_to LtdTemplate, :set_classes
12
+ end
13
+
14
+ def test_new_1
15
+ assert LtdTemplate.new, "Failed to create new LtdTemplate"
16
+ end
17
+
18
+ end
19
+
20
+ # END
@@ -0,0 +1,30 @@
1
+ require 'minitest/autorun'
2
+ require 'ltdtemplate'
3
+
4
+ class TestLtdTemplate_01 < MiniTest::Unit::TestCase
5
+
6
+ def setup
7
+ @tpl = LtdTemplate.new
8
+ end
9
+
10
+ def test_imethods_user_api
11
+ [
12
+ :set_classes, :parse, :render
13
+ ].each { |method| assert_respond_to @tpl, method }
14
+ end
15
+
16
+ def test_imethods_library_api
17
+ [
18
+ :push_namespace, :pop_namespace, :use, :using, :get_tokens
19
+ ].each { |method| assert_respond_to @tpl, method }
20
+ end
21
+
22
+ def test_imethods_internal_api
23
+ [
24
+ :check_limit
25
+ ].each { |method| assert_respond_to @tpl, method }
26
+ end
27
+
28
+ end
29
+
30
+ # END
@@ -0,0 +1,61 @@
1
+ require 'minitest/autorun'
2
+ require 'ltdtemplate'
3
+
4
+ class TestLtdTemplate_02 < MiniTest::Unit::TestCase
5
+
6
+ def setup
7
+ @tpl = LtdTemplate.new
8
+ end
9
+
10
+ def test_template_literals1
11
+ @tpl.parse '<<>>'
12
+ assert_equal '', @tpl.render
13
+
14
+ @tpl.parse 'literal1'
15
+ assert_equal 'literal1', @tpl.render
16
+
17
+ @tpl.parse 'literal2<<>>'
18
+ assert_equal 'literal2', @tpl.render
19
+
20
+ @tpl.parse '<<>>literal3'
21
+ assert_equal 'literal3', @tpl.render
22
+
23
+ @tpl.parse 'literal<<>>4'
24
+ assert_equal 'literal4', @tpl.render
25
+ end
26
+
27
+ def test_template_literals2
28
+ @tpl.parse '<<<<>>'
29
+ assert_equal '<<', @tpl.render
30
+
31
+ @tpl.parse '<<>>>>'
32
+ assert_equal '>>', @tpl.render
33
+
34
+ @tpl.parse '<<<<>>>>'
35
+ assert_equal '<<>>', @tpl.render
36
+
37
+ @tpl.parse 'a<<b<<c>>d>>e'
38
+ assert_equal 'a<<bd>>e', @tpl.render
39
+
40
+ end
41
+
42
+ def test_template_trim
43
+ @tpl.parse " \n <<'x>> \n "
44
+ assert_equal " \n x \n ", @tpl.render
45
+
46
+ @tpl.parse " \n <<.'x>> \n "
47
+ assert_equal "x \n ", @tpl.render
48
+
49
+ @tpl.parse " \n <<'x.>> \n "
50
+ assert_equal " \n x", @tpl.render
51
+
52
+ @tpl.parse " \n <<.'x.>> \n "
53
+ assert_equal "x", @tpl.render
54
+
55
+ @tpl.parse " \n <<.>> \n "
56
+ assert_equal "", @tpl.render
57
+ end
58
+
59
+ end
60
+
61
+ # END
@@ -0,0 +1,48 @@
1
+ require 'minitest/autorun'
2
+ require 'ltdtemplate'
3
+
4
+ class TestLtdTemplate_03 < MiniTest::Unit::TestCase
5
+
6
+ def setup
7
+ @tpl1 = LtdTemplate.new
8
+ @tpl2 = LtdTemplate.new
9
+ end
10
+
11
+ def test_nil
12
+ nil1a = @tpl1.factory :nil
13
+ nil1b = @tpl1.factory :nil
14
+ assert_equal nil1a.object_id, nil1b.object_id, "Shared nil in tpl1"
15
+ nil2a = @tpl2.factory :nil
16
+ nil2b = @tpl2.factory :nil
17
+ assert_equal nil2a.object_id, nil2b.object_id, "Shared nil in tpl2"
18
+ refute_equal nil1a.object_id, nil2a.object_id,
19
+ "Different nil in tpl1, tpl2"
20
+ end
21
+
22
+ def test_boolean
23
+ true1a = @tpl1.factory :boolean, true
24
+ true1b = @tpl1.factory :boolean, true
25
+ assert_equal true1a.object_id, true1b.object_id,
26
+ "Shared true in tpl1"
27
+ true2a = @tpl2.factory :boolean, true
28
+ true2b = @tpl2.factory :boolean, true
29
+ assert_equal true2a.object_id, true2b.object_id,
30
+ "Shared true in tpl2"
31
+ refute_equal true1a.object_id, true2a.object_id,
32
+ "Different true in tpl1, tpl2"
33
+
34
+ false1a = @tpl1.factory :boolean, false
35
+ false1b = @tpl1.factory :boolean, false
36
+ assert_equal false1a.object_id, false1b.object_id,
37
+ "Shared false in tpl1"
38
+ false2a = @tpl2.factory :boolean, false
39
+ false2b = @tpl2.factory :boolean, false
40
+ assert_equal false2a.object_id, false2b.object_id
41
+ "Shared false in tpl2"
42
+ refute_equal false1a.object_id, false2a.object_id
43
+ "Different false in tpl1, tpl2"
44
+ end
45
+
46
+ end
47
+
48
+ # END
data/test/04number.rb ADDED
@@ -0,0 +1,36 @@
1
+ require 'minitest/autorun'
2
+ require 'ltdtemplate'
3
+
4
+ class TestLtdTemplate_04 < MiniTest::Unit::TestCase
5
+
6
+ def setup
7
+ @tpl = LtdTemplate.new
8
+ end
9
+
10
+ def test_imethods
11
+ num = @tpl.factory :number
12
+ [
13
+ :get_value, :to_boolean, :to_native, :to_text
14
+ ].each { |method| assert_respond_to num, method }
15
+ end
16
+
17
+ def test_basic
18
+ @tpl.parse '<<1>>'
19
+ assert_equal("1", @tpl.render, "literal")
20
+ @tpl.parse '<<1.type>>'
21
+ assert_equal("number", @tpl.render, "type")
22
+ @tpl.parse '<<1+(2,3)>>'
23
+ assert_equal("6", @tpl.render, "1+2+3")
24
+ @tpl.parse '<<1->>'
25
+ assert_equal("-1", @tpl.render, "neg 1")
26
+ @tpl.parse '<<1-(2,3)>>'
27
+ assert_equal("-4", @tpl.render, "1-2-3")
28
+ @tpl.parse '<<2*(3,4)>>'
29
+ assert_equal("24", @tpl.render, "2*3*4")
30
+ @tpl.parse '<<24/(2,3)>>'
31
+ assert_equal("4", @tpl.render, "24/2/3")
32
+ end
33
+
34
+ end
35
+
36
+ # END
data/test/05string.rb ADDED
@@ -0,0 +1,30 @@
1
+ require 'minitest/autorun'
2
+ require 'ltdtemplate'
3
+
4
+ class TestLtdTemplate_05 < MiniTest::Unit::TestCase
5
+
6
+ def setup
7
+ @tpl = LtdTemplate.new
8
+ end
9
+
10
+ def test_imethods
11
+ num = @tpl.factory :string, ''
12
+ [
13
+ :get_value, :to_boolean, :to_native, :to_text
14
+ ].each { |method| assert_respond_to num, method }
15
+ end
16
+
17
+ def test_basic
18
+ @tpl.parse '<<"str">>'
19
+ assert_equal("str", @tpl.render, "literal")
20
+ @tpl.parse '<<"str".type>>'
21
+ assert_equal("string", @tpl.render, "type")
22
+ @tpl.parse '<<"str"+("ing","value")>>'
23
+ assert_equal("stringvalue", @tpl.render, "string +")
24
+ @tpl.parse '<<"str"*(3)>>'
25
+ assert_equal("strstrstr", @tpl.render, "string *")
26
+ end
27
+
28
+ end
29
+
30
+ # END