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.
@@ -0,0 +1,2 @@
1
+ demo.sqlite3
2
+ test/dbdebug.log
@@ -0,0 +1,18 @@
1
+ 2010-05-08 Eugene Hlyzov <hlyzov@gmail.com>
2
+ allow space between '-' and variable declaration
3
+ meaningful aliases for control symbols
4
+ add ability to omit some control symbols
5
+ allow using ' symbol to stress a variable
6
+ add 'pick all' and 'drop all' instructions
7
+ 2010-01-15 Eugene Hlyzov <hlyzov@gmail.com>
8
+ [feature] Add configuration options
9
+ please check README - there is a change in default behaviour
10
+ 2009-11-01 Eugene Hlyzov <hlyzov@gmail.com>
11
+ [feature] Add ability to specify static values
12
+ 2009-09-07 Eugene Hlyzov <hlyzov@gmail.com>
13
+ [feature] Add ability to suppress labels
14
+ minor refactoring
15
+ 2009-09-06 Eugene Hlyzov <hlyzov@gmail.com>
16
+ fix bug with nil association
17
+ fix bug with wrong label
18
+ add tests for fixed bugs
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2009 Eugene Hlyzov (hlyzov[at]gmail[dot]com)
2
+ Ivan Nemytchenko (nemytchenko[at]gmail[dot]com)
3
+ Alexandr Alexandrov (elequtree[at]gmail[dot]com)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,213 @@
1
+ ## Overview ##
2
+
3
+ Tequila basically is HAML for JSON generation.
4
+
5
+ Today while developing web applications with rich user UI, almost everything you need from rails backend is bunch of data in JSON format.
6
+
7
+ - Almost always, when you had to create unobvious set of data, your controllers became fat and ugly.
8
+
9
+ @humans.to_json(
10
+ :methods => [:login, :enhanced_name, :hello_world], :except => [:created_at, :updated_at],
11
+ :include => {:pets => { :except => [:human_id, :pet_type_id, :created_at, :updated_at],
12
+ :methods => :pet_type_name,
13
+ :include => { :toys => { :methods => [:railsize2], :only => [:color, :size]} }
14
+ }}
15
+ )
16
+
17
+ - And you had to create a lot of small helper methods in your models if you want for example fullname instead of firstname, middlename and lastname or you want pretty address, instead of separate fields for zip, country, state and street adress in your JSON.
18
+ - And you had to create child node in your JSON for has-one or belongs-to associations if you want to get even one attribute form association.
19
+ - You cannot rename keys in your json. You might want to use keyword "type", but it is reserved by rails.
20
+ - What about calling your method with some parameters while generating JSON? No native way, sorry...
21
+
22
+ Tequila is an instrument, which lets easily move your JSON-generation logic from controllers to views. Take a look at features:
23
+
24
+ ## Features ##
25
+
26
+ ![Tequila features](http://inem.github.com/images/tequila-features.png)
27
+
28
+ - [Here is Jazz template](http://gist.github.com/173339/)
29
+ - [And here is it's JSON output](http://gist.github.com/173255/)
30
+
31
+ ## Installation ##
32
+
33
+ ./script/plugin install git://github.com/inem/tequila.git
34
+
35
+ After that drop some Tequila code into an apropriate template (should have .jazz extension) - and you are there!
36
+
37
+ ## Examples ##
38
+
39
+ -@humans => people
40
+ :only
41
+ .name => login
42
+ :code => enhanced_name
43
+ name + "!!!"
44
+ :code => hello_world
45
+ "Hello world!"
46
+ +pets
47
+ :except
48
+ .human_id
49
+ .pet_type_id
50
+ +toys
51
+ :methods
52
+ .railsize("$", price.to_s)
53
+ +pet_type
54
+ :only
55
+ .class_name
56
+
57
+ -@humans => aa
58
+ :except
59
+ .name
60
+ :code => super_name
61
+ "→#{name}→"
62
+ +pets
63
+ :except
64
+ .human_id
65
+ .pet_type_id
66
+ +toys
67
+ :methods
68
+ .railsize("$", price.to_s)
69
+ +pet_type
70
+ :only
71
+ .class_name
72
+
73
+ -@humans => humanoids
74
+ :only
75
+ .name => login
76
+ :code => humanoid_names
77
+ name + "is Humanoid"
78
+ +pets => animals
79
+ :only
80
+ .human_id
81
+ .name
82
+ :code => humanoid_animals
83
+ name + "is Humanoid pet"
84
+ +toys
85
+ :only
86
+ .label
87
+ <pet_type
88
+ :only
89
+ .class_name => pet_type
90
+
91
+ ## Basic syntax ##
92
+
93
+ Have you ever used to_json method in your Rails app? If so, you should be familiar with basic Tequila syntax. You still have your :only, :except and :methods keywords. Instead of writing
94
+
95
+ :include => { :association1 => {...}}
96
+
97
+ You should just remember that every variable definition must be started '-' and every association with '+'.
98
+ So example above can be written much shortly:
99
+
100
+ +association1
101
+
102
+ ## Advanced ##
103
+
104
+ ### Labels ###
105
+
106
+ Label can be defined via expression (=> label) which can be added everywhere where it makes sense. It means that you can add label to such elements as :code, +association, etc.. and can not add label to :except declaration and :gluening object. Some functionality of these features are covered by standart Rails to_json method and some aren't. For instance, if you bind alias 'animals' to assoication 'pets' it will automatically leads to bound 'animal' label to each Pet model below such declaration (see [example](http://gist.github.com/173255/))
107
+
108
+ ### Call methods with params ###
109
+
110
+ It is pretty simple. You just can pass any amount of params in any method in :methods definition. You should not create any presenters for model methods if you want to call them with params. Want to use instance method as param? No problem! With a bit of Tequila magic you can simple do so:
111
+
112
+ ...
113
+ :methods
114
+ .calculate(1,2,3, total_count)
115
+
116
+ ### Code blocks ###
117
+
118
+ Code block are extremely useful if you need to add evaluable expression in the generated json but you don't want to store it as model (or presenter) method. For example you have attribute label, but for some reason you have to return "[# {label}]" just in one place of the code. The following feature allows you to keep your models clean:
119
+
120
+ ...
121
+ :code => plabel
122
+ '[' + label + ']'
123
+ end
124
+
125
+ As result, in the generated data structure you will have additional field 'plabel' with desired value.
126
+
127
+ ### Gluing ###
128
+
129
+ It is another nice feature which you always want to have (may be instinctively ;). Sometimes you have 'belongs_to' association where you need just one attribute. And 'gluing' allows you to.. glue attributes from child associations. Compare:
130
+
131
+ ...
132
+ :tag
133
+ :only
134
+ .label
135
+ +tagger
136
+ :only
137
+ .name
138
+ end
139
+ # Out: {'tag' => {'label' => 'Happy Christmas!', 'tagger' => {'name' => 'Mr Lawrence"}}
140
+
141
+ Generated json fragment looks like too comprehensive. Let us rewrite this fragment using gluing feature:
142
+
143
+ ...
144
+ :tag
145
+ :only
146
+ .label
147
+ <tagger
148
+ :only
149
+ .name => tagger_name
150
+ end
151
+ # Out: {'tag' => {'label' => 'Happy Christmas!', 'tagger_name' => 'Mr Lawrence"}}
152
+
153
+ Of course it can be handled via additional model methods, but latter is mere artifical solution. We are going to DRY, aren't we?
154
+
155
+ ### Configuration ###
156
+
157
+ Since 0.1.3 there are ability to configure initial label rendering. Configuration options can be inserted either globally via Tequila::Config::Default class or locally with following syntax:
158
+
159
+ #!some_configure_option
160
+ USUAL_TEQUILA_CODE_HERE
161
+
162
+ there are two available options:
163
+ 1. hide_initial_label!
164
+ 2. show_initial_label!
165
+
166
+ It has a sense in a case when you are rendering collection of
167
+ objects and don't want to output its label. Compare:
168
+
169
+ # show_initial_label! used
170
+ {"links" => [{"link" => {..}}, {"link => {..}}]}
171
+ vs
172
+ # hide_initial_label! used
173
+ [{"link" => {..}}, {"link => {..}}]
174
+
175
+ As we are going to be fully compatible with Rails, hide_initial_label! is default option now.
176
+ To cancel it you can just add following lines somewhere in initializers:
177
+
178
+ class Tequila::Config::Default
179
+ show_initial_label!
180
+ end
181
+
182
+ ## Issues ##
183
+
184
+ Strict order of definitions required! All blocks are optional.
185
+
186
+ 1. :only or :except
187
+ 2. :methods
188
+ 3. :code blocks
189
+ 4. +asscociations
190
+ 5. &lt;gluening
191
+
192
+ ### Benchmarks ###
193
+
194
+
195
+
196
+ user system total real
197
+ to_json 7.280000 0.440000 7.720000 ( 7.811976)
198
+ jazz 25.920000 1.840000 27.760000 ( 28.229717)
199
+ jazz with preparse 17.210000 1.580000 18.790000 ( 19.122837)
200
+
201
+ At least for these tests it looks like to_json is ~2.4x faster..
202
+ But for some reason you use Ruby instead of C, right? Despite of the fact that Tequila is not too fast today we are happy to have such instrument and are going to develop it further. And we have good plans about it...
203
+
204
+ ### Plans ###
205
+
206
+ - Grammatic review
207
+ - Implement HashMapper fucntionality
208
+ - Arbitrary order of defenitions
209
+ - More tests for edge cases
210
+ - More syntax sugar
211
+ - Speedup
212
+
213
+ And.. we are always open for your feedback! :)
@@ -0,0 +1,38 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ require 'test/bench'
6
+
7
+ desc 'Default: run unit tests.'
8
+ task :default => :test
9
+
10
+ desc 'Test the tequila plugin.'
11
+ Rake::TestTask.new(:test) do |t|
12
+ t.libs << 'lib'
13
+ t.libs << 'test'
14
+ t.pattern = 'test/**/*_test.rb'
15
+ t.verbose = true
16
+ end
17
+
18
+
19
+ desc 'Benchmark'
20
+ task :bench do
21
+ TequilaBenchmark.run
22
+ end
23
+
24
+ begin
25
+ require 'jeweler'
26
+ Jeweler::Tasks.new do |gemspec|
27
+ gemspec.version = '0.2.1'
28
+ gemspec.name = "tequila"
29
+ gemspec.summary = "Language for advanced JSON generation"
30
+ gemspec.description = "Language for advanced JSON generation"
31
+ gemspec.email = "eugene.hlyzov@gmail.com"
32
+ gemspec.homepage = "http://github.com/inem/tequila"
33
+ gemspec.authors = ["Eugene Hlyzov", "Ivan Nemytchenko"]
34
+ end
35
+ Jeweler::GemcutterTasks.new
36
+ rescue LoadError
37
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
38
+ end
data/TODO ADDED
@@ -0,0 +1,15 @@
1
+ BUGS:
2
+ TODO:
3
+ - option to generate keys without quotes
4
+ - gluing for mas_many associations
5
+ - comments //
6
+ - :plain (some symbol for this?)
7
+ - :only => .aaa
8
+ .aaa .bbb
9
+ .bbb
10
+ - :except => !aaa
11
+ .aaa !bbb
12
+ .bbb
13
+ - :plain by default
14
+ - define :all by symbol
15
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.0
data/init.rb ADDED
@@ -0,0 +1,3 @@
1
+ require 'ostruct'
2
+ require 'treetop'
3
+ require 'tequila_jazz_handler'
@@ -0,0 +1,71 @@
1
+ module TequilaPreprocessor
2
+ EOB = "end\n".freeze
3
+ EOC = "end\n".freeze
4
+ JAZZ = ".jazz".freeze
5
+
6
+ def TequilaPreprocessor.run(source, offset = -1)
7
+ source = replace(source)
8
+ prev = offset
9
+ text = ""
10
+ code = nil
11
+ lines = source.split("\n")
12
+ for line in lines do
13
+ unless (line.strip =~ /\/\//) == 0
14
+ spaces, rest = spaces(line)
15
+ raise "#{spaces} spaces in line <#{line}>" if (spaces & 1 != 0)
16
+ tabs = spaces >> 1
17
+ if code
18
+ if tabs <= code
19
+ code = nil
20
+ text += EOC
21
+ else
22
+ text += "#{line}\n"
23
+ next
24
+ end
25
+ end
26
+ case rest
27
+ when /^:code/
28
+ code = tabs
29
+ when /^(\+|\-|\<|source|join|merge)/
30
+ diff = prev - tabs
31
+ diff += 1 if diff >= 0
32
+ diff.times { text += EOB }
33
+ prev = tabs
34
+ end
35
+ text += "#{line}\n"
36
+ end
37
+ end
38
+ text += EOC if code
39
+ (prev - offset).times { text += EOB }
40
+ text
41
+ end
42
+
43
+ private
44
+ def TequilaPreprocessor.replace(source)
45
+ text = ''
46
+ while (i = (/(^ *)(&|include(\ )+)(.*$)/ =~ source)) do
47
+ text += source[0...i]
48
+ source = $'
49
+ spaces = $1.size
50
+ filename = $4 + ".jazz"
51
+ text += include_file(filename, spaces)
52
+ end
53
+ text += source
54
+ end
55
+
56
+ def TequilaPreprocessor.include_file(filename, offset)
57
+ lines = ''
58
+ prefix = ' ' * offset
59
+ f = open(filename, 'r')
60
+ while (s = f.gets) do
61
+ lines << prefix + s
62
+ end
63
+ f.close
64
+ lines
65
+ end
66
+
67
+ def TequilaPreprocessor.spaces(line)
68
+ (/^ +/ =~ line) ? [$&.size, $'] : [0, line]
69
+ end
70
+ end
71
+
@@ -0,0 +1,266 @@
1
+ grammar Tequila
2
+
3
+ rule program
4
+ allow_space configuration:configuration_line* expression allow_space {
5
+ def eval(binding)
6
+ tree = Tequila::Tree.new
7
+ configuration.elements.each {|cl| cl.eval(tree)}
8
+ expression.eval(tree, tree.root, binding)
9
+ end
10
+ }
11
+ end
12
+
13
+ rule configuration_line
14
+ allow_space '#!' key:term allow_space value:term? {
15
+ def eval(tree)
16
+ if value.text_value.blank?
17
+ tree.config.send(key.text_value.intern)
18
+ else
19
+ tree.config.send(key.text_value.intern, value.text_value)
20
+ end
21
+ end
22
+ }
23
+ end
24
+
25
+ rule expression
26
+ source_keyword var_type:('@'/'@@'/'$')? object {
27
+ def eval(tree, parent, binding)
28
+ object.eval(tree, parent, binding, :variable, {:var_type => var_type.text_value})
29
+ end
30
+ }
31
+ end
32
+
33
+ rule source_keyword
34
+ '-' allow_space
35
+ / 'source' must_space
36
+ end
37
+
38
+ rule object
39
+ data_mark? object_name:term suppress_label:'~'? label:label_decl? atd:attributes_decl? mtd:methods_decl? std:statics_decl? cbd:code_decl* asd:association_decl* gld:gluing_decl* eob {
40
+
41
+ def name
42
+ object_name.text_value
43
+ end
44
+
45
+ def eval(tree, parent, binding, type, params ={})
46
+
47
+ node = Tequila::Node.new((:variable == type) ? params[:var_type] + name : name, type).eval(binding)
48
+
49
+ unless suppress_label.empty?
50
+ node.suppress_label = true
51
+ end
52
+
53
+ if label.elements
54
+ node.label = label.term.text_value
55
+ end
56
+
57
+ if atd.elements
58
+ atd.update_node(node)
59
+ end
60
+
61
+ if std.elements
62
+ std.update_node(node)
63
+ end
64
+
65
+ if mtd.elements
66
+ mtd.update_node(node)
67
+ end
68
+
69
+ if cbd.elements
70
+ cbd.elements.each {|cb| cb.update_node(node)}
71
+ end
72
+
73
+ tree.add_child_to(parent, node)
74
+
75
+ asd.elements.each {|e| e.eval(tree, node, binding) }
76
+ gld.elements.each {|e| e.eval(tree, node, binding) }
77
+
78
+ tree
79
+ end
80
+ }
81
+ end
82
+
83
+ rule association_decl
84
+ allow_space ('+' allow_space /'join' must_space) object {
85
+ def eval(tree, node, binding)
86
+ object.eval(tree, node, binding, :association)
87
+ end
88
+ }
89
+ end
90
+
91
+ rule gluing_decl
92
+ allow_space ('<' allow_space /'merge' must_space) object {
93
+ def eval(tree, node, binding)
94
+ object.eval(tree, node, binding, :bounded)
95
+ end
96
+ }
97
+ end
98
+
99
+ rule attributes_decl
100
+ allow_space ':'? (only_attributes_decl / except_attributes_decl) {
101
+ def update_node(node)
102
+ elements[2].update_node(node)
103
+ end
104
+ }
105
+ end
106
+
107
+ rule only_attributes_decl
108
+ (allow_space atd:pick_all_decl+
109
+ / ('only'/'pick') atd:only_attribute_decl+) {
110
+ def update_node(node)
111
+ if atd.elements
112
+ atd.elements.each {|x| x.update_node(node, :only)}
113
+ end
114
+ end
115
+ }
116
+ end
117
+
118
+ rule except_attributes_decl
119
+ (allow_space atd:drop_all_decl+
120
+ / ('except'/'drop') atd:except_attribute_decl+) {
121
+ def update_node(node)
122
+ if atd.elements
123
+ atd.elements.each {|x| x.update_node(node, :except)}
124
+ end
125
+ end
126
+ }
127
+ end
128
+
129
+ rule pick_all_decl
130
+ 'pick' must_space 'all' {
131
+ def update_node(node, _)
132
+ node.all_attributes!
133
+ end
134
+ }
135
+ end
136
+
137
+ rule drop_all_decl
138
+ 'drop' must_space 'all' {
139
+ def update_node(node, _)
140
+ node.no_attributes!
141
+ end
142
+ }
143
+ end
144
+
145
+
146
+ rule only_attribute_decl
147
+ allow_space '.' term label:label_decl? {
148
+ def update_node(node, key)
149
+ m = Tequila::Node::Attribute.new(term.text_value)
150
+ if label.elements
151
+ m.label = label.term.text_value
152
+ end
153
+ node.add_attribute(key, m)
154
+ end
155
+ }
156
+ end
157
+
158
+ rule except_attribute_decl
159
+ allow_space '.' term {
160
+ def update_node(node, key)
161
+ m = Tequila::Node::Attribute.new(term.text_value)
162
+ node.add_attribute(key, m)
163
+ end
164
+ }
165
+ end
166
+
167
+ rule attribute_decl
168
+ allow_space '.' term label:label_decl? {
169
+ def update_node(node, key)
170
+ m = Tequila::Node::Attribute.new(term.text_value)
171
+ if label.elements
172
+ m.label = label.term.text_value
173
+ end
174
+ node.add_attribute(key, m)
175
+ end
176
+ }
177
+ end
178
+
179
+ rule methods_decl
180
+ allow_space ':'? 'methods' mtd:(method_decl)+ {
181
+ def update_node(node)
182
+ mtd.elements.each {|x| x.update_node(node)}
183
+ end
184
+ }
185
+ end
186
+
187
+ rule method_decl
188
+ allow_space '.' term params:('(' allow_space first_param:(!(','/')') .)+ allow_space rest_params:(',' allow_space param:(!(','/')') .)+ allow_space)* ')')? label:label_decl? {
189
+ def update_node(node)
190
+ m = Tequila::Node::Method.new(term.text_value)
191
+
192
+ if params.elements
193
+ m.params = parameters
194
+ end
195
+
196
+ if label.elements
197
+ m.label = label.term.text_value
198
+ end
199
+ node.methods += [m]
200
+ end
201
+
202
+ def parameters
203
+ [params.first_param.text_value] + params.rest_params.elements.map{|p| p.param.text_value}
204
+ end
205
+
206
+ }
207
+ end
208
+
209
+ rule statics_decl
210
+ allow_space ':'? 'static' std:(static_decl)+ {
211
+ def update_node(node)
212
+ std.elements.each {|x| x.update_node(node)}
213
+ end
214
+ }
215
+ end
216
+
217
+ rule static_decl
218
+ allow_space label:term label_decl {
219
+ def update_node(node)
220
+ node.statics += [Tequila::Node::Static.new(label.text_value, label_decl.term.text_value)]
221
+ end
222
+ }
223
+ end
224
+
225
+ rule code_decl
226
+ allow_space ':'? 'code' label_decl code:(!eob . )* code_end {
227
+ def update_node(node)
228
+ node.code_blocks += [Tequila::Node::CodeBlock.new(label_decl.term.text_value, code.text_value)]
229
+ end
230
+ }
231
+ end
232
+
233
+ rule label_decl
234
+ allow_space ('=>'/'label') allow_space data_mark? term
235
+ end
236
+
237
+ rule code_end
238
+ eob
239
+ end
240
+
241
+ rule eob #end of block
242
+ allow_space 'end'
243
+ end
244
+
245
+ rule term
246
+ [a-zA-Z0-9_\?\!]+
247
+ end
248
+
249
+ rule must_space
250
+ white_space+
251
+ end
252
+
253
+ rule allow_space
254
+ white_space*
255
+ end
256
+
257
+ rule white_space
258
+ [ \n\r]
259
+ end
260
+
261
+ rule data_mark
262
+ "'"
263
+ end
264
+ end
265
+
266
+