hierarchical_config 0.11 → 0.13

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ruby.yml +52 -0
  3. data/.gitignore +2 -0
  4. data/.rubocop.yml +68 -0
  5. data/.ruby-version +1 -0
  6. data/Gemfile +15 -2
  7. data/Gemfile.2.4 +19 -0
  8. data/Gemfile.2.4.lock +117 -0
  9. data/Gemfile.lock +124 -25
  10. data/README.md +0 -2
  11. data/bin/console +3 -3
  12. data/bin/tapioca +29 -0
  13. data/hierarchical_config.gemspec +14 -13
  14. data/lib/hierarchical_config/version.rb +3 -1
  15. data/lib/hierarchical_config.rb +164 -102
  16. data/sorbet/config +2 -0
  17. data/sorbet/rbi/annotations/activesupport.rbi +128 -0
  18. data/sorbet/rbi/annotations/rainbow.rbi +269 -0
  19. data/sorbet/rbi/gems/activesupport@7.0.4.2.rbi +16155 -0
  20. data/sorbet/rbi/gems/ast@2.4.2.rbi +584 -0
  21. data/sorbet/rbi/gems/binding_of_caller@1.0.0.rbi +55 -0
  22. data/sorbet/rbi/gems/coderay@1.1.3.rbi +3426 -0
  23. data/sorbet/rbi/gems/concurrent-ruby@1.2.2.rbi +11545 -0
  24. data/sorbet/rbi/gems/debug_inspector@1.1.0.rbi +23 -0
  25. data/sorbet/rbi/gems/diff-lcs@1.5.0.rbi +1083 -0
  26. data/sorbet/rbi/gems/i18n@1.12.0.rbi +2296 -0
  27. data/sorbet/rbi/gems/interception@0.5.rbi +138 -0
  28. data/sorbet/rbi/gems/json@2.6.3.rbi +1541 -0
  29. data/sorbet/rbi/gems/method_source@1.0.0.rbi +272 -0
  30. data/sorbet/rbi/gems/minitest@5.17.0.rbi +1457 -0
  31. data/sorbet/rbi/gems/netrc@0.11.0.rbi +158 -0
  32. data/sorbet/rbi/gems/parallel@1.22.1.rbi +277 -0
  33. data/sorbet/rbi/gems/parser@3.2.1.0.rbi +7252 -0
  34. data/sorbet/rbi/gems/pry-rescue@1.5.2.rbi +186 -0
  35. data/sorbet/rbi/gems/pry-stack_explorer@0.6.1.rbi +295 -0
  36. data/sorbet/rbi/gems/pry@0.14.2.rbi +10081 -0
  37. data/sorbet/rbi/gems/rainbow@3.1.1.rbi +402 -0
  38. data/sorbet/rbi/gems/rake@13.0.6.rbi +3018 -0
  39. data/sorbet/rbi/gems/rbi@0.0.16.rbi +3008 -0
  40. data/sorbet/rbi/gems/regexp_parser@2.7.0.rbi +3580 -0
  41. data/sorbet/rbi/gems/rexml@3.2.5.rbi +4717 -0
  42. data/sorbet/rbi/gems/rspec-core@3.12.1.rbi +10845 -0
  43. data/sorbet/rbi/gems/rspec-expectations@3.12.2.rbi +8100 -0
  44. data/sorbet/rbi/gems/rspec-mocks@3.12.3.rbi +5299 -0
  45. data/sorbet/rbi/gems/rspec-support@3.12.0.rbi +1611 -0
  46. data/sorbet/rbi/gems/rspec@3.12.0.rbi +82 -0
  47. data/sorbet/rbi/gems/rubocop-ast@1.27.0.rbi +6998 -0
  48. data/sorbet/rbi/gems/rubocop-performance@1.16.0.rbi +3004 -0
  49. data/sorbet/rbi/gems/rubocop@1.46.0.rbi +54549 -0
  50. data/sorbet/rbi/gems/ruby-progressbar@1.11.0.rbi +1239 -0
  51. data/sorbet/rbi/gems/spoom@1.1.15.rbi +2383 -0
  52. data/sorbet/rbi/gems/tapioca@0.11.1.rbi +3255 -0
  53. data/sorbet/rbi/gems/thor@1.2.1.rbi +3956 -0
  54. data/sorbet/rbi/gems/tzinfo@2.0.6.rbi +5917 -0
  55. data/sorbet/rbi/gems/unicode-display_width@2.4.2.rbi +65 -0
  56. data/sorbet/rbi/gems/unparser@0.6.7.rbi +4524 -0
  57. data/sorbet/rbi/gems/webrick@1.7.0.rbi +2555 -0
  58. data/sorbet/rbi/gems/yard-sorbet@0.8.0.rbi +441 -0
  59. data/sorbet/rbi/gems/yard@0.9.28.rbi +17841 -0
  60. data/sorbet/tapioca/config.yml +13 -0
  61. data/sorbet/tapioca/require.rb +4 -0
  62. metadata +74 -50
  63. data/.travis.yml +0 -6
@@ -1,84 +1,174 @@
1
- require 'ostruct'
1
+ # typed: strict
2
+
2
3
  require 'yaml'
3
4
  require 'erb'
4
5
  require 'set'
6
+ require 'sorbet-runtime'
7
+ require 'active_support'
8
+ require 'active_support/core_ext/hash/keys'
5
9
 
6
- require "hierarchical_config/version"
10
+ require 'hierarchical_config/version'
7
11
 
8
12
  module HierarchicalConfig
9
13
  REQUIRED = :REQUIRED
10
- #this is the incantation that works for ruby 1.8.7 (syck)
11
- YAML.add_builtin_type( 'REQUIRED' ){ REQUIRED }
12
- #and this works for 1.9.3 (Psych)
13
- YAML.add_domain_type( nil, 'REQUIRED' ){ REQUIRED }
14
-
15
- class OpenStruct < ::OpenStruct
16
- def method_missing( mid, *args ) # :nodoc:
17
- mname = mid.id2name
18
- len = args.length
19
- if mname.chomp!('=')
20
- if len != 1
21
- raise ArgumentError, "wrong number of arguments (#{len} for 1)", caller(1)
22
- end
23
- modifiable[new_ostruct_member(mname)] = args[0]
24
- elsif mname =~ /\?$/
25
- !!send(mname.gsub("?",""))
26
- elsif len == 0 && @table.key?( mid )
27
- @table[mid]
14
+ T.unsafe(YAML).add_domain_type(nil, 'REQUIRED'){REQUIRED}
15
+
16
+ ClassOrModule = T.type_alias{T.any(Class, Module)}
17
+
18
+ module ConfigStruct
19
+ extend T::Sig
20
+ include Kernel
21
+
22
+ sig{returns(T::Hash[Symbol, T.untyped])}
23
+ def to_hash
24
+ Hash[self.class.props.keys.map{|key| [key, item_to_hash(send(key))]}] # rubocop:disable Style/HashConversion
25
+ end
26
+
27
+ sig{params(key: T.any(String, Symbol)).returns(T.untyped)}
28
+ def [](key)
29
+ send(key)
30
+ end
31
+
32
+ private
33
+
34
+ sig{params(item: BasicObject).returns(T.any(BasicObject, T::Hash[T.untyped, T.untyped]))}
35
+ def item_to_hash(item)
36
+ case item
37
+ when ConfigStruct
38
+ item.to_hash
39
+ when Array
40
+ item.map{|i| item_to_hash(i)}
28
41
  else
29
- raise NoMethodError, "undefined method `#{mname}' for #{self}", caller(1)
42
+ item
30
43
  end
31
44
  end
45
+ end
46
+
47
+ @@root_index = T.let(0, Integer) # rubocop:disable Style/ClassVars
32
48
 
33
- def [](attribute)
34
- send(attribute)
49
+ class << self
50
+ extend T::Sig
51
+
52
+ sig{params(value: T.untyped, path: String).returns(T::Array[String])}
53
+ def detect_errors(value, path)
54
+ errors = T.let([], T::Array[String])
55
+ case value
56
+ when Hash
57
+ value.each do |key, item|
58
+ errors += detect_errors(item, "#{path}.#{key}")
59
+ end
60
+ when Array
61
+ value.each_with_index do |item, index|
62
+ errors += detect_errors(item, "#{path}[#{index}]")
63
+ end
64
+ when REQUIRED
65
+ errors << "#{path} is REQUIRED"
66
+ end
67
+ errors
35
68
  end
36
69
 
37
- alias :each :each_pair
70
+ sig{params(current_item: Object, name: String, parent_class: ClassOrModule).returns(T.any(Class, T::Types::Base))}
71
+ def build_types(current_item, name, parent_class)
72
+ case current_item
73
+ when Hash
74
+ new_type_name = ActiveSupport::Inflector.camelize(ActiveSupport::Inflector.underscore(name))
75
+
76
+ return Hash if current_item.keys.to_a.any?{|k| k =~ /^[0-9]/ || k =~ /[- ]/}
38
77
 
39
- def to_hash
40
- @table.inject({}) do |hash, key_value|
41
- key, value = *key_value
42
- hash[key] = item_to_hash(value)
43
- hash
78
+ new_type =
79
+ if parent_class.const_defined?(new_type_name, false)
80
+ parent_class.const_get(new_type_name, false)
81
+ else
82
+ parent_class.const_set(new_type_name, Class.new(T::Struct).tap{|c| c.include ConfigStruct})
83
+ end
84
+
85
+ current_item.each do |key, value|
86
+ next if new_type.props.key?(key.to_sym)
87
+
88
+ new_type.const key.to_sym, build_types(value, key, new_type)
89
+ new_type.send(:define_method, "#{key}?") do
90
+ !!send(key)
91
+ end
92
+ end
93
+
94
+ new_type
95
+ when Array
96
+ types = current_item.each_with_index.map do |item, index|
97
+ build_types(item, "#{name}_#{index}", parent_class)
98
+ end
99
+ case types.size
100
+ when 0
101
+ T.untyped
102
+ when 1
103
+ T::Array[types.first]
104
+ else
105
+ T::Array[T.unsafe(T).any(*types)]
106
+ end
107
+ else
108
+ current_item.class
44
109
  end
45
110
  end
46
111
 
47
- private
112
+ sig{params(current_item: Object, name: String, parent_class: ClassOrModule).returns(T.untyped)}
113
+ def build_config(current_item, name, parent_class)
114
+ case current_item
115
+ when Hash
116
+ return current_item.symbolize_keys if current_item.keys.to_a.any?{|k| k =~ /^[0-9]/ || k =~ /[- ]/}
48
117
 
49
- def item_to_hash(value)
50
- case value
118
+ current_type = parent_class.const_get(ActiveSupport::Inflector.camelize(name))
119
+ current_type.new(Hash[current_item.map{|key, value| [key.to_sym, build_config(value, key, current_type)]}]) # rubocop:disable Style/HashConversion
51
120
  when Array
52
- value.map{|item| item_to_hash(item)}
53
- when OpenStruct
54
- value.to_hash
121
+ current_item.each_with_index.map do |item, index|
122
+ build_config(item, "#{name}_#{index}", parent_class)
123
+ end.freeze
55
124
  else
56
- value
125
+ current_item.freeze
57
126
  end
58
127
  end
59
- end
60
128
 
61
- class << self
62
- def load_config( name, dir, environment, preprocess_with=:erb )
129
+ sig{returns(Class)}
130
+ def build_new_root
131
+ @@root_index += 1 # rubocop:disable Style/ClassVars
132
+ const_set("ConfigRoot#{@@root_index}", Class.new)
133
+ end
134
+
135
+ sig do
136
+ params(
137
+ name: String,
138
+ dir: String,
139
+ environment: String,
140
+ preprocess_with: T.nilable(Symbol),
141
+ root_class: ClassOrModule,
142
+ ).returns(T::Struct)
143
+ end
144
+ def load_config(name, dir, environment, preprocess_with = :erb, root_class = build_new_root)
63
145
  primary_config_file = "#{dir}/#{name}.yml"
64
146
  overrides_config_file = "#{dir}/#{name}-overrides.yml"
65
147
 
66
- config_hash = load_hash_for_env( primary_config_file, environment, preprocess_with )
148
+ config_hash = load_hash_for_env(primary_config_file, environment, preprocess_with)
67
149
 
68
- if File.exists?( overrides_config_file )
69
- overrides_config_hash = load_hash_for_env( overrides_config_file, environment, preprocess_with )
70
- config_hash = deep_merge( config_hash, overrides_config_hash )
150
+ if File.exist?(overrides_config_file)
151
+ overrides_config_hash = load_hash_for_env(overrides_config_file, environment, preprocess_with)
152
+ config_hash = deep_merge(config_hash, overrides_config_hash)
71
153
  end
72
154
 
73
- config_hash, errors = lock_down_and_ostructify!( config_hash, name, environment )
155
+ errors = detect_errors(config_hash, name)
156
+ raise errors.map{|error| "#{error} for #{environment}"}.inspect unless errors.empty?
74
157
 
75
- raise errors.inspect unless errors.empty?
158
+ build_types(config_hash, name, root_class)
76
159
 
77
- config_hash
160
+ build_config(config_hash, name, root_class)
78
161
  end
79
162
 
80
- def load_hash_for_env( file, environment, preprocess_with )
81
- file_contents = IO.read(file)
163
+ sig do
164
+ params(
165
+ file: String,
166
+ environment: String,
167
+ preprocess_with: T.nilable(Symbol),
168
+ ).returns(T::Hash[String, BasicObject])
169
+ end
170
+ def load_hash_for_env(file, environment, preprocess_with)
171
+ file_contents = File.read(file)
82
172
  yaml_contents = case preprocess_with
83
173
  when :erb
84
174
  ERB.new(file_contents).result
@@ -87,103 +177,75 @@ module HierarchicalConfig
87
177
  else
88
178
  raise "Unknown preprocessor <#{preprocess_with}>"
89
179
  end
90
- yaml_config = YAML::load(yaml_contents)
180
+ yaml_config = YAML.safe_load(yaml_contents)
91
181
 
92
182
  ordered_stanza_labels = []
93
183
  ordered_stanza_labels << 'defaults' if yaml_config.key? 'defaults'
94
- ordered_stanza_labels += yaml_config.keys.grep(/^defaults\[.*#{environment}/).sort_by{ |a| a.count(',') }
184
+ ordered_stanza_labels += yaml_config.keys.grep(/^defaults\[.*#{environment}/).sort_by{|a| a.count(',')}
95
185
  ordered_stanza_labels << environment if yaml_config.key? environment
96
186
 
97
187
  config = deep_merge_hashes_in_keys(ordered_stanza_labels, yaml_config)
98
188
 
99
189
  env_config_labels = []
100
190
  env_config_labels << 'env_vars' if yaml_config.key? 'env_vars'
101
- env_config_labels += yaml_config.keys.grep(/^env_vars\[.*#{environment}/).sort_by{ |a| a.count(',') }
191
+ env_config_labels += yaml_config.keys.grep(/^env_vars\[.*#{environment}/).sort_by{|a| a.count(',')}
102
192
 
103
193
  env_config = deep_merge_hashes_in_keys(env_config_labels, yaml_config)
104
194
  env_config = fill_in_env_vars(env_config)
105
195
 
106
196
  deep_merge(config, env_config)
107
-
108
197
  rescue StandardError => e
109
198
  raise <<-ERROR
110
199
  Error loading config from file #{file}.
111
- #{$!.inspect}
112
- #{$@}
200
+ #{$ERROR_INFO.inspect}
201
+ #{$ERROR_POSITION}
202
+ #{e}
113
203
  ERROR
114
204
  end
115
205
 
116
206
  private
117
207
 
208
+ sig do
209
+ params(keys: T::Array[String],
210
+ root_hash: T::Hash[String,
211
+ T::Hash[String, T.untyped]]).returns(T::Hash[T.untyped, T.untyped])
212
+ end
118
213
  def deep_merge_hashes_in_keys(keys, root_hash)
119
214
  keys.inject({}) do |acc, label|
120
- deep_merge( acc, root_hash[label] )
215
+ deep_merge(acc, T.must(root_hash[label]))
121
216
  end
122
217
  end
123
218
 
219
+ sig{params(hash: T::Hash[T.untyped, T.untyped]).returns(T::Hash[T.untyped, T.untyped])}
124
220
  def fill_in_env_vars(hash)
125
221
  r = {}
126
- hash.each do |key,value|
222
+ hash.each do |key, value|
127
223
  if value.is_a? Hash
128
224
  leaf_hash = fill_in_env_vars(value)
129
- r[key]=leaf_hash unless leaf_hash.keys.empty?
225
+ r[key] = leaf_hash unless leaf_hash.keys.empty?
130
226
  elsif !value.nil? && ENV.key?(value)
131
- r[key]=ENV[value]
227
+ r[key] = ENV.fetch(value, nil)
132
228
  end
133
229
  end
134
230
  r
135
231
  end
136
232
 
137
233
  # merges two hashes with nested hashes if present
138
- def deep_merge( hash1, hash2 )
234
+ sig do
235
+ params(hash1: T::Hash[T.untyped, T.untyped],
236
+ hash2: T::Hash[T.untyped, T.untyped]).returns(T::Hash[T.untyped, T.untyped])
237
+ end
238
+ def deep_merge(hash1, hash2)
139
239
  hash1 = hash1.dup
140
- ( hash1.keys + hash2.keys ).each do | key |
141
- if hash1.key?( key ) && hash2.key?( key ) &&
142
- hash1[key].is_a?( Hash ) && hash2[key].is_a?( Hash )
143
- hash1[key] = deep_merge( hash1[key], hash2[key] )
144
- elsif hash2.key?( key )
240
+ (hash1.keys + hash2.keys).each do |key|
241
+ if hash1.key?(key) && hash2.key?(key) &&
242
+ hash1[key].is_a?(Hash) && hash2[key].is_a?(Hash)
243
+ hash1[key] = deep_merge(hash1[key], hash2[key])
244
+ elsif hash2.key?(key)
145
245
  hash1[key] = hash2[key]
146
246
  end
147
247
  end
148
248
  hash1
149
249
  end
150
-
151
- # Mutator method that does three things:
152
- # * checks if any of the keys were required and not set. Upon finding
153
- # it adds key to the error set
154
- # * recursively sets open structs for deep hashes
155
- # * recursively freezes config objects
156
- def lock_down_and_ostructify!( _hash, path, environment)
157
- hash = Hash[_hash.map{|k,v|[k.to_s, v]}] #stringify keys
158
- errors = []
159
- hash.each do | key, value |
160
- hash[key], child_errors = lock_down_and_ostructify_item!(value, path + '.' + key, environment)
161
- errors += child_errors
162
- end
163
- return OpenStruct.new(hash).freeze, errors
164
- end
165
-
166
- def lock_down_and_ostructify_item!(value, path, environment)
167
- errors = []
168
- return_value = case value
169
- when Hash
170
- child_hash, child_errors = lock_down_and_ostructify!( value, path, environment )
171
- errors += child_errors
172
- child_hash
173
- when Array
174
- value.each_with_index.map do |item, index|
175
- child_item, child_errors = lock_down_and_ostructify_item!( item, "#{path}[#{index}]", environment )
176
- errors += child_errors
177
- child_item
178
- end.freeze
179
- when REQUIRED
180
- errors << "#{path} is REQUIRED for #{environment}"
181
- nil
182
- else
183
- value.freeze
184
- end
185
-
186
- return return_value, errors
187
- end
188
250
  end
189
251
  end
data/sorbet/config ADDED
@@ -0,0 +1,2 @@
1
+ --dir
2
+ .
@@ -0,0 +1,128 @@
1
+ # typed: strict
2
+
3
+ # DO NOT EDIT MANUALLY
4
+ # This file was pulled from a central RBI files repository.
5
+ # Please run `bin/tapioca annotations` to update it.
6
+
7
+ module ActiveSupport::Testing::Declarative
8
+ sig { params(name: String, block: T.proc.bind(T.untyped).void).void }
9
+ def test(name, &block); end
10
+ end
11
+
12
+ class ActiveSupport::EnvironmentInquirer
13
+ sig { returns(T::Boolean) }
14
+ def development?; end
15
+
16
+ sig { returns(T::Boolean) }
17
+ def production?; end
18
+
19
+ sig { returns(T::Boolean) }
20
+ def test?; end
21
+
22
+ # @method_missing: delegated to String through ActiveSupport::StringInquirer
23
+ sig { returns(T::Boolean) }
24
+ def staging?; end
25
+ end
26
+
27
+ module ActiveSupport::Testing::SetupAndTeardown::ClassMethods
28
+ sig { params(args: T.untyped, block: T.nilable(T.proc.bind(T.untyped).void)).void }
29
+ def setup(*args, &block); end
30
+
31
+ sig { params(args: T.untyped, block: T.nilable(T.proc.bind(T.untyped).void)).void }
32
+ def teardown(*args, &block); end
33
+ end
34
+
35
+ class ActiveSupport::TestCase
36
+ sig { params(args: T.untyped, block: T.nilable(T.proc.bind(T.attached_class).void)).void }
37
+ def self.setup(*args, &block); end
38
+
39
+ sig { params(args: T.untyped, block: T.nilable(T.proc.bind(T.attached_class).void)).void }
40
+ def self.teardown(*args, &block); end
41
+
42
+ sig { params(name: String, block: T.proc.bind(T.attached_class).void).void }
43
+ def self.test(name, &block); end
44
+ end
45
+
46
+ class Object
47
+ sig { returns(T::Boolean) }
48
+ def blank?; end
49
+
50
+ sig { returns(T::Boolean) }
51
+ def present?; end
52
+ end
53
+
54
+ class Hash
55
+ sig { returns(T::Boolean) }
56
+ def extractable_options?; end
57
+ end
58
+
59
+ class Array
60
+ sig { params(position: Integer).returns(T.self_type) }
61
+ def from(position); end
62
+
63
+ sig { params(position: Integer).returns(T.self_type) }
64
+ def to(position); end
65
+
66
+ sig { params(elements: T.untyped).returns(T::Array[T.untyped]) }
67
+ def including(*elements); end
68
+
69
+ sig { params(elements: T.untyped).returns(T.self_type) }
70
+ def excluding(*elements); end
71
+
72
+ sig { params(elements: T.untyped).returns(T.self_type) }
73
+ def without(*elements); end
74
+
75
+ sig { returns(T.nilable(Elem)) }
76
+ def second; end
77
+
78
+ sig { returns(T.nilable(Elem)) }
79
+ def third; end
80
+
81
+ sig { returns(T.nilable(Elem)) }
82
+ def fourth; end
83
+
84
+ sig { returns(T.nilable(Elem)) }
85
+ def fifth; end
86
+
87
+ sig { returns(T.nilable(Elem)) }
88
+ def forty_two; end
89
+
90
+ sig { returns(T.nilable(Elem)) }
91
+ def third_to_last; end
92
+
93
+ sig { returns(T.nilable(Elem)) }
94
+ def second_to_last; end
95
+
96
+ sig { params(options: T::Hash[T.untyped, T.untyped]).returns(String) }
97
+ def to_sentence(options = {}); end
98
+
99
+ sig { params(format: Symbol).returns(String) }
100
+ def to_fs(format = :default); end
101
+
102
+ sig { params(format: Symbol).returns(String) }
103
+ def to_formatted_s(format = :default); end
104
+
105
+ sig { returns(String) }
106
+ def to_xml; end
107
+
108
+ sig { returns(T::Hash[T.untyped, T.untyped]) }
109
+ def extract_options!; end
110
+
111
+ sig { type_parameters(:FillType).params(number: Integer, fill_with: T.type_parameter(:FillType), block: T.nilable(T.proc.params(group: T::Array[T.any(Elem, T.type_parameter(:FillType))]).void)).returns(T::Array[T::Array[T.any(Elem, T.type_parameter(:FillType))]]) }
112
+ def in_groups(number, fill_with = T.unsafe(nil), &block); end
113
+
114
+ sig { type_parameters(:FillType).params(number: Integer, fill_with: T.type_parameter(:FillType), block: T.nilable(T.proc.params(group: T::Array[T.any(Elem, T.type_parameter(:FillType))]).void)).returns(T::Array[T::Array[T.any(Elem, T.type_parameter(:FillType))]]) }
115
+ def in_groups_of(number, fill_with = T.unsafe(nil), &block); end
116
+
117
+ sig { params(value: T.untyped, block: T.nilable(T.proc.params(element: Elem).returns(T.untyped))).returns(T::Array[T::Array[Elem]]) }
118
+ def split(value = nil, &block); end
119
+
120
+ sig { params(object: T.untyped).returns(T::Array[T.untyped]) }
121
+ def self.wrap(object); end
122
+
123
+ sig { returns(T.untyped) }
124
+ def extract!; end
125
+
126
+ sig { returns(ActiveSupport::ArrayInquirer) }
127
+ def inquiry; end
128
+ end