toys 0.3.1 → 0.3.2
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/.yardopts +0 -2
- data/CHANGELOG.md +20 -0
- data/README.md +4 -24
- data/bin/toys +1 -1
- data/{lib/toys/builtins → builtins}/do.rb +1 -1
- data/{lib/toys/builtins → builtins}/system.rb +0 -0
- data/lib/toys.rb +9 -14
- data/lib/toys/standard_cli.rb +151 -0
- data/lib/toys/version.rb +2 -2
- metadata +19 -28
- data/lib/toys/cli.rb +0 -271
- data/lib/toys/config_dsl.rb +0 -432
- data/lib/toys/context.rb +0 -278
- data/lib/toys/errors.rb +0 -42
- data/lib/toys/helpers.rb +0 -52
- data/lib/toys/helpers/exec.rb +0 -469
- data/lib/toys/helpers/file_utils.rb +0 -39
- data/lib/toys/loader.rb +0 -423
- data/lib/toys/middleware.rb +0 -55
- data/lib/toys/middleware/base.rb +0 -51
- data/lib/toys/middleware/set_verbosity.rb +0 -54
- data/lib/toys/middleware/show_group_usage.rb +0 -68
- data/lib/toys/middleware/show_tool_usage.rb +0 -64
- data/lib/toys/middleware/show_usage_errors.rb +0 -57
- data/lib/toys/template.rb +0 -123
- data/lib/toys/templates.rb +0 -55
- data/lib/toys/templates/clean.rb +0 -80
- data/lib/toys/templates/gem_build.rb +0 -115
- data/lib/toys/templates/minitest.rb +0 -108
- data/lib/toys/templates/rubocop.rb +0 -81
- data/lib/toys/templates/yardoc.rb +0 -95
- data/lib/toys/tool.rb +0 -831
- data/lib/toys/utils/module_lookup.rb +0 -101
- data/lib/toys/utils/usage.rb +0 -163
@@ -1,39 +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
|
-
require "fileutils"
|
31
|
-
|
32
|
-
module Toys
|
33
|
-
module Helpers
|
34
|
-
##
|
35
|
-
# File system utilities. See the "fileutils" standard library.
|
36
|
-
#
|
37
|
-
FileUtils = ::FileUtils
|
38
|
-
end
|
39
|
-
end
|
data/lib/toys/loader.rb
DELETED
@@ -1,423 +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
|
-
##
|
32
|
-
# The Loader service loads tools from configuration files, and finds the
|
33
|
-
# appropriate tool given a set of command line arguments.
|
34
|
-
#
|
35
|
-
class Loader
|
36
|
-
##
|
37
|
-
# Create a Loader
|
38
|
-
#
|
39
|
-
# @param [String,nil] config_dir_name A directory with this name that
|
40
|
-
# appears in the loader path, is treated as a configuration directory
|
41
|
-
# whose contents are loaded into the toys configuration. Optional.
|
42
|
-
# If not provided, toplevel configuration directories are disabled.
|
43
|
-
# @param [String,nil] config_file_name A file with this name that appears
|
44
|
-
# in the loader path, is treated as a toplevel configuration file
|
45
|
-
# whose contents are loaded into the toys configuration. Optional.
|
46
|
-
# If not provided, toplevel configuration files are disabled.
|
47
|
-
# @param [String,nil] index_file_name A file with this name that appears
|
48
|
-
# in any configuration directory (not just a toplevel directory) is
|
49
|
-
# loaded first as a standalone configuration file. If not provided,
|
50
|
-
# standalone configuration files are disabled.
|
51
|
-
# @param [String,nil] preload_file_name A file with this name that appears
|
52
|
-
# in any configuration directory (not just a toplevel directory) is
|
53
|
-
# loaded before any configuration files. It is not treated as a
|
54
|
-
# configuration file in that the configuration DSL is not honored. You
|
55
|
-
# may use such a file to define auxiliary Ruby modules and classes that
|
56
|
-
# used by the tools defined in that directory.
|
57
|
-
# @param [Array] middleware An array of middleware that will be used by
|
58
|
-
# default for all tools loaded by this CLI.
|
59
|
-
# @param [String] root_desc The description of the root tool.
|
60
|
-
#
|
61
|
-
def initialize(config_dir_name: nil, config_file_name: nil,
|
62
|
-
index_file_name: nil, preload_file_name: nil,
|
63
|
-
middleware: [], root_desc: nil)
|
64
|
-
@config_dir_name = config_dir_name
|
65
|
-
@config_file_name = config_file_name
|
66
|
-
@index_file_name = index_file_name
|
67
|
-
@preload_file_name = preload_file_name
|
68
|
-
@middleware = middleware
|
69
|
-
check_init_options
|
70
|
-
@load_worklist = []
|
71
|
-
root_tool = Tool.new([])
|
72
|
-
root_tool.middleware_stack.concat(@middleware)
|
73
|
-
root_tool.long_desc = root_desc if root_desc
|
74
|
-
@tools = {[] => [root_tool, nil]}
|
75
|
-
@max_priority = @min_priority = 0
|
76
|
-
end
|
77
|
-
|
78
|
-
##
|
79
|
-
# Add one or more configuration files/directories to the loader.
|
80
|
-
# This might point to a directory that defines a default set of tools.
|
81
|
-
#
|
82
|
-
# @param [String,Array<String>] paths One or more paths to add.
|
83
|
-
# @param [Boolean] high_priority If true, add these paths at the top of
|
84
|
-
# the priority list. Defaults to false, indicating new paths should
|
85
|
-
# be at the bottom of the priority list.
|
86
|
-
#
|
87
|
-
def add_config_paths(paths, high_priority: false)
|
88
|
-
paths = Array(paths)
|
89
|
-
paths = paths.reverse if high_priority
|
90
|
-
paths.each do |path|
|
91
|
-
add_config_path(path, high_priority: high_priority)
|
92
|
-
end
|
93
|
-
self
|
94
|
-
end
|
95
|
-
|
96
|
-
##
|
97
|
-
# Add a single configuration file/directory to the loader.
|
98
|
-
# This might point to a directory that defines a default set of tools.
|
99
|
-
#
|
100
|
-
# @param [String] path A path to add.
|
101
|
-
# @param [Boolean] high_priority If true, add this path at the top of the
|
102
|
-
# priority list. Defaults to false, indicating the new path should be
|
103
|
-
# at the bottom of the priority list.
|
104
|
-
#
|
105
|
-
def add_config_path(path, high_priority: false)
|
106
|
-
path = check_path(path)
|
107
|
-
priority = high_priority ? (@max_priority += 1) : (@min_priority -= 1)
|
108
|
-
@load_worklist << [path, [], priority]
|
109
|
-
self
|
110
|
-
end
|
111
|
-
|
112
|
-
##
|
113
|
-
# Add one or more path directories to the loader. These directories are
|
114
|
-
# searched for toplevel config directories and files.
|
115
|
-
#
|
116
|
-
# @param [String,Array<String>] paths One or more paths to add.
|
117
|
-
# @param [Boolean] high_priority If true, add these paths at the top of
|
118
|
-
# the priority list. Defaults to false, indicating new paths should
|
119
|
-
# be at the bottom of the priority list.
|
120
|
-
#
|
121
|
-
def add_paths(paths, high_priority: false)
|
122
|
-
paths = Array(paths)
|
123
|
-
paths = paths.reverse if high_priority
|
124
|
-
paths.each do |path|
|
125
|
-
add_path(path, high_priority: high_priority)
|
126
|
-
end
|
127
|
-
self
|
128
|
-
end
|
129
|
-
|
130
|
-
##
|
131
|
-
# Add a single path directory to the loader. This directory is searched
|
132
|
-
# for toplevel config directories and files.
|
133
|
-
#
|
134
|
-
# @param [String] path A path to add.
|
135
|
-
# @param [Boolean] high_priority If true, add this path at the top of the
|
136
|
-
# priority list. Defaults to false, indicating the new path should be
|
137
|
-
# at the bottom of the priority list.
|
138
|
-
#
|
139
|
-
def add_path(path, high_priority: false)
|
140
|
-
path = check_path(path, type: :dir)
|
141
|
-
priority = high_priority ? (@max_priority += 1) : (@min_priority -= 1)
|
142
|
-
if @config_file_name
|
143
|
-
p = ::File.join(path, @config_file_name)
|
144
|
-
if !::File.directory?(p) && ::File.readable?(p)
|
145
|
-
@load_worklist << [p, [], priority]
|
146
|
-
end
|
147
|
-
end
|
148
|
-
if @config_dir_name
|
149
|
-
p = ::File.join(path, @config_dir_name)
|
150
|
-
if ::File.directory?(p) && ::File.readable?(p)
|
151
|
-
@load_worklist << [p, [], priority]
|
152
|
-
end
|
153
|
-
end
|
154
|
-
self
|
155
|
-
end
|
156
|
-
|
157
|
-
##
|
158
|
-
# Given a list of command line arguments, find the appropriate tool to
|
159
|
-
# handle the command, loading it from the configuration if necessary.
|
160
|
-
# This always returns a tool. If the specific tool path is not defined and
|
161
|
-
# cannot be found in any configuration, it returns the nearest group that
|
162
|
-
# _would_ contain that tool, up to the root tool.
|
163
|
-
#
|
164
|
-
# @param [String] args Command line arguments
|
165
|
-
# @return [Toys::Tool]
|
166
|
-
#
|
167
|
-
def lookup(args)
|
168
|
-
orig_prefix = args.take_while { |arg| !arg.start_with?("-") }
|
169
|
-
cur_prefix = orig_prefix.dup
|
170
|
-
loop do
|
171
|
-
load_for_prefix(cur_prefix)
|
172
|
-
p = orig_prefix.dup
|
173
|
-
while p.length >= cur_prefix.length
|
174
|
-
return @tools[p].first if @tools.key?(p)
|
175
|
-
p.pop
|
176
|
-
end
|
177
|
-
raise "Bug: No tools found" unless cur_prefix.pop
|
178
|
-
end
|
179
|
-
end
|
180
|
-
|
181
|
-
##
|
182
|
-
# Returns a list of subtools for the given path, loading from the
|
183
|
-
# configuration if necessary.
|
184
|
-
#
|
185
|
-
# @param [Array<String>] words The name of the parent tool
|
186
|
-
# @param [Boolean] recursive If true, return all subtools recursively
|
187
|
-
# rather than just the immediate children (the default)
|
188
|
-
# @return [Array<Toys::Tool>]
|
189
|
-
#
|
190
|
-
def list_subtools(words, recursive: false)
|
191
|
-
load_for_prefix(words)
|
192
|
-
found_tools = []
|
193
|
-
len = words.length
|
194
|
-
@tools.each do |n, tp|
|
195
|
-
next if n.empty?
|
196
|
-
if recursive
|
197
|
-
next if n.length <= len || n.slice(0, len) != words
|
198
|
-
else
|
199
|
-
next unless n.slice(0..-2) == words
|
200
|
-
end
|
201
|
-
found_tools << tp.first
|
202
|
-
end
|
203
|
-
sort_tools_by_name(found_tools)
|
204
|
-
end
|
205
|
-
|
206
|
-
##
|
207
|
-
# Execute the tool given by the given arguments, in the given context.
|
208
|
-
#
|
209
|
-
# @param [Toys::Context::Base] context_base The context in which to
|
210
|
-
# execute.
|
211
|
-
# @param [String] args Command line arguments
|
212
|
-
# @param [Integer] verbosity Starting verbosity. Defaults to 0.
|
213
|
-
# @return [Integer] The exit code
|
214
|
-
#
|
215
|
-
# @private
|
216
|
-
#
|
217
|
-
def execute(context_base, args, verbosity: 0)
|
218
|
-
tool = lookup(args)
|
219
|
-
tool.execute(context_base, args.slice(tool.full_name.length..-1), verbosity: verbosity)
|
220
|
-
end
|
221
|
-
|
222
|
-
##
|
223
|
-
# Returns a tool specified by the given words, with the given priority.
|
224
|
-
# Does not do any loading. If the tool is not present, creates it.
|
225
|
-
#
|
226
|
-
# @param [Array<String>] words The name of the tool.
|
227
|
-
# @param [Integer] priority The priority of the request.
|
228
|
-
# @param [Boolean] assume_parent If true, does not check the parent tool's
|
229
|
-
# priority.
|
230
|
-
# @return [Toys::Tool,nil] The tool, or `nil` if the given priority is
|
231
|
-
# insufficient.
|
232
|
-
#
|
233
|
-
# @private
|
234
|
-
#
|
235
|
-
def get_or_create_tool(words, priority, assume_parent: false)
|
236
|
-
if tool_defined?(words)
|
237
|
-
tool, tool_priority = @tools[words]
|
238
|
-
return tool if priority.nil? || tool_priority.nil? || tool_priority == priority
|
239
|
-
return nil if tool_priority > priority
|
240
|
-
end
|
241
|
-
unless assume_parent
|
242
|
-
parent = get_or_create_tool(words[0..-2], priority)
|
243
|
-
return nil if parent.nil?
|
244
|
-
end
|
245
|
-
prune_from(words)
|
246
|
-
tool = Tool.new(words)
|
247
|
-
tool.middleware_stack.concat(@middleware)
|
248
|
-
@tools[words] = [tool, priority]
|
249
|
-
tool
|
250
|
-
end
|
251
|
-
|
252
|
-
##
|
253
|
-
# Adds a tool directly to the loader.
|
254
|
-
# This should be used only for testing, as it overrides normal priority
|
255
|
-
# checking.
|
256
|
-
#
|
257
|
-
# @param [Toys::Tool] tool Tool to add.
|
258
|
-
# @param [Integer,nil] priority Priority for the tool.
|
259
|
-
#
|
260
|
-
# @private
|
261
|
-
#
|
262
|
-
def put_tool!(tool, priority = nil)
|
263
|
-
@tools[tool.full_name] = [tool, priority]
|
264
|
-
self
|
265
|
-
end
|
266
|
-
|
267
|
-
##
|
268
|
-
# Returns true if the given tool name currently exists in the loader.
|
269
|
-
# Does not load the tool if not found.
|
270
|
-
#
|
271
|
-
# @param [Array<String>] words The name of the tool.
|
272
|
-
# @return [Boolean]
|
273
|
-
#
|
274
|
-
# @private
|
275
|
-
#
|
276
|
-
def tool_defined?(words)
|
277
|
-
@tools.key?(words)
|
278
|
-
end
|
279
|
-
|
280
|
-
##
|
281
|
-
# Load configuration from the given path.
|
282
|
-
#
|
283
|
-
# @private
|
284
|
-
#
|
285
|
-
def include_path(path, words, remaining_words, priority)
|
286
|
-
handle_path(check_path(path), words, remaining_words, priority)
|
287
|
-
end
|
288
|
-
|
289
|
-
##
|
290
|
-
# Determine the next setting for remaining_words, given a word.
|
291
|
-
#
|
292
|
-
# @private
|
293
|
-
#
|
294
|
-
def self.next_remaining_words(remaining_words, word)
|
295
|
-
if remaining_words.nil?
|
296
|
-
nil
|
297
|
-
elsif remaining_words.empty?
|
298
|
-
remaining_words
|
299
|
-
elsif remaining_words.first == word
|
300
|
-
remaining_words.slice(1..-1)
|
301
|
-
end
|
302
|
-
end
|
303
|
-
|
304
|
-
private
|
305
|
-
|
306
|
-
def check_init_options
|
307
|
-
if @config_dir_name && ::File.extname(@config_dir_name) == ".rb"
|
308
|
-
raise LookupError, "Illegal config dir name #{@config_dir_name.inspect}"
|
309
|
-
end
|
310
|
-
if @config_file_name && ::File.extname(@config_file_name) != ".rb"
|
311
|
-
raise LookupError, "Illegal config file name #{@config_file_name.inspect}"
|
312
|
-
end
|
313
|
-
if @index_file_name && ::File.extname(@index_file_name) != ".rb"
|
314
|
-
raise LookupError, "Illegal index file name #{@index_file_name.inspect}"
|
315
|
-
end
|
316
|
-
if @preload_file_name && ::File.extname(@preload_file_name) != ".rb"
|
317
|
-
raise LookupError, "Illegal preload file name #{@preload_file_name.inspect}"
|
318
|
-
end
|
319
|
-
end
|
320
|
-
|
321
|
-
def prune_from(words)
|
322
|
-
return unless @tools.key?(words)
|
323
|
-
@tools.delete_if { |k, _v| k[0, words.size] == words }
|
324
|
-
end
|
325
|
-
|
326
|
-
def load_for_prefix(prefix)
|
327
|
-
cur_worklist = @load_worklist
|
328
|
-
@load_worklist = []
|
329
|
-
cur_worklist.each do |path, words, priority|
|
330
|
-
handle_path(path, words, calc_remaining_words(prefix, words), priority)
|
331
|
-
end
|
332
|
-
end
|
333
|
-
|
334
|
-
def handle_path(path, words, remaining_words, priority)
|
335
|
-
if remaining_words
|
336
|
-
load_path(path, words, remaining_words, priority)
|
337
|
-
else
|
338
|
-
@load_worklist << [path, words, priority]
|
339
|
-
end
|
340
|
-
end
|
341
|
-
|
342
|
-
def load_path(path, words, remaining_words, priority)
|
343
|
-
if ::File.extname(path) == ".rb"
|
344
|
-
tool = get_or_create_tool(words, priority)
|
345
|
-
if tool
|
346
|
-
ConfigDSL.evaluate(path, tool, remaining_words, priority, self, :tool, ::IO.read(path))
|
347
|
-
end
|
348
|
-
else
|
349
|
-
require_preload_in(path)
|
350
|
-
load_index_in(path, words, remaining_words, priority)
|
351
|
-
::Dir.entries(path).each do |child|
|
352
|
-
load_child_in(path, child, words, remaining_words, priority)
|
353
|
-
end
|
354
|
-
end
|
355
|
-
end
|
356
|
-
|
357
|
-
def require_preload_in(path)
|
358
|
-
return unless @preload_file_name
|
359
|
-
preload_path = ::File.join(path, @preload_file_name)
|
360
|
-
preload_path = check_path(preload_path, type: :file, lenient: true)
|
361
|
-
require preload_path if preload_path
|
362
|
-
end
|
363
|
-
|
364
|
-
def load_index_in(path, words, remaining_words, priority)
|
365
|
-
return unless @index_file_name
|
366
|
-
index_path = ::File.join(path, @index_file_name)
|
367
|
-
index_path = check_path(index_path, type: :file, lenient: true)
|
368
|
-
load_path(index_path, words, remaining_words, priority) if index_path
|
369
|
-
end
|
370
|
-
|
371
|
-
def load_child_in(path, child, words, remaining_words, priority)
|
372
|
-
return if child.start_with?(".")
|
373
|
-
return if [@preload_file_name, @index_file_name].include?(child)
|
374
|
-
child_path = check_path(::File.join(path, child))
|
375
|
-
child_word = ::File.basename(child, ".rb")
|
376
|
-
next_words = words + [child_word]
|
377
|
-
next_remaining = Loader.next_remaining_words(remaining_words, child_word)
|
378
|
-
handle_path(child_path, next_words, next_remaining, priority)
|
379
|
-
end
|
380
|
-
|
381
|
-
def check_path(path, lenient: false, type: nil)
|
382
|
-
path = ::File.expand_path(path)
|
383
|
-
type ||= ::File.extname(path) == ".rb" ? :file : :dir
|
384
|
-
case type
|
385
|
-
when :file
|
386
|
-
if ::File.directory?(path) || !::File.readable?(path)
|
387
|
-
return nil if lenient
|
388
|
-
raise LookupError, "Cannot read file #{path}"
|
389
|
-
end
|
390
|
-
when :dir
|
391
|
-
if !::File.directory?(path) || !::File.readable?(path)
|
392
|
-
return nil if lenient
|
393
|
-
raise LookupError, "Cannot read directory #{path}"
|
394
|
-
end
|
395
|
-
else
|
396
|
-
raise ArgumentError, "Illegal type #{type}"
|
397
|
-
end
|
398
|
-
path
|
399
|
-
end
|
400
|
-
|
401
|
-
def sort_tools_by_name(tools)
|
402
|
-
tools.sort do |a, b|
|
403
|
-
a = a.full_name
|
404
|
-
b = b.full_name
|
405
|
-
while !a.empty? && !b.empty? && a.first == b.first
|
406
|
-
a = a.slice(1..-1)
|
407
|
-
b = b.slice(1..-1)
|
408
|
-
end
|
409
|
-
a.first.to_s <=> b.first.to_s
|
410
|
-
end
|
411
|
-
end
|
412
|
-
|
413
|
-
def calc_remaining_words(words1, words2)
|
414
|
-
index = 0
|
415
|
-
lengths = [words1.length, words2.length]
|
416
|
-
loop do
|
417
|
-
return words1.slice(index..-1) if lengths.include?(index)
|
418
|
-
return nil if words1[index] != words2[index]
|
419
|
-
index += 1
|
420
|
-
end
|
421
|
-
end
|
422
|
-
end
|
423
|
-
end
|