lumberjack-dsl 0.0.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/.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: []