OILS / doc / ul-table.md View on Github | oilshell.org

687 lines, 507 significant
1ul-table: Markdown Tables Without New Syntax
2================================
3
4`ul-table` is an HTML processor that lets you write **tables** as bulleted
5**lists**, in Markdown.
6
7<div id="toc">
8</div>
9
10## Simple Example
11
12To make this table:
13
14<style>
15table {
16 margin: 0 auto;
17}
18td {
19 padding-left: 1em;
20 padding-right: 1em;
21}
22</style>
23
24<table>
25
26- thead
27 - Shell
28 - Version
29- tr
30 - [bash](https://www.gnu.org/software/bash/)
31 - 5.2
32- tr
33 - [OSH](https://oils.pub/)
34 - 0.25.0
35
36</table>
37
38You write:
39
40<!-- TODO: Add pygments highlighting -->
41
42```
43<table>
44
45- thead
46 - Shell
47 - Version
48- tr
49 - [bash](https://www.gnu.org/software/bash/)
50 - 5.2
51- tr
52 - [OSH](https://oils.pub/)
53 - 0.25.0
54
55</table>
56```
57
58Any Markdown processor will produce this:
59
60- thead
61 - Shell
62 - Version
63- tr
64 - [bash](https://www.gnu.org/software/bash/)
65 - 5.2
66- tr
67 - [OSH](https://oils.pub/)
68 - 0.25.0
69
70And then **our** `ul-table` plugin transforms that into the table shown.
71
72So the conversion takes **2 steps**. The intermediate form is what sourcehut
73or Github will show, because they currently don't support `ul-table`.
74
75This is good, because it means that `ul-table` degrades gracefully! You can
76use it anywhere without worrying about breakage.
77
78## About `ul-table`
79
80### Why?
81
82Because it's tedious to read, write, and edit `<tr>` and `<td>` and `</td>` and
83`</tr>`. Aligning columns is also tedious in HTML.
84
85<!--
86This means your docs are still readable without it, e.g. on sourcehut or
87Github. It degrades gracefully.
88-->
89
90Design goals:
91
92- Don't invent any new syntax.
93 - Reuse your knowledge of Markdown
94 - Reuse your knowledge of HTML
95- Scale to large, complex tables.
96- Expose the **full** power of HTML
97
98### Structure
99
100You make tables with a **two-level Markdown list**, between `<table>` tags.
101The top level list contains either:
102
103<table>
104
105- tr
106 - `thead`
107 - zero or one, at the beginning
108- tr
109 - `tr`
110 - zero or more, after `thead`
111
112</table>
113
114The second level contains the contents of cells, but you **don't** write `td`
115or `<td>`.
116
117(This format looks similar to [tables in
118reStructuredText](https://sublime-and-sphinx-guide.readthedocs.io/en/latest/tables.html)).
119
120### Markdown &rarr; HTML &rarr; HTML Conversion
121
122As mentioned, it takes two steps to convert:
123
1241. Any Markdown translator will produce a
125 `<table> <ul> <li> ... </li> </ul> </table>` structure.
1261. **Our** `ul-table` plugin transforms that into a
127 `<table> <tr> <td> </td> </tr> </table>` structure, which is a normal HTML
128 table.
129
130So `ul-table` is an HTML processor, **not** a Markdown processor. But it's
131meant to be used with Markdown.
132
133## Details
134
135### Comparison: Tedious Inline HTML
136
137Here's the equivalent in CommonMark:
138
139 <table>
140 <thead>
141 <tr>
142 <td>Shell</td>
143 <td>Version</td>
144 </tr>
145 </thead>
146 <tr>
147 <td>
148
149 <!-- be careful not to indent this 4 spaces! -->
150 [bash](https://www.gnu.org/software/bash/)
151
152 </td>
153 <td>5.2</td>
154 </tr>
155 <tr>
156 <td>
157
158 [OSH](https://oils.pub/)
159
160 </td>
161 <td>0.25.0</td>
162 </tr>
163
164 </table>
165
166It uses the rule where you can embed Markdown inside HTML inside Markdown.
167With `ul-table`, you **don't** need this mutual nesting.
168
169The `ul-table` text is also shorter!
170
171---
172
173Trivia: with CommonMark, you get an extra `<p>` element:
174
175 <td>
176 <p>OSH</p>
177 </td>
178
179`ul-table` can produce simpler HTML:
180
181 <td>
182 OSH
183 </td>
184
185### Stylesheet
186
187To make the table look nice, I add a `<style>` tag, inside Markdown:
188
189 <style>
190 table {
191 margin: 0 auto;
192 }
193 td {
194 padding-left: 1em;
195 padding-right: 1em;
196 }
197 </style>
198
199## Adding HTML Attributes
200
201HTML attributes like `<tr class=foo>` and `<td id=bar>` let you format and
202style your table.
203
204You can add attributes to cells, columns, and rows.
205
206### Cells
207
208<style>
209.hi { background-color: thistle }
210</style>
211
212<table>
213
214- thead
215 - Name
216 - Age
217- tr
218 - Alice
219 - 42 <cell-attrs class=hi />
220- tr
221 - Bob
222 - 9
223
224</table>
225
226Add cell attributes with a `cell-attrs` tag after the cell contents:
227
228```
229- thead
230 - Name
231 - Age
232- tr
233 - Alice
234 - 42 <cell-attrs class=hi />
235- tr
236 - Bob
237 - 9
238```
239
240
241It's important that `cell-attrs` is a **self-closing** tag:
242
243 <cell-attrs /> # Yes
244 <cell-attrs> # No: this is an opening tag
245
246How does this work? `ul-table` takes the attributes from `<cell-attrs />`, and
247puts it on the generated `<td>`.
248
249### Columns
250
251<style>
252.num {
253 text-align: right;
254}
255</style>
256
257<table>
258
259- thead
260 - Name
261 - Age <cell-attrs class=num />
262- tr
263 - Alice
264 - 42
265- tr
266 - Bob
267 - 9
268
269</table>
270
271To add attributes to **every cell in a column**, put `<cell-attrs />` in the
272`thead` section:
273
274<style>
275.num {
276 background-color: bisque;
277 align: right;
278}
279</style>
280
281```
282- thead
283 - Name
284 - Age <cell-attrs class=num />
285- tr
286 - Alice
287 - 42 <!-- this cell gets class=num -->
288- tr
289 - Bob
290 - 9 <!-- this cells gets class=num -->
291```
292
293This is particularly useful for aligning numbers to the right:
294
295 <style>
296 .num {
297 align: right;
298 }
299 </style>
300
301If the same attribute appears in a column in both `thead` and `tr`, the values
302are **concatenated**, with a space. Example:
303
304 <td class="from-thead from-tr">
305
306### Rows
307
308<style>
309.special-row {
310 background-color: powderblue;
311}
312</style>
313
314<table>
315
316- thead
317 - Name
318 - Age
319- tr
320 - Alice
321 - 42
322- tr <row-attrs class="special-row "/>
323 - Bob
324 - 9
325
326</table>
327
328To add row attributes, put `<row-attrs />` after the `- tr`:
329
330 - thead
331 - Name
332 - Age
333 - tr
334 - Alice
335 - 42
336 - tr <row-attrs class="special-row" />
337 - Bob
338 - 9
339
340## Example: Markdown and HTML Inside Cells
341
342Here's an example that uses more features. Source code of this table:
343[doc/ul-table.md]($oils-src).
344
345[bash]: $xref
346
347<table id="foo">
348
349- thead
350 - Shell
351 - Version
352 - Example Code
353- tr
354 - [bash][]
355 - 5.2
356 - ```
357 echo sh=$bash
358 ls /tmp | wc -l
359 echo
360 ```
361- tr
362 - [dash]($xref)
363 - 1.5
364 - <em>Inline HTML</em>
365- tr
366 - [mksh]($xref)
367 - 4.0
368 - <table>
369 <tr>
370 <td>HTML table</td>
371 <td>inside</td>
372 </tr>
373 <tr>
374 <td>this table</td>
375 <td>no way to re-enter inline markdown though?</td>
376 </tr>
377 </table>
378- tr
379 - [zsh]($xref)
380 - 3.6
381 - Unordered List
382 - one
383 - two
384- tr
385 - [yash]($xref)
386 - 1.0
387 - Ordered List
388 1. one
389 1. two
390- tr
391 - [ksh]($xref)
392 - This is
393 paragraph one.
394
395 This is
396 paragraph two
397 - Another cell with ...
398
399 ... multiple paragraphs.
400
401</table>
402
403&nbsp;
404
405Another table:
406
407<style>
408.osh-code { color: darkred }
409.ysh-code { color: darkblue }
410</style>
411
412
413<table>
414
415- thead
416 - OSH
417 - YSH
418- tr
419 - ```
420 my-copy() {
421 cp --verbose "$@"
422 }
423 ```
424 <cell-attrs class=osh-code />
425 - ```
426 proc my-copy {
427 cp --verbose @ARGV
428 }
429 ```
430 <cell-attrs class=ysh-code />
431- tr
432 - x
433 - y
434
435</table>
436
437
438## Markdown Quirks to Be Aware Of
439
440Here are some quirks I ran into when creating ul-tables.
441
442(1) CommonMark doesn't allow empty list items:
443
444 - thead
445 -
446 - above is not rendered as a list item
447
448You can work around this by using a comment, or invisible character:
449
450 - tr
451 - <!-- empty -->
452 - above is OK
453 - tr
454 - &nbsp;
455 - also OK
456
457- [Related CommonMark thread](https://talk.commonmark.org/t/clarify-following-empty-list-items-in-0-31-2/4599)
458
459(2) Similarly, a cell with a literal hyphen may need a comment or space in
460front of it:
461
462 - tr
463 - <!-- hyphen --> -
464 - &nbsp; -
465
466## Comparisons
467
468### CommonMark Doesn't Have Tables
469
470Related discussions:
471
472- 2014: [Tables in pure Markdown](https://talk.commonmark.org/t/tables-in-pure-markdown/81)
473- 2022: [Obvious Markdown syntax for Tables](https://talk.commonmark.org/t/obvious-markdown-syntax-for-tables/4143/9)
474
475### Github Tables are Awkward
476
477Github-flavored Markdown has an non-standard extension for tables:
478
479- [Github: Organizing Information With Tables](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/organizing-information-with-tables)
480
481This style is hard to read and write, especially with large tables:
482
483```
484| Command | Description |
485| --- | --- |
486| git status | List all new or modified files |
487| git diff | Show file differences that haven't been staged |
488```
489
490Our style is less noisy, and more easily editable:
491
492```
493<table>
494
495- thead
496 - Command
497 - Description
498- tr
499 - git status
500 - List all new or modified files
501- tr
502 - git diff
503 - Show file differences that haven't been staged
504
505</table>
506```
507
508- Related wiki page: [Markdown Tables]($wiki)
509
510### MediaWiki Tables
511
512Here is a **long** page describing how to make tables on Wikipedia:
513
514- <https://en.wikipedia.org/wiki/Help:Table>
515
516I created the equivalent of the opening example:
517
518```
519{| class="wikitable"
520! Shell !! Version
521|-
522| [https://www.gnu.org/software/bash/ Bash] || 5.2
523|-
524| [https://www.oilshell.org/ OSH] || 0.25.0
525|}
526```
527
528In general, it has more "ASCII art", and invents a lot of new syntax.
529
530I prefer `ul-table` because it reuses Markdown and HTML syntax.
531
532## Conclusion
533
534`ul-table` is a nice way of writing and maintaining HTML tables. The appendix
535has links and details.
536
537### Related Docs
538
539- [How We Build Oils Documentation](doc-toolchain.html)
540- [Examples of HTML Plugins](doc-plugins.html)
541
542## Appendix: Implemention
543
544- [doctools/ul_table.py]($oils-src) - about 500 lines
545- [lazylex/html.py]($oils-src) - about 500 lines
546
547### Algorithm Notes
548
549- lazy lexing
550- recursive descent parser
551 - TODO: show grammar
552
553TODO: I would like someone to produce a **DOM**-based implementation!
554
555Our implementation is pretty low-level. It's meant to avoid the "big load
556anti-pattern" (allocating too much), so it's a necessarily more verbose.
557
558A DOM-based implementation should be much less than 1000 lines.
559
560## Appendix: Real Examples
561
562- [Guide to Procs and Funcs]($oils-doc:proc-func.html) has a big `ul-table`.
563 - Source: [doc/proc-func.md]($oils-src)
564
565I converted the tables in these September posts to `ul-table`:
566
567- [What Oils Looks Like in 2024](https://www.oilshell.org/blog/2024/09/project-overview.html)
568- [After 8 Years, Oils Is Still Small and Flexible](https://www.oilshell.org/blog/2024/09/line-counts.html)
569- [Garbage Collection Makes YSH Different](https://www.oilshell.org/blog/2024/09/gc.html)
570- [A Retrospective on the Oils Project](https://www.oilshell.org/blog/2024/09/retrospective.html)
571
572The markup was much shorter and simpler after conversion!
573
574TODO:
575
576- More tables to Make
577 - Interior/Exterior
578 - Narrow Waist
579- Wiki pages could use conversion
580 - [Alternative Shells]($wiki)
581 - [Alternative Regex Syntax]($wiki)
582 - [Survey of Config Languages]($wiki)
583 - [Polyglot Language Understanding]($wiki)
584 - [The Biggest Shell Programs in the World]($wiki)
585
586## HTML Quirks
587
588- `<th>` is like `<td>`, but it belongs in `<thead><tr>`. Browsers make it
589 bold and centered.
590- You can't put `class=` on `<colgroup>` and `<col>` and align columns left and
591 right.
592 - You have to put `class=` on *every* `<td>` cell instead.
593 - `ul-table` solves this with "inherited" `<cell-attrs />` in the `thead`
594 section.
595
596<!--
597
598### FAQ
599
600(1) Why do row with attributes look like `tr <row-attrs />`? The first `tr`
601doesn't seem neecssary.
602
603This is because of the CommonMark quirk above: a list item without **text** is
604treated as **empty**. So we require the extra `tr` text.
605
606It's also consistent with plain rows, without attributes.
607
608-->
609
610## Ideas for Features
611
612- Support `tfoot`?
613- Emit `tbody`?
614
615---
616
617We could help users edit well-formed tables with enforced column names:
618
619 - thead
620 - <cell-attrs ult-name=name /> Name
621 - <cell-attrs ult-name=age /> Age
622 - tr
623 - <cell-attrs ult-name=name /> Hi
624 - <cell-attrs ult-name=age /> 5
625
626This is a bit verbose, but may be worth it for large tables.
627
628Less verbose syntax idea:
629
630 - thead
631 - <ult col=NAME /> <cell-attrs class=foo /> Name
632 - <ult col=AGE /> Age
633 - tr
634 - <ult col=NAME /> Hi
635 - <ult col=AGE /> 5
636
637Even less verbose:
638
639 - thead
640 - {NAME} Name
641 - {AGE} Age
642 - tr
643 - {NAME} Hi
644 - {AGE} 5
645
646The obvious problem is that we might want the literal text `{NAME}` in the
647header. It's unlikely, but possible.
648
649
650<!--
651
652TODO: We should detect cell-attrs before the closing `</li>`, or in any
653position?
654
655<table>
656
657- thead
658 - OSH
659 - YSH
660- tr
661 - ```
662 my-copy() {
663 cp --verbose "$@"
664 }
665 ```
666 <cell-attrs class=osh-code />
667 - ```
668 proc my-copy {
669 cp --verbose @ARGV
670 }
671 ```
672 <cell-attrs class=ysh-code />
673
674</table>
675
676-->
677
678
679<!--
680TODO:
681
682- change back to oilshell.org/ for publishing
683- Compare to wikipedia
684 - https://en.wikipedia.org/wiki/Help:Table
685 - table caption - this is just <caption>
686 - rowspan
687-->