tzispa_rig 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
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: []