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,205 +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 Definition
34
- ##
35
- # Representation of a group of flags with the same requirement settings.
36
- #
37
- class FlagGroup
38
- ##
39
- # Create a flag group.
40
- # @private
41
- #
42
- def initialize(name, desc, long_desc)
43
- @name = name
44
- @desc = Utils::WrappableString.make(desc || default_desc)
45
- @long_desc = Utils::WrappableString.make_array(long_desc || default_long_desc)
46
- @flag_definitions = []
47
- end
48
-
49
- ##
50
- # Returns the symbolic name for this group
51
- # @return [String,Symbol,nil]
52
- #
53
- attr_reader :name
54
-
55
- ##
56
- # Returns the short description string.
57
- # @return [Toys::Utils::WrappableString]
58
- #
59
- attr_reader :desc
60
-
61
- ##
62
- # Returns the long description strings as an array.
63
- # @return [Array<Toys::Utils::WrappableString>]
64
- #
65
- attr_reader :long_desc
66
-
67
- ##
68
- # Returns an array of flags that are in this group.
69
- # Do not modify the returned array.
70
- # @return [Array<Toys::Definition::Flag>]
71
- #
72
- attr_reader :flag_definitions
73
-
74
- ##
75
- # Returns true if this group is empty
76
- # @return [Boolean]
77
- #
78
- def empty?
79
- flag_definitions.empty?
80
- end
81
-
82
- ## @private
83
- def <<(flag)
84
- flag_definitions << flag
85
- end
86
-
87
- ## @private
88
- def default_desc
89
- "Flags"
90
- end
91
-
92
- ## @private
93
- def default_long_desc
94
- nil
95
- end
96
-
97
- ## @private
98
- def validation_error(_seen)
99
- nil
100
- end
101
-
102
- ##
103
- # A FlagGroup containing all required flags
104
- #
105
- class Required < FlagGroup
106
- ## @private
107
- def validation_error(seen)
108
- flag_definitions.each do |flag|
109
- unless seen.include?(flag.key)
110
- return "Flag \"#{flag.display_name}\" is required"
111
- end
112
- end
113
- nil
114
- end
115
-
116
- ## @private
117
- def default_desc
118
- "Required Flags"
119
- end
120
-
121
- ## @private
122
- def default_long_desc
123
- "These flags are required."
124
- end
125
- end
126
-
127
- ##
128
- # A FlagGroup containing all optional flags
129
- #
130
- class Optional < FlagGroup
131
- end
132
-
133
- ##
134
- # A FlagGroup in which exactly one flag must be set
135
- #
136
- class ExactlyOne < FlagGroup
137
- ## @private
138
- def validation_error(seen)
139
- set_flag = nil
140
- flag_definitions.each do |flag|
141
- if seen.include?(flag.key)
142
- if set_flag
143
- return "Exactly one out of group \"#{desc}\" is required, but both" \
144
- " \"#{set_flag.display_name}\" and \"#{flag.display_name}\" were set"
145
- else
146
- set_flag = flag
147
- end
148
- end
149
- end
150
- return "Exactly one out of group \"#{desc}\" is required" unless set_flag
151
- nil
152
- end
153
-
154
- ## @private
155
- def default_long_desc
156
- "Exactly one of these flags must be set."
157
- end
158
- end
159
-
160
- ##
161
- # A FlagGroup in which at most one flag must be set
162
- #
163
- class AtMostOne < FlagGroup
164
- ## @private
165
- def validation_error(seen)
166
- set_flag = nil
167
- flag_definitions.each do |flag|
168
- if seen.include?(flag.key)
169
- if set_flag
170
- return "At most one out of group \"#{desc}\" is required, but both" \
171
- " \"#{set_flag.display_name}\" and \"#{flag.display_name}\" were set"
172
- else
173
- set_flag = flag
174
- end
175
- end
176
- end
177
- nil
178
- end
179
-
180
- ## @private
181
- def default_long_desc
182
- "At most one of these flags must be set."
183
- end
184
- end
185
-
186
- ##
187
- # A FlagGroup in which at least one flag must be set
188
- #
189
- class AtLeastOne < FlagGroup
190
- ## @private
191
- def validation_error(seen)
192
- flag_definitions.each do |flag|
193
- return nil if seen.include?(flag.key)
194
- end
195
- "At least one out of group \"#{desc}\" is required"
196
- end
197
-
198
- ## @private
199
- def default_long_desc
200
- "At least one of these flags must be set."
201
- end
202
- end
203
- end
204
- end
205
- end
@@ -1,190 +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 Definition
34
- ##
35
- # Information about source toys directories and files.
36
- #
37
- class SourceInfo
38
- ##
39
- # Create a SourceInfo.
40
- # @private
41
- #
42
- def initialize(parent, context_directory, source, source_type, source_name, data_dir_name)
43
- @parent = parent
44
- @context_directory = context_directory
45
- @source = source
46
- @source_type = source_type
47
- @source_path = source if source.is_a?(::String)
48
- @source_proc = source if source.is_a?(::Proc)
49
- @source_name = source_name
50
- @data_dir =
51
- if data_dir_name && @source_path
52
- dir = ::File.join(::File.dirname(@source_path), data_dir_name)
53
- dir if ::File.directory?(dir) && ::File.readable?(dir)
54
- end
55
- end
56
-
57
- ##
58
- # Return the parent SourceInfo, or nil if this is the root.
59
- # @return [Toys::Definition::SourceInfo,nil]
60
- #
61
- attr_reader :parent
62
-
63
- ##
64
- # Return the context directory path (normally the directory containing
65
- # the toplevel toys file or directory). May return nil if there is no
66
- # context (e.g. the tool is being defined from a block).
67
- # @return [String,nil]
68
- #
69
- attr_reader :context_directory
70
-
71
- ##
72
- # Return the source, which may be a path or a proc.
73
- # @return [String,Proc]
74
- #
75
- attr_reader :source
76
-
77
- ##
78
- # Return the type of source.
79
- # @return [:file,:directory,:proc]
80
- #
81
- attr_reader :source_type
82
-
83
- ##
84
- # Return the path of the current source file or directory, or nil if this
85
- # source is not a file system path.
86
- # @return [String,nil]
87
- #
88
- attr_reader :source_path
89
-
90
- ##
91
- # Return the source proc, or nil if this source is not a proc.
92
- # @return [Proc,nil]
93
- #
94
- attr_reader :source_proc
95
-
96
- ##
97
- # Return the user-visible name of this source.
98
- # @return [String]
99
- #
100
- attr_reader :source_name
101
- alias to_s source_name
102
-
103
- ##
104
- # Return the absolute path to the given data file or directory.
105
- #
106
- # @param [String] path The relative path to find
107
- # @param [nil,:file,:directory] type Type of file system object to find,
108
- # or nil to return any type.
109
- # @return [String,nil] Absolute path of the result, or nil if not found.
110
- #
111
- def find_data(path, type: nil)
112
- if @data_dir
113
- full_path = ::File.join(@data_dir, path)
114
- case type
115
- when :file
116
- return full_path if ::File.file?(full_path)
117
- when :directory
118
- return full_path if ::File.directory?(full_path)
119
- else
120
- return full_path if ::File.readable?(full_path)
121
- end
122
- end
123
- parent&.find_data(path, type: type)
124
- end
125
-
126
- ##
127
- # Create a child SourceInfo relative to the parent path.
128
- # @private
129
- #
130
- def relative_child(filename, data_dir_name)
131
- raise "Cannot create relative child of a proc" unless source_path
132
- child_path = ::File.join(source_path, filename)
133
- child_path, type = SourceInfo.check_path(child_path, true)
134
- return nil unless child_path
135
- SourceInfo.new(self, context_directory, child_path, type, child_path, data_dir_name)
136
- end
137
-
138
- ##
139
- # Create a child SourceInfo with an absolute path.
140
- # @private
141
- #
142
- def absolute_child(child_path)
143
- child_path, type = SourceInfo.check_path(child_path, false)
144
- SourceInfo.new(self, context_directory, child_path, type, child_path, nil)
145
- end
146
-
147
- ##
148
- # Create a root source info for a file path.
149
- # @private
150
- #
151
- def self.create_path_root(source_path)
152
- source_path, type = check_path(source_path, false)
153
- context_directory = ::File.dirname(source_path)
154
- new(nil, context_directory, source_path, type, source_path, nil)
155
- end
156
-
157
- ##
158
- # Create a root source info for a proc.
159
- # @private
160
- #
161
- def self.create_proc_root(source_proc, source_name)
162
- new(nil, nil, source_proc, :proc, source_name, nil)
163
- end
164
-
165
- ##
166
- # Check a path and determine the canonical path and type.
167
- # @private
168
- #
169
- def self.check_path(path, lenient)
170
- path = ::File.expand_path(path)
171
- unless ::File.readable?(path)
172
- raise LoaderError, "Cannot read: #{path}" unless lenient
173
- return [nil, nil]
174
- end
175
- if ::File.file?(path)
176
- unless ::File.extname(path) == ".rb"
177
- raise LoaderError, "File is not a ruby file: #{path}" unless lenient
178
- return [nil, nil]
179
- end
180
- [path, :file]
181
- elsif ::File.directory?(path)
182
- [path, :directory]
183
- else
184
- raise LoaderError, "Unknown type: #{path}" unless lenient
185
- [nil, nil]
186
- end
187
- end
188
- end
189
- end
190
- end
@@ -1,842 +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
- require "set"
34
-
35
- module Toys
36
- module Definition
37
- ##
38
- # A Tool is a single command that can be invoked using Toys.
39
- # It has a name, a series of one or more words that you use to identify
40
- # the tool on the command line. It also has a set of formal flags and
41
- # command line arguments supported, and a block that gets run when the
42
- # tool is executed.
43
- #
44
- class Tool
45
- ##
46
- # Built-in acceptors (i.e. those recognized by OptionParser).
47
- # You can reference these acceptors directly. Otherwise, you have to add
48
- # one explicitly to the tool using {Tool#add_acceptor}.
49
- #
50
- OPTPARSER_ACCEPTORS = ::Set.new(
51
- [
52
- ::Object,
53
- ::NilClass,
54
- ::String,
55
- ::Integer,
56
- ::Float,
57
- ::Numeric,
58
- ::TrueClass,
59
- ::FalseClass,
60
- ::Array,
61
- ::Regexp,
62
- ::OptionParser::DecimalInteger,
63
- ::OptionParser::OctalInteger,
64
- ::OptionParser::DecimalNumeric
65
- ]
66
- ).freeze
67
-
68
- ##
69
- # Create a new tool.
70
- # @private
71
- #
72
- def initialize(loader, parent, full_name, priority, middleware_stack)
73
- @parent = parent
74
- @full_name = full_name.dup.freeze
75
- @priority = priority
76
- @middleware_stack = middleware_stack
77
-
78
- @acceptors = {}
79
- @mixins = {}
80
- @templates = {}
81
-
82
- reset_definition(loader)
83
- end
84
-
85
- ##
86
- # Reset the definition of this tool, deleting all definition data but
87
- # leaving named acceptors, mixins, and templates intact.
88
- # Should be called only from the DSL.
89
- # @private
90
- #
91
- def reset_definition(loader)
92
- @tool_class = DSL::Tool.new_class(@full_name, @priority, loader)
93
-
94
- @source_info = nil
95
- @definition_finished = false
96
-
97
- @desc = Utils::WrappableString.new("")
98
- @long_desc = []
99
-
100
- @default_data = {}
101
- @used_flags = []
102
- @initializers = []
103
-
104
- default_flag_group = Definition::FlagGroup.new(nil, nil, nil)
105
- @flag_groups = [default_flag_group]
106
- @flag_group_names = {nil => default_flag_group}
107
-
108
- @flag_definitions = []
109
- @required_arg_definitions = []
110
- @optional_arg_definitions = []
111
- @remaining_args_definition = nil
112
-
113
- @disable_argument_parsing = false
114
- @includes_modules = false
115
- @custom_context_directory = nil
116
- end
117
-
118
- ##
119
- # Return the name of the tool as an array of strings.
120
- # This array may not be modified.
121
- # @return [Array<String>]
122
- #
123
- attr_reader :full_name
124
-
125
- ##
126
- # Return the priority of this tool definition.
127
- # @return [Integer]
128
- #
129
- attr_reader :priority
130
-
131
- ##
132
- # Return the tool class.
133
- # @return [Class]
134
- #
135
- attr_reader :tool_class
136
-
137
- ##
138
- # Returns the short description string.
139
- # @return [Toys::Utils::WrappableString]
140
- #
141
- attr_reader :desc
142
-
143
- ##
144
- # Returns the long description strings as an array.
145
- # @return [Array<Toys::Utils::WrappableString>]
146
- #
147
- attr_reader :long_desc
148
-
149
- ##
150
- # Return a list of all defined flag groups, in order.
151
- # @return [Array<Toys::Definition::FlagGroup>]
152
- #
153
- attr_reader :flag_groups
154
-
155
- ##
156
- # Return a list of all defined flags.
157
- # @return [Array<Toys::Definition::Flag>]
158
- #
159
- attr_reader :flag_definitions
160
-
161
- ##
162
- # Return a list of all defined required positional arguments.
163
- # @return [Array<Toys::Definition::Arg>]
164
- #
165
- attr_reader :required_arg_definitions
166
-
167
- ##
168
- # Return a list of all defined optional positional arguments.
169
- # @return [Array<Toys::Definition::Arg>]
170
- #
171
- attr_reader :optional_arg_definitions
172
-
173
- ##
174
- # Return the remaining arguments specification, or `nil` if remaining
175
- # arguments are currently not supported by this tool.
176
- # @return [Toys::Definition::Arg,nil]
177
- #
178
- attr_reader :remaining_args_definition
179
-
180
- ##
181
- # Return a list of flags that have been used in the flag definitions.
182
- # @return [Array<String>]
183
- #
184
- attr_reader :used_flags
185
-
186
- ##
187
- # Return the default argument data.
188
- # @return [Hash]
189
- #
190
- attr_reader :default_data
191
-
192
- ##
193
- # Returns the middleware stack
194
- # @return [Array<Object>]
195
- #
196
- attr_reader :middleware_stack
197
-
198
- ##
199
- # Returns info on the source of this tool, or nil if the source is not
200
- # defined.
201
- # @return [Toys::Definition::SourceInfo,nil]
202
- #
203
- attr_reader :source_info
204
-
205
- ##
206
- # Returns the custom context directory set for this tool, or nil if none
207
- # is set.
208
- # @return [String,nil]
209
- #
210
- attr_reader :custom_context_directory
211
-
212
- ##
213
- # Returns the local name of this tool.
214
- # @return [String]
215
- #
216
- def simple_name
217
- full_name.last
218
- end
219
-
220
- ##
221
- # Returns a displayable name of this tool, generally the full name
222
- # delimited by spaces.
223
- # @return [String]
224
- #
225
- def display_name
226
- full_name.join(" ")
227
- end
228
-
229
- ##
230
- # Returns true if this tool is a root tool.
231
- # @return [Boolean]
232
- #
233
- def root?
234
- full_name.empty?
235
- end
236
-
237
- ##
238
- # Returns true if this tool is marked as runnable.
239
- # @return [Boolean]
240
- #
241
- def runnable?
242
- tool_class.public_instance_methods(false).include?(:run)
243
- end
244
-
245
- ##
246
- # Returns true if this tool has at least one included module.
247
- # @return [Boolean]
248
- #
249
- def includes_modules?
250
- @includes_modules
251
- end
252
-
253
- ##
254
- # Returns true if there is a specific description set for this tool.
255
- # @return [Boolean]
256
- #
257
- def includes_description?
258
- !long_desc.empty? || !desc.empty?
259
- end
260
-
261
- ##
262
- # Returns true if at least one flag or positional argument is defined
263
- # for this tool.
264
- # @return [Boolean]
265
- #
266
- def includes_arguments?
267
- !default_data.empty? || !flag_definitions.empty? ||
268
- !required_arg_definitions.empty? || !optional_arg_definitions.empty? ||
269
- !remaining_args_definition.nil?
270
- end
271
-
272
- ##
273
- # Returns true if this tool has any definition information.
274
- # @return [Boolean]
275
- #
276
- def includes_definition?
277
- includes_arguments? || runnable? || argument_parsing_disabled? ||
278
- includes_modules? || includes_description?
279
- end
280
-
281
- ##
282
- # Returns true if this tool's definition has been finished and is locked.
283
- # @return [Boolean]
284
- #
285
- def definition_finished?
286
- @definition_finished
287
- end
288
-
289
- ##
290
- # Returns true if this tool has disabled argument parsing.
291
- # @return [Boolean]
292
- #
293
- def argument_parsing_disabled?
294
- @disable_argument_parsing
295
- end
296
-
297
- ##
298
- # Returns all arg definitions in order: required, optional, remaining.
299
- # @return [Array<Toys::Definition::Arg>]
300
- #
301
- def arg_definitions
302
- result = required_arg_definitions + optional_arg_definitions
303
- result << remaining_args_definition if remaining_args_definition
304
- result
305
- end
306
-
307
- ##
308
- # Returns a list of all custom acceptors used by this tool.
309
- # @return [Array<Toys::Definition::Acceptor>]
310
- #
311
- def custom_acceptors
312
- result = []
313
- flag_definitions.each do |f|
314
- result << f.accept if f.accept.is_a?(Acceptor)
315
- end
316
- arg_definitions.each do |a|
317
- result << a.accept if a.accept.is_a?(Acceptor)
318
- end
319
- result.uniq
320
- end
321
-
322
- ##
323
- # Resolve the given acceptor. You may pass in a
324
- # {Toys::Definition::Acceptor}, an acceptor name, a well-known acceptor
325
- # understood by OptionParser, or `nil`.
326
- #
327
- # Returns either `nil` or an acceptor that is usable by OptionParser.
328
- #
329
- # If an acceptor name is given, it may be resolved by this tool or any of
330
- # its ancestors. Raises {Toys::ToolDefinitionError} if the name is not
331
- # recognized.
332
- #
333
- # @param [Object] accept An acceptor input.
334
- # @return [Object] The resolved acceptor.
335
- #
336
- def resolve_acceptor(accept)
337
- return accept if accept.nil? || accept.is_a?(Acceptor)
338
- name = accept
339
- accept = @acceptors.fetch(name) do |k|
340
- if @parent
341
- @parent.resolve_acceptor(k)
342
- elsif OPTPARSER_ACCEPTORS.include?(k)
343
- k
344
- end
345
- end
346
- if accept.nil?
347
- raise ToolDefinitionError, "Unknown acceptor: #{name.inspect}"
348
- end
349
- accept
350
- end
351
-
352
- ##
353
- # Get the named template from this tool or its ancestors.
354
- #
355
- # @param [String] name The template name
356
- # @return [Class,nil] The template class, or `nil` if not found.
357
- #
358
- def resolve_template(name)
359
- @templates.fetch(name.to_s) { |k| @parent ? @parent.resolve_template(k) : nil }
360
- end
361
-
362
- ##
363
- # Get the named mixin from this tool or its ancestors.
364
- #
365
- # @param [String] name The mixin name
366
- # @return [Module,nil] The mixin module, or `nil` if not found.
367
- #
368
- def resolve_mixin(name)
369
- @mixins.fetch(name.to_s) { |k| @parent ? @parent.resolve_mixin(k) : nil }
370
- end
371
-
372
- ##
373
- # Include the given mixin in the tool class.
374
- #
375
- # @param [String,Symbol,Module] name The mixin name or module
376
- #
377
- def include_mixin(name)
378
- tool_class.include(name)
379
- self
380
- end
381
-
382
- ##
383
- # Sets the path to the file that defines this tool.
384
- # A tool may be defined from at most one path. If a different path is
385
- # already set, raises {Toys::ToolDefinitionError}
386
- #
387
- # @param [Toys::Definition::SourceInfo] source Source info
388
- #
389
- def lock_source(source)
390
- if source_info && source_info.source != source.source
391
- raise ToolDefinitionError,
392
- "Cannot redefine tool #{display_name.inspect} in #{source.source_name}" \
393
- " (already defined in #{source_info.source_name})"
394
- end
395
- @source_info = source
396
- end
397
-
398
- ##
399
- # Set the short description string.
400
- #
401
- # The description may be provided as a {Toys::Utils::WrappableString}, a
402
- # single string (which will be wrapped), or an array of strings, which will
403
- # be interpreted as string fragments that will be concatenated and wrapped.
404
- #
405
- # @param [Toys::Utils::WrappableString,String,Array<String>] desc
406
- #
407
- def desc=(desc)
408
- check_definition_state
409
- @desc = Utils::WrappableString.make(desc)
410
- end
411
-
412
- ##
413
- # Set the long description strings.
414
- #
415
- # Each string may be provided as a {Toys::Utils::WrappableString}, a single
416
- # string (which will be wrapped), or an array of strings, which will be
417
- # interpreted as string fragments that will be concatenated and wrapped.
418
- #
419
- # @param [Array<Toys::Utils::WrappableString,String,Array<String>>] long_desc
420
- #
421
- def long_desc=(long_desc)
422
- check_definition_state
423
- @long_desc = Utils::WrappableString.make_array(long_desc)
424
- end
425
-
426
- ##
427
- # Append long description strings.
428
- #
429
- # Each string may be provided as a {Toys::Utils::WrappableString}, a single
430
- # string (which will be wrapped), or an array of strings, which will be
431
- # interpreted as string fragments that will be concatenated and wrapped.
432
- #
433
- # @param [Array<Toys::Utils::WrappableString,String,Array<String>>] long_desc
434
- #
435
- def append_long_desc(long_desc)
436
- check_definition_state
437
- @long_desc += Utils::WrappableString.make_array(long_desc)
438
- end
439
-
440
- ##
441
- # Add an acceptor to the tool. This acceptor may be refereneced by name
442
- # when adding a flag or an arg.
443
- #
444
- # @param [Toys::Definition::Acceptor] acceptor The acceptor to add.
445
- #
446
- def add_acceptor(acceptor)
447
- if @acceptors.key?(acceptor.name)
448
- raise ToolDefinitionError,
449
- "An acceptor named #{acceptor.name.inspect} has already been" \
450
- " defined in tool #{display_name.inspect}."
451
- end
452
- @acceptors[acceptor.name] = acceptor
453
- self
454
- end
455
-
456
- ##
457
- # Add a named mixin module to this tool.
458
- #
459
- # @param [String] name The name of the mixin.
460
- # @param [Module] mixin_module The mixin module.
461
- #
462
- def add_mixin(name, mixin_module)
463
- name = name.to_s
464
- if @mixins.key?(name)
465
- raise ToolDefinitionError,
466
- "A mixin named #{name.inspect} has already been defined in tool" \
467
- " #{display_name.inspect}."
468
- end
469
- @mixins[name] = mixin_module
470
- self
471
- end
472
-
473
- ##
474
- # Add a named template class to this tool.
475
- #
476
- # @param [String] name The name of the template.
477
- # @param [Class] template_class The template class.
478
- #
479
- def add_template(name, template_class)
480
- name = name.to_s
481
- if @templates.key?(name)
482
- raise ToolDefinitionError,
483
- "A template named #{name.inspect} has already been defined in tool" \
484
- " #{display_name.inspect}."
485
- end
486
- @templates[name] = template_class
487
- self
488
- end
489
-
490
- ##
491
- # Disable argument parsing for this tool
492
- #
493
- def disable_argument_parsing
494
- check_definition_state
495
- if includes_arguments?
496
- raise ToolDefinitionError,
497
- "Cannot disable argument parsing for tool #{display_name.inspect}" \
498
- " because arguments have already been defined."
499
- end
500
- @disable_argument_parsing = true
501
- self
502
- end
503
-
504
- ##
505
- # Add a flag group to the group list.
506
- #
507
- # @param [Symbol] type The type of group. Allowed values: `:required`,
508
- # `:optional`, `:exactly_one`, `:at_most_one`, `:at_least_one`.
509
- # Default is `:optional`.
510
- # @param [String,Array<String>,Toys::Utils::WrappableString] desc Short
511
- # description for the group. See {Toys::Definition::Tool#desc=} for a
512
- # description of allowed formats. Defaults to `"Flags"`.
513
- # @param [Array<String,Array<String>,Toys::Utils::WrappableString>] long_desc
514
- # Long description for the flag group. See
515
- # {Toys::Definition::Tool#long_desc=} for a description of allowed
516
- # formats. Defaults to the empty array.
517
- # @param [String,Symbol,nil] name The name of the group, or nil for no
518
- # name.
519
- # @param [Boolean] report_collisions If `true`, raise an exception if a
520
- # the given name is already taken. If `false`, ignore. Default is
521
- # `true`.
522
- # @param [Boolean] prepend If `true`, prepend rather than append the
523
- # group to the list. Default is `false`.
524
- #
525
- def add_flag_group(type: :optional, desc: nil, long_desc: nil,
526
- name: nil, report_collisions: true, prepend: false)
527
- if !name.nil? && @flag_group_names.key?(name)
528
- return self unless report_collisions
529
- raise ToolDefinitionError, "Flag group #{name} already exists"
530
- end
531
- unless type.is_a?(::Class)
532
- type = Utils::ModuleLookup.to_module_name(type)
533
- type = Definition::FlagGroup.const_get(type)
534
- end
535
- group = type.new(name, desc, long_desc)
536
- @flag_group_names[name] = group unless name.nil?
537
- if prepend
538
- @flag_groups.unshift(group)
539
- else
540
- @flag_groups.push(group)
541
- end
542
- self
543
- end
544
-
545
- ##
546
- # Add a flag to the current tool. Each flag must specify a key which
547
- # the script may use to obtain the flag value from the context.
548
- # You may then provide the flags themselves in `OptionParser` form.
549
- #
550
- # @param [String,Symbol] key The key to use to retrieve the value from
551
- # the execution context.
552
- # @param [Array<String>] flags The flags in OptionParser format.
553
- # @param [Object] accept An acceptor that validates and/or converts the
554
- # value. You may provide either the name of an acceptor you have
555
- # defined, or one of the default acceptors provided by OptionParser.
556
- # Optional. If not specified, accepts any value as a string.
557
- # @param [Object] default The default value. This is the value that will
558
- # be set in the context if this flag is not provided on the command
559
- # line. Defaults to `nil`.
560
- # @param [Proc,nil] handler An optional handler for setting/updating the
561
- # value. If given, it should take two arguments, the new given value
562
- # and the previous value, and it should return the new value that
563
- # should be set. The default handler simply replaces the previous
564
- # value. i.e. the default is effectively `-> (val, _prev) { val }`.
565
- # @param [Boolean] report_collisions Raise an exception if a flag is
566
- # requested that is already in use or marked as disabled. Default is
567
- # true.
568
- # @param [Toys::Definition::FlagGroup,String,Symbol,nil] group Group for
569
- # this flag. You may provide a group name, a FlagGroup object, or
570
- # `nil` which denotes the default group.
571
- # @param [String,Array<String>,Toys::Utils::WrappableString] desc Short
572
- # description for the flag. See {Toys::Definition::Tool#desc=} for a
573
- # description of allowed formats. Defaults to the empty string.
574
- # @param [Array<String,Array<String>,Toys::Utils::WrappableString>] long_desc
575
- # Long description for the flag. See
576
- # {Toys::Definition::Tool#long_desc=} for a description of allowed
577
- # formats. Defaults to the empty array.
578
- # @param [String] display_name A display name for this flag, used in help
579
- # text and error messages.
580
- #
581
- def add_flag(key, flags = [],
582
- accept: nil, default: nil, handler: nil,
583
- report_collisions: true, group: nil,
584
- desc: nil, long_desc: nil, display_name: nil)
585
- unless group.is_a?(Definition::FlagGroup)
586
- group_name = group
587
- group = @flag_group_names[group_name]
588
- raise ToolDefinitionError, "No such flag group: #{group_name.inspect}" if group.nil?
589
- end
590
- check_definition_state(is_arg: true)
591
- accept = resolve_acceptor(accept)
592
- flag_def = Definition::Flag.new(key, flags, @used_flags, report_collisions,
593
- accept, handler, default, display_name, group)
594
- flag_def.desc = desc if desc
595
- flag_def.long_desc = long_desc if long_desc
596
- if flag_def.active?
597
- @flag_definitions << flag_def
598
- group << flag_def
599
- end
600
- @default_data[key] = default
601
- self
602
- end
603
-
604
- ##
605
- # Mark one or more flags as disabled, preventing their use by any
606
- # subsequent flag definition. This may be used to prevent middleware from
607
- # defining a particular flag.
608
- #
609
- # @param [String...] flags The flags to disable
610
- #
611
- def disable_flag(*flags)
612
- check_definition_state(is_arg: true)
613
- flags = flags.uniq
614
- intersection = @used_flags & flags
615
- unless intersection.empty?
616
- raise ToolDefinitionError, "Cannot disable flags already used: #{intersection.inspect}"
617
- end
618
- @used_flags.concat(flags)
619
- self
620
- end
621
-
622
- ##
623
- # Add a required positional argument to the current tool. You must specify
624
- # a key which the script may use to obtain the argument value from the
625
- # context.
626
- #
627
- # @param [String,Symbol] key The key to use to retrieve the value from
628
- # the execution context.
629
- # @param [Object] accept An acceptor that validates and/or converts the
630
- # value. You may provide either the name of an acceptor you have
631
- # defined, or one of the default acceptors provided by OptionParser.
632
- # Optional. If not specified, accepts any value as a string.
633
- # @param [String] display_name A name to use for display (in help text and
634
- # error reports). Defaults to the key in upper case.
635
- # @param [String,Array<String>,Toys::Utils::WrappableString] desc Short
636
- # description for the arg. See {Toys::Definition::Tool#desc=} for a
637
- # description of allowed formats. Defaults to the empty string.
638
- # @param [Array<String,Array<String>,Toys::Utils::WrappableString>] long_desc
639
- # Long description for the arg. See
640
- # {Toys::Definition::Tool#long_desc=} for a description of allowed
641
- # formats. Defaults to the empty array.
642
- #
643
- def add_required_arg(key, accept: nil, display_name: nil, desc: nil, long_desc: nil)
644
- check_definition_state(is_arg: true)
645
- accept = resolve_acceptor(accept)
646
- arg_def = Definition::Arg.new(key, :required, accept, nil, desc, long_desc, display_name)
647
- @required_arg_definitions << arg_def
648
- self
649
- end
650
-
651
- ##
652
- # Add an optional positional argument to the current tool. You must specify
653
- # a key which the script may use to obtain the argument value from the
654
- # context. If an optional argument is not given on the command line, the
655
- # value is set to the given default.
656
- #
657
- # @param [String,Symbol] key The key to use to retrieve the value from
658
- # the execution context.
659
- # @param [Object] default The default value. This is the value that will
660
- # be set in the context if this argument is not provided on the command
661
- # line. Defaults to `nil`.
662
- # @param [Object] accept An acceptor that validates and/or converts the
663
- # value. You may provide either the name of an acceptor you have
664
- # defined, or one of the default acceptors provided by OptionParser.
665
- # Optional. If not specified, accepts any value as a string.
666
- # @param [String] display_name A name to use for display (in help text and
667
- # error reports). Defaults to the key in upper case.
668
- # @param [String,Array<String>,Toys::Utils::WrappableString] desc Short
669
- # description for the arg. See {Toys::Definition::Tool#desc=} for a
670
- # description of allowed formats. Defaults to the empty string.
671
- # @param [Array<String,Array<String>,Toys::Utils::WrappableString>] long_desc
672
- # Long description for the arg. See
673
- # {Toys::Definition::Tool#long_desc=} for a description of allowed
674
- # formats. Defaults to the empty array.
675
- #
676
- def add_optional_arg(key, default: nil, accept: nil, display_name: nil,
677
- desc: nil, long_desc: nil)
678
- check_definition_state(is_arg: true)
679
- accept = resolve_acceptor(accept)
680
- arg_def = Definition::Arg.new(key, :optional, accept, default,
681
- desc, long_desc, display_name)
682
- @optional_arg_definitions << arg_def
683
- @default_data[key] = default
684
- self
685
- end
686
-
687
- ##
688
- # Specify what should be done with unmatched positional arguments. You must
689
- # specify a key which the script may use to obtain the remaining args
690
- # from the context.
691
- #
692
- # @param [String,Symbol] key The key to use to retrieve the value from
693
- # the execution context.
694
- # @param [Object] default The default value. This is the value that will
695
- # be set in the context if no unmatched arguments are provided on the
696
- # command line. Defaults to the empty array `[]`.
697
- # @param [Object] accept An acceptor that validates and/or converts the
698
- # value. You may provide either the name of an acceptor you have
699
- # defined, or one of the default acceptors provided by OptionParser.
700
- # Optional. If not specified, accepts any value as a string.
701
- # @param [String] display_name A name to use for display (in help text and
702
- # error reports). Defaults to the key in upper case.
703
- # @param [String,Array<String>,Toys::Utils::WrappableString] desc Short
704
- # description for the arg. See {Toys::Definition::Tool#desc=} for a
705
- # description of allowed formats. Defaults to the empty string.
706
- # @param [Array<String,Array<String>,Toys::Utils::WrappableString>] long_desc
707
- # Long description for the arg. See
708
- # {Toys::Definition::Tool#long_desc=} for a description of allowed
709
- # formats. Defaults to the empty array.
710
- #
711
- def set_remaining_args(key, default: [], accept: nil, display_name: nil,
712
- desc: nil, long_desc: nil)
713
- check_definition_state(is_arg: true)
714
- accept = resolve_acceptor(accept)
715
- arg_def = Definition::Arg.new(key, :remaining, accept, default,
716
- desc, long_desc, display_name)
717
- @remaining_args_definition = arg_def
718
- @default_data[key] = default
719
- self
720
- end
721
-
722
- ##
723
- # Set the runnable block
724
- #
725
- # @param [Proc] proc The runnable block
726
- #
727
- def runnable=(proc)
728
- @tool_class.to_run(&proc)
729
- end
730
-
731
- ##
732
- # Add an initializer.
733
- #
734
- # @param [Proc] proc The initializer block
735
- # @param [Object...] args Arguments to pass to the initializer
736
- #
737
- def add_initializer(proc, *args)
738
- check_definition_state
739
- @initializers << [proc, args]
740
- self
741
- end
742
-
743
- ##
744
- # Set the custom context directory.
745
- #
746
- # @param [String] dir
747
- #
748
- def custom_context_directory=(dir)
749
- check_definition_state
750
- @custom_context_directory = dir
751
- end
752
-
753
- ##
754
- # Return the effective context directory.
755
- # If there is a custom context directory, uses that. Otherwise, looks for
756
- # a custom context directory up the tool ancestor chain. If none is
757
- # found, uses the default context directory from the source info. It is
758
- # possible for there to be no context directory at all, in which case,
759
- # returns nil.
760
- #
761
- # @return [String,nil]
762
- #
763
- def context_directory
764
- lookup_custom_context_directory || source_info&.context_directory
765
- end
766
-
767
- ##
768
- # Lookup the custom context directory in this tool and its ancestors.
769
- # @private
770
- #
771
- def lookup_custom_context_directory
772
- custom_context_directory || @parent&.lookup_custom_context_directory
773
- end
774
-
775
- ##
776
- # Mark this tool as having at least one module included
777
- # @private
778
- #
779
- def mark_includes_modules
780
- check_definition_state
781
- @includes_modules = true
782
- self
783
- end
784
-
785
- ##
786
- # Complete definition and run middleware configs. Should be called from
787
- # the Loader only.
788
- # @private
789
- #
790
- def finish_definition(loader)
791
- unless @definition_finished
792
- ContextualError.capture("Error installing tool middleware!", tool_name: full_name) do
793
- config_proc = proc {}
794
- middleware_stack.reverse_each do |middleware|
795
- config_proc = make_config_proc(middleware, loader, config_proc)
796
- end
797
- config_proc.call
798
- end
799
- flag_groups.each do |flag_group|
800
- flag_group.flag_definitions.sort_by!(&:sort_str)
801
- end
802
- @definition_finished = true
803
- end
804
- self
805
- end
806
-
807
- ##
808
- # Run all initializers against a tool. Should be called from the Runner
809
- # only.
810
- # @private
811
- #
812
- def run_initializers(tool)
813
- @initializers.each do |func, args|
814
- tool.instance_exec(*args, &func)
815
- end
816
- end
817
-
818
- ##
819
- # Check that the tool can still be defined. Should be called internally
820
- # or from the DSL only.
821
- # @private
822
- #
823
- def check_definition_state(is_arg: false)
824
- if @definition_finished
825
- raise ToolDefinitionError,
826
- "Defintion of tool #{display_name.inspect} is already finished"
827
- end
828
- if is_arg && argument_parsing_disabled?
829
- raise ToolDefinitionError,
830
- "Tool #{display_name.inspect} has disabled argument parsing"
831
- end
832
- self
833
- end
834
-
835
- private
836
-
837
- def make_config_proc(middleware, loader, next_config)
838
- proc { middleware.config(self, loader, &next_config) }
839
- end
840
- end
841
- end
842
- end