config_leaf 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,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .idea
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Bil Bas (Spooner)
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,86 @@
1
+ ConfigLeaf
2
+ ==========
3
+
4
+ ConfigLeaf allows an object to be configured using a terse syntax
5
+ like Object#instance_eval, while not exposing the internals
6
+ (protected/private methods and ivars) of the object!
7
+
8
+ If the user doesn't like to use this ConfigLeaf wrapping, then they
9
+ can just use normal #tap syntax by requesting a block parameter.
10
+
11
+ Installation
12
+ ------------
13
+
14
+ Add this line to your application's Gemfile:
15
+
16
+ gem 'config_leaf'
17
+
18
+ And then execute:
19
+
20
+ $ bundle
21
+
22
+ Or install it yourself as:
23
+
24
+ $ gem install config_leaf
25
+
26
+ Usage
27
+ -----
28
+
29
+ ConfigLeaf can be used directly by the end-user after an object has been created,
30
+ but it more likely used by those wanting to allow block setting of config after
31
+ their object has been instantiated:
32
+
33
+ require 'config_leaf'
34
+
35
+ class Cheese
36
+ attr_accessor :value, :list
37
+
38
+ def initialize(&block)
39
+ # Set defaults.
40
+ @value = 0
41
+ @list = []
42
+
43
+ # Allow the user access via ConfigLeaf syntax.
44
+ ConfigLeaf.wrap object, &block if block_given?
45
+ end
46
+
47
+ def reverse!
48
+ @list.reverse!
49
+ end
50
+
51
+ protected
52
+ def explode!
53
+ DeathStar.instance.boom!
54
+ end
55
+ end
56
+
57
+ # User uses the ConfigLeaf block.
58
+ object = Cheese.new do
59
+ list [1, 2, 3] # Calls object.list = [1, 2, 3]
60
+ list << 4 # Calls object.list << 4
61
+ value 5 # Calls object.value = 5
62
+ value list.size # Calls object.value = object.list.size
63
+ reverse! # Calls object.reverse!
64
+
65
+ # These would be possible if using instance_eval, but not here.
66
+ #explode! # Not allowed because it is protected (raises an exception).
67
+ #@value = 5 # Would set @value on the wrapper, not in the wrapped object, so has no effect.
68
+ end
69
+
70
+ # User chooses to not use ConfigLeaf block syntax by requesting a block parameter.
71
+ object = Cheese.new do |c|
72
+ c.list = [1, 2, 3]
73
+ c.list << 4
74
+ c.value = 5
75
+ c.value = c.list.size
76
+ c.reverse!
77
+ end
78
+
79
+ Contributing
80
+ ------------
81
+
82
+ 1. Fork it
83
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
84
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
85
+ 4. Push to the branch (`git push origin my-new-feature`)
86
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env rake
2
+ require 'bundler/setup'
3
+
4
+ require 'bundler/gem_tasks'
5
+ require 'rake/clean'
6
+ require 'rake/testtask'
7
+ require 'rspec/core/rake_task'
8
+
9
+ CLOBBER.include('doc/**/*')
10
+
11
+ desc 'Generate Yard docs.'
12
+ task :yard do
13
+ system 'yard doc lib'
14
+ end
15
+
16
+ RSpec::Core::RakeTask.new do
17
+ end
18
+
19
+ task :default => :spec
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/config_leaf/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Bil Bas (Spooner)"]
6
+ gem.email = ["bil.bagpuss@gmail.com"]
7
+ gem.description = <<END
8
+ ConfigLeaf allows an object to be configured using a terse syntax
9
+ like Object#instance_eval, while not exposing the internals
10
+ (protected/private methods and ivars) of the object!
11
+ END
12
+ gem.summary = %q{Terse configuration syntax for objects}
13
+ gem.homepage = "https://github.com/spooner/config_leaf"
14
+
15
+ gem.files = `git ls-files`.split($\)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.name = "config_leaf"
19
+ gem.require_paths = ["lib"]
20
+ gem.version = ConfigLeaf::VERSION
21
+
22
+ gem.add_development_dependency "rspec", "~> 2.11.0"
23
+ gem.add_development_dependency "rr", "~> 1.0.4"
24
+ gem.add_development_dependency "yard", "~> 0.8.3"
25
+ gem.add_development_dependency "redcarpet", "~> 2.1.1"
26
+ gem.add_development_dependency "rake"
27
+ end
@@ -0,0 +1,3 @@
1
+ module ConfigLeaf
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,47 @@
1
+ module ConfigLeaf
2
+
3
+ class Wrapper
4
+ # @return [Object] Object that the Wrapper object is redirecting to.
5
+ attr_reader :_owner
6
+
7
+ class << self
8
+ # Synonym for .new.
9
+ alias_method :wrap, :new
10
+ end
11
+
12
+ # If passed a block, the DSLWrapper will #instance_eval it automatically.
13
+ #
14
+ # @param owner [Object] Object to redirect the public methods of.
15
+ def initialize(owner, &block)
16
+ @_owner = owner
17
+
18
+ metaclass = class << self; self; end
19
+
20
+ (@_owner.public_methods - Object.public_instance_methods).each do |target_method|
21
+ redirection_method = target_method.to_s.chomp('=').to_sym
22
+
23
+ metaclass.class_eval do
24
+ define_method redirection_method do |*args, &inner_block|
25
+ if @_owner.respond_to? "#{redirection_method}=" and (args.any? or not @_owner.respond_to? redirection_method)
26
+ # Has a setter and we are passing argument(s) or if we haven't got a corresponding getter.
27
+ @_owner.send "#{redirection_method}=", *args, &inner_block
28
+ elsif @_owner.respond_to? redirection_method
29
+ # We have a getter or general method
30
+ @_owner.send redirection_method, *args, &inner_block
31
+ else
32
+ # Should never reach here, but let's be paranoid.
33
+ raise NoMethodError, "#{@_owner} does not have a public method, ##{redirection_method}"
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ instance_eval &block if block_given?
40
+ end
41
+
42
+ private
43
+ def method_missing(meth, *args, &block)
44
+ raise NoMethodError, "#{_owner} does not have either public method, ##{meth} or ##{meth}="
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,82 @@
1
+ require 'config_leaf/version'
2
+ require 'config_leaf/wrapper'
3
+
4
+ module ConfigLeaf
5
+ class << self
6
+ # Wraps an object and redirects public methods to it, to allow for a terse, block-based API.
7
+ #
8
+ # * Safer alternative to running Object#instance_eval directly, since protected/private methods and instance variables are not exposed.
9
+ # * Less wordy than a system which operates like Object#tap (`object.tap {|o| o.fish = 5; o.run }`)
10
+ #
11
+ # A method call, #meth called on the wrapper will try to call #meth or #meth= on the owner, as appropriate.
12
+ #
13
+ # @example Using ConfigLeaf to configure an object externally.
14
+ # # To create a DSL block for a given object.
15
+ # class Cheese
16
+ # attr_accessor :value, :list
17
+ #
18
+ # def initialize
19
+ # @value = 0
20
+ # @list = []
21
+ # end
22
+ #
23
+ # def invert
24
+ # @list.reverse!
25
+ # end
26
+ # end
27
+ #
28
+ # object = Cheese.new
29
+ # ConfigLeaf.wrap object do
30
+ # list [1, 2, 3] # Calls object.list = [1, 2, 3]
31
+ # list << 4 # Calls object.list << 4
32
+ # value 5 # Calls object.value = 5
33
+ # value list.size # Calls object.value = object.list.size
34
+ # invert # Calls object.invert
35
+ # end
36
+ #
37
+ # @example Allowing the user to configure the object externally.
38
+ # # To create a DSL block for a given object.
39
+ # class Cheese
40
+ # attr_accessor :value, :list
41
+ #
42
+ # def initialize(&block)
43
+ # @value = 0
44
+ # @list = []
45
+ #
46
+ # ConfigLeaf.wrap object, &block if block_given?
47
+ # end
48
+ #
49
+ # def invert
50
+ # @list.reverse!
51
+ # end
52
+ # end
53
+ #
54
+ # # User chooses to use the ConfigLeaf block syntax.
55
+ # object = Cheese.new do
56
+ # list [1, 2, 3] # Calls object.list = [1, 2, 3]
57
+ # list << 4 # Calls object.list << 4
58
+ # value 5 # Calls object.value = 5
59
+ # value list.size # Calls object.value = object.list.size
60
+ # invert # Calls object.invert
61
+ # end
62
+ #
63
+ # # User chooses to not use ConfigLeaf block syntax by requesting a block parameter.
64
+ # object = Cheese.new do |c|
65
+ # c.list = [1, 2, 3]
66
+ # c.list << 4
67
+ # c.value = 5
68
+ # c.value = c.list.size
69
+ # c.invert
70
+ # end
71
+ #
72
+ def wrap(object, &block)
73
+ raise ArgumentError, "block is required" unless block_given?
74
+
75
+ if block.arity == 0 # e.g. { }
76
+ Wrapper.wrap object, &block
77
+ else # e.g. {|me| }
78
+ block.call object
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,45 @@
1
+ require File.expand_path("../../teststrap", __FILE__)
2
+
3
+ describe ConfigLeaf do
4
+ describe ".wrap" do
5
+ it "should fail without a block" do
6
+ lambda { ConfigLeaf.wrap Object.new }.should raise_error ArgumentError, "block is required"
7
+ end
8
+
9
+ context "without block arguments" do
10
+ it "should alter the context to that of the wrapper within the block" do
11
+ passed = nil
12
+ ConfigLeaf.wrap Object.new do
13
+ passed = self
14
+ end
15
+ passed.should be_kind_of ConfigLeaf::Wrapper
16
+ end
17
+ end
18
+
19
+ context "with block arguments" do
20
+ it "should pass object into the block" do
21
+ object = Object.new
22
+ passed = nil
23
+
24
+ ConfigLeaf.wrap object do |x|
25
+ passed = x
26
+ end
27
+
28
+ passed.should eq object
29
+ end
30
+
31
+ it "should not alter the context in the block" do
32
+ object = Object.new
33
+ block_self = nil
34
+ original_self = self
35
+
36
+ ConfigLeaf.wrap object do |x|
37
+ block_self = self
38
+ end
39
+
40
+ # Use id or the two rspec contexts will barf.
41
+ original_self.__id__.should eq block_self.__id__
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,110 @@
1
+ require File.expand_path("../../teststrap", __FILE__)
2
+
3
+ class Owner
4
+ def frog; end
5
+ def fish; end
6
+ def fish=(fish); end
7
+ def knees=(knees); end
8
+ def add_cheese(a, b); end
9
+ def add_peas(a, &block); end
10
+
11
+ protected
12
+ def wibble; end
13
+ end
14
+
15
+ describe ConfigLeaf::Wrapper do
16
+ let(:owner) { Owner.new }
17
+ let(:subject) { described_class.wrap owner }
18
+
19
+ let :expected_methods do
20
+ methods = [:frog, :fish, :add_cheese, :_owner, :add_peas, :knees]
21
+ methods.map! &:to_s if RUBY_VERSION =~ /^1\.8\./
22
+ methods
23
+ end
24
+
25
+ describe "#public_methods" do
26
+ it "should be the same as the owner's + #_owner" do
27
+ subject.public_methods(false).sort.should eq expected_methods.sort
28
+ end
29
+ end
30
+
31
+ describe "#_owner" do
32
+ it "should be set to the owner of the wrapper" do
33
+ subject._owner.should eq owner
34
+ end
35
+ end
36
+
37
+ context "dynamic method redirection" do
38
+ it "a method that doesn't exist on the wrapped object should fail" do
39
+ lambda { subject.wobble }.should raise_error NoMethodError, /<Owner:0x\w+> does not have either public method, #wobble or #wobble=/
40
+ end
41
+
42
+ it "a protected method on the wrapped object should not be accessible" do
43
+ lambda { subject.wibble }.should raise_error NoMethodError, /<Owner:0x\w+> does not have either public method, #wibble or #wibble=/
44
+ end
45
+
46
+ it "redirect to a setter, that has no corresponding getter, even if no arguments are passed" do
47
+ lambda { subject.knees }.should raise_error ArgumentError
48
+ end
49
+
50
+ it "redirect to a getter, that has no corresponding setter, even if arguments are passed" do
51
+ lambda { subject.frog 25 }.should raise_error ArgumentError
52
+ end
53
+
54
+ it "redirect a getter (that has no corresponding setter)" do
55
+ mock(owner, :frog).returns 5
56
+ subject.frog.should eq 5
57
+ end
58
+
59
+ it "redirect a setter (that has no corresponding getter)" do
60
+ mock(owner, :knees=).with(1).returns(5)
61
+ subject.knees(1).should eq 5
62
+ end
63
+
64
+ it "redirect a getter (that has a corresponding setter)" do
65
+ mock(owner, :fish).returns(5)
66
+ subject.fish.should eq 5
67
+ end
68
+
69
+ it "redirect a setter (that has a corresponding getter)" do
70
+ mock(owner, :fish=).with(5).returns(5)
71
+ subject.fish(5).should eq 5
72
+ end
73
+
74
+ it "redirect a setter (that has a corresponding getter)" do
75
+ mock(owner).add_cheese(1, 2).returns(99)
76
+ subject.add_cheese(1, 2).should eq 99
77
+ end
78
+
79
+ it "should redirect a block as well as pass arguments" do
80
+ mock(owner).add_peas(1).yields(:knees)
81
+
82
+ yielded = nil
83
+ subject.add_peas(1) do |arg|
84
+ yielded = arg
85
+ end
86
+
87
+ yielded.should eq :knees
88
+ end
89
+ end
90
+
91
+ describe "" do
92
+ it "should instance_eval when given a block on the constructor (.new)" do
93
+ mock(owner).frog(5)
94
+
95
+ ConfigLeaf::Wrapper.new owner do
96
+ frog 5
97
+ end
98
+ end
99
+ end
100
+
101
+ describe ".wrap" do
102
+ it "should be an alias for .new" do
103
+ described_class.wrap(owner).should be_kind_of described_class
104
+ end
105
+
106
+ it "should be an alias for .new given a block" do
107
+ described_class.wrap(owner) {}.should be_kind_of described_class
108
+ end
109
+ end
110
+ end
data/spec/teststrap.rb ADDED
@@ -0,0 +1,8 @@
1
+ require 'rspec/autorun'
2
+ require 'rr'
3
+
4
+ RSpec.configure do |config|
5
+ config.mock_framework = :rr
6
+ end
7
+
8
+ require File.expand_path('../../lib/config_leaf', __FILE__)
metadata ADDED
@@ -0,0 +1,153 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: config_leaf
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Bil Bas (Spooner)
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-10-19 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 2.11.0
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 2.11.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: rr
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 1.0.4
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 1.0.4
46
+ - !ruby/object:Gem::Dependency
47
+ name: yard
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 0.8.3
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 0.8.3
62
+ - !ruby/object:Gem::Dependency
63
+ name: redcarpet
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: 2.1.1
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: 2.1.1
78
+ - !ruby/object:Gem::Dependency
79
+ name: rake
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ description: ! 'ConfigLeaf allows an object to be configured using a terse syntax
95
+
96
+ like Object#instance_eval, while not exposing the internals
97
+
98
+ (protected/private methods and ivars) of the object!
99
+
100
+ '
101
+ email:
102
+ - bil.bagpuss@gmail.com
103
+ executables: []
104
+ extensions: []
105
+ extra_rdoc_files: []
106
+ files:
107
+ - .gitignore
108
+ - Gemfile
109
+ - LICENSE
110
+ - README.md
111
+ - Rakefile
112
+ - config_leaf.gemspec
113
+ - lib/config_leaf.rb
114
+ - lib/config_leaf/version.rb
115
+ - lib/config_leaf/wrapper.rb
116
+ - spec/config_leaf/config_leaf_spec.rb
117
+ - spec/config_leaf/wrapper_spec.rb
118
+ - spec/teststrap.rb
119
+ homepage: https://github.com/spooner/config_leaf
120
+ licenses: []
121
+ post_install_message:
122
+ rdoc_options: []
123
+ require_paths:
124
+ - lib
125
+ required_ruby_version: !ruby/object:Gem::Requirement
126
+ none: false
127
+ requirements:
128
+ - - ! '>='
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ segments:
132
+ - 0
133
+ hash: 196843955
134
+ required_rubygems_version: !ruby/object:Gem::Requirement
135
+ none: false
136
+ requirements:
137
+ - - ! '>='
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
140
+ segments:
141
+ - 0
142
+ hash: 196843955
143
+ requirements: []
144
+ rubyforge_project:
145
+ rubygems_version: 1.8.23
146
+ signing_key:
147
+ specification_version: 3
148
+ summary: Terse configuration syntax for objects
149
+ test_files:
150
+ - spec/config_leaf/config_leaf_spec.rb
151
+ - spec/config_leaf/wrapper_spec.rb
152
+ - spec/teststrap.rb
153
+ has_rdoc: