typed 0.1.0

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.
@@ -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
+