diff --git a/spec/std/colorize_spec.cr b/spec/std/colorize_spec.cr index 3a4170667ec9..0ac84cbbc4ba 100644 --- a/spec/std/colorize_spec.cr +++ b/spec/std/colorize_spec.cr @@ -1,5 +1,6 @@ require "spec" require "colorize" +require "../support/env" private def colorize(obj, *args) obj.colorize(*args).toggle(true) @@ -17,7 +18,70 @@ private class ColorizeToS end end +private class ColorizeTTY < IO + def tty? : Bool + true + end + + def read(slice : Bytes) + 0 + end + + def write(slice : Bytes) : Nil + end +end + describe "colorize" do + it ".default_enabled?" do + io = IO::Memory.new + tty = ColorizeTTY.new + + with_env("TERM": nil, "NO_COLOR": nil) do + Colorize.default_enabled?(io).should be_false + Colorize.default_enabled?(tty).should be_true + Colorize.default_enabled?(io, io).should be_false + Colorize.default_enabled?(io, tty).should be_false + Colorize.default_enabled?(tty, io).should be_false + Colorize.default_enabled?(tty, tty).should be_true + end + + with_env("TERM": nil, "NO_COLOR": "") do + Colorize.default_enabled?(io).should be_false + Colorize.default_enabled?(tty).should be_true + Colorize.default_enabled?(io, io).should be_false + Colorize.default_enabled?(io, tty).should be_false + Colorize.default_enabled?(tty, io).should be_false + Colorize.default_enabled?(tty, tty).should be_true + end + + with_env("TERM": nil, "NO_COLOR": "1") do + Colorize.default_enabled?(io).should be_false + Colorize.default_enabled?(tty).should be_false + Colorize.default_enabled?(io, io).should be_false + Colorize.default_enabled?(io, tty).should be_false + Colorize.default_enabled?(tty, io).should be_false + Colorize.default_enabled?(tty, tty).should be_false + end + + with_env("TERM": "xterm", "NO_COLOR": nil) do + Colorize.default_enabled?(io).should be_false + Colorize.default_enabled?(tty).should be_true + Colorize.default_enabled?(io, io).should be_false + Colorize.default_enabled?(io, tty).should be_false + Colorize.default_enabled?(tty, io).should be_false + Colorize.default_enabled?(tty, tty).should be_true + end + + with_env("TERM": "dumb", "NO_COLOR": nil) do + Colorize.default_enabled?(io).should be_false + Colorize.default_enabled?(tty).should be_false + Colorize.default_enabled?(io, io).should be_false + Colorize.default_enabled?(io, tty).should be_false + Colorize.default_enabled?(tty, io).should be_false + Colorize.default_enabled?(tty, tty).should be_false + end + end + it "colorizes without change" do colorize("hello").to_s.should eq("hello") end diff --git a/src/colorize.cr b/src/colorize.cr index 13b33c1a46cb..41ab5d11b997 100644 --- a/src/colorize.cr +++ b/src/colorize.cr @@ -117,7 +117,8 @@ # # See `Colorize::Mode` for available text decorations. module Colorize - # Objects will only be colored if this is `true`. + # Objects will only be colored if this is `true`, unless overridden by + # `Colorize::Object#toggle`. # # ``` # require "colorize" @@ -129,27 +130,33 @@ module Colorize # "hello".colorize.red.to_s # => "hello" # ``` # - # NOTE: This is by default enabled according to `.on_tty_only!`. - class_property? enabled : Bool do - STDOUT.tty? && STDERR.tty? && ENV["TERM"]? != "dumb" && !ENV["NO_COLOR"]?.try(&.empty?.!) - end + # NOTE: This is by default enabled if `.default_enabled?` is true for `STDOUT` + # and `STDERR`. + class_property? enabled : Bool { default_enabled?(STDOUT, STDERR) } - # Makes `Colorize.enabled` `true` if and only if both of `STDOUT.tty?` - # and `STDERR.tty?` are `true` and the tty is not considered a dumb terminal. - # This is determined by the environment variable called `TERM`. - # If `TERM=dumb`, color won't be enabled. - # If `NO_COLOR` contains any non-empty value, color won't be enabled - # conforming to https://no-color.org - # - # Returns the new value of `Colorize.enabled?`. + # Resets `Colorize.enabled?` to its initial default value, i.e. whether + # `.default_enabled?` is true for `STDOUT` and `STDERR`. Returns this new + # value. # - # This can be used to revert `Colorize.enabled?` to its default value after + # This can be used to revert `Colorize.enabled?` to its initial state after # colorization is explicitly enabled or disabled. def self.on_tty_only! : Bool @@enabled = nil enabled? end + # Returns whether colorization should be enabled by default on the given + # standard output and error streams. + # + # This is true if both streams are terminals (i.e. `IO#tty?` returns true), + # the `TERM` environment variable is not equal to `dumb`, and the + # [`NO_COLOR` environment variable](https://no-color.org) is not set to a + # non-empty string. + def self.default_enabled?(stdout : IO, stderr : IO = stdout) : Bool + stdout.tty? && (stderr == stdout || stderr.tty?) && + ENV["TERM"]? != "dumb" && !ENV["NO_COLOR"]?.try(&.empty?.!) + end + # Resets the color and text decoration of the *io*. # # ``` diff --git a/src/compiler/crystal/command.cr b/src/compiler/crystal/command.cr index fae6a50a21a4..ab0de5b5b2a5 100644 --- a/src/compiler/crystal/command.cr +++ b/src/compiler/crystal/command.cr @@ -59,7 +59,7 @@ class Crystal::Command @compiler : Compiler? def initialize(@options : Array(String)) - @color = ENV["TERM"]? != "dumb" && !ENV["NO_COLOR"]?.try(&.empty?.!) + @color = Colorize.default_enabled?(STDOUT, STDERR) @error_trace = false @progress_tracker = ProgressTracker.new end @@ -762,7 +762,7 @@ class Crystal::Command private def error(msg, exit_code = 1) # This is for the case where the main command is wrong - @color = false if ARGV.includes?("--no-color") || ENV["TERM"]? == "dumb" || ENV["NO_COLOR"]?.try(&.empty?.!) + @color = false if ARGV.includes?("--no-color") || !Colorize.default_enabled?(STDOUT, STDERR) Crystal.error msg, @color, exit_code: exit_code end