console 1.29.3 → 1.30.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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/console/capture.rb +27 -2
- data/lib/console/clock.rb +7 -2
- data/lib/console/compatible/logger.rb +31 -2
- data/lib/console/config.rb +93 -0
- data/lib/console/event/failure.rb +33 -4
- data/lib/console/event/generic.rb +15 -1
- data/lib/console/event/spawn.rb +39 -7
- data/lib/console/event.rb +7 -1
- data/lib/console/filter.rb +63 -3
- data/lib/console/format/safe.rb +36 -8
- data/lib/console/format.rb +4 -1
- data/lib/console/interface.rb +17 -9
- data/lib/console/logger.rb +23 -38
- data/lib/console/output/default.rb +12 -6
- data/lib/console/output/failure.rb +8 -1
- data/lib/console/output/null.rb +5 -2
- data/lib/console/output/sensitive.rb +42 -1
- data/lib/console/output/serialized.rb +25 -4
- data/lib/console/output/split.rb +12 -1
- data/lib/console/output/terminal.rb +67 -15
- data/lib/console/output/wrapper.rb +12 -1
- data/lib/console/output.rb +12 -1
- data/lib/console/progress.rb +56 -9
- data/lib/console/resolver.rb +19 -2
- data/lib/console/terminal/formatter/failure.rb +18 -6
- data/lib/console/terminal/formatter/progress.rb +16 -3
- data/lib/console/terminal/formatter/spawn.rb +16 -5
- data/lib/console/terminal/formatter.rb +16 -0
- data/lib/console/terminal/text.rb +61 -20
- data/lib/console/terminal/xterm.rb +17 -2
- data/lib/console/terminal.rb +7 -9
- data/lib/console/version.rb +2 -2
- data/lib/console/warn.rb +2 -2
- data/lib/console.rb +2 -1
- data/license.md +1 -0
- data/readme.md +7 -1
- data/releases.md +24 -1
- data.tar.gz.sig +4 -3
- metadata +5 -2
- metadata.gz.sig +0 -0
data/lib/console/filter.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
4
|
+
# Copyright, 2019-2025, by Samuel Williams.
|
5
5
|
# Copyright, 2019, by Bryan Powell.
|
6
6
|
# Copyright, 2020, by Michael Adams.
|
7
7
|
# Copyright, 2021, by Robert Schulze.
|
@@ -9,18 +9,28 @@
|
|
9
9
|
module Console
|
10
10
|
UNKNOWN = :unknown
|
11
11
|
|
12
|
+
# A log filter which can be used to filter log messages based on severity, subject, and other criteria.
|
12
13
|
class Filter
|
13
14
|
if Object.const_defined?(:Ractor) and RUBY_VERSION >= "3.1"
|
15
|
+
# Define a method which can be shared between ractors.
|
14
16
|
def self.define_immutable_method(name, &block)
|
15
17
|
block = Ractor.make_shareable(block)
|
16
18
|
self.define_method(name, &block)
|
17
19
|
end
|
18
20
|
else
|
21
|
+
# Define a method.
|
19
22
|
def self.define_immutable_method(name, &block)
|
20
23
|
define_method(name, &block)
|
21
24
|
end
|
22
25
|
end
|
23
26
|
|
27
|
+
# Create a new log filter with specific log levels.
|
28
|
+
#
|
29
|
+
# ```ruby
|
30
|
+
# class MyLogger < Console::Filter[debug: 0, okay: 1, bad: 2, terrible: 3]
|
31
|
+
# ```
|
32
|
+
#
|
33
|
+
# @parameter levels [Hash(Symbol, Integer)] A hash of log levels.
|
24
34
|
def self.[] **levels
|
25
35
|
klass = Class.new(self)
|
26
36
|
minimum_level, maximum_level = levels.values.minmax
|
@@ -54,6 +64,12 @@ module Console
|
|
54
64
|
return klass
|
55
65
|
end
|
56
66
|
|
67
|
+
# Create a new log filter.
|
68
|
+
#
|
69
|
+
# @parameter output [Console::Output] The output destination.
|
70
|
+
# @parameter verbose [Boolean] Enable verbose output.
|
71
|
+
# @parameter level [Integer] The log level.
|
72
|
+
# @parameter options [Hash] Additional options.
|
57
73
|
def initialize(output, verbose: true, level: self.class::DEFAULT_LEVEL, **options)
|
58
74
|
@output = output
|
59
75
|
@verbose = verbose
|
@@ -64,6 +80,12 @@ module Console
|
|
64
80
|
@options = options
|
65
81
|
end
|
66
82
|
|
83
|
+
# Create a new log filter with the given options, from an existing log filter.
|
84
|
+
#
|
85
|
+
# @parameter level [Integer] The log level.
|
86
|
+
# @parameter verbose [Boolean] Enable verbose output.
|
87
|
+
# @parameter options [Hash] Additional options.
|
88
|
+
# @returns [Console::Filter] The new log filter.
|
67
89
|
def with(level: @level, verbose: @verbose, **options)
|
68
90
|
dup.tap do |logger|
|
69
91
|
logger.level = level
|
@@ -72,14 +94,24 @@ module Console
|
|
72
94
|
end
|
73
95
|
end
|
74
96
|
|
97
|
+
# @attribute [Console::Output] The output destination.
|
75
98
|
attr_accessor :output
|
99
|
+
|
100
|
+
# @attribute [Boolean] Whether to enable verbose output.
|
76
101
|
attr :verbose
|
102
|
+
|
103
|
+
# @attribute [Integer] The current log level.
|
77
104
|
attr :level
|
78
105
|
|
106
|
+
# @attribute [Hash(Module, Integer)] The log levels for specific subject (classes).
|
79
107
|
attr :subjects
|
80
108
|
|
109
|
+
# @attribute [Hash] Additional options.
|
81
110
|
attr_accessor :options
|
82
111
|
|
112
|
+
# Set the log level.
|
113
|
+
#
|
114
|
+
# @parameter level [Integer | Symbol] The log level.
|
83
115
|
def level= level
|
84
116
|
if level.is_a? Symbol
|
85
117
|
@level = self.class::LEVELS[level]
|
@@ -88,19 +120,30 @@ module Console
|
|
88
120
|
end
|
89
121
|
end
|
90
122
|
|
123
|
+
# Set verbose output (enable by default with no arguments).
|
124
|
+
#
|
125
|
+
# @parameter value [Boolean] Enable or disable verbose output.
|
91
126
|
def verbose!(value = true)
|
92
127
|
@verbose = value
|
93
128
|
@output.verbose!(value)
|
94
129
|
end
|
95
130
|
|
131
|
+
# Disable all logging.
|
96
132
|
def off!
|
97
133
|
@level = self.class::MAXIMUM_LEVEL + 1
|
98
134
|
end
|
99
135
|
|
136
|
+
# Enable all logging.
|
100
137
|
def all!
|
101
138
|
@level = self.class::MINIMUM_LEVEL - 1
|
102
139
|
end
|
103
140
|
|
141
|
+
# Filter log messages based on the subject and log level.
|
142
|
+
#
|
143
|
+
# You must provide the subject's class, not an instance of the class.
|
144
|
+
#
|
145
|
+
# @parameter subject [Module] The subject to filter.
|
146
|
+
# @parameter level [Integer] The log level.
|
104
147
|
def filter(subject, level)
|
105
148
|
unless subject.is_a?(Module)
|
106
149
|
raise ArgumentError, "Expected a class, got #{subject.inspect}"
|
@@ -109,8 +152,13 @@ module Console
|
|
109
152
|
@subjects[subject] = level
|
110
153
|
end
|
111
154
|
|
155
|
+
# Whether logging is enabled for the given subject and log level.
|
156
|
+
#
|
112
157
|
# You can enable and disable logging for classes. This function checks if logging for a given subject is enabled.
|
113
|
-
#
|
158
|
+
#
|
159
|
+
# @parameter subject [Module] The subject to check.
|
160
|
+
# @parameter level [Integer] The log level.
|
161
|
+
# @returns [Boolean] Whether logging is enabled.
|
114
162
|
def enabled?(subject, level = self.class::MINIMUM_LEVEL)
|
115
163
|
subject = subject.class unless subject.is_a?(Module)
|
116
164
|
|
@@ -124,19 +172,24 @@ module Console
|
|
124
172
|
end
|
125
173
|
|
126
174
|
# Enable specific log level for the given class.
|
175
|
+
#
|
127
176
|
# @parameter name [Module] The class to enable.
|
128
177
|
def enable(subject, level = self.class::MINIMUM_LEVEL)
|
129
178
|
# Set the filter level of logging for a given subject which passes all log messages:
|
130
179
|
filter(subject, level)
|
131
180
|
end
|
132
181
|
|
182
|
+
# Disable logging for the given class.
|
183
|
+
#
|
184
|
+
# @parameter name [Module] The class to disable.
|
133
185
|
def disable(subject)
|
134
186
|
# Set the filter level of the logging for a given subject which filters all log messages:
|
135
187
|
filter(subject, self.class::MAXIMUM_LEVEL + 1)
|
136
188
|
end
|
137
189
|
|
138
190
|
# Clear any specific filters for the given class.
|
139
|
-
#
|
191
|
+
#
|
192
|
+
# @parameter subject [Module] The class to disable.
|
140
193
|
def clear(subject)
|
141
194
|
unless subject.is_a?(Module)
|
142
195
|
raise ArgumentError, "Expected a class, got #{subject.inspect}"
|
@@ -145,6 +198,13 @@ module Console
|
|
145
198
|
@subjects.delete(subject)
|
146
199
|
end
|
147
200
|
|
201
|
+
# Log a message with the given severity.
|
202
|
+
#
|
203
|
+
# @parameter subject [Object] The subject of the log message.
|
204
|
+
# @parameter arguments [Array] The arguments to log.
|
205
|
+
# @parameter options [Hash] Additional options to pass to the output.
|
206
|
+
# @parameter block [Proc] A block passed to the output.
|
207
|
+
# @returns [Nil] Always returns nil.
|
148
208
|
def call(subject, *arguments, **options, &block)
|
149
209
|
severity = options[:severity] || UNKNOWN
|
150
210
|
level = self.class::LEVELS[severity]
|
data/lib/console/format/safe.rb
CHANGED
@@ -1,21 +1,32 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2023-
|
4
|
+
# Copyright, 2023-2025, by Samuel Williams.
|
5
5
|
|
6
6
|
require "json"
|
7
7
|
|
8
8
|
module Console
|
9
|
+
# @namespace
|
9
10
|
module Format
|
10
|
-
#
|
11
|
-
#
|
11
|
+
# A safe format for converting objects to strings.
|
12
|
+
#
|
13
|
+
# Handles issues like circular references and encoding errors.
|
12
14
|
class Safe
|
15
|
+
# Create a new safe format.
|
16
|
+
#
|
17
|
+
# @parameter format [JSON] The format to use for serialization.
|
18
|
+
# @parameter limit [Integer] The maximum depth to recurse into objects.
|
19
|
+
# @parameter encoding [Encoding] The encoding to use for strings.
|
13
20
|
def initialize(format: ::JSON, limit: 8, encoding: ::Encoding::UTF_8)
|
14
21
|
@format = format
|
15
22
|
@limit = limit
|
16
23
|
@encoding = encoding
|
17
24
|
end
|
18
25
|
|
26
|
+
# Dump the given object to a string.
|
27
|
+
#
|
28
|
+
# @parameter object [Object] The object to dump.
|
29
|
+
# @returns [String] The dumped object.
|
19
30
|
def dump(object)
|
20
31
|
@format.dump(object, @limit)
|
21
32
|
rescue SystemStackError, StandardError => error
|
@@ -24,6 +35,10 @@ module Console
|
|
24
35
|
|
25
36
|
private
|
26
37
|
|
38
|
+
# Filter the backtrace to remove duplicate frames and reduce verbosity.
|
39
|
+
#
|
40
|
+
# @parameter error [Exception] The exception to filter.
|
41
|
+
# @returns [Array(String)] The filtered backtrace.
|
27
42
|
def filter_backtrace(error)
|
28
43
|
frames = error.backtrace
|
29
44
|
filtered = {}
|
@@ -61,6 +76,13 @@ module Console
|
|
61
76
|
return frames
|
62
77
|
end
|
63
78
|
|
79
|
+
# Dump the given object to a string, replacing it with a safe representation if there is an error.
|
80
|
+
#
|
81
|
+
# This is a slow path so we try to avoid it.
|
82
|
+
#
|
83
|
+
# @parameter object [Object] The object to dump.
|
84
|
+
# @parameter error [Exception] The error that occurred while dumping the object.
|
85
|
+
# @returns [Hash] The dumped (truncated) object including error details.
|
64
86
|
def safe_dump(object, error)
|
65
87
|
object = safe_dump_recurse(object)
|
66
88
|
|
@@ -74,6 +96,10 @@ module Console
|
|
74
96
|
return object
|
75
97
|
end
|
76
98
|
|
99
|
+
# Replace the given object with a safe truncated representation.
|
100
|
+
#
|
101
|
+
# @parameter object [Object] The object to replace.
|
102
|
+
# @returns [String] The replacement string.
|
77
103
|
def replacement_for(object)
|
78
104
|
case object
|
79
105
|
when Array
|
@@ -85,15 +111,17 @@ module Console
|
|
85
111
|
end
|
86
112
|
end
|
87
113
|
|
114
|
+
# Create a new hash with identity comparison.
|
88
115
|
def default_objects
|
89
116
|
Hash.new.compare_by_identity
|
90
117
|
end
|
91
118
|
|
92
|
-
# This will recursively generate a safe version of the object.
|
93
|
-
#
|
94
|
-
#
|
95
|
-
#
|
96
|
-
#
|
119
|
+
# This will recursively generate a safe version of the object. Nested hashes and arrays will be transformed recursively. Strings will be encoded with the given encoding. Primitive values will be returned as-is. Other values will be converted using `as_json` if available, otherwise `to_s`.
|
120
|
+
#
|
121
|
+
# @parameter object [Object] The object to dump.
|
122
|
+
# @parameter limit [Integer] The maximum depth to recurse into objects.
|
123
|
+
# @parameter objects [Hash] The objects that have already been visited.
|
124
|
+
# @returns [Object] The dumped object as a primitive representation.
|
97
125
|
def safe_dump_recurse(object, limit = @limit, objects = default_objects)
|
98
126
|
if limit <= 0 || objects[object]
|
99
127
|
return replacement_for(object)
|
data/lib/console/format.rb
CHANGED
@@ -1,12 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2023-
|
4
|
+
# Copyright, 2023-2025, by Samuel Williams.
|
5
5
|
|
6
6
|
require_relative "format/safe"
|
7
7
|
|
8
8
|
module Console
|
9
9
|
module Format
|
10
|
+
# A safe format for converting objects to strings.
|
11
|
+
#
|
12
|
+
# @returns [Console::Format::Safe]
|
10
13
|
def self.default
|
11
14
|
Safe.new(format: ::JSON)
|
12
15
|
end
|
data/lib/console/interface.rb
CHANGED
@@ -3,51 +3,59 @@
|
|
3
3
|
# Released under the MIT License.
|
4
4
|
# Copyright, 2024-2025, by Samuel Williams.
|
5
5
|
|
6
|
-
|
6
|
+
require "fiber/local"
|
7
|
+
require_relative "config"
|
7
8
|
|
8
9
|
module Console
|
9
10
|
# The public logger interface.
|
10
11
|
module Interface
|
12
|
+
extend Fiber::Local
|
13
|
+
|
14
|
+
# Create a new (thread local) logger instance.
|
15
|
+
def self.local
|
16
|
+
Config::DEFAULT.make_logger
|
17
|
+
end
|
18
|
+
|
11
19
|
# Get the current logger instance.
|
12
20
|
def logger
|
13
|
-
|
21
|
+
Interface.instance
|
14
22
|
end
|
15
23
|
|
16
24
|
# Set the current logger instance.
|
17
25
|
#
|
18
26
|
# The current logger instance is assigned per-fiber.
|
19
27
|
def logger= instance
|
20
|
-
|
28
|
+
Interface.instance= instance
|
21
29
|
end
|
22
30
|
|
23
31
|
# Emit a debug log message.
|
24
32
|
def debug(...)
|
25
|
-
|
33
|
+
Interface.instance.debug(...)
|
26
34
|
end
|
27
35
|
|
28
36
|
# Emit an informational log message.
|
29
37
|
def info(...)
|
30
|
-
|
38
|
+
Interface.instance.info(...)
|
31
39
|
end
|
32
40
|
|
33
41
|
# Emit a warning log message.
|
34
42
|
def warn(...)
|
35
|
-
|
43
|
+
Interface.instance.warn(...)
|
36
44
|
end
|
37
45
|
|
38
46
|
# Emit an error log message.
|
39
47
|
def error(...)
|
40
|
-
|
48
|
+
Interface.instance.error(...)
|
41
49
|
end
|
42
50
|
|
43
51
|
# Emit a fatal log message.
|
44
52
|
def fatal(...)
|
45
|
-
|
53
|
+
Interface.instance.fatal(...)
|
46
54
|
end
|
47
55
|
|
48
56
|
# Emit a log message with arbitrary arguments and options.
|
49
57
|
def call(...)
|
50
|
-
|
58
|
+
Interface.instance.call(...)
|
51
59
|
end
|
52
60
|
end
|
53
61
|
end
|
data/lib/console/logger.rb
CHANGED
@@ -1,68 +1,47 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
4
|
+
# Copyright, 2019-2025, by Samuel Williams.
|
5
5
|
# Copyright, 2021, by Bryan Powell.
|
6
6
|
# Copyright, 2021, by Robert Schulze.
|
7
|
+
# Copyright, 2025, by Shigeru Nakajima.
|
7
8
|
|
8
9
|
require_relative "output"
|
9
10
|
require_relative "output/failure"
|
10
11
|
|
11
|
-
require_relative "filter"
|
12
|
-
require_relative "event"
|
13
|
-
require_relative "resolver"
|
14
12
|
require_relative "progress"
|
15
13
|
|
16
|
-
require "fiber/local"
|
17
|
-
|
18
14
|
module Console
|
15
|
+
# The standard logger interface with support for log levels and verbosity.
|
16
|
+
#
|
17
|
+
# The log levels are: `debug`, `info`, `warn`, `error`, and `fatal`.
|
19
18
|
class Logger < Filter[debug: 0, info: 1, warn: 2, error: 3, fatal: 4]
|
20
|
-
extend Fiber::Local
|
21
|
-
|
22
19
|
# Set the default log level based on `$DEBUG` and `$VERBOSE`.
|
23
20
|
# You can also specify CONSOLE_LEVEL=debug or CONSOLE_LEVEL=info in environment.
|
24
21
|
# https://mislav.net/2011/06/ruby-verbose-mode/ has more details about how it all fits together.
|
25
|
-
|
22
|
+
#
|
23
|
+
# @parameter env [Hash] The environment to read the log level from.
|
24
|
+
# @parameter verbose [Boolean] The verbose flag.
|
25
|
+
# @parameter debug [Boolean] The debug flag.
|
26
|
+
# @returns [Integer] The default log level.
|
27
|
+
def self.default_log_level(env = ENV, verbose: $VERBOSE, debug: $DEBUG)
|
26
28
|
if level = env["CONSOLE_LEVEL"]
|
27
29
|
LEVELS[level.to_sym] || level.to_i
|
28
|
-
elsif
|
30
|
+
elsif debug
|
29
31
|
DEBUG
|
30
|
-
elsif
|
32
|
+
elsif verbose.nil?
|
31
33
|
WARN
|
32
34
|
else
|
33
35
|
INFO
|
34
36
|
end
|
35
37
|
end
|
36
38
|
|
37
|
-
# Controls verbose output using `$VERBOSE`.
|
38
|
-
def self.verbose?(env = ENV)
|
39
|
-
!$VERBOSE.nil? || env["CONSOLE_VERBOSE"]
|
40
|
-
end
|
41
|
-
|
42
|
-
def self.default_logger(output = $stderr, env = ENV, **options)
|
43
|
-
if options[:verbose].nil?
|
44
|
-
options[:verbose] = self.verbose?(env)
|
45
|
-
end
|
46
|
-
|
47
|
-
if options[:level].nil?
|
48
|
-
options[:level] = self.default_log_level(env)
|
49
|
-
end
|
50
|
-
|
51
|
-
output = Output.new(output, env, **options)
|
52
|
-
|
53
|
-
logger = self.new(output, **options)
|
54
|
-
|
55
|
-
Resolver.default_resolver(logger)
|
56
|
-
|
57
|
-
return logger
|
58
|
-
end
|
59
|
-
|
60
|
-
def self.local
|
61
|
-
self.default_logger
|
62
|
-
end
|
63
|
-
|
64
39
|
DEFAULT_LEVEL = 1
|
65
40
|
|
41
|
+
# Create a new logger.
|
42
|
+
#
|
43
|
+
# @parameter output [Console::Output] The output destination.
|
44
|
+
# @parameter options [Hash] Additional options.
|
66
45
|
def initialize(output, **options)
|
67
46
|
# This is the expected default behaviour, but it may be nice to have a way to override it.
|
68
47
|
output = Output::Failure.new(output, **options)
|
@@ -70,6 +49,12 @@ module Console
|
|
70
49
|
super(output, **options)
|
71
50
|
end
|
72
51
|
|
52
|
+
# Create a progress indicator for the given subject.
|
53
|
+
#
|
54
|
+
# @parameter subject [String] The subject of the progress indicator.
|
55
|
+
# @parameter total [Integer] The total number of items to process.
|
56
|
+
# @parameter options [Hash] Additional options passed to {Progress}.
|
57
|
+
# @returns [Progress] The progress indicator.
|
73
58
|
def progress(subject, total, **options)
|
74
59
|
options[:severity] ||= :info
|
75
60
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2021-
|
4
|
+
# Copyright, 2021-2025, by Samuel Williams.
|
5
5
|
|
6
6
|
require_relative "terminal"
|
7
7
|
require_relative "serialized"
|
@@ -9,14 +9,20 @@ require_relative "failure"
|
|
9
9
|
|
10
10
|
module Console
|
11
11
|
module Output
|
12
|
+
# Default output format selection.
|
12
13
|
module Default
|
13
|
-
|
14
|
-
|
14
|
+
# Create a new output format based on the given stream.
|
15
|
+
#
|
16
|
+
# @parameter io [IO] The output stream.
|
17
|
+
# @parameter options [Hash] Additional options to customize the output.
|
18
|
+
# @returns [Console::Output::Terminal | Console::Output::Serialized] The output instance, depending on whether the `io` is a terminal or not.
|
19
|
+
def self.new(stream, **options)
|
20
|
+
stream ||= $stderr
|
15
21
|
|
16
|
-
if
|
17
|
-
output = Terminal.new(
|
22
|
+
if stream.tty?
|
23
|
+
output = Terminal.new(stream, **options)
|
18
24
|
else
|
19
|
-
output = Serialized.new(
|
25
|
+
output = Serialized.new(stream, **options)
|
20
26
|
end
|
21
27
|
|
22
28
|
return output
|
@@ -10,11 +10,18 @@ module Console
|
|
10
10
|
module Output
|
11
11
|
# A wrapper for outputting failure messages, which can include exceptions.
|
12
12
|
class Failure < Wrapper
|
13
|
+
# Create a new failure output wrapper.
|
13
14
|
def initialize(output, **options)
|
14
15
|
super(output, **options)
|
15
16
|
end
|
16
17
|
|
17
18
|
# The exception must be either the last argument or passed as an option.
|
19
|
+
#
|
20
|
+
# @parameter subject [String] The subject of the message.
|
21
|
+
# @parameter arguments [Array] The arguments to output.
|
22
|
+
# @parameter exception [Exception] The exception to output.
|
23
|
+
# @parameter options [Hash] Additional options to pass to the output.
|
24
|
+
# @parameter block [Proc] An optional block to pass to the output.
|
18
25
|
def call(subject = nil, *arguments, exception: nil, **options, &block)
|
19
26
|
if exception.nil?
|
20
27
|
last = arguments.last
|
@@ -28,7 +35,7 @@ module Console
|
|
28
35
|
options[:exception] = exception
|
29
36
|
end
|
30
37
|
|
31
|
-
super(subject, *arguments, **options)
|
38
|
+
super(subject, *arguments, **options, &block)
|
32
39
|
end
|
33
40
|
end
|
34
41
|
end
|
data/lib/console/output/null.rb
CHANGED
@@ -1,20 +1,23 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2023-
|
4
|
+
# Copyright, 2023-2025, by Samuel Williams.
|
5
5
|
|
6
6
|
module Console
|
7
7
|
module Output
|
8
|
+
# A null output that does nothing.
|
8
9
|
class Null
|
10
|
+
# Create a new null output.
|
9
11
|
def initialize(...)
|
10
12
|
end
|
11
13
|
|
14
|
+
# The last output is always self.
|
12
15
|
def last_output
|
13
16
|
self
|
14
17
|
end
|
15
18
|
|
19
|
+
# Do nothing.
|
16
20
|
def call(...)
|
17
|
-
# Do nothing.
|
18
21
|
end
|
19
22
|
end
|
20
23
|
end
|
@@ -1,13 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2021-
|
4
|
+
# Copyright, 2021-2025, by Samuel Williams.
|
5
5
|
|
6
6
|
require_relative "wrapper"
|
7
7
|
|
8
8
|
module Console
|
9
9
|
module Output
|
10
|
+
# Redact sensitive information from output.
|
10
11
|
class Sensitive < Wrapper
|
12
|
+
# Default redaction pattern.
|
11
13
|
REDACT = /
|
12
14
|
phone
|
13
15
|
| email
|
@@ -34,28 +36,52 @@ module Console
|
|
34
36
|
| password
|
35
37
|
/xi
|
36
38
|
|
39
|
+
# Create a new sensitive output wrapper.
|
40
|
+
#
|
41
|
+
# @parameter output [Console::Output] The output to wrap.
|
42
|
+
# @parameter redact [Regexp] The pattern to redact.
|
43
|
+
# @parameter options [Hash] Additional options to pass to the output.
|
37
44
|
def initialize(output, redact: REDACT, **options)
|
38
45
|
super(output, **options)
|
39
46
|
|
40
47
|
@redact = redact
|
41
48
|
end
|
42
49
|
|
50
|
+
# Check if the given text should be redacted.
|
51
|
+
#
|
52
|
+
# @parameter text [String] The text to check.
|
53
|
+
# @returns [Boolean] Whether the text should be redacted.
|
43
54
|
def redact?(text)
|
44
55
|
text.match?(@redact)
|
45
56
|
end
|
46
57
|
|
58
|
+
# Redact sensitive information from a hash.
|
59
|
+
#
|
60
|
+
# @parameter arguments [Hash] The hash to redact.
|
61
|
+
# @parameter filter [Proc] An optional filter to apply to redacted text.
|
62
|
+
# @returns [Hash] The redacted hash.
|
47
63
|
def redact_hash(arguments, filter)
|
48
64
|
arguments.transform_values do |value|
|
49
65
|
redact(value, filter)
|
50
66
|
end
|
51
67
|
end
|
52
68
|
|
69
|
+
# Redact sensitive information from an array.
|
70
|
+
#
|
71
|
+
# @parameter array [Array] The array to redact.
|
72
|
+
# @parameter filter [Proc] An optional filter to apply to redacted text.
|
73
|
+
# @returns [Array] The redacted array.
|
53
74
|
def redact_array(array, filter)
|
54
75
|
array.map do |value|
|
55
76
|
redact(value, filter)
|
56
77
|
end
|
57
78
|
end
|
58
79
|
|
80
|
+
# Redact sensitive information from the given argument.
|
81
|
+
#
|
82
|
+
# @parameter argument [String | Array | Hash] The argument to redact.
|
83
|
+
# @parameter filter [Proc] An optional filter to apply to redacted text.
|
84
|
+
# @returns [String | Array | Hash] The redacted argument.
|
59
85
|
def redact(argument, filter)
|
60
86
|
case argument
|
61
87
|
when String
|
@@ -75,17 +101,32 @@ module Console
|
|
75
101
|
end
|
76
102
|
end
|
77
103
|
|
104
|
+
# A simple filter for redacting sensitive information.
|
78
105
|
class Filter
|
106
|
+
# Create a new filter.
|
107
|
+
#
|
108
|
+
# @parameter substitutions [Hash] The substitutions to apply.
|
79
109
|
def initialize(substitutions)
|
80
110
|
@substitutions = substitutions
|
81
111
|
@pattern = Regexp.union(substitutions.keys)
|
82
112
|
end
|
83
113
|
|
114
|
+
# Apply the filter to the given text. This will replace all occurrences of the pattern with the corresponding substitution.
|
115
|
+
#
|
116
|
+
# @parameter text [String] The text to filter.
|
117
|
+
# @returns [String] The filtered text.
|
84
118
|
def call(text)
|
85
119
|
text.gsub(@pattern, @substitutions)
|
86
120
|
end
|
87
121
|
end
|
88
122
|
|
123
|
+
# Write a message to the output, filtering sensitive information if necessary.
|
124
|
+
#
|
125
|
+
# @parameter subject [String] The subject of the message.
|
126
|
+
# @parameter arguments [Array] The arguments to output.
|
127
|
+
# @parameter sensitive [Boolean | Filter | Hash] Whether to filter sensitive information.
|
128
|
+
# @parameter options [Hash] Additional options to pass to the output.
|
129
|
+
# @parameter block [Proc] An optional block to pass to the output.
|
89
130
|
def call(subject = nil, *arguments, sensitive: true, **options, &block)
|
90
131
|
if sensitive
|
91
132
|
if sensitive.respond_to?(:call)
|