bit_magic 0.1.1
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 +7 -0
- data/.coco.yml +4 -0
- data/.gitignore +10 -0
- data/.travis.yml +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +24 -0
- data/LICENSE.txt +21 -0
- data/README.md +290 -0
- data/Rakefile +39 -0
- data/TODO.md +25 -0
- data/bin/console +14 -0
- data/bin/setup +9 -0
- data/bit_magic.gemspec +30 -0
- data/lib/bit_magic.rb +9 -0
- data/lib/bit_magic/adapters/active_record_adapter.rb +250 -0
- data/lib/bit_magic/adapters/base.rb +61 -0
- data/lib/bit_magic/adapters/magician.rb +226 -0
- data/lib/bit_magic/adapters/mongoid_adapter.rb +233 -0
- data/lib/bit_magic/bit_field.rb +103 -0
- data/lib/bit_magic/bits.rb +285 -0
- data/lib/bit_magic/bits_generator.rb +399 -0
- data/lib/bit_magic/error.rb +7 -0
- data/lib/bit_magic/railtie.rb +28 -0
- data/lib/bit_magic/version.rb +7 -0
- metadata +129 -0
@@ -0,0 +1,226 @@
|
|
1
|
+
module BitMagic
|
2
|
+
module Adapters
|
3
|
+
# A container for bit_magic settings and fields
|
4
|
+
# Meant to be used internally by the Base adapter (and by extension, all adapters).
|
5
|
+
#
|
6
|
+
# @attr_reader [Hash] field_options bit_magic options that define fields
|
7
|
+
# @attr_reader [Hash] action_options bit_magic options that define settings
|
8
|
+
# @attr_reader [Hash] field_list name => bits key pairs based off field_options
|
9
|
+
# @attr_reader [Integer] bits_length total number of bits defined in field_options
|
10
|
+
# @attr_reader [Integer] max_bit the largest bit defined in field_options
|
11
|
+
# @attr_reader [Symbol] bit_magic_name the name given to bit_magic
|
12
|
+
class Magician
|
13
|
+
|
14
|
+
DEFAULT_ACTION_OPTIONS = Bits::DEFAULT_OPTIONS.merge({
|
15
|
+
:query_by_value => true, # can be true, false, or a number of bits
|
16
|
+
:helpers => true, # TODO: enable deeper access control over injected helper methods
|
17
|
+
:bits_wrapper => Bits,
|
18
|
+
:bits_generator => BitsGenerator,
|
19
|
+
}).freeze
|
20
|
+
|
21
|
+
# Checks if the key is a a field key definition (Integer or Range, defining
|
22
|
+
# bit or bit ranges in question).
|
23
|
+
#
|
24
|
+
# @param [Integer, Range<Integer>] checkValue an integer bit or bit range
|
25
|
+
# to check if it is a valid bit index
|
26
|
+
#
|
27
|
+
# @return [Integer, Array<Integer>] will return an integer or an array of
|
28
|
+
# integers if the checkValue is a valid bit index or bit range
|
29
|
+
def self.field_key_value_check(check)
|
30
|
+
if check.is_a?(Integer)
|
31
|
+
check
|
32
|
+
elsif check.is_a?(Range)
|
33
|
+
list = check.to_a
|
34
|
+
valid = list.reduce(true) {|m, i| m && i.is_a?(Integer) }
|
35
|
+
list if valid and list.length > 0
|
36
|
+
elsif check.is_a?(Array)
|
37
|
+
valid = true
|
38
|
+
list = check.collect {|i|
|
39
|
+
key = self.field_key_value_check(i)
|
40
|
+
key.nil? ? (valid = false; break;) : key
|
41
|
+
}.flatten
|
42
|
+
list if valid and list.length > 0
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Separate field and action options from the options passed to bit_magic
|
47
|
+
#
|
48
|
+
# Valid keys for field options are:
|
49
|
+
# Integer - for a specific bit position
|
50
|
+
# Range - for a range of bits
|
51
|
+
# Array<Integer> - an array of bit positions
|
52
|
+
# The value of the key-pair should be a Symbol.
|
53
|
+
#
|
54
|
+
# Action options are everything else that's not a field option
|
55
|
+
#
|
56
|
+
# @param [Hash] options the options passed to bit_magic
|
57
|
+
#
|
58
|
+
# @return [Hash, Hash] returns an array of two options [field, actions]
|
59
|
+
# field options and action options, in that order respectively
|
60
|
+
def self.options_splitter(options = {})
|
61
|
+
field_opts = {}
|
62
|
+
action_opts = DEFAULT_ACTION_OPTIONS.dup
|
63
|
+
|
64
|
+
options.each_pair do |check_key, value|
|
65
|
+
if key = self.field_key_value_check(check_key)
|
66
|
+
field_opts[key] = value
|
67
|
+
else
|
68
|
+
if (!options[:allow_failed_fields]) and (check_key.is_a?(Integer) or check_key.is_a?(Range) or check_key.is_a?(Array))
|
69
|
+
raise BitMagic::FieldError.new("key-pair expected to be a valid field option, but it is not: #{check_key.inspect} => #{value.inspect}. If this is an action option, you can disable this error by passing ':allow_failed_fields => true' as an option")
|
70
|
+
end
|
71
|
+
action_opts[check_key] = value
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
[field_opts.freeze, action_opts.freeze]
|
76
|
+
end
|
77
|
+
|
78
|
+
attr_reader :field_options, :action_options
|
79
|
+
attr_reader :field_list
|
80
|
+
attr_reader :bits_length, :max_bit
|
81
|
+
attr_reader :bit_magic_name
|
82
|
+
|
83
|
+
# Initialize a new Magician, a container for bit_magic settings and fields
|
84
|
+
#
|
85
|
+
# @param [Symbol] name the name to be used as a namespace
|
86
|
+
# @param [Hash] options the options given to bit_magic. Keys that are
|
87
|
+
# Integer, Arrays or Range objects are treated as bit allocations, their
|
88
|
+
# value should be the name of the bit field. Keys that are anything else
|
89
|
+
# (usually Symbol) are treated as action options or settings.
|
90
|
+
#
|
91
|
+
# @example Initialize a Magician (usually you would not do this directly)
|
92
|
+
# magician = Magician.new(:example, 0 => :is_odd, [1, 2] => :eyes, 3..6 => :fingers, default: 7)
|
93
|
+
# # here, field names are :is_odd, :eyes, and :fingers
|
94
|
+
# # with bits indices 0, [1, 2], and [3, 4, 5, 6] respectively
|
95
|
+
# # default is an action option, set to 7
|
96
|
+
#
|
97
|
+
# @return a Magician
|
98
|
+
def initialize(name, options = {})
|
99
|
+
@bit_magic_name = name
|
100
|
+
@field_options, @action_options = self.class.options_splitter(options)
|
101
|
+
validate_field_options!
|
102
|
+
end
|
103
|
+
|
104
|
+
# Define helper methods on the instance, namespaced to the name given in
|
105
|
+
# the bit_magic invocation.
|
106
|
+
#
|
107
|
+
# This is an internal method, only meant to be used by the Base adapter to
|
108
|
+
# during its setup phase. Should not be used directly.
|
109
|
+
#
|
110
|
+
# Methods that are defined is namespaced to the name during initialization.
|
111
|
+
# Referred to here as NAMESPACE. These methods are available on instances.
|
112
|
+
#
|
113
|
+
# NAMESPACE - defined by Base adapter, returns a Bits wrapper
|
114
|
+
# NAMESPACE_enabled?(*field_names) - checks if all the given field names
|
115
|
+
# or bit indices are enabled (value > 0)
|
116
|
+
# NAMESPACE_disabled?(*field_names) - checks if all the given field names
|
117
|
+
# or bit indices are disabled (value == 0)
|
118
|
+
#
|
119
|
+
# The following are helpers, defined based on field names during initialization
|
120
|
+
# Refered to here as 'name'.
|
121
|
+
#
|
122
|
+
# name - returns the value of the field
|
123
|
+
# name=(new_value) - sets the value of the field to the new value
|
124
|
+
# name? - available only if the field is a single bit, returns true if
|
125
|
+
# the value of the bit is 1, or false if 0
|
126
|
+
#
|
127
|
+
#
|
128
|
+
# @param [Class] klass the class to inject methods into.
|
129
|
+
#
|
130
|
+
# @return nothing useful.
|
131
|
+
def define_bit_magic_methods(klass)
|
132
|
+
names = @field_list
|
133
|
+
bit_magic_name = @bit_magic_name
|
134
|
+
|
135
|
+
klass.instance_eval do
|
136
|
+
|
137
|
+
define_method(:"#{bit_magic_name}_enabled?") do |*fields|
|
138
|
+
self.send(bit_magic_name).enabled?(*fields)
|
139
|
+
end
|
140
|
+
|
141
|
+
define_method(:"#{bit_magic_name}_disabled?") do |*fields|
|
142
|
+
self.send(bit_magic_name).disabled?(*fields)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
if @action_options[:helpers]
|
147
|
+
|
148
|
+
klass.instance_eval do
|
149
|
+
names.each_pair do |name, bits|
|
150
|
+
|
151
|
+
define_method(:"#{name}") do
|
152
|
+
self.send(bit_magic_name)[name]
|
153
|
+
end
|
154
|
+
|
155
|
+
define_method(:"#{name}=") do |val|
|
156
|
+
self.send(bit_magic_name)[name] = val
|
157
|
+
end
|
158
|
+
|
159
|
+
if bits.is_a?(Integer) or bits.length == 1
|
160
|
+
define_method(:"#{name}?") do
|
161
|
+
self.send(bit_magic_name)[name] == 1
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
169
|
+
|
170
|
+
end
|
171
|
+
|
172
|
+
# List of bits defined in @field_options
|
173
|
+
#
|
174
|
+
# @return [Array<Integer>] an array of bits defined in @field_options
|
175
|
+
def bits
|
176
|
+
@field_list.values.flatten
|
177
|
+
end
|
178
|
+
|
179
|
+
# Used by the Base#bit_magic to create a Bits wrapper around instances
|
180
|
+
#
|
181
|
+
# @return [Class] a Bits class
|
182
|
+
def bits_wrapper
|
183
|
+
self.action_options[:bits_wrapper] || Bits
|
184
|
+
end
|
185
|
+
|
186
|
+
# Used by adapters to generate value lists for particular bit operations
|
187
|
+
#
|
188
|
+
# @return [BitsGenerator] a BitsGenerator object with this magician's field
|
189
|
+
# list
|
190
|
+
def bits_generator
|
191
|
+
@bits_generator ||= (self.action_options[:bits_generator] || BitsGenerator).new self
|
192
|
+
end
|
193
|
+
|
194
|
+
# Inspect this object. This is customized just to shorten the output to
|
195
|
+
# actually be readable.
|
196
|
+
def inspect
|
197
|
+
"#<#{self.class.to_s} name=#{@bit_magic_name} field_list=#{@field_list.inspect}>"
|
198
|
+
end
|
199
|
+
|
200
|
+
protected
|
201
|
+
# Internal use only. Sets @field_list @bits_length and @max_bit from @field_options
|
202
|
+
def validate_field_options!
|
203
|
+
@field_list = {}
|
204
|
+
|
205
|
+
@field_options.each_pair do |bits, name|
|
206
|
+
name = name.to_sym if name.is_a?(String)
|
207
|
+
|
208
|
+
if name.is_a?(Symbol)
|
209
|
+
raise FieldError.new("'#{name}' defined more than once") if @field_list.has_key?(name)
|
210
|
+
@field_list[name] = bits
|
211
|
+
else
|
212
|
+
raise FieldError.new("field name must be a symbol or string, #{name.inspect} is not")
|
213
|
+
end
|
214
|
+
|
215
|
+
end
|
216
|
+
|
217
|
+
bits = self.bits
|
218
|
+
@bits_length = bits.uniq.length
|
219
|
+
@max_bit = bits.max
|
220
|
+
@field_list.freeze
|
221
|
+
end
|
222
|
+
|
223
|
+
end
|
224
|
+
|
225
|
+
end
|
226
|
+
end
|
@@ -0,0 +1,233 @@
|
|
1
|
+
require_relative './base'
|
2
|
+
|
3
|
+
module BitMagic
|
4
|
+
module Adapters
|
5
|
+
# This is the adapter for Mongoid. It's implemented as a concern to be
|
6
|
+
# included inside Mongoid::Document models.
|
7
|
+
#
|
8
|
+
# It's expected that you have an integer column (default name 'flags',
|
9
|
+
# override using the attribute_name option). It's suggested, though not
|
10
|
+
# required, that you set the default value same as the bit_magic default.
|
11
|
+
#
|
12
|
+
# If you have more than one model that you want to use BitMagic in, it's
|
13
|
+
# recommended that you just include this adapter globally:
|
14
|
+
# require 'bit_magic/adapters/mongoid_adapter'
|
15
|
+
# Mongoid::Document.include BitMagic::Adapters::MongoidAdapter
|
16
|
+
#
|
17
|
+
# Otherwise, you can include it on a per model basis before calling bit_magic
|
18
|
+
#
|
19
|
+
# class Example
|
20
|
+
# include Mongoid::Document
|
21
|
+
# # this line below can be excluded if you included the adapter globally
|
22
|
+
# include BitMagic::Adapters::MongoidAdapter
|
23
|
+
#
|
24
|
+
# bit_magic :settings, 0 => :is_odd, [1, 2, 3] => :amount, 4 => :is_cool
|
25
|
+
# field :flags, type: Integer, default: 0
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# After that, you can start using query helpers and instance helpers.
|
29
|
+
# Query helpers return a standard Mongoid::Criteria, so you can do everything
|
30
|
+
# you normally would in a query (like chaining additional conditions).
|
31
|
+
# Instance helpers are wrapped around by a Bits object, in this case, 'settings'
|
32
|
+
# but also have helper methods added based on the name of the fields.
|
33
|
+
#
|
34
|
+
module MongoidAdapter
|
35
|
+
VERSION = "0.1.0".freeze
|
36
|
+
extend ActiveSupport::Concern
|
37
|
+
|
38
|
+
included do
|
39
|
+
self.extend Base
|
40
|
+
end
|
41
|
+
|
42
|
+
module ClassMethods
|
43
|
+
# Cast the given value into a Boolean, follows Mongoid::Boolean rules
|
44
|
+
BIT_MAGIC_BOOLEAN_CASTER = lambda do |val|
|
45
|
+
!!Mongoid::Boolean.mongoize(val)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Adapter options specific to this adapter
|
49
|
+
#
|
50
|
+
# :named_scopes Enables (true) or disables (false) individual scopes to
|
51
|
+
# query fields
|
52
|
+
#
|
53
|
+
# :query_by_value whether to use bitwise operations or IN (?) when querying
|
54
|
+
# by default will use IN (?) if the total bits defined by bit_magic is
|
55
|
+
# less than or equal to 8. true to always query by value, false to always
|
56
|
+
# query using bitwise operations
|
57
|
+
BIT_MAGIC_ADAPTER_DEFAULTS = {
|
58
|
+
:bool_caster => BIT_MAGIC_BOOLEAN_CASTER,
|
59
|
+
:named_scopes => true,
|
60
|
+
:query_by_value => 8
|
61
|
+
}.freeze
|
62
|
+
|
63
|
+
# Method used to set adapter defaults as options to Magician,
|
64
|
+
# Used by the bit_magic definition to add custom options to the magician
|
65
|
+
#
|
66
|
+
# @param [Hash] options some options list
|
67
|
+
#
|
68
|
+
# @return new options list including our custom defaults
|
69
|
+
def bit_magic_adapter_defaults(options)
|
70
|
+
BIT_MAGIC_ADAPTER_DEFAULTS.merge(options)
|
71
|
+
end
|
72
|
+
|
73
|
+
# This method is called by Base#bit_magic after setting up the magician
|
74
|
+
# Here, we inject query helpers, scopes, and other useful methods
|
75
|
+
#
|
76
|
+
# Query helpers: (NAMESPACE is the name given to bit_magic)
|
77
|
+
# All the methods that generate where queries take an optional options
|
78
|
+
# hash as the last value. Can be used to alter options given to bit_magic.
|
79
|
+
# eg: passing '{query_by_value: false}' as the last argument will force
|
80
|
+
# the query to generate bitwise operations instead of '$in => []' queries
|
81
|
+
#
|
82
|
+
# NAMESPACE_query_helper(field_names = nil)
|
83
|
+
# an internal method used by other query helpers
|
84
|
+
# NAMESPACE_where_in(array, column_name = nil)
|
85
|
+
# generates a 'column_name => {:$in => [...]}' query for the array numbers
|
86
|
+
# column_name defaults to attribute_name in the options
|
87
|
+
# NAMESPACE_with_all(*field_names, options = {})
|
88
|
+
# takes one or more field names, and queries for values where ALL of
|
89
|
+
# them are enabled. For fields with multiple bits, they must be max value
|
90
|
+
# This is the equivalent of: field[0] and field[1] and field[2] ...
|
91
|
+
# NAMESPACE_with_any(*field_names, options = {})
|
92
|
+
# takes one or more field names, and queries for values where any of
|
93
|
+
# them are enabled
|
94
|
+
# This is the equivalent of: field[0] or field[1] or field[2] ...
|
95
|
+
# NAMESPACE_without_any(*field_names, options = {})
|
96
|
+
# takes one or more field names, and queries for values where at least
|
97
|
+
# one of them is disabled. For fields with multiple bits, any value
|
98
|
+
# other than maximum number
|
99
|
+
# This is the equivalent of "!field[0] or !field[1] or !field[2] ..."
|
100
|
+
# NAMESPACE_without_all(*field_names, options = {})
|
101
|
+
# takes one or more field names and queries for values where none of
|
102
|
+
# them are enabled (all disabled). For fields with multiple bits,
|
103
|
+
# value must be zero.
|
104
|
+
# This is the equivalent of: !field[0] and !field[1] and !field[2] ...
|
105
|
+
# NAMESPACE_equals(field_value_list, options = {})
|
106
|
+
# * this will truncate values to match the number of bits available
|
107
|
+
# field_value_list is a Hash with field_name => value key-pairs.
|
108
|
+
# generates a query that matches the bits to the value, exactly
|
109
|
+
# This is the equivalent of: field[0] = val and field[1] = value ...
|
110
|
+
#
|
111
|
+
# Additional named scopes
|
112
|
+
# These can be disabled by passing 'named_scopes: false' as an option
|
113
|
+
# FIELD is the field name for the bit/bit range
|
114
|
+
#
|
115
|
+
# NAMESPACE_FIELD
|
116
|
+
# queries for values where FIELD has been enabled
|
117
|
+
# NAMESPACE_not_FIELD
|
118
|
+
# queries for values where FIELD has been disabled (not enabled)
|
119
|
+
# NAMESPACE_FIELD_equals(value)
|
120
|
+
# * only exists for fields with more than one bit
|
121
|
+
# queries for values where FIELD is exactly equal to value
|
122
|
+
#
|
123
|
+
# @param [Symbol] name the namespace (prefix) for our query helpers
|
124
|
+
#
|
125
|
+
# @return nothing important
|
126
|
+
def bit_magic_adapter(name)
|
127
|
+
query_prep = :"#{name}_query_helper"
|
128
|
+
query_in = :"#{name}_where_in"
|
129
|
+
|
130
|
+
self.class_eval do
|
131
|
+
define_singleton_method(query_prep) do |field_names = nil|
|
132
|
+
magician = @bit_magic_fields[name]
|
133
|
+
bit_gen = magician.bits_generator
|
134
|
+
|
135
|
+
options = (field_names.is_a?(Array) and field_names.last.is_a?(Hash)) ? field_names.pop : {}
|
136
|
+
|
137
|
+
by_value = options.key?(:query_by_value) ? options[:query_by_value] : magician.action_options[:query_by_value]
|
138
|
+
|
139
|
+
by_value = (magician.bits_length <= by_value) if by_value.is_a?(Integer)
|
140
|
+
column_name = options[:column_name] || magician.action_options[:column_name] || magician.action_options[:attribute_name]
|
141
|
+
|
142
|
+
[magician, bit_gen, by_value, column_name]
|
143
|
+
end
|
144
|
+
|
145
|
+
define_singleton_method(query_in) do |column_name, arr|
|
146
|
+
where(column_name => {:$in => arr})
|
147
|
+
end
|
148
|
+
|
149
|
+
define_singleton_method(:"#{name}_with_all") do |*field_names|
|
150
|
+
magician, bit_gen, by_value, column_name = self.send(query_prep, field_names)
|
151
|
+
|
152
|
+
if by_value === true
|
153
|
+
self.send(query_in, column_name, bit_gen.all_of(*field_names))
|
154
|
+
else
|
155
|
+
where(column_name => {:$bitsAllSet => bit_gen.all_of_number(*field_names)})
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
define_singleton_method(:"#{name}_without_any") do |*field_names|
|
160
|
+
magician, bit_gen, by_value, column_name = self.send(query_prep, field_names)
|
161
|
+
|
162
|
+
if by_value === true
|
163
|
+
self.send(query_in, column_name, bit_gen.instead_of(*field_names))
|
164
|
+
else
|
165
|
+
where(column_name => {:$bitsAnyClear => bit_gen.any_of_number(*field_names)})
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
define_singleton_method(:"#{name}_without_all") do |*field_names|
|
170
|
+
magician, bit_gen, by_value, column_name = self.send(query_prep, field_names)
|
171
|
+
|
172
|
+
if by_value === true
|
173
|
+
self.send(query_in, column_name, bit_gen.none_of(*field_names))
|
174
|
+
else
|
175
|
+
where(column_name => {:$bitsAllClear => bit_gen.any_of_number(*field_names)})
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# Query for if any of these bits are set.
|
180
|
+
define_singleton_method(:"#{name}_with_any") do |*field_names|
|
181
|
+
magician, bit_gen, by_value, column_name = self.send(query_prep, field_names)
|
182
|
+
|
183
|
+
if by_value === true
|
184
|
+
self.send(query_in, column_name, bit_gen.any_of(*field_names))
|
185
|
+
else
|
186
|
+
where(column_name => {:$bitsAnySet => bit_gen.any_of_number(*field_names)})
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
define_singleton_method(:"#{name}_equals") do |field_value, options = {}|
|
191
|
+
magician, bit_gen, by_value, column_name = self.send(query_prep, [options])
|
192
|
+
|
193
|
+
if by_value === true
|
194
|
+
self.send(query_in, column_name, bit_gen.equal_to(field_value))
|
195
|
+
else
|
196
|
+
all_num, none_num = bit_gen.equal_to_numbers(field_value)
|
197
|
+
where(column_name => {:$bitsAllSet => all_num, :$bitsAllClear => none_num})
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
end
|
202
|
+
|
203
|
+
|
204
|
+
if @bit_magic_fields and @bit_magic_fields[name] and @bit_magic_fields[name].action_options[:named_scopes]
|
205
|
+
fields = @bit_magic_fields[name].field_list
|
206
|
+
|
207
|
+
self.class_eval do
|
208
|
+
fields.each_pair do |field, value|
|
209
|
+
define_singleton_method(:"#{name}_#{field}") do
|
210
|
+
self.send(:"#{name}_with_all", field)
|
211
|
+
end
|
212
|
+
|
213
|
+
define_singleton_method(:"#{name}_not_#{field}") do
|
214
|
+
self.send(:"#{name}_without_all", field)
|
215
|
+
end
|
216
|
+
|
217
|
+
if value.is_a?(Array) and value.length > 1
|
218
|
+
define_singleton_method(:"#{name}_#{field}_equals") do |val|
|
219
|
+
self.send(:"#{name}_equals", field => val)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
end
|
224
|
+
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|