simonmenke-ec 0.0.2

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/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2009 Simon Menke
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
data/README.textile ADDED
File without changes
@@ -0,0 +1,73 @@
1
+
2
+ module EC
3
+
4
+ class ListSpecification < Specification
5
+
6
+ attr_accessor :child # :nodoc:
7
+ attr_accessor :update_policy # :nodoc:
8
+
9
+ def initialize(name=:root, parent=nil, &block) # :nodoc:
10
+ @child = nil
11
+ @update_policy = :merge
12
+ super(name, parent) do |spec|
13
+ spec.should_be_a Array
14
+ block.call(spec) if block
15
+ end
16
+ end
17
+
18
+ def process(value, parent=nil, path=[:root]) # :nodoc:
19
+ @processors.each do |processor|
20
+ processor.call(self, value, parent, path)
21
+ end
22
+ value.each_with_index do |v , idx|
23
+ @child.process(v, value, path + [idx])
24
+ end
25
+ end
26
+
27
+ # specify a list as the list element
28
+ def list(&block)
29
+ @child ||= EC::ListSpecification.new(:item, self)
30
+ block.call(@child)
31
+ end
32
+
33
+ # specify a terminal node as the list element
34
+ def conf(&block)
35
+ @child = EC::Specification.new(:item, self)
36
+ block.call(@child)
37
+ end
38
+
39
+ # specify a map as the list element
40
+ def map(&block)
41
+ @child = EC::MapSpecification.new(:item, self)
42
+ block.call(@child)
43
+ end
44
+
45
+ # set the update policy
46
+ def on_update(policy)
47
+ unless [:merger, :replace].include? policy
48
+ raise SpecificationError, "EC supports only :merge and :replace policies"
49
+ end
50
+ @update_policy = policy
51
+ end
52
+
53
+ def update(base, delta) # :nodoc:
54
+ case @update_policy
55
+ when :merge then merge(base, delta)
56
+ when :replace then replace(base, delta)
57
+ end
58
+ end
59
+
60
+ def merge(base, delta) # :nodoc:
61
+ result = []
62
+ result += base if base
63
+ result += delta if delta
64
+ result if base and delta
65
+ end
66
+
67
+ def replace(base, delta) # :nodoc:
68
+ delta == nil ? base : delta
69
+ end
70
+
71
+ end
72
+
73
+ end
data/lib/ec/macros.rb ADDED
@@ -0,0 +1,57 @@
1
+
2
+ module EC
3
+
4
+ class ValidationError < StandardError ; end
5
+ class SpecificationError < StandardError ; end
6
+
7
+ module Macros
8
+
9
+ # Add a processor
10
+ def processor(&block)
11
+ @processors << block
12
+ end
13
+
14
+ # Raise unless this spec is present in the parent
15
+ def should_be_present
16
+ unless parent.is_a? MapSpecification
17
+ raise SpecificationError, "#{pretty_path(parent.path)} needs to be of type Map"
18
+ end
19
+ parent.processor do |spec, value, parent, path|
20
+ unless value.include? self.name.to_sym
21
+ raise ValidationError, "#{pretty_path(path)} needs a #{self.name.inspect} entry"
22
+ end
23
+ end
24
+ end
25
+
26
+ # Raise unless this spec is of type <tt>type</tt>
27
+ def should_be_a(type)
28
+ processor do |spec, value, parent, path|
29
+ unless value.is_a? type
30
+ raise ValidationError, "#{pretty_path(path)} needs to be a #{type.inspect}"
31
+ end
32
+ end
33
+ end
34
+
35
+ # Raise if this spec is empty
36
+ def should_not_be_empty
37
+ processor do |spec, value, parent, path|
38
+ empty = if value.respond_to? :empty?
39
+ value.empty?
40
+ elsif value.respond_to? :count
41
+ value.count == 0
42
+ elsif value.respond_to? :size
43
+ value.size == 0
44
+ elsif value.respond_to? :length
45
+ value.length == 0
46
+ else
47
+ raise ValidationError, "#{pretty_path(path)} doesn't respond to :empty?, :count, :size or :length"
48
+ end
49
+ if empty
50
+ raise ValidationError, "#{pretty_path(path)} must not be empty."
51
+ end
52
+ end
53
+ end
54
+
55
+ end
56
+
57
+ end
@@ -0,0 +1,79 @@
1
+
2
+ module EC
3
+
4
+ class MapSpecification < Specification
5
+
6
+ attr_accessor :children # :nodoc:
7
+ attr_accessor :update_policy # :nodoc:
8
+
9
+ def initialize(name=:root, parent=nil, &block) # :nodoc:
10
+ @children = {}
11
+ @update_policy = :merge
12
+ super(name, parent) do |spec|
13
+ spec.should_be_a Hash
14
+ block.call(spec) if block
15
+ end
16
+ end
17
+
18
+ def process(value, parent=nil, path=[:root]) # :nodoc:
19
+ @processors.each do |processor|
20
+ processor.call(self, value, parent, path)
21
+ end
22
+ @children.each do |k,v|
23
+ v.process(value[k], value, path + [k]) if value.include? k
24
+ end
25
+ end
26
+
27
+ # specify a list with key name.
28
+ def list(name, &block)
29
+ @children[name.to_sym] ||= EC::ListSpecification.new(name, self)
30
+ block.call(@children[name.to_sym])
31
+ end
32
+
33
+ # specify a terminal node with key name.
34
+ def conf(name, &block)
35
+ @children[name.to_sym] ||= EC::Specification.new(name, self)
36
+ block.call(@children[name.to_sym])
37
+ end
38
+
39
+ # specify a map with key name.
40
+ def map(name, &block)
41
+ @children[name.to_sym] ||= EC::MapSpecification.new(name, self)
42
+ block.call(@children[name.to_sym])
43
+ end
44
+
45
+ # set the update policy
46
+ def on_update(policy)
47
+ unless [:merger, :replace].include? policy
48
+ raise SpecificationError, "EC supports only :merge and :replace policies"
49
+ end
50
+ @update_policy = policy
51
+ end
52
+
53
+ def update(base, delta) # :nodoc:
54
+ case @update_policy
55
+ when :merge then merge(base, delta)
56
+ when :replace then replace(base, delta)
57
+ end
58
+ end
59
+
60
+ def merge(base, delta) # :nodoc:
61
+ result = (base || {}).dup
62
+ delta.each do |k,v|
63
+ key = k.to_sym
64
+ if @children[key]
65
+ result[key] = @children[key].update(result[key], v)
66
+ else
67
+ result[key] = v
68
+ end
69
+ end
70
+ result
71
+ end
72
+
73
+ def replace(base, delta) # :nodoc:
74
+ delta == nil ? base : delta
75
+ end
76
+
77
+ end
78
+
79
+ end
@@ -0,0 +1,44 @@
1
+
2
+ module EC
3
+
4
+ class Specification
5
+
6
+ include Macros
7
+
8
+ # the name/key of this specification
9
+ attr_accessor :name
10
+ # the parent of this specification
11
+ attr_accessor :parent
12
+ attr_accessor :processors # :nodoc:
13
+
14
+ def initialize(name=:root, parent=nil, &block) # :nodoc:
15
+ @name = name
16
+ @parent = parent
17
+ @processors = []
18
+ block.call(self) if block
19
+ end
20
+
21
+ def process(value, parent=nil, path=[:root]) # :nodoc:
22
+ @processors.each do |processor|
23
+ processor.call(self, value, parent, path)
24
+ end
25
+ end
26
+
27
+ def update(base, delta) # :nodoc:
28
+ delta == nil ? base : delta
29
+ end
30
+
31
+ private
32
+
33
+ def pretty_path(path) # :nodoc:
34
+ path.collect do |component|
35
+ case component
36
+ when Symbol, String then ".#{component}"
37
+ when Fixnum then "[#{component}]"
38
+ end
39
+ end.join().sub(/^\./, '')
40
+ end
41
+
42
+ end
43
+
44
+ end
data/lib/ec/store.rb ADDED
@@ -0,0 +1,54 @@
1
+
2
+ module EC
3
+
4
+ class Store
5
+
6
+ attr_accessor :specification
7
+
8
+ # pass a block to specify this store
9
+ def initialize(&block)
10
+ specify &block
11
+ @data = nil
12
+ end
13
+
14
+ # edit this stores specifictaion
15
+ def specify(&block)
16
+ @specification ||= EC::MapSpecification.new
17
+ block.call(@specification) if block
18
+ @specification
19
+ end
20
+
21
+ # reset the date in this store. When you pass true the specifictaion will be reset too.
22
+ def reset!(clear_specification=false)
23
+ @data = nil
24
+ @specification = nil if clear_specification
25
+ end
26
+
27
+ # configre this store with a ruby object. The root must be a Hash.
28
+ def configure_with_ruby(ruby)
29
+ @data = @specification.update(@data, ruby)
30
+ end
31
+
32
+ # configre this store with a YAML file.
33
+ def configure_with_yaml(path)
34
+ configure_with_ruby YAML.load_file(path)
35
+ end
36
+
37
+ # validate this store. This method will raise a EC::ValidationError when the store is invalid
38
+ def validate
39
+ @specification.process(@data)
40
+ end
41
+
42
+ # access the data in this store.
43
+ def config
44
+ @data || {}
45
+ end
46
+
47
+ end
48
+
49
+ # access the global store.
50
+ def self.store
51
+ @__store = EC::Store.new
52
+ end
53
+
54
+ end
data/lib/ec.rb ADDED
@@ -0,0 +1,12 @@
1
+
2
+ require 'yaml'
3
+
4
+ module EC
5
+
6
+ end
7
+
8
+ require File.dirname(__FILE__)+'/ec/store'
9
+ require File.dirname(__FILE__)+'/ec/macros'
10
+ require File.dirname(__FILE__)+'/ec/specification'
11
+ require File.dirname(__FILE__)+'/ec/map_specification'
12
+ require File.dirname(__FILE__)+'/ec/list_specification'
@@ -0,0 +1,71 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class ListSpecificationTest < Test::Unit::TestCase
4
+
5
+ context "ListSpecification" do
6
+
7
+ setup do
8
+
9
+ @spec = EC::ListSpecification.new do |list|
10
+ list.map do |map|
11
+ map.conf :name do |name|
12
+ name.should_be_present
13
+ name.should_be_a String
14
+ name.should_not_be_empty
15
+ end
16
+ map.conf :age do |age|
17
+ age.should_be_a Numeric
18
+ end
19
+ end
20
+ end
21
+
22
+ end
23
+
24
+ should "raise ValidationError with nil" do
25
+ lambda{
26
+ @spec.process(nil)
27
+ }.should raise_error(EC::ValidationError, /root needs to be a Array/)
28
+ end
29
+
30
+ should "raise ValidationError with empty string" do
31
+ lambda{
32
+ @spec.process('')
33
+ }.should raise_error(EC::ValidationError, /root needs to be a Array/)
34
+ end
35
+
36
+ should "not raise ValidationError with empty array" do
37
+ lambda{
38
+ @spec.process([])
39
+ }.should_not raise_error(EC::ValidationError)
40
+ end
41
+
42
+ should "not raise ValidationError with valid array" do
43
+ lambda{
44
+ @spec.process([
45
+ {:name => 'simon', :age => 22},
46
+ {:name => 'anais'}
47
+ ])
48
+ }.should_not raise_error(EC::ValidationError)
49
+ end
50
+
51
+ should "not raise ValidationError with invalid age" do
52
+ lambda{
53
+ @spec.process([
54
+ {:name => 'simon'},
55
+ {:name => 'anais', :age => '21'}
56
+ ])
57
+ }.should raise_error(EC::ValidationError, "root[1].age needs to be a Numeric")
58
+ end
59
+
60
+ should "not raise ValidationError with invalid map" do
61
+ lambda{
62
+ @spec.process([
63
+ {},
64
+ {:name => 'anais'}
65
+ ])
66
+ }.should raise_error(EC::ValidationError, "root[0] needs a :name entry")
67
+ end
68
+
69
+ end
70
+
71
+ end
@@ -0,0 +1,57 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class MapSpecificationTest < Test::Unit::TestCase
4
+
5
+ context "MapSpecification" do
6
+
7
+ setup do
8
+ @spec = EC::MapSpecification.new do |map|
9
+ map.should_be_a Hash
10
+ map.should_not_be_empty
11
+ map.conf(:hello) do |hello|
12
+ hello.should_be_a String
13
+ hello.should_not_be_empty
14
+ end
15
+ map.conf(:bye) do |bye|
16
+ bye.should_be_present
17
+ end
18
+ map.conf(:bye) do |bye|
19
+ bye.should_be_a String
20
+ bye.should_not_be_empty
21
+ end
22
+ end
23
+ end
24
+
25
+ should "raise ValidationError with nil" do
26
+ lambda{
27
+ @spec.process(nil)
28
+ }.should raise_error(EC::ValidationError, /root needs to be a Hash/)
29
+ end
30
+
31
+ should "raise ValidationError with empty string" do
32
+ lambda{
33
+ @spec.process('')
34
+ }.should raise_error(EC::ValidationError, /root needs to be a Hash/)
35
+ end
36
+
37
+ should "raise ValidationError with empty hash" do
38
+ lambda{
39
+ @spec.process({})
40
+ }.should raise_error(EC::ValidationError, /root must not be empty./)
41
+ end
42
+
43
+ should "not raise ValidationError with none empty hash" do
44
+ lambda{
45
+ @spec.process({ :bye => 'moon' })
46
+ }.should_not raise_error(EC::ValidationError)
47
+ end
48
+
49
+ should "raise ValidationError with invalid child" do
50
+ lambda{
51
+ @spec.process({ :bye => 1 })
52
+ }.should raise_error(EC::ValidationError, /root.bye needs to be a String/)
53
+ end
54
+
55
+ end
56
+
57
+ end
@@ -0,0 +1,28 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class SpecificationTest < Test::Unit::TestCase
4
+
5
+ context "Specification" do
6
+
7
+ setup do
8
+ @spec = EC::Specification.new do |value|
9
+ value.should_be_a String
10
+ value.should_not_be_empty
11
+ end
12
+ end
13
+
14
+ should "raise ValidationError with nil" do
15
+ lambda{
16
+ @spec.process(nil)
17
+ }.should raise_error(EC::ValidationError, /root needs to be a String/)
18
+ end
19
+
20
+ should "raise ValidationError with empty string" do
21
+ lambda{
22
+ @spec.process('')
23
+ }.should raise_error(EC::ValidationError, /root must not be empty./)
24
+ end
25
+
26
+ end
27
+
28
+ end
@@ -0,0 +1,65 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class StoreTest < Test::Unit::TestCase
4
+
5
+ context 'Store' do
6
+
7
+ setup do
8
+ @path = File.dirname(__FILE__)+'/test.yml'
9
+ File.open(@path, 'w+') do |f|
10
+ f.write YAML.dump({'general' => { 'name' => 'ec', 'version' => '0.0.1' }})
11
+ end
12
+
13
+ @path2 = File.dirname(__FILE__)+'/test2.yml'
14
+ File.open(@path2, 'w+') do |f|
15
+ f.write YAML.dump({'general' => { 'name' => 'ec2' }})
16
+ end
17
+
18
+ @store = EC::Store.new do |root|
19
+ root.map(:general) do |general|
20
+ general.should_be_present
21
+ general.conf(:name) do |name|
22
+ name.should_be_present
23
+ name.should_be_a String
24
+ name.should_not_be_empty
25
+ end
26
+ end
27
+ end
28
+
29
+ @store.configure_with_yaml(@path)
30
+ end
31
+
32
+ should "be valid" do
33
+ lambda { @store.validate }.should_not raise_error(EC::ValidationError)
34
+ end
35
+
36
+ should "have correct data" do
37
+ @store.config[:general][:name].should be('ec')
38
+ @store.config[:general][:version].should be('0.0.1')
39
+ end
40
+
41
+ should "merge data" do
42
+ @store.configure_with_yaml(@path2)
43
+ @store.config[:general][:name].should be('ec2')
44
+ @store.config[:general][:version].should be('0.0.1')
45
+ end
46
+
47
+ should "reset!" do
48
+ @store.reset!
49
+ @store.config.should be({})
50
+ end
51
+
52
+ should "reset!(true)" do
53
+ @store.reset!(true)
54
+ @store.config.should be({})
55
+ @store.specification.should be(nil)
56
+ end
57
+
58
+ end
59
+
60
+ def teardown
61
+ File.unlink @path if File.exist? @path
62
+ File.unlink @path2 if File.exist? @path2
63
+ end
64
+
65
+ end
@@ -0,0 +1,55 @@
1
+ require 'test/unit'
2
+ require 'rubygems'
3
+ require 'context'
4
+ require 'matchy'
5
+
6
+ require File.dirname(__FILE__)+'/../lib/ec'
7
+
8
+ class Test::Unit::TestCase
9
+
10
+ def raise_error(obj = StandardError, msg_re=nil)
11
+ e = ::EC::RaiseExceptionExpectation.new(obj, self)
12
+ msg_re = Regexp.new(Regexp.escape(msg_re)) if msg_re.is_a? String
13
+ e.msg_re = msg_re
14
+ e
15
+ end
16
+
17
+ end
18
+
19
+ module EC
20
+ class RaiseExceptionExpectation < Matchy::Expectations::Base
21
+ attr_accessor :msg_re
22
+ def matches?(receiver)
23
+ @receiver = receiver
24
+ begin
25
+ receiver.call
26
+ return false
27
+ rescue StandardError => e
28
+ @error = e
29
+ return false unless e.class.ancestors.include?(@expected)
30
+ return false if @msg_re and e.message !~ @msg_re
31
+
32
+ return true
33
+ end
34
+ end
35
+
36
+ def failure_message
37
+ extra = ""
38
+ if @error
39
+ if !@error.class.ancestors.include?(@expected)
40
+ extra = "but #{@error.class.name} was raised instead"
41
+ elsif @msg_re
42
+ extra = "but #{@error.message.inspect} did not match #{@msg_re.inspect}"
43
+ end
44
+ else
45
+ extra = "but none was raised"
46
+ end
47
+
48
+ "Expected #{@receiver.inspect} to raise #{@expected.name}, #{extra}."
49
+ end
50
+
51
+ def negative_failure_message
52
+ "Expected #{@receiver.inspect} to not raise #{@expected.name}."
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,65 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class UpdateTest < Test::Unit::TestCase
4
+
5
+ context "MapSpecification" do
6
+
7
+ setup do
8
+ @spec = EC::MapSpecification.new do |map|
9
+ map.should_not_be_empty
10
+ map.conf(:hello) do |hello|
11
+ hello.should_be_a String
12
+ hello.should_not_be_empty
13
+ end
14
+ map.conf(:bye) do |bye|
15
+ bye.should_be_present
16
+ end
17
+ map.conf(:bye) do |bye|
18
+ bye.should_be_a String
19
+ bye.should_not_be_empty
20
+ end
21
+ end
22
+ end
23
+
24
+ should "update with replace policy" do
25
+ @spec.update_policy = :replace
26
+ result = @spec.update({:hello => 'world', :bye => 'moon'}, {:hello => 'sun', :bye => 'earth'})
27
+ result.should be({:hello => 'sun', :bye => 'earth'})
28
+ end
29
+
30
+ should "update with merge policy" do
31
+ @spec.update_policy = :merge
32
+ result = @spec.update({:hello => 'world', :bye => 'moon'}, {:bye => 'earth'})
33
+ result.should be({:hello => 'world', :bye => 'earth'})
34
+ end
35
+
36
+ end
37
+
38
+ context "ListSpecification" do
39
+
40
+ setup do
41
+ @spec = EC::ListSpecification.new do |list|
42
+ list.should_not_be_empty
43
+ list.conf do |hello|
44
+ hello.should_be_a String
45
+ hello.should_not_be_empty
46
+ end
47
+ end
48
+ end
49
+
50
+ should "update with replace policy" do
51
+ @spec.update_policy = :replace
52
+ result = @spec.update(%w( world moon ), %w( sun earth ))
53
+ result.should be(%w( sun earth ))
54
+ end
55
+
56
+ should "update with merge policy" do
57
+ @spec.update_policy = :merge
58
+ result = @spec.update(%w( world moon ), %w( sun earth ))
59
+ result.should be(%w( world moon sun earth ))
60
+ end
61
+
62
+ end
63
+
64
+
65
+ end
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: simonmenke-ec
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Simon Menke
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-01-24 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: simon.menke@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - test/list_specification_test.rb
26
+ - test/map_specification_test.rb
27
+ - test/specification_test.rb
28
+ - test/store_test.rb
29
+ - test/test_helper.rb
30
+ - test/update_test.rb
31
+ - LICENSE.txt
32
+ - README.textile
33
+ - lib/ec/list_specification.rb
34
+ - lib/ec/macros.rb
35
+ - lib/ec/map_specification.rb
36
+ - lib/ec/specification.rb
37
+ - lib/ec/store.rb
38
+ - lib/ec.rb
39
+ has_rdoc: true
40
+ homepage: http://github.com/simonmenke/ec
41
+ post_install_message:
42
+ rdoc_options: []
43
+
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: "0"
51
+ version:
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ version:
58
+ requirements: []
59
+
60
+ rubyforge_project: ec
61
+ rubygems_version: 1.2.0
62
+ signing_key:
63
+ specification_version: 2
64
+ summary: EC (EasyConfig) brings verifiable configuration.
65
+ test_files:
66
+ - test/list_specification_test.rb
67
+ - test/map_specification_test.rb
68
+ - test/specification_test.rb
69
+ - test/store_test.rb
70
+ - test/test_helper.rb
71
+ - test/update_test.rb