tessella 0.3.0
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/LICENSE +21 -0
- data/README.md +900 -0
- data/doc/README.md +74 -0
- data/doc/bitfield.md +123 -0
- data/doc/charts.md +1370 -0
- data/doc/check.rb +75 -0
- data/doc/cli.md +483 -0
- data/doc/composition.md +599 -0
- data/doc/getting-started.md +121 -0
- data/doc/glyphs.md +134 -0
- data/doc/images.md +325 -0
- data/doc/library.md +239 -0
- data/doc/lint.md +270 -0
- data/doc/meter.md +210 -0
- data/doc/rule.md +51 -0
- data/doc/seq.md +169 -0
- data/doc/sparkline.md +63 -0
- data/doc/table.md +347 -0
- data/doc/targets.md +222 -0
- data/doc/text.md +84 -0
- data/doc/tree.md +108 -0
- data/doc/wave.md +122 -0
- data/examples/README.md +103 -0
- data/examples/ages.txt +9 -0
- data/examples/bad.csv +3 -0
- data/examples/boxes.txt +3 -0
- data/examples/broken.txt +4 -0
- data/examples/calibration.rb +12 -0
- data/examples/cities.csv +4 -0
- data/examples/cjk.rb +13 -0
- data/examples/comment.rb +9 -0
- data/examples/comment.sh +13 -0
- data/examples/cpu.txt +60 -0
- data/examples/dashboard.sh +25 -0
- data/examples/datasheet.rb +16 -0
- data/examples/disc.pgm +0 -0
- data/examples/disks.sh +18 -0
- data/examples/disks.txt +5 -0
- data/examples/dose.txt +7 -0
- data/examples/drift.txt +4 -0
- data/examples/ethernet.txt +3 -0
- data/examples/figure.c +5 -0
- data/examples/fit.rb +16 -0
- data/examples/floors.rb +21 -0
- data/examples/gallery.sh +20 -0
- data/examples/gauges.sh +31 -0
- data/examples/gradient.rb +16 -0
- data/examples/growth.txt +6 -0
- data/examples/hosts.csv +4 -0
- data/examples/icmp.txt +3 -0
- data/examples/ipv4.txt +12 -0
- data/examples/kinds.csv +4 -0
- data/examples/langs.csv +4 -0
- data/examples/lint.rb +16 -0
- data/examples/load.rb +14 -0
- data/examples/login.txt +10 -0
- data/examples/neg.csv +4 -0
- data/examples/normal.txt +200 -0
- data/examples/note.txt +2 -0
- data/examples/notes-fixed.md +17 -0
- data/examples/notes.md +17 -0
- data/examples/ohlc.txt +7 -0
- data/examples/os.csv +10 -0
- data/examples/outline.txt +8 -0
- data/examples/plot.txt +5 -0
- data/examples/refusals.rb +17 -0
- data/examples/report.sh +21 -0
- data/examples/rfc.sh +22 -0
- data/examples/samples.txt +7 -0
- data/examples/shapes.pgm +0 -0
- data/examples/sizes.tsv +3 -0
- data/examples/spi.txt +6 -0
- data/examples/status.sh +23 -0
- data/examples/stem.txt +12 -0
- data/examples/table-drift.txt +4 -0
- data/examples/temps.txt +31 -0
- data/examples/trig.sh +13 -0
- data/examples/trig.txt +102 -0
- data/examples/week.txt +8 -0
- data/examples/wide.txt +2 -0
- data/exe/tessella +6 -0
- data/lib/tessella/axis.rb +71 -0
- data/lib/tessella/bitfield.rb +194 -0
- data/lib/tessella/block.rb +56 -0
- data/lib/tessella/borders.rb +85 -0
- data/lib/tessella/canvas/area.rb +155 -0
- data/lib/tessella/canvas/ascii.rb +120 -0
- data/lib/tessella/canvas/block.rb +40 -0
- data/lib/tessella/canvas/braille.rb +30 -0
- data/lib/tessella/canvas/clip.rb +49 -0
- data/lib/tessella/canvas/density.rb +51 -0
- data/lib/tessella/canvas/dot.rb +27 -0
- data/lib/tessella/canvas/eighths.rb +43 -0
- data/lib/tessella/canvas/half.rb +51 -0
- data/lib/tessella/canvas/sextant.rb +36 -0
- data/lib/tessella/canvas.rb +168 -0
- data/lib/tessella/cell.rb +10 -0
- data/lib/tessella/chart.rb +211 -0
- data/lib/tessella/check.rb +52 -0
- data/lib/tessella/cli/bitfield.rb +121 -0
- data/lib/tessella/cli/chart.rb +615 -0
- data/lib/tessella/cli/chart_candle.rb +36 -0
- data/lib/tessella/cli/chart_heatmap.rb +21 -0
- data/lib/tessella/cli/chart_help.rb +85 -0
- data/lib/tessella/cli/chart_pie.rb +38 -0
- data/lib/tessella/cli/compose.rb +156 -0
- data/lib/tessella/cli/compose_options.rb +259 -0
- data/lib/tessella/cli/glyphs.rb +97 -0
- data/lib/tessella/cli/image.rb +250 -0
- data/lib/tessella/cli/input.rb +44 -0
- data/lib/tessella/cli/iso_time.rb +28 -0
- data/lib/tessella/cli/lint.rb +102 -0
- data/lib/tessella/cli/lint_options.rb +99 -0
- data/lib/tessella/cli/main.rb +121 -0
- data/lib/tessella/cli/meter.rb +80 -0
- data/lib/tessella/cli/rule.rb +57 -0
- data/lib/tessella/cli/seq.rb +117 -0
- data/lib/tessella/cli/sparkline.rb +72 -0
- data/lib/tessella/cli/table.rb +167 -0
- data/lib/tessella/cli/text.rb +78 -0
- data/lib/tessella/cli/tree.rb +100 -0
- data/lib/tessella/cli/wave.rb +148 -0
- data/lib/tessella/cli.rb +36 -0
- data/lib/tessella/color.rb +106 -0
- data/lib/tessella/compose.rb +243 -0
- data/lib/tessella/compose_fill.rb +176 -0
- data/lib/tessella/compose_grid.rb +36 -0
- data/lib/tessella/compose_shadow.rb +41 -0
- data/lib/tessella/error.rb +13 -0
- data/lib/tessella/frame.rb +170 -0
- data/lib/tessella/glyphs.rb +106 -0
- data/lib/tessella/image.rb +454 -0
- data/lib/tessella/image_octant.rb +46 -0
- data/lib/tessella/image_petscii.rb +116 -0
- data/lib/tessella/kinds/bar.rb +344 -0
- data/lib/tessella/kinds/box.rb +213 -0
- data/lib/tessella/kinds/box_upright.rb +110 -0
- data/lib/tessella/kinds/candlestick.rb +164 -0
- data/lib/tessella/kinds/columns.rb +259 -0
- data/lib/tessella/kinds/date_axis.rb +102 -0
- data/lib/tessella/kinds/error_bars.rb +71 -0
- data/lib/tessella/kinds/heatmap.rb +130 -0
- data/lib/tessella/kinds/line.rb +173 -0
- data/lib/tessella/kinds/multibar.rb +98 -0
- data/lib/tessella/kinds/multibar_columns.rb +130 -0
- data/lib/tessella/kinds/multibar_fine.rb +45 -0
- data/lib/tessella/kinds/multibar_rows.rb +93 -0
- data/lib/tessella/kinds/pie.rb +123 -0
- data/lib/tessella/kinds/pie_rim.rb +78 -0
- data/lib/tessella/kinds/sideways.rb +62 -0
- data/lib/tessella/kinds/stem.rb +134 -0
- data/lib/tessella/kinds/threshold.rb +73 -0
- data/lib/tessella/legend.rb +31 -0
- data/lib/tessella/lint/alphabet.rb +56 -0
- data/lib/tessella/lint/drift.rb +82 -0
- data/lib/tessella/lint/fences.rb +51 -0
- data/lib/tessella/lint/geometry.rb +120 -0
- data/lib/tessella/lint/geometry_checks.rb +224 -0
- data/lib/tessella/lint/lines.rb +115 -0
- data/lib/tessella/lint/mixing.rb +61 -0
- data/lib/tessella/lint/runner.rb +73 -0
- data/lib/tessella/lint/shadows.rb +120 -0
- data/lib/tessella/lint/target_rows.rb +66 -0
- data/lib/tessella/lint.rb +158 -0
- data/lib/tessella/meter.rb +133 -0
- data/lib/tessella/meter_upright.rb +43 -0
- data/lib/tessella/png.rb +456 -0
- data/lib/tessella/raster.rb +92 -0
- data/lib/tessella/rmagick_reader.rb +52 -0
- data/lib/tessella/rule.rb +59 -0
- data/lib/tessella/seq.rb +522 -0
- data/lib/tessella/sparkline.rb +57 -0
- data/lib/tessella/stats.rb +199 -0
- data/lib/tessella/table.rb +547 -0
- data/lib/tessella/table_cells.rb +225 -0
- data/lib/tessella/target.rb +105 -0
- data/lib/tessella/text.rb +76 -0
- data/lib/tessella/text_forms.rb +150 -0
- data/lib/tessella/tree.rb +221 -0
- data/lib/tessella/version.rb +5 -0
- data/lib/tessella/wave.rb +353 -0
- data/lib/tessella/width.rb +122 -0
- data/lib/tessella/width_class.rb +77 -0
- data/lib/tessella/width_invisible.rb +66 -0
- data/lib/tessella/width_invisible_high.rb +60 -0
- data/lib/tessella.rb +94 -0
- metadata +226 -0
data/doc/table.md
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
# Tables
|
|
2
|
+
|
|
3
|
+
`table` draws rows of text as a framed table, sized to its content,
|
|
4
|
+
with numbers right-aligned.
|
|
5
|
+
|
|
6
|
+
```ruby
|
|
7
|
+
Tessella.table(rows, header: true, style: nil, ascii: false,
|
|
8
|
+
align: nil, title: nil, width: nil, min_width: 4,
|
|
9
|
+
color: false) # => Block
|
|
10
|
+
Tessella::Table.parse(text, tsv: false) # => Array of rows
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
tessella table [--tsv] [--no-header] [--style S] [--ascii]
|
|
15
|
+
[--align SPEC] [--title T] [--max-width N] [file]
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Input
|
|
19
|
+
|
|
20
|
+
`rows` is an Array of Arrays of Strings or numbers, the first row the
|
|
21
|
+
header unless `header: false`. A number is drawn as written (`12`,
|
|
22
|
+
`7.5`), and a column of them right-aligns; any other value is refused
|
|
23
|
+
by its position: "row 2, column 1: a cell must be a String or a
|
|
24
|
+
number: nil". `Table.parse` reads CSV with RFC 4180 quoting
|
|
25
|
+
(a quoted field may hold commas, doubled quotes and newlines), or
|
|
26
|
+
tab-separated text with `tsv: true`. The command line reads CSV by
|
|
27
|
+
default and TSV with `--tsv`.
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
$ cat kinds.csv
|
|
31
|
+
kind,canvas,notes
|
|
32
|
+
line,braille,"joins points, one series per column"
|
|
33
|
+
bar,none,"negative values split at zero"
|
|
34
|
+
stem,none,"back-to-back with two columns"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```text
|
|
38
|
+
$ tessella table kinds.csv
|
|
39
|
+
┌──────┬─────────┬─────────────────────────────────────┐
|
|
40
|
+
│ kind │ canvas │ notes │
|
|
41
|
+
├──────┼─────────┼─────────────────────────────────────┤
|
|
42
|
+
│ line │ braille │ joins points, one series per column │
|
|
43
|
+
│ bar │ none │ negative values split at zero │
|
|
44
|
+
│ stem │ none │ back-to-back with two columns │
|
|
45
|
+
└──────┴─────────┴─────────────────────────────────────┘
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
A column whose every body cell is a number is right-aligned:
|
|
49
|
+
|
|
50
|
+
```text
|
|
51
|
+
$ tessella table langs.csv
|
|
52
|
+
┌──────┬─────┐
|
|
53
|
+
│ lang │ loc │
|
|
54
|
+
├──────┼─────┤
|
|
55
|
+
│ ruby │ 12 │
|
|
56
|
+
│ c │ 30 │
|
|
57
|
+
│ rust │ 7.5 │
|
|
58
|
+
└──────┴─────┘
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`--no-header` draws every row as body:
|
|
62
|
+
|
|
63
|
+
```text
|
|
64
|
+
$ tessella table --tsv --no-header sizes.tsv
|
|
65
|
+
┌──────┬──────┐
|
|
66
|
+
│ name │ size │
|
|
67
|
+
│ lib │ 7876 │
|
|
68
|
+
│ test │ 5120 │
|
|
69
|
+
└──────┴──────┘
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Alignment and title
|
|
73
|
+
|
|
74
|
+
`--align` takes one letter per column, `l`, `r` or `c`; the library
|
|
75
|
+
also takes an Array of `:left`, `:right`, `:center`. `--title` adds a
|
|
76
|
+
row above the header, centred across the table.
|
|
77
|
+
|
|
78
|
+
```text
|
|
79
|
+
$ tessella table --title Kinds --align lcr kinds.csv
|
|
80
|
+
┌──────────────────────────────────────────────────────┐
|
|
81
|
+
│ Kinds │
|
|
82
|
+
├──────┬─────────┬─────────────────────────────────────┤
|
|
83
|
+
│ kind │ canvas │ notes │
|
|
84
|
+
├──────┼─────────┼─────────────────────────────────────┤
|
|
85
|
+
│ line │ braille │ joins points, one series per column │
|
|
86
|
+
│ bar │ none │ negative values split at zero │
|
|
87
|
+
│ stem │ none │ back-to-back with two columns │
|
|
88
|
+
└──────┴─────────┴─────────────────────────────────────┘
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Styles
|
|
92
|
+
|
|
93
|
+
`light` (the default), `heavy`, `double`, `rounded`, the four
|
|
94
|
+
dashed ones (`dashed`, `dashed_fine`, `heavy_dashed`,
|
|
95
|
+
`heavy_dashed_fine`), the two mixed doubles `double_h` and
|
|
96
|
+
`double_v`, `ascii`, `minimal` (dividers only, no outer frame) and
|
|
97
|
+
`markdown` (a GitHub pipe table, ready to paste). The mixed doubles
|
|
98
|
+
bring their own tees, `╤ ╧ ╞ ╪ ╡` and `╥ ╨ ╟ ╫ ╢`:
|
|
99
|
+
|
|
100
|
+
```text
|
|
101
|
+
$ tessella table --style double_v langs.csv
|
|
102
|
+
╓──────╥─────╖
|
|
103
|
+
║ lang ║ loc ║
|
|
104
|
+
╟──────╫─────╢
|
|
105
|
+
║ ruby ║ 12 ║
|
|
106
|
+
║ c ║ 30 ║
|
|
107
|
+
║ rust ║ 7.5 ║
|
|
108
|
+
╙──────╨─────╜
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
```text
|
|
112
|
+
$ tessella table --style minimal kinds.csv
|
|
113
|
+
╷ ╷
|
|
114
|
+
kind │ canvas │ notes
|
|
115
|
+
╶──────┼─────────┼─────────────────────────────────────╴
|
|
116
|
+
line │ braille │ joins points, one series per column
|
|
117
|
+
bar │ none │ negative values split at zero
|
|
118
|
+
stem │ none │ back-to-back with two columns
|
|
119
|
+
╵ ╵
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
```text
|
|
123
|
+
$ tessella table --style markdown kinds.csv
|
|
124
|
+
| kind | canvas | notes |
|
|
125
|
+
|------|---------|-------------------------------------|
|
|
126
|
+
| line | braille | joins points, one series per column |
|
|
127
|
+
| bar | none | negative values split at zero |
|
|
128
|
+
| stem | none | back-to-back with two columns |
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
```text
|
|
132
|
+
$ tessella table --style ascii kinds.csv
|
|
133
|
+
+------+---------+-------------------------------------+
|
|
134
|
+
| kind | canvas | notes |
|
|
135
|
+
+------+---------+-------------------------------------+
|
|
136
|
+
| line | braille | joins points, one series per column |
|
|
137
|
+
| bar | none | negative values split at zero |
|
|
138
|
+
| stem | none | back-to-back with two columns |
|
|
139
|
+
+------+---------+-------------------------------------+
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Custom borders
|
|
143
|
+
|
|
144
|
+
`--style` (`style:`) also takes eleven glyphs: a frame's six (`tl tr
|
|
145
|
+
bl br h v`), then the five tees, in the order `top_t bot_t sep_l
|
|
146
|
+
sep_t sep_r` (the tee on the top edge, on the bottom edge, at the
|
|
147
|
+
left end of the header rule, its crossings, its right end):
|
|
148
|
+
|
|
149
|
+
```text
|
|
150
|
+
$ tessella table --style '╒╕╘╛═│╤╧╞╪╡' langs.csv
|
|
151
|
+
╒══════╤═════╕
|
|
152
|
+
│ lang │ loc │
|
|
153
|
+
╞══════╪═════╡
|
|
154
|
+
│ ruby │ 12 │
|
|
155
|
+
│ c │ 30 │
|
|
156
|
+
│ rust │ 7.5 │
|
|
157
|
+
╘══════╧═════╛
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Six glyphs are not enough for a table:
|
|
161
|
+
|
|
162
|
+
<!-- tessella: allow-mixed -->
|
|
163
|
+
```text
|
|
164
|
+
$ tessella table --style '╓╖╙╜─║' langs.csv 2>&1 | \
|
|
165
|
+
fold -s -w 72 | sed 's/ $//'
|
|
166
|
+
tessella table: a table's border set needs 11 glyphs, its tees after
|
|
167
|
+
the six of a frame: "╓╖╙╜─║"
|
|
168
|
+
Usage: tessella table [options] [file] (stdin if none)
|
|
169
|
+
--tsv --no-header --align SPEC --style NAME|GLYPHS --[no-]ascii
|
|
170
|
+
--title TEXT --max-width N
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### ASCII
|
|
174
|
+
|
|
175
|
+
Under `ascii: true` a named style draws as `ascii`, except `markdown`,
|
|
176
|
+
which is ASCII already and stays a pipe table; a custom set is drawn
|
|
177
|
+
if it is all ASCII and refused if not. A cell or title that is not
|
|
178
|
+
ASCII is refused. In a markdown table a `|` in a cell is written
|
|
179
|
+
`\|`, so it does not split the cell.
|
|
180
|
+
|
|
181
|
+
```text
|
|
182
|
+
$ tessella table --ascii --style '****=!+++++' langs.csv
|
|
183
|
+
*======+=====*
|
|
184
|
+
! lang ! loc !
|
|
185
|
+
+======+=====+
|
|
186
|
+
! ruby ! 12 !
|
|
187
|
+
! c ! 30 !
|
|
188
|
+
! rust ! 7.5 !
|
|
189
|
+
*======+=====*
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Multi-line cells, and fitting a width
|
|
193
|
+
|
|
194
|
+
These two are library options, not command-line flags.
|
|
195
|
+
|
|
196
|
+
A cell holding `"\n"` draws on several lines within its row,
|
|
197
|
+
top-aligned. `width: N` fits the framed table to N columns. It
|
|
198
|
+
narrows the widest column one column at a time, then whichever is
|
|
199
|
+
widest by then, so wide columns come down together and a narrow one
|
|
200
|
+
is never cut first. The narrowed columns' cells are word-wrapped, a
|
|
201
|
+
word too long for its column breaking inside.
|
|
202
|
+
|
|
203
|
+
```ruby
|
|
204
|
+
# frozen_string_literal: true
|
|
205
|
+
|
|
206
|
+
# examples/fit.rb: a table fitted to a width. At 40 columns only the
|
|
207
|
+
# widest column narrows and wraps; at 26 it has come down to the next
|
|
208
|
+
# widest, and both narrow together. A cell holding a newline keeps its
|
|
209
|
+
# own lines. Fitted to 20, the table is refused, naming the width it
|
|
210
|
+
# needs.
|
|
211
|
+
require "tessella"
|
|
212
|
+
|
|
213
|
+
rows = Tessella::Table.parse(File.read("kinds.csv"))
|
|
214
|
+
rows << ["box", "none", "quartiles\nand whiskers"]
|
|
215
|
+
[40, 26, 20].each do |width|
|
|
216
|
+
puts Tessella.table(rows, width: width)
|
|
217
|
+
rescue ArgumentError => e
|
|
218
|
+
puts e.message
|
|
219
|
+
end
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
```text
|
|
223
|
+
$ ruby fit.rb
|
|
224
|
+
┌──────┬─────────┬─────────────────────┐
|
|
225
|
+
│ kind │ canvas │ notes │
|
|
226
|
+
├──────┼─────────┼─────────────────────┤
|
|
227
|
+
│ line │ braille │ joins points, one │
|
|
228
|
+
│ │ │ series per column │
|
|
229
|
+
│ bar │ none │ negative values │
|
|
230
|
+
│ │ │ split at zero │
|
|
231
|
+
│ stem │ none │ back-to-back with │
|
|
232
|
+
│ │ │ two columns │
|
|
233
|
+
│ box │ none │ quartiles │
|
|
234
|
+
│ │ │ and whiskers │
|
|
235
|
+
└──────┴─────────┴─────────────────────┘
|
|
236
|
+
┌──────┬────────┬────────┐
|
|
237
|
+
│ kind │ canvas │ notes │
|
|
238
|
+
├──────┼────────┼────────┤
|
|
239
|
+
│ line │ braill │ joins │
|
|
240
|
+
│ │ e │ points │
|
|
241
|
+
│ │ │ , one │
|
|
242
|
+
│ │ │ series │
|
|
243
|
+
│ │ │ per │
|
|
244
|
+
│ │ │ column │
|
|
245
|
+
│ bar │ none │ negati │
|
|
246
|
+
│ │ │ ve │
|
|
247
|
+
│ │ │ values │
|
|
248
|
+
│ │ │ split │
|
|
249
|
+
│ │ │ at │
|
|
250
|
+
│ │ │ zero │
|
|
251
|
+
│ stem │ none │ back-t │
|
|
252
|
+
│ │ │ o-back │
|
|
253
|
+
│ │ │ with │
|
|
254
|
+
│ │ │ two │
|
|
255
|
+
│ │ │ column │
|
|
256
|
+
│ │ │ s │
|
|
257
|
+
│ box │ none │ quarti │
|
|
258
|
+
│ │ │ les │
|
|
259
|
+
│ │ │ and │
|
|
260
|
+
│ │ │ whiske │
|
|
261
|
+
│ │ │ rs │
|
|
262
|
+
└──────┴────────┴────────┘
|
|
263
|
+
table needs 22 columns at the least (min_width 4)
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
No column narrows below `min_width:` (4 by default), or below its
|
|
267
|
+
own width when that is smaller. When every column is at that floor
|
|
268
|
+
and the table is still too wide, it is refused rather than drawn
|
|
269
|
+
wider than asked, and the message names the width it needs: the last
|
|
270
|
+
line above.
|
|
271
|
+
|
|
272
|
+
`min_width:` may also be an Array, one floor per column: a column
|
|
273
|
+
that must stay readable (a name, an identifier) keeps its width, and
|
|
274
|
+
the narrowing falls on the others. Each column's floor is its own
|
|
275
|
+
value, or its natural width when smaller, and the table is refused
|
|
276
|
+
only when every column is at its floor. The command line has no flag
|
|
277
|
+
for it.
|
|
278
|
+
|
|
279
|
+
```ruby
|
|
280
|
+
# frozen_string_literal: true
|
|
281
|
+
|
|
282
|
+
# examples/floors.rb: one table fitted to 70 columns twice. With one
|
|
283
|
+
# floor for every column, the variable name is cut with the path; with
|
|
284
|
+
# a floor per column, the names keep their width and the path alone
|
|
285
|
+
# wraps. A list of the wrong length is refused.
|
|
286
|
+
require "tessella"
|
|
287
|
+
|
|
288
|
+
rows = [
|
|
289
|
+
%w[name variable path mode size age],
|
|
290
|
+
["tado-pairing", "CLIMAT_PAIRING",
|
|
291
|
+
"/home/user/.config/climat-control/tado-pairing.json", "0600",
|
|
292
|
+
"1.2 kB", "3 d"]
|
|
293
|
+
]
|
|
294
|
+
puts Tessella.table(rows, width: 70, min_width: 4)
|
|
295
|
+
puts Tessella.table(rows, width: 70, min_width: [12, 14, 12, 4, 6, 7])
|
|
296
|
+
begin
|
|
297
|
+
Tessella.table(rows, width: 70, min_width: [12, 14])
|
|
298
|
+
rescue ArgumentError => e
|
|
299
|
+
puts e.message
|
|
300
|
+
end
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
```text
|
|
304
|
+
$ ruby floors.rb
|
|
305
|
+
┌──────────────┬───────────────┬───────────────┬──────┬────────┬─────┐
|
|
306
|
+
│ name │ variable │ path │ mode │ size │ age │
|
|
307
|
+
├──────────────┼───────────────┼───────────────┼──────┼────────┼─────┤
|
|
308
|
+
│ tado-pairing │ CLIMAT_PAIRIN │ /home/user/.c │ 0600 │ 1.2 kB │ 3 d │
|
|
309
|
+
│ │ G │ onfig/climat- │ │ │ │
|
|
310
|
+
│ │ │ control/tado- │ │ │ │
|
|
311
|
+
│ │ │ pairing.json │ │ │ │
|
|
312
|
+
└──────────────┴───────────────┴───────────────┴──────┴────────┴─────┘
|
|
313
|
+
┌──────────────┬────────────────┬──────────────┬──────┬────────┬─────┐
|
|
314
|
+
│ name │ variable │ path │ mode │ size │ age │
|
|
315
|
+
├──────────────┼────────────────┼──────────────┼──────┼────────┼─────┤
|
|
316
|
+
│ tado-pairing │ CLIMAT_PAIRING │ /home/user/. │ 0600 │ 1.2 kB │ 3 d │
|
|
317
|
+
│ │ │ config/clima │ │ │ │
|
|
318
|
+
│ │ │ t-control/ta │ │ │ │
|
|
319
|
+
│ │ │ do-pairing.j │ │ │ │
|
|
320
|
+
│ │ │ son │ │ │ │
|
|
321
|
+
└──────────────┴────────────────┴──────────────┴──────┴────────┴─────┘
|
|
322
|
+
min_width must be a positive Integer or 6 of them: [12, 14]
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
The `markdown` style refuses multi-line cells and `width:`: a pipe
|
|
326
|
+
table cannot hold either.
|
|
327
|
+
|
|
328
|
+
## Colour
|
|
329
|
+
|
|
330
|
+
An ANSI colour code in a cell takes no column. It survives padding,
|
|
331
|
+
and wrapping too: it is closed at the end of each wrapped line and
|
|
332
|
+
reopened on the next, so every line stands on its own. `color: true`
|
|
333
|
+
sets the header in bold.
|
|
334
|
+
|
|
335
|
+
## Errors
|
|
336
|
+
|
|
337
|
+
Bad input is refused naming the line (exit 1): an unterminated
|
|
338
|
+
quoted field, a row with a different number of fields from the
|
|
339
|
+
first, a non-ASCII cell or title under `--ascii`, a table wider than
|
|
340
|
+
`--max-width`.
|
|
341
|
+
|
|
342
|
+
```text
|
|
343
|
+
$ printf 'a,b\nc\n' | tessella table
|
|
344
|
+
tessella table: line 2: expected 2 fields, got 1
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
An unknown style or a malformed `--align` is a usage error (exit 2).
|
data/doc/targets.md
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# Width targets
|
|
2
|
+
|
|
3
|
+
A figure lines up only if every glyph takes the columns tessella
|
|
4
|
+
counted, and that depends on where the figure is read. tessella can
|
|
5
|
+
draw for three targets:
|
|
6
|
+
|
|
7
|
+
| Target | Where | Widths |
|
|
8
|
+
|--------|------------------------------|-----------------------------------------------------------|
|
|
9
|
+
| `term` | a Western terminal (default) | ambiguous glyphs 1 column, wide ones (CJK, emoji) 2 |
|
|
10
|
+
| `cjk` | a CJK-configured terminal | ambiguous glyphs 2 columns: box drawing, blocks, `·`, `é` |
|
|
11
|
+
| `web` | a browser | as `term`, but some glyphs may turn into wide emoji |
|
|
12
|
+
|
|
13
|
+
([Width classes](glyphs.md#width-classes) explains ambiguous and
|
|
14
|
+
wide.)
|
|
15
|
+
|
|
16
|
+
```text
|
|
17
|
+
tessella --target term|cjk|web COMMAND ...
|
|
18
|
+
TESSELLA_TARGET=cjk tessella COMMAND ...
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
Tessella.target = :cjk # for the rest of the fiber
|
|
23
|
+
Tessella::Target.with(:web) { Tessella.table(rows).to_s }
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`--target` wins over `TESSELLA_TARGET`, and an empty variable means
|
|
27
|
+
`term`. An unknown target, or `--target` with no name, is a usage
|
|
28
|
+
error, exit 2, even before `--help`:
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
$ tessella --target vt100 table langs.csv
|
|
32
|
+
tessella: unknown target: "vt100" (term, cjk, web)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## term
|
|
36
|
+
|
|
37
|
+
The default. `cities.csv` holds a CJK label, which takes two columns
|
|
38
|
+
everywhere, and an accented one:
|
|
39
|
+
|
|
40
|
+
```text
|
|
41
|
+
$ tessella table cities.csv
|
|
42
|
+
┌────────┬─────┐
|
|
43
|
+
│ city │ pop │
|
|
44
|
+
├────────┼─────┤
|
|
45
|
+
│ 東京 │ 14 │
|
|
46
|
+
│ Paris │ 2 │
|
|
47
|
+
│ Zürich │ 0.4 │
|
|
48
|
+
└────────┴─────┘
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
An emoji sequence, joined by ZWJ (👨👩👧) or skin-toned (👍🏽), is
|
|
52
|
+
one glyph two columns wide in every target, as terminals draw it.
|
|
53
|
+
|
|
54
|
+
## cjk
|
|
55
|
+
|
|
56
|
+
In a CJK-configured terminal box drawing is two columns wide, so a
|
|
57
|
+
Unicode frame cannot line up there. Under `cjk` every module's
|
|
58
|
+
`ascii:` defaults to true: the structure is drawn in ASCII, while the
|
|
59
|
+
text stays as given and is measured with ambiguous glyphs as two
|
|
60
|
+
columns.
|
|
61
|
+
|
|
62
|
+
```text
|
|
63
|
+
$ tessella --target cjk table cities.csv
|
|
64
|
+
+---------+-----+
|
|
65
|
+
| city | pop |
|
|
66
|
+
+---------+-----+
|
|
67
|
+
| 東京 | 14 |
|
|
68
|
+
| Paris | 2 |
|
|
69
|
+
| Zürich | 0.4 |
|
|
70
|
+
+---------+-----+
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Read here, in a Western font, the `Zürich` row looks one column
|
|
74
|
+
short: `ü` is ambiguous, and the table gave it the two columns a CJK
|
|
75
|
+
terminal will. There, the row lines up.
|
|
76
|
+
|
|
77
|
+
`ascii: false` still asks for Unicode structure. A frame and a table
|
|
78
|
+
then round every inner width up to a whole number of border glyphs
|
|
79
|
+
and count their border runs in columns, so each line has one width in
|
|
80
|
+
a CJK terminal and the box closes there. Read in a Western terminal,
|
|
81
|
+
as on this page, the box looks a column too wide here and there.
|
|
82
|
+
Other figures (trees, waves, charts) drawn in Unicode under `cjk` do
|
|
83
|
+
not line up in a CJK terminal: leave them to the ASCII default.
|
|
84
|
+
|
|
85
|
+
On the command line, `--no-ascii` asks for Unicode under `cjk`;
|
|
86
|
+
without it, a Unicode `--style` is refused rather than quietly drawn
|
|
87
|
+
in ASCII:
|
|
88
|
+
|
|
89
|
+
```text
|
|
90
|
+
$ tessella --target cjk table --no-ascii --style light cities.csv
|
|
91
|
+
┌─────┬───┐
|
|
92
|
+
│ city │ pop │
|
|
93
|
+
├─────┼───┤
|
|
94
|
+
│ 東京 │ 14 │
|
|
95
|
+
│ Paris │ 2 │
|
|
96
|
+
│ Zürich │ 0.4 │
|
|
97
|
+
└─────┴───┘
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
```text
|
|
101
|
+
$ tessella --target cjk compose frame --style light note.txt 2>&1 | head -1
|
|
102
|
+
tessella compose: --style light needs --no-ascii under target cjk
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
From Ruby, `ascii: false`:
|
|
106
|
+
|
|
107
|
+
```ruby
|
|
108
|
+
# frozen_string_literal: true
|
|
109
|
+
|
|
110
|
+
# examples/cjk.rb: a table and a frame asked for in Unicode under the
|
|
111
|
+
# cjk target. Their inner widths are rounded up to whole border
|
|
112
|
+
# glyphs, so they close in a CJK terminal; read in a Western one, as
|
|
113
|
+
# here, the boxes look a column wide here and there.
|
|
114
|
+
require "tessella"
|
|
115
|
+
|
|
116
|
+
Tessella::Target.with(:cjk) do
|
|
117
|
+
rows = Tessella::Table.parse(File.read("cities.csv"))
|
|
118
|
+
puts Tessella.table(rows, ascii: false)
|
|
119
|
+
puts Tessella.frame(File.read("note.txt"), ascii: false)
|
|
120
|
+
end
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
```text
|
|
124
|
+
$ ruby cjk.rb
|
|
125
|
+
┌─────┬───┐
|
|
126
|
+
│ city │ pop │
|
|
127
|
+
├─────┼───┤
|
|
128
|
+
│ 東京 │ 14 │
|
|
129
|
+
│ Paris │ 2 │
|
|
130
|
+
│ Zürich │ 0.4 │
|
|
131
|
+
└─────┴───┘
|
|
132
|
+
┌─────────┐
|
|
133
|
+
│ ruby is 12 kLOC │
|
|
134
|
+
│ c is the bulk │
|
|
135
|
+
└─────────┘
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## web
|
|
139
|
+
|
|
140
|
+
A figure read in a browser (a README on GitHub, a page of docs) is at
|
|
141
|
+
risk where it draws an arrow head. The arrow glyphs fall in three
|
|
142
|
+
groups:
|
|
143
|
+
|
|
144
|
+
| Glyphs | On the web |
|
|
145
|
+
|--------------------|-------------------------------------------------------|
|
|
146
|
+
| `▸ ◂ ▴ ▾` | text glyphs most monospace fonts carry: they line up |
|
|
147
|
+
| `→ ← ↑ ↓` | text glyphs most monospace fonts carry: they line up |
|
|
148
|
+
| `► ◄` | not emoji-capable, bolder; some fonts lack them |
|
|
149
|
+
| `▶ ◀ ↔ ↕ ↗ ↘ ↙ ↖` | emoji-capable: may become a wide colour emoji |
|
|
150
|
+
|
|
151
|
+
So on the web, end a drawn line with a small head, `────▸`, or a
|
|
152
|
+
bolder `────►`, and keep `→ ← ↑ ↓` for arrows standing alone. `► ◄`
|
|
153
|
+
never turn into emoji, but a font without them still draws them at a
|
|
154
|
+
fallback width: they trade a little of `▸`'s safety for a head that
|
|
155
|
+
reads at a glance.
|
|
156
|
+
|
|
157
|
+
tessella's sequence diagrams use the bolder heads in every target,
|
|
158
|
+
terminal included: `►` and `◄` for calls and replies, `►►` and `◄◄`
|
|
159
|
+
for asynchronous messages ([sequence diagrams](seq.md#arrow-heads)).
|
|
160
|
+
`tessella glyphs head` lists the heads with the targets each suits
|
|
161
|
+
([glyph sets](glyphs.md#arrow-heads)).
|
|
162
|
+
|
|
163
|
+
When a figure does carry an emoji-capable glyph, `--target web` puts
|
|
164
|
+
U+FE0E, the text-presentation selector, right after it. The selector
|
|
165
|
+
is invisible; here it is made visible as `[FE0E]`:
|
|
166
|
+
|
|
167
|
+
```text
|
|
168
|
+
$ tessella --target web rule --width 24 --ornament '▶ next' | \
|
|
169
|
+
ruby -pe 'gsub("\u{FE0E}", "[FE0E]")'
|
|
170
|
+
──────── ▶[FE0E] next ────────
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
It stops the browser drawing the glyph as an emoji, and is the one
|
|
174
|
+
exception to tessella's rule of no invisible characters in its
|
|
175
|
+
output. It does not fix the glyph's width: a glyph the page's
|
|
176
|
+
monospace font lacks is drawn from a fallback font, at that font's
|
|
177
|
+
width. In Chrome on GitHub's code fonts `▶` takes two columns and
|
|
178
|
+
`↗` 1.4, with or without the selector. On a row that must line up,
|
|
179
|
+
the selector is not enough; `▸` is the arrow head that lines up.
|
|
180
|
+
|
|
181
|
+
The same holds beyond arrows. Measured in Chrome on GitHub's code
|
|
182
|
+
fonts, box drawing, quadrant and half blocks, eighths and shades are
|
|
183
|
+
exactly one column, but braille is about 1.2 and `◆` two. So under
|
|
184
|
+
`--target web` a chart defaults to quadrant blocks instead of braille
|
|
185
|
+
(`--canvas braille` still asks for it), the fill pattern that was `◆`
|
|
186
|
+
is `■`, and `tessella lint --target web` flags a row of braille.
|
|
187
|
+
|
|
188
|
+
`tessella lint --target web` (the same as `--web`) checks a figure
|
|
189
|
+
for this. It flags a bare emoji-capable glyph, and accepts one
|
|
190
|
+
followed by U+FE0E:
|
|
191
|
+
|
|
192
|
+
```text
|
|
193
|
+
$ printf 'A ──▶ B\n' | tessella lint --target web
|
|
194
|
+
stdin:1: emoji-capable U+25B6 ▶ (web target)
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
```text
|
|
198
|
+
$ printf 'A ──▸ B\n' | tessella lint --target web && echo clean
|
|
199
|
+
clean
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
To add the selector to a figure edited by hand, use a tool rather
|
|
203
|
+
than typing an invisible character:
|
|
204
|
+
|
|
205
|
+
```sh
|
|
206
|
+
ruby -pi -e 'gsub(/([▶◀])(?!\u{FE0E})/, "\\1\u{FE0E}")' FIGURE.md
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Checking for a target
|
|
210
|
+
|
|
211
|
+
`tessella lint --target T` measures a figure with the target's widths
|
|
212
|
+
([lint](lint.md#the-target)): under `cjk` it names an ambiguous glyph
|
|
213
|
+
on a row that must line up.
|
|
214
|
+
|
|
215
|
+
```text
|
|
216
|
+
$ tessella table cities.csv | tessella lint --target cjk
|
|
217
|
+
stdin:6: ambiguous U+00FC ü on aligned row (cjk target: 2 columns)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
`tessella glyphs --target T` lists the glyph sets safe for a target,
|
|
221
|
+
and `--check` says which targets a set of one's own suits
|
|
222
|
+
([glyph sets](glyphs.md)).
|
data/doc/text.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Text styles
|
|
2
|
+
|
|
3
|
+
`text` writes letters and digits in one of Unicode's styled
|
|
4
|
+
alphabets: 𝐛𝐨𝐥𝐝, 𝑖𝑡𝑎𝑙𝑖𝑐, 𝔣𝔯𝔞𝔨𝔱𝔲𝔯, ℍ𝕖𝕝𝕝𝕠, Sᴍᴀʟʟ ᴄᴀᴘꜱ, ⓒⓘⓡⓒⓛⓔⓓ and the
|
|
5
|
+
rest. They are characters, not formatting, so they survive where no
|
|
6
|
+
markup does: a terminal banner, a commit subject, a plain-text title.
|
|
7
|
+
|
|
8
|
+
```ruby
|
|
9
|
+
Tessella.text("Hello", style: :double_struck) # => "ℍ𝕖𝕝𝕝𝕠"
|
|
10
|
+
Tessella.text("ℍ𝕖𝕝𝕝𝕠", style: :plain) # => "Hello"
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
tessella text --style S [--strict] [TEXT ...]
|
|
15
|
+
tessella text --plain [TEXT ...]
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The command styles its arguments, joined by blanks, or stdin when
|
|
19
|
+
there are none:
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
$ tessella text --style bold Hello
|
|
23
|
+
𝐇𝐞𝐥𝐥𝐨
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
$ tessella text --style double-struck Hello 2024
|
|
28
|
+
ℍ𝕖𝕝𝕝𝕠 𝟚𝟘𝟚𝟜
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```text
|
|
32
|
+
$ echo hi | tessella text --style fullwidth
|
|
33
|
+
hi
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## The styles
|
|
37
|
+
|
|
38
|
+
`bold`, `italic`, `bold-italic`, `script`, `bold-script`, `fraktur`,
|
|
39
|
+
`bold-fraktur`, `double-struck`, `sans`, `sans-bold`, `sans-italic`,
|
|
40
|
+
`sans-bold-italic`, `monospace`, `small-caps`, `superscript`,
|
|
41
|
+
`subscript`, `circled`, `negative-circled`, `parenthesized`,
|
|
42
|
+
`squared`, `negative-squared` and `fullwidth`. In Ruby a style is a
|
|
43
|
+
Symbol with `_` for `-`: `:double_struck`.
|
|
44
|
+
|
|
45
|
+
The forms come from Unicode's own data; the gaps in the mathematical
|
|
46
|
+
alphabets are filled from Letterlike Symbols (`ℎ`, `ℬ`, `ℂ`, ...), as
|
|
47
|
+
Unicode intends. Capitals keep their case in small caps; `squared`
|
|
48
|
+
and the two negative styles have capitals only. `fullwidth` glyphs
|
|
49
|
+
are two columns wide.
|
|
50
|
+
|
|
51
|
+
`--plain` (`style: :plain`) turns any of them back into plain
|
|
52
|
+
letters and digits:
|
|
53
|
+
|
|
54
|
+
```text
|
|
55
|
+
$ tessella text --plain 'ℍ𝕖𝕝𝕝𝕠 𝟚𝟘𝟚𝟜'
|
|
56
|
+
Hello 2024
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Characters without a form
|
|
60
|
+
|
|
61
|
+
Not every style has every character: digits exist only in `bold`,
|
|
62
|
+
`double-struck`, `sans`, `sans-bold` and `monospace`; small caps have
|
|
63
|
+
no `x`; most letters have no subscript. Such a character passes
|
|
64
|
+
through unchanged, and the command names it on stderr:
|
|
65
|
+
|
|
66
|
+
```text
|
|
67
|
+
$ tessella text --style small-caps 'Hello x'
|
|
68
|
+
tessella text: no small-caps form for: x
|
|
69
|
+
Hᴇʟʟᴏ x
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`--strict` (`strict: true`) refuses the text instead: the command
|
|
73
|
+
prints nothing and exits 1.
|
|
74
|
+
|
|
75
|
+
```text
|
|
76
|
+
$ tessella text --style small-caps --strict 'Hello x'
|
|
77
|
+
tessella text: no small-caps form for: x
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## When not to
|
|
81
|
+
|
|
82
|
+
Every style is non-ASCII, so `--ascii` refuses them all. And they are
|
|
83
|
+
for a decorative line, not for prose: a screen reader spells them out
|
|
84
|
+
or skips them, and a search for "Hello" does not find "ℍ𝕖𝕝𝕝𝕠".
|