toys-core 0.14.7 → 0.15.1

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.
@@ -0,0 +1,262 @@
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
+ require "logger"
32
+ require "toys/utils/terminal"
33
+ @terminal = output || $stderr
34
+ @terminal = Terminal.new(output: @terminal) unless @terminal.is_a?(Terminal)
35
+ @log_header_severity_styles = {
36
+ "FATAL" => [:bright_magenta, :bold, :underline],
37
+ "ERROR" => [:bright_red, :bold],
38
+ "WARN" => [:bright_yellow],
39
+ "INFO" => [:bright_cyan],
40
+ "DEBUG" => [:white],
41
+ }
42
+ end
43
+
44
+ ##
45
+ # The terminal underlying this UI
46
+ #
47
+ # @return [Toys::Utils::Terminal]
48
+ #
49
+ attr_reader :terminal
50
+
51
+ ##
52
+ # A hash that maps severities to styles recognized by
53
+ # {Toys::Utils::Terminal}. Used to style the header for each log entry.
54
+ # This hash can be modified in place to adjust the behavior of loggers
55
+ # created by this UI.
56
+ #
57
+ # @return [Hash{String => Array<Symbol>}]
58
+ #
59
+ attr_reader :log_header_severity_styles
60
+
61
+ ##
62
+ # Convenience method that returns a hash of arguments that can be passed
63
+ # to the {Toys::CLI} constructor. Includes the `:error_handler` and
64
+ # `:logger_factory` arguments.
65
+ #
66
+ # @return [Hash]
67
+ #
68
+ def cli_args
69
+ {
70
+ error_handler: error_handler,
71
+ logger_factory: logger_factory,
72
+ }
73
+ end
74
+
75
+ ##
76
+ # Returns an error handler conforming to the `:error_handler` argument to
77
+ # the {Toys::CLI} constructor. Specifically, it returns the
78
+ # {#error_handler_impl} method as a proc.
79
+ #
80
+ # @return [Proc]
81
+ #
82
+ def error_handler
83
+ @error_handler ||= method(:error_handler_impl).to_proc
84
+ end
85
+
86
+ ##
87
+ # Returns a logger factory conforming to the `:logger_factory` argument
88
+ # to the {Toys::CLI} constructor. Specifically, it returns the
89
+ # {#logger_factory_impl} method as a proc.
90
+ #
91
+ # @return [Proc]
92
+ #
93
+ def logger_factory
94
+ @logger_factory ||= method(:logger_factory_impl).to_proc
95
+ end
96
+
97
+ ##
98
+ # Implementation of the error handler. As dictated by the error handler
99
+ # specification in {Toys::CLI}, this must take a {Toys::ContextualError}
100
+ # as an argument, and return an exit code.
101
+ #
102
+ # The base implementation uses {#display_error_notice} and
103
+ # {#display_signal_notice} to print an appropriate message to the UI's
104
+ # terminal, and uses {#exit_code_for} to determine the correct exit code.
105
+ # Any of those methods can be overridden by a subclass to alter their
106
+ # behavior, or this main implementation method can be overridden to
107
+ # change the overall behavior.
108
+ #
109
+ # @param error [Toys::ContextualError] The error received
110
+ # @return [Integer] The exit code
111
+ #
112
+ def error_handler_impl(error)
113
+ cause = error.cause
114
+ if cause.is_a?(::SignalException)
115
+ display_signal_notice(cause)
116
+ else
117
+ display_error_notice(error)
118
+ end
119
+ exit_code_for(cause)
120
+ end
121
+
122
+ ##
123
+ # Implementation of the logger factory. As dictated by the logger factory
124
+ # specification in {Toys::CLI}, this must take a {Toys::ToolDefinition}
125
+ # as an argument, and return a `Logger`.
126
+ #
127
+ # The base implementation returns a logger that writes to the UI's
128
+ # terminal, using {#logger_formatter_impl} as the formatter. It sets the
129
+ # level to `Logger::WARN` by default. Either this method or the helper
130
+ # methods can be overridden to change this behavior.
131
+ #
132
+ # @param _tool {Toys::ToolDefinition} The tool definition of the tool to
133
+ # be executed
134
+ # @return [Logger]
135
+ #
136
+ def logger_factory_impl(_tool)
137
+ logger = ::Logger.new(@terminal)
138
+ logger.formatter = method(:logger_formatter_impl).to_proc
139
+ logger.level = ::Logger::WARN
140
+ logger
141
+ end
142
+
143
+ ##
144
+ # Returns an exit code appropriate for the given exception. Currently,
145
+ # the logic interprets signals (returning the convention of 128 + signo),
146
+ # usage errors (returning the conventional value of 2), and tool not
147
+ # runnable errors (returning the conventional value of 126), and defaults
148
+ # to 1 for all other error types.
149
+ #
150
+ # This method is used by {#error_handler_impl} and can be overridden to
151
+ # change its behavior.
152
+ #
153
+ # @param error [Exception] The exception raised. This method expects the
154
+ # original exception, rather than a ContextualError.
155
+ # @return [Integer] The appropriate exit code
156
+ #
157
+ def exit_code_for(error)
158
+ case error
159
+ when ArgParsingError
160
+ 2
161
+ when NotRunnableError
162
+ 126
163
+ when ::SignalException
164
+ error.signo + 128
165
+ else
166
+ 1
167
+ end
168
+ end
169
+
170
+ ##
171
+ # Displays a default output for a signal received.
172
+ #
173
+ # This method is used by {#error_handler_impl} and can be overridden to
174
+ # change its behavior.
175
+ #
176
+ # @param error [SignalException]
177
+ #
178
+ def display_signal_notice(error)
179
+ @terminal.puts
180
+ if error.is_a?(::Interrupt)
181
+ @terminal.puts("INTERRUPTED", :bold)
182
+ else
183
+ @terminal.puts("SIGNAL RECEIVED: #{error.signm || error.signo}", :bold)
184
+ end
185
+ end
186
+
187
+ ##
188
+ # Displays a default output for an error. Displays the error, the
189
+ # backtrace, and contextual information regarding what tool was run and
190
+ # where in its code the error occurred.
191
+ #
192
+ # This method is used by {#error_handler_impl} and can be overridden to
193
+ # change its behavior.
194
+ #
195
+ # @param error [Toys::ContextualError]
196
+ #
197
+ def display_error_notice(error)
198
+ @terminal.puts
199
+ @terminal.puts(cause_string(error.cause))
200
+ @terminal.puts(context_string(error), :bold)
201
+ end
202
+
203
+ ##
204
+ # Implementation of the formatter used by loggers created by this UI's
205
+ # logger factory. This interface is defined by the standard `Logger`
206
+ # class.
207
+ #
208
+ # This method can be overridden to change the behavior of loggers created
209
+ # by this UI.
210
+ #
211
+ # @param severity [String]
212
+ # @param time [Time]
213
+ # @param _progname [String]
214
+ # @param msg [Object]
215
+ # @return [String]
216
+ #
217
+ def logger_formatter_impl(severity, time, _progname, msg)
218
+ msg_str =
219
+ case msg
220
+ when ::String
221
+ msg
222
+ when ::Exception
223
+ "#{msg.message} (#{msg.class})\n" << (msg.backtrace || []).join("\n")
224
+ else
225
+ msg.inspect
226
+ end
227
+ timestr = time.strftime("%Y-%m-%d %H:%M:%S")
228
+ header = format("[%<time>s %<sev>5s]", time: timestr, sev: severity)
229
+ styles = log_header_severity_styles[severity]
230
+ header = @terminal.apply_styles(header, *styles) if styles
231
+ "#{header} #{msg_str}\n"
232
+ end
233
+
234
+ private
235
+
236
+ def cause_string(cause)
237
+ lines = ["#{cause.class}: #{cause.message}"]
238
+ cause.backtrace.each_with_index.reverse_each do |bt, i|
239
+ lines << " #{(i + 1).to_s.rjust(3)}: #{bt}"
240
+ end
241
+ lines.join("\n")
242
+ end
243
+
244
+ def context_string(error)
245
+ lines = [
246
+ error.banner || "Unexpected error!",
247
+ " #{error.cause.class}: #{error.cause.message}",
248
+ ]
249
+ if error.config_path
250
+ lines << " in config file: #{error.config_path}:#{error.config_line}"
251
+ end
252
+ if error.tool_name
253
+ lines << " while executing tool: #{error.tool_name.join(' ').inspect}"
254
+ if error.tool_args
255
+ lines << " with arguments: #{error.tool_args.inspect}"
256
+ end
257
+ end
258
+ lines.join("\n")
259
+ end
260
+ end
261
+ end
262
+ end
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "stringio"
4
- require "monitor"
5
-
6
3
  begin
7
4
  require "io/console"
8
5
  rescue ::LoadError
@@ -116,6 +113,7 @@ module Toys
116
113
  # setting is inferred from whether the output has a tty.
117
114
  #
118
115
  def initialize(input: $stdin, output: $stdout, styled: nil)
116
+ require "monitor"
119
117
  @input = input
120
118
  @output = output
121
119
  @styled =
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "toys/compat"
4
-
5
3
  module Toys
6
4
  module Utils
7
5
  ##
@@ -50,6 +48,8 @@ module Toys
50
48
  # you can omit this argument, as it will default to `::ENV`.
51
49
  #
52
50
  def initialize(env: ::ENV)
51
+ require "fileutils"
52
+ require "toys/compat"
53
53
  @env = env
54
54
  end
55
55
 
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. Most are defined in {Toys::DSL::Tool}.
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
- # `require "toys/utils/exec"`.
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.14.7
4
+ version: 0.15.1
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-07-19 00:00:00.000000000 Z
11
+ date: 2023-10-15 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.14.7/file.CHANGELOG.html
82
+ changelog_uri: https://dazuma.github.io/toys/gems/toys-core/v0.15.1/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.14.7
85
+ documentation_uri: https://dazuma.github.io/toys/gems/toys-core/v0.15.1
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.1.6
101
+ rubygems_version: 3.4.10
101
102
  signing_key:
102
103
  specification_version: 4
103
104
  summary: Framework for creating command line executables