File: code.rb

package info (click to toggle)
ruby-ansi 1.5.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 404 kB
  • sloc: ruby: 1,880; makefile: 5
file content (349 lines) | stat: -rw-r--r-- 8,912 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
module ANSI

  # Global variable can be used to prevent ANSI codes
  # from being used in ANSI's methods that do so to string.
  #
  # NOTE: This has no effect on methods that return ANSI codes.
  $ansi = true

  if RUBY_PLATFORM =~ /(win32|w32)/
    begin
      require 'Win32/Console/ANSI'
    rescue LoadError
      warn "ansi: 'gem install win32console' to use color on Windows"
      $ansi = false
    end
  end

  require 'ansi/constants'

  # TODO: up, down, right, left, etc could have yielding methods too?

  # ANSI Codes
  #
  # Ansi::Code module makes it very easy to use ANSI codes.
  # These are especially nice for beautifying shell output.
  #
  #   Ansi::Code.red + "Hello" + Ansi::Code.blue + "World"
  #   => "\e[31mHello\e[34mWorld"
  #
  #   Ansi::Code.red{ "Hello" } + Ansi::Code.blue{ "World" }
  #   => "\e[31mHello\e[0m\e[34mWorld\e[0m"
  #
  # IMPORTANT! Do not mixin Ansi::Code, instead use {ANSI::Mixin}.
  #
  # See {ANSI::CHART} for list of all supported codes.
  #
  module Code
    extend self

    # include ANSI Constants
    include Constants

    # Regexp for matching most ANSI codes.
    PATTERN = /\e\[(\d+)m/

    # ANSI clear code.
    ENDCODE = "\e[0m"

    # List of primary styles.
    def self.styles
      %w{bold dark italic underline underscore blink rapid reverse negative concealed strike}
    end

    # List of primary colors.
    def self.colors
      %w{black red green yellow blue magenta cyan white}
    end

    # Return ANSI code given a list of symbolic names.
    def [](*codes)
      code(*codes)
    end

    # Dynamically create color on color methods.
    #
    # @deprecated
    #
    colors.each do |color|
      colors.each do |on_color|
        module_eval <<-END, __FILE__, __LINE__
          def #{color}_on_#{on_color}(string=nil)
            if string
              return string unless $ansi
              #warn "use ANSI block notation for future versions"
              return #{color.upcase} + ON_#{color.upcase} + string + ENDCODE
            end
            if block_given?
              return yield unless $ansi
              #{color.upcase} + ON_#{on_color.upcase} + yield.to_s + ENDCODE
            else
              #{color.upcase} + ON_#{on_color.upcase}
            end
          end
        END
      end
    end

    # Use method missing to dispatch ANSI code methods.
    def method_missing(code, *args, &blk)
      esc = nil

      if CHART.key?(code)
        esc = "\e[#{CHART[code]}m"
      elsif SPECIAL_CHART.key?(code)
        esc = SPECIAL_CHART[code]
      end

      if esc
        if string = args.first
          return string unless $ansi
          #warn "use ANSI block notation for future versions"
          return "#{esc}#{string}#{ENDCODE}"
        end
        if block_given?
          return yield unless $ansi
          return "#{esc}#{yield}#{ENDCODE}"
        end
        esc
      else
        super(code, *args, &blk)
      end
    end

    # TODO: How to deal with position codes when $ansi is false?
    # Should we raise an error or just not push the codes?
    # For now, we will leave this it as is.

    # Like +move+ but returns to original position after
    # yielding the block.
    def display(line, column=0) #:yield:
      result = "\e[s"
      result << "\e[#{line.to_i};#{column.to_i}H"
      if block_given?
        result << yield
        result << "\e[u"
      #elsif string
      #  result << string
      #  result << "\e[u"
      end
      result
    end

    # Move cursor to line and column.
    def move(line, column=0)
      "\e[#{line.to_i};#{column.to_i}H"
    end

    # Move cursor up a specified number of spaces.
    def up(spaces=1)
      "\e[#{spaces.to_i}A"
    end

    # Move cursor down a specified number of spaces.
    def down(spaces=1)
      "\e[#{spaces.to_i}B"
    end

    # Move cursor left a specified number of spaces.
    def left(spaces=1)
      "\e[#{spaces.to_i}D"
    end
    alias :back :left

    # Move cursor right a specified number of spaces.
    def right(spaces=1)
      "\e[#{spaces.to_i}C"
    end
    alias :forward :right

    ##
    #def position
    #  "\e[#;#R"
    #end

    # Apply ANSI codes to a first argument or block value.
    #
    # @example
    #   ansi("Valentine", :red, :on_white)
    #
    # @example
    #   ansi(:red, :on_white){ "Valentine" }
    #
    # @return [String]
    #   String wrapped ANSI code.
    #
    def ansi(*codes) #:yield:
      if block_given?
        string = yield.to_s
      else
        # first argument must be the string
        string = codes.shift.to_s
      end

      return string unless $ansi

      c = code(*codes)

      c + string.gsub(ENDCODE, ENDCODE + c) + ENDCODE
    end

    # TODO: Allow selective removal using *codes argument?

    # Remove ANSI codes from string or block value.
    #
    # @param [String] string
    #   String from which to remove ANSI codes.
    #
    # @return [String]
    #   String wrapped ANSI code.
    #
    def unansi(string=nil) #:yield:
      if block_given?
        string = yield.to_s
      else
        string = string.to_s
      end
      string.gsub(PATTERN, '')
    end

    # Alias for #ansi method.
    #
    # @deprecated
    #   Here for backward compatibility.
    alias_method :style, :ansi

    # Alias for #unansi method.
    #
    # @deprecated 
    #   Here for backwards compatibility.
    alias_method :unstyle, :unansi

    # Alternate term for #ansi.
    #
    # @deprecated 
    #   May change in future definition.
    alias_method :color, :ansi

    # Alias for unansi.
    #
    # @deprecated 
    #   May change in future definition.
    alias_method :uncolor, :unansi

    # Look-up code from chart, or if Integer simply pass through.
    # Also resolves :random and :on_random.
    #
    # @param codes [Array<Symbol,Integer]
    #   Symbols or integers to convert to ANSI code.
    #
    # @return [String] ANSI code
    def code(*codes)
      list = []
      codes.each do |code|
        list << \
          case code
          when Integer
            code
          when Array
            rgb_code(*code)
          when :random, 'random'
            random
          when :on_random, 'on_random'
            random(true)
          when String
            # TODO: code =~ /\d\d\d\d\d\d/ ?
            if code.start_with?('#')
              hex_code(code)
            else
              CHART[code.to_sym]
            end
          else
            CHART[code.to_sym]
          end
      end
      "\e[" + (list * ";") + "m"
    end

    # Provides a random primary ANSI color.
    #
    # @param background [Boolean]
    #   Use `true` for background color, otherwise foreground color.
    #
    # @return [Integer] ANSI color number
    def random(background=false)
      (background ? 40 : 30) + rand(8)
    end

    # Creates an XTerm 256 color escape code from RGB value(s). The
    # RGB value can be three arguments red, green and blue respectively
    # each from 0 to 255, or the RGB value can be a single CSS-style
    # hex string.
    #
    # @param background [Boolean]
    #   Use `true` for background color, otherwise foreground color.
    #
    def rgb(*args)
      case args.size
      when 1, 2
        hex, background = *args
        esc = "\e[" + hex_code(hex, background) + "m"
      when 3, 4
        red, green, blue, background = *args
        esc = "\e[" + rgb_code(red, green, blue, background) + "m"
      else
        raise ArgumentError
      end

      if block_given?
        return yield.to_s unless $ansi
        return "#{esc}#{yield}#{ENDCODE}"
      else
        return esc
      end
    end

  #private

    # Creates an xterm-256 color from rgb value.
    #
    # @param background [Boolean]
    #   Use `true` for background color, otherwise foreground color.
    #
    def rgb_code(red, green, blue, background=false)
      "#{background ? 48 : 38};5;#{rgb_256(red, green, blue)}"
    end

    # Creates an xterm-256 color code from a CSS-style color string.
    #
    # @param string [String]
    #   Hex string in CSS style, .e.g. `#5FA0C2`.
    #
    # @param background [Boolean]
    #   Use `true` for background color, otherwise foreground color.
    #
    def hex_code(string, background=false)
      string.tr!('#','')
      x = (string.size == 6 ? 2 : 1)
      r, g, b = [0,1,2].map{ |i| string[i*x,2].to_i(16) }
      rgb_code(r, g, b, background)
    end

    # Given red, green and blue values between 0 and 255, this method
    # returns the closest XTerm 256 color value.
    #
    def rgb_256(r, g, b)
      # TODO: what was rgb_valid for?
      #r, g, b = [r, g, b].map{ |c| rgb_valid(c); (6 * (c.to_f / 256.0)).to_i }
      r, g, b = [r, g, b].map{ |c| (6 * (c.to_f / 256.0)).to_i }
      v = (r * 36 + g * 6 + b + 16).abs
      raise ArgumentError, "RGB value is outside 0-255 range -- #{v}" if v > 255
      v
    end

  end

  #
  extend Code
end