typed-class 0.5.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/lib/typed_class/attribute.rb +30 -0
- data/lib/typed_class/instance_checks.rb +14 -0
- data/lib/typed_class/metadata.rb +44 -0
- data/lib/typed_class/option.rb +13 -0
- data/lib/typed_class/typed_class.rb +125 -0
- data/lib/typed_class/util.rb +50 -0
- data/lib/typed_class.rb +10 -0
- metadata +87 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module TypedClassInternal
|
|
2
|
+
|
|
3
|
+
class Attribute
|
|
4
|
+
|
|
5
|
+
attr_reader :name, :klass, :default
|
|
6
|
+
|
|
7
|
+
def initialize(name, klass, opts={})
|
|
8
|
+
@name = Util.assert_class(name, Symbol)
|
|
9
|
+
@klass = Util.assert_class(klass, Class)
|
|
10
|
+
@optional = opts.delete(:optional)
|
|
11
|
+
@default = Util.assert_class_or_nil(opts.delete(:default), @klass)
|
|
12
|
+
|
|
13
|
+
if !@default.nil?
|
|
14
|
+
Util.check_state(@optional.nil? || @optional,
|
|
15
|
+
"Attribute for klass[%s] with name[%s] has a default - it MUST be optional[%s]" % [@klass, @name, @optional])
|
|
16
|
+
@optional = true
|
|
17
|
+
end
|
|
18
|
+
if @optional.nil?
|
|
19
|
+
@optional = false
|
|
20
|
+
end
|
|
21
|
+
Util.assert_empty_opts(opts)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def optional?
|
|
25
|
+
@optional
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module TypedClassInternal
|
|
2
|
+
|
|
3
|
+
# Here to facilitate better error messages
|
|
4
|
+
module InstanceChecks
|
|
5
|
+
|
|
6
|
+
def InstanceChecks.opts_must_be_a_hash(opts)
|
|
7
|
+
if !opts.is_a?(Hash)
|
|
8
|
+
raise "Expected a hash of name/value pairs but got an argument of type[%s]" % opts.class.name
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module TypedClassInternal
|
|
2
|
+
|
|
3
|
+
class Metadata
|
|
4
|
+
|
|
5
|
+
attr_reader :klass, :attributes, :after_initialize
|
|
6
|
+
|
|
7
|
+
def initialize(klass)
|
|
8
|
+
@klass = Util.assert_class(klass, Class)
|
|
9
|
+
|
|
10
|
+
@attributes = []
|
|
11
|
+
@after_initialize = []
|
|
12
|
+
@defaults = {}
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def add_attribute(attribute)
|
|
16
|
+
Util.assert_class(attribute, TypedClassInternal::Attribute)
|
|
17
|
+
|
|
18
|
+
# We have to delete the attribute. In Ruby, the class can always
|
|
19
|
+
# be re-opened in which case it is possible to redefine a given
|
|
20
|
+
# field.
|
|
21
|
+
@attributes.delete_if { |a| a.name == attribute.name }
|
|
22
|
+
|
|
23
|
+
@attributes << attribute
|
|
24
|
+
if attribute.default
|
|
25
|
+
@defaults[attribute.name] = attribute.default
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def add_after_initialize(block)
|
|
30
|
+
@after_initialize << block
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def get_default(attr_name)
|
|
34
|
+
Util.assert_class(attr_name, Symbol)
|
|
35
|
+
@defaults[attr_name]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def klass_name
|
|
39
|
+
@klass.to_s
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
class TypedClass
|
|
2
|
+
|
|
3
|
+
@@map = {}
|
|
4
|
+
@@metadata = nil
|
|
5
|
+
|
|
6
|
+
def after_initialize
|
|
7
|
+
metadata = @@map[self.class.to_s.to_sym]
|
|
8
|
+
::TypedClassInternal::Util.check_state(metadata, "No metadata for class[%s]" % self.class)
|
|
9
|
+
metadata.after_initialize.each do |block|
|
|
10
|
+
instance_eval(&block)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def TypedClass.inherited(child)
|
|
15
|
+
::TypedClassInternal::Util.assert_class(child, Class)
|
|
16
|
+
::TypedClassInternal::Util.check_state(child.superclass == TypedClass,
|
|
17
|
+
"Class must extend TypedClass directly")
|
|
18
|
+
@@metadata = @@map[child.to_s.to_sym]
|
|
19
|
+
if @@metadata.nil?
|
|
20
|
+
@@map[child.to_s.to_sym] = @@metadata = TypedClassInternal::Metadata.new(child)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def TypedClass.after_initialize(&block)
|
|
25
|
+
::TypedClassInternal::Util.check_not_nil(@@metadata, "metadata not defined")
|
|
26
|
+
@@metadata.add_after_initialize(block)
|
|
27
|
+
TypedClass.rewrite_constructor(@@metadata)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def TypedClass.field(attr_name, attr_klass, opts={})
|
|
31
|
+
::TypedClassInternal::Util.check_not_nil(@@metadata, "metadata not defined")
|
|
32
|
+
::TypedClassInternal::Util.assert_class(attr_name, Symbol)
|
|
33
|
+
default = opts.delete(:default)
|
|
34
|
+
::TypedClassInternal::Util.assert_empty_opts(opts)
|
|
35
|
+
|
|
36
|
+
if attr_klass.is_a?(Class)
|
|
37
|
+
klass = attr_klass
|
|
38
|
+
optional = false
|
|
39
|
+
else
|
|
40
|
+
::TypedClassInternal::Util.assert_class(attr_klass, ::TypedClassInternal::Option)
|
|
41
|
+
klass = attr_klass.klass
|
|
42
|
+
optional = true
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
if default
|
|
46
|
+
::TypedClassInternal::Util.assert_class(default, klass)
|
|
47
|
+
end
|
|
48
|
+
::TypedClassInternal::Util.check_state(default.nil? || optional,
|
|
49
|
+
"Required fields cannot have defaults. Class[%s] Field[%s] Default[%s]" % [klass.name, attr_name, default])
|
|
50
|
+
|
|
51
|
+
attr = ::TypedClassInternal::Attribute.new(attr_name, klass, :default => default, :optional => optional)
|
|
52
|
+
@@metadata.add_attribute(attr)
|
|
53
|
+
TypedClass.rewrite_constructor(@@metadata)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def TypedClass.get_default(klass, attr_name)
|
|
57
|
+
::TypedClassInternal::Util.assert_class(klass, Class)
|
|
58
|
+
::TypedClassInternal::Util.assert_class(attr_name, Symbol)
|
|
59
|
+
metadata = @@map[klass.to_s.to_sym]
|
|
60
|
+
::TypedClassInternal::Util.check_state(metadata, "No metadata for klass[%s]" % klass)
|
|
61
|
+
metadata.get_default(attr_name)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def TypedClass.option(klass)
|
|
65
|
+
::TypedClassInternal::Option.new(klass)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def TypedClass.rewrite_constructor(metadata)
|
|
69
|
+
s = TypedClass.constructor_string(metadata)
|
|
70
|
+
metadata.klass.class_eval s
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def TypedClass.constructor_string(metadata)
|
|
74
|
+
::TypedClassInternal::Util.assert_class(metadata, ::TypedClassInternal::Metadata)
|
|
75
|
+
|
|
76
|
+
required_params = []
|
|
77
|
+
init_body = ""
|
|
78
|
+
have_options = false
|
|
79
|
+
|
|
80
|
+
metadata.attributes.each do |attr|
|
|
81
|
+
if attr.optional?
|
|
82
|
+
have_options = true
|
|
83
|
+
init_body << " @%s = ::TypedClassInternal::Util.assert_class_or_nil(opts.delete(:%s), %s)" % [attr.name, attr.name, attr.klass.to_s]
|
|
84
|
+
else
|
|
85
|
+
required_params << attr.name.to_s
|
|
86
|
+
init_body << " @%s = ::TypedClassInternal::Util.assert_class(%s, %s)" % [attr.name, attr.name, attr.klass.to_s]
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
if metadata.get_default(attr.name)
|
|
90
|
+
init_body << " || ::TypedClass.get_default(%s, :%s)" % [metadata.klass.to_s, attr.name]
|
|
91
|
+
end
|
|
92
|
+
init_body << "\n"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
s = ""
|
|
96
|
+
if !metadata.attributes.empty?
|
|
97
|
+
s = " attr_reader :%s\n" % [metadata.attributes.map(&:name).join(", :")]
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
s << " def initialize(%s" % [required_params.join(", ")]
|
|
101
|
+
if have_options
|
|
102
|
+
if !required_params.empty?
|
|
103
|
+
s << ", "
|
|
104
|
+
end
|
|
105
|
+
s << "opts={}"
|
|
106
|
+
init_body << " ::TypedClassInternal::Util.assert_empty_opts(opts)\n"
|
|
107
|
+
|
|
108
|
+
assertion = " ::TypedClassInternal::InstanceChecks.opts_must_be_a_hash(opts)\n"
|
|
109
|
+
|
|
110
|
+
init_body = "%s\n%s" % [assertion, init_body]
|
|
111
|
+
end
|
|
112
|
+
s << ")\n"
|
|
113
|
+
|
|
114
|
+
s << init_body
|
|
115
|
+
|
|
116
|
+
if !metadata.after_initialize.empty?
|
|
117
|
+
s << " after_initialize\n"
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
s << " end\n"
|
|
121
|
+
|
|
122
|
+
s
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
module TypedClassInternal
|
|
2
|
+
|
|
3
|
+
module Util
|
|
4
|
+
|
|
5
|
+
# Throws an error if opts is not empty. Useful when parsing
|
|
6
|
+
# arguments to a function
|
|
7
|
+
def Util.assert_empty_opts(opts)
|
|
8
|
+
Util.check_state(opts.empty?,
|
|
9
|
+
"Invalid opts: %s\n%s" % [opts.keys.inspect, opts.inspect])
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def Util.assert_class(value, klass)
|
|
13
|
+
Util.check_not_nil(value, "Value cannot be nil")
|
|
14
|
+
Util.check_not_nil(klass, "Klass cannot be nil")
|
|
15
|
+
Util.check_state(value.is_a?(klass),
|
|
16
|
+
"Value is of type[#{value.class}] - class[#{klass}] is required. value[#{value.inspect}]")
|
|
17
|
+
value
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def Util.assert_class_or_nil(value, klass)
|
|
21
|
+
if !value.nil?
|
|
22
|
+
Util.assert_class(value, klass)
|
|
23
|
+
end
|
|
24
|
+
value
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def Util.check_state(expression, error_message=nil)
|
|
28
|
+
if expression.nil? || !expression
|
|
29
|
+
raise error_message || "check_state failed"
|
|
30
|
+
end
|
|
31
|
+
nil
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def Util.check_not_nil(reference, error_message=nil)
|
|
35
|
+
if reference.nil?
|
|
36
|
+
raise error_message || "argument cannot be nil"
|
|
37
|
+
end
|
|
38
|
+
reference
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def Util.check_not_blank(reference, error_message=nil)
|
|
42
|
+
if reference.to_s.empty?
|
|
43
|
+
raise error_message || "argument cannot be blank"
|
|
44
|
+
end
|
|
45
|
+
reference
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
end
|
data/lib/typed_class.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: typed-class
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
hash: 11
|
|
5
|
+
prerelease:
|
|
6
|
+
segments:
|
|
7
|
+
- 0
|
|
8
|
+
- 5
|
|
9
|
+
- 0
|
|
10
|
+
version: 0.5.0
|
|
11
|
+
platform: ruby
|
|
12
|
+
authors:
|
|
13
|
+
- Michael Bryzek
|
|
14
|
+
- Peter Bakhirev
|
|
15
|
+
autorequire:
|
|
16
|
+
bindir: bin
|
|
17
|
+
cert_chain: []
|
|
18
|
+
|
|
19
|
+
date: 2013-08-07 00:00:00 -04:00
|
|
20
|
+
default_executable:
|
|
21
|
+
dependencies:
|
|
22
|
+
- !ruby/object:Gem::Dependency
|
|
23
|
+
name: rspec
|
|
24
|
+
prerelease: false
|
|
25
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
|
26
|
+
none: false
|
|
27
|
+
requirements:
|
|
28
|
+
- - ">="
|
|
29
|
+
- !ruby/object:Gem::Version
|
|
30
|
+
hash: 3
|
|
31
|
+
segments:
|
|
32
|
+
- 0
|
|
33
|
+
version: "0"
|
|
34
|
+
type: :development
|
|
35
|
+
version_requirements: *id001
|
|
36
|
+
description: Allows you to define very simple classes with immutable variables, type safe constructors, and generated readers
|
|
37
|
+
email: mbryzek@alum.mit.edu
|
|
38
|
+
executables: []
|
|
39
|
+
|
|
40
|
+
extensions: []
|
|
41
|
+
|
|
42
|
+
extra_rdoc_files: []
|
|
43
|
+
|
|
44
|
+
files:
|
|
45
|
+
- lib/typed_class.rb
|
|
46
|
+
- lib/typed_class/attribute.rb
|
|
47
|
+
- lib/typed_class/instance_checks.rb
|
|
48
|
+
- lib/typed_class/metadata.rb
|
|
49
|
+
- lib/typed_class/option.rb
|
|
50
|
+
- lib/typed_class/util.rb
|
|
51
|
+
- lib/typed_class/typed_class.rb
|
|
52
|
+
has_rdoc: true
|
|
53
|
+
homepage: http://rubygems.org/gems/typed_class
|
|
54
|
+
licenses:
|
|
55
|
+
- Apache License, Version 2.0
|
|
56
|
+
post_install_message:
|
|
57
|
+
rdoc_options: []
|
|
58
|
+
|
|
59
|
+
require_paths:
|
|
60
|
+
- lib
|
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
62
|
+
none: false
|
|
63
|
+
requirements:
|
|
64
|
+
- - ">="
|
|
65
|
+
- !ruby/object:Gem::Version
|
|
66
|
+
hash: 3
|
|
67
|
+
segments:
|
|
68
|
+
- 0
|
|
69
|
+
version: "0"
|
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
71
|
+
none: false
|
|
72
|
+
requirements:
|
|
73
|
+
- - ">="
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
hash: 3
|
|
76
|
+
segments:
|
|
77
|
+
- 0
|
|
78
|
+
version: "0"
|
|
79
|
+
requirements: []
|
|
80
|
+
|
|
81
|
+
rubyforge_project:
|
|
82
|
+
rubygems_version: 1.4.2
|
|
83
|
+
signing_key:
|
|
84
|
+
specification_version: 3
|
|
85
|
+
summary: A DSL for defining very simple, strongly typed classes
|
|
86
|
+
test_files: []
|
|
87
|
+
|