console 1.10.0 → 1.12.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: 640269ce4b1afa8413ce60f657ab3c3ed01a036a32e554c8643aa599a0052422
4
- data.tar.gz: b5fa60cc24de752c16af2160856fe0cfe3c038f24a409e9d3069d701afbfcb9b
3
+ metadata.gz: '019b21198110cd3da94ef40ce3d666ea19be493e1ef2fa85e818bdd03d67e864'
4
+ data.tar.gz: 33994cbf5c6349aed18c79f3b20527785de1dd1c2307c49b864382750c2d807d
5
5
  SHA512:
6
- metadata.gz: 78b6e1c53280b26a315a422a418f36d228a583f095b41a406a33c67808ac2c299ab33399641880d5d3b7753d0ad88262fc536deac6ddd1ef6c8edf970b7d591e
7
- data.tar.gz: d24238b81168471f70c4404badf944b24f01a92101b841513cb0ced7c6998b4a63efabb88bad49c042e72c174b1c56f2cd51cf385f7814d41d709cde040e1ab3
6
+ metadata.gz: dc7b76666ff0f3dda0cfaf3d966f8cd00bae86d0930f34452e6dcd20dc42fac72d2ba9a7533f6dc9bf7790cdb4e1c98c97830434bc36a463ae156104c6caa657
7
+ data.tar.gz: 9bc0323787283b1fd20357914b1ff12eb1cd19bc20f68c7132bacf7533817829f514d5b408b5421366bf07a38265417ad3b3d93cc1984844affe5d91b0f0889c
data/lib/console.rb CHANGED
@@ -20,6 +20,7 @@
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
25
 
25
26
  module Console
@@ -33,6 +33,10 @@ module Console
33
33
  @buffer.last
34
34
  end
35
35
 
36
+ def include?(pattern)
37
+ JSON.dump(@buffer).include?(pattern)
38
+ end
39
+
36
40
  def clear
37
41
  @buffer.clear
38
42
  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
@@ -47,6 +47,8 @@ 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)
50
52
  end
51
53
 
52
54
  def to_h
@@ -66,13 +68,18 @@ module Console
66
68
  output.puts " #{terminal[:exception_detail]}#{line}#{terminal.reset}"
67
69
  end
68
70
 
71
+ root_pattern = /^#{@root}\// if @root
72
+
69
73
  exception.backtrace&.each_with_index do |line, index|
70
74
  path, offset, message = line.split(":")
75
+ style = :exception_backtrace
71
76
 
72
77
  # Make the path a bit more readable
73
- path.gsub!(/^#{@root}\//, "./") if @root
78
+ if root_pattern and path.sub!(root_pattern, "").nil?
79
+ style = :exception_backtrace_other
80
+ end
74
81
 
75
- 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}"
76
83
  end
77
84
 
78
85
  if exception.cause
@@ -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
@@ -19,6 +19,7 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  require_relative 'generic'
22
+ require_relative '../clock'
22
23
 
23
24
  module Console
24
25
  module Event
@@ -41,8 +42,16 @@ module Console
41
42
  {name: @name, value: @value, tags: @tags}
42
43
  end
43
44
 
45
+ def value_string
46
+ "#{@name}: #{@value}"
47
+ end
48
+
44
49
  def format(output, terminal, verbose)
45
- output.puts "#{@name}=#{@value} #{@tags.inspect}"
50
+ if @tags&.any?
51
+ output.puts "#{value_string} #{@tags.inspect}"
52
+ else
53
+ output.puts value_string
54
+ end
46
55
  end
47
56
  end
48
57
  end
@@ -26,11 +26,12 @@ module Console
26
26
  class Filter
27
27
  def self.[] **levels
28
28
  klass = Class.new(self)
29
+ min_level, max_level = levels.values.minmax
29
30
 
30
31
  klass.instance_exec do
31
32
  const_set(:LEVELS, levels)
32
- const_set(:MINIMUM_LEVEL, levels.values.min)
33
- const_set(:MAXIMUM_LEVEL, levels.values.max)
33
+ const_set(:MINIMUM_LEVEL, min_level)
34
+ const_set(:MAXIMUM_LEVEL, max_level)
34
35
 
35
36
  levels.each do |name, level|
36
37
  const_set(name.to_s.upcase, level)
@@ -102,7 +103,7 @@ module Console
102
103
  end
103
104
 
104
105
  def all!
105
- @level = -1
106
+ @level = self.class::MINIMUM_LEVEL - 1
106
107
  end
107
108
 
108
109
  # You can enable and disable logging for classes. This function checks if logging for a given subject is enabled.
@@ -18,11 +18,14 @@
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 'output'
21
22
  require_relative 'filter'
23
+ require_relative 'measure'
22
24
  require_relative 'progress'
23
25
 
24
26
  require_relative 'resolver'
25
27
  require_relative 'terminal/logger'
28
+ require_relative 'serialized/logger'
26
29
 
27
30
  require 'fiber/local'
28
31
 
@@ -34,7 +37,7 @@ module Console
34
37
  # You can also specify CONSOLE_LEVEL=debug or CONSOLE_LEVEL=info in environment.
35
38
  # https://mislav.net/2011/06/ruby-verbose-mode/ has more details about how it all fits together.
36
39
  def self.default_log_level(env = ENV)
37
- if level = (env['CONSOLE_LEVEL'] || env['CONSOLE_LOG_LEVEL'])
40
+ if level = env['CONSOLE_LEVEL']
38
41
  LEVELS[level.to_sym] || level.to_i
39
42
  elsif $DEBUG
40
43
  DEBUG
@@ -50,17 +53,25 @@ module Console
50
53
  !$VERBOSE.nil? || env['CONSOLE_VERBOSE']
51
54
  end
52
55
 
53
- def self.default_logger(output, verbose: self.verbose?, level: self.default_log_level)
54
- terminal = Terminal::Logger.new(output, verbose: verbose)
56
+ def self.default_logger(output = $stderr, env = ENV, **options)
57
+ if options[:verbose].nil?
58
+ options[:verbose] = self.verbose?(env)
59
+ end
60
+
61
+ if options[:level].nil?
62
+ options[:level] = self.default_log_level(env)
63
+ end
64
+
65
+ output = Output.new(output, env, **options)
66
+ logger = self.new(output, **options)
55
67
 
56
- logger = self.new(terminal, verbose: verbose, level: level)
57
- resolver = Resolver.default_resolver(logger)
68
+ Resolver.default_resolver(logger)
58
69
 
59
70
  return logger
60
71
  end
61
72
 
62
73
  def self.local
63
- self.default_logger($stderr)
74
+ self.default_logger
64
75
  end
65
76
 
66
77
  DEFAULT_LEVEL = 1
@@ -73,8 +84,15 @@ module Console
73
84
  Progress.new(self, subject, total, **options)
74
85
  end
75
86
 
76
- # @deprecated Please use {progress}.
77
- alias measure progress
87
+ def measure(subject, name = "block", **tags, &block)
88
+ measure = Measure.new(self, subject, **tags)
89
+
90
+ if block_given?
91
+ return measure.duration(name, &block)
92
+ else
93
+ return measure
94
+ end
95
+ end
78
96
 
79
97
  def failure(subject, exception, *arguments, &block)
80
98
  fatal(subject, *arguments, Event::Failure.new(exception), &block)
@@ -0,0 +1,49 @@
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 'event/measure'
22
+ require_relative 'clock'
23
+
24
+ module Console
25
+ class Measure
26
+ def initialize(output, subject, **tags)
27
+ @output = output
28
+ @subject = subject
29
+ @tags = tags
30
+ end
31
+
32
+ attr :tags
33
+
34
+ # Measure the execution of a block of code.
35
+ def duration(name, &block)
36
+ @output.info(@subject) {Event::Enter.new(name)}
37
+
38
+ start_time = Clock.now
39
+
40
+ result = yield(self)
41
+
42
+ duration = Clock.now - start_time
43
+
44
+ @output.info(@subject) {Event::Exit.new(name, duration, **@tags)}
45
+
46
+ return result
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,40 @@
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
+ require_relative 'output/default'
22
+ require_relative 'output/json'
23
+ require_relative 'output/text'
24
+ require_relative 'output/xterm'
25
+
26
+ module Console
27
+ module Output
28
+ def self.new(output = nil, env = ENV, **options)
29
+ if names = env['CONSOLE_OUTPUT']
30
+ names = names.split(',').map(&:to_sym).reverse
31
+
32
+ names.inject(output) do |output, name|
33
+ Output.const_get(name).new(output, **options)
34
+ end
35
+ else
36
+ return Output::Default.new(output, **options)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,38 @@
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
+ require_relative 'xterm'
22
+ require_relative 'json'
23
+
24
+ module Console
25
+ module Output
26
+ module Default
27
+ def self.new(output, **options)
28
+ output ||= $stderr
29
+
30
+ if output.tty?
31
+ XTerm.new(output, **options)
32
+ else
33
+ JSON.new(output, **options)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,31 @@
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
+ require_relative '../serialized/logger'
22
+
23
+ module Console
24
+ module Output
25
+ module JSON
26
+ def self.new(output, **options)
27
+ Serialized::Logger.new(output, format: ::JSON, **options)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,118 @@
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
+ require_relative '../serialized/logger'
22
+
23
+ module Console
24
+ module Output
25
+ class Sensitive
26
+ def initialize(output, **options)
27
+ @output = output
28
+ end
29
+
30
+ REDACT = /
31
+ phone
32
+ | email
33
+ | full_?name
34
+ | first_?name
35
+ | last_?name
36
+
37
+ | device_name
38
+ | user_agent
39
+
40
+ | zip
41
+ | address
42
+ | location
43
+ | latitude
44
+ | longitude
45
+
46
+ | ip
47
+ | gps
48
+
49
+ | sex
50
+ | gender
51
+
52
+ | token
53
+ | password
54
+ /xi
55
+
56
+ def redact?(text)
57
+ text.match?(REDACT)
58
+ end
59
+
60
+ def redact_hash(arguments, filter)
61
+ arguments.transform_values do |value|
62
+ redact(value, filter)
63
+ end
64
+ end
65
+
66
+ def redact_array(array, filter)
67
+ array.map do |value|
68
+ redact(value, filter)
69
+ end
70
+ end
71
+
72
+ def redact(argument, filter)
73
+ case argument
74
+ when String
75
+ if filter
76
+ filter.call(argument)
77
+ elsif redact?(argument)
78
+ "[REDACTED]"
79
+ else
80
+ argument
81
+ end
82
+ when Array
83
+ redact_array(argument, filter)
84
+ when Hash
85
+ redact_hash(argument, filter)
86
+ else
87
+ redact(argument.to_s, filter)
88
+ end
89
+ end
90
+
91
+ class Filter
92
+ def initialize(substitutions)
93
+ @substitutions = substitutions
94
+ @pattern = Regexp.union(substitutions.keys)
95
+ end
96
+
97
+ def call(text)
98
+ text.gsub(@pattern, @substitutions)
99
+ end
100
+ end
101
+
102
+ def call(subject = nil, *arguments, sensitive: true, **options, &block)
103
+ if sensitive
104
+ if sensitive.respond_to?(:call)
105
+ filter = sensitive
106
+ elsif sensitive.is_a?(Hash)
107
+ filter = Filter.new(sensitive)
108
+ end
109
+
110
+ subject = redact(subject, filter)
111
+ arguments = redact_array(arguments, filter)
112
+ end
113
+
114
+ @output.call(subject, *arguments, **options)
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,31 @@
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
+ require_relative '../terminal/logger'
22
+
23
+ module Console
24
+ module Output
25
+ module Text
26
+ def self.new(output, **options)
27
+ Terminal::Logger.new(output, format: Terminal::Text, **options)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,31 @@
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
+ require_relative '../terminal/logger'
22
+
23
+ module Console
24
+ module Output
25
+ module XTerm
26
+ def self.new(output, **options)
27
+ Terminal::Logger.new(output, format: Terminal::XTerm, **options)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -19,6 +19,7 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  require_relative 'event/progress'
22
+ require_relative 'clock'
22
23
 
23
24
  module Console
24
25
  class Progress
@@ -93,7 +94,7 @@ module Console
93
94
 
94
95
  def to_s
95
96
  if estimated_remaining_time = self.estimated_remaining_time
96
- "#{@current}/#{@total} completed in #{self.formatted_duration self.duration}, #{self.formatted_duration estimated_remaining_time} remaining."
97
+ "#{@current}/#{@total} completed in #{Clock.formatted_duration(self.duration)}, #{Clock.formatted_duration(estimated_remaining_time)} remaining."
97
98
  else
98
99
  "#{@current}/#{@total} completed, waiting for estimate..."
99
100
  end
@@ -108,33 +109,13 @@ module Console
108
109
  end
109
110
 
110
111
  def output?
111
- if duration = duration_since_last_output
112
+ if remaining.zero?
113
+ return true
114
+ elsif duration = duration_since_last_output
112
115
  return duration > @minimum_output_duration
113
116
  else
114
117
  return true
115
118
  end
116
119
  end
117
-
118
- def formatted_duration(duration)
119
- if duration < 60.0
120
- return "#{duration.round(2)}s"
121
- end
122
-
123
- duration /= 60.0
124
-
125
- if duration < 60.0
126
- return "#{duration.round}m"
127
- end
128
-
129
- duration /= 60.0
130
-
131
- if duration < 60.0
132
- return "#{duration.round(1)}h"
133
- end
134
-
135
- duration /= 24.0
136
-
137
- return "#{duration.round(1)}d"
138
- end
139
120
  end
140
121
  end
@@ -26,6 +26,8 @@ module Console
26
26
  #
27
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
28
  #
29
+ # You can enable all log levels for a given class by using `CONSOLE_ON=MyClass`. Similarly you can disable all logging using `CONSOLE_OFF=MyClass`.
30
+ #
29
31
  # @parameter logger [Logger] A logger instance to set the logging levels on.
30
32
  # @parameter env [Hash] The environment to read levels from.
31
33
  #
@@ -33,23 +35,42 @@ module Console
33
35
  # @returns [Resolver] If there were custom logging levels, then the created resolver is returned.
34
36
  def self.default_resolver(logger, env = ENV)
35
37
  # Find all CONSOLE_<LEVEL> variables from environment:
36
- levels = Logger::LEVELS
38
+ levels = logger.class::LEVELS
37
39
  .map{|label, level| [level, env["CONSOLE_#{label.upcase}"]&.split(',')]}
38
40
  .to_h
39
41
  .compact
40
42
 
43
+ off_klasses = env['CONSOLE_OFF']&.split(',')
44
+ on_klasses = env['CONSOLE_ON']&.split(',')
45
+
46
+ resolver = nil
47
+
41
48
  # 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
49
+ if on_klasses&.any?
50
+ resolver ||= Resolver.new
44
51
 
45
- levels.each do |level, names|
46
- resolver.bind(names) do |klass|
47
- logger.enable(klass, level)
48
- end
52
+ resolver.bind(on_klasses) do |klass|
53
+ logger.enable(klass, logger.class::MINIMUM_LEVEL - 1)
49
54
  end
55
+ end
56
+
57
+ if off_klasses&.any?
58
+ resolver ||= Resolver.new
50
59
 
51
- return resolver
60
+ resolver.bind(off_klasses) do |klass|
61
+ logger.disable(klass)
62
+ end
52
63
  end
64
+
65
+ levels.each do |level, names|
66
+ resolver ||= Resolver.new
67
+
68
+ resolver.bind(names) do |klass|
69
+ logger.enable(klass, level)
70
+ end
71
+ end
72
+
73
+ return resolver
53
74
  end
54
75
 
55
76
  def initialize
@@ -27,7 +27,7 @@ require 'json'
27
27
  module Console
28
28
  module Serialized
29
29
  class Logger
30
- def initialize(io = $stderr, format: JSON)
30
+ def initialize(io = $stderr, format: JSON, **options)
31
31
  @io = io
32
32
  @start = Time.now
33
33
  @format = format
@@ -25,6 +25,7 @@ require_relative 'text'
25
25
  require_relative 'xterm'
26
26
 
27
27
  require 'json'
28
+ require 'fiber'
28
29
 
29
30
  module Console
30
31
  module Terminal
@@ -54,11 +55,11 @@ module Console
54
55
  end
55
56
 
56
57
  class Logger
57
- def initialize(io = $stderr, verbose: nil, start_at: Terminal.start_at!, **options)
58
+ def initialize(io = $stderr, verbose: nil, start_at: Terminal.start_at!, format: nil, **options)
58
59
  @io = io
59
60
  @start_at = start_at
60
61
 
61
- @terminal = Terminal.for(io)
62
+ @terminal = format.nil? ? Terminal.for(io) : format.new(io)
62
63
 
63
64
  if verbose.nil?
64
65
  @verbose = !@terminal.colors?
@@ -66,7 +67,6 @@ module Console
66
67
  @verbose = verbose
67
68
  end
68
69
 
69
- @terminal[:logger_prefix] ||= @terminal.style(nil, nil, nil)
70
70
  @terminal[:logger_suffix] ||= @terminal.style(:white, nil, :faint)
71
71
  @terminal[:subject] ||= @terminal.style(nil, nil, :bold)
72
72
  @terminal[:debug] = @terminal.style(:cyan)
@@ -136,7 +136,7 @@ module Console
136
136
  def format_argument(argument, output)
137
137
  case argument
138
138
  when Exception
139
- Event::Failure.new(argument).format(output, @terminal, @verbose)
139
+ Event::Failure.for(argument).format(output, @terminal, @verbose)
140
140
  when Event::Generic
141
141
  argument.format(output, @terminal, @verbose)
142
142
  else
@@ -154,11 +154,21 @@ module Console
154
154
  end
155
155
  end
156
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
+
157
167
  def format_object_subject(severity, prefix, subject, output)
158
168
  prefix_style = @terminal[severity]
159
169
 
160
170
  if @verbose
161
- suffix = " #{@terminal[:logger_suffix]}[oid=0x#{subject.object_id.to_s(16)}] [pid=#{Process.pid}] [#{Time.now}]#{@terminal.reset}"
171
+ suffix = default_suffix(subject)
162
172
  end
163
173
 
164
174
  prefix = "#{prefix_style}#{prefix}:#{@terminal.reset} "
@@ -170,7 +180,7 @@ module Console
170
180
  prefix_style = @terminal[severity]
171
181
 
172
182
  if @verbose
173
- suffix = " #{@terminal[:logger_suffix]}[pid=#{Process.pid}] [#{Time.now}]#{@terminal.reset}"
183
+ suffix = default_suffix
174
184
  end
175
185
 
176
186
  prefix = "#{prefix_style}#{prefix}:#{@terminal.reset} "
@@ -187,15 +197,7 @@ module Console
187
197
  end
188
198
 
189
199
  def time_offset_prefix
190
- offset = Time.now - @start_at
191
- minutes = (offset/60).floor
192
- seconds = (offset - (minutes*60))
193
-
194
- if minutes > 0
195
- "#{minutes}m#{seconds.floor}s"
196
- else
197
- "#{seconds.round(2)}s"
198
- end.rjust(6)
200
+ Clock.formatted_duration(Time.now - @start_at).rjust(6)
199
201
  end
200
202
 
201
203
  def build_prefix(name)
@@ -19,5 +19,5 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  module Console
22
- VERSION = "1.10.0"
22
+ VERSION = "1.12.0"
23
23
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: console
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.10.0
4
+ version: 1.12.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-10-30 00:00:00.000000000 Z
11
+ date: 2021-04-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fiber-local
@@ -104,14 +104,23 @@ files:
104
104
  - lib/console.rb
105
105
  - lib/console/buffer.rb
106
106
  - lib/console/capture.rb
107
+ - lib/console/clock.rb
107
108
  - lib/console/event.rb
108
109
  - lib/console/event/failure.rb
109
110
  - lib/console/event/generic.rb
111
+ - lib/console/event/measure.rb
110
112
  - lib/console/event/metric.rb
111
113
  - lib/console/event/progress.rb
112
114
  - lib/console/event/spawn.rb
113
115
  - lib/console/filter.rb
114
116
  - lib/console/logger.rb
117
+ - lib/console/measure.rb
118
+ - lib/console/output.rb
119
+ - lib/console/output/default.rb
120
+ - lib/console/output/json.rb
121
+ - lib/console/output/sensitive.rb
122
+ - lib/console/output/text.rb
123
+ - lib/console/output/xterm.rb
115
124
  - lib/console/progress.rb
116
125
  - lib/console/resolver.rb
117
126
  - lib/console/serialized/logger.rb
@@ -140,7 +149,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
140
149
  - !ruby/object:Gem::Version
141
150
  version: '0'
142
151
  requirements: []
143
- rubygems_version: 3.1.2
152
+ rubygems_version: 3.2.3
144
153
  signing_key:
145
154
  specification_version: 4
146
155
  summary: Beautiful logging for Ruby.