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.
@@ -1,34 +1,23 @@
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
- require_relative '../buffer'
7
- require_relative '../filter'
8
6
  require_relative '../format'
9
-
10
7
  require 'time'
11
-
12
8
  require 'fiber/annotation'
13
9
 
14
10
  module Console
15
- module Serialized
16
- class Logger
17
- def initialize(io = $stderr, format: Format.default, verbose: false, **options)
18
- @io = io
19
- @start = Time.now
11
+ module Output
12
+ class Serialized
13
+ def initialize(output, format: Format.default, **options)
14
+ @io = output
20
15
  @format = format
21
- @verbose = verbose
22
16
  end
23
17
 
24
18
  attr :io
25
- attr :start
26
19
  attr :format
27
20
 
28
- def verbose!(value = true)
29
- @verbose = true
30
- end
31
-
32
21
  def dump(record)
33
22
  @format.dump(record)
34
23
  end
@@ -72,41 +61,12 @@ module Console
72
61
  record[:message] = message
73
62
  end
74
63
 
75
- if exception = find_exception(message)
76
- record[:error] = {
77
- kind: exception.class,
78
- message: exception.message,
79
- stack: format_stack(exception)
80
- }
81
- end
82
-
83
64
  record.update(options)
84
65
 
85
66
  @io.puts(self.dump(record))
86
67
  end
87
-
88
- private
89
-
90
- def find_exception(message)
91
- message.find{|part| part.is_a?(Exception)}
92
- end
93
-
94
- def format_stack(exception)
95
- buffer = StringIO.new
96
- format_backtrace(exception, buffer)
97
- return buffer.string
98
- end
99
-
100
- def format_backtrace(exception, buffer)
101
- buffer.puts exception.backtrace
102
-
103
- if exception = exception.cause
104
- buffer.puts
105
- buffer.puts "Caused by: #{exception.class} #{exception.message}"
106
-
107
- format_backtrace(exception, buffer)
108
- end
109
- end
110
68
  end
69
+
70
+ JSON = Serialized
111
71
  end
112
72
  end
@@ -1,7 +1,7 @@
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
  module Console
7
7
  module Output
@@ -18,9 +18,9 @@ module Console
18
18
  @outputs.each{|output| output.verbose!(value)}
19
19
  end
20
20
 
21
- def call(level, subject = nil, *arguments, **options, &block)
21
+ def call(...)
22
22
  @outputs.each do |output|
23
- output.call(level, subject, *arguments, **options, &block)
23
+ output.call(...)
24
24
  end
25
25
  end
26
26
  end
@@ -1,53 +1,61 @@
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, 2021, by Robert Schulze.
6
6
 
7
- require_relative '../buffer'
8
- require_relative '../event'
9
7
  require_relative '../clock'
10
-
11
- require_relative 'text'
12
- require_relative 'xterm'
8
+ require_relative '../terminal'
13
9
 
14
10
  require 'json'
15
11
  require 'fiber'
16
12
  require 'fiber/annotation'
13
+ require 'stringio'
17
14
 
18
15
  module Console
19
- module Terminal
20
- # This, and all related methods, is considered private.
21
- CONSOLE_START_AT = 'CONSOLE_START_AT'
22
-
23
- # Exports CONSOLE_START which can be used to synchronize the start times of all child processes when they log using delta time.
24
- def self.start_at!(environment = ENV)
25
- if time_string = environment[CONSOLE_START_AT]
26
- start_at = Time.parse(time_string) rescue nil
16
+ module Output
17
+ class Terminal
18
+ class Buffer < StringIO
19
+ def initialize(prefix = nil)
20
+ @prefix = prefix
21
+
22
+ super()
23
+ end
24
+
25
+ attr :prefix
26
+
27
+ def puts(*args, prefix: @prefix)
28
+ args.each do |arg|
29
+ self.write(prefix) if prefix
30
+ super(arg)
31
+ end
32
+ end
33
+
34
+ alias << puts
27
35
  end
28
36
 
29
- unless start_at
30
- start_at = Time.now
31
- environment[CONSOLE_START_AT] = start_at.to_s
32
- end
37
+ # This, and all related methods, is considered private.
38
+ CONSOLE_START_AT = 'CONSOLE_START_AT'
33
39
 
34
- return start_at
35
- end
36
-
37
- def self.for(io)
38
- if io.isatty
39
- XTerm.new(io)
40
- else
41
- Text.new(io)
40
+ # Exports CONSOLE_START which can be used to synchronize the start times of all child processes when they log using delta time.
41
+ def self.start_at!(environment = ENV)
42
+ if time_string = environment[CONSOLE_START_AT]
43
+ start_at = Time.parse(time_string) rescue nil
44
+ end
45
+
46
+ unless start_at
47
+ start_at = Time.now
48
+ environment[CONSOLE_START_AT] = start_at.to_s
49
+ end
50
+
51
+ return start_at
42
52
  end
43
- end
44
-
45
- class Logger
46
- def initialize(io = $stderr, verbose: nil, start_at: Terminal.start_at!, format: nil, **options)
47
- @io = io
53
+
54
+ def initialize(output, verbose: nil, start_at: Terminal.start_at!, format: nil, **options)
55
+ @io = output
48
56
  @start_at = start_at
49
57
 
50
- @terminal = format.nil? ? Terminal.for(io) : format.new(io)
58
+ @terminal = format.nil? ? Console::Terminal.for(@io) : format.new(@io)
51
59
 
52
60
  if verbose.nil?
53
61
  @verbose = !@terminal.colors?
@@ -66,7 +74,8 @@ module Console
66
74
  @terminal[:annotation] = @terminal.reset
67
75
  @terminal[:value] = @terminal.style(:blue)
68
76
 
69
- self.register_defaults(@terminal)
77
+ @formatters = {}
78
+ self.register_formatters
70
79
  end
71
80
 
72
81
  attr :io
@@ -80,20 +89,23 @@ module Console
80
89
  @verbose = value
81
90
  end
82
91
 
83
- def register_defaults(terminal)
84
- Event.constants.each do |constant|
85
- klass = Event.const_get(constant)
86
- klass.register(terminal)
92
+ def register_formatters(namespace = Console::Terminal::Formatter)
93
+ namespace.constants.each do |name|
94
+ formatter = namespace.const_get(name)
95
+ @formatters[formatter::KEY] = formatter.new(@terminal)
87
96
  end
88
97
  end
89
98
 
90
99
  UNKNOWN = :unknown
91
100
 
92
- def call(subject = nil, *arguments, name: nil, severity: UNKNOWN, **options, &block)
101
+ def call(subject = nil, *arguments, name: nil, severity: UNKNOWN, event: nil, **options, &block)
102
+ width = @terminal.width
103
+
93
104
  prefix = build_prefix(name || severity.to_s)
94
105
  indent = " " * prefix.size
95
106
 
96
107
  buffer = Buffer.new("#{indent}| ")
108
+ indent_size = buffer.prefix.size
97
109
 
98
110
  format_subject(severity, prefix, subject, buffer)
99
111
 
@@ -109,6 +121,10 @@ module Console
109
121
  end
110
122
  end
111
123
 
124
+ if event
125
+ format_event(event, buffer, width - indent_size)
126
+ end
127
+
112
128
  if options&.any?
113
129
  format_options(options, buffer)
114
130
  end
@@ -118,20 +134,24 @@ module Console
118
134
 
119
135
  protected
120
136
 
137
+ def format_event(event, buffer, width)
138
+ event = event.to_hash
139
+ type = event[:type]
140
+
141
+ if formatter = @formatters[type]
142
+ formatter.format(event, buffer, verbose: @verbose, width: width)
143
+ else
144
+ format_value(::JSON.pretty_generate(event), buffer)
145
+ end
146
+ end
147
+
121
148
  def format_options(options, output)
122
149
  format_value(::JSON.pretty_generate(options), output)
123
150
  end
124
151
 
125
152
  def format_argument(argument, output)
126
- case argument
127
- when Exception
128
- Event::Failure.for(argument).format(output, @terminal, @verbose)
129
- when Event::Generic
130
- argument.format(output, @terminal, @verbose)
131
- else
132
- argument.to_s.each_line do |line|
133
- output.puts line
134
- end
153
+ argument.to_s.each_line do |line|
154
+ output.puts line
135
155
  end
136
156
  end
137
157
 
@@ -216,5 +236,17 @@ module Console
216
236
  end
217
237
  end
218
238
  end
239
+
240
+ module Text
241
+ def self.new(output, **options)
242
+ Terminal.new(output, format: Console::Terminal::Text, **options)
243
+ end
244
+ end
245
+
246
+ module XTerm
247
+ def self.new(output, **options)
248
+ Terminal.new(output, format: Console::Terminal::XTerm, **options)
249
+ end
250
+ end
219
251
  end
220
252
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2021-2024, by Samuel Williams.
5
+
6
+ module Console
7
+ module Output
8
+ class Wrapper
9
+ def initialize(delegate, **options)
10
+ @delegate = delegate
11
+ end
12
+
13
+ def verbose!(value = true)
14
+ @delegate.verbose!(value)
15
+ end
16
+
17
+ def call(...)
18
+ @delegate.call(...)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,12 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2021-2023, by Samuel Williams.
4
+ # Copyright, 2021-2024, by Samuel Williams.
5
5
 
6
6
  require_relative 'output/default'
7
- require_relative 'output/json'
8
- require_relative 'output/text'
9
- require_relative 'output/xterm'
7
+ require_relative 'output/serialized'
8
+ require_relative 'output/terminal'
10
9
  require_relative 'output/null'
11
10
 
12
11
  module Console
@@ -1,10 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2020-2023, by Samuel Williams.
4
+ # Copyright, 2020-2024, by Samuel Williams.
5
5
  # Copyright, 2022, by Anton Sozontov.
6
6
 
7
- require_relative 'event/progress'
8
7
  require_relative 'clock'
9
8
 
10
9
  module Console
@@ -13,9 +12,9 @@ module Console
13
12
  Process.clock_gettime(Process::CLOCK_MONOTONIC)
14
13
  end
15
14
 
16
- def initialize(output, subject, total = 0, minimum_output_duration: 0.1)
17
- @output = output
15
+ def initialize(subject, total = 0, minimum_output_duration: 0.1, **options)
18
16
  @subject = subject
17
+ @options = options
19
18
 
20
19
  @start_time = Progress.now
21
20
 
@@ -54,11 +53,22 @@ module Console
54
53
  end
55
54
  end
56
55
 
56
+ def to_hash
57
+ Hash.new.tap do |hash|
58
+ hash[:type] = :progress
59
+ hash[:current] = @current
60
+ hash[:total] = @total
61
+
62
+ hash[:duration] = self.duration
63
+ hash[:estimated_remaining_time] = self.estimated_remaining_time
64
+ end
65
+ end
66
+
57
67
  def increment(amount = 1)
58
68
  @current += amount
59
69
 
60
70
  if output?
61
- @output.info(@subject, self) {Event::Progress.new(@current, @total)}
71
+ Console.call(@subject, self.to_s, event: self.to_hash, **@options)
62
72
  @last_output_time = Progress.now
63
73
  end
64
74
 
@@ -68,14 +78,14 @@ module Console
68
78
  def resize(total)
69
79
  @total = total
70
80
 
71
- @output.info(@subject, self) {Event::Progress.new(@current, @total)}
81
+ Console.call(@subject, self.to_s, event: self.to_hash, **@options)
72
82
  @last_output_time = Progress.now
73
83
 
74
84
  return self
75
85
  end
76
86
 
77
- def mark(...)
78
- @output.info(@subject, ...)
87
+ def mark(*arguments, **options)
88
+ Console.call(@subject, *arguments, **options, **@options)
79
89
  end
80
90
 
81
91
  def to_s
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2024, by Samuel Williams.
5
+
6
+ module Console
7
+ module Terminal
8
+ module Formatter
9
+ class Failure
10
+ KEY = :failure
11
+
12
+ def initialize(terminal)
13
+ @terminal = terminal
14
+
15
+ @terminal[:exception_title] ||= @terminal.style(:red, nil, :bold)
16
+ @terminal[:exception_detail] ||= @terminal.style(:yellow)
17
+ @terminal[:exception_backtrace] ||= @terminal.style(:red)
18
+ @terminal[:exception_backtrace_other] ||= @terminal.style(:red, nil, :faint)
19
+ @terminal[:exception_message] ||= @terminal.style(:default)
20
+ end
21
+
22
+ def format(event, output, prefix: nil, verbose: false, width: 80)
23
+ title = event[:class]
24
+ message = event[:message]
25
+ backtrace = event[:backtrace]
26
+ root = event[:root]
27
+
28
+ lines = message.lines.map(&:chomp)
29
+
30
+ output.puts " #{prefix}#{@terminal[:exception_title]}#{title}#{@terminal.reset}: #{lines.shift}"
31
+
32
+ lines.each do |line|
33
+ output.puts " #{@terminal[:exception_detail]}#{line}#{@terminal.reset}"
34
+ end
35
+
36
+ root_pattern = /^#{root}\// if root
37
+
38
+ backtrace&.each_with_index do |line, index|
39
+ path, offset, message = line.split(":", 3)
40
+ style = :exception_backtrace
41
+
42
+ # Make the path a bit more readable:
43
+ if root_pattern and path.sub!(root_pattern, "").nil?
44
+ style = :exception_backtrace_other
45
+ end
46
+
47
+ output.puts " #{index == 0 ? "→" : " "} #{@terminal[style]}#{path}:#{offset}#{@terminal[:exception_message]} #{message}#{@terminal.reset}"
48
+ end
49
+
50
+ if cause = event[:cause]
51
+ format(cause, output, prefix: "Caused by ", verbose: verbose, width: width)
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2024, by Samuel Williams.
5
+
6
+ module Console
7
+ module Terminal
8
+ module Formatter
9
+ class Progress
10
+ KEY = :progress
11
+
12
+ BLOCK = [
13
+ " ",
14
+ "▏",
15
+ "▎",
16
+ "▍",
17
+ "▌",
18
+ "▋",
19
+ "▊",
20
+ "▉",
21
+ "█",
22
+ ]
23
+
24
+ def initialize(terminal)
25
+ @terminal = terminal
26
+ @terminal[:progress_bar] ||= terminal.style(:blue, :white)
27
+ end
28
+
29
+ def format(event, output, verbose: false, width: 80)
30
+ current = event[:current].to_f
31
+ total = event[:total].to_f
32
+ value = current / total
33
+
34
+ # Clamp value to 1.0 to avoid rendering issues:
35
+ if value > 1.0
36
+ value = 1.0
37
+ end
38
+
39
+ output.puts "#{@terminal[:progress_bar]}#{self.bar(value, width-10)}#{@terminal.reset} #{sprintf('%6.2f', value * 100)}%"
40
+ end
41
+
42
+ private
43
+
44
+ def bar(value, width)
45
+ blocks = width * value
46
+ full_blocks = blocks.floor
47
+ partial_block = ((blocks - full_blocks) * BLOCK.size).floor
48
+
49
+ if partial_block.zero?
50
+ BLOCK.last * full_blocks
51
+ else
52
+ "#{BLOCK.last * full_blocks}#{BLOCK[partial_block]}"
53
+ end.ljust(width)
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2019-2024, by Samuel Williams.
5
+
6
+ module Console
7
+ module Terminal
8
+ module Formatter
9
+ # Format spawn events.
10
+ class Spawn
11
+ KEY = :spawn
12
+
13
+ def initialize(terminal)
14
+ @terminal = terminal
15
+ @terminal[:spawn_command] ||= @terminal.style(:blue, nil, :bold)
16
+ end
17
+
18
+ def format(event, output, verbose: false, width: 80)
19
+ environment, arguments, options = event.values_at(:environment, :arguments, :options)
20
+
21
+ arguments = arguments.flatten.collect(&:to_s)
22
+
23
+ output.puts "#{@terminal[:spawn_command]}#{arguments.join(' ')}#{@terminal.reset}#{chdir_string(options)}"
24
+
25
+ if verbose and environment
26
+ environment.each do |key, value|
27
+ output.puts "export #{key}=#{value}"
28
+ end
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def chdir_string(options)
35
+ if options and chdir = options[:chdir]
36
+ " in #{chdir}"
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -1,7 +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 'io/console'
7
7
 
@@ -26,6 +26,10 @@ module Console
26
26
  false
27
27
  end
28
28
 
29
+ def width
30
+ 80
31
+ end
32
+
29
33
  def style(foreground, background = nil, *attributes)
30
34
  end
31
35
 
@@ -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 'io/console'
7
7
 
@@ -41,6 +41,13 @@ module Console
41
41
 
42
42
  def size
43
43
  @output.winsize
44
+ rescue Errno::ENOTTY
45
+ # Fake it...
46
+ [24, 80]
47
+ end
48
+
49
+ def width
50
+ size.last
44
51
  end
45
52
 
46
53
  def style(foreground, background = nil, *attributes)
@@ -1,6 +1,23 @@
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
- require_relative 'terminal/logger'
6
+ require_relative 'terminal/text'
7
+ require_relative 'terminal/xterm'
8
+
9
+ require_relative 'terminal/formatter/progress'
10
+ require_relative 'terminal/formatter/failure'
11
+ require_relative 'terminal/formatter/spawn'
12
+
13
+ module Console
14
+ module Terminal
15
+ def self.for(io)
16
+ if io.tty?
17
+ XTerm.new(io)
18
+ else
19
+ Text.new(io)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -1,8 +1,8 @@
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
  module Console
7
- VERSION = "1.24.0"
7
+ VERSION = "1.26.0"
8
8
  end
data/lib/console.rb CHANGED
@@ -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
  # Copyright, 2019, by Bryan Powell.
6
6
  # Copyright, 2020, by Michael Adams.
7
7
  # Copyright, 2021, by Cédric Boutillier.
@@ -43,12 +43,4 @@ module Console
43
43
  Logger.instance.call(...)
44
44
  end
45
45
  end
46
-
47
- def logger= logger
48
- warn "Setting logger on #{self} is deprecated. Use Console.logger= instead.", uplevel: 1
49
- end
50
-
51
- def logger
52
- Logger.instance
53
- end
54
46
  end
data.tar.gz.sig CHANGED
Binary file