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
@@ -0,0 +1,192 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2019 Daniel Azuma
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
20
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21
+ # IN THE SOFTWARE.
22
+ ;
23
+
24
+ module Toys
25
+ ##
26
+ # Information about source toys directories and files.
27
+ #
28
+ class SourceInfo
29
+ ##
30
+ # Create a SourceInfo.
31
+ # @private
32
+ #
33
+ def initialize(parent, context_directory, source, source_type, source_name, data_dir_name)
34
+ @parent = parent
35
+ @context_directory = context_directory
36
+ @source = source
37
+ @source_type = source_type
38
+ @source_path = source if source.is_a?(::String)
39
+ @source_proc = source if source.is_a?(::Proc)
40
+ @source_name = source_name
41
+ @data_dir =
42
+ if data_dir_name && @source_path
43
+ dir = ::File.join(::File.dirname(@source_path), data_dir_name)
44
+ dir if ::File.directory?(dir) && ::File.readable?(dir)
45
+ end
46
+ end
47
+
48
+ ##
49
+ # The parent of this SourceInfo.
50
+ #
51
+ # @return [Toys::SourceInfo] The parent.
52
+ # @return [nil] if this SourceInfo is the root.
53
+ #
54
+ attr_reader :parent
55
+
56
+ ##
57
+ # The context directory path (normally the directory containing the
58
+ # toplevel toys file or directory).
59
+ #
60
+ # @return [String] The context directory path.
61
+ # @return [nil] if there is no context directory (perhaps because the tool
62
+ # is being defined from a block)
63
+ #
64
+ attr_reader :context_directory
65
+
66
+ ##
67
+ # The source, which may be a path or a proc.
68
+ #
69
+ # @return [String] Path to the source file or directory.
70
+ # @return [Proc] The block serving as the source.
71
+ #
72
+ attr_reader :source
73
+
74
+ ##
75
+ # Return the type of source.
76
+ #
77
+ # @return [:file,:directory,:proc]
78
+ #
79
+ attr_reader :source_type
80
+
81
+ ##
82
+ # The path of the current source file or directory.
83
+ #
84
+ # @return [String] The source path
85
+ # @return [nil] if this source is not a file system path.
86
+ #
87
+ attr_reader :source_path
88
+
89
+ ##
90
+ # The source proc.
91
+ #
92
+ # @return [Proc] The source proc
93
+ # @return [nil] if this source is not a proc.
94
+ #
95
+ attr_reader :source_proc
96
+
97
+ ##
98
+ # The user-visible name of this source.
99
+ #
100
+ # @return [String]
101
+ #
102
+ attr_reader :source_name
103
+ alias to_s source_name
104
+
105
+ ##
106
+ # Locate the given data file or directory and return an absolute path.
107
+ #
108
+ # @param path [String] The relative path to find
109
+ # @param type [nil,:file,:directory] Type of file system object to find,
110
+ # or nil (the default) to return any type.
111
+ # @return [String] Absolute path of the resulting data.
112
+ # @return [nil] if the data was not found.
113
+ #
114
+ def find_data(path, type: nil)
115
+ if @data_dir
116
+ full_path = ::File.join(@data_dir, path)
117
+ case type
118
+ when :file
119
+ return full_path if ::File.file?(full_path)
120
+ when :directory
121
+ return full_path if ::File.directory?(full_path)
122
+ else
123
+ return full_path if ::File.readable?(full_path)
124
+ end
125
+ end
126
+ parent&.find_data(path, type: type)
127
+ end
128
+
129
+ ##
130
+ # Create a child SourceInfo relative to the parent path.
131
+ # @private
132
+ #
133
+ def relative_child(filename, data_dir_name)
134
+ raise "Cannot create relative child of a proc" unless source_path
135
+ child_path = ::File.join(source_path, filename)
136
+ child_path, type = SourceInfo.check_path(child_path, true)
137
+ return nil unless child_path
138
+ SourceInfo.new(self, context_directory, child_path, type, child_path, data_dir_name)
139
+ end
140
+
141
+ ##
142
+ # Create a child SourceInfo with an absolute path.
143
+ # @private
144
+ #
145
+ def absolute_child(child_path)
146
+ child_path, type = SourceInfo.check_path(child_path, false)
147
+ SourceInfo.new(self, context_directory, child_path, type, child_path, nil)
148
+ end
149
+
150
+ ##
151
+ # Create a root source info for a file path.
152
+ # @private
153
+ #
154
+ def self.create_path_root(source_path)
155
+ source_path, type = check_path(source_path, false)
156
+ context_directory = ::File.dirname(source_path)
157
+ new(nil, context_directory, source_path, type, source_path, nil)
158
+ end
159
+
160
+ ##
161
+ # Create a root source info for a proc.
162
+ # @private
163
+ #
164
+ def self.create_proc_root(source_proc, source_name)
165
+ new(nil, nil, source_proc, :proc, source_name, nil)
166
+ end
167
+
168
+ ##
169
+ # Check a path and determine the canonical path and type.
170
+ # @private
171
+ #
172
+ def self.check_path(path, lenient)
173
+ path = ::File.expand_path(path)
174
+ unless ::File.readable?(path)
175
+ raise LoaderError, "Cannot read: #{path}" unless lenient
176
+ return [nil, nil]
177
+ end
178
+ if ::File.file?(path)
179
+ unless ::File.extname(path) == ".rb"
180
+ raise LoaderError, "File is not a ruby file: #{path}" unless lenient
181
+ return [nil, nil]
182
+ end
183
+ [path, :file]
184
+ elsif ::File.directory?(path)
185
+ [path, :directory]
186
+ else
187
+ raise LoaderError, "Unknown type: #{path}" unless lenient
188
+ [nil, nil]
189
+ end
190
+ end
191
+ end
192
+ end
@@ -1,32 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright 2018 Daniel Azuma
3
+ # Copyright 2019 Daniel Azuma
4
4
  #
5
- # All rights reserved.
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:
6
11
  #
7
- # Redistribution and use in source and binary forms, with or without
8
- # modification, are permitted provided that the following conditions are met:
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
9
14
  #
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.
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
20
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21
+ # IN THE SOFTWARE.
30
22
  ;
31
23
 
32
24
  module Toys
@@ -36,7 +28,7 @@ module Toys
36
28
  #
37
29
  # This middleware adds `-v`, `--verbose`, `-q`, and `--quiet` flags, if
38
30
  # not already defined by the tool. These flags affect the setting of
39
- # {Toys::Tool::Keys::VERBOSITY}, and, thus, the logger level.
31
+ # {Toys::Context::Key::VERBOSITY}, and, thus, the logger level.
40
32
  #
41
33
  class AddVerbosityFlags
42
34
  include Middleware
@@ -56,7 +48,7 @@ module Toys
56
48
  ##
57
49
  # Create a AddVerbosityFlags middleware.
58
50
  #
59
- # @param [Boolean,Array<String>,Proc] verbose_flags Specify flags
51
+ # @param verbose_flags [Boolean,Array<String>,Proc] Specify flags
60
52
  # to increase verbosity. The value may be any of the following:
61
53
  #
62
54
  # * An array of flags that increase verbosity.
@@ -64,7 +56,7 @@ module Toys
64
56
  # * The `false` value to disable verbose flags.
65
57
  # * A proc that takes a tool and returns any of the above.
66
58
  #
67
- # @param [Boolean,Array<String>,Proc] quiet_flags Specify flags
59
+ # @param quiet_flags [Boolean,Array<String>,Proc] Specify flags
68
60
  # to decrease verbosity. The value may be any of the following:
69
61
  #
70
62
  # * An array of flags that decrease verbosity.
@@ -79,26 +71,29 @@ module Toys
79
71
 
80
72
  ##
81
73
  # Configure the tool flags.
74
+ # @private
82
75
  #
83
- def config(tool_definition, _loader)
84
- unless tool_definition.argument_parsing_disabled?
85
- StandardMiddleware.append_common_flag_group(tool_definition)
86
- add_verbose_flags(tool_definition)
87
- add_quiet_flags(tool_definition)
76
+ def config(tool, _loader)
77
+ unless tool.argument_parsing_disabled?
78
+ StandardMiddleware.append_common_flag_group(tool)
79
+ add_verbose_flags(tool)
80
+ add_quiet_flags(tool)
88
81
  end
89
82
  yield
90
83
  end
91
84
 
92
85
  private
93
86
 
94
- def add_verbose_flags(tool_definition)
95
- verbose_flags = resolve_flags_spec(@verbose_flags, tool_definition,
96
- DEFAULT_VERBOSE_FLAGS)
87
+ INCREMENT_HANDLER = ->(_val, cur) { cur.to_i + 1 }
88
+ DECREMENT_HANDLER = ->(_val, cur) { cur.to_i - 1 }
89
+
90
+ def add_verbose_flags(tool)
91
+ verbose_flags = resolve_flags_spec(@verbose_flags, tool, DEFAULT_VERBOSE_FLAGS)
97
92
  unless verbose_flags.empty?
98
- tool_definition.add_flag(
99
- Tool::Keys::VERBOSITY, verbose_flags,
93
+ tool.add_flag(
94
+ Context::Key::VERBOSITY, verbose_flags,
100
95
  report_collisions: false,
101
- handler: ->(_val, cur) { cur + 1 },
96
+ handler: INCREMENT_HANDLER,
102
97
  desc: "Increase verbosity",
103
98
  long_desc: "Increase verbosity, causing additional logging levels to display.",
104
99
  group: StandardMiddleware::COMMON_FLAG_GROUP
@@ -106,13 +101,13 @@ module Toys
106
101
  end
107
102
  end
108
103
 
109
- def add_quiet_flags(tool_definition)
110
- quiet_flags = resolve_flags_spec(@quiet_flags, tool_definition, DEFAULT_QUIET_FLAGS)
104
+ def add_quiet_flags(tool)
105
+ quiet_flags = resolve_flags_spec(@quiet_flags, tool, DEFAULT_QUIET_FLAGS)
111
106
  unless quiet_flags.empty?
112
- tool_definition.add_flag(
113
- Tool::Keys::VERBOSITY, quiet_flags,
107
+ tool.add_flag(
108
+ Context::Key::VERBOSITY, quiet_flags,
114
109
  report_collisions: false,
115
- handler: ->(_val, cur) { cur - 1 },
110
+ handler: DECREMENT_HANDLER,
116
111
  desc: "Decrease verbosity",
117
112
  long_desc: "Decrease verbosity, causing fewer logging levels to display.",
118
113
  group: StandardMiddleware::COMMON_FLAG_GROUP
@@ -1,32 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright 2018 Daniel Azuma
3
+ # Copyright 2019 Daniel Azuma
4
4
  #
5
- # All rights reserved.
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:
6
11
  #
7
- # Redistribution and use in source and binary forms, with or without
8
- # modification, are permitted provided that the following conditions are met:
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
9
14
  #
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.
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
20
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21
+ # IN THE SOFTWARE.
30
22
  ;
31
23
 
32
24
  module Toys
@@ -40,34 +32,41 @@ module Toys
40
32
  class HandleUsageErrors
41
33
  include Middleware
42
34
 
35
+ ##
36
+ # Exit code for usage error. (2 by convention)
37
+ # @return [Integer]
38
+ #
39
+ USAGE_ERROR_EXIT_CODE = 2
40
+
43
41
  ##
44
42
  # Create a HandleUsageErrors middleware.
45
43
  #
46
- # @param [Intgeer] exit_code The exit code to return if a usage error
47
- # occurs. Default is -1.
48
- # @param [IO] stream Output stream to write to. Default is stderr.
49
- # @param [Boolean,nil] styled_output Cause the tool to display help text
44
+ # @param exit_code [Integer] The exit code to return if a usage error
45
+ # occurs. Default is {USAGE_ERROR_EXIT_CODE}.
46
+ # @param stream [IO] Output stream to write to. Default is stderr.
47
+ # @param styled_output [Boolean,nil] Cause the tool to display help text
50
48
  # with ansi styles. If `nil`, display styles if the output stream is
51
49
  # a tty. Default is `nil`.
52
50
  #
53
- def initialize(exit_code: -1, stream: $stderr, styled_output: nil)
54
- @exit_code = exit_code
51
+ def initialize(exit_code: nil, stream: $stderr, styled_output: nil)
52
+ require "toys/utils/terminal"
53
+ @exit_code = exit_code || USAGE_ERROR_EXIT_CODE
55
54
  @terminal = Utils::Terminal.new(output: stream, styled: styled_output)
56
55
  end
57
56
 
58
57
  ##
59
58
  # Intercept and handle usage errors during execution.
59
+ # @private
60
60
  #
61
- def run(tool)
62
- if tool[Tool::Keys::USAGE_ERROR]
63
- help_text = Utils::HelpText.from_tool(tool)
64
- @terminal.puts(tool[Tool::Keys::USAGE_ERROR], :bright_red, :bold)
65
- @terminal.puts("")
66
- @terminal.puts(help_text.usage_string(wrap_width: @terminal.width))
67
- Tool.exit(@exit_code)
68
- else
69
- yield
70
- end
61
+ def run(context)
62
+ yield
63
+ rescue ArgParsingError => e
64
+ require "toys/utils/help_text"
65
+ help_text = Utils::HelpText.from_context(context)
66
+ @terminal.puts(e.usage_errors.join("\n"), :bright_red, :bold)
67
+ @terminal.puts("")
68
+ @terminal.puts(help_text.usage_string(wrap_width: @terminal.width))
69
+ Context.exit(@exit_code)
71
70
  end
72
71
  end
73
72
  end
@@ -1,32 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright 2018 Daniel Azuma
3
+ # Copyright 2019 Daniel Azuma
4
4
  #
5
- # All rights reserved.
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:
6
11
  #
7
- # Redistribution and use in source and binary forms, with or without
8
- # modification, are permitted provided that the following conditions are met:
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
9
14
  #
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.
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
20
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21
+ # IN THE SOFTWARE.
30
22
  ;
31
23
 
32
24
  module Toys
@@ -69,41 +61,27 @@ module Toys
69
61
  " https://www.rubydoc.info/gems/toys-core for more info.",
70
62
  "To replace this message, set the description and long description" \
71
63
  " of the root tool, or configure the SetDefaultDescriptions" \
72
- " middleware."
64
+ " middleware.",
73
65
  ].freeze
74
66
 
75
- ##
76
- # A mapping of names for acceptable types
77
- # @return [Hash]
78
- #
79
- ACCEPTABLE_NAMES = {
80
- nil => "string",
81
- ::String => "nonempty string",
82
- ::TrueClass => "boolean",
83
- ::FalseClass => "boolean",
84
- ::OptionParser::DecimalInteger => "decimal integer",
85
- ::OptionParser::OctalInteger => "octal integer",
86
- ::OptionParser::DecimalNumeric => "decimal numeric"
87
- }.freeze
88
-
89
67
  ##
90
68
  # Create a SetDefaultDescriptions middleware given default descriptions.
91
69
  #
92
- # @param [String,nil] default_tool_desc The default short description for
70
+ # @param default_tool_desc [String,nil] The default short description for
93
71
  # runnable tools, or `nil` not to set one. Defaults to
94
72
  # {DEFAULT_TOOL_DESC}.
95
- # @param [String,nil] default_tool_long_desc The default long description
73
+ # @param default_tool_long_desc [String,nil] The default long description
96
74
  # for runnable tools, or `nil` not to set one. Defaults to `nil`.
97
- # @param [String,nil] default_namespace_desc The default short
75
+ # @param default_namespace_desc [String,nil] The default short
98
76
  # description for non-runnable tools, or `nil` not to set one.
99
77
  # Defaults to {DEFAULT_TOOL_DESC}.
100
- # @param [String,nil] default_namespace_long_desc The default long
78
+ # @param default_namespace_long_desc [String,nil] The default long
101
79
  # description for non-runnable tools, or `nil` not to set one.
102
80
  # Defaults to `nil`.
103
- # @param [String,nil] default_root_desc The default short description for
81
+ # @param default_root_desc [String,nil] The default short description for
104
82
  # the root tool, or `nil` not to set one. Defaults to
105
83
  # {DEFAULT_ROOT_DESC}.
106
- # @param [String,nil] default_root_long_desc The default long description
84
+ # @param default_root_long_desc [String,nil] The default long description
107
85
  # for the root tool, or `nil` not to set one. Defaults to
108
86
  # {DEFAULT_ROOT_LONG_DESC}.
109
87
  #
@@ -123,15 +101,20 @@ module Toys
123
101
 
124
102
  ##
125
103
  # Add default description text to tools.
104
+ # @private
126
105
  #
127
106
  def config(tool, loader)
128
107
  data = {tool: tool, loader: loader}
129
- tool.flag_definitions.each do |flag|
108
+ tool.flags.each do |flag|
130
109
  config_desc(flag, generate_flag_desc(flag, data), generate_flag_long_desc(flag, data))
131
110
  end
132
- tool.arg_definitions.each do |arg|
111
+ tool.positional_args.each do |arg|
133
112
  config_desc(arg, generate_arg_desc(arg, data), generate_arg_long_desc(arg, data))
134
113
  end
114
+ tool.flag_groups.each do |flag_group|
115
+ config_desc(flag_group, generate_flag_group_desc(flag_group, data),
116
+ generate_flag_group_long_desc(flag_group, data))
117
+ end
135
118
  config_desc(tool, generate_tool_desc(tool, data), generate_tool_long_desc(tool, data))
136
119
  yield
137
120
  end
@@ -143,13 +126,13 @@ module Toys
143
126
  # By default, it uses the parameters given to the middleware object.
144
127
  # Override this method to provide different logic.
145
128
  #
146
- # @param [Toys::Tool] tool The tool to document.
147
- # @param [Hash] data Additional data that might be useful. Currently,
129
+ # @param tool [Toys::Tool] The tool to document.
130
+ # @param data [Hash] Additional data that might be useful. Currently,
148
131
  # the {Toys::Loader} is passed with key `:loader`. Future versions
149
132
  # of Toys may provide additional information.
150
- # @return [String,Array<String>,Toys::Utils::WrappableString,nil] The
151
- # default description, or `nil` not to set a default. See
152
- # {Toys::Tool#desc=} for info on the format.
133
+ # @return [String,Array<String>,Toys::WrappableString] The default
134
+ # description. See {Toys::Tool#desc=} for info on the format.
135
+ # @return [nil] if this middleware should not set the description.
153
136
  #
154
137
  def generate_tool_desc(tool, data)
155
138
  if tool.root?
@@ -166,13 +149,14 @@ module Toys
166
149
  # By default, it uses the parameters given to the middleware object.
167
150
  # Override this method to provide different logic.
168
151
  #
169
- # @param [Toys::Tool] tool The tool to document
170
- # @param [Hash] data Additional data that might be useful. Currently,
171
- # the {Toys::Loader} is passed with key `:loader`. Future versions
172
- # of Toys may provide additional information.
173
- # @return [Array<Toys::Utils::WrappableString,String,Array<String>>,nil]
174
- # The default long description, or `nil` not to set a default. See
175
- # {Toys::Tool#long_desc=} for info on the format.
152
+ # @param tool [Toys::Tool] The tool to document
153
+ # @param data [Hash] Additional data that might be useful. Currently,
154
+ # the {Toys::Loader} is passed with key `:loader`. Future versions of
155
+ # Toys may provide additional information.
156
+ # @return [Array<Toys::WrappableString,String,Array<String>>] The default
157
+ # long description. See {Toys::Tool#long_desc=} for info on the
158
+ # format.
159
+ # @return [nil] if this middleware should not set the long description.
176
160
  #
177
161
  def generate_tool_long_desc(tool, data)
178
162
  if tool.root?
@@ -188,17 +172,17 @@ module Toys
188
172
  # This method implements the logic for generating a flag description.
189
173
  # Override this method to provide different logic.
190
174
  #
191
- # @param [Toys::Tool::FlagDefinition] flag The flag to document
192
- # @param [Hash] data Additional data that might be useful. Currently,
193
- # the {Toys::Tool} is passed with key `:tool`. Future versions of
194
- # Toys may provide additional information.
195
- # @return [String,Array<String>,Toys::Utils::WrappableString,nil] The
196
- # default description, or `nil` not to set a default. See
197
- # {Toys::Tool#desc=} for info on the format.
175
+ # @param flag [Toys::Flag] The flag to document
176
+ # @param data [Hash] Additional data that might be useful. Currently,
177
+ # the {Toys::Tool} is passed with key `:tool`. Future
178
+ # versions of Toys may provide additional information.
179
+ # @return [String,Array<String>,Toys::WrappableString] The default
180
+ # description. See {Toys::Tool#desc=} for info on the format.
181
+ # @return [nil] if this middleware should not set the description.
198
182
  #
199
183
  def generate_flag_desc(flag, data) # rubocop:disable Lint/UnusedMethodArgument
200
184
  name = flag.key.to_s.tr("_", "-").gsub(/[^\w-]/, "").downcase.inspect
201
- acceptable = flag.flag_type == :value ? acceptable_name(flag.accept) : "boolean flag"
185
+ acceptable = flag.flag_type == :value ? flag.acceptor.type_desc : "boolean flag"
202
186
  default_clause = flag.default ? " (default is #{flag.default.inspect})" : ""
203
187
  "Sets the #{name} option as type #{acceptable}#{default_clause}."
204
188
  end
@@ -207,13 +191,14 @@ module Toys
207
191
  # This method implements logic for generating a flag long description.
208
192
  # Override this method to provide different logic.
209
193
  #
210
- # @param [Toys::Tool::FlagDefinition] flag The flag to document
211
- # @param [Hash] data Additional data that might be useful. Currently,
194
+ # @param flag [Toys::Flag] The flag to document
195
+ # @param data [Hash] Additional data that might be useful. Currently,
212
196
  # the {Toys::Tool} is passed with key `:tool`. Future versions of
213
- # Toys may provide additional information.
214
- # @return [Array<Toys::Utils::WrappableString,String,Array<String>>,nil]
215
- # The default long description, or `nil` not to set a default. See
216
- # {Toys::Tool#long_desc=} for info on the format.
197
+ # versions of Toys may provide additional information.
198
+ # @return [Array<Toys::WrappableString,String,Array<String>>] The default
199
+ # long description. See {Toys::Tool#long_desc=} for info on the
200
+ # format.
201
+ # @return [nil] if this middleware should not set the long description.
217
202
  #
218
203
  def generate_flag_long_desc(flag, data) # rubocop:disable Lint/UnusedMethodArgument
219
204
  nil
@@ -223,16 +208,16 @@ module Toys
223
208
  # This method implements the logic for generating an arg description.
224
209
  # Override this method to provide different logic.
225
210
  #
226
- # @param [Toys::Tool::ArgDefinition] arg The arg to document
227
- # @param [Hash] data Additional data that might be useful. Currently,
211
+ # @param arg [Toys::PositionalArg] The arg to document
212
+ # @param data [Hash] Additional data that might be useful. Currently,
228
213
  # the {Toys::Tool} is passed with key `:tool`. Future versions of
229
214
  # Toys may provide additional information.
230
- # @return [String,Array<String>,Toys::Utils::WrappableString,nil] The
231
- # default description, or `nil` not to set a default. See
232
- # {Toys::Tool#desc=} for info on the format.
215
+ # @return [String,Array<String>,Toys::WrappableString] The default
216
+ # description. See {Toys::Tool#desc=} for info on the format.
217
+ # @return [nil] if this middleware should not set the description.
233
218
  #
234
219
  def generate_arg_desc(arg, data) # rubocop:disable Lint/UnusedMethodArgument
235
- acceptable = acceptable_name(arg.accept)
220
+ acceptable = arg.acceptor.type_desc
236
221
  default_clause = arg.default ? " (default is #{arg.default.inspect})" : ""
237
222
  case arg.type
238
223
  when :required
@@ -248,26 +233,63 @@ module Toys
248
233
  # This method implements logic for generating an arg long description.
249
234
  # Override this method to provide different logic.
250
235
  #
251
- # @param [Toys::Tool::ArgDefinition] arg The arg to document
252
- # @param [Hash] data Additional data that might be useful. Currently,
236
+ # @param arg [Toys::PositionalArg] The arg to document
237
+ # @param data [Hash] Additional data that might be useful. Currently,
253
238
  # the {Toys::Tool} is passed with key `:tool`. Future versions of
254
239
  # Toys may provide additional information.
255
- # @return [Array<Toys::Utils::WrappableString,String,Array<String>>,nil]
256
- # The default long description, or `nil` not to set a default. See
257
- # {Toys::Tool#long_desc=} for info on the format.
240
+ # @return [Array<Toys::WrappableString,String,Array<String>>] The default
241
+ # long description. See {Toys::Tool#long_desc=} for info on the
242
+ # format.
243
+ # @return [nil] if this middleware should not set the long description.
258
244
  #
259
245
  def generate_arg_long_desc(arg, data) # rubocop:disable Lint/UnusedMethodArgument
260
246
  nil
261
247
  end
262
248
 
263
249
  ##
264
- # Return a reasonable name for an acceptor
250
+ # This method implements the logic for generating a flag group
251
+ # description. Override this method to provide different logic.
265
252
  #
266
- # @param [Object] accept An acceptor to name
267
- # @return [String]
253
+ # @param group [Toys::FlagGroup] The flag group to document
254
+ # @param data [Hash] Additional data that might be useful. Currently,
255
+ # the {Toys::Tool} is passed with key `:tool`. Future
256
+ # versions of Toys may provide additional information.
257
+ # @return [String,Array<String>,Toys::WrappableString] The default
258
+ # description. See {Toys::Tool#desc=} for info on the format.
259
+ # @return [nil] if this middleware should not set the description.
260
+ #
261
+ def generate_flag_group_desc(group, data) # rubocop:disable Lint/UnusedMethodArgument
262
+ if group.is_a?(FlagGroup::Required)
263
+ "Required Flags"
264
+ else
265
+ "Flags"
266
+ end
267
+ end
268
+
269
+ ##
270
+ # This method implements the logic for generating a flag group long
271
+ # description. Override this method to provide different logic.
268
272
  #
269
- def acceptable_name(accept)
270
- ACCEPTABLE_NAMES[accept] || accept.to_s.downcase
273
+ # @param group [Toys::FlagGroup] The flag group to document
274
+ # @param data [Hash] Additional data that might be useful. Currently,
275
+ # the {Toys::Tool} is passed with key `:tool`. Future
276
+ # versions of Toys may provide additional information.
277
+ # @return [Array<Toys::WrappableString,String,Array<String>>] The default
278
+ # long description. See {Toys::Tool#long_desc=} for info on the
279
+ # format.
280
+ # @return [nil] if this middleware should not set the long description.
281
+ #
282
+ def generate_flag_group_long_desc(group, data) # rubocop:disable Lint/UnusedMethodArgument
283
+ case group
284
+ when FlagGroup::Required
285
+ ["These flags are required."]
286
+ when FlagGroup::ExactlyOne
287
+ ["Exactly one of these flags must be set."]
288
+ when FlagGroup::AtMostOne
289
+ ["At most one of these flags must be set."]
290
+ when FlagGroup::AtLeastOne
291
+ ["At least one of these flags must be set."]
292
+ end
271
293
  end
272
294
 
273
295
  private