typist 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 +7 -0
- data/.gitignore +4 -0
- data/.travis.yml +7 -0
- data/Gemfile +3 -0
- data/README.md +133 -0
- data/Rakefile +16 -0
- data/lib/typist/constructor.rb +48 -0
- data/lib/typist/data.rb +38 -0
- data/lib/typist/error.rb +11 -0
- data/lib/typist/func.rb +49 -0
- data/lib/typist/util.rb +15 -0
- data/lib/typist/version.rb +4 -0
- data/lib/typist.rb +16 -0
- data/spec/lib/typist/constructor_spec.rb +62 -0
- data/spec/lib/typist/data_spec.rb +144 -0
- data/spec/lib/typist/func_spec.rb +83 -0
- data/spec/lib/typist/util_spec.rb +19 -0
- data/spec/lib/typist_spec.rb +31 -0
- data/spec/spec_helper.rb +14 -0
- data/typist.gemspec +21 -0
- metadata +125 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2757d8cd18bfb8ebf6112c05e260f20805f6fb4d
|
4
|
+
data.tar.gz: b5a017f46fba7063f1d8661bfadc2dca4876f0bd
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 175cca390f72642c375d281d836c90a38bbad5bb774049babbb17b3a626a2e74fc1fe51971cb6af2549f867dad42027cfdb12ef33d37728077b0bf8cccc96086
|
7
|
+
data.tar.gz: e0502bdfa7442b48ae9a7baae0985c4f49c353bbeedcccf759f03e46755fbd18cc3f81573878803c06c42d1a396e414295fd66b6149d20af74ec799c01d0d6ee
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
# typist
|
2
|
+
|
3
|
+
[](https://travis-ci.org/nahiluhmot/typist)
|
4
|
+
[](https://codeclimate.com/github/nahiluhmot/typist)
|
5
|
+
|
6
|
+
`typist` is a gem that allows you to define Algebraic Data Types (ADTs) in Ruby.
|
7
|
+
For a tutorial on ADTs, I recommend [Learn You a Haskell's tutorial](http://learnyouahaskell.com/making-our-own-types-and-typeclasses).
|
8
|
+
|
9
|
+
Features:
|
10
|
+
|
11
|
+
* A rich DSL that allows for idiomatic defintions of the data types
|
12
|
+
* Pattern matching
|
13
|
+
* Runtime support for incomplete pattern matches
|
14
|
+
* Class-load time support for invalid pattern matches
|
15
|
+
|
16
|
+
Planned Improvements:
|
17
|
+
|
18
|
+
* Type classes
|
19
|
+
* Optional runtime type checking
|
20
|
+
|
21
|
+
# Installation
|
22
|
+
|
23
|
+
From the command line:
|
24
|
+
|
25
|
+
```shell
|
26
|
+
$ gem install typist
|
27
|
+
```
|
28
|
+
|
29
|
+
From a Gemfile:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
gem 'typist'
|
33
|
+
```
|
34
|
+
|
35
|
+
# Usage
|
36
|
+
|
37
|
+
To define a data type, first extend the `Typist` module in a top-level statement, or in the module in which you'd like your data type defined.
|
38
|
+
For example, to create a new data type in the `Test` module:
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
module Test
|
42
|
+
extend Typist
|
43
|
+
|
44
|
+
...
|
45
|
+
end
|
46
|
+
```
|
47
|
+
|
48
|
+
Once `Typist` has been extended, the `data` function will define a data type.
|
49
|
+
The following defines a new data type called `Tree` in the `Test` module:
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
module Test
|
53
|
+
extend Typist
|
54
|
+
|
55
|
+
data :Tree do
|
56
|
+
...
|
57
|
+
end
|
58
|
+
end
|
59
|
+
```
|
60
|
+
|
61
|
+
Type constructors may be defined using the `constructor` function.
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
module Test
|
65
|
+
extend Typist
|
66
|
+
|
67
|
+
data :Tree do
|
68
|
+
constructor :Leaf
|
69
|
+
constructor :Node, :value, :left, :right
|
70
|
+
end
|
71
|
+
end
|
72
|
+
```
|
73
|
+
|
74
|
+
Now, `Tree::Leaf` and `Tree::Node` are defined.
|
75
|
+
The arguments that come after the constructor name are the instance variables -- accessors are defined for each of them.
|
76
|
+
To create a new Leaf, run `Tree.leaf`.
|
77
|
+
To create a new Node, run `Tree.node(:value => val, :right => Tree.leaf, :left => Tree.leaf)`.
|
78
|
+
|
79
|
+
Finally, the DSL allows the user to define and pattern match in functions.
|
80
|
+
The `func` method in the context of a data type declares a new function.
|
81
|
+
For example:
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
module Test
|
85
|
+
extend Typist
|
86
|
+
|
87
|
+
data :Tree do
|
88
|
+
constructor :Leaf
|
89
|
+
constructor :Node, :value, :left, :right
|
90
|
+
|
91
|
+
func :contains? do
|
92
|
+
match Tree::Leaf do |element|
|
93
|
+
false
|
94
|
+
end
|
95
|
+
|
96
|
+
match Tree::Node do |element|
|
97
|
+
case value <=> element
|
98
|
+
when -1
|
99
|
+
left.contains?(element)
|
100
|
+
when 1
|
101
|
+
right.contains?(element)
|
102
|
+
else
|
103
|
+
true
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
```
|
110
|
+
|
111
|
+
This defines `#contains?` method on `Tree::Node` and `Tree::Leaf`.
|
112
|
+
Example usage:
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
leaf = Tree.leaf
|
116
|
+
node = Tree.node(:value => 'a', :left => Tree.leaf, right => Tree.leaf)
|
117
|
+
|
118
|
+
leaf.contains?('a')
|
119
|
+
# => false
|
120
|
+
|
121
|
+
node.contains?('a')
|
122
|
+
# => true
|
123
|
+
```
|
124
|
+
|
125
|
+
# Contributing
|
126
|
+
|
127
|
+
1. Fork the repository
|
128
|
+
2. Create a branch
|
129
|
+
3. Add tests
|
130
|
+
4. Commit your changes
|
131
|
+
5. Push the branch
|
132
|
+
6. DO NOT bump the version
|
133
|
+
7. Submit a Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'lib'))
|
2
|
+
|
3
|
+
require 'rake'
|
4
|
+
require 'typist'
|
5
|
+
require 'rspec/core/rake_task'
|
6
|
+
require 'cane/rake_task'
|
7
|
+
|
8
|
+
task :default => [:spec, :quality]
|
9
|
+
|
10
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
11
|
+
t.pattern = 'spec/**/*_spec.rb'
|
12
|
+
end
|
13
|
+
|
14
|
+
Cane::RakeTask.new(:quality) do |cane|
|
15
|
+
cane.canefile = '.cane'
|
16
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# This class represents a data-type contstructor.
|
2
|
+
class Typist::Constructor
|
3
|
+
attr_reader :name, :vars
|
4
|
+
|
5
|
+
# Create a new constructor with the given name and instance variable(s).
|
6
|
+
def initialize(name, *vars)
|
7
|
+
@name = name
|
8
|
+
@vars = vars
|
9
|
+
end
|
10
|
+
|
11
|
+
# Get the Class that this Constructor defines.
|
12
|
+
def get_class
|
13
|
+
@class ||= Class.new
|
14
|
+
end
|
15
|
+
|
16
|
+
# Turn the constructor into a class definition, then define a convenience
|
17
|
+
# method in the given module.
|
18
|
+
def define!(context)
|
19
|
+
get_class.tap do |klass|
|
20
|
+
define_class(context, klass)
|
21
|
+
define_initializer(context, klass)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def define_class(context, klass)
|
26
|
+
attrs = vars
|
27
|
+
klass.instance_eval do
|
28
|
+
include context
|
29
|
+
attr_accessor(*attrs)
|
30
|
+
|
31
|
+
define_method(:initialize) do |hash = {}|
|
32
|
+
hash.each { |key, val| instance_variable_set(:"@#{key}", val) }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
context.const_set(name, klass)
|
36
|
+
end
|
37
|
+
private :define_class
|
38
|
+
|
39
|
+
def define_initializer(context, klass)
|
40
|
+
method_name = Typist::Util.snakeify(name)
|
41
|
+
|
42
|
+
context.module_eval do
|
43
|
+
define_method(method_name) { |*args| klass.new(*args) }
|
44
|
+
module_function method_name
|
45
|
+
end
|
46
|
+
end
|
47
|
+
private :define_initializer
|
48
|
+
end
|
data/lib/typist/data.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# Instances of this class may be included like a module.
|
2
|
+
class Typist::Data
|
3
|
+
attr_reader :name, :constructors, :funcs, :block
|
4
|
+
|
5
|
+
# Create a new data type with the given name. The block will be evaluated in
|
6
|
+
# the context of the new instance.
|
7
|
+
def initialize(name, &block)
|
8
|
+
@name = name
|
9
|
+
@constructors = []
|
10
|
+
@funcs = []
|
11
|
+
@block = block
|
12
|
+
end
|
13
|
+
|
14
|
+
# Define a constructor for this data type.
|
15
|
+
def constructor(*args, &block)
|
16
|
+
constructors << Typist::Constructor.new(*args, &block)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Define a function which may pattern-matched against
|
20
|
+
def func(*args, &block)
|
21
|
+
funcs << Typist::Func.new(*args, &block)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Get the module that is defined.
|
25
|
+
def get_module
|
26
|
+
@module ||= Module.new
|
27
|
+
end
|
28
|
+
|
29
|
+
# Define the module, constructors, and functions.
|
30
|
+
def define!(mod = Kernel)
|
31
|
+
get_module.tap do |context|
|
32
|
+
mod.const_set(name, context)
|
33
|
+
instance_eval(&block) unless block.nil?
|
34
|
+
constructors.each { |constructor| constructor.define!(context) }
|
35
|
+
funcs.each { |func| func.define!(context) }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/typist/error.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# This module holds all errors thrown in the gem.
|
2
|
+
module Typist::Error
|
3
|
+
# This class is never thrown, but can be used as a catch-all.
|
4
|
+
class BaseError < StandardError; end
|
5
|
+
|
6
|
+
# This is thrown when a pattern match is not exhaustive.
|
7
|
+
class PatternError < BaseError; end
|
8
|
+
|
9
|
+
# This is thrown when an invalid type contstructor is matched against.
|
10
|
+
class MatchError < BaseError; end
|
11
|
+
end
|
data/lib/typist/func.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# This class defines the `func` DSL.
|
2
|
+
class Typist::Func
|
3
|
+
attr_reader :name, :matches, :block
|
4
|
+
|
5
|
+
# Create a new function with the given name (String/Symbol). If a block is
|
6
|
+
# given, it will be evaluated in the context of the new instance.
|
7
|
+
def initialize(name, &block)
|
8
|
+
@name = name
|
9
|
+
@matches = {}
|
10
|
+
@block = block
|
11
|
+
end
|
12
|
+
|
13
|
+
# Pattern match against the given class.
|
14
|
+
def match(klass, &block)
|
15
|
+
matches[klass] = block
|
16
|
+
end
|
17
|
+
|
18
|
+
# Given a module define this function, plus the pattern matches for all of its
|
19
|
+
# subclasses.
|
20
|
+
def define!(context)
|
21
|
+
instance_eval(&block) unless block.nil?
|
22
|
+
define_base(context)
|
23
|
+
matches.each { |klass, block| define_match(context, klass, &block) }
|
24
|
+
context
|
25
|
+
end
|
26
|
+
|
27
|
+
def define_base(context)
|
28
|
+
method_name = name
|
29
|
+
|
30
|
+
context.class_eval do
|
31
|
+
define_method(method_name) do |*_, &_|
|
32
|
+
raise Typist::Error::PatternError,
|
33
|
+
"Patterns not exhaustive in #{context}##{method_name}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
private :define_base
|
38
|
+
|
39
|
+
def define_match(context, klass, &block)
|
40
|
+
unless klass.ancestors.include?(context)
|
41
|
+
raise Typist::Error::MatchError,
|
42
|
+
"#{klass} is not a valid constructor for #{context}"
|
43
|
+
end
|
44
|
+
|
45
|
+
method_name = name
|
46
|
+
klass.class_eval { define_method(method_name, &block) }
|
47
|
+
end
|
48
|
+
private :define_match
|
49
|
+
end
|
data/lib/typist/util.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# This module contains common logic used throughout the gem.
|
2
|
+
module Typist::Util
|
3
|
+
# Convert a CamelCase String to snake_case. Shamelessly stolen from
|
4
|
+
# ActiveSupport.
|
5
|
+
def snakeify(string)
|
6
|
+
string.to_s.
|
7
|
+
gsub(/::/, '/').
|
8
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
9
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
10
|
+
tr("-", "_").
|
11
|
+
downcase
|
12
|
+
end
|
13
|
+
|
14
|
+
module_function :snakeify
|
15
|
+
end
|
data/lib/typist.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# This is the top level module for the gem, used as a namespace and as a
|
2
|
+
# location for high-level functions.
|
3
|
+
module Typist
|
4
|
+
autoload :Constructor, 'typist/constructor'
|
5
|
+
autoload :Data, 'typist/data'
|
6
|
+
autoload :Error, 'typist/error'
|
7
|
+
autoload :Func, 'typist/func'
|
8
|
+
autoload :Util, 'typist/util'
|
9
|
+
|
10
|
+
# Define a new data type.
|
11
|
+
def data(*args, &block)
|
12
|
+
Data.new(*args, &block).define!(self.is_a?(Module) ? self : Kernel)
|
13
|
+
end
|
14
|
+
|
15
|
+
module_function :data
|
16
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Typist::Constructor do
|
4
|
+
describe '#initialize' do
|
5
|
+
subject { described_class.new(:Node, :value, :left, :right) }
|
6
|
+
|
7
|
+
it 'sets the @name instance variable' do
|
8
|
+
subject.name.should == :Node
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'sets the @vars instance variable' do
|
12
|
+
subject.vars.should == [:value, :left, :right]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '#get_class' do
|
17
|
+
subject { described_class.new(:Leaf) }
|
18
|
+
|
19
|
+
it 'returns a Class' do
|
20
|
+
subject.get_class.should be_a Class
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '#define!' do
|
25
|
+
subject { Typist::Constructor.new(:TestClass, :arg1, :arg2) }
|
26
|
+
let(:mod) { Module.new }
|
27
|
+
|
28
|
+
before { subject.define!(mod) }
|
29
|
+
|
30
|
+
describe 'the defined class' do
|
31
|
+
let(:instance) {
|
32
|
+
mod::TestClass.new(:arg1 => ?a, :arg2 => ?b, :arg3 => ?c)
|
33
|
+
}
|
34
|
+
|
35
|
+
it 'has each @var as an accessor' do
|
36
|
+
instance.should respond_to(:arg1)
|
37
|
+
instance.should respond_to(:arg2)
|
38
|
+
instance.should_not respond_to(:arg3)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'accepts a hash in its initializer which sets instance variables' do
|
42
|
+
instance.instance_variable_get(:@arg1).should == ?a
|
43
|
+
instance.instance_variable_get(:@arg2).should == ?b
|
44
|
+
instance.instance_variable_get(:@arg3).should == ?c
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'includes the given module' do
|
48
|
+
subject.get_class.ancestors.should include(mod)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe 'the given module' do
|
53
|
+
let(:instance) { mod.test_class(:arg1 => 1, :arg2 => 2) }
|
54
|
+
|
55
|
+
it 'defines a method to create an instance of the defined class' do
|
56
|
+
instance.should be_a mod::TestClass
|
57
|
+
instance.arg1.should == 1
|
58
|
+
instance.arg2.should == 2
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Typist::Data do
|
4
|
+
describe '#initialize' do
|
5
|
+
context 'when no block is given' do
|
6
|
+
subject { described_class.new(:Test) }
|
7
|
+
|
8
|
+
it 'sets the @name' do
|
9
|
+
subject.name.should == :Test
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'sets the @constructors to []' do
|
13
|
+
subject.constructors.should be_empty
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'sets the @funcs to []' do
|
17
|
+
subject.funcs.should be_empty
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'when a block is given' do
|
22
|
+
subject {
|
23
|
+
described_class.new(:Test) do
|
24
|
+
instance_variable_set(:@test_var, true)
|
25
|
+
end
|
26
|
+
}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe '#constructor' do
|
31
|
+
subject { described_class.new(:BinaryTree) }
|
32
|
+
|
33
|
+
it 'adds a constructor' do
|
34
|
+
expect { subject.constructor(:Leaf) }
|
35
|
+
.to change { subject.constructors.length }.by(1)
|
36
|
+
|
37
|
+
subject.constructors.should be_all { |constructor|
|
38
|
+
constructor.is_a?(Typist::Constructor)
|
39
|
+
}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe '#func' do
|
44
|
+
subject { described_class.new(:Trie) }
|
45
|
+
|
46
|
+
it 'adds a constructor' do
|
47
|
+
expect { subject.func(:empty) }
|
48
|
+
.to change { subject.funcs.length }.by(1)
|
49
|
+
|
50
|
+
subject.funcs.should be_all { |funcs|
|
51
|
+
funcs.is_a?(Typist::Func)
|
52
|
+
}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe '#get_module' do
|
57
|
+
subject { described_class.new(:Set) }
|
58
|
+
|
59
|
+
it 'returns a module' do
|
60
|
+
subject.get_module.should be_a(Module)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe '#define!' do
|
65
|
+
subject! {
|
66
|
+
defined?(Tree) ? Tree : Typist::Data.new(:Tree) do
|
67
|
+
constructor :Leaf
|
68
|
+
constructor :Node, :value, :left, :right
|
69
|
+
|
70
|
+
func :empty? do
|
71
|
+
match(Tree::Leaf) { true }
|
72
|
+
match(Tree::Node) { false }
|
73
|
+
end
|
74
|
+
|
75
|
+
func :size do
|
76
|
+
match(Tree::Leaf) { 0 }
|
77
|
+
match(Tree::Node) { (left.size + right.size).succ }
|
78
|
+
end
|
79
|
+
|
80
|
+
func :contains? do
|
81
|
+
match(Tree::Leaf) { |_| false }
|
82
|
+
|
83
|
+
match(Tree::Node) do |element|
|
84
|
+
case value <=> element
|
85
|
+
when -1
|
86
|
+
left.contains?(value)
|
87
|
+
when 1
|
88
|
+
right.contains?(value)
|
89
|
+
else
|
90
|
+
true
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
func :== do
|
96
|
+
match(Tree::Leaf) { |tree| tree.is_a?(Tree::Leaf) }
|
97
|
+
match(Tree::Node) do |tree|
|
98
|
+
[
|
99
|
+
tree.is_a?(Tree::Node),
|
100
|
+
value == tree.value,
|
101
|
+
left == tree.left,
|
102
|
+
right == tree.right
|
103
|
+
].all?
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end.tap(&:define!)
|
107
|
+
}
|
108
|
+
|
109
|
+
let(:leaf) { Tree.leaf }
|
110
|
+
let(:node) {
|
111
|
+
Tree.node(
|
112
|
+
:value => 4,
|
113
|
+
:left => Tree.leaf,
|
114
|
+
:right => Tree.leaf
|
115
|
+
)
|
116
|
+
}
|
117
|
+
|
118
|
+
it 'defines convenience methods' do
|
119
|
+
Tree.should respond_to(:node)
|
120
|
+
Tree.should respond_to(:leaf)
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'defines each constructor' do
|
124
|
+
Tree::Node.should include(Tree)
|
125
|
+
Tree::Leaf.should include(Tree)
|
126
|
+
|
127
|
+
node.value.should == 4
|
128
|
+
node.left.should == Tree.leaf
|
129
|
+
node.right.should == Tree.leaf
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'defines functions on the constructors' do
|
133
|
+
leaf.should be_empty
|
134
|
+
node.should_not be_empty
|
135
|
+
|
136
|
+
leaf.contains?(4).should be_false
|
137
|
+
node.contains?(4).should be_true
|
138
|
+
node.contains?(5).should be_false
|
139
|
+
|
140
|
+
leaf.size.should == 0
|
141
|
+
node.size.should == 1
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Typist::Func do
|
4
|
+
describe '#initialize' do
|
5
|
+
context 'when there is no block' do
|
6
|
+
subject { described_class.new(:a_function) }
|
7
|
+
|
8
|
+
it 'sets the @name instance variable to the first argument' do
|
9
|
+
subject.name.should == :a_function
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'has no matches intially' do
|
13
|
+
subject.matches.should be_empty
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'when there is a block' do
|
18
|
+
subject {
|
19
|
+
Typist::Func.new(:this_function) do
|
20
|
+
@test_var = 15
|
21
|
+
end
|
22
|
+
}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '#match' do
|
27
|
+
subject { described_class.new(:test_func) }
|
28
|
+
|
29
|
+
before { subject.match(Fixnum) { :it_works } }
|
30
|
+
|
31
|
+
it 'adds that to the @matches hash' do
|
32
|
+
subject.matches[Fixnum].call.should == :it_works
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe '#define!' do
|
37
|
+
|
38
|
+
context 'when an invalid class is matched against' do
|
39
|
+
subject {
|
40
|
+
module BaseModule; end
|
41
|
+
|
42
|
+
class TestClass; end
|
43
|
+
|
44
|
+
Typist::Func.new(:alpha) do
|
45
|
+
match(TestClass) { puts 'This should not happen!' }
|
46
|
+
end
|
47
|
+
}
|
48
|
+
|
49
|
+
it 'raises a MatchError' do
|
50
|
+
expect { subject.define!(BaseModule) }
|
51
|
+
.to raise_error(Typist::Error::MatchError)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context 'when only valid subclasses are matched against' do
|
56
|
+
subject {
|
57
|
+
module BaseModule; end
|
58
|
+
|
59
|
+
class Matches; include BaseModule; end
|
60
|
+
class DoesNotMatch; include BaseModule; end
|
61
|
+
|
62
|
+
Typist::Func.new(:alpha) do
|
63
|
+
match(Matches) { 'qt3.14' }
|
64
|
+
end
|
65
|
+
}
|
66
|
+
|
67
|
+
before { subject.define!(BaseModule) }
|
68
|
+
|
69
|
+
context 'when a subclass does not match against this function' do
|
70
|
+
it 'raises a PatternError' do
|
71
|
+
expect { DoesNotMatch.new.alpha }
|
72
|
+
.to raise_error(Typist::Error::PatternError)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context 'when a subclass does match against the function' do
|
77
|
+
it 'evaluates its match' do
|
78
|
+
Matches.new.alpha.should == 'qt3.14'
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Typist::Util do
|
4
|
+
subject { Typist::Util }
|
5
|
+
|
6
|
+
describe '#snakeify' do
|
7
|
+
let(:hash) {
|
8
|
+
{
|
9
|
+
'ThisString' => 'this_string',
|
10
|
+
'thatString' => 'that_string',
|
11
|
+
'HeLlO' => 'he_ll_o'
|
12
|
+
}
|
13
|
+
}
|
14
|
+
|
15
|
+
it 'converts CamelCase Strings to snake_case' do
|
16
|
+
hash.each { |camel, snake| subject.snakeify(camel).should == snake }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Typist do
|
4
|
+
it { should be_a Module }
|
5
|
+
|
6
|
+
describe '#data' do
|
7
|
+
before do
|
8
|
+
extend Typist
|
9
|
+
|
10
|
+
data :Boolean do
|
11
|
+
constructor :True
|
12
|
+
constructor :False
|
13
|
+
|
14
|
+
func :to_i do
|
15
|
+
match(Boolean::False) { 0 }
|
16
|
+
match(Boolean::True) { 1 }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'defines a data type' do
|
22
|
+
Boolean.should be_a Module
|
23
|
+
|
24
|
+
Boolean.false.should be_a Boolean::False
|
25
|
+
Boolean.true.should be_a Boolean::True
|
26
|
+
|
27
|
+
Boolean.false.to_i.should == 0
|
28
|
+
Boolean.true.to_i.should == 1
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
|
3
|
+
require 'rspec'
|
4
|
+
require 'typist'
|
5
|
+
|
6
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
|
7
|
+
|
8
|
+
RSpec.configure do |config|
|
9
|
+
config.mock_with :rspec
|
10
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
11
|
+
config.color_enabled = true
|
12
|
+
config.formatter = :documentation
|
13
|
+
config.tty = true
|
14
|
+
end
|
data/typist.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/typist/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ['Tom Hulihan']
|
6
|
+
gem.email = %w{hulihan.tom159@gmail.com}
|
7
|
+
gem.description = %q{Algebraic data types for Ruby}
|
8
|
+
gem.summary = %q{Algebraic data types for Ruby}
|
9
|
+
gem.homepage = 'https://github.com/nahiluhmot/typist'
|
10
|
+
gem.license = 'MIT'
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = 'typist'
|
15
|
+
gem.require_paths = %w{lib}
|
16
|
+
gem.version = Typist::VERSION
|
17
|
+
gem.add_development_dependency 'rake'
|
18
|
+
gem.add_development_dependency 'rspec'
|
19
|
+
gem.add_development_dependency 'cane'
|
20
|
+
gem.add_development_dependency 'pry'
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: typist
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tom Hulihan
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-02-21 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
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: cane
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pry
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: Algebraic data types for Ruby
|
70
|
+
email:
|
71
|
+
- hulihan.tom159@gmail.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- ".gitignore"
|
77
|
+
- ".travis.yml"
|
78
|
+
- Gemfile
|
79
|
+
- README.md
|
80
|
+
- Rakefile
|
81
|
+
- lib/typist.rb
|
82
|
+
- lib/typist/constructor.rb
|
83
|
+
- lib/typist/data.rb
|
84
|
+
- lib/typist/error.rb
|
85
|
+
- lib/typist/func.rb
|
86
|
+
- lib/typist/util.rb
|
87
|
+
- lib/typist/version.rb
|
88
|
+
- spec/lib/typist/constructor_spec.rb
|
89
|
+
- spec/lib/typist/data_spec.rb
|
90
|
+
- spec/lib/typist/func_spec.rb
|
91
|
+
- spec/lib/typist/util_spec.rb
|
92
|
+
- spec/lib/typist_spec.rb
|
93
|
+
- spec/spec_helper.rb
|
94
|
+
- typist.gemspec
|
95
|
+
homepage: https://github.com/nahiluhmot/typist
|
96
|
+
licenses:
|
97
|
+
- MIT
|
98
|
+
metadata: {}
|
99
|
+
post_install_message:
|
100
|
+
rdoc_options: []
|
101
|
+
require_paths:
|
102
|
+
- lib
|
103
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
104
|
+
requirements:
|
105
|
+
- - ">="
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '0'
|
108
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
109
|
+
requirements:
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '0'
|
113
|
+
requirements: []
|
114
|
+
rubyforge_project:
|
115
|
+
rubygems_version: 2.2.0
|
116
|
+
signing_key:
|
117
|
+
specification_version: 4
|
118
|
+
summary: Algebraic data types for Ruby
|
119
|
+
test_files:
|
120
|
+
- spec/lib/typist/constructor_spec.rb
|
121
|
+
- spec/lib/typist/data_spec.rb
|
122
|
+
- spec/lib/typist/func_spec.rb
|
123
|
+
- spec/lib/typist/util_spec.rb
|
124
|
+
- spec/lib/typist_spec.rb
|
125
|
+
- spec/spec_helper.rb
|