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.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/README.rdoc +71 -0
- data/Rakefile +1 -0
- data/lib/typed.rb +13 -0
- data/lib/typed/default.rb +18 -0
- data/lib/typed/hash.rb +137 -0
- data/lib/typed/schema.rb +40 -0
- data/lib/typed/version.rb +3 -0
- data/spec/hash_spec.rb +181 -0
- data/spec/spec_helper.rb +5 -0
- data/typed.gemspec +25 -0
- metadata +122 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.rdoc
ADDED
@@ -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
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/typed.rb
ADDED
@@ -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
|
data/lib/typed/hash.rb
ADDED
@@ -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
|
data/lib/typed/schema.rb
ADDED
@@ -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
|
data/spec/hash_spec.rb
ADDED
@@ -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
|
+
|
data/spec/spec_helper.rb
ADDED
data/typed.gemspec
ADDED
@@ -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
|
+
|