tty 0.0.6 → 0.0.7
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.
- data/README.md +78 -12
- data/benchmarks/shell.rb +26 -0
- data/benchmarks/table.rb +35 -0
- data/lib/tty.rb +23 -1
- data/lib/tty/coercer.rb +13 -0
- data/lib/tty/coercer/boolean.rb +39 -0
- data/lib/tty/coercer/float.rb +23 -0
- data/lib/tty/coercer/integer.rb +23 -0
- data/lib/tty/coercer/range.rb +33 -0
- data/lib/tty/shell.rb +6 -2
- data/lib/tty/shell/question.rb +158 -138
- data/lib/tty/shell/reader.rb +92 -0
- data/lib/tty/shell/response.rb +219 -0
- data/lib/tty/shell/response_delegation.rb +53 -0
- data/lib/tty/table.rb +90 -16
- data/lib/tty/table/border.rb +34 -8
- data/lib/tty/table/border/ascii.rb +16 -25
- data/lib/tty/table/border/null.rb +0 -6
- data/lib/tty/table/border/unicode.rb +16 -25
- data/lib/tty/table/column_set.rb +1 -1
- data/lib/tty/table/error.rb +10 -0
- data/lib/tty/table/operation/wrapped.rb +0 -6
- data/lib/tty/table/orientation.rb +57 -0
- data/lib/tty/table/orientation/horizontal.rb +19 -0
- data/lib/tty/table/orientation/vertical.rb +19 -0
- data/lib/tty/table/renderer.rb +7 -0
- data/lib/tty/table/renderer/ascii.rb +1 -1
- data/lib/tty/table/renderer/basic.rb +2 -2
- data/lib/tty/table/renderer/unicode.rb +1 -1
- data/lib/tty/table/validatable.rb +20 -0
- data/lib/tty/terminal.rb +15 -14
- data/lib/tty/terminal/color.rb +1 -1
- data/lib/tty/terminal/echo.rb +41 -0
- data/lib/tty/terminal/home.rb +31 -0
- data/lib/tty/text.rb +85 -0
- data/lib/tty/text/truncation.rb +83 -0
- data/lib/tty/text/wrapping.rb +96 -0
- data/lib/tty/version.rb +1 -1
- data/spec/tty/coercer/boolean/coerce_spec.rb +113 -0
- data/spec/tty/coercer/float/coerce_spec.rb +32 -0
- data/spec/tty/coercer/integer/coerce_spec.rb +39 -0
- data/spec/tty/coercer/range/coerce_spec.rb +73 -0
- data/spec/tty/shell/ask_spec.rb +14 -1
- data/spec/tty/shell/question/argument_spec.rb +30 -0
- data/spec/tty/shell/question/character_spec.rb +16 -0
- data/spec/tty/shell/question/default_spec.rb +25 -0
- data/spec/tty/shell/question/in_spec.rb +23 -0
- data/spec/tty/shell/question/initialize_spec.rb +11 -211
- data/spec/tty/shell/question/modifier/whitespace_spec.rb +1 -1
- data/spec/tty/shell/question/modify_spec.rb +44 -0
- data/spec/tty/shell/question/valid_spec.rb +46 -0
- data/spec/tty/shell/question/validate_spec.rb +30 -0
- data/spec/tty/shell/reader/getc_spec.rb +40 -0
- data/spec/tty/shell/response/read_bool_spec.rb +41 -0
- data/spec/tty/shell/response/read_char_spec.rb +17 -0
- data/spec/tty/shell/response/read_date_spec.rb +20 -0
- data/spec/tty/shell/response/read_email_spec.rb +43 -0
- data/spec/tty/shell/response/read_multiple_spec.rb +24 -0
- data/spec/tty/shell/response/read_number_spec.rb +29 -0
- data/spec/tty/shell/response/read_range_spec.rb +29 -0
- data/spec/tty/shell/response/read_spec.rb +68 -0
- data/spec/tty/shell/response/read_string_spec.rb +19 -0
- data/spec/tty/table/access_spec.rb +6 -0
- data/spec/tty/table/border/new_spec.rb +3 -3
- data/spec/tty/table/initialize_spec.rb +17 -1
- data/spec/tty/table/options_spec.rb +7 -1
- data/spec/tty/table/orientation_spec.rb +98 -0
- data/spec/tty/table/renders_with_spec.rb +76 -0
- data/spec/tty/table/rotate_spec.rb +72 -0
- data/spec/tty/table/to_s_spec.rb +13 -1
- data/spec/tty/table/validatable/validate_options_spec.rb +34 -0
- data/spec/tty/terminal/color/remove_spec.rb +34 -1
- data/spec/tty/terminal/echo_spec.rb +22 -0
- data/spec/tty/text/truncate_spec.rb +13 -0
- data/spec/tty/text/truncation/initialize_spec.rb +29 -0
- data/spec/tty/text/truncation/truncate_spec.rb +73 -0
- data/spec/tty/text/wrap_spec.rb +14 -0
- data/spec/tty/text/wrapping/initialize_spec.rb +25 -0
- data/spec/tty/text/wrapping/wrap_spec.rb +80 -0
- data/tty.gemspec +1 -0
- metadata +101 -8
@@ -0,0 +1,92 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
module TTY
|
4
|
+
class Shell
|
5
|
+
|
6
|
+
# A class responsible for reading character input from STDIN
|
7
|
+
class Reader
|
8
|
+
|
9
|
+
# @api private
|
10
|
+
attr_reader :shell
|
11
|
+
private :shell
|
12
|
+
|
13
|
+
# Key input constants for decimal codes
|
14
|
+
CARRIAGE_RETURN = 13.freeze
|
15
|
+
NEWLINE = 10.freeze
|
16
|
+
BACKSPACE = 127.freeze
|
17
|
+
DELETE = 8.freeze
|
18
|
+
|
19
|
+
# Initialize a Reader
|
20
|
+
#
|
21
|
+
# @api public
|
22
|
+
def initialize(shell=nil)
|
23
|
+
@shell = shell || Shell.new
|
24
|
+
end
|
25
|
+
|
26
|
+
# Get input in unbuffered mode.
|
27
|
+
#
|
28
|
+
#
|
29
|
+
# @api bublic
|
30
|
+
def buffer(&block)
|
31
|
+
bufferring = shell.output.sync
|
32
|
+
# Immediately flush output
|
33
|
+
shell.output.sync = true
|
34
|
+
|
35
|
+
value = block.call if block_given?
|
36
|
+
|
37
|
+
shell.output.sync = bufferring
|
38
|
+
value
|
39
|
+
end
|
40
|
+
|
41
|
+
# Get a value from STDIN one key at a time. Each key press is echoed back
|
42
|
+
# to the shell masked with character(if given). The input finishes when
|
43
|
+
# enter key is pressed.
|
44
|
+
#
|
45
|
+
# @param [String] mask
|
46
|
+
# the character to use as mask
|
47
|
+
#
|
48
|
+
# @return [String]
|
49
|
+
#
|
50
|
+
# @api public
|
51
|
+
def getc(mask=(not_set=true))
|
52
|
+
value = ""
|
53
|
+
|
54
|
+
buffer do
|
55
|
+
begin
|
56
|
+
while (char = shell.input.getbyte) and
|
57
|
+
!(char == CARRIAGE_RETURN || char == NEWLINE)
|
58
|
+
|
59
|
+
if (char == BACKSPACE || char == DELETE)
|
60
|
+
value.slice!(-1, 1) unless value.empty?
|
61
|
+
else
|
62
|
+
print_char char, not_set, mask
|
63
|
+
value << char
|
64
|
+
end
|
65
|
+
end
|
66
|
+
ensure
|
67
|
+
TTY.terminal.echo_on
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
value
|
72
|
+
end
|
73
|
+
|
74
|
+
# Get a value from STDIN using line input.
|
75
|
+
#
|
76
|
+
# @api public
|
77
|
+
def gets
|
78
|
+
shell.input.gets
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
# Print out character back to shell STDOUT
|
84
|
+
#
|
85
|
+
# @api private
|
86
|
+
def print_char(char, not_set, mask)
|
87
|
+
shell.output.putc((not_set || !mask) ? char : mask)
|
88
|
+
end
|
89
|
+
|
90
|
+
end # Reader
|
91
|
+
end # Shell
|
92
|
+
end # TTY
|
@@ -0,0 +1,219 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'date'
|
4
|
+
|
5
|
+
module TTY
|
6
|
+
# A class responsible for shell prompt interactions.
|
7
|
+
class Shell
|
8
|
+
|
9
|
+
# A class representing a question.
|
10
|
+
class Response
|
11
|
+
|
12
|
+
VALID_TYPES = [:boolean, :string, :symbol, :integer, :float, :date, :datetime]
|
13
|
+
attr_reader :reader
|
14
|
+
private :reader
|
15
|
+
|
16
|
+
attr_reader :shell
|
17
|
+
private :shell
|
18
|
+
|
19
|
+
attr_reader :question
|
20
|
+
private :question
|
21
|
+
|
22
|
+
# Initialize a Response
|
23
|
+
#
|
24
|
+
# @api public
|
25
|
+
def initialize(question, shell=nil)
|
26
|
+
@question = question
|
27
|
+
@shell = shell || Shell.new
|
28
|
+
@reader = Reader.new(shell)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Read input from STDIN either character or line
|
32
|
+
#
|
33
|
+
# @param [Symbol] type
|
34
|
+
#
|
35
|
+
# @return [undefined]
|
36
|
+
#
|
37
|
+
# @api public
|
38
|
+
def read(type=nil)
|
39
|
+
question.evaluate_response read_input
|
40
|
+
end
|
41
|
+
|
42
|
+
# @api private
|
43
|
+
def read_input
|
44
|
+
reader = Reader.new(shell)
|
45
|
+
|
46
|
+
if question.mask? && question.echo?
|
47
|
+
reader.getc(question.mask)
|
48
|
+
else
|
49
|
+
TTY.terminal.echo(question.echo) {
|
50
|
+
question.character? ? reader.getc(question.mask) : reader.gets
|
51
|
+
}
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Read answer and cast to String type
|
56
|
+
#
|
57
|
+
# @param [String] error
|
58
|
+
# error to display on failed conversion to string type
|
59
|
+
#
|
60
|
+
# @api public
|
61
|
+
def read_string(error=nil)
|
62
|
+
question.evaluate_response String(read_input)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Read answer's first character
|
66
|
+
#
|
67
|
+
# @api public
|
68
|
+
def read_char
|
69
|
+
question.character true
|
70
|
+
question.evaluate_response String(read_input).chars.to_a[0]
|
71
|
+
end
|
72
|
+
|
73
|
+
# Read multiple line answer and cast to String type
|
74
|
+
#
|
75
|
+
# @api public
|
76
|
+
def read_text
|
77
|
+
question.evaluate_response String(read_input)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Read ansewr and cast to Symbol type
|
81
|
+
#
|
82
|
+
# @api public
|
83
|
+
def read_symbol(error=nil)
|
84
|
+
question.evaluate_response read_input.to_sym
|
85
|
+
end
|
86
|
+
|
87
|
+
# Read answer from predifined choicse
|
88
|
+
#
|
89
|
+
# @api public
|
90
|
+
def read_choice(type=nil)
|
91
|
+
question.argument(:required) unless question.default?
|
92
|
+
question.evaluate_response read_input
|
93
|
+
end
|
94
|
+
|
95
|
+
# Read integer value
|
96
|
+
#
|
97
|
+
# @api public
|
98
|
+
def read_int(error=nil)
|
99
|
+
question.evaluate_response TTY::Coercer::Integer.coerce(read_input)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Read float value
|
103
|
+
#
|
104
|
+
# @api public
|
105
|
+
def read_float(error=nil)
|
106
|
+
question.evaluate_response TTY::Coercer::Float.coerce(read_input)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Read regular expression
|
110
|
+
#
|
111
|
+
# @api public
|
112
|
+
def read_regex(error=nil)
|
113
|
+
question.evaluate_response Kernel.send(:Regex, read_input)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Read range expression
|
117
|
+
#
|
118
|
+
# @api public
|
119
|
+
def read_range
|
120
|
+
question.evaluate_response TTY::Coercer::Range.coerce(read_input)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Read date
|
124
|
+
#
|
125
|
+
# @api public
|
126
|
+
def read_date
|
127
|
+
question.evaluate_response Date.parse(read_input)
|
128
|
+
end
|
129
|
+
|
130
|
+
# Read datetime
|
131
|
+
#
|
132
|
+
# @api public
|
133
|
+
def read_datetime
|
134
|
+
question.evaluate_response DateTime.parse(read_input)
|
135
|
+
end
|
136
|
+
|
137
|
+
# Read boolean
|
138
|
+
#
|
139
|
+
# @api public
|
140
|
+
def read_bool(error=nil)
|
141
|
+
question.evaluate_response TTY::Coercer::Boolean.coerce read_input
|
142
|
+
end
|
143
|
+
|
144
|
+
# Read file contents
|
145
|
+
#
|
146
|
+
# @api public
|
147
|
+
def read_file(error=nil)
|
148
|
+
question.evaluate_response File.open(File.join(directory, read_input))
|
149
|
+
end
|
150
|
+
|
151
|
+
# Read string answer and validate against email regex
|
152
|
+
#
|
153
|
+
# @return [String]
|
154
|
+
#
|
155
|
+
# @api public
|
156
|
+
def read_email
|
157
|
+
question.validate(/^[a-z0-9._%+-]+@([a-z0-9-]+\.)+[a-z]{2,6}$/i)
|
158
|
+
if question.error
|
159
|
+
question.prompt question.statement
|
160
|
+
end
|
161
|
+
with_exception { read_string }
|
162
|
+
end
|
163
|
+
|
164
|
+
# Read answer provided on multiple lines
|
165
|
+
#
|
166
|
+
# @api public
|
167
|
+
def read_multiple
|
168
|
+
response = ""
|
169
|
+
loop do
|
170
|
+
value = question.evaluate_response read_input
|
171
|
+
break if !value || value == ""
|
172
|
+
next if value !~ /\S/
|
173
|
+
response << value
|
174
|
+
end
|
175
|
+
response
|
176
|
+
end
|
177
|
+
|
178
|
+
# Read password
|
179
|
+
#
|
180
|
+
# @api public
|
181
|
+
def read_password
|
182
|
+
question.echo false
|
183
|
+
question.evaluate_response read_input
|
184
|
+
end
|
185
|
+
|
186
|
+
private
|
187
|
+
|
188
|
+
# Ignore exception
|
189
|
+
#
|
190
|
+
# @api private
|
191
|
+
def with_exception(&block)
|
192
|
+
yield
|
193
|
+
rescue
|
194
|
+
question.error? ? block.call : raise
|
195
|
+
end
|
196
|
+
|
197
|
+
# @param [Symbol] type
|
198
|
+
# :boolean, :string, :numeric, :array
|
199
|
+
#
|
200
|
+
# @api private
|
201
|
+
def read_type(class_or_name)
|
202
|
+
raise TypeError, "Type #{type} is not valid" if type && !valid_type?(type)
|
203
|
+
case type
|
204
|
+
when :string, ::String
|
205
|
+
read_string
|
206
|
+
when :symbol, ::Symbol
|
207
|
+
read_symbol
|
208
|
+
when :float, ::Float
|
209
|
+
read_float
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def valid_type?(type)
|
214
|
+
self.class::VALID_TYPES.include? type.to_sym
|
215
|
+
end
|
216
|
+
|
217
|
+
end # Response
|
218
|
+
end # Shell
|
219
|
+
end # TTY
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
module TTY
|
4
|
+
class Shell
|
5
|
+
module ResponseDelegation
|
6
|
+
extend TTY::Delegatable
|
7
|
+
|
8
|
+
delegatable_method :dispatch, :read
|
9
|
+
|
10
|
+
delegatable_method :dispatch, :read_string
|
11
|
+
|
12
|
+
delegatable_method :dispatch, :read_char
|
13
|
+
|
14
|
+
delegatable_method :dispatch, :read_text
|
15
|
+
|
16
|
+
delegatable_method :dispatch, :read_symbol
|
17
|
+
|
18
|
+
delegatable_method :dispatch, :read_choice
|
19
|
+
|
20
|
+
delegatable_method :dispatch, :read_int
|
21
|
+
|
22
|
+
delegatable_method :dispatch, :read_float
|
23
|
+
|
24
|
+
delegatable_method :dispatch, :read_regex
|
25
|
+
|
26
|
+
delegatable_method :dispatch, :read_range
|
27
|
+
|
28
|
+
delegatable_method :dispatch, :read_date
|
29
|
+
|
30
|
+
delegatable_method :dispatch, :read_datetime
|
31
|
+
|
32
|
+
delegatable_method :dispatch, :read_bool
|
33
|
+
|
34
|
+
delegatable_method :dispatch, :read_file
|
35
|
+
|
36
|
+
delegatable_method :dispatch, :read_email
|
37
|
+
|
38
|
+
delegatable_method :dispatch, :read_multiple
|
39
|
+
|
40
|
+
delegatable_method :dispatch, :read_password
|
41
|
+
|
42
|
+
# Create response instance when question readed is invoked
|
43
|
+
#
|
44
|
+
# @param [TTY::Shell::Response] response
|
45
|
+
#
|
46
|
+
# @api private
|
47
|
+
def dispatch(response = Response.new(self, shell))
|
48
|
+
@response ||= response
|
49
|
+
end
|
50
|
+
|
51
|
+
end # ResponseDelegation
|
52
|
+
end # Shell
|
53
|
+
end # TTY
|
data/lib/tty/table.rb
CHANGED
@@ -40,16 +40,22 @@ module TTY
|
|
40
40
|
# @api private
|
41
41
|
attr_reader :alignments
|
42
42
|
|
43
|
+
# The table border class
|
44
|
+
#
|
45
|
+
# @api private
|
46
|
+
attr_reader :border_class
|
47
|
+
|
48
|
+
# The table orientation out of :horizontal and :vertical
|
49
|
+
#
|
50
|
+
# @reutnr [TTY::Table::Orientation]
|
51
|
+
#
|
52
|
+
# @api public
|
53
|
+
attr_reader :orientation
|
54
|
+
|
43
55
|
# Subset of safe methods that both Array and Hash implement
|
44
56
|
def_delegators(:@rows, :[], :assoc, :flatten, :include?, :index,
|
45
57
|
:length, :select, :to_a, :values_at, :pretty_print, :rassoc)
|
46
58
|
|
47
|
-
# The table orientation
|
48
|
-
#
|
49
|
-
def direction
|
50
|
-
# TODO implement table orientation
|
51
|
-
end
|
52
|
-
|
53
59
|
# Create a new Table where each argument is a row
|
54
60
|
#
|
55
61
|
# @example
|
@@ -74,20 +80,36 @@ module TTY
|
|
74
80
|
# rows = [ ['a1', 'a2'], ['b1', 'b2'] ]
|
75
81
|
# table = Table.new :header => ['Header 1', 'Header 2'], :rows => rows
|
76
82
|
#
|
83
|
+
# @example of parameters passed as hash
|
84
|
+
# Table.new [ {'Header1' => ['a1','a2'], 'Header2' => ['b1', 'b2'] }] }
|
85
|
+
#
|
77
86
|
# @param [Array[Symbol], Hash] *args
|
78
87
|
#
|
79
88
|
# @api public
|
80
89
|
def self.new(*args, &block)
|
81
90
|
options = Utils.extract_options!(args)
|
82
91
|
if args.size.nonzero?
|
83
|
-
|
84
|
-
header = args.size.zero? ? nil : args.first
|
85
|
-
super({:header => header, :rows => rows}.merge(options), &block)
|
92
|
+
super(extract_tuples(args).merge(options), &block)
|
86
93
|
else
|
87
94
|
super(options, &block)
|
88
95
|
end
|
89
96
|
end
|
90
97
|
|
98
|
+
# Extract header and row tuples from arguments
|
99
|
+
#
|
100
|
+
# @param [Array] args
|
101
|
+
#
|
102
|
+
# @api private
|
103
|
+
def self.extract_tuples(args)
|
104
|
+
rows = args.pop
|
105
|
+
header = args.size.zero? ? nil : args.first
|
106
|
+
if rows.first.is_a?(Hash)
|
107
|
+
header = rows.map(&:keys).flatten.uniq
|
108
|
+
rows = rows.inject([]) { |arr, el| arr + el.values }
|
109
|
+
end
|
110
|
+
{ :header => header, :rows => rows }
|
111
|
+
end
|
112
|
+
|
91
113
|
# Initialize a Table
|
92
114
|
#
|
93
115
|
# @param [Hash] options
|
@@ -107,26 +129,78 @@ module TTY
|
|
107
129
|
#
|
108
130
|
# @api private
|
109
131
|
def initialize(options={}, &block)
|
110
|
-
|
111
|
-
|
132
|
+
validate_options! options
|
133
|
+
|
134
|
+
@header = options.fetch(:header) { nil }
|
135
|
+
@rows = coerce options.fetch(:rows) { [] }
|
112
136
|
@renderer = pick_renderer options[:renderer]
|
137
|
+
@orientation = Orientation.coerce options.fetch(:orientation) { :horizontal }
|
113
138
|
# TODO: assert that row_size is the same as column widths & aligns
|
114
|
-
|
115
|
-
@
|
116
|
-
@alignments = Operation::AlignmentSet.new options[:column_aligns] || []
|
139
|
+
@column_widths = Array(options.delete(:column_widths)).map(&:to_i)
|
140
|
+
@alignments = Operation::AlignmentSet.new Array(options.delete(:column_aligns)).map(&:to_sym)
|
117
141
|
|
118
142
|
assert_row_sizes @rows
|
143
|
+
@orientation.transform(self)
|
119
144
|
yield_or_eval &block if block_given?
|
120
145
|
end
|
121
146
|
|
147
|
+
# Sets table orientation
|
148
|
+
#
|
149
|
+
# @param [String,Symbol] value
|
150
|
+
#
|
151
|
+
# @api public
|
152
|
+
def orientation=(value)
|
153
|
+
@orientation = Orientation.coerce value
|
154
|
+
end
|
155
|
+
|
156
|
+
# Marks this table as rotated
|
157
|
+
#
|
158
|
+
# @api public
|
159
|
+
def rotated?
|
160
|
+
@rotated
|
161
|
+
end
|
162
|
+
|
163
|
+
# Rotate the table between vertical and horizontal orientation
|
164
|
+
#
|
165
|
+
# @return [self]
|
166
|
+
#
|
167
|
+
# @api private
|
168
|
+
def rotate
|
169
|
+
orientation.transform(self)
|
170
|
+
self
|
171
|
+
end
|
172
|
+
|
173
|
+
# Rotate the table vertically
|
174
|
+
#
|
175
|
+
# @api private
|
176
|
+
def rotate_vertical
|
177
|
+
@rows = ([header].compact + rows).transpose
|
178
|
+
@header = [] if header
|
179
|
+
@rotated = true
|
180
|
+
end
|
181
|
+
|
182
|
+
# Rotate the table horizontally
|
183
|
+
#
|
184
|
+
# @api private
|
185
|
+
def rotate_horizontal
|
186
|
+
transposed = rows.transpose
|
187
|
+
if header && header.empty?
|
188
|
+
@rows = transposed[1..-1]
|
189
|
+
@header = transposed[0]
|
190
|
+
elsif rotated?
|
191
|
+
@rows = transposed
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
122
195
|
# Lookup element of the table given a row(i) and column(j)
|
123
196
|
#
|
124
197
|
# @api public
|
125
|
-
def [](i, j)
|
198
|
+
def [](i, j=false)
|
199
|
+
return row(i) unless j
|
126
200
|
if i >= 0 && j >= 0
|
127
201
|
rows.fetch(i){return nil}[j]
|
128
202
|
else
|
129
|
-
raise
|
203
|
+
raise TTY::Table::TupleMissing.new(i,j)
|
130
204
|
end
|
131
205
|
end
|
132
206
|
alias at []
|