typist 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://travis-ci.org/nahiluhmot/typist.png?branch=master)](https://travis-ci.org/nahiluhmot/typist)
|
4
|
+
[![Code Climate](https://codeclimate.com/github/nahiluhmot/typist.png)](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
|