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/check.rb
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Keeps the documents in doc/ true to the code and to examples/.
|
|
5
|
+
#
|
|
6
|
+
# Two kinds of fence are checked, in every doc/*.md:
|
|
7
|
+
#
|
|
8
|
+
# - A render: a ```text fence that opens with "$ COMMAND". COMMAND
|
|
9
|
+
# may run over several lines, each but its last ending in a
|
|
10
|
+
# backslash, as sh reads it. The rest of the fence is what COMMAND
|
|
11
|
+
# prints, stdout and stderr merged. COMMAND runs under sh, in
|
|
12
|
+
# examples/, with this checkout's exe/ first on PATH and its lib/
|
|
13
|
+
# on RUBYLIB, so `tessella` and `require "tessella"` are the ones
|
|
14
|
+
# beside these documents.
|
|
15
|
+
# - A listing: a ```ruby or ```sh fence holding a line
|
|
16
|
+
# "# examples/NAME" in its first three. Its body is the file
|
|
17
|
+
# examples/NAME, whole.
|
|
18
|
+
#
|
|
19
|
+
# ruby doc/check.rb report each fence that differs
|
|
20
|
+
# ruby doc/check.rb --update rewrite the fences from what runs now
|
|
21
|
+
#
|
|
22
|
+
# Exit 0 when every fence agrees (or after --update), 1 otherwise.
|
|
23
|
+
|
|
24
|
+
require "open3"
|
|
25
|
+
|
|
26
|
+
ROOT = File.expand_path("..", __dir__)
|
|
27
|
+
EXAMPLES = File.join(ROOT, "examples")
|
|
28
|
+
RUN_ENV = {
|
|
29
|
+
"PATH" => "#{File.join(ROOT, "exe")}:#{ENV.fetch("PATH")}",
|
|
30
|
+
"RUBYLIB" => File.join(ROOT, "lib")
|
|
31
|
+
}.freeze
|
|
32
|
+
RENDER = /^```text\n\$ (?<key>(?:[^\n]*\\\n)*[^\n]*)\n(?<body>.*?)^```$/m
|
|
33
|
+
LISTING = %r{
|
|
34
|
+
^```(?<lang>ruby|sh)\n
|
|
35
|
+
(?<body>(?:(?!```)[^\n]*\n){0,2}?\#\ examples/(?<key>[\w.-]+)[^\n]*\n.*?)
|
|
36
|
+
^```$
|
|
37
|
+
}mx
|
|
38
|
+
|
|
39
|
+
def render(cmd)
|
|
40
|
+
Open3.capture2e(RUN_ENV, "sh", "-c", cmd, chdir: EXAMPLES).first
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def listing(name) = File.read(File.join(EXAMPLES, name))
|
|
44
|
+
|
|
45
|
+
def fence(kind, match, body)
|
|
46
|
+
return "```text\n$ #{match[:key]}\n#{body}```" if kind == :render
|
|
47
|
+
|
|
48
|
+
"```#{match[:lang]}\n#{body}```"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# The text with every fence rewritten from what it should hold; each
|
|
52
|
+
# fence that differs is noted in stale.
|
|
53
|
+
def refresh(text, doc, stale)
|
|
54
|
+
[[RENDER, :render], [LISTING, :listing]].reduce(text) do |out, (re, kind)|
|
|
55
|
+
out.gsub(re) do
|
|
56
|
+
m = Regexp.last_match
|
|
57
|
+
got = send(kind, m[:key])
|
|
58
|
+
stale << "#{doc}: #{m[:key]}" unless got == m[:body]
|
|
59
|
+
fence(kind, m, got)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
update = ARGV.delete("--update")
|
|
65
|
+
stale = []
|
|
66
|
+
Dir[File.join(__dir__, "*.md")].each do |path|
|
|
67
|
+
text = File.read(path)
|
|
68
|
+
out = refresh(text, File.basename(path), stale)
|
|
69
|
+
File.write(path, out) if update && out != text
|
|
70
|
+
end
|
|
71
|
+
exit 0 if update || stale.empty?
|
|
72
|
+
|
|
73
|
+
warn "out of date:"
|
|
74
|
+
stale.each { warn " #{it}" }
|
|
75
|
+
exit 1
|
data/doc/cli.md
ADDED
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
# The `tessella` command
|
|
2
|
+
|
|
3
|
+
```text
|
|
4
|
+
tessella [--target T] COMMAND [options] [file]
|
|
5
|
+
tessella --help
|
|
6
|
+
tessella --version
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
The first argument names the command. Each command reads one file,
|
|
10
|
+
or stdin when none is given (`compose` reads several), and prints one
|
|
11
|
+
figure on stdout. Each has its own `--help`. `--target term|cjk|web`,
|
|
12
|
+
before the command, or `TESSELLA_TARGET` in the environment, draws
|
|
13
|
+
for a [width target](targets.md). Every drawing command takes
|
|
14
|
+
`--ascii` and `--no-ascii`; the second matters only under `cjk`,
|
|
15
|
+
where ASCII is the default.
|
|
16
|
+
|
|
17
|
+
| Command | Draws | Page |
|
|
18
|
+
|------------|--------------------------------------------|--------------------------------|
|
|
19
|
+
| `chart` | line, scatter, stairs, density, bar, histogram, box, stem, heatmap, candlestick, pie | [charts](charts.md) |
|
|
20
|
+
| `compose` | frame, beside, stack, grid, indent, caption, shadow, fill | [composition](composition.md) |
|
|
21
|
+
| `tree` | an indented outline as a tree | [tree](tree.md) |
|
|
22
|
+
| `table` | CSV or TSV rows as a table | [table](table.md) |
|
|
23
|
+
| `bitfield` | an RFC-style packet or register layout | [bitfield](bitfield.md) |
|
|
24
|
+
| `wave` | a timing diagram | [wave](wave.md) |
|
|
25
|
+
| `seq` | a sequence diagram | [seq](seq.md) |
|
|
26
|
+
| `image` | a picture as text | [images](images.md) |
|
|
27
|
+
| `lint` | checks a text figure, drawn or hand-made | [lint](lint.md) |
|
|
28
|
+
| `sparkline` | a one-line chart, one cell a value | [sparkline](sparkline.md) |
|
|
29
|
+
| `meter` | a one-line progress bar or gauge | [meter](meter.md) |
|
|
30
|
+
| `rule` | a divider, with an optional ornament | [rule](rule.md) |
|
|
31
|
+
| `text` | letters in a Unicode style: bold, script… | [text](text.md) |
|
|
32
|
+
| `glyphs` | lists glyph sets and their width classes | [glyphs](glyphs.md) |
|
|
33
|
+
|
|
34
|
+
## Exit status
|
|
35
|
+
|
|
36
|
+
Every command keeps one contract:
|
|
37
|
+
|
|
38
|
+
| Status | Meaning |
|
|
39
|
+
|--------|---------------------------------------------------------------|
|
|
40
|
+
| 0 | the figure was drawn (or `--help`, `--version` answered) |
|
|
41
|
+
| 1 | bad input: a file that cannot be read, a malformed line, a value out of range, a figure wider than `--max-width` |
|
|
42
|
+
| 2 | a usage mistake: an unknown command or option, a bad option value, an option the command or kind does not take |
|
|
43
|
+
|
|
44
|
+
The message goes to stderr as `tessella COMMAND: message`, naming the
|
|
45
|
+
line, row or block it is about; a usage mistake adds the usage line.
|
|
46
|
+
|
|
47
|
+
A file that cannot be read is reported the same way by every
|
|
48
|
+
command, naming it (`stdin` when it was stdin):
|
|
49
|
+
|
|
50
|
+
```text
|
|
51
|
+
$ tessella table missing.csv
|
|
52
|
+
tessella table: cannot read missing.csv: No such file or directory
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```text
|
|
56
|
+
$ tessella chart --kind radar langs.csv
|
|
57
|
+
tessella chart: invalid argument: --kind radar
|
|
58
|
+
Usage: tessella chart [options] [file]
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
```text
|
|
62
|
+
$ tessella 2>&1 | head -2
|
|
63
|
+
tessella: no command
|
|
64
|
+
Usage: tessella [--target T] COMMAND [options] [file]
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## `--max-width N`
|
|
68
|
+
|
|
69
|
+
Every diagram command (`compose`, `tree`, `table`, `bitfield`,
|
|
70
|
+
`wave`, `seq`, `image`) takes `--max-width N`, and refuses with exit 1
|
|
71
|
+
a figure wider than N columns rather than print one that would wrap.
|
|
72
|
+
A build that writes figures into a document with an 80-column rule
|
|
73
|
+
can let tessella enforce it:
|
|
74
|
+
|
|
75
|
+
```text
|
|
76
|
+
$ tessella seq --max-width 30 login.txt
|
|
77
|
+
tessella seq: output is 41 columns, over --max-width 30
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## `tessella chart`
|
|
81
|
+
|
|
82
|
+
```text
|
|
83
|
+
tessella chart [--kind K] [--width N] [--height N] [--title S]
|
|
84
|
+
[--xlabel S] [--ylabel S] [--xlim A,B] [--ylim A,B]
|
|
85
|
+
[--canvas C] [--xscale F] [--yscale F] [--xdate [FMT]]
|
|
86
|
+
[--yerr] [--threshold N] [--above C] [--below C]
|
|
87
|
+
[--ascii] [--color]
|
|
88
|
+
[--style post|pre] [--stacked]
|
|
89
|
+
[--orientation horizontal|vertical]
|
|
90
|
+
[--area] [--gradient C,C,...] [--blocks B]
|
|
91
|
+
[--bins N] [--closed left|right]
|
|
92
|
+
[--scale N] [--fill G] [--no-eighths] [--track G|none]
|
|
93
|
+
[--cell-width N] [--colormap C,C,...] [--min A] [--max B]
|
|
94
|
+
[--donut] [--rim cells|smooth|basic]
|
|
95
|
+
[file]
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
| Option | Takes | Default |
|
|
99
|
+
|------------------------|----------------------------------------|-----------------|
|
|
100
|
+
| `--kind K` | `line scatter stairs density bar histogram box stem heatmap candlestick pie` | `line` |
|
|
101
|
+
| `--width N` | plot area width, cells | 40 |
|
|
102
|
+
| `--height N` | plot area height, cells | 15 |
|
|
103
|
+
| `--title S` | a title above the frame | |
|
|
104
|
+
| `--xlabel S` | a label under the x axis | |
|
|
105
|
+
| `--ylabel S` | a label left of the y axis | |
|
|
106
|
+
| `--xlim A,B` | x limits | from the data |
|
|
107
|
+
| `--ylim A,B` | y limits | from the data |
|
|
108
|
+
| `--canvas C` | `braille block quadrant half eighths dot ascii density` | `braille` (`block` under `--target web`) |
|
|
109
|
+
| `--xscale F` | `ln log2 log10` | |
|
|
110
|
+
| `--yscale F` | plotted kinds: `ln log2 log10` | |
|
|
111
|
+
| `--xdate [FMT]` | plotted kinds: x as ISO 8601 dates | format by span |
|
|
112
|
+
| `--yerr` | line, scatter: third column is the error | off |
|
|
113
|
+
| `--threshold N` | line, scatter, stairs: a dashed rule at N | none |
|
|
114
|
+
| `--above C`, `--below C` | with --color: colours beyond and short of N | red; series |
|
|
115
|
+
| `--ascii` | ASCII only | off |
|
|
116
|
+
| `--color` | ANSI colour in the plot area | off |
|
|
117
|
+
| `--style S` | stairs: `post pre` | `post` |
|
|
118
|
+
| `--orientation O` | all but stem: value axis direction | by kind |
|
|
119
|
+
| `--cell-width N` | heatmap: columns a cell | 2 |
|
|
120
|
+
| `--colormap C,...` | heatmap, with --color: colour stops | viridis-like |
|
|
121
|
+
| `--min A`, `--max B` | heatmap: fix the range | the data's |
|
|
122
|
+
| `--donut` | pie: a ring, not a disc | off |
|
|
123
|
+
| `--rim R` | pie: `cells smooth basic` edge | `cells` |
|
|
124
|
+
| `--stacked` | bar: series end to end, not grouped | grouped |
|
|
125
|
+
| `--blocks B` | bar, histogram: `eighths half block quadrant braille` | `eighths` |
|
|
126
|
+
| `--area` | line, stairs: fill under each series | off |
|
|
127
|
+
| `--gradient C,...` | area: colours by height, or `none` | green,yellow,red |
|
|
128
|
+
| `--bins N` | histogram: number of bins | Sturges' rule |
|
|
129
|
+
| `--closed C` | histogram: `left right` | `left` |
|
|
130
|
+
| `--scale N` | stem: value of one stem step | 10 |
|
|
131
|
+
| `--fill G` | bar, histogram: bar glyph | `█` (`#`) |
|
|
132
|
+
| `--no-eighths` | bar, histogram: whole cells only | |
|
|
133
|
+
| `--track G` | bar, histogram: track glyph, or `none` | `·` (`.`) |
|
|
134
|
+
|
|
135
|
+
`tessella chart --help` lists the kinds with the columns each reads,
|
|
136
|
+
and describes every option. The input format, column by kind, is in
|
|
137
|
+
[charts](charts.md#the-input-format).
|
|
138
|
+
|
|
139
|
+
## `tessella compose`
|
|
140
|
+
|
|
141
|
+
```text
|
|
142
|
+
tessella compose frame [--title T] [--style S] [--pad N] [--fill P]
|
|
143
|
+
[--halo H] BLOCK
|
|
144
|
+
tessella compose beside [--gap N] [--align top|middle|bottom] BLOCK...
|
|
145
|
+
tessella compose stack [--gap N] [--rule C] [--center] BLOCK...
|
|
146
|
+
tessella compose indent --prefix STR [--suffix STR] BLOCK
|
|
147
|
+
tessella compose caption --text T [--above] BLOCK
|
|
148
|
+
tessella compose shadow [--glyph G] BLOCK
|
|
149
|
+
tessella compose fill [--pattern P] [--halo H] BLOCK
|
|
150
|
+
tessella compose grid [--columns N] [--gap N] [--row-gap N] [--align A]
|
|
151
|
+
BLOCK...
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
All take `--ascii` and `--max-width N`. A BLOCK is a file, or `-` for
|
|
155
|
+
stdin, once. See [composition](composition.md).
|
|
156
|
+
|
|
157
|
+
## The diagram commands
|
|
158
|
+
|
|
159
|
+
```text
|
|
160
|
+
tessella tree [--style light|heavy|rounded] [--ascii] [--root NAME] [file]
|
|
161
|
+
tessella table [--tsv] [--no-header] [--style S] [--ascii]
|
|
162
|
+
[--align SPEC] [--title T] [file]
|
|
163
|
+
tessella bitfield [--word 8|16|32|64] [--octets] [--no-ruler] [--abbrev]
|
|
164
|
+
[file]
|
|
165
|
+
tessella wave [--period N] [--ascii] [--ruler] [file]
|
|
166
|
+
tessella seq [--ascii] [--numbered] [--footer] [file]
|
|
167
|
+
tessella image [--style S] [--width N] [--dither M] [--ramp STR]
|
|
168
|
+
[--gamma F] [--threshold F] [--edge-threshold F]
|
|
169
|
+
[--invert] [--ascii] [file]
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Each also takes `--max-width N`. A frame's `--style` also takes six
|
|
173
|
+
glyphs (`tl tr bl br h v`), a table's eleven (those six, then `top_t
|
|
174
|
+
bot_t sep_l sep_t sep_r`): see
|
|
175
|
+
[custom borders](composition.md#custom-borders). Table styles are `light heavy double
|
|
176
|
+
rounded dashed dashed_fine heavy_dashed heavy_dashed_fine double_h
|
|
177
|
+
double_v ascii minimal markdown`; image styles `ramp blocks half
|
|
178
|
+
quadrant sextant braille octant edges`, dithers `fs atkinson bayer none`.
|
|
179
|
+
|
|
180
|
+
## `tessella lint`
|
|
181
|
+
|
|
182
|
+
```text
|
|
183
|
+
tessella lint [--width N] [--prefix STR] [--suffix STR] [--ruler]
|
|
184
|
+
[--forbid SEQ]...
|
|
185
|
+
[--web] [--no-wide] [--ascii] [--allow-mixed]
|
|
186
|
+
[--fences] [--geometry] [--border GLYPHS]... [file ...]
|
|
187
|
+
tessella lint --alphabet [file ...]
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Unlike the drawing commands, `lint` takes several files and prints
|
|
191
|
+
its findings on stderr, as `FILE:LINE: message`; `--ruler` and
|
|
192
|
+
`--alphabet` print on stdout. It exits 1 on any finding. See
|
|
193
|
+
[lint](lint.md).
|
|
194
|
+
|
|
195
|
+
## Pipelines
|
|
196
|
+
|
|
197
|
+
Every command reads stdin and writes stdout, so they chain; `compose`
|
|
198
|
+
reads `-` as stdin:
|
|
199
|
+
|
|
200
|
+
```text
|
|
201
|
+
$ tessella chart --kind bar --width 20 langs.csv | \
|
|
202
|
+
tessella compose caption --text 'Lines of code' -
|
|
203
|
+
┌ ┐
|
|
204
|
+
ruby ┤██████▍········· 12
|
|
205
|
+
c ┤████████████████ 30
|
|
206
|
+
rust ┤████············ 7.5
|
|
207
|
+
└ ┘
|
|
208
|
+
|
|
209
|
+
Lines of code
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Scripts
|
|
213
|
+
|
|
214
|
+
The commands are built to be scripted: each reads a file or stdin,
|
|
215
|
+
writes the figure on stdout and nothing else, and says what went wrong
|
|
216
|
+
by its exit status. The scripts below are in
|
|
217
|
+
[examples/](../examples/), POSIX sh for FreeBSD and GNU/Linux alike,
|
|
218
|
+
and each runs from any directory.
|
|
219
|
+
|
|
220
|
+
### Data from another program
|
|
221
|
+
|
|
222
|
+
Anything that prints columns can feed a chart; a header row names
|
|
223
|
+
the series.
|
|
224
|
+
|
|
225
|
+
```sh
|
|
226
|
+
#!/bin/sh
|
|
227
|
+
# examples/trig.sh -- data made on the spot by awk, piped straight
|
|
228
|
+
# into a chart: a header row names the two series.
|
|
229
|
+
# usage: sh trig.sh
|
|
230
|
+
set -eu
|
|
231
|
+
|
|
232
|
+
awk 'BEGIN {
|
|
233
|
+
print "x sin cos"
|
|
234
|
+
for (i = 0; i <= 100; i++)
|
|
235
|
+
printf "%g %g %g\n", i / 10, sin(i / 10), cos(i / 10)
|
|
236
|
+
}' |
|
|
237
|
+
tessella chart --title trig --xlabel x --width 30 --height 6 \
|
|
238
|
+
--canvas block
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
```text
|
|
242
|
+
$ sh trig.sh
|
|
243
|
+
trig
|
|
244
|
+
┌──────────────────────────────┐
|
|
245
|
+
1 │▀▜▄▀▀▀▚▖ ▗▀▀▀▚▟▀▀▀▚ │ sin
|
|
246
|
+
│ ▟▀▙ ▝▖ ▟▘ ▞▀▄ ▀▖ │ cos
|
|
247
|
+
│▞ ▝▚ ▝▖ ▞▘ ▗▞ ▝▖ ▜▖ │
|
|
248
|
+
│▀▀▀▀▀█▀▀▀▀▛▀▀█▀▀▀▀▛▀▀▀▀▀▛▀▀▀▜▛│
|
|
249
|
+
│ ▙ ▝▖▞ ▗▛ ▝▄ ▀│
|
|
250
|
+
-1 │ ▜▄▄▄▛▚▄▄▟▀ ▝▚▄▄▄│
|
|
251
|
+
└──────────────────────────────┘
|
|
252
|
+
0 10
|
|
253
|
+
x
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Several figures, one result
|
|
257
|
+
|
|
258
|
+
`compose` reads only one block from stdin, so the other blocks go
|
|
259
|
+
through files; a temp directory, removed on exit, keeps them.
|
|
260
|
+
|
|
261
|
+
```sh
|
|
262
|
+
#!/bin/sh
|
|
263
|
+
# examples/report.sh -- a chart and a table drawn from one CSV, set
|
|
264
|
+
# side by side, framed and captioned: compose reads one block from
|
|
265
|
+
# stdin, so the others go through a temp directory.
|
|
266
|
+
# usage: sh report.sh [file.csv] (default langs.csv)
|
|
267
|
+
set -eu
|
|
268
|
+
|
|
269
|
+
cd -- "$(dirname -- "$0")" || exit 1
|
|
270
|
+
csv=${1:-langs.csv}
|
|
271
|
+
|
|
272
|
+
tmpdir=
|
|
273
|
+
cleanup() { [ -z "$tmpdir" ] || rm -rf -- "$tmpdir"; }
|
|
274
|
+
trap cleanup EXIT INT HUP TERM
|
|
275
|
+
tmpdir=$(mktemp -d "${TMPDIR:-/tmp}/report.XXXXXX") || exit 1
|
|
276
|
+
|
|
277
|
+
tessella chart --kind bar --width 20 -- "$csv" > "$tmpdir/chart"
|
|
278
|
+
tessella table --style rounded -- "$csv" > "$tmpdir/table"
|
|
279
|
+
tessella compose beside --gap 2 --align middle \
|
|
280
|
+
"$tmpdir/chart" "$tmpdir/table" |
|
|
281
|
+
tessella compose frame --title Load - |
|
|
282
|
+
tessella compose caption --text "Source: $csv" -
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
```text
|
|
286
|
+
$ sh report.sh
|
|
287
|
+
┌───────────────────── Load ─────────────────────┐
|
|
288
|
+
│ ╭──────┬─────╮ │
|
|
289
|
+
│ ┌ ┐ │ lang │ loc │ │
|
|
290
|
+
│ ruby ┤██████▍········· 12 ├──────┼─────┤ │
|
|
291
|
+
│ c ┤████████████████ 30 │ ruby │ 12 │ │
|
|
292
|
+
│ rust ┤████············ 7.5 │ c │ 30 │ │
|
|
293
|
+
│ └ ┘ │ rust │ 7.5 │ │
|
|
294
|
+
│ ╰──────┴─────╯ │
|
|
295
|
+
└────────────────────────────────────────────────┘
|
|
296
|
+
|
|
297
|
+
Source: langs.csv
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
The same in a loop: one picture in four styles.
|
|
301
|
+
|
|
302
|
+
```sh
|
|
303
|
+
#!/bin/sh
|
|
304
|
+
# examples/gallery.sh -- one picture in several styles, each captioned
|
|
305
|
+
# with its name, then set side by side.
|
|
306
|
+
# usage: sh gallery.sh [image] (default disc.pgm)
|
|
307
|
+
set -eu
|
|
308
|
+
|
|
309
|
+
cd -- "$(dirname -- "$0")" || exit 1
|
|
310
|
+
image=${1:-disc.pgm}
|
|
311
|
+
|
|
312
|
+
tmpdir=
|
|
313
|
+
cleanup() { [ -z "$tmpdir" ] || rm -rf -- "$tmpdir"; }
|
|
314
|
+
trap cleanup EXIT INT HUP TERM
|
|
315
|
+
tmpdir=$(mktemp -d "${TMPDIR:-/tmp}/gallery.XXXXXX") || exit 1
|
|
316
|
+
|
|
317
|
+
for style in ramp half quadrant edges; do
|
|
318
|
+
tessella image --style "$style" --width 14 -- "$image" |
|
|
319
|
+
tessella compose caption --text "$style" - > "$tmpdir/$style"
|
|
320
|
+
done
|
|
321
|
+
tessella compose beside --gap 3 --align bottom \
|
|
322
|
+
"$tmpdir/ramp" "$tmpdir/half" "$tmpdir/quadrant" "$tmpdir/edges"
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
<!-- tessella: allow-mixed -->
|
|
326
|
+
```text
|
|
327
|
+
$ sh gallery.sh
|
|
328
|
+
.. ────
|
|
329
|
+
.--===-:. ▄ █ ▀▄ ▖▞▞▞▖▚ ╱──────╲╲
|
|
330
|
+
:=+*##*+=-. █ █▀█▀▄▀▄ ▗▚▚▛▟▙▛▌▌▚ ╱╱╱╱───╲╲╲╲
|
|
331
|
+
.-+*#%%#*+-: ▄▀█▀███▀▄▄ ▘▌▛▟█▙█▜▐▗ │││ ││
|
|
332
|
+
.:=+*##*+=-. ▀▄▀█▀█▀▄ ▘▚▀▛▟▜▞▌▌▚ ╲╲╲╲──╱╱╱╱╱
|
|
333
|
+
:-====-:. ▀ ▀▄▀ ▀ ▘▘▚▘▌▘▚▝ ╲╲──────╱╱
|
|
334
|
+
.... ▀ ▘ ──────
|
|
335
|
+
|
|
336
|
+
ramp half quadrant edges
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### Figures for source files and plain-text documents
|
|
340
|
+
|
|
341
|
+
A tree as a C comment, refused rather than wrapped if it outgrows the
|
|
342
|
+
column budget:
|
|
343
|
+
|
|
344
|
+
```sh
|
|
345
|
+
#!/bin/sh
|
|
346
|
+
# examples/comment.sh -- a tree as a C block comment, for a header
|
|
347
|
+
# kept to ASCII and 72 columns: --max-width fails the build (exit 1)
|
|
348
|
+
# rather than let the figure wrap.
|
|
349
|
+
# usage: sh comment.sh
|
|
350
|
+
set -eu
|
|
351
|
+
|
|
352
|
+
cd -- "$(dirname -- "$0")" || exit 1
|
|
353
|
+
|
|
354
|
+
printf '/*\n'
|
|
355
|
+
tessella tree --ascii --max-width 69 outline.txt |
|
|
356
|
+
tessella compose indent --ascii --prefix ' * ' -
|
|
357
|
+
printf ' */\n'
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
```text
|
|
361
|
+
$ sh comment.sh
|
|
362
|
+
/*
|
|
363
|
+
* tessella
|
|
364
|
+
* |-- lib # the library
|
|
365
|
+
* | |-- tessella.rb # the entry
|
|
366
|
+
* | `-- tessella
|
|
367
|
+
* | `-- cli
|
|
368
|
+
* |-- exe
|
|
369
|
+
* | `-- tessella # the command
|
|
370
|
+
* `-- test
|
|
371
|
+
*/
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
An RFC-style figure: everything ASCII, stacked, captioned, indented
|
|
375
|
+
three columns, and held to 72 columns.
|
|
376
|
+
|
|
377
|
+
```sh
|
|
378
|
+
#!/bin/sh
|
|
379
|
+
# examples/rfc.sh -- an RFC-style section: a message exchange and the
|
|
380
|
+
# header it carries, both ASCII, stacked under a rule and indented
|
|
381
|
+
# three columns, as an RFC sets its figures.
|
|
382
|
+
# usage: sh rfc.sh
|
|
383
|
+
set -eu
|
|
384
|
+
|
|
385
|
+
cd -- "$(dirname -- "$0")" || exit 1
|
|
386
|
+
|
|
387
|
+
tmpdir=
|
|
388
|
+
cleanup() { [ -z "$tmpdir" ] || rm -rf -- "$tmpdir"; }
|
|
389
|
+
trap cleanup EXIT INT HUP TERM
|
|
390
|
+
tmpdir=$(mktemp -d "${TMPDIR:-/tmp}/rfc.XXXXXX") || exit 1
|
|
391
|
+
|
|
392
|
+
printf 'Client -> Server: Echo Request\nServer --> Client: Echo Reply\n' |
|
|
393
|
+
tessella seq --ascii > "$tmpdir/seq"
|
|
394
|
+
tessella bitfield icmp.txt |
|
|
395
|
+
tessella compose caption --ascii --text 'Figure 1: ICMP header' - \
|
|
396
|
+
> "$tmpdir/header"
|
|
397
|
+
tessella compose stack --ascii --gap 1 --rule '-' --center \
|
|
398
|
+
"$tmpdir/seq" "$tmpdir/header" |
|
|
399
|
+
tessella compose indent --ascii --prefix ' ' --max-width 72 -
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
```text
|
|
403
|
+
$ sh rfc.sh
|
|
404
|
+
+---------+ +---------+
|
|
405
|
+
| Client | | Server |
|
|
406
|
+
+----+----+ +----+----+
|
|
407
|
+
| |
|
|
408
|
+
| Echo Request |
|
|
409
|
+
|------------->|
|
|
410
|
+
| |
|
|
411
|
+
| Echo Reply |
|
|
412
|
+
|<- - - - - - -|
|
|
413
|
+
| |
|
|
414
|
+
|
|
415
|
+
-----------------------------------------------------------------
|
|
416
|
+
|
|
417
|
+
0 1 2 3
|
|
418
|
+
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
|
419
|
+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
420
|
+
| Type | Code | Checksum |
|
|
421
|
+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
422
|
+
|
|
423
|
+
Figure 1: ICMP header
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### Exit status in a script
|
|
427
|
+
|
|
428
|
+
Exit 1 is the data's fault, exit 2 the caller's. A script that draws
|
|
429
|
+
many inputs can skip a bad file and still stop on a bad call:
|
|
430
|
+
|
|
431
|
+
```sh
|
|
432
|
+
#!/bin/sh
|
|
433
|
+
# examples/status.sh -- telling bad data (exit 1) from a bad call
|
|
434
|
+
# (exit 2) in a script: draw what can be drawn, report the rest.
|
|
435
|
+
# usage: sh status.sh
|
|
436
|
+
set -eu
|
|
437
|
+
|
|
438
|
+
cd -- "$(dirname -- "$0")" || exit 1
|
|
439
|
+
|
|
440
|
+
draw() {
|
|
441
|
+
# shellcheck disable=SC3043 # local: every target sh has it
|
|
442
|
+
local status
|
|
443
|
+
status=0
|
|
444
|
+
tessella chart --kind bar --width 20 "$@" 2>/dev/null || status=$?
|
|
445
|
+
case $status in
|
|
446
|
+
0) ;;
|
|
447
|
+
1) printf '%s: bad data, skipped\n' "$*" ;;
|
|
448
|
+
*) printf '%s: bad call (exit %s)\n' "$*" "$status" ;;
|
|
449
|
+
esac
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
draw langs.csv
|
|
453
|
+
draw bad.csv
|
|
454
|
+
draw --bins 3 langs.csv
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
```text
|
|
458
|
+
$ sh status.sh
|
|
459
|
+
┌ ┐
|
|
460
|
+
ruby ┤██████▍········· 12
|
|
461
|
+
c ┤████████████████ 30
|
|
462
|
+
rust ┤████············ 7.5
|
|
463
|
+
└ ┘
|
|
464
|
+
bad.csv: bad data, skipped
|
|
465
|
+
--bins 3 langs.csv: bad call (exit 2)
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
## From Ruby
|
|
469
|
+
|
|
470
|
+
The command line is a library too, loaded apart from the drawing
|
|
471
|
+
library:
|
|
472
|
+
|
|
473
|
+
```ruby
|
|
474
|
+
require "tessella/cli" # loads tessella, optparse, the commands
|
|
475
|
+
|
|
476
|
+
status = Tessella::CLI.run(%w[chart --kind bar langs.csv],
|
|
477
|
+
stdin: $stdin, stdout: $stdout,
|
|
478
|
+
stderr: $stderr)
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
`CLI.run` returns the exit status instead of exiting, and writes
|
|
482
|
+
only to the streams it is given, so a test can pass `StringIO`s.
|
|
483
|
+
`require "tessella"` alone loads neither the commands nor `optparse`.
|