castor 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +149 -5
- data/lib/castor.rb +1 -0
- data/lib/castor/configuration.rb +18 -72
- data/lib/castor/configuration/node.rb +72 -0
- data/lib/castor/version.rb +1 -1
- data/spec/castor_spec.rb +39 -17
- metadata +3 -2
data/README.md
CHANGED
@@ -1,6 +1,154 @@
|
|
1
1
|
# Castor
|
2
2
|
|
3
|
-
|
3
|
+
Castor is intended to help define third-party developer facing configurations for gems.
|
4
|
+
|
5
|
+
## Usage
|
6
|
+
|
7
|
+
The goal of Castor is to use it to define Gem configurations. As such you would probably do something like this:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
class MyGem
|
11
|
+
def configure(&block)
|
12
|
+
@config = Castor.configure do |config|
|
13
|
+
config.def :name, :old_value
|
14
|
+
end
|
15
|
+
block.call(@config) if block
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
gem = MyGem.new.configure do |config|
|
20
|
+
config.name = :new_value
|
21
|
+
end
|
22
|
+
```
|
23
|
+
|
24
|
+
There are many ways to define config values.
|
25
|
+
|
26
|
+
### Basic use-case
|
27
|
+
|
28
|
+
Setting config nodes this way will not use validation in any way, but makes it easy to quickly define nodes
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
config.def :name, :value
|
32
|
+
```
|
33
|
+
|
34
|
+
### Mass assignment
|
35
|
+
|
36
|
+
Self explanatory. As will the basic setter, this doesn't offer any validation.
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
config.def_many(:name => :value, :other_name => 'toto')
|
40
|
+
```
|
41
|
+
|
42
|
+
|
43
|
+
### Adding validations
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
config.def :validated_symbol do
|
47
|
+
type Symbol
|
48
|
+
default :value
|
49
|
+
end
|
50
|
+
|
51
|
+
config.def :validated_range do
|
52
|
+
value_in 1..50
|
53
|
+
default 1
|
54
|
+
end
|
55
|
+
```
|
56
|
+
|
57
|
+
The config nodes `validated_symbol` and `validated_range` will now have validation.
|
58
|
+
|
59
|
+
`validated_symbol` will only be accepted if new value is a Symbol
|
60
|
+
|
61
|
+
`validated_range` will only accept values between 1 and 50
|
62
|
+
|
63
|
+
If the validation fails, an `InvalidValueError` will be thrown
|
64
|
+
|
65
|
+
### Lazy evaluations
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
configuration = Castor.configure do |config|
|
69
|
+
i = 0
|
70
|
+
config.def :next_id, :lazy => lambda { i += 1 }
|
71
|
+
|
72
|
+
config.def :time_now do
|
73
|
+
type Time, Date
|
74
|
+
default { Time.now }
|
75
|
+
end
|
76
|
+
|
77
|
+
config.def :some_name, "a value"
|
78
|
+
end
|
79
|
+
|
80
|
+
# You can always pass lambdas as values,
|
81
|
+
# they will be lazy-evaluated when called
|
82
|
+
configuration.some_name = lambda { "some other value" }
|
83
|
+
|
84
|
+
configuration.next_id
|
85
|
+
# => 1
|
86
|
+
configuration.next_id
|
87
|
+
# => 2
|
88
|
+
configuration.time_now
|
89
|
+
# => 2013-03-22 23:32:03 -0400
|
90
|
+
|
91
|
+
configuration.some_name
|
92
|
+
# => "some other value"
|
93
|
+
```
|
94
|
+
|
95
|
+
Castor will validate the return value and throw an `InvalidValueError` if it is invalid
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
config.time_now = lambda { 3 }
|
99
|
+
config.time_now
|
100
|
+
#=> InvalidValueError
|
101
|
+
```
|
102
|
+
|
103
|
+
### Procs
|
104
|
+
|
105
|
+
Because Castor lazy-evals lambdas, if what you need is an actual proc, you'll have to enforce the type
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
configuration = Castor.configure do |config|
|
109
|
+
config.def :a_proc do
|
110
|
+
type Proc
|
111
|
+
default { :some_value }
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
configuration.a_proc
|
116
|
+
# => #<Proc:0x007ffdda2edda0 ...
|
117
|
+
```
|
118
|
+
|
119
|
+
### Nested config
|
120
|
+
|
121
|
+
You can nest Castor configurations. Castor will not create setters for the intermediate node. A user could therefore not overwrite it by accident.
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
configuration = Castor.configure do |config|
|
125
|
+
config.nested_config :nested => true do |nested|
|
126
|
+
nested.def :value, 5
|
127
|
+
end
|
128
|
+
|
129
|
+
config.other_nested Castor.configure{|nested|
|
130
|
+
nested.def :other_value, "value"
|
131
|
+
}
|
132
|
+
end
|
133
|
+
|
134
|
+
configuration.nested_config.value
|
135
|
+
# => 5
|
136
|
+
|
137
|
+
configuration.other_nested.other_value
|
138
|
+
# => "value"
|
139
|
+
|
140
|
+
configuration.other_nested = 3
|
141
|
+
# => NoMethodError
|
142
|
+
```
|
143
|
+
|
144
|
+
### Going Meta
|
145
|
+
|
146
|
+
It's possible to use `#{node}!` method to get the `Castor::Configuration::Node` object. It's currently not very useful :(.
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
config.time_now!
|
150
|
+
# => #<Castor::Configuration::Node:0x007ffdda363af0 ...
|
151
|
+
```
|
4
152
|
|
5
153
|
## Installation
|
6
154
|
|
@@ -16,10 +164,6 @@ Or install it yourself as:
|
|
16
164
|
|
17
165
|
$ gem install castor
|
18
166
|
|
19
|
-
## Usage
|
20
|
-
|
21
|
-
TODO: Write usage instructions here
|
22
|
-
|
23
167
|
## Contributing
|
24
168
|
|
25
169
|
1. Fork it
|
data/lib/castor.rb
CHANGED
data/lib/castor/configuration.rb
CHANGED
@@ -1,15 +1,17 @@
|
|
1
|
-
require 'pry'
|
2
|
-
|
3
1
|
module Castor
|
4
2
|
class Configuration
|
3
|
+
include Enumerable
|
4
|
+
|
5
5
|
def initialize(block)
|
6
6
|
@values = {}
|
7
7
|
instance_eval(&block)
|
8
|
+
@initialized = true
|
8
9
|
end
|
9
10
|
|
10
|
-
def
|
11
|
-
|
11
|
+
def def(name, *args, &block)
|
12
|
+
return super(name, *args, block) if @initialized
|
12
13
|
|
14
|
+
options = args.last.is_a?(::Hash) ? args.pop : {}
|
13
15
|
config_value = nil
|
14
16
|
|
15
17
|
if options[:nested]
|
@@ -23,7 +25,7 @@ module Castor
|
|
23
25
|
default (args.first || options.delete(:lazy))
|
24
26
|
} unless block
|
25
27
|
|
26
|
-
config_value = Castor::Configuration::
|
28
|
+
config_value = Castor::Configuration::Node.new(name, block)
|
27
29
|
|
28
30
|
selfclass.define_method(name) do
|
29
31
|
config_value.value
|
@@ -33,7 +35,7 @@ module Castor
|
|
33
35
|
config_value.value = args
|
34
36
|
end
|
35
37
|
|
36
|
-
selfclass.define_method("#{name}!") do
|
38
|
+
selfclass.define_method("#{name}!") do
|
37
39
|
config_value
|
38
40
|
end
|
39
41
|
end
|
@@ -41,9 +43,9 @@ module Castor
|
|
41
43
|
@values[name] = config_value
|
42
44
|
end
|
43
45
|
|
44
|
-
def
|
46
|
+
def def_many(attributes)
|
45
47
|
attributes.each do |key, value|
|
46
|
-
self.
|
48
|
+
self.def key, value
|
47
49
|
end
|
48
50
|
end
|
49
51
|
|
@@ -51,73 +53,17 @@ module Castor
|
|
51
53
|
class << self; self; end;
|
52
54
|
end
|
53
55
|
|
56
|
+
def each(&block)
|
57
|
+
@values.each(&block)
|
58
|
+
end
|
54
59
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
self.value = @default
|
60
|
-
end
|
61
|
-
|
62
|
-
def value=(new_value)
|
63
|
-
if validate!(new_value)
|
64
|
-
@value = new_value
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
def value
|
69
|
-
lazy? ? lazy_value : @value
|
70
|
-
end
|
71
|
-
|
72
|
-
private
|
73
|
-
|
74
|
-
def lazy_value
|
75
|
-
v = @value.call
|
76
|
-
validate!(v, true)
|
77
|
-
v
|
78
|
-
end
|
79
|
-
|
80
|
-
def desc(description)
|
81
|
-
@description = description
|
82
|
-
end
|
83
|
-
|
84
|
-
def type(*types)
|
85
|
-
@types = types.flatten
|
86
|
-
end
|
87
|
-
|
88
|
-
def value_in(*values)
|
89
|
-
@possible_values = if values.length == 1 && values.first.is_a?(Enumerable)
|
90
|
-
values.first
|
60
|
+
def merge(hash)
|
61
|
+
hash.each do |k, v|
|
62
|
+
if self.send(k).is_a?(Castor::Configuration)
|
63
|
+
self.send(k).merge(v)
|
91
64
|
else
|
92
|
-
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
def default(default_value = nil, &block)
|
97
|
-
@default = default_value || block
|
98
|
-
end
|
99
|
-
|
100
|
-
def lazy?(lazy_value = nil)
|
101
|
-
lazy_value = lazy_value || @value || @default
|
102
|
-
lazy_value.is_a?(Proc) && !(@types && @types.include?(Proc))
|
103
|
-
end
|
104
|
-
|
105
|
-
def validate!(new_value, jit = false)
|
106
|
-
return true if lazy?(new_value) && !jit
|
107
|
-
|
108
|
-
if (@possible_values && !@possible_values.include?(new_value))
|
109
|
-
raise_validation_error(new_value, "Value must be included in #{@possible_values.to_s}")
|
110
|
-
end
|
111
|
-
|
112
|
-
if (@types && @types.none?{|klass| new_value.is_a?(klass)})
|
113
|
-
raise_validation_error(new_value, "Value must be in types #{@types.to_s}")
|
65
|
+
self.send("#{k}=", v)
|
114
66
|
end
|
115
|
-
|
116
|
-
true
|
117
|
-
end
|
118
|
-
|
119
|
-
def raise_validation_error(new_value, message)
|
120
|
-
raise InvalidValueError.new("Invalid value #{new_value} for #{@name}. #{message}")
|
121
67
|
end
|
122
68
|
end
|
123
69
|
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Castor
|
2
|
+
class Configuration
|
3
|
+
class Node
|
4
|
+
def initialize(name, block)
|
5
|
+
@name = name
|
6
|
+
instance_eval(&block)
|
7
|
+
self.value = @default
|
8
|
+
end
|
9
|
+
|
10
|
+
def value=(new_value)
|
11
|
+
if validate!(new_value)
|
12
|
+
@value = new_value
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def value
|
17
|
+
lazy? ? lazy_value : @value
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def lazy_value
|
23
|
+
v = @value.call
|
24
|
+
validate!(v, true)
|
25
|
+
v
|
26
|
+
end
|
27
|
+
|
28
|
+
def desc(description)
|
29
|
+
@description = description
|
30
|
+
end
|
31
|
+
|
32
|
+
def type(*types)
|
33
|
+
@types = types.flatten
|
34
|
+
end
|
35
|
+
|
36
|
+
def value_in(*values)
|
37
|
+
@possible_values = if values.length == 1 && values.first.is_a?(Enumerable)
|
38
|
+
values.first
|
39
|
+
else
|
40
|
+
values.flatten
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def default(default_value = nil, &block)
|
45
|
+
@default = default_value || block
|
46
|
+
end
|
47
|
+
|
48
|
+
def lazy?(lazy_value = nil)
|
49
|
+
lazy_value = lazy_value || @value || @default
|
50
|
+
lazy_value.is_a?(Proc) && !(@types && @types.include?(Proc))
|
51
|
+
end
|
52
|
+
|
53
|
+
def validate!(new_value, jit = false)
|
54
|
+
return true if lazy?(new_value) && !jit
|
55
|
+
|
56
|
+
if (@possible_values && !@possible_values.include?(new_value))
|
57
|
+
raise_validation_error(new_value, "Value must be included in #{@possible_values.to_s}")
|
58
|
+
end
|
59
|
+
|
60
|
+
if (@types && @types.none?{|klass| new_value.is_a?(klass)})
|
61
|
+
raise_validation_error(new_value, "Value must be in types #{@types.to_s}")
|
62
|
+
end
|
63
|
+
|
64
|
+
true
|
65
|
+
end
|
66
|
+
|
67
|
+
def raise_validation_error(new_value, message)
|
68
|
+
raise InvalidValueError.new("Invalid value #{new_value} for #{@name}. #{message}")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/lib/castor/version.rb
CHANGED
data/spec/castor_spec.rb
CHANGED
@@ -5,38 +5,39 @@ describe Castor do
|
|
5
5
|
Castor.configure do |config|
|
6
6
|
|
7
7
|
# Complete syntax
|
8
|
-
config.toto do
|
8
|
+
config.def :toto do
|
9
9
|
type Integer
|
10
10
|
value_in 1..50
|
11
11
|
default 42
|
12
12
|
end
|
13
13
|
|
14
14
|
# Short syntax
|
15
|
-
config.titi "hello"
|
15
|
+
config.def :titi, "hello"
|
16
16
|
|
17
17
|
# Mass-assign syntax
|
18
|
-
config.(:mass => :assign, :is => :working, :for => 100)
|
18
|
+
config.def_many(:mass => :assign, :is => :working, :for => 100)
|
19
19
|
|
20
20
|
# Nested
|
21
|
-
config.more :nested => true do |nested_config|
|
22
|
-
nested_config.titi :toto
|
21
|
+
config.def :more, :nested => true do |nested_config|
|
22
|
+
nested_config.def :titi, :toto
|
23
23
|
end
|
24
24
|
|
25
25
|
# Nested through new Castor
|
26
|
-
config.other_nested Castor.configure{|nested_config|
|
27
|
-
nested_config.is_nested true
|
26
|
+
config.def :other_nested, Castor.configure{|nested_config|
|
27
|
+
nested_config.def :is_nested, true
|
28
28
|
}
|
29
29
|
|
30
30
|
# Lazy Eval
|
31
|
-
config.time_now :lazy => lambda { Time.now }
|
31
|
+
config.def :time_now, :lazy => lambda { Time.now }
|
32
32
|
|
33
33
|
# Lazy eval with block
|
34
|
-
config.lazy_increment do
|
34
|
+
config.def :lazy_increment do
|
35
35
|
type Fixnum
|
36
36
|
default 3
|
37
37
|
end
|
38
38
|
|
39
|
-
|
39
|
+
# Expected procs
|
40
|
+
config.def :proc do
|
40
41
|
type Proc
|
41
42
|
default { 3 }
|
42
43
|
end
|
@@ -44,11 +45,12 @@ describe Castor do
|
|
44
45
|
}
|
45
46
|
|
46
47
|
context "default values" do
|
47
|
-
its(:toto)
|
48
|
-
its(:titi)
|
49
|
-
its(:mass)
|
50
|
-
its(:is)
|
51
|
-
its(:for)
|
48
|
+
its(:toto) { should == 42 }
|
49
|
+
its(:titi) { should == "hello" }
|
50
|
+
its(:mass) { should == :assign }
|
51
|
+
its(:is) { should == :working }
|
52
|
+
its(:for) { should == 100 }
|
53
|
+
its(:time_now) { should be_a Time }
|
52
54
|
end
|
53
55
|
|
54
56
|
context "nested values" do
|
@@ -59,7 +61,7 @@ describe Castor do
|
|
59
61
|
end
|
60
62
|
|
61
63
|
context "lazy values" do
|
62
|
-
it "doesn't override procs" do
|
64
|
+
it "doesn't override the behavior of expected procs" do
|
63
65
|
subject.proc.should be_a Proc
|
64
66
|
end
|
65
67
|
end
|
@@ -86,9 +88,29 @@ describe Castor do
|
|
86
88
|
end
|
87
89
|
|
88
90
|
context "to a value out of range" do
|
89
|
-
it "throws an
|
91
|
+
it "throws an error" do
|
90
92
|
expect { subject.toto = 100 }.to raise_error Castor::Configuration::InvalidValueError
|
91
93
|
end
|
92
94
|
end
|
95
|
+
|
96
|
+
context "setting a value not specified" do
|
97
|
+
it "throws an error" do
|
98
|
+
expect { subject.undefined_config_value(3) }.to raise_error NoMethodError
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context "by merging with a Hash" do
|
103
|
+
before {
|
104
|
+
subject.merge :toto => 21, :more => { :titi => 21 }
|
105
|
+
}
|
106
|
+
|
107
|
+
its(:toto) { should == 21 }
|
108
|
+
|
109
|
+
it "deep merges hashes" do
|
110
|
+
subject.more.titi.should == 21
|
111
|
+
end
|
112
|
+
end
|
93
113
|
end
|
114
|
+
|
115
|
+
it "behaves like an enumerable"
|
94
116
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: castor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-04-02 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: pry
|
@@ -60,6 +60,7 @@ files:
|
|
60
60
|
- castor.gemspec
|
61
61
|
- lib/castor.rb
|
62
62
|
- lib/castor/configuration.rb
|
63
|
+
- lib/castor/configuration/node.rb
|
63
64
|
- lib/castor/version.rb
|
64
65
|
- spec/castor_spec.rb
|
65
66
|
- spec/spec_helper.rb
|