flagset 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1e36620b007fc35a4b3958aa81ad25d9d23042c4
4
+ data.tar.gz: f8862c9d9352cc2f403e7cd83007a5e30b0fa646
5
+ SHA512:
6
+ metadata.gz: 715c230d4e172b077f8e5684963e3ada9a51bc9f1e2c7ddc7081cbfe1617b7458ccca4f48a5a18d379b97914ccc1e142f9ea19d909a7ff81082f77c1f2ccee2b
7
+ data.tar.gz: 3c4d1e911b572d5ffba929e8a94e46cfece977fa99b0ac54e4ec38bb5ab01cc801adb5913e796403406841dbe56cec29ca16bb010c70376660d4d3950be95e40
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.1
5
+ before_install: gem install bundler -v 1.13.1
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in flagset.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Yusuke Takeuchi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,147 @@
1
+ # FlagSet
2
+ FlagSet is a Ruby library to make sets of finite flags.
3
+
4
+ ## Installation
5
+ ```
6
+ gem install flagset
7
+ ```
8
+ ## Usage
9
+ ### define
10
+ You define a class with FlagSet.define:
11
+ ```ruby
12
+ Auth = FlagSet.define{
13
+ flag :allow_read_name
14
+ flag :allow_read_email
15
+ flag :allow_read_posts
16
+ }
17
+ ```
18
+ Simpler ways are also available:
19
+ ```ruby
20
+ Auth = FlagSet.define{
21
+ flag :allow_read_name, :allow_read_email, :allow_read_posts
22
+ }
23
+ ```
24
+ Or
25
+ ```ruby
26
+ Auth = FlagSet.define(:allow_read_name, :allow_read_email, :allow_read_posts)
27
+ ```
28
+
29
+ ### create
30
+ ```ruby
31
+ Auth.new
32
+ # => #<Auth: []> (empty set)
33
+ Auth.new(:allow_read_name, :allow_read_posts)
34
+ # => #<Auth: [:allow_read_name,:allow_read_posts]>
35
+ Auth[:allow_read_name, :allow_read_posts]
36
+ # => #<Auth: [:allow_read_name,:allow_read_posts]> (same as above)
37
+ ```
38
+
39
+ ### create with class methods
40
+ ```ruby
41
+ Auth.allow_read_email
42
+ # => #<Auth: [:allow_read_email]> (same as Auth.new(:allow_read_email))
43
+
44
+ Auth.allow_read_name | Auth.allow_read_posts
45
+ # => #<Auth: [:allow_read_name,:allow_read_posts]>
46
+ ```
47
+ You can use special name *all* and *none*
48
+ ```ruby
49
+ Auth.all
50
+ # => #<Auth: [:allow_read_name,:allow_read_email,:allow_read_posts]>
51
+ Auth.none
52
+ # => #<Auth: []>
53
+ ```
54
+
55
+ ### aliases
56
+ *all* and *none* are aliases that denotes set of flags. You can also define aliases your own.
57
+ ```ruby
58
+ Auth2 = FlagSet.define{
59
+ aliased :read_all, [:read_name, :read_email, :read_posts]
60
+ flag :read_name, :read_email, :read_posts
61
+
62
+ flag :write_posts, :write_messages
63
+ aliased :write_all, [:write_posts, :write_messages]
64
+ # You can put aliased before or after the original names
65
+
66
+ aliased :read_write_all, [:read_all, :write_all]
67
+ }
68
+
69
+ Auth2.read_all
70
+ # => #<Auth2: [:read_name,:read_email,:read_posts]>
71
+ Auth2.write_all
72
+ # => #<Auth2: [:write_posts,:write_messages]>
73
+ Auth2.read_write_all == Auth2.all
74
+ # => true
75
+ ```
76
+
77
+ ### Queries
78
+ You can check the state of the flags with *has_all_of?* and *has_any_of?* methods:
79
+ ```ruby
80
+ Auth2.read_name.has_all_of?(:read_all)
81
+ # => false
82
+ Auth2.read_all.has_all_of?(:read_name, :read_email)
83
+ # => true
84
+ Auth2.write_all.has_all_of?(Auth2[:write_posts, :write_messages])
85
+ # => true
86
+ Auth2[:read_all].has_any_of?(:read_posts)
87
+ # => true
88
+ Auth2.read_all.has_any_of?(:write_all)
89
+ # => false
90
+ Auth2.all.has_any_of?(:none)
91
+ # => false
92
+ ```
93
+
94
+ You can also use named query methods:
95
+ ```ruby
96
+ Auth2.read_all.read_name?
97
+ # => true
98
+ Auth2.write_posts.write_all?
99
+ # => true
100
+ ```
101
+ Note that the query method *#foo?* is equivalent to *has_any_of?(:foo)*, so
102
+ aliases can be confusing when used as query methods.
103
+
104
+ There are special query methods *all?*, *any?*, *none?*
105
+ ```ruby
106
+ # #all? returns true if self is equal to class.all
107
+ Auth2.read_all.all? #=> false
108
+ (Auth2.read_all | Auth2.write_all).all? #=> true
109
+
110
+ # #any? returns true if self is not equal to class.none
111
+ Auth2.read_email.any? # => true
112
+ Auth2.new.any? # => false
113
+
114
+ # #none? returns true if self is equal to class.none
115
+ Auth2.read_posts.none? #=> false
116
+ Auth2.none.none? #=> true
117
+ ```
118
+
119
+ ### Set operations
120
+ Basic set operations are supported.
121
+ ```ruby
122
+ Auth2.read_all & Auth2.read_name
123
+ # => #<Auth2: [:read_name]>
124
+
125
+ Auth2.read_name | Auth2.read_email
126
+ # => #<Auth2: [:read_name,:read_email]>
127
+
128
+ Auth2.read_all - :read_posts
129
+ # => #<Auth2: [:read_name,:read_email]>
130
+
131
+ Auth2[:read_name, :read_email] ^ [:read_email, :read_posts]
132
+ # => #<Auth2: [:read_name,:read_posts]>
133
+
134
+ ~Auth2[:write_all]
135
+ # => #<Auth2: [:read_name,:read_email,:read_posts]>
136
+ ```
137
+ The arguments of .new and set operations can be one of these:
138
+
139
+ * Symbol
140
+ * the same class as self
141
+ * Integer
142
+
143
+ or a single Array of above.
144
+
145
+
146
+ ## License
147
+ MIT License
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :test
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "flagset"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/flagset.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'flagset/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "flagset"
8
+ spec.version = Flagset::VERSION
9
+ spec.authors = ["Yusuke Takeuchi"]
10
+ spec.email = ["v.takeuchi@gmail.com"]
11
+
12
+ spec.summary = %q{Module to define classes respresenting a set of flags.}
13
+ spec.description = spec.summary
14
+ spec.homepage = "https://github.com/YusukeTakeuchi/flagset"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
+ f.match(%r{^(test|spec|features)/})
18
+ end
19
+ spec.bindir = "exe"
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.13"
24
+ spec.add_development_dependency "rake", "~> 10.0"
25
+ spec.add_development_dependency "minitest", "~> 5.0"
26
+ spec.add_development_dependency "equalizer"
27
+ end
@@ -0,0 +1,3 @@
1
+ module Flagset
2
+ VERSION = "0.1.0"
3
+ end
data/lib/flagset.rb ADDED
@@ -0,0 +1,311 @@
1
+ require 'tsort'
2
+ require 'equalizer'
3
+ require "flagset/version"
4
+
5
+ module FlagSet
6
+
7
+ def self.define(*names, &block)
8
+ if names.any? and block
9
+ raise ArgumentError, 'symbols and block cannot be specified at once'
10
+ end
11
+ builder = Builder.new
12
+ unless block
13
+ block = lambda{| *_ |
14
+ names.each{| name |
15
+ flag name
16
+ }
17
+ }
18
+ end
19
+ builder.define(&block)
20
+ Class.new(Base){
21
+ define_singleton_method(:flagset_builder){
22
+ builder
23
+ }
24
+ define_flags_class_methods
25
+ define_flags_instance_methods
26
+ }
27
+ end
28
+
29
+ class Builder
30
+ attr_reader :elementary_flag_names
31
+ attr_reader :all_flags_and_ints
32
+ attr_reader :current_all_mask
33
+
34
+ def initialize
35
+ @elementary_flag_names = []
36
+ @all_flags_and_ints = {}
37
+ @current_all_mask = 0
38
+ end
39
+
40
+ def define(&block)
41
+ @unresolved_aliases = {}.extend(AliasResolvingTSort)
42
+ instance_eval(&block)
43
+ @all_flags_and_ints[:all] = @current_all_mask
44
+ @all_flags_and_ints[:none] = 0
45
+ aliases = @unresolved_aliases.keys
46
+ (@unresolved_aliases.tsort & aliases).each{| name |
47
+ @all_flags_and_ints[name] =
48
+ @unresolved_aliases[name].inject(0){| v,t_name |
49
+ v | (@all_flags_and_ints[t_name] or raise NameError, "unknown flag name: #{t_name}")
50
+ }
51
+ }
52
+ end
53
+
54
+ # @param [Array<Symbol>] names
55
+ # @option opts [Integer] :bits the bits to represent
56
+ # @option opts [Integer] :bit alias for :bits
57
+ def flag(*names, **opts)
58
+ if bits = opts[:bit] || opts[:bits]
59
+ if opts[:bit] and opts[:bits]
60
+ raise ArgumentError, 'bit and bits cannot be specified at once'
61
+ end
62
+ unless names.size == 1
63
+ raise ArgumentError, 'only one name can be specified with bit option'
64
+ end
65
+ if @current_all_mask & bits != 0
66
+ raise ArgumentError, 'conflicting bits'
67
+ end
68
+ name = names.first
69
+ error_if_already_in_use(name)
70
+ @elementary_flag_names << name
71
+ @all_flags_and_ints[name] = bits
72
+ @current_all_mask |= bits
73
+ else
74
+ names.each{| fname |
75
+ error_if_already_in_use(fname)
76
+ bit = next_available_bit
77
+ @elementary_flag_names << fname
78
+ @all_flags_and_ints[fname] = bit
79
+ @current_all_mask |= bit
80
+ }
81
+ end
82
+ end
83
+
84
+ def next_available_bit
85
+ bit = 1
86
+ until @current_all_mask & bit == 0
87
+ bit <<= 1
88
+ end
89
+ bit
90
+ end
91
+
92
+ def aliased(name, targets)
93
+ error_if_already_in_use(name)
94
+ targets = [targets] unless targets.kind_of?(Enumerable)
95
+ @unresolved_aliases[name] = targets
96
+ end
97
+
98
+ def error_if_already_in_use(name)
99
+ if @all_flags_and_ints[name] or @unresolved_aliases[name]
100
+ raise ArgumentError, "flag name #{name} is already in use"
101
+ end
102
+ end
103
+
104
+ module AliasResolvingTSort
105
+ include TSort
106
+
107
+ def tsort_each_node(&block)
108
+ each_key(&block)
109
+ end
110
+
111
+ def tsort_each_child(node, &block)
112
+ (self[node] || []).each(&block)
113
+ end
114
+ end
115
+ end
116
+
117
+ # The class FlagSet.define creates derives from Base
118
+ class Base
119
+ include Equalizer.new(:to_i)
120
+
121
+ def initialize(*args)
122
+ @int_value = args_to_int_value(*args)
123
+ end
124
+
125
+ def to_i
126
+ @int_value
127
+ end
128
+
129
+ def to_s
130
+ '#<%s: [%s]>' % [
131
+ self.class,
132
+ to_names.map(&:inspect).join(',')
133
+ ]
134
+ end
135
+ alias inspect to_s
136
+
137
+ def to_names
138
+ self.class.int_to_names(@int_value)
139
+ end
140
+
141
+ private
142
+ def args_to_int_value(*args)
143
+ if args.size == 1
144
+ if args.first.kind_of?(Enumerable)
145
+ objs = args.first
146
+ else
147
+ objs = [args.first]
148
+ end
149
+ else
150
+ objs = args
151
+ end
152
+ enum_to_int_value(objs)
153
+ end
154
+
155
+ # @param [Array<Symbol,Base,Integer>] objs
156
+ def enum_to_int_value(objs)
157
+ objs.inject(0){| v,obj |
158
+ v | (
159
+ case obj
160
+ when Symbol
161
+ self.class.name_to_int(obj) or
162
+ raise ArgumentError, "#{obj} is not defined in #{self.class}"
163
+ when self.class
164
+ obj.to_i
165
+ when Integer
166
+ unless consistent_int?(obj) and (~self.class.all_flags_mask & obj) == 0
167
+ raise ArgumentError, "0x#{obj.to_s(16)} is a flag value inconsistent with #{self.class}"
168
+ end
169
+ obj
170
+ else
171
+ raise TypeError, "cannot convert to #{self.class}: #{obj}"
172
+ end
173
+ )
174
+ }
175
+ end
176
+
177
+ # check if v is consistent with the set of flags
178
+ # v is inconsistent in cases like:
179
+ # F = FlagSet.define{
180
+ # flag :some_flag, bits: 0xFF
181
+ # }
182
+ # F.new(1)
183
+ # @param [Integer] v
184
+ def consistent_int?(v)
185
+ self.class.elementary_flag_names.all?{| name |
186
+ mask = self.class.all_flags_and_ints[name]
187
+ [0, mask].include?(mask & v)
188
+ }
189
+ end
190
+
191
+ class << self
192
+ def name_to_int(name)
193
+ all_flags_and_ints[name] or raise NameError, "unknown flag name for #{self.class}: #{name}"
194
+ end
195
+
196
+ def int_to_names(int)
197
+ elementary_flag_names.select{| name |
198
+ (int & (all_flags_and_ints[name])) != 0
199
+ }
200
+ end
201
+
202
+ def flagset_builder
203
+ # abstract
204
+ end
205
+
206
+ # elementary flags are flags that are not aliases
207
+ #
208
+ # @return [Array<Symbol>]
209
+ def elementary_flag_names
210
+ flagset_builder.elementary_flag_names
211
+ end
212
+
213
+ # @return [Hash<Symbol,Integer>]
214
+ def all_flags_and_ints
215
+ flagset_builder.all_flags_and_ints
216
+ end
217
+
218
+ # @return [Integer]
219
+ def all_flags_mask
220
+ flagset_builder.current_all_mask
221
+ end
222
+
223
+ def [](*args)
224
+ new(*args)
225
+ end
226
+
227
+ def define_flags_class_methods
228
+ all_flags_and_ints.each{| k,v |
229
+ define_singleton_method(k){
230
+ new(v)
231
+ }
232
+ }
233
+ end
234
+
235
+ def define_flags_instance_methods
236
+ all_flags_and_ints.each{| k,v |
237
+ next if [:all, :none].include?(k)
238
+ define_method("#{k}?"){
239
+ has_any_of?(v)
240
+ }
241
+ }
242
+ end
243
+ end
244
+
245
+ public
246
+ ### set manipulation methods ###
247
+
248
+ def &(*args)
249
+ self.class.new(@int_value & args_to_int_value(*args))
250
+ end
251
+ alias intersect &
252
+
253
+ def |(*args)
254
+ self.class.new(@int_value | args_to_int_value(*args))
255
+ end
256
+ alias union |
257
+ alias + |
258
+
259
+ def -(*args)
260
+ self.class.new(@int_value & ~args_to_int_value(*args))
261
+ end
262
+ alias difference -
263
+
264
+ def ^(*args)
265
+ self.class.new(@int_value ^ args_to_int_value(*args))
266
+ end
267
+
268
+ def ~@
269
+ self.class.new(self.class.all_flags_mask & ~@int_value)
270
+ end
271
+
272
+ def subset?(*args)
273
+ @int_value & ~args_to_int_value(*args) == 0
274
+ end
275
+
276
+ def proper_subset?(*args)
277
+ v = args_to_int_value(*args)
278
+ @int_value & ~v == 0 and @int_value != v
279
+ end
280
+
281
+ def superset?(*args)
282
+ ~@int_value & args_to_int_value(*args) == 0
283
+ end
284
+ alias has_all_of? superset?
285
+
286
+ def proper_superset?(*args)
287
+ v = args_to_int_value(*args)
288
+ ~@int_value & v == 0 and @int_value != v
289
+ end
290
+
291
+ def intersect?(*args)
292
+ @int_value & args_to_int_value(*args) != 0
293
+ end
294
+ alias has_any_of? intersect?
295
+ alias === intersect?
296
+
297
+ def all?
298
+ @int_value == self.class.all_flags_mask
299
+ end
300
+
301
+ def any?
302
+ @int_value != 0
303
+ end
304
+
305
+ def none?
306
+ @int_value == 0
307
+ end
308
+
309
+ end
310
+
311
+ end
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: flagset
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Yusuke Takeuchi
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-11-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.13'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.13'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '5.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: equalizer
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Module to define classes respresenting a set of flags.
70
+ email:
71
+ - v.takeuchi@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - ".travis.yml"
78
+ - Gemfile
79
+ - LICENSE
80
+ - README.md
81
+ - Rakefile
82
+ - bin/console
83
+ - bin/setup
84
+ - flagset.gemspec
85
+ - lib/flagset.rb
86
+ - lib/flagset/version.rb
87
+ homepage: https://github.com/YusukeTakeuchi/flagset
88
+ licenses: []
89
+ metadata: {}
90
+ post_install_message:
91
+ rdoc_options: []
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ requirements: []
105
+ rubyforge_project:
106
+ rubygems_version: 2.5.1
107
+ signing_key:
108
+ specification_version: 4
109
+ summary: Module to define classes respresenting a set of flags.
110
+ test_files: []