striuct 0.0.11.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/.gemtest +0 -0
- data/History.rdoc +110 -0
- data/Manifest.txt +15 -0
- data/README.ja.rdoc +244 -0
- data/README.rdoc +220 -0
- data/Rakefile +22 -0
- data/example.rb +214 -0
- data/lib/striuct/classutil.rb +26 -0
- data/lib/striuct/core.rb +56 -0
- data/lib/striuct/import.rb +95 -0
- data/lib/striuct/subclass.rb +262 -0
- data/lib/striuct/subclass_eigen.rb +428 -0
- data/lib/striuct/version.rb +4 -0
- data/lib/striuct.rb +4 -0
- data/test/test_helper.rb +3 -0
- data/test/test_helper_import.rb +4 -0
- data/test/test_striuct.rb +580 -0
- data/test/test_striuct_import.rb +42 -0
- metadata +94 -0
data/example.rb
ADDED
@@ -0,0 +1,214 @@
|
|
1
|
+
#/usr/bin/ruby -w
|
2
|
+
|
3
|
+
require_relative 'lib/striuct'
|
4
|
+
|
5
|
+
def debug(message)
|
6
|
+
puts "line: #{caller[0].slice(/:(\w+)/, 1)}"
|
7
|
+
puts message.inspect, '-' * 80
|
8
|
+
end
|
9
|
+
|
10
|
+
#* Macro "member" provides one of Struct+ interfaces for condtions and a flavor.
|
11
|
+
class User < Striuct.new
|
12
|
+
member :id, Integer
|
13
|
+
member :address, /\A((\w+) ?)+\z/
|
14
|
+
member :age, (20..140)
|
15
|
+
member :name, /\A\w+\z/, /\A\w+ \w+\z/
|
16
|
+
end
|
17
|
+
|
18
|
+
# pass
|
19
|
+
user = User.new 128381, 'Tokyo Japan', 20
|
20
|
+
debug user
|
21
|
+
|
22
|
+
# pass
|
23
|
+
user.age = 30
|
24
|
+
user.name = 'taro yamada'
|
25
|
+
debug user
|
26
|
+
|
27
|
+
# fail (Exception Striuct::ConditionError)
|
28
|
+
begin
|
29
|
+
user[:id] = 10.0
|
30
|
+
rescue
|
31
|
+
debug $!
|
32
|
+
end
|
33
|
+
|
34
|
+
begin
|
35
|
+
user[1] = 'Tokyo-to'
|
36
|
+
rescue
|
37
|
+
debug $!
|
38
|
+
end
|
39
|
+
|
40
|
+
begin
|
41
|
+
user.age = 19
|
42
|
+
rescue
|
43
|
+
debug $!
|
44
|
+
end
|
45
|
+
|
46
|
+
begin
|
47
|
+
user.name = nil
|
48
|
+
rescue
|
49
|
+
debug $!
|
50
|
+
end
|
51
|
+
|
52
|
+
#* but, linked objects are able to clash
|
53
|
+
debug user
|
54
|
+
debug user.strict?
|
55
|
+
debug user
|
56
|
+
debug user.strict?
|
57
|
+
|
58
|
+
# more detail checker do you need, you can use functional object.
|
59
|
+
module Game
|
60
|
+
class Character
|
61
|
+
end
|
62
|
+
|
63
|
+
class DB < Striuct.new
|
64
|
+
member :monsters, ->monsters{(monsters - characters).empty?}
|
65
|
+
member :characters, ->characters{characters.all?{|c|c.kind_of? Character}}
|
66
|
+
end
|
67
|
+
|
68
|
+
monster = Character.new
|
69
|
+
db = DB.new
|
70
|
+
|
71
|
+
begin
|
72
|
+
db.characters = [1, 2]
|
73
|
+
rescue
|
74
|
+
debug $!
|
75
|
+
end
|
76
|
+
|
77
|
+
db.characters = [monster, Character.new]
|
78
|
+
debug db
|
79
|
+
|
80
|
+
begin
|
81
|
+
db.monsters = [:dummy]
|
82
|
+
rescue
|
83
|
+
debug $!
|
84
|
+
end
|
85
|
+
|
86
|
+
db.monsters = [monster]
|
87
|
+
debug db
|
88
|
+
end
|
89
|
+
|
90
|
+
# "inference", check under first passed object's class
|
91
|
+
class FlexibleContainer < Striuct.new
|
92
|
+
member :anything, inference
|
93
|
+
member :number, inference, Numeric
|
94
|
+
end
|
95
|
+
|
96
|
+
fc1, fc2 = FlexibleContainer.new, FlexibleContainer.new
|
97
|
+
fc1.anything = 'str'
|
98
|
+
debug fc1
|
99
|
+
begin
|
100
|
+
fc1.anything = :sym
|
101
|
+
rescue
|
102
|
+
debug $!
|
103
|
+
end
|
104
|
+
|
105
|
+
begin
|
106
|
+
fc2.anything = :sym
|
107
|
+
rescue
|
108
|
+
debug $!
|
109
|
+
end
|
110
|
+
|
111
|
+
fc2.anything = 'string too'
|
112
|
+
|
113
|
+
debug fc2
|
114
|
+
|
115
|
+
begin
|
116
|
+
fc1.number = 'str'
|
117
|
+
rescue
|
118
|
+
debug $!
|
119
|
+
end
|
120
|
+
|
121
|
+
fc1.number = 1.0
|
122
|
+
debug fc1
|
123
|
+
|
124
|
+
begin
|
125
|
+
fc2.number = 1
|
126
|
+
rescue
|
127
|
+
debug $!
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
# with flavor for type cast
|
132
|
+
class User2 < Striuct.new
|
133
|
+
member :age, /\A\d+\z/, Numeric do |arg|
|
134
|
+
Integer arg
|
135
|
+
end
|
136
|
+
|
137
|
+
member :name, ->v{v.respond_to? :to_str} do |v|
|
138
|
+
v.to_str.to_sym
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
user2 = User2.new
|
143
|
+
user2.age = 9
|
144
|
+
debug user2
|
145
|
+
|
146
|
+
user2.age = 10.1
|
147
|
+
debug user2
|
148
|
+
|
149
|
+
user2.age = '10'
|
150
|
+
debug user2
|
151
|
+
|
152
|
+
begin
|
153
|
+
user2.name = 10
|
154
|
+
rescue
|
155
|
+
debug $!
|
156
|
+
end
|
157
|
+
|
158
|
+
user2.name = 's'
|
159
|
+
debug user2.class
|
160
|
+
|
161
|
+
# use default value
|
162
|
+
class User3 < Striuct.new
|
163
|
+
member :lank, Fixnum
|
164
|
+
default :lank, 3
|
165
|
+
member :name
|
166
|
+
end
|
167
|
+
|
168
|
+
user3 = User3.new
|
169
|
+
user3
|
170
|
+
debug user3
|
171
|
+
|
172
|
+
# Standard Struct always define "nil is default". ...realy?
|
173
|
+
debug user3.assign?(:name)
|
174
|
+
user3.name = nil
|
175
|
+
debug user3.assign?(:name)
|
176
|
+
|
177
|
+
# Standard Struct no check member name.
|
178
|
+
NoGuard = Struct.new :__send__, :'? !'
|
179
|
+
noguard = NoGuard.new false
|
180
|
+
debug noguard.__send__
|
181
|
+
debug noguard.methods.include?(:'? !') # lost!!
|
182
|
+
|
183
|
+
# Striuct provides safety levels for naming.
|
184
|
+
class SafetyNaming < Striuct.new
|
185
|
+
begin
|
186
|
+
member :__send__
|
187
|
+
rescue
|
188
|
+
debug $!
|
189
|
+
end
|
190
|
+
|
191
|
+
begin
|
192
|
+
member :'? !'
|
193
|
+
rescue
|
194
|
+
debug $!
|
195
|
+
end
|
196
|
+
|
197
|
+
# set lower
|
198
|
+
protect_level :struct
|
199
|
+
|
200
|
+
member :__send__, :'? !'
|
201
|
+
end
|
202
|
+
|
203
|
+
|
204
|
+
# and keeping Struct's good interface
|
205
|
+
Sth1 = Striuct.new :id, :last_name, :family_name, :address, :age
|
206
|
+
|
207
|
+
debug Sth1.new
|
208
|
+
|
209
|
+
Sth2 = Striuct.new do
|
210
|
+
def my_special_method
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
debug Sth2.new.respond_to?(:my_special_method)
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class Striuct
|
2
|
+
|
3
|
+
|
4
|
+
# @author Kenichi Kamiya
|
5
|
+
module ClassUtil
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
# @macro delegate_class_method
|
10
|
+
def delegate_class_method(name)
|
11
|
+
define_method name do |*args, &block|
|
12
|
+
self.class.__send__ name, *args, &block
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# @macro delegate_class_methods
|
17
|
+
def delegate_class_methods(*names)
|
18
|
+
raise ArgumentError, 'wrong number of argument 0 for 1+' unless names.length >= 1
|
19
|
+
|
20
|
+
names.each{|name|delegate_class_method name}
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
end
|
data/lib/striuct/core.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
# Copyright (C) 2011 Kenichi Kamiya
|
2
|
+
|
3
|
+
require_relative 'subclass_eigen'
|
4
|
+
require_relative 'subclass'
|
5
|
+
|
6
|
+
# @author Kenichi Kamiya
|
7
|
+
class Striuct
|
8
|
+
|
9
|
+
class ConditionError < ArgumentError; end
|
10
|
+
|
11
|
+
class << self
|
12
|
+
alias_method :new_instance, :new
|
13
|
+
private :new_instance
|
14
|
+
|
15
|
+
# @param [Symbol, String] *names
|
16
|
+
# @return [Class] - with Subclass, Subclass:Eigen
|
17
|
+
def new(*names, &block)
|
18
|
+
# warning for Ruby's Struct.new user
|
19
|
+
arg1 = names.first
|
20
|
+
if arg1.instance_of?(String) and /\A[A-Z]/ =~ arg1
|
21
|
+
warn "no define constant #{arg1}"
|
22
|
+
end
|
23
|
+
|
24
|
+
Class.new self do
|
25
|
+
names.each do |name|
|
26
|
+
member name
|
27
|
+
end
|
28
|
+
|
29
|
+
class_eval(&block) if block_given?
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# @yieldreturn [Class] (see Striuct.new) - reject floating class
|
34
|
+
# @return [void]
|
35
|
+
def define(&block)
|
36
|
+
raise ArgumentError, 'must with block' unless block_given?
|
37
|
+
|
38
|
+
new(&block).tap do |subclass|
|
39
|
+
subclass.instance_eval do
|
40
|
+
raise 'not yet finished' if members.empty?
|
41
|
+
close
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def inherited(subclass)
|
49
|
+
subclass.class_eval do
|
50
|
+
extend Subclass::Eigen
|
51
|
+
include Subclass
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
class Striuct
|
2
|
+
|
3
|
+
# @author Kenichi Kamiya
|
4
|
+
module StructExtension
|
5
|
+
|
6
|
+
module Eigen
|
7
|
+
def has_member?(name)
|
8
|
+
members.include? name
|
9
|
+
end
|
10
|
+
|
11
|
+
alias_method :member?, :has_member?
|
12
|
+
alias_method :has_key?, :has_member?
|
13
|
+
alias_method :key?, :has_key?
|
14
|
+
|
15
|
+
def has_condition?(name)
|
16
|
+
false
|
17
|
+
end
|
18
|
+
|
19
|
+
alias_method :restrict?, :has_condition?
|
20
|
+
|
21
|
+
def conditionable?(condition)
|
22
|
+
false
|
23
|
+
end
|
24
|
+
|
25
|
+
def sufficent?(name, value)
|
26
|
+
true
|
27
|
+
end
|
28
|
+
|
29
|
+
alias_method :accept?, :sufficent?
|
30
|
+
|
31
|
+
def has_default?(name)
|
32
|
+
false
|
33
|
+
end
|
34
|
+
|
35
|
+
def cname?(name)
|
36
|
+
[Symbol, String].any?{|klass|name.instance_of? klass}
|
37
|
+
end
|
38
|
+
|
39
|
+
def has_flavor?(name)
|
40
|
+
false
|
41
|
+
end
|
42
|
+
|
43
|
+
# @return [Struct]
|
44
|
+
def load_pairs(pairs)
|
45
|
+
raise TypeError, 'no pairs object' unless pairs.respond_to? :each_pair
|
46
|
+
|
47
|
+
new.tap do |r|
|
48
|
+
pairs.each_pair do |name, value|
|
49
|
+
if member? name
|
50
|
+
r[name] = value
|
51
|
+
else
|
52
|
+
raise ArgumentError, " #{name} is not our member"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def define(lock=false)
|
59
|
+
raise ArgumentError unless lock.equal?(false)
|
60
|
+
|
61
|
+
new.tap{|instance|yield instance}
|
62
|
+
end
|
63
|
+
|
64
|
+
# @return [StrictStruct]
|
65
|
+
def to_strict
|
66
|
+
StrictStruct.new(*members)
|
67
|
+
end
|
68
|
+
|
69
|
+
def closed?
|
70
|
+
true
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def assign?(name)
|
75
|
+
! self[name].nil?
|
76
|
+
end
|
77
|
+
|
78
|
+
def strict?
|
79
|
+
false
|
80
|
+
end
|
81
|
+
|
82
|
+
def secure?
|
83
|
+
false
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
class Struct
|
93
|
+
extend Striuct::StructExtension::Eigen
|
94
|
+
include Striuct::StructExtension
|
95
|
+
end
|
@@ -0,0 +1,262 @@
|
|
1
|
+
require_relative 'classutil'
|
2
|
+
|
3
|
+
class Striuct
|
4
|
+
|
5
|
+
|
6
|
+
# @author Kenichi Kamiya
|
7
|
+
module Subclass
|
8
|
+
|
9
|
+
extend ClassUtil
|
10
|
+
include Enumerable
|
11
|
+
|
12
|
+
def initialize(*values)
|
13
|
+
@db = {}
|
14
|
+
|
15
|
+
if values.size <= size
|
16
|
+
values.each_with_index do |v, idx|
|
17
|
+
self[idx] = v
|
18
|
+
end
|
19
|
+
|
20
|
+
excess = members.last(size - values.size)
|
21
|
+
|
22
|
+
excess.each do |name|
|
23
|
+
self[name] = default_for name if has_default? name
|
24
|
+
end
|
25
|
+
else
|
26
|
+
raise ArgumentError, "struct size differs (max: #{size})"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# @return [Boolean]
|
31
|
+
def ==(other)
|
32
|
+
if self.class.equal? other.class
|
33
|
+
each_pair.all?{|k, v|v == other[k]}
|
34
|
+
else
|
35
|
+
false
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def eql?(other)
|
40
|
+
if self.class.equal? other.class
|
41
|
+
each_pair.all?{|k, v|v.eql? other[k]}
|
42
|
+
else
|
43
|
+
false
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [Integer]
|
48
|
+
def hash
|
49
|
+
values.map(&:hash).hash
|
50
|
+
end
|
51
|
+
|
52
|
+
# @return [String]
|
53
|
+
def inspect
|
54
|
+
"#<#{self.class} (StrictStruct)".tap do |s|
|
55
|
+
each_pair do |name, value|
|
56
|
+
suffix = (has_default?(name) && default?(name)) ? '(default)' : nil
|
57
|
+
s << " #{name}=#{value.inspect}#{suffix}"
|
58
|
+
end
|
59
|
+
|
60
|
+
s << ">"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# @return [String]
|
65
|
+
def to_s
|
66
|
+
"#<struct #{self.class}".tap do |s|
|
67
|
+
each_pair do |name, value|
|
68
|
+
s << " #{name}=#{value.inspect}"
|
69
|
+
end
|
70
|
+
|
71
|
+
s << '>'
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
delegate_class_methods(
|
76
|
+
:members, :keys, :has_member?, :member?, :has_key?, :key?, :length,
|
77
|
+
:size, :keyable_for, :restrict?, :has_default?, :default_for,
|
78
|
+
:names, :has_flavor?, :flavor_for, :has_conditions?, :inference?
|
79
|
+
)
|
80
|
+
|
81
|
+
private :keyable_for, :flavor_for
|
82
|
+
|
83
|
+
# @param [Symbol, String, Fixnum] key
|
84
|
+
def [](key)
|
85
|
+
__subscript__(key){|name|__get__ name}
|
86
|
+
end
|
87
|
+
|
88
|
+
# @param [Symbol, String, Fixnum] key
|
89
|
+
# @param [Object] value
|
90
|
+
def []=(key, value)
|
91
|
+
__subscript__(key){|name|__set__ name, value}
|
92
|
+
end
|
93
|
+
|
94
|
+
# @yield [name]
|
95
|
+
# @yieldparam [Symbol] name - sequential under defined
|
96
|
+
# @yieldreturn [self]
|
97
|
+
# @return [Enumerator]
|
98
|
+
def each_name
|
99
|
+
return to_enum(__method__) unless block_given?
|
100
|
+
self.class.each_name{|name|yield name}
|
101
|
+
self
|
102
|
+
end
|
103
|
+
|
104
|
+
alias_method :each_member, :each_name
|
105
|
+
alias_method :each_key, :each_name
|
106
|
+
|
107
|
+
# @yield [value]
|
108
|
+
# @yieldparam [Object] value - sequential under defined (see #each_name)
|
109
|
+
# @yieldreturn [self]
|
110
|
+
# @return [Enumerator]
|
111
|
+
def each_value
|
112
|
+
return to_enum(__method__) unless block_given?
|
113
|
+
each_member{|member|yield self[member]}
|
114
|
+
end
|
115
|
+
|
116
|
+
alias_method :each, :each_value
|
117
|
+
|
118
|
+
# @yield [name, value]
|
119
|
+
# @yieldparam [Symbol] name (see #each_name)
|
120
|
+
# @yieldparam [Object] value (see #each_value)
|
121
|
+
# @yieldreturn [self]
|
122
|
+
# @return [Enumerator]
|
123
|
+
def each_pair
|
124
|
+
return to_enum(__method__) unless block_given?
|
125
|
+
each_name{|name|yield name, self[name]}
|
126
|
+
end
|
127
|
+
|
128
|
+
# @return [Array]
|
129
|
+
def values
|
130
|
+
[].tap do |r|
|
131
|
+
each_value do |v|
|
132
|
+
r << v
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
alias_method :to_a, :values
|
138
|
+
|
139
|
+
# @param [Fixnum, Range] *keys
|
140
|
+
# @return [Array]
|
141
|
+
def values_at(*keys)
|
142
|
+
[].tap do |r|
|
143
|
+
keys.each do |key|
|
144
|
+
case key
|
145
|
+
when Fixnum
|
146
|
+
r << self[key]
|
147
|
+
when Range
|
148
|
+
key.each do |n|
|
149
|
+
r << self[n]
|
150
|
+
end
|
151
|
+
else
|
152
|
+
raise TypeError
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# @param [Symbol, String] name
|
159
|
+
def assign?(name)
|
160
|
+
name = keyable_for name
|
161
|
+
raise NameError unless member? name
|
162
|
+
|
163
|
+
@db.has_key? name
|
164
|
+
end
|
165
|
+
|
166
|
+
# @param [Symbol, String] name
|
167
|
+
def unassign(name)
|
168
|
+
raise "can't modify frozen #{self.class}" if frozen?
|
169
|
+
name = keyable_for name
|
170
|
+
raise NameError unless member? name
|
171
|
+
|
172
|
+
@db.delete name
|
173
|
+
end
|
174
|
+
|
175
|
+
# @param [Symbol, String] name
|
176
|
+
# @param [Object] *values - no argument and use own
|
177
|
+
def sufficent?(name, value=self[name])
|
178
|
+
self.class.__send__(__method__, name, value, self)
|
179
|
+
end
|
180
|
+
|
181
|
+
alias_method :accept?, :sufficent?
|
182
|
+
|
183
|
+
def strict?
|
184
|
+
each_pair.all?{|name, value|self.class.sufficent? name, value}
|
185
|
+
end
|
186
|
+
|
187
|
+
def secure?
|
188
|
+
frozen? && self.class.closed? && strict?
|
189
|
+
end
|
190
|
+
|
191
|
+
# @return [self]
|
192
|
+
def freeze
|
193
|
+
@db.freeze
|
194
|
+
super
|
195
|
+
end
|
196
|
+
|
197
|
+
# @param [Symbol, String] name
|
198
|
+
def default?(name)
|
199
|
+
default_for(name) == self[name]
|
200
|
+
end
|
201
|
+
|
202
|
+
private
|
203
|
+
|
204
|
+
def initialize_copy(org)
|
205
|
+
@db = @db.clone
|
206
|
+
end
|
207
|
+
|
208
|
+
def __get__(name)
|
209
|
+
name = keyable_for name
|
210
|
+
raise NameError unless member? name
|
211
|
+
|
212
|
+
@db[name]
|
213
|
+
end
|
214
|
+
|
215
|
+
def __set__(name, value)
|
216
|
+
raise "can't modify frozen #{self.class}" if frozen?
|
217
|
+
name = keyable_for name
|
218
|
+
raise NameError unless member? name
|
219
|
+
|
220
|
+
if accept? name, value
|
221
|
+
if has_flavor? name
|
222
|
+
value = instance_exec value, &flavor_for(name)
|
223
|
+
end
|
224
|
+
|
225
|
+
if inference? name
|
226
|
+
self.class.__send__ :__found_family__!, name, value
|
227
|
+
end
|
228
|
+
|
229
|
+
@db[name] = value
|
230
|
+
else
|
231
|
+
raise ConditionError, 'deficent value for all conditions'
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
alias_method :assign, :__set__
|
236
|
+
public :assign
|
237
|
+
|
238
|
+
def __subscript__(key)
|
239
|
+
case key
|
240
|
+
when Symbol, String
|
241
|
+
name = keyable_for key
|
242
|
+
if member? name
|
243
|
+
yield name
|
244
|
+
else
|
245
|
+
raise NameError
|
246
|
+
end
|
247
|
+
when Fixnum
|
248
|
+
if name = members[key]
|
249
|
+
yield name
|
250
|
+
else
|
251
|
+
raise IndexError
|
252
|
+
end
|
253
|
+
else
|
254
|
+
raise ArgumentError
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
|
259
|
+
end
|
260
|
+
|
261
|
+
|
262
|
+
end
|