console 1.24.0 → 1.26.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9a62f7c19673292c5cd3c0d8b9d93350ca4b36ac0a5c0a0466deef6543b388ba
4
- data.tar.gz: f382353a7b052c056936fdd2c95ad0bd20cfb8619db2f6d2af1ce7e9e0cd7967
3
+ metadata.gz: ea53f121a472d6011335a91ac4a96f4f3630c4524b1158166be906a51e4dd186
4
+ data.tar.gz: 20cdb45a16606315c9a0e054fb9e3460698e6fcccd190bd7668902f179c7b5fa
5
5
  SHA512:
6
- metadata.gz: 6638ede52de5f0954a38adf71e910997eb643d2e39ef59645f320ce16ebefd4f804cb062e47e03472ac742fb46591c310217da72eb63eb87fe91d93090f8ee5f
7
- data.tar.gz: 15f4929956b45e0c445e95774dfa3ac1c4bb485401e1b0003835c9ae5435a25ebda70b69ad475cf0e6b48131b0cc41258e3c5bb8c70121daaa9fe482c12b5404
6
+ metadata.gz: 42748b35f41a887222716d8bd0b9e55f92a5506f31fa7cb0a3e5ac5155341c55d6ca2849745b77d568b7b11e3d79faf0193377b9547fce98c5c16ca1043f9222
7
+ data.tar.gz: de9535e3fb41fcce945512f9d482329cfc695888c43da753d1f7c392e074e4b4b1e6584fbfd1c39b4851c25f00563b1e0ee3efbd5240294649ff4dc68519483d
checksums.yaml.gz.sig CHANGED
Binary file
@@ -1,9 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2023, by Samuel Williams.
4
+ # Copyright, 2023-2024, by Samuel Williams.
5
5
 
6
6
  module Console
7
+ # This namespace is reserved for logging adapters provided by other gems.
7
8
  module Adapter
8
9
  end
9
10
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2019-2023, by Samuel Williams.
4
+ # Copyright, 2019-2024, by Samuel Williams.
5
5
 
6
6
  require_relative 'filter'
7
7
 
@@ -9,27 +9,45 @@ module Console
9
9
  # A general sink which captures all events into a buffer.
10
10
  class Capture
11
11
  def initialize
12
- @buffer = []
12
+ @records = []
13
13
  @verbose = false
14
14
  end
15
15
 
16
- attr :buffer
16
+ attr :records
17
+
18
+ # @deprecated Use {#records} instead of {#buffer}.
19
+ alias buffer records
20
+
21
+ alias to_a records
22
+
17
23
  attr :verbose
18
24
 
19
- def last
20
- @buffer.last
25
+ def include?(pattern)
26
+ @records.any? do |record|
27
+ record[:subject].to_s&.match?(pattern) or record[:message].to_s&.match?(pattern)
28
+ end
21
29
  end
22
30
 
23
- def include?(pattern)
24
- JSON.dump(@buffer).include?(pattern)
31
+ def each(&block)
32
+ @records.each(&block)
33
+ end
34
+
35
+ include Enumerable
36
+
37
+ def first
38
+ @records.first
39
+ end
40
+
41
+ def last
42
+ @records.last
25
43
  end
26
44
 
27
45
  def clear
28
- @buffer.clear
46
+ @records.clear
29
47
  end
30
48
 
31
49
  def empty?
32
- @buffer.empty?
50
+ @records.empty?
33
51
  end
34
52
 
35
53
  def verbose!(value = true)
@@ -40,36 +58,42 @@ module Console
40
58
  @verbose
41
59
  end
42
60
 
43
- def call(subject = nil, *arguments, severity: UNKNOWN, **options, &block)
44
- message = {
61
+ def call(subject = nil, *arguments, severity: UNKNOWN, event: nil, **options, &block)
62
+ record = {
45
63
  time: ::Time.now.iso8601,
46
64
  severity: severity,
47
65
  **options,
48
66
  }
49
67
 
50
68
  if subject
51
- message[:subject] = subject
69
+ record[:subject] = subject
70
+ end
71
+
72
+ if event
73
+ record[:event] = event.to_hash
52
74
  end
53
75
 
54
76
  if arguments.any?
55
- message[:arguments] = arguments
77
+ record[:arguments] = arguments
56
78
  end
57
79
 
58
80
  if annotation = Fiber.current.annotation
59
- message[:annotation] = annotation
81
+ record[:annotation] = annotation
60
82
  end
61
83
 
62
84
  if block_given?
63
85
  if block.arity.zero?
64
- message[:message] = yield
86
+ record[:message] = yield
65
87
  else
66
88
  buffer = StringIO.new
67
89
  yield buffer
68
- message[:message] = buffer.string
90
+ record[:message] = buffer.string
69
91
  end
92
+ else
93
+ record[:message] = arguments.join(" ")
70
94
  end
71
95
 
72
- @buffer << message
96
+ @records << record
73
97
  end
74
98
  end
75
99
  end
@@ -1,12 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2022-2023, by Samuel Williams.
4
+ # Copyright, 2022-2024, by Samuel Williams.
5
5
 
6
6
  require 'logger'
7
7
 
8
8
  module Console
9
9
  module Compatible
10
+ # A compatible interface for {::Logger} which can be used with {Console}.
10
11
  class Logger < ::Logger
11
12
  class LogDevice
12
13
  def initialize(subject, output)
@@ -1,83 +1,74 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2019-2022, by Samuel Williams.
4
+ # Copyright, 2019-2024, by Samuel Williams.
5
5
  # Copyright, 2021, by Robert Schulze.
6
6
 
7
7
  require_relative 'generic'
8
8
 
9
9
  module Console
10
10
  module Event
11
+ # Represents a failure event.
12
+ #
13
+ # ```ruby
14
+ # Console::Event::Failure.for(exception).emit(self)
15
+ # ```
11
16
  class Failure < Generic
12
- def self.current_working_directory
17
+ def self.default_root
13
18
  Dir.getwd
14
19
  rescue # e.g. Errno::EMFILE
15
20
  nil
16
21
  end
17
22
 
18
23
  def self.for(exception)
19
- self.new(exception, self.current_working_directory)
24
+ self.new(exception, self.default_root)
20
25
  end
21
26
 
22
- def initialize(exception, root = nil)
27
+ def self.log(subject, exception, **options)
28
+ Console.error(subject, **self.for(exception).to_hash, **options)
29
+ end
30
+
31
+ def initialize(exception, root = Dir.getwd)
23
32
  @exception = exception
24
33
  @root = root
25
34
  end
26
35
 
27
- attr :exception
28
- attr :root
29
-
30
- def self.register(terminal)
31
- terminal[:exception_title] ||= terminal.style(:red, nil, :bold)
32
- terminal[:exception_detail] ||= terminal.style(:yellow)
33
- terminal[:exception_backtrace] ||= terminal.style(:red)
34
- terminal[:exception_backtrace_other] ||= terminal.style(:red, nil, :faint)
35
- terminal[:exception_message] ||= terminal.style(:default)
36
+ def to_hash
37
+ Hash.new.tap do |hash|
38
+ hash[:type] = :failure
39
+ hash[:root] = @root if @root
40
+ extract(@exception, hash)
41
+ end
36
42
  end
37
43
 
38
- def to_h
39
- {exception: @exception, root: @root}
44
+ def emit(*arguments, **options)
45
+ options[:severity] ||= :error
46
+
47
+ super
40
48
  end
41
49
 
42
- def format(output, terminal, verbose)
43
- format_exception(@exception, nil, output, terminal, verbose)
44
- end
50
+ private
45
51
 
46
- if Exception.method_defined?(:detailed_message)
47
- def detailed_message(exception)
48
- exception.detailed_message
49
- end
50
- else
51
- def detailed_message(exception)
52
- exception.message
53
- end
54
- end
55
-
56
- def format_exception(exception, prefix, output, terminal, verbose)
57
- lines = detailed_message(exception).lines.map(&:chomp)
58
-
59
- output.puts " #{prefix}#{terminal[:exception_title]}#{exception.class}#{terminal.reset}: #{lines.shift}"
60
-
61
- lines.each do |line|
62
- output.puts " #{terminal[:exception_detail]}#{line}#{terminal.reset}"
63
- end
52
+ def extract(exception, hash)
53
+ hash[:class] = exception.class.name
64
54
 
65
- root_pattern = /^#{@root}\// if @root
66
-
67
- exception.backtrace&.each_with_index do |line, index|
68
- path, offset, message = line.split(":", 3)
69
- style = :exception_backtrace
55
+ if exception.respond_to?(:detailed_message)
56
+ message = exception.detailed_message
70
57
 
71
- # Make the path a bit more readable
72
- if root_pattern and path.sub!(root_pattern, "").nil?
73
- style = :exception_backtrace_other
74
- end
58
+ # We want to remove the trailling exception class as we format it differently:
59
+ message.sub!(/\s*\(.*?\)$/, '')
75
60
 
76
- output.puts " #{index == 0 ? "→" : " "} #{terminal[style]}#{path}:#{offset}#{terminal[:exception_message]} #{message}#{terminal.reset}"
61
+ hash[:message] = message
62
+ else
63
+ hash[:message] = exception.message
77
64
  end
78
65
 
79
- if exception.cause
80
- format_exception(exception.cause, "Caused by ", output, terminal, verbose)
66
+ hash[:backtrace] = exception.backtrace
67
+
68
+ if cause = exception.cause
69
+ hash[:cause] = Hash.new.tap do |cause_hash|
70
+ extract(cause, cause_hash)
71
+ end
81
72
  end
82
73
  end
83
74
  end
@@ -1,22 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2019-2022, by Samuel Williams.
4
+ # Copyright, 2019-2024, by Samuel Williams.
5
5
 
6
6
  module Console
7
7
  module Event
8
8
  class Generic
9
- def self.register(terminal)
9
+ def as_json(...)
10
+ to_hash
10
11
  end
11
12
 
12
- def to_h
13
+ def to_json(...)
14
+ JSON.generate(as_json, ...)
13
15
  end
14
16
 
15
- def to_json(*arguments)
16
- JSON.generate([self.class, to_h], *arguments)
17
+ def to_s
18
+ to_json
17
19
  end
18
20
 
19
- def format(buffer, terminal)
21
+ def emit(*arguments, **options)
22
+ Console.call(*arguments, event: self, **options)
20
23
  end
21
24
  end
22
25
  end
@@ -1,12 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2019-2022, by Samuel Williams.
4
+ # Copyright, 2019-2024, by Samuel Williams.
5
5
 
6
6
  require_relative 'generic'
7
+ require_relative '../clock'
7
8
 
8
9
  module Console
9
10
  module Event
11
+ # Represents a spawn event.
12
+ #
13
+ # ```ruby
14
+ # Console.info(self, **Console::Event::Spawn.for("ls", "-l"))
15
+ #
16
+ # event = Console::Event::Spawn.for("ls", "-l")
17
+ # event.status = Process.wait
18
+ # ```
10
19
  class Spawn < Generic
11
20
  def self.for(*arguments, **options)
12
21
  # Extract out the command environment:
@@ -22,44 +31,43 @@ module Console
22
31
  @environment = environment
23
32
  @arguments = arguments
24
33
  @options = options
34
+
35
+ @start_time = Clock.now
36
+
37
+ @end_time = nil
38
+ @status = nil
25
39
  end
26
40
 
27
- attr :environment
28
- attr :arguments
29
- attr :options
30
-
31
- def chdir_string(options)
32
- if options and chdir = options[:chdir]
33
- " in #{chdir}"
41
+ def duration
42
+ if @end_time
43
+ @end_time - @start_time
34
44
  end
35
45
  end
36
46
 
37
- def self.register(terminal)
38
- terminal[:shell_command] ||= terminal.style(:blue, nil, :bold)
39
- end
40
-
41
- def to_h
47
+ def to_hash
42
48
  Hash.new.tap do |hash|
49
+ hash[:type] = :spawn
43
50
  hash[:environment] = @environment if @environment&.any?
44
51
  hash[:arguments] = @arguments if @arguments&.any?
45
52
  hash[:options] = @options if @options&.any?
53
+
54
+ hash[:status] = @status.to_i if @status
55
+
56
+ if duration = self.duration
57
+ hash[:duration] = duration
58
+ end
46
59
  end
47
60
  end
48
61
 
49
- def format(output, terminal, verbose)
50
- arguments = @arguments.flatten.collect(&:to_s)
51
-
52
- output.puts " #{terminal[:shell_command]}#{arguments.join(' ')}#{terminal.reset}#{chdir_string(options)}"
53
-
54
- if verbose and @environment
55
- @environment.each do |key, value|
56
- output.puts " export #{key}=#{value}"
57
- end
58
- end
62
+ def emit(*arguments, **options)
63
+ options[:severity] ||= :info
64
+ super
65
+ end
66
+
67
+ def status=(status)
68
+ @end_time = Time.now
69
+ @status = status
59
70
  end
60
71
  end
61
72
  end
62
-
63
- # Deprecated.
64
- Shell = Event::Spawn
65
73
  end
data/lib/console/event.rb CHANGED
@@ -1,8 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2019-2022, by Samuel Williams.
4
+ # Copyright, 2019-2024, by Samuel Williams.
5
5
 
6
6
  require_relative 'event/spawn'
7
7
  require_relative 'event/failure'
8
- require_relative 'event/progress'
@@ -1,15 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2019-2023, by Samuel Williams.
4
+ # Copyright, 2019-2024, by Samuel Williams.
5
5
  # Copyright, 2019, by Bryan Powell.
6
6
  # Copyright, 2020, by Michael Adams.
7
7
  # Copyright, 2021, by Robert Schulze.
8
8
 
9
- require_relative 'buffer'
10
-
11
9
  module Console
12
- UNKNOWN = 'unknown'
10
+ UNKNOWN = :unknown
13
11
 
14
12
  class Filter
15
13
  if Object.const_defined?(:Ractor) and RUBY_VERSION >= '3.1'
@@ -37,7 +35,7 @@ module Console
37
35
 
38
36
  define_immutable_method(name) do |subject = nil, *arguments, **options, &block|
39
37
  if self.enabled?(subject, level)
40
- self.call(subject, *arguments, severity: name, **options, **@options, &block)
38
+ @output.call(subject, *arguments, severity: name, **@options, **options, &block)
41
39
  end
42
40
  end
43
41
 
@@ -145,8 +143,13 @@ module Console
145
143
  @subjects.delete(subject)
146
144
  end
147
145
 
148
- def call(*arguments, **options, &block)
149
- @output.call(*arguments, **options, &block)
146
+ def call(subject, *arguments, **options, &block)
147
+ severity = options[:severity] || UNKNOWN
148
+ level = self.class::LEVELS[severity]
149
+
150
+ if self.enabled?(subject, level)
151
+ @output.call(subject, *arguments, **options, &block)
152
+ end
150
153
  end
151
154
  end
152
155
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2023, by Samuel Williams.
4
+ # Copyright, 2023-2024, by Samuel Williams.
5
5
 
6
6
  require_relative 'format/safe'
7
7
 
@@ -10,9 +10,5 @@ module Console
10
10
  def self.default
11
11
  Safe.new(format: ::JSON)
12
12
  end
13
-
14
- def self.default_json
15
- self.default
16
- end
17
13
  end
18
14
  end
@@ -1,17 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2019-2022, by Samuel Williams.
4
+ # Copyright, 2019-2024, by Samuel Williams.
5
5
  # Copyright, 2021, by Bryan Powell.
6
6
  # Copyright, 2021, by Robert Schulze.
7
7
 
8
8
  require_relative 'output'
9
9
  require_relative 'filter'
10
- require_relative 'progress'
11
-
10
+ require_relative 'event'
12
11
  require_relative 'resolver'
13
- require_relative 'terminal/logger'
14
- require_relative 'serialized/logger'
12
+ require_relative 'progress'
15
13
 
16
14
  require 'fiber/local'
17
15
 
@@ -67,12 +65,24 @@ module Console
67
65
  end
68
66
 
69
67
  def progress(subject, total, **options)
70
- Progress.new(self, subject, total, **options)
68
+ options[:severity] ||= :info
69
+
70
+ Progress.new(subject, total, **options)
71
+ end
72
+
73
+ def error(subject, *arguments, **options, &block)
74
+ # This is a special case where we want to create a failure event from an exception.
75
+ # It's common to see `Console.error(self, exception)` in code.
76
+ if arguments.first.is_a?(Exception)
77
+ exception = arguments.shift
78
+ options[:event] = Event::Failure.for(exception)
79
+ end
80
+
81
+ super
71
82
  end
72
83
 
73
- # @deprecated Use `fatal` instead.
74
- def failure(subject, exception, *arguments, &block)
75
- self.fatal(subject, exception, *arguments, &block)
84
+ def failure(subject, exception, **options)
85
+ error(subject, event: Event::Failure.for(exception), **options)
76
86
  end
77
87
  end
78
88
  end
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021-2022, by Samuel Williams.
4
+ # Copyright, 2021-2024, by Samuel Williams.
5
5
 
6
- require_relative 'xterm'
7
- require_relative 'json'
6
+ require_relative 'terminal'
7
+ require_relative 'serialized'
8
8
 
9
9
  module Console
10
10
  module Output
@@ -13,9 +13,9 @@ module Console
13
13
  output ||= $stderr
14
14
 
15
15
  if output.tty?
16
- XTerm.new(output, **options)
16
+ Terminal.new(output, **options)
17
17
  else
18
- JSON.new(output, **options)
18
+ Serialized.new(output, **options)
19
19
  end
20
20
  end
21
21
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2023, by Samuel Williams.
4
+ # Copyright, 2023-2024, by Samuel Williams.
5
5
 
6
6
  module Console
7
7
  module Output
@@ -1,17 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021-2022, by Samuel Williams.
4
+ # Copyright, 2021-2024, by Samuel Williams.
5
5
 
6
- require_relative '../serialized/logger'
6
+ require_relative 'wrapper'
7
7
 
8
8
  module Console
9
9
  module Output
10
- class Sensitive
11
- def initialize(output, **options)
12
- @output = output
13
- end
14
-
10
+ class Sensitive < Wrapper
15
11
  REDACT = /
16
12
  phone
17
13
  | email
@@ -38,8 +34,14 @@ module Console
38
34
  | password
39
35
  /xi
40
36
 
37
+ def initialize(output, redact: REDACT, **options)
38
+ super(output, **options)
39
+
40
+ @redact = redact
41
+ end
42
+
41
43
  def redact?(text)
42
- text.match?(REDACT)
44
+ text.match?(@redact)
43
45
  end
44
46
 
45
47
  def redact_hash(arguments, filter)
@@ -96,7 +98,7 @@ module Console
96
98
  arguments = redact_array(arguments, filter)
97
99
  end
98
100
 
99
- @output.call(subject, *arguments, **options)
101
+ super(subject, *arguments, **options)
100
102
  end
101
103
  end
102
104
  end