striuct 0.0.11.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,428 @@
1
+ class Striuct; module Subclass
2
+
3
+ # @author Kenichi Kamiya
4
+ module Eigen
5
+
6
+ INFERENCE = Object.new.freeze
7
+
8
+ NAMING_RISKS = {
9
+ conflict: 10,
10
+ no_identifier: 9,
11
+ bad_manners: 5,
12
+ no_ascii: 3,
13
+ strict: 0
14
+ }.freeze
15
+
16
+ PROTECT_LEVELS = {
17
+ struct: {error: 99, warn: 99},
18
+ warning: {error: 99, warn: 5},
19
+ error: {error: 9, warn: 5},
20
+ prevent: {error: 5, warn: 1},
21
+ nervous: {error: 1, warn: 1}
22
+ }.each(&:freeze).freeze
23
+
24
+ if respond_to? :private_constant
25
+ private_constant :INFERENCE, :NAMING_RISKS, :PROTECT_LEVELS
26
+ end
27
+
28
+ class << self
29
+ private
30
+
31
+ def extended(klass)
32
+ klass.class_eval do
33
+ @names, @conditions, @flavors, @defaults = [], {}, {}, {}
34
+ @inferences, @protect_level = {}, :prevent
35
+ end
36
+ end
37
+ end
38
+
39
+ # @return [Subclass]
40
+ def new(*values)
41
+ new_instance(*values)
42
+ end
43
+
44
+ # @param [#each_pair] pairs ex: Hash
45
+ # @return [Subclass]
46
+ def load_pairs(pairs)
47
+ raise TypeError, 'no pairs object' unless pairs.respond_to? :each_pair
48
+
49
+ new.tap do |r|
50
+ pairs.each_pair do |name, value|
51
+ if member? name
52
+ r[name] = value
53
+ else
54
+ raise ArgumentError, " #{name} is not our member"
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ # @yieldparam [Subclass] instance
61
+ # @yieldreturn [Subclass] instance
62
+ # @return [void]
63
+ def define(lock=true)
64
+ raise ArgumentError, 'must with block' unless block_given?
65
+
66
+ new.tap do |instance|
67
+ yield instance
68
+
69
+ if each_member.all?{|name|instance.assign? name}
70
+ instance.freeze if lock
71
+ else
72
+ raise "not yet finished"
73
+ end
74
+ end
75
+ end
76
+
77
+ # @return [Array<Symbol>]
78
+ def names
79
+ @names.dup
80
+ end
81
+
82
+ alias_method :members, :names
83
+ alias_method :keys, :names
84
+
85
+ def has_member?(name)
86
+ @names.include? keyable_for(name)
87
+ end
88
+
89
+ alias_method :member?, :has_member?
90
+ alias_method :has_key?, :has_member?
91
+ alias_method :key?, :has_key?
92
+
93
+ def has_conditions?(name)
94
+ raise NameError unless member? name
95
+
96
+ ! @conditions[name].nil?
97
+ end
98
+
99
+ alias_method :restrict?, :has_conditions?
100
+
101
+ # @param [Symbol, String] name
102
+ # @param [Object] value
103
+ # @param [Object] context - expected own instance
104
+ def sufficent?(name, value, context=self)
105
+ name = keyable_for name
106
+ raise NameError unless member? name
107
+
108
+ if restrict? name
109
+ conditions_for(name).any?{|condition|
110
+ case condition
111
+ when Proc
112
+ context.instance_exec value, &condition
113
+ when Method
114
+ condition.call value
115
+ else
116
+ condition === value
117
+ end
118
+ }
119
+ else
120
+ true
121
+ end
122
+ end
123
+
124
+ alias_method :accept?, :sufficent?
125
+
126
+ # @param [Symbol, String] name
127
+ def has_flavor?(name)
128
+ name = keyable_for name
129
+ raise NameError unless member? name
130
+
131
+ ! @flavors[name].nil?
132
+ end
133
+
134
+ # @param [Symbol, String] name
135
+ def has_default?(name)
136
+ name = keyable_for name
137
+ raise NameError unless member? name
138
+
139
+ @defaults.has_key? name
140
+ end
141
+
142
+ # @yield [name]
143
+ # @yieldparam [Symbol] name - sequential under defined
144
+ # @yieldreturn [self]
145
+ # @return [Enumerator]
146
+ def each_name(&block)
147
+ return to_enum(__method__) unless block_given?
148
+ @names.each(&block)
149
+ self
150
+ end
151
+
152
+ alias_method :each_member, :each_name
153
+ alias_method :each_key, :each_name
154
+
155
+ # @return [Integer]
156
+ def length
157
+ @names.length
158
+ end
159
+
160
+ alias_method :size, :length
161
+
162
+ # @param [Object] name
163
+ def cname?(name)
164
+ check_safety_naming(keyable_for name){|r|r}
165
+ rescue Exception
166
+ false
167
+ end
168
+
169
+ # @param [Object] condition
170
+ def conditionable?(condition)
171
+ case condition
172
+ when Proc, Method
173
+ condition.arity == 1
174
+ else
175
+ condition.respond_to? :===
176
+ end
177
+ end
178
+
179
+ def closed?
180
+ __stores__.any?(&:frozen?)
181
+ end
182
+
183
+ # @param [Symbol, String] name
184
+ def inference?(name)
185
+ name = keyable_for name
186
+ raise NameError unless member? name
187
+
188
+ @inferences.has_key? name
189
+ end
190
+
191
+ # @return [self]
192
+ def freeze
193
+ __stores__.each(&:freeze)
194
+ super
195
+ end
196
+
197
+ private
198
+
199
+ def inference
200
+ INFERENCE
201
+ end
202
+
203
+ def __stores__
204
+ [@names, @flavors, @defaults]
205
+ end
206
+
207
+ def initialize_copy(org)
208
+ @names, @flavors, @defaults = *__stores__.map(&:dup)
209
+ @conditions, @inferences, @protect_level = \
210
+ @conditions.dup, @inferences.dup, @protect_level
211
+ end
212
+
213
+ # @return [self]
214
+ def close
215
+ __stores__.each(&:freeze)
216
+ self
217
+ end
218
+
219
+ # @param [Symbol, String] name
220
+ # @return [Symbol]
221
+ def keyable_for(name)
222
+ case name
223
+ when Symbol, String
224
+ r = name.to_sym
225
+
226
+ if r.instance_of? Symbol
227
+ r
228
+ else
229
+ raise 'must not happen'
230
+ end
231
+ else
232
+ raise TypeError
233
+ end
234
+ end
235
+
236
+ # @param [Symbol] name
237
+ # @return [Symbol]
238
+ def estimate_naming(name)
239
+ if (instance_methods + private_instance_methods).include? name
240
+ return :conflict
241
+ end
242
+
243
+ return :no_ascii unless name.encoding.equal? Encoding::ASCII
244
+
245
+ case name
246
+ when /[\W]/, /\A[^a-zA-Z_]/, :''
247
+ :no_identifier
248
+ when /\Aeach/, /\A__\w*__\z/, /[!?]\z/
249
+ :bad_manners
250
+ when /\A[a-zA-Z_]\w*\z/
251
+ :strict
252
+ else
253
+ raise 'must not happen'
254
+ end
255
+ end
256
+
257
+ # @param [Symbol] name
258
+ def check_safety_naming(name)
259
+ estimation = estimate_naming name
260
+ risk = NAMING_RISKS[estimation]
261
+ plevels = PROTECT_LEVELS[@protect_level]
262
+ caution = "undesirable naming '#{name}', because #{estimation}"
263
+
264
+ r = case
265
+ when risk >= plevels[:error]
266
+ raise NameError, caution unless block_given?
267
+ false
268
+ when risk >= plevels[:warn]
269
+ warn caution unless block_given?
270
+ false
271
+ else
272
+ true
273
+ end
274
+
275
+ yield r if block_given?
276
+ end
277
+
278
+ # @macro [attach] protect_level
279
+ # @param [Symbol] level
280
+ def protect_level(level)
281
+ raise NameError unless PROTECT_LEVELS.has_key? level
282
+
283
+ @protect_level = level
284
+ nil
285
+ end
286
+
287
+ # @macro [attach] member
288
+ # @return [nil]
289
+ def define_member(name, *conditions, &flavor)
290
+ raise "already closed to add member in #{self}" if closed?
291
+ name = keyable_for name
292
+ raise ArgumentError, %Q!already exist name "#{name}"! if member? name
293
+ check_safety_naming name
294
+
295
+ @names << name
296
+ __getter__! name
297
+ __setter__! name, *conditions, &flavor
298
+ nil
299
+ end
300
+
301
+ alias_method :def_member, :define_member
302
+ alias_method :member, :define_member
303
+
304
+ # @macro [attach] define_members
305
+ # @return [nil]
306
+ def define_members(*names)
307
+ raise "already closed to add members in #{self}" if closed?
308
+ unless names.length >= 1
309
+ raise ArgumentError, 'wrong number of arguments (0 for 1+)'
310
+ end
311
+
312
+ names.each do |name|
313
+ define_member name
314
+ end
315
+
316
+ nil
317
+ end
318
+
319
+ alias_method :def_members, :define_members
320
+
321
+ def __getter__!(name)
322
+ define_method name do
323
+ __get__ name
324
+ end
325
+
326
+ nil
327
+ end
328
+
329
+ def __setter__!(name, *conditions, &flavor)
330
+ __set_conditions__! name, *conditions
331
+ __set_flavor__! name, &flavor if block_given?
332
+
333
+ define_method "#{name}=" do |value|
334
+ __set__ name, value
335
+ end
336
+
337
+ nil
338
+ end
339
+
340
+ def __set_conditions__!(name, *conditions)
341
+ if conditions.reject!{|c|INFERENCE.equal? c}
342
+ @inferences[name] = true
343
+ end
344
+
345
+ unless conditions.empty?
346
+ if conditions.all?{|c|conditionable? c}
347
+ @conditions[name] = conditions
348
+ else
349
+ raise TypeError, 'wrong object for condition'
350
+ end
351
+ end
352
+
353
+ nil
354
+ end
355
+
356
+ def __set_flavor__!(name, &flavor)
357
+ if (arity = flavor.arity) == 1
358
+ @flavors[name] = flavor
359
+ else
360
+ raise ArgumentError, "wrong number of block argument #{arity} for 1"
361
+ end
362
+
363
+ nil
364
+ end
365
+
366
+ def __found_family__!(name, our)
367
+ family = our.class
368
+
369
+ unless name.instance_of?(Symbol) and inference?(name) and member?(name)
370
+ raise 'must not happen'
371
+ end
372
+
373
+ raise ArgumentError unless conditionable? family
374
+
375
+ @conditions[name] = [family]
376
+ @inferences.delete name
377
+
378
+ nil
379
+ end
380
+
381
+ def get_conditions(name)
382
+ name = keyable_for name
383
+ raise NameError, 'no defined member' unless member? name
384
+
385
+ @conditions[name]
386
+ end
387
+
388
+ alias_method :conditions_for, :get_conditions
389
+
390
+ def get_flavor(name)
391
+ name = keyable_for name
392
+ raise NameError, 'no defined member' unless member? name
393
+
394
+ @flavors[name]
395
+ end
396
+
397
+ alias_method :flavor_for, :get_flavor
398
+
399
+ # @param [Symbol, String] name
400
+ def get_default_value(name)
401
+ name = keyable_for name
402
+ raise NameError, 'no defined member' unless member? name
403
+
404
+ @defaults[name]
405
+ end
406
+
407
+ alias_method :default_for, :get_default_value
408
+ public :default_for
409
+
410
+ # @macro [attach] default
411
+ # @return [nil]
412
+ def set_default_value(name, value)
413
+ raise "already closed to modify member attributes in #{self}" if closed?
414
+ name = keyable_for name
415
+ raise NameError, 'no defined member' unless member? name
416
+ raise ConditionError unless accept? name, value
417
+
418
+ @defaults[name] = value
419
+ nil
420
+ end
421
+
422
+ alias_method :default, :set_default_value
423
+
424
+
425
+ end
426
+
427
+
428
+ end; end
@@ -0,0 +1,4 @@
1
+ class Striuct
2
+ VERSION = '0.0.11.1'.freeze
3
+ Version = VERSION
4
+ end
data/lib/striuct.rb ADDED
@@ -0,0 +1,4 @@
1
+ require_relative 'striuct/version'
2
+ require_relative 'striuct/core'
3
+
4
+ StrictStruct = Striuct
@@ -0,0 +1,3 @@
1
+ require 'stringio'
2
+ require 'test/unit'
3
+ require File.dirname(__FILE__) + '/../lib/striuct'
@@ -0,0 +1,4 @@
1
+ require 'stringio'
2
+ require 'test/unit'
3
+ require File.dirname(__FILE__) + '/../lib/striuct'
4
+ require File.dirname(__FILE__) + '/../lib/striuct/import'