termgui 0.0.4

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.
Files changed (101) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +14 -0
  3. data/README.md +321 -0
  4. data/lib/termgui.rb +1 -0
  5. data/src/action.rb +58 -0
  6. data/src/box.rb +90 -0
  7. data/src/color.rb +174 -0
  8. data/src/cursor.rb +69 -0
  9. data/src/editor/editor_base.rb +152 -0
  10. data/src/editor/editor_base_handlers.rb +116 -0
  11. data/src/element.rb +61 -0
  12. data/src/element_bounds.rb +111 -0
  13. data/src/element_box.rb +64 -0
  14. data/src/element_render.rb +102 -0
  15. data/src/element_style.rb +51 -0
  16. data/src/emitter.rb +102 -0
  17. data/src/emitter_state.rb +19 -0
  18. data/src/enterable.rb +93 -0
  19. data/src/event.rb +92 -0
  20. data/src/focus.rb +102 -0
  21. data/src/geometry.rb +53 -0
  22. data/src/image.rb +60 -0
  23. data/src/input.rb +85 -0
  24. data/src/input_grab.rb +17 -0
  25. data/src/input_time.rb +97 -0
  26. data/src/key.rb +114 -0
  27. data/src/log.rb +24 -0
  28. data/src/node.rb +117 -0
  29. data/src/node_attributes.rb +27 -0
  30. data/src/node_visit.rb +52 -0
  31. data/src/renderer.rb +119 -0
  32. data/src/renderer_cursor.rb +18 -0
  33. data/src/renderer_draw.rb +28 -0
  34. data/src/renderer_image.rb +31 -0
  35. data/src/renderer_print.rb +40 -0
  36. data/src/screen.rb +96 -0
  37. data/src/screen_element.rb +59 -0
  38. data/src/screen_input.rb +43 -0
  39. data/src/screen_renderer.rb +53 -0
  40. data/src/style.rb +149 -0
  41. data/src/tco/colouring.rb +248 -0
  42. data/src/tco/config.rb +57 -0
  43. data/src/tco/palette.rb +603 -0
  44. data/src/tco/tco_termgui.rb +30 -0
  45. data/src/termgui.rb +29 -0
  46. data/src/util/css.rb +98 -0
  47. data/src/util/css_query.rb +23 -0
  48. data/src/util/easing.rb +364 -0
  49. data/src/util/hash_object.rb +131 -0
  50. data/src/util/imagemagick.rb +27 -0
  51. data/src/util/justify.rb +20 -0
  52. data/src/util/unicode-categories.rb +572 -0
  53. data/src/util/wrap.rb +102 -0
  54. data/src/util.rb +110 -0
  55. data/src/widget/button.rb +33 -0
  56. data/src/widget/checkbox.rb +47 -0
  57. data/src/widget/col.rb +30 -0
  58. data/src/widget/image.rb +106 -0
  59. data/src/widget/inline.rb +40 -0
  60. data/src/widget/input_number.rb +73 -0
  61. data/src/widget/inputbox.rb +85 -0
  62. data/src/widget/label.rb +33 -0
  63. data/src/widget/modal.rb +69 -0
  64. data/src/widget/row.rb +26 -0
  65. data/src/widget/selectbox.rb +100 -0
  66. data/src/widget/textarea.rb +54 -0
  67. data/src/xml/xml.rb +80 -0
  68. data/test/action_test.rb +34 -0
  69. data/test/box_test.rb +15 -0
  70. data/test/css_test.rb +39 -0
  71. data/test/editor/editor_base_test.rb +201 -0
  72. data/test/element_bounds_test.rb +77 -0
  73. data/test/element_box_test.rb +8 -0
  74. data/test/element_render_test.rb +124 -0
  75. data/test/element_style_test.rb +85 -0
  76. data/test/element_test.rb +10 -0
  77. data/test/emitter_test.rb +108 -0
  78. data/test/event_test.rb +19 -0
  79. data/test/focus_test.rb +37 -0
  80. data/test/geometry_test.rb +12 -0
  81. data/test/input_test.rb +47 -0
  82. data/test/key_test.rb +14 -0
  83. data/test/log_test.rb +21 -0
  84. data/test/node_test.rb +105 -0
  85. data/test/performance/performance1.rb +48 -0
  86. data/test/renderer_test.rb +74 -0
  87. data/test/renderer_test_rect.rb +4 -0
  88. data/test/screen_test.rb +58 -0
  89. data/test/style_test.rb +18 -0
  90. data/test/termgui_test.rb +10 -0
  91. data/test/test_all.rb +30 -0
  92. data/test/util_hash_object_test.rb +93 -0
  93. data/test/util_test.rb +26 -0
  94. data/test/widget/checkbox_test.rb +99 -0
  95. data/test/widget/col_test.rb +87 -0
  96. data/test/widget/inline_test.rb +40 -0
  97. data/test/widget/label_test.rb +94 -0
  98. data/test/widget/row_test.rb +40 -0
  99. data/test/wrap_test.rb +11 -0
  100. data/test/xml_test.rb +77 -0
  101. metadata +148 -0
@@ -0,0 +1,40 @@
1
+ require_relative 'style'
2
+ require_relative 'key'
3
+
4
+ # takes care of printing renderer buffer in different ways
5
+ module RendererPrint
6
+ # prints current buffer as string
7
+ def print
8
+ s = []
9
+ @buffer.each_index do |y|
10
+ @buffer[y].each do |p|
11
+ s .push p.ch
12
+ end
13
+ s .push '\n'
14
+ end
15
+ s.join('')
16
+ end
17
+
18
+ def print_rows
19
+ rows = []
20
+ @buffer.each_index do |y|
21
+ line = []
22
+ @buffer[y].each do |p|
23
+ line .push p.ch
24
+ end
25
+ rows.push(line.join(''))
26
+ end
27
+ rows
28
+ end
29
+
30
+ # prints to stdout a representation in ruby string concatenated syntax so its easy for devs copy&paste for test asserts
31
+ def print_dev_stdout
32
+ print.split('\\n').each { |line| puts "'#{line}\\n' + " }
33
+ end
34
+
35
+ def print_dev
36
+ s = "'' + \n"
37
+ print.split('\\n').each { |line| s = "#{s}#{line}\\n' + \n" }
38
+ s + "''"
39
+ end
40
+ end
data/src/screen.rb ADDED
@@ -0,0 +1,96 @@
1
+ require_relative 'element'
2
+ require_relative 'renderer'
3
+ require_relative 'input'
4
+ require_relative 'event'
5
+ require_relative 'focus'
6
+ require_relative 'action'
7
+ require_relative 'util'
8
+ require_relative 'screen_element'
9
+ require_relative 'screen_input'
10
+ require_relative 'screen_renderer'
11
+
12
+ module TermGui
13
+ # Main user API entry point
14
+ # Manages instances of Input, Event, Renderer (by default disabling its buffer)
15
+ # Is a Node so new elements can be append_child
16
+ # Once `start`is called it will block execution and start an event loop
17
+ # on each interval user input is read and event listeners are called
18
+ class Screen < Node
19
+ include ScreenElement
20
+ include ScreenInput
21
+ include ScreenRenderer
22
+ attr_reader :width, :height, :input_stream, :output_stream, :renderer, :input, :event, :focus, :action
23
+ attr_accessor :silent
24
+
25
+ def initialize(
26
+ children: [], text: '', attributes: {}, exit_keys: %w[q C-c], no_exit_keys: false,
27
+ width: nil, height: nil, silent: false
28
+ )
29
+ super(name: 'screen', children: children, text: text, attributes: attributes, parent: nil)
30
+ install(%i[destroy after_destroy start after_start])
31
+ @width = width == nil ? terminal_width : width
32
+ @height = height == nil ? terminal_height : height
33
+ @input_stream = $stdin
34
+ @silent = silent
35
+ @exit_keys = exit_keys
36
+ @output_stream = $stdout
37
+ @renderer = Renderer.new(@width, @height)
38
+ @input = Input.new
39
+ @event = EventManager.new @input
40
+ @focus = FocusManager.new(root: self, event: @event)
41
+ @action = ActionManager.new(focus: @focus, event: @event)
42
+ @renderer.no_buffer = true
43
+ install_exit_keys unless no_exit_keys
44
+ end
45
+
46
+ def self.new_for_testing(**args)
47
+ instance = new(args.merge(no_exit_keys: true, silent: true))
48
+ instance.renderer.no_buffer = false
49
+ instance
50
+ end
51
+
52
+ def terminal_width
53
+ $stdout.winsize[1]
54
+ rescue StandardError
55
+ 80
56
+ end
57
+
58
+ def terminal_height
59
+ $stdout.winsize[0]
60
+ rescue StandardError
61
+ 24
62
+ end
63
+
64
+ # start listening for user input. This starts an user input event loop
65
+ # that ends when screen.destroy is called
66
+ def start(clean: false)
67
+ emit :start
68
+ unless clean
69
+ clear
70
+ cursor_hide # TODO: move this to a CursorManager :start listener
71
+ render
72
+ end
73
+ emit :after_start
74
+ yield if block_given?
75
+ @input.start
76
+ end
77
+
78
+ def destroy
79
+ emit :destroy
80
+ @input.stop
81
+ cursor_show # TODO: move this to a CursorManager :destroy listener
82
+ emit :after_destroy
83
+ end
84
+
85
+ # writes directly to @output_stream. Shouldn't be used directly since these changes won't be tracked by the buffer.
86
+ def write(s)
87
+ @output_stream.write s unless @silent
88
+ s
89
+ end
90
+
91
+ def alert
92
+ puts "\a"
93
+ end
94
+ end
95
+ end
96
+ Screen = TermGui::Screen
@@ -0,0 +1,59 @@
1
+ require_relative 'geometry'
2
+
3
+ # so screen emulates an Element. TODO: make Screen < Element and get rid of width and height and all of this...
4
+ module ScreenElement
5
+ # complies with Element#render and also is capable of rendering given elements
6
+ def render(element = nil)
7
+ if element == self || element.nil?
8
+ children.each { |child| child.render self }
9
+ elsif !element.nil?
10
+ element.render self
11
+ end
12
+ end
13
+
14
+ def abs_x
15
+ 0
16
+ end
17
+
18
+ def abs_y
19
+ 0
20
+ end
21
+
22
+ def abs_width
23
+ @width
24
+ end
25
+
26
+ def style
27
+ @style ||= Style.new
28
+ @style
29
+ end
30
+
31
+ def style=(s)
32
+ @style = s
33
+ end
34
+
35
+ def final_style
36
+ style
37
+ end
38
+
39
+ def offset
40
+ v = get_attribute('offset')
41
+ unless v
42
+ v = Offset.new
43
+ set_attribute('offset', v)
44
+ end
45
+ v
46
+ end
47
+
48
+ def offset=(value)
49
+ set_attribute('offset', value)
50
+ end
51
+
52
+ def abs_height
53
+ @height
54
+ end
55
+
56
+ def root_screen
57
+ self
58
+ end
59
+ end
@@ -0,0 +1,43 @@
1
+ # adds Input related methods to screen
2
+ module ScreenInput
3
+ attr_accessor :exit_keys
4
+
5
+ # Analog to HTML DOM / Node.js setTimeout() using input event loop
6
+ # @param {Number} seconds
7
+ def set_timeout(seconds = @input.interval, listener = nil, &block)
8
+ the_listener = listener == nil ? block : listener
9
+ throw 'No listener provided' if the_listener == nil
10
+ @input.set_timeout(seconds, the_listener)
11
+ end
12
+
13
+ def clear_timeout(listener)
14
+ @input.clear_timeout(listener)
15
+ end
16
+
17
+ # Analog to HTML DOM / Node.js setInterval() using input event loop
18
+ # @param {Number} seconds
19
+ def set_interval(seconds = @input.interval, listener = nil, &block)
20
+ the_listener = listener == nil ? block : listener
21
+ throw 'No listener provided' if the_listener == nil
22
+ @input.set_interval(seconds, the_listener)
23
+ end
24
+
25
+ def clear_interval(listener)
26
+ @input.clear_interval(listener)
27
+ end
28
+
29
+ def install_exit_keys
30
+ return if @exit_keys_listener
31
+
32
+ @exit_keys_listener = @input.subscribe('key') do |e|
33
+ destroy if @exit_keys.include?(e.key)
34
+ end
35
+ end
36
+
37
+ def uninstall_exit_keys
38
+ return unless @exit_keys_listener
39
+
40
+ @input.off('key', @exit_keys_listener)
41
+ @exit_keys_listener = nil
42
+ end
43
+ end
@@ -0,0 +1,53 @@
1
+ # adds rendering related methods to screen
2
+ module ScreenRenderer
3
+ # renders given text at given position
4
+ def text(x: 0, y: 0, text: ' ', style: nil)
5
+ write @renderer.text(x: x, y: y, text: text, style: style)
6
+ end
7
+
8
+ def rect(x: 0, y: 0, width: self.width, height: self.height, ch: Pixel.EMPTY_CH, style: nil)
9
+ write @renderer.rect(x: x, y: y, width: width, height: height, ch: ch, style: style)
10
+ end
11
+
12
+ def image(x: 0, y: 0, image: nil, ch: Pixel.EMPTY_CH, style: Style.new, fg: false, bg: true, transparent_color: nil, h: height - y, w: width - x)
13
+ write @renderer.image(x: x, y: y, image: image, ch: ch || Pixel.EMPTY_CH, style: style, fg: fg, bg: bg, transparent_color: transparent_color, h: h, w: w)
14
+ end
15
+
16
+ def circle(x: nil, y: nil, radius: nil, stroke_ch: ' ', stroke: nil, fill: nil, fill_ch: stroke_ch)
17
+ write @renderer.circle(x: x, y: y, radius: radius, stroke_ch: stroke_ch, stroke: stroke, fill: fill, fill_ch: fill_ch)
18
+ end
19
+
20
+ def clear
21
+ @renderer.style = Style.new
22
+ write "#{@renderer.clear}#{@renderer.style.print}"
23
+ end
24
+
25
+ def style=(style)
26
+ @renderer.style = style
27
+ write @renderer.style.print
28
+ end
29
+
30
+ def box(x, y, width, height, border_style = :classic, style = nil, content = ' ')
31
+ self.style = style if style
32
+ box = draw_box(width: width, height: height, style: border_style, content: content)
33
+ (box.map.with_index do |line, index|
34
+ text(x: x, y: y + index, text: line, style: style)
35
+ end).join('')
36
+ end
37
+
38
+ def print
39
+ @renderer.print
40
+ end
41
+
42
+ def cursor_move(x, y)
43
+ write @renderer.move(x, y)
44
+ end
45
+
46
+ def cursor_show
47
+ write @renderer.cursor_show
48
+ end
49
+
50
+ def cursor_hide
51
+ write @renderer.cursor_hide
52
+ end
53
+ end
data/src/style.rb ADDED
@@ -0,0 +1,149 @@
1
+ require_relative 'util'
2
+ require_relative 'util/hash_object'
3
+ require_relative 'tco/tco_termgui'
4
+
5
+ module TermGui
6
+ # refers to properties directly implemented using ansi escape codes
7
+ # responsible of printing escape ansi codes for style
8
+ # Styles are data objects, supporting hash to instantiate, assign, equals, print
9
+ class BaseStyle
10
+ include HashObject
11
+
12
+ attr_accessor :fg, :bg, :underline, :bold, :blink, :inverse, :fraktur, :framed
13
+
14
+ def initialize(fg: nil, bg: nil, bold: nil, blink: nil, inverse: nil, underline: nil, framed: nil, fraktur: nil, bright: nil, wrap: nil, border: nil, padding: nil, style: nil)
15
+ # TODO: for some reason **args is not working here that's why we have all subclasses props
16
+ @fg = fg
17
+ @bg = bg
18
+ @bold = bold || bright
19
+ @blink = blink
20
+ @inverse = inverse
21
+ @underline = underline
22
+ @fraktur = fraktur
23
+ @framed = framed
24
+ end
25
+
26
+ # Prints the style as escape sequences.
27
+ # This method shouln't be overriden by subclasses since it only makes sense for basic properties defined here.
28
+ def print(s = nil)
29
+ if s == nil
30
+ TermGui.open_style(self)
31
+ else
32
+ TermGui.print(s, self)
33
+ end
34
+ end
35
+
36
+ def self.fast_colouring(value)
37
+ TermGui.fast_colouring(value)
38
+ end
39
+
40
+ def reset
41
+ @bg = @fg = @wrap = @border = nil
42
+ end
43
+
44
+ # returns true if self has the same properties of given hash or Style and each property value is equals (comparission using ==)
45
+ def equals(style)
46
+ object_equal(self, BaseStyle.from_hash(style))
47
+ end
48
+
49
+ # if a hash is given returns a new Style instance with given properties. If an Style instance if given, returns it.
50
+ # It also ensures focus, action and enter properties are defined cloning self if not
51
+ def self.from_hash(obj)
52
+ if obj == nil
53
+ return nil
54
+ elsif obj.instance_of?(Hash)
55
+ r = merge_hash_into_object obj, new
56
+ else
57
+ r = obj
58
+ end
59
+
60
+ if r.is_a? Style
61
+ r.focus = r.focus || r.clone
62
+ r.border = Border.from_hash(r.border) if r.border
63
+ r.action = r.action || r.clone
64
+ r.enter = r.enter || r.clone
65
+ end
66
+ r
67
+ end
68
+
69
+ def pretty_print(delete_nil = true, delete_empty = true)
70
+ h = to_hash
71
+ h.keys.each do |k|
72
+ h.delete k if delete_nil && h[k] == nil
73
+ if delete_empty && (h[k].respond_to?(:to_hash) || h[k].is_a?(Hash)) && object_variables_to_hash(h[k]).keys.reject { |k| h[k] == nil }.empty?
74
+ h.delete k
75
+ end
76
+ end
77
+ "{#{h.keys.map { |k| "#{k}: #{pretty_print_value(h[k])}" }.join(', ')}}" .split(/, [^\s]+: \{\}/).join('')
78
+ end
79
+
80
+ def pretty_print_value(v)
81
+ v.is_a?(String) ? v : v.respond_to?(:pretty_print) ? v.pretty_print : v.to_s
82
+ end
83
+
84
+ def self.from_json(s)
85
+ r = from_hash(json_parse(s))
86
+ if r.is_a? Style
87
+ r.border = from_hash(r.border || new)
88
+ r.focus = from_hash(r.focus || new)
89
+ r.enter = from_hash(r.enter || new)
90
+ r.action = from_hash(r.action || new)
91
+ end
92
+ r
93
+ end
94
+
95
+ def bright
96
+ @bold
97
+ end
98
+
99
+ def bright=(value)
100
+ @bold = value
101
+ end
102
+ end
103
+
104
+ # Element style. This is the class of `element.style` - `get_attribute('style')``
105
+ class Style < BaseStyle
106
+ attr_accessor :border, :wrap, :padding, :focus, :enter, :action
107
+
108
+ def initialize(**args)
109
+ super
110
+ @wrap = args[:wrap]
111
+ # TODO: move this border checking & init to hash_object
112
+ if args[:border].nil?
113
+ @border = nil
114
+ elsif args[:border].instance_of? Border
115
+ @border = args[:border]
116
+ elsif args[:border].instance_of? Hash
117
+ @border = Border.new
118
+ @border.assign(args[:border])
119
+ end
120
+ padding = args[:padding]
121
+ focus = args[:focus] || clone
122
+ enter = args[:enter] || clone
123
+ action = args[:action] || clone
124
+
125
+ @padding = padding
126
+ @focus = focus
127
+ @enter = enter
128
+ @action = action
129
+ end
130
+ end
131
+
132
+ # style for the border
133
+ class Border < BaseStyle
134
+ attr_reader :style
135
+
136
+ def initialize(**args)
137
+ super
138
+ @style = args[:style]&.to_s || 'single'
139
+ end
140
+
141
+ def style=(style)
142
+ @style = style&.to_s
143
+ end
144
+ end
145
+ end
146
+
147
+ BaseStyle = TermGui::BaseStyle
148
+ Border = TermGui::Border
149
+ Style = TermGui::Style
@@ -0,0 +1,248 @@
1
+ # rubocop:disable Layout/EndAlignment
2
+
3
+ # adapted from tco
4
+ # tco - terminal colouring application and library
5
+ # Copyright (c) 2013, 2014 Radek Pazdera
6
+
7
+ require_relative 'palette'
8
+
9
+ module Tco
10
+ class Colouring
11
+ ANSI_FG_BASE = 30
12
+ ANSI_BG_BASE = 40
13
+
14
+ attr_reader :palette
15
+
16
+ def initialize(configuration)
17
+ @palette = Palette.new configuration.options['palette']
18
+ @output_type = configuration.options['output']
19
+ @disabled = configuration.options['disabled']
20
+
21
+ configuration.colour_values.each do |id, value|
22
+ @palette.set_colour_value(parse_colour_id(id), parse_rgb_value(value))
23
+ end
24
+
25
+ @names = {}
26
+ configuration.names.each do |name, colour_def|
27
+ @names[name] = resolve_colour_def colour_def
28
+ end
29
+
30
+ @styles = {}
31
+ configuration.styles.each do |name, style|
32
+ @styles[name] = Style.new(resolve_colour_def(style[:fg]),
33
+ resolve_colour_def(style[:bg]),
34
+ style[:bright], style[:underline])
35
+ end
36
+ end
37
+
38
+ # Decorate a string according to the style passed. The input string
39
+ # is processed line-by-line (the escape sequences are added to each
40
+ # line). This is due to some problems I've been having with some
41
+ # terminal emulators not handling multi-line coloured sequences well.
42
+ def decorate(string, style)
43
+ # (fg, bg, bright, underline)
44
+ # fg = style.fg
45
+ # bg = style.bg
46
+ # bright = style.bright
47
+ # underline = style.underline
48
+ return string if !STDOUT.isatty || @output_type == :raw || @disabled
49
+
50
+ fg = get_colour_instance style.fg
51
+ bg = get_colour_instance style.bg
52
+
53
+ output = []
54
+ lines = string.lines.map(&:chomp)
55
+ lines = [''] if lines.length.zero?
56
+ lines.each do |line|
57
+ unless line.length < 0
58
+ line = case @palette.type
59
+ when 'ansi' then colour_ansi line, fg, bg
60
+ when 'extended' then colour_extended line, fg, bg
61
+ else raise "Unknown palette '#{@palette.type}'."
62
+ end
63
+
64
+ line = e(1) + line if style.bright
65
+ line = e(4) + line if style.underline
66
+ line = e(5) + line if style.blink
67
+ line = e(7) + line if style.inverse
68
+ line = e(20) + line if style.fraktur
69
+ line = e(51) + line if style.framed
70
+
71
+ if (style.bright || style.underline || style.blink || style.inverse || style.fraktur || style.framed) && (fg == nil) && (bg == nil)
72
+ line << e(0)
73
+ end
74
+ end
75
+
76
+ output.push line
77
+ end
78
+
79
+ output << '' if string =~ /\n$/
80
+ output.join "\n"
81
+ end
82
+
83
+ def get_style(name)
84
+ raise "Style '#{name}' not found." unless @styles.key? name
85
+
86
+ @styles[name]
87
+ end
88
+
89
+ def set_output(output_type)
90
+ raise "Output '#{output_type}' not supported." unless %i[term raw].include? output_type
91
+
92
+ @output_type = output_type
93
+ end
94
+
95
+ def get_best_font_colour(background)
96
+ black = Tco::Colour.new([0, 0, 0])
97
+ white = Tco::Colour.new([255, 255, 255])
98
+
99
+ if background.is_a?(Colour) &&
100
+ (background - black).abs < (background - white).abs
101
+ return white
102
+ end
103
+
104
+ black
105
+ end
106
+
107
+ def get_colour_instance(value)
108
+ if value.is_a?(String)
109
+ resolve_colour_def value
110
+ elsif value.is_a?(Symbol)
111
+ resolve_colour_def value.to_s
112
+ elsif value.is_a?(Array)
113
+ Colour.new value
114
+ elsif value.is_a?(Colour) || value.is_a?(Unknown)
115
+ value
116
+ elsif value == nil
117
+ nil
118
+ else
119
+ raise "Colour value type '#{value.class}' not supported."
120
+ end
121
+ end
122
+
123
+ private
124
+
125
+ def e(seq)
126
+ if @output_type == :raw
127
+ "\\033[#{seq}m"
128
+ else
129
+ "\033[#{seq}m"
130
+ end
131
+ end
132
+
133
+ def colour_ansi(string, fg = nil, bg = nil)
134
+ unless fg == nil
135
+ colour_id = if fg.is_a? Unknown
136
+ fg.id
137
+ else
138
+ @palette.match_colour(fg)
139
+ end
140
+ string = e(colour_id + 30) + string
141
+ end
142
+
143
+ unless bg == nil
144
+ colour_id = if bg.is_a? Unknown
145
+ bg.id
146
+ else
147
+ @palette.match_colour(bg)
148
+ end
149
+ string = e(colour_id + 40) + string
150
+ end
151
+
152
+ string << e(0) unless (fg == nil) && (bg == nil)
153
+
154
+ string
155
+ end
156
+
157
+ def colour_extended(string, fg = nil, bg = nil)
158
+ unless fg == nil
159
+ colour_id = if fg.is_a? Unknown
160
+ fg.id
161
+ else
162
+ @palette.match_colour(fg)
163
+ end
164
+ string = e("38;5;#{colour_id}") + string
165
+ end
166
+
167
+ unless bg == nil
168
+ colour_id = if bg.is_a? Unknown
169
+ bg.id
170
+ else
171
+ @palette.match_colour(bg)
172
+ end
173
+ string = e("48;5;#{colour_id}") + string
174
+ end
175
+
176
+ string << e(0) unless (fg == nil) && (bg == nil)
177
+
178
+ string
179
+ end
180
+
181
+ def parse_colour_id(id_in_string)
182
+ id = String.new(id_in_string)
183
+ if id[0] == '@'
184
+ id[0] = ''
185
+ return id.to_i
186
+ end
187
+
188
+ raise "Invalid colour id #{id_in_string}."
189
+ end
190
+
191
+ def parse_rgb_value(rgb_value_in_string)
192
+ error_msg = "Invalid RGB value '#{rgb_value_in_string}'."
193
+ rgb_value = String.new rgb_value_in_string
194
+ if rgb_value[0] == '#'
195
+ rgb_value[0] = ''
196
+ elsif rgb_value[0..1] == '0x'
197
+ rgb_value[0..1] = ''
198
+ else
199
+ raise error_msg
200
+ end
201
+
202
+ raise error_msg unless rgb_value =~ /^[0-9a-fA-F]+$/
203
+
204
+ case rgb_value.length
205
+ when 3
206
+ rgb_value.scan(/./).map { |c| c.to_i 16 }
207
+ when 6
208
+ rgb_value.scan(/../).map { |c| c.to_i 16 }
209
+ else
210
+ raise error_msg
211
+ end
212
+ end
213
+
214
+ def resolve_colour_name(name)
215
+ raise "Name '#{name}' not found." unless @names.key? name
216
+
217
+ @names[name]
218
+ end
219
+
220
+ def resolve_colour_def(colour_def)
221
+ return nil if colour_def == '' || colour_def == 'default'
222
+
223
+ begin
224
+ id = parse_colour_id colour_def
225
+ if @palette.is_known? id
226
+ Colour.new @palette.get_colour_value id
227
+ else
228
+ Unknown.new id
229
+ end
230
+ rescue RuntimeError
231
+ begin
232
+ Colour.new parse_rgb_value colour_def
233
+ rescue RuntimeError
234
+ begin
235
+ colour_def = resolve_colour_name colour_def
236
+ if colour_def.is_a? String
237
+ resolve_colour_def colour_def
238
+ else
239
+ colour_def
240
+ end
241
+ rescue RuntimeError
242
+ raise "Invalid colour definition '#{colour_def}'."
243
+ end
244
+ end
245
+ end
246
+ end
247
+ end
248
+ end