toys-core 0.3.3 → 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,250 +0,0 @@
1
- # Copyright 2018 Daniel Azuma
2
- #
3
- # All rights reserved.
4
- #
5
- # Redistribution and use in source and binary forms, with or without
6
- # modification, are permitted provided that the following conditions are met:
7
- #
8
- # * Redistributions of source code must retain the above copyright notice,
9
- # this list of conditions and the following disclaimer.
10
- # * Redistributions in binary form must reproduce the above copyright notice,
11
- # this list of conditions and the following disclaimer in the documentation
12
- # and/or other materials provided with the distribution.
13
- # * Neither the name of the copyright holder, nor the names of any other
14
- # contributors to this software, may be used to endorse or promote products
15
- # derived from this software without specific prior written permission.
16
- #
17
- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18
- # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
- # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20
- # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21
- # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22
- # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23
- # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24
- # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25
- # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26
- # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
- # POSSIBILITY OF SUCH DAMAGE.
28
- ;
29
-
30
- module Toys
31
- module Utils
32
- ##
33
- # A helper class that generates usage documentation for a tool.
34
- #
35
- # This class generates full usage documentation, including description,
36
- # switches, and arguments. It is used by middleware that implements help
37
- # and related options.
38
- #
39
- class Usage
40
- ##
41
- # Default width of first column
42
- # @return [Integer]
43
- #
44
- DEFAULT_LEFT_COLUMN_WIDTH = 32
45
-
46
- ##
47
- # Default indent
48
- # @return [Integer]
49
- #
50
- DEFAULT_INDENT = 4
51
-
52
- ##
53
- # Create a usage helper given an execution context.
54
- #
55
- # @param [Toys::Context] context The current execution context.
56
- # @return [Toys::Utils::Usage]
57
- #
58
- def self.from_context(context)
59
- new(context[Context::TOOL], context[Context::LOADER], context[Context::BINARY_NAME])
60
- end
61
-
62
- ##
63
- # Create a usage helper.
64
- #
65
- # @param [Toys::Tool] tool The tool for which to generate documentation.
66
- # @param [Toys::Loader] loader A loader that can provide subcommands.
67
- # @param [String] binary_name The name of the binary. e.g. `"toys"`.
68
- #
69
- # @return [Toys::Utils::Usage]
70
- #
71
- def initialize(tool, loader, binary_name)
72
- @tool = tool
73
- @loader = loader
74
- @binary_name = binary_name
75
- end
76
-
77
- ##
78
- # Generate a usage string.
79
- #
80
- # @param [Boolean] recursive If true, and the tool is a group tool,
81
- # display all subcommands recursively. Defaults to false.
82
- # @param [String,nil] search An optional string to search for when
83
- # listing subcommands. Defaults to `nil` which finds all subcommands.
84
- # @param [Boolean] show_path If true, shows the path to the config file
85
- # containing the tool definition (if set). Defaults to false.
86
- # @param [Integer] left_column_width Width of the first column. Default
87
- # is {DEFAULT_LEFT_COLUMN_WIDTH}.
88
- # @param [Integer] indent Indent width. Default is {DEFAULT_INDENT}.
89
- #
90
- # @return [String] A usage string.
91
- #
92
- def string(recursive: false, search: nil, show_path: false,
93
- left_column_width: nil, indent: nil,
94
- wrap_width: nil, right_column_wrap_width: nil)
95
- left_column_width ||= DEFAULT_LEFT_COLUMN_WIDTH
96
- indent ||= DEFAULT_INDENT
97
- right_column_wrap_width ||= wrap_width - left_column_width - indent - 1 if wrap_width
98
- lines = []
99
- lines << (@tool.includes_executor? ? tool_banner : group_banner)
100
- add_description(lines, wrap_width, show_path)
101
- add_switches(lines, indent, left_column_width, right_column_wrap_width)
102
- if @tool.includes_executor?
103
- add_positional_arguments(lines, indent, left_column_width, right_column_wrap_width)
104
- else
105
- add_command_list(lines, recursive, search, indent,
106
- left_column_width, right_column_wrap_width)
107
- end
108
- lines.join("\n") + "\n"
109
- end
110
-
111
- private
112
-
113
- #
114
- # Returns the banner string for a normal tool
115
- #
116
- def tool_banner
117
- banner = ["Usage:", @binary_name] + @tool.full_name
118
- banner << "[<options...>]" unless @tool.switch_definitions.empty?
119
- @tool.required_arg_definitions.each do |arg_info|
120
- banner << "<#{arg_info.canonical_name}>"
121
- end
122
- @tool.optional_arg_definitions.each do |arg_info|
123
- banner << "[<#{arg_info.canonical_name}>]"
124
- end
125
- if @tool.remaining_args_definition
126
- banner << "[<#{@tool.remaining_args_definition.canonical_name}...>]"
127
- end
128
- banner.join(" ")
129
- end
130
-
131
- #
132
- # Returns the banner string for a group
133
- #
134
- def group_banner
135
- banner = ["Usage:", @binary_name] +
136
- @tool.full_name +
137
- ["<command>", "<command-arguments...>"]
138
- banner.join(" ")
139
- end
140
-
141
- def add_description(lines, wrap_width, show_path)
142
- long_desc = @tool.effective_long_desc(wrap_width: wrap_width)
143
- unless long_desc.empty?
144
- lines << ""
145
- lines.concat(long_desc)
146
- end
147
- if show_path && @tool.definition_path
148
- lines << ""
149
- lines << "Defined in #{@tool.definition_path}"
150
- end
151
- end
152
-
153
- #
154
- # Add switches from the tool to the given optionparser. Causes the
155
- # optparser to generate documentation for those switches.
156
- #
157
- def add_switches(lines, indent, left_column_width, right_column_wrap_width)
158
- return if @tool.switch_definitions.empty?
159
- lines << ""
160
- lines << "Options:"
161
- @tool.switch_definitions.each do |switch|
162
- add_switch(lines, switch, indent, left_column_width, right_column_wrap_width)
163
- end
164
- end
165
-
166
- #
167
- # Add a single switch
168
- #
169
- def add_switch(lines, switch, indent, left_column_width, right_column_wrap_width)
170
- switches_str = (switch.single_switch_syntax.map(&:str_without_value) +
171
- switch.double_switch_syntax.map(&:str_without_value)).join(", ")
172
- switches_str << switch.value_delim << switch.value_label if switch.value_label
173
- switches_str = " #{switches_str}" if switch.single_switch_syntax.empty?
174
- add_doc(lines, switches_str, switch.wrapped_docs(right_column_wrap_width),
175
- indent, left_column_width)
176
- end
177
-
178
- #
179
- # Add documentation for the tool's positional arguments, to the given
180
- # option parser.
181
- #
182
- def add_positional_arguments(lines, indent, left_column_width, right_column_wrap_width)
183
- args_to_display = @tool.required_arg_definitions + @tool.optional_arg_definitions
184
- args_to_display << @tool.remaining_args_definition if @tool.remaining_args_definition
185
- return if args_to_display.empty?
186
- lines << ""
187
- lines << "Positional arguments:"
188
- args_to_display.each do |arg_info|
189
- add_doc(lines, arg_info.canonical_name, arg_info.wrapped_docs(right_column_wrap_width),
190
- indent, left_column_width)
191
- end
192
- end
193
-
194
- #
195
- # Add documentation for the tool's subcommands, to the given option
196
- # parser.
197
- #
198
- def add_command_list(lines, recursive, search, indent,
199
- left_column_width, right_column_wrap_width)
200
- name_len = @tool.full_name.length
201
- subtools = find_commands(recursive, search)
202
- return if subtools.empty?
203
- lines << ""
204
- lines << (search ? "Commands with search term #{search.inspect}:" : "Commands:")
205
- subtools.each do |subtool|
206
- tool_name = subtool.full_name.slice(name_len..-1).join(" ")
207
- doc =
208
- if subtool.is_a?(Alias)
209
- ["(Alias of #{subtool.display_target})"]
210
- else
211
- subtool.effective_desc(wrap_width: right_column_wrap_width)
212
- end
213
- add_doc(lines, tool_name, doc, indent, left_column_width)
214
- end
215
- end
216
-
217
- #
218
- # Add a line with possible documentation strings.
219
- #
220
- def add_doc(lines, initial, doc, indent, left_column_width)
221
- initial = "#{' ' * indent}#{initial.ljust(left_column_width)}"
222
- remaining_doc =
223
- if initial.size <= indent + left_column_width
224
- lines << "#{initial} #{doc.first}"
225
- doc[1..-1] || []
226
- else
227
- lines << initial
228
- doc
229
- end
230
- remaining_doc.each do |d|
231
- lines << "#{' ' * (indent + left_column_width)} #{d}"
232
- end
233
- end
234
-
235
- #
236
- # Find subcommands of the current tool
237
- #
238
- def find_commands(recursive, search)
239
- subtools = @loader.list_subtools(@tool.full_name, recursive: recursive)
240
- return subtools if search.nil? || search.empty?
241
- regex = Regexp.new("(^|\\s)#{Regexp.escape(search)}(\\s|$)", Regexp::IGNORECASE)
242
- subtools.find_all do |tool|
243
- regex =~ tool.display_name ||
244
- tool.effective_desc.find { |d| regex =~ d } ||
245
- tool.effective_long_desc.find { |d| regex =~ d }
246
- end
247
- end
248
- end
249
- end
250
- end