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 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: Caesar::Config class for loading DSLs as config files.
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 Caesar::Glass to make it
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.4
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(:path => conffile)
126
+ @config = PartyConfig.new(conffile)
127
127
 
128
- p @config.food.order.call # => 8kg
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
- class Food < Caesars #:nodoc: all
111
+ require 'caesars'
112
+
113
+ class Food < Caesars
112
114
  chill :order
113
115
  end
114
- class Drink < Caesars #:nodoc: all
116
+ class Drink < Caesars
115
117
  end
116
118
 
117
- class PartyConfig < Caesars::Config #:nodoc: all
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(:path => conffile)
125
+ @config = PartyConfig.new(conffile)
124
126
 
125
- p @config.food.order.call # => 8kg
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.2"
4
- s.date = %q{2009-03-05}
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.4.2"
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
- str = criteria.collect { |v| "[:#{v}]" }.join << "[:#{att}]"
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
- def self.chill(meth)
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 #{meth}(*names,&b)
109
- # caesar.toplevel.unnamed_chilled_attribute
110
- return @caesars_pointer[:'#{meth}'] if names.empty? && b.nil?
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
- names << :'#{meth}' if names.empty?
114
-
115
- names.each do |name|
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[:'#{meth}']
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 9.oz
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
- i = instance_variable_get(varname)
148
- else
149
- i = instance_variable_set(varname, #{modname.to_s}.new(name))
150
- i.instance_eval(&b)
151
- end
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 :path
238
+ attr_accessor :paths
239
+ attr_accessor :options
179
240
  attr_accessor :verbose
180
241
 
181
242
  @@glasses = []
182
243
 
183
- def initialize(args={:path=>'', :verbose=>false})
184
- args.each_pair do |n,v|
185
- self.send("#{n}=", v)
186
- end
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 [](name)
196
- self.send(name) if respond_to?(name)
197
- end
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
- def keys
200
- @@glasses.collect { |glass| glass.methname }
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
- if exists?
206
- puts "Loading config from #{@path}" if @verbose
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 @path
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 #{@path}."
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 exists?
226
- File.exists?(@path)
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.2
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-05 00:00:00 -05:00
12
+ date: 2009-03-11 00:00:00 -04:00
13
13
  default_executable:
14
14
  dependencies: []
15
15