tequila 0.2.1
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.
- data/.gitignore +2 -0
- data/Changelog +18 -0
- data/MIT-LICENSE +22 -0
- data/README.markdown +213 -0
- data/Rakefile +38 -0
- data/TODO +15 -0
- data/VERSION +1 -0
- data/init.rb +3 -0
- data/lib/preprocessor.rb +71 -0
- data/lib/tequila.treetop +266 -0
- data/lib/tequila_jazz_handler.rb +30 -0
- data/lib/tree.rb +315 -0
- data/test/bench.rb +25 -0
- data/test/db/database.yml +3 -0
- data/test/db/fixtures/humans.yml +12 -0
- data/test/db/fixtures/pet_types.yml +9 -0
- data/test/db/fixtures/pets.yml +19 -0
- data/test/db/fixtures/toys.yml +12 -0
- data/test/db/preparing.rb +76 -0
- data/test/pets.jazz +3 -0
- data/test/tequila_test.rb +392 -0
- data/test/test_helper.rb +9 -0
- metadata +95 -0
@@ -0,0 +1,30 @@
|
|
1
|
+
#-*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'treetop'
|
4
|
+
require 'tequila'
|
5
|
+
require 'preprocessor'
|
6
|
+
require 'tree'
|
7
|
+
|
8
|
+
module Tequila
|
9
|
+
class JazzHandler < ActionView::TemplateHandler
|
10
|
+
include ActionView::TemplateHandlers::Compilable
|
11
|
+
|
12
|
+
def compile(template)
|
13
|
+
<<CODE.gsub('\n',';')
|
14
|
+
controller.response.content_type = Mime::JSON
|
15
|
+
src = ::TequilaPreprocessor.run(template.source)
|
16
|
+
_tequila_out =::TequilaParser.new.parse(src).eval(binding).build_hash.to_json
|
17
|
+
CODE
|
18
|
+
end
|
19
|
+
|
20
|
+
def cache_fragment(block, name = {}, options = nil)
|
21
|
+
@view.fragment_for(block, name, options) do
|
22
|
+
eval("_tequila_out", block.binding)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
ActionView::Template.register_template_handler :jazz, Tequila::JazzHandler
|
30
|
+
ActionView::Template.exempt_from_layout(/.jazz$/)
|
data/lib/tree.rb
ADDED
@@ -0,0 +1,315 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module Tequila
|
4
|
+
|
5
|
+
class Config
|
6
|
+
|
7
|
+
class Default
|
8
|
+
@@show_initial_label = false
|
9
|
+
|
10
|
+
def self.show_initial_label!
|
11
|
+
@@show_initial_label = true
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.hide_initial_label!
|
15
|
+
@@show_initial_label = false
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.show_initial_label?
|
19
|
+
@@show_initial_label
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_reader :show_initial_label
|
24
|
+
|
25
|
+
def show_initial_label!
|
26
|
+
@show_initial_label = true
|
27
|
+
end
|
28
|
+
|
29
|
+
def hide_initial_label!
|
30
|
+
@show_initial_label = false
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize
|
34
|
+
@show_initial_label = Default.show_initial_label?
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
class Tree
|
40
|
+
|
41
|
+
attr_reader :root
|
42
|
+
attr_accessor :config
|
43
|
+
|
44
|
+
def initialize
|
45
|
+
@root = OpenStruct.new({:name => :root})
|
46
|
+
@tree = { @root => [] }
|
47
|
+
@config = Tequila::Config.new
|
48
|
+
end
|
49
|
+
|
50
|
+
def add_child_to(branch, child)
|
51
|
+
@tree[branch] ||= []
|
52
|
+
@tree[branch] << child
|
53
|
+
end
|
54
|
+
|
55
|
+
def parent_for?(node)
|
56
|
+
@tree.keys.select {|n| @tree[n].include?(node)}[0]
|
57
|
+
end
|
58
|
+
|
59
|
+
def nodes
|
60
|
+
@tree.values.inject([]){ |res, nodes| res + nodes }.uniq
|
61
|
+
end
|
62
|
+
|
63
|
+
def build_hash
|
64
|
+
res = @tree[root].inject({}) do |out, n|
|
65
|
+
out.merge(build_hash_with_context(n, n.content.call))
|
66
|
+
end
|
67
|
+
((res.values.first.kind_of? Array) && !config.show_initial_label) ?
|
68
|
+
res.values.first : res
|
69
|
+
end
|
70
|
+
|
71
|
+
def build_hash_with_context(node, context)
|
72
|
+
if context.kind_of? Array
|
73
|
+
{ node.label =>
|
74
|
+
context.map do |elementary_context|
|
75
|
+
build_hash_with_context(node, elementary_context)
|
76
|
+
end
|
77
|
+
}
|
78
|
+
elsif @tree[node]
|
79
|
+
node_value = node.apply(context).merge(
|
80
|
+
@tree[node].inject({}) do |out, n|
|
81
|
+
new_context = n.content.call(context)
|
82
|
+
if new_context.nil?
|
83
|
+
out
|
84
|
+
else
|
85
|
+
if n.bounded?
|
86
|
+
out.merge(build_hash_with_context(n, new_context).values.first)
|
87
|
+
else
|
88
|
+
out.merge(build_hash_with_context(n, new_context))
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end)
|
92
|
+
node.suppress_label ?
|
93
|
+
node_value :
|
94
|
+
{ node.label.singularize => node_value }
|
95
|
+
else
|
96
|
+
node.suppress_label ?
|
97
|
+
node.apply(context) :
|
98
|
+
{ node.label.singularize => node.apply(context) }
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def to_s
|
103
|
+
nodes.inject('') do |res, node|
|
104
|
+
res <<
|
105
|
+
node.to_s <<
|
106
|
+
(node.bounded? ? " bounded_to: " : " parent: ") <<
|
107
|
+
"#{parent_for?(node).name}\n"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
class Node
|
113
|
+
|
114
|
+
attr_accessor :methods
|
115
|
+
attr_accessor :attributes
|
116
|
+
attr_accessor :code_blocks
|
117
|
+
attr_accessor :label
|
118
|
+
attr_accessor :suppress_label
|
119
|
+
attr_accessor :statics
|
120
|
+
attr_reader :name
|
121
|
+
attr_reader :content
|
122
|
+
attr_reader :type
|
123
|
+
|
124
|
+
# it is used for attributes if 'all' keyword was specified
|
125
|
+
attr_reader :pick_all
|
126
|
+
attr_reader :drop_all
|
127
|
+
|
128
|
+
class ImpreciseAttributesDeclarationError < StandardError; end
|
129
|
+
class NoAttributeError < StandardError; end
|
130
|
+
|
131
|
+
class CodeBlock
|
132
|
+
attr_reader :label
|
133
|
+
attr_reader :code
|
134
|
+
|
135
|
+
def initialize(label, code)
|
136
|
+
@label = label
|
137
|
+
@code = code
|
138
|
+
end
|
139
|
+
|
140
|
+
def to_s
|
141
|
+
label + code + "\n"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
class Method
|
146
|
+
attr_accessor :label
|
147
|
+
attr_accessor :params
|
148
|
+
attr_reader :name
|
149
|
+
|
150
|
+
def initialize(name)
|
151
|
+
@name = name
|
152
|
+
@label = name
|
153
|
+
@params = []
|
154
|
+
end
|
155
|
+
|
156
|
+
def to_s
|
157
|
+
name +
|
158
|
+
(params.empty? ? '' : "(#{params * ','})") +
|
159
|
+
(name == label ? '' : " => #{label}")
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
class Static
|
164
|
+
attr_accessor :label
|
165
|
+
attr_accessor :value
|
166
|
+
|
167
|
+
def initialize label, value
|
168
|
+
@label = label
|
169
|
+
@value = value
|
170
|
+
end
|
171
|
+
|
172
|
+
def to_s
|
173
|
+
"#{label}: #{value}"
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
class Attribute
|
178
|
+
attr_accessor :label
|
179
|
+
attr_reader :name
|
180
|
+
|
181
|
+
def initialize(name)
|
182
|
+
@name = name
|
183
|
+
@label = name
|
184
|
+
end
|
185
|
+
|
186
|
+
def to_s
|
187
|
+
name == label ?
|
188
|
+
name : "#{name}(#{label}) "
|
189
|
+
end
|
190
|
+
|
191
|
+
end
|
192
|
+
|
193
|
+
#====================================#
|
194
|
+
# main class #
|
195
|
+
#====================================#
|
196
|
+
|
197
|
+
def initialize(name, type, bounded = false)
|
198
|
+
@name = name
|
199
|
+
@label = @name.gsub(/[@\$]/,'')
|
200
|
+
@type = type
|
201
|
+
@methods = []
|
202
|
+
@attributes = { :only => [], :except => []}
|
203
|
+
@code_blocks = []
|
204
|
+
@statics = []
|
205
|
+
@suppress_label = false
|
206
|
+
end
|
207
|
+
|
208
|
+
def eval(vars)
|
209
|
+
@content = case type
|
210
|
+
when :variable
|
211
|
+
lambda { Kernel.eval name, vars }
|
212
|
+
when :association, :bounded
|
213
|
+
lambda { |context| context.send(name.intern)}
|
214
|
+
end
|
215
|
+
self
|
216
|
+
end
|
217
|
+
|
218
|
+
def apply(context)
|
219
|
+
mapping = {}
|
220
|
+
if context.respond_to?(:attributes) && !drop_all
|
221
|
+
|
222
|
+
if (pick_all || drop_all)
|
223
|
+
unless attributes[:only].empty? && attributes[:except].empty?
|
224
|
+
raise ImpreciseAttributesDeclarationError, "declaration conflict"
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
context_attributes = context.attributes
|
229
|
+
#p "All: #{context_attributes.keys.inspect}"
|
230
|
+
if attributes[:only].size > 0
|
231
|
+
chosen_attributes = attributes[:only].map(&:name)
|
232
|
+
#p "Chosen: #{chosen_attributes}"
|
233
|
+
unless (foreign_attributes = (chosen_attributes.to_set - context_attributes.keys.to_set)).empty?
|
234
|
+
raise NoAttributeError, "can't find attributes: #{foreign_attributes.to_a.join(', ')}"
|
235
|
+
end
|
236
|
+
attributes[:only].inject({}) do |res, att|
|
237
|
+
res[att.label] = context_attributes[att.name]
|
238
|
+
res
|
239
|
+
end
|
240
|
+
elsif attributes[:except].size > 0
|
241
|
+
|
242
|
+
ignored_attributes = attributes[:except].map(&:name)
|
243
|
+
#p "Ignored: #{ignored_attributes.inspect}"
|
244
|
+
unless (foreign_attributes = (ignored_attributes.to_set - context_attributes.keys.to_set)).empty?
|
245
|
+
raise NoAttributeError, "can't find attributes: #{foreign_attributes.to_a.join(', ')}"
|
246
|
+
end
|
247
|
+
context_attributes.delete_if { |att_name, _| ignored_attributes.include?(att_name) }
|
248
|
+
else
|
249
|
+
# use all variables by default if they are supported
|
250
|
+
context_attributes
|
251
|
+
end
|
252
|
+
else
|
253
|
+
{}
|
254
|
+
end.merge(
|
255
|
+
(methods || []).inject({}) do |res, m|
|
256
|
+
res[m.label] = context.send(m.name.intern, *(m.params.map {|p| context.instance_eval p}))
|
257
|
+
res
|
258
|
+
end
|
259
|
+
).merge(
|
260
|
+
code_blocks.inject({}) do |res, cb|
|
261
|
+
res[cb.label] = context.instance_eval cb.code
|
262
|
+
res
|
263
|
+
end
|
264
|
+
).merge(
|
265
|
+
statics.inject({}) do |res, s|
|
266
|
+
res[s.label] = s.value
|
267
|
+
res
|
268
|
+
end
|
269
|
+
)
|
270
|
+
end
|
271
|
+
|
272
|
+
def add_attribute(key, attr)
|
273
|
+
raise ImpreciseAttributesDeclarationError if (pick_all || drop_all)
|
274
|
+
unless @attributes[key].include?(attr)
|
275
|
+
@attributes[key] << attr
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
def no_attributes!
|
280
|
+
if pick_all
|
281
|
+
raise ImpreciseAttributesDeclarationError, "declaration conflict"
|
282
|
+
else
|
283
|
+
@drop_all = true
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
def all_attributes!
|
288
|
+
if drop_all
|
289
|
+
raise ImpreciseAttributesDeclarationError, "declaration conflict"
|
290
|
+
else
|
291
|
+
@pick_all = true
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
def bounded?
|
296
|
+
:bounded == type
|
297
|
+
end
|
298
|
+
|
299
|
+
def to_s
|
300
|
+
"Node: " <<
|
301
|
+
((name == label) ? name : "#{name}(#{label})") <<
|
302
|
+
(methods.empty? ? ' ' : " Methods: #{methods.map(&:to_s).join(',')} ") <<
|
303
|
+
(if attributes[:except].size > 0
|
304
|
+
"Except: #{attributes[:except].to_s}"
|
305
|
+
elsif attributes[:only].size > 0
|
306
|
+
"Only: #{attributes[:only].to_s}"
|
307
|
+
else
|
308
|
+
''
|
309
|
+
end) <<
|
310
|
+
((code_blocks.size > 0) ? "Code blocks: \n" << code_blocks.map(&:to_s).join : '')
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
end
|
315
|
+
|
data/test/bench.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'benchmark'
|
2
|
+
require 'test/test_helper'
|
3
|
+
|
4
|
+
module TequilaBenchmark
|
5
|
+
def TequilaBenchmark.run(count = 500)
|
6
|
+
humans = Human.all
|
7
|
+
src = <<END
|
8
|
+
-humans
|
9
|
+
:except
|
10
|
+
.id
|
11
|
+
+pets
|
12
|
+
:only
|
13
|
+
.id
|
14
|
+
+toys
|
15
|
+
END
|
16
|
+
Benchmark.bm(18) do |x|
|
17
|
+
x.report('to_json') { (1..count).each { json = humans.to_json(:except => :id, :include => { :pets => { :include => :toys, :only => :id }}) } }
|
18
|
+
x.report('jazz') { (1..count).each { json = TequilaParser.new.parse(TequilaPreprocessor.run(src)).eval(binding).build_hash.to_json } }
|
19
|
+
x.report('jazz with preparse') {
|
20
|
+
tree = TequilaParser.new.parse(TequilaPreprocessor.run(src))
|
21
|
+
(1..count).each { json = tree.eval(binding).build_hash.to_json }
|
22
|
+
}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
skooby:
|
2
|
+
name: Skooby Doo
|
3
|
+
pet_type: dog
|
4
|
+
human: nem
|
5
|
+
toys: hum, con
|
6
|
+
poiyo:
|
7
|
+
name: Poiyo
|
8
|
+
pet_type: fish
|
9
|
+
human: ex
|
10
|
+
toys: lhc, hum
|
11
|
+
barbos:
|
12
|
+
name: Pes Barbos
|
13
|
+
pet_type: dog
|
14
|
+
human: sletix
|
15
|
+
toys: grz, con
|
16
|
+
matroskin:
|
17
|
+
name: Matroskin
|
18
|
+
pet_type: cat
|
19
|
+
human: aaa
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'activerecord'
|
3
|
+
require 'sqlite3'
|
4
|
+
|
5
|
+
# setup db connection
|
6
|
+
|
7
|
+
ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + 'debug.log')
|
8
|
+
ActiveRecord::Base.configurations = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
|
9
|
+
ActiveRecord::Base.establish_connection('demo')
|
10
|
+
|
11
|
+
# setup db layout
|
12
|
+
|
13
|
+
ActiveRecord::Schema.define :version => 0 do
|
14
|
+
create_table :humans, :force => true do |t|
|
15
|
+
t.string :name
|
16
|
+
t.string :address
|
17
|
+
end
|
18
|
+
|
19
|
+
create_table :pets, :force => true do |t|
|
20
|
+
t.string :name
|
21
|
+
t.integer :pet_type_id
|
22
|
+
t.integer :human_id
|
23
|
+
end
|
24
|
+
|
25
|
+
create_table :pet_types, :force => true do |t|
|
26
|
+
t.string :class_name
|
27
|
+
end
|
28
|
+
|
29
|
+
create_table :toys, :force => true do |t|
|
30
|
+
t.string :label
|
31
|
+
t.float :price
|
32
|
+
end
|
33
|
+
|
34
|
+
create_table :pets_toys, :force => true, :id => false do |t|
|
35
|
+
t.integer :toy_id
|
36
|
+
t.integer :pet_id
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
# define models
|
42
|
+
|
43
|
+
class Human < ActiveRecord::Base
|
44
|
+
set_table_name :humans
|
45
|
+
has_many :pets
|
46
|
+
end
|
47
|
+
|
48
|
+
class Pet < ActiveRecord::Base
|
49
|
+
has_and_belongs_to_many :toys
|
50
|
+
belongs_to :pet_type
|
51
|
+
belongs_to :human
|
52
|
+
def which(first = 'big', second = 'cheerful')
|
53
|
+
"#{name} is #{first} and #{second}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class PetType < ActiveRecord::Base
|
58
|
+
end
|
59
|
+
|
60
|
+
class Toy < ActiveRecord::Base
|
61
|
+
has_and_belongs_to_many :pets
|
62
|
+
|
63
|
+
def railsize(*strs)
|
64
|
+
strs.join.gsub('PHP','Ruby').gsub('Django','Rails')
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# load fixtures
|
69
|
+
|
70
|
+
require 'active_record/fixtures'
|
71
|
+
|
72
|
+
Fixtures.reset_cache
|
73
|
+
fixtures_folder = File.dirname(__FILE__) + '/fixtures'
|
74
|
+
fixtures = Dir[File.join(fixtures_folder, '*.yml')].map {|f| File.basename(f, '.yml') }
|
75
|
+
Fixtures.create_fixtures(fixtures_folder, fixtures)
|
76
|
+
|