grayswx-dynastruct 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
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