terminal-table 2.0.0 → 3.0.0

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.
File without changes
@@ -0,0 +1,89 @@
1
+ #!/bin/env ruby
2
+
3
+ $:.unshift File.dirname(__FILE__) + '/../lib'
4
+ require 'terminal-table/import'
5
+
6
+ Terminal::Table::Style.defaults = { :border => :unicode_round }
7
+ # Terminal::Table::UnicodeThickEdgeBorder.new()
8
+
9
+ puts
10
+ puts table(['a', 'b'], [1, 2], [3, 4])
11
+
12
+ puts
13
+ puts table(['name', 'content'], ['ftp.example.com', '1.1.1.1'], ['www.example.com', '|lalalala|lalala|'])
14
+
15
+ puts
16
+ t = table ['a', 'b']
17
+ t.style = {:padding_left => 2, :width => 80}
18
+ t << [1, 2]
19
+ t << [3, 4]
20
+ t << :separator
21
+ t << [4, 6]
22
+ puts t
23
+
24
+ puts
25
+ user_table = table do |v|
26
+ v.title = "Contact Information"
27
+ v.headings = 'First Name', 'Last Name', 'Email'
28
+ v << %w( TJ Holowaychuk tj@vision-media.ca )
29
+ v << %w( Bob Someone bob@vision-media.ca )
30
+ v << %w( Joe Whatever bob@vision-media.ca )
31
+ end
32
+ puts user_table
33
+
34
+ puts
35
+ user_table = table do |v|
36
+ v.style.width = 80
37
+ v.headings = 'First Name', 'Last Name', 'Email'
38
+ v << %w( TJ Holowaychuk tj@vision-media.ca )
39
+ v << %w( Bob Someone bob@vision-media.ca )
40
+ v << %w( Joe Whatever bob@vision-media.ca )
41
+ end
42
+ puts user_table
43
+
44
+ puts
45
+ user_table = table do
46
+ self.headings = 'First Name', 'Last Name', 'Email'
47
+ add_row ['TJ', 'Holowaychuk', 'tj@vision-media.ca']
48
+ add_row ['Bob', 'Someone', 'bob@vision-media.ca']
49
+ add_row ['Joe', 'Whatever', 'joe@vision-media.ca']
50
+ add_separator
51
+ add_row ['Total', { :value => '3', :colspan => 2, :alignment => :right }]
52
+ align_column 1, :center
53
+ end
54
+ puts user_table
55
+
56
+ puts
57
+ user_table = table do
58
+ self.headings = ['First Name', 'Last Name', {:value => 'Phones', :colspan => 2, :alignment => :center}]
59
+ #add_row ['Bob', 'Someone', '123', '456']
60
+ add_row [{:value => "Bob Someone", :colspan => 3, :alignment => :center}, '123456']
61
+ add_row :separator
62
+ add_row ['TJ', 'Holowaychuk', {:value => "No phones\navaiable", :colspan => 2, :alignment => :center}]
63
+ add_row :separator
64
+ add_row ['Joe', 'Whatever', '4324', '343242']
65
+ end
66
+ puts user_table
67
+
68
+ rows = []
69
+ rows << ['Lines', 100]
70
+ rows << ['Comments', 20]
71
+ rows << ['Ruby', 70]
72
+ rows << ['JavaScript', 30]
73
+ puts table([nil, 'Lines'], *rows)
74
+
75
+ rows = []
76
+ rows << ['Lines', 100]
77
+ rows << ['Comments', 20]
78
+ rows << ['Ruby', 70]
79
+ rows << ['JavaScript', 30]
80
+ puts table(nil, *rows)
81
+
82
+ rows = []
83
+ rows << ['Lines', 100]
84
+ rows << ['Comments', 20]
85
+ rows << ['Ruby', 70]
86
+ rows << ['JavaScript', 30]
87
+ table = table([{ :value => 'Stats', :colspan => 2, :alignment => :center }], *rows)
88
+ table.align_column 1, :right
89
+ puts table
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Methods to suppress left/right borders using border_left & border_right
4
+
5
+ require_relative "../lib/terminal-table"
6
+ table = Terminal::Table.new do |t|
7
+ t.headings = ['id', 'name']
8
+ t.rows = [[1, 'One'], [2, 'Two'], [3, 'Three']]
9
+ t.style = { :border_left => false, :border_top => false, :border_bottom => false }
10
+ end
11
+
12
+ puts table
13
+ puts
14
+
15
+ # no right
16
+ table.style = {:border_right => false }
17
+ puts table
18
+ puts
19
+
20
+ # no right
21
+ table.style = {:border_left => true }
22
+ puts table
23
+ puts
24
+
25
+ table.style.border = Terminal::Table::UnicodeBorder.new
26
+ puts table
27
+
28
+
29
+ table.style = {:border_right => false, :border_left => true }
30
+ puts table
31
+
32
+ table.style = {:border_right => true, :border_left => false }
33
+ puts table
34
+
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require_relative "../lib/terminal-table"
3
+ puts Terminal::Table.new(headings: ['heading A', 'heading B'], rows: [['a', 'b'], ['a', 'b']], style: {border: Terminal::Table::MarkdownBorder.new})
4
+
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env ruby
2
+ require 'colorize'
3
+ require_relative '../lib/terminal-table.rb'
4
+
5
+ original_sample_data = [
6
+ ["Sep 2016", 33, [-38, -53.52], 46, [-25, -35.21]],
7
+ ["Oct 2016", 35, [2, 6.06], 50, [4, 8.69]]
8
+ ]
9
+
10
+ table = Terminal::Table.new headings: ["Month".cyan,"Monthly IT".cyan,"IT Difference OPM".cyan,
11
+ "Monthly OOT".cyan,"OOT Difference OPM".cyan], rows: original_sample_data
12
+
13
+ table.style = { padding_left: 2, padding_right: 2, border_x: "-".blue, border_y: "|".blue, border_i: "+".blue }
14
+
15
+ puts table
16
+
17
+ puts ""
18
+ puts "^ good table"
19
+ puts "v wonky table"
20
+ puts ""
21
+
22
+ split_column_sample_data = [
23
+ ["Sep 2016", 33, -38, -53.52, 46, -25, -35.21],
24
+ ["Oct 2016", 35, 2, 6.06, 50, 4, 8.69]
25
+ ]
26
+
27
+ table = Terminal::Table.new headings: ["Month".cyan,"Monthly IT".cyan,
28
+ {value: "IT Difference OPM".cyan, colspan: 2}, "Monthly OOT".cyan,
29
+ {value: "OOT Difference OPM".cyan, colspan: 2}], rows: split_column_sample_data
30
+
31
+ table.style = { padding_left: 2, padding_right: 2, border_x: "-".blue, border_y: "|".blue, border_i: "+".blue }
32
+
33
+ puts table
34
+
35
+
36
+ table = Terminal::Table.new headings: ["Month","Monthly IT",
37
+ {value: "IT Difference OPM", colspan: 2}, "Monthly OOT",
38
+ {value: "OOT Difference OPM", colspan: 2}], rows: split_column_sample_data
39
+
40
+ table.style = { padding_left: 2, padding_right: 2, border_x: "-".blue, border_y: "|".cyan, border_i: "+" }
41
+
42
+ puts table
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env ruby
2
+ require_relative "../lib/terminal-table"
3
+
4
+ #
5
+ # An example of how to manually add separators with non-default
6
+ # border_type to enable a footer row.
7
+ #
8
+ table = Terminal::Table.new do |t|
9
+ # set the style
10
+ t.style = { border: :unicode_thick_edge }
11
+
12
+ # header row
13
+ t.headings = ['fruit', 'count']
14
+
15
+ # some row data
16
+ t.add_row ['apples', 7]
17
+ t.add_row ['bananas', 19]
18
+ t.add_separator border_type: :strong
19
+ # footer row
20
+ t.add_row ['total', 26]
21
+ end
22
+
23
+ puts table.render
@@ -21,6 +21,6 @@
21
21
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
22
  #++
23
23
 
24
- %w(cell row separator style table table_helper version).each do |file|
25
- require "terminal-table/#{file}"
24
+ %w(cell row separator style table table_helper util version).each do |file|
25
+ require_relative "./terminal-table/#{file}"
26
26
  end
@@ -57,7 +57,7 @@ module Terminal
57
57
  def render(line = 0)
58
58
  left = " " * @table.style.padding_left
59
59
  right = " " * @table.style.padding_right
60
- display_width = Unicode::DisplayWidth.of(escape(lines[line]))
60
+ display_width = Unicode::DisplayWidth.of(Util::ansi_escape(lines[line]))
61
61
  render_width = lines[line].to_s.size - display_width + width
62
62
  align("#{left}#{lines[line]}#{right}", alignment, render_width + @table.cell_padding)
63
63
  end
@@ -68,7 +68,7 @@ module Terminal
68
68
  # removes all ANSI escape sequences (e.g. color)
69
69
 
70
70
  def value_for_column_width_recalc
71
- lines.map{ |s| escape(s) }.max_by{ |s| Unicode::DisplayWidth.of(s) }
71
+ lines.map{ |s| Util::ansi_escape(s) }.max_by{ |s| Unicode::DisplayWidth.of(s) }
72
72
  end
73
73
 
74
74
  ##
@@ -81,14 +81,6 @@ module Terminal
81
81
  end
82
82
  inner_width + padding
83
83
  end
84
-
85
- ##
86
- # removes all ANSI escape sequences (e.g. color)
87
- def escape(line)
88
- line.to_s.gsub(/\x1b(\[|\(|\))[;?0-9]*[0-9A-Za-z]/, '').
89
- gsub(/\x1b(\[|\(|\))[;?0-9]*[0-9A-Za-z]/, '').
90
- gsub(/(\x03|\x1a)/, '')
91
- end
92
84
  end
93
85
  end
94
86
  end
@@ -36,17 +36,31 @@ module Terminal
36
36
  end
37
37
 
38
38
  def render
39
- y = @table.style.border_y
39
+ vleft, vcenter, vright = @table.style.vertical
40
40
  (0...height).to_a.map do |line|
41
- y + cells.map do |cell|
41
+ vleft + cells.map do |cell|
42
42
  cell.render(line)
43
- end.join(y) + y
43
+ end.join(vcenter) + vright
44
44
  end.join("\n")
45
45
  end
46
46
 
47
47
  def number_of_columns
48
48
  @cells.collect(&:colspan).inject(0, &:+)
49
49
  end
50
+
51
+ # used to find indices where we have table '+' crossings.
52
+ # in cases where the colspan > 1, then we will skip over some numbers
53
+ # if colspan is always 1, then the list should be incrementing by 1.
54
+ #
55
+ # skip 0 entry, because it's the left side.
56
+ # skip last entry, because it's the right side.
57
+ # we only care about "+/T" style crossings.
58
+ def crossings
59
+ idx = 0
60
+ @cells[0...-1].map { |c| idx += c.colspan }
61
+ end
62
+
50
63
  end
64
+
51
65
  end
52
66
  end
@@ -2,13 +2,65 @@ module Terminal
2
2
  class Table
3
3
  class Separator < Row
4
4
 
5
+ ##
6
+ # `prevrow`, `nextrow` contain references to adjacent rows.
7
+ #
8
+ # `border_type` is a symbol used to control which type of border is used
9
+ # on the separator (:top for top-edge, :bot for bottom-edge,
10
+ # :div for interior, and :strong for emphasized-interior)
11
+ #
12
+ # `implicit` is false for user-added separators, and true for
13
+ # implicit/auto-generated separators.
14
+
15
+ def initialize(*args, border_type: :div, implicit: false)
16
+ super
17
+ @prevrow, @nextrow = nil, nil
18
+ @border_type = border_type
19
+ @implicit = implicit
20
+ end
21
+
22
+ attr_accessor :border_type
23
+ attr_reader :implicit
24
+
5
25
  def render
6
- arr_x = (0...@table.number_of_columns).to_a.map do |i|
7
- @table.style.border_x * (@table.column_width(i) + @table.cell_padding)
26
+ left_edge, ctrflat, ctrud, right_edge, ctrdn, ctrup = @table.style.horizontal(border_type)
27
+
28
+ prev_crossings = @prevrow.respond_to?(:crossings) ? @prevrow.crossings : []
29
+ next_crossings = @nextrow.respond_to?(:crossings) ? @nextrow.crossings : []
30
+ rval = [left_edge]
31
+ numcols = @table.number_of_columns
32
+ (0...numcols).each do |idx|
33
+ rval << ctrflat * (@table.column_width(idx) + @table.cell_padding)
34
+ pcinc = prev_crossings.include?(idx+1)
35
+ ncinc = next_crossings.include?(idx+1)
36
+ border_center = if pcinc && ncinc
37
+ ctrud
38
+ elsif pcinc
39
+ ctrup
40
+ elsif ncinc
41
+ ctrdn
42
+ elsif !ctrud.empty?
43
+ # special case if the center-up-down intersection is empty
44
+ # which happens when verticals/intersections are removed. in that case
45
+ # we do not want to replace with a flat element so return empty-string in else block
46
+ ctrflat
47
+ else
48
+ ''
49
+ end
50
+ rval << border_center if idx < numcols-1
8
51
  end
9
- border_i = @table.style.border_i
10
- border_i + arr_x.join(border_i) + border_i
52
+
53
+ rval << right_edge
54
+ rval.join
55
+ end
56
+
57
+ # Save off neighboring rows, so that we can use them later in determining
58
+ # which types of table edges to use.
59
+ def save_adjacent_rows(prevrow, nextrow)
60
+ @prevrow = prevrow
61
+ @nextrow = nextrow
11
62
  end
63
+
12
64
  end
13
65
  end
14
66
  end
@@ -1,5 +1,171 @@
1
+ # coding: utf-8
2
+ require 'forwardable'
3
+
1
4
  module Terminal
2
5
  class Table
6
+
7
+ class Border
8
+
9
+ attr_accessor :data, :top, :bottom, :left, :right
10
+ def initialize
11
+ @top, @bottom, @left, @right = true, true, true, true
12
+ end
13
+ def []=(key, val)
14
+ @data[key] = val
15
+ end
16
+ def [](key)
17
+ @data[key]
18
+ end
19
+ def initialize_dup(other)
20
+ super
21
+ @data = other.data.dup
22
+ end
23
+ def remove_verticals
24
+ self.class.const_get("VERTICALS").each { |key| @data[key] = "" }
25
+ self.class.const_get("INTERSECTIONS").each { |key| @data[key] = "" }
26
+ end
27
+ def remove_horizontals
28
+ self.class.const_get("HORIZONTALS").each { |key| @data[key] = "" }
29
+ end
30
+
31
+ # If @left, return the edge else empty-string.
32
+ def maybeleft(key) ; @left ? @data[key] : '' ; end
33
+
34
+ # If @right, return the edge else empty-string.
35
+ def mayberight(key) ; @right ? @data[key] : '' ; end
36
+
37
+ end
38
+
39
+ class AsciiBorder < Border
40
+ HORIZONTALS = %i[x]
41
+ VERTICALS = %i[y]
42
+ INTERSECTIONS = %i[i]
43
+
44
+ def initialize
45
+ super
46
+ @data = { x: "-", y: "|", i: "+" }
47
+ end
48
+
49
+ # Get vertical border elements
50
+ # @return [Array] 3-element list of [left, center, right]
51
+ def vertical
52
+ [maybeleft(:y), @data[:y], mayberight(:y)] # left, center, right
53
+ end
54
+
55
+ # Get horizontal border elements
56
+ # @return [Array] a 6 element list of: [i-left, horizontal-bar, i-up/down, i-right, i-down, i-up]
57
+ def horizontal(_type)
58
+ x, i = @data[:x], @data[:i]
59
+ [maybeleft(:i), x, i, mayberight(:i), i, i]
60
+ end
61
+ end
62
+
63
+ class MarkdownBorder < AsciiBorder
64
+ def initialize
65
+ super
66
+ @top, @bottom = false, false
67
+ @data = { x: "-", y: "|", i: "|" }
68
+ end
69
+ end
70
+
71
+ class UnicodeBorder < Border
72
+
73
+ ALLOWED_SEPARATOR_BORDER_STYLES = %i[
74
+ top bot
75
+ div dash dot3 dot4
76
+ thick thick_dash thick_dot3 thick_dot4
77
+ heavy heavy_dash heavy_dot3 heavy_dot4
78
+ bold bold_dash bold_dot3 bold_dot4
79
+ double
80
+ ]
81
+
82
+ HORIZONTALS = %i[x sx ax bx nx bx_dot3 bx_dot4 bx_dash x_dot3 x_dot4 x_dash]
83
+ VERTICALS = %i[y yw ye]
84
+ INTERSECTIONS = %i[nw n ne nd
85
+ aw ai ae ad au
86
+ bw bi be bd bu
87
+ w i e dn up
88
+ sw s se su]
89
+ def initialize
90
+ super
91
+ @data = {
92
+ nil => nil,
93
+ nw: "┌", nx: "─", n: "┬", ne: "┐",
94
+ yw: "│", y: "│", ye: "│",
95
+ aw: "╞", ax: "═", ai: "╪", ae: "╡", ad: '╤', au: "╧", # double
96
+ bw: "┝", bx: "━", bi: "┿", be: "┥", bd: '┯', bu: "┷", # heavy/bold/thick
97
+ w: "├", x: "─", i: "┼", e: "┤", dn: "┬", up: "┴", # normal div
98
+ sw: "└", sx: "─", s: "┴", se: "┘",
99
+ # alternative dots/dashes
100
+ x_dot4: '┈', x_dot3: '┄', x_dash: '╌',
101
+ bx_dot4: '┉', bx_dot3: '┅', bx_dash: '╍',
102
+ }
103
+ end
104
+ # Get vertical border elements
105
+ # @return [Array] 3-element list of [left, center, right]
106
+ def vertical
107
+ [maybeleft(:yw), @data[:y], mayberight(:ye)]
108
+ end
109
+
110
+ # Get horizontal border elements
111
+ # @return [Array] a 6 element list of: [i-left, horizontal-bar, i-up/down, i-right, i-down, i-up]
112
+ def horizontal(type)
113
+ raise ArgumentError, "Border type is #{type.inspect}, must be one of #{ALLOWED_SEPARATOR_BORDER_STYLES.inspect}" unless ALLOWED_SEPARATOR_BORDER_STYLES.include?(type)
114
+ lookup = case type
115
+ when :top
116
+ [:nw, :nx, :n, :ne, :n, nil]
117
+ when :bot
118
+ [:sw, :sx, :s, :se, nil, :s]
119
+ when :double
120
+ # typically used for the separator below the heading row or above a footer row)
121
+ [:aw, :ax, :ai, :ae, :ad, :au]
122
+ when :thick, :thick_dash, :thick_dot3, :thick_dot4,
123
+ :heavy, :heavy_dash, :heavy_dot3, :heavy_dot4,
124
+ :bold, :bold_dash, :bold_dot3, :bold_dot4
125
+ # alternate thick/bold border
126
+ xref = type.to_s.sub(/^(thick|heavy|bold)/,'bx').to_sym
127
+ [:bw, xref, :bi, :be, :bd, :bu]
128
+ when :dash, :dot3, :dot4
129
+ # alternate thin dividers
130
+ xref = "x_#{type}".to_sym
131
+ [:w, xref, :i, :e, :dn, :up]
132
+ else # :div (center, non-emphasized)
133
+ [:w, :x, :i, :e, :dn, :up]
134
+ end
135
+ rval = lookup.map { |key| @data.fetch(key) }
136
+ rval[0] = '' unless @left
137
+ rval[3] = '' unless @right
138
+ rval
139
+ end
140
+ end
141
+
142
+ # Unicode Border With rounded edges
143
+ class UnicodeRoundBorder < UnicodeBorder
144
+ def initialize
145
+ super
146
+ @data.merge!({nw: '╭', ne: '╮', sw: '╰', se: '╯'})
147
+ end
148
+ end
149
+
150
+ # Unicode Border with thick outer edges
151
+ class UnicodeThickEdgeBorder < UnicodeBorder
152
+ def initialize
153
+ super
154
+ @data = {
155
+ nil => nil,
156
+ nw: "┏", nx: "━", n: "┯", ne: "┓", nd: nil,
157
+ yw: "┃", y: "│", ye: "┃",
158
+ aw: "┣", ax: "═", ai: "╪", ae: "┫", ad: '╤', au: "╧", # double
159
+ bw: "┣", bx: "━", bi: "┿", be: "┫", bd: '┯', bu: "┷", # heavy/bold/thick
160
+ w: "┠", x: "─", i: "┼", e: "┨", dn: "┬", up: "┴", # normal div
161
+ sw: "┗", sx: "━", s: "┷", se: "┛", su: nil,
162
+ # alternative dots/dashes
163
+ x_dot4: '┈', x_dot3: '┄', x_dash: '╌',
164
+ bx_dot4: '┉', bx_dot3: '┅', bx_dash: '╍',
165
+ }
166
+ end
167
+ end
168
+
3
169
  # A Style object holds all the formatting information for a Table object
4
170
  #
5
171
  # To create a table with a certain style, use either the constructor
@@ -22,20 +188,51 @@ module Terminal
22
188
  # Terminal::Table::Style.defaults = {:width => 80}
23
189
  #
24
190
  class Style
191
+ extend Forwardable
192
+ def_delegators :@border, :vertical, :horizontal, :remove_verticals, :remove_horizontals
193
+
25
194
  @@defaults = {
26
- :border_x => "-", :border_y => "|", :border_i => "+",
27
- :border_top => true, :border_bottom => true,
195
+ :border => AsciiBorder.new,
28
196
  :padding_left => 1, :padding_right => 1,
29
197
  :margin_left => '',
30
198
  :width => nil, :alignment => nil,
31
- :all_separators => false
199
+ :all_separators => false,
32
200
  }
33
201
 
34
- attr_accessor :border_x
35
- attr_accessor :border_y
36
- attr_accessor :border_i
37
- attr_accessor :border_top
38
- attr_accessor :border_bottom
202
+ ## settors/gettor for legacy ascii borders
203
+ def border_x=(val) ; @border[:x] = val ; end
204
+ def border_y=(val) ; @border[:y] = val ; end
205
+ def border_i=(val) ; @border[:i] = val ; end
206
+ def border_y ; @border[:y] ; end
207
+ def border_y_width ; Util::ansi_escape(@border[:y]).length ; end
208
+
209
+ # Accessor for instance of Border
210
+ attr_reader :border
211
+ def border=(val)
212
+ if val.is_a? Symbol
213
+ # convert symbol name like :foo_bar to get class FooBarBorder
214
+ klass_str = val.to_s.split('_').collect(&:capitalize).join + "Border"
215
+ begin
216
+ klass = Terminal::Table::const_get(klass_str)
217
+ @border = klass.new
218
+ rescue NameError
219
+ raise "Cannot lookup class Terminal::Table::#{klass_str} from symbol #{val.inspect}"
220
+ end
221
+ else
222
+ @border = val
223
+ end
224
+ end
225
+
226
+ def border_top=(val) ; @border.top = val ; end
227
+ def border_bottom=(val) ; @border.bottom = val ; end
228
+ def border_left=(val) ; @border.left = val ; end
229
+ def border_right=(val) ; @border.right = val ; end
230
+
231
+ def border_top ; @border.top ; end
232
+ def border_bottom ; @border.bottom ; end
233
+ def border_left ; @border.left ; end
234
+ def border_right ; @border.right ; end
235
+
39
236
 
40
237
  attr_accessor :padding_left
41
238
  attr_accessor :padding_right
@@ -47,23 +244,30 @@ module Terminal
47
244
 
48
245
  attr_accessor :all_separators
49
246
 
50
-
247
+
51
248
  def initialize options = {}
52
249
  apply self.class.defaults.merge(options)
53
250
  end
54
251
 
55
252
  def apply options
56
- options.each { |m, v| __send__ "#{m}=", v }
253
+ options.each do |m, v|
254
+ __send__ "#{m}=", v
255
+ end
57
256
  end
58
-
257
+
59
258
  class << self
60
259
  def defaults
61
- @@defaults
260
+ klass_defaults = @@defaults.dup
261
+ # border is an object that needs to be duplicated on instantiation,
262
+ # otherwise everything will be referencing the same object-id.
263
+ klass_defaults[:border] = klass_defaults[:border].dup
264
+ klass_defaults
62
265
  end
63
-
266
+
64
267
  def defaults= options
65
268
  @@defaults = defaults.merge(options)
66
269
  end
270
+
67
271
  end
68
272
 
69
273
  def on_change attr
@@ -74,6 +278,7 @@ module Terminal
74
278
  yield attr.to_sym, value
75
279
  end
76
280
  end
281
+
77
282
  end
78
283
  end
79
284
  end