caesars 0.3.2 → 0.4.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.
Files changed (6) hide show
  1. data/CHANGES.txt +17 -4
  2. data/README.rdoc +39 -28
  3. data/bin/example +37 -16
  4. data/caesars.gemspec +1 -1
  5. data/lib/caesars.rb +178 -40
  6. metadata +1 -1
data/CHANGES.txt CHANGED
@@ -1,14 +1,27 @@
1
1
  CAESAR -- CHANGES
2
2
 
3
3
 
4
+ #### 0.4.0 (2009-03-05) ###############################
5
+
6
+ * CHANGE: Removed bloody method. We now parse blocks immediately.
7
+ * CHANGE: Renamed virgin method to chill.
8
+ * NEW: Caesar::Config class for loading DSLs as config files.
9
+ See Example 3.
10
+ * NEW: Added find_deferred method to automatically jump up the
11
+ heirarchy when looking for a specific attribute.
12
+ * NEW: Added to_hash and [] methods to Caesar::Glass to make it
13
+ more hashlike.
14
+ * FIX: "chilled" attributes weren't available by method name
15
+
16
+
4
17
  #### 0.3.2 (2009-03-04) ###############################
5
18
 
6
19
  * FIX: Added file and line info for eval code (better debugging).
7
20
  * CHANGE: The top level DSL method names are now determined by
8
- by the class name. Some::ClassName becomes classname.
9
- This is less confusing than allowing it to be anything
10
- and makes it possible to use several DSLs in the same
11
- namespace.
21
+ by the class name. Some::ClassName becomes classname.
22
+ This is less confusing than allowing it to be anything
23
+ and makes it possible to use several DSLs in the same
24
+ namespace.
12
25
 
13
26
 
14
27
  #### 0.3.1 (2009-03-04) ###############################
data/README.rdoc CHANGED
@@ -1,4 +1,4 @@
1
- = Caesars - v0.3
1
+ = Caesars - v0.4
2
2
 
3
3
  A simple class for rapid DSL prototyping in Ruby.
4
4
 
@@ -17,21 +17,16 @@ Or for GitHub fans:
17
17
  * gem sources -a http://gems.github.com (you only have to do this once, ever...), then:
18
18
  * gem install delano-caesar
19
19
 
20
-
21
- == Usage
22
-
20
+ == EXAMPLE 1 -- Flavour
21
+
23
22
 
24
- # ------------------------------------------------------------------
25
- # EXAMPLE 1 -- Flavour
26
- #
27
-
28
23
  class Flavour < Caesars # Subclass Caesars.
29
24
  end
30
25
 
31
26
  extend Flavour::DSL # Bring the DSL into the current namespace.
32
27
  # This module is created dynamically based
33
28
  # on the name of the subclass.
34
-
29
+
35
30
  flavour do # Start drinking! I mean, start writing your
36
31
  spicy true # domain specific language!
37
32
  clamy true # Use any attribute name you want.
@@ -41,19 +36,17 @@ Or for GitHub fans:
41
36
 
42
37
  p @flavour # => #<Flavour:0x3f56b0 ...>
43
38
  p @flavour.spicy # => true
39
+
40
+
41
+
42
+ == EXAMPLE 2 -- Staff
43
+
44
+ require 'caesars'
44
45
 
46
+ class Staff < Caesars
45
47
 
46
-
47
- # ------------------------------------------------------------------
48
- # EXAMPLE 2 -- Staff
49
- #
50
-
51
- # Tell Caesars which attributes have children using Caesars#bloody and
52
- # which have blocks that you want to execute later using Caesars#virgin.
53
- class Staff < Caesars
54
- bloody :location
55
- bloody :person
56
- virgin :calculate
48
+ chill :calculate # Delay execution of the blocks for the calculate
49
+ # attribute. They will be stored as Procs.
57
50
  end
58
51
 
59
52
  extend Staff::DSL
@@ -90,12 +83,13 @@ Or for GitHub fans:
90
83
  satisfaction :low
91
84
  calculate :salary do
92
85
  self.splashdown.delano.rate.to_f * self.splashdown.delano.hours
86
+
93
87
  end
94
88
  end
95
89
  end
96
90
  end
97
91
 
98
- p @staff_fte # => #<KitchenStaff: ...>
92
+ p @staff_fte # => #<Staff: ...>
99
93
  p @staff_fte.desc # => Our hard-working, "full-time" staff
100
94
 
101
95
  # Deeper attributes are also available via instance methods
@@ -107,18 +101,35 @@ Or for GitHub fans:
107
101
  # You can also access them using hash syntax
108
102
  p @staff_fte.splashdown[:steve][:role] # => [:manager, :cook]
109
103
 
110
- # The "bloody" attributes keep track of all values that are used. These are available as arrays
111
- # via "NAME_values" methods. The goes for the virgin ones.
112
- p @staff_fte.location_values # => [:splashdown]
113
- p @staff_fte.person_values.uniq # => [:steve, :sheila, :delano, :angela]
114
- p @staff_fte.calculate_values # => [:salary, :salary]
115
-
116
- # The "virgin" methods store their blocks as Procs and are not executed automatically.
104
+ # The "chilled" attributes store their blocks as Procs and are not executed automatically.
117
105
  # You can call them manually and send arguments like you normally would.
118
106
  p @staff_fte.splashdown.delano.salary.call # => 475.95
119
107
  p @staff_fte.splashdown.sheila.salary.call(rand(100)) # => 549.77
108
+ p @staff_fte.splashdown.keys
109
+
120
110
 
111
+ == EXAMPLE 3 -- External Config file
121
112
 
113
+ require 'caesars'
114
+
115
+ class Food < Caesars
116
+ chill :order
117
+ end
118
+ class Drink < Caesars
119
+ end
120
+
121
+ class PartyConfig < Caesars::Config
122
+ dsl Food::DSL
123
+ dsl Drink::DSL
124
+ end
125
+
126
+ conffile = File.join(File.dirname(__FILE__), 'party.conf')
127
+ @config = PartyConfig.new(:path => conffile)
128
+
129
+ p @config.food.order.call # => 8kg
130
+ p @config[:drink][:wine] # => 12L
131
+ p @config # => <PartyConfig:0x3f780c ...>
132
+ p @config.keys # => [:food, :drink]
122
133
 
123
134
  == More Info
124
135
 
data/bin/example CHANGED
@@ -1,12 +1,14 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ # Caesars -- A working example
3
4
  #
4
- # Caesars Example
5
+ # If your reading this via the rdocs you won't be able to see the code
6
+ # See: http://github.com/delano/caesar/blob/master/bin/example
5
7
  #
6
8
  # Usage: bin/example
7
9
  #
8
10
 
9
- $:.unshift(File.join(File.join(File.dirname(__FILE__), '..'), 'lib')) # Make sure our local lib is first in line
11
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) # Make sure our local lib is first in line
10
12
 
11
13
  require 'caesars'
12
14
 
@@ -37,12 +39,10 @@ p @flavour.spicy # => true
37
39
  # EXAMPLE 2 -- Staff
38
40
  #
39
41
 
40
- # Tell Caesars which attributes have children using Caesars#bloody and
41
- # which have blocks that you want to execute later using Caesars#virgin.
42
42
  class Staff < Caesars
43
- bloody :location
44
- bloody :person
45
- virgin :calculate
43
+
44
+ chill :calculate # Delay execution of the blocks for the calculate
45
+ # attribute. They will be stored as Procs.
46
46
  end
47
47
 
48
48
  extend Staff::DSL
@@ -79,12 +79,13 @@ staff :fte do
79
79
  satisfaction :low
80
80
  calculate :salary do
81
81
  self.splashdown.delano.rate.to_f * self.splashdown.delano.hours
82
+
82
83
  end
83
84
  end
84
85
  end
85
86
  end
86
87
 
87
- p @staff_fte # => #<KitchenStaff: ...>
88
+ p @staff_fte # => #<Staff: ...>
88
89
  p @staff_fte.desc # => Our hard-working, "full-time" staff
89
90
 
90
91
  # Deeper attributes are also available via instance methods
@@ -96,14 +97,34 @@ p @staff_fte.splashdown.delano.satisfaction # => :low
96
97
  # You can also access them using hash syntax
97
98
  p @staff_fte.splashdown[:steve][:role] # => [:manager, :cook]
98
99
 
99
- # The "bloody" attributes keep track of all values that are used. These are available as arrays
100
- # via "NAME_values" methods. The goes for the virgin ones.
101
- p @staff_fte.location_values # => [:splashdown]
102
- p @staff_fte.person_values.uniq # => [:steve, :sheila, :delano, :angela]
103
- p @staff_fte.calculate_values # => [:salary, :salary]
104
-
105
- # The "virgin" methods store their blocks as Procs and are not executed automatically.
100
+ # The "chilled" attributes store their blocks as Procs and are not executed automatically.
106
101
  # You can call them manually and send arguments like you normally would.
107
102
  p @staff_fte.splashdown.delano.salary.call # => 475.95
108
103
  p @staff_fte.splashdown.sheila.salary.call(rand(100)) # => 549.77
109
- p @staff_fte.splashdown.keys
104
+ p @staff_fte.splashdown.keys
105
+
106
+
107
+ # ------------------------------------------------------------------
108
+ # EXAMPLE 3 -- External Config file
109
+ #
110
+
111
+ class Food < Caesars
112
+ chill :order
113
+ end
114
+ class Drink < Caesars
115
+ end
116
+
117
+ class PartyConfig < Caesars::Config
118
+ dsl Food::DSL
119
+ dsl Drink::DSL
120
+ end
121
+
122
+ conffile = File.join(File.dirname(__FILE__), 'party.conf')
123
+ @config = PartyConfig.new(:path => conffile)
124
+
125
+ p @config.food.order.call # => 8kg
126
+ p @config[:drink][:wine] # => 12L
127
+ p @config # => <PartyConfig:0x3f780c ...>
128
+ p @config.keys # => [:food, :drink]
129
+
130
+
data/caesars.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  @spec = Gem::Specification.new do |s|
2
2
  s.name = %q{caesars}
3
- s.version = "0.3.2"
3
+ s.version = "0.4.0"
4
4
  s.date = %q{2009-03-04}
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=
data/lib/caesars.rb CHANGED
@@ -1,15 +1,13 @@
1
1
 
2
2
  # Caesars -- A simple class for rapid DSL prototyping.
3
3
  #
4
- # Subclass Caesars, then tell it which attributes have children using
5
- # Caesars.bloody and which have blocks that you want to execute later
6
- # using Caesars.virgin. That's it! Just start drinking! I mean, start
7
- # writing your domain specific language!
4
+ # Subclass Caesars and start drinking! I mean, start prototyping
5
+ # your own domain specific language!
8
6
  #
9
- # See README.rdoc for a usage example.
7
+ # See bin/example
10
8
  #
11
9
  class Caesars
12
- VERSION = "0.3.2"
10
+ VERSION = "0.4.0"
13
11
  # A subclass of ::Hash that provides method names for hash parameters.
14
12
  # It's like a lightweight OpenStruct.
15
13
  # ch = Caesars::Hash[:tabasco => :lots!]
@@ -17,11 +15,15 @@ class Caesars
17
15
  #
18
16
  class Hash < ::Hash
19
17
  def method_missing(meth)
20
- (self.has_key?(meth)) ? self[meth] : nil
18
+ self[meth] if self.has_key?(meth)
21
19
  end
22
20
  end
21
+
23
22
  # An instance of Caesars::Hash which contains the data specified by your DSL
24
23
  attr_accessor :caesars_properties
24
+
25
+ @@caesars_chilled = []
26
+
25
27
  # Creates an instance of Caesars.
26
28
  # +name+ is .
27
29
  def initialize(name=nil)
@@ -29,49 +31,107 @@ class Caesars
29
31
  @caesars_properties = Caesars::Hash.new
30
32
  @caesars_pointer = @caesars_properties
31
33
  end
34
+
35
+ def keys
36
+ @caesars_properties.keys
37
+ end
38
+
39
+ def to_hash
40
+ @caesars_properties
41
+ end
42
+
43
+ # Look for an attribute, bubbling up to the parent if it's not found
44
+ # +criteria+ is an array of attribute names, orders according to their
45
+ # relationship.
46
+ #
47
+ # # Looking for 'attribute'.
48
+ # # First checks at @caesars_properties[grandparent][parent][attribute]
49
+ # # Then, @caesars_properties[grandparent][attribute]
50
+ # # Finally, @caesars_properties[attribute]
51
+ # find_deferred('grandparent', 'parent', 'attribute')
52
+ #
53
+ # Returns the attribute if found or nil
54
+ #
55
+ def find_deferred(*criteria)
56
+ # This is a nasty implementation. Sorry me! I'll enjoy a few
57
+ # caesars and be right with you.
58
+ att = criteria.pop
59
+ val = nil
60
+ while !criteria.empty?
61
+ str = criteria.collect { |v| "[:#{v}]" }.join << "[:#{att}]"
62
+ val = eval "@caesars_properties#{str} if defined?(@caesars_properties#{str})"
63
+ break if val
64
+ criteria.pop
65
+ end
66
+ # One last try in the root namespace
67
+ val = @caesars_properties[att.to_sym] if defined?(@caesars_properties[att.to_sym]) && !val
68
+ val
69
+ end
70
+
71
+ # Act a bit like a hash for the case:
72
+ # @subclass[:property]
73
+ def [](name)
74
+ return @caesars_properties[name] if @caesars_properties.has_key?(name)
75
+ return @caesars_properties[name.to_sym] if @caesars_properties.has_key?(name.to_sym)
76
+ end
77
+
32
78
  # This method handles all of the attributes that do not contain blocks.
33
- def method_missing(name, *args, &b)
34
- return @caesars_properties[name] if @caesars_properties.has_key?(name) && args.empty? && b.nil?
35
- if @caesars_pointer[name]
36
- @caesars_pointer[name] = [@caesars_pointer[name]] unless @caesars_pointer[name].is_a?(Array)
37
- @caesars_pointer[name] += args
79
+ # It's used in the DSL for handling attributes dyanamically (that weren't defined
80
+ # previously) and also in subclasses of Caesar for returning the appropriate
81
+ # attribute values.
82
+ def method_missing(meth, *args, &b)
83
+ return @caesars_properties[meth] if @caesars_properties.has_key?(meth) && args.empty? && b.nil?
84
+ return nil if args.empty? && b.nil?
85
+
86
+ if b
87
+ # Use the name of the bloody method if no name is supplied.
88
+ args << meth if args.empty?
89
+ args.each do |name|
90
+ prev = @caesars_pointer
91
+ @caesars_pointer[name] ||= Caesars::Hash.new
92
+ @caesars_pointer = @caesars_pointer[name]
93
+ b.call if b
94
+ @caesars_pointer = prev
95
+ end
96
+
97
+ elsif @caesars_pointer[meth]
98
+ @caesars_pointer[meth] = [@caesars_pointer[meth]] unless @caesars_pointer[meth].is_a?(Array)
99
+ @caesars_pointer[meth] += args
38
100
  elsif !args.empty?
39
- @caesars_pointer[name] = args.size == 1 ? args.first : args
101
+ @caesars_pointer[meth] = args.size == 1 ? args.first : args
40
102
  end
103
+
41
104
  end
42
- # see bin/example for usage.
43
- def self.virgin(meth)
44
- self.bloody(meth, false)
45
- end
46
- # see bin/example for usage.
47
- def self.bloody(meth, execute=true)
105
+
106
+ def self.chill(meth)
48
107
  define_method(meth) do |*names,&b| # |*names,&b| syntax does not parse in Ruby 1.8
49
- all = instance_variable_get("@" << meth.to_s) || []
108
+ # caesar.toplevel.unnamed_chilled_attribute
109
+ return @caesars_pointer[meth] if names.empty? && b.nil?
50
110
 
111
+ # Use the name of the bloody method if no name is supplied.
112
+ names << meth if names.empty?
113
+
51
114
  names.each do |name|
52
- instance_variable_set("@" << meth.to_s, all << name)
53
-
54
- if execute
55
- prev = @caesars_pointer
56
- @caesars_pointer[name] ||= Caesars::Hash.new
57
- @caesars_pointer = @caesars_pointer[name]
58
- b.call if b
59
- @caesars_pointer = prev
60
- else
61
- @caesars_pointer[name] = b
62
- end
63
-
115
+ @caesars_pointer[name] = b
64
116
  end
117
+
65
118
  nil
66
119
  end
67
- define_method("#{meth}_values") do ||
68
- instance_variable_get("@" << meth.to_s) || []
69
- end
120
+ #define_method("#{meth}_values") do ||
121
+ # instance_variable_get("@" << meth.to_s) || []
122
+ #end
70
123
  end
71
124
  # Executes automatically when Caesars is subclassed. This creates the
72
- # YourClass::DSL module which contains a single method: method_missing.
73
- # This is used to catch the top level DSL method. That's why you can
74
- # used any method name you like.
125
+ # YourClass::DSL module which contains a single method named after YourClass
126
+ # that is used to catch the top level DSL method.
127
+ #
128
+ # For example, if your class is called Glasses::HighBall, your top level method
129
+ # would be: highball.
130
+ #
131
+ # highball :mine do
132
+ # volume 9.oz
133
+ # end
134
+ #
75
135
  def self.inherited(modname)
76
136
  meth = (modname.to_s.split(/::/))[-1].downcase # Some::ClassName => classname
77
137
  module_eval %Q{
@@ -80,13 +140,91 @@ class Caesars
80
140
  name = !args.empty? ? args.first.to_s : nil
81
141
  varname = "@#{meth.to_s}"
82
142
  varname << "_\#{name}" if name
83
- i = instance_variable_set(varname, #{modname.to_s}.new(name))
84
- i.instance_eval(&b) if b
143
+
144
+ # When the top level DSL method is called without a block
145
+ # 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
85
152
  i
86
153
  end
154
+
155
+ def self.methname
156
+ :"#{meth}"
157
+ end
158
+
87
159
  end
88
160
  }, __FILE__, __LINE__
89
161
  end
162
+
163
+ end
164
+
165
+
166
+ # A helper for loading a DSL from a config file.
167
+ #
168
+ # Usage:
169
+ #
170
+ # class Staff < Caesars; end;
171
+ # class StaffConfig < Caesars::Config
172
+ # dsl Staff::DSL
173
+ # end
174
+ # @config = StaffConfig.new(:path => '/path/2/staff_dsl.rb')
175
+ # p @config.staff # => <Staff:0x7ea450 ... >
176
+ #
177
+ class Caesars::Config
178
+ attr_accessor :path
179
+ attr_accessor :verbose
180
+
181
+ @@glasses = []
182
+
183
+ def initialize(args={:path=>'', :verbose=>false})
184
+ args.each_pair do |n,v|
185
+ self.send("#{n}=", v)
186
+ end
187
+
188
+ refresh
189
+ end
190
+
191
+ def self.dsl(glass)
192
+ @@glasses << glass
193
+ end
194
+
195
+ def [](name)
196
+ self.send(name) if respond_to?(name)
197
+ end
198
+
199
+ def keys
200
+ @@glasses.collect { |glass| glass.methname }
201
+ end
202
+
203
+ def refresh
204
+
205
+ if exists?
206
+ puts "Loading config from #{@path}" if @verbose
207
+
208
+ begin
209
+ @@glasses.each { |glass| extend glass }
210
+ dsl = File.read @path
211
+
212
+ # We're using eval so the DSL code can be executed in this
213
+ # namespace.
214
+ eval %Q{
215
+ #{dsl}
216
+ }
217
+
218
+ rescue SyntaxError => ex
219
+ puts "Syntax error in #{@path}."
220
+ exit 1
221
+ end
222
+ end
223
+ end
224
+
225
+ def exists?
226
+ File.exists?(@path)
227
+ end
90
228
  end
91
229
 
92
230
 
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.3.2
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Delano Mandelbaum