caesars 0.4.2 → 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES.txt +23 -2
- data/README.rdoc +8 -4
- data/bin/example +10 -5
- data/caesars.gemspec +2 -2
- data/lib/caesars.rb +156 -50
- metadata +2 -2
data/CHANGES.txt
CHANGED
@@ -1,6 +1,27 @@
|
|
1
1
|
CAESAR -- CHANGES
|
2
2
|
|
3
3
|
|
4
|
+
#### 0.5.1 (2009-03-11) ###############################
|
5
|
+
|
6
|
+
* FIX: Method-syntax was broken for attributes of top level method
|
7
|
+
* FIX: Caesars::Hash#refresh was setting @options to nil
|
8
|
+
* UPDATED: docs and bin/example to reflect Caesars::Hash changes.
|
9
|
+
* FIX: instance_variables in Ruby 1.9.1 returns Symbols
|
10
|
+
|
11
|
+
#### 0.5.0 (2009-03-11) ###############################
|
12
|
+
|
13
|
+
* FIX: find_deferred now gracefully handles nil errors
|
14
|
+
* NEW: empty? method in Caesars::Config
|
15
|
+
* NEW: post processing hook in Caesars::Config#refresh
|
16
|
+
* NEW: Caesars::Hash#to_hash now recursively casts children to ::Hash.
|
17
|
+
* FIX: Added Array support to Caesars::Hash
|
18
|
+
* NEW: Setters for Caesars attributes
|
19
|
+
* NEW: Caesars::Config supports multiple config files
|
20
|
+
* NEW: Top level methods used more than once now merges values
|
21
|
+
rather than overwrites.
|
22
|
+
* NEW: Caesars::Config supports reloading config files on the fly
|
23
|
+
|
24
|
+
|
4
25
|
#### 0.4.2 (2009-03-05) ###############################
|
5
26
|
|
6
27
|
* FIX: missing bin/party.conf in gem release
|
@@ -14,11 +35,11 @@ food :extra do; end; # => food_extra
|
|
14
35
|
|
15
36
|
* CHANGE: Removed bloody method. We now parse blocks immediately.
|
16
37
|
* CHANGE: Renamed virgin method to chill.
|
17
|
-
* NEW:
|
38
|
+
* NEW: Caesars::Config class for loading DSLs as config files.
|
18
39
|
See Example 3.
|
19
40
|
* NEW: Added find_deferred method to automatically jump up the
|
20
41
|
heirarchy when looking for a specific attribute.
|
21
|
-
* NEW: Added to_hash and [] methods to
|
42
|
+
* NEW: Added to_hash and [] methods to Caesars to make it
|
22
43
|
more hashlike.
|
23
44
|
* FIX: "chilled" attributes weren't available by method name
|
24
45
|
|
data/README.rdoc
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
= Caesars - v0.
|
1
|
+
= Caesars - v0.5
|
2
2
|
|
3
3
|
A simple class for rapid DSL prototyping in Ruby.
|
4
4
|
|
@@ -123,13 +123,17 @@ Or for GitHub fans:
|
|
123
123
|
end
|
124
124
|
|
125
125
|
conffile = File.join(File.dirname(__FILE__), 'party.conf')
|
126
|
-
@config = PartyConfig.new(
|
126
|
+
@config = PartyConfig.new(conffile)
|
127
127
|
|
128
|
-
p @config.food.order.call # =>
|
128
|
+
p @config.food.order.call # => 10kg
|
129
129
|
p @config[:drink][:wine] # => 12L
|
130
130
|
p @config # => <PartyConfig:0x3f780c ...>
|
131
131
|
p @config.keys # => [:food, :drink]
|
132
|
-
|
132
|
+
|
133
|
+
# [... make changes to party.conf ...]
|
134
|
+
|
135
|
+
@config.refresh
|
136
|
+
|
133
137
|
== More Info
|
134
138
|
|
135
139
|
* GitHub[http://github.com/delano/caesar]
|
data/bin/example
CHANGED
@@ -108,23 +108,28 @@ p @staff_fte.splashdown.keys
|
|
108
108
|
# EXAMPLE 3 -- External Config file
|
109
109
|
#
|
110
110
|
|
111
|
-
|
111
|
+
require 'caesars'
|
112
|
+
|
113
|
+
class Food < Caesars
|
112
114
|
chill :order
|
113
115
|
end
|
114
|
-
class Drink < Caesars
|
116
|
+
class Drink < Caesars
|
115
117
|
end
|
116
118
|
|
117
|
-
class PartyConfig < Caesars::Config
|
119
|
+
class PartyConfig < Caesars::Config
|
118
120
|
dsl Food::DSL
|
119
121
|
dsl Drink::DSL
|
120
122
|
end
|
121
123
|
|
122
124
|
conffile = File.join(File.dirname(__FILE__), 'party.conf')
|
123
|
-
@config = PartyConfig.new(
|
125
|
+
@config = PartyConfig.new(conffile)
|
124
126
|
|
125
|
-
p @config.food.order.call # =>
|
127
|
+
p @config.food.order.call # => 10kg
|
126
128
|
p @config[:drink][:wine] # => 12L
|
127
129
|
p @config # => <PartyConfig:0x3f780c ...>
|
128
130
|
p @config.keys # => [:food, :drink]
|
129
131
|
|
132
|
+
# [... make changes to party.conf ...]
|
133
|
+
|
134
|
+
@config.refresh
|
130
135
|
|
data/caesars.gemspec
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
@spec = Gem::Specification.new do |s|
|
2
2
|
s.name = %q{caesars}
|
3
|
-
s.version = "0.
|
4
|
-
s.date = %q{2009-03-
|
3
|
+
s.version = "0.5.1"
|
4
|
+
s.date = %q{2009-03-11}
|
5
5
|
s.specification_version = 1 if s.respond_to? :specification_version=
|
6
6
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
7
7
|
|
data/lib/caesars.rb
CHANGED
@@ -7,7 +7,7 @@
|
|
7
7
|
# See bin/example
|
8
8
|
#
|
9
9
|
class Caesars
|
10
|
-
VERSION = "0.
|
10
|
+
VERSION = "0.5.1"
|
11
11
|
# A subclass of ::Hash that provides method names for hash parameters.
|
12
12
|
# It's like a lightweight OpenStruct.
|
13
13
|
# ch = Caesars::Hash[:tabasco => :lots!]
|
@@ -17,13 +17,29 @@ class Caesars
|
|
17
17
|
def method_missing(meth)
|
18
18
|
self[meth] if self.has_key?(meth)
|
19
19
|
end
|
20
|
+
|
21
|
+
# Returns a clone of itself and all children cast as ::Hash objects
|
22
|
+
def to_hash(hash=self)
|
23
|
+
return hash unless hash.is_a?(Caesars::Hash) # nothing to do
|
24
|
+
target = ::Hash[dup]
|
25
|
+
hash.keys.each do |key|
|
26
|
+
if hash[key].is_a? Caesars::Hash
|
27
|
+
target[key] = hash[key].to_hash
|
28
|
+
next
|
29
|
+
elsif hash[key].is_a? Array
|
30
|
+
target[key] = hash[key].collect { |h| to_hash(h) }
|
31
|
+
next
|
32
|
+
end
|
33
|
+
target[key] = hash[key]
|
34
|
+
end
|
35
|
+
target
|
36
|
+
end
|
37
|
+
|
20
38
|
end
|
21
39
|
|
22
40
|
# An instance of Caesars::Hash which contains the data specified by your DSL
|
23
41
|
attr_accessor :caesars_properties
|
24
42
|
|
25
|
-
@@caesars_chilled = []
|
26
|
-
|
27
43
|
|
28
44
|
def initialize(name=nil)
|
29
45
|
@caesars_name = name if name
|
@@ -37,12 +53,13 @@ class Caesars
|
|
37
53
|
end
|
38
54
|
|
39
55
|
def to_hash
|
40
|
-
@caesars_properties
|
56
|
+
@caesars_properties.to_hash
|
41
57
|
end
|
42
58
|
|
43
59
|
# Look for an attribute, bubbling up to the parent if it's not found
|
44
60
|
# +criteria+ is an array of attribute names, orders according to their
|
45
|
-
# relationship.
|
61
|
+
# relationship. The last element is considered to the desired attribute.
|
62
|
+
# It can be an array.
|
46
63
|
#
|
47
64
|
# # Looking for 'attribute'.
|
48
65
|
# # First checks at @caesars_properties[grandparent][parent][attribute]
|
@@ -50,7 +67,7 @@ class Caesars
|
|
50
67
|
# # Finally, @caesars_properties[attribute]
|
51
68
|
# find_deferred('grandparent', 'parent', 'attribute')
|
52
69
|
#
|
53
|
-
# Returns the attribute if found or nil
|
70
|
+
# Returns the attribute if found or nil.
|
54
71
|
#
|
55
72
|
def find_deferred(*criteria)
|
56
73
|
# This is a nasty implementation. Sorry me! I'll enjoy a few
|
@@ -58,8 +75,7 @@ class Caesars
|
|
58
75
|
att = criteria.pop
|
59
76
|
val = nil
|
60
77
|
while !criteria.empty?
|
61
|
-
|
62
|
-
val = eval "@caesars_properties#{str} if defined?(@caesars_properties#{str})"
|
78
|
+
val = find(criteria, att)
|
63
79
|
break if val
|
64
80
|
criteria.pop
|
65
81
|
end
|
@@ -67,19 +83,43 @@ class Caesars
|
|
67
83
|
val = @caesars_properties[att.to_sym] if defined?(@caesars_properties[att.to_sym]) && !val
|
68
84
|
val
|
69
85
|
end
|
70
|
-
|
86
|
+
|
87
|
+
# Looks for the specific attribute specified.
|
88
|
+
# +criteria+ is an array of attribute names, orders according to their
|
89
|
+
# relationship. The last element is considered to the desired attribute.
|
90
|
+
# It can be an array.
|
91
|
+
#
|
92
|
+
# Unlike find_deferred, it will return only the value specified, otherwise nil.
|
93
|
+
def find(*criteria)
|
94
|
+
criteria.flatten! if criteria.first.is_a?(Array)
|
95
|
+
str = criteria.collect { |v| "[:'#{v}']" if v }.join
|
96
|
+
val = eval "@caesars_properties#{str} if defined?(@caesars_properties#{str})"
|
97
|
+
val
|
98
|
+
end
|
99
|
+
|
71
100
|
# Act a bit like a hash for the case:
|
72
101
|
# @subclass[:property]
|
73
102
|
def [](name)
|
74
103
|
return @caesars_properties[name] if @caesars_properties.has_key?(name)
|
75
104
|
return @caesars_properties[name.to_sym] if @caesars_properties.has_key?(name.to_sym)
|
76
105
|
end
|
77
|
-
|
106
|
+
|
107
|
+
# Act a bit like a hash for the case:
|
108
|
+
# @subclass[:property] = value
|
109
|
+
def []=(name, value)
|
110
|
+
@caesars_properties[name] = value
|
111
|
+
end
|
112
|
+
|
78
113
|
# This method handles all of the attributes that do not contain blocks.
|
79
114
|
# It's used in the DSL for handling attributes dyanamically (that weren't defined
|
80
115
|
# previously) and also in subclasses of Caesar for returning the appropriate
|
81
116
|
# attribute values.
|
82
117
|
def method_missing(meth, *args, &b)
|
118
|
+
# Handle the setter, attribute=
|
119
|
+
if meth.to_s =~ /=$/ && @caesars_properties.has_key?(meth.to_s.chop.to_sym)
|
120
|
+
return @caesars_properties[meth.to_s.chop.to_sym] = (args.size == 1) ? args.first : args
|
121
|
+
end
|
122
|
+
|
83
123
|
return @caesars_properties[meth] if @caesars_properties.has_key?(meth) && args.empty? && b.nil?
|
84
124
|
return nil if args.empty? && b.nil?
|
85
125
|
|
@@ -88,13 +128,15 @@ class Caesars
|
|
88
128
|
args << meth if args.empty?
|
89
129
|
args.each do |name|
|
90
130
|
prev = @caesars_pointer
|
131
|
+
#(@caesars_pointer[:"#{meth}_values"] ||= []) << name
|
91
132
|
@caesars_pointer[name] ||= Caesars::Hash.new
|
92
133
|
@caesars_pointer = @caesars_pointer[name]
|
93
134
|
b.call if b
|
94
135
|
@caesars_pointer = prev
|
95
136
|
end
|
96
137
|
|
97
|
-
elsif @caesars_pointer[meth]
|
138
|
+
elsif @caesars_pointer.kind_of?(Hash) && @caesars_pointer[meth]
|
139
|
+
|
98
140
|
@caesars_pointer[meth] = [@caesars_pointer[meth]] unless @caesars_pointer[meth].is_a?(Array)
|
99
141
|
@caesars_pointer[meth] += args
|
100
142
|
elsif !args.empty?
|
@@ -102,25 +144,43 @@ class Caesars
|
|
102
144
|
end
|
103
145
|
|
104
146
|
end
|
105
|
-
|
106
|
-
|
147
|
+
|
148
|
+
# A class method which can be used by subclasses to specify which methods
|
149
|
+
# should delay execution of their blocks. Here's an example:
|
150
|
+
#
|
151
|
+
# class Food < Caesars
|
152
|
+
# chill :count
|
153
|
+
# end
|
154
|
+
#
|
155
|
+
# food do
|
156
|
+
# taste :delicious
|
157
|
+
# count do |items|
|
158
|
+
# puts items + 2
|
159
|
+
# end
|
160
|
+
# end
|
161
|
+
#
|
162
|
+
# @config.food.order.call(3) # => 5
|
163
|
+
#
|
164
|
+
def self.chill(caesars_meth)
|
107
165
|
module_eval %Q{
|
108
|
-
def #{
|
109
|
-
#
|
110
|
-
return @
|
111
|
-
|
166
|
+
def #{caesars_meth}(*caesars_names,&b)
|
167
|
+
# caesars.toplevel.unnamed_chilled_attribute
|
168
|
+
return @caesars_properties[:'#{caesars_meth}'] if @caesars_properties.has_key?(:'#{caesars_meth}') && caesars_names.empty? && b.nil?
|
169
|
+
|
112
170
|
# Use the name of the bloody method if no name is supplied.
|
113
|
-
|
114
|
-
|
115
|
-
|
171
|
+
caesars_names << :'#{caesars_meth}' if caesars_names.empty?
|
172
|
+
|
173
|
+
caesars_names.each do |name|
|
116
174
|
@caesars_pointer[name] = b
|
117
175
|
end
|
118
176
|
|
119
|
-
@caesars_pointer[:'#{
|
177
|
+
@caesars_pointer[:'#{caesars_meth}']
|
120
178
|
end
|
121
179
|
}
|
122
180
|
nil
|
123
181
|
end
|
182
|
+
|
183
|
+
|
124
184
|
# Executes automatically when Caesars is subclassed. This creates the
|
125
185
|
# YourClass::DSL module which contains a single method named after YourClass
|
126
186
|
# that is used to catch the top level DSL method.
|
@@ -129,7 +189,7 @@ class Caesars
|
|
129
189
|
# would be: highball.
|
130
190
|
#
|
131
191
|
# highball :mine do
|
132
|
-
# volume
|
192
|
+
# volume "9oz"
|
133
193
|
# end
|
134
194
|
#
|
135
195
|
def self.inherited(modname)
|
@@ -140,16 +200,16 @@ class Caesars
|
|
140
200
|
name = !args.empty? ? args.first.to_s : nil
|
141
201
|
varname = "@#{meth.to_s}"
|
142
202
|
varname << "_\#{name}" if name
|
203
|
+
inst = instance_variable_get(varname)
|
143
204
|
|
144
205
|
# When the top level DSL method is called without a block
|
145
206
|
# it will return the appropriate instance variable name
|
146
|
-
if b.nil?
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
i
|
207
|
+
return inst if b.nil?
|
208
|
+
|
209
|
+
# Add to existing instance, if it exists. Otherwise create one anew.
|
210
|
+
inst = instance_variable_set(varname, inst || #{modname.to_s}.new(name))
|
211
|
+
inst.instance_eval(&b)
|
212
|
+
inst
|
153
213
|
end
|
154
214
|
|
155
215
|
def self.methname
|
@@ -175,56 +235,102 @@ end
|
|
175
235
|
# p @config.staff # => <Staff:0x7ea450 ... >
|
176
236
|
#
|
177
237
|
class Caesars::Config
|
178
|
-
attr_accessor :
|
238
|
+
attr_accessor :paths
|
239
|
+
attr_accessor :options
|
179
240
|
attr_accessor :verbose
|
180
241
|
|
181
242
|
@@glasses = []
|
182
243
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
244
|
+
# +args+ is a last of config file paths to load into this instance.
|
245
|
+
# If the last argument is a hash, it's assumed to be a list of
|
246
|
+
# options. The available options are:
|
247
|
+
#
|
248
|
+
# <li>:verbose => true or false</li>
|
249
|
+
#
|
250
|
+
def initialize(*args)
|
251
|
+
# We store the options hash b/c we reapply them when we refresh.
|
252
|
+
@options = args.last.is_a?(Hash) ? args.pop : {}
|
253
|
+
@paths = args.empty? ? [] : args
|
254
|
+
@options = {}
|
187
255
|
|
188
256
|
refresh
|
189
257
|
end
|
190
|
-
|
191
|
-
def self.dsl(glass)
|
192
|
-
@@glasses << glass
|
193
|
-
end
|
194
258
|
|
195
|
-
def
|
196
|
-
|
197
|
-
|
259
|
+
def init
|
260
|
+
# Remove instance variables used to populate DSL data
|
261
|
+
instance_variables.each do |varname|
|
262
|
+
next if varname == :'@options' || varname == :'@paths' # Ruby 1.9.1
|
263
|
+
next if varname == '@options' || varname == '@paths' # Ruby 1.8
|
264
|
+
instance_variable_set(varname, nil)
|
265
|
+
end
|
198
266
|
|
199
|
-
|
200
|
-
|
267
|
+
# Re-apply options
|
268
|
+
@options.each_pair do |n,v|
|
269
|
+
self.send("#{n}=", v) if respond_to?("#{n}=")
|
270
|
+
end
|
271
|
+
|
272
|
+
check_paths # make sure files exist
|
273
|
+
end
|
274
|
+
|
275
|
+
# This method is a stub. It gets called by refresh after the
|
276
|
+
# config file has be loaded. You can use it to do some post
|
277
|
+
# processing on the configuration before it's used elsewhere.
|
278
|
+
def postprocess
|
201
279
|
end
|
202
280
|
|
203
281
|
def refresh
|
282
|
+
init
|
204
283
|
|
205
|
-
|
206
|
-
puts "Loading config from #{
|
284
|
+
@paths.each do |path|
|
285
|
+
puts "Loading config from #{path}" if @verbose
|
207
286
|
|
208
287
|
begin
|
209
288
|
@@glasses.each { |glass| extend glass }
|
210
|
-
dsl = File.read
|
289
|
+
dsl = File.read path
|
211
290
|
|
212
291
|
# We're using eval so the DSL code can be executed in this
|
213
292
|
# namespace.
|
214
293
|
eval %Q{
|
215
294
|
#{dsl}
|
216
|
-
}
|
295
|
+
}, binding, __FILE__, __LINE__
|
296
|
+
|
297
|
+
postprocess
|
217
298
|
|
218
299
|
rescue SyntaxError => ex
|
219
|
-
puts "Syntax error in #{
|
300
|
+
puts "Syntax error in #{path}."
|
301
|
+
puts ex.message
|
220
302
|
exit 1
|
221
303
|
end
|
222
304
|
end
|
223
305
|
end
|
224
306
|
|
225
|
-
def
|
226
|
-
|
307
|
+
def check_paths
|
308
|
+
@paths.each do |path|
|
309
|
+
raise "You provided a nil value" unless path
|
310
|
+
raise "Config file #{path} does not exist!" unless File.exists?(path)
|
311
|
+
end
|
227
312
|
end
|
313
|
+
|
314
|
+
|
315
|
+
def empty?
|
316
|
+
keys.each do |obj|
|
317
|
+
return false if self.respond_to?(obj.to_sym)
|
318
|
+
end
|
319
|
+
true
|
320
|
+
end
|
321
|
+
|
322
|
+
def self.dsl(glass)
|
323
|
+
@@glasses << glass
|
324
|
+
end
|
325
|
+
|
326
|
+
def [](name)
|
327
|
+
self.send(name) if respond_to?(name)
|
328
|
+
end
|
329
|
+
|
330
|
+
def keys
|
331
|
+
@@glasses.collect { |glass| glass.methname }
|
332
|
+
end
|
333
|
+
|
228
334
|
end
|
229
335
|
|
230
336
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: caesars
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Delano Mandelbaum
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-03-
|
12
|
+
date: 2009-03-11 00:00:00 -04:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|