configuration_dsl 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,6 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
6
+ README.rdoc
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development do
9
+ gem "bundler", "~> 1.0.0"
10
+ gem "jeweler", "~> 1.5.2"
11
+ gem "rcov", ">= 0"
12
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,18 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ git (1.2.5)
5
+ jeweler (1.5.2)
6
+ bundler (~> 1.0.0)
7
+ git (>= 1.2.5)
8
+ rake
9
+ rake (0.8.7)
10
+ rcov (0.9.9)
11
+
12
+ PLATFORMS
13
+ ruby
14
+
15
+ DEPENDENCIES
16
+ bundler (~> 1.0.0)
17
+ jeweler (~> 1.5.2)
18
+ rcov
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Christopher J Bottaro
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,150 @@
1
+ = configuration_dsl
2
+
3
+ Easily configure classes and objects using a DSL.
4
+
5
+ == Description
6
+
7
+ configuration_dsl encapsulates the pattern of using a DSL to "configure" objects and/or classes:
8
+
9
+ class MyAwesomeClass
10
+ ... some setup code here ...
11
+ configure do
12
+ greeting "Hello!"
13
+ count 5
14
+ end
15
+ end
16
+
17
+ MyAwesomeClass.configuration.greeting
18
+ # => "Hello!"
19
+
20
+ MyAwesomeClass.configuration.count
21
+ # => 5
22
+
23
+ == Usage
24
+
25
+ Bet you're wondering what goes in that "some setup code here" placeholder. It's nothing too complicated. Here's a complete example.
26
+
27
+ module Configuration
28
+ DEFAULTS = {
29
+ :greeting => "Hi.",
30
+ :count => 1
31
+ }
32
+ end
33
+
34
+ require "configuration_dsl"
35
+
36
+ class MyAwesomeClass
37
+ extend ConfigurationDsl
38
+ configure_with(Configuration)
39
+ configure do
40
+ greeting "Hello!"
41
+ end
42
+ end
43
+
44
+ MyAwesomeClass.configuration.greeting
45
+ # => "Hello!"
46
+
47
+ MyAwesomeClass.configuration.count
48
+ # => 1
49
+
50
+ The basic idea is that you define a module with your configuration options. The module must contain a constant called DEFAULTS that has the configuration option names and their default values. You can then optionally define setter methods for each configuration option (more on that later), or just use the ones that are automatically created for you.
51
+
52
+ == Why a module?
53
+
54
+ Why are the configuration options stored in a module? It makes for pretty documentation. Instead of using the automatically generated setters, you can write (and document!) your own.
55
+
56
+ module Configuration
57
+ DEFAULTS = {
58
+ :greeting => "Hi.",
59
+ :count => 1
60
+ }
61
+
62
+ # Set the phrase used to greet someone.
63
+ def greeting(phrase)
64
+ configuration.greeting = value
65
+ end
66
+
67
+ # How many times to greet someone. +n+ must be greater than zero.
68
+ def count(n)
69
+ raise ArgumentError, "count must be greater than zero" if n <= 0
70
+ configuration.count = n
71
+ end
72
+ end
73
+
74
+ Just run rdoc on that module and you have all your configuration options documented in one easy to link to place.
75
+
76
+ == Working with objects
77
+
78
+ configuration_dsl works with plain objects (in addition to classes). Given the module Configuration from our previous examples:
79
+
80
+ foo = Foo.new
81
+ foo.extend(ConfigurationDsl)
82
+ foo.configure_with(Configuration)
83
+ foo.configure do
84
+ greeting "What up?"
85
+ count 2
86
+ end
87
+ foo.configuration.greeting
88
+ # => "What up?"
89
+ foo.configuration.count
90
+ # => 2
91
+
92
+ == Working with classes
93
+
94
+ If you use configuration_dsl with classes, then derived classes should inherit the configuration in a sane and predictable way.
95
+
96
+ == Complex setters
97
+
98
+ You can have a single setter for multiple configuration options.
99
+
100
+ module Configuration
101
+ DEFAULTS = {
102
+ :frequence => 0,
103
+ :callback => nil
104
+ }
105
+
106
+ def set_callback(f, &block)
107
+ configuration.frequence = f
108
+ configuration.callback = block
109
+ end
110
+ end
111
+
112
+ == Callback
113
+
114
+ You can set a callback to be called after each time +configure+ is called. Just pass a block to +configure_with+.
115
+
116
+ class MyClass
117
+ extend ConfigurationDsl
118
+ configure_with(SomeConfigurationModule) do
119
+ @configure_count ||= 0
120
+ @configure_count += 1
121
+ end
122
+
123
+ configure do
124
+ some_option "something"
125
+ end
126
+ end
127
+
128
+ MyClass.configure do
129
+ another_option "something else"
130
+ end
131
+
132
+ MyClass.instance_variable_get(:@configure_count)
133
+ # => 2
134
+
135
+ This is useful if you need to run some kind of initialization code after your class or object has been configured.
136
+
137
+ == Contributing to configuration_dsl
138
+
139
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
140
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
141
+ * Fork the project
142
+ * Start a feature/bugfix branch
143
+ * Commit and push until you are happy with your contribution
144
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
145
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
146
+
147
+ == Copyright
148
+
149
+ Copyright (c) 2011 Christopher J Bottaro. See LICENSE.txt for further details.
150
+
data/Rakefile ADDED
@@ -0,0 +1,53 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
+ gem.name = "configuration_dsl"
16
+ gem.homepage = "http://github.com/cjbottaro/configuration_dsl"
17
+ gem.license = "MIT"
18
+ gem.summary = %Q{Configure classes and objects using a DSL.}
19
+ gem.description = %Q{Easily configure a class (or object) with a DSL.}
20
+ gem.email = "cjbottaro@alumni.cs.utexas.edu"
21
+ gem.authors = ["Christopher J Bottaro"]
22
+ # Include your dependencies below. Runtime dependencies are required when using your gem,
23
+ # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
24
+ # gem.add_runtime_dependency 'jabber4r', '> 0.1'
25
+ # gem.add_development_dependency 'rspec', '> 1.2.3'
26
+ end
27
+ Jeweler::RubygemsDotOrgTasks.new
28
+
29
+ require 'rake/testtask'
30
+ Rake::TestTask.new(:test) do |test|
31
+ test.libs << 'lib' << 'test'
32
+ test.pattern = 'test/**/test_*.rb'
33
+ test.verbose = true
34
+ end
35
+
36
+ require 'rcov/rcovtask'
37
+ Rcov::RcovTask.new do |test|
38
+ test.libs << 'test'
39
+ test.pattern = 'test/**/test_*.rb'
40
+ test.verbose = true
41
+ end
42
+
43
+ task :default => :test
44
+
45
+ require 'rake/rdoctask'
46
+ Rake::RDocTask.new do |rdoc|
47
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
48
+
49
+ rdoc.rdoc_dir = 'rdoc'
50
+ rdoc.title = "configuration_dsl #{version}"
51
+ rdoc.rdoc_files.include('README*')
52
+ rdoc.rdoc_files.include('lib/**/*.rb')
53
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.0
@@ -0,0 +1,52 @@
1
+ require "configuration_dsl/dsl"
2
+ require "configuration_dsl/impl"
3
+
4
+ module ConfigurationDsl
5
+
6
+ class Error < RuntimeError; end
7
+
8
+ VERSION = File.read(File.dirname(__FILE__)+"/../VERSION")
9
+
10
+ def configure_with(configuration_module, &block)
11
+ @configuration_dsl ||= Impl.new(self)
12
+ @configuration_dsl.module = configuration_module
13
+ @configuration_dsl.callback = block if block_given?
14
+ @configuration_dsl.default_configuration!
15
+
16
+ # Automatically define setters.
17
+ @configuration_dsl.module.module_eval do
18
+ self::DEFAULTS.keys.each do |name|
19
+ next if method_defined?(name) # Don't override custom setters.
20
+ module_eval <<-code
21
+ def #{name}(value)
22
+ configuration.#{name} = value
23
+ end
24
+ code
25
+ end
26
+ end
27
+ end
28
+
29
+ def configure(&block)
30
+ @configuration_dsl ||= Impl.new(self)
31
+
32
+ # Instance eval the block.
33
+ if block_given?
34
+ _module = @configuration_dsl.find_module
35
+ raise Error, "cannot find configuration module" unless _module
36
+ dsl = Dsl.new(Impl.dup_struct(configuration)) # Dup it to unfreeze it.
37
+ dsl.send(:extend, _module)
38
+ dsl.instance_eval(&block)
39
+ @configuration_dsl.configuration = dsl.configuration.freeze
40
+ end
41
+
42
+ # Run the callback.
43
+ callback = @configuration_dsl.find_callback
44
+ instance_eval(&callback) if callback
45
+ end
46
+
47
+ def configuration
48
+ @configuration_dsl ||= Impl.new(self)
49
+ @configuration_dsl.find_configuration
50
+ end
51
+
52
+ end
@@ -0,0 +1,9 @@
1
+ module ConfigurationDsl
2
+ class Dsl
3
+ attr_reader :configuration
4
+
5
+ def initialize(configuration)
6
+ @configuration = configuration
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,59 @@
1
+ module ConfigurationDsl
2
+ class Impl
3
+ attr_accessor :module, :callback, :configuration
4
+
5
+ def self.dup_struct(struct)
6
+ members = struct.members.collect{ |member| member.to_sym } # Normalize between Ruby versions.
7
+ values = struct.values.collect do |value|
8
+ if value.kind_of?(Class)
9
+ value
10
+ else
11
+ value.dup rescue value
12
+ end
13
+ end
14
+ Struct.new(*members).new(*values)
15
+ end
16
+
17
+ def initialize(object)
18
+ @object = object
19
+ end
20
+
21
+ def find_module
22
+ ancestors.each do |object|
23
+ next unless (impl = object.instance_variable_get(:@configuration_dsl))
24
+ return impl.module if impl.module
25
+ end
26
+ nil
27
+ end
28
+
29
+ def find_callback
30
+ ancestors.each do |object|
31
+ next unless (impl = object.instance_variable_get(:@configuration_dsl))
32
+ return impl.callback if impl.callback
33
+ end
34
+ nil
35
+ end
36
+
37
+ def find_configuration
38
+ ancestors.each do |object|
39
+ next unless (impl = object.instance_variable_get(:@configuration_dsl))
40
+ return impl.configuration if impl.configuration
41
+ end
42
+ nil
43
+ end
44
+
45
+ def default_configuration
46
+ defaults = find_module.const_get(:DEFAULTS)
47
+ Struct.new(*defaults.keys).new(*defaults.values)
48
+ end
49
+
50
+ def default_configuration!
51
+ @configuration = default_configuration.freeze
52
+ end
53
+
54
+ def ancestors
55
+ @object.respond_to?(:ancestors) ? @object.ancestors : [@object]
56
+ end
57
+
58
+ end
59
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,52 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'test/unit'
11
+
12
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
13
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
14
+ require 'configuration_dsl'
15
+
16
+ class Test::Unit::TestCase
17
+ attr_reader :configuration_module, :auto_module, :object
18
+
19
+ def setup
20
+ @configuration_module = Module.new do
21
+ const_set(:DEFAULTS, {
22
+ :a => nil,
23
+ :b => :b,
24
+ :c => "c"
25
+ })
26
+
27
+ def a(v)
28
+ configuration.a = v
29
+ end
30
+
31
+ def b(v)
32
+ configuration.b = v
33
+ end
34
+
35
+ def c(v)
36
+ configuration.c = v
37
+ end
38
+ end
39
+
40
+ @auto_module = Module.new do
41
+ const_set(:DEFAULTS, {
42
+ :a => nil,
43
+ :b => :b,
44
+ :c => "c"
45
+ })
46
+ def c(v)
47
+ configuration.c = "c:#{v}"
48
+ end
49
+ end
50
+ end
51
+
52
+ end
@@ -0,0 +1,155 @@
1
+ require 'helper'
2
+
3
+ class TestConfigurationDsl < Test::Unit::TestCase
4
+
5
+ def both_configure
6
+ object.extend(ConfigurationDsl)
7
+ object.configure_with(configuration_module)
8
+ assert_equal nil, object.configuration.a
9
+ assert_equal :b, object.configuration.b
10
+ assert_equal "c", object.configuration.c
11
+
12
+ object.configure{ a("a") }
13
+ assert_equal "a", object.configuration.a
14
+ assert_equal :b, object.configuration.b
15
+ assert_equal "c", object.configuration.c
16
+ end
17
+
18
+ def both_callback
19
+ object.extend(ConfigurationDsl)
20
+ object.configure_with(configuration_module){ @something = "blahtest" }
21
+ object.configure
22
+ assert_equal "blahtest", object.instance_variable_get(:@something)
23
+ end
24
+
25
+ def test_inheritance
26
+ klass = Class.new
27
+ klass.extend ConfigurationDsl
28
+ klass.configure_with(configuration_module)
29
+ klass.configure do
30
+ b "bee"
31
+ end
32
+
33
+ derived = Class.new(klass)
34
+ assert_equal nil, derived.configuration.a
35
+ assert_equal "bee", derived.configuration.b
36
+ assert_equal "c", derived.configuration.c
37
+
38
+ derived.configure do
39
+ c :sea
40
+ end
41
+ assert_equal nil, derived.configuration.a
42
+ assert_equal "bee", derived.configuration.b
43
+ assert_equal :sea, derived.configuration.c
44
+
45
+ # Make sure the inherited class's configuration didn't change.
46
+ assert_equal nil, klass.configuration.a
47
+ assert_equal "bee", klass.configuration.b
48
+ assert_equal "c", klass.configuration.c
49
+ end
50
+
51
+ def test_deep_inheritance
52
+
53
+ # Obscure bug where the base class extends ConfigurationDsl and calls
54
+ # configure_with, but never configure. Then a class inherits and does
55
+ # call configure.
56
+
57
+ klass = Class.new
58
+ klass.extend ConfigurationDsl
59
+ klass.configure_with(configuration_module)
60
+
61
+ derived = Class.new(klass)
62
+ derived.configure do
63
+ c :sea
64
+ end
65
+
66
+ assert_equal :sea, derived.configuration.c
67
+ end
68
+
69
+ def test_inherit_does_not_dup_classes
70
+ base = Class.new
71
+ base.extend ConfigurationDsl
72
+ base.configure_with(configuration_module)
73
+
74
+ require "timeout"
75
+ base.configure do
76
+ a Timeout::Error
77
+ end
78
+
79
+ derived = Class.new(base)
80
+ assert_equal Timeout::Error, derived.configuration.a
81
+ end
82
+
83
+ def test_override_inheritance
84
+ klass = Class.new
85
+ klass.extend ConfigurationDsl
86
+ klass.configure_with(configuration_module)
87
+ klass.configure do
88
+ b "bee"
89
+ end
90
+
91
+ derived = Class.new(klass)
92
+ assert_equal nil, derived.configuration.a
93
+ assert_equal "bee", derived.configuration.b
94
+ assert_equal "c", derived.configuration.c
95
+
96
+ new_module = Module.new do
97
+ const_set(:DEFAULTS, {
98
+ :x => "x",
99
+ :y => "y",
100
+ :z => "z"
101
+ })
102
+ def x(v); configuration.x = v; end
103
+ def y(v); configuration.y = v; end
104
+ def z(v); configuration.z = v; end
105
+ end
106
+
107
+ derived.configure_with(new_module)
108
+ assert_equal "x", derived.configuration.x
109
+ assert_equal "y", derived.configuration.y
110
+ assert_equal "z", derived.configuration.z
111
+ end
112
+
113
+ def test_frozen_configuration
114
+ object = Object.new
115
+ object.extend ConfigurationDsl
116
+ object.configure_with(configuration_module)
117
+ assert_equal :b, object.configuration.b
118
+ assert_raises(RuntimeError){ object.configuration.b = "something" }
119
+ object.configure do
120
+ b "bee"
121
+ end
122
+ assert_equal "bee", object.configuration.b
123
+ assert_raises(RuntimeError){ object.configuration.b = "something" }
124
+ end
125
+
126
+ def both_auto_setters
127
+ object.extend ConfigurationDsl
128
+ object.configure_with(auto_module)
129
+ object.configure do
130
+ a "aye"
131
+ b "bee"
132
+ c "sea"
133
+ end
134
+ assert_equal "aye", object.configuration.a
135
+ assert_equal "bee", object.configuration.b
136
+ assert_equal "c:sea", object.configuration.c # Custom setters.
137
+ end
138
+
139
+ instance_methods.each do |method_name|
140
+ if method_name.to_s =~ /^both_/
141
+ base_name = method_name.to_s.sub(/^both_/, "")
142
+ class_eval <<-code
143
+ def test_object_#{base_name}
144
+ @object = Object.new
145
+ #{method_name}
146
+ end
147
+ def test_class_#{base_name}
148
+ @object = Class.new
149
+ #{method_name}
150
+ end
151
+ code
152
+ end
153
+ end
154
+
155
+ end
metadata ADDED
@@ -0,0 +1,120 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: configuration_dsl
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 2
8
+ - 0
9
+ version: 0.2.0
10
+ platform: ruby
11
+ authors:
12
+ - Christopher J Bottaro
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-02-02 00:00:00 -06:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: bundler
22
+ requirement: &id001 !ruby/object:Gem::Requirement
23
+ none: false
24
+ requirements:
25
+ - - ~>
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 1
29
+ - 0
30
+ - 0
31
+ version: 1.0.0
32
+ type: :development
33
+ prerelease: false
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: jeweler
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ~>
41
+ - !ruby/object:Gem::Version
42
+ segments:
43
+ - 1
44
+ - 5
45
+ - 2
46
+ version: 1.5.2
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: *id002
50
+ - !ruby/object:Gem::Dependency
51
+ name: rcov
52
+ requirement: &id003 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ segments:
58
+ - 0
59
+ version: "0"
60
+ type: :development
61
+ prerelease: false
62
+ version_requirements: *id003
63
+ description: Easily configure a class (or object) with a DSL.
64
+ email: cjbottaro@alumni.cs.utexas.edu
65
+ executables: []
66
+
67
+ extensions: []
68
+
69
+ extra_rdoc_files:
70
+ - LICENSE.txt
71
+ - README.rdoc
72
+ files:
73
+ - .document
74
+ - Gemfile
75
+ - Gemfile.lock
76
+ - LICENSE.txt
77
+ - README.rdoc
78
+ - Rakefile
79
+ - VERSION
80
+ - lib/configuration_dsl.rb
81
+ - lib/configuration_dsl/dsl.rb
82
+ - lib/configuration_dsl/impl.rb
83
+ - test/helper.rb
84
+ - test/test_configuration_dsl.rb
85
+ has_rdoc: true
86
+ homepage: http://github.com/cjbottaro/configuration_dsl
87
+ licenses:
88
+ - MIT
89
+ post_install_message:
90
+ rdoc_options: []
91
+
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ hash: -826723195589293974
100
+ segments:
101
+ - 0
102
+ version: "0"
103
+ required_rubygems_version: !ruby/object:Gem::Requirement
104
+ none: false
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ segments:
109
+ - 0
110
+ version: "0"
111
+ requirements: []
112
+
113
+ rubyforge_project:
114
+ rubygems_version: 1.3.7
115
+ signing_key:
116
+ specification_version: 3
117
+ summary: Configure classes and objects using a DSL.
118
+ test_files:
119
+ - test/helper.rb
120
+ - test/test_configuration_dsl.rb