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.
@@ -0,0 +1,524 @@
1
+ module TableTennis
2
+ module Util
3
+ # Named color names and misc color helpers.
4
+ module Colors
5
+ module_function
6
+
7
+ NAMED = {
8
+ #
9
+ # css colors
10
+ # https://htmlcolorcodes.com/color-names/
11
+ #
12
+
13
+ # red
14
+ "indianred" => "#cd5c5c",
15
+ "lightcoral" => "#f08080",
16
+ "salmon" => "#fa8072",
17
+ "darksalmon" => "#e9967a",
18
+ "lightsalmon" => "#ffa07a",
19
+ "crimson" => "#dc143c",
20
+ "red" => "#ff0000",
21
+ "firebrick" => "#b22222",
22
+ "darkred" => "#8b0000",
23
+ # pink
24
+ "pink" => "#ffc0cb",
25
+ "lightpink" => "#ffb6c1",
26
+ "hotpink" => "#ff69b4",
27
+ "deeppink" => "#ff1493",
28
+ "mediumvioletred" => "#c71585",
29
+ "palevioletred" => "#db7093",
30
+ # orange
31
+ # "lightsalmon" => "#ffa07a",
32
+ "coral" => "#ff7f50",
33
+ "tomato" => "#ff6347",
34
+ "orangered" => "#ff4500",
35
+ "darkorange" => "#ff8c00",
36
+ "orange" => "#ffa500",
37
+ # gold
38
+ "gold" => "#ffd700",
39
+ "yellow" => "#ffff00",
40
+ "lightyellow" => "#ffffe0",
41
+ "lemonchiffon" => "#fffacd",
42
+ "lightgoldenrodyellow" => "#fafad2",
43
+ "papayawhip" => "#ffefd5",
44
+ "moccasin" => "#ffe4b5",
45
+ "peachpuff" => "#ffdab9",
46
+ "palegoldenrod" => "#eee8aa",
47
+ "khaki" => "#f0e68c",
48
+ "darkkhaki" => "#bdb76b",
49
+ # purple
50
+ "lavender" => "#e6e6fa",
51
+ "thistle" => "#d8bfd8",
52
+ "plum" => "#dda0dd",
53
+ "violet" => "#ee82ee",
54
+ "orchid" => "#da70d6",
55
+ "fuchsia" => "#ff00ff",
56
+ "magenta" => "#ff00ff",
57
+ "mediumorchid" => "#ba55d3",
58
+ "mediumpurple" => "#9370db",
59
+ "rebeccapurple" => "#663399",
60
+ "blueviolet" => "#8a2be2",
61
+ "darkviolet" => "#9400d3",
62
+ "darkorchid" => "#9932cc",
63
+ "darkmagenta" => "#8b008b",
64
+ "purple" => "#800080",
65
+ "indigo" => "#4b0082",
66
+ "slateblue" => "#6a5acd",
67
+ "darkslateblue" => "#483d8b",
68
+ # "mediumslateblue" => "#7b68ee",
69
+ # green
70
+ "greenyellow" => "#adff2f",
71
+ "chartreuse" => "#7fff00",
72
+ "lawngreen" => "#7cfc00",
73
+ "lime" => "#00ff00",
74
+ "limegreen" => "#32cd32",
75
+ "palegreen" => "#98fb98",
76
+ "lightgreen" => "#90ee90",
77
+ "mediumspringgreen" => "#00fa9a",
78
+ "springgreen" => "#00ff7f",
79
+ "mediumseagreen" => "#3cb371",
80
+ "seagreen" => "#2e8b57",
81
+ "forestgreen" => "#228b22",
82
+ "green" => "#008000",
83
+ "darkgreen" => "#006400",
84
+ "yellowgreen" => "#9acd32",
85
+ "olivedrab" => "#6b8e23",
86
+ "olive" => "#808000",
87
+ "darkolivegreen" => "#556b2f",
88
+ "mediumaquamarine" => "#66cdaa",
89
+ "darkseagreen" => "#8fbc8b",
90
+ "lightseagreen" => "#20b2aa",
91
+ "darkcyan" => "#008b8b",
92
+ "teal" => "#008080",
93
+ # blue
94
+ "aqua" => "#00ffff",
95
+ "cyan" => "#00ffff",
96
+ "lightcyan" => "#e0ffff",
97
+ "paleturquoise" => "#afeeee",
98
+ "aquamarine" => "#7fffd4",
99
+ "turquoise" => "#40e0d0",
100
+ "mediumturquoise" => "#48d1cc",
101
+ "darkturquoise" => "#00ced1",
102
+ "cadetblue" => "#5f9ea0",
103
+ "steelblue" => "#4682b4",
104
+ "lightsteelblue" => "#b0c4de",
105
+ "powderblue" => "#b0e0e6",
106
+ "lightblue" => "#add8e6",
107
+ "skyblue" => "#87ceeb",
108
+ "lightskyblue" => "#87cefa",
109
+ "deepskyblue" => "#00bfff",
110
+ "dodgerblue" => "#1e90ff",
111
+ "cornflowerblue" => "#6495ed",
112
+ "mediumslateblue" => "#7b68ee",
113
+ "royalblue" => "#4169e1",
114
+ "blue" => "#0000ff",
115
+ "mediumblue" => "#0000cd",
116
+ "darkblue" => "#00008b",
117
+ "navy" => "#000080",
118
+ "midnightblue" => "#191970",
119
+ # brown
120
+ "cornsilk" => "#fff8dc",
121
+ "blanchedalmond" => "#ffebcd",
122
+ "bisque" => "#ffe4c4",
123
+ "navajowhite" => "#ffdead",
124
+ "wheat" => "#f5deb3",
125
+ "burlywood" => "#deb887",
126
+ "tan" => "#d2b48c",
127
+ "rosybrown" => "#bc8f8f",
128
+ "sandybrown" => "#f4a460",
129
+ "goldenrod" => "#daa520",
130
+ "darkgoldenrod" => "#b8860b",
131
+ "peru" => "#cd853f",
132
+ "chocolate" => "#d2691e",
133
+ "saddlebrown" => "#8b4513",
134
+ "sienna" => "#a0522d",
135
+ "brown" => "#a52a2a",
136
+ "maroon" => "#800000",
137
+ # white
138
+ "white" => "#ffffff",
139
+ "snow" => "#fffafa",
140
+ "honeydew" => "#f0fff0",
141
+ "mintcream" => "#f5fffa",
142
+ "azure" => "#f0ffff",
143
+ "aliceblue" => "#f0f8ff",
144
+ "ghostwhite" => "#f8f8ff",
145
+ "whitesmoke" => "#f5f5f5",
146
+ "seashell" => "#fff5ee",
147
+ "beige" => "#f5f5dc",
148
+ "oldlace" => "#fdf5e6",
149
+ "floralwhite" => "#fffaf0",
150
+ "ivory" => "#fffff0",
151
+ "antiquewhite" => "#faebd7",
152
+ "linen" => "#faf0e6",
153
+ "lavenderblush" => "#fff0f5",
154
+ "mistyrose" => "#ffe4e1",
155
+ # gray
156
+ "gainsboro" => "#dcdcdc",
157
+ "lightgray" => "#d3d3d3",
158
+ "silver" => "#c0c0c0",
159
+ "darkgray" => "#a9a9a9",
160
+ "gray" => "#808080",
161
+ "dimgray" => "#696969",
162
+ "lightslategray" => "#778899",
163
+ "slategray" => "#708090",
164
+ "darkslategray" => "#2f4f4f",
165
+ "black" => "#000000",
166
+
167
+ #
168
+ # tailwind
169
+ #
170
+
171
+ "slate-50" => "#f8fafc",
172
+ "slate-100" => "#f1f5f9",
173
+ "slate-200" => "#e2e8f0",
174
+ "slate-300" => "#cbd5e1",
175
+ "slate-400" => "#94a3b8",
176
+ "slate-500" => "#64748b",
177
+ "slate-600" => "#475569",
178
+ "slate-700" => "#334155",
179
+ "slate-800" => "#1e293b",
180
+ "slate-900" => "#0f172a",
181
+ "gray-50" => "#f9fafb",
182
+ "gray-100" => "#f3f4f6",
183
+ "gray-200" => "#e5e7eb",
184
+ "gray-300" => "#d1d5db",
185
+ "gray-400" => "#9ca3af",
186
+ "gray-500" => "#6b7280",
187
+ "gray-600" => "#4b5563",
188
+ "gray-700" => "#374151",
189
+ "gray-800" => "#1f2937",
190
+ "gray-900" => "#111827",
191
+ "zinc-50" => "#fafafa",
192
+ "zinc-100" => "#f4f4f5",
193
+ "zinc-200" => "#e4e4e7",
194
+ "zinc-300" => "#d4d4d8",
195
+ "zinc-400" => "#a1a1aa",
196
+ "zinc-500" => "#71717a",
197
+ "zinc-600" => "#52525b",
198
+ "zinc-700" => "#3f3f46",
199
+ "zinc-800" => "#27272a",
200
+ "zinc-900" => "#18181b",
201
+ "neutral-50" => "#fafafa",
202
+ "neutral-100" => "#f5f5f5",
203
+ "neutral-200" => "#e5e5e5",
204
+ "neutral-300" => "#d4d4d4",
205
+ "neutral-400" => "#a3a3a3",
206
+ "neutral-500" => "#737373",
207
+ "neutral-600" => "#525252",
208
+ "neutral-700" => "#404040",
209
+ "neutral-800" => "#262626",
210
+ "neutral-900" => "#171717",
211
+ "stone-50" => "#fafaf9",
212
+ "stone-100" => "#f5f5f4",
213
+ "stone-200" => "#e7e5e4",
214
+ "stone-300" => "#d6d3d1",
215
+ "stone-400" => "#a8a29e",
216
+ "stone-500" => "#78716c",
217
+ "stone-600" => "#57534e",
218
+ "stone-700" => "#44403c",
219
+ "stone-800" => "#292524",
220
+ "stone-900" => "#1c1917",
221
+ "red-50" => "#fef2f2",
222
+ "red-100" => "#fee2e2",
223
+ "red-200" => "#fecaca",
224
+ "red-300" => "#fca5a5",
225
+ "red-400" => "#f87171",
226
+ "red-500" => "#ef4444",
227
+ "red-600" => "#dc2626",
228
+ "red-700" => "#b91c1c",
229
+ "red-800" => "#991b1b",
230
+ "red-900" => "#7f1d1d",
231
+ "orange-50" => "#fff7ed",
232
+ "orange-100" => "#ffedd5",
233
+ "orange-200" => "#fed7aa",
234
+ "orange-300" => "#fdba74",
235
+ "orange-400" => "#fb923c",
236
+ "orange-500" => "#f97316",
237
+ "orange-600" => "#ea580c",
238
+ "orange-700" => "#c2410c",
239
+ "orange-800" => "#9a3412",
240
+ "orange-900" => "#7c2d12",
241
+ "amber-50" => "#fffbeb",
242
+ "amber-100" => "#fef3c7",
243
+ "amber-200" => "#fde68a",
244
+ "amber-300" => "#fcd34d",
245
+ "amber-400" => "#fbbf24",
246
+ "amber-500" => "#f59e0b",
247
+ "amber-600" => "#d97706",
248
+ "amber-700" => "#b45309",
249
+ "amber-800" => "#92400e",
250
+ "amber-900" => "#78350f",
251
+ "yellow-50" => "#fefce8",
252
+ "yellow-100" => "#fef9c3",
253
+ "yellow-200" => "#fef08a",
254
+ "yellow-300" => "#fde047",
255
+ "yellow-400" => "#facc15",
256
+ "yellow-500" => "#eab308",
257
+ "yellow-600" => "#ca8a04",
258
+ "yellow-700" => "#a16207",
259
+ "yellow-800" => "#854d0e",
260
+ "yellow-900" => "#713f12",
261
+ "lime-50" => "#f7fee7",
262
+ "lime-100" => "#ecfccb",
263
+ "lime-200" => "#d9f99d",
264
+ "lime-300" => "#bef264",
265
+ "lime-400" => "#a3e635",
266
+ "lime-500" => "#84cc16",
267
+ "lime-600" => "#65a30d",
268
+ "lime-700" => "#4d7c0f",
269
+ "lime-800" => "#3f6212",
270
+ "lime-900" => "#365314",
271
+ "green-50" => "#f0fdf4",
272
+ "green-100" => "#dcfce7",
273
+ "green-200" => "#bbf7d0",
274
+ "green-300" => "#86efac",
275
+ "green-400" => "#4ade80",
276
+ "green-500" => "#22c55e",
277
+ "green-600" => "#16a34a",
278
+ "green-700" => "#15803d",
279
+ "green-800" => "#166534",
280
+ "green-900" => "#14532d",
281
+ "emerald-50" => "#ecfdf5",
282
+ "emerald-100" => "#d1fae5",
283
+ "emerald-200" => "#a7f3d0",
284
+ "emerald-300" => "#6ee7b7",
285
+ "emerald-400" => "#34d399",
286
+ "emerald-500" => "#10b981",
287
+ "emerald-600" => "#059669",
288
+ "emerald-700" => "#047857",
289
+ "emerald-800" => "#065f46",
290
+ "emerald-900" => "#064e3b",
291
+ "teal-50" => "#f0fdfa",
292
+ "teal-100" => "#ccfbf1",
293
+ "teal-200" => "#99f6e4",
294
+ "teal-300" => "#5eead4",
295
+ "teal-400" => "#2dd4bf",
296
+ "teal-500" => "#14b8a6",
297
+ "teal-600" => "#0d9488",
298
+ "teal-700" => "#0f766e",
299
+ "teal-800" => "#115e59",
300
+ "teal-900" => "#134e4a",
301
+ "cyan-50" => "#ecfeff",
302
+ "cyan-100" => "#cffafe",
303
+ "cyan-200" => "#a5f3fc",
304
+ "cyan-300" => "#67e8f9",
305
+ "cyan-400" => "#22d3ee",
306
+ "cyan-500" => "#06b6d4",
307
+ "cyan-600" => "#0891b2",
308
+ "cyan-700" => "#0e7490",
309
+ "cyan-800" => "#155e75",
310
+ "cyan-900" => "#164e63",
311
+ "sky-50" => "#f0f9ff",
312
+ "sky-100" => "#e0f2fe",
313
+ "sky-200" => "#bae6fd",
314
+ "sky-300" => "#7dd3fc",
315
+ "sky-400" => "#38bdf8",
316
+ "sky-500" => "#0ea5e9",
317
+ "sky-600" => "#0284c7",
318
+ "sky-700" => "#0369a1",
319
+ "sky-800" => "#075985",
320
+ "sky-900" => "#0c4a6e",
321
+ "blue-50" => "#eff6ff",
322
+ "blue-100" => "#dbeafe",
323
+ "blue-200" => "#bfdbfe",
324
+ "blue-300" => "#93c5fd",
325
+ "blue-400" => "#60a5fa",
326
+ "blue-500" => "#3b82f6",
327
+ "blue-600" => "#2563eb",
328
+ "blue-700" => "#1d4ed8",
329
+ "blue-800" => "#1e40af",
330
+ "blue-900" => "#1e3a8a",
331
+ "indigo-50" => "#eef2ff",
332
+ "indigo-100" => "#e0e7ff",
333
+ "indigo-200" => "#c7d2fe",
334
+ "indigo-300" => "#a5b4fc",
335
+ "indigo-400" => "#818cf8",
336
+ "indigo-500" => "#6366f1",
337
+ "indigo-600" => "#4f46e5",
338
+ "indigo-700" => "#4338ca",
339
+ "indigo-800" => "#3730a3",
340
+ "indigo-900" => "#312e81",
341
+ "violet-50" => "#f5f3ff",
342
+ "violet-100" => "#ede9fe",
343
+ "violet-200" => "#ddd6fe",
344
+ "violet-300" => "#c4b5fd",
345
+ "violet-400" => "#a78bfa",
346
+ "violet-500" => "#8b5cf6",
347
+ "violet-600" => "#7c3aed",
348
+ "violet-700" => "#6d28d9",
349
+ "violet-800" => "#5b21b6",
350
+ "violet-900" => "#4c1d95",
351
+ "purple-50" => "#faf5ff",
352
+ "purple-100" => "#f3e8ff",
353
+ "purple-200" => "#e9d5ff",
354
+ "purple-300" => "#d8b4fe",
355
+ "purple-400" => "#c084fc",
356
+ "purple-500" => "#a855f7",
357
+ "purple-600" => "#9333ea",
358
+ "purple-700" => "#7e22ce",
359
+ "purple-800" => "#6b21a8",
360
+ "purple-900" => "#581c87",
361
+ "fuchsia-50" => "#fdf4ff",
362
+ "fuchsia-100" => "#fae8ff",
363
+ "fuchsia-200" => "#f5d0fe",
364
+ "fuchsia-300" => "#f0abfc",
365
+ "fuchsia-400" => "#e879f9",
366
+ "fuchsia-500" => "#d946ef",
367
+ "fuchsia-600" => "#c026d3",
368
+ "fuchsia-700" => "#a21caf",
369
+ "fuchsia-800" => "#86198f",
370
+ "fuchsia-900" => "#701a75",
371
+ "pink-50" => "#fdf2f8",
372
+ "pink-100" => "#fce7f3",
373
+ "pink-200" => "#fbcfe8",
374
+ "pink-300" => "#f9a8d4",
375
+ "pink-400" => "#f472b6",
376
+ "pink-500" => "#ec4899",
377
+ "pink-600" => "#db2777",
378
+ "pink-700" => "#be185d",
379
+ "pink-800" => "#9d174d",
380
+ "pink-900" => "#831843",
381
+ "rose-50" => "#fff1f2",
382
+ "rose-100" => "#ffe4e6",
383
+ "rose-200" => "#fecdd3",
384
+ "rose-300" => "#fda4af",
385
+ "rose-400" => "#fb7185",
386
+ "rose-500" => "#f43f5e",
387
+ "rose-600" => "#e11d48",
388
+ "rose-700" => "#be123c",
389
+ "rose-800" => "#9f1239",
390
+ "rose-900" => "#881337",
391
+ }
392
+
393
+ SYMBOLS = (Paint::ANSI_COLORS.keys + Paint::ANSI_EFFECTS.keys).to_set
394
+
395
+ # we support a few different kinds of colors
396
+ def get(color)
397
+ case color
398
+ # nil is ok
399
+ when nil
400
+ return nil
401
+
402
+ # hex string
403
+ when /^#?[0-9a-f]{3}([0-9a-f]{3})?$/i
404
+ return color
405
+
406
+ when Symbol
407
+ # Paint ansi color or ansi effect
408
+ if !SYMBOLS.include?(color)
409
+ puts "warning: TableTennis #{color.inspect} unknown ansi color/effect symbol"
410
+ return
411
+ end
412
+ return color
413
+
414
+ when String
415
+ if !(hex = NAMED[color.downcase])
416
+ puts "warning: TableTennis #{color.inspect} unknown named color string"
417
+ return
418
+ end
419
+ return hex
420
+
421
+ else
422
+ puts "warning: TableTennis #{color.inspect} unknown color"
423
+ return
424
+ end
425
+ end
426
+
427
+ # sRGB to Y (relative luminance)
428
+ def luma(hex)
429
+ rgb = to_rgb(hex)
430
+ return if !rgb
431
+ rgb = rgb.map { _1 / 255.0 }
432
+ coeff = [0.2126, 0.7152, 0.0722]
433
+ rgb.zip(coeff).sum { (_1**2.2) * _2 }.round(3)
434
+ end
435
+
436
+ DARK_LUMA = 0.36
437
+
438
+ # is this color dark?
439
+ def dark?(color)
440
+ luma = luma(color)
441
+ !luma || luma < DARK_LUMA
442
+ end
443
+
444
+ def contrast(bg)
445
+ dark?(bg) ? "white" : "black"
446
+ end
447
+
448
+ # [r,g,b] => "#rrggbb"
449
+ def to_hex(rgb)
450
+ sprintf("#%02x%02x%02x", *rgb)
451
+ end
452
+
453
+ # "#?(rgb|rrggbb|rrrrggggbbbb)" => [r,g,b]
454
+ def to_rgb(hex)
455
+ if !hex.match(/\A#?([0-9a-f]+)\z/i)
456
+ return
457
+ end
458
+ hex = $1
459
+ case hex.length
460
+ when 3 then hex.scan(/./).map { "#{_1}#{_1}" }
461
+ when 6 then hex.scan(/../)
462
+ when 9 then hex.scan(/.../)
463
+ when 12 then hex.scan(/..../)
464
+ else; return
465
+ end.map { _1[0, 2].to_i(16) }
466
+ end
467
+
468
+ # lookup RGB for an ansi fg or bg color
469
+ def ansi_color_to_hex(num)
470
+ return if !(0..15).cover?(num)
471
+ rgb = if num < 8
472
+ Paint::RGB_COLORS_ANSI.values[num]
473
+ else
474
+ Paint::RGB_COLORS_ANSI_BRIGHT.values[num - 8]
475
+ end
476
+ to_hex(rgb)
477
+ end
478
+
479
+ # print all colors to $stdout, helps with creating themes
480
+ def spectrum
481
+ max = NAMED.keys.map(&:length).max
482
+ fmt = " %-#{max}s %s %0.3f "
483
+ NAMED.each do |name, color|
484
+ str = sprintf(fmt, name, color, luma(color))
485
+ str1 = Paint[str, color, "white"]
486
+ str2 = Paint[str, color, "black"]
487
+ str3 = Paint[" white ", "white", color]
488
+ str4 = Paint[" black ", "black", color]
489
+ puts "#{str1}#{str2}#{str3}#{str4}"
490
+ end
491
+
492
+ fmt = " ansi %-6s %-8s "
493
+ colors = Paint::ANSI_COLORS_FOREGROUND.keys
494
+ puts
495
+ colors.each do
496
+ fg = sprintf(fmt, "normal", _1.inspect)
497
+ str1 = Paint[fg, _1, :white]
498
+ str2 = Paint[fg, _1, :black]
499
+ puts "#{str1}#{str2}"
500
+ end
501
+ puts
502
+ colors.each do
503
+ str = sprintf(fmt, ":bold", _1.inspect)
504
+ str1 = Paint[str, _1, :bold, :white]
505
+ str2 = Paint[str, _1, :bold, :black]
506
+ puts "#{str1}#{str2}"
507
+ end
508
+ puts
509
+ colors.each do
510
+ str = sprintf(fmt, ":faint", _1.inspect)
511
+ str1 = Paint[str, _1, :faint, :white]
512
+ str2 = Paint[str, _1, :faint, :black]
513
+ puts "#{str1}#{str2}"
514
+ end
515
+ puts
516
+ colors.each do
517
+ str1 = Paint[sprintf(" :white on %-8s ", _1.inspect), :white, _1]
518
+ str2 = Paint[sprintf(" :black on %-8s ", _1.inspect), :black, _1]
519
+ puts "#{str1}#{str2}"
520
+ end
521
+ end
522
+ end
523
+ end
524
+ end
@@ -0,0 +1,25 @@
1
+ module TableTennis
2
+ module Util
3
+ # A mixin to avoid putting all row data into xxx.inspect. This makes
4
+ # development much easier.
5
+ module Inspectable
6
+ def inspect = ENV["MINITEST"] ? super : inspect_without_rows
7
+
8
+ def inspect_without_rows
9
+ vars = instance_variables.filter_map do
10
+ value = instance_variable_get(_1)
11
+ if value.is_a?(Array) && _1.to_s =~ /rows$/
12
+ value = if value.length == 1
13
+ "[1 row]"
14
+ else
15
+ "[#{value.length} rows]"
16
+ end
17
+ end
18
+ "#{_1}=#{value.inspect}"
19
+ end.join(" ")
20
+
21
+ "<#{self.class.name} #{vars}>"
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,55 @@
1
+ module TableTennis
2
+ module Util
3
+ # Helpers for color scales (adding background colors to a column based on the
4
+ # value of the float in the cell). Like conditional formattiong with a color
5
+ # range in google sheets.
6
+ module Scale
7
+ prepend MemoWise
8
+
9
+ module_function
10
+
11
+ WHITE, GREEN, RED, YELLOW, BLUE = "#fff", "#57bb8a", "#e67c73", "ffd666", "#6c9eeb"
12
+
13
+ SCALES = {
14
+ # white => color
15
+ g: [WHITE, GREEN],
16
+ y: [WHITE, YELLOW],
17
+ r: [WHITE, RED],
18
+ b: [WHITE, BLUE],
19
+
20
+ # green/yellow/red => white
21
+ gw: [GREEN, WHITE],
22
+ yw: [YELLOW, WHITE],
23
+ rw: [RED, WHITE],
24
+ bw: [BLUE, WHITE],
25
+
26
+ # red <=> green
27
+ rg: [RED, WHITE, GREEN],
28
+ gr: [GREEN, WHITE, RED],
29
+ gyr: [GREEN, YELLOW, RED],
30
+ }
31
+
32
+ def interpolate(name, t)
33
+ t = t.clamp(0, 1).to_f
34
+ stops = color_stops(name)
35
+ rgb = if stops.first.length == 2
36
+ stops.map { lerp(t, _1, _2) }
37
+ elsif t < 0.5
38
+ t *= 2
39
+ stops.map { lerp(t, _1, _2) }
40
+ else
41
+ t = (t - 0.5) * 2
42
+ stops.map { lerp(t, _2, _3) }
43
+ end
44
+ Colors.to_hex(rgb)
45
+ end
46
+
47
+ def color_stops(name)
48
+ SCALES[name].map { Colors.to_rgb(_1) }.transpose
49
+ end
50
+ memo_wise self: :color_stops
51
+
52
+ def lerp(t, v0, v1) = t * (v1 - v0) + v0
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,62 @@
1
+ module TableTennis
2
+ module Util
3
+ # Helpers for measuring and truncating strings.
4
+ module Strings
5
+ prepend MemoWise
6
+
7
+ module_function
8
+
9
+ # strip ansi codes
10
+ def unpaint(str) = str.gsub(/\e\[[0-9;]*m/, "")
11
+
12
+ def width(text)
13
+ simple?(text) ? text.length : Unicode::DisplayWidth.of(text)
14
+ end
15
+
16
+ # truncate a string based on the display width of the grapheme clusters.
17
+ # Should handle emojis and international characters
18
+ def truncate(text, stop)
19
+ if simple?(text)
20
+ return (text.length > stop) ? "#{text[0, stop - 1]}…" : text
21
+ end
22
+
23
+ # get grapheme clusters, and attach zero width graphemes to the previous grapheme
24
+ list = [].tap do |accum|
25
+ text.grapheme_clusters.each do
26
+ if width(_1) == 0 && !accum.empty?
27
+ accum[-1] = "#{accum[-1]}#{_1}"
28
+ else
29
+ accum << _1
30
+ end
31
+ end
32
+ end
33
+
34
+ width = 0
35
+ list.each_index do
36
+ w = Unicode::DisplayWidth.of(list[_1])
37
+ next if (width += w) <= stop
38
+
39
+ # we've gone too far. do we need to pop for the ellipsis?
40
+ text = list[0, _1]
41
+ text.pop if w == 1
42
+ return "#{text.join}…"
43
+ end
44
+ text
45
+ end
46
+
47
+ SIMPLE = /\A[\x00-\x7F–—…·‘’“”•áéíñóúÓ]*\Z/
48
+
49
+ # Is this a "simple" string? (no emojis, etc). Caches results for small
50
+ # strings for performance reasons.
51
+ def simple?(str)
52
+ if str.length <= 8
53
+ @simple ||= Hash.new { _1[_2] = _2.match?(SIMPLE) }
54
+ @simple[str]
55
+ else
56
+ str.match?(SIMPLE)
57
+ end
58
+ end
59
+ private_class_method :simple?
60
+ end
61
+ end
62
+ end