typed_attr 0.0.1

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.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ZTlhNzIwY2U5N2E1ZDMyNzYwOWJlYWU3ODY5NDJiNjY4NWJhMjdjZA==
5
+ data.tar.gz: !binary |-
6
+ MGNiZTk2YTJhNzIyZjA1NGUzY2NkMGFhYjFmOTY4M2I1Yjk4ODFlMQ==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ NTZiZWIyZmM4ZjZjZGNkMjMxZTdlODMyNTQwMzFhMjU1NDNhMDQwNDAxNmE0
10
+ MGU2MjU0ZWY5YjgwMzQyMjg3MTIxNDU5NTlmMTQzZWViOWE5YTAyOWNkOWYx
11
+ ODM4NTE3NjMzYWQ5MTg1MmM3ZTA1ODM0ZTk4NjRhNTY2ZDUyNGQ=
12
+ data.tar.gz: !binary |-
13
+ ODk0MTI3NWM0N2JlNGNiNDgzYWZkOWM1MTc3MmNhNGMxMTYwOTVjODQzN2Mx
14
+ OTNkYTdjM2FkMTJlZTU3ZmZhNzAyZjQ5M2ZhNzY1NjRmMGVjZDhkZjhlNDRh
15
+ OGJlNjY2NDQ2YWMzMDVmNDlkZTFjYTRiYjA4NWUxYzZkZGZhNjk=
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ -f d --color
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in typed_attr.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Kurt Stephens
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # TypedAttr
2
+
3
+ Typed Attributes and Composite Types for Functional Programming in Ruby
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'typed_attr'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install typed_attr
18
+
19
+ ## Usage
20
+
21
+ TypedAttr simplifies typed functional programming in Ruby.
22
+
23
+ The creation of data types is critical to functional programming.
24
+ Ruby does not enforce any typing of attributes or parameters.
25
+
26
+ We introduce a class macro "typed_attr". It constructs an #initialize method
27
+ given a list of attributes and their expected types.
28
+
29
+ Example:
30
+
31
+ require 'typed_attr'
32
+ class Account
33
+ typed_attr name: String, amount: Money
34
+ end
35
+ Account.new("Foo", Money.new(1234))
36
+
37
+ Methods can use "typecheck" to perform checks on arguments:
38
+
39
+ def m x, y
40
+ typecheck x, Positive, Integer
41
+ typecheck y, String
42
+ y * x
43
+ end
44
+ m(-1, "string") # => raise TypeError
45
+ m(2, "string") # => "stringstring"
46
+
47
+ Composite Types can be constructed to match deeper data structures:
48
+
49
+ h = { "a" => 1, "b" => :symbol }
50
+ typecheck h, Hash.of(String.with(Integer|Symbol))
51
+
52
+ ## Contributing
53
+
54
+ 1. Fork it
55
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
56
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
57
+ 4. Push to the branch (`git push origin my-new-feature`)
58
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,88 @@
1
+ class Module
2
+ class CompositeType < self
3
+ def initialize a, b
4
+ @a = a
5
+ @b = b
6
+ end
7
+ def to_s
8
+ @to_s ||= "#{@a}.#{op}(#{@b})".freeze
9
+ end
10
+ end
11
+
12
+ class ContainerType < CompositeType
13
+ def === x
14
+ @a === x and x.all?{|e| @b === e }
15
+ end
16
+ def op; 'of'; end
17
+ end
18
+
19
+ # Constructs a type of Enumeration of an element type.
20
+ #
21
+ # Array.of(String)
22
+ def of t
23
+ ContainerType.new(self, t)
24
+ end
25
+
26
+ class PairType < CompositeType
27
+ def === x
28
+ Enumerable === x and @a === x[0] and @b === x[1]
29
+ end
30
+ def op; 'with'; end
31
+ end
32
+
33
+ # Constructs a type of Pairs.
34
+ #
35
+ # Hash.of(String.with(Integer))
36
+ def with t
37
+ PairType.new(self, t)
38
+ end
39
+
40
+ class AlternateType < CompositeType
41
+ def === x
42
+ @a === x or @b === x
43
+ end
44
+ def to_s
45
+ @to_s ||= "#{@a}|#{@b}".freeze
46
+ end
47
+ end
48
+
49
+ # Constructs a type of Pairs.
50
+ #
51
+ # Array.of(String|Integer)
52
+ def | t
53
+ AlternateType.new(self, t)
54
+ end
55
+ end
56
+
57
+
58
+ module Numericlike
59
+ def self.=== x
60
+ case
61
+ when Numeric === x
62
+ x
63
+ when x.respond_to?(:to_numeric)
64
+ x.to_numeric
65
+ end
66
+ end
67
+ end
68
+
69
+ module Positive
70
+ def self.=== x
71
+ n = Numericlike === x and n > 0
72
+ end
73
+ end
74
+
75
+ module Negative
76
+ def self.=== x
77
+ n = Numericlike == x and n < 0
78
+ end
79
+ end
80
+
81
+ # Note: IO and StringIO do not share a common ancestor Module
82
+ # that distingushes them as being capable of "IO".
83
+ # So we create one here -- devdriven.com 2013/11/14
84
+ require 'stringio'
85
+ module IOable
86
+ ::IO.send(:include, self)
87
+ ::StringIO.send(:include, self)
88
+ end
@@ -0,0 +1,9 @@
1
+ module Enumerable
2
+ # Not sure why this doesn't exist or
3
+ # where to put it.
4
+ def map_with_index
5
+ i = -1
6
+ map { | e | yield e, i += 1 }
7
+ end
8
+ end
9
+
@@ -0,0 +1,3 @@
1
+ module TypedAttr
2
+ VERSION = "0.0.1"
3
+ end
data/lib/typed_attr.rb ADDED
@@ -0,0 +1,72 @@
1
+ require 'typed_attr/enumerable' # map_with_index
2
+
3
+ # Typed Attributes.
4
+ module TypedAttr
5
+ def self.included target
6
+ super
7
+ target.extend(ModuleMethods)
8
+ end
9
+
10
+ # typecheck value, pattern, ...
11
+ #
12
+ # Check that every pattern === value.
13
+ # Raise TypeError if any do not.
14
+ # Returns value.
15
+ def typecheck value, *checks
16
+ unless checks.all? { | check | check === value }
17
+ raise TypeError, "expected (#{checks * ', '}), given #{value}"
18
+ end
19
+ value
20
+ end
21
+
22
+ module ModuleMethods
23
+ OPTIONS = { }
24
+ def typed_attr_option opts
25
+ OPTIONS.update(opts)
26
+ end
27
+
28
+ # typed_attr name: Type, ...
29
+ # typed_attr Type, :name, ...
30
+ #
31
+ # Generates an initialize method that will accept each :name as a typechecked positional argument.
32
+ # Unspecified arguments are undefined and not typechecked.
33
+ # Additional arguments are ignored.
34
+ def typed_attr *types_and_names
35
+ if h = types_and_names.first and Hash === h and types_and_names.size == 1
36
+ names = h.keys
37
+ name_to_type = h
38
+ else
39
+ name_to_type = Hash[*types_and_names.reverse]
40
+ names =
41
+ (0 .. types_and_names.size).to_a.
42
+ keep_if(&:odd?).
43
+ map { | i | types_and_names[i] }
44
+ end
45
+ expr = <<"END"
46
+ def initialize *__args
47
+ initialize_typed_attrs *__args
48
+ end
49
+
50
+ def initialize_typed_attrs *__args
51
+ #{names.map_with_index do | name, i |
52
+ "@#{name} = __args[#{i}] if __args.size > #{i}"
53
+ end * "\n "}
54
+ #{"binding.pry if #{OPTIONS[:pry_if] || true}" if OPTIONS[:pry]}
55
+ #{names.map_with_index do | name, i |
56
+ type = name_to_type[name]
57
+ "typecheck @#{name}, #{type} if __args.size > #{i}"
58
+ end * "\n "}
59
+ end
60
+ attr_reader #{names.map(&:to_sym).map(&:inspect) * ', '}
61
+ END
62
+ $stderr.puts "#{self}\n#{expr}" if OPTIONS[:debug]
63
+ class_eval expr
64
+ end
65
+ end
66
+ end
67
+
68
+
69
+ # Pollute Object.
70
+ Object.send(:include, TypedAttr)
71
+
72
+ require 'typed_attr/composite_type'
@@ -0,0 +1,128 @@
1
+ require 'spec_helper'
2
+ require 'typed_attr'
3
+
4
+ describe Class::CompositeType do
5
+ context "Numericlike" do
6
+ let(:numeric_like) do
7
+ Class.new do
8
+ def to_numeric; -1234; end
9
+ end
10
+ end
11
+
12
+ it "should be true for Numeric" do
13
+ v = 1234
14
+ x = Numericlike === v
15
+ x.should == v
16
+ end
17
+
18
+ it "should be true for anything that responds to :to_numeric" do
19
+ v = numeric_like.new
20
+ x = Numericlike === v
21
+ x.should == -1234
22
+ end
23
+
24
+ it "should be false for non-Numeric" do
25
+ v = "a String"
26
+ v.respond_to?(:to_numeric).should be_false
27
+ (Numericlike === v).should be_false
28
+ end
29
+ end
30
+
31
+ context "ContainerType" do
32
+ it "should not fail when empty" do
33
+ typecheck [ ], Array.of(String)
34
+ end
35
+ it "should not fail with a matching element" do
36
+ typecheck [ "String" ], Array.of(String)
37
+ end
38
+ it "should fail when contains unmatching element" do
39
+ expect do
40
+ typecheck [ "String", 1234 ], Array.of(String)
41
+ end.to raise_error
42
+ end
43
+ it "should not fail when is not an Enumerable" do
44
+ expect do
45
+ typecheck 1234, Array.of(String)
46
+ end.to raise_error
47
+ end
48
+ it "should not fail when is not equvalent" do
49
+ expect do
50
+ typecheck [ ], Hash.of(String)
51
+ end.to raise_error
52
+ end
53
+ end
54
+
55
+ context "PairType" do
56
+ it "should not fail when empty" do
57
+ v = { }
58
+ typecheck v, Hash.of(String.with(Integer))
59
+ end
60
+
61
+ it "should not fail" do
62
+ v = { "foo" => 1 }
63
+ typecheck v, Hash.of(String.with(Integer))
64
+ end
65
+
66
+ it "should fail" do
67
+ v = { "foo" => :symbol }
68
+ (Hash.of(String.with(Integer)) === v).should == false
69
+ end
70
+
71
+ it "should fail" do
72
+ v = { :symbol => 2 }
73
+ (Hash.of(String.with(Integer)) === v).should == false
74
+ end
75
+ end
76
+
77
+ context "AlternateType" do
78
+ it "should not fail when empty" do
79
+ v = [ ]
80
+ (Array.of(String|Integer) === v).should === true
81
+ end
82
+
83
+ it "should not fail" do
84
+ v = [ "String", 1234 ]
85
+ (Array.of(String|Integer) === v).should === true
86
+ end
87
+
88
+ it "should fail" do
89
+ v = [ "String", 1234, :symbol ]
90
+ (Array.of(String|Integer) === v).should === false
91
+ end
92
+ end
93
+
94
+ context "Positive" do
95
+ it "should be true for Numeric" do
96
+ v = 1234
97
+ (Positive === v).should == true
98
+ end
99
+
100
+ it "should be false for negative" do
101
+ v = -1234
102
+ (Positive === v).should be_false
103
+ end
104
+
105
+ it "should be false for non-Numeric" do
106
+ v = "a String"
107
+ (Positive === v).should be_false
108
+ end
109
+ end
110
+
111
+ context "misc" do
112
+ it "example 1" do
113
+ h = { "a" => 1, "b" => :symbol }
114
+ typecheck h, Hash.of(String.with(Integer|Symbol))
115
+ end
116
+
117
+ it "example 2" do
118
+ h = { "a" => 1, "b" => "string" }
119
+ (Hash.of(String.with(Integer|Symbol)) === h).should == false
120
+ end
121
+
122
+ it "should handle to_s" do
123
+ Hash.of(String.with(Integer|Symbol)).to_s.should == "Hash.of(String.with(Integer|Symbol))"
124
+ end
125
+
126
+ end
127
+
128
+ end
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+ require 'typed_attr'
3
+
4
+ describe TypedAttr do
5
+ context "typecheck" do
6
+ it "should not raise if it does match" do
7
+ typecheck "1234", String
8
+ end
9
+
10
+ it "should raise TypeError if it does not match" do
11
+ expect do
12
+ typecheck 1234, String
13
+ end.to raise_error(TypeError)
14
+ end
15
+
16
+ it "should handle multiple checks" do
17
+ expect do
18
+ typecheck 1234, Integer, Positive
19
+ end.to_not raise_error
20
+
21
+ expect do
22
+ typecheck 1234, Integer, Negative
23
+ end.to raise_error(TypeError)
24
+
25
+ expect do
26
+ typecheck 1234, String, Positive
27
+ end.to raise_error(TypeError)
28
+ end
29
+ end
30
+
31
+ context "typed_attr" do
32
+ Old = Class.new do
33
+ typed_attr String, :a, Numeric, :b
34
+ end
35
+ New = Class.new do
36
+ typed_attr a: String, b: Numeric
37
+ end
38
+
39
+ [ Old, New ].each do | cls |
40
+ context "#{cls} syntax" do
41
+ it "should handle ()" do
42
+ obj = cls.new
43
+ obj.a.should == nil
44
+ obj.b.should == nil
45
+ end
46
+
47
+ it "should handle (String)" do
48
+ obj = cls.new("String")
49
+ obj.a.should == "String"
50
+ obj.b.should == nil
51
+ end
52
+
53
+ it "should handle (String, Fixnum)" do
54
+ obj = cls.new("String", 123)
55
+ obj.a.should == "String"
56
+ obj.b.should == 123
57
+ end
58
+
59
+ it "should handle (String, Fixnum, ANYTHING)" do
60
+ obj = cls.new("String", 123, Object.new)
61
+ obj.a.should == "String"
62
+ obj.b.should == 123
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,14 @@
1
+ gem 'pry'
2
+ require 'pry'
3
+
4
+ gem 'simplecov'
5
+ require 'simplecov'
6
+ SimpleCov.start do
7
+ add_filter "spec/"
8
+ end
9
+
10
+ RSpec.configure do |config|
11
+ # ## Mock Framework
12
+ config.mock_with :rspec
13
+ config.order = "random"
14
+ end
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'typed_attr/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "typed_attr"
8
+ spec.version = TypedAttr::VERSION
9
+ spec.authors = ["Kurt Stephens"]
10
+ spec.email = ["ks.github@kurtstephens.com"]
11
+ spec.description = %q{Typed Attributes and Composite Types for Functional Programming in Ruby}
12
+ spec.summary = %q{typed_attr name: String, ...}
13
+ spec.homepage = "https://github.com/kstephens/typed_attr"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "rspec", "~> 2.14"
24
+ spec.add_development_dependency "simplecov", "~> 0.8"
25
+ spec.add_development_dependency "pry", "~> 0.9"
26
+ end
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: typed_attr
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Kurt Stephens
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-11-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '2.14'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '2.14'
55
+ - !ruby/object:Gem::Dependency
56
+ name: simplecov
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '0.8'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '0.8'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: '0.9'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: '0.9'
83
+ description: Typed Attributes and Composite Types for Functional Programming in Ruby
84
+ email:
85
+ - ks.github@kurtstephens.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - .gitignore
91
+ - .rspec
92
+ - Gemfile
93
+ - LICENSE.txt
94
+ - README.md
95
+ - Rakefile
96
+ - lib/typed_attr.rb
97
+ - lib/typed_attr/composite_type.rb
98
+ - lib/typed_attr/enumerable.rb
99
+ - lib/typed_attr/version.rb
100
+ - spec/lib/typed_attr/composite_type_spec.rb
101
+ - spec/lib/typed_attr_spec.rb
102
+ - spec/spec_helper.rb
103
+ - typed_attr.gemspec
104
+ homepage: https://github.com/kstephens/typed_attr
105
+ licenses:
106
+ - MIT
107
+ metadata: {}
108
+ post_install_message:
109
+ rdoc_options: []
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ! '>='
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ required_rubygems_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ! '>='
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ requirements: []
123
+ rubyforge_project:
124
+ rubygems_version: 2.1.10
125
+ signing_key:
126
+ specification_version: 4
127
+ summary: ! 'typed_attr name: String, ...'
128
+ test_files:
129
+ - spec/lib/typed_attr/composite_type_spec.rb
130
+ - spec/lib/typed_attr_spec.rb
131
+ - spec/spec_helper.rb