table_tennis 0.0.3 → 0.0.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1d899b2742e12311a8f7e7b80cf4f6aad23eaeb45962c175740793bc1ef3e195
4
- data.tar.gz: 0ffd8dedbfd54dda07db0cf3bd3a171bc869a21779e43828dc7e7e6b0dcfc929
3
+ metadata.gz: '009a0c9bbb140d308d55441602f96a534e51d989259c70b2e3cc2661213252cb'
4
+ data.tar.gz: 6422661f90e1e6e15ddb67c0fff92d6d1c1f3d3454e95a57ae79652d83b981ca
5
5
  SHA512:
6
- metadata.gz: b2f3a271ad8075d5ee6e590a580df33128124e3f3f97a8d9fae9e5d3fff1ef31dcb36ba4363bfd4d269c47c8a7fffeda79dbc2d4d6e5d5db755f9f2173f5dd25
7
- data.tar.gz: 78c83e5ecfad29dcc4e998426f798e11a023f74b02e9a4c46c4425925bc133556f2f5d0aea9416a313ab1513b9e9bba0605729e3cc391c47ec6e5abfa113f8e6
6
+ metadata.gz: af82ae82579222df273e5244c2dde9137547cce527b4c5e4b900feeb7129beb7d9b5d9ea80ae849f62ac79befd14c38be0f8e8c311a651f8dc63660b1953190a
7
+ data.tar.gz: 7eae197b20fe45f0b65f9987097e38233dac3ea113fcabbfd27011340a18910f9c8d701a3352cb482819f266391e98a0daaeec0857e966982e6d6ffcf2aa2121
data/.gitignore CHANGED
@@ -1,5 +1,7 @@
1
1
  .envrc
2
2
  .tool-versions
3
3
  .vscode
4
- *.gem
5
- terminals/
4
+ pkg/
5
+
6
+ # gems don't need to include this since the gemspec is enough
7
+ Gemfile.lock
data/Gemfile CHANGED
@@ -3,6 +3,8 @@ gemspec
3
3
 
4
4
  group :development, :test do
5
5
  gem "amazing_print"
6
+ gem "image_optim"
7
+ gem "image_optim_pack"
6
8
  gem "minitest"
7
9
  gem "minitest-hooks"
8
10
  gem "mocha"
@@ -10,5 +12,5 @@ group :development, :test do
10
12
  gem "pry"
11
13
  gem "rake"
12
14
  gem "simplecov", require: false
13
- gem "standard", require: false
15
+ gem "standard", require: false, platform: :mri
14
16
  end
data/README.md CHANGED
@@ -29,6 +29,7 @@ gem "table_tennis"
29
29
  - auto-layout to fit your terminal window
30
30
  - auto-format floats and dates
31
31
  - auto-color numeric columns
32
+ - auto-link markdown links
32
33
  - titles, row numbers, zebra stripes...
33
34
 
34
35
  ### Themes
@@ -42,10 +43,10 @@ TableTennis examines the background color of your terminal to pick either the da
42
43
  Construct your table with an array of rows. Rows are hashes, ActiveRecord objects, structs, Data records, or anything that responds to `to_h`. It also supports oddballs like arrays (as rows) or even a single hash.
43
44
 
44
45
  ```ruby
45
- puts TableTennis.new([{a: "hello", b: "world"}, {a: "foo", b: "bar"})
46
+ puts TableTennis.new([{a: "hello", b: "world"}, {a: "foo", b: "bar"}])
46
47
  puts TableTennis.new(Recipe.all.to_a) # activerecord
47
48
  puts TableTennis.new(array_of_structs) # these use to_h
48
- puts TableTennis.new([[1,2],[3,4]]]) # array of arrays
49
+ puts TableTennis.new([[1,2],[3,4]]) # array of arrays
49
50
  puts TableTennis.new(authors[0]) # single hash
50
51
  ```
51
52
 
@@ -79,6 +80,7 @@ options = {
79
80
  | `row_numbers` | `false` | Show row numbers in the table. |
80
81
  | `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. |
81
82
  | `search` | ─ | string/regex to highlight in output |
83
+ | `separators` | `true` | Include column and header separators in output. |
82
84
  | `strftime` | see → | strftime string for formatting Date/Time objects. The default is `"%Y-%m-%d"`, which looks like `2025-04-21` |
83
85
  | `theme` | nil | 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.|
84
86
  | `title` | ─ | Add a title line to the table. |
@@ -113,6 +115,16 @@ puts TableTennis.new(rows, row_numbers: true, zebra: true)
113
115
  | - | - | - |
114
116
  | ![droids](./screenshots/droids.png) | ![hope](./screenshots/hope.png) | ![row numbers](./screenshots/row_numbers.png) |
115
117
 
118
+ ### Links
119
+
120
+ Modern terminals support **hyperlinks** in the text stream. If your table includes markdown-style links, TableTennis will use [OSC 8](https://github.com/Alhadis/OSC8-Adoption) to render them. For example:
121
+
122
+ ```ruby
123
+ puts TableTennis.new([{site: "[search](https://google.com)"}])
124
+ ```
125
+
126
+ ![link](./screenshots/link.png)
127
+
116
128
  ### Advanced Usage
117
129
 
118
130
  TableTennis can be configured a few different ways:
@@ -149,8 +161,21 @@ We love CSV tools and use them all the time! Here are a few that we rely on:
149
161
  - [Terminal::Table](https://github.com/tj/terminal-table) - wonderful rubygem for pretty printing tables, great for non-hash data like confusion matrices
150
162
  - [visidata](https://www.visidata.org) - the best for poking around large files, it does everything
151
163
 
164
+ ### Changelog
165
+
166
+ #### 0.0.5 (April '25)
167
+
168
+ - Support for markdown style links in the cells
169
+
170
+ #### 0.0.4 (April '25)
171
+
172
+ - Separators can be turned off with `separators: false`.
173
+ - Headers are in color now. Tweaked a few of the dark colors as well.
174
+ - Tricky math to better protect narrow columns with auto-layout.
175
+
152
176
  ### Special Thanks
153
177
 
154
178
  - [termbg](https://github.com/dalance/termbg) and [termenv](https://github.com/muesli/termenv) for showing how to safely detect the terminal background color. These libraries are widely used for Rust/Go, but as far as I know nothing similar exists for Ruby.
155
179
  - The [Paint gem](https://github.com/janlelis/paint) for help with ansi colors.
180
+ - I copied the header color themes from [tabiew](https://github.com/shshemi/tabiew). Great project!
156
181
  - Google Sheets for providing nice color scales
data/Rakefile CHANGED
@@ -1,5 +1,7 @@
1
1
  require "bundler/setup"
2
2
  require "rake/testtask"
3
+ require "bundler"
4
+ Bundler::GemHelper.install_tasks
3
5
 
4
6
  Rake::TestTask.new do
5
7
  # let the user pass *.rb on the command line
data/demo.tape ADDED
@@ -0,0 +1,23 @@
1
+ #
2
+ # script for vhs (https://github.com/charmbracelet/vhs)
3
+ #
4
+
5
+ # vhs settings
6
+ Output "/tmp/demo.gif"
7
+ Set FontFamily Menlo
8
+ Set FontSize 28
9
+ Set Width 1520
10
+ Set Height 1100
11
+ Set Margin 0
12
+ Set Padding 0
13
+
14
+ # fire up the app
15
+ Hide
16
+ Type@1ms "tennis --clear --demo 3 --title 'Star Wars People' --zebra --columns name,height,homeworld,species,films --color-scale height"
17
+ Enter
18
+ Wait+Screen /Star/
19
+ Show
20
+
21
+ # now take the screenshot
22
+ Sleep 0.5
23
+ Screenshot "/tmp/dark.png"
data/justfile CHANGED
@@ -1,79 +1,71 @@
1
+ default: test
1
2
 
2
- # read gem version
3
- gemver := `grep -Eo "[0-9]+\.[0-9]+\.[0-9]+" lib/table_tennis/version.rb`
4
-
5
- #
6
- # dev
7
- #
3
+ # check repo - lint & test
4
+ check: lint test
8
5
 
9
- default:
10
- @just --list
6
+ # for ci. don't bother linting on windows
7
+ ci:
8
+ @if [[ "{{os()}}" != "windows" ]]; then just lint ; fi
9
+ @just test
11
10
 
12
- check: lint test
11
+ # check test coverage
12
+ coverage:
13
+ COVERAGE=1 just test
14
+ open /tmp/coverage/index.html
13
15
 
14
- ci: check
16
+ # format with rubocop
17
+ format: (lint "-a")
15
18
 
19
+ gem-local:
20
+ @just _banner rake install:local...
21
+ bundle exec rake install:local
16
22
 
17
- format:
18
- @just banner format...
19
- bundle exec rubocop -a
23
+ # this will tag, build and push to rubygems
24
+ gem-push: check
25
+ @just _banner rake release...
26
+ rake release
20
27
 
28
+ # optimize images
21
29
  image_optim:
22
- @bundle exec image_optim --allow-lossy --svgo-precision=1 -r .
30
+ @# advpng/pngout are slow. consider --verbose as well
31
+ @bundle exec image_optim --allow-lossy --svgo-precision=1 --no-advpng --no-pngout -r .
23
32
 
24
- lint:
25
- @just banner lint...
26
- bundle exec rubocop
33
+ # lint with rubocop
34
+ lint *ARGS:
35
+ @just _banner lint...
36
+ bundle exec rubocop {{ARGS}}
27
37
 
38
+ # start pry with the lib loaded
28
39
  pry:
29
40
  bundle exec pry -I lib -r table_tennis.rb
30
41
 
42
+ # run tennis repeatedly
31
43
  tennis-watch *ARGS:
32
44
  @watchexec --stop-timeout=0 --clear clear tennis {{ARGS}}
33
45
 
46
+ # run tests
34
47
  test *ARGS:
35
- @just banner rake test {{ARGS}}
48
+ @just _banner rake test {{ARGS}}
36
49
  @bundle exec rake test {{ARGS}}
37
50
 
51
+ # run tests repeatedly
38
52
  test-watch *ARGS:
39
- @watchexec --stop-timeout=0 --clear clear just test "{{ARGS}}"
53
+ watchexec --stop-timeout=0 --clear clear just test "{{ARGS}}"
40
54
 
41
- #
42
- # coverage/profiling
43
- #
44
-
45
- coverage:
46
- COVERAGE=1 just test
47
- open /tmp/coverage/index.html
48
-
49
- #
50
- # gem tasks
51
- #
52
-
53
- # you can test locally from another project by dropping gem file into vendor/cache
54
- gem-push:
55
- @just banner bundle install and verify git...
56
- bundle install
57
- #@just check-git-status
58
- @just banner gem build...
59
- gem build table_tennis.gemspec
60
- @just banner tag...
61
- git tag -a "v{{gemver}}" -m "Tagging {{gemver}}"
62
- git push --tags
63
- @just banner gem push...
64
- gem push "table_tennis-{{gemver}}.gem"
55
+ # create sceenshot using vhs
56
+ vhs:
57
+ @just _banner "running vhs..."
58
+ vhs demo.tape
59
+ magick /tmp/dark.png -crop 1448x1004+18+16 screenshots/dark.png
65
60
 
66
61
  #
67
62
  # util
68
63
  #
69
64
 
70
- banner *ARGS: (_banner BG_GREEN ARGS)
71
- warning *ARGS: (_banner BG_YELLOW ARGS)
72
- fatal *ARGS: (_banner BG_RED ARGS)
65
+ _banner *ARGS: (_message BG_GREEN ARGS)
66
+ _warning *ARGS: (_message BG_YELLOW ARGS)
67
+ _fatal *ARGS: (_message BG_RED ARGS)
73
68
  @exit 1
74
- _banner color *ARGS:
69
+ _message color *ARGS:
75
70
  @msg=$(printf "[%s] %s" $(date +%H:%M:%S) "{{ARGS}}") ; \
76
71
  printf "{{color+BOLD+WHITE}}%-72s{{ NORMAL }}\n" "$msg"
77
-
78
- check-git-status:
79
- @if [ ! -z "$(git status --porcelain)" ]; then just fatal "git status is dirty, bailing."; fi
@@ -18,6 +18,7 @@ module TableTennis
18
18
  row_numbers: false, # show line numbers?
19
19
  save: nil, # csv file path to save the table when created
20
20
  search: nil, # string/regex to highlight in output
21
+ separators: true, # if true, show separators between columns
21
22
  strftime: nil, # string for formatting dates
22
23
  theme: nil, # :dark, :light or :ansi. :dark is the default
23
24
  title: nil, # string for table title, if any
@@ -51,6 +52,7 @@ module TableTennis
51
52
  placeholder: :str,
52
53
  row_numbers: :bool,
53
54
  save: :str,
55
+ separators: :bool,
54
56
  strftime: :str,
55
57
  title: :str,
56
58
  titleize: :bool,
@@ -16,14 +16,23 @@ module TableTennis
16
16
  fn || :fn_default
17
17
  end
18
18
 
19
- rows.each do |row|
20
- row.each_index do
21
- value = row[_1]
19
+ rows.each.with_index do |row, r|
20
+ row.each_index do |c|
21
+ value = row[c]
22
22
  # Try to format using the column fn. This can return nil. For
23
23
  # example, a float column and value is nil, not a float, etc.
24
- formatted = send(fns[_1], value)
24
+ str = send(fns[c], value)
25
+
25
26
  # If the column formatter failed, use the default formatter
26
- row[_1] = formatted || fn_default(value) || config.placeholder
27
+ str ||= fn_default(value) || config.placeholder
28
+
29
+ # look for markdown-style links
30
+ if (link = detect_link(str))
31
+ str, data.links[[r, c]] = link
32
+ end
33
+
34
+ # done
35
+ row[c] = str
27
36
  end
28
37
  end
29
38
  end
@@ -61,7 +70,7 @@ module TableTennis
61
70
  # default formatting. cleanup whitespace
62
71
  def fn_default(value)
63
72
  return if value.nil?
64
- str = (value.is_a?(String) ? value : value.to_s)
73
+ str = value.is_a?(String) ? value : value.to_s
65
74
  str = str.strip.gsub("\n", "\\n").gsub("\r", "\\r") if str.match?(/\s/)
66
75
  return if str.empty?
67
76
  str
@@ -91,6 +100,14 @@ module TableTennis
91
100
  x
92
101
  end
93
102
 
103
+ def detect_link(str)
104
+ # fail fast, for speed
105
+ return unless str.length >= 6 && str[0] == "["
106
+ if str =~ /^\[([^\]]+)\]\(([^\)]+)\)$/
107
+ [$1, $2]
108
+ end
109
+ end
110
+
94
111
  # str to_xxx that are resistant to commas
95
112
  def to_f(str) = str.delete(",").to_f
96
113
  def to_i(str) = str.delete(",").to_i
@@ -28,8 +28,7 @@ module TableTennis
28
28
  # some math
29
29
  #
30
30
 
31
- AUTOLAYOUT_MIN_COLUMN_WIDTH = 2
32
- AUTOLAYOUT_FUDGE = 2
31
+ FUDGE = 2
33
32
 
34
33
  # Fit columns into terminal width. This is copied from the very simple HTML
35
34
  # table column algorithm. Returns a hash of column name to width.
@@ -37,30 +36,35 @@ module TableTennis
37
36
  # set provisional widths
38
37
  columns.each { _1.width = _1.measure }
39
38
 
40
- # how much space is available, and do we already fit?
39
+ # How much space is available, and do we already fit?
41
40
  screen_width = IO.console.winsize[1]
42
- available = screen_width - chrome_width - AUTOLAYOUT_FUDGE
41
+ available = screen_width - chrome_width - FUDGE
43
42
  return if available >= data_width
44
43
 
45
- # min/max column widths, which we use below
46
- min = columns.map { [_1.width, AUTOLAYOUT_MIN_COLUMN_WIDTH].min }
47
- max = columns.map(&:width)
44
+ # We don't fit, so we are going to shrink (truncate) some columns.
45
+ # Potentially all the way down to a lower bound. But what is the lower
46
+ # bound? It's nice to have a generous value so that narrow columns have
47
+ # a shot at avoiding truncation. That isn't always possible, though.
48
+ lower_bound = (available / columns.length).clamp(2, 10)
48
49
 
49
- # W = difference between the available space and the minimum table width
50
- # D = difference between maximum and minimum width of the table
51
- w = available - min.sum
52
- d = max.sum - min.sum
50
+ # Calculate a "min" and a "max" for each column, then allocate available
51
+ # space proportionally to each column. This is similar to the algorithm
52
+ # for HTML tables.
53
+ min = max = columns.map(&:width)
54
+ min = min.map { [_1, lower_bound].min }
53
55
 
54
- # edge case if we don't even have enough room for min
55
- if w <= 0
56
+ # W = difference between the available space and the minimum table width
57
+ # D = difference between maximum and minimum table width
58
+ # ratio = W / D
59
+ # col.width = col.min + ((col.max - col.min) * ratio)
60
+ ratio = (available - min.sum).to_f / (max.sum - min.sum)
61
+ if ratio <= 0
62
+ # even min doesn't fit, we gotta overflow
56
63
  columns.each.with_index { _1.width = min[_2] }
57
64
  return
58
65
  end
59
-
60
- # expand min to fit available space
61
- columns.each.with_index do
62
- # width = min + (delta * W / D)
63
- _1.width = min[_2] + ((max[_2] - min[_2]) * w / d.to_f).to_i
66
+ columns.zip(min, max).each do |column, min, max|
67
+ column.width = min + ((max - min) * ratio).to_i
64
68
  end
65
69
 
66
70
  # because we always round down, there might be some extra space to distribute
@@ -16,6 +16,7 @@ module TableTennis
16
16
  def run
17
17
  return if !config.color
18
18
  paint_title if config.title
19
+ paint_headers
19
20
  paint_row_numbers if config.row_numbers
20
21
  paint_rows if config.mark || config.zebra
21
22
  paint_columns if config.color_scales
@@ -32,6 +33,14 @@ module TableTennis
32
33
  set_style(r: :title, style: :title)
33
34
  end
34
35
 
36
+ NHEADER_COLORS = 6
37
+
38
+ def paint_headers
39
+ columns.each.with_index do |column, c|
40
+ set_style(r: :header, c:, style: :"header#{c % Theme::NHEADER_COLORS}")
41
+ end
42
+ end
43
+
35
44
  def paint_row_numbers
36
45
  set_style(c: 0, style: :chrome)
37
46
  end
@@ -127,8 +136,12 @@ module TableTennis
127
136
 
128
137
  def mark_style(user_mark)
129
138
  case user_mark
130
- when String, Symbol then [nil, user_mark] # assume bg color
131
- when Array then user_mark # a Paint array
139
+ when String, Symbol
140
+ # pick a nice fg color
141
+ [Util::Colors.contrast(user_mark), user_mark]
142
+ when Array
143
+ # a Paint array
144
+ user_mark
132
145
  else; :mark # default
133
146
  end
134
147
  end
@@ -32,12 +32,12 @@ module TableTennis
32
32
  if config.title
33
33
  io.puts render_separator(NW, BAR, NE)
34
34
  io.puts render_title
35
- io.puts render_separator(W, N, E)
35
+ io.puts render_separator(W, N, E) if config.separators
36
36
  else
37
37
  io.puts render_separator(NW, N, NE)
38
38
  end
39
39
  io.puts render_row(:header)
40
- io.puts render_separator(W, C, E)
40
+ io.puts render_separator(W, C, E) if config.separators
41
41
  rows.each_index { io.puts render_row(_1) }
42
42
  io.puts render_separator(SW, S, SE)
43
43
  end
@@ -59,9 +59,10 @@ module TableTennis
59
59
 
60
60
  # assemble line by rendering cells
61
61
  enum = (r != :header) ? rows[r].each : columns.map(&:header)
62
+ joiner = config.separators ? " #{pipe} " : " "
62
63
  line = enum.map.with_index do |value, c|
63
64
  render_cell(value, r, c, row_style)
64
- end.join(" #{pipe} ")
65
+ end.join(joiner)
65
66
  line = "#{pipe} #{line} #{pipe}"
66
67
 
67
68
  # afterward, apply row color
@@ -82,6 +83,11 @@ module TableTennis
82
83
  # add ansi codes for search
83
84
  value = value.gsub(search) { paint(_1, :search) } if search
84
85
 
86
+ # add ansi codes for links
87
+ if config.color && (link = data.links[[r, c]])
88
+ value = theme.link(value, link)
89
+ end
90
+
85
91
  # pad and paint
86
92
  if whitespace > 0
87
93
  spaces = " " * whitespace
@@ -95,6 +101,7 @@ module TableTennis
95
101
  end
96
102
 
97
103
  def render_separator(l, m, r)
104
+ m = "" if !config.separators
98
105
  line = [].tap do |buf|
99
106
  columns.each.with_index do |column, c|
100
107
  buf << ((c == 0) ? l : m)
@@ -21,7 +21,7 @@ module TableTennis
21
21
  prepend MemoWise
22
22
  include Util::Inspectable
23
23
 
24
- attr_accessor :config, :input_rows, :styles
24
+ attr_accessor :config, :input_rows, :links, :styles
25
25
 
26
26
  def initialize(rows:, config: nil)
27
27
  @config, @input_rows = config, rows
@@ -38,15 +38,20 @@ module TableTennis
38
38
  raise ArgumentError, "input_rows must be an array of hash-like objects, not #{input_rows.class}"
39
39
  end
40
40
 
41
+ @links = {}
41
42
  @styles = {}
42
43
  end
43
44
 
44
45
  # Lazily calculate the list of columns.
45
46
  def columns
46
47
  names = config&.columns
47
- names ||= {}.tap do |memo|
48
- fat_rows.each { |row| row.each_key { memo[_1] = 1 } }
49
- end.keys
48
+ if !fat_rows.empty?
49
+ names ||= {}.tap do |memo|
50
+ fat_rows.each { |row| row.each_key { memo[_1] = 1 } }
51
+ end.keys
52
+ else
53
+ names = []
54
+ end
50
55
  names.each do |name|
51
56
  if !fat_rows.any? { _1.key?(name) }
52
57
  raise ArgumentError, "specified column `#{name}` not found in any row of input data"
@@ -88,13 +93,22 @@ module TableTennis
88
93
 
89
94
  # layout math
90
95
  #
91
- # |•xxxx•|•xxxx•|•xxxx•|•xxxx•||•xxxx•|•xxxx•|•xxxx•|•xxxx•|
92
- # ↑↑ ↑ ↑
93
- # 12 3 <- three chrome chars per column │
94
- #
95
- # extra chrome char at the end
96
- #
97
- def chrome_width = columns.length * 3 + 1
96
+ # with separators
97
+ # |•xxxx•|•xxxx•|•xxxx•|•xxxx•|•xxxx•|•xxxx•|•xxxx•|•xxxx•|
98
+ # ↑↑ ↑ ↑
99
+ # 12 3 <- three chrome chars per column
100
+ #
101
+ # extra chrome char at the end
102
+ # without
103
+ # |•xxxx••xxxx••xxxx••xxxx••xxxx••xxxx••xxxx••xxxx•|
104
+ # ↑ ↑ ↑
105
+ # 1 2 <- two chrome chars per column │
106
+ # │
107
+ # extra chrome char at beginning and end
108
+ def chrome_width
109
+ config.separators ? (columns.length * 3 + 1) : (columns.length * 2 + 2)
110
+ end
111
+ memo_wise :chrome_width
98
112
 
99
113
  # for debugging
100
114
  def debug(str)
@@ -5,27 +5,49 @@ module TableTennis
5
5
  prepend MemoWise
6
6
 
7
7
  RESET = Paint::NOTHING
8
+ OSC_8 = "\e]8;;"
9
+ ST = "\e\\"
10
+
11
+ NHEADER_COLORS = 6
8
12
  THEMES = {
9
13
  dark: {
10
- title: "#7f0",
14
+ title: ["blue-400", :bold],
11
15
  chrome: "gray-500",
12
16
  cell: "gray-200",
17
+ header0: ["#ff6188", :bold],
18
+ header1: ["#fc9867", :bold],
19
+ header2: ["#ffd866", :bold],
20
+ header3: ["#a9dc76", :bold],
21
+ header4: ["#78dce8", :bold],
22
+ header5: ["#ab9df2", :bold],
13
23
  mark: %w[white blue-500],
14
24
  search: %w[black yellow-300],
15
- zebra: %w[white #333],
25
+ zebra: %w[white #222],
16
26
  },
17
27
  light: {
18
- title: "blue-600",
28
+ title: ["blue-600", :bold],
19
29
  chrome: "#bbb",
20
30
  cell: "gray-800",
31
+ header0: ["#ee4066", :bold],
32
+ header1: ["#da7645", :bold],
33
+ header2: ["#ddb644", :bold],
34
+ header3: ["#87ba54", :bold],
35
+ header4: ["#56bac6", :bold],
36
+ header5: ["#897bd0", :bold],
21
37
  mark: %w[white blue-500],
22
38
  search: %w[black yellow-300],
23
39
  zebra: %w[black gray-200],
24
40
  },
25
41
  ansi: {
26
- title: :green,
42
+ title: %i[green bold],
27
43
  chrome: %i[faint default],
28
44
  cell: :default,
45
+ header0: nil, # not supported
46
+ header1: nil, # not supported
47
+ header2: nil, # not supported
48
+ header3: nil, # not supported
49
+ header4: nil, # not supported
50
+ header5: nil, # not supported
29
51
  mark: %i[white blue],
30
52
  search: %i[white magenta],
31
53
  zebra: nil, # not supported
@@ -50,6 +72,7 @@ module TableTennis
50
72
  if value.is_a?(Symbol) && THEME_KEYS.include?(value)
51
73
  value = THEMES[name][value]
52
74
  end
75
+
53
76
  # turn value(s) into colors
54
77
  colors = Array(value).map { Util::Colors.get(_1) }
55
78
  return if colors == [] || colors == [nil]
@@ -75,6 +98,12 @@ module TableTennis
75
98
  end
76
99
  memo_wise :paint
77
100
 
101
+ # use osc 8 to create a terminal hyperlink. underline too
102
+ def link(str, link)
103
+ linked = "#{OSC_8}#{link}#{ST}#{str}#{OSC_8}#{ST}"
104
+ Paint[linked, :underline]
105
+ end
106
+
78
107
  # for debugging, mostly
79
108
  def self.info
80
109
  sample = if !Config.detect_color?
@@ -439,6 +439,14 @@ module TableTennis
439
439
 
440
440
  # is this color dark?
441
441
  def dark?(color)
442
+ color = get(color)
443
+ return true if !color
444
+
445
+ # take a guess at symbols too
446
+ if color.is_a?(Symbol)
447
+ return !%i[white gray].include?(color)
448
+ end
449
+
442
450
  luma = luma(color)
443
451
  !luma || luma < DARK_LUMA
444
452
  end
@@ -22,6 +22,12 @@ module TableTennis
22
22
  simple?(text) ? text.length : Unicode::DisplayWidth.of(text)
23
23
  end
24
24
 
25
+ def hyperlink(value)
26
+ if value =~ /^\[([^\]]*)\]\(([^\)]*)\)$/
27
+ [$1, $2]
28
+ end
29
+ end
30
+
25
31
  # truncate a string based on the display width of the grapheme clusters.
26
32
  # Should handle emojis and international characters
27
33
  def truncate(text, stop)
@@ -1,3 +1,3 @@
1
1
  module TableTennis
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.5"
3
3
  end
data/screenshots/dark.png CHANGED
Binary file
Binary file
data/screenshots/hope.png CHANGED
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
data/table_tennis.gemspec CHANGED
@@ -7,10 +7,11 @@ Gem::Specification.new do |s|
7
7
  s.email = "amd@gurge.com"
8
8
 
9
9
  s.summary = "Stylish tables in your terminal."
10
- s.homepage = "http://github.com/gurgeous/table_tennis"
10
+ s.homepage = "https://github.com/gurgeous/table_tennis"
11
11
  s.license = "MIT"
12
12
  s.required_ruby_version = ">= 3.0.0"
13
13
  s.metadata = {
14
+ "homepage_uri" => s.homepage,
14
15
  "rubygems_mfa_required" => "true",
15
16
  "source_code_uri" => s.homepage,
16
17
  }
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.3
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adam Doppelt
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-04-20 00:00:00.000000000 Z
10
+ date: 2025-04-28 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: csv
@@ -88,10 +88,10 @@ files:
88
88
  - ".gitignore"
89
89
  - ".rubocop.yml"
90
90
  - Gemfile
91
- - Gemfile.lock
92
91
  - LICENSE
93
92
  - README.md
94
93
  - Rakefile
94
+ - demo.tape
95
95
  - justfile
96
96
  - lib/table_tennis.rb
97
97
  - lib/table_tennis/column.rb
@@ -116,16 +116,18 @@ files:
116
116
  - screenshots/droids.png
117
117
  - screenshots/hope.png
118
118
  - screenshots/light.png
119
+ - screenshots/link.png
119
120
  - screenshots/row_numbers.png
120
121
  - screenshots/scales.png
121
122
  - screenshots/themes.png
122
123
  - table_tennis.gemspec
123
- homepage: http://github.com/gurgeous/table_tennis
124
+ homepage: https://github.com/gurgeous/table_tennis
124
125
  licenses:
125
126
  - MIT
126
127
  metadata:
128
+ homepage_uri: https://github.com/gurgeous/table_tennis
127
129
  rubygems_mfa_required: 'true'
128
- source_code_uri: http://github.com/gurgeous/table_tennis
130
+ source_code_uri: https://github.com/gurgeous/table_tennis
129
131
  rdoc_options: []
130
132
  require_paths:
131
133
  - lib
data/Gemfile.lock DELETED
@@ -1,122 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- table_tennis (0.0.3)
5
- csv (~> 3.3)
6
- ffi (~> 1.17)
7
- memo_wise (~> 1.11)
8
- paint (~> 2.3)
9
- unicode-display_width (~> 3.1)
10
-
11
- GEM
12
- remote: https://rubygems.org/
13
- specs:
14
- amazing_print (1.7.2)
15
- ast (2.4.3)
16
- coderay (1.1.3)
17
- csv (3.3.2)
18
- docile (1.4.1)
19
- ffi (1.17.1)
20
- ffi (1.17.1-aarch64-linux-gnu)
21
- ffi (1.17.1-aarch64-linux-musl)
22
- ffi (1.17.1-arm-linux-gnu)
23
- ffi (1.17.1-arm-linux-musl)
24
- ffi (1.17.1-arm64-darwin)
25
- ffi (1.17.1-x86-linux-gnu)
26
- ffi (1.17.1-x86-linux-musl)
27
- ffi (1.17.1-x86_64-darwin)
28
- ffi (1.17.1-x86_64-linux-gnu)
29
- ffi (1.17.1-x86_64-linux-musl)
30
- json (2.10.2)
31
- language_server-protocol (3.17.0.4)
32
- lint_roller (1.1.0)
33
- memo_wise (1.11.0)
34
- method_source (1.1.0)
35
- minitest (5.25.5)
36
- minitest-hooks (1.5.2)
37
- minitest (> 5.3)
38
- mocha (2.7.1)
39
- ruby2_keywords (>= 0.0.5)
40
- ostruct (0.6.1)
41
- paint (2.3.0)
42
- parallel (1.26.3)
43
- parser (3.3.7.4)
44
- ast (~> 2.4.1)
45
- racc
46
- prism (1.4.0)
47
- pry (0.15.2)
48
- coderay (~> 1.1)
49
- method_source (~> 1.0)
50
- racc (1.8.1)
51
- rainbow (3.1.1)
52
- rake (13.2.1)
53
- regexp_parser (2.10.0)
54
- rubocop (1.73.2)
55
- json (~> 2.3)
56
- language_server-protocol (~> 3.17.0.2)
57
- lint_roller (~> 1.1.0)
58
- parallel (~> 1.10)
59
- parser (>= 3.3.0.2)
60
- rainbow (>= 2.2.2, < 4.0)
61
- regexp_parser (>= 2.9.3, < 3.0)
62
- rubocop-ast (>= 1.38.0, < 2.0)
63
- ruby-progressbar (~> 1.7)
64
- unicode-display_width (>= 2.4.0, < 4.0)
65
- rubocop-ast (1.43.0)
66
- parser (>= 3.3.7.2)
67
- prism (~> 1.4)
68
- rubocop-performance (1.24.0)
69
- lint_roller (~> 1.1)
70
- rubocop (>= 1.72.1, < 2.0)
71
- rubocop-ast (>= 1.38.0, < 2.0)
72
- ruby-progressbar (1.13.0)
73
- ruby2_keywords (0.0.5)
74
- simplecov (0.22.0)
75
- docile (~> 1.1)
76
- simplecov-html (~> 0.11)
77
- simplecov_json_formatter (~> 0.1)
78
- simplecov-html (0.13.1)
79
- simplecov_json_formatter (0.1.4)
80
- standard (1.47.0)
81
- language_server-protocol (~> 3.17.0.2)
82
- lint_roller (~> 1.0)
83
- rubocop (~> 1.73.0)
84
- standard-custom (~> 1.0.0)
85
- standard-performance (~> 1.7)
86
- standard-custom (1.0.2)
87
- lint_roller (~> 1.0)
88
- rubocop (~> 1.50)
89
- standard-performance (1.7.0)
90
- lint_roller (~> 1.1)
91
- rubocop-performance (~> 1.24.0)
92
- unicode-display_width (3.1.4)
93
- unicode-emoji (~> 4.0, >= 4.0.4)
94
- unicode-emoji (4.0.4)
95
-
96
- PLATFORMS
97
- aarch64-linux-gnu
98
- aarch64-linux-musl
99
- arm-linux-gnu
100
- arm-linux-musl
101
- arm64-darwin
102
- ruby
103
- x86-linux-gnu
104
- x86-linux-musl
105
- x86_64-darwin
106
- x86_64-linux-gnu
107
- x86_64-linux-musl
108
-
109
- DEPENDENCIES
110
- amazing_print
111
- minitest
112
- minitest-hooks
113
- mocha
114
- ostruct
115
- pry
116
- rake
117
- simplecov
118
- standard
119
- table_tennis!
120
-
121
- BUNDLED WITH
122
- 2.6.5