toys-core 0.11.5 → 0.13.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/CHANGELOG.md +62 -0
- data/LICENSE.md +1 -1
- data/README.md +5 -2
- data/docs/guide.md +1 -1
- data/lib/toys/acceptor.rb +13 -4
- data/lib/toys/arg_parser.rb +7 -7
- data/lib/toys/cli.rb +170 -120
- data/lib/toys/compat.rb +71 -23
- data/lib/toys/completion.rb +18 -6
- data/lib/toys/context.rb +24 -15
- data/lib/toys/core.rb +6 -2
- data/lib/toys/dsl/base.rb +87 -0
- data/lib/toys/dsl/flag.rb +26 -20
- data/lib/toys/dsl/flag_group.rb +18 -14
- data/lib/toys/dsl/internal.rb +206 -0
- data/lib/toys/dsl/positional_arg.rb +26 -16
- data/lib/toys/dsl/tool.rb +180 -218
- data/lib/toys/errors.rb +64 -8
- data/lib/toys/flag.rb +662 -656
- data/lib/toys/flag_group.rb +24 -10
- data/lib/toys/input_file.rb +13 -7
- data/lib/toys/loader.rb +293 -140
- data/lib/toys/middleware.rb +46 -22
- data/lib/toys/mixin.rb +10 -8
- data/lib/toys/positional_arg.rb +21 -20
- data/lib/toys/settings.rb +914 -0
- data/lib/toys/source_info.rb +147 -35
- data/lib/toys/standard_middleware/add_verbosity_flags.rb +2 -0
- data/lib/toys/standard_middleware/apply_config.rb +6 -4
- data/lib/toys/standard_middleware/handle_usage_errors.rb +1 -0
- data/lib/toys/standard_middleware/set_default_descriptions.rb +19 -18
- data/lib/toys/standard_middleware/show_help.rb +19 -5
- data/lib/toys/standard_middleware/show_root_version.rb +2 -0
- data/lib/toys/standard_mixins/bundler.rb +24 -15
- data/lib/toys/standard_mixins/exec.rb +43 -34
- data/lib/toys/standard_mixins/fileutils.rb +3 -1
- data/lib/toys/standard_mixins/gems.rb +21 -17
- data/lib/toys/standard_mixins/git_cache.rb +46 -0
- data/lib/toys/standard_mixins/highline.rb +8 -8
- data/lib/toys/standard_mixins/terminal.rb +5 -5
- data/lib/toys/standard_mixins/xdg.rb +56 -0
- data/lib/toys/template.rb +11 -9
- data/lib/toys/{tool.rb → tool_definition.rb} +292 -226
- data/lib/toys/utils/completion_engine.rb +7 -2
- data/lib/toys/utils/exec.rb +162 -132
- data/lib/toys/utils/gems.rb +85 -60
- data/lib/toys/utils/git_cache.rb +813 -0
- data/lib/toys/utils/help_text.rb +117 -37
- data/lib/toys/utils/terminal.rb +11 -3
- data/lib/toys/utils/xdg.rb +293 -0
- data/lib/toys/wrappable_string.rb +9 -2
- data/lib/toys-core.rb +18 -6
- metadata +14 -7
@@ -0,0 +1,206 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Toys
|
4
|
+
module DSL
|
5
|
+
##
|
6
|
+
# Internal utility calls used by the DSL.
|
7
|
+
#
|
8
|
+
# @private
|
9
|
+
#
|
10
|
+
module Internal
|
11
|
+
class << self
|
12
|
+
##
|
13
|
+
# Called by the Loader and InputFile to prepare a tool class for running
|
14
|
+
# the DSL.
|
15
|
+
#
|
16
|
+
# @private
|
17
|
+
#
|
18
|
+
def prepare(tool_class, words, priority, remaining_words, source, loader)
|
19
|
+
unless tool_class.is_a?(DSL::Tool)
|
20
|
+
class << tool_class
|
21
|
+
alias_method :super_include, :include
|
22
|
+
end
|
23
|
+
tool_class.extend(DSL::Tool)
|
24
|
+
end
|
25
|
+
unless tool_class.instance_variable_defined?(:@__words)
|
26
|
+
tool_class.instance_variable_set(:@__words, words)
|
27
|
+
tool_class.instance_variable_set(:@__priority, priority)
|
28
|
+
tool_class.instance_variable_set(:@__loader, loader)
|
29
|
+
tool_class.instance_variable_set(:@__source, [])
|
30
|
+
end
|
31
|
+
tool_class.instance_variable_set(:@__remaining_words, remaining_words)
|
32
|
+
tool_class.instance_variable_get(:@__source).push(source)
|
33
|
+
old_source = ::Thread.current[:__toys_current_source]
|
34
|
+
begin
|
35
|
+
::Thread.current[:__toys_current_source] = source
|
36
|
+
yield
|
37
|
+
ensure
|
38
|
+
tool_class.instance_variable_get(:@__source).pop
|
39
|
+
::Thread.current[:__toys_current_source] = old_source
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
##
|
44
|
+
# Called by the DSL implementation to get, and optionally activate, the
|
45
|
+
# current tool.
|
46
|
+
#
|
47
|
+
# @private
|
48
|
+
#
|
49
|
+
def current_tool(tool_class, activate)
|
50
|
+
memoize_var = activate ? :@__active_tool : :@__cur_tool
|
51
|
+
if tool_class.instance_variable_defined?(memoize_var)
|
52
|
+
tool_class.instance_variable_get(memoize_var)
|
53
|
+
else
|
54
|
+
loader = tool_class.instance_variable_get(:@__loader)
|
55
|
+
words = tool_class.instance_variable_get(:@__words)
|
56
|
+
priority = tool_class.instance_variable_get(:@__priority)
|
57
|
+
cur_tool =
|
58
|
+
if activate
|
59
|
+
loader.activate_tool(words, priority)
|
60
|
+
else
|
61
|
+
loader.get_tool(words, priority)
|
62
|
+
end
|
63
|
+
if cur_tool && activate
|
64
|
+
source = tool_class.instance_variable_get(:@__source).last
|
65
|
+
cur_tool.lock_source(source)
|
66
|
+
end
|
67
|
+
tool_class.instance_variable_set(memoize_var, cur_tool)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
##
|
72
|
+
# Called by the DSL implementation to add a getter to the tool class.
|
73
|
+
#
|
74
|
+
# @private
|
75
|
+
#
|
76
|
+
def maybe_add_getter(tool_class, key)
|
77
|
+
if key.is_a?(::Symbol) && key.to_s =~ /^[_a-zA-Z]\w*[!?]?$/ && key != :run
|
78
|
+
unless tool_class.public_method_defined?(key)
|
79
|
+
tool_class.class_eval do
|
80
|
+
define_method(key) do
|
81
|
+
self[key]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
##
|
89
|
+
# Called by the DSL implementation to find a named mixin.
|
90
|
+
#
|
91
|
+
# @private
|
92
|
+
#
|
93
|
+
def resolve_mixin(mixin, cur_tool, loader)
|
94
|
+
mod =
|
95
|
+
case mixin
|
96
|
+
when ::String
|
97
|
+
cur_tool.lookup_mixin(mixin)
|
98
|
+
when ::Symbol
|
99
|
+
loader.resolve_standard_mixin(mixin.to_s)
|
100
|
+
when ::Module
|
101
|
+
mixin
|
102
|
+
end
|
103
|
+
raise ToolDefinitionError, "Mixin not found: #{mixin.inspect}" unless mod
|
104
|
+
mod
|
105
|
+
end
|
106
|
+
|
107
|
+
##
|
108
|
+
# Called by the DSL implementation to load a long description from a
|
109
|
+
# file.
|
110
|
+
#
|
111
|
+
# @private
|
112
|
+
#
|
113
|
+
def load_long_desc_file(path)
|
114
|
+
if ::File.extname(path) == ".txt"
|
115
|
+
begin
|
116
|
+
::File.readlines(path).map do |line|
|
117
|
+
line = line.chomp
|
118
|
+
line =~ /^\s/ ? [line] : line
|
119
|
+
end
|
120
|
+
rescue ::SystemCallError => e
|
121
|
+
raise Toys::ToolDefinitionError, e.to_s
|
122
|
+
end
|
123
|
+
else
|
124
|
+
raise Toys::ToolDefinitionError, "Cannot load long desc from file type: #{path}"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
##
|
129
|
+
# Called by the Tool base class to set config values for a subclass.
|
130
|
+
#
|
131
|
+
# @private
|
132
|
+
#
|
133
|
+
def configure_class(tool_class, given_name = nil)
|
134
|
+
return if tool_class.name.nil? || tool_class.instance_variable_defined?(:@__loader)
|
135
|
+
|
136
|
+
mod_names = tool_class.name.split("::")
|
137
|
+
class_name = mod_names.pop
|
138
|
+
parent = parent_from_mod_name_segments(mod_names)
|
139
|
+
loader = parent.instance_variable_get(:@__loader)
|
140
|
+
name = given_name ? loader.split_path(given_name) : class_name_to_tool_name(class_name)
|
141
|
+
|
142
|
+
priority = parent.instance_variable_get(:@__priority)
|
143
|
+
words = parent.instance_variable_get(:@__words) + name
|
144
|
+
subtool = loader.get_tool(words, priority, tool_class)
|
145
|
+
|
146
|
+
remaining_words = parent.instance_variable_get(:@__remaining_words)
|
147
|
+
next_remaining = name.reduce(remaining_words) do |running_words, word|
|
148
|
+
Loader.next_remaining_words(running_words, word)
|
149
|
+
end
|
150
|
+
|
151
|
+
tool_class.instance_variable_set(:@__words, words)
|
152
|
+
tool_class.instance_variable_set(:@__priority, priority)
|
153
|
+
tool_class.instance_variable_set(:@__loader, loader)
|
154
|
+
tool_class.instance_variable_set(:@__source, [current_source_from_context])
|
155
|
+
tool_class.instance_variable_set(:@__remaining_words, next_remaining)
|
156
|
+
tool_class.instance_variable_set(:@__cur_tool, subtool)
|
157
|
+
end
|
158
|
+
|
159
|
+
##
|
160
|
+
# Called by the Tool base class to add the DSL to a subclass.
|
161
|
+
#
|
162
|
+
# @private
|
163
|
+
#
|
164
|
+
def setup_class_dsl(tool_class)
|
165
|
+
return if tool_class.name.nil? || tool_class.is_a?(DSL::Tool)
|
166
|
+
class << tool_class
|
167
|
+
alias_method :super_include, :include
|
168
|
+
end
|
169
|
+
tool_class.extend(DSL::Tool)
|
170
|
+
end
|
171
|
+
|
172
|
+
private
|
173
|
+
|
174
|
+
def class_name_to_tool_name(class_name)
|
175
|
+
name = class_name.to_s.sub(/^_+/, "").sub(/_+$/, "").gsub(/_+/, "-")
|
176
|
+
while name.sub!(/([^-])([A-Z])/, "\\1-\\2") do end
|
177
|
+
[name.downcase!]
|
178
|
+
end
|
179
|
+
|
180
|
+
def parent_from_mod_name_segments(mod_names)
|
181
|
+
parent = mod_names.reduce(::Object) do |running_mod, seg|
|
182
|
+
running_mod.const_get(seg)
|
183
|
+
end
|
184
|
+
if !parent.is_a?(::Toys::Tool) && parent.instance_variable_defined?(:@__tool_class)
|
185
|
+
parent = parent.instance_variable_get(:@__tool_class)
|
186
|
+
end
|
187
|
+
unless parent.ancestors.include?(::Toys::Context)
|
188
|
+
raise ToolDefinitionError, "Toys::Tool can be subclassed only from the Toys DSL"
|
189
|
+
end
|
190
|
+
parent
|
191
|
+
end
|
192
|
+
|
193
|
+
def current_source_from_context
|
194
|
+
source = ::Thread.current[:__toys_current_source]
|
195
|
+
if source.nil?
|
196
|
+
raise ToolDefinitionError, "Toys::Tool can be subclassed only from a Toys config file"
|
197
|
+
end
|
198
|
+
unless source.source_type == :file
|
199
|
+
raise ToolDefinitionError, "Toys::Tool cannot be subclassed inside a tool block"
|
200
|
+
end
|
201
|
+
source
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
@@ -10,7 +10,7 @@ module Toys
|
|
10
10
|
# {Toys::DSL::Tool#required_arg}, {Toys::DSL::Tool#optional_arg}, or
|
11
11
|
# {Toys::DSL::Tool#remaining_args}.
|
12
12
|
#
|
13
|
-
#
|
13
|
+
# ### Example
|
14
14
|
#
|
15
15
|
# tool "mytool" do
|
16
16
|
# optional_arg :value do
|
@@ -22,16 +22,6 @@ module Toys
|
|
22
22
|
# end
|
23
23
|
#
|
24
24
|
class PositionalArg
|
25
|
-
## @private
|
26
|
-
def initialize(acceptor, default, completion, display_name, desc, long_desc)
|
27
|
-
@default = default
|
28
|
-
@display_name = display_name
|
29
|
-
@desc = desc
|
30
|
-
@long_desc = long_desc || []
|
31
|
-
accept(acceptor, **{})
|
32
|
-
complete(completion, **{})
|
33
|
-
end
|
34
|
-
|
35
25
|
##
|
36
26
|
# Set the acceptor for this argument's values.
|
37
27
|
# You can pass either the string name of an acceptor defined in this tool
|
@@ -103,7 +93,7 @@ module Toys
|
|
103
93
|
# across the strings in the array. In this case, whitespace is not
|
104
94
|
# compacted.
|
105
95
|
#
|
106
|
-
#
|
96
|
+
# ### Examples
|
107
97
|
#
|
108
98
|
# If you pass in a sentence as a simple string, it may be word wrapped
|
109
99
|
# when displayed:
|
@@ -134,7 +124,7 @@ module Toys
|
|
134
124
|
# word-wrapped when displayed. To insert a blank line, include an empty
|
135
125
|
# string as one of the descriptions.
|
136
126
|
#
|
137
|
-
#
|
127
|
+
# ### Example
|
138
128
|
#
|
139
129
|
# long_desc "This initial paragraph might get word wrapped.",
|
140
130
|
# "This next paragraph is followed by a blank line.",
|
@@ -151,21 +141,41 @@ module Toys
|
|
151
141
|
self
|
152
142
|
end
|
153
143
|
|
154
|
-
##
|
144
|
+
##
|
145
|
+
# Called only from DSL::Tool
|
146
|
+
#
|
147
|
+
# @private
|
148
|
+
#
|
149
|
+
def initialize(acceptor, default, completion, display_name, desc, long_desc)
|
150
|
+
@default = default
|
151
|
+
@display_name = display_name
|
152
|
+
@desc = desc
|
153
|
+
@long_desc = long_desc || []
|
154
|
+
accept(acceptor, **{})
|
155
|
+
complete(completion, **{})
|
156
|
+
end
|
157
|
+
|
158
|
+
##
|
159
|
+
# @private
|
160
|
+
#
|
155
161
|
def _add_required_to(tool, key)
|
156
162
|
tool.add_required_arg(key,
|
157
163
|
accept: @acceptor, complete: @completion,
|
158
164
|
display_name: @display_name, desc: @desc, long_desc: @long_desc)
|
159
165
|
end
|
160
166
|
|
161
|
-
##
|
167
|
+
##
|
168
|
+
# @private
|
169
|
+
#
|
162
170
|
def _add_optional_to(tool, key)
|
163
171
|
tool.add_optional_arg(key,
|
164
172
|
accept: @acceptor, default: @default, complete: @completion,
|
165
173
|
display_name: @display_name, desc: @desc, long_desc: @long_desc)
|
166
174
|
end
|
167
175
|
|
168
|
-
##
|
176
|
+
##
|
177
|
+
# @private
|
178
|
+
#
|
169
179
|
def _set_remaining_on(tool, key)
|
170
180
|
tool.set_remaining_args(key,
|
171
181
|
accept: @acceptor, default: @default, complete: @completion,
|