enums 1.1.1 → 1.2.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 +5 -0
- data/README.md +13 -6
- data/lib/enums.rb +30 -175
- data/lib/enums/enum.rb +65 -0
- data/lib/enums/enum_builder.rb +75 -0
- data/lib/enums/flags.rb +193 -0
- data/lib/enums/flags_builder.rb +77 -0
- data/lib/enums/version.rb +2 -2
- data/test/test_enum.rb +1 -1
- data/test/test_flags.rb +68 -0
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c0e37d71cacfb8ee57b2b037caa082f6994912f6
|
4
|
+
data.tar.gz: faf257d577a56cb4a0b1d0df423cef87dbda37ed
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1f2faa8be748c71b7e2fe9c3dc93f0a31ce14ec4cc85211815bf4e6a27ef228525d649fc0d21bb340f725ab99eac317e2f2a2de9c85573707fc78ace22ac591f
|
7
|
+
data.tar.gz: 54805411531355f799da430f9a6c243062f6676c9df2b0c92296e2a278f47511fb1b62f615b382db59f541cc286f6875ea3ba26989bf21006d4bdec04559e9e2
|
data/Manifest.txt
CHANGED
data/README.md
CHANGED
@@ -76,12 +76,16 @@ class Color < Enum
|
|
76
76
|
def self.keys() [:red, :blue, :green]; end
|
77
77
|
def self.members() [RED, BLUE, GREEN]; end
|
78
78
|
|
79
|
-
def self.
|
80
|
-
|
79
|
+
def self.zero() members[0]; end
|
80
|
+
|
81
|
+
def self.value( value )
|
82
|
+
@hash_by_value ||= Hash[ values.zip( members ) ]
|
83
|
+
@hash_by_value[ value ]
|
84
|
+
end
|
81
85
|
|
82
86
|
def self.key( key )
|
83
|
-
@
|
84
|
-
@
|
87
|
+
@hash_by_key ||= Hash[ keys.zip( members ) ]
|
88
|
+
@hash_by_key[ key ]
|
85
89
|
end
|
86
90
|
def self.[]( key ) self.key( key ); end
|
87
91
|
|
@@ -90,12 +94,15 @@ class Color < Enum
|
|
90
94
|
def green?() self == GREEN; end
|
91
95
|
end
|
92
96
|
|
93
|
-
|
94
|
-
|
97
|
+
|
98
|
+
def Color( index )
|
99
|
+
Color.members[ index ]
|
95
100
|
end
|
96
101
|
```
|
97
102
|
|
98
103
|
|
104
|
+
|
105
|
+
|
99
106
|
Use like:
|
100
107
|
|
101
108
|
``` ruby
|
data/lib/enums.rb
CHANGED
@@ -7,193 +7,44 @@ require 'pp'
|
|
7
7
|
## our own code
|
8
8
|
require 'enums/version' # note: let version always go first
|
9
9
|
|
10
|
-
##################################
|
11
|
-
## auto-create/builds enum class.
|
12
|
-
##
|
13
|
-
## Example:
|
14
|
-
## Enum.new( :State, :fundraising, :expired_refund, :successful)
|
15
|
-
## auto-creates/builds:
|
16
|
-
##
|
17
|
-
## class Enum
|
18
|
-
## def initialize( key, value )
|
19
|
-
## @key = key
|
20
|
-
## @value = value
|
21
|
-
## end
|
22
|
-
## end
|
23
|
-
##
|
24
|
-
## class State < Enum
|
25
|
-
##
|
26
|
-
## FUNDRAISING = new(:fundraising, 0)
|
27
|
-
## EXPIRED_REFUND = new(:expired_refund, 1)
|
28
|
-
## SUCCESSFUL = new(:successful, 2)
|
29
|
-
##
|
30
|
-
## def self.fundraising() FUNDRAISING; end
|
31
|
-
## def self.expired_refund() EXPIRED_REFUND; end
|
32
|
-
## def self.successful() SUCCESSFUL; end
|
33
|
-
##
|
34
|
-
## def fundraising?() self == FUNDRAISING; end
|
35
|
-
## def expired_refund?() self == EXPIRED_REFUND; end
|
36
|
-
## def successful?() self == SUCCESSFUL; end
|
37
|
-
## end
|
38
|
-
##
|
39
|
-
## pp state = State.fundraising #=> #<State @key=:fundraising, @value=0>
|
40
|
-
## pp state.fundraising? #=> true
|
41
|
-
## pp state.expired_refund? #=> false
|
42
|
-
## pp state.successful? #=> false
|
43
|
-
## pp state = State.expired_refund #=> #<State @key=:expired_refund, @value=1>
|
44
|
-
## pp state.fundraising? #=> false
|
45
|
-
## pp state.expired_refund? #=> true
|
46
|
-
## pp state.successful? #=> false
|
47
|
-
|
48
|
-
|
49
10
|
|
11
|
+
## forward declarations
|
50
12
|
module Safe
|
51
|
-
|
52
|
-
##
|
53
|
-
|
54
|
-
|
55
|
-
klass.extend( SafeHelper )
|
56
|
-
end
|
57
|
-
|
58
|
-
module SafeHelper; end
|
59
|
-
## note: also extends (include a helper methods to Safe itself)
|
60
|
-
## lets you use:
|
61
|
-
## module Safe
|
62
|
-
## enum :Color, :red, :green, :blue
|
63
|
-
## end
|
64
|
-
extend SafeHelper
|
65
|
-
|
66
|
-
|
67
|
-
## base class for enum
|
68
|
-
class Enum
|
69
|
-
## return a new Enum read-only class
|
70
|
-
attr_reader :key
|
71
|
-
attr_reader :value
|
72
|
-
|
73
|
-
def initialize( key, value )
|
74
|
-
@key = key
|
75
|
-
@value = value
|
76
|
-
self.freeze ## make "immutable"
|
77
|
-
self
|
78
|
-
end
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
def self.value( index )
|
83
|
-
## todo/fix: check for out-of-bound / unknown enum
|
84
|
-
## puts "#{name}.value(#{index})"
|
85
|
-
## pp keys
|
86
|
-
## pp values
|
87
|
-
members[ index ]
|
88
|
-
end
|
89
|
-
|
90
|
-
def zero?() self == self.class.zero; end
|
91
|
-
|
92
|
-
def self.zero
|
93
|
-
members[0]
|
94
|
-
end
|
95
|
-
|
96
|
-
def self.key( key )
|
97
|
-
@hash ||= Hash[ keys.zip( members ) ].freeze
|
98
|
-
@hash[key]
|
99
|
-
end
|
100
|
-
|
101
|
-
def self.[]( key ) ## convenience alias for key
|
102
|
-
self.key( key )
|
103
|
-
end
|
104
|
-
|
105
|
-
def self.values
|
106
|
-
@values ||= (0...keys.size).to_a.freeze
|
107
|
-
@values
|
108
|
-
end
|
109
|
-
|
110
|
-
def self.size() keys.size; end
|
111
|
-
def self.length() size; end ## alias (as is the ruby tradition)
|
112
|
-
|
113
|
-
|
114
|
-
def self.convert( arg )
|
115
|
-
## todo/check: support keys too - why? why not?
|
116
|
-
## e.g. Color(0), Color(1)
|
117
|
-
## Color(:red), Color(:blue) - why? why not?
|
118
|
-
value( arg )
|
13
|
+
## note: use ClassMethods pattern for auto-including class methods
|
14
|
+
## note ClassMethods module is called SafeHelper
|
15
|
+
def self.included( klass )
|
16
|
+
klass.extend( SafeHelper )
|
119
17
|
end
|
120
18
|
|
19
|
+
module SafeHelper; end
|
20
|
+
## note: also extends (include a helper methods to Safe itself)
|
21
|
+
## lets you use:
|
22
|
+
## module Safe
|
23
|
+
## enum :Color, :red, :green, :blue
|
24
|
+
## end
|
25
|
+
extend SafeHelper
|
26
|
+
end # module Safe
|
121
27
|
|
122
28
|
|
123
|
-
###################
|
124
|
-
## meta-programming "macro" - build class (on the fly)
|
125
|
-
def self.build_class( class_name, *keys )
|
126
|
-
|
127
|
-
## todo/fix:
|
128
|
-
## check class name MUST start with uppercase letter
|
129
|
-
|
130
|
-
## check if all keys are symbols and follow the ruby id(entifier) naming rules
|
131
|
-
keys.each do |key|
|
132
|
-
if key.is_a?( Symbol ) && key =~ /\A[a-z][a-zA-Z0-9_]*\z/
|
133
|
-
else
|
134
|
-
raise ArgumentError.new( "[Enum] arguments to Enum.new must be all symbols following the ruby id naming rules; >#{key}< failed" )
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
klass = Class.new( Enum )
|
139
|
-
|
140
|
-
## add self.new too - note: call/forward to "old" orginal self.new of Event (base) class
|
141
|
-
klass.define_singleton_method( :new ) do |*args|
|
142
|
-
old_new( *args )
|
143
|
-
end
|
144
|
-
|
145
|
-
keys.each_with_index do |key,index|
|
146
|
-
klass.class_eval( <<RUBY )
|
147
|
-
#{key.upcase} = new( :#{key}, #{index} )
|
148
29
|
|
149
|
-
|
150
|
-
|
151
|
-
|
30
|
+
require 'enums/enum'
|
31
|
+
require 'enums/enum_builder'
|
32
|
+
require 'enums/flags'
|
33
|
+
require 'enums/flags_builder'
|
152
34
|
|
153
|
-
def self.#{key}
|
154
|
-
#{key.upcase}
|
155
|
-
end
|
156
|
-
RUBY
|
157
|
-
end
|
158
35
|
|
159
|
-
klass.class_eval( <<RUBY )
|
160
|
-
def self.keys
|
161
|
-
@keys ||= #{keys}.freeze
|
162
|
-
end
|
163
|
-
|
164
|
-
def self.members
|
165
|
-
@members ||= [#{keys.map {|key|key.upcase}.join(',')}].freeze
|
166
|
-
end
|
167
|
-
RUBY
|
168
|
-
|
169
|
-
## note: use Kernel for "namespacing"
|
170
|
-
## make all enums Kernel convenience converters (always) global
|
171
|
-
## including uppercase methods (e.g. State(), Color(), etc.) does NOT work otherwise (with other module includes)
|
172
|
-
|
173
|
-
## add global convenience converter function
|
174
|
-
## e.g. State(0) is same as State.convert(0)
|
175
|
-
## Color(0) is same as Color.convert(0)
|
176
|
-
Kernel.class_eval( <<RUBY )
|
177
|
-
def #{class_name}( arg )
|
178
|
-
#{class_name}.convert( arg )
|
179
|
-
end
|
180
|
-
RUBY
|
181
|
-
|
182
|
-
## note: use Safe (module) and NO Object for namespacing
|
183
|
-
## use include Safe to make all enums constants and machinery global
|
184
|
-
Safe.const_set( class_name, klass ) ## returns klass (plus sets global constant class name)
|
185
|
-
end
|
186
36
|
|
187
37
|
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
end
|
192
|
-
end # class Enum
|
38
|
+
module Safe
|
39
|
+
module SafeHelper
|
40
|
+
def enum( class_name, *args, flags: false, options: {} )
|
193
41
|
|
42
|
+
## note: allow "standalone" option flags or
|
43
|
+
## option hash
|
44
|
+
defaults = { flags: flags }
|
45
|
+
options = defaults.merge( options )
|
46
|
+
pp options
|
194
47
|
|
195
|
-
module SafeHelper
|
196
|
-
def enum( class_name, *args )
|
197
48
|
########################################
|
198
49
|
# note: lets you use:
|
199
50
|
# enum :Color, :red, :green, :blue
|
@@ -205,7 +56,11 @@ module SafeHelper
|
|
205
56
|
keys = args
|
206
57
|
end
|
207
58
|
|
208
|
-
|
59
|
+
if options[:flags]
|
60
|
+
Flags.new( class_name, *keys )
|
61
|
+
else
|
62
|
+
Enum.new( class_name, *keys )
|
63
|
+
end
|
209
64
|
end
|
210
65
|
end # module SafeHelper
|
211
66
|
end # module Safe
|
data/lib/enums/enum.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
###############################
|
4
|
+
## base class for enum
|
5
|
+
|
6
|
+
module Safe
|
7
|
+
class Enum
|
8
|
+
## return a new Enum read-only class
|
9
|
+
attr_reader :key
|
10
|
+
attr_reader :value
|
11
|
+
|
12
|
+
def initialize( key, value )
|
13
|
+
@key = key
|
14
|
+
@value = value
|
15
|
+
self.freeze ## make "immutable"
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
def self.keys
|
21
|
+
@keys ||= members.map {|member| member.key}.freeze
|
22
|
+
@keys
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.key( key )
|
26
|
+
## note: returns nil now for unknown keys
|
27
|
+
## use/raise IndexError or something - why? why not?
|
28
|
+
@hash_by_key ||= Hash[ keys.zip( members ) ].freeze
|
29
|
+
@hash_by_key[key]
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.[]( key ) ## convenience alias for key
|
33
|
+
self.key( key )
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
def self.values
|
38
|
+
@values ||= members.map {|member| member.value}.freeze
|
39
|
+
@values
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.value( value )
|
43
|
+
## note: returns nil now for unknown values
|
44
|
+
## use/raise IndexError or something - why? why not?
|
45
|
+
@hash_by_value ||= Hash[ values.zip( members ) ].freeze
|
46
|
+
@hash_by_value[value]
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
def self.size() keys.size; end
|
51
|
+
def self.length() size; end ## alias (as is the ruby tradition)
|
52
|
+
|
53
|
+
|
54
|
+
def self.convert( arg )
|
55
|
+
## todo/check: support keys too - why? why not?
|
56
|
+
## e.g. Color(0), Color(1)
|
57
|
+
## Color(:red), Color(:blue) - why? why not?
|
58
|
+
## note: will ALWAYS look-up by (member) index and NOT by value (integer number value might be different!!)
|
59
|
+
members[ arg ]
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.zero() members[0]; end
|
63
|
+
def zero?() self == self.class.zero; end
|
64
|
+
end # class Enum
|
65
|
+
end # module Safe
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
|
4
|
+
module Safe
|
5
|
+
class Enum
|
6
|
+
|
7
|
+
|
8
|
+
###################
|
9
|
+
## meta-programming "macro" - build class (on the fly)
|
10
|
+
def self.build_class( class_name, *keys )
|
11
|
+
|
12
|
+
## todo/fix:
|
13
|
+
## check class name MUST start with uppercase letter
|
14
|
+
|
15
|
+
## check if all keys are symbols and follow the ruby id(entifier) naming rules
|
16
|
+
keys.each do |key|
|
17
|
+
if key.is_a?( Symbol ) && key =~ /\A[a-z][a-zA-Z0-9_]*\z/
|
18
|
+
else
|
19
|
+
raise ArgumentError.new( "[Enum] arguments to Enum.new must be all symbols following the ruby id naming rules; >#{key}< failed" )
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
klass = Class.new( Enum )
|
24
|
+
|
25
|
+
## add self.new too - note: call/forward to "old" orginal self.new of Event (base) class
|
26
|
+
klass.define_singleton_method( :new ) do |*args|
|
27
|
+
old_new( *args )
|
28
|
+
end
|
29
|
+
|
30
|
+
keys.each_with_index do |key,index|
|
31
|
+
klass.class_eval( <<RUBY )
|
32
|
+
#{key.upcase} = new( :#{key}, #{index} )
|
33
|
+
|
34
|
+
def #{key}?
|
35
|
+
self == #{key.upcase}
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.#{key}
|
39
|
+
#{key.upcase}
|
40
|
+
end
|
41
|
+
RUBY
|
42
|
+
end
|
43
|
+
|
44
|
+
klass.class_eval( <<RUBY )
|
45
|
+
def self.members
|
46
|
+
@members ||= [#{keys.map {|key|key.upcase}.join(',')}].freeze
|
47
|
+
end
|
48
|
+
RUBY
|
49
|
+
|
50
|
+
## note: use Kernel for "namespacing"
|
51
|
+
## make all enums Kernel convenience converters (always) global
|
52
|
+
## including uppercase methods (e.g. State(), Color(), etc.) does NOT work otherwise (with other module includes)
|
53
|
+
|
54
|
+
## add global convenience converter function
|
55
|
+
## e.g. State(0) is same as State.convert(0)
|
56
|
+
## Color(0) is same as Color.convert(0)
|
57
|
+
Kernel.class_eval( <<RUBY )
|
58
|
+
def #{class_name}( arg )
|
59
|
+
#{class_name}.convert( arg )
|
60
|
+
end
|
61
|
+
RUBY
|
62
|
+
|
63
|
+
## note: use Safe (module) and NO Object for namespacing
|
64
|
+
## use include Safe to make all enums constants and machinery global
|
65
|
+
Safe.const_set( class_name, klass ) ## returns klass (plus sets global constant class name)
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
class << self
|
70
|
+
alias_method :old_new, :new # note: store "old" orginal version of new
|
71
|
+
alias_method :new, :build_class # replace original version with build_class
|
72
|
+
end
|
73
|
+
|
74
|
+
end # class Enum
|
75
|
+
end # module Safe
|
data/lib/enums/flags.rb
ADDED
@@ -0,0 +1,193 @@
|
|
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
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module Safe
|
2
|
+
class Flags
|
3
|
+
|
4
|
+
|
5
|
+
###################
|
6
|
+
## meta-programming "macro" - build class (on the fly)
|
7
|
+
def self.build_class( class_name, *keys )
|
8
|
+
|
9
|
+
## todo/fix:
|
10
|
+
## check class name MUST start with uppercase letter
|
11
|
+
|
12
|
+
## check if all keys are symbols and follow the ruby id(entifier) naming rules
|
13
|
+
keys.each do |key|
|
14
|
+
if key.is_a?( Symbol ) && key =~ /\A[a-z][a-zA-Z0-9_]*\z/
|
15
|
+
else
|
16
|
+
raise ArgumentError.new( "[Flags] arguments to Flags.new must be all symbols following the ruby id naming rules; >#{key}< failed" )
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
klass = Class.new( Flags )
|
21
|
+
## add self.new too - note: call/forward to "old" orginal self.new of Event (base) class
|
22
|
+
klass.define_singleton_method( :new ) do |*args|
|
23
|
+
old_new( *args )
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
## add nested flag class for "typesafe" flags
|
28
|
+
klass.class_eval( <<RUBY )
|
29
|
+
class Flag < Safe::Flag; end
|
30
|
+
RUBY
|
31
|
+
|
32
|
+
keys.each_with_index do |key,index|
|
33
|
+
klass.class_eval( <<RUBY )
|
34
|
+
#{key.upcase} = Flag.new( :#{key}, 1<<#{index} )
|
35
|
+
|
36
|
+
def self.#{key}
|
37
|
+
#{key.upcase}
|
38
|
+
end
|
39
|
+
|
40
|
+
def #{key}?
|
41
|
+
_member?( #{key.upcase} )
|
42
|
+
end
|
43
|
+
RUBY
|
44
|
+
end
|
45
|
+
|
46
|
+
klass.class_eval( <<RUBY )
|
47
|
+
def self.members
|
48
|
+
@members ||= [#{keys.map {|key|key.upcase}.join(',')}].freeze
|
49
|
+
end
|
50
|
+
RUBY
|
51
|
+
|
52
|
+
## note: use Kernel for "namespacing"
|
53
|
+
## make all enums Kernel convenience converters (always) global
|
54
|
+
## including uppercase methods (e.g. State(), Color(), etc.) does NOT work otherwise (with other module includes)
|
55
|
+
|
56
|
+
## add global convenience converter function
|
57
|
+
## e.g. State(0) is same as State.convert(0)
|
58
|
+
## Color(0) is same as Color.convert(0)
|
59
|
+
Kernel.class_eval( <<RUBY )
|
60
|
+
def #{class_name}( arg )
|
61
|
+
#{class_name}.convert( arg )
|
62
|
+
end
|
63
|
+
RUBY
|
64
|
+
|
65
|
+
## note: use Safe (module) and NO Object for namespacing
|
66
|
+
## use include Safe to make all enums constants and machinery global
|
67
|
+
Safe.const_set( class_name, klass ) ## returns klass (plus sets global constant class name)
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
class << self
|
72
|
+
alias_method :old_new, :new # note: store "old" orginal version of new
|
73
|
+
alias_method :new, :build_class # replace original version with build_class
|
74
|
+
end
|
75
|
+
|
76
|
+
end # class Flags
|
77
|
+
end # module Safe
|
data/lib/enums/version.rb
CHANGED
data/test/test_enum.rb
CHANGED
data/test/test_flags.rb
ADDED
@@ -0,0 +1,68 @@
|
|
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
|
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.2.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-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rdoc
|
@@ -55,9 +55,14 @@ files:
|
|
55
55
|
- README.md
|
56
56
|
- Rakefile
|
57
57
|
- lib/enums.rb
|
58
|
+
- lib/enums/enum.rb
|
59
|
+
- lib/enums/enum_builder.rb
|
60
|
+
- lib/enums/flags.rb
|
61
|
+
- lib/enums/flags_builder.rb
|
58
62
|
- lib/enums/version.rb
|
59
63
|
- test/helper.rb
|
60
64
|
- test/test_enum.rb
|
65
|
+
- test/test_flags.rb
|
61
66
|
homepage: https://github.com/s6ruby/enums
|
62
67
|
licenses:
|
63
68
|
- Public Domain
|