lumberjack-dsl 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --colour
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "rspec"
data/MIT-LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2007-2008 Ryan Allen, FlashDen Pty Ltd
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,57 @@
1
+ Lumberjack is best summed up as a generic DSL for constructing object trees.
2
+
3
+ It works great for configuration files, for generating a tree of configuration
4
+ objects for later reflection or what-not. But in reality you could use it for
5
+ whatever you're willing to dream up.
6
+
7
+ I apologise for the lack of documentation :) Below is a code example to get you
8
+ started, any questions, shoot me a message!
9
+
10
+ Now, that code example:
11
+
12
+ require 'lumberjack'
13
+
14
+ class Person
15
+
16
+ attr_accessor :age, :gripes, :chums
17
+
18
+ def initialize(name)
19
+ @name = name
20
+ @gripes = []
21
+ @chums = []
22
+ end
23
+ end
24
+
25
+ class Gripe
26
+ def initialize(desc)
27
+ @desc = desc
28
+ end
29
+ end
30
+
31
+ tree = Lumberjack.construct do
32
+
33
+ # we're in list / instanciate object scope
34
+
35
+ @john = person 'John (is a doondy head)' do
36
+ # we instanticated an object, so now we're in attr assignment scope
37
+ age 12 # this is equiv to @john.age = 12
38
+ gripes do # open up a colection on john...
39
+ # now we're back in list / instanticate object scope
40
+ gripe 'untested code' # creating a gripe
41
+ gripe 'no beer' # and another
42
+ end # out of gripes, back to john attr assignment
43
+ end # out of john
44
+
45
+ # we're back to creating people:
46
+
47
+ person 'Ryan' do
48
+ age 25
49
+ end
50
+
51
+ person 'Tim' do
52
+ age 'Infinite'
53
+ chums @john # instance vars are shared across Lumberjack.construct
54
+ end
55
+ end
56
+
57
+ puts tree.inspect
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rspec/core/rake_task'
4
+ RSpec::Core::RakeTask.new(:spec) do |s|
5
+ s.pattern = '*_spec.rb'
6
+ end
7
+
8
+ task :default => :spec
@@ -0,0 +1,5 @@
1
+ person 'John S', 50
2
+ person 'Andrea', 46
3
+ person 'John R', 26
4
+ person 'Sarah', 24
5
+ person 'Ethan', 10
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+ Gem::Specification.new do |s|
3
+ s.name = "lumberjack-dsl"
4
+ s.version = "0.0.1"
5
+ s.authors = ["Ryan Allen", "Steve Hodgkiss", "John Barton", "James Dowling"]
6
+ s.email = ["ryan@yeahnah.org", "steve@hodgkiss.me", "jrbarton@gmail.com", "jamesd741@gmail.com"]
7
+ s.homepage = ""
8
+ s.summary = %q{Lumberjack is best summed up as a generic DSL for constructing object trees.}
9
+ s.description = %q{}
10
+
11
+ s.rubyforge_project = "lumberjack-dsl"
12
+
13
+ s.files = `git ls-files`.split("\n")
14
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
15
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
16
+ s.require_paths = ["."]
17
+
18
+ s.add_development_dependency "rspec"
19
+ end
data/lumberjack.rb ADDED
@@ -0,0 +1,153 @@
1
+ class Lumberjack
2
+
3
+ def self.construct(initial_scope = [], &block)
4
+ builder = new(initial_scope)
5
+ builder.__process(block)
6
+ end
7
+
8
+ @@methods_to_keep = /^__/, /class/, /instance_eval/, /method_missing/,
9
+ /instance_variable_(g|s)et/, /instance_variables/, /inspect/, /send/,
10
+ /^object_id/, /^respond_to/
11
+
12
+ instance_methods.each do |m|
13
+ undef_method m unless @@methods_to_keep.find { |r| r.match m }
14
+ end
15
+
16
+ def initialize(initial_scope)
17
+ @initial_scope = initial_scope
18
+ @scope_stack ||= []
19
+ end
20
+
21
+ def __process(block)
22
+ @scope = [@initial_scope]
23
+ instance_eval(&block) if block
24
+ tree
25
+ rescue
26
+ raise $!
27
+ end
28
+
29
+ # syntatic sugar for scope resolution
30
+ def /(ignore_me)
31
+ self
32
+ end
33
+
34
+ def load_tree_file(filename)
35
+ File.open filename, 'r' do |f|
36
+ eval f.read, binding, __FILE__, __LINE__
37
+ end
38
+ end
39
+
40
+ def shared_branch(branch_name, &block)
41
+ instance_variable_set "@#{branch_name}", lambda(&block)
42
+ end
43
+
44
+ def graft_branch(branch_name)
45
+ branch = instance_variable_get("@#{branch_name}")
46
+ raise "Attemption to graft branch #{branch_name} which is undefined" unless branch
47
+ instance_eval(&branch)
48
+ end
49
+
50
+ def prune(method, value)
51
+ current_scope.delete_if do |twig|
52
+ twig.respond_to?(method) && twig.send(method) == value
53
+ end
54
+ end
55
+
56
+ def method_missing(*args, &block)
57
+ if still_modifying_scope?(args, block)
58
+ @scope_stack << args.first
59
+ else
60
+ assign_to_current_scope(args, block)
61
+ end
62
+ self
63
+ end
64
+
65
+ private
66
+
67
+ def assign_to_current_scope(args, block)
68
+ if current_scope.respond_to?(:<<)
69
+ assign_to_array_with(args, block)
70
+ else
71
+ assign_to_instance_with(args, block)
72
+ end
73
+ end
74
+
75
+ def assign_to_instance_with(args, block)
76
+ accessor = args.shift
77
+ if accessor.to_s[-1].chr == '!'
78
+ # create class based on the accessor name
79
+ assign_subvalues_to_instance(accessor, args, block)
80
+ elsif block and args.empty?
81
+ # accessor is to refer to an array
82
+ current_array_instance = get_accessor_value(accessor)
83
+ evaluate_block_within_context(current_array_instance, block)
84
+ elsif args.length == 1
85
+ # splat to avoid array and directly assign the argument
86
+ current_scope.send("#{accessor}=", *args)
87
+ else
88
+ # assign array
89
+ current_scope.send("#{accessor}=", args)
90
+ end
91
+ end
92
+
93
+ def assign_to_array_with(args, block)
94
+ klass = args.shift
95
+ instance = build_class(klass, args)
96
+ current_scope << instance # add this instance to the scoped Array
97
+ evaluate_block_within_context(instance, block) if block
98
+ end
99
+
100
+ def assign_subvalues_to_instance(accessor, args, block)
101
+ accessor_class = accessor.to_s[0...-1]
102
+ instance = build_class(accessor_class, args)
103
+ instance.parent = current_scope if instance.respond_to?(:parent=)
104
+ current_scope.send("#{accessor_class}=", instance)
105
+ evaluate_block_within_context(instance, block) if block
106
+ end
107
+
108
+ def build_class(klass, args)
109
+ @scope_stack << klass
110
+ scoped_class = @scope_stack.join('/')
111
+ @scope_stack = []
112
+ classify(scoped_class).new(*args)
113
+ end
114
+
115
+ def evaluate_block_within_context(accessor, block)
116
+ @scope.push accessor
117
+ instance_eval(&block)
118
+ @scope.pop
119
+ end
120
+
121
+ def get_accessor_value(accessor)
122
+ if current_scope.send("#{accessor}").nil?
123
+ current_scope.send("#{accessor}=", [])
124
+ end
125
+ current_scope.send("#{accessor}")
126
+ end
127
+
128
+ def still_modifying_scope?(args, block)
129
+ # if we only have one arg, and no block, then we're trying to build a
130
+ # module scope, i.e. a/b/c/d would resolve to A::B::C::D,
131
+ args.length == 1 && block.nil?
132
+ end
133
+
134
+ def current_scope
135
+ @scope.last
136
+ end
137
+
138
+ def tree
139
+ @scope.first
140
+ end
141
+
142
+ # Turns an underscored path into the class it represents
143
+ #
144
+ # Usage: classify("some/cool_klass") => Some::CoolKlass
145
+ def classify(class_name)
146
+ klass = class_name.split('/').collect do |component|
147
+ camels = component.split('_')
148
+ camels.collect { |c| c.capitalize }.join
149
+ end.join('::')
150
+ eval("::#{klass}")
151
+ end
152
+
153
+ end
@@ -0,0 +1,311 @@
1
+ require 'bundler'
2
+ Bundler.require
3
+ $: << '.'
4
+ require 'lumberjack'
5
+
6
+ class Family
7
+ attr_accessor :name, :members, :heritage
8
+ def initialize(name = nil, args = {})
9
+ @name = name
10
+ args.each { |k,v| send "#{k}=", v }
11
+ end
12
+ end
13
+
14
+ class Person
15
+ attr_accessor :given_name, :age
16
+ def initialize(name = nil, age = nil)
17
+ @given_name, @age = name, age
18
+ end
19
+ end
20
+
21
+ class Showroom < Array
22
+ end
23
+
24
+ class Vehicle
25
+ class Heavy < Vehicle
26
+ class ReallyHeavy < Heavy
27
+ end
28
+ end
29
+ attr_accessor :name, :wheels, :person
30
+ def initialize(args = {:name => 'A Car, ya mum'})
31
+ @name = args[:name]
32
+ @wheels = SetOfWheels.new
33
+ end
34
+ end
35
+
36
+ class SetOfWheels < Array
37
+ end
38
+
39
+ class Wheel
40
+ attr_accessor :wear
41
+ def initialize(args)
42
+ @wear = args[:wear]
43
+ end
44
+ end
45
+
46
+ describe Lumberjack do
47
+
48
+ it 'construct returns an empty list' do
49
+ Lumberjack.construct.should be_empty
50
+ end
51
+
52
+ it 'testcan create a single class' do
53
+ tree = Lumberjack.construct do
54
+ family {} # api change w/ scoping requires a block to be passed, otherwise can't tell if you're
55
+ # trying to resolve a nested scope
56
+ end
57
+ tree.length.should eq 1
58
+ tree.first.should be_instance_of Family
59
+ end
60
+
61
+ it 'test can create a single class passing in args' do
62
+ tree = Lumberjack.construct do
63
+ family 'Allen', :heritage => :mixed
64
+ end
65
+ tree.length.should eq 1
66
+ tree.first.should be_instance_of Family
67
+ tree.first.name.should eq 'Allen'
68
+ tree.first.heritage.should eq :mixed
69
+ end
70
+
71
+ it 'can create two classes passing in args' do
72
+ tree = Lumberjack.construct do
73
+ family 'Allen', :heritage => [:english, :irish]
74
+ family 'Ta\'eed', :heritage => [:iranian, :english]
75
+ end
76
+ tree.length.should eq 2
77
+ tree[0].should be_instance_of Family
78
+ tree[0].name.should eq 'Allen'
79
+ tree[0].heritage.should eq [:english, :irish]
80
+ tree[1].should be_instance_of Family
81
+ tree[1].name.should eq 'Ta\'eed'
82
+ tree[1].heritage.should eq [:iranian, :english]
83
+ end
84
+
85
+ it 'can set instance members with block' do
86
+ tree = Lumberjack.construct do
87
+ family do
88
+ name 'Allen'
89
+ heritage [:english, :irish]
90
+ end
91
+ end
92
+ tree.length.should eq 1
93
+ tree[0].should be_instance_of Family
94
+ tree[0].name.should eq 'Allen'
95
+ tree[0].heritage.should eq [:english, :irish]
96
+ end
97
+
98
+ it 'can used mixed constructor and instance members in_blocke' do
99
+ tree = Lumberjack.construct do
100
+ family 'Allen' do
101
+ heritage [:english, :irish]
102
+ end
103
+ end
104
+ tree.length.should eq 1
105
+ tree[0].should be_instance_of Family
106
+ tree[0].name.should eq 'Allen'
107
+ tree[0].heritage.should eq [:english, :irish]
108
+ end
109
+
110
+ it 'create list in scoped instance if block with no args' do
111
+ tree = Lumberjack.construct do
112
+ family 'Allen' do
113
+ heritage [:english, :irish]
114
+ members do # working from here
115
+ person 'Tim', 58
116
+ person 'Jan', 54
117
+ person 'Ryan' do
118
+ age 24
119
+ end
120
+ end
121
+ end
122
+ end
123
+ tree.length.should eq 1
124
+ tree[0].should be_instance_of Family
125
+ tree[0].name.should eq 'Allen'
126
+ tree[0].heritage.should eq [:english, :irish]
127
+ tree[0].members.length.should eq 3
128
+ tree[0].members[0].given_name.should eq 'Tim'
129
+ tree[0].members[0].age.should eq 58
130
+ tree[0].members[1].given_name.should eq 'Jan'
131
+ tree[0].members[1].age.should eq 54
132
+ tree[0].members[2].given_name.should eq 'Ryan'
133
+ tree[0].members[2].age.should eq 24
134
+ end
135
+
136
+ it 'can take generate arrays with comma semantics and tell the difference' do
137
+ tree = Lumberjack.construct do
138
+ family 'Allen' do
139
+ heritage :english, :irish
140
+ end
141
+ end
142
+ tree[0].heritage.should eq [:english, :irish]
143
+ end
144
+
145
+ it 'will_push element onto object if list accessor is already initialized' do
146
+ vehicles = Lumberjack.construct do
147
+ vehicle :name => 'unicycle' do
148
+ wheels do
149
+ wheel :wear => 'bald'
150
+ end
151
+ end
152
+ end
153
+ vehicles[0].wheels.should be_instance_of SetOfWheels
154
+ end
155
+
156
+ it 'can set initial context to something else besdies an array' do
157
+ showroom = Lumberjack.construct Showroom.new do
158
+ vehicle :name => 'a FERRARRI!!!1'
159
+ vehicle :name => 'a MASERATI!!!1'
160
+ vehicle :name => 'a PORCHE OMG!!!'
161
+ end
162
+ showroom.should be_instance_of Showroom
163
+ showroom.length.should eq 3
164
+ end
165
+
166
+ # biggest hack ever, use a ! to isntanciate a class to an accessor, must be
167
+ # inferable by the accessor name, such a large hack, but we need it for
168
+ # production, and i'm sure other people will need it, so lets leave this
169
+ # gaping flaw of lumberjack for the time being till we can think of something
170
+ # more nice and appropriate :/ :D
171
+ it 'can create instance of class via bang method' do
172
+ cars = Lumberjack.construct do
173
+ vehicle :name => 'Prius (are owned by rich hippies)' do
174
+ person! 'Ryan' do # i so put my foot in here, i'm not a rich hippy!
175
+ age 25
176
+ end
177
+ end
178
+ end
179
+ eval('Vehicle').should eq Vehicle
180
+ cars[0].class.should eq Vehicle
181
+ cars[0].name.should eq 'Prius (are owned by rich hippies)'
182
+ cars[0].person.should be_instance_of Person
183
+ cars[0].person.age.should eq 25
184
+ cars[0].person.given_name.should eq 'Ryan'
185
+ end
186
+
187
+ it 'can create list of primitives' do # not sure this is valid useage (of course it is you big dummy ryan from the past!)
188
+ tree = Lumberjack.construct do
189
+ array [:one, :two, :three]
190
+ array [:four, :five, :six]
191
+ end
192
+ tree.should eq [[:one, :two, :three], [:four, :five, :six]]
193
+ end
194
+
195
+ it 'we got backslashes that resolve scope or something' do
196
+ cars = Lumberjack.construct do
197
+ vehicle :name => 'Normal Car'
198
+ # unfortunatley we need to use parantehseshtheses here :(
199
+ vehicle/heavy(:name => 'Heavy Car')
200
+ vehicle/heavy/really_heavy(:name => 'Really Heavy Heavy Car')
201
+ end
202
+ cars[0].should be_instance_of Vehicle
203
+ cars[1].should be_instance_of Vehicle::Heavy
204
+ cars[2].should be_instance_of Vehicle::Heavy::ReallyHeavy
205
+ end
206
+
207
+ it 'we can load in other files' do
208
+ family = Lumberjack.construct do
209
+ family 'Barton' do
210
+ heritage [:dutch, :mongrel_aussie]
211
+ members do
212
+ load_tree_file 'examples/people.rb'
213
+ end
214
+ end
215
+ end
216
+
217
+ family.length.should eq 1
218
+ family.first.should be_instance_of Family
219
+ family.first.name.should eq 'Barton'
220
+ family.first.heritage.should eq [:dutch, :mongrel_aussie]
221
+
222
+ family.first.members.size.should eq 5
223
+
224
+ family.first.members.first.given_name.should eq 'John S'
225
+ family.first.members.first.age.should eq 50
226
+
227
+ family.first.members.last.given_name.should eq 'Ethan'
228
+ family.first.members.last.age.should eq 10
229
+ end
230
+
231
+ it 'we can share branches that are defined' do
232
+ families = Lumberjack.construct do
233
+
234
+ shared_branch :kids do
235
+ person 'Jack', 11
236
+ person 'Jill', 10
237
+ end
238
+
239
+ family "Dad's new family" do
240
+ members do
241
+ person 'Dad', 45
242
+ graft_branch :kids
243
+ end
244
+ end
245
+
246
+ family "Mum's new family" do
247
+ members do
248
+ person 'Mum', 40
249
+ person 'Red-headed step-child', 8
250
+ graft_branch :kids
251
+ end
252
+ end
253
+ end
254
+
255
+ families.length.should eq 2
256
+ families[0].should be_instance_of Family
257
+ families[1].should be_instance_of Family
258
+
259
+ families[0].members.size.should eq 3
260
+ families[0].members.any? {|m| m.given_name == 'Jack'}.should be_true
261
+ families[0].members.any? {|m| m.given_name == 'Jill'}.should be_true
262
+
263
+ families[1].members.size.should eq 4
264
+ families[1].members.any? {|m| m.given_name == 'Jack'}.should be_true
265
+ families[1].members.any? {|m| m.given_name == 'Jill'}.should be_true
266
+ end
267
+
268
+ it 'we can remove twigs with prune' do
269
+ families = Lumberjack.construct do
270
+
271
+ shared_branch :kids do
272
+ person 'Jack', 12
273
+ person 'Jane', 10
274
+ person 'Bob', 10
275
+ end
276
+
277
+ shared_branch :update_kids do
278
+ prune :age, 10
279
+ person 'Will', 11
280
+ end
281
+
282
+ family "Dad's family" do
283
+ members do
284
+ person 'Dad', 45
285
+ person 'Mum', 49
286
+ prune :given_name, 'Mum'
287
+ person 'Mum', 26
288
+
289
+ graft_branch :kids
290
+ graft_branch :update_kids
291
+ end
292
+ end
293
+ end
294
+
295
+ families[0].members.any? {|m| m.given_name == 'Will' && m.age == 11}.should be_true
296
+ families[0].members.any? {|m| m.given_name == 'Jack' && m.age == 12}.should be_true
297
+ families[0].members.any? {|m| m.given_name == 'Mum' && m.age == 26}.should be_true
298
+ families[0].members.any? {|m| m.given_name == 'Dad' && m.age == 45}.should be_true
299
+ end
300
+
301
+ it 'doesnt share branches that are undefined' do
302
+ # TODO: why does this output funny stuff
303
+ expect {
304
+ Lumberjack.construct do
305
+ family 'wont work' do
306
+ graft_branch :non_existant
307
+ end
308
+ end
309
+ }.to raise_error RuntimeError
310
+ end
311
+ end
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lumberjack-dsl
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Ryan Allen
9
+ - Steve Hodgkiss
10
+ - John Barton
11
+ - James Dowling
12
+ autorequire:
13
+ bindir: bin
14
+ cert_chain: []
15
+ date: 2012-03-13 00:00:00.000000000 Z
16
+ dependencies:
17
+ - !ruby/object:Gem::Dependency
18
+ name: rspec
19
+ requirement: &70259449806300 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ! '>='
23
+ - !ruby/object:Gem::Version
24
+ version: '0'
25
+ type: :development
26
+ prerelease: false
27
+ version_requirements: *70259449806300
28
+ description: ''
29
+ email:
30
+ - ryan@yeahnah.org
31
+ - steve@hodgkiss.me
32
+ - jrbarton@gmail.com
33
+ - jamesd741@gmail.com
34
+ executables: []
35
+ extensions: []
36
+ extra_rdoc_files: []
37
+ files:
38
+ - .gitignore
39
+ - .rspec
40
+ - Gemfile
41
+ - MIT-LICENSE
42
+ - README.markdown
43
+ - Rakefile
44
+ - examples/people.rb
45
+ - lumberjack.gemspec
46
+ - lumberjack.rb
47
+ - lumberjack_spec.rb
48
+ homepage: ''
49
+ licenses: []
50
+ post_install_message:
51
+ rdoc_options: []
52
+ require_paths:
53
+ - .
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ! '>='
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ requirements: []
67
+ rubyforge_project: lumberjack-dsl
68
+ rubygems_version: 1.8.11
69
+ signing_key:
70
+ specification_version: 3
71
+ summary: Lumberjack is best summed up as a generic DSL for constructing object trees.
72
+ test_files: []