Class: Philiprehberger::BitField::Base

Inherits:
Object
  • Object
show all
Includes:
Comparable
Defined in:
lib/philiprehberger/bit_field.rb

Overview

Named bit flags with symbolic access, set operations, and serialization

Examples:

class Permissions < Philiprehberger::BitField::Base
  flag :read, 0
  flag :write, 1
  flag :execute, 2
  group :read_write, [:read, :write]
end
perms = Permissions.new(:read, :write)
perms.read?    # => true
perms.execute? # => false

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*initial_flags) ⇒ Base

Create a new bit field with the given flags set

Parameters:

  • initial_flags (Array<Symbol>)

    flags to set initially



139
140
141
142
# File 'lib/philiprehberger/bit_field.rb', line 139

def initialize(*initial_flags)
  @value = 0
  initial_flags.each { |f| set(f) }
end

Class Method Details

.flag(name, position) ⇒ void

This method returns an undefined value.

Define a named flag at the given bit position

Parameters:

  • name (Symbol)

    the flag name

  • position (Integer)

    the bit position (0-based)

Raises:



31
32
33
34
35
36
37
38
39
40
41
# File 'lib/philiprehberger/bit_field.rb', line 31

def flag(name, position)
  raise Error, 'position must be a non-negative integer' unless position.is_a?(Integer) && position >= 0
  raise Error, "flag #{name} already defined" if flags_map.key?(name)
  raise Error, "position #{position} already used" if flags_map.any? { |_, p| p == position }

  flags_map[name] = position

  define_method(:"#{name}?") do
    flag_set?(name)
  end
end

.flagsArray<Symbol>

Return all defined flag names

Returns:

  • (Array<Symbol>)


61
62
63
# File 'lib/philiprehberger/bit_field.rb', line 61

def flags
  flags_map.keys
end

.flags_mapObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



118
119
120
# File 'lib/philiprehberger/bit_field.rb', line 118

def flags_map
  @flags_map ||= {}
end

.from_h(hash) ⇒ Base

Create an instance from a hash

Parameters:

  • hash (Hash)

    hash with :flags/:value or “flags”/“value” keys

Returns:

Raises:



110
111
112
113
114
115
# File 'lib/philiprehberger/bit_field.rb', line 110

def from_h(hash)
  value = hash[:value] || hash['value']
  raise Error, 'hash must include a value key' if value.nil?

  from_i(value)
end

.from_i(value) ⇒ Base

Create an instance from an integer value

Parameters:

  • value (Integer)

    the integer representation

Returns:

Raises:



89
90
91
92
93
94
95
# File 'lib/philiprehberger/bit_field.rb', line 89

def from_i(value)
  raise Error, 'value must be a non-negative integer' unless value.is_a?(Integer) && value >= 0

  instance = new
  instance.instance_variable_set(:@value, value)
  instance
end

.from_json(json_string) ⇒ Base

Create an instance from a JSON string

Parameters:

  • json_string (String)

    JSON with “flags” and “value” keys

Returns:



101
102
103
104
# File 'lib/philiprehberger/bit_field.rb', line 101

def from_json(json_string)
  data = JSON.parse(json_string)
  from_h(data)
end

.group(name, flag_names) ⇒ void

This method returns an undefined value.

Define a named group of flags

Parameters:

  • name (Symbol)

    the group name

  • flag_names (Array<Symbol>)

    the flags in this group

Raises:



48
49
50
51
52
53
54
55
56
# File 'lib/philiprehberger/bit_field.rb', line 48

def group(name, flag_names)
  raise Error, "group #{name} already defined" if groups_map.key?(name)

  flag_names.each do |f|
    raise Error, "unknown flag #{f} in group #{name}" unless flags_map.key?(f)
  end

  groups_map[name] = flag_names.dup.freeze
end

.groupsHash{Symbol => Array<Symbol>}

Return all defined group definitions

Returns:

  • (Hash{Symbol => Array<Symbol>})


68
69
70
# File 'lib/philiprehberger/bit_field.rb', line 68

def groups
  groups_map.dup
end

.groups_mapObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



123
124
125
# File 'lib/philiprehberger/bit_field.rb', line 123

def groups_map
  @groups_map ||= {}
end

.inherited(subclass) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



128
129
130
131
132
# File 'lib/philiprehberger/bit_field.rb', line 128

def inherited(subclass)
  super
  subclass.instance_variable_set(:@flags_map, flags_map.dup)
  subclass.instance_variable_set(:@groups_map, groups_map.dup)
end

.strict(*initial_flags) ⇒ Base

Create an instance that raises on unknown flag names

Parameters:

  • initial_flags (Array<Symbol>)

    flags to set initially

Returns:

Raises:

  • (Error)

    if any flag name is not defined



77
78
79
80
81
82
83
# File 'lib/philiprehberger/bit_field.rb', line 77

def strict(*initial_flags)
  initial_flags.each do |f|
    raise Error, "unknown flag: #{f}" unless flags_map.key?(f)
  end

  new(*initial_flags)
end

Instance Method Details

#&(other) ⇒ Base

Bitwise AND

Parameters:

  • other (Base)

    another bit field of the same type

Returns:

Raises:



316
317
318
319
320
# File 'lib/philiprehberger/bit_field.rb', line 316

def &(other)
  raise Error, 'cannot combine different bit field types' unless other.is_a?(self.class)

  self.class.from_i(@value & other.to_i)
end

#<=>(other) ⇒ Integer?

Compare by integer value

Parameters:

  • other (Base)

    another bit field

Returns:

  • (Integer, nil)


336
337
338
339
340
# File 'lib/philiprehberger/bit_field.rb', line 336

def <=>(other)
  return nil unless other.is_a?(self.class)

  @value <=> other.to_i
end

#==(other) ⇒ Boolean Also known as: eql?

Equality check

Parameters:

  • other (Base)

    another bit field

Returns:

  • (Boolean)


346
347
348
# File 'lib/philiprehberger/bit_field.rb', line 346

def ==(other)
  other.is_a?(self.class) && @value == other.to_i
end

#^(other) ⇒ Base

Bitwise XOR

Parameters:

  • other (Base)

    another bit field of the same type

Returns:

Raises:



326
327
328
329
330
# File 'lib/philiprehberger/bit_field.rb', line 326

def ^(other)
  raise Error, 'cannot combine different bit field types' unless other.is_a?(self.class)

  self.class.from_i(@value ^ other.to_i)
end

#added_flags(other) ⇒ Array<Symbol>

Return flags set in self but not in other

Parameters:

  • other (Base)

    another bit field of the same type

Returns:

  • (Array<Symbol>)

Raises:



361
362
363
364
365
# File 'lib/philiprehberger/bit_field.rb', line 361

def added_flags(other)
  raise Error, 'cannot compare different bit field types' unless other.is_a?(self.class)

  self.class.flags.select { |f| flag_set?(f) && !other.flag_set?(f) }
end

#clear(flag) ⇒ self

Clear a flag

Parameters:

  • flag (Symbol)

    the flag name

Returns:

  • (self)


175
176
177
178
179
# File 'lib/philiprehberger/bit_field.rb', line 175

def clear(flag)
  pos = position_for(flag)
  @value &= ~(1 << pos)
  self
end

#clear_allself

Clear all defined flags

Returns:

  • (self)


202
203
204
205
# File 'lib/philiprehberger/bit_field.rb', line 202

def clear_all
  @value = 0
  self
end

#clear_flags(*flag_names) ⇒ self

Clear multiple specific flags at once

Parameters:

  • flag_names (Array<Symbol>)

    flags to clear

Returns:

  • (self)


220
221
222
223
# File 'lib/philiprehberger/bit_field.rb', line 220

def clear_flags(*flag_names)
  flag_names.each { |f| clear(f) }
  self
end

#clear_group(group_name) ⇒ self

Clear all flags in a group

Parameters:

  • group_name (Symbol)

    the group name

Returns:

  • (self)


238
239
240
241
# File 'lib/philiprehberger/bit_field.rb', line 238

def clear_group(group_name)
  group_flags(group_name).each { |f| clear(f) }
  self
end

#flag_clear?(flag) ⇒ Boolean

Check if a flag is clear (not set)

Parameters:

  • flag (Symbol)

    the flag name

Returns:

  • (Boolean)


157
158
159
# File 'lib/philiprehberger/bit_field.rb', line 157

def flag_clear?(flag)
  !flag_set?(flag)
end

#flag_set?(flag) ⇒ Boolean

Check if a flag is set

Parameters:

  • flag (Symbol)

    the flag name

Returns:

  • (Boolean)


148
149
150
151
# File 'lib/philiprehberger/bit_field.rb', line 148

def flag_set?(flag)
  pos = position_for(flag)
  @value.anybits?(1 << pos)
end

#flagsArray<Symbol>

Return all defined flag names

Returns:

  • (Array<Symbol>)


284
285
286
# File 'lib/philiprehberger/bit_field.rb', line 284

def flags
  self.class.flags
end

#group_any_set?(group_name) ⇒ Boolean

Check if any flag in a group is set

Parameters:

  • group_name (Symbol)

    the group name

Returns:

  • (Boolean)


255
256
257
# File 'lib/philiprehberger/bit_field.rb', line 255

def group_any_set?(group_name)
  group_flags(group_name).any? { |f| flag_set?(f) }
end

#group_none_set?(group_name) ⇒ Boolean

Check if no flags in a group are set

Parameters:

  • group_name (Symbol)

    the group name

Returns:

  • (Boolean)


263
264
265
# File 'lib/philiprehberger/bit_field.rb', line 263

def group_none_set?(group_name)
  group_flags(group_name).none? { |f| flag_set?(f) }
end

#group_set?(group_name) ⇒ Boolean

Check if all flags in a group are set

Parameters:

  • group_name (Symbol)

    the group name

Returns:

  • (Boolean)


247
248
249
# File 'lib/philiprehberger/bit_field.rb', line 247

def group_set?(group_name)
  group_flags(group_name).all? { |f| flag_set?(f) }
end

#hashInteger

Hash code for use in Hash keys

Returns:

  • (Integer)


353
354
355
# File 'lib/philiprehberger/bit_field.rb', line 353

def hash
  [self.class, @value].hash
end

#removed_flags(other) ⇒ Array<Symbol>

Return flags set in other but not in self

Parameters:

  • other (Base)

    another bit field of the same type

Returns:

  • (Array<Symbol>)

Raises:



371
372
373
374
375
# File 'lib/philiprehberger/bit_field.rb', line 371

def removed_flags(other)
  raise Error, 'cannot compare different bit field types' unless other.is_a?(self.class)

  self.class.flags.select { |f| !flag_set?(f) && other.flag_set?(f) }
end

#set(flag) ⇒ self

Set a flag

Parameters:

  • flag (Symbol)

    the flag name

Returns:

  • (self)


165
166
167
168
169
# File 'lib/philiprehberger/bit_field.rb', line 165

def set(flag)
  pos = position_for(flag)
  @value |= (1 << pos)
  self
end

#set_allself

Set all defined flags

Returns:

  • (self)


194
195
196
197
# File 'lib/philiprehberger/bit_field.rb', line 194

def set_all
  self.class.flags.each { |f| set(f) }
  self
end

#set_flags(*flag_names) ⇒ self

Set multiple specific flags at once

Parameters:

  • flag_names (Array<Symbol>)

    flags to set

Returns:

  • (self)


211
212
213
214
# File 'lib/philiprehberger/bit_field.rb', line 211

def set_flags(*flag_names)
  flag_names.each { |f| set(f) }
  self
end

#set_group(group_name) ⇒ self

Set all flags in a group

Parameters:

  • group_name (Symbol)

    the group name

Returns:

  • (self)


229
230
231
232
# File 'lib/philiprehberger/bit_field.rb', line 229

def set_group(group_name)
  group_flags(group_name).each { |f| set(f) }
  self
end

#to_aArray<Symbol>

Return an array of set flag names

Returns:

  • (Array<Symbol>)


277
278
279
# File 'lib/philiprehberger/bit_field.rb', line 277

def to_a
  self.class.flags.select { |f| flag_set?(f) }
end

#to_hHash{Symbol => Object}

Return a hash representation

Returns:

  • (Hash{Symbol => Object})


291
292
293
# File 'lib/philiprehberger/bit_field.rb', line 291

def to_h
  { flags: to_a, value: @value }
end

#to_iInteger

Return the integer representation

Returns:

  • (Integer)


270
271
272
# File 'lib/philiprehberger/bit_field.rb', line 270

def to_i
  @value
end

#to_json(*_args) ⇒ String

Return a JSON string representation

Returns:

  • (String)


298
299
300
# File 'lib/philiprehberger/bit_field.rb', line 298

def to_json(*_args)
  { flags: to_a.map(&:to_s), value: @value }.to_json
end

#toggle(flag) ⇒ self

Toggle a flag

Parameters:

  • flag (Symbol)

    the flag name

Returns:

  • (self)


185
186
187
188
189
# File 'lib/philiprehberger/bit_field.rb', line 185

def toggle(flag)
  pos = position_for(flag)
  @value ^= (1 << pos)
  self
end

#|(other) ⇒ Base

Bitwise OR

Parameters:

  • other (Base)

    another bit field of the same type

Returns:

Raises:



306
307
308
309
310
# File 'lib/philiprehberger/bit_field.rb', line 306

def |(other)
  raise Error, 'cannot combine different bit field types' unless other.is_a?(self.class)

  self.class.from_i(@value | other.to_i)
end