caesars 0.4.2 → 0.5.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/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