toys-core 0.4.5 → 0.5.0
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 +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:
|