olfactory 0.0.1 → 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.
- data/.rspec +2 -0
- data/README.md +432 -11
- data/lib/olfactory/template.rb +200 -42
- data/lib/olfactory/template_definition.rb +93 -28
- data/lib/olfactory/version.rb +1 -1
- data/lib/olfactory.rb +5 -0
- data/olfactory.gemspec +4 -4
- data/spec/olfactory/template_spec.rb +1080 -0
- data/spec/spec_helper.rb +8 -3
- data/spec/support/saveable.rb +14 -0
- metadata +21 -16
data/lib/olfactory/template.rb
CHANGED
@@ -9,6 +9,7 @@ module Olfactory
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def build(block, options = {})
|
12
|
+
self.add_defaults(:before) if options[:defaults].nil? || options[:defaults]
|
12
13
|
if block # Block can be nil (when we want only defaults)
|
13
14
|
if options[:value]
|
14
15
|
block.call(self, options[:value])
|
@@ -16,61 +17,189 @@ module Olfactory
|
|
16
17
|
block.call(self)
|
17
18
|
end
|
18
19
|
end
|
19
|
-
if options[:defaults].nil? || options[:defaults]
|
20
|
-
# Only set defaults if configuration wasn't specified
|
21
|
-
self.add_defaults
|
22
|
-
end
|
20
|
+
self.add_defaults(:after) if options[:defaults].nil? || options[:defaults]
|
23
21
|
|
24
22
|
self
|
25
23
|
end
|
26
24
|
|
25
|
+
def save!
|
26
|
+
# Items, then subtemplates
|
27
|
+
[self.definition.t_items, self.definition.t_subtemplates].each do |field_group_definitions|
|
28
|
+
field_group_definitions.each do |field_name, field_definition|
|
29
|
+
if field_value = self[field_name]
|
30
|
+
if field_definition[:collection] && field_definition[:collection] <= Array
|
31
|
+
field_value.each { |value| value.save! if value.respond_to?(:save!) }
|
32
|
+
elsif field_definition[:collection] && field_definition[:collection] <= Hash
|
33
|
+
field_value.values.each { |value| value.save! if value.respond_to?(:save!) }
|
34
|
+
else
|
35
|
+
field_value.save! if field_value.respond_to?(:save!)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
27
42
|
def method_missing(meth, *args, &block)
|
28
43
|
# Explicit fields
|
29
|
-
if
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
44
|
+
if field_definition = self.definition.find_field_definition(meth)
|
45
|
+
populate_field(field_definition, meth, args, block)
|
46
|
+
else
|
47
|
+
super # Unknown method
|
48
|
+
end
|
49
|
+
end
|
50
|
+
def can_set_field?(meth)
|
51
|
+
!(self.default_mode && self.has_key?(meth))
|
52
|
+
end
|
53
|
+
def extract_variable_name(args)
|
54
|
+
variable_name = args.first
|
55
|
+
raise "Must provide a name when adding to a named field!" if variable_name.nil?
|
56
|
+
variable_name
|
57
|
+
end
|
58
|
+
def populate_field(field_definition, meth, args, block)
|
59
|
+
if field_definition[:type] == :macro
|
60
|
+
field_value = build_macro(field_definition, args, block)
|
61
|
+
do_not_set_value = true
|
62
|
+
elsif field_definition[:type] == :subtemplate && can_set_field?(meth)
|
63
|
+
subtemplate_name = field_definition.has_key?(:template) ? field_definition[:template] : field_definition[:name]
|
64
|
+
subtemplate_definition = Olfactory.templates[subtemplate_name]
|
65
|
+
subtemplate_definition ||= Olfactory.templates[field_definition[:singular]]
|
66
|
+
if subtemplate_definition
|
67
|
+
if field_definition[:collection] && field_definition[:collection] <= Array
|
68
|
+
# Embeds many
|
69
|
+
if meth == field_definition[:singular]
|
70
|
+
# Singular
|
71
|
+
grammar = :singular
|
72
|
+
preset_name = args.first
|
73
|
+
|
74
|
+
field_value = build_one_subtemplate(subtemplate_definition, preset_name, block)
|
75
|
+
else
|
76
|
+
# Plural
|
77
|
+
grammar = :plural
|
78
|
+
quantity = args.detect { |value| value.class <= Integer }
|
79
|
+
preset_name = args.detect { |value| value != quantity }
|
80
|
+
|
81
|
+
field_value = build_many_subtemplates(subtemplate_definition, quantity, preset_name, block)
|
82
|
+
do_not_set_value if field_value.nil?
|
83
|
+
end
|
84
|
+
elsif field_definition[:collection] && field_definition[:collection] <= Hash
|
85
|
+
# Embeds many named
|
86
|
+
if meth == field_definition[:singular]
|
87
|
+
# Singular
|
88
|
+
grammar = :singular
|
89
|
+
variable_name = extract_variable_name(args)
|
90
|
+
args = args[1..(args.size-1)]
|
91
|
+
preset_name = args.first
|
92
|
+
|
93
|
+
field_value = build_one_subtemplate(subtemplate_definition, preset_name, block)
|
94
|
+
do_not_set_value if field_value.nil? # || field_value.empty?
|
50
95
|
else
|
51
|
-
|
96
|
+
# Plural
|
97
|
+
grammar = :plural
|
98
|
+
do_not_set_value = true
|
99
|
+
# UNSUPPORTED
|
52
100
|
end
|
53
101
|
else
|
54
|
-
|
102
|
+
# Embeds one
|
103
|
+
preset_name = args.first
|
104
|
+
|
105
|
+
field_value = build_one_subtemplate(subtemplate_definition, preset_name, block)
|
106
|
+
do_not_set_value if field_value.nil?
|
107
|
+
end
|
108
|
+
else
|
109
|
+
raise "Could not find a template matching '#{subtemplate_name}'!"
|
110
|
+
end
|
111
|
+
elsif field_definition[:type] == :item && can_set_field?(meth)
|
112
|
+
if field_definition[:collection] && field_definition[:collection] <= Array
|
113
|
+
# Has many
|
114
|
+
if meth == field_definition[:singular]
|
115
|
+
# Singular
|
116
|
+
grammar = :singular
|
117
|
+
obj = args.count == 1 ? args.first : args
|
118
|
+
|
119
|
+
field_value = build_one_item(field_definition, obj, block)
|
120
|
+
do_not_set_value = true if field_value.nil?
|
121
|
+
else
|
122
|
+
# Plural
|
123
|
+
grammar = :plural
|
124
|
+
quantity = args.first if block && args.first.class <= Integer
|
125
|
+
arr = args.first if args.count == 1 && args.first.class <= Array
|
126
|
+
|
127
|
+
field_value = build_many_items(field_definition, quantity, arr, args, block)
|
128
|
+
do_not_set_value = true if field_value.empty?
|
129
|
+
end
|
130
|
+
elsif field_definition[:collection] && field_definition[:collection] <= Hash
|
131
|
+
# Has many named
|
132
|
+
if meth == field_definition[:singular]
|
133
|
+
# Singular
|
134
|
+
grammar = :singular
|
135
|
+
variable_name = extract_variable_name(args)
|
136
|
+
args = args[1..(args.size-1)]
|
137
|
+
obj = args.first
|
138
|
+
|
139
|
+
field_value = build_one_item(field_definition, obj, block)
|
140
|
+
do_not_set_value = true if field_value.nil?
|
141
|
+
else
|
142
|
+
# Plural
|
143
|
+
grammar = :plural
|
144
|
+
hash = args.first if args.first.class <= Hash
|
145
|
+
|
146
|
+
# Hash
|
147
|
+
if hash
|
148
|
+
field_value = hash
|
149
|
+
end
|
150
|
+
do_not_set_value = true if field_value.nil? || field_value.empty?
|
55
151
|
end
|
152
|
+
else
|
153
|
+
# Has one
|
154
|
+
obj = args.first
|
155
|
+
|
156
|
+
field_value = build_one_item(field_definition, obj, block)
|
56
157
|
end
|
57
|
-
# No field definition
|
58
158
|
else
|
59
|
-
|
159
|
+
do_not_set_value = true
|
160
|
+
end
|
161
|
+
|
162
|
+
# Add field value to template
|
163
|
+
if !do_not_set_value
|
164
|
+
if field_definition[:collection]
|
165
|
+
self[field_definition[:name]] ||= field_definition[:collection].new
|
166
|
+
if field_definition[:collection] <= Array
|
167
|
+
if grammar == :plural
|
168
|
+
return_value = self[field_definition[:name]].concat(field_value)
|
169
|
+
elsif grammar == :singular
|
170
|
+
return_value = self[field_definition[:name]] << field_value
|
171
|
+
end
|
172
|
+
elsif field_definition[:collection] <= Hash
|
173
|
+
if grammar == :plural
|
174
|
+
return_value = self[field_definition[:name]].merge!(field_value)
|
175
|
+
elsif grammar == :singular
|
176
|
+
return_value = self[field_definition[:name]][variable_name] = field_value
|
177
|
+
end
|
178
|
+
end
|
179
|
+
else
|
180
|
+
return_value = self[field_definition[:name]] = field_value
|
181
|
+
end
|
60
182
|
end
|
183
|
+
return_value
|
61
184
|
end
|
62
185
|
def transient(name, value)
|
63
186
|
self.transients[name] = value if !(self.default_mode && self.transients.has_key?(name))
|
64
187
|
end
|
65
|
-
def add_defaults
|
188
|
+
def add_defaults(mode)
|
66
189
|
# Prevents overwrites of custom values by defaults
|
67
190
|
self.default_mode = true # Hackish for sure, but its efficient...
|
68
191
|
|
69
|
-
|
192
|
+
case mode
|
193
|
+
when :before
|
194
|
+
default_definition = definition.t_before
|
195
|
+
when :after
|
196
|
+
default_definition = definition.t_after
|
197
|
+
end
|
198
|
+
|
70
199
|
if default_definition[:evaluator]
|
71
200
|
default_definition[:evaluator].call(self)
|
72
|
-
elsif default_definition[:
|
73
|
-
preset_definition = definition.find_preset_definition(default_definition[:
|
201
|
+
elsif default_definition[:preset]
|
202
|
+
preset_definition = definition.find_preset_definition(default_definition[:preset])
|
74
203
|
preset_definition[:evaluator].call(self)
|
75
204
|
end
|
76
205
|
|
@@ -81,22 +210,51 @@ module Olfactory
|
|
81
210
|
macro_definition[:evaluator].call(self, *args)
|
82
211
|
end
|
83
212
|
end
|
84
|
-
def
|
213
|
+
def build_one_subtemplate(subtemplate_definition, preset_name, block)
|
214
|
+
# Block
|
85
215
|
if block
|
86
216
|
subtemplate_definition.build(block, :transients => self.transients)
|
217
|
+
# Preset Name
|
218
|
+
elsif preset_name
|
219
|
+
subtemplate_definition.build_preset(preset_name, 1, :transients => self.transients)
|
220
|
+
# Default (nothing)
|
221
|
+
else
|
222
|
+
subtemplate_definition.build(nil, :transients => self.transients)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
def build_many_subtemplates(subtemplate_definition, quantity, preset_name, block)
|
226
|
+
# Integer, Block
|
227
|
+
if quantity && block
|
228
|
+
Array.new(quantity) { subtemplate_definition.build(block, :transients => self.transients) }
|
229
|
+
# Integer, Preset Name
|
230
|
+
elsif quantity && preset_name
|
231
|
+
subtemplate_definition.build_preset(preset_name, quantity, :transients => self.transients)
|
232
|
+
# Integer
|
233
|
+
elsif quantity
|
234
|
+
Array.new(quantity) { subtemplate_definition.build(nil, :transients => self.transients) }
|
87
235
|
else
|
88
|
-
|
236
|
+
nil
|
89
237
|
end
|
90
238
|
end
|
91
|
-
def
|
239
|
+
def build_one_item(item_definition, obj, block)
|
92
240
|
if block
|
93
|
-
block.call
|
241
|
+
block.call
|
242
|
+
elsif obj
|
243
|
+
obj
|
94
244
|
else
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
245
|
+
nil
|
246
|
+
end
|
247
|
+
end
|
248
|
+
def build_many_items(item_definition, quantity, arr, args, block)
|
249
|
+
# Integer, Block
|
250
|
+
if quantity && block
|
251
|
+
Array.new(quantity) { block.call }
|
252
|
+
# Array
|
253
|
+
elsif arr
|
254
|
+
arr
|
255
|
+
# Object, Object...
|
256
|
+
else
|
257
|
+
args
|
100
258
|
end
|
101
259
|
end
|
102
260
|
end
|
@@ -1,82 +1,147 @@
|
|
1
1
|
# -*- encoding : utf-8 -*-
|
2
2
|
module Olfactory
|
3
3
|
class TemplateDefinition
|
4
|
-
attr_accessor :t_items, :t_subtemplates, :t_macros, :t_presets, :
|
4
|
+
attr_accessor :t_items, :t_subtemplates, :t_macros, :t_presets, :t_before, :t_after
|
5
5
|
|
6
6
|
def initialize
|
7
7
|
self.t_items = {}
|
8
8
|
self.t_subtemplates = {}
|
9
9
|
self.t_macros = {}
|
10
10
|
self.t_presets = {}
|
11
|
-
self.
|
11
|
+
self.t_before = {}
|
12
|
+
self.t_after = {}
|
12
13
|
end
|
13
14
|
|
14
15
|
def build(block, options = {})
|
15
|
-
if options[:preset]
|
16
|
-
self.build_preset(options[:preset], options.reject { |k,v|
|
16
|
+
if options[:preset] || options[:quantity]
|
17
|
+
self.build_preset(options[:preset], (options[:quantity] || 1), options.reject { |k,v| [:preset, :quantity].include?(k) })
|
17
18
|
else
|
18
19
|
new_template = Template.new(self, options)
|
19
20
|
new_template.build(block, options)
|
20
21
|
end
|
21
22
|
new_template
|
22
23
|
end
|
23
|
-
def build_preset(preset_name, options = {})
|
24
|
-
if
|
24
|
+
def build_preset(preset_name, quantity, options = {})
|
25
|
+
raise "Quantity must be an integer!" if !(quantity.class <= Integer)
|
26
|
+
|
27
|
+
if quantity > 1
|
25
28
|
# Build multiple
|
26
|
-
|
27
|
-
|
28
|
-
# Build single
|
29
|
-
new_template = Template.new(self, options)
|
30
|
-
preset_block = preset_definition[:evaluator]
|
31
|
-
if preset_definition[:preset_name].class == Regexp
|
32
|
-
new_template.build(preset_block, options.merge(:value => preset_name))
|
29
|
+
if preset_name
|
30
|
+
Array.new(quantity) { self.build_preset(preset_name, 1, options) }
|
33
31
|
else
|
34
|
-
|
32
|
+
Array.new(quantity) { self.build(nil, options) }
|
35
33
|
end
|
36
|
-
|
37
|
-
|
34
|
+
elsif quantity == 1
|
35
|
+
if preset_definition = self.find_preset_definition(preset_name)
|
36
|
+
# Build single
|
37
|
+
new_template = Template.new(self, options)
|
38
|
+
preset_block = preset_definition[:evaluator]
|
39
|
+
if preset_definition[:regexp]
|
40
|
+
new_template.build(preset_block, options.merge(:value => preset_name))
|
41
|
+
else
|
42
|
+
new_template.build(preset_block, options)
|
43
|
+
end
|
44
|
+
elsif preset_name.nil?
|
45
|
+
self.build(nil, options)
|
46
|
+
else
|
47
|
+
raise "Missing preset matching '#{preset_name}' for template!"
|
48
|
+
end
|
49
|
+
else quantity <= 0
|
50
|
+
raise "Can't build 0 or less items!"
|
38
51
|
end
|
39
52
|
end
|
40
53
|
|
41
|
-
def
|
42
|
-
|
54
|
+
def find_field_definition(name)
|
55
|
+
definition = find_macro_definition(name)
|
56
|
+
definition ||= find_subtemplate_definition(name)
|
57
|
+
definition ||= find_item_definition(name)
|
58
|
+
definition
|
59
|
+
end
|
60
|
+
|
61
|
+
def find_definition_in_list(name, definition_list)
|
62
|
+
definition = definition_list[name]
|
63
|
+
definition ||= definition_list.values.detect do |v|
|
64
|
+
v.has_key?(:alias) && (v[:alias] == name || (v.respond_to?("include") && v.include?(name)))
|
65
|
+
end
|
66
|
+
definition
|
67
|
+
end
|
68
|
+
|
69
|
+
def find_macro_definition(name)
|
70
|
+
self.find_definition_in_list(name, self.t_macros)
|
71
|
+
end
|
72
|
+
|
73
|
+
def find_subtemplate_definition(name)
|
74
|
+
definition = self.find_definition_in_list(name, self.t_subtemplates)
|
75
|
+
definition ||= self.t_subtemplates.values.detect { |v| v.has_key?(:singular) && v[:singular] == name }
|
76
|
+
definition
|
77
|
+
end
|
78
|
+
|
79
|
+
def find_item_definition(name)
|
80
|
+
definition = self.find_definition_in_list(name, self.t_items)
|
81
|
+
definition ||= self.t_items.values.detect { |v| v.has_key?(:singular) && v[:singular] == name }
|
82
|
+
definition
|
83
|
+
end
|
84
|
+
|
85
|
+
def find_preset_definition(name)
|
86
|
+
preset_definition = self.find_definition_in_list(name, self.t_presets)
|
43
87
|
if preset_definition.nil?
|
44
88
|
# Try to find a regexp named preset that matches
|
45
|
-
|
46
|
-
preset_definition = self.t_presets[
|
89
|
+
name = self.t_presets.keys.detect { |p_name| p_name.class == Regexp && p_name.match(name.to_s) }
|
90
|
+
preset_definition = self.t_presets[name] if name
|
47
91
|
end
|
48
92
|
preset_definition
|
49
93
|
end
|
50
94
|
|
51
95
|
# Defines a value holding field
|
52
96
|
def has_one(name, options = {}, &block)
|
53
|
-
self.t_items[name] = {
|
97
|
+
self.t_items[name] = { :type => :item,
|
98
|
+
:name => name,
|
99
|
+
:evaluator => block
|
100
|
+
}.merge(options)
|
54
101
|
end
|
55
102
|
def has_many(name, options = {}, &block)
|
56
|
-
self.
|
103
|
+
self.has_one(name, options.merge(:collection => (options[:named] ? Hash : Array)), &block)
|
57
104
|
end
|
58
105
|
|
59
106
|
# Defines a hash-holding field
|
60
107
|
def embeds_one(name, options = {}, &block)
|
61
|
-
self.t_subtemplates[name] = { :
|
108
|
+
self.t_subtemplates[name] = { :type => :subtemplate,
|
109
|
+
:name => name,
|
110
|
+
:evaluator => block
|
111
|
+
}.merge(options)
|
62
112
|
end
|
63
113
|
def embeds_many(name, options = {}, &block)
|
64
|
-
self.
|
114
|
+
self.embeds_one(name, options.merge(:collection => (options[:named] ? Hash : Array)), &block)
|
65
115
|
end
|
66
116
|
|
67
117
|
# Defines a macro
|
68
118
|
def macro(name, options = {}, &block)
|
69
|
-
self.t_macros[name] = { :
|
119
|
+
self.t_macros[name] = { :type => :macro,
|
120
|
+
:name => name,
|
121
|
+
:evaluator => block
|
122
|
+
}.merge(options)
|
70
123
|
end
|
71
124
|
|
72
125
|
# Defines a preset of values
|
73
126
|
def preset(name, options = {}, &block)
|
74
|
-
self.t_presets[name] = {
|
127
|
+
self.t_presets[name] = { :type => :preset,
|
128
|
+
:name => name,
|
129
|
+
:evaluator => block
|
130
|
+
}.merge(options)
|
131
|
+
self.t_presets[name] = self.t_presets[name].merge(:regexp => name) if name.class <= Regexp
|
75
132
|
end
|
76
133
|
|
77
134
|
# Defines defaults
|
78
|
-
def
|
79
|
-
self.
|
135
|
+
def before(options = {}, &block)
|
136
|
+
self.t_before = { :type => :default,
|
137
|
+
:evaluator => block
|
138
|
+
}.merge(options)
|
139
|
+
self.t_before = self.t_before.merge(:preset => options[:preset]) if options[:preset]
|
140
|
+
end
|
141
|
+
def after(options = {}, &block)
|
142
|
+
self.t_after = { :type => :default,
|
143
|
+
:evaluator => block }.merge(options)
|
144
|
+
self.t_after = self.t_after.merge(:preset => options[:preset]) if options[:preset]
|
80
145
|
end
|
81
146
|
end
|
82
147
|
end
|
data/lib/olfactory/version.rb
CHANGED
data/lib/olfactory.rb
CHANGED
@@ -17,6 +17,11 @@ module Olfactory
|
|
17
17
|
def self.build_template(name, options = {}, &block)
|
18
18
|
self.templates[name].build(block, options)
|
19
19
|
end
|
20
|
+
def self.create_template(name, options = {}, &block)
|
21
|
+
template = self.templates[name].build(block, options)
|
22
|
+
template.save!
|
23
|
+
template
|
24
|
+
end
|
20
25
|
|
21
26
|
def self.reload
|
22
27
|
@@templates = {}
|
data/olfactory.gemspec
CHANGED
@@ -16,8 +16,8 @@ Gem::Specification.new do |s|
|
|
16
16
|
s.require_paths = ['lib']
|
17
17
|
s.required_ruby_version = Gem::Requirement.new(">= 1.9.2")
|
18
18
|
|
19
|
-
s.add_development_dependency("rspec",
|
20
|
-
s.add_development_dependency("pry")
|
21
|
-
s.add_development_dependency("pry-nav")
|
22
|
-
s.add_development_dependency("pry-stack_explorer")
|
19
|
+
s.add_development_dependency("rspec", "~> 3.1")
|
20
|
+
s.add_development_dependency("pry", "~> 0.10")
|
21
|
+
s.add_development_dependency("pry-nav", "~> 0.2")
|
22
|
+
s.add_development_dependency("pry-stack_explorer", "~> 0.4.9")
|
23
23
|
end
|