toys-core 0.3.2 → 0.3.3
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -0
- data/lib/toys/config_dsl.rb +26 -23
- data/lib/toys/core_version.rb +1 -1
- data/lib/toys/helpers.rb +3 -1
- data/lib/toys/helpers/{file_utils.rb → fileutils.rb} +7 -2
- data/lib/toys/helpers/highline.rb +127 -0
- data/lib/toys/helpers/spinner.rb +136 -0
- data/lib/toys/middleware/add_verbosity_switches.rb +2 -2
- data/lib/toys/middleware/handle_usage_errors.rb +5 -1
- data/lib/toys/middleware/show_usage.rb +11 -7
- data/lib/toys/middleware/show_version.rb +1 -1
- data/lib/toys/templates/clean.rb +1 -1
- data/lib/toys/templates/gem_build.rb +3 -1
- data/lib/toys/templates/minitest.rb +2 -2
- data/lib/toys/tool.rb +258 -99
- data/lib/toys/utils/module_lookup.rb +17 -5
- data/lib/toys/utils/usage.rb +101 -47
- data/lib/toys/utils/wrappable_string.rb +92 -0
- metadata +20 -3
@@ -48,18 +48,21 @@ module Toys
|
|
48
48
|
# @return [String] Converted string
|
49
49
|
#
|
50
50
|
def to_path_name(str)
|
51
|
-
str.to_s.
|
51
|
+
str = str.to_s.sub(/^_/, "").sub(/_$/, "").gsub(/_+/, "_")
|
52
|
+
while str.sub!(/([^_])([A-Z])/, "\\1_\\2") do end
|
53
|
+
str.downcase
|
52
54
|
end
|
53
55
|
|
54
56
|
##
|
55
57
|
# Convert the given string to a module name. Specifically, converts
|
56
|
-
# to `UpperCamelCase
|
58
|
+
# to `UpperCamelCase`, and then to a symbol.
|
57
59
|
#
|
58
60
|
# @param [String,Symbol] str String to convert.
|
59
|
-
# @return [
|
61
|
+
# @return [Symbol] Converted name
|
60
62
|
#
|
61
63
|
def to_module_name(str)
|
62
|
-
str.to_s.
|
64
|
+
str = str.to_s.sub(/^_/, "").sub(/_$/, "").gsub(/_+/, "_")
|
65
|
+
str.to_s.gsub(/(^|_)([a-zA-Z])/) { |_m| $2.upcase }.to_sym
|
63
66
|
end
|
64
67
|
|
65
68
|
##
|
@@ -77,7 +80,16 @@ module Toys
|
|
77
80
|
#
|
78
81
|
def lookup!(collection, name)
|
79
82
|
require "toys/#{to_path_name(collection)}/#{to_path_name(name)}"
|
80
|
-
|
83
|
+
collection_sym = to_module_name(collection)
|
84
|
+
unless ::Toys.constants.include?(collection_sym)
|
85
|
+
raise ::NameError, "Module does not exist: Toys::#{collection_sym}"
|
86
|
+
end
|
87
|
+
collection_mod = ::Toys.const_get(collection_sym)
|
88
|
+
name_sym = to_module_name(name)
|
89
|
+
unless collection_mod.constants.include?(name_sym)
|
90
|
+
raise ::NameError, "Module does not exist: Toys::#{collection_sym}::#{name_sym}"
|
91
|
+
end
|
92
|
+
collection_mod.const_get(name_sym)
|
81
93
|
end
|
82
94
|
|
83
95
|
##
|
data/lib/toys/utils/usage.rb
CHANGED
@@ -37,6 +37,18 @@ module Toys
|
|
37
37
|
# and related options.
|
38
38
|
#
|
39
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
|
+
|
40
52
|
##
|
41
53
|
# Create a usage helper given an execution context.
|
42
54
|
#
|
@@ -44,22 +56,22 @@ module Toys
|
|
44
56
|
# @return [Toys::Utils::Usage]
|
45
57
|
#
|
46
58
|
def self.from_context(context)
|
47
|
-
new(context[Context::TOOL], context[Context::
|
59
|
+
new(context[Context::TOOL], context[Context::LOADER], context[Context::BINARY_NAME])
|
48
60
|
end
|
49
61
|
|
50
62
|
##
|
51
63
|
# Create a usage helper.
|
52
64
|
#
|
53
65
|
# @param [Toys::Tool] tool The tool for which to generate documentation.
|
54
|
-
# @param [String] binary_name The name of the binary. e.g. `"toys"`.
|
55
66
|
# @param [Toys::Loader] loader A loader that can provide subcommands.
|
67
|
+
# @param [String] binary_name The name of the binary. e.g. `"toys"`.
|
56
68
|
#
|
57
69
|
# @return [Toys::Utils::Usage]
|
58
70
|
#
|
59
|
-
def initialize(tool,
|
71
|
+
def initialize(tool, loader, binary_name)
|
60
72
|
@tool = tool
|
61
|
-
@binary_name = binary_name
|
62
73
|
@loader = loader
|
74
|
+
@binary_name = binary_name
|
63
75
|
end
|
64
76
|
|
65
77
|
##
|
@@ -71,27 +83,29 @@ module Toys
|
|
71
83
|
# listing subcommands. Defaults to `nil` which finds all subcommands.
|
72
84
|
# @param [Boolean] show_path If true, shows the path to the config file
|
73
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}.
|
74
89
|
#
|
75
90
|
# @return [String] A usage string.
|
76
91
|
#
|
77
|
-
def string(recursive: false, search: nil, show_path: false
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
end
|
88
|
-
add_switches(optparse)
|
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)
|
89
102
|
if @tool.includes_executor?
|
90
|
-
add_positional_arguments(
|
103
|
+
add_positional_arguments(lines, indent, left_column_width, right_column_wrap_width)
|
91
104
|
else
|
92
|
-
add_command_list(
|
105
|
+
add_command_list(lines, recursive, search, indent,
|
106
|
+
left_column_width, right_column_wrap_width)
|
93
107
|
end
|
94
|
-
|
108
|
+
lines.join("\n") + "\n"
|
95
109
|
end
|
96
110
|
|
97
111
|
private
|
@@ -118,40 +132,62 @@ module Toys
|
|
118
132
|
# Returns the banner string for a group
|
119
133
|
#
|
120
134
|
def group_banner
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
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
|
125
151
|
end
|
126
152
|
|
127
153
|
#
|
128
154
|
# Add switches from the tool to the given optionparser. Causes the
|
129
155
|
# optparser to generate documentation for those switches.
|
130
156
|
#
|
131
|
-
def add_switches(
|
157
|
+
def add_switches(lines, indent, left_column_width, right_column_wrap_width)
|
132
158
|
return if @tool.switch_definitions.empty?
|
133
|
-
|
134
|
-
|
159
|
+
lines << ""
|
160
|
+
lines << "Options:"
|
135
161
|
@tool.switch_definitions.each do |switch|
|
136
|
-
|
162
|
+
add_switch(lines, switch, indent, left_column_width, right_column_wrap_width)
|
137
163
|
end
|
138
164
|
end
|
139
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
|
+
|
140
178
|
#
|
141
179
|
# Add documentation for the tool's positional arguments, to the given
|
142
180
|
# option parser.
|
143
181
|
#
|
144
|
-
def add_positional_arguments(
|
182
|
+
def add_positional_arguments(lines, indent, left_column_width, right_column_wrap_width)
|
145
183
|
args_to_display = @tool.required_arg_definitions + @tool.optional_arg_definitions
|
146
184
|
args_to_display << @tool.remaining_args_definition if @tool.remaining_args_definition
|
147
185
|
return if args_to_display.empty?
|
148
|
-
|
149
|
-
|
186
|
+
lines << ""
|
187
|
+
lines << "Positional arguments:"
|
150
188
|
args_to_display.each do |arg_info|
|
151
|
-
|
152
|
-
|
153
|
-
optparse.separator(" #{d}")
|
154
|
-
end
|
189
|
+
add_doc(lines, arg_info.canonical_name, arg_info.wrapped_docs(right_column_wrap_width),
|
190
|
+
indent, left_column_width)
|
155
191
|
end
|
156
192
|
end
|
157
193
|
|
@@ -159,23 +195,40 @@ module Toys
|
|
159
195
|
# Add documentation for the tool's subcommands, to the given option
|
160
196
|
# parser.
|
161
197
|
#
|
162
|
-
def add_command_list(
|
198
|
+
def add_command_list(lines, recursive, search, indent,
|
199
|
+
left_column_width, right_column_wrap_width)
|
163
200
|
name_len = @tool.full_name.length
|
164
201
|
subtools = find_commands(recursive, search)
|
165
202
|
return if subtools.empty?
|
166
|
-
|
167
|
-
|
168
|
-
optparse.separator("Commands with search term #{search.inspect}:")
|
169
|
-
else
|
170
|
-
optparse.separator("Commands:")
|
171
|
-
end
|
203
|
+
lines << ""
|
204
|
+
lines << (search ? "Commands with search term #{search.inspect}:" : "Commands:")
|
172
205
|
subtools.each do |subtool|
|
173
|
-
tool_name = subtool.full_name.slice(name_len..-1).join(" ")
|
174
|
-
|
175
|
-
|
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] || []
|
176
226
|
else
|
177
|
-
|
227
|
+
lines << initial
|
228
|
+
doc
|
178
229
|
end
|
230
|
+
remaining_doc.each do |d|
|
231
|
+
lines << "#{' ' * (indent + left_column_width)} #{d}"
|
179
232
|
end
|
180
233
|
end
|
181
234
|
|
@@ -187,8 +240,9 @@ module Toys
|
|
187
240
|
return subtools if search.nil? || search.empty?
|
188
241
|
regex = Regexp.new("(^|\\s)#{Regexp.escape(search)}(\\s|$)", Regexp::IGNORECASE)
|
189
242
|
subtools.find_all do |tool|
|
190
|
-
regex =~ tool.display_name ||
|
191
|
-
regex =~
|
243
|
+
regex =~ tool.display_name ||
|
244
|
+
tool.effective_desc.find { |d| regex =~ d } ||
|
245
|
+
tool.effective_long_desc.find { |d| regex =~ d }
|
192
246
|
end
|
193
247
|
end
|
194
248
|
end
|
@@ -0,0 +1,92 @@
|
|
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 string intended to be wrapped.
|
34
|
+
#
|
35
|
+
class WrappableString
|
36
|
+
##
|
37
|
+
# Create a wrapped string.
|
38
|
+
# @param [String] string The string.
|
39
|
+
#
|
40
|
+
def initialize(string = "")
|
41
|
+
@string = string
|
42
|
+
end
|
43
|
+
|
44
|
+
##
|
45
|
+
# Returns the string.
|
46
|
+
# @return [String]
|
47
|
+
#
|
48
|
+
attr_reader :string
|
49
|
+
|
50
|
+
##
|
51
|
+
# Returns the string.
|
52
|
+
# @return [String]
|
53
|
+
#
|
54
|
+
def to_s
|
55
|
+
string
|
56
|
+
end
|
57
|
+
|
58
|
+
## @private
|
59
|
+
def ==(other)
|
60
|
+
other.is_a?(WrappableString) ? other.string == string : false
|
61
|
+
end
|
62
|
+
alias eql? ==
|
63
|
+
|
64
|
+
## @private
|
65
|
+
def hash
|
66
|
+
string.hash
|
67
|
+
end
|
68
|
+
|
69
|
+
##
|
70
|
+
# Wraps the string to the given width.
|
71
|
+
#
|
72
|
+
# @param [Integer] width Width in characters.
|
73
|
+
# @return [Array<String>] Wrapped lines
|
74
|
+
#
|
75
|
+
def wrap(width)
|
76
|
+
lines = []
|
77
|
+
str = string.gsub(/\s/, " ").sub(/^\s+/, "")
|
78
|
+
until str.empty?
|
79
|
+
i = str.index(/\S(\s|$)/) + 1
|
80
|
+
loop do
|
81
|
+
next_i = str.index(/\S(\s|$)/, i)
|
82
|
+
break if next_i.nil? || next_i >= width
|
83
|
+
i = next_i + 1
|
84
|
+
end
|
85
|
+
lines << str[0, i]
|
86
|
+
str = str[i..-1].sub(/^\s+/, "")
|
87
|
+
end
|
88
|
+
lines
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: toys-core
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Azuma
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-05-
|
11
|
+
date: 2018-05-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: highline
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.7'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.7'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: minitest
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -100,7 +114,9 @@ files:
|
|
100
114
|
- lib/toys/errors.rb
|
101
115
|
- lib/toys/helpers.rb
|
102
116
|
- lib/toys/helpers/exec.rb
|
103
|
-
- lib/toys/helpers/
|
117
|
+
- lib/toys/helpers/fileutils.rb
|
118
|
+
- lib/toys/helpers/highline.rb
|
119
|
+
- lib/toys/helpers/spinner.rb
|
104
120
|
- lib/toys/loader.rb
|
105
121
|
- lib/toys/middleware.rb
|
106
122
|
- lib/toys/middleware/add_verbosity_switches.rb
|
@@ -119,6 +135,7 @@ files:
|
|
119
135
|
- lib/toys/tool.rb
|
120
136
|
- lib/toys/utils/module_lookup.rb
|
121
137
|
- lib/toys/utils/usage.rb
|
138
|
+
- lib/toys/utils/wrappable_string.rb
|
122
139
|
homepage: https://github.com/dazuma/toys
|
123
140
|
licenses:
|
124
141
|
- BSD-3-Clause
|