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