console 1.19.0 → 1.29.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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/bake/console.rb +3 -3
  4. data/lib/console/adapter.rb +2 -1
  5. data/lib/console/capture.rb +46 -17
  6. data/lib/console/compatible/logger.rb +4 -3
  7. data/lib/console/event/failure.rb +43 -39
  8. data/lib/console/event/generic.rb +9 -6
  9. data/lib/console/event/spawn.rb +35 -27
  10. data/lib/console/event.rb +3 -4
  11. data/lib/console/filter.rb +24 -10
  12. data/lib/console/format/safe.rb +136 -0
  13. data/lib/console/format.rb +14 -0
  14. data/lib/console/interface.rb +53 -0
  15. data/lib/console/logger.rb +17 -16
  16. data/lib/console/output/default.rb +8 -5
  17. data/lib/console/output/failure.rb +32 -0
  18. data/lib/console/output/null.rb +5 -1
  19. data/lib/console/output/sensitive.rb +12 -10
  20. data/lib/console/{serialized/logger.rb → output/serialized.rb} +14 -49
  21. data/lib/console/output/split.rb +3 -3
  22. data/lib/console/{terminal/logger.rb → output/terminal.rb} +98 -54
  23. data/lib/console/output/wrapper.rb +28 -0
  24. data/lib/console/output.rb +7 -8
  25. data/lib/console/progress.rb +20 -9
  26. data/lib/console/resolver.rb +5 -5
  27. data/lib/console/terminal/formatter/failure.rb +57 -0
  28. data/lib/console/terminal/formatter/progress.rb +58 -0
  29. data/lib/console/terminal/formatter/spawn.rb +42 -0
  30. data/lib/console/terminal/text.rb +6 -2
  31. data/lib/console/terminal/xterm.rb +10 -3
  32. data/lib/console/terminal.rb +19 -2
  33. data/lib/console/version.rb +2 -2
  34. data/lib/console/warn.rb +33 -0
  35. data/lib/console.rb +4 -22
  36. data/license.md +5 -1
  37. data/readme.md +39 -2
  38. data/releases.md +25 -0
  39. data.tar.gz.sig +0 -0
  40. metadata +31 -92
  41. metadata.gz.sig +0 -0
  42. data/lib/console/buffer.rb +0 -25
  43. data/lib/console/event/progress.rb +0 -60
  44. data/lib/console/output/json.rb +0 -16
  45. data/lib/console/output/text.rb +0 -16
  46. data/lib/console/output/xterm.rb +0 -16
  47. data/lib/console/split.rb +0 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e68f107945b1831b98497a3a7d6a251b030788dfb0f85e212af7da334816f791
4
- data.tar.gz: 0460616df8b87571a01eba8d41cdfa2c30ae2efb4c7847c322273cc1de8d74fd
3
+ metadata.gz: 1fa166f5900ef3a9c90b337ce6dd57faa99527fd9746a47aa61bb7f7535e606d
4
+ data.tar.gz: c6d7d06fdd475006e000c3da6d65fdcd3a5c58e10c90afa8b380f795d45a16d1
5
5
  SHA512:
6
- metadata.gz: 9d7f4c0af841a44c643e24dec336bb1a71642fe292fe204382282e6ef1766a6fe6ec4390486c6e9b3bc6d841b5d603739711ed14848fc4024f6d9c2b169e2e09
7
- data.tar.gz: 65399cb6f4c81c5bbe67a746797fa37a96d8a01f1b540abdb01cccad73ea174439a2c27131db84fa2ee80b6860a69150ed77a19e9d3a298cc22dc1e6ed4a97bf
6
+ metadata.gz: 620b134d260d72d3b69a57e455584d556d611aa8a441fbd7624722b1cfaf3541015335717b82da8ea2aa0682f94e2898c9f28c4162fa5b5842297035f81bd5c0
7
+ data.tar.gz: be9318b5f775a9bd33f5c8951ffae4b921dbe0618f209080645a3eb12dcd096ef092d94f1239564e03497961ebbfbbb926d43294b8b524a94486e6b62a835b1a
checksums.yaml.gz.sig CHANGED
Binary file
data/bake/console.rb CHANGED
@@ -1,18 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2020-2022, by Samuel Williams.
4
+ # Copyright, 2020-2024, by Samuel Williams.
5
5
 
6
6
  # Increase the verbosity of the logger to info.
7
7
  def info
8
- require_relative '../lib/console'
8
+ require_relative "../lib/console"
9
9
 
10
10
  Console.logger.info!
11
11
  end
12
12
 
13
13
  # Increase the verbosity of the logger to debug.
14
14
  def debug
15
- require_relative '../lib/console'
15
+ require_relative "../lib/console"
16
16
 
17
17
  Console.logger.debug!
18
18
  end
@@ -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,35 +1,54 @@
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 'filter'
6
+ require_relative "filter"
7
+ require_relative "output/failure"
7
8
 
8
9
  module Console
9
10
  # A general sink which captures all events into a buffer.
10
11
  class Capture
11
12
  def initialize
12
- @buffer = []
13
+ @records = []
13
14
  @verbose = false
14
15
  end
15
16
 
16
- attr :buffer
17
+ attr :records
18
+
19
+ # @deprecated Use {#records} instead of {#buffer}.
20
+ alias buffer records
21
+
22
+ alias to_a records
23
+
17
24
  attr :verbose
18
25
 
19
- def last
20
- @buffer.last
26
+ def include?(pattern)
27
+ @records.any? do |record|
28
+ record[:subject].to_s&.match?(pattern) or record[:message].to_s&.match?(pattern)
29
+ end
21
30
  end
22
31
 
23
- def include?(pattern)
24
- JSON.dump(@buffer).include?(pattern)
32
+ def each(&block)
33
+ @records.each(&block)
34
+ end
35
+
36
+ include Enumerable
37
+
38
+ def first
39
+ @records.first
40
+ end
41
+
42
+ def last
43
+ @records.last
25
44
  end
26
45
 
27
46
  def clear
28
- @buffer.clear
47
+ @records.clear
29
48
  end
30
49
 
31
50
  def empty?
32
- @buffer.empty?
51
+ @records.empty?
33
52
  end
34
53
 
35
54
  def verbose!(value = true)
@@ -40,32 +59,42 @@ module Console
40
59
  @verbose
41
60
  end
42
61
 
43
- def call(subject = nil, *arguments, severity: UNKNOWN, **options, &block)
44
- message = {
62
+ def call(subject = nil, *arguments, severity: UNKNOWN, event: nil, **options, &block)
63
+ record = {
45
64
  time: ::Time.now.iso8601,
46
65
  severity: severity,
47
66
  **options,
48
67
  }
49
68
 
50
69
  if subject
51
- message[:subject] = subject
70
+ record[:subject] = subject
71
+ end
72
+
73
+ if event
74
+ record[:event] = event.to_hash
52
75
  end
53
76
 
54
77
  if arguments.any?
55
- message[:arguments] = arguments
78
+ record[:arguments] = arguments
79
+ end
80
+
81
+ if annotation = Fiber.current.annotation
82
+ record[:annotation] = annotation
56
83
  end
57
84
 
58
85
  if block_given?
59
86
  if block.arity.zero?
60
- message[:message] = yield
87
+ record[:message] = yield
61
88
  else
62
89
  buffer = StringIO.new
63
90
  yield buffer
64
- message[:message] = buffer.string
91
+ record[:message] = buffer.string
65
92
  end
93
+ else
94
+ record[:message] = arguments.join(" ")
66
95
  end
67
96
 
68
- @buffer << message
97
+ @records << record
69
98
  end
70
99
  end
71
100
  end
@@ -1,12 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2022, by Samuel Williams.
4
+ # Copyright, 2022-2024, by Samuel Williams.
5
5
 
6
- require 'logger'
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)
@@ -29,7 +30,7 @@ module Console
29
30
  end
30
31
  end
31
32
 
32
- def initialize(subject, output)
33
+ def initialize(subject, output = Console)
33
34
  super(nil)
34
35
 
35
36
  @progname = subject
@@ -1,73 +1,77 @@
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
+ # Copyright, 2024, by Patrik Wenger.
6
7
 
7
- require_relative 'generic'
8
+ require_relative "generic"
8
9
 
9
10
  module Console
10
11
  module Event
12
+ # Represents a failure event.
13
+ #
14
+ # ```ruby
15
+ # Console::Event::Failure.for(exception).emit(self)
16
+ # ```
11
17
  class Failure < Generic
12
- def self.current_working_directory
18
+ def self.default_root
13
19
  Dir.getwd
14
20
  rescue # e.g. Errno::EMFILE
15
21
  nil
16
22
  end
17
23
 
18
24
  def self.for(exception)
19
- self.new(exception, self.current_working_directory)
25
+ self.new(exception, self.default_root)
20
26
  end
21
27
 
22
- def initialize(exception, root = nil)
23
- @exception = exception
24
- @root = root
28
+ def self.log(subject, exception, **options)
29
+ Console.error(subject, **self.for(exception).to_hash, **options)
25
30
  end
26
31
 
27
- attr :exception
28
- attr :root
32
+ attr_reader :exception
29
33
 
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)
34
+ def initialize(exception, root = Dir.getwd)
35
+ @exception = exception
36
+ @root = root
36
37
  end
37
38
 
38
- def to_h
39
- {exception: @exception, root: @root}
39
+ def to_hash
40
+ Hash.new.tap do |hash|
41
+ hash[:type] = :failure
42
+ hash[:root] = @root if @root
43
+ extract(@exception, hash)
44
+ end
40
45
  end
41
46
 
42
- def format(output, terminal, verbose)
43
- format_exception(@exception, nil, output, terminal, verbose)
47
+ def emit(*arguments, **options)
48
+ options[:severity] ||= :error
49
+
50
+ super
44
51
  end
45
52
 
46
- def format_exception(exception, prefix, output, terminal, verbose)
47
- lines = exception.message.lines.map(&:chomp)
48
-
49
- output.puts " #{prefix}#{terminal[:exception_title]}#{exception.class}#{terminal.reset}: #{lines.shift}"
50
-
51
- lines.each do |line|
52
- output.puts " #{terminal[:exception_detail]}#{line}#{terminal.reset}"
53
- end
54
-
55
- root_pattern = /^#{@root}\// if @root
53
+ private
54
+
55
+ def extract(exception, hash)
56
+ hash[:class] = exception.class.name
56
57
 
57
- exception.backtrace&.each_with_index do |line, index|
58
- path, offset, message = line.split(":")
59
- style = :exception_backtrace
58
+ if exception.respond_to?(:detailed_message)
59
+ message = exception.detailed_message
60
60
 
61
- # Make the path a bit more readable
62
- if root_pattern and path.sub!(root_pattern, "").nil?
63
- style = :exception_backtrace_other
64
- end
61
+ # We want to remove the trailling exception class as we format it differently:
62
+ message.sub!(/\s*\(.*?\)$/, "")
65
63
 
66
- output.puts " #{index == 0 ? "→" : " "} #{terminal[style]}#{path}:#{offset}#{terminal[:exception_message]} #{message}#{terminal.reset}"
64
+ hash[:message] = message
65
+ else
66
+ hash[:message] = exception.message
67
67
  end
68
68
 
69
- if exception.cause
70
- format_exception(exception.cause, "Caused by ", output, terminal, verbose)
69
+ hash[:backtrace] = exception.backtrace
70
+
71
+ if cause = exception.cause
72
+ hash[:cause] = Hash.new.tap do |cause_hash|
73
+ extract(cause, cause_hash)
74
+ end
71
75
  end
72
76
  end
73
77
  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
- require_relative 'generic'
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
- require_relative 'event/spawn'
7
- require_relative 'event/failure'
8
- require_relative 'event/progress'
6
+ require_relative "event/spawn"
7
+ require_relative "event/failure"
@@ -1,17 +1,26 @@
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, 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
13
+ if Object.const_defined?(:Ractor) and RUBY_VERSION >= "3.1"
14
+ def self.define_immutable_method(name, &block)
15
+ block = Ractor.make_shareable(block)
16
+ self.define_method(name, &block)
17
+ end
18
+ else
19
+ def self.define_immutable_method(name, &block)
20
+ define_method(name, &block)
21
+ end
22
+ end
23
+
15
24
  def self.[] **levels
16
25
  klass = Class.new(self)
17
26
  minimum_level, maximum_level = levels.values.minmax
@@ -24,17 +33,17 @@ module Console
24
33
  levels.each do |name, level|
25
34
  const_set(name.to_s.upcase, level)
26
35
 
27
- define_method(name) do |subject = nil, *arguments, **options, &block|
36
+ define_immutable_method(name) do |subject = nil, *arguments, **options, &block|
28
37
  if self.enabled?(subject, level)
29
- self.call(subject, *arguments, severity: name, **options, **@options, &block)
38
+ @output.call(subject, *arguments, severity: name, **@options, **options, &block)
30
39
  end
31
40
  end
32
41
 
33
- define_method("#{name}!") do
42
+ define_immutable_method("#{name}!") do
34
43
  @level = level
35
44
  end
36
45
 
37
- define_method("#{name}?") do
46
+ define_immutable_method("#{name}?") do
38
47
  @level <= level
39
48
  end
40
49
  end
@@ -134,8 +143,13 @@ module Console
134
143
  @subjects.delete(subject)
135
144
  end
136
145
 
137
- def call(*arguments, **options, &block)
138
- @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
139
153
  end
140
154
  end
141
155
  end
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2023-2024, by Samuel Williams.
5
+
6
+ require "json"
7
+
8
+ module Console
9
+ module Format
10
+ # This class is used to safely dump objects.
11
+ # It will attempt to dump the object using the given format, but if it fails, it will generate a safe version of the object.
12
+ class Safe
13
+ def initialize(format: ::JSON, limit: 8, encoding: ::Encoding::UTF_8)
14
+ @format = format
15
+ @limit = limit
16
+ @encoding = encoding
17
+ end
18
+
19
+ def dump(object)
20
+ @format.dump(object, @limit)
21
+ rescue SystemStackError, StandardError => error
22
+ @format.dump(safe_dump(object, error))
23
+ end
24
+
25
+ private
26
+
27
+ def filter_backtrace(error)
28
+ frames = error.backtrace
29
+ filtered = {}
30
+ filtered_count = nil
31
+ skipped = nil
32
+
33
+ frames = frames.filter_map do |frame|
34
+ if filtered[frame]
35
+ if filtered_count == nil
36
+ filtered_count = 1
37
+ skipped = frame.dup
38
+ else
39
+ filtered_count += 1
40
+ nil
41
+ end
42
+ else
43
+ if skipped
44
+ if filtered_count > 1
45
+ skipped.replace("[... #{filtered_count} frames skipped ...]")
46
+ end
47
+
48
+ filtered_count = nil
49
+ skipped = nil
50
+ end
51
+
52
+ filtered[frame] = true
53
+ frame
54
+ end
55
+ end
56
+
57
+ if skipped && filtered_count > 1
58
+ skipped.replace("[... #{filtered_count} frames skipped ...]")
59
+ end
60
+
61
+ return frames
62
+ end
63
+
64
+ def safe_dump(object, error)
65
+ object = safe_dump_recurse(object)
66
+
67
+ object[:truncated] = true
68
+ object[:error] = {
69
+ class: safe_dump_recurse(error.class.name),
70
+ message: safe_dump_recurse(error.message),
71
+ backtrace: safe_dump_recurse(filter_backtrace(error)),
72
+ }
73
+
74
+ return object
75
+ end
76
+
77
+ def replacement_for(object)
78
+ case object
79
+ when Array
80
+ "[...]"
81
+ when Hash
82
+ "{...}"
83
+ else
84
+ "..."
85
+ end
86
+ end
87
+
88
+ def default_objects
89
+ Hash.new.compare_by_identity
90
+ end
91
+
92
+ # This will recursively generate a safe version of the object.
93
+ # Nested hashes and arrays will be transformed recursively.
94
+ # Strings will be encoded with the given encoding.
95
+ # Primitive values will be returned as-is.
96
+ # Other values will be converted using `as_json` if available, otherwise `to_s`.
97
+ def safe_dump_recurse(object, limit = @limit, objects = default_objects)
98
+ if limit <= 0 || objects[object]
99
+ return replacement_for(object)
100
+ end
101
+
102
+ case object
103
+ when Hash
104
+ objects[object] = true
105
+
106
+ object.to_h do |key, value|
107
+ [
108
+ String(key).encode(@encoding, invalid: :replace, undef: :replace),
109
+ safe_dump_recurse(value, limit - 1, objects)
110
+ ]
111
+ end
112
+ when Array
113
+ objects[object] = true
114
+
115
+ object.map do |value|
116
+ safe_dump_recurse(value, limit - 1, objects)
117
+ end
118
+ when String
119
+ object.encode(@encoding, invalid: :replace, undef: :replace)
120
+ when Numeric, TrueClass, FalseClass, NilClass
121
+ object
122
+ else
123
+ objects[object] = true
124
+
125
+ # We could do something like this but the chance `as_json` will blow up.
126
+ # We'd need to be extremely careful about it.
127
+ # if object.respond_to?(:as_json)
128
+ # safe_dump_recurse(object.as_json, limit - 1, objects)
129
+ # else
130
+
131
+ safe_dump_recurse(object.to_s, limit - 1, objects)
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2023-2024, by Samuel Williams.
5
+
6
+ require_relative "format/safe"
7
+
8
+ module Console
9
+ module Format
10
+ def self.default
11
+ Safe.new(format: ::JSON)
12
+ end
13
+ end
14
+ end