grayswx-dynastruct 0.0.1

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/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
+