grayswx-dynastruct 0.0.3 → 0.0.4

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