castor 0.0.1 → 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/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
|