通过创建新的字符串对象在字符串上调用方法

时间:2019-05-17 06:53:56

标签: ruby

好的,所以我有一个在字符串类中动态修补的方法:

class String
    @@colours = [[154, 184, 208, 203, 198, 164, 129, 92], [63, 33, 39, 44, 49, 83, 118], [40,41,42,43,211, 210, 209, 208]].tap { |itself| itself.concat(itself.map(&:reverse)) }

    define_method(:colourize) do |final = ''|
        colour = @@colours.sample
        colour_size = colour.size - 1
        index, div, val = 0, length / colour_size, ''
        div = 1 if div == 0
        colour_size -= 1

        each_char.with_index do |c, i|
            index += 1 if (i % div == 0 && index < colour_size) && i > 1
            val.concat("\e[38;5;#{colour[index]}m#{c}")
        end

        val + "\e[0m" + final
    end
end

这会使String对象着色(仅在Linux系统上的BASH Shell中测试)。但是我每次都要写colourize方法。

puts 'Hello World'.colourize

是否有一种修补String的方法,以便在创建“ Hello World”或将其分配给变量时,默认情况下将调用colourize方法?

修补String#intialize并不能简单地工作。

1 个答案:

答案 0 :(得分:3)

  

是否有一种方法可以修补String,所以[…]默认情况下将调用colourize方法?

这是一个坏主意。字符串是Ruby的基础,就像数组,哈希和符号一样。在创建的每个字符串中放入颜色代码,无论如何,很可能会破坏某些内容。

仅在实际打印字符串时才可能添加颜色代码(并且可能仅在打印到tty时才添加)。

这使我们进入puts。它将给定的对象写到标准输出中,如果需要,将它们转换为字符串。不幸的是,字符串不需要任何转换,因此对to_s的调用被跳过了,我们没有任何方法可以挂入。

但是我们可以不修补String,而可以修补顶级puts

(出于演示目的,我使用upcase是因为我无法在此处呈现ANSI转义序列)

def puts(*args)
  Kernel.puts(*args.map { |a| a.is_a?(String) ? a.upcase : a })
end

puts 'hello'

输出:

HELLO

由于修补核心类是一件肮脏的事,所以让我们看看是否可以找到一种更清洁的方法。 Kernel.puts的文档说:

  

相当于$stdout.puts(obj, ...)

这听起来很有希望:$stdout作为全局变量很容易更改。我们需要的是一个提供自定义puts方法并将所有其他方法委托给原始stdout的对象。这就是SimpleDelegator的用途:

class OutputDecorator < SimpleDelegator
  def puts(*args)
    super *args.map { |a| a.is_a?(String) ? a.upcase : a }
  end
end

让我们尝试一下:

puts 'before'

$stdout = OutputDecorator.new($stdout)

puts 'within'

$stdout = $stdout.__getobj__

puts 'after'

输出:

before
WITHIN
after