typed-class 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,13 @@
1
+ module TypedClassInternal
2
+
3
+ class Option
4
+
5
+ attr_reader :klass
6
+
7
+ def initialize(klass)
8
+ @klass = ::TypedClassInternal::Util.assert_class(klass, Class)
9
+ end
10
+
11
+ end
12
+
13
+ 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
@@ -0,0 +1,10 @@
1
+ module TypedClassInternal
2
+
3
+ require 'typed_class/attribute'
4
+ require 'typed_class/instance_checks'
5
+ require 'typed_class/metadata'
6
+ require 'typed_class/option'
7
+ require 'typed_class/util'
8
+ require 'typed_class/typed_class'
9
+
10
+ end
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
+