console 1.9.0 → 1.11.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: 79f9ed13eedb54202ce2111a451e6e7d744f97db971a6b619ba3decb5aa6c963
4
- data.tar.gz: 8c77221c0e50be91b7c1857fde7b596ec7e48eebd67a43287bd01aa3f95e70b4
3
+ metadata.gz: 5928a2a5d4c07c44e8ad937edbab2b8fa326ff13553ffb4bf89c0854184f1bd8
4
+ data.tar.gz: e0495d1933a3a0991b23193aae3988fb679389f02514d57c9a15ce7ccbf73497
5
5
  SHA512:
6
- metadata.gz: 021e0bd5c98d7019af39186b6fa06e49aed619cb5228051a85725bd43f3f05d8015b6d9bc84baf3a49f51a9b13ac62ed65cb20051f9561939d0fb72c791b8475
7
- data.tar.gz: '029abdaded5a4d383e7e4b666c99e2b06c0aaabed87c1da9740c6925ca5e9308eb41b52d61835e0b6a3223efff9b25a98aac555fe43c2ca441ef48f5f27e6f3f'
6
+ metadata.gz: e69af353f0397d3620654d9108b2cb7fffe4d601e4a4f9feea7cdf09a7cd40aa3f310faded79abfc8de5735c257b59713bb9d5a91d5320a1dca15d0df4a92804
7
+ data.tar.gz: c46ec0e33e56260345710facc882d89edb4782179ccdb7ec97ed70c3d83087465b4758ceecdaa7f8ac4e2508dc18f46968974bee19a694c62c5314157a8402fe
data/lib/console.rb CHANGED
@@ -20,83 +20,24 @@
20
20
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
21
  # THE SOFTWARE.
22
22
 
23
+ require_relative 'console/version'
23
24
  require_relative 'console/logger'
24
- require_relative 'console/resolver'
25
- require_relative 'console/terminal/logger'
26
25
 
27
26
  module Console
28
- class << self
29
- attr_accessor :logger
30
-
31
- # Set the default log level based on `$DEBUG` and `$VERBOSE`.
32
- # You can also specify CONSOLE_LEVEL=debug or CONSOLE_LEVEL=info in environment.
33
- # https://mislav.net/2011/06/ruby-verbose-mode/ has more details about how it all fits together.
34
- def default_log_level(env = ENV)
35
- if level = (env['CONSOLE_LEVEL'] || env['CONSOLE_LOG_LEVEL'])
36
- Logger::LEVELS[level.to_sym] || Logger.warn
37
- elsif $DEBUG
38
- Logger::DEBUG
39
- elsif $VERBOSE.nil?
40
- Logger::WARN
41
- else
42
- Logger::INFO
43
- end
44
- end
45
-
46
- # You can change the log level for different classes using CONSOLE_<LEVEL> env vars.
47
- #
48
- # e.g. `CONSOLE_WARN=Acorn,Banana CONSOLE_DEBUG=Cat` will set the log level for Acorn and Banana to warn and Cat to
49
- # debug. This overrides the default log level.
50
- #
51
- # @param logger [Logger] A logger instance to set the logging levels on.
52
- # @param env [Hash] Environment to read levels from.
53
- #
54
- # @return [nil] if there were no custom logging levels specified in the environment.
55
- # @return [Resolver] if there were custom logging levels, then the created resolver is returned.
56
- def default_resolver(logger, env = ENV)
57
- # find all CONSOLE_<LEVEL> variables from environment
58
- levels = Logger::LEVELS
59
- .map { |label, level| [level, env["CONSOLE_#{label.to_s.upcase}"]&.split(',')] }
60
- .to_h
61
- .compact
62
-
63
- # if we have any levels, then create a class resolver, and each time a class is resolved, set the log level for
64
- # that class to the specified level
65
- if levels.any?
66
- resolver = Resolver.new
67
- levels.each do |level, names|
68
- resolver.bind(names) do |klass|
69
- logger.enable(klass, level)
70
- end
71
- end
72
- return resolver
73
- end
74
- end
75
-
76
- # Controls verbose output using `$VERBOSE`.
77
- def verbose?
78
- !$VERBOSE.nil?
79
- end
80
-
81
- def build(output, verbose: self.verbose?, level: self.default_log_level)
82
- terminal = Terminal::Logger.new(output, verbose: verbose)
83
-
84
- logger = Logger.new(terminal, verbose: verbose, level: level)
85
-
86
- return logger
87
- end
27
+ def self.logger
28
+ Logger.instance
88
29
  end
89
30
 
90
- # Create the logger instance:
91
- @logger = self.build($stderr)
92
- @resolver = self.default_resolver(@logger)
31
+ def self.logger= instance
32
+ Logger.instance= instance
33
+ end
93
34
 
94
35
  def logger= logger
95
36
  @logger = logger
96
37
  end
97
38
 
98
39
  def logger
99
- @logger || Console.logger
40
+ @logger || Logger.instance
100
41
  end
101
42
 
102
43
  def self.extended(klass)
@@ -21,15 +21,20 @@
21
21
  require_relative 'filter'
22
22
 
23
23
  module Console
24
+ # A general sink which captures all events into a buffer.
24
25
  class Capture
25
26
  def initialize
26
- @events = []
27
+ @buffer = []
27
28
  end
28
29
 
29
- attr :events
30
+ attr :buffer
30
31
 
31
32
  def last
32
- @events.last
33
+ @buffer.last
34
+ end
35
+
36
+ def clear
37
+ @buffer.clear
33
38
  end
34
39
 
35
40
  def verbose!(value = true)
@@ -37,7 +42,7 @@ module Console
37
42
 
38
43
  def call(subject = nil, *arguments, severity: UNKNOWN, **options, &block)
39
44
  message = {
40
- time: Time.now.iso8601,
45
+ time: ::Time.now.iso8601,
41
46
  severity: severity,
42
47
  **options,
43
48
  }
@@ -60,7 +65,7 @@ module Console
60
65
  end
61
66
  end
62
67
 
63
- @events << message
68
+ @buffer << message
64
69
  end
65
70
  end
66
71
  end
@@ -0,0 +1,50 @@
1
+ # Copyright, 2021, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ module Console
22
+ module Clock
23
+ def self.formatted_duration(duration)
24
+ if duration < 60.0
25
+ return "#{duration.round(2)}s"
26
+ end
27
+
28
+ duration /= 60.0
29
+
30
+ if duration < 60.0
31
+ return "#{duration.round}m"
32
+ end
33
+
34
+ duration /= 60.0
35
+
36
+ if duration < 60.0
37
+ return "#{duration.round(1)}h"
38
+ end
39
+
40
+ duration /= 24.0
41
+
42
+ return "#{duration.round(1)}d"
43
+ end
44
+
45
+ # Get the current elapsed monotonic time.
46
+ def self.now
47
+ ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
48
+ end
49
+ end
50
+ end
data/lib/console/event.rb CHANGED
@@ -20,4 +20,5 @@
20
20
 
21
21
  require_relative 'event/spawn'
22
22
  require_relative 'event/failure'
23
+ require_relative 'event/metric'
23
24
  require_relative 'event/progress'
@@ -47,6 +47,12 @@ module Console
47
47
  terminal[:exception_title] ||= terminal.style(:red, nil, :bold)
48
48
  terminal[:exception_detail] ||= terminal.style(:yellow)
49
49
  terminal[:exception_backtrace] ||= terminal.style(:red)
50
+ terminal[:exception_backtrace_other] ||= terminal.style(:red, nil, :faint)
51
+ terminal[:exception_message] ||= terminal.style(:default)
52
+ end
53
+
54
+ def to_h
55
+ {exception: @exception, root: @root}
50
56
  end
51
57
 
52
58
  def format(output, terminal, verbose)
@@ -62,13 +68,18 @@ module Console
62
68
  output.puts " #{terminal[:exception_detail]}#{line}#{terminal.reset}"
63
69
  end
64
70
 
71
+ root_pattern = /^#{@root}\// if @root
72
+
65
73
  exception.backtrace&.each_with_index do |line, index|
66
74
  path, offset, message = line.split(":")
75
+ style = :exception_backtrace
67
76
 
68
77
  # Make the path a bit more readable
69
- path.gsub!(/^#{@root}\//, "./") if @root
78
+ if root_pattern and path.sub!(root_pattern, "").nil?
79
+ style = :exception_backtrace_other
80
+ end
70
81
 
71
- output.puts " #{index == 0 ? "→" : " "} #{terminal[:exception_backtrace]}#{path}:#{offset}#{terminal.reset} #{message}"
82
+ output.puts " #{index == 0 ? "→" : " "} #{terminal[style]}#{path}:#{offset}#{terminal[:exception_message]} #{message}#{terminal.reset}"
72
83
  end
73
84
 
74
85
  if exception.cause
@@ -24,6 +24,13 @@ module Console
24
24
  def self.register(terminal)
25
25
  end
26
26
 
27
+ def to_h
28
+ end
29
+
30
+ def as_json
31
+ to_h
32
+ end
33
+
27
34
  def format(buffer, terminal)
28
35
  end
29
36
  end
@@ -0,0 +1,42 @@
1
+ # Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require_relative 'metric'
22
+ require_relative '../clock'
23
+
24
+ module Console
25
+ module Event
26
+ class Enter < Generic
27
+ def initialize(name)
28
+ @name = name
29
+ end
30
+
31
+ def format(output, terminal, verbose)
32
+ output.puts "→ #{@name}"
33
+ end
34
+ end
35
+
36
+ class Exit < Metric
37
+ def value_string
38
+ "← #{@name} took #{Clock.formatted_duration(@value)}"
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,58 @@
1
+ # Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require_relative 'generic'
22
+ require_relative '../clock'
23
+
24
+ module Console
25
+ module Event
26
+ class Metric < Generic
27
+ def self.[](**parameters)
28
+ parameters.map(&self.method(:new))
29
+ end
30
+
31
+ def initialize(name, value, **tags)
32
+ @name = name
33
+ @value = value
34
+ @tags = tags
35
+ end
36
+
37
+ attr :name
38
+ attr :value
39
+ attr :tags
40
+
41
+ def to_h
42
+ {name: @name, value: @value, tags: @tags}
43
+ end
44
+
45
+ def value_string
46
+ "#{@name}: #{@value}"
47
+ end
48
+
49
+ def format(output, terminal, verbose)
50
+ if @tags&.any?
51
+ output.puts "#{value_string} #{@tags.inspect}"
52
+ else
53
+ output.puts value_string
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -63,7 +63,7 @@ module Console
63
63
  terminal[:progress_bar] ||= terminal.style(:blue, :white)
64
64
  end
65
65
 
66
- def as_json
66
+ def to_h
67
67
  {current: @current, total: @total}
68
68
  end
69
69
 
@@ -24,6 +24,7 @@ module Console
24
24
  module Event
25
25
  class Spawn < Generic
26
26
  def self.for(*arguments, **options)
27
+ # Extract out the command environment:
27
28
  if arguments.first.is_a?(Hash)
28
29
  self.new(*arguments, **options)
29
30
  else
@@ -51,6 +52,10 @@ module Console
51
52
  terminal[:shell_command] ||= terminal.style(:blue, nil, :bold)
52
53
  end
53
54
 
55
+ def to_h
56
+ {environment: @environment, arguments: @arguments, options: @options}
57
+ end
58
+
54
59
  def format(output, terminal, verbose)
55
60
  arguments = @arguments.flatten.collect(&:to_s)
56
61
 
@@ -46,7 +46,7 @@ module Console
46
46
  end
47
47
 
48
48
  define_method("#{name}?") do
49
- @level >= level
49
+ @level <= level
50
50
  end
51
51
  end
52
52
  end
@@ -20,17 +20,72 @@
20
20
 
21
21
  require_relative 'filter'
22
22
  require_relative 'measure'
23
+ require_relative 'progress'
24
+
25
+ require_relative 'resolver'
26
+ require_relative 'terminal/logger'
27
+
28
+ require 'fiber/local'
23
29
 
24
30
  module Console
25
31
  class Logger < Filter[debug: 0, info: 1, warn: 2, error: 3, fatal: 4]
32
+ extend Fiber::Local
33
+
34
+ # Set the default log level based on `$DEBUG` and `$VERBOSE`.
35
+ # You can also specify CONSOLE_LEVEL=debug or CONSOLE_LEVEL=info in environment.
36
+ # https://mislav.net/2011/06/ruby-verbose-mode/ has more details about how it all fits together.
37
+ def self.default_log_level(env = ENV)
38
+ if level = (env['CONSOLE_LEVEL'] || env['CONSOLE_LOG_LEVEL'])
39
+ LEVELS[level.to_sym] || level.to_i
40
+ elsif $DEBUG
41
+ DEBUG
42
+ elsif $VERBOSE.nil?
43
+ WARN
44
+ else
45
+ INFO
46
+ end
47
+ end
48
+
49
+ # Controls verbose output using `$VERBOSE`.
50
+ def self.verbose?(env = ENV)
51
+ !$VERBOSE.nil? || env['CONSOLE_VERBOSE']
52
+ end
53
+
54
+ def self.default_logger(output, verbose: self.verbose?, level: self.default_log_level)
55
+ terminal = Terminal::Logger.new(output, verbose: verbose)
56
+
57
+ logger = self.new(terminal, verbose: verbose, level: level)
58
+ Resolver.default_resolver(logger)
59
+
60
+ return logger
61
+ end
62
+
63
+ def self.local
64
+ self.default_logger($stderr)
65
+ end
66
+
26
67
  DEFAULT_LEVEL = 1
27
68
 
28
69
  def initialize(output, **options)
29
70
  super(output, **options)
30
71
  end
31
72
 
32
- def measure(subject, total)
33
- Measure.new(self, subject, total)
73
+ def progress(subject, total, **options)
74
+ Progress.new(self, subject, total, **options)
75
+ end
76
+
77
+ def measure(subject, name = "block", **options, &block)
78
+ measure = Measure.new(self, subject)
79
+
80
+ if block_given?
81
+ measure.duration(name, **options, &block)
82
+ end
83
+
84
+ return measure
85
+ end
86
+
87
+ def failure(subject, exception, *arguments, &block)
88
+ fatal(subject, *arguments, Event::Failure.new(exception), &block)
34
89
  end
35
90
  end
36
91
  end
@@ -1,4 +1,4 @@
1
- # Copyright, 2019, by Samuel G. D. Williams. <http://www.codeotaku.com>
1
+ # Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  # of this software and associated documentation files (the "Software"), to deal
@@ -18,98 +18,29 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
+ require_relative 'event/measure'
22
+ require_relative 'clock'
23
+
21
24
  module Console
22
25
  class Measure
23
- def initialize(output, subject, total = 0)
26
+ def initialize(output, subject)
24
27
  @output = output
25
28
  @subject = subject
26
-
27
- @start_time = Time.now
28
-
29
- @current = 0
30
- @total = total
31
- end
32
-
33
- attr :subject
34
-
35
- attr :current
36
-
37
- attr :total
38
-
39
- def duration
40
- Time.now - @start_time
41
- end
42
-
43
- def progress
44
- @current.to_f / @total.to_f
45
- end
46
-
47
- def remaining
48
- @total - @current
49
- end
50
-
51
- def average_duration
52
- if @current > 0
53
- duration / @current
54
- end
55
- end
56
-
57
- def estimated_remaining_time
58
- if average_duration = self.average_duration
59
- average_duration * remaining
60
- end
61
- end
62
-
63
- def increment(amount = 1)
64
- @current += amount
65
-
66
- @output.info(@subject, self) {Event::Progress.new(@current, @total)}
67
-
68
- return self
69
29
  end
70
30
 
71
- def resize(total)
72
- @total = total
73
-
74
- @output.info(@subject, self) {Event::Progress.new(@current, @total)}
75
-
76
- return self
77
- end
78
-
79
- def mark(*arguments)
80
- @output.info(@subject, *arguments)
81
- end
82
-
83
- def to_s
84
- if estimated_remaining_time = self.estimated_remaining_time
85
- "#{@current}/#{@total} completed in #{self.formatted_duration self.duration}, #{self.formatted_duration estimated_remaining_time} remaining."
86
- else
87
- "#{@current}/#{@total} completed, waiting for estimate..."
88
- end
89
- end
90
-
91
- private
92
-
93
- def formatted_duration(duration)
94
- if duration < 60.0
95
- return "#{duration.round(2)}s"
96
- end
97
-
98
- duration /= 60.0
31
+ # Measure the execution of a block of code.
32
+ def duration(name, **tags, &block)
33
+ @output.info(@subject) {Event::Enter.new(name)}
99
34
 
100
- if duration < 60.0
101
- return "#{duration.round}m"
102
- end
35
+ start_time = Clock.now
103
36
 
104
- duration /= 60.0
37
+ yield
105
38
 
106
- if duration < 60.0
107
- return "#{duration.round(1)}h"
108
- end
39
+ duration = Clock.now - start_time
109
40
 
110
- duration /= 24.0
41
+ @output.info(@subject) {Event::Exit.new(name, duration, **tags)}
111
42
 
112
- return "#{duration.round(1)}d"
43
+ return duration
113
44
  end
114
45
  end
115
46
  end
@@ -0,0 +1,142 @@
1
+ # Copyright, 2019, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require_relative 'event/progress'
22
+
23
+ module Console
24
+ class Progress
25
+ def self.now
26
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
27
+ end
28
+
29
+ def initialize(output, subject, total = 0, minimum_output_duration: 1.0)
30
+ @output = output
31
+ @subject = subject
32
+
33
+ @start_time = Progress.now
34
+
35
+ @last_output_time = nil
36
+ @minimum_output_duration = 0.1
37
+
38
+ @current = 0
39
+ @total = total
40
+ end
41
+
42
+ attr :subject
43
+ attr :current
44
+ attr :total
45
+
46
+ def duration
47
+ Progress.now - @start_time
48
+ end
49
+
50
+ def progress
51
+ @current.to_f / @total.to_f
52
+ end
53
+
54
+ def remaining
55
+ @total - @current
56
+ end
57
+
58
+ def average_duration
59
+ if @current > 0
60
+ duration / @current
61
+ end
62
+ end
63
+
64
+ def estimated_remaining_time
65
+ if average_duration = self.average_duration
66
+ average_duration * remaining
67
+ end
68
+ end
69
+
70
+ def increment(amount = 1)
71
+ @current += amount
72
+
73
+ if output?
74
+ @output.info(@subject, self) {Event::Progress.new(@current, @total)}
75
+ @last_output_time = Progress.now
76
+ end
77
+
78
+ return self
79
+ end
80
+
81
+ def resize(total)
82
+ @total = total
83
+
84
+ @output.info(@subject, self) {Event::Progress.new(@current, @total)}
85
+ @last_output_time = Progress.now
86
+
87
+ return self
88
+ end
89
+
90
+ def mark(*arguments)
91
+ @output.info(@subject, *arguments)
92
+ end
93
+
94
+ def to_s
95
+ if estimated_remaining_time = self.estimated_remaining_time
96
+ "#{@current}/#{@total} completed in #{formatted_duration(self.duration)}, #{formatted_duration(estimated_remaining_time)} remaining."
97
+ else
98
+ "#{@current}/#{@total} completed, waiting for estimate..."
99
+ end
100
+ end
101
+
102
+ private
103
+
104
+ def duration_since_last_output
105
+ if @last_output_time
106
+ Progress.now - @last_output_time
107
+ end
108
+ end
109
+
110
+ def output?
111
+ if remaining.zero?
112
+ return true
113
+ elsif duration = duration_since_last_output
114
+ return duration > @minimum_output_duration
115
+ else
116
+ return true
117
+ end
118
+ end
119
+
120
+ def formatted_duration(duration)
121
+ if duration < 60.0
122
+ return "#{duration.round(2)}s"
123
+ end
124
+
125
+ duration /= 60.0
126
+
127
+ if duration < 60.0
128
+ return "#{duration.round}m"
129
+ end
130
+
131
+ duration /= 60.0
132
+
133
+ if duration < 60.0
134
+ return "#{duration.round(1)}h"
135
+ end
136
+
137
+ duration /= 24.0
138
+
139
+ return "#{duration.round(1)}d"
140
+ end
141
+ end
142
+ end
@@ -22,6 +22,36 @@ require_relative 'filter'
22
22
 
23
23
  module Console
24
24
  class Resolver
25
+ # You can change the log level for different classes using CONSOLE_<LEVEL> env vars.
26
+ #
27
+ # e.g. `CONSOLE_WARN=Acorn,Banana CONSOLE_DEBUG=Cat` will set the log level for the classes Acorn and Banana to `warn` and Cat to `debug`. This overrides the default log level.
28
+ #
29
+ # @parameter logger [Logger] A logger instance to set the logging levels on.
30
+ # @parameter env [Hash] The environment to read levels from.
31
+ #
32
+ # @returns [Nil] If there were no custom logging levels specified in the environment.
33
+ # @returns [Resolver] If there were custom logging levels, then the created resolver is returned.
34
+ def self.default_resolver(logger, env = ENV)
35
+ # Find all CONSOLE_<LEVEL> variables from environment:
36
+ levels = Logger::LEVELS
37
+ .map{|label, level| [level, env["CONSOLE_#{label.upcase}"]&.split(',')]}
38
+ .to_h
39
+ .compact
40
+
41
+ # If we have any levels, then create a class resolver, and each time a class is resolved, set the log level for that class to the specified level:
42
+ if levels.any?
43
+ resolver = Resolver.new
44
+
45
+ levels.each do |level, names|
46
+ resolver.bind(names) do |klass|
47
+ logger.enable(klass, level)
48
+ end
49
+ end
50
+
51
+ return resolver
52
+ end
53
+ end
54
+
25
55
  def initialize
26
56
  @names = {}
27
57
 
@@ -24,6 +24,9 @@ require_relative '../event'
24
24
  require_relative 'text'
25
25
  require_relative 'xterm'
26
26
 
27
+ require 'json'
28
+ require 'fiber'
29
+
27
30
  module Console
28
31
  module Terminal
29
32
  # This, and all related methods, is considered private.
@@ -64,7 +67,6 @@ module Console
64
67
  @verbose = verbose
65
68
  end
66
69
 
67
- @terminal[:logger_prefix] ||= @terminal.style(nil, nil, nil)
68
70
  @terminal[:logger_suffix] ||= @terminal.style(:white, nil, :faint)
69
71
  @terminal[:subject] ||= @terminal.style(nil, nil, :bold)
70
72
  @terminal[:debug] = @terminal.style(:cyan)
@@ -77,7 +79,9 @@ module Console
77
79
  end
78
80
 
79
81
  attr :io
82
+
80
83
  attr_accessor :verbose
84
+
81
85
  attr :start
82
86
  attr :terminal
83
87
 
@@ -94,7 +98,7 @@ module Console
94
98
 
95
99
  UNKNOWN = 'unknown'
96
100
 
97
- def call(subject = nil, *arguments, name: nil, severity: UNKNOWN, &block)
101
+ def call(subject = nil, *arguments, name: nil, severity: UNKNOWN, **options, &block)
98
102
  prefix = build_prefix(name || severity.to_s)
99
103
  indent = " " * prefix.size
100
104
 
@@ -104,6 +108,10 @@ module Console
104
108
  format_subject(severity, prefix, subject, buffer)
105
109
  end
106
110
 
111
+ if options&.any?
112
+ format_options(options, buffer)
113
+ end
114
+
107
115
  arguments.each do |argument|
108
116
  format_argument(argument, buffer)
109
117
  end
@@ -121,10 +129,14 @@ module Console
121
129
 
122
130
  protected
123
131
 
132
+ def format_options(options, output)
133
+ format_value(options.to_json, output)
134
+ end
135
+
124
136
  def format_argument(argument, output)
125
137
  case argument
126
138
  when Exception
127
- Event::Failure.new(argument).format(output, @terminal, @verbose)
139
+ Event::Failure.for(argument).format(output, @terminal, @verbose)
128
140
  when Event::Generic
129
141
  argument.format(output, @terminal, @verbose)
130
142
  else
@@ -142,11 +154,21 @@ module Console
142
154
  end
143
155
  end
144
156
 
157
+ def default_suffix(object = nil)
158
+ buffer = +" #{@terminal[:logger_suffix]}"
159
+
160
+ if object
161
+ buffer << "[oid=0x#{object.object_id.to_s(16)}] "
162
+ end
163
+
164
+ buffer << "[ec=0x#{Fiber.current.object_id.to_s(16)}] [pid=#{Process.pid}] [#{::Time.now}]#{@terminal.reset}"
165
+ end
166
+
145
167
  def format_object_subject(severity, prefix, subject, output)
146
168
  prefix_style = @terminal[severity]
147
169
 
148
170
  if @verbose
149
- suffix = " #{@terminal[:logger_suffix]}[oid=0x#{subject.object_id.to_s(16)}] [pid=#{Process.pid}] [#{Time.now}]#{@terminal.reset}"
171
+ suffix = default_suffix(subject)
150
172
  end
151
173
 
152
174
  prefix = "#{prefix_style}#{prefix}:#{@terminal.reset} "
@@ -158,7 +180,7 @@ module Console
158
180
  prefix_style = @terminal[severity]
159
181
 
160
182
  if @verbose
161
- suffix = " #{@terminal[:logger_suffix]}[pid=#{Process.pid}] [#{Time.now}]#{@terminal.reset}"
183
+ suffix = default_suffix
162
184
  end
163
185
 
164
186
  prefix = "#{prefix_style}#{prefix}:#{@terminal.reset} "
@@ -175,15 +197,7 @@ module Console
175
197
  end
176
198
 
177
199
  def time_offset_prefix
178
- offset = Time.now - @start_at
179
- minutes = (offset/60).floor
180
- seconds = (offset - (minutes*60))
181
-
182
- if minutes > 0
183
- "#{minutes}m#{seconds.floor}s"
184
- else
185
- "#{seconds.round(2)}s"
186
- end.rjust(6)
200
+ Clock.formatted_duration(Time.now - @start_at).rjust(6)
187
201
  end
188
202
 
189
203
  def build_prefix(name)
@@ -19,5 +19,5 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  module Console
22
- VERSION = "1.9.0"
22
+ VERSION = "1.11.0"
23
23
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: console
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.9.0
4
+ version: 1.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-27 00:00:00.000000000 Z
11
+ date: 2021-04-10 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: fiber-local
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: bake
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -90,14 +104,18 @@ files:
90
104
  - lib/console.rb
91
105
  - lib/console/buffer.rb
92
106
  - lib/console/capture.rb
107
+ - lib/console/clock.rb
93
108
  - lib/console/event.rb
94
109
  - lib/console/event/failure.rb
95
110
  - lib/console/event/generic.rb
111
+ - lib/console/event/measure.rb
112
+ - lib/console/event/metric.rb
96
113
  - lib/console/event/progress.rb
97
114
  - lib/console/event/spawn.rb
98
115
  - lib/console/filter.rb
99
116
  - lib/console/logger.rb
100
117
  - lib/console/measure.rb
118
+ - lib/console/progress.rb
101
119
  - lib/console/resolver.rb
102
120
  - lib/console/serialized/logger.rb
103
121
  - lib/console/split.rb
@@ -125,7 +143,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
125
143
  - !ruby/object:Gem::Version
126
144
  version: '0'
127
145
  requirements: []
128
- rubygems_version: 3.1.2
146
+ rubygems_version: 3.2.3
129
147
  signing_key:
130
148
  specification_version: 4
131
149
  summary: Beautiful logging for Ruby.