enums 1.1.1 → 1.2.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 +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
|