table_tennis 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.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +1 -1
- data/README.md +30 -25
- data/justfile +1 -1
- data/lib/table_tennis/config.rb +7 -7
- data/lib/table_tennis/stage/layout.rb +1 -1
- data/lib/table_tennis/stage/render.rb +10 -2
- data/lib/table_tennis/table_data.rb +1 -1
- data/lib/table_tennis/util/console.rb +23 -0
- data/lib/table_tennis/util/magic_options.rb +70 -40
- data/lib/table_tennis/util/strings.rb +42 -7
- data/lib/table_tennis/util/termbg.rb +8 -6
- data/lib/table_tennis/version.rb +1 -1
- data/lib/table_tennis.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 85ab2cbb42c928e7231529feaa15416f752b3efa3ca13d0ba54331b79cb41cc0
|
4
|
+
data.tar.gz: bbd1d0477437ab63c0302a8796ca058eb94a5084595a190032695188680214ef
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7729436cacaa34103047f9d039e1a81626ad8b9f86b33062e3e8ab58067aa69bc56242d19e7861ec44284c6e277b3daf32966129c0d41da7b389dce591371ee0
|
7
|
+
data.tar.gz: 97165044e352b52af3b31870b1999624a4a3b3f6dd8624cd69a9935d594461a1842568973fb8a101438d4a26b6ddc2df754ff6b88fcc5f61a982894fb33c329c
|
data/.github/workflows/test.yml
CHANGED
data/README.md
CHANGED
@@ -69,32 +69,32 @@ options = {
|
|
69
69
|
|
70
70
|
#### Popular Options
|
71
71
|
|
72
|
-
| option
|
73
|
-
|
|
74
|
-
| `color_scales` | ─
|
75
|
-
| `columns`
|
76
|
-
| `headers`
|
77
|
-
| `mark`
|
78
|
-
| `row_numbers`
|
79
|
-
| `search`
|
80
|
-
| `separators`
|
81
|
-
| `title`
|
82
|
-
| `titleize`
|
83
|
-
| `zebra`
|
72
|
+
| option | default | details |
|
73
|
+
| -------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
74
|
+
| `color_scales` | ─ | Color code a column of floats, similar to the "conditional formatting" feature in Google Sheets. See [docs below](#color-scales). |
|
75
|
+
| `columns` | ─ | Manually set which columns to include. Leave unset to show all columns. |
|
76
|
+
| `headers` | ─ | Specify some or all column headers. For example, `{user_id: "Customer"}`. When unset, headers are inferred. |
|
77
|
+
| `mark` | ─ | `mark` is a way to highlight specific columns with a nice color. For example, use `mark: ->(row) { row[:planet] == "tatooine" }` to highlight those rows. Your lambda can also return a specific bg color or Paint color array. |
|
78
|
+
| `row_numbers` | `false` | Show row numbers in the table. |
|
79
|
+
| `search` | ─ | String/regex to highlight in output. |
|
80
|
+
| `separators` | `true` | Include column and header separators in output. |
|
81
|
+
| `title` | ─ | Add a title line to the table. |
|
82
|
+
| `titleize` | ─ | Titleize column headers, so `person_id` becomes `Person`. |
|
83
|
+
| `zebra` | `false` | Turn on zebra stripes. |
|
84
84
|
|
85
85
|
#### More Advanced Options
|
86
86
|
|
87
|
-
| option
|
88
|
-
|
|
89
|
-
| `coerce`
|
90
|
-
| `color`
|
91
|
-
| `delims`
|
92
|
-
| `digits`
|
93
|
-
| `layout`
|
94
|
-
| `placeholder` | `"—"`
|
95
|
-
| `save`
|
96
|
-
| `strftime`
|
97
|
-
| `theme`
|
87
|
+
| option | default | details |
|
88
|
+
| ------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
89
|
+
| `coerce` | `true` | if true, try to coerce strings into numbers where possible so we can format with `digits`. You may want to disable this if you already have nicely formatted ints/floats and you don't want TableTennis to mess with them. |
|
90
|
+
| `color` | ─ | Are ANSI colors enabled? Specify `true` or `false`, or leave it as nil to autodetect. Autodetect will turn on color unless redirecting to a file. When using autodetect, you can force it on by setting `ENV["FORCE_COLOR"]`, or off with `ENV["NO_COLOR"]`. |
|
91
|
+
| `delims` | `true` | Format ints & floats with comma delimiter, like 123,456. |
|
92
|
+
| `digits` | `3` | Format floats to this number of digits. TableTennis will look for either `Float` cells or string floats. |
|
93
|
+
| `layout` | `true` | This controls column widths. Leave unset or use `true` for autolayout. Autolayout will shrink the table to fit inside the terminal. `false` turns off layout and columns will be full width. Use an int to fix all columns to a certain width, or a hash to just set a few. |
|
94
|
+
| `placeholder` | `"—"` | Put this into empty cells. |
|
95
|
+
| `save` | ─ | If you set this to a file path, TableTennis will save your table as a CSV file too. Useful if you want to do something else with the data. |
|
96
|
+
| `strftime` | see → | strftime string for formatting Date/Time objects. The default is `"%Y-%m-%d"`, which looks like `2025-04-21` |
|
97
|
+
| `theme` | ─ | When unset, will be autodetected based on terminal background color. If autodetect fails the theme defaults to :dark. You can also manually specify `:dark`, `:light` or `:ansi`. If colors are turned off this setting has no effect. |
|
98
98
|
|
99
99
|
### Color Scales
|
100
100
|
|
@@ -120,8 +120,8 @@ puts TableTennis.new(rows, search: /hope.*empire/i })
|
|
120
120
|
puts TableTennis.new(rows, row_numbers: true, zebra: true)
|
121
121
|
```
|
122
122
|
|
123
|
-
| `:mark`
|
124
|
-
|
|
123
|
+
| `:mark` | `:search` | `:row_numbers` and `:zebra` |
|
124
|
+
| ----------------------------------- | ------------------------------- | --------------------------------------------- |
|
125
125
|
|  |  |  |
|
126
126
|
|
127
127
|
### Links
|
@@ -172,6 +172,11 @@ We love CSV tools and use them all the time! Here are a few that we rely on:
|
|
172
172
|
|
173
173
|
### Changelog
|
174
174
|
|
175
|
+
#### 0.0.7 (Aug '25)
|
176
|
+
|
177
|
+
- handle data that already contains ANSI colors (thx @ronaldtse, #12)
|
178
|
+
- don't crash if IO.console is nil (thx @ronaldtse, #14)
|
179
|
+
|
175
180
|
#### 0.0.6 (May '25)
|
176
181
|
|
177
182
|
- added `coerce:` option to disable string => numeric coercion
|
data/justfile
CHANGED
data/lib/table_tennis/config.rb
CHANGED
@@ -30,11 +30,10 @@ module TableTennis
|
|
30
30
|
|
31
31
|
SCHEMA = {
|
32
32
|
coerce: :bool,
|
33
|
-
color_scales: ->
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
"values must be the name of a color scale"
|
33
|
+
color_scales: -> do
|
34
|
+
Config.magic_validate!(:color_scales, _1, {Symbol => Symbol})
|
35
|
+
if !(invalid = _1.values - Util::Scale::SCALES.keys).empty?
|
36
|
+
raise ArgumentError, "invalid color scale(s): #{invalid.inspect}"
|
38
37
|
end
|
39
38
|
end,
|
40
39
|
color: :bool,
|
@@ -45,7 +44,7 @@ module TableTennis
|
|
45
44
|
headers: {sym: :str},
|
46
45
|
layout: -> do
|
47
46
|
return if _1 == true || _1 == false || _1.is_a?(Integer)
|
48
|
-
Config.magic_validate(_1, {Symbol => Integer})
|
47
|
+
Config.magic_validate!(:layout, _1, {Symbol => Integer})
|
49
48
|
end,
|
50
49
|
mark: :proc,
|
51
50
|
placeholder: :str,
|
@@ -53,7 +52,7 @@ module TableTennis
|
|
53
52
|
save: :str,
|
54
53
|
search: -> do
|
55
54
|
if !(_1.is_a?(String) || _1.is_a?(Regexp))
|
56
|
-
"expected string/regex"
|
55
|
+
raise ArgumentError, "expected string/regex"
|
57
56
|
end
|
58
57
|
end,
|
59
58
|
separators: :bool,
|
@@ -83,6 +82,7 @@ module TableTennis
|
|
83
82
|
end
|
84
83
|
self[:color_scales] = value
|
85
84
|
end
|
85
|
+
alias_method :color_scale=, :color_scales=
|
86
86
|
|
87
87
|
def placeholder=(value)
|
88
88
|
value = "" if value.nil?
|
@@ -37,7 +37,7 @@ module TableTennis
|
|
37
37
|
columns.each { _1.width = _1.measure }
|
38
38
|
|
39
39
|
# How much space is available, and do we already fit?
|
40
|
-
screen_width =
|
40
|
+
screen_width = Util::Console.winsize[1]
|
41
41
|
available = screen_width - chrome_width - FUDGE
|
42
42
|
return if available >= data_width
|
43
43
|
|
@@ -50,7 +50,7 @@ module TableTennis
|
|
50
50
|
title_width = data.table_width - 4
|
51
51
|
title = Util::Strings.truncate(config.title, title_width)
|
52
52
|
title_style = data.get_style(r: :title) || :cell
|
53
|
-
line = paint(
|
53
|
+
line = paint(Util::Strings.center(title, title_width), title_style || :cell)
|
54
54
|
paint("#{pipe} #{line} #{pipe}", Theme::BG)
|
55
55
|
end
|
56
56
|
|
@@ -81,7 +81,7 @@ module TableTennis
|
|
81
81
|
style ||= :cell
|
82
82
|
|
83
83
|
# add ansi codes for search
|
84
|
-
value = value
|
84
|
+
value = search_cell(value) if search
|
85
85
|
|
86
86
|
# add ansi codes for links
|
87
87
|
if config.color && (link = data.links[[r, c]])
|
@@ -155,6 +155,14 @@ module TableTennis
|
|
155
155
|
end
|
156
156
|
end
|
157
157
|
memo_wise :search
|
158
|
+
|
159
|
+
# add ansi codes for search
|
160
|
+
def search_cell(value)
|
161
|
+
return value if !value.match?(search)
|
162
|
+
# edge case - we can't gsub a painted cell, it can mess up the escaping
|
163
|
+
value = Util::Strings.unpaint(value)
|
164
|
+
value.gsub(search) { paint(_1, :search) }
|
165
|
+
end
|
158
166
|
end
|
159
167
|
end
|
160
168
|
end
|
@@ -114,7 +114,7 @@ module TableTennis
|
|
114
114
|
def debug(str)
|
115
115
|
return if !config&.debug
|
116
116
|
str = "[#{Time.now.strftime("%H:%M:%S")}] #{str}"
|
117
|
-
str = str.ljust(@debug_width ||=
|
117
|
+
str = str.ljust(@debug_width ||= Util::Console.winsize[1])
|
118
118
|
puts Paint[str, :white, :green]
|
119
119
|
end
|
120
120
|
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module TableTennis
|
2
|
+
module Util
|
3
|
+
# static wrapper around IO.console to handle the case where IO.console is nil
|
4
|
+
module Console
|
5
|
+
module_function
|
6
|
+
|
7
|
+
# supported when IO.console is nil
|
8
|
+
def winsize(...)
|
9
|
+
IO.console&.winsize(...) || [48, 80]
|
10
|
+
end
|
11
|
+
|
12
|
+
# not supported, don't call these
|
13
|
+
%i[fileno getbyte raw syswrite].each do |name|
|
14
|
+
define_method(name) do |*args, **kwargs, &block|
|
15
|
+
if !IO.console
|
16
|
+
raise "IO.console.#{name} not supported when IO.console is nil"
|
17
|
+
end
|
18
|
+
IO.console.send(name, *args, **kwargs, &block)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -32,7 +32,8 @@
|
|
32
32
|
# (1) A simple type like :bool, :int, :num, :float, :str or :sym.
|
33
33
|
# (2) An array type like :bools, :ints, :nums, :floats, :strs, or :syms.
|
34
34
|
# (3) A range, regexp or Class.
|
35
|
-
# (4) A lambda
|
35
|
+
# (4) A custom validation lambda. The lambda should raise an ArgumentError if
|
36
|
+
# the value is invalid.
|
36
37
|
# (5) An array of possible values (typically numbers, strings, or symbols). The
|
37
38
|
# value must be one of those possibilities.
|
38
39
|
# (6) A hash with one element { class => class }. This specifies the hash
|
@@ -135,15 +136,7 @@ module TableTennis
|
|
135
136
|
|
136
137
|
def magic_set(name, value)
|
137
138
|
raise ArgumentError, "unknown #{self.class}.#{name}=" if !magic_attributes.key?(name)
|
138
|
-
|
139
|
-
value = self.class.magic_coerce(value, type)
|
140
|
-
if !value.nil? && (error = MagicOptions.magic_validate(value, type))
|
141
|
-
if !type.is_a?(Proc)
|
142
|
-
error = "#{error}, got #{value.inspect}"
|
143
|
-
end
|
144
|
-
raise ArgumentError, "#{self.class}.#{name}= #{error}"
|
145
|
-
end
|
146
|
-
magic_values[name] = value
|
139
|
+
magic_values[name] = self.class.magic_validate!(name, value, magic_attributes[name])
|
147
140
|
end
|
148
141
|
|
149
142
|
# these are part of the public api
|
@@ -151,39 +144,76 @@ module TableTennis
|
|
151
144
|
alias_method :[]=, :magic_set
|
152
145
|
|
153
146
|
#
|
154
|
-
# magic_validate and static helpers
|
147
|
+
# magic_validate! and static helpers
|
155
148
|
#
|
156
149
|
|
157
|
-
|
150
|
+
# validate name=value against type, raise on failure
|
151
|
+
def self.magic_validate!(name, value, type)
|
152
|
+
# we validate against coerced values, but squirrel away the original
|
153
|
+
# uncoerced in case we need to use it inside an error message
|
154
|
+
original, value = value, magic_coerce(value, type)
|
155
|
+
return if value.nil?
|
156
|
+
|
158
157
|
case type
|
159
|
-
when Array
|
160
|
-
|
161
|
-
when
|
162
|
-
|
163
|
-
when
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
158
|
+
when Array then validate_any_of(value, type)
|
159
|
+
when Class, :bool then validate_class(value, type)
|
160
|
+
when Hash then validate_hash(value, type)
|
161
|
+
when Proc then type.call(value)
|
162
|
+
when Range then validate_range(value, type)
|
163
|
+
when Regexp then validate_regexp(value, type)
|
164
|
+
when :bools, :floats, :ints, :nums, :strs, :syms then validate_array(value, type)
|
165
|
+
else
|
166
|
+
raise "impossible"
|
167
|
+
end
|
168
|
+
value
|
169
|
+
rescue ArgumentError => ex
|
170
|
+
# add context to msg if necessary
|
171
|
+
msg = ex.message
|
172
|
+
if !msg.include?("#{name} = #{original.inspect}")
|
173
|
+
msg = "#{self}.#{name} = #{original.inspect} failed, #{msg}"
|
174
|
+
end
|
175
|
+
raise ArgumentError, msg
|
176
|
+
end
|
177
|
+
|
178
|
+
#
|
179
|
+
# validators
|
180
|
+
#
|
181
|
+
|
182
|
+
def self.validate_any_of(value, possibilities)
|
183
|
+
if !possibilities.include?(value)
|
184
|
+
raise ArgumentError, "expected one of #{possibilities.inspect}"
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def self.validate_class(value, klass)
|
189
|
+
if !magic_is_a?(value, klass)
|
190
|
+
raise ArgumentError, "expected #{magic_pretty(klass)}"
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def self.validate_hash(value, hash_type)
|
195
|
+
kk, vk = hash_type.first
|
196
|
+
if !(value.is_a?(Hash) && value.all? { magic_is_a?(_1, kk) && magic_is_a?(_2, vk) })
|
197
|
+
raise ArgumentError, "expected hash of #{magic_pretty(kk)} => #{magic_pretty(vk)}"
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def self.validate_range(value, range)
|
202
|
+
if !value.is_a?(Numeric) || !range.include?(value)
|
203
|
+
raise ArgumentError, "expected to be in range #{range.inspect}"
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def self.validate_regexp(value, regexp)
|
208
|
+
if !value.is_a?(String) || !value.match?(regexp)
|
209
|
+
raise ArgumentError, "expected to be a string matching #{regexp}"
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def self.validate_array(value, array_type)
|
214
|
+
klass = magic_resolve(array_type.to_s[..-2].to_sym)
|
215
|
+
if !(value.is_a?(Array) && value.all? { magic_is_a?(_1, klass) })
|
216
|
+
raise ArgumentError, "expected array of #{array_type}"
|
187
217
|
end
|
188
218
|
end
|
189
219
|
|
@@ -6,6 +6,9 @@ module TableTennis
|
|
6
6
|
|
7
7
|
module_function
|
8
8
|
|
9
|
+
# does this string contain ansi codes?
|
10
|
+
def painted?(str) = str.match?(/\e/)
|
11
|
+
|
9
12
|
# strip ansi codes
|
10
13
|
def unpaint(str) = str.gsub(/\e\[[0-9;]*m/, "")
|
11
14
|
|
@@ -18,8 +21,24 @@ module TableTennis
|
|
18
21
|
str
|
19
22
|
end
|
20
23
|
|
21
|
-
|
22
|
-
|
24
|
+
# measure width of text, with support for emojis, painted/ansi strings, etc
|
25
|
+
def width(str)
|
26
|
+
if simple?(str)
|
27
|
+
str.length
|
28
|
+
elsif painted?(str)
|
29
|
+
unpaint(str).length
|
30
|
+
else
|
31
|
+
Unicode::DisplayWidth.of(str)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# center text, like String#center but works with painted strings
|
36
|
+
def center(str, width)
|
37
|
+
# artificially inflate width to include escape codes
|
38
|
+
if painted?(str)
|
39
|
+
width += str.length - unpaint(str).length
|
40
|
+
end
|
41
|
+
str.center(width)
|
23
42
|
end
|
24
43
|
|
25
44
|
def hyperlink(str)
|
@@ -31,12 +50,26 @@ module TableTennis
|
|
31
50
|
end
|
32
51
|
|
33
52
|
# truncate a string based on the display width of the grapheme clusters.
|
34
|
-
# Should handle emojis and international characters
|
35
|
-
def truncate(
|
36
|
-
if simple?(
|
37
|
-
|
53
|
+
# Should handle emojis and international characters. Painted strings too.
|
54
|
+
def truncate(str, stop)
|
55
|
+
if simple?(str)
|
56
|
+
(str.length > stop) ? "#{str[0, stop - 1]}…" : str
|
57
|
+
elsif painted?(str)
|
58
|
+
# generate truncated plain version
|
59
|
+
plain = truncate0(unpaint(str), stop)
|
60
|
+
# make a best effort to apply the colors
|
61
|
+
if (opening_codes = str[/\e\[(?:[0-9];?)+m/])
|
62
|
+
"#{opening_codes}#{plain}#{Paint::NOTHING}"
|
63
|
+
else
|
64
|
+
plain
|
65
|
+
end
|
66
|
+
else
|
67
|
+
truncate0(str, stop)
|
38
68
|
end
|
69
|
+
end
|
39
70
|
|
71
|
+
# slow, but handles graphemes
|
72
|
+
def truncate0(text, stop)
|
40
73
|
# get grapheme clusters, and attach zero width graphemes to the previous grapheme
|
41
74
|
list = [].tap do |accum|
|
42
75
|
text.grapheme_clusters.each do
|
@@ -61,8 +94,10 @@ module TableTennis
|
|
61
94
|
|
62
95
|
text
|
63
96
|
end
|
97
|
+
private_class_method :truncate0
|
64
98
|
|
65
|
-
|
99
|
+
# note that escape \e (0x1b) is excluded
|
100
|
+
SIMPLE = /\A[\x00-\x1a\x1c-\x7F–—…·‘’“”•áéíñóúÓ]*\Z/
|
66
101
|
|
67
102
|
# Is this a "simple" string? (no emojis, etc). Caches results for small
|
68
103
|
# strings for performance reasons.
|
@@ -54,6 +54,8 @@ module TableTennis
|
|
54
54
|
"bad TERM"
|
55
55
|
elsif ENV["ZELLIJ"]
|
56
56
|
"zellij"
|
57
|
+
elsif !IO.console
|
58
|
+
"no console"
|
57
59
|
end
|
58
60
|
if error
|
59
61
|
debug("osc_supported? #{{host:, platform:, term:}} => #{error}")
|
@@ -75,7 +77,7 @@ module TableTennis
|
|
75
77
|
|
76
78
|
debug("osc_query(#{attr})")
|
77
79
|
begin
|
78
|
-
|
80
|
+
Util::Console.raw do
|
79
81
|
logs << " IO.console.raw"
|
80
82
|
|
81
83
|
# we send two messages - the cursor query is widely supported, so we
|
@@ -89,7 +91,7 @@ module TableTennis
|
|
89
91
|
end.join
|
90
92
|
|
91
93
|
logs << " syswrite #{msg.inspect}"
|
92
|
-
|
94
|
+
Util::Console.syswrite(msg)
|
93
95
|
|
94
96
|
# there should always be at least one response. If this is a response to
|
95
97
|
# the cursor message, the first message didn't work
|
@@ -116,18 +118,18 @@ module TableTennis
|
|
116
118
|
def read_term_response
|
117
119
|
# fast forward to ESC
|
118
120
|
loop do
|
119
|
-
return if !(ch =
|
121
|
+
return if !(ch = Util::Console.getbyte&.chr)
|
120
122
|
break ch if ch == ESC
|
121
123
|
end
|
122
124
|
# next char should be either [ or ]
|
123
|
-
return if !(type =
|
125
|
+
return if !(type = Util::Console.getbyte&.chr)
|
124
126
|
return if !(type == "[" || type == "]")
|
125
127
|
|
126
128
|
# now read the response. note that the response can end in different ways
|
127
129
|
# and we have to check for all of them
|
128
130
|
buf = "#{ESC}#{type}"
|
129
131
|
loop do
|
130
|
-
return if !(ch =
|
132
|
+
return if !(ch = Util::Console.getbyte&.chr)
|
131
133
|
buf << ch
|
132
134
|
break if type == "[" && buf.end_with?("R")
|
133
135
|
break if type == "]" && buf.end_with?(BEL, ST)
|
@@ -162,7 +164,7 @@ module TableTennis
|
|
162
164
|
return if !respond_to?(:tcgetpgrp)
|
163
165
|
end
|
164
166
|
|
165
|
-
io =
|
167
|
+
io = Util::Console
|
166
168
|
if (ttypgrp = tcgetpgrp(io.fileno)) <= 0
|
167
169
|
debug("tcpgrp(#{io.fileno}) => #{ttypgrp}, errno=#{FFI.errno}")
|
168
170
|
return
|
data/lib/table_tennis/version.rb
CHANGED
data/lib/table_tennis.rb
CHANGED
@@ -25,6 +25,7 @@ require "table_tennis/stage/painter"
|
|
25
25
|
require "table_tennis/stage/render"
|
26
26
|
|
27
27
|
require "table_tennis/util/colors"
|
28
|
+
require "table_tennis/util/console"
|
28
29
|
require "table_tennis/util/identify"
|
29
30
|
require "table_tennis/util/scale"
|
30
31
|
require "table_tennis/util/strings"
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: table_tennis
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adam Doppelt
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-08-06 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: csv
|
@@ -106,6 +106,7 @@ files:
|
|
106
106
|
- lib/table_tennis/table_data.rb
|
107
107
|
- lib/table_tennis/theme.rb
|
108
108
|
- lib/table_tennis/util/colors.rb
|
109
|
+
- lib/table_tennis/util/console.rb
|
109
110
|
- lib/table_tennis/util/identify.rb
|
110
111
|
- lib/table_tennis/util/inspectable.rb
|
111
112
|
- lib/table_tennis/util/magic_options.rb
|