toys-core 0.4.5 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +1 -0
- data/CHANGELOG.md +10 -0
- data/lib/toys-core.rb +1 -0
- data/lib/toys/cli.rb +29 -2
- data/lib/toys/core_version.rb +1 -1
- data/lib/toys/definition/data_finder.rb +108 -0
- data/lib/toys/definition/tool.rb +58 -18
- data/lib/toys/dsl/tool.rb +57 -22
- data/lib/toys/input_file.rb +2 -2
- data/lib/toys/loader.rb +99 -31
- data/lib/toys/standard_middleware/show_help.rb +66 -22
- data/lib/toys/tool.rb +12 -0
- data/lib/toys/utils/help_text.rb +103 -0
- metadata +26 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bd284ba44663511c6004cf0d742c4e1b5a703cbe596fd6612350be3b316ec60f
|
4
|
+
data.tar.gz: 9fdc655c88053d669727399c4c4c7321d4d2ececd579c9420bcddb1f0a40d65d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ccdfadc2660d171d39776f63e708839edfaef4b63e21a2ddc39feb6456763a0d54fa27cc611cd2c9180210c291fb1f2510fa925b9f9ebea98f7e3794d42ca00e
|
7
|
+
data.tar.gz: e8d68670c340ac2a2b51a83a1201334ccbbda1a69464a25b9a376badfaf891895ffcc189ad54afbd1a2901b4673ded5fcb61296065ed205e98bfd05dbf4dc810
|
data/.yardopts
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
# Release History
|
2
2
|
|
3
|
+
### 0.5.0 / 2018-10-07
|
4
|
+
|
5
|
+
* FIXED: Template instantiation was failing if the hosting tool was priority-masked.
|
6
|
+
* ADDED: Several additional characters can optionally be used as tool path delimiters.
|
7
|
+
* ADDED: Support for preloaded files and directories
|
8
|
+
* ADDED: Support for data directories
|
9
|
+
* ADDED: Ability to display just the list of subtools of a tool
|
10
|
+
* IMPROVED: The tool directive can now take an array as the tool name.
|
11
|
+
* IMPROVED: The tool directive can now take an `if_defined` argument.
|
12
|
+
|
3
13
|
### 0.4.5 / 2018-08-05
|
4
14
|
|
5
15
|
* CHANGED: Dropped preload file feature
|
data/lib/toys-core.rb
CHANGED
@@ -68,6 +68,7 @@ require "toys/core_version"
|
|
68
68
|
require "toys/definition/acceptor"
|
69
69
|
require "toys/definition/alias"
|
70
70
|
require "toys/definition/arg"
|
71
|
+
require "toys/definition/data_finder"
|
71
72
|
require "toys/definition/flag"
|
72
73
|
require "toys/definition/tool"
|
73
74
|
require "toys/dsl/arg"
|
data/lib/toys/cli.rb
CHANGED
@@ -58,9 +58,25 @@ module Toys
|
|
58
58
|
# loaded first as a standalone configuration file. If not provided,
|
59
59
|
# standalone configuration files are disabled.
|
60
60
|
# The default toys CLI sets this to `".toys.rb"`.
|
61
|
+
# @param [String,nil] preload_file_name A file with this name that appears
|
62
|
+
# in any configuration directory is preloaded before any tools in that
|
63
|
+
# configuration directory are defined.
|
64
|
+
# The default toys CLI sets this to `".preload.rb"`.
|
65
|
+
# @param [String,nil] preload_directory_name A directory with this name
|
66
|
+
# that appears in any configuration directory is searched for Ruby
|
67
|
+
# files, which are preloaded before any tools in that configuration
|
68
|
+
# directory are defined.
|
69
|
+
# The default toys CLI sets this to `".preload"`.
|
70
|
+
# @param [String,nil] data_directory_name A directory with this name that
|
71
|
+
# appears in any configuration directory is added to the data directory
|
72
|
+
# search path for any tool file in that directory.
|
73
|
+
# The default toys CLI sets this to `".data"`.
|
61
74
|
# @param [Array] middleware_stack An array of middleware that will be used
|
62
75
|
# by default for all tools loaded by this CLI. If not provided, uses
|
63
76
|
# {Toys::CLI.default_middleware_stack}.
|
77
|
+
# @param [String] extra_delimiters A string containing characters that can
|
78
|
+
# function as delimiters in a tool name. Defaults to empty. Allowed
|
79
|
+
# characters are period, colon, and slash.
|
64
80
|
# @param [Toys::Utils::ModuleLookup] mixin_lookup A lookup for well-known
|
65
81
|
# mixin modules. If not provided, uses
|
66
82
|
# {Toys::CLI.default_mixin_lookup}.
|
@@ -81,23 +97,30 @@ module Toys
|
|
81
97
|
# Default is a {Toys::CLI::DefaultErrorHandler} writing to the logger.
|
82
98
|
#
|
83
99
|
def initialize(
|
84
|
-
binary_name: nil, middleware_stack: nil,
|
100
|
+
binary_name: nil, middleware_stack: nil, extra_delimiters: "",
|
85
101
|
config_dir_name: nil, config_file_name: nil, index_file_name: nil,
|
102
|
+
preload_file_name: nil, preload_directory_name: nil, data_directory_name: nil,
|
86
103
|
mixin_lookup: nil, middleware_lookup: nil, template_lookup: nil,
|
87
104
|
logger: nil, base_level: nil, error_handler: nil
|
88
105
|
)
|
89
106
|
@logger = logger || self.class.default_logger
|
90
107
|
@base_level = base_level || @logger.level
|
91
108
|
@middleware_stack = middleware_stack || self.class.default_middleware_stack
|
109
|
+
@extra_delimiters = extra_delimiters
|
92
110
|
@binary_name = binary_name || ::File.basename($PROGRAM_NAME)
|
93
111
|
@config_dir_name = config_dir_name
|
94
112
|
@config_file_name = config_file_name
|
95
113
|
@index_file_name = index_file_name
|
114
|
+
@preload_file_name = preload_file_name
|
115
|
+
@preload_directory_name = preload_directory_name
|
116
|
+
@data_directory_name = data_directory_name
|
96
117
|
@mixin_lookup = mixin_lookup || self.class.default_mixin_lookup
|
97
118
|
@middleware_lookup = middleware_lookup || self.class.default_middleware_lookup
|
98
119
|
@template_lookup = template_lookup || self.class.default_template_lookup
|
99
120
|
@loader = Loader.new(
|
100
|
-
index_file_name: index_file_name,
|
121
|
+
index_file_name: index_file_name, extra_delimiters: @extra_delimiters,
|
122
|
+
preload_directory_name: @preload_directory_name, preload_file_name: @preload_file_name,
|
123
|
+
data_directory_name: @data_directory_name,
|
101
124
|
mixin_lookup: @mixin_lookup, template_lookup: @template_lookup,
|
102
125
|
middleware_lookup: @middleware_lookup, middleware_stack: @middleware_stack
|
103
126
|
)
|
@@ -251,7 +274,11 @@ module Toys
|
|
251
274
|
config_dir_name: @config_dir_name,
|
252
275
|
config_file_name: @config_file_name,
|
253
276
|
index_file_name: @index_file_name,
|
277
|
+
preload_directory_name: @preload_directory_name,
|
278
|
+
preload_file_name: @preload_file_name,
|
279
|
+
data_directory_name: @data_directory_name,
|
254
280
|
middleware_stack: @middleware_stack,
|
281
|
+
extra_delimiters: @extra_delimiters,
|
255
282
|
mixin_lookup: @mixin_lookup,
|
256
283
|
middleware_lookup: @middleware_lookup,
|
257
284
|
template_lookup: @template_lookup,
|
data/lib/toys/core_version.rb
CHANGED
@@ -0,0 +1,108 @@
|
|
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
|
+
# Finds data files.
|
36
|
+
#
|
37
|
+
class DataFinder
|
38
|
+
##
|
39
|
+
# Create a new finder.
|
40
|
+
#
|
41
|
+
# @param [String,nil] data_name Name of the data directory, or nil if
|
42
|
+
# data directories are disabled.
|
43
|
+
# @param [Toys::Definition::DataFinder,nil] parent The parent, or nil if
|
44
|
+
# this is the root.
|
45
|
+
# @param [String,nil] directory The data directory, or nil for none.
|
46
|
+
# @private
|
47
|
+
#
|
48
|
+
def initialize(data_name, parent, directory)
|
49
|
+
@data_name = data_name
|
50
|
+
@parent = parent
|
51
|
+
@directory = directory
|
52
|
+
end
|
53
|
+
|
54
|
+
##
|
55
|
+
# Create a new finder for the given directory.
|
56
|
+
#
|
57
|
+
# @param [String] directory Toys directory path
|
58
|
+
# @return [Toys::Definition::DataFinder] The finder
|
59
|
+
#
|
60
|
+
def finder_for(directory)
|
61
|
+
return self if @data_name.nil?
|
62
|
+
directory = ::File.join(directory, @data_name)
|
63
|
+
return self unless ::File.directory?(directory)
|
64
|
+
DataFinder.new(@data_name, self, directory)
|
65
|
+
end
|
66
|
+
|
67
|
+
##
|
68
|
+
# Return the absolute path to the given data file or directory.
|
69
|
+
#
|
70
|
+
# @param [String] path The relative path to find
|
71
|
+
# @param [nil,:file,:directory] type Type of file system object to find,
|
72
|
+
# or nil to return any type.
|
73
|
+
# @return [String,nil] Absolute path of the result, or nil if not found.
|
74
|
+
#
|
75
|
+
def find_data(path, type: nil)
|
76
|
+
return nil if @directory.nil?
|
77
|
+
full_path = ::File.join(@directory, path)
|
78
|
+
case type
|
79
|
+
when :file
|
80
|
+
return full_path if ::File.file?(full_path)
|
81
|
+
when :directory
|
82
|
+
return full_path if ::File.directory?(full_path)
|
83
|
+
else
|
84
|
+
return full_path if ::File.readable?(full_path)
|
85
|
+
end
|
86
|
+
@parent.find_data(path, type: type)
|
87
|
+
end
|
88
|
+
|
89
|
+
##
|
90
|
+
# Create an empty finder.
|
91
|
+
#
|
92
|
+
# @param [String,nil] data_name Name of the data directory, or nil if
|
93
|
+
# data directories are disabled.
|
94
|
+
# @return [Toys::Definition::DataFinder]
|
95
|
+
#
|
96
|
+
def self.create_empty(data_name)
|
97
|
+
new(data_name, nil, nil)
|
98
|
+
end
|
99
|
+
|
100
|
+
##
|
101
|
+
# A default empty finder.
|
102
|
+
#
|
103
|
+
# @return [Toys::Definition::DataFinder]
|
104
|
+
#
|
105
|
+
EMPTY = create_empty(nil)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
data/lib/toys/definition/tool.rb
CHANGED
@@ -72,11 +72,27 @@ module Toys
|
|
72
72
|
def initialize(loader, parent, full_name, priority, middleware_stack)
|
73
73
|
@parent = parent
|
74
74
|
@full_name = full_name.dup.freeze
|
75
|
-
@tool_class = DSL::Tool.new_class(@full_name, priority, loader)
|
76
75
|
@priority = priority
|
77
76
|
@middleware_stack = middleware_stack
|
78
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
|
+
|
79
94
|
@source_path = nil
|
95
|
+
@data_finder = nil
|
80
96
|
@definition_finished = false
|
81
97
|
|
82
98
|
@desc = Utils::WrappableString.new("")
|
@@ -86,17 +102,13 @@ module Toys
|
|
86
102
|
@used_flags = []
|
87
103
|
@initializers = []
|
88
104
|
|
89
|
-
@acceptors = {}
|
90
|
-
@mixins = {}
|
91
|
-
@templates = {}
|
92
|
-
|
93
105
|
@flag_definitions = []
|
94
106
|
@required_arg_definitions = []
|
95
107
|
@optional_arg_definitions = []
|
96
108
|
@remaining_args_definition = nil
|
97
109
|
|
98
110
|
@disable_argument_parsing = false
|
99
|
-
@
|
111
|
+
@includes_modules = false
|
100
112
|
end
|
101
113
|
|
102
114
|
##
|
@@ -209,7 +221,15 @@ module Toys
|
|
209
221
|
# @return [Boolean]
|
210
222
|
#
|
211
223
|
def runnable?
|
212
|
-
|
224
|
+
tool_class.public_instance_methods(false).include?(:run)
|
225
|
+
end
|
226
|
+
|
227
|
+
##
|
228
|
+
# Returns true if this tool has at least one included module.
|
229
|
+
# @return [Boolean]
|
230
|
+
#
|
231
|
+
def includes_modules?
|
232
|
+
@includes_modules
|
213
233
|
end
|
214
234
|
|
215
235
|
##
|
@@ -236,7 +256,8 @@ module Toys
|
|
236
256
|
# @return [Boolean]
|
237
257
|
#
|
238
258
|
def includes_definition?
|
239
|
-
includes_arguments? || runnable?
|
259
|
+
includes_arguments? || runnable? || argument_parsing_disabled? ||
|
260
|
+
includes_modules? || includes_description?
|
240
261
|
end
|
241
262
|
|
242
263
|
##
|
@@ -346,14 +367,16 @@ module Toys
|
|
346
367
|
# already set, raises {Toys::ToolDefinitionError}
|
347
368
|
#
|
348
369
|
# @param [String] path The path to the file defining this tool
|
370
|
+
# @param [Toys::Definition::DataFinder] data_finder Data finder
|
349
371
|
#
|
350
|
-
def lock_source_path(path)
|
372
|
+
def lock_source_path(path, data_finder)
|
351
373
|
if source_path && source_path != path
|
352
374
|
raise ToolDefinitionError,
|
353
375
|
"Cannot redefine tool #{display_name.inspect} in #{path}" \
|
354
376
|
" (already defined in #{source_path})"
|
355
377
|
end
|
356
378
|
@source_path = path
|
379
|
+
@data_finder = data_finder
|
357
380
|
end
|
358
381
|
|
359
382
|
##
|
@@ -648,12 +671,24 @@ module Toys
|
|
648
671
|
end
|
649
672
|
|
650
673
|
##
|
651
|
-
#
|
674
|
+
# Find the given data file or directory in this tool's search path.
|
675
|
+
#
|
676
|
+
# @param [String] path The path to find
|
677
|
+
# @param [nil,:file,:directory] type Type of file system object to find,
|
678
|
+
# or nil to return any type.
|
679
|
+
# @return [String,nil] Absolute path of the result, or nil if not found.
|
680
|
+
#
|
681
|
+
def find_data(path, type: nil)
|
682
|
+
@data_finder ? @data_finder.find_data(path, type: type) : nil
|
683
|
+
end
|
684
|
+
|
685
|
+
##
|
686
|
+
# Mark this tool as having at least one module included
|
652
687
|
# @private
|
653
688
|
#
|
654
|
-
def
|
689
|
+
def mark_includes_modules
|
655
690
|
check_definition_state
|
656
|
-
@
|
691
|
+
@includes_modules = true
|
657
692
|
self
|
658
693
|
end
|
659
694
|
|
@@ -687,12 +722,11 @@ module Toys
|
|
687
722
|
end
|
688
723
|
end
|
689
724
|
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
725
|
+
##
|
726
|
+
# Check that the tool can still be defined. Should be called internally
|
727
|
+
# or from the DSL only.
|
728
|
+
# @private
|
729
|
+
#
|
696
730
|
def check_definition_state(is_arg: false)
|
697
731
|
if @definition_finished
|
698
732
|
raise ToolDefinitionError,
|
@@ -704,6 +738,12 @@ module Toys
|
|
704
738
|
end
|
705
739
|
self
|
706
740
|
end
|
741
|
+
|
742
|
+
private
|
743
|
+
|
744
|
+
def make_config_proc(middleware, loader, next_config)
|
745
|
+
proc { middleware.config(self, loader, &next_config) }
|
746
|
+
end
|
707
747
|
end
|
708
748
|
end
|
709
749
|
end
|
data/lib/toys/dsl/tool.rb
CHANGED
@@ -64,9 +64,8 @@ module Toys
|
|
64
64
|
#
|
65
65
|
module Tool
|
66
66
|
## @private
|
67
|
-
def method_added(
|
68
|
-
|
69
|
-
cur_tool.mark_runnable if cur_tool && meth == :run
|
67
|
+
def method_added(_meth)
|
68
|
+
DSL::Tool.current_tool(self, true)&.check_definition_state
|
70
69
|
end
|
71
70
|
|
72
71
|
##
|
@@ -177,18 +176,34 @@ module Toys
|
|
177
176
|
##
|
178
177
|
# Create a subtool. You must provide a block defining the subtool.
|
179
178
|
#
|
180
|
-
#
|
181
|
-
#
|
182
|
-
#
|
183
|
-
#
|
179
|
+
# @param [String,Array<String>] words The name of the subtool
|
180
|
+
# @param [:combine,:reset,:ignore] if_defined What to do if a definition
|
181
|
+
# already exists for this tool. Possible values are `:combine` (the
|
182
|
+
# default) indicating the definition should be combined with the
|
183
|
+
# existing definition, `:reset` indicating the earlier definition
|
184
|
+
# should be reset and the new definition applied instead, or
|
185
|
+
# `:ignore` indicating the new definition should be ignored.
|
184
186
|
# @return [Toys::DSL::Tool] self, for chaining.
|
185
187
|
#
|
186
|
-
def tool(
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
188
|
+
def tool(words, if_defined: :combine, &block)
|
189
|
+
subtool_words = @__words
|
190
|
+
next_remaining = @__remaining_words
|
191
|
+
Array(words).each do |word|
|
192
|
+
word = word.to_s
|
193
|
+
subtool_words += [word]
|
194
|
+
next_remaining = Loader.next_remaining_words(next_remaining, word)
|
195
|
+
end
|
196
|
+
subtool = @__loader.get_tool_definition(subtool_words, @__priority)
|
197
|
+
if subtool.includes_definition?
|
198
|
+
case if_defined
|
199
|
+
when :ignore
|
200
|
+
return self
|
201
|
+
when :reset
|
202
|
+
subtool.reset_definition(@__loader)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
subtool_class = subtool.tool_class
|
206
|
+
DSL::Tool.prepare(subtool_class, next_remaining, @__path, @__data_finder) do
|
192
207
|
subtool_class.class_eval(&block)
|
193
208
|
end
|
194
209
|
self
|
@@ -231,8 +246,7 @@ module Toys
|
|
231
246
|
# @return [Toys::DSL::Tool] self, for chaining.
|
232
247
|
#
|
233
248
|
def expand(template_class, *args)
|
234
|
-
cur_tool = DSL::Tool.current_tool(self,
|
235
|
-
return self if cur_tool.nil?
|
249
|
+
cur_tool = DSL::Tool.current_tool(self, false)
|
236
250
|
name = template_class.to_s
|
237
251
|
if template_class.is_a?(::String)
|
238
252
|
template_class = cur_tool.resolve_template(template_class)
|
@@ -607,11 +621,14 @@ module Toys
|
|
607
621
|
if included_modules.include?(mod)
|
608
622
|
raise ToolDefinitionError, "Mixin already included: #{mod.name}"
|
609
623
|
end
|
610
|
-
|
611
|
-
|
624
|
+
cur_tool.mark_includes_modules
|
625
|
+
if mod.respond_to?(:initialization_callback)
|
626
|
+
callback = mod.initialization_callback
|
627
|
+
cur_tool.add_initializer(callback, *args) if callback
|
612
628
|
end
|
613
|
-
if mod.respond_to?(:inclusion_callback)
|
614
|
-
|
629
|
+
if mod.respond_to?(:inclusion_callback)
|
630
|
+
callback = mod.inclusion_callback
|
631
|
+
class_exec(*args, &callback) if callback
|
615
632
|
end
|
616
633
|
super(mod)
|
617
634
|
end
|
@@ -633,6 +650,18 @@ module Toys
|
|
633
650
|
super(DSL::Tool.resolve_mixin(mod, cur_tool, @__loader))
|
634
651
|
end
|
635
652
|
|
653
|
+
##
|
654
|
+
# Find the given data path (file or directory)
|
655
|
+
#
|
656
|
+
# @param [String] path The path to find
|
657
|
+
# @param [nil,:file,:directory] type Type of file system object to find,
|
658
|
+
# or nil to return any type.
|
659
|
+
# @return [String,nil] Absolute path of the result, or nil if not found.
|
660
|
+
#
|
661
|
+
def find_data(path, type: nil)
|
662
|
+
@__data_finder ? @__data_finder.find_data(path, type: type) : nil
|
663
|
+
end
|
664
|
+
|
636
665
|
## @private
|
637
666
|
def self.new_class(words, priority, loader)
|
638
667
|
tool_class = ::Class.new(::Toys::Tool)
|
@@ -642,13 +671,13 @@ module Toys
|
|
642
671
|
tool_class.instance_variable_set(:@__loader, loader)
|
643
672
|
tool_class.instance_variable_set(:@__remaining_words, nil)
|
644
673
|
tool_class.instance_variable_set(:@__path, nil)
|
674
|
+
tool_class.instance_variable_set(:@__data_finder, nil)
|
645
675
|
tool_class
|
646
676
|
end
|
647
677
|
|
648
678
|
## @private
|
649
679
|
def self.current_tool(tool_class, activate)
|
650
680
|
memoize_var = activate ? :@__active_tool : :@__cur_tool
|
651
|
-
path = tool_class.instance_variable_get(:@__path)
|
652
681
|
if tool_class.instance_variable_defined?(memoize_var)
|
653
682
|
cur_tool = tool_class.instance_variable_get(memoize_var)
|
654
683
|
else
|
@@ -667,18 +696,24 @@ module Toys
|
|
667
696
|
end
|
668
697
|
tool_class.instance_variable_set(memoize_var, cur_tool)
|
669
698
|
end
|
670
|
-
|
699
|
+
if cur_tool && activate
|
700
|
+
path = tool_class.instance_variable_get(:@__path)
|
701
|
+
data_finder = tool_class.instance_variable_get(:@__data_finder)
|
702
|
+
cur_tool.lock_source_path(path, data_finder)
|
703
|
+
end
|
671
704
|
cur_tool
|
672
705
|
end
|
673
706
|
|
674
707
|
## @private
|
675
|
-
def self.prepare(tool_class, remaining_words, path)
|
708
|
+
def self.prepare(tool_class, remaining_words, path, data_finder)
|
676
709
|
tool_class.instance_variable_set(:@__remaining_words, remaining_words)
|
677
710
|
tool_class.instance_variable_set(:@__path, path)
|
711
|
+
tool_class.instance_variable_set(:@__data_finder, data_finder)
|
678
712
|
yield
|
679
713
|
ensure
|
680
714
|
tool_class.instance_variable_set(:@__remaining_words, nil)
|
681
715
|
tool_class.instance_variable_set(:@__path, nil)
|
716
|
+
tool_class.instance_variable_set(:@__data_finder, nil)
|
682
717
|
end
|
683
718
|
|
684
719
|
## @private
|
data/lib/toys/input_file.rb
CHANGED
@@ -40,7 +40,7 @@ module Toys::InputFile # rubocop:disable Style/ClassAndModuleChildren
|
|
40
40
|
end
|
41
41
|
|
42
42
|
## @private
|
43
|
-
def self.evaluate(tool_class, remaining_words, path)
|
43
|
+
def self.evaluate(tool_class, remaining_words, path, data_finder)
|
44
44
|
namespace = ::Module.new
|
45
45
|
namespace.module_eval do
|
46
46
|
include ::Toys::Tool::Keys
|
@@ -51,7 +51,7 @@ module Toys::InputFile # rubocop:disable Style/ClassAndModuleChildren
|
|
51
51
|
str = build_eval_string(name, ::IO.read(path))
|
52
52
|
if str
|
53
53
|
const_set(name, namespace)
|
54
|
-
::Toys::DSL::Tool.prepare(tool_class, remaining_words, path) do
|
54
|
+
::Toys::DSL::Tool.prepare(tool_class, remaining_words, path, data_finder) do
|
55
55
|
::Toys::ContextualError.capture_path("Error while loading Toys config!", path) do
|
56
56
|
# rubocop:disable Security/Eval
|
57
57
|
eval(str, __binding, path, 0)
|
data/lib/toys/loader.rb
CHANGED
@@ -34,6 +34,8 @@ module Toys
|
|
34
34
|
# The Loader service loads tools from configuration files, and finds the
|
35
35
|
# appropriate tool given a set of command line arguments.
|
36
36
|
#
|
37
|
+
# This class is not thread-safe.
|
38
|
+
#
|
37
39
|
class Loader
|
38
40
|
## @private
|
39
41
|
ToolData = ::Struct.new(:definitions, :top_priority, :active_priority) do
|
@@ -53,8 +55,21 @@ module Toys
|
|
53
55
|
# in any configuration directory (not just a toplevel directory) is
|
54
56
|
# loaded first as a standalone configuration file. If not provided,
|
55
57
|
# standalone configuration files are disabled.
|
58
|
+
# @param [String,nil] preload_file_name A file with this name that appears
|
59
|
+
# in any configuration directory is preloaded before any tools in that
|
60
|
+
# configuration directory are defined.
|
61
|
+
# @param [String,nil] preload_directory_name A directory with this name
|
62
|
+
# that appears in any configuration directory is searched for Ruby
|
63
|
+
# files, which are preloaded before any tools in that configuration
|
64
|
+
# directory are defined.
|
65
|
+
# @param [String,nil] data_directory_name A directory with this name that
|
66
|
+
# appears in any configuration directory is added to the data directory
|
67
|
+
# search path for any tool file in that directory.
|
56
68
|
# @param [Array] middleware_stack An array of middleware that will be used
|
57
69
|
# by default for all tools loaded by this loader.
|
70
|
+
# @param [String] extra_delimiters A string containing characters that can
|
71
|
+
# function as delimiters in a tool name. Defaults to empty. Allowed
|
72
|
+
# characters are period, colon, and slash.
|
58
73
|
# @param [Toys::Utils::ModuleLookup] mixin_lookup A lookup for well-known
|
59
74
|
# mixin modules. Defaults to an empty lookup.
|
60
75
|
# @param [Toys::Utils::ModuleLookup] middleware_lookup A lookup for
|
@@ -62,7 +77,8 @@ module Toys
|
|
62
77
|
# @param [Toys::Utils::ModuleLookup] template_lookup A lookup for
|
63
78
|
# well-known template classes. Defaults to an empty lookup.
|
64
79
|
#
|
65
|
-
def initialize(index_file_name: nil,
|
80
|
+
def initialize(index_file_name: nil, preload_directory_name: nil, preload_file_name: nil,
|
81
|
+
data_directory_name: nil, middleware_stack: [], extra_delimiters: "",
|
66
82
|
mixin_lookup: nil, middleware_lookup: nil, template_lookup: nil)
|
67
83
|
if index_file_name && ::File.extname(index_file_name) != ".rb"
|
68
84
|
raise ::ArgumentError, "Illegal index file name #{index_file_name.inspect}"
|
@@ -71,10 +87,14 @@ module Toys
|
|
71
87
|
@middleware_lookup = middleware_lookup || Utils::ModuleLookup.new
|
72
88
|
@template_lookup = template_lookup || Utils::ModuleLookup.new
|
73
89
|
@index_file_name = index_file_name
|
90
|
+
@preload_file_name = preload_file_name
|
91
|
+
@preload_directory_name = preload_directory_name
|
92
|
+
@empty_data_finder = Definition::DataFinder.create_empty(data_directory_name)
|
74
93
|
@middleware_stack = middleware_stack
|
75
94
|
@worklist = []
|
76
95
|
@tool_data = {}
|
77
96
|
@max_priority = @min_priority = 0
|
97
|
+
@extra_delimiters = process_extra_delimiters(extra_delimiters)
|
78
98
|
get_tool_definition([], -999_999)
|
79
99
|
end
|
80
100
|
|
@@ -90,7 +110,7 @@ module Toys
|
|
90
110
|
paths = Array(path)
|
91
111
|
priority = high_priority ? (@max_priority += 1) : (@min_priority -= 1)
|
92
112
|
paths.each do |p|
|
93
|
-
@worklist << [:file, check_path(p), [], priority]
|
113
|
+
@worklist << [:file, check_path(p), [], @empty_data_finder, priority]
|
94
114
|
end
|
95
115
|
self
|
96
116
|
end
|
@@ -108,7 +128,7 @@ module Toys
|
|
108
128
|
def add_block(high_priority: false, path: nil, &block)
|
109
129
|
path ||= "(Block #{block.object_id})"
|
110
130
|
priority = high_priority ? (@max_priority += 1) : (@min_priority -= 1)
|
111
|
-
@worklist << [block, path, [], priority]
|
131
|
+
@worklist << [block, path, [], @empty_data_finder, priority]
|
112
132
|
self
|
113
133
|
end
|
114
134
|
|
@@ -123,23 +143,23 @@ module Toys
|
|
123
143
|
# Returns a tuple of the found tool, and the array of remaining arguments
|
124
144
|
# that are not part of the tool name and should be passed as tool args.
|
125
145
|
#
|
126
|
-
# @param [String] args Command line arguments
|
146
|
+
# @param [Array<String>] args Command line arguments
|
127
147
|
# @return [Array(Toys::Definition::Tool,Array<String>)]
|
128
148
|
#
|
129
149
|
def lookup(args)
|
130
|
-
orig_prefix
|
150
|
+
orig_prefix, args = find_orig_prefix(args)
|
131
151
|
cur_prefix = orig_prefix
|
132
152
|
loop do
|
133
153
|
load_for_prefix(cur_prefix)
|
134
|
-
|
154
|
+
prefix = orig_prefix
|
135
155
|
loop do
|
136
|
-
tool_definition = get_active_tool(
|
156
|
+
tool_definition = get_active_tool(prefix, [])
|
137
157
|
if tool_definition
|
138
158
|
finish_definitions_in_tree(tool_definition.full_name)
|
139
|
-
return [tool_definition, args.slice(
|
159
|
+
return [tool_definition, args.slice(prefix.length..-1)]
|
140
160
|
end
|
141
|
-
break if
|
142
|
-
|
161
|
+
break if prefix.empty? || prefix.length <= cur_prefix.length
|
162
|
+
prefix = prefix.slice(0..-2)
|
143
163
|
end
|
144
164
|
raise "Unexpected error" if cur_prefix.empty?
|
145
165
|
cur_prefix = cur_prefix.slice(0..-2)
|
@@ -189,13 +209,16 @@ module Toys
|
|
189
209
|
end
|
190
210
|
|
191
211
|
##
|
192
|
-
# Returns
|
193
|
-
#
|
212
|
+
# Returns the active tool specified by the given words, with the given
|
213
|
+
# priority, without doing any loading. If the given priority matches the
|
214
|
+
# currently active tool, returns it. If the given priority is lower than
|
215
|
+
# the active priority, returns `nil`. If the given priority is higher than
|
216
|
+
# the active priority, returns and activates a new tool.
|
194
217
|
#
|
195
218
|
# @param [Array<String>] words The name of the tool.
|
196
219
|
# @param [Integer] priority The priority of the request.
|
197
220
|
# @return [Toys::Definition::Tool,Toys::Definition::Alias,nil] The tool or
|
198
|
-
# alias, or `nil` if the given priority is insufficient
|
221
|
+
# alias, or `nil` if the given priority is insufficient.
|
199
222
|
#
|
200
223
|
# @private
|
201
224
|
#
|
@@ -259,9 +282,10 @@ module Toys
|
|
259
282
|
if tool_data.top_priority.nil? || tool_data.top_priority < priority
|
260
283
|
tool_data.top_priority = priority
|
261
284
|
end
|
262
|
-
|
263
|
-
|
285
|
+
tool_data.definitions[priority] ||= begin
|
286
|
+
middlewares = @middleware_stack.map { |m| resolve_middleware(m) }
|
264
287
|
Definition::Tool.new(self, parent, words, priority, middlewares)
|
288
|
+
end
|
265
289
|
end
|
266
290
|
|
267
291
|
##
|
@@ -290,7 +314,7 @@ module Toys
|
|
290
314
|
# @private
|
291
315
|
#
|
292
316
|
def load_path(path, words, remaining_words, priority)
|
293
|
-
load_validated_path(check_path(path), words, remaining_words, priority)
|
317
|
+
load_validated_path(check_path(path), words, remaining_words, @empty_data_finder, priority)
|
294
318
|
end
|
295
319
|
|
296
320
|
##
|
@@ -301,13 +325,13 @@ module Toys
|
|
301
325
|
def load_proc(proc, words, remaining_words, priority, path)
|
302
326
|
if remaining_words
|
303
327
|
tool_class = get_tool_definition(words, priority).tool_class
|
304
|
-
::Toys::DSL::Tool.prepare(tool_class, remaining_words, path) do
|
328
|
+
::Toys::DSL::Tool.prepare(tool_class, remaining_words, path, @empty_data_finder) do
|
305
329
|
::Toys::ContextualError.capture("Error while loading Toys config!") do
|
306
330
|
tool_class.class_eval(&proc)
|
307
331
|
end
|
308
332
|
end
|
309
333
|
else
|
310
|
-
@worklist << [proc, path, words, priority]
|
334
|
+
@worklist << [proc, path, words, @empty_data_finder, priority]
|
311
335
|
end
|
312
336
|
end
|
313
337
|
|
@@ -328,6 +352,28 @@ module Toys
|
|
328
352
|
|
329
353
|
private
|
330
354
|
|
355
|
+
ALLOWED_DELIMITERS = %r{^[\./:]*$}
|
356
|
+
|
357
|
+
def process_extra_delimiters(input)
|
358
|
+
unless ALLOWED_DELIMITERS =~ input
|
359
|
+
raise ::ArgumentError, "Illegal delimiters in #{input.inspect}"
|
360
|
+
end
|
361
|
+
chars = ::Regexp.escape(input.chars.uniq.join)
|
362
|
+
chars.empty? ? nil : ::Regexp.new("[#{chars}]")
|
363
|
+
end
|
364
|
+
|
365
|
+
def find_orig_prefix(args)
|
366
|
+
if @extra_delimiters
|
367
|
+
first_split = (args.first || "").split(@extra_delimiters)
|
368
|
+
if first_split.size > 1
|
369
|
+
args = first_split + args.slice(1..-1)
|
370
|
+
return [first_split, args]
|
371
|
+
end
|
372
|
+
end
|
373
|
+
orig_prefix = args.take_while { |arg| !arg.start_with?("-") }
|
374
|
+
[orig_prefix, args]
|
375
|
+
end
|
376
|
+
|
331
377
|
def get_tool_data(words)
|
332
378
|
@tool_data[words] ||= ToolData.new({}, nil, nil)
|
333
379
|
end
|
@@ -395,51 +441,73 @@ module Toys
|
|
395
441
|
def load_for_prefix(prefix)
|
396
442
|
cur_worklist = @worklist
|
397
443
|
@worklist = []
|
398
|
-
cur_worklist.each do |source, path, words, priority|
|
444
|
+
cur_worklist.each do |source, path, words, data_finder, priority|
|
399
445
|
remaining_words = calc_remaining_words(prefix, words)
|
400
446
|
if source.respond_to?(:call)
|
401
447
|
load_proc(source, words, remaining_words, priority, path)
|
402
448
|
elsif source == :file
|
403
|
-
load_validated_path(path, words, remaining_words, priority)
|
449
|
+
load_validated_path(path, words, remaining_words, data_finder, priority)
|
404
450
|
end
|
405
451
|
end
|
406
452
|
end
|
407
453
|
|
408
|
-
def load_validated_path(path, words, remaining_words, priority)
|
454
|
+
def load_validated_path(path, words, remaining_words, data_finder, priority)
|
409
455
|
if remaining_words
|
410
|
-
load_relevant_path(path, words, remaining_words, priority)
|
456
|
+
load_relevant_path(path, words, remaining_words, data_finder, priority)
|
411
457
|
else
|
412
|
-
@worklist << [:file, path, words, priority]
|
458
|
+
@worklist << [:file, path, words, data_finder, priority]
|
413
459
|
end
|
414
460
|
end
|
415
461
|
|
416
|
-
def load_relevant_path(path, words, remaining_words, priority)
|
462
|
+
def load_relevant_path(path, words, remaining_words, data_finder, priority)
|
417
463
|
if ::File.extname(path) == ".rb"
|
418
464
|
tool_class = get_tool_definition(words, priority).tool_class
|
419
|
-
|
465
|
+
InputFile.evaluate(tool_class, remaining_words, path, data_finder)
|
420
466
|
else
|
421
|
-
|
467
|
+
do_preload(path)
|
468
|
+
data_finder = data_finder.finder_for(path)
|
469
|
+
load_index_in(path, words, remaining_words, data_finder, priority)
|
422
470
|
::Dir.entries(path).each do |child|
|
423
|
-
load_child_in(path, child, words, remaining_words, priority)
|
471
|
+
load_child_in(path, child, words, remaining_words, data_finder, priority)
|
424
472
|
end
|
425
473
|
end
|
426
474
|
end
|
427
475
|
|
428
|
-
def load_index_in(path, words, remaining_words, priority)
|
476
|
+
def load_index_in(path, words, remaining_words, data_finder, priority)
|
429
477
|
return unless @index_file_name
|
430
478
|
index_path = ::File.join(path, @index_file_name)
|
431
479
|
index_path = check_path(index_path, type: :file, lenient: true)
|
432
|
-
load_relevant_path(index_path, words, remaining_words, priority) if index_path
|
480
|
+
load_relevant_path(index_path, words, remaining_words, data_finder, priority) if index_path
|
433
481
|
end
|
434
482
|
|
435
|
-
def load_child_in(path, child, words, remaining_words, priority)
|
483
|
+
def load_child_in(path, child, words, remaining_words, data_finder, priority)
|
436
484
|
return if child.start_with?(".")
|
437
485
|
return if child == @index_file_name
|
438
486
|
child_path = check_path(::File.join(path, child))
|
439
487
|
child_word = ::File.basename(child, ".rb")
|
440
488
|
next_words = words + [child_word]
|
441
489
|
next_remaining = Loader.next_remaining_words(remaining_words, child_word)
|
442
|
-
load_validated_path(child_path, next_words, next_remaining, priority)
|
490
|
+
load_validated_path(child_path, next_words, next_remaining, data_finder, priority)
|
491
|
+
end
|
492
|
+
|
493
|
+
def do_preload(path)
|
494
|
+
if @preload_file_name
|
495
|
+
preload_file = ::File.join(path, @preload_file_name)
|
496
|
+
if !::File.directory?(preload_file) && ::File.readable?(preload_file)
|
497
|
+
require preload_file
|
498
|
+
end
|
499
|
+
end
|
500
|
+
if @preload_directory_name
|
501
|
+
preload_dir = ::File.join(path, @preload_directory_name)
|
502
|
+
if ::File.directory?(preload_dir) && ::File.readable?(preload_dir)
|
503
|
+
::Dir.entries(preload_dir).each do |child|
|
504
|
+
preload_file = ::File.join(preload_dir, child)
|
505
|
+
if !::File.directory?(preload_file) && ::File.readable?(preload_file)
|
506
|
+
require preload_file
|
507
|
+
end
|
508
|
+
end
|
509
|
+
end
|
510
|
+
end
|
443
511
|
end
|
444
512
|
|
445
513
|
def check_path(path, lenient: false, type: nil)
|
@@ -56,6 +56,12 @@ module Toys
|
|
56
56
|
#
|
57
57
|
DEFAULT_USAGE_FLAGS = ["--usage"].freeze
|
58
58
|
|
59
|
+
##
|
60
|
+
# Default list subtools flags
|
61
|
+
# @return [Array<String>]
|
62
|
+
#
|
63
|
+
DEFAULT_LIST_FLAGS = ["--tools"].freeze
|
64
|
+
|
59
65
|
##
|
60
66
|
# Default recursive flags
|
61
67
|
# @return [Array<String>]
|
@@ -80,6 +86,12 @@ module Toys
|
|
80
86
|
#
|
81
87
|
SHOW_USAGE_KEY = Object.new.freeze
|
82
88
|
|
89
|
+
##
|
90
|
+
# Key set when the show subtool list flag is present
|
91
|
+
# @return [Object]
|
92
|
+
#
|
93
|
+
SHOW_LIST_KEY = Object.new.freeze
|
94
|
+
|
83
95
|
##
|
84
96
|
# Key for the recursive setting
|
85
97
|
# @return [Object]
|
@@ -117,6 +129,14 @@ module Toys
|
|
117
129
|
# * The `false` value for no flags. (Default)
|
118
130
|
# * A proc that takes a tool and returns any of the above.
|
119
131
|
#
|
132
|
+
# @param [Boolean,Array<String>,Proc] list_flags Specify flags to
|
133
|
+
# display subtool list. The value may be any of the following:
|
134
|
+
#
|
135
|
+
# * An array of flags.
|
136
|
+
# * The `true` value to use {DEFAULT_LIST_FLAGS}.
|
137
|
+
# * The `false` value for no flags. (Default)
|
138
|
+
# * A proc that takes a tool and returns any of the above.
|
139
|
+
#
|
120
140
|
# @param [Boolean,Array<String>,Proc] recursive_flags Specify flags
|
121
141
|
# to control recursive subtool search. The value may be any of the
|
122
142
|
# following:
|
@@ -156,6 +176,7 @@ module Toys
|
|
156
176
|
#
|
157
177
|
def initialize(help_flags: false,
|
158
178
|
usage_flags: false,
|
179
|
+
list_flags: false,
|
159
180
|
recursive_flags: false,
|
160
181
|
search_flags: false,
|
161
182
|
default_recursive: false,
|
@@ -167,6 +188,7 @@ module Toys
|
|
167
188
|
styled_output: nil)
|
168
189
|
@help_flags = help_flags
|
169
190
|
@usage_flags = usage_flags
|
191
|
+
@list_flags = list_flags
|
170
192
|
@recursive_flags = recursive_flags
|
171
193
|
@search_flags = search_flags
|
172
194
|
@default_recursive = default_recursive ? true : false
|
@@ -183,20 +205,17 @@ module Toys
|
|
183
205
|
#
|
184
206
|
def config(tool_definition, loader)
|
185
207
|
unless tool_definition.argument_parsing_disabled?
|
208
|
+
has_subtools = loader.has_subtools?(tool_definition.full_name)
|
186
209
|
help_flags = add_help_flags(tool_definition)
|
187
210
|
usage_flags = add_usage_flags(tool_definition)
|
188
|
-
|
189
|
-
|
190
|
-
tool_definition.set_remaining_args(TOOL_NAME_KEY,
|
191
|
-
display_name: "TOOL_NAME",
|
192
|
-
desc: "The tool for which to display help")
|
193
|
-
end
|
194
|
-
end
|
195
|
-
if (!help_flags.empty? || @fallback_execution) &&
|
196
|
-
loader.has_subtools?(tool_definition.full_name)
|
211
|
+
list_flags = has_subtools ? add_list_flags(tool_definition) : []
|
212
|
+
if (!help_flags.empty? || !list_flags.empty? || @fallback_execution) && has_subtools
|
197
213
|
add_recursive_flags(tool_definition)
|
198
214
|
add_search_flags(tool_definition)
|
199
215
|
end
|
216
|
+
if !help_flags.empty? || !usage_flags.empty? || !list_flags.empty?
|
217
|
+
add_root_args(tool_definition)
|
218
|
+
end
|
200
219
|
end
|
201
220
|
yield
|
202
221
|
end
|
@@ -206,17 +225,16 @@ module Toys
|
|
206
225
|
#
|
207
226
|
def run(tool)
|
208
227
|
if tool[SHOW_USAGE_KEY]
|
209
|
-
|
210
|
-
|
211
|
-
terminal.puts(
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
output_help(str)
|
228
|
+
terminal.puts(get_help_text(tool).usage_string(wrap_width: terminal.width))
|
229
|
+
elsif tool[SHOW_LIST_KEY]
|
230
|
+
terminal.puts(get_help_text(tool).list_string(recursive: tool[RECURSIVE_SUBTOOLS_KEY],
|
231
|
+
search: tool[SEARCH_STRING_KEY],
|
232
|
+
wrap_width: terminal.width))
|
233
|
+
elsif should_show_help(tool)
|
234
|
+
output_help(get_help_text(tool).help_string(recursive: tool[RECURSIVE_SUBTOOLS_KEY],
|
235
|
+
search: tool[SEARCH_STRING_KEY],
|
236
|
+
show_source_path: @show_source_path,
|
237
|
+
wrap_width: terminal.width))
|
220
238
|
else
|
221
239
|
yield
|
222
240
|
end
|
@@ -228,6 +246,11 @@ module Toys
|
|
228
246
|
@terminal ||= Utils::Terminal.new(output: @stream, styled: @styled_output)
|
229
247
|
end
|
230
248
|
|
249
|
+
def should_show_help(tool)
|
250
|
+
@fallback_execution && !tool[Tool::Keys::TOOL_DEFINITION].runnable? ||
|
251
|
+
tool[SHOW_HELP_KEY]
|
252
|
+
end
|
253
|
+
|
231
254
|
def output_help(str)
|
232
255
|
if less_path
|
233
256
|
Utils::Exec.new.exec([less_path, "-R"], in: [:string, str])
|
@@ -288,6 +311,18 @@ module Toys
|
|
288
311
|
usage_flags
|
289
312
|
end
|
290
313
|
|
314
|
+
def add_list_flags(tool_definition)
|
315
|
+
list_flags = resolve_flags_spec(@list_flags, tool_definition, DEFAULT_LIST_FLAGS)
|
316
|
+
unless list_flags.empty?
|
317
|
+
tool_definition.add_flag(
|
318
|
+
SHOW_LIST_KEY, list_flags,
|
319
|
+
report_collisions: false,
|
320
|
+
desc: "List the subtools under this tool"
|
321
|
+
)
|
322
|
+
end
|
323
|
+
list_flags
|
324
|
+
end
|
325
|
+
|
291
326
|
def add_recursive_flags(tool_definition)
|
292
327
|
recursive_flags = resolve_flags_spec(@recursive_flags, tool_definition,
|
293
328
|
DEFAULT_RECURSIVE_FLAGS)
|
@@ -297,7 +332,8 @@ module Toys
|
|
297
332
|
tool_definition.add_flag(
|
298
333
|
RECURSIVE_SUBTOOLS_KEY, recursive_flags,
|
299
334
|
report_collisions: false, default: @default_recursive,
|
300
|
-
desc: "
|
335
|
+
desc: "List all subtools recursively when displaying help" \
|
336
|
+
" (default is #{@default_recursive})"
|
301
337
|
)
|
302
338
|
end
|
303
339
|
recursive_flags
|
@@ -309,12 +345,20 @@ module Toys
|
|
309
345
|
tool_definition.add_flag(
|
310
346
|
SEARCH_STRING_KEY, search_flags,
|
311
347
|
report_collisions: false,
|
312
|
-
desc: "Search subtools for the given regular expression"
|
348
|
+
desc: "Search subtools for the given regular expression when displaying help"
|
313
349
|
)
|
314
350
|
end
|
315
351
|
search_flags
|
316
352
|
end
|
317
353
|
|
354
|
+
def add_root_args(tool_definition)
|
355
|
+
if @allow_root_args && tool_definition.root? && tool_definition.arg_definitions.empty?
|
356
|
+
tool_definition.set_remaining_args(TOOL_NAME_KEY,
|
357
|
+
display_name: "TOOL_NAME",
|
358
|
+
desc: "The tool for which to display help")
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
318
362
|
def resolve_flags_spec(flags, tool_definition, defaults)
|
319
363
|
flags = flags.call(tool_definition) if flags.respond_to?(:call)
|
320
364
|
case flags
|
data/lib/toys/tool.rb
CHANGED
@@ -259,6 +259,18 @@ module Toys
|
|
259
259
|
end
|
260
260
|
end
|
261
261
|
|
262
|
+
##
|
263
|
+
# Find the given data file or directory in this tool's search path.
|
264
|
+
#
|
265
|
+
# @param [String] path The path to find
|
266
|
+
# @param [nil,:file,:directory] type Type of file system object to find,
|
267
|
+
# or nil to return any type.
|
268
|
+
# @return [String,nil] Absolute path of the result, or nil if not found.
|
269
|
+
#
|
270
|
+
def find_data(path, type: nil)
|
271
|
+
@__data[Keys::TOOL_DEFINITION].find_data(path, type: type)
|
272
|
+
end
|
273
|
+
|
262
274
|
##
|
263
275
|
# Exit immediately with the given status code
|
264
276
|
#
|
data/lib/toys/utils/help_text.rb
CHANGED
@@ -129,6 +129,29 @@ module Toys
|
|
129
129
|
assembler.result
|
130
130
|
end
|
131
131
|
|
132
|
+
##
|
133
|
+
# Generate a subtool list string.
|
134
|
+
#
|
135
|
+
# @param [Boolean] recursive If true, and the tool is a namespace,
|
136
|
+
# display all subcommands recursively. Defaults to false.
|
137
|
+
# @param [String,nil] search An optional string to search for when
|
138
|
+
# listing subcommands. Defaults to `nil` which finds all subcommands.
|
139
|
+
# @param [Integer] indent Indent width. Default is {DEFAULT_INDENT}.
|
140
|
+
# @param [Integer,nil] wrap_width Wrap width of the column, or `nil` to
|
141
|
+
# disable wrap. Default is `nil`.
|
142
|
+
# @param [Boolean] styled Output ansi styles. Default is `true`.
|
143
|
+
#
|
144
|
+
# @return [String] A usage string.
|
145
|
+
#
|
146
|
+
def list_string(recursive: false, search: nil,
|
147
|
+
indent: nil, wrap_width: nil, styled: true)
|
148
|
+
indent ||= DEFAULT_INDENT
|
149
|
+
subtools = find_subtools(recursive, search)
|
150
|
+
assembler = ListStringAssembler.new(@tool, subtools, recursive, search,
|
151
|
+
indent, wrap_width, styled)
|
152
|
+
assembler.result
|
153
|
+
end
|
154
|
+
|
132
155
|
private
|
133
156
|
|
134
157
|
def find_subtools(recursive, search)
|
@@ -493,6 +516,86 @@ module Toys
|
|
493
516
|
"#{' ' * (@indent + @indent2)}#{str}"
|
494
517
|
end
|
495
518
|
end
|
519
|
+
|
520
|
+
## @private
|
521
|
+
class ListStringAssembler
|
522
|
+
def initialize(tool, subtools, recursive, search_term, indent, wrap_width, styled)
|
523
|
+
@tool = tool
|
524
|
+
@subtools = subtools
|
525
|
+
@recursive = recursive
|
526
|
+
@search_term = search_term
|
527
|
+
@indent = indent
|
528
|
+
@wrap_width = wrap_width
|
529
|
+
assemble(styled)
|
530
|
+
end
|
531
|
+
|
532
|
+
attr_reader :result
|
533
|
+
|
534
|
+
private
|
535
|
+
|
536
|
+
def assemble(styled)
|
537
|
+
@lines = Utils::Terminal.new(output: ::StringIO.new, styled: styled)
|
538
|
+
add_header
|
539
|
+
add_list
|
540
|
+
@result = @lines.output.string
|
541
|
+
end
|
542
|
+
|
543
|
+
def add_header
|
544
|
+
top_line = @recursive ? "Recursive list of tools" : "List of tools"
|
545
|
+
@lines <<
|
546
|
+
if @tool.root?
|
547
|
+
"#{top_line}:"
|
548
|
+
else
|
549
|
+
"#{top_line} under #{bold(@tool.display_name)}:"
|
550
|
+
end
|
551
|
+
@lines << ""
|
552
|
+
if @search_term
|
553
|
+
@lines << "Showing search results for \"#{@search_term}\""
|
554
|
+
@lines << ""
|
555
|
+
end
|
556
|
+
end
|
557
|
+
|
558
|
+
def add_list
|
559
|
+
name_len = @tool.full_name.length
|
560
|
+
@subtools.each do |subtool|
|
561
|
+
tool_name = subtool.full_name.slice(name_len..-1).join(" ")
|
562
|
+
desc =
|
563
|
+
if subtool.is_a?(Definition::Alias)
|
564
|
+
"(Alias of #{subtool.display_target})"
|
565
|
+
else
|
566
|
+
subtool.desc
|
567
|
+
end
|
568
|
+
add_prefix_with_desc(bold(tool_name), desc)
|
569
|
+
end
|
570
|
+
end
|
571
|
+
|
572
|
+
def add_prefix_with_desc(prefix, desc)
|
573
|
+
if desc.empty?
|
574
|
+
@lines << prefix
|
575
|
+
elsif !desc.is_a?(Utils::WrappableString)
|
576
|
+
@lines << "#{prefix} - #{desc}"
|
577
|
+
else
|
578
|
+
desc = wrap_indent(Utils::WrappableString.new(["#{prefix} -"] + desc.fragments))
|
579
|
+
@lines << desc[0]
|
580
|
+
desc[1..-1].each do |line|
|
581
|
+
@lines << indent_str(line)
|
582
|
+
end
|
583
|
+
end
|
584
|
+
end
|
585
|
+
|
586
|
+
def wrap_indent(input)
|
587
|
+
return Utils::WrappableString.wrap_lines(input, nil) unless @wrap_width
|
588
|
+
Utils::WrappableString.wrap_lines(input, @wrap_width, @wrap_width - @indent)
|
589
|
+
end
|
590
|
+
|
591
|
+
def bold(str)
|
592
|
+
@lines.apply_styles(str, :bold)
|
593
|
+
end
|
594
|
+
|
595
|
+
def indent_str(str)
|
596
|
+
"#{' ' * @indent}#{str}"
|
597
|
+
end
|
598
|
+
end
|
496
599
|
end
|
497
600
|
end
|
498
601
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: toys-core
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
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-08
|
11
|
+
date: 2018-10-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: highline
|
@@ -66,34 +66,48 @@ dependencies:
|
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '5.2'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: redcarpet
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.4'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.4'
|
69
83
|
- !ruby/object:Gem::Dependency
|
70
84
|
name: rubocop
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
72
86
|
requirements:
|
73
87
|
- - "~>"
|
74
88
|
- !ruby/object:Gem::Version
|
75
|
-
version: 0.
|
89
|
+
version: 0.59.1
|
76
90
|
type: :development
|
77
91
|
prerelease: false
|
78
92
|
version_requirements: !ruby/object:Gem::Requirement
|
79
93
|
requirements:
|
80
94
|
- - "~>"
|
81
95
|
- !ruby/object:Gem::Version
|
82
|
-
version: 0.
|
96
|
+
version: 0.59.1
|
83
97
|
- !ruby/object:Gem::Dependency
|
84
98
|
name: yard
|
85
99
|
requirement: !ruby/object:Gem::Requirement
|
86
100
|
requirements:
|
87
101
|
- - "~>"
|
88
102
|
- !ruby/object:Gem::Version
|
89
|
-
version: 0.9.
|
103
|
+
version: 0.9.16
|
90
104
|
type: :development
|
91
105
|
prerelease: false
|
92
106
|
version_requirements: !ruby/object:Gem::Requirement
|
93
107
|
requirements:
|
94
108
|
- - "~>"
|
95
109
|
- !ruby/object:Gem::Version
|
96
|
-
version: 0.9.
|
110
|
+
version: 0.9.16
|
97
111
|
description: Toys-Core is the command line tool framework underlying Toys. It can
|
98
112
|
be used to create command line binaries using the internal Toys APIs.
|
99
113
|
email:
|
@@ -113,6 +127,7 @@ files:
|
|
113
127
|
- lib/toys/definition/acceptor.rb
|
114
128
|
- lib/toys/definition/alias.rb
|
115
129
|
- lib/toys/definition/arg.rb
|
130
|
+
- lib/toys/definition/data_finder.rb
|
116
131
|
- lib/toys/definition/flag.rb
|
117
132
|
- lib/toys/definition/tool.rb
|
118
133
|
- lib/toys/dsl/arg.rb
|
@@ -145,7 +160,11 @@ files:
|
|
145
160
|
homepage: https://github.com/dazuma/toys
|
146
161
|
licenses:
|
147
162
|
- BSD-3-Clause
|
148
|
-
metadata:
|
163
|
+
metadata:
|
164
|
+
changelog_uri: https://github.com/dazuma/toys/blob/master/toys-core/CHANGELOG.md
|
165
|
+
source_code_uri: https://github.com/dazuma/toys
|
166
|
+
bug_tracker_uri: https://github.com/dazuma/toys/issues
|
167
|
+
documentation_uri: https://www.rubydoc.info/gems/toys-core
|
149
168
|
post_install_message:
|
150
169
|
rdoc_options: []
|
151
170
|
require_paths:
|