toys-core 0.3.3 → 0.3.4

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.
@@ -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