stronger 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 61d3f70e5efe9e26b9972e5686bb01a54b068338
4
+ data.tar.gz: 430212356514e889ac68086bf07599ac3623b6a3
5
+ SHA512:
6
+ metadata.gz: 1ddf9da54b794322d601dab9eb27c1acea7cc6627cf71a50ee9ef7e7ed9ce2a8a6f7abab1b08b103dcedc836c7ed6842a4d08a8c54584e1095a2b54822acfe53
7
+ data.tar.gz: d13e59be287c071627738c6fdf898797173a387b959255f15fe1f5ce267881a2afdf65ad37ea365629eaadc9fe262c3f1695ac4a2b3bca3ea3e23c342888ef29
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ *.gem
data/LICENSE ADDED
@@ -0,0 +1,17 @@
1
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
2
+ this software and associated documentation files (the "Software"), to deal in
3
+ the Software without restriction, including without limitation the rights to
4
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
5
+ of the Software, and to permit persons to whom the Software is furnished to do
6
+ so, subject to the following conditions:
7
+
8
+ The above copyright notice and this permission notice shall be included in all
9
+ copies or substantial portions of the Software.
10
+
11
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
12
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
13
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
15
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
16
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
17
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,105 @@
1
+ # Stronger
2
+
3
+ Stronger is a basic run-time type-safety kit for Ruby, which introduces a couple
4
+ of tools for simple run-time type checking.
5
+
6
+ There's nothing revolutionary here. Just a few convenient types and methods to
7
+ ease the burden of the type-conscious rubyist.
8
+
9
+ ## Installation
10
+
11
+ `gem install stronger` and `require 'stronger'` in your code.
12
+
13
+ ## Features
14
+
15
+ ### Collections
16
+
17
+ Stronger provides some type-checking wrappers around Ruby's Hash and Array
18
+ classes, which help give you a bit of type-safety in the values you can expect
19
+ from these collections.
20
+
21
+ ##### Array
22
+
23
+ Typed arrays check anything appended.
24
+
25
+ ```ruby
26
+ arr = Stronger::TypedArray.new(Integer)
27
+ arr.push 5
28
+
29
+ arr.push 4 # raises TypeError
30
+ ```
31
+
32
+ They also refuse to concatenate with other TypedArrays not of the same type.
33
+
34
+ ##### Hash
35
+
36
+ Typed hashes check values added to ensure type.
37
+
38
+ ```ruby
39
+ hash = Stronger::TypedHash.new(String)
40
+ hash[:first] = 'Groovy'
41
+ hash[2] = :woah_there # raises TypeError
42
+ ```
43
+
44
+ ### Properties
45
+
46
+ Some class methods are provided in the `PropertyDefinition` module which allows
47
+ you to define properties on classes. Properties are required by default,
48
+ can be set via the constructor, the private `set_properties` method, or setters.
49
+
50
+ ```ruby
51
+ class Person
52
+ property :name, type: String
53
+ property :phone_number, type: String, required: false
54
+ end
55
+
56
+ addr = AddressBookEntry.new(name: "Boris")
57
+ addr.name #=> 'Boris'
58
+ addr.name = 3 # Raises Stronger::InvalidProperty
59
+ ```
60
+
61
+
62
+ ### Interfaces
63
+
64
+ Stronger has a very simple (and very dumb) concept of an `Interface`,
65
+ which is just a list of methods objects must respond to encapsulated in an
66
+ object. These `Interface` objects can be treated as types however, which makes
67
+ duck-typing with Collections and Properties a lot nicer.
68
+
69
+ ```ruby
70
+ AnimalInterface = Stronger::Interface.new(:move, :make_noise)
71
+
72
+ class Dog
73
+ def move
74
+ end
75
+
76
+ def make_noise
77
+ end
78
+ end
79
+ ```
80
+
81
+ The `AnimalInterface` defined above can be used in place of classes for types
82
+ in Stronger's properties and typed collections.
83
+
84
+ ### Type checking methods
85
+
86
+ The `Stronger::TypeChecking` refinement provides a `is_strong?` method on
87
+ `Object` which allows you to check types yourself. It behaves like `is_a?`
88
+ but includes support for interface checking.
89
+
90
+ ```ruby
91
+ use Stronger::TypeChecking
92
+
93
+ BoatInterface = Stronger::Interface.new(:float)
94
+
95
+ Object.new.is_strong?(Object) # => true
96
+ Object.new.is_strong?(Boat) # => false
97
+ ```
98
+
99
+ ## Contributing
100
+
101
+ Fork and open a PR. Please include tests and ensure `rake test` runs.
102
+
103
+ ## License
104
+
105
+ See LICENSE
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'rake/testtask'
2
+ $LOAD_PATH << File.join(File.dirname(__FILE__), 'lib')
3
+ $LOAD_PATH << File.join(File.dirname(__FILE__), 'test')
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs << 'test'
7
+ t.pattern = 'test/*_test.rb'
8
+ end
data/TODO ADDED
@@ -0,0 +1 @@
1
+ Need to use attr instead of property for struct method
data/lib/stronger.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'stronger/interface'
2
+ require 'stronger/type_checking'
3
+ require 'stronger/hash'
4
+ require 'stronger/array'
5
+ require 'stronger/property'
6
+ require 'stronger/property_set'
7
+ require 'stronger/property_definition'
@@ -0,0 +1,16 @@
1
+ require 'stronger/collection'
2
+ module Stronger
3
+ class TypedArray < ::Array
4
+ include Collection
5
+ [:push, :shift, :[]=, :<<].each do |name|
6
+ define_method(name) do |value|
7
+ check_value_type!(value)
8
+ super(value)
9
+ end
10
+ end
11
+
12
+ def concat(arr)
13
+ check_collection_type!(arr)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,27 @@
1
+ module Stronger
2
+ module Collection
3
+ Interface = Stronger::Interface.new(:type)
4
+ using TypeChecking
5
+
6
+ attr_reader :type
7
+ def initialize(type, *rest)
8
+ @type = type
9
+ super(*rest)
10
+ end
11
+
12
+ private
13
+
14
+ def check_value_type!(val)
15
+ unless val.is_strong?(type)
16
+ raise TypeError, "#{self} expects values of type: #{type}"
17
+ end
18
+ end
19
+
20
+ def check_collection_type!(col)
21
+ unless col.implements?(Collection::Interface) and col.type < type
22
+ raise TypeError,
23
+ "#{self} may only be concatenated with an array of the same type!"
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,13 @@
1
+ require 'stronger/collection'
2
+ module Stronger
3
+ class TypedHash < ::Hash
4
+ include Collection
5
+
6
+ [:store, :[]=].each do |name|
7
+ define_method(name) do |key, val|
8
+ check_value_type!(val)
9
+ super(key, val)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,8 @@
1
+ module Stronger
2
+ class Interface
3
+ attr_reader :methods
4
+ def initialize(*methods)
5
+ @methods = methods
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,19 @@
1
+ module Stronger
2
+ class Property
3
+ attr_reader :name, :type
4
+ def initialize(name, type:, required: true)
5
+ @name, @type, @required = name, type, required
6
+ end
7
+
8
+ def required?
9
+ !!required
10
+ end
11
+
12
+ def typed?
13
+ !type.nil?
14
+ end
15
+
16
+ private
17
+ attr_reader :required
18
+ end
19
+ end
@@ -0,0 +1,68 @@
1
+ module Stronger
2
+ module PropertyDefinition
3
+ module ClassMethods
4
+ def properties
5
+ @properties ||= Array.new
6
+ end
7
+
8
+ def property_names
9
+ properties.map(&:name)
10
+ end
11
+
12
+ def property(*names, **opts)
13
+ new_props = names.map {|name| Property.new(name, **opts) }
14
+ properties.concat(new_props)
15
+ new_props.each{|p| expose_property(p) unless opts.delete(:private)}
16
+ end
17
+
18
+ private
19
+
20
+ def expose_property(property)
21
+ define_property_method(property.name) do |properties|
22
+ properties[property.name]
23
+ end
24
+
25
+ define_property_method("#{property.name}=") do |properties, value|
26
+ properties[property.name] = value
27
+ end
28
+ end
29
+
30
+ # Defines a method by the given name using a given block, to which
31
+ # the properties on the instance on which the method was called
32
+ # will be yielded as the first argument, and the arguments given to
33
+ # the called method as the rest of the arguments. PropertyErrors
34
+ # raised due to operations on the yielded properties will be rescued
35
+ # and re-raised with their stack trace origin set to the defined method.
36
+ def define_property_method(name, &blk)
37
+ define_method(name) do |*args|
38
+ begin
39
+ blk.call(properties, *args)
40
+ rescue PropertyError => e
41
+ e.set_backtrace caller
42
+ raise e
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ def self.included(base)
49
+ base.extend(ClassMethods)
50
+ end
51
+
52
+ def initialize(**property_values)
53
+ set_properties(property_values)
54
+ rescue PropertyError => e
55
+ e.set_backtrace caller
56
+ raise e
57
+ end
58
+
59
+ private
60
+ attr_reader :properties
61
+
62
+ def set_properties(values)
63
+ @properties = PropertySet.new(values, self.class.properties)
64
+ rescue PropertyError => e
65
+ raise e
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,79 @@
1
+ require 'forwardable'
2
+
3
+ module Stronger
4
+ class PropertyError < StandardError; end
5
+ class InvalidProperty < PropertyError; end
6
+ class UndefinedProperty < PropertyError; end
7
+ class MissingProperty < PropertyError; end
8
+
9
+ class PropertySet
10
+ extend Forwardable
11
+
12
+ # TODO: given_props should be called values
13
+ def initialize(given_values, config_props)
14
+ @config_props = config_props
15
+ @values = Hash.new
16
+
17
+ given_values.each {|k,v| set(k, v) }
18
+ validate_required_present!
19
+ end
20
+ def_delegator :values, :include?
21
+
22
+ def []=(key, value)
23
+ property = get_property(key)
24
+ validate_type!(value, property)
25
+ values[key] = value
26
+ end
27
+ alias_method :set, :[]=
28
+
29
+ def [](key)
30
+ get_property(key)
31
+ values[key] || (raise MissingProperty,
32
+ "#{key} is not required and has not been set!")
33
+ end
34
+
35
+ def delete(name)
36
+ unless get_property(name).required?
37
+ values.delete(name)
38
+ else
39
+ raise MissingProperty, "You can't delete required property #{name}!"
40
+ end
41
+ end
42
+
43
+ protected
44
+ attr_reader :config_props, :values
45
+
46
+ def values
47
+ @values ||= Hash.new
48
+ end
49
+
50
+ def validate_type!(value, property)
51
+ unless value.is_a?(property.type)
52
+ raise InvalidProperty,
53
+ "Property #{property.name} should be a #{property.type}"
54
+ end
55
+ end
56
+
57
+ def get_property(name)
58
+ property = config_props.find{|p| p.name == name}
59
+ return property ||
60
+ (raise UndefinedProperty, "#{name} is not a valid property!")
61
+ end
62
+
63
+ def validate_required_present!
64
+ config_props.select{|p| p.required?}.each do |property|
65
+ values.fetch(property.name) do
66
+ raise MissingProperty, "Property #{property.name} is required!"
67
+ end
68
+ end
69
+ end
70
+
71
+ def extra_property_message(extra_property_names)
72
+ if extra_property_names.length > 1
73
+ "#{extra_property_names} are not recognized property names!"
74
+ else
75
+ "#{extra_property_names.first} is not a valid property name!"
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,55 @@
1
+ module Stronger
2
+ module TypeChecking
3
+ refine Object do
4
+ def is_strong?(type)
5
+ if type.is_a?(Interface)
6
+ implements?(type)
7
+ elsif type.is_a?(Class) or type.is_a?(Module)
8
+ is_a?(type)
9
+ else
10
+ raise ArgumentError, "Don't know how to compare type of "\
11
+ "#{self} against #{type}. Try using a "\
12
+ "Stronger::Interface, Class or Module"
13
+ end
14
+ end
15
+
16
+ def implements?(interface)
17
+ interface.methods.all?{|m| respond_to?(m) }
18
+ end
19
+ end
20
+
21
+ module Implementers
22
+ def <(type)
23
+ if type.is_a?(Interface)
24
+ instance_implements?(type)
25
+ else
26
+ super(type)
27
+ end
28
+ end
29
+
30
+ def >(type)
31
+ !(self <= type)
32
+ end
33
+
34
+ def instance_implements?(interface)
35
+ (interface.methods - instance_methods).empty?
36
+ end
37
+
38
+ def implement(interface)
39
+ interface.methods.each do |name|
40
+ define_method(name) do
41
+ raise NotImplementedError, "#{self.class} should implement #{name}!"
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ refine Class do
48
+ include Implementers
49
+ end
50
+
51
+ refine Module do
52
+ include Implementers
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,3 @@
1
+ module Stronger
2
+ VERSION = '0.1.0'
3
+ end
data/stronger.gemspec ADDED
@@ -0,0 +1,17 @@
1
+ $LOAD_PATH.push File.expand_path('../lib', __FILE__)
2
+ require 'stronger/version'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'stronger'
6
+ s.version = Stronger::VERSION
7
+ s.platform = Gem::Platform::RUBY
8
+ s.authors = ['Jack Forrest']
9
+ s.email = ['jack@jrforrest.net']
10
+ s.homepage = 'https://github.com/jrforrest/stronger'
11
+ s.summary = 'Run-time type checking utils'
12
+ s.description = 'Provides several utilities for run-time type-checking with Ruby'
13
+ s.license = 'MIT'
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files test`.split("\n")
17
+ end
@@ -0,0 +1,37 @@
1
+ require 'test_helper'
2
+
3
+ module Stronger
4
+ class ArrayTest < MiniTest::Test
5
+ include ExAssertions
6
+
7
+ DuckInterface = Interface.new(:quack)
8
+ class Duck
9
+ def quack; end
10
+ end
11
+
12
+ def test_array
13
+ array = TypedArray.new(DuckInterface)
14
+ refute_ex_raised TypeError, ->{array.push(Duck.new)},
15
+ "The array allows objects of its configured type to be pushed."
16
+ assert_ex_raised TypeError, ->{array.push(Object.new)},
17
+ "Types not implementing the array's type will raise a TypeError "\
18
+ "when pushed."
19
+ assert_ex_raised TypeError, -> {array << Object.new},
20
+ "Type checking works on << alias for array"
21
+ end
22
+
23
+ def test_concat
24
+ assert_ex_raised TypeError,
25
+ ->{ TypedArray.new(DuckInterface).concat(::Array.new) },
26
+ "Can not concatenate a typed array with an un-typed array."
27
+ assert_ex_raised TypeError,
28
+ ->{ TypedArray.new(String).concat(Array.new(Fixnum)) },
29
+ "Can not concatenate a typed array with a typed array "\
30
+ "with an incompatible type"
31
+ refute_ex_raised TypeError,
32
+ ->{ TypedArray.new(Numeric).concat(TypedArray.new(Fixnum)) },
33
+ "A typed array may be concatenated with another typed array which has "\
34
+ "a type that implements its own type."
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,21 @@
1
+ # A couple of assertions for testing exceptions.
2
+ #
3
+ # These already exist in MiniTest but I don't like the way they work.
4
+ module ExAssertions
5
+ private
6
+
7
+ def refute_ex_raised(ex, lam, message = nil)
8
+ refute(raised?(ex, &lam), message)
9
+ end
10
+
11
+ def assert_ex_raised(ex, lam, message = nil)
12
+ assert(raised?(ex, &lam), message)
13
+ end
14
+
15
+ def raised?(ex)
16
+ yield
17
+ return false
18
+ rescue ex => e
19
+ return true
20
+ end
21
+ end
data/test/hash_test.rb ADDED
@@ -0,0 +1,19 @@
1
+ require 'test_helper'
2
+
3
+ module Stronger
4
+ class HashTest < MiniTest::Test
5
+ include ExAssertions
6
+
7
+ def test_hash
8
+ hash = TypedHash.new(String)
9
+ assert_ex_raised TypeError, ->{ hash[:one] = 1 },
10
+ "Trying to set a value in the hash which is not of the type "\
11
+ "with which the hash was instantiated should raise a TypeError"
12
+ hash[:two] = "2"
13
+ assert_equal "2", hash[:two],
14
+ "Setting a hash value with the appropriate type should work."
15
+ assert_ex_raised TypeError, ->{ hash.store(:three, 3)},
16
+ "Store shouldn't work either."
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,55 @@
1
+ require 'test_helper'
2
+
3
+ module Stronger
4
+ class PropertyDefinitionTest < MiniTest::Test
5
+ include ExAssertions
6
+
7
+ class PropertyDefinitionDummy
8
+ include PropertyDefinition
9
+
10
+ property :name, type: String
11
+ property :address, type: String, required: false
12
+ end
13
+
14
+ def test_valid
15
+ assert valid_dummy,
16
+ "The valid dummy should instantiate with no errors"
17
+ assert_equal valid_values[:name], valid_dummy.name,
18
+ "The name method should expose the name"
19
+ end
20
+
21
+ def test_invalid
22
+ assert_ex_raised MissingProperty, ->{invalid_dummy},
23
+ "Creating an instance without all of the required properties "\
24
+ "should result in a MissingProperty exception"
25
+ end
26
+
27
+ def test_set_invalid
28
+ assert_ex_raised InvalidProperty, ->{valid_dummy.address = 25},
29
+ "Attempting to set a property to the wrong type should raise an "\
30
+ "InvalidProperty exception"
31
+ end
32
+
33
+ private
34
+
35
+ def valid_dummy
36
+ @valid_dummy ||= make_dummy(valid_values)
37
+ end
38
+
39
+ def invalid_dummy
40
+ @invalid_dummuy ||= make_dummy(invalid_values)
41
+ end
42
+
43
+ def make_dummy(values)
44
+ PropertyDefinitionDummy.new(**values)
45
+ end
46
+
47
+ def invalid_values
48
+ {address: "one"}
49
+ end
50
+
51
+ def valid_values
52
+ {name: "Riiiiiiick!"}
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,123 @@
1
+ require 'test_helper'
2
+
3
+ module Stronger
4
+ class PropertySetTest < Minitest::Test
5
+ include ExAssertions
6
+
7
+ def test_valid_properties
8
+ refute_ex_raised PropertyError, ->{make_set(valid_properties)},
9
+ "Creating a propety set with valid values should not raise "\
10
+ "an exception"
11
+ end
12
+
13
+ def test_extra_properties
14
+ assert_ex_raised UndefinedProperty, ->{make_set(extra_properties)},
15
+ "Extra values given to a property set should result in an "\
16
+ "InvalidProperty exception"
17
+ end
18
+
19
+ def test_invalid_type
20
+ assert_ex_raised InvalidProperty,
21
+ ->{make_set(improperly_typed_properties)},
22
+ "Giving values with the wrong type should result in "\
23
+ "an InvalidProperty exception"
24
+ end
25
+
26
+ def test_missing_required
27
+ assert_ex_raised MissingProperty, ->{make_set(insufficient_properties)},
28
+ "Failing to supply required values should result in a MissingProperty "\
29
+ "error"
30
+ end
31
+
32
+ def test_value_accessors
33
+ name = 'Baron Bower'
34
+
35
+ valid_set[:name] = name
36
+ assert_equal name, valid_set[:name],
37
+ "A property can be set and get using hash syntax"
38
+ assert_ex_raised UndefinedProperty, ->{valid_set[:bobbyhill]},
39
+ "Attempting to access a property not defined on the set "\
40
+ "should result in an undefined property exception"
41
+ end
42
+
43
+ def test_set_invalid_value
44
+ assert_ex_raised InvalidProperty, ->{valid_set[:name] = 25},
45
+ "Attempting to set a property to an invalid value should raise an "\
46
+ "invalid property exception"
47
+ assert_ex_raised UndefinedProperty, ->{valid_set[:bobbyhill] = 25},
48
+ "Attempting to set a property which does not exist should result "\
49
+ "in an undefined property error"
50
+ assert_ex_raised InvalidProperty, ->{valid_set[:name] = nil},
51
+ "Attempting to make a property nil should result in "\
52
+ " an InvalidProperty exception"
53
+ end
54
+
55
+ def test_delete_value
56
+ address = valid_set[:address]
57
+ assert_equal address, valid_set.delete(:address),
58
+ "Deleting a non-required value should return that value"
59
+ assert_ex_raised MissingProperty, ->{valid_set.delete(:name)},
60
+ "Deleting a required value should result in a MissingProperty "\
61
+ "exception"
62
+ end
63
+
64
+ def test_access_missing_value
65
+ assert_ex_raised MissingProperty, ->{partial_valid_set[:address]},
66
+ "A MissingProperty error should be raised when a property "\
67
+ "which is not present is accessed"
68
+ end
69
+
70
+ def test_include?
71
+ refute partial_valid_set.include?(:address),
72
+ "Include returns false for a value which is not present"
73
+
74
+ partial_valid_set[:address] = "hiya"
75
+ assert partial_valid_set.include?(:address),
76
+ "Include returns true for a value which is present"
77
+
78
+ partial_valid_set.delete(:address)
79
+ refute partial_valid_set.include?(:address),
80
+ "Include still returns false for a valid which was present "\
81
+ "but was deleted"
82
+ end
83
+
84
+
85
+ private
86
+
87
+ def partial_valid_set
88
+ @partial_avlid_set ||=
89
+ make_set(valid_properties.tap{|p| p.delete :address})
90
+ end
91
+
92
+ def valid_set
93
+ @valid_set ||= make_set(valid_properties)
94
+ end
95
+
96
+ def make_set(properties)
97
+ PropertySet.new(properties, configured_properties)
98
+ end
99
+
100
+ def configured_properties
101
+ @props ||= [
102
+ Property.new(:name, type: String, required: true),
103
+ Property.new(:address, type: String, required: false) ]
104
+ end
105
+
106
+ def insufficient_properties
107
+ valid_properties.tap{|h| h.delete(:name) }
108
+ end
109
+
110
+ def improperly_typed_properties
111
+ valid_properties.tap {|h| h[:name] = 25}
112
+ end
113
+
114
+ def valid_properties
115
+ { name: "Joseph Miller",
116
+ address: "2344 whatever dr. Raleigh NC 27610" }
117
+ end
118
+
119
+ def extra_properties
120
+ valid_properties.merge({dog: 'Betty Beagle'})
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,6 @@
1
+ require 'minitest/autorun'
2
+ require 'stronger'
3
+ require 'pry'
4
+
5
+ # Test Helpers
6
+ require 'ex_assertions'
@@ -0,0 +1,68 @@
1
+ require 'test_helper'
2
+
3
+ module Stronger
4
+ class TypeCheckingTest < MiniTest::Test
5
+ include ExAssertions
6
+ using TypeChecking
7
+
8
+ DuckInterface = Interface.new(:quack, :walk)
9
+ KickInterface = Interface.new(:give_it_the_boot)
10
+
11
+ module Puntable
12
+ implement KickInterface
13
+ end
14
+
15
+ class Duck
16
+ implement DuckInterface
17
+ include Puntable
18
+
19
+ def quack; end
20
+ def walk; end
21
+ end
22
+
23
+ class Goose
24
+ def quack; end
25
+ def walk; end
26
+ end
27
+
28
+ def test_class_type
29
+ assert String.new.is_strong?(String),
30
+ "Stronger typecheck should match on the class of the object"
31
+ end
32
+
33
+ def test_interface_instance
34
+ assert duck.is_strong?(DuckInterface),
35
+ "An object responding to all of the methods required by an "\
36
+ "interface should be identify with that interface as a type"
37
+ assert duck.is_strong?(Duck),
38
+ "An object of a class should be identified with that class as "\
39
+ "a type."
40
+ assert duck.is_strong?(Puntable),
41
+ "An object belonging to a class which includes a module should "\
42
+ "be identified as a type of that module."
43
+ assert_ex_raised NotImplementedError, ->{duck.give_it_the_boot},
44
+ "An object belonging to an interface should have that method "\
45
+ "defined with a default behavior of raising a NotImplementedError"
46
+ end
47
+
48
+ def test_ducktyped_interface
49
+ assert Goose.instance_implements?(DuckInterface),
50
+ "Even a class which does not explicitly implement an interface will "\
51
+ "identify with that interface if it implements all "\
52
+ "the necessary methods"
53
+ refute Goose.instance_implements?(KickInterface),
54
+ "A class does not implement an interface if it does not satisfy "\
55
+ "all of its methods"
56
+ end
57
+
58
+ def test_instance_implements
59
+ assert Duck.instance_implements?(KickInterface)
60
+ end
61
+
62
+ protected
63
+
64
+ def duck
65
+ @duck ||= Duck.new
66
+ end
67
+ end
68
+ end
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: stronger
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jack Forrest
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-12-28 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Provides several utilities for run-time type-checking with Ruby
14
+ email:
15
+ - jack@jrforrest.net
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ".gitignore"
21
+ - LICENSE
22
+ - README.md
23
+ - Rakefile
24
+ - TODO
25
+ - lib/stronger.rb
26
+ - lib/stronger/array.rb
27
+ - lib/stronger/collection.rb
28
+ - lib/stronger/hash.rb
29
+ - lib/stronger/interface.rb
30
+ - lib/stronger/property.rb
31
+ - lib/stronger/property_definition.rb
32
+ - lib/stronger/property_set.rb
33
+ - lib/stronger/type_checking.rb
34
+ - lib/stronger/version.rb
35
+ - stronger.gemspec
36
+ - test/array_test.rb
37
+ - test/ex_assertions.rb
38
+ - test/hash_test.rb
39
+ - test/property_definition_test.rb
40
+ - test/property_set_test.rb
41
+ - test/test_helper.rb
42
+ - test/type_checking_test.rb
43
+ homepage: https://github.com/jrforrest/stronger
44
+ licenses:
45
+ - MIT
46
+ metadata: {}
47
+ post_install_message:
48
+ rdoc_options: []
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ requirements: []
62
+ rubyforge_project:
63
+ rubygems_version: 2.4.8
64
+ signing_key:
65
+ specification_version: 4
66
+ summary: Run-time type checking utils
67
+ test_files:
68
+ - test/array_test.rb
69
+ - test/ex_assertions.rb
70
+ - test/hash_test.rb
71
+ - test/property_definition_test.rb
72
+ - test/property_set_test.rb
73
+ - test/test_helper.rb
74
+ - test/type_checking_test.rb
75
+ has_rdoc: