enums 1.2.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Manifest.txt +3 -3
- data/README.md +190 -0
- data/lib/enums.rb +12 -9
- data/lib/enums/enum.rb +4 -3
- data/lib/enums/enum_builder.rb +17 -7
- data/lib/enums/flag.rb +147 -0
- data/lib/enums/{flags_builder.rb → flag_builder.rb} +22 -16
- data/lib/enums/version.rb +1 -1
- data/test/test_enum.rb +4 -0
- data/test/test_flag.rb +89 -0
- metadata +5 -5
- data/lib/enums/flags.rb +0 -193
- data/test/test_flags.rb +0 -68
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 21d675f9ace380c191b6ee58c484ab192dbdb440
|
4
|
+
data.tar.gz: 42c0d89f1a150c48a432b3c2d1b1fbbe00ac1aa0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f990e4ab5fcc741b1226a3f4eb0d8f9e53f8b2b446d1c8175a4f8bd3464d6fcde42a59cc966735bd2216abc4ec397fd3f043c89254815209c7c61fd0464459ba
|
7
|
+
data.tar.gz: 22ff3b44aea19efd814a45f3ad46184bfaf9df24bbb3e23bb306b91b9f068ff68381de7404293ecb47d5e8b8e6e8d669d92f184d2adcd36ff372740b2c35ce57
|
data/Manifest.txt
CHANGED
@@ -6,9 +6,9 @@ Rakefile
|
|
6
6
|
lib/enums.rb
|
7
7
|
lib/enums/enum.rb
|
8
8
|
lib/enums/enum_builder.rb
|
9
|
-
lib/enums/
|
10
|
-
lib/enums/
|
9
|
+
lib/enums/flag.rb
|
10
|
+
lib/enums/flag_builder.rb
|
11
11
|
lib/enums/version.rb
|
12
12
|
test/helper.rb
|
13
13
|
test/test_enum.rb
|
14
|
-
test/
|
14
|
+
test/test_flag.rb
|
data/README.md
CHANGED
@@ -49,6 +49,11 @@ Enum.new( :Color, :red, :green, :blue )
|
|
49
49
|
enum :Color, :red, :green, :blue
|
50
50
|
# or
|
51
51
|
enum :Color, [:red, :green, :blue]
|
52
|
+
# or
|
53
|
+
enum :Color, { red: 0,
|
54
|
+
green: 1,
|
55
|
+
blue: 2 }
|
56
|
+
|
52
57
|
```
|
53
58
|
|
54
59
|
|
@@ -143,6 +148,10 @@ Enum.new( :State, :fundraising, :expired_refund, :successful )
|
|
143
148
|
enum :State, :fundraising, :expired_refund, :successful
|
144
149
|
# or
|
145
150
|
enum :State, [:fundraising, :expired_refund, :successful]
|
151
|
+
# or
|
152
|
+
enum :State, { fundraising: 0,
|
153
|
+
expired_refund: 1,
|
154
|
+
successful: 2 }
|
146
155
|
|
147
156
|
|
148
157
|
State.values #=> [0, 1, 2]
|
@@ -189,6 +198,187 @@ and so on.
|
|
189
198
|
|
190
199
|
|
191
200
|
|
201
|
+
|
202
|
+
### What about enums with flags and bitwise-operators for set (`|`) / unset (`&~`) / toggle (`^`)?
|
203
|
+
|
204
|
+
Use the `flags` option or the `Flag` class. Example:
|
205
|
+
|
206
|
+
``` ruby
|
207
|
+
Flag.new( :FileAttrib, :read_only, :hidden, :system, :archive )
|
208
|
+
# -or -
|
209
|
+
enum :FileAttrib, [:read_only, :hidden, :system, :archive], flags: true
|
210
|
+
# -or -
|
211
|
+
enum :FileAttrib, :read_only, :hidden, :system, :archive, flags: true
|
212
|
+
# -or -
|
213
|
+
enum :FileAttrib, { read_only: 1<<0, # 2^0 = 1 = 0b00000001
|
214
|
+
hidden: 1<<1, # 2^1 = 2 = 0b00000010
|
215
|
+
system: 1<<2, # 2^2 = 4 = 0b00000100
|
216
|
+
archive: 1<<5, # 2^5 = 32 = 0b00100000
|
217
|
+
},
|
218
|
+
flags: true
|
219
|
+
```
|
220
|
+
|
221
|
+
(Auto-)builds a class and code like:
|
222
|
+
|
223
|
+
``` ruby
|
224
|
+
class Flag
|
225
|
+
def initialize( key, value )
|
226
|
+
@key = key
|
227
|
+
@value = value
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
|
232
|
+
class FileAttrib < Flag
|
233
|
+
|
234
|
+
READ_ONLY = new( :read_only, 1<<0 )
|
235
|
+
HIDDEN = new( :hidden, 1<<1 )
|
236
|
+
SYSTEM = new( :system, 1<<2 )
|
237
|
+
ARCHIVE = new( :archive, 1<<3 )
|
238
|
+
|
239
|
+
def self.read_only() READ_ONLY; end
|
240
|
+
def self.hidden() HIDDEN; end
|
241
|
+
def self.system() SYSTEM; end
|
242
|
+
def self.archive() ARCHIVE; end
|
243
|
+
|
244
|
+
def self.values() [1<<0,1<<1,1<<2,1<<3]; end
|
245
|
+
def self.keys() [:read_only, :hidden, :system, :archive]; end
|
246
|
+
def self.members() [READ_ONLY, HIDDEN, SYSTEM, ARCHIVE]; end
|
247
|
+
|
248
|
+
def self.zero() @zero ||= new(0); end
|
249
|
+
|
250
|
+
def self.key( key )
|
251
|
+
@hash_by_key ||= Hash[ keys.zip( members ) ]
|
252
|
+
@hash_by_key[ key ]
|
253
|
+
end
|
254
|
+
def self.[]( key ) self.key( key ); end
|
255
|
+
|
256
|
+
def read_only?() member?( READ_ONLY ); end
|
257
|
+
def hidden?() member?( HIDDEN ); end
|
258
|
+
def system?() member?( SYSTEM ); end
|
259
|
+
def archive?() member?( ARCHIVE ); end
|
260
|
+
|
261
|
+
def member?( other ) @value & other.value == other.value; end
|
262
|
+
|
263
|
+
def bitwise_or( other )
|
264
|
+
self.class.new( @value | other.value )
|
265
|
+
end
|
266
|
+
alias_method :|, :bitwise_or
|
267
|
+
alias_method :set, :bitwise_or
|
268
|
+
alias_method :flag, :bitwise_or
|
269
|
+
|
270
|
+
def bitwise_and( other )
|
271
|
+
self.class.new( @value & other.value )
|
272
|
+
end
|
273
|
+
alias_method :&, :bitwise_and
|
274
|
+
|
275
|
+
def unset( other )
|
276
|
+
self.class.new( @value & ~other.value )
|
277
|
+
end
|
278
|
+
alias_method :unflag, :unset
|
279
|
+
|
280
|
+
def bitwise_xor( other )
|
281
|
+
self.class.new( @value ^ other.value )
|
282
|
+
end
|
283
|
+
alias_method :^, :bitwise_xor
|
284
|
+
alias_method :toggle, :bitwise_xor
|
285
|
+
|
286
|
+
# ...
|
287
|
+
|
288
|
+
def initialize( *args )
|
289
|
+
# ...
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
|
294
|
+
def FileAttrib( *args )
|
295
|
+
FileAttrib.new( *args )
|
296
|
+
end
|
297
|
+
```
|
298
|
+
|
299
|
+
Use like:
|
300
|
+
|
301
|
+
``` ruby
|
302
|
+
FileAttrib.values #=> [1, 2, 4, 8]
|
303
|
+
FileAttrib.keys #=> [:read_only, :hidden, :system, :archive]
|
304
|
+
|
305
|
+
FileAttrib.read_only #=> <FileAttrib @key=:read_only, @value=1>
|
306
|
+
FileAttrib::READ_ONLY #=> <FileAttrib @key=:read_only, @value=1>
|
307
|
+
FileAttribs[:read_only] #=> <FileAttrib @key=:read_only, @value=1>
|
308
|
+
|
309
|
+
FileAttrib(0) #=> <FileAttrib @key=:0000, @value=0>
|
310
|
+
FileAttrib.read_only | FileAttrib.hidden #=> <FileAttrib @key=:0011, @value=3>
|
311
|
+
# -or-
|
312
|
+
FileAttrib.new( FileAttrib.read_only | FileAttrib.hidden )
|
313
|
+
FileAttrib.new( FileAttrib::READ_ONLY | FileAttrib::HIDDEN )
|
314
|
+
FileAttrib.new( :read_only, :hidden )
|
315
|
+
# -or-
|
316
|
+
FileAttrib( FileAttrib.read_only | FileAttrib.hidden )
|
317
|
+
FileAttrib( FileAttrib::READ_ONLY | FileAttrib::HIDDEN )
|
318
|
+
FileAttrib( :read_only, :hidden )
|
319
|
+
#=> <FileAttrib @key=:0011, @value=3>
|
320
|
+
|
321
|
+
attrib = FileAttrib.new #=> <FileAttrib @key=:0000, @value=0>
|
322
|
+
attrib |= FileAttrib.read_only #=> <FileAttrib @key=:0001, @value=1>
|
323
|
+
attrib.read_only? #=> true
|
324
|
+
# -or-
|
325
|
+
attrib.member?( FileAttrib.read_only ) #=> true
|
326
|
+
attrib.member?( FileAttrib.READ_ONLY ) #=> true
|
327
|
+
attrib.member?( :read_only ) #=> true
|
328
|
+
attrib & FileAttrib.read_only == FileAttrib.read_only #=> true
|
329
|
+
|
330
|
+
attrib ^= FileAttrib.read_only #=> <FileAttrib @key=:0000, @value=0>
|
331
|
+
attrib.read_only? #=> false
|
332
|
+
attrib ^= FileAttrib.read_only #=> <FileAttrib @key=:0001, @value=1>
|
333
|
+
attrib.read_only? #=> true
|
334
|
+
|
335
|
+
attrib &= ~FileAttrib.read_only #=> <FileAttrib @key=:0000, @value=0>
|
336
|
+
attrib.read_only? #=> false
|
337
|
+
|
338
|
+
attrib.is_a? Flag #=> true
|
339
|
+
attrib.is_a? FileAttrib #=> true
|
340
|
+
# ...
|
341
|
+
```
|
342
|
+
|
343
|
+
and so on.
|
344
|
+
|
345
|
+
|
346
|
+
### What about enums for (algebraic) union data types with variants?
|
347
|
+
|
348
|
+
Yes, yes, yes. Use the `Union` class or the `data` helper from the safedata library.
|
349
|
+
Note, now you can go "wild" and use strings, arrays or really anything or nothing (that is, unit types) for your values. Example:
|
350
|
+
|
351
|
+
``` ruby
|
352
|
+
data :Color, :Red, [1],
|
353
|
+
:Green, [2],
|
354
|
+
:Blue, [3]
|
355
|
+
|
356
|
+
# -or-
|
357
|
+
|
358
|
+
data :Color, :Red, ['red'],
|
359
|
+
:Green, ['green'],
|
360
|
+
:Blue, ['blue']
|
361
|
+
|
362
|
+
# -or-
|
363
|
+
|
364
|
+
data :Color, :Red, [1,'red'],
|
365
|
+
:Green, [2, 'green'],
|
366
|
+
:Blue, [3, 'blue']
|
367
|
+
|
368
|
+
# -or-
|
369
|
+
|
370
|
+
data :Color, :Red, [255, 0, 0],
|
371
|
+
:Green, [0, 255, 0],
|
372
|
+
:Blue, [0, 0, 255],
|
373
|
+
:Other, [:r, :g, :b]
|
374
|
+
```
|
375
|
+
|
376
|
+
and so on and so forth.
|
377
|
+
See the [safedata library documentation for more »](https://github.com/s6ruby/safedata)
|
378
|
+
|
379
|
+
|
380
|
+
|
381
|
+
|
192
382
|
## More "Real World" Enum Samples
|
193
383
|
|
194
384
|
- [The "Red Paper" about sruby](https://github.com/s6ruby/redpaper) - Small, Smart, Secure, Safe, Solid & Sound (S6) Ruby - The Ruby Programming Language for Contract / Transaction Scripts on the Blockchain World Computer - Yes, It's Just Ruby
|
data/lib/enums.rb
CHANGED
@@ -29,15 +29,20 @@ end # module Safe
|
|
29
29
|
|
30
30
|
require 'enums/enum'
|
31
31
|
require 'enums/enum_builder'
|
32
|
-
require 'enums/
|
33
|
-
require 'enums/
|
32
|
+
require 'enums/flag'
|
33
|
+
require 'enums/flag_builder'
|
34
34
|
|
35
35
|
|
36
36
|
|
37
37
|
|
38
38
|
module Safe
|
39
|
+
|
40
|
+
## note make Flags an alias of Flag
|
41
|
+
Flags = Flag
|
42
|
+
|
43
|
+
|
39
44
|
module SafeHelper
|
40
|
-
def enum( class_name, *args, flags: false, options: {} )
|
45
|
+
def enum( class_name, *args, flags: false, options: {}, **kwargs )
|
41
46
|
|
42
47
|
## note: allow "standalone" option flags or
|
43
48
|
## option hash
|
@@ -50,16 +55,14 @@ module SafeHelper
|
|
50
55
|
# enum :Color, :red, :green, :blue
|
51
56
|
# -or-
|
52
57
|
# enum :Color, [:red, :green, :blue]
|
53
|
-
if args[0].is_a?( Array )
|
54
|
-
|
55
|
-
else
|
56
|
-
keys = args
|
58
|
+
if args.size > 0 && args[0].is_a?( Array )
|
59
|
+
args = args[0]
|
57
60
|
end
|
58
61
|
|
59
62
|
if options[:flags]
|
60
|
-
|
63
|
+
Flag.new( class_name, *args, **kwargs )
|
61
64
|
else
|
62
|
-
Enum.new( class_name, *
|
65
|
+
Enum.new( class_name, *args, **kwargs )
|
63
66
|
end
|
64
67
|
end
|
65
68
|
end # module SafeHelper
|
data/lib/enums/enum.rb
CHANGED
@@ -47,6 +47,10 @@ class Enum
|
|
47
47
|
end
|
48
48
|
|
49
49
|
|
50
|
+
def self.zero() members[0]; end
|
51
|
+
def zero?() self == self.class.zero; end
|
52
|
+
|
53
|
+
|
50
54
|
def self.size() keys.size; end
|
51
55
|
def self.length() size; end ## alias (as is the ruby tradition)
|
52
56
|
|
@@ -58,8 +62,5 @@ class Enum
|
|
58
62
|
## note: will ALWAYS look-up by (member) index and NOT by value (integer number value might be different!!)
|
59
63
|
members[ arg ]
|
60
64
|
end
|
61
|
-
|
62
|
-
def self.zero() members[0]; end
|
63
|
-
def zero?() self == self.class.zero; end
|
64
65
|
end # class Enum
|
65
66
|
end # module Safe
|
data/lib/enums/enum_builder.rb
CHANGED
@@ -7,13 +7,23 @@ class Enum
|
|
7
7
|
|
8
8
|
###################
|
9
9
|
## meta-programming "macro" - build class (on the fly)
|
10
|
-
def self.build_class( class_name, *
|
10
|
+
def self.build_class( class_name, *args, **kwargs )
|
11
|
+
|
12
|
+
if args.size > 0
|
13
|
+
keys = args
|
14
|
+
values = (0...keys.size).to_a # note: use ... (exclusive) range
|
15
|
+
e = Hash[ keys.zip( values ) ]
|
16
|
+
else
|
17
|
+
## assume kwargs
|
18
|
+
e = kwargs
|
19
|
+
end
|
20
|
+
|
11
21
|
|
12
22
|
## todo/fix:
|
13
23
|
## check class name MUST start with uppercase letter
|
14
24
|
|
15
25
|
## check if all keys are symbols and follow the ruby id(entifier) naming rules
|
16
|
-
keys.each do |key|
|
26
|
+
e.keys.each do |key|
|
17
27
|
if key.is_a?( Symbol ) && key =~ /\A[a-z][a-zA-Z0-9_]*\z/
|
18
28
|
else
|
19
29
|
raise ArgumentError.new( "[Enum] arguments to Enum.new must be all symbols following the ruby id naming rules; >#{key}< failed" )
|
@@ -23,13 +33,13 @@ def self.build_class( class_name, *keys )
|
|
23
33
|
klass = Class.new( Enum )
|
24
34
|
|
25
35
|
## add self.new too - note: call/forward to "old" orginal self.new of Event (base) class
|
26
|
-
klass.define_singleton_method( :new ) do |*
|
27
|
-
old_new( *
|
36
|
+
klass.define_singleton_method( :new ) do |*new_args|
|
37
|
+
old_new( *new_args )
|
28
38
|
end
|
29
39
|
|
30
|
-
|
40
|
+
e.each do |key,value|
|
31
41
|
klass.class_eval( <<RUBY )
|
32
|
-
#{key.upcase} = new( :#{key}, #{
|
42
|
+
#{key.upcase} = new( :#{key}, #{value} )
|
33
43
|
|
34
44
|
def #{key}?
|
35
45
|
self == #{key.upcase}
|
@@ -43,7 +53,7 @@ RUBY
|
|
43
53
|
|
44
54
|
klass.class_eval( <<RUBY )
|
45
55
|
def self.members
|
46
|
-
@members ||= [#{keys.map {|key|key.upcase}.join(',')}].freeze
|
56
|
+
@members ||= [#{e.keys.map {|key|key.upcase}.join(',')}].freeze
|
47
57
|
end
|
48
58
|
RUBY
|
49
59
|
|
data/lib/enums/flag.rb
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
###############################
|
4
|
+
## base class for flag/flags
|
5
|
+
|
6
|
+
|
7
|
+
|
8
|
+
module Safe
|
9
|
+
class Flag
|
10
|
+
attr_reader :key
|
11
|
+
attr_reader :value
|
12
|
+
|
13
|
+
def initialize( *args )
|
14
|
+
fmt = "%08b"
|
15
|
+
|
16
|
+
if args.size == 0
|
17
|
+
@value = 0 ## same as new(0)
|
18
|
+
@key = :"#{fmt % @value}" ## use :none for 0 key - why? why not?
|
19
|
+
elsif args.size == 1 && args[0].is_a?(Integer)
|
20
|
+
@value = args[0]
|
21
|
+
@key = :"#{fmt % @value}" ## todo: lookup if value is a known flag (with key) - why? why not?
|
22
|
+
elsif args.size == 2 && args[0].is_a?(Symbol) && args[1].is_a?(Integer)
|
23
|
+
@key = args[0]
|
24
|
+
@value = args[1]
|
25
|
+
else
|
26
|
+
## assume flag object or symbols
|
27
|
+
@value = 0
|
28
|
+
args.each do |arg|
|
29
|
+
flag = _typecast_flag!( arg )
|
30
|
+
@value |= flag.value
|
31
|
+
end
|
32
|
+
@key = :"#{fmt % @value}" ## todo: lookup if value is a known flag (with key) - why? why not?
|
33
|
+
end
|
34
|
+
self.freeze ## make "immutable" - should be a value object like an integer number!!!
|
35
|
+
self # return self for chaining
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
def _typecheck_flag!( o )
|
40
|
+
if self.class == o.class
|
41
|
+
o
|
42
|
+
else
|
43
|
+
raise TypeError.new( "[Flag] flag >#{self.class.name}< type expected; got >#{o.class.inspect}<" )
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def _typecast_flag!( o )
|
48
|
+
if o.is_a? Symbol ## auto-convert symbol to flag
|
49
|
+
o = self.class.key( o ) ## lookup symbol in "parent" flags class
|
50
|
+
end
|
51
|
+
_typecheck_flag!( o )
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
def ==( other )
|
56
|
+
if self.class == other.class
|
57
|
+
@value == other.value
|
58
|
+
elsif other.is_a?( Integer ) ## note: also allow compare by "plain" integer
|
59
|
+
@value == other
|
60
|
+
else
|
61
|
+
false
|
62
|
+
end
|
63
|
+
end
|
64
|
+
alias_method :eql?, :==
|
65
|
+
|
66
|
+
|
67
|
+
def member?( other ) _member?(_typecast_flag!( other )); end
|
68
|
+
def _member?( other ) @value & other.value == other.value; end
|
69
|
+
|
70
|
+
|
71
|
+
def bitwise_or( other )
|
72
|
+
self.class.new( @value | _typecheck_flag!( other ).value )
|
73
|
+
end
|
74
|
+
alias_method :|, :bitwise_or
|
75
|
+
|
76
|
+
def bitwise_and( other )
|
77
|
+
self.class.new( @value & _typecheck_flag!( other ).value )
|
78
|
+
end
|
79
|
+
alias_method :&, :bitwise_and
|
80
|
+
|
81
|
+
def bitwise_xor( other )
|
82
|
+
self.class.new( @value ^ _typecheck_flag!( other ).value )
|
83
|
+
end
|
84
|
+
alias_method :^, :bitwise_xor
|
85
|
+
|
86
|
+
def bitwise_inverse
|
87
|
+
self.class.new( ~@value )
|
88
|
+
end
|
89
|
+
alias_method :~, :bitwise_inverse
|
90
|
+
|
91
|
+
|
92
|
+
def set( other ) ## note: typecast also allows symbol e.g (:read_only, etc.)
|
93
|
+
self.class.new( @value | _typecast_flag!( other ).value )
|
94
|
+
end
|
95
|
+
alias_method :flag, :set
|
96
|
+
|
97
|
+
def unset( other )
|
98
|
+
self.class.new( @value & ~_typecast_flag!( other ).value )
|
99
|
+
end
|
100
|
+
alias_method :unflag, :unset
|
101
|
+
|
102
|
+
def toggle( other )
|
103
|
+
self.class.new( @value ^ _typecast_flag!( other ).value )
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
|
108
|
+
|
109
|
+
def self.keys()
|
110
|
+
# note: does NOT include none - why? why not?
|
111
|
+
@keys ||= members.map { |member| member.key }.freeze
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.key( key )
|
115
|
+
@hash ||= Hash[ keys.zip( members ) ].freeze
|
116
|
+
@hash[key]
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.[]( key ) ## convenience alias for key
|
120
|
+
self.key( key )
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
def self.values()
|
125
|
+
# note: does NOT include none - why? why not?
|
126
|
+
@values ||= members.map { |member| member.value }.freeze
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
def self.zero() @zero ||= new(0); end
|
131
|
+
def zero?() @value == 0; end
|
132
|
+
|
133
|
+
|
134
|
+
def self.convert( *args )
|
135
|
+
new( *args )
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
## add size|length too why? why not?
|
140
|
+
## add value() lookup?
|
141
|
+
## not for now - why? combined values are undefined!! what to return??
|
142
|
+
|
143
|
+
## add to_i, to_int - why? why not?
|
144
|
+
## def to_i() @value; end
|
145
|
+
## def to_int() @value; end
|
146
|
+
end # class Flag
|
147
|
+
end # module Safe
|
@@ -1,37 +1,43 @@
|
|
1
1
|
module Safe
|
2
|
-
class
|
2
|
+
class Flag
|
3
3
|
|
4
4
|
|
5
5
|
###################
|
6
6
|
## meta-programming "macro" - build class (on the fly)
|
7
|
-
def self.build_class( class_name, *
|
7
|
+
def self.build_class( class_name, *args, **kwargs )
|
8
|
+
|
9
|
+
if args.size > 0
|
10
|
+
keys = args
|
11
|
+
values = (0...keys.size).to_a # note: use ... (exclusive) range
|
12
|
+
values = values.map { |value| 1<<value } ## use power of twos (e.g. 2^0, 2^1, etc.)
|
13
|
+
f = Hash[ keys.zip( values ) ]
|
14
|
+
else
|
15
|
+
## assume kwargs
|
16
|
+
f = kwargs
|
17
|
+
end
|
18
|
+
|
8
19
|
|
9
20
|
## todo/fix:
|
10
21
|
## check class name MUST start with uppercase letter
|
11
22
|
|
12
23
|
## check if all keys are symbols and follow the ruby id(entifier) naming rules
|
13
|
-
keys.each do |key|
|
24
|
+
f.keys.each do |key|
|
14
25
|
if key.is_a?( Symbol ) && key =~ /\A[a-z][a-zA-Z0-9_]*\z/
|
15
26
|
else
|
16
|
-
raise ArgumentError.new( "[
|
27
|
+
raise ArgumentError.new( "[Flag] arguments to Flag.new must be all symbols following the ruby id naming rules; >#{key}< failed" )
|
17
28
|
end
|
18
29
|
end
|
19
30
|
|
20
|
-
klass = Class.new(
|
31
|
+
klass = Class.new( Flag )
|
21
32
|
## add self.new too - note: call/forward to "old" orginal self.new of Event (base) class
|
22
|
-
klass.define_singleton_method( :new ) do |*
|
23
|
-
old_new( *
|
33
|
+
klass.define_singleton_method( :new ) do |*new_args|
|
34
|
+
old_new( *new_args )
|
24
35
|
end
|
25
36
|
|
26
37
|
|
27
|
-
|
28
|
-
klass.class_eval( <<RUBY )
|
29
|
-
class Flag < Safe::Flag; end
|
30
|
-
RUBY
|
31
|
-
|
32
|
-
keys.each_with_index do |key,index|
|
38
|
+
f.each do |key,value|
|
33
39
|
klass.class_eval( <<RUBY )
|
34
|
-
#{key.upcase} =
|
40
|
+
#{key.upcase} = new( :#{key}, #{value} )
|
35
41
|
|
36
42
|
def self.#{key}
|
37
43
|
#{key.upcase}
|
@@ -45,7 +51,7 @@ RUBY
|
|
45
51
|
|
46
52
|
klass.class_eval( <<RUBY )
|
47
53
|
def self.members
|
48
|
-
@members ||= [#{keys.map {|key|key.upcase}.join(',')}].freeze
|
54
|
+
@members ||= [#{f.keys.map {|key|key.upcase}.join(',')}].freeze
|
49
55
|
end
|
50
56
|
RUBY
|
51
57
|
|
@@ -73,5 +79,5 @@ class << self
|
|
73
79
|
alias_method :new, :build_class # replace original version with build_class
|
74
80
|
end
|
75
81
|
|
76
|
-
end # class
|
82
|
+
end # class Flag
|
77
83
|
end # module Safe
|
data/lib/enums/version.rb
CHANGED
data/test/test_enum.rb
CHANGED
@@ -32,6 +32,10 @@ class TestEnum < MiniTest::Test
|
|
32
32
|
pp Color
|
33
33
|
pp Color(0)
|
34
34
|
|
35
|
+
enum :Roman, {i: 1, iv: 4, v: 5, ix: 9, x: 10 }
|
36
|
+
pp Roman
|
37
|
+
pp Roman(0) ## first member by index
|
38
|
+
|
35
39
|
|
36
40
|
def test_state
|
37
41
|
assert_equal [:FUNDRAISING, :EXPIRED_REFUND, :SUCCESSFUL], State.constants
|
data/test/test_flag.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
##
|
4
|
+
# to run use
|
5
|
+
# ruby -I ./lib -I ./test test/test_flag.rb
|
6
|
+
|
7
|
+
|
8
|
+
require 'helper'
|
9
|
+
|
10
|
+
|
11
|
+
module Safe
|
12
|
+
## Flags.new( :FileAttrib, :read_only, :hidden, :system, :archive )
|
13
|
+
enum :FileAttrib, [:read_only, :hidden, :system, :archive], flags: true
|
14
|
+
|
15
|
+
|
16
|
+
pp FileAttrib
|
17
|
+
pp FileAttrib(0)
|
18
|
+
|
19
|
+
puts "Safe.constants:"
|
20
|
+
pp Safe.constants #=> [:SafeHelper, :Enum, :State, :Color]
|
21
|
+
puts "Flag.constants:"
|
22
|
+
pp Flag.constants #=> []
|
23
|
+
puts "FileAttrib.constants:"
|
24
|
+
pp FileAttrib.constants #=> []
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
class TestFlag < MiniTest::Test
|
29
|
+
|
30
|
+
include Safe ## make all enums (and "convenience" converters) global
|
31
|
+
|
32
|
+
|
33
|
+
def test_attrib
|
34
|
+
|
35
|
+
assert_equal [1<<0, 1<<1, 1<<2, 1<<3], FileAttrib.values
|
36
|
+
assert_equal [:read_only, :hidden, :system, :archive], FileAttrib.keys
|
37
|
+
|
38
|
+
assert_equal FileAttrib.read_only, FileAttrib::READ_ONLY
|
39
|
+
assert_equal FileAttrib.hidden, FileAttrib::HIDDEN
|
40
|
+
assert_equal FileAttrib.system, FileAttrib::SYSTEM
|
41
|
+
assert_equal FileAttrib.archive, FileAttrib::ARCHIVE
|
42
|
+
|
43
|
+
assert_equal FileAttrib.read_only, FileAttrib[:read_only]
|
44
|
+
assert_equal FileAttrib.hidden, FileAttrib[:hidden]
|
45
|
+
assert_equal FileAttrib.system, FileAttrib[:system]
|
46
|
+
assert_equal FileAttrib.archive, FileAttrib[:archive]
|
47
|
+
|
48
|
+
assert_equal 1<<0, FileAttrib.read_only.value
|
49
|
+
assert_equal :read_only, FileAttrib.read_only.key
|
50
|
+
assert_equal true, FileAttrib.read_only.is_a?( Flag )
|
51
|
+
assert_equal true, FileAttrib.read_only.is_a?( FileAttrib )
|
52
|
+
|
53
|
+
pp FileAttrib.members
|
54
|
+
assert_equal :read_only, FileAttrib.members[0].key
|
55
|
+
assert_equal 1<<0, FileAttrib.members[0].value
|
56
|
+
assert_equal :hidden, FileAttrib.members[1].key
|
57
|
+
assert_equal 1<<1, FileAttrib.members[1].value
|
58
|
+
|
59
|
+
assert_equal 1<<0, FileAttrib.read_only.value
|
60
|
+
assert_equal :read_only, FileAttrib.read_only.key
|
61
|
+
assert_equal 1<<0, FileAttrib::READ_ONLY.value
|
62
|
+
assert_equal :read_only, FileAttrib::READ_ONLY.key
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_text_style
|
66
|
+
Flag.new( :TextStyle, :bold, :italic, :underline )
|
67
|
+
|
68
|
+
assert_equal [1<<0, 1<<1, 1<<2], TextStyle.values
|
69
|
+
assert_equal [:bold, :italic, :underline], TextStyle.keys
|
70
|
+
|
71
|
+
style = TextStyle(0)
|
72
|
+
assert_equal true, style == 0
|
73
|
+
style |= TextStyle.bold
|
74
|
+
assert_equal true, style.member?( :bold )
|
75
|
+
assert_equal true, style.member?( TextStyle.bold )
|
76
|
+
assert_equal true, style & TextStyle.bold == TextStyle.bold
|
77
|
+
assert_equal true, style & TextStyle.bold != 0
|
78
|
+
|
79
|
+
assert_equal false, style.member?( :italic )
|
80
|
+
assert_equal false, style.member?( TextStyle.italic )
|
81
|
+
style |= TextStyle.italic
|
82
|
+
assert_equal true, style.member?( :italic )
|
83
|
+
assert_equal true, style.member?( TextStyle.italic )
|
84
|
+
assert_equal true, style & TextStyle.italic == TextStyle.italic
|
85
|
+
assert_equal true, style & TextStyle.italic != 0
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
end # class TestFlag
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: enums
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gerald Bauer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-03-
|
11
|
+
date: 2019-03-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rdoc
|
@@ -57,12 +57,12 @@ files:
|
|
57
57
|
- lib/enums.rb
|
58
58
|
- lib/enums/enum.rb
|
59
59
|
- lib/enums/enum_builder.rb
|
60
|
-
- lib/enums/
|
61
|
-
- lib/enums/
|
60
|
+
- lib/enums/flag.rb
|
61
|
+
- lib/enums/flag_builder.rb
|
62
62
|
- lib/enums/version.rb
|
63
63
|
- test/helper.rb
|
64
64
|
- test/test_enum.rb
|
65
|
-
- test/
|
65
|
+
- test/test_flag.rb
|
66
66
|
homepage: https://github.com/s6ruby/enums
|
67
67
|
licenses:
|
68
68
|
- Public Domain
|
data/lib/enums/flags.rb
DELETED
@@ -1,193 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
###############################
|
4
|
-
## base class for flag/flags
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
module Safe
|
9
|
-
class Flag
|
10
|
-
|
11
|
-
attr_reader :key
|
12
|
-
attr_reader :value
|
13
|
-
|
14
|
-
def initialize( key, value )
|
15
|
-
@key = key
|
16
|
-
@value = value
|
17
|
-
self.freeze ## make "immutable"
|
18
|
-
self
|
19
|
-
end
|
20
|
-
|
21
|
-
|
22
|
-
def self._typecheck!( o )
|
23
|
-
if self == o.class
|
24
|
-
o
|
25
|
-
else
|
26
|
-
raise TypeError.new( "[Flag] flag >#{name}< type expected; got >#{o.class.inspect}<" )
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def self._value!( o )
|
31
|
-
if o.is_a? Integer
|
32
|
-
o
|
33
|
-
else
|
34
|
-
_typecheck!( o )
|
35
|
-
o.value
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
def bitwise_or( other ) ## bitwise-or
|
40
|
-
## note: returns "plain" integer
|
41
|
-
@value | self.class._value!( other )
|
42
|
-
end
|
43
|
-
alias_method :|, :bitwise_or
|
44
|
-
|
45
|
-
def bitwise_inverse() ## bitwise-inverse
|
46
|
-
## note: returns "plain" integer
|
47
|
-
~@value
|
48
|
-
end
|
49
|
-
alias_method :~, :bitwise_inverse
|
50
|
-
|
51
|
-
|
52
|
-
def coerce( other )
|
53
|
-
puts "[Flag] coerce( self= >#{self.inspect}<, other= >#{other.inspect}< #{other.class.name} )"
|
54
|
-
if other.is_a?( Integer )
|
55
|
-
[other, @value]
|
56
|
-
else
|
57
|
-
raise TypeError.new( "[Flag] coerce - wrong type >#{other.inspect}< #{other.class.name} - Integer number expected" )
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end # class Flag
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
class Flags
|
65
|
-
attr_reader :value
|
66
|
-
|
67
|
-
def initialize( *args )
|
68
|
-
if args.size == 0
|
69
|
-
@value = 0 ## same as new(0)
|
70
|
-
elsif args.size == 1 && args[0].is_a?(Integer)
|
71
|
-
@value = args[0]
|
72
|
-
else
|
73
|
-
## assume flag object or symbols
|
74
|
-
@value = 0
|
75
|
-
args.each do |arg|
|
76
|
-
flag = _typecast_flag!( arg )
|
77
|
-
@value |= flag.value
|
78
|
-
end
|
79
|
-
end
|
80
|
-
self.freeze ## make "immutable" - should be a value object like an integer number!!!
|
81
|
-
self # return self for chaining
|
82
|
-
end
|
83
|
-
|
84
|
-
|
85
|
-
def _value_for_flag!( o )
|
86
|
-
self.class::Flag._value!( o )
|
87
|
-
end
|
88
|
-
|
89
|
-
def _typecheck_flag!( o )
|
90
|
-
self.class::Flag._typecheck!( o )
|
91
|
-
end
|
92
|
-
|
93
|
-
def _typecast_flag!( o )
|
94
|
-
if o.is_a? Symbol ## auto-convert symbol to flag
|
95
|
-
o = self.class.key( o ) ## lookup symbol in "parent" flags class
|
96
|
-
end
|
97
|
-
_typecheck_flag!( o )
|
98
|
-
end
|
99
|
-
|
100
|
-
|
101
|
-
def member?( other ) _member?(_typecast_flag!( other )); end
|
102
|
-
def _member?( other ) @value & other.value != 0; end
|
103
|
-
|
104
|
-
|
105
|
-
def bitwise_or( other )
|
106
|
-
_unsafe_bitwise_or( _value_for_flag!( other ) )
|
107
|
-
end
|
108
|
-
alias_method :|, :bitwise_or
|
109
|
-
|
110
|
-
def _unsafe_bitwise_or( other ) ## always assumes other is an integer
|
111
|
-
self.class.new( @value | other )
|
112
|
-
end
|
113
|
-
|
114
|
-
def set( other ) ## typesafe version of bitwise-or (|=) (no "plain" Integer allowed)
|
115
|
-
_unsafe_bitwise_or( _typecast_flag!( other ).value )
|
116
|
-
end
|
117
|
-
alias_method :flag, :set
|
118
|
-
|
119
|
-
|
120
|
-
def bitwise_and( other )
|
121
|
-
_unsafe_bitwise_and( _value_for_flag!( other ) )
|
122
|
-
end
|
123
|
-
alias_method :&, :bitwise_and
|
124
|
-
|
125
|
-
def _unsafe_bitwise_and( other )
|
126
|
-
self.class.new( @value & other )
|
127
|
-
end
|
128
|
-
|
129
|
-
|
130
|
-
def unset( other ) ## typesafe version of bitwise-and/bitwise-inverse (&=~) (no "plain" Integer allowed)
|
131
|
-
_unsafe_bitwise_and( ~_typecast_flag!( other ).value )
|
132
|
-
end
|
133
|
-
alias_method :unflag, :unset
|
134
|
-
|
135
|
-
|
136
|
-
def bitwise_xor( other )
|
137
|
-
_unsafe_bitwise_xor( _value_for_flag!( other ))
|
138
|
-
end
|
139
|
-
alias_method :^, :bitwise_xor
|
140
|
-
|
141
|
-
def _unsafe_bitwise_xor( other )
|
142
|
-
self.class.new( @value ^ other )
|
143
|
-
end
|
144
|
-
|
145
|
-
def toggle( other ) ## typesafe version of bitwise-xor (^=) (no "plain" Integer allowed)
|
146
|
-
_unsafe_bitwise_xor( _typecast_flag!( other ).value )
|
147
|
-
end
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
def self.keys()
|
152
|
-
# note: does NOT include none - why? why not?
|
153
|
-
@keys ||= members.map { |member| member.key }.freeze
|
154
|
-
end
|
155
|
-
|
156
|
-
def self.key( key )
|
157
|
-
@hash_by_key ||= Hash[ keys.zip( members ) ].freeze
|
158
|
-
@hash_by_key[key]
|
159
|
-
end
|
160
|
-
|
161
|
-
def self.[]( key ) ## convenience alias for key
|
162
|
-
self.key( key )
|
163
|
-
end
|
164
|
-
|
165
|
-
|
166
|
-
def self.values()
|
167
|
-
# note: does NOT include none - why? why not?
|
168
|
-
@values ||= members.map { |member| member.value }.freeze
|
169
|
-
end
|
170
|
-
|
171
|
-
|
172
|
-
def self.convert( *args )
|
173
|
-
new( *args )
|
174
|
-
end
|
175
|
-
|
176
|
-
def self.zero() @zero ||= new(0); end
|
177
|
-
def zero?() @value == 0; end
|
178
|
-
|
179
|
-
### todo/fix:
|
180
|
-
## add ==/eql?
|
181
|
-
## for self AND flag AND integer
|
182
|
-
## always compare integer value
|
183
|
-
|
184
|
-
|
185
|
-
## add size|length too why? why not?
|
186
|
-
## add value() lookup?
|
187
|
-
## not for now - why? combined values are undefined!! what to return??
|
188
|
-
|
189
|
-
## add to_i, to_int - why? why not?
|
190
|
-
## def to_i() @value; end
|
191
|
-
## def to_int() @value; end
|
192
|
-
end # class Flags
|
193
|
-
end # module Safe
|
data/test/test_flags.rb
DELETED
@@ -1,68 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
##
|
4
|
-
# to run use
|
5
|
-
# ruby -I ./lib -I ./test test/test_flags.rb
|
6
|
-
|
7
|
-
|
8
|
-
require 'helper'
|
9
|
-
|
10
|
-
|
11
|
-
module Safe
|
12
|
-
## Flags.new( :FileAttribs, :readonly, :hidden, :system, :archive )
|
13
|
-
enum :FileAttribs, [:readonly, :hidden, :system, :archive], flags: true
|
14
|
-
|
15
|
-
|
16
|
-
pp FileAttribs
|
17
|
-
pp FileAttribs::Flag
|
18
|
-
pp FileAttribs(0)
|
19
|
-
|
20
|
-
puts "Safe.constants:"
|
21
|
-
pp Safe.constants #=> [:SafeHelper, :Enum, :State, :Color]
|
22
|
-
puts "Flags.constants:"
|
23
|
-
pp Flags.constants #=> []
|
24
|
-
puts "Flag.constants:"
|
25
|
-
pp Flag.constants #=> []
|
26
|
-
puts "FileAttribs.constants:"
|
27
|
-
pp FileAttribs.constants #=> []
|
28
|
-
end
|
29
|
-
|
30
|
-
|
31
|
-
class TestFlags < MiniTest::Test
|
32
|
-
|
33
|
-
include Safe ## make all enums (and "convenience" converters) global
|
34
|
-
|
35
|
-
|
36
|
-
def test_attribs
|
37
|
-
|
38
|
-
assert_equal [1<<0, 1<<1, 1<<2, 1<<3], FileAttribs.values
|
39
|
-
assert_equal [:readonly, :hidden, :system, :archive], FileAttribs.keys
|
40
|
-
|
41
|
-
assert_equal FileAttribs.readonly, FileAttribs::READONLY
|
42
|
-
assert_equal FileAttribs.hidden, FileAttribs::HIDDEN
|
43
|
-
assert_equal FileAttribs.system, FileAttribs::SYSTEM
|
44
|
-
assert_equal FileAttribs.archive, FileAttribs::ARCHIVE
|
45
|
-
|
46
|
-
assert_equal FileAttribs.readonly, FileAttribs[:readonly]
|
47
|
-
assert_equal FileAttribs.hidden, FileAttribs[:hidden]
|
48
|
-
assert_equal FileAttribs.system, FileAttribs[:system]
|
49
|
-
assert_equal FileAttribs.archive, FileAttribs[:archive]
|
50
|
-
|
51
|
-
assert_equal 1<<0, FileAttribs.readonly.value
|
52
|
-
assert_equal :readonly, FileAttribs.readonly.key
|
53
|
-
assert_equal true, FileAttribs.readonly.is_a?( Flag )
|
54
|
-
assert_equal true, FileAttribs.readonly.is_a?( FileAttribs::Flag )
|
55
|
-
|
56
|
-
pp FileAttribs.members
|
57
|
-
assert_equal :readonly, FileAttribs.members[0].key
|
58
|
-
assert_equal 1<<0, FileAttribs.members[0].value
|
59
|
-
assert_equal :hidden, FileAttribs.members[1].key
|
60
|
-
assert_equal 1<<1, FileAttribs.members[1].value
|
61
|
-
|
62
|
-
assert_equal 1<<0, FileAttribs.readonly.value
|
63
|
-
assert_equal :readonly, FileAttribs.readonly.key
|
64
|
-
assert_equal 1<<0, FileAttribs::READONLY.value
|
65
|
-
assert_equal :readonly, FileAttribs::READONLY.key
|
66
|
-
end
|
67
|
-
|
68
|
-
end # class TestFlags
|