table_tennis 0.0.1
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 +7 -0
- data/.github/workflows/test.yml +26 -0
- data/.gitignore +5 -0
- data/.rubocop.yml +58 -0
- data/Gemfile +14 -0
- data/Gemfile.lock +122 -0
- data/LICENSE +21 -0
- data/README.md +154 -0
- data/Rakefile +12 -0
- data/justfile +75 -0
- data/lib/table_tennis/column.rb +41 -0
- data/lib/table_tennis/config.rb +221 -0
- data/lib/table_tennis/row.rb +9 -0
- data/lib/table_tennis/stage/base.rb +19 -0
- data/lib/table_tennis/stage/format.rb +68 -0
- data/lib/table_tennis/stage/layout.rb +76 -0
- data/lib/table_tennis/stage/painter.rb +84 -0
- data/lib/table_tennis/stage/render.rb +146 -0
- data/lib/table_tennis/table.rb +79 -0
- data/lib/table_tennis/table_data.rb +161 -0
- data/lib/table_tennis/theme.rb +92 -0
- data/lib/table_tennis/util/colors.rb +524 -0
- data/lib/table_tennis/util/inspectable.rb +25 -0
- data/lib/table_tennis/util/scale.rb +55 -0
- data/lib/table_tennis/util/strings.rb +62 -0
- data/lib/table_tennis/util/termbg.rb +275 -0
- data/lib/table_tennis/version.rb +3 -0
- data/lib/table_tennis.rb +29 -0
- data/screenshots/dark.png +0 -0
- data/screenshots/droids.png +0 -0
- data/screenshots/hope.png +0 -0
- data/screenshots/light.png +0 -0
- data/screenshots/row_numbers.png +0 -0
- data/screenshots/scales.png +0 -0
- data/screenshots/themes.png +0 -0
- data/table_tennis.gemspec +28 -0
- metadata +145 -0
@@ -0,0 +1,275 @@
|
|
1
|
+
module TableTennis
|
2
|
+
module Util
|
3
|
+
# Very complicated module for determining the terminal background color,
|
4
|
+
# used to select the default color theme.
|
5
|
+
module Termbg
|
6
|
+
prepend MemoWise
|
7
|
+
|
8
|
+
module_function
|
9
|
+
|
10
|
+
# get fg color as "#RRGGBB", or nil if we can't tel
|
11
|
+
def fg = osc_query(10) || env_colorfgbg&.fetch(0)
|
12
|
+
memo_wise self: :fg
|
13
|
+
|
14
|
+
# get bg color as "#RRGGBB", or nil if we can't tell
|
15
|
+
def bg = osc_query(11) || env_colorfgbg&.fetch(1)
|
16
|
+
memo_wise self: :bg
|
17
|
+
|
18
|
+
# mostly for debugging
|
19
|
+
def info
|
20
|
+
{
|
21
|
+
fg:,
|
22
|
+
bg:,
|
23
|
+
bg_luma: bg ? Colors.luma(bg) : nil,
|
24
|
+
tty?: "#{$stdin.tty?}/#{$stdout.tty?}/#{$stderr.tty?}",
|
25
|
+
in_foreground?: in_foreground?,
|
26
|
+
osc_supported?: osc_supported?,
|
27
|
+
"$COLORFGBG": ENV["COLORFGBG"],
|
28
|
+
"$TERM": ENV["TERM"],
|
29
|
+
colorfgbg: env_colorfgbg,
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
#
|
34
|
+
# osc_query
|
35
|
+
#
|
36
|
+
|
37
|
+
# escape chars
|
38
|
+
ESC, BEL, ST, = "\e", "\a", "\e\\"
|
39
|
+
|
40
|
+
# Operating System Control queries
|
41
|
+
OSC_FG, OSC_BG = 10, 11
|
42
|
+
|
43
|
+
def osc_supported?
|
44
|
+
host, platform, term = [
|
45
|
+
RbConfig::CONFIG["host_os"],
|
46
|
+
RbConfig::CONFIG["platform"],
|
47
|
+
ENV["TERM"],
|
48
|
+
]
|
49
|
+
error = if host !~ /darwin|freebsd|linux|netbsd|openbsd/
|
50
|
+
"bad host"
|
51
|
+
elsif platform !~ /^(arm64|x86_64)/
|
52
|
+
"bad platform"
|
53
|
+
elsif term =~ /^(screen|tmux|dumb)/i
|
54
|
+
"bad TERM"
|
55
|
+
elsif ENV["ZELLIJ"]
|
56
|
+
"zellij"
|
57
|
+
end
|
58
|
+
if error
|
59
|
+
debug("osc_supported? #{{host:, platform:, term:}} => #{error}")
|
60
|
+
return false
|
61
|
+
end
|
62
|
+
debug("osc_supported? #{{host:, platform:, term:}} => success")
|
63
|
+
true
|
64
|
+
end
|
65
|
+
|
66
|
+
def osc_query(attr)
|
67
|
+
# let's be conservative
|
68
|
+
return if !osc_supported?
|
69
|
+
|
70
|
+
# mucking with the tty will hang if we are not in the foreground
|
71
|
+
return if !in_foreground?
|
72
|
+
|
73
|
+
# we can't touch stdout inside IO.console.raw, so save these for later
|
74
|
+
logs = []
|
75
|
+
|
76
|
+
debug("osc_query(#{attr})")
|
77
|
+
begin
|
78
|
+
IO.console.raw do
|
79
|
+
logs << " IO.console.raw"
|
80
|
+
|
81
|
+
# we send two messages - the cursor query is widely supported, so we
|
82
|
+
# always end with that. if the first message is ignored we will still
|
83
|
+
# get an answer to the second so we know when to stop reading from stdin
|
84
|
+
msg = [].tap do
|
85
|
+
# operating system control with Ps=attr
|
86
|
+
_1 << "\e]#{attr};?\a"
|
87
|
+
# device status report with Ps = 6 (cursor position)
|
88
|
+
_1 << "\e[6n"
|
89
|
+
end.join
|
90
|
+
|
91
|
+
logs << " syswrite #{msg.inspect}"
|
92
|
+
IO.console.syswrite(msg)
|
93
|
+
|
94
|
+
# there should always be at least one response. If this is a response to
|
95
|
+
# the cursor message, the first message didn't work
|
96
|
+
response1 = read_term_response.tap do
|
97
|
+
logs << " got #{_1.inspect}"
|
98
|
+
if !(_1 && _1[1] == "]")
|
99
|
+
logs << " not OSC, bailing"
|
100
|
+
return
|
101
|
+
end
|
102
|
+
response2 = read_term_response # skip cursor response
|
103
|
+
logs << " got #{response2.inspect}"
|
104
|
+
end
|
105
|
+
decoded = decode_osc_response(response1)
|
106
|
+
logs << "=> #{decoded}"
|
107
|
+
decoded
|
108
|
+
end
|
109
|
+
ensure
|
110
|
+
logs.each { debug(_1) }
|
111
|
+
end
|
112
|
+
end
|
113
|
+
private_class_method :osc_query
|
114
|
+
|
115
|
+
# read a response, which could be either an OSC or cursor response
|
116
|
+
def read_term_response
|
117
|
+
# fast forward to ESC
|
118
|
+
loop do
|
119
|
+
return if !(ch = IO.console.getbyte&.chr)
|
120
|
+
break ch if ch == ESC
|
121
|
+
end
|
122
|
+
# next char should be either [ or ]
|
123
|
+
return if !(type = IO.console.getbyte&.chr)
|
124
|
+
return if !(type == "[" || type == "]")
|
125
|
+
|
126
|
+
# now read the response. note that the response can end in different ways
|
127
|
+
# and we have to check for all of them
|
128
|
+
buf = "#{ESC}#{type}"
|
129
|
+
loop do
|
130
|
+
return if !(ch = IO.console.getbyte&.chr)
|
131
|
+
buf << ch
|
132
|
+
break if type == "[" && buf.end_with?("R")
|
133
|
+
break if type == "]" && buf.end_with?(BEL, ST)
|
134
|
+
end
|
135
|
+
buf
|
136
|
+
end
|
137
|
+
private_class_method :read_term_response
|
138
|
+
|
139
|
+
#
|
140
|
+
# color math
|
141
|
+
#
|
142
|
+
|
143
|
+
def decode_osc_response(response)
|
144
|
+
if response =~ %r{;rgb:([0-9a-f/]+)}i
|
145
|
+
rgb = $1.split("/")
|
146
|
+
return if rgb.length != 3
|
147
|
+
hex = rgb.join
|
148
|
+
return if hex.length % 3 != 0
|
149
|
+
Colors.to_hex(Colors.to_rgb(hex))
|
150
|
+
end
|
151
|
+
end
|
152
|
+
private_class_method :decode_osc_response
|
153
|
+
|
154
|
+
#
|
155
|
+
# in_foreground?
|
156
|
+
#
|
157
|
+
|
158
|
+
# returns true/false or nil (if unknown)
|
159
|
+
def in_foreground?
|
160
|
+
if !respond_to?(:tcgetpgrp)
|
161
|
+
load_ffi!
|
162
|
+
return if !respond_to?(:tcgetpgrp)
|
163
|
+
end
|
164
|
+
|
165
|
+
io = IO.console
|
166
|
+
if (ttypgrp = tcgetpgrp(io.fileno)) <= 0
|
167
|
+
debug("tcpgrp(#{io.fileno}) => #{ttypgrp}, errno=#{FFI.errno}")
|
168
|
+
return
|
169
|
+
end
|
170
|
+
debug("tcpgrp(#{io.fileno}) => #{ttypgrp}")
|
171
|
+
|
172
|
+
# now compare against our process group
|
173
|
+
infg = Process.getpgrp == ttypgrp
|
174
|
+
debug("Process.getpgrp => #{Process.getpgrp}, in_foreground? #{infg}")
|
175
|
+
infg
|
176
|
+
end
|
177
|
+
private_class_method :in_foreground?
|
178
|
+
memo_wise self: :in_foreground?
|
179
|
+
|
180
|
+
def load_ffi!
|
181
|
+
module_eval do
|
182
|
+
extend FFI::Library
|
183
|
+
ffi_lib "c"
|
184
|
+
attach_function :tcgetpgrp, %i[int], :int32
|
185
|
+
debug("ffi attach libc.tcgetpgrp => success")
|
186
|
+
end
|
187
|
+
rescue LoadError => ex
|
188
|
+
debug("ffi attach libc.tcgetpgrp => failed #{ex.message}")
|
189
|
+
end
|
190
|
+
private_class_method :load_ffi!
|
191
|
+
memo_wise self: :load_ffi!
|
192
|
+
|
193
|
+
def env_colorfgbg(env = ENV["COLORFGBG"])
|
194
|
+
if env !~ /^\d+;\d+$/
|
195
|
+
debug("env_colorfgbg: COLORFGBG '#{env.inspect}'") if env
|
196
|
+
return
|
197
|
+
end
|
198
|
+
colors = env.split(";").map { Colors.ansi_color_to_hex(_1.to_i) }
|
199
|
+
debug("env_colorfgbg: #{env.inspect}' => #{colors.inspect}")
|
200
|
+
colors
|
201
|
+
end
|
202
|
+
private_class_method :env_colorfgbg
|
203
|
+
|
204
|
+
def debug(s)
|
205
|
+
puts "termbg: #{s}" if ENV["TM_DEBUG"]
|
206
|
+
end
|
207
|
+
private_class_method :debug
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
#
|
213
|
+
# This comment is down here to avoid polluting ruby-lsp hover.
|
214
|
+
#
|
215
|
+
# Is the terminal dark or light? To answer this simple question, we need to
|
216
|
+
# query the terminal to get the current background color.
|
217
|
+
#
|
218
|
+
# https://github.com/dalance/termbg
|
219
|
+
# https://github.com/muesli/termenv
|
220
|
+
# https://github.com/rocky/shell-term-background
|
221
|
+
#
|
222
|
+
# This is absurdly difficult, so here is our approach:
|
223
|
+
#
|
224
|
+
# 1. Use OSC 11 to query the bgcolor using a magical escape sequence. We write
|
225
|
+
# the escape sequence to stdout and read the response from stdin. Not all
|
226
|
+
# terminals support this. Also, the terminal must be in "raw" mode for this
|
227
|
+
# to work. Raw mode means disable echo and disable line buffering.
|
228
|
+
#
|
229
|
+
# https://en.wikipedia.org/wiki/ANSI_escape_code
|
230
|
+
# https://github.com/ruby/io-console/blob/master/lib/ffi/io/console/common.rb
|
231
|
+
# https://www.xfree86.org/4.8.0/ctlseqs.html
|
232
|
+
# https://stackoverflow.com/questions/2507337/
|
233
|
+
#
|
234
|
+
# 2. Because not all terminals support OSC 11, we actually send two magic escape
|
235
|
+
# sequences - OSC 11 and a "where is the cursor" message. Because the second
|
236
|
+
# query is universally supported we always get a response. That's how we
|
237
|
+
# avoid breaking stdin by over/under reading.
|
238
|
+
#
|
239
|
+
# 3. Mucking with the tty can hang (!) under some circumstances, which is a poor
|
240
|
+
# outcome for a fun ruby library like this. Only try this if we are "in the
|
241
|
+
# foreground". You can easily try this with the following command:
|
242
|
+
#
|
243
|
+
# $ watchexec stty sane # this hangs
|
244
|
+
# $ watchexec --wrap-process=none stty sane # this works fine
|
245
|
+
#
|
246
|
+
# https://github.com/watchexec/watchexec/issues/874
|
247
|
+
# https://github.com/ruby/io-console
|
248
|
+
#
|
249
|
+
# 4. To detect if we are in the foreground, compare the process group against
|
250
|
+
# the group the process group that owns stdin. If they match, we are good to
|
251
|
+
# go.
|
252
|
+
#
|
253
|
+
# https://unix.stackexchange.com/questions/736821/
|
254
|
+
#
|
255
|
+
# 5. Sadly, ruby does not have an easy way to get the process group of stdin.
|
256
|
+
# Instead, we have to use ruby-termios, ffi or $stdin.ioctl using a magic
|
257
|
+
# ioctl number (this magic number differs across platforms). I went with
|
258
|
+
# ffi.
|
259
|
+
#
|
260
|
+
# https://github.com/arika/ruby-termios
|
261
|
+
# https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/ioctls.h
|
262
|
+
# https://github.com/swiftlang/swift/blob/main/stdlib/public/Platform/TiocConstants.swift
|
263
|
+
#
|
264
|
+
# 6. If the foreground is lighter than the background, the background is dark.
|
265
|
+
#
|
266
|
+
# 7. As a fallback to OSC 11, we also support $COLORFGBG. Terminals can set this
|
267
|
+
# environment variable to communicate colors to apps. Support is spotty,
|
268
|
+
# unfortunately. Even when COLORFGBG is set, it is not updated after the
|
269
|
+
# terminal is started. So it can be out of date if the user mucks with
|
270
|
+
# colors.
|
271
|
+
#
|
272
|
+
# https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
|
273
|
+
# https://unix.stackexchange.com/questions/245378/
|
274
|
+
# https://www.xfree86.org/4.8.0/XLookupColor.3.html#toc4
|
275
|
+
#
|
data/lib/table_tennis.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# gems
|
2
|
+
require "csv"
|
3
|
+
require "ffi"
|
4
|
+
require "forwardable"
|
5
|
+
require "io/console"
|
6
|
+
require "memo_wise"
|
7
|
+
require "paint"
|
8
|
+
require "unicode/display_width"
|
9
|
+
|
10
|
+
# mixins must be at top
|
11
|
+
require "table_tennis/util/inspectable"
|
12
|
+
|
13
|
+
require "table_tennis/column"
|
14
|
+
require "table_tennis/config"
|
15
|
+
require "table_tennis/row"
|
16
|
+
require "table_tennis/table_data"
|
17
|
+
require "table_tennis/table"
|
18
|
+
require "table_tennis/theme"
|
19
|
+
|
20
|
+
require "table_tennis/stage/base"
|
21
|
+
require "table_tennis/stage/format"
|
22
|
+
require "table_tennis/stage/layout"
|
23
|
+
require "table_tennis/stage/painter"
|
24
|
+
require "table_tennis/stage/render"
|
25
|
+
|
26
|
+
require "table_tennis/util/colors"
|
27
|
+
require "table_tennis/util/scale"
|
28
|
+
require "table_tennis/util/strings"
|
29
|
+
require "table_tennis/util/termbg"
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require_relative "lib/table_tennis/version"
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = "table_tennis"
|
5
|
+
s.version = TableTennis::VERSION
|
6
|
+
s.authors = ["Adam Doppelt"]
|
7
|
+
s.email = "amd@gurge.com"
|
8
|
+
|
9
|
+
s.summary = "Stylish tables in your terminal."
|
10
|
+
s.homepage = "http://github.com/gurgeous/table_tennis"
|
11
|
+
s.license = "MIT"
|
12
|
+
s.required_ruby_version = ">= 3.0.0"
|
13
|
+
s.metadata = {
|
14
|
+
"rubygems_mfa_required" => "true",
|
15
|
+
"source_code_uri" => s.homepage,
|
16
|
+
}
|
17
|
+
|
18
|
+
# what's in the gem?
|
19
|
+
s.files = `git ls-files`.split("\n").grep_v(%r{^(bin|samples|test)/})
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
|
22
|
+
# gem dependencies
|
23
|
+
s.add_dependency "csv", "~> 3.3" # required for Ruby 3.4+
|
24
|
+
s.add_dependency "ffi", "~> 1.17" # required for Ruby 3.2+
|
25
|
+
s.add_dependency "memo_wise", "~> 1.11"
|
26
|
+
s.add_dependency "paint", "~> 2.3"
|
27
|
+
s.add_dependency "unicode-display_width", "~> 3.1"
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: table_tennis
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Adam Doppelt
|
8
|
+
bindir: bin
|
9
|
+
cert_chain: []
|
10
|
+
date: 2025-04-11 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: csv
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - "~>"
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '3.3'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - "~>"
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '3.3'
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: ffi
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - "~>"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '1.17'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '1.17'
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: memo_wise
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '1.11'
|
47
|
+
type: :runtime
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '1.11'
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: paint
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '2.3'
|
61
|
+
type: :runtime
|
62
|
+
prerelease: false
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '2.3'
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
name: unicode-display_width
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '3.1'
|
75
|
+
type: :runtime
|
76
|
+
prerelease: false
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '3.1'
|
82
|
+
email: amd@gurge.com
|
83
|
+
executables: []
|
84
|
+
extensions: []
|
85
|
+
extra_rdoc_files: []
|
86
|
+
files:
|
87
|
+
- ".github/workflows/test.yml"
|
88
|
+
- ".gitignore"
|
89
|
+
- ".rubocop.yml"
|
90
|
+
- Gemfile
|
91
|
+
- Gemfile.lock
|
92
|
+
- LICENSE
|
93
|
+
- README.md
|
94
|
+
- Rakefile
|
95
|
+
- justfile
|
96
|
+
- lib/table_tennis.rb
|
97
|
+
- lib/table_tennis/column.rb
|
98
|
+
- lib/table_tennis/config.rb
|
99
|
+
- lib/table_tennis/row.rb
|
100
|
+
- lib/table_tennis/stage/base.rb
|
101
|
+
- lib/table_tennis/stage/format.rb
|
102
|
+
- lib/table_tennis/stage/layout.rb
|
103
|
+
- lib/table_tennis/stage/painter.rb
|
104
|
+
- lib/table_tennis/stage/render.rb
|
105
|
+
- lib/table_tennis/table.rb
|
106
|
+
- lib/table_tennis/table_data.rb
|
107
|
+
- lib/table_tennis/theme.rb
|
108
|
+
- lib/table_tennis/util/colors.rb
|
109
|
+
- lib/table_tennis/util/inspectable.rb
|
110
|
+
- lib/table_tennis/util/scale.rb
|
111
|
+
- lib/table_tennis/util/strings.rb
|
112
|
+
- lib/table_tennis/util/termbg.rb
|
113
|
+
- lib/table_tennis/version.rb
|
114
|
+
- screenshots/dark.png
|
115
|
+
- screenshots/droids.png
|
116
|
+
- screenshots/hope.png
|
117
|
+
- screenshots/light.png
|
118
|
+
- screenshots/row_numbers.png
|
119
|
+
- screenshots/scales.png
|
120
|
+
- screenshots/themes.png
|
121
|
+
- table_tennis.gemspec
|
122
|
+
homepage: http://github.com/gurgeous/table_tennis
|
123
|
+
licenses:
|
124
|
+
- MIT
|
125
|
+
metadata:
|
126
|
+
rubygems_mfa_required: 'true'
|
127
|
+
source_code_uri: http://github.com/gurgeous/table_tennis
|
128
|
+
rdoc_options: []
|
129
|
+
require_paths:
|
130
|
+
- lib
|
131
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
132
|
+
requirements:
|
133
|
+
- - ">="
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: 3.0.0
|
136
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
137
|
+
requirements:
|
138
|
+
- - ">="
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
version: '0'
|
141
|
+
requirements: []
|
142
|
+
rubygems_version: 3.6.2
|
143
|
+
specification_version: 4
|
144
|
+
summary: Stylish tables in your terminal.
|
145
|
+
test_files: []
|