grayswx-dynastruct 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
File without changes
data/Rakefile ADDED
@@ -0,0 +1,33 @@
1
+ %w'dynastruct rubygems rake/clean rake/testtask
2
+ rake/gempackagetask'.each {|w| require w }
3
+
4
+ task :default => [:test]
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ spec = Gem::Specification.new do |s|
11
+ s.name = 'dynastruct'
12
+ s.version = '0.0.1'
13
+ s.summary = 'Structs that can change their shape on the fly.'
14
+ s.author = 'grayswx'
15
+ s.homepage = 'http://github.com/grayswx/dynastruct/tree/master'
16
+ s.rubyforge_project = 'n/a'
17
+ s.email = 'grayswx@gmail.com'
18
+ s.files = FileList['[A-Z]*', '*.rb', '{lib,test}/**/*']
19
+ s.require_paths = ['.', 'lib']
20
+ s.has_rdoc = true
21
+ end
22
+
23
+ Rake::GemPackageTask.new spec do |pkg|
24
+ pkg.need_tar = true
25
+ pkg.need_zip = true
26
+ end
27
+
28
+ desc "Generate a gemspec file for GitHub"
29
+ task :gemspec do
30
+ File.open("#{spec.name}.gemspec", 'w') do |f|
31
+ f.write spec.to_ruby
32
+ end
33
+ end
data/dynastruct.rb ADDED
@@ -0,0 +1,3 @@
1
+ path = File.join(File.dirname(File.expand_path(__FILE__)), 'lib')
2
+ $:.unshift path unless $:.include? path
3
+ require 'd_struct'
@@ -0,0 +1,5 @@
1
+ class NoProcError < StandardError; end
2
+ class ValidationError < StandardError; end
3
+ class KeyError < StandardError; end
4
+
5
+
data/lib/d_field.rb ADDED
@@ -0,0 +1,78 @@
1
+ require 'd_exceptions'
2
+
3
+ class DField
4
+
5
+ # Creates a new kind of DField.
6
+ def initialize( *validators )
7
+ @validators = validators or []
8
+ end
9
+
10
+ ##### Usage Methods
11
+
12
+ # Tests whether or not the given object is a valid object for this field
13
+ def valid?( target )
14
+ @validators.all? {|v| v[target]}
15
+ end
16
+
17
+ # Reads an object of this field type from an IO stream.
18
+ def read( ios )
19
+ raise NoProcError, "Field type cannot read." unless @reader
20
+ @reader[ios]
21
+ end
22
+
23
+ # Writes an object to an IO stream.
24
+ def write( object, ios )
25
+ raise NoProcError, "Field type cannot write." unless @writer
26
+ @writer[object, ios]
27
+ end
28
+
29
+ ##### Creation Methods
30
+
31
+ # Adds a validator to the object.
32
+ def add_validator( &validator )
33
+ @validators << validator
34
+ end
35
+
36
+ # Sets the reader for this field.
37
+ def reader( &proc )
38
+ @reader = proc
39
+ end
40
+
41
+ # Sets the writer for this field.
42
+ def writer( &proc )
43
+ @writer = proc
44
+ end
45
+
46
+ ##### Shortcut creation methods.
47
+
48
+ # Makes a shortcut field creator of the given type.
49
+ def self.make_shortcut( name, test, validator )
50
+ temp, $bypass = $bypass, [name, test, validator]
51
+ class << self
52
+ make_shortcut(*$bypass)
53
+ end
54
+ $bypass = temp
55
+ end
56
+ class << self
57
+ def self.make_shortcut( name, test, validator )
58
+ define_method "of_#{name}".to_sym do |type|
59
+ fieldvar = "@fields_of_#{name}".to_sym
60
+ intern_hash = instance_variable_get fieldvar
61
+ unless intern_hash
62
+ intern_hash = {}
63
+ instance_variable_set fieldvar, intern_hash
64
+ end
65
+ raise ArgumentError, 'Wrong type provided.' unless test[type]
66
+ intern_hash[type] ||= self.new(lambda {|o| validator[type, o]})
67
+ end
68
+ end
69
+ end
70
+
71
+ make_shortcut('class',
72
+ lambda {|t| t.kind_of? Class },
73
+ lambda {|t,o| o.kind_of? t })
74
+ make_shortcut('method',
75
+ lambda {|t| t.kind_of? Symbol },
76
+ lambda {|t,o| o.respond_to? t })
77
+
78
+ end
data/lib/d_key.rb ADDED
@@ -0,0 +1,165 @@
1
+ require 'd_field'
2
+
3
+ class DKey < DField
4
+
5
+ Tuple = Struct.new :index, :hooks, :type
6
+
7
+ # Creates a new DKey.
8
+ # Fields is a list of fields which trigger the test.
9
+ def initialize( parent = nil, *fields, &test )
10
+
11
+ super(lambda do |struct|
12
+ struct.key.ancestor? self
13
+ end)
14
+ reader do |ios|
15
+ struct = DStruct.new self
16
+ # Must do a manual loop here because size can change on the fly.
17
+ index = 0
18
+ while index < struct.key.size do
19
+ struct[index] = struct.key[index].read ios
20
+ index += 1
21
+ end
22
+ end
23
+ writer do |struct, ios|
24
+ struct.each_index do |i|
25
+ struct.key[i].write struct[i], ios
26
+ end
27
+ end
28
+
29
+ @fields = {}
30
+ @fields_i = []
31
+ @children = []
32
+ if parent then
33
+ @size = parent.size
34
+ @parent = parent
35
+ @test = test
36
+ parent.add_child self
37
+ parent.add_hooks fields, self
38
+ else
39
+ raise ArgumentError, "Cannot transform with no parent." \
40
+ if test or not fields.empty?
41
+ @size = 0
42
+ @parent = nil
43
+ end
44
+
45
+ end
46
+
47
+ ##### Usage Methods
48
+
49
+ attr_reader :size, :parent
50
+
51
+ # Returns the index of a field name or index.
52
+ # Also makes sure that the field exists.
53
+ def index( name )
54
+ # If an index.
55
+ if name.kind_of? Integer then
56
+ raise KeyError, 'Key out of bounds.' unless name >= 0 and name < size
57
+ return name
58
+ end
59
+ # If a String or Symbol.
60
+ name = name.to_sym
61
+ field = @fields[name]
62
+ return field.index if field
63
+ return @parent.index(name) if @parent
64
+ raise KeyError, 'Key does not exist.'
65
+ end
66
+
67
+ # Returns a hash of names to field types.
68
+ def fields()
69
+ base = @parent ? @parent.fields : {}
70
+ @fields.each do |key, val|
71
+ base[key] = val.type
72
+ end
73
+ base
74
+ end
75
+
76
+ # Returns the field with the given index or name.
77
+ def field( field )
78
+ field = index_to_field field if field.kind_of? Integer
79
+ @fields[field.to_sym].type
80
+ end
81
+ alias :[] :field
82
+
83
+ # Returns the children of this key.
84
+ def children()
85
+ @children.dup
86
+ end
87
+
88
+ # Returns whether or not the given key is an ancestor.
89
+ def ancestor?( key )
90
+ return true if self == key
91
+ return @parent.ancestor? key if @parent
92
+ false
93
+ end
94
+
95
+ # Returns the new key that results from assigning
96
+ # a given value to a given field.
97
+ # A ValidationError occurs if the assignment is not valid.
98
+ def update( struct, field, value )
99
+ field = index_to_field field if field.kind_of? Integer
100
+ field = field.to_sym
101
+ raise KeyError, 'Key not present' unless @fields.key? field
102
+ field = @fields[field]
103
+ if field.type.valid? value then
104
+ yield field.index
105
+ return catch(:hook_match) do
106
+ field.hooks.each do |key|
107
+ throw :hook_match, key if key.test_hook struct
108
+ end
109
+ self
110
+ end
111
+ end
112
+ raise ValidationError, 'Value not valid.'
113
+ end
114
+
115
+ ##### Helper Methods
116
+
117
+ # Converts an index to a field name.
118
+ def index_to_field( index )
119
+ raise KeyError, 'Key out of bounds' if index < 0 or index >= size
120
+ psize = @parent ? @parent.size : 0
121
+ return @parent.index_to_field index if index < psize
122
+ @fields_i[index - psize]
123
+ end
124
+
125
+ # Increments this and child keys' indices by 1.
126
+ def increment_indices()
127
+ @size += 1
128
+ @fields.each do |key, tuple|
129
+ tuple.index += 1
130
+ end
131
+ @children.each do |child|
132
+ child.increment_indices
133
+ end
134
+ end
135
+
136
+ # Tests if the struct meets the specified criteria for
137
+ # transforming to this key.
138
+ def test_hook( struct )
139
+ @test[struct]
140
+ end
141
+
142
+ ##### Creation Methods
143
+
144
+ # Registers a field with a name.
145
+ def register( name, field )
146
+ name = name.to_sym
147
+ @fields[name] = Tuple.new @size, [], field
148
+ @fields_i[@size] = name
149
+ @size += 1
150
+ @children.each {|c| c.increment_indices }
151
+ end
152
+
153
+ # Adds a child to this key.
154
+ def add_child( child )
155
+ @children << child
156
+ end
157
+
158
+ # Adds hooks to this key.
159
+ def add_hooks( hooks, key )
160
+ hooks.each do |hook|
161
+ @fields[hook.to_sym].hooks << key
162
+ end
163
+ end
164
+
165
+ end
data/lib/d_struct.rb ADDED
@@ -0,0 +1,36 @@
1
+ require 'd_key'
2
+
3
+ class DStruct
4
+
5
+ def initialize( key )
6
+ @key = key
7
+ @data = []
8
+ end
9
+
10
+ attr_reader :key
11
+
12
+ ##### Usage Methods.
13
+
14
+ # Gets the element of the struct.
15
+ def []( field )
16
+ @data[@key.index field]
17
+ end
18
+
19
+ # Assigns the element of the struct.
20
+ def []=( field, value )
21
+ @key = @key.update self, field, value do |i|
22
+ @data[i] = value
23
+ end
24
+ end
25
+
26
+ def each( &proc )
27
+ @data.each &proc
28
+ end
29
+
30
+ def each_index( &proc )
31
+ @data.each_index &proc
32
+ end
33
+
34
+ ##### Helper Methods.
35
+
36
+ end
@@ -0,0 +1,78 @@
1
+ #! /usr/bin/ruby
2
+ require File.join(File.dirname(__FILE__), 'test_helper')
3
+ class DFieldTest < Test::Unit::TestCase
4
+
5
+ context 'A new DField' do
6
+
7
+ setup do
8
+ @field = DField.new
9
+ end
10
+ should 'validate anything' do
11
+ @field.valid?(nil).should be true
12
+ @field.valid?(42).should be true
13
+ end
14
+ should 'not be able to read' do
15
+ lambda{@field.read(StringIO.new)}.should raise_error NoProcError
16
+ end
17
+ should 'not be able to write' do
18
+ lambda{@field.write('', StringIO.new)}.should raise_error NoProcError
19
+ end
20
+
21
+ context 'with a direct writer' do
22
+ setup do
23
+ @field.writer {|v,o| o.write v }
24
+ end
25
+ should 'write out "abc"' do
26
+ ios = StringIO.new
27
+ @field.write 'abc', ios
28
+ ios.string.should be 'abc'
29
+ end
30
+ end
31
+
32
+ context 'with a direct word reader' do
33
+ setup do
34
+ @field.reader {|i| i.gets(' ').strip }
35
+ end
36
+ should 'read in "abc"' do
37
+ ios = StringIO.new 'abc'
38
+ @field.read(ios).should be 'abc'
39
+ end
40
+ end
41
+
42
+ end
43
+
44
+ context 'A new DField of class Integer' do
45
+ setup do
46
+ @field = DField.of_class Integer
47
+ end
48
+ should "validate Integers." do
49
+ @field.valid?(42).should be true
50
+ @field.valid?(0).should be true
51
+ end
52
+ should "not validate non Integers." do
53
+ @field.valid?('hello').should be false
54
+ @field.valid?(45.5).should be false
55
+ end
56
+ should 'intern' do
57
+ @field.should be DField.of_class Integer
58
+ end
59
+ end
60
+
61
+ context 'A new DField of method :to_str' do
62
+ setup do
63
+ @field = DField.of_method :to_str
64
+ end
65
+ should 'validate Strings.' do
66
+ @field.valid?('hello').should be true
67
+ @field.valid?('').should be true
68
+ end
69
+ should 'not validate Numerics.' do
70
+ @field.valid?(42).should be false
71
+ @field.valid?(1.5).should be false
72
+ end
73
+ should 'intern' do
74
+ @field.should be DField.of_method :to_str
75
+ end
76
+ end
77
+
78
+ end
@@ -0,0 +1,167 @@
1
+ #! /usr/bin/ruby
2
+ ### XXX Test the read / write actions. Maybe in Struct?
3
+
4
+
5
+ require File.join(File.dirname(__FILE__), 'test_helper')
6
+ class DKeyTest < Test::Unit::TestCase
7
+
8
+ context 'A new DKey' do
9
+
10
+ setup { @key = DKey.new }
11
+
12
+ should 'have no children' do
13
+ @key.children.empty?.should be true
14
+ end
15
+ should 'have no parent' do
16
+ @key.parent.should be nil
17
+ end
18
+ should 'have 0 elements' do
19
+ @key.size.should be 0
20
+ end
21
+ should 'have no fields' do
22
+ @key.fields.empty?.should be true
23
+ end
24
+ should 'have itself as an ancestor' do
25
+ @key.ancestor?(@key).should be true
26
+ end
27
+ should 'raise a KeyError when trying to update' do
28
+ lambda{@key.update(nil,:aoe,42)}.should raise_error KeyError
29
+ end
30
+ should 'raise a KeyError when trying to find a field' do
31
+ lambda{@key.field(0)}.should raise_error KeyError
32
+ end
33
+ should 'raise a KeyError when accessing a non-existant index' do
34
+ lambda{@key.index(:c)}.should raise_error KeyError
35
+ lambda{@key.index(15)}.should raise_error KeyError
36
+ end
37
+
38
+ context 'with a child' do
39
+
40
+ setup { @child = DKey.new @key }
41
+
42
+ should 'have 1 child' do
43
+ @key.children.size.should be 1
44
+ end
45
+
46
+ context 'and a grandchild' do
47
+
48
+ setup { @grand = DKey.new @child }
49
+
50
+ should 'have the correct ancestors' do
51
+ @grand.ancestor?(@key).should be true
52
+ @grand.ancestor?(@child).should be true
53
+ @child.ancestor?(@key).should be true
54
+ @key.ancestor?(@child).should be false
55
+ @key.ancestor?(@grand).should be false
56
+ end
57
+
58
+ should 'correctly validate DStructs' do
59
+ @k_s = DStruct.new @key
60
+ @c_s = DStruct.new @child
61
+ @g_s = DStruct.new @grand
62
+ @key.valid?(@k_s).should be true
63
+ @key.valid?(@c_s).should be true
64
+ @key.valid?(@g_s).should be true
65
+ @child.valid?(@k_s).should be false
66
+ @child.valid?(@g_s).should be true
67
+ @grand.valid?(@c_s).should be false
68
+ end
69
+ end
70
+ end
71
+
72
+ end
73
+
74
+ context 'A DKey with two strict fields' do
75
+
76
+ setup do
77
+ @key = DKey.new
78
+ @str_field = DField.of_class String
79
+ @int_field = DField.of_class Integer
80
+ @key.register 'str', @str_field
81
+ @key.register :int, @int_field
82
+ end
83
+
84
+ should 'have the correct fields' do
85
+ @key.fields.should be(
86
+ {:str => @str_field, :int => @int_field})
87
+ end
88
+
89
+ should 'return the correct fields' do
90
+ @key.field(0).should be @str_field
91
+ @key.field(1).should be @int_field
92
+ @key.field(:str).should be @str_field
93
+ @key.field('int').should be @int_field
94
+ end
95
+
96
+ should 'have the correct indices' do
97
+ @key.index(:str).should be 0
98
+ @key.index('int').should be 1
99
+ @key.index(1).should be 1
100
+ end
101
+
102
+ should 'return itself when updated correctly' do
103
+ @key.update(nil, 'str', 'abc'){}.should be @key
104
+ @key.update(nil, 'int', 12345){}.should be @key
105
+ end
106
+
107
+ should 'raise a ValidationError when updated incorrectly' do
108
+ lambda{@key.update(nil, :str, 123){}}.should raise_error ValidationError
109
+ lambda{@key.update(nil, :int, 'p'){}}.should raise_error ValidationError
110
+ end
111
+
112
+ context 'and a child with 1 field.' do
113
+
114
+ setup {
115
+ @child = DKey.new @key
116
+ @child.register :i2, @int_field
117
+ }
118
+
119
+ context 'The child' do
120
+ should 'have the correct size' do
121
+ @child.size.should be 3
122
+ end
123
+ should 'have the correct indices' do
124
+ @child.index(:int).should be 1
125
+ @child.index('i2').should be 2
126
+ end
127
+ should 'have the correct fields' do
128
+ @child.fields.should be(
129
+ {:str => @str_field, :int => @int_field, :i2 => @int_field})
130
+ end
131
+ end
132
+
133
+ context 'The parent' do
134
+ should 'have the correct fields' do
135
+ @key.fields.should be(
136
+ {:str => @str_field, :int => @int_field})
137
+ end
138
+
139
+ should 'have the correct indices' do
140
+ @key.index('str').should be 0
141
+ @key.index(:int).should be 1
142
+ end
143
+ end
144
+
145
+ context 'When the parent is updated. The child' do
146
+
147
+ setup { @key.register :s2, @str_field }
148
+
149
+ should 'update its fields' do
150
+ @child.fields.should be(
151
+ {:str => @str_field, :int => @int_field,
152
+ :s2 => @str_field, :i2 => @int_field})
153
+ end
154
+
155
+ should 'update its indices' do
156
+ @child.index(:s2).should be 2
157
+ @child.index(:i2).should be 3
158
+ end
159
+
160
+ should 'update its size' do
161
+ @child.size.should be 4
162
+ end
163
+
164
+ end
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,88 @@
1
+ #! /usr/bin/ruby
2
+ require File.join(File.dirname(__FILE__), 'test_helper')
3
+ class DStructTest < Test::Unit::TestCase
4
+
5
+ context 'A new DStruct of a 1 element key' do
6
+
7
+ setup do
8
+ @field = DField.of_class Integer
9
+ @key = DKey.new
10
+ @key.register :a, @field
11
+ @struct = DStruct.new @key
12
+ end
13
+
14
+ should 'raise a KeyError when a non-existant field is accessed or assigned' do
15
+ lambda{@struct['b']}.should raise_error KeyError
16
+ lambda{@struct[:x] = 42}.should raise_error KeyError
17
+ end
18
+
19
+ should 'correctly store data' do
20
+ @struct[:a].should be nil
21
+ @struct['a'] = 42
22
+ @struct['a'].should be 42
23
+ end
24
+
25
+ should 'raise a ValidationError when incorrectly assigned' do
26
+ lambda{@struct['a'] = 'abc'}.should raise_error ValidationError
27
+ end
28
+
29
+ end
30
+
31
+ context 'A transforming struct' do
32
+
33
+ setup do
34
+ @f_str = DField.of_class String
35
+ @f_sym = DField.of_class Symbol
36
+ @f_int = DField.of_class Integer
37
+ @k_base = DKey.new
38
+ @k_base.register :type, @f_sym
39
+ @k_base.register :typ2, @f_sym
40
+
41
+ @k_date = DKey.new(@k_base, :type) do |s|
42
+ s[:type] == :date
43
+ end
44
+ @k_date.register :date, @f_str
45
+
46
+ @k_nums = DKey.new(@k_base, :type, :typ2) do |s|
47
+ s[:type] == :num and s[:typ2] == :s
48
+ end
49
+ @k_nums.register :num1, @f_int
50
+ @k_nums.register :num2, @f_int
51
+ @k_nums.register :num3, @f_int
52
+
53
+ @k_same = DKey.new(@k_base, :typ2) do |s|
54
+ s[:type] == s[:typ2]
55
+ end
56
+
57
+ @struct = DStruct.new @k_base
58
+ end
59
+
60
+ should 'be able to start as a child' do
61
+ a = DStruct.new @k_date
62
+ a[:date] = 'today'
63
+ end
64
+
65
+ should 'transform into the date structure' do
66
+ @struct[:type] = :date
67
+ @struct.key.should be @k_date
68
+ end
69
+
70
+ should 'transform into the nums structure' do
71
+ @struct[:type] = :num
72
+ @struct[:typ2] = :a
73
+ @struct.key.should be @k_base
74
+ @struct[:typ2] = :s
75
+ @struct.key.should be @k_nums
76
+ end
77
+
78
+ should 'only transform when the selected key is changed' do
79
+ @struct[:typ2] = :a
80
+ @struct[:type] = :a
81
+ @struct.key.should be @k_base
82
+ @struct[:typ2] = :a
83
+ @struct.key.should be @k_same
84
+ end
85
+
86
+ end
87
+
88
+ end
@@ -0,0 +1,2 @@
1
+ require File.join(File.dirname(__FILE__), '../dynastruct')
2
+ %w'rubygems shoulda matchy stringio'.each {|w| require w }
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: grayswx-dynastruct
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - grayswx
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-12-23 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: grayswx@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - README
26
+ - Rakefile
27
+ - te.rb
28
+ - dynastruct.rb
29
+ - lib/d_key.rb
30
+ - lib/d_exceptions.rb
31
+ - lib/d_struct.rb
32
+ - lib/d_field.rb
33
+ - test/d_struct_test.rb
34
+ - test/d_key_test.rb
35
+ - test/test_helper.rb
36
+ - test/d_field_test.rb
37
+ has_rdoc: true
38
+ homepage: http://github.com/grayswx/dynastruct/tree/master
39
+ post_install_message:
40
+ rdoc_options: []
41
+
42
+ require_paths:
43
+ - .
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: "0"
50
+ version:
51
+ required_rubygems_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: "0"
56
+ version:
57
+ requirements: []
58
+
59
+ rubyforge_project: n/a
60
+ rubygems_version: 1.2.0
61
+ signing_key:
62
+ specification_version: 2
63
+ summary: Structs that can change their shape on the fly.
64
+ test_files: []
65
+