enums 1.2.0 → 1.3.0
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.
- 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
|