typed 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem 'activesupport'
4
+ gem 'must', '>= 0.2.5'
@@ -0,0 +1,71 @@
1
+ = typed
2
+
3
+ A Ruby library for Typed variables
4
+
5
+
6
+ == DESCRIPTION:
7
+
8
+ No more "NoMethodError: undefined method"!
9
+ We need some typed variables to avoid silly and stealth mistakes.
10
+
11
+
12
+ == SYNOPSIS:
13
+
14
+ >> vars = Typed::Hash.new
15
+ => {}
16
+
17
+ # Class/Module means not values but type definitions
18
+ >> vars[:num] = Numeric
19
+ >> vars[:num] = 10
20
+ => 10
21
+ >> vars[:num] = "a"
22
+ TypeError: num(Numeric) got String: "a"
23
+
24
+ # Types are automatically guessed
25
+ >> vars[:foo] = 10
26
+ => 10
27
+ >> vars[:foo] = "a"
28
+ TypeError: foo(Fixnum) got String: "a"
29
+
30
+ # Referrence without assigned raises error
31
+ >> vars[:xxx]
32
+ Typed::NotDefined: 'xxx' is not initialized
33
+
34
+ # Hash/Array can be used for complex schema.
35
+ >> vars[:services] = {Integer => [{Symbol => String}]}
36
+ >> vars[:services] = {
37
+ 21 => [{:tcp => "ftp"}, {:udp => "fsp"}],
38
+ 25 => [{:tcp => "smtp"}],
39
+ }
40
+ => {25=>[{:tcp=>"smtp"}], 21=>[{:tcp=>"ftp"}, {:udp=>"fsp"}]}
41
+ >> vars[:services] = {22 => {:tcp => "ssh"}}
42
+ TypeError: services({Integer=>[{Symbol=>String}]}) got {Fixnum=>{Symbol=>String}}: {22=>{:tcp=>"ssh"}}
43
+
44
+
45
+ == REQUIREMENTS:
46
+
47
+ * activesupport gem
48
+ * must gem
49
+
50
+
51
+ == CAUTIONS:
52
+
53
+ * Typed::Hash can't assign Class/Module cause they are treated as type definitions
54
+ * must gem adds Object#must method
55
+
56
+
57
+ == INSTALL:
58
+
59
+ sudo gem install typed
60
+
61
+
62
+ == DOCUMENT:
63
+
64
+ * http://github.com/maiha/typed
65
+
66
+
67
+ == LICENSE:
68
+
69
+ (The MIT License)
70
+
71
+ Copyright (c) 2012 maiha@wota.jp
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,13 @@
1
+ require "must"
2
+ require "active_support/core_ext"
3
+ require "typed/version"
4
+ require "typed/hash"
5
+
6
+ module Typed
7
+ NotDefined = Class.new(RuntimeError)
8
+ LazyValue = Struct.new(:block)
9
+
10
+ autoload :Default, "typed/default"
11
+ autoload :Schema , "typed/schema"
12
+ end
13
+
@@ -0,0 +1,18 @@
1
+ module Typed
2
+ class Default
3
+ def initialize(kvs)
4
+ @kvs = kvs
5
+ end
6
+
7
+ def []=(key, val)
8
+ return if @kvs.exist?(key)
9
+ @kvs[key] = val
10
+ end
11
+
12
+ def regsiter_lazy(key, block)
13
+ return if @kvs.exist?(key)
14
+ raise ArgumentError, "Lazy default value needs block: #{key}" unless block
15
+ @kvs[key] = LazyValue.new(block)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,137 @@
1
+ module Typed
2
+ class Hash
3
+ include Enumerable
4
+
5
+ DEFAULT_OPTIONS = {
6
+ :schema => true,
7
+ }
8
+
9
+ delegate :keys, :to=>"@hash"
10
+
11
+ def initialize(options = {})
12
+ @hash = {}
13
+ @options = DEFAULT_OPTIONS.merge(options.must(::Hash))
14
+ @schema = Schema.new(self)
15
+ @default = Default.new(self)
16
+ end
17
+
18
+ ######################################################################
19
+ ### Default values
20
+
21
+ def default(key = nil, &block)
22
+ if key
23
+ @default.regsiter_lazy(key, block)
24
+ else
25
+ @default
26
+ end
27
+ end
28
+
29
+ ######################################################################
30
+ ### Schema values
31
+
32
+ def schema(key = nil)
33
+ if key
34
+ @schema[key]
35
+ else
36
+ @schema
37
+ end
38
+ end
39
+
40
+ ######################################################################
41
+ ### Accessor
42
+
43
+ def [](key)
44
+ if exist?(key)
45
+ return load(key)
46
+ else
47
+ from = caller.is_a?(Array) ? caller.first : self.class
48
+ raise NotDefined, "'#{key}' is not initialized\n#{from}"
49
+ end
50
+ end
51
+
52
+ def update(key, val)
53
+ @hash[key] = val
54
+ end
55
+
56
+ def []=(key, val)
57
+ if check_schema?(key)
58
+ if @schema.exist?(key)
59
+ @schema.check!(key, val)
60
+ else
61
+ case val
62
+ when LazyValue
63
+ # not schema
64
+ when [], {}
65
+ # ambiguous
66
+ @schema[key] = val
67
+ when nil, true, false
68
+ # no information
69
+ else
70
+ struct = Must::StructInfo.new(val).compact
71
+ @schema[key] = struct
72
+ return if struct == val
73
+ end
74
+ end
75
+ end
76
+ update(key, val)
77
+ end
78
+
79
+ ######################################################################
80
+ ### Testing
81
+
82
+ def exist?(key)
83
+ @hash.has_key?(key)
84
+ end
85
+
86
+ def set?(key)
87
+ !! (exist?(key) && self[key])
88
+ end
89
+
90
+ def check(key, type = nil)
91
+ return @schema.check!(key, self[key]) unless type
92
+
93
+ self[key].must.struct(type) {
94
+ summary = self[key].inspect
95
+ summary = summary[0,200] + "..." if summary.size > 200
96
+ raise TypeError, "'#{key}' expects '#{type.inspect}', but got #{summary}"
97
+ }
98
+ end
99
+
100
+ ######################################################################
101
+ ### Hash compat
102
+
103
+ def each(&block)
104
+ keys.each do |key|
105
+ val = self[key]
106
+ block.call([key,val])
107
+ end
108
+ end
109
+
110
+ def values
111
+ keys.map{|key| self[key]}
112
+ end
113
+
114
+ ######################################################################
115
+ ### Utils
116
+
117
+ def inspect
118
+ keys = @hash.keys.map(&:to_s).sort.join(',')
119
+ "{#{keys}}"
120
+ end
121
+
122
+ private
123
+ def load(key)
124
+ # LazyValue should be evaluated at runtime
125
+ value = @hash[key]
126
+ if value.is_a?(LazyValue)
127
+ value = value.block.call
128
+ self[key] = value
129
+ end
130
+ return value
131
+ end
132
+
133
+ def check_schema?(key)
134
+ true
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,40 @@
1
+ module Typed
2
+ class Schema
3
+ NotFound = Class.new(RuntimeError)
4
+
5
+ def initialize(kvs)
6
+ @kvs = kvs
7
+ @types = {}
8
+ end
9
+
10
+ def exist?(key)
11
+ @types.has_key?(key)
12
+ end
13
+
14
+ def [](key)
15
+ @types[key]
16
+ end
17
+
18
+ def []=(key, val)
19
+ if exist?(key)
20
+ raise "%s is already typed as `%s'" % [key, @types[key].inspect]
21
+ else
22
+ @types[key] = val
23
+ end
24
+ end
25
+
26
+ def check!(key, val)
27
+ struct = @types[key] or
28
+ raise Schema::NotFound, key.to_s
29
+
30
+ if val.must.struct?(struct)
31
+ return true
32
+ else
33
+ expected = @types[key].inspect
34
+ got = Must::StructInfo.new(val).compact.inspect
35
+ value = val.inspect.size > 200 ? val.inspect[0,200] + "..." : val.inspect
36
+ raise TypeError, "%s(%s) got %s: %s" % [key, expected, got, value]
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,3 @@
1
+ module Typed
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,181 @@
1
+ require "spec_helper"
2
+
3
+ describe Typed::Hash do
4
+ def data
5
+ subject
6
+ end
7
+
8
+ ######################################################################
9
+ ### Accessor
10
+
11
+ it "should behave like hash" do
12
+ data["a"] = 1
13
+ data["b"] = 2
14
+ data.keys.sort.should == ["a", "b"]
15
+ data.values.sort.should == [1, 2]
16
+ end
17
+
18
+ it "should behave like hash with lazy values" do
19
+ data["a"] = 1
20
+ data.default("b") { 2 }
21
+ data.keys.sort.should == ["a", "b"]
22
+ data.values.sort.should == [1, 2]
23
+ end
24
+
25
+ describe "#[]" do
26
+ it "should return value if exists" do
27
+ data[:foo] = 1
28
+ data[:foo].should == 1
29
+ end
30
+
31
+ it "should raise NotDefined if value not exists" do
32
+ lambda {
33
+ data[:foo]
34
+ }.should raise_error(Typed::NotDefined)
35
+ end
36
+ end
37
+
38
+ describe "#[]=" do
39
+ it "should set not data but schema when schema given" do
40
+ data[:foo] = String
41
+ data.exist?(:foo).should == false
42
+ end
43
+
44
+ it "should check schema if exists" do
45
+ data[:foo] = String
46
+
47
+ lambda {
48
+ data[:foo] = "foo"
49
+ }.should_not raise_error(TypeError)
50
+
51
+ lambda {
52
+ data[:foo] = 1
53
+ }.should raise_error(TypeError)
54
+ end
55
+
56
+ it "should check schema if exists" do
57
+ data[:foo] = String
58
+
59
+ lambda {
60
+ data[:foo] = "foo"
61
+ }.should_not raise_error(TypeError)
62
+
63
+ lambda {
64
+ data[:foo] = 1
65
+ }.should raise_error(TypeError)
66
+ end
67
+
68
+ it "should guess and check schema if not exists" do
69
+ data[:foo] = 1
70
+ lambda {
71
+ data[:foo] = "test"
72
+ }.should raise_error(TypeError)
73
+ end
74
+
75
+ it "can override existing value if same type" do
76
+ data[:foo] = 1
77
+ data[:foo] = 2
78
+ data[:foo].should == 2
79
+ end
80
+ end
81
+
82
+ ######################################################################
83
+ ### Testing
84
+
85
+ describe "#exist?" do
86
+ it "should return true if the value is set" do
87
+ data[:foo] = 1
88
+ data.exist?(:foo).should == true
89
+ end
90
+
91
+ it "should return false if the value is not set" do
92
+ data.exist?(:foo).should == false
93
+ end
94
+ end
95
+
96
+ describe "#set?" do
97
+ it "should return true if the value is set and not (nil|false)" do
98
+ data[:foo] = 0
99
+ data.set?(:foo).should == true
100
+
101
+ data.default[:bar] = 0
102
+ data.set?(:bar).should == true
103
+
104
+ data.default(:baz) { 0 }
105
+ data.set?(:baz).should == true
106
+ end
107
+
108
+ it "should return false if the value is set but (nil|false)" do
109
+ data[:foo] = nil
110
+ data.set?(:foo).should == false
111
+
112
+ data[:foo] = false
113
+ data.set?(:foo).should == false
114
+ end
115
+ end
116
+
117
+ describe "#check" do
118
+ it "should satisfy its type" do
119
+ data[:foo] = 1
120
+ lambda {
121
+ data.check(:foo, Integer)
122
+ }.should_not raise_error
123
+ end
124
+
125
+ it "should satisfy its struct" do
126
+ data[:foo] = {:a => "text"}
127
+ lambda {
128
+ data.check(:foo, {Symbol => String})
129
+ }.should_not raise_error
130
+ end
131
+
132
+ it "should raise TypeError if not satisfied" do
133
+ data[:foo] = {:a => "text"}
134
+ lambda {
135
+ data.check(:foo, Integer)
136
+ }.should raise_error(TypeError)
137
+ end
138
+ end
139
+
140
+ ######################################################################
141
+ ### Default values
142
+
143
+ describe "#default" do
144
+ it "should set default value" do
145
+ data.default[:foo] = 1
146
+ data.exist?(:foo).should == true
147
+ data[:foo].should == 1
148
+ end
149
+
150
+ it "should be overriden by []=" do
151
+ data.default[:foo] = 1
152
+ data[:foo] = 2
153
+ data[:foo].should == 2
154
+ end
155
+
156
+ it "should not affect data when already set" do
157
+ data[:foo] = 1
158
+ data.default[:foo] = 2
159
+ data[:foo].should == 1
160
+ end
161
+ end
162
+
163
+ describe "#default(&block)" do
164
+ it "should set lazy default value" do
165
+ @a = 1
166
+ data.default(:foo) { @a }
167
+ @a = 2
168
+ data[:foo].should == 2
169
+ end
170
+
171
+ it "should freeze lazy default value once accessed" do
172
+ @a = 1
173
+ data.default(:foo) { @a }
174
+ @a = 2
175
+ data[:foo].should == 2
176
+ @a = 3
177
+ data[:foo].should == 2
178
+ end
179
+ end
180
+ end
181
+
@@ -0,0 +1,5 @@
1
+ $:.unshift File.expand_path('../../lib', __FILE__)
2
+
3
+ require 'rspec'
4
+ require 'typed'
5
+
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "typed/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "typed"
7
+ s.version = Typed::VERSION
8
+ s.authors = ["maiha"]
9
+ s.email = ["maiha@wota.jp"]
10
+ s.homepage = "https://github.com/maiha/typed"
11
+ s.summary = %q{A Ruby library for Typed variables}
12
+ s.description = %q{Typed::Hash}
13
+
14
+ s.rubyforge_project = "typed"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_dependency "activesupport"
22
+ s.add_dependency "must", ">= 0.2.5"
23
+
24
+ s.add_development_dependency "rspec"
25
+ end
metadata ADDED
@@ -0,0 +1,122 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: typed
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - maiha
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-02-10 00:00:00 +09:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: activesupport
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: must
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 29
44
+ segments:
45
+ - 0
46
+ - 2
47
+ - 5
48
+ version: 0.2.5
49
+ type: :runtime
50
+ version_requirements: *id002
51
+ - !ruby/object:Gem::Dependency
52
+ name: rspec
53
+ prerelease: false
54
+ requirement: &id003 !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ hash: 3
60
+ segments:
61
+ - 0
62
+ version: "0"
63
+ type: :development
64
+ version_requirements: *id003
65
+ description: Typed::Hash
66
+ email:
67
+ - maiha@wota.jp
68
+ executables: []
69
+
70
+ extensions: []
71
+
72
+ extra_rdoc_files: []
73
+
74
+ files:
75
+ - .gitignore
76
+ - Gemfile
77
+ - README.rdoc
78
+ - Rakefile
79
+ - lib/typed.rb
80
+ - lib/typed/default.rb
81
+ - lib/typed/hash.rb
82
+ - lib/typed/schema.rb
83
+ - lib/typed/version.rb
84
+ - spec/hash_spec.rb
85
+ - spec/spec_helper.rb
86
+ - typed.gemspec
87
+ has_rdoc: true
88
+ homepage: https://github.com/maiha/typed
89
+ licenses: []
90
+
91
+ post_install_message:
92
+ rdoc_options: []
93
+
94
+ require_paths:
95
+ - lib
96
+ required_ruby_version: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ hash: 3
102
+ segments:
103
+ - 0
104
+ version: "0"
105
+ required_rubygems_version: !ruby/object:Gem::Requirement
106
+ none: false
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ hash: 3
111
+ segments:
112
+ - 0
113
+ version: "0"
114
+ requirements: []
115
+
116
+ rubyforge_project: typed
117
+ rubygems_version: 1.3.7
118
+ signing_key:
119
+ specification_version: 3
120
+ summary: A Ruby library for Typed variables
121
+ test_files: []
122
+