preconditions 0.1.2 → 0.2.0

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