preconditions 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -7,98 +7,56 @@ Preconditions</a> class
7
7
 
8
8
  ## Overview ##
9
9
 
10
- The Preconditions package provides a single module, Preconditions, with methods for:
11
-
12
- * Checking if an argument is nil
13
- * Checking if an argument satisfies a boolean condition (e.g. x > 10)
14
- * Checking if an argument responds to a certain method (duck typing)
15
- * Checking if an argument is of a specific type (strict typing)
16
-
17
- Each of these methods will raise an appropriate exception if the rule it is
18
- applying is violated.
19
-
20
- Preconditions can be checked either by using the `Preconditions` module
21
- directly, as in `Preconditions::check_not_nil(x)`, or by mixing the
22
- `Preconditions` module into your class to make the methods available without the
23
- `Preconditions` prefix. If the module is mixed in the precondition checking
24
- methods will be available to both class and instance methods equivalently.
10
+ The preconditions package provides a single module, `Preconditions`, which in turn provides a number of methods to check
11
+ the validity of method arguments. Two API styles are provided: a standard "command query" interface, where each check
12
+ is a single method call with an optional message format, and a fluent DSL interface, where checks are built up using
13
+ a more natural language.
25
14
 
26
15
  ## Usage ##
27
16
 
28
- To check for nil arguments:
29
-
30
- def my_meth(arg)
31
- Preconditions.check_not_nil(arg)
32
- ...
33
- end
34
-
35
- If you wish to supply a message simply include a string as your second
36
- parameter:
37
-
38
- def my_meth(arg)
39
- Preconditions.check_not_nil(arg, "nil values are evil!")
40
- ...
41
- end
17
+ To use the command-query API you can access the `check_XXX` methods directly through the Preconditions module, like so:
42
18
 
43
- You can even use a format if you want to interpolate some variables into your
44
- message lazily:
45
-
46
- def my_meth(arg)
47
- Preconditions.check_not_nil(arg, "Using nil in context of: %s", @bigobj.to_s)
48
- ...
19
+ class MyMath
20
+ def sqrt(num)
21
+ Preconditions.check_not_nil(num)
22
+ Preconditions.check_type(num, Integer, "num argument must be an integer: non integer types are unsupported")
23
+ Preconditions.check_argument(num >= 0, "num argument must be greater than zero")
24
+ num.sqrt
25
+ end
49
26
  end
50
27
 
51
- All methods support both a message and a message/format argument pair.
28
+ You can also `include Preconditions` to import the command-query calls into your class for use without the
29
+ `Preconditions` module prefix. The full list of command-query calls is documented in the `Preconditions` module itself.
52
30
 
53
- To check an argument property:
31
+ To use the fluent DSL API use the `check(arg).is {}` form like so:
54
32
 
55
- def sqrt(num)
56
- Preconditions.check_argument(num > 0)
57
- ...
58
- end
59
-
60
- or alternatively
61
-
62
- def sqrt(num)
63
- Preconditions.check_block("sqrt doesn't yet support complex numbers") { num > 0 }
64
- ...
33
+ class MyMath
34
+ def sqrt(num)
35
+ Preconditions.check(num) { is_not_nil and has_type(Integer) and satisfies(">= 0") { arg >= 0 } }
36
+ num.sqrt
37
+ end
65
38
  end
66
39
 
67
- To check the type of an object being supplied:
40
+ Note that there is less opportunity for custom messaging in the fluent API. However, a second argument to `check` can
41
+ be supplied to add the argument name to any raised errors:
68
42
 
69
- def sqrt(num)
70
- Preconditions.check_type(num, Integer,
71
- "sqrt is integer only, you supplied a %s", num.class)
72
- ...
43
+ class MyMath
44
+ def sqrt(num)
45
+ Preconditions.check(num, 'num') { is_not_nil and has_type(Integer) and satisfies(">= 0") { arg >= 0 } }
46
+ num.sqrt
47
+ end
73
48
  end
74
49
 
75
- To check if an object will respond to a method you intend to call:
50
+ In this case, if `num` is the value -10 then an [ArgumentError] will be raised with a message along the lines of
51
+ "Argument 'num' must be >= 0, but was -10".
76
52
 
77
- def sqrt(num)
78
- Preconditions.check_reponds_to(num, :sqrt,
79
- "yup, we're that lazy")
80
- ...
81
- end
53
+ The set of available checks is documented in the `ConditionChecker` documentation.
82
54
 
83
- If you wish to avoid the `Preconditions` prefix on every call, you can include the `Preconditions` module into your class:
84
-
85
- class SpiffyClass
86
- include Preconditions
87
-
88
- def a(x)
89
- check_not_nil(x)
90
- ...
91
- end
92
-
93
- def self.b(y)
94
- check_not_nil(y)
95
- ...
96
- end
97
- end
55
+ The `check` method on the fluent API will not be imported when the `Preconditions` module is included: it can only be
56
+ addressed with the `Preconditions` prefix. This is to prevent possible name clashes with existing `check` methods in
57
+ client code (check being a somewhat common verb). The use of `and` as a separator in the DSL expression is purely
58
+ for readability: newlines and semi-colons work just as well (all DSL methods either raise an exception or return true).
98
59
 
99
- Where possible the `check_` methods will return the object being checked. In
100
- the cases where this is not possible (`check_argument` and `check_block`) the
101
- boolean result of the expression is returned.
102
60
 
103
61
  ## Contributing to preconditions ##
104
62
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.2
1
+ 0.2.0
@@ -0,0 +1,81 @@
1
+ # The ConditionChecker supplied the DSL host environment to evaluate expressions in the Preconditions DSL language.
2
+ # The #is method takes a block that is evaluated in the context of the ConditionChecker instance, with access to all
3
+ # of the ConditionChecker methods. Typical usage will be of the form:
4
+ #
5
+ # Preconditions.check(x).is { not_nil and responds_to(:hello)
6
+ #
7
+ class ConditionChecker
8
+
9
+ attr_reader :arg, :name
10
+
11
+ def initialize(arg, name=nil, &block)
12
+ @arg = arg
13
+ @name = name
14
+ end
15
+
16
+ # DSL call. Establishes that the checked argument is non-nil.
17
+ # @raises [ArgumentError] if the argument is nil
18
+ # @return true
19
+ def is_not_nil
20
+ if arg.nil?
21
+ raise ArgumentError, format_message("may not be nil")
22
+ end
23
+ true
24
+ end
25
+
26
+ # DSL call. Establishes that the checked argument is of the given type. nil is treated as a bottom type (i.e. it
27
+ # is compatible with all types)
28
+ # @raises [TypeError] if the argument is not type-compatible with the argument
29
+ # @return true
30
+ def has_type(type)
31
+ if !arg.nil? && !arg.kind_of?(type)
32
+ raise TypeError, format_message("must be of type #{type.name}, but is of type #{arg.class.name}")
33
+ end
34
+ true
35
+ end
36
+
37
+ # DSL call. Establishes that the checked argument can respond to the requested method, identified by symbol.
38
+ # @raises [NameError] if the argument cannot respond to the supplied method name
39
+ # @return true
40
+ def can_respond_to(method_symbol)
41
+ if !arg.respond_to?(method_symbol)
42
+ raise NameError, format_message("must respond to method #{method_symbol}")
43
+ end
44
+ true
45
+ end
46
+
47
+ # DSL call. Establishes that the checked argument satisfies an arbitrary condition specified in a block. If the
48
+ # block evaluates to true the argument passes; if it evaluates to false it fails and an [ArgumentError] is raised.
49
+ # An optional message may be supplied to describe what the block is checking.
50
+ #
51
+ # If the block requests a bound variable that variable will be assigned the value of the argument we're checking. The
52
+ # block may also access the argument value using the `arg` name. Note, also, that the block is a closure and will
53
+ # have natural access to variables in lexical scope at the time the DSL expression is created. Thus, one can do
54
+ # something like:
55
+ # def sqrt(num)
56
+ # Preconditions.check(num) { satisfies { num >= 0 } }
57
+ # end
58
+ def satisfies(msg = nil, &block)
59
+ success = if block.arity > 0
60
+ yield(arg)
61
+ else
62
+ instance_eval(&block)
63
+ end
64
+ if !success
65
+ raise ArgumentError, msg.nil? ? format_message("must satisfy given conditions") : format_message("must be #{msg}")
66
+ end
67
+ true
68
+ end
69
+
70
+ private
71
+
72
+ def format_message(message)
73
+ name_str = if name
74
+ "'#{name}' "
75
+ else
76
+ ''
77
+ end
78
+ "Argument #{name_str}#{message}"
79
+ end
80
+
81
+ end
data/lib/preconditions.rb CHANGED
@@ -1,7 +1,8 @@
1
- #
1
+ require 'condition_checker'
2
+
2
3
  module Preconditions
3
4
 
4
- module PreconditionMethods
5
+ module PreconditionMixinMethods
5
6
 
6
7
  # Check that arg is not nil, raising an ArgumentError with an optional
7
8
  # message and format if it is nil
@@ -72,7 +73,7 @@ module Preconditions
72
73
  end
73
74
 
74
75
  def self.included(receiver)
75
- receiver.extend PreconditionMethods
76
+ receiver.extend PreconditionMixinMethods
76
77
  end
77
78
 
78
79
  private
@@ -89,11 +90,24 @@ module Preconditions
89
90
  end
90
91
  end
91
92
 
93
+ module PreconditionModuleClassMethods
94
+ # Introduce a DSL expression to check the given argument, with the optionally given name. This will return a
95
+ #[ConditionChecker] instance that can be used to build the DSL expression.
96
+ def check(argument, name = nil, &block)
97
+ cc = ConditionChecker.new(argument, name)
98
+ if block_given?
99
+ cc.instance_eval(&block)
100
+ end
101
+ argument
102
+ end
103
+ end
104
+
92
105
  class << self
93
- include PreconditionMethods
106
+ include PreconditionMixinMethods
107
+ include PreconditionModuleClassMethods
94
108
  end
95
109
 
96
110
  def self.included(receiver)
97
- receiver.send :include, PreconditionMethods
111
+ receiver.send :include, PreconditionMixinMethods
98
112
  end
99
113
  end
@@ -0,0 +1,75 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{preconditions}
8
+ s.version = "0.2.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = [%q{tucker}]
12
+ s.date = %q{2011-05-24}
13
+ s.description = %q{The Preconditions library provides a simple set of methods for checking arguments being passed into a method. Instead of writing custom checks and raising exceptions directly in your code you can use Preconditions to verify basic properties of your arguments (not-nil, satisfying a boolean expression, being of a certain type/duck-type) and raise the appropriate exception for you.
14
+ }
15
+ s.email = %q{tucker+rubygems@glyde.com}
16
+ s.extra_rdoc_files = [
17
+ "LICENSE.txt",
18
+ "README.md"
19
+ ]
20
+ s.files = [
21
+ ".document",
22
+ "Gemfile",
23
+ "LICENSE.txt",
24
+ "README.md",
25
+ "Rakefile",
26
+ "VERSION",
27
+ "lib/condition_checker.rb",
28
+ "lib/preconditions.rb",
29
+ "preconditions.gemspec",
30
+ "spec/preconditions_dsl_spec.rb",
31
+ "spec/preconditions_spec.rb",
32
+ "spec/spec_helper.rb"
33
+ ]
34
+ s.homepage = %q{http://github.com/ctucker/preconditions}
35
+ s.licenses = [%q{MIT}]
36
+ s.require_paths = [%q{lib}]
37
+ s.rubygems_version = %q{1.8.3}
38
+ s.summary = %q{Preconditions checking support inspired by Guava}
39
+ s.test_files = [
40
+ "spec/preconditions_dsl_spec.rb",
41
+ "spec/preconditions_spec.rb",
42
+ "spec/spec_helper.rb"
43
+ ]
44
+
45
+ if s.respond_to? :specification_version then
46
+ s.specification_version = 3
47
+
48
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
49
+ s.add_development_dependency(%q<yard>, ["~> 0.6.0"])
50
+ s.add_development_dependency(%q<bluecloth>, [">= 0"])
51
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
52
+ s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
53
+ s.add_development_dependency(%q<rcov>, [">= 0"])
54
+ s.add_development_dependency(%q<rspec>, [">= 2.6.0"])
55
+ s.add_development_dependency(%q<ci_reporter>, [">= 0"])
56
+ else
57
+ s.add_dependency(%q<yard>, ["~> 0.6.0"])
58
+ s.add_dependency(%q<bluecloth>, [">= 0"])
59
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
60
+ s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
61
+ s.add_dependency(%q<rcov>, [">= 0"])
62
+ s.add_dependency(%q<rspec>, [">= 2.6.0"])
63
+ s.add_dependency(%q<ci_reporter>, [">= 0"])
64
+ end
65
+ else
66
+ s.add_dependency(%q<yard>, ["~> 0.6.0"])
67
+ s.add_dependency(%q<bluecloth>, [">= 0"])
68
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
69
+ s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
70
+ s.add_dependency(%q<rcov>, [">= 0"])
71
+ s.add_dependency(%q<rspec>, [">= 2.6.0"])
72
+ s.add_dependency(%q<ci_reporter>, [">= 0"])
73
+ end
74
+ end
75
+
@@ -0,0 +1,59 @@
1
+ require "rspec"
2
+ require 'preconditions'
3
+
4
+ describe "dsl expression" do
5
+
6
+ it "should pass with no checks in the is() block" do
7
+ arg = 1
8
+ res = Preconditions.check(arg) {}
9
+ res.should == arg
10
+ end
11
+
12
+ it "should pass with no is() call" do
13
+ arg = 1
14
+ res = Preconditions.check(arg)
15
+ # res will be a ConditionChecker instance, but should not have raised an exception
16
+ end
17
+
18
+ it "should raise on a failing condition check" do
19
+ arg = nil
20
+ expect {
21
+ Preconditions.check(arg, 'arg') { is_not_nil }
22
+ }.to raise_exception(ArgumentError)
23
+ end
24
+
25
+ it "should raise on a failing condition check in a conjugated list" do
26
+ arg = 1
27
+ expect {
28
+ Preconditions.check(arg, 'arg') { is_not_nil and has_type(String) }
29
+ }.to raise_exception(TypeError)
30
+ end
31
+
32
+ it "should return the argument if all condition checks pass" do
33
+ arg = 1
34
+ res = Preconditions.check(arg, 'arg') { is_not_nil and has_type(Integer) }
35
+ res.should == arg
36
+ end
37
+
38
+ it "should evaluate a satisfies block using the instance-local arg value" do
39
+ x = 1
40
+ expect {
41
+ Preconditions.check(x, 'x') { is_not_nil and satisfies("less than zero") { arg <= 0 } }
42
+ }.to raise_exception(ArgumentError, "Argument 'x' must be less than zero")
43
+ end
44
+
45
+ it "should pass argument in to yielded satisfies block if requested" do
46
+ x = 1
47
+ expect {
48
+ Preconditions.check(x, 'x') { is_not_nil and satisfies("less than zero") { |v| v <= 0 } }
49
+ }.to raise_exception(ArgumentError, "Argument 'x' must be less than zero")
50
+ end
51
+
52
+ it "should evaluate a satisfies block binding in the arg name as an accessible variable" do
53
+ x = 1
54
+ expect {
55
+ Preconditions.check(x, 'x') { is_not_nil and satisfies("less than zero") { x <= 0 } }
56
+ }.to raise_exception(ArgumentError, "Argument 'x' must be less than zero")
57
+ end
58
+
59
+ end
@@ -71,6 +71,12 @@ end
71
71
  it "returns the argument when it supports the requested method" do
72
72
  subject.check_responds_to(1, :succ).should == 1
73
73
  end
74
+
75
+ it "should not expose dsl-only method #check when included" do
76
+ should_have_check = subject.class == Module
77
+ subject.respond_to?(:check).should == should_have_check
78
+ end
79
+
74
80
  end
75
81
 
76
82
  end
data/spec/spec_helper.rb CHANGED
@@ -6,7 +6,3 @@ require 'preconditions'
6
6
  # Requires supporting files with custom matchers and macros, etc,
7
7
  # in ./support/ and its subdirectories.
8
8
  Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
9
-
10
- RSpec.configure do |config|
11
-
12
- end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: preconditions
3
3
  version: !ruby/object:Gem::Version
4
- hash: 31
4
+ hash: 23
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 1
9
8
  - 2
10
- version: 0.1.2
9
+ - 0
10
+ version: 0.2.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - tucker
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-05-22 00:00:00 Z
18
+ date: 2011-05-24 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  version_requirements: &id001 !ruby/object:Gem::Requirement
@@ -29,10 +29,10 @@ dependencies:
29
29
  - 6
30
30
  - 0
31
31
  version: 0.6.0
32
- name: yard
33
- prerelease: false
34
32
  type: :development
35
33
  requirement: *id001
34
+ prerelease: false
35
+ name: yard
36
36
  - !ruby/object:Gem::Dependency
37
37
  version_requirements: &id002 !ruby/object:Gem::Requirement
38
38
  none: false
@@ -43,10 +43,10 @@ dependencies:
43
43
  segments:
44
44
  - 0
45
45
  version: "0"
46
- name: bluecloth
47
- prerelease: false
48
46
  type: :development
49
47
  requirement: *id002
48
+ prerelease: false
49
+ name: bluecloth
50
50
  - !ruby/object:Gem::Dependency
51
51
  version_requirements: &id003 !ruby/object:Gem::Requirement
52
52
  none: false
@@ -59,10 +59,10 @@ dependencies:
59
59
  - 0
60
60
  - 0
61
61
  version: 1.0.0
62
- name: bundler
63
- prerelease: false
64
62
  type: :development
65
63
  requirement: *id003
64
+ prerelease: false
65
+ name: bundler
66
66
  - !ruby/object:Gem::Dependency
67
67
  version_requirements: &id004 !ruby/object:Gem::Requirement
68
68
  none: false
@@ -75,10 +75,10 @@ dependencies:
75
75
  - 5
76
76
  - 2
77
77
  version: 1.5.2
78
- name: jeweler
79
- prerelease: false
80
78
  type: :development
81
79
  requirement: *id004
80
+ prerelease: false
81
+ name: jeweler
82
82
  - !ruby/object:Gem::Dependency
83
83
  version_requirements: &id005 !ruby/object:Gem::Requirement
84
84
  none: false
@@ -89,10 +89,10 @@ dependencies:
89
89
  segments:
90
90
  - 0
91
91
  version: "0"
92
- name: rcov
93
- prerelease: false
94
92
  type: :development
95
93
  requirement: *id005
94
+ prerelease: false
95
+ name: rcov
96
96
  - !ruby/object:Gem::Dependency
97
97
  version_requirements: &id006 !ruby/object:Gem::Requirement
98
98
  none: false
@@ -105,10 +105,10 @@ dependencies:
105
105
  - 6
106
106
  - 0
107
107
  version: 2.6.0
108
- name: rspec
109
- prerelease: false
110
108
  type: :development
111
109
  requirement: *id006
110
+ prerelease: false
111
+ name: rspec
112
112
  - !ruby/object:Gem::Dependency
113
113
  version_requirements: &id007 !ruby/object:Gem::Requirement
114
114
  none: false
@@ -119,10 +119,10 @@ dependencies:
119
119
  segments:
120
120
  - 0
121
121
  version: "0"
122
- name: ci_reporter
123
- prerelease: false
124
122
  type: :development
125
123
  requirement: *id007
124
+ prerelease: false
125
+ name: ci_reporter
126
126
  description: |
127
127
  The Preconditions library provides a simple set of methods for checking arguments being passed into a method. Instead of writing custom checks and raising exceptions directly in your code you can use Preconditions to verify basic properties of your arguments (not-nil, satisfying a boolean expression, being of a certain type/duck-type) and raise the appropriate exception for you.
128
128
 
@@ -141,10 +141,12 @@ files:
141
141
  - README.md
142
142
  - Rakefile
143
143
  - VERSION
144
+ - lib/condition_checker.rb
144
145
  - lib/preconditions.rb
146
+ - preconditions.gemspec
147
+ - spec/preconditions_dsl_spec.rb
145
148
  - spec/preconditions_spec.rb
146
149
  - spec/spec_helper.rb
147
- - test/helper.rb
148
150
  homepage: http://github.com/ctucker/preconditions
149
151
  licenses:
150
152
  - MIT
@@ -174,11 +176,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
174
176
  requirements: []
175
177
 
176
178
  rubyforge_project:
177
- rubygems_version: 1.7.2
179
+ rubygems_version: 1.8.3
178
180
  signing_key:
179
181
  specification_version: 3
180
182
  summary: Preconditions checking support inspired by Guava
181
183
  test_files:
184
+ - spec/preconditions_dsl_spec.rb
182
185
  - spec/preconditions_spec.rb
183
186
  - spec/spec_helper.rb
184
- - test/helper.rb
data/test/helper.rb DELETED
@@ -1,17 +0,0 @@
1
- require 'rubygems'
2
- require 'bundler'
3
- begin
4
- Bundler.setup(:default, :development)
5
- rescue Bundler::BundlerError => e
6
- $stderr.puts e.message
7
- $stderr.puts "Run `bundle install` to install missing gems"
8
- exit e.status_code
9
- end
10
- require 'test/unit'
11
-
12
- $LOAD_PATH.unshift(File.dirname(__FILE__))
13
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
14
- require 'preconditions'
15
-
16
- class Test::Unit::TestCase
17
- end