toys-core 0.14.7 → 0.15.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 +28 -0
- data/LICENSE.md +1 -1
- data/README.md +10 -5
- data/docs/guide.md +534 -39
- data/lib/toys/cli.rb +76 -172
- data/lib/toys/compat.rb +15 -30
- data/lib/toys/context.rb +51 -0
- data/lib/toys/core.rb +1 -1
- data/lib/toys/dsl/flag.rb +40 -2
- data/lib/toys/dsl/flag_group.rb +15 -5
- data/lib/toys/dsl/internal.rb +23 -8
- data/lib/toys/dsl/positional_arg.rb +32 -1
- data/lib/toys/dsl/tool.rb +174 -68
- data/lib/toys/errors.rb +2 -2
- data/lib/toys/middleware.rb +3 -2
- data/lib/toys/settings.rb +1 -1
- data/lib/toys/standard_mixins/bundler.rb +16 -1
- data/lib/toys/standard_mixins/exec.rb +29 -8
- data/lib/toys/standard_mixins/gems.rb +17 -3
- data/lib/toys/standard_mixins/highline.rb +12 -12
- data/lib/toys/standard_mixins/terminal.rb +7 -7
- data/lib/toys/tool_definition.rb +153 -50
- data/lib/toys/utils/exec.rb +22 -1
- data/lib/toys/utils/gems.rb +3 -0
- data/lib/toys/utils/standard_ui.rb +261 -0
- data/lib/toys-core.rb +51 -3
- metadata +6 -5
@@ -0,0 +1,261 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Toys
|
4
|
+
module Utils
|
5
|
+
##
|
6
|
+
# An object that implements standard UI elements, such as error reports and
|
7
|
+
# logging, as provided by the `toys` command line. Specifically, it
|
8
|
+
# implements pretty formatting of log entries and stack traces, and renders
|
9
|
+
# using ANSI coloring where available via {Toys::Utils::Terminal}.
|
10
|
+
#
|
11
|
+
# This object can be used to implement `toys`-style behavior when creating
|
12
|
+
# a CLI object. For example:
|
13
|
+
#
|
14
|
+
# require "toys/utils/standard_ui"
|
15
|
+
# ui = Toys::Utils::StandardUI.new
|
16
|
+
# cli = Toys::CLI.new(**ui.cli_args)
|
17
|
+
#
|
18
|
+
class StandardUI
|
19
|
+
##
|
20
|
+
# Create a Standard UI.
|
21
|
+
#
|
22
|
+
# By default, all output is written to `$stderr`, and will share a single
|
23
|
+
# {Toys::Utils::Terminal} object, allowing multiple tools and/or threads
|
24
|
+
# to interleave messages without interrupting one another.
|
25
|
+
#
|
26
|
+
# @param output [IO,Toys::Utils::Terminal] Where to write output. You can
|
27
|
+
# pass a terminal object, or an IO stream that will be wrapped in a
|
28
|
+
# terminal output. Default is `$stderr`.
|
29
|
+
#
|
30
|
+
def initialize(output: nil)
|
31
|
+
@terminal = output || $stderr
|
32
|
+
require "toys/utils/terminal"
|
33
|
+
@terminal = Terminal.new(output: @terminal) unless @terminal.is_a?(Terminal)
|
34
|
+
@log_header_severity_styles = {
|
35
|
+
"FATAL" => [:bright_magenta, :bold, :underline],
|
36
|
+
"ERROR" => [:bright_red, :bold],
|
37
|
+
"WARN" => [:bright_yellow],
|
38
|
+
"INFO" => [:bright_cyan],
|
39
|
+
"DEBUG" => [:white],
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
##
|
44
|
+
# The terminal underlying this UI
|
45
|
+
#
|
46
|
+
# @return [Toys::Utils::Terminal]
|
47
|
+
#
|
48
|
+
attr_reader :terminal
|
49
|
+
|
50
|
+
##
|
51
|
+
# A hash that maps severities to styles recognized by
|
52
|
+
# {Toys::Utils::Terminal}. Used to style the header for each log entry.
|
53
|
+
# This hash can be modified in place to adjust the behavior of loggers
|
54
|
+
# created by this UI.
|
55
|
+
#
|
56
|
+
# @return [Hash{String => Array<Symbol>}]
|
57
|
+
#
|
58
|
+
attr_reader :log_header_severity_styles
|
59
|
+
|
60
|
+
##
|
61
|
+
# Convenience method that returns a hash of arguments that can be passed
|
62
|
+
# to the {Toys::CLI} constructor. Includes the `:error_handler` and
|
63
|
+
# `:logger_factory` arguments.
|
64
|
+
#
|
65
|
+
# @return [Hash]
|
66
|
+
#
|
67
|
+
def cli_args
|
68
|
+
{
|
69
|
+
error_handler: error_handler,
|
70
|
+
logger_factory: logger_factory,
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
##
|
75
|
+
# Returns an error handler conforming to the `:error_handler` argument to
|
76
|
+
# the {Toys::CLI} constructor. Specifically, it returns the
|
77
|
+
# {#error_handler_impl} method as a proc.
|
78
|
+
#
|
79
|
+
# @return [Proc]
|
80
|
+
#
|
81
|
+
def error_handler
|
82
|
+
@error_handler ||= method(:error_handler_impl).to_proc
|
83
|
+
end
|
84
|
+
|
85
|
+
##
|
86
|
+
# Returns a logger factory conforming to the `:logger_factory` argument
|
87
|
+
# to the {Toys::CLI} constructor. Specifically, it returns the
|
88
|
+
# {#logger_factory_impl} method as a proc.
|
89
|
+
#
|
90
|
+
# @return [Proc]
|
91
|
+
#
|
92
|
+
def logger_factory
|
93
|
+
@logger_factory ||= method(:logger_factory_impl).to_proc
|
94
|
+
end
|
95
|
+
|
96
|
+
##
|
97
|
+
# Implementation of the error handler. As dictated by the error handler
|
98
|
+
# specification in {Toys::CLI}, this must take a {Toys::ContextualError}
|
99
|
+
# as an argument, and return an exit code.
|
100
|
+
#
|
101
|
+
# The base implementation uses {#display_error_notice} and
|
102
|
+
# {#display_signal_notice} to print an appropriate message to the UI's
|
103
|
+
# terminal, and uses {#exit_code_for} to determine the correct exit code.
|
104
|
+
# Any of those methods can be overridden by a subclass to alter their
|
105
|
+
# behavior, or this main implementation method can be overridden to
|
106
|
+
# change the overall behavior.
|
107
|
+
#
|
108
|
+
# @param error [Toys::ContextualError] The error received
|
109
|
+
# @return [Integer] The exit code
|
110
|
+
#
|
111
|
+
def error_handler_impl(error)
|
112
|
+
cause = error.cause
|
113
|
+
if cause.is_a?(::SignalException)
|
114
|
+
display_signal_notice(cause)
|
115
|
+
else
|
116
|
+
display_error_notice(error)
|
117
|
+
end
|
118
|
+
exit_code_for(cause)
|
119
|
+
end
|
120
|
+
|
121
|
+
##
|
122
|
+
# Implementation of the logger factory. As dictated by the logger factory
|
123
|
+
# specification in {Toys::CLI}, this must take a {Toys::ToolDefinition}
|
124
|
+
# as an argument, and return a `Logger`.
|
125
|
+
#
|
126
|
+
# The base implementation returns a logger that writes to the UI's
|
127
|
+
# terminal, using {#logger_formatter_impl} as the formatter. It sets the
|
128
|
+
# level to `Logger::WARN` by default. Either this method or the helper
|
129
|
+
# methods can be overridden to change this behavior.
|
130
|
+
#
|
131
|
+
# @param _tool {Toys::ToolDefinition} The tool definition of the tool to
|
132
|
+
# be executed
|
133
|
+
# @return [Logger]
|
134
|
+
#
|
135
|
+
def logger_factory_impl(_tool)
|
136
|
+
logger = ::Logger.new(@terminal)
|
137
|
+
logger.formatter = method(:logger_formatter_impl).to_proc
|
138
|
+
logger.level = ::Logger::WARN
|
139
|
+
logger
|
140
|
+
end
|
141
|
+
|
142
|
+
##
|
143
|
+
# Returns an exit code appropriate for the given exception. Currently,
|
144
|
+
# the logic interprets signals (returning the convention of 128 + signo),
|
145
|
+
# usage errors (returning the conventional value of 2), and tool not
|
146
|
+
# runnable errors (returning the conventional value of 126), and defaults
|
147
|
+
# to 1 for all other error types.
|
148
|
+
#
|
149
|
+
# This method is used by {#error_handler_impl} and can be overridden to
|
150
|
+
# change its behavior.
|
151
|
+
#
|
152
|
+
# @param error [Exception] The exception raised. This method expects the
|
153
|
+
# original exception, rather than a ContextualError.
|
154
|
+
# @return [Integer] The appropriate exit code
|
155
|
+
#
|
156
|
+
def exit_code_for(error)
|
157
|
+
case error
|
158
|
+
when ArgParsingError
|
159
|
+
2
|
160
|
+
when NotRunnableError
|
161
|
+
126
|
162
|
+
when ::SignalException
|
163
|
+
error.signo + 128
|
164
|
+
else
|
165
|
+
1
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
##
|
170
|
+
# Displays a default output for a signal received.
|
171
|
+
#
|
172
|
+
# This method is used by {#error_handler_impl} and can be overridden to
|
173
|
+
# change its behavior.
|
174
|
+
#
|
175
|
+
# @param error [SignalException]
|
176
|
+
#
|
177
|
+
def display_signal_notice(error)
|
178
|
+
@terminal.puts
|
179
|
+
if error.is_a?(::Interrupt)
|
180
|
+
@terminal.puts("INTERRUPTED", :bold)
|
181
|
+
else
|
182
|
+
@terminal.puts("SIGNAL RECEIVED: #{error.signm || error.signo}", :bold)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
##
|
187
|
+
# Displays a default output for an error. Displays the error, the
|
188
|
+
# backtrace, and contextual information regarding what tool was run and
|
189
|
+
# where in its code the error occurred.
|
190
|
+
#
|
191
|
+
# This method is used by {#error_handler_impl} and can be overridden to
|
192
|
+
# change its behavior.
|
193
|
+
#
|
194
|
+
# @param error [Toys::ContextualError]
|
195
|
+
#
|
196
|
+
def display_error_notice(error)
|
197
|
+
@terminal.puts
|
198
|
+
@terminal.puts(cause_string(error.cause))
|
199
|
+
@terminal.puts(context_string(error), :bold)
|
200
|
+
end
|
201
|
+
|
202
|
+
##
|
203
|
+
# Implementation of the formatter used by loggers created by this UI's
|
204
|
+
# logger factory. This interface is defined by the standard `Logger`
|
205
|
+
# class.
|
206
|
+
#
|
207
|
+
# This method can be overridden to change the behavior of loggers created
|
208
|
+
# by this UI.
|
209
|
+
#
|
210
|
+
# @param severity [String]
|
211
|
+
# @param time [Time]
|
212
|
+
# @param _progname [String]
|
213
|
+
# @param msg [Object]
|
214
|
+
# @return [String]
|
215
|
+
#
|
216
|
+
def logger_formatter_impl(severity, time, _progname, msg)
|
217
|
+
msg_str =
|
218
|
+
case msg
|
219
|
+
when ::String
|
220
|
+
msg
|
221
|
+
when ::Exception
|
222
|
+
"#{msg.message} (#{msg.class})\n" << (msg.backtrace || []).join("\n")
|
223
|
+
else
|
224
|
+
msg.inspect
|
225
|
+
end
|
226
|
+
timestr = time.strftime("%Y-%m-%d %H:%M:%S")
|
227
|
+
header = format("[%<time>s %<sev>5s]", time: timestr, sev: severity)
|
228
|
+
styles = log_header_severity_styles[severity]
|
229
|
+
header = @terminal.apply_styles(header, *styles) if styles
|
230
|
+
"#{header} #{msg_str}\n"
|
231
|
+
end
|
232
|
+
|
233
|
+
private
|
234
|
+
|
235
|
+
def cause_string(cause)
|
236
|
+
lines = ["#{cause.class}: #{cause.message}"]
|
237
|
+
cause.backtrace.each_with_index.reverse_each do |bt, i|
|
238
|
+
lines << " #{(i + 1).to_s.rjust(3)}: #{bt}"
|
239
|
+
end
|
240
|
+
lines.join("\n")
|
241
|
+
end
|
242
|
+
|
243
|
+
def context_string(error)
|
244
|
+
lines = [
|
245
|
+
error.banner || "Unexpected error!",
|
246
|
+
" #{error.cause.class}: #{error.cause.message}",
|
247
|
+
]
|
248
|
+
if error.config_path
|
249
|
+
lines << " in config file: #{error.config_path}:#{error.config_line}"
|
250
|
+
end
|
251
|
+
if error.tool_name
|
252
|
+
lines << " while executing tool: #{error.tool_name.join(' ').inspect}"
|
253
|
+
if error.tool_args
|
254
|
+
lines << " with arguments: #{error.tool_args.inspect}"
|
255
|
+
end
|
256
|
+
end
|
257
|
+
lines.join("\n")
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
data/lib/toys-core.rb
CHANGED
@@ -12,10 +12,51 @@
|
|
12
12
|
# This module contains the command line framework underlying Toys. It can be
|
13
13
|
# used to create command line executables using the Toys DSL and classes.
|
14
14
|
#
|
15
|
+
# ## Common starting points
|
16
|
+
#
|
17
|
+
# Some of the most commonly needed class documentation is listed below:
|
18
|
+
#
|
19
|
+
# * For information on the DSL used to write tools, start with
|
20
|
+
# {Toys::DSL::Tool}.
|
21
|
+
# * The base class for tool runtime (i.e. that defines the basic methods
|
22
|
+
# available to a tool's implementation) is {Toys::Context}.
|
23
|
+
# * For information on writing mixins, see {Toys::Mixin}.
|
24
|
+
# * For information on writing templates, see {Toys::Template}.
|
25
|
+
# * For information on writing acceptors, see {Toys::Acceptor}.
|
26
|
+
# * For information on writing custom shell completions, see {Toys::Completion}.
|
27
|
+
# * Standard mixins are defined under the {Toys::StandardMixins} module.
|
28
|
+
# * Various utilities are defined under {Toys::Utils}. Some of these serve as
|
29
|
+
# the implementations of corresponding mixins.
|
30
|
+
# * The main entrypoint for the command line framework is {Toys::CLI}.
|
31
|
+
#
|
32
|
+
# Other important internal classes are listed below.
|
33
|
+
#
|
34
|
+
# * The definition of a tool is represented by {Toys::ToolDefinition} along
|
35
|
+
# the helpers {Toys::Flag}, {Toys::PositionalArg}, and {Toys::FlagGroup}.
|
36
|
+
# * Argument parsing is implemented by {Toys::ArgParser}.
|
37
|
+
# * The process of finding and loading a tool definition given a tool name, is
|
38
|
+
# implemented by {Toys::Loader}.
|
39
|
+
# * Text wrapping is handled by {Toys::WrappableString}.
|
40
|
+
# * The settings system is implemented by {Toys::Settings}.
|
41
|
+
#
|
15
42
|
module Toys
|
16
43
|
##
|
17
44
|
# Namespace for DSL classes. These classes provide the directives that can be
|
18
|
-
# used in configuration files.
|
45
|
+
# used in configuration files.
|
46
|
+
#
|
47
|
+
# DSL directives that can appear at the top level of Toys files and tool
|
48
|
+
# blocks are defined by the {Toys::DSL::Tool} module.
|
49
|
+
#
|
50
|
+
# Directives that can appear within a block passed to {Toys::DSL::Tool#flag}
|
51
|
+
# are defined by the {Toys::DSL::Flag} class.
|
52
|
+
#
|
53
|
+
# Directives that can appear within a {Toys::DSL::Tool#flag_group} block or
|
54
|
+
# any of its related directives, are defined by the {Toys::DSL::FlagGroup}
|
55
|
+
# class.
|
56
|
+
#
|
57
|
+
# Directives that can appear within a {Toys::DSL::Tool#required_arg},
|
58
|
+
# {Toys::DSL::Tool#optional_arg}, or {Toys::DSL::Tool#remaining_args} block,
|
59
|
+
# are defined by the {Toys::DSL::PositionalArg} class.
|
19
60
|
#
|
20
61
|
module DSL
|
21
62
|
end
|
@@ -23,6 +64,9 @@ module Toys
|
|
23
64
|
##
|
24
65
|
# Namespace for standard middleware classes.
|
25
66
|
#
|
67
|
+
# These middleware are provided by Toys-Core and can be referenced by name
|
68
|
+
# when creating a {Toys::CLI}.
|
69
|
+
#
|
26
70
|
module StandardMiddleware
|
27
71
|
##
|
28
72
|
# @private
|
@@ -42,6 +86,9 @@ module Toys
|
|
42
86
|
##
|
43
87
|
# Namespace for standard mixin classes.
|
44
88
|
#
|
89
|
+
# These mixins are provided by Toys-Core and can be included by name by
|
90
|
+
# passing a symbol to {Toys::DSL::Tool#include}.
|
91
|
+
#
|
45
92
|
module StandardMixins
|
46
93
|
end
|
47
94
|
|
@@ -49,8 +96,9 @@ module Toys
|
|
49
96
|
# Namespace for common utility classes.
|
50
97
|
#
|
51
98
|
# These classes are not loaded by default, and must be required explicitly.
|
52
|
-
# For example, before using {Toys::Utils::Exec}, you must
|
53
|
-
#
|
99
|
+
# For example, before using {Toys::Utils::Exec}, you must:
|
100
|
+
#
|
101
|
+
# require "toys/utils/exec"
|
54
102
|
#
|
55
103
|
module Utils
|
56
104
|
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.15.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: 2023-
|
11
|
+
date: 2023-10-13 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Toys-Core is the command line tool framework underlying Toys. It can
|
14
14
|
be used to create command line executables using the Toys DSL and classes.
|
@@ -71,6 +71,7 @@ files:
|
|
71
71
|
- lib/toys/utils/git_cache.rb
|
72
72
|
- lib/toys/utils/help_text.rb
|
73
73
|
- lib/toys/utils/pager.rb
|
74
|
+
- lib/toys/utils/standard_ui.rb
|
74
75
|
- lib/toys/utils/terminal.rb
|
75
76
|
- lib/toys/utils/xdg.rb
|
76
77
|
- lib/toys/wrappable_string.rb
|
@@ -78,10 +79,10 @@ homepage: https://github.com/dazuma/toys
|
|
78
79
|
licenses:
|
79
80
|
- MIT
|
80
81
|
metadata:
|
81
|
-
changelog_uri: https://dazuma.github.io/toys/gems/toys-core/v0.
|
82
|
+
changelog_uri: https://dazuma.github.io/toys/gems/toys-core/v0.15.0/file.CHANGELOG.html
|
82
83
|
source_code_uri: https://github.com/dazuma/toys/tree/main/toys-core
|
83
84
|
bug_tracker_uri: https://github.com/dazuma/toys/issues
|
84
|
-
documentation_uri: https://dazuma.github.io/toys/gems/toys-core/v0.
|
85
|
+
documentation_uri: https://dazuma.github.io/toys/gems/toys-core/v0.15.0
|
85
86
|
post_install_message:
|
86
87
|
rdoc_options: []
|
87
88
|
require_paths:
|
@@ -97,7 +98,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
97
98
|
- !ruby/object:Gem::Version
|
98
99
|
version: '0'
|
99
100
|
requirements: []
|
100
|
-
rubygems_version: 3.
|
101
|
+
rubygems_version: 3.4.10
|
101
102
|
signing_key:
|
102
103
|
specification_version: 4
|
103
104
|
summary: Framework for creating command line executables
|