typestrict 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +26 -0
- data/Manifest.txt +8 -0
- data/README +89 -0
- data/README.txt +72 -0
- data/Rakefile +16 -0
- data/bin/typestrict +3 -0
- data/lib/typestrict.rb +159 -0
- data/test/test_strict.rb +8 -0
- metadata +108 -0
data/History.txt
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
=== 0.0.4 / 2010-08-03
|
2
|
+
|
3
|
+
* 1 major enhancement
|
4
|
+
|
5
|
+
* Renamed package to typestrict
|
6
|
+
|
7
|
+
|
8
|
+
=== 0.0.3 / 2010-08-03
|
9
|
+
|
10
|
+
* 1 major enhancement
|
11
|
+
|
12
|
+
* Introduced TypeError and verbose output - now displays list of errors caught and raises single Strict::TypeError
|
13
|
+
|
14
|
+
=== 0.0.2 / 2010-08-03
|
15
|
+
|
16
|
+
* 1 major enhancement
|
17
|
+
|
18
|
+
* Gemify
|
19
|
+
|
20
|
+
|
21
|
+
=== 0.0.1 / 2010-08-02
|
22
|
+
|
23
|
+
* 1 major enhancement
|
24
|
+
|
25
|
+
* Birthday!
|
26
|
+
|
data/Manifest.txt
ADDED
data/README
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
strict is a rudimentary static typesystem for ruby objects - it includes easy validation of individual ruby objects and Hash sets.
|
2
|
+
|
3
|
+
For an example of validation:
|
4
|
+
|
5
|
+
require 'typestrict'
|
6
|
+
|
7
|
+
class Rectangle
|
8
|
+
include Strict
|
9
|
+
|
10
|
+
def initialize(p)
|
11
|
+
|
12
|
+
validate_map!({
|
13
|
+
:width => :natural_number,
|
14
|
+
:height => :natural_number,
|
15
|
+
:float => :boolean
|
16
|
+
}, p)
|
17
|
+
|
18
|
+
@width = p[:width]
|
19
|
+
@height = p[:height]
|
20
|
+
@float = p[:float]
|
21
|
+
end
|
22
|
+
|
23
|
+
def set_dimensions(dimensions)
|
24
|
+
validate_map!({
|
25
|
+
:width => :natural_number,
|
26
|
+
:height => :natural_number
|
27
|
+
}, dimensions) #checks for the presence + validity of all declared parameters
|
28
|
+
|
29
|
+
@height = dimensions[:height]
|
30
|
+
@width = dimensions[:width]
|
31
|
+
end
|
32
|
+
|
33
|
+
def scale_height(scale_coefficent)
|
34
|
+
enforce!(:float, scale_coefficent)
|
35
|
+
return scale_coefficent*@height #alays valid
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
Strict defines a number of 'super-types', currently which are:
|
40
|
+
:natural_number
|
41
|
+
:float
|
42
|
+
:boolean
|
43
|
+
:string_array
|
44
|
+
:numeric_array
|
45
|
+
:float_array
|
46
|
+
:integer_array
|
47
|
+
:hex_color_string
|
48
|
+
|
49
|
+
Strict simply implements an extendable static type-checker over dynamic ruby objects. For an example of validation with troublesome datasets:
|
50
|
+
|
51
|
+
#enforce!(:string_array, ["one", "two", 3, "four"])
|
52
|
+
#./strict.rb:3:in `enforce_primitive!': 3 must be of type String (RuntimeError)
|
53
|
+
|
54
|
+
type_matrix = {
|
55
|
+
:fruit => [:apple, :orange, :banana],
|
56
|
+
:height => :natural_number,
|
57
|
+
:width => :float,
|
58
|
+
:broken => :boolean
|
59
|
+
}
|
60
|
+
|
61
|
+
invalid_data = {
|
62
|
+
:fruit => :pear,
|
63
|
+
:height => -1,
|
64
|
+
:width => 100,
|
65
|
+
:broken => 1}
|
66
|
+
|
67
|
+
|
68
|
+
validate_map!(type_matrix, invalid_data)
|
69
|
+
#./strict.rb:27:in `enforce_weak_primitives!': 1 must be of one of the types of [TrueClass, FalseClass] (RuntimeError)
|
70
|
+
#./strict.rb:3:in `enforce_primitive!': 10 must be of type Float (RuntimeError)
|
71
|
+
#./strict.rb:38:in `enforce!': pear should take on a value within [:apple, :orange, :banana] (RuntimeError)
|
72
|
+
#./strict.rb:53:in `enforce!': -1 must be > 0 (RuntimeError)
|
73
|
+
|
74
|
+
A valid dataset for this type_matrix would be:
|
75
|
+
|
76
|
+
valid_data = {
|
77
|
+
:fruit => :apple,
|
78
|
+
:height => 200,
|
79
|
+
:width => 100.011,
|
80
|
+
:broken => true
|
81
|
+
}
|
82
|
+
|
83
|
+
validate_map!(type_matrix, invalid_data) # => all checks pass
|
84
|
+
|
85
|
+
TODO
|
86
|
+
----
|
87
|
+
Implement generic handler for super-types
|
88
|
+
Performance analysis
|
89
|
+
Implement low-level?
|
data/README.txt
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
= strict
|
2
|
+
|
3
|
+
* http://www.raeez.com/strict
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
Strict implements a traditional static type-system over dynamic ruby objects. Strict is designed to help debug simple type errors.
|
8
|
+
|
9
|
+
== FEATURES/PROBLEMS:
|
10
|
+
|
11
|
+
Implements a Static typesystem over Object
|
12
|
+
Support for ruby Primitives (strict subsets, weak subsets etc.)
|
13
|
+
Support for user-defined types
|
14
|
+
|
15
|
+
== SYNOPSIS:
|
16
|
+
|
17
|
+
def add_nums(a, b)
|
18
|
+
enforce!(:numeric, a)
|
19
|
+
enforce!(:numeric, b)
|
20
|
+
return a + b
|
21
|
+
end
|
22
|
+
|
23
|
+
def launch_rocket(params)
|
24
|
+
enforce_map!({
|
25
|
+
:destination => [:moon, :mars, :saturn, :jupiter, :iss],
|
26
|
+
:rocket_type => [:appollo, :space_x],
|
27
|
+
:fuel_litres => :numeric,
|
28
|
+
:mission_id => :string}, params)
|
29
|
+
... #launch the rocket
|
30
|
+
end
|
31
|
+
|
32
|
+
== REQUIREMENTS:
|
33
|
+
|
34
|
+
none
|
35
|
+
|
36
|
+
== INSTALL:
|
37
|
+
|
38
|
+
sudo gem install strict
|
39
|
+
|
40
|
+
== DEVELOPERS:
|
41
|
+
|
42
|
+
After checking out the source, run:
|
43
|
+
|
44
|
+
$ rake
|
45
|
+
|
46
|
+
This task will install any missing dependencies, run the tests/specs,
|
47
|
+
and generate the RDoc.
|
48
|
+
|
49
|
+
== LICENSE:
|
50
|
+
|
51
|
+
(The MIT License)
|
52
|
+
|
53
|
+
Copyright (c) 2010 FIX
|
54
|
+
|
55
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
56
|
+
a copy of this software and associated documentation files (the
|
57
|
+
'Software'), to deal in the Software without restriction, including
|
58
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
59
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
60
|
+
permit persons to whom the Software is furnished to do so, subject to
|
61
|
+
the following conditions:
|
62
|
+
|
63
|
+
The above copyright notice and this permission notice shall be
|
64
|
+
included in all copies or substantial portions of the Software.
|
65
|
+
|
66
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
67
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
68
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
69
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
70
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
71
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
72
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'hoe'
|
3
|
+
require 'lib/typestrict'
|
4
|
+
|
5
|
+
Hoe.spec 'Strict' do
|
6
|
+
self.name = 'typestrict'
|
7
|
+
self.version = Strict::VERSION
|
8
|
+
self.author = 'Raeez Lorgat'
|
9
|
+
self.description = 'Static typesystem for ruby'
|
10
|
+
self.email = 'raeez@mit.edu'
|
11
|
+
self.summary = 'Strict implements a static typesystem over dynamic ruby objects'
|
12
|
+
self.url = 'http://www.raeez.com/strict'
|
13
|
+
end
|
14
|
+
|
15
|
+
desc "Release and publish documentation"
|
16
|
+
task :repubdoc => [:release, :publish_docs]
|
data/bin/typestrict
ADDED
data/lib/typestrict.rb
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
module Strict
|
2
|
+
VERSION = '0.0.4'
|
3
|
+
|
4
|
+
class TypeError < Exception
|
5
|
+
attr :errors
|
6
|
+
def initialize(error_list)
|
7
|
+
enforce!(:string_array, error_list, 'lib/strict')
|
8
|
+
@errors = error_list
|
9
|
+
end
|
10
|
+
|
11
|
+
def inspect
|
12
|
+
msg = "TypeError#{@errors.size > 1 ? "s" : ""} caught:\n"
|
13
|
+
@errors.each do |e|
|
14
|
+
msg += "#{e}\n"
|
15
|
+
end
|
16
|
+
return msg
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
@@errors = []
|
21
|
+
@@raise_exception = true
|
22
|
+
|
23
|
+
def setmode_raise!
|
24
|
+
@@errors = []
|
25
|
+
@@raise_exception = true
|
26
|
+
end
|
27
|
+
|
28
|
+
def setmode_catch!
|
29
|
+
@@errors = []
|
30
|
+
@@raise_exception = false
|
31
|
+
end
|
32
|
+
|
33
|
+
def catch_error error
|
34
|
+
@@errors << error
|
35
|
+
t = TypeError.new([error])
|
36
|
+
raise(t, t.inspect, caller) if @@raise_exception
|
37
|
+
end
|
38
|
+
|
39
|
+
def raise_hell!
|
40
|
+
t = TypeError.new(@@errors)
|
41
|
+
raise(t, t.inspect, caller) if @@errors.size > 0
|
42
|
+
end
|
43
|
+
|
44
|
+
def header(context, data)
|
45
|
+
"#{context}; #{data.class.inspect}::#{data.inspect}"
|
46
|
+
end
|
47
|
+
|
48
|
+
def enforce_primitive!(type, data, context="Value")
|
49
|
+
catch_error "#{header(context, data)} must be of type #{type}" unless (data.is_a? type and type.is_a? Class)
|
50
|
+
return data
|
51
|
+
end
|
52
|
+
|
53
|
+
def enforce_strict_primitives!(types, data, context="Value")
|
54
|
+
enforce_primitive!(Array, types, 'lib/strict')
|
55
|
+
types.each {|type| enforce_primitive!(Object, type, 'lib/strict')}
|
56
|
+
types.each do |type|
|
57
|
+
enforce_primitive!(type, data, context)
|
58
|
+
end
|
59
|
+
return data
|
60
|
+
end
|
61
|
+
|
62
|
+
def enforce_weak_primitives!(types, data, context="Value")
|
63
|
+
enforce_primitive!(Array, types, 'lib/strict')
|
64
|
+
types.each {|type| enforce_primitive!(Object, type, 'lib/strict')}
|
65
|
+
|
66
|
+
match_found = false
|
67
|
+
types.each do |type|
|
68
|
+
begin
|
69
|
+
s = @@errors.size
|
70
|
+
enforce_primitive!(type, data, context)
|
71
|
+
match_found = true unless s != @@errors.size
|
72
|
+
rescue
|
73
|
+
next
|
74
|
+
end
|
75
|
+
end
|
76
|
+
catch_error "#{header(context, data)} must be of one of the types of #{types.inspect}" unless match_found
|
77
|
+
return data
|
78
|
+
end
|
79
|
+
|
80
|
+
def enforce_non_nil!(obj, context="Value")
|
81
|
+
catch_error "#{context}: Object is nil!" if obj.nil?
|
82
|
+
return obj
|
83
|
+
end
|
84
|
+
|
85
|
+
def enforce!(supertype, data, context="Value") #write a more generic handler, for new types?
|
86
|
+
enforce_non_nil!(data, context)
|
87
|
+
|
88
|
+
if supertype.is_a? Array #enumeration of values
|
89
|
+
catch_error "#{header(context, data)} should take on a value within #{supertype.inspect}" unless supertype.include? data
|
90
|
+
|
91
|
+
else if supertype.is_a? Symbol #distinct type
|
92
|
+
case supertype
|
93
|
+
when :string
|
94
|
+
enforce_primitive!(String, data, context)
|
95
|
+
catch_error "#{header(context, data)} can't be empty string" unless (data.size > 0)
|
96
|
+
|
97
|
+
when :hex_color
|
98
|
+
enforce!(:string, data, context)
|
99
|
+
catch_error "#{header(context, data)} must be six characters long" unless (data.size == 6)
|
100
|
+
data.upcase.each_byte {|c| catch_error "#{header(context, data)} must contain only hexadecimal characters" unless ((48 <= c and c <= 57) or (65 <= c and c <= 70))}
|
101
|
+
|
102
|
+
when :natural_number
|
103
|
+
enforce_primitive!(Fixnum, data, context)
|
104
|
+
catch_error "#{header(context, data)} must be > 0" unless (data > 0)
|
105
|
+
|
106
|
+
when :integer
|
107
|
+
enforce_primitive!(Fixnum, data, context)
|
108
|
+
|
109
|
+
when :numeric
|
110
|
+
enforce_primitive!(Numeric, data, context)
|
111
|
+
|
112
|
+
when :boolean
|
113
|
+
enforce_weak_primitives!([TrueClass, FalseClass], data, context)
|
114
|
+
|
115
|
+
when :float
|
116
|
+
enforce_primitive!(Float, data, context)
|
117
|
+
|
118
|
+
when :string_array
|
119
|
+
enforce_primitive!(Array, data, context)
|
120
|
+
data.each {|item| enforce_primitive!(String, item, context)}
|
121
|
+
|
122
|
+
when :numeric_array
|
123
|
+
enforce_primitive!(Array, data, context)
|
124
|
+
data.each {|item| enforce_primitive!(Numeric, item, context)}
|
125
|
+
|
126
|
+
when :float_array
|
127
|
+
enforce_primitive!(Array, data, context)
|
128
|
+
data.each {|item| enforce_primitive!(Float, item, context)}
|
129
|
+
|
130
|
+
when :integer_array
|
131
|
+
enforce_primitive!(Array, data, context)
|
132
|
+
data.each {|item| enforce_primitive!(Fixnum, item, context)}
|
133
|
+
|
134
|
+
else
|
135
|
+
catch_error "undefined symbol-supertype encountered: #{supertype.inspect}"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
return data
|
140
|
+
end
|
141
|
+
|
142
|
+
def enforce_map!(matrix, map)
|
143
|
+
enforce_primitive!(Hash, matrix, "lib/strict")
|
144
|
+
enforce_primitive!(Hash, map, "lib/strict")
|
145
|
+
|
146
|
+
setmode_catch!
|
147
|
+
|
148
|
+
matrix.each do |param, type|
|
149
|
+
if map.has_key? param
|
150
|
+
enforce!(type, map[param], "map[#{param.inspect}]")
|
151
|
+
else
|
152
|
+
catch_error "map: missing required param: #{param.inspect}"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
raise_hell!
|
156
|
+
setmode_raise!
|
157
|
+
return map
|
158
|
+
end
|
159
|
+
end
|
data/test/test_strict.rb
ADDED
metadata
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: typestrict
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 4
|
10
|
+
version: 0.0.4
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Raeez Lorgat
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-08-03 00:00:00 -04:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: rubyforge
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 7
|
30
|
+
segments:
|
31
|
+
- 2
|
32
|
+
- 0
|
33
|
+
- 4
|
34
|
+
version: 2.0.4
|
35
|
+
type: :development
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: hoe
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 21
|
46
|
+
segments:
|
47
|
+
- 2
|
48
|
+
- 6
|
49
|
+
- 1
|
50
|
+
version: 2.6.1
|
51
|
+
type: :development
|
52
|
+
version_requirements: *id002
|
53
|
+
description: Static typesystem for ruby
|
54
|
+
email: raeez@mit.edu
|
55
|
+
executables:
|
56
|
+
- typestrict
|
57
|
+
extensions: []
|
58
|
+
|
59
|
+
extra_rdoc_files:
|
60
|
+
- History.txt
|
61
|
+
- Manifest.txt
|
62
|
+
- README.txt
|
63
|
+
files:
|
64
|
+
- History.txt
|
65
|
+
- Manifest.txt
|
66
|
+
- README
|
67
|
+
- README.txt
|
68
|
+
- Rakefile
|
69
|
+
- bin/typestrict
|
70
|
+
- lib/typestrict.rb
|
71
|
+
- test/test_strict.rb
|
72
|
+
has_rdoc: true
|
73
|
+
homepage: http://www.raeez.com/strict
|
74
|
+
licenses: []
|
75
|
+
|
76
|
+
post_install_message:
|
77
|
+
rdoc_options:
|
78
|
+
- --main
|
79
|
+
- README.txt
|
80
|
+
require_paths:
|
81
|
+
- lib
|
82
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
hash: 3
|
88
|
+
segments:
|
89
|
+
- 0
|
90
|
+
version: "0"
|
91
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
92
|
+
none: false
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
hash: 3
|
97
|
+
segments:
|
98
|
+
- 0
|
99
|
+
version: "0"
|
100
|
+
requirements: []
|
101
|
+
|
102
|
+
rubyforge_project: strict
|
103
|
+
rubygems_version: 1.3.7
|
104
|
+
signing_key:
|
105
|
+
specification_version: 3
|
106
|
+
summary: Strict implements a static typesystem over dynamic ruby objects
|
107
|
+
test_files:
|
108
|
+
- test/test_strict.rb
|