diff --git a/spec/std/colorize_spec.cr b/spec/std/colorize_spec.cr index dc80dd557e7b..3eeb919a8c5f 100644 --- a/spec/std/colorize_spec.cr +++ b/spec/std/colorize_spec.cr @@ -1,6 +1,14 @@ require "spec" require "colorize" +private class ColorizeToS + def to_s(io) + io << "hello" + io << "world".colorize.blue + io << "bye" + end +end + describe "colorize" do it "colorizes without change" do "hello".colorize.to_s.should eq("hello") @@ -97,44 +105,56 @@ describe "colorize" do "hello".colorize(:red).inspect.should eq("\e[31m\"hello\"\e[0m") end - it "colorizes io with method" do + it "colorizes with surround" do io = IO::Memory.new with_color.red.surround(io) do io << "hello" + with_color.green.surround(io) do + io << "world" + end + io << "bye" end - io.to_s.should eq("\e[31mhello\e[0m") + io.to_s.should eq("\e[31mhello\e[0;32mworld\e[0;31mbye\e[0m") end - it "colorizes io with symbol" do + it "colorizes with surround and reset" do io = IO::Memory.new - with_color(:red).surround(io) do + with_color.red.surround(io) do io << "hello" + with_color.green.bold.surround(io) do + io << "world" + end + io << "bye" end - io.to_s.should eq("\e[31mhello\e[0m") + io.to_s.should eq("\e[31mhello\e[0;32;1mworld\e[0;31mbye\e[0m") end - it "colorizes with push and pop" do + it "colorizes with surround and no reset" do io = IO::Memory.new - with_color.red.push(io) do + with_color.red.surround(io) do io << "hello" - with_color.green.push(io) do + with_color.red.surround(io) do io << "world" end io << "bye" end - io.to_s.should eq("\e[31mhello\e[0;32mworld\e[0;31mbye\e[0m") + io.to_s.should eq("\e[31mhelloworldbye\e[0m") end - it "colorizes with push and pop resets" do + it "colorizes with surround and default" do io = IO::Memory.new - with_color.red.push(io) do + with_color.red.surround(io) do io << "hello" - with_color.green.bold.push(io) do + with_color.surround(io) do io << "world" end io << "bye" end - io.to_s.should eq("\e[31mhello\e[0;32;1mworld\e[0;31mbye\e[0m") + io.to_s.should eq("\e[31mhello\e[0mworld\e[31mbye\e[0m") + end + + it "colorizes with to_s" do + ColorizeToS.new.colorize.red.to_s.should eq("\e[31mhello\e[0;34mworld\e[0;31mbye\e[0m") end it "toggles off" do diff --git a/src/colorize.cr b/src/colorize.cr index fbc00b4b7334..d94602f857ff 100644 --- a/src/colorize.cr +++ b/src/colorize.cr @@ -239,64 +239,88 @@ struct Colorize::Object(T) end def surround(io = STDOUT) - must_append_end = append_start(io) - yield io - append_end(io) if must_append_end - end + return yield io unless @on - STACK = [] of Colorize::Object(String) + Object.surround(io, to_named_tuple) do |io| + yield io + end + end + # DEPRECATED: Use `#surround`. def push(io = STDOUT) - last_color = STACK.last? - - append_start(io, !!last_color) + {{ puts "Warning: `Colorize::Object#push` is deprecated and will be removed, use `Colorize::Object#surround` instead".id }} + surround(io) { |io| yield io } + end - STACK.push self - yield io - STACK.pop + private def to_named_tuple + { + fore: @fore, + back: @back, + mode: @mode, + } + end - if last_color - last_color.append_start(io, true) - else - append_end(io) + @@last_color = { + fore: FORE_DEFAULT, + back: BACK_DEFAULT, + mode: 0, + } + + protected def self.surround(io, color) + last_color = @@last_color + must_append_end = append_start(io, color) + @@last_color = color + + begin + yield io + ensure + append_start(io, last_color) if must_append_end + @@last_color = last_color end end - protected def append_start(io, reset = false) - return false unless @on + private def self.append_start(io, color) + last_color_is_default = + @@last_color[:fore] == FORE_DEFAULT && + @@last_color[:back] == BACK_DEFAULT && + @@last_color[:mode] == 0 + + fore = color[:fore] + back = color[:back] + mode = color[:mode] - fore_is_default = @fore == FORE_DEFAULT - back_is_default = @back == BACK_DEFAULT - mode_is_default = @mode == 0 + fore_is_default = fore == FORE_DEFAULT + back_is_default = back == BACK_DEFAULT + mode_is_default = mode == 0 - if fore_is_default && back_is_default && mode_is_default && !reset + if fore_is_default && back_is_default && mode_is_default && last_color_is_default || @@last_color == color false else io << "\e[" printed = false - if reset + unless last_color_is_default io << MODE_DEFAULT printed = true end unless fore_is_default io << ";" if printed - io << @fore + io << fore printed = true end unless back_is_default io << ";" if printed - io << @back + io << back printed = true end unless mode_is_default # Can't reuse MODES constant because it has bold/bright duplicated {% for name in %w(bold dim underline blink reverse hidden) %} - if (@mode & MODE_{{name.upcase.id}}_FLAG) != 0 + if (mode & MODE_{{name.upcase.id}}_FLAG) != 0 io << ";" if printed io << MODE_{{name.upcase.id}} printed = true @@ -309,8 +333,4 @@ struct Colorize::Object(T) true end end - - protected def append_end(io) - io << "\e[0m" - end end