toys-core 0.14.7 → 0.15.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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