toys-core 0.24.0 → 0.25.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: fcfc1b86a084e30ed82bd6712d7c4e3d1c0e4754eb4b090181a00a1d0809810a
4
- data.tar.gz: 8c8b9ec8d4186ff53e6a3d5a967db37c274bc5caa57da108207af6d5138f1458
3
+ metadata.gz: c9a692b91f8fcd9e2aee00a360ac14b676ec1876bbf889468440e3827ce8877b
4
+ data.tar.gz: 6fc4cbd7769db055405a17be755dd0dc0e9612f9e5fd00451c3a1480c2924c6c
5
5
  SHA512:
6
- metadata.gz: 71071ccc6322724bac6c56d8ef7889588008a0b29a5119488815f1bc1073556392d0c0697c4762cdd118a93dc1cb605f38a94c81673dcbf7a8c0f756a6063cb6
7
- data.tar.gz: 27b5e9b5b252e5db056ec9a7bb2bcba68fa8d8b1c8dc20ecdbf166ca1aae905d0a68c262bb9c3d3b3f69675c097c9b0f6612494bd61b445115d525d1aa167e6f
6
+ metadata.gz: 9f3850a2d2d3eaf18f9ace3730a4bc34355400b8ff75308e0e739d77a5e664a7377c0fa9bacbd9726be3b99fc4bb5c5c6e742f5171d3ff4185dab90fe50f61f5
7
+ data.tar.gz: f0337ba0809e6e56f233fe22f2e4b423f19b36df4f1163498c8bd1ef6e545301bbbb0e51b12a92a2f218d001d5a9092e739ff088ea5caf6547e07568c4692a04
data/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # Release History
2
2
 
3
+ ### v0.25.0 / 2026-10-10
4
+
5
+ This is a release candidate release toward version 1.0. It includes a variety of fixes and small features, largely around adherence to clig.dev and other common CLI guidelines.
6
+
7
+ * ADDED: Added `Toys::Utils::Terminal.infer_styled`, the rule a terminal uses to decide whether to style output
8
+ * ADDED: The `FORCE_COLOR` environment variable forces styled output on, or off when set to 0 or false
9
+ * ADDED: The `show_help` middleware includes `-h` in the default help flags
10
+ * ADDED: `StandardUI` logs with a simple severity prefix unless verbosity is positive
11
+ * ADDED: Issue warnings for deprecated interfaces
12
+ * ADDED: `Utils::Pager` supports an output option
13
+ * FIXED: The `:push` handler creates a new array rather than appending in place, so that a frozen or non-cloneable default doesn't cause problems
14
+ * FIXED: An empty `NO_COLOR` environment variable no longer disables styled output, per the no-color.org spec
15
+ * FIXED: Styled output is disabled when the `TERM` environment variable is set to `dumb`
16
+ * FIXED: The highline mixin decides whether to use color from its own output stream, and honors `NO_COLOR`, `FORCE_COLOR`, and `TERM=dumb`
17
+ * FIXED: Help text displayed through a pager honors the styled output setting, including `NO_COLOR`, `FORCE_COLOR`, and `TERM=dumb`
18
+ * FIXED: Tool-not-found suggestions include namespaces
19
+ * FIXED: Tool initializers run within the logger level implied by the verbosity
20
+ * FIXED: `StandardUI` no longer colors the `DEBUG` log header
21
+ * FIXED: Re-vendor `simple_xdg` 0.1.2 to pick up home directory fix
22
+ * FIXED: Re-vendor `git_cache` 0.2.0 to report an unwritable cache directory clearly and fix repo lock races
23
+ * FIXED: Show_help writes paged help to the configured stream
24
+ * DOCS: Documentation no longer references a few obsolete features such as the `TOYS_PATH` variable
25
+
3
26
  ### v0.24.0 / 2026-09-11
4
27
 
5
28
  Feature updates:
data/docs/guide.md CHANGED
@@ -705,7 +705,8 @@ cli = Toys::CLI.new(logger_factory: my_logger_factory)
705
705
  #### StandardUI logging
706
706
 
707
707
  {Toys::Utils::StandardUI} implements the logger used by the `toys` gem, which
708
- formats log entries with the severity and timestamp using ANSI coloring.
708
+ formats log entries with a severity prefix using ANSI coloring, and adds a
709
+ timestamp when the tool is run with positive verbosity.
709
710
 
710
711
  You can use this logger by passing the proc returned by
711
712
  {Toys::Utils::StandardUI#logger_factory_proc} to the CLI constructor:
@@ -718,8 +719,10 @@ cli = Toys::CLI.new(logger_factory: standard_ui.logger_factory_proc)
718
719
  You can also customize the logger by subclassing StandardUI and overriding its
719
720
  methods or adjusting its parameters. In particular, you can alter the
720
721
  {Toys::Utils::StandardUI#log_header_severity_styles} mapping to adjust styling,
721
- or override {Toys::Utils::StandardUI#create_logger} or
722
- {Toys::Utils::StandardUI#format_log_entry} to adjust content and
722
+ or override {Toys::Utils::StandardUI#create_logger},
723
+ {Toys::Utils::StandardUI#verbose_log_format?},
724
+ {Toys::Utils::StandardUI#format_simple_log_entry}, or
725
+ {Toys::Utils::StandardUI#format_verbose_log_entry} to adjust content and
723
726
  formatting.
724
727
 
725
728
  ### Handling errors
@@ -641,7 +641,8 @@ module Toys
641
641
  if @tool.runnable? || !@seen_flags.empty?
642
642
  ExtraArgumentsError.new(arguments: @unmatched_positional)
643
643
  else
644
- dictionary = @loader.list_subtools(@tool.full_name).map(&:simple_name)
644
+ subtools = @loader.list_subtools(@tool.full_name, include_namespaces: true)
645
+ dictionary = subtools.map(&:simple_name)
645
646
  first_arg = @unmatched_positional.first
646
647
  ToolUnrecognizedError.new(full_name: @tool.full_name + [first_arg],
647
648
  suggestions: Compat.suggestions(first_arg, dictionary))
data/lib/toys/cli.rb CHANGED
@@ -88,6 +88,8 @@ module Toys
88
88
  # as an argument, and returns a `Logger` to use when running that tool.
89
89
  # Optional. If not provided (and no global logger is set),
90
90
  # {Toys::CLI.default_logger_factory} is called to get a basic default.
91
+ # See {Toys::Runner} for how a logger can receive the verbosity of
92
+ # the run.
91
93
  # @param base_level [Integer] The logger level that should correspond
92
94
  # to zero verbosity.
93
95
  # Optional. If not provided, defaults to the level the logger has
@@ -351,8 +353,8 @@ module Toys
351
353
  # source list. If the given search directory path does not exist or does
352
354
  # not contain either the file or directory, nothing is added.
353
355
  #
354
- # The main Toys executable uses this method to load tools from directories
355
- # in the `TOYS_PATH`.
356
+ # The main Toys executable uses this method to load tools from the
357
+ # directories in its upward walk and from the home directory.
356
358
  #
357
359
  # @param search_path [String,Pathname] A directory path to search for the
358
360
  # well-known source file and directory. Must be a String or a Pathname.
@@ -405,9 +407,6 @@ module Toys
405
407
  # given directory and works up through parent directories until it reaches
406
408
  # the file system root or it encounters one of the "terminate" directories.
407
409
  #
408
- # The main Toys executable uses this method to load tools from the current
409
- # directory and its ancestors.
410
- #
411
410
  # @param start [String,Pathname,nil] The first directory path to search.
412
411
  # If not given, defaults to the current working directory. If provided,
413
412
  # must be a String or a Pathname. Paths should generally be absolute.
@@ -581,6 +580,9 @@ module Toys
581
580
  high_priority: false,
582
581
  source_name: nil,
583
582
  context_directory: :parent)
583
+ Warnings.warn(:add_config_path_deprecated,
584
+ "CLI#add_config_path is deprecated. Use add_source instead.",
585
+ uplevel: 1)
584
586
  path = SourceSpec.check_and_normalize_path(path)
585
587
  context_directory = resolve_context_directory(context_directory, path)
586
588
  spec = SourceSpec.path(path, source_name: source_name, context_directory: context_directory)
@@ -631,6 +633,9 @@ module Toys
631
633
  source_name: nil,
632
634
  context_directory: nil,
633
635
  &block)
636
+ Warnings.warn(:add_config_block_deprecated,
637
+ "CLI#add_config_block is deprecated. Use add_source instead.",
638
+ uplevel: 1)
634
639
  spec = SourceSpec.block(source_name: source_name, context_directory: context_directory, &block)
635
640
  add_source(spec, high_priority: high_priority)
636
641
  end
data/lib/toys/compat.rb CHANGED
@@ -22,127 +22,133 @@ module Toys
22
22
  #
23
23
  RUBY_VERSION_CODE = ruby_version
24
24
 
25
- ##
26
- # @private
27
- # Whether the current Ruby implementation is JRuby
28
- #
29
- # @return [boolean]
30
- #
31
- def self.jruby?
32
- ::RUBY_ENGINE == "jruby"
33
- end
25
+ class << self
26
+ ##
27
+ # @private
28
+ # Whether the current Ruby implementation is JRuby
29
+ #
30
+ # @return [boolean]
31
+ #
32
+ def jruby?
33
+ ::RUBY_ENGINE == "jruby"
34
+ end
34
35
 
35
- ##
36
- # @private
37
- # Whether the current Ruby implementation is TruffleRuby
38
- #
39
- # @return [boolean]
40
- #
41
- def self.truffleruby?
42
- ::RUBY_ENGINE == "truffleruby"
43
- end
36
+ ##
37
+ # @private
38
+ # Whether the current Ruby implementation is TruffleRuby
39
+ #
40
+ # @return [boolean]
41
+ #
42
+ def truffleruby?
43
+ ::RUBY_ENGINE == "truffleruby"
44
+ end
44
45
 
45
- ##
46
- # @private
47
- # Whether we are running on Windows
48
- #
49
- # @return [boolean]
50
- #
51
- def self.windows?
52
- ::RbConfig::CONFIG["host_os"] =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/
53
- end
46
+ ##
47
+ # @private
48
+ # Whether we are running on Windows
49
+ #
50
+ # @return [boolean]
51
+ #
52
+ def windows?
53
+ ::RbConfig::CONFIG["host_os"] =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/
54
+ end
54
55
 
55
- ##
56
- # @private
57
- # Whether we are running on Mac OS
58
- #
59
- # @return [boolean]
60
- #
61
- def self.macos?
62
- ::RbConfig::CONFIG["host_os"] =~ /darwin/
63
- end
56
+ ##
57
+ # @private
58
+ # Whether we are running on Mac OS
59
+ #
60
+ # @return [boolean]
61
+ #
62
+ def macos?
63
+ ::RbConfig::CONFIG["host_os"] =~ /darwin/
64
+ end
64
65
 
65
- ##
66
- # @private
67
- # Whether fork is supported on the current Ruby and OS
68
- #
69
- # @return [boolean]
70
- #
71
- def self.allow_fork?
72
- !jruby? && !truffleruby? && !windows?
73
- end
66
+ ##
67
+ # @private
68
+ # Whether fork is supported on the current Ruby and OS
69
+ #
70
+ # @return [boolean]
71
+ #
72
+ def allow_fork?
73
+ !jruby? && !truffleruby? && !windows?
74
+ end
74
75
 
75
- ##
76
- # @private
77
- # Whether it is possible to get suggestions from DidYouMean. If this
78
- # returns false, {Compat.suggestions} will always return the empty array.
79
- #
80
- # @return [boolean]
81
- #
82
- def self.supports_suggestions?
83
- unless defined?(@supports_suggestions)
84
- begin
85
- require "did_you_mean"
86
- rescue ::LoadError
87
- require "rubygems"
76
+ ##
77
+ # @private
78
+ # Whether it is possible to get suggestions from DidYouMean. If this
79
+ # returns false, {Compat.suggestions} will always return the empty array.
80
+ #
81
+ # @return [boolean]
82
+ #
83
+ def supports_suggestions?
84
+ unless defined?(@supports_suggestions)
88
85
  begin
89
86
  require "did_you_mean"
90
87
  rescue ::LoadError
91
- # Oh well, it's not available
88
+ require "rubygems"
89
+ begin
90
+ require "did_you_mean"
91
+ rescue ::LoadError
92
+ # Oh well, it's not available
93
+ end
92
94
  end
95
+ @supports_suggestions = defined?(::DidYouMean::SpellChecker)
93
96
  end
94
- @supports_suggestions = defined?(::DidYouMean::SpellChecker)
97
+ @supports_suggestions
95
98
  end
96
- @supports_suggestions
97
- end
98
99
 
99
- ##
100
- # @private
101
- # A list of suggestions from DidYouMean.
102
- #
103
- # @param word [String] A value that seems wrong
104
- # @param list [Array<String>] A list of valid values
105
- #
106
- # @return [Array<String>] A possibly empty array of suggestions from the
107
- # valid list that could match the given word.
108
- #
109
- def self.suggestions(word, list)
110
- if supports_suggestions?
111
- ::DidYouMean::SpellChecker.new(dictionary: list).correct(word)
112
- else
113
- []
100
+ ##
101
+ # @private
102
+ # A list of suggestions from DidYouMean.
103
+ #
104
+ # @param word [String] A value that seems wrong
105
+ # @param list [Array<String>] A list of valid values
106
+ #
107
+ # @return [Array<String>] A possibly empty array of suggestions from the
108
+ # valid list that could match the given word.
109
+ #
110
+ def suggestions(word, list)
111
+ if supports_suggestions?
112
+ ::DidYouMean::SpellChecker.new(dictionary: list).correct(word)
113
+ else
114
+ []
115
+ end
114
116
  end
115
- end
116
117
 
117
- ##
118
- # @private
119
- # A list of gems that should generally not be included in a bundle, usually
120
- # because the Ruby implementation handles the library specially and cannot
121
- # install the real gem. Currently, this includes the `pathname` gem for
122
- # TruffleRuby, since TruffleRuby includes a special version of it.
123
- #
124
- # @return [Array<String>]
125
- #
126
- def self.gems_to_omit_from_bundles
127
- if truffleruby?
128
- ["pathname"]
129
- else
130
- []
118
+ ##
119
+ # @private
120
+ # A list of gems that should generally not be included in a bundle,
121
+ # usually because the Ruby implementation handles the library specially
122
+ # and cannot install the real gem. Currently, this includes the
123
+ # `pathname` gem for TruffleRuby, since TruffleRuby includes a special
124
+ # version of it.
125
+ #
126
+ # @return [Array<String>]
127
+ #
128
+ def gems_to_omit_from_bundles
129
+ if truffleruby?
130
+ ["pathname"]
131
+ else
132
+ []
133
+ end
131
134
  end
132
- end
133
135
 
134
- ##
135
- # @private
136
- # Sets the backtrace on an exception. In Ruby < 3.4, Exception#set_backtrace
137
- # required a string array, whereas in newer Rubies, it would accept an array
138
- # of backtrace locations.
139
- #
140
- # @param [Exception] exception
141
- # @param [nil,Array<String>,Array<Thread::Backtrace::Location>] backtrace
142
- #
143
- def self.set_backtrace(exception, backtrace)
144
- backtrace = backtrace.map(&:to_s) if RUBY_VERSION_CODE < 30400 && !backtrace.nil?
145
- exception.set_backtrace(backtrace)
136
+ ##
137
+ # @private
138
+ # Sets the backtrace on an exception. In Ruby `< 3.4`,
139
+ # `Exception#set_backtrace` required a string array, whereas in newer
140
+ # Rubies, it would accept an array of backtrace locations.
141
+ #
142
+ # @param [Exception] exception
143
+ # @param [nil,Array<String>,Array<Thread::Backtrace::Location>] backtrace
144
+ #
145
+ # @return [Exception] The modified exception
146
+ #
147
+ def set_backtrace(exception, backtrace)
148
+ backtrace = backtrace.map(&:to_s) if RUBY_VERSION_CODE < 30400 && !backtrace.nil?
149
+ exception.set_backtrace(backtrace)
150
+ exception
151
+ end
146
152
  end
147
153
  end
148
154
  end
data/lib/toys/core.rb CHANGED
@@ -9,6 +9,6 @@ module Toys
9
9
  # Current version of Toys core.
10
10
  # @return [String]
11
11
  #
12
- VERSION = "0.24.0"
12
+ VERSION = "0.25.0"
13
13
  end
14
14
  end
data/lib/toys/flag.rb CHANGED
@@ -377,10 +377,12 @@ module Toys
377
377
  SET_HANDLER = proc { |val| val }
378
378
 
379
379
  ##
380
- # The push handler pushes the given value using the `<<` operator.
380
+ # The push handler appends the given value, returning a new array. The
381
+ # previous array is not modified, so a shared or frozen default array
382
+ # can be used safely.
381
383
  # @return [Proc]
382
384
  #
383
- PUSH_HANDLER = proc { |val, prev| prev.nil? ? [val] : prev << val }
385
+ PUSH_HANDLER = proc { |val, prev| prev.nil? ? [val] : prev + [val] }
384
386
 
385
387
  ##
386
388
  # The default handler is the set handler, which replaces the previous value.
data/lib/toys/runner.rb CHANGED
@@ -38,6 +38,15 @@ module Toys
38
38
  # end
39
39
  # end
40
40
  #
41
+ # While a tool runs, the Runner sets the level of the tool's logger to reflect
42
+ # the verbosity, as described under the `base_level` parameter of
43
+ # {#initialize}. A logger can also receive the verbosity itself, by
44
+ # implementing a `verbosity` attribute: if the logger responds to both
45
+ # `verbosity` and `verbosity=`, the Runner sets it to the verbosity (an
46
+ # Integer) for the duration of the run, and restores the previous value
47
+ # afterward. This lets a logger vary its behavior, such as its formatting, by
48
+ # verbosity independently of its level.
49
+ #
41
50
  class Runner
42
51
  ##
43
52
  # The singleton default logger_factory Proc, which simply returns a new
@@ -74,7 +83,8 @@ module Toys
74
83
  # @param logger_factory [Proc,nil] A proc that takes a
75
84
  # {Toys::ToolDefinition} as an argument, and returns a logger to use
76
85
  # when running that tool. If not given,
77
- # {Toys::Runner::DEFAULT_LOGGER_FACTORY} is used.
86
+ # {Toys::Runner::DEFAULT_LOGGER_FACTORY} is used. See {Toys::Runner}
87
+ # for how a logger can receive the verbosity of the run.
78
88
  # @param base_level [Integer,nil] The logger level that corresponds to zero
79
89
  # verbosity. If not provided, the level the logger has before a run
80
90
  # adjusts it is used (typically Logger::WARN). A run nested inside
@@ -404,15 +414,15 @@ module Toys
404
414
  end
405
415
 
406
416
  # Prepares the runtime environment for the tool, applying lib paths and
407
- # initializers, and then calls the tool within its middleware stack and
408
- # within the logger level implied by the verbosity, returning the
409
- # resulting exit code.
417
+ # initializers, and then calls the tool within its middleware stack,
418
+ # returning the resulting exit code. The initializers and the tool both
419
+ # run within the logger level implied by the verbosity.
410
420
  def execute_tool(context, &block)
411
421
  @tool.source_info&.find_lib_paths&.reverse_each do |path|
412
422
  $LOAD_PATH.unshift(path) unless $LOAD_PATH.include?(path)
413
423
  end
414
- @tool.run_initializers(context)
415
424
  with_logger_level(context) do
425
+ @tool.run_initializers(context)
416
426
  executor = build_executor(context, &block)
417
427
  catch(:result) do
418
428
  executor.call
@@ -426,23 +436,39 @@ module Toys
426
436
  # afterward. The restoration must cover the level assignment itself,
427
437
  # because a base level that cannot be combined with the verbosity fails
428
438
  # there, and a base level left recorded behind a failed run would be
429
- # picked up by later runs sharing the logger. Yields directly if the tool
430
- # has no logger.
431
- def with_logger_level(context)
439
+ # picked up by later runs sharing the logger. Also sets the verbosity on
440
+ # a logger that supports it, as described in {Runner}. Yields directly if
441
+ # the tool has no logger.
442
+ def with_logger_level(context, &block)
432
443
  cur_logger = context[Context::Key::LOGGER]
433
444
  return yield unless cur_logger
445
+ verbosity = context[Context::Key::VERBOSITY].to_i
434
446
  original_level = cur_logger.level
435
447
  base_level = @base_level || base_levels[cur_logger] || original_level
436
448
  saved_base_level = set_base_level(cur_logger, base_level)
437
449
  begin
438
- cur_logger.level = base_level - context[Context::Key::VERBOSITY].to_i
439
- yield
450
+ cur_logger.level = base_level - verbosity
451
+ with_logger_verbosity(cur_logger, verbosity, &block)
440
452
  ensure
441
453
  set_base_level(cur_logger, saved_base_level)
442
454
  cur_logger.level = original_level
443
455
  end
444
456
  end
445
457
 
458
+ # Sets the verbosity on a logger that supports it for the duration of
459
+ # the block, and restores it afterward. Yields directly if the logger
460
+ # does not have both a reader and a writer for the verbosity.
461
+ def with_logger_verbosity(logger, verbosity)
462
+ return yield unless logger.respond_to?(:verbosity) && logger.respond_to?(:verbosity=)
463
+ original_verbosity = logger.verbosity
464
+ begin
465
+ logger.verbosity = verbosity
466
+ yield
467
+ ensure
468
+ logger.verbosity = original_verbosity
469
+ end
470
+ end
471
+
446
472
  # The base levels currently in effect on this thread, keyed by logger
447
473
  # identity. A run that adjusts a logger's level records the base
448
474
  # level it used, for the extent of that run, because a nested run cannot
@@ -22,8 +22,9 @@ module Toys
22
22
  # occurs. Default is {USAGE_ERROR_EXIT_CODE}.
23
23
  # @param stream [IO] Output stream to write to. Default is stderr.
24
24
  # @param styled_output [boolean,nil] Cause the tool to display help text
25
- # with ansi styles. If `nil`, display styles if the output stream is
26
- # a tty. Default is `nil`.
25
+ # with ansi styles. If `nil`, whether to display styles is inferred
26
+ # from the output stream and the environment. See
27
+ # {Toys::Utils::Terminal.infer_styled}. Default is `nil`.
27
28
  #
28
29
  def initialize(exit_code: nil, stream: $stderr, styled_output: nil)
29
30
  @exit_code = exit_code || USAGE_ERROR_EXIT_CODE
@@ -17,7 +17,7 @@ module Toys
17
17
  # Default help flags
18
18
  # @return [Array<String>]
19
19
  #
20
- DEFAULT_HELP_FLAGS = ["-?", "--help"].freeze
20
+ DEFAULT_HELP_FLAGS = ["-h", "-?", "--help"].freeze
21
21
 
22
22
  ##
23
23
  # Default usage flags
@@ -165,8 +165,9 @@ module Toys
165
165
  # false) if the output stream is not a tty.
166
166
  # @param stream [IO] Output stream to write to. Default is stdout.
167
167
  # @param styled_output [boolean,nil] Cause the tool to display help text
168
- # with ansi styles. If `nil`, display styles if the output stream is
169
- # a tty. Default is `nil`.
168
+ # with ansi styles. If `nil`, whether to display styles is inferred
169
+ # from the output stream and the environment. See
170
+ # {Toys::Utils::Terminal.infer_styled}. Default is `nil`.
170
171
  #
171
172
  def initialize(help_flags: false,
172
173
  usage_flags: false,
@@ -297,10 +298,11 @@ module Toys
297
298
  include_hidden: context[SHOW_ALL_SUBTOOLS_KEY],
298
299
  show_source_path: @show_source_path,
299
300
  separate_sources: @separate_sources,
300
- wrap_width: terminal.width
301
+ wrap_width: terminal.width,
302
+ styled: terminal.styled
301
303
  )
302
304
  require "toys/utils/pager"
303
- Utils::Pager.start(command: @use_pager, fallback_io: terminal) do |io|
305
+ Utils::Pager.start(command: @use_pager, output: @stream, fallback_io: terminal) do |io|
304
306
  io.puts(str)
305
307
  end
306
308
  rescue Utils::HelpText::HelpGenerationError => e
@@ -27,6 +27,11 @@ module Toys
27
27
  # The arguments will be passed on to the
28
28
  # [HighLine constructor](https://www.rubydoc.info/gems/highline/HighLine:initialize).
29
29
  #
30
+ # Whether the HighLine object uses color is inferred from its output
31
+ # stream and the environment, using the same rule as a terminal. See
32
+ # {Toys::Utils::Terminal.infer_styled}. You can override it by setting
33
+ # `use_color` on the HighLine object.
34
+ #
30
35
  module Highline
31
36
  include Mixin
32
37
 
@@ -131,10 +136,12 @@ module Toys
131
136
 
132
137
  on_initialize do |*args|
133
138
  require "toys/utils/gems"
139
+ require "toys/utils/terminal"
134
140
  ::Toys::Utils::Gems.activate("highline", "~> 2.0")
135
141
  require "highline"
136
- self[::Toys::StandardMixins::Highline::KEY] = ::HighLine.new(*args)
137
- self[::Toys::StandardMixins::Highline::KEY].use_color = $stdout.tty?
142
+ highline = ::HighLine.new(*args)
143
+ highline.use_color = ::Toys::Utils::Terminal.infer_styled(highline.output)
144
+ self[::Toys::StandardMixins::Highline::KEY] = highline
138
145
  end
139
146
  end
140
147
  end