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 CHANGED
@@ -1,6 +1,154 @@
1
1
  # Castor
2
2
 
3
- TODO: Write a gem description
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
@@ -1,5 +1,6 @@
1
1
  require "castor/version"
2
2
  require "castor/configuration"
3
+ require "castor/configuration/node"
3
4
 
4
5
  module Castor
5
6
  def self.configure(&block)
@@ -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 method_missing(name, *args, &block)
11
- options = args.last.is_a?(::Hash) ? args.pop : {}
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::Value.new(name, block)
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 |arg|
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 call(attributes)
46
+ def def_many(attributes)
45
47
  attributes.each do |key, value|
46
- self.send key, value
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
- class Value
56
- def initialize(name, block)
57
- @name = name
58
- instance_eval(&block)
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
- values.flatten
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
@@ -1,3 +1,3 @@
1
1
  module Castor
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -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
- config.proc do
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) { should == 42 }
48
- its(:titi) { should == "hello" }
49
- its(:mass) { should == :assign }
50
- its(:is) { should == :working }
51
- its(:for) { should == 100 }
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 exception" do
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.1
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-03-20 00:00:00.000000000 Z
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