tzispa_rig 0.2.3

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c00ae9acd7890ced20f0f74e4c54143d458f0907
4
+ data.tar.gz: 905759eff704cabd933220611ddeaa6b46cc29f4
5
+ SHA512:
6
+ metadata.gz: defca0f436d1495db948ba55eb11c0ba10fae686ac0f222daaf2abe581b384df0a3c1d40da525c4d0acbf930a306fb4113d2dc33872524b4a98e30df33a0c421
7
+ data.tar.gz: abee73f6183769d5591560d1e2a5dbf1b0d877b4184771237505cfaa2b9ff14ffe5a7d368d4b2259d480d7bd97c1a1f89bcdaebd389c16a58678c6488011ba5a
data/CHANGELOG.md ADDED
@@ -0,0 +1,24 @@
1
+ Tzispa Rig
2
+
3
+ Rig templates implementation
4
+
5
+ ## v0.2.3
6
+ - Fix in the api call regex syntax
7
+ - Added prefix in api calls
8
+
9
+ ## v0.2.2
10
+ - Bug fix in iblk render
11
+ - Remake of the Binder class defining two specialized classes: TemplateBinder and LoopBinder
12
+ - Bug fix and improvements in the statements parser
13
+ - Bug fix in the loop_binder method not allowing 2 loops with equal ids at the same level
14
+
15
+ ## v0.2.1
16
+ - Regexp optimizations
17
+ - Some classes has been renamed for better code readability
18
+
19
+ ## v0.2.0
20
+ - Implemented new parser to break away parsing and rendering
21
+ - Implemented template caching class 'Engine' with on demand parsing: a block template is parsed only when if it's new or if it has been modified
22
+
23
+ ## v0.1.0
24
+ - Initial release: code moved from tzispa main gem
data/README.md ADDED
@@ -0,0 +1,3 @@
1
+ Txispa Rig
2
+
3
+ General purpose template engine
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+ require 'tzispa/rig'
5
+
6
+ module Tzispa
7
+ module Rig
8
+
9
+
10
+ class Binder
11
+ extend Forwardable
12
+
13
+ attr_reader :context, :dataStruct, :parser
14
+ def_delegators :@parser, :attribute_tags
15
+ def_delegators :@context, :app, :request, :response
16
+
17
+
18
+ def initialize(parser, context)
19
+ @parser = parser
20
+ @context = context
21
+ @dataStruct = attribute_tags.count > 0 ? Struct.new(*attribute_tags) : Struct.new(nil)
22
+ end
23
+
24
+ def loop_binder(loop_id)
25
+ loop_parser = @parser.loop_parser loop_id
26
+ raise ArgumentError.new("#{self.class}:: there isn't any loop tagged '#{loop_id}'") unless loop_parser && loop_parser.count > 0
27
+ raise ArgumentError.new("#{self.class}:: there are #{loop_parser.count} loops tagged '#{loop_id}' at the same level: only one allowed") unless loop_parser.count == 1
28
+ LoopBinder.new loop_parser[0], @context
29
+ end
30
+
31
+ end
32
+
33
+
34
+ class TemplateBinder < Binder
35
+ extend Forwardable
36
+
37
+ attr_reader :template
38
+ def_delegators :@template, :params
39
+
40
+
41
+ def initialize(template, context)
42
+ super(template.parser, context)
43
+ @template = template
44
+ end
45
+
46
+ def data(**params)
47
+ (@data ||= @dataStruct.new).tap { |d|
48
+ params.each{ |k,v|
49
+ d[k] = v
50
+ }
51
+ }
52
+ end
53
+
54
+ def self.for(template, context)
55
+ if template.is_block?
56
+ binder_class = template.binder_class
57
+ binder_class.new( template, context ) if binder_class
58
+ else
59
+ self.new(template, context)
60
+ end
61
+ end
62
+
63
+ end
64
+
65
+
66
+ class LoopBinder < Binder
67
+
68
+ attr_reader :data
69
+
70
+ def bind!(&generator)
71
+ @source_object = eval "self", generator.binding
72
+ @data = instance_eval(&generator).to_enum(:each)
73
+ self
74
+ end
75
+
76
+ def method_missing(method, *args, &generator)
77
+ @source_object.send method, *args, &generator
78
+ end
79
+
80
+ def loop_item(**params)
81
+ (LoopItem.new self).tap { |item|
82
+ params.each{ |k,v|
83
+ item.data[k] = v
84
+ }
85
+ }
86
+ end
87
+
88
+ end
89
+
90
+
91
+ class LoopItem
92
+
93
+ attr_reader :context, :data
94
+
95
+ def initialize(binder)
96
+ @context = binder.context
97
+ @data = binder.dataStruct.new
98
+ end
99
+
100
+ end
101
+
102
+
103
+ end
104
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tzispa
4
+ module Rig
5
+ class Parameters
6
+
7
+
8
+ attr_reader :inner
9
+ attr_reader :outer
10
+ attr_reader :data
11
+
12
+
13
+ def initialize(params=nil, iouter=',', iinner='=')
14
+ @data = Hash.new
15
+ @outer = iouter
16
+ @inner = iinner
17
+ setData(params) if params
18
+ end
19
+
20
+ def set(key,value)
21
+ @data[key.to_sym] = value
22
+ end
23
+
24
+ def get(key)
25
+ @data[key.to_sym]
26
+ end
27
+
28
+ def [](key)
29
+ @data[key.to_sym]
30
+ end
31
+
32
+ def has?(key)
33
+ @data.key?(key.to_sym)
34
+ end
35
+
36
+ def to_s
37
+ @data.map { |k,v| "#{k}#{inner}#{v}" }.join(outer)
38
+ end
39
+
40
+ def merge(params)
41
+ setData(params)
42
+ end
43
+
44
+ private
45
+
46
+ def setData(params)
47
+ params.split(outer).each do |param|
48
+ key,value = param.split(inner)
49
+ @data[key.to_sym] = value
50
+ end
51
+ end
52
+
53
+
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,165 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tzispa/rig/engine'
4
+ require 'tzispa/rig/formatter'
5
+ require 'tzispa/helpers/security'
6
+
7
+ module Tzispa
8
+ module Rig
9
+ class Parser
10
+
11
+ include Tzispa::Helpers::Security
12
+
13
+ EMPTY_STRING = ''.freeze
14
+
15
+ attr_reader :text
16
+
17
+ RIG_SYNTAX = {
18
+ :re_flags => /<flags:(\[(\w+=[^,\]]+(,\w+=[^,\]]+)*?)\])\/>/.freeze,
19
+ :blk => /<(blk):(\w+(?:\.\w+)?)(?:\[(\w+=[^,\]]+(?:,\w+=[^,\]]+)*)\])?\/>/.freeze,
20
+ :iblk => /<(iblk):(\w+):(\w+(?:\.\w+)?)(?:\[(\w+=[^,\]]+(?:,\w+=[^,\]]+)*)\])?:(\w+(?:\.\w+)?)(?:\[(\w+=[^,\]]+(?:,\w+=[^,\]]+)*)\])?\/>/.freeze,
21
+ :static => /<(static):(\w+(?:\.\w+)?)(?:\[(\w+=[^,\]]+(?:,\w+=[^,\]]+)*)\])?\/>/.freeze,
22
+ :re_var => /<var(\[%[A-Z]?[0-9]*[a-z]\])?:(\w+)\/>/.freeze,
23
+ :re_metavar => /\{%([^%]+?)%\}/.freeze,
24
+ :re_loop => /<loop:(\w+)>(.*?)<\/loop:\1>/m.freeze,
25
+ :re_ife => /<ife:(\w+)>(.*?)(<else:\1\/>(.*?))?<\/ife:\1>/m.freeze,
26
+ :re_purl => /<purl:(\w+)(\[(\w+=[^,\]]+(,\w+=[^,\]]+)*?)\])?\/>/.freeze,
27
+ :re_api => /<api:(\w+(?:\.\w+)?):(\w+)(?::([^:]+))?(?::([^\/]+))?\/>/.freeze
28
+ }
29
+
30
+ def initialize(engine, binder=nil, text=nil)
31
+ @engine = engine
32
+ @binder = binder || engine.binder
33
+ @context = engine.context
34
+ @text = text
35
+ end
36
+
37
+ def parse!
38
+ @text ||= @engine.content
39
+ @binder.bind! if @binder && @binder.respond_to?( :bind! )
40
+ if @engine.type == :block
41
+ parseStatement
42
+ parseExpression
43
+ end
44
+ parseBlock
45
+ self
46
+ end
47
+
48
+
49
+ private
50
+
51
+ def parseExpression
52
+ parseMetavar
53
+ parseVar
54
+ parsePurl
55
+ parseAPI
56
+ end
57
+
58
+ def parseStatement
59
+ parseLoop
60
+ parseIfe
61
+ end
62
+
63
+ def parseFlags
64
+ @text.gsub!(RIG_SYNTAX[:re_flags]) { |match|
65
+ flags = Regexp.last_match(1)
66
+ @engine.flags = flags
67
+ EMPTY_STRING
68
+ }
69
+ end
70
+
71
+ def parseLoop
72
+ @text.gsub!(RIG_SYNTAX[:re_loop]) { |match|
73
+ loopid = Regexp.last_match(1)
74
+ loopbody = Regexp.last_match(2)
75
+ lenrig = @binder.respond_to?("#{loopid}") ? @binder.send("#{loopid}") : []
76
+ lenrig.respond_to?(:map) ? lenrig.map { |item| Parser.new(@engine, item, loopbody.dup).parse!.text }.join : String.new.freeze
77
+ }
78
+ end
79
+
80
+ def parseIfe
81
+ @text.gsub!(RIG_SYNTAX[:re_ife]) { |match|
82
+ ifetest = Regexp.last_match(1)
83
+ test = @binder.respond_to?("#{ifetest}") ? @binder.send("#{ifetest}") : false
84
+ ifebody = Regexp.last_match(2)
85
+ elsebody = Regexp.last_match(4)
86
+ Parser.new(@engine, @binder, test ? "#{ifebody}" : "#{elsebody}").parse!.text
87
+ }
88
+ end
89
+
90
+ def parseMetavar
91
+ @text.gsub!(RIG_SYNTAX[:re_metavar]) { |match|
92
+ varid = Regexp.last_match(1)
93
+ if @context.respond_to?(varid)
94
+ @context.send(varid)
95
+ elsif !@context.env.nil? and !@context.env[varid].nil?
96
+ @context.env[varid]
97
+ elsif @binder.respond_to?(varid)
98
+ @binder.send(varid)
99
+ else
100
+ Formatter.unknown(varid)
101
+ end
102
+ }
103
+ end
104
+
105
+ def parseVar
106
+ @text.gsub!(RIG_SYNTAX[:re_var]) { |match|
107
+ fmt = Regexp.last_match(1)
108
+ varid = Regexp.last_match(2)
109
+ value = @binder.respond_to?(varid) ? @binder.send(varid) : Formatter.unknown(varid)
110
+ Formatter.rigvar(value, fmt)
111
+ }
112
+ end
113
+
114
+ def parsePurl
115
+ @text.gsub!(RIG_SYNTAX[:re_purl]) { |match|
116
+ urlid = Regexp.last_match[1]
117
+ urlparams = Parameters.new(Regexp.last_match[3])
118
+ @engine.app.router_path urlid.to_sym, urlparams.data
119
+ }
120
+ end
121
+
122
+ def parseAPI
123
+ @text.gsub!(RIG_SYNTAX[:re_api]) { |match|
124
+ handler, verb, predicate, sufix = Regexp.last_match[1], Regexp.last_match[2], Regexp.last_match[3], Regexp.last_match[4]
125
+ sign = Parser.sign_array [handler, verb, predicate], @engine.app.config.salt
126
+ @engine.app.router_path :api, {handler: handler, verb: verb, predicate: predicate, sign: sign, sufix: sufix}
127
+ }
128
+ end
129
+
130
+ def parseBlock
131
+ reBlocks = Regexp.new(RIG_SYNTAX[:re_blk].to_s + '|' + RIG_SYNTAX[:re_iblk].to_s + '|' + RIG_SYNTAX[:re_static].to_s)
132
+
133
+ @text.gsub!(reBlocks) {
134
+ strtype = (Regexp.last_match[2] || '') << (Regexp.last_match[7] || '') << (Regexp.last_match[16] || '')
135
+ case strtype
136
+ when 'blk'
137
+ rigtype = :block
138
+ rigname = Regexp.last_match[3]
139
+ rigparams = Regexp.last_match[5]
140
+ when 'iblk'
141
+ rigtype = :block
142
+ rigtest = Regexp.last_match[8]
143
+ if (@binder.respond_to?("#{rigtest}?") ? @binder.send("#{rigtest}?") : false)
144
+ rigname = Regexp.last_match[9]
145
+ rigparams = Regexp.last_match[11]
146
+ else
147
+ rigname = Regexp.last_match[12]
148
+ rigparams = Regexp.last_match[14]
149
+ end
150
+ when 'static'
151
+ rigtype = :static
152
+ rigname = Regexp.last_match[17]
153
+ rigparams = Regexp.last_match[19]
154
+ else
155
+ raise ArgumentError.new("Unknown Rig type: #{strtype}")
156
+ end
157
+ engine = Engine.new(name: rigname, type: rigtype, parent: @engine, params: rigparams)
158
+ engine.render!
159
+ engine.text
160
+ }
161
+ end
162
+
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,419 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+ require 'tzispa/domain'
5
+ require 'tzispa/utils/string'
6
+
7
+
8
+ module Tzispa
9
+ module Rig
10
+
11
+ class ParsedEntity
12
+ extend Forwardable
13
+
14
+ STRING_EMPTY = ''.freeze
15
+ RE_ANCHOR = /(@@\h+@@)/
16
+
17
+ attr_reader :type, :parser
18
+ def_delegators :@parser, :template
19
+
20
+ def initialize(parser, type)
21
+ @parser = parser
22
+ @type = type
23
+ end
24
+
25
+ def self.instance(parser, type, match)
26
+ case type
27
+ when :meta
28
+ ParsedMeta.new parser, type, match[1]
29
+ when :var
30
+ ParsedVar.new parser, type, match[1], match[2]
31
+ when :purl
32
+ ParsedUrl.new parser, type, match[1], match[3]
33
+ when :api
34
+ ParsedApi.new parser, type, match[1], match[2], match[3], match[4]
35
+ when :loop
36
+ ParsedLoop.new parser, type, match[3], match[4]
37
+ when :ife
38
+ ParsedIfe.new parser, type, match[7], match[8], match[10]
39
+ when :blk
40
+ ParsedBlock.new parser, type, match[3], match[4]
41
+ when :iblk
42
+ ParsedIBlock.new parser, type, match[7], match[8], match[9], match[10], match[11]
43
+ when :static
44
+ ParsedStatic.new parser, type, match[14], match[15]
45
+ end
46
+ end
47
+
48
+ def anchor
49
+ #[[object_id].pack("h*")].pack("m0")
50
+ @anchor ||= "@@#{"%x" % object_id}@@".freeze
51
+ end
52
+
53
+ end
54
+
55
+ class ParsedMeta < ParsedEntity
56
+
57
+ attr_reader :id
58
+
59
+ def initialize(parser, type, id)
60
+ super(parser, type)
61
+ @id = id.to_sym
62
+ end
63
+
64
+ def render(binder)
65
+ if binder.data.respond_to? @id
66
+ binder.data.send(@id).to_s
67
+ else
68
+ unknown
69
+ end
70
+ end
71
+
72
+ private
73
+
74
+ def unknown
75
+ @unknown ||= "#{@id}:unknown!!".freeze
76
+ end
77
+
78
+ end
79
+
80
+
81
+ class ParsedVar < ParsedEntity
82
+
83
+ attr_reader :id
84
+
85
+ def initialize(parser, type, format, id)
86
+ super(parser, type)
87
+ @format = format
88
+ @id = id.to_sym
89
+ end
90
+
91
+ def render(binder)
92
+ binder.data.respond_to?(@id) ? binder.data.send(@id).to_s : unknown
93
+ end
94
+
95
+ private
96
+
97
+ def unknown
98
+ @unknown ||= "#{@id}:unknown!!".freeze
99
+ end
100
+
101
+ end
102
+
103
+
104
+ class ParsedUrl < ParsedEntity
105
+
106
+ def initialize(parser, type, path_id, params)
107
+ super(parser, type)
108
+ @path_id = path_id.to_sym
109
+ @params = params
110
+ end
111
+
112
+ def render(binder)
113
+ b_params = @params.dup.gsub(RE_ANCHOR) { |match|
114
+ parser.the_parsed.select { |p| p.anchor == match}.first.render(binder)
115
+ } if @params
116
+ binder.context.path @path_id, Parameters.new(b_params).data
117
+ end
118
+
119
+ end
120
+
121
+
122
+ class ParsedApi < ParsedEntity
123
+
124
+ attr_reader :handler, :verb, :predicate
125
+
126
+ def initialize(parser, type, handler, verb, predicate, sufix)
127
+ super(parser, type)
128
+ @handler = handler
129
+ @verb = verb
130
+ @predicate = predicate
131
+ @sufix = sufix
132
+ end
133
+
134
+ def render(binder)
135
+ b_handler = bind_value @handler.dup, binder
136
+ b_verb = bind_value @verb.dup, binder
137
+ b_predicate = bind_value( @predicate.dup, binder ) if @predicate
138
+ b_sufix = bind_value( @sufix.dup, binder ) if @sufix
139
+ binder.context.api b_handler, b_verb, b_predicate, b_sufix
140
+ end
141
+
142
+ private
143
+
144
+ def bind_value(value, binder)
145
+ value.gsub(RE_ANCHOR) { |match|
146
+ parser.the_parsed.select { |p| p.anchor == match}.first.render(binder)
147
+ }
148
+ end
149
+
150
+ end
151
+
152
+
153
+ class ParsedLoop < ParsedEntity
154
+ extend Forwardable
155
+
156
+ attr_reader :id
157
+ def_delegators :@loop_parser, :attribute_tags, :loop_parser
158
+
159
+ def initialize(parser, type, id, body)
160
+ super(parser, type)
161
+ @id = id.to_sym
162
+ @body = body
163
+ end
164
+
165
+ def parse!
166
+ @loop_parser = ParserNext.new( template, @body ).parse!
167
+ self
168
+ end
169
+
170
+ def render(binder)
171
+ String.new.tap { |text|
172
+ looper = binder.data.send(@id) if binder.data.respond_to?(@id)
173
+ looper.data.each { |loop_item|
174
+ text << @loop_parser.render(loop_item) if loop_item
175
+ } if looper
176
+ }
177
+ end
178
+
179
+ end
180
+
181
+
182
+ class ParsedIfe < ParsedEntity
183
+
184
+ attr_reader :test
185
+
186
+ def initialize(parser, type, test, then_body, else_body)
187
+ super(parser, type)
188
+ @test = test.to_sym
189
+ @then_body = then_body
190
+ @else_body = else_body
191
+ end
192
+
193
+ def parse!
194
+ @then_parser = ParserNext.new( template, @then_body ).parse!
195
+ @else_parser = ParserNext.new( template, @else_body ).parse! if @else_body
196
+ self
197
+ end
198
+
199
+ def attribute_tags
200
+ @attribute_tags ||= [@test].concat(@then_parser.attribute_tags).concat((@else_parser && @else_parser.attribute_tags) || Array.new).compact.uniq.freeze
201
+ end
202
+
203
+ def loop_parser(id)
204
+ @then_parser.loop_parser(id).concat((@else_parser && @else_parser.loop_parser(id)) || Array.new).compact.freeze
205
+ end
206
+
207
+ def render(binder)
208
+ test_eval = binder.data && binder.data.respond_to?(@test) && binder.data.send(@test)
209
+ ifeparser = test_eval ? @then_parser : @else_parser
210
+ ifeparser ? ifeparser.render(binder) : STRING_EMPTY
211
+ end
212
+
213
+ end
214
+
215
+ class ParsedBlock < ParsedEntity
216
+
217
+ attr_reader :params
218
+
219
+ def initialize(parser, type, id, params)
220
+ super(parser, type)
221
+ @id = id
222
+ @params = params
223
+ end
224
+
225
+ def parse!
226
+ @parsed_block = template.engine.block name: @id, parent: template
227
+ self
228
+ end
229
+
230
+ def render(binder)
231
+ blk = @parsed_block.dup
232
+ if @params
233
+ b_params = @params.dup.gsub(RE_ANCHOR) { |match|
234
+ parser.the_parsed.select { |p| p.anchor == match}.first.render(binder)
235
+ }
236
+ blk.params = b_params
237
+ end
238
+ blk.render binder.context
239
+ end
240
+
241
+ end
242
+
243
+ class ParsedIBlock < ParsedEntity
244
+
245
+ attr_reader :id
246
+
247
+ def initialize(parser, type, test, id_then, params_then, id_else, params_else)
248
+ super(parser, type)
249
+ @id = test
250
+ @id_then = id_then
251
+ @params_then = params_then
252
+ @id_else = id_else
253
+ @params_else = params_else
254
+ end
255
+
256
+ def parse!
257
+ @block_then = template.engine.block name: @id_then, parent: template
258
+ @block_else = template.engine.block name: @id_else, parent: template
259
+ self
260
+ end
261
+
262
+ def render(binder)
263
+ if binder.data.respond_to?(@id) && binder.data.send(@id)
264
+ blk = @block_then.dup
265
+ if @params_then
266
+ b_params = @params_then.dup.gsub(RE_ANCHOR) { |match|
267
+ parser.the_parsed.select { |p| p.anchor == match}.first.render(binder)
268
+ }
269
+ blk.params = b_params
270
+ end
271
+ else
272
+ blk = @block_else.dup
273
+ if @params_else
274
+ b_params = @params_else.dup.gsub(RE_ANCHOR) { |match|
275
+ parser.the_parsed.select { |p| p.anchor == match}.first.render(binder)
276
+ }
277
+ blk.params = b_params
278
+ end
279
+ end
280
+ blk.render binder.context
281
+ end
282
+
283
+ end
284
+
285
+
286
+ class ParsedStatic < ParsedEntity
287
+
288
+ def initialize(parser, type, id, params)
289
+ super(parser, type)
290
+ @id = id
291
+ @params = params
292
+ end
293
+
294
+ def parse!
295
+ @parsed_static = template.engine.static name: @id, parent: template
296
+ self
297
+ end
298
+
299
+ def render(binder)
300
+ blk = @parsed_static.dup
301
+ if @params
302
+ b_params = @params.dup.gsub(RE_ANCHOR) { |match|
303
+ parser.the_parsed.select { |p| p.anchor == match}.first.render(binder)
304
+ }
305
+ blk.params = b_params
306
+ end
307
+ blk.render binder.context
308
+ end
309
+
310
+ end
311
+
312
+
313
+ class ParserNext
314
+
315
+ EMPTY_STRING = ''
316
+
317
+ RIG_EMPTY = {
318
+ :flags => /<flags:(\[(\w+=[^,\]]+(,\w+=[^,\]]+)*?)\])\/>/
319
+ }.freeze
320
+
321
+ RIG_EXPRESSIONS = {
322
+ :meta => /\{%([^%]+?)%\}/.freeze,
323
+ :var => /<var(\[%[A-Z]?[0-9]*[a-z]\])?:(\w+)\/>/,
324
+ :purl => /<purl:(\w+(?:\.\w+)?)(\[(\w+=[^,\]]+(,\w+=[^,\]]+)*?)\])?\/>/,
325
+ :api => /<api:([^:\.]+(?:\.[^:]+)?):([^:\/]+)(?::([^:\/]+))?(?::([^\/]+))?\/>/
326
+ }.freeze
327
+
328
+ RIG_STATEMENTS = /(<(loop):(\w+)>(.*?)<\/loop:\3>)|(<(ife):(\w+)>(.*?)(<else:\7\/>(.*?))?<\/ife:\7>)/m
329
+
330
+ RIG_TEMPLATES = {
331
+ :blk => /<(blk):(\w+(?:\.\w+)?)(?:\[(\w+=[^,\]]+(?:,\w+=[^,\]]+)*)\])?\/>/,
332
+ :iblk => /<(iblk):(\w+):(\w+(?:\.\w+)?)(?:\[(\w+=[^,\]]+(?:,\w+=[^,\]]+)*)\])?:(\w+(?:\.\w+)?)(?:\[(\w+=[^,\]]+(?:,\w+=[^,\]]+)*)\])?\/>/,
333
+ :static => /<(static):(\w+(?:\.\w+)?)(?:\[(\w+=[^,\]]+(?:,\w+=[^,\]]+)*)\])?\/>/
334
+ }.freeze
335
+
336
+ attr_reader :flags, :template, :the_parsed
337
+
338
+ def initialize(template, text=nil)
339
+ @template = template
340
+ @inner_text = text ? text : template.content
341
+ @the_parsed = Array.new
342
+ end
343
+
344
+ def parse!
345
+ @tags = nil
346
+ parse_flags
347
+ if @template.is_block?
348
+ parse_statements
349
+ parse_expressions
350
+ end
351
+ parse_templates
352
+ self
353
+ end
354
+
355
+ def render(binder, context=nil)
356
+ @inner_text.dup.tap { |text|
357
+ @the_parsed.each { |value|
358
+ text.gsub! value.anchor, value.render(binder)
359
+ }
360
+ }.freeze
361
+ end
362
+
363
+ def attribute_tags
364
+ @attribute_tags ||= @the_parsed.map { |p|
365
+ p.id.to_sym if p.respond_to? :id
366
+ }.concat(@the_parsed.map{ |p|
367
+ p.attribute_tags if p.type==:ife
368
+ }).compact.flatten.uniq.freeze
369
+ end
370
+
371
+ def loop_parser(id)
372
+ @the_parsed.select{ |p| p.type==:loop && p.id==id}.concat(
373
+ @the_parsed.select{ |p| p.type==:ife }.map { |p| p.loop_parser(id) }.flatten.compact
374
+ )
375
+ end
376
+
377
+ private
378
+
379
+ def parse_flags
380
+ @inner_text.gsub!(RIG_EMPTY[:flags]) { |match|
381
+ @flags = Regexp.last_match(1)
382
+ EMPTY_STRING
383
+ }
384
+ end
385
+
386
+ def parse_expressions
387
+ RIG_EXPRESSIONS.each_key { |kre|
388
+ @inner_text.gsub!(RIG_EXPRESSIONS[kre]) { |match|
389
+ pe = ParsedEntity.instance(self, kre, Regexp.last_match )
390
+ @the_parsed << pe
391
+ pe.anchor
392
+ }
393
+ }
394
+ end
395
+
396
+ def parse_statements
397
+ @inner_text.gsub!(RIG_STATEMENTS) { |match|
398
+ #puts Regexp.last_match.inspect
399
+ type = (Regexp.last_match[2] || String.new) << (Regexp.last_match[6] || String.new)
400
+ pe = ParsedEntity.instance(self, type.to_sym, Regexp.last_match )
401
+ @the_parsed << pe.parse!
402
+ pe.anchor
403
+ }
404
+ end
405
+
406
+ def parse_templates
407
+ reTemplates = Regexp.new RIG_TEMPLATES.values.map{ |re| "(#{re})"}.join('|')
408
+ @inner_text.gsub!(reTemplates) { |match|
409
+ type = (Regexp.last_match[2] || String.new) << (Regexp.last_match[6] || String.new) << (Regexp.last_match[13] || String.new)
410
+ pe = ParsedEntity.instance(self, type.to_sym, Regexp.last_match )
411
+ @the_parsed << pe.parse!
412
+ pe.anchor
413
+ }
414
+ end
415
+
416
+ end
417
+
418
+ end
419
+ end
@@ -0,0 +1,165 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tzispa
4
+ module Rig
5
+
6
+ class NotFound < NameError; end
7
+
8
+ class ReadError < IOError; end
9
+
10
+ class File
11
+
12
+ attr_reader :file, :content, :modified
13
+
14
+ def initialize(file)
15
+ @file = file
16
+ @loaded = false
17
+ end
18
+
19
+ def loaded?
20
+ @loaded
21
+ end
22
+
23
+ def modified?
24
+ @modified != File.mtime(@file)
25
+ end
26
+
27
+ def exists?
28
+ ::File.exists?(@file)
29
+ end
30
+
31
+ def load!
32
+ begin
33
+ raise NotFound.new("Template file '#{@file}' not found") unless exists?
34
+ ::File.open(@file, 'r:UTF-8') { |f|
35
+ @content = String.new
36
+ @modified = f.mtime
37
+ while line = f.gets
38
+ @content << line
39
+ end
40
+ }
41
+ @loaded = true
42
+ rescue Errno::ENOENT
43
+ raise ReadError.new "Template file '#{@file}' could not be read"
44
+ end
45
+ self
46
+ end
47
+
48
+ end
49
+
50
+
51
+ class Template < File
52
+ extend Forwardable
53
+
54
+ BASIC_TYPES = [:layout, :block, :static].freeze
55
+ DEFAULT_FORMAT = 'htm'.freeze
56
+ RIG_EXTENSION = 'rig'.freeze
57
+
58
+ attr_reader :name, :type, :domain, :format, :params, :parser, :engine, :subdomain
59
+ def_delegators :@parser, :attribute_tags
60
+
61
+ def initialize(name:, type:, domain: nil, parent: nil, format: nil, params: nil, engine: nil)
62
+ subdom_name = name.downcase.split('.')
63
+ @subdomain = subdom_name.first if subdom_name.length > 1
64
+ @name = subdom_name.last
65
+ @params = Parameters.new(params)
66
+ send('type=', type)
67
+ if parent
68
+ @engine = engine || parent.engine
69
+ @domain = domain || parent.domain
70
+ @format = format || parent.format
71
+ else
72
+ @engine = engine
73
+ @domain = domain
74
+ @format = format || DEFAULT_FORMAT
75
+ end
76
+ raise ArgumentError.new('Missing parameter(s): domain and engine must be especified') unless @domain && @engine
77
+ super "#{@domain.path}/rig/#{@type.to_s.downcase}/#{@subdomain+'/' if @subdomain}#{@name}.#{RIG_EXTENSION}.#{@format}"
78
+ end
79
+
80
+ def parse!
81
+ @parser = ParserNext.new self
82
+ @parser.parse!
83
+ self
84
+ end
85
+
86
+ def render(context)
87
+ parse! unless @parser
88
+ binder = TemplateBinder.for self, context
89
+ binder.bind! if binder && binder.respond_to?(:bind!)
90
+ @parser.render binder, context
91
+ end
92
+
93
+ def is_block?
94
+ @type == :block
95
+ end
96
+
97
+ def is_layout?
98
+ @type == :layout
99
+ end
100
+
101
+ def is_static?
102
+ @type == :static
103
+ end
104
+
105
+ def params=(value)
106
+ @params = Parameters.new(value)
107
+ end
108
+
109
+ def params
110
+ @params.data
111
+ end
112
+
113
+ def binder_class
114
+ @domain.require "rig/#{@type}/#{@subdomain+'/' if @subdomain}#{@name.downcase}"
115
+ TzString.constantize "#{TzString.camelize @domain.name}::Rig::#{@type.to_s.capitalize}::#{TzString.camelize(@subdomain+'::') if @subdomain}#{TzString.camelize @name }"
116
+ end
117
+
118
+ private
119
+
120
+ def type=(value)
121
+ raise ArgumentError.new("#{value} is not a Rig block") unless BASIC_TYPES.include?(value)
122
+ @type = value
123
+ end
124
+
125
+ end
126
+
127
+
128
+ class Engine
129
+
130
+ attr_reader :app
131
+
132
+ def initialize(app)
133
+ @app = app
134
+ @pool= Hash.new
135
+ @mutex = Mutex.new
136
+ end
137
+
138
+ def layout(name:, format:nil, params:nil)
139
+ rig_template name, :layout, format, params, nil
140
+ end
141
+
142
+ def block(name:, format:nil, params:nil, parent:nil)
143
+ rig_template name, :block, format, params, parent
144
+ end
145
+
146
+ def static(name:, format:nil, params:nil, parent:nil)
147
+ rig_template name, :static, format, params, parent
148
+ end
149
+
150
+ def rig_template(name, type, format, params, parent)
151
+ if @mutex.owned?
152
+ tpl = @pool["#{type}__#{name}"] || Template.new( name: name, type: type, format: format, domain: @app.domain, params: params, parent: parent, engine: self )
153
+ tpl.loaded? && !tpl.modified? ? tpl : tpl.load!.parse!
154
+ else
155
+ @mutex.synchronize {
156
+ tpl = @pool["#{type}__#{name}"] || Template.new( name: name, type: type, format: format, domain: @app.domain, params: params, parent: parent, engine: self )
157
+ tpl.loaded? && !tpl.modified? ? tpl : tpl.load!.parse!
158
+ }
159
+ end
160
+ end
161
+
162
+ end
163
+
164
+ end
165
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tzispa
4
+ module Rig
5
+
6
+ VERSION = '0.2.3'.freeze
7
+ GEM_NAME = 'tzispa_rig'.freeze
8
+
9
+ end
10
+ end
data/lib/tzispa/rig.rb ADDED
@@ -0,0 +1,11 @@
1
+ module Tzispa
2
+ module Rig
3
+
4
+ require 'tzispa/rig/parameters'
5
+ require 'tzispa/rig/binder'
6
+ require 'tzispa/rig/template'
7
+ require 'tzispa/rig/parsernext'
8
+ require 'tzispa/rig/version'
9
+
10
+ end
11
+ end
data/lib/tzispa_rig.rb ADDED
@@ -0,0 +1,7 @@
1
+ module Tzispa
2
+ module Rig
3
+
4
+ require 'tzispa/rig'
5
+
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tzispa_rig
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.3
5
+ platform: ruby
6
+ authors:
7
+ - Juan Antonio Piñero
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-02-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: tzispa_helpers
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: tzispa_utils
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.1'
41
+ description: General purpose template engine
42
+ email:
43
+ - japinero@area-integral.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - CHANGELOG.md
49
+ - README.md
50
+ - lib/tzispa/rig.rb
51
+ - lib/tzispa/rig/binder.rb
52
+ - lib/tzispa/rig/parameters.rb
53
+ - lib/tzispa/rig/parser.rb
54
+ - lib/tzispa/rig/parsernext.rb
55
+ - lib/tzispa/rig/template.rb
56
+ - lib/tzispa/rig/version.rb
57
+ - lib/tzispa_rig.rb
58
+ homepage: https://www.area-integral.com
59
+ licenses:
60
+ - MIT
61
+ metadata: {}
62
+ post_install_message:
63
+ rdoc_options: []
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - "~>"
69
+ - !ruby/object:Gem::Version
70
+ version: '2.0'
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '2.0'
76
+ requirements: []
77
+ rubyforge_project:
78
+ rubygems_version: 2.5.1
79
+ signing_key:
80
+ specification_version: 4
81
+ summary: General purpose template engine
82
+ test_files: []