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 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