simonmenke-ec 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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