toys-core 0.7.0 → 0.8.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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +98 -0
  3. data/LICENSE.md +16 -24
  4. data/README.md +307 -59
  5. data/docs/guide.md +44 -4
  6. data/lib/toys-core.rb +58 -49
  7. data/lib/toys/acceptor.rb +672 -0
  8. data/lib/toys/alias.rb +106 -0
  9. data/lib/toys/arg_parser.rb +624 -0
  10. data/lib/toys/cli.rb +422 -181
  11. data/lib/toys/compat.rb +83 -0
  12. data/lib/toys/completion.rb +442 -0
  13. data/lib/toys/context.rb +354 -0
  14. data/lib/toys/core_version.rb +18 -26
  15. data/lib/toys/dsl/flag.rb +213 -56
  16. data/lib/toys/dsl/flag_group.rb +237 -51
  17. data/lib/toys/dsl/positional_arg.rb +210 -0
  18. data/lib/toys/dsl/tool.rb +968 -317
  19. data/lib/toys/errors.rb +46 -28
  20. data/lib/toys/flag.rb +821 -0
  21. data/lib/toys/flag_group.rb +282 -0
  22. data/lib/toys/input_file.rb +18 -26
  23. data/lib/toys/loader.rb +110 -100
  24. data/lib/toys/middleware.rb +24 -31
  25. data/lib/toys/mixin.rb +90 -59
  26. data/lib/toys/module_lookup.rb +125 -0
  27. data/lib/toys/positional_arg.rb +184 -0
  28. data/lib/toys/source_info.rb +192 -0
  29. data/lib/toys/standard_middleware/add_verbosity_flags.rb +38 -43
  30. data/lib/toys/standard_middleware/handle_usage_errors.rb +39 -40
  31. data/lib/toys/standard_middleware/set_default_descriptions.rb +111 -89
  32. data/lib/toys/standard_middleware/show_help.rb +130 -113
  33. data/lib/toys/standard_middleware/show_root_version.rb +29 -35
  34. data/lib/toys/standard_mixins/exec.rb +116 -78
  35. data/lib/toys/standard_mixins/fileutils.rb +16 -24
  36. data/lib/toys/standard_mixins/gems.rb +29 -30
  37. data/lib/toys/standard_mixins/highline.rb +34 -41
  38. data/lib/toys/standard_mixins/terminal.rb +72 -26
  39. data/lib/toys/template.rb +51 -35
  40. data/lib/toys/tool.rb +1161 -206
  41. data/lib/toys/utils/completion_engine.rb +171 -0
  42. data/lib/toys/utils/exec.rb +279 -182
  43. data/lib/toys/utils/gems.rb +58 -49
  44. data/lib/toys/utils/help_text.rb +117 -111
  45. data/lib/toys/utils/terminal.rb +69 -62
  46. data/lib/toys/wrappable_string.rb +162 -0
  47. metadata +24 -22
  48. data/lib/toys/definition/acceptor.rb +0 -191
  49. data/lib/toys/definition/alias.rb +0 -112
  50. data/lib/toys/definition/arg.rb +0 -140
  51. data/lib/toys/definition/flag.rb +0 -370
  52. data/lib/toys/definition/flag_group.rb +0 -205
  53. data/lib/toys/definition/source_info.rb +0 -190
  54. data/lib/toys/definition/tool.rb +0 -842
  55. data/lib/toys/dsl/arg.rb +0 -132
  56. data/lib/toys/runner.rb +0 -188
  57. data/lib/toys/standard_middleware.rb +0 -47
  58. data/lib/toys/utils/module_lookup.rb +0 -135
  59. data/lib/toys/utils/wrappable_string.rb +0 -165
@@ -1,132 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Copyright 2018 Daniel Azuma
4
- #
5
- # All rights reserved.
6
- #
7
- # Redistribution and use in source and binary forms, with or without
8
- # modification, are permitted provided that the following conditions are met:
9
- #
10
- # * Redistributions of source code must retain the above copyright notice,
11
- # this list of conditions and the following disclaimer.
12
- # * Redistributions in binary form must reproduce the above copyright notice,
13
- # this list of conditions and the following disclaimer in the documentation
14
- # and/or other materials provided with the distribution.
15
- # * Neither the name of the copyright holder, nor the names of any other
16
- # contributors to this software, may be used to endorse or promote products
17
- # derived from this software without specific prior written permission.
18
- #
19
- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
- # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
- # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
- # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23
- # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24
- # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25
- # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26
- # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27
- # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28
- # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
- # POSSIBILITY OF SUCH DAMAGE.
30
- ;
31
-
32
- module Toys
33
- module DSL
34
- ##
35
- # DSL for an arg definition block. Lets you set arg attributes in a block
36
- # instead of a long series of keyword arguments.
37
- #
38
- # These directives are available inside a block passed to
39
- # {Toys::DSL::Tool#required_arg}, {Toys::DSL::Tool#optional_arg}, or
40
- # {Toys::DSL::Tool#remaining_args}.
41
- #
42
- class Arg
43
- ## @private
44
- def initialize(accept, default, display_name, desc, long_desc)
45
- @accept = accept
46
- @default = default
47
- @display_name = display_name
48
- @desc = desc
49
- @long_desc = long_desc || []
50
- end
51
-
52
- ##
53
- # Set the OptionParser acceptor.
54
- #
55
- # @param [Object] accept
56
- # @return [Toys::DSL::Tool] self, for chaining.
57
- #
58
- def accept(accept)
59
- @accept = accept
60
- self
61
- end
62
-
63
- ##
64
- # Set the default value.
65
- #
66
- # @param [Object] default
67
- # @return [Toys::DSL::Tool] self, for chaining.
68
- #
69
- def default(default)
70
- @default = default
71
- self
72
- end
73
-
74
- ##
75
- # Set the name of this arg as it appears in help screens.
76
- #
77
- # @param [String] display_name
78
- # @return [Toys::DSL::Tool] self, for chaining.
79
- #
80
- def display_name(display_name)
81
- @display_name = display_name
82
- self
83
- end
84
-
85
- ##
86
- # Set the short description. See {Toys::DSL::Tool#desc} for the allowed
87
- # formats.
88
- #
89
- # @param [String,Array<String>,Toys::Utils::WrappableString] desc
90
- # @return [Toys::DSL::Tool] self, for chaining.
91
- #
92
- def desc(desc)
93
- @desc = desc
94
- self
95
- end
96
-
97
- ##
98
- # Adds to the long description. This may be called multiple times, and
99
- # the results are cumulative. See {Toys::DSL::Tool#long_desc} for the
100
- # allowed formats.
101
- #
102
- # @param [String,Array<String>,Toys::Utils::WrappableString...] long_desc
103
- # @return [Toys::DSL::Tool] self, for chaining.
104
- #
105
- def long_desc(*long_desc)
106
- @long_desc += long_desc
107
- self
108
- end
109
-
110
- ## @private
111
- def _add_required_to(tool, key)
112
- tool.add_required_arg(key,
113
- accept: @accept, display_name: @display_name,
114
- desc: @desc, long_desc: @long_desc)
115
- end
116
-
117
- ## @private
118
- def _add_optional_to(tool, key)
119
- tool.add_optional_arg(key,
120
- accept: @accept, default: @default, display_name: @display_name,
121
- desc: @desc, long_desc: @long_desc)
122
- end
123
-
124
- ## @private
125
- def _set_remaining_on(tool, key)
126
- tool.set_remaining_args(key,
127
- accept: @accept, default: @default, display_name: @display_name,
128
- desc: @desc, long_desc: @long_desc)
129
- end
130
- end
131
- end
132
- end
@@ -1,188 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Copyright 2018 Daniel Azuma
4
- #
5
- # All rights reserved.
6
- #
7
- # Redistribution and use in source and binary forms, with or without
8
- # modification, are permitted provided that the following conditions are met:
9
- #
10
- # * Redistributions of source code must retain the above copyright notice,
11
- # this list of conditions and the following disclaimer.
12
- # * Redistributions in binary form must reproduce the above copyright notice,
13
- # this list of conditions and the following disclaimer in the documentation
14
- # and/or other materials provided with the distribution.
15
- # * Neither the name of the copyright holder, nor the names of any other
16
- # contributors to this software, may be used to endorse or promote products
17
- # derived from this software without specific prior written permission.
18
- #
19
- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
- # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
- # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
- # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23
- # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24
- # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25
- # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26
- # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27
- # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28
- # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
- # POSSIBILITY OF SUCH DAMAGE.
30
- ;
31
-
32
- require "optparse"
33
-
34
- module Toys
35
- ##
36
- # An internal class that orchestrates execution of a tool.
37
- #
38
- # Generaly, you should not need to use this class directly. Instead, run a
39
- # tool using {Toys::CLI#run}.
40
- #
41
- class Runner
42
- ##
43
- # Create a runner for a particular tool in a particular CLI.
44
- #
45
- # @param [Toys::CLI] cli The CLI that is running the tool. This will
46
- # provide needed context information.
47
- # @param [Toys::Definition::Tool] tool_definition The tool to run.
48
- #
49
- def initialize(cli, tool_definition)
50
- @cli = cli
51
- @tool_definition = tool_definition
52
- end
53
-
54
- ##
55
- # Run the tool, provided given arguments.
56
- #
57
- # @param [Array<String>] args Command line arguments passed to the tool.
58
- # @param [Integer] verbosity Initial verbosity. Default is 0.
59
- #
60
- # @return [Integer] The resulting status code
61
- #
62
- def run(args, verbosity: 0)
63
- data = create_data(args, verbosity)
64
- parse_args(args, data) unless @tool_definition.argument_parsing_disabled?
65
- tool = @tool_definition.tool_class.new(@cli, data)
66
- @tool_definition.run_initializers(tool)
67
-
68
- original_level = @cli.logger.level
69
- @cli.logger.level = @cli.base_level - data[Tool::Keys::VERBOSITY]
70
- begin
71
- perform_execution(tool)
72
- ensure
73
- @cli.logger.level = original_level
74
- end
75
- end
76
-
77
- private
78
-
79
- def create_data(args, base_verbosity)
80
- data = @tool_definition.default_data.dup
81
- data[Tool::Keys::TOOL_DEFINITION] = @tool_definition
82
- data[Tool::Keys::TOOL_SOURCE] = @tool_definition.source_info
83
- data[Tool::Keys::TOOL_NAME] = @tool_definition.full_name
84
- data[Tool::Keys::VERBOSITY] = base_verbosity
85
- data[Tool::Keys::ARGS] = args
86
- data[Tool::Keys::USAGE_ERROR] = nil
87
- data
88
- end
89
-
90
- def parse_args(args, data)
91
- optparse, seen = create_option_parser(data)
92
- remaining = optparse.parse(args)
93
- validate_flags(args, seen)
94
- remaining = parse_required_args(remaining, args, data)
95
- remaining = parse_optional_args(remaining, data)
96
- parse_remaining_args(remaining, args, data)
97
- rescue ::OptionParser::ParseError => e
98
- data[Tool::Keys::USAGE_ERROR] = e.message
99
- end
100
-
101
- def create_option_parser(data)
102
- seen = []
103
- optparse = ::OptionParser.new
104
- # The following clears out the Officious (hidden default flags).
105
- optparse.remove
106
- optparse.remove
107
- optparse.new
108
- optparse.new
109
- @tool_definition.flag_definitions.each do |flag|
110
- optparse.on(*flag.optparser_info) do |val|
111
- seen << flag.key
112
- data[flag.key] = flag.handler.call(val, data[flag.key])
113
- end
114
- end
115
- @tool_definition.custom_acceptors do |accept|
116
- optparse.accept(accept)
117
- end
118
- [optparse, seen]
119
- end
120
-
121
- def validate_flags(args, seen)
122
- @tool_definition.flag_groups.each do |group|
123
- error = group.validation_error(seen)
124
- raise create_parse_error(args, error) if error
125
- end
126
- end
127
-
128
- def parse_required_args(remaining, args, data)
129
- @tool_definition.required_arg_definitions.each do |arg_info|
130
- if remaining.empty?
131
- reason = "No value given for required argument #{arg_info.display_name}"
132
- raise create_parse_error(args, reason)
133
- end
134
- data[arg_info.key] = arg_info.process_value(remaining.shift)
135
- end
136
- remaining
137
- end
138
-
139
- def parse_optional_args(remaining, data)
140
- @tool_definition.optional_arg_definitions.each do |arg_info|
141
- break if remaining.empty?
142
- data[arg_info.key] = arg_info.process_value(remaining.shift)
143
- end
144
- remaining
145
- end
146
-
147
- def parse_remaining_args(remaining, args, data)
148
- return if remaining.empty?
149
- unless @tool_definition.remaining_args_definition
150
- if @tool_definition.runnable?
151
- raise create_parse_error(remaining, "Extra arguments provided")
152
- else
153
- raise create_parse_error(@tool_definition.full_name + args, "Tool not found")
154
- end
155
- end
156
- data[@tool_definition.remaining_args_definition.key] =
157
- remaining.map { |arg| @tool_definition.remaining_args_definition.process_value(arg) }
158
- end
159
-
160
- def create_parse_error(path, reason)
161
- ::OptionParser::ParseError.new(*path).tap do |e|
162
- e.reason = reason
163
- end
164
- end
165
-
166
- def perform_execution(tool)
167
- executor = proc do
168
- if @tool_definition.runnable?
169
- tool.run
170
- else
171
- @cli.logger.fatal("No implementation for tool #{@tool_definition.display_name.inspect}")
172
- tool.exit(-1)
173
- end
174
- end
175
- @tool_definition.middleware_stack.reverse_each do |middleware|
176
- executor = make_executor(middleware, tool, executor)
177
- end
178
- catch(:result) do
179
- executor.call
180
- 0
181
- end
182
- end
183
-
184
- def make_executor(middleware, tool, next_executor)
185
- proc { middleware.run(tool, &next_executor) }
186
- end
187
- end
188
- end
@@ -1,47 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Copyright 2018 Daniel Azuma
4
- #
5
- # All rights reserved.
6
- #
7
- # Redistribution and use in source and binary forms, with or without
8
- # modification, are permitted provided that the following conditions are met:
9
- #
10
- # * Redistributions of source code must retain the above copyright notice,
11
- # this list of conditions and the following disclaimer.
12
- # * Redistributions in binary form must reproduce the above copyright notice,
13
- # this list of conditions and the following disclaimer in the documentation
14
- # and/or other materials provided with the distribution.
15
- # * Neither the name of the copyright holder, nor the names of any other
16
- # contributors to this software, may be used to endorse or promote products
17
- # derived from this software without specific prior written permission.
18
- #
19
- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
- # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
- # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
- # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23
- # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24
- # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25
- # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26
- # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27
- # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28
- # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
- # POSSIBILITY OF SUCH DAMAGE.
30
- ;
31
-
32
- module Toys
33
- ##
34
- # Namespace for standard middleware classes.
35
- #
36
- module StandardMiddleware
37
- ## @private
38
- COMMON_FLAG_GROUP = :__common
39
-
40
- ## @private
41
- def self.append_common_flag_group(tool)
42
- tool.add_flag_group(type: :optional, name: COMMON_FLAG_GROUP,
43
- desc: "Common Flags", report_collisions: false)
44
- COMMON_FLAG_GROUP
45
- end
46
- end
47
- end
@@ -1,135 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Copyright 2018 Daniel Azuma
4
- #
5
- # All rights reserved.
6
- #
7
- # Redistribution and use in source and binary forms, with or without
8
- # modification, are permitted provided that the following conditions are met:
9
- #
10
- # * Redistributions of source code must retain the above copyright notice,
11
- # this list of conditions and the following disclaimer.
12
- # * Redistributions in binary form must reproduce the above copyright notice,
13
- # this list of conditions and the following disclaimer in the documentation
14
- # and/or other materials provided with the distribution.
15
- # * Neither the name of the copyright holder, nor the names of any other
16
- # contributors to this software, may be used to endorse or promote products
17
- # derived from this software without specific prior written permission.
18
- #
19
- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
- # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
- # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
- # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23
- # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24
- # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25
- # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26
- # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27
- # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28
- # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
- # POSSIBILITY OF SUCH DAMAGE.
30
- ;
31
-
32
- module Toys
33
- module Utils
34
- ##
35
- # A helper module that provides methods to do module lookups. This is
36
- # used to obtain named helpers, middleware, and templates from the
37
- # respective modules.
38
- #
39
- class ModuleLookup
40
- class << self
41
- ##
42
- # Convert the given string to a path element. Specifically, converts
43
- # to `lower_snake_case`.
44
- #
45
- # @param [String,Symbol] str String to convert.
46
- # @return [String] Converted string
47
- #
48
- def to_path_name(str)
49
- str = str.to_s.sub(/^_/, "").sub(/_$/, "").gsub(/_+/, "_")
50
- while str.sub!(/([^_])([A-Z])/, "\\1_\\2") do end
51
- str.downcase
52
- end
53
-
54
- ##
55
- # Convert the given string to a module name. Specifically, converts
56
- # to `UpperCamelCase`, and then to a symbol.
57
- #
58
- # @param [String,Symbol] str String to convert.
59
- # @return [Symbol] Converted name
60
- #
61
- def to_module_name(str)
62
- str = str.to_s.sub(/^_/, "").sub(/_$/, "").gsub(/_+/, "_")
63
- str.to_s.gsub(/(^|_)([a-zA-Z])/) { |_m| $2.upcase }.to_sym
64
- end
65
-
66
- ##
67
- # Given a require path, return the module expected to be defined.
68
- #
69
- # @param [String] path File path, delimited by forward slash
70
- # @return [Module] The module loaded from that path
71
- #
72
- def path_to_module(path)
73
- path.split("/").reduce(::Object) do |running_mod, seg|
74
- mod_name = to_module_name(seg)
75
- unless running_mod.constants.include?(mod_name)
76
- raise ::NameError, "Module #{running_mod.name}::#{mod_name} not found"
77
- end
78
- running_mod.const_get(mod_name)
79
- end
80
- end
81
- end
82
-
83
- ##
84
- # Create an empty ModuleLookup
85
- #
86
- def initialize
87
- @paths = []
88
- end
89
-
90
- ##
91
- # Add a lookup path for modules.
92
- #
93
- # @param [String] path_base The base require path
94
- # @param [Module] module_base The base module, or `nil` (the default) to
95
- # infer a default from the path base.
96
- # @param [Boolean] high_priority If true, add to the head of the lookup
97
- # path, otherwise add to the end.
98
- #
99
- def add_path(path_base, module_base: nil, high_priority: false)
100
- module_base ||= ModuleLookup.path_to_module(path_base)
101
- if high_priority
102
- @paths.unshift([path_base, module_base])
103
- else
104
- @paths << [path_base, module_base]
105
- end
106
- self
107
- end
108
-
109
- ##
110
- # Obtain a named module. Returns `nil` if the name is not present.
111
- #
112
- # @param [String,Symbol] name The name of the module to return.
113
- #
114
- # @return [Module] The specified module
115
- #
116
- def lookup(name)
117
- @paths.each do |path_base, module_base|
118
- path = "#{path_base}/#{ModuleLookup.to_path_name(name)}"
119
- begin
120
- require path
121
- rescue ::LoadError
122
- next
123
- end
124
- mod_name = ModuleLookup.to_module_name(name)
125
- unless module_base.constants.include?(mod_name)
126
- raise ::NameError,
127
- "File #{path.inspect} did not define #{module_base.name}::#{mod_name}"
128
- end
129
- return module_base.const_get(mod_name)
130
- end
131
- nil
132
- end
133
- end
134
- end
135
- end