mirror of
https://github.com/johnkerl/miller.git
synced 2026-07-17 16:38:54 +00:00
Add --omd-aligned flag for column-padded markdown output (#2057)
Adds a new Markdown-only flag --omd-aligned (alias --omarkdown-aligned) that left-justifies cells and pads each column to a uniform width. The rendered table is unaffected; the goal is readability of the raw markdown source. The flag implies --omd, so users do not need to pass both. Implementation batches records by schema (like pprint), computes max column widths, then emits header / separator / data rows with bars vertically aligned. Default markdown writer behavior is unchanged.
This commit is contained in:
parent
1b69ee1588
commit
e0cf596853
10 changed files with 244 additions and 5 deletions
|
|
@ -551,6 +551,24 @@ which renders like this when dropped into various web tools (e.g. github.comment
|
|||
As of Miller 4.3.0, markdown format is supported only for output, not input; as of Miller 6.11.0, markdown format
|
||||
is supported for input as well.
|
||||
|
||||
By default, markdown cells are not padded -- which renders identically in a Markdown viewer
|
||||
but can be awkward to read or maintain as raw text. Use `--omd-aligned` to pad each column
|
||||
to a uniform width so the raw markdown source lines up. This flag implies `--omd`, so you
|
||||
do not need to pass `--omd` in addition:
|
||||
|
||||
<pre class="pre-highlight-in-pair">
|
||||
<b>mlr --omd-aligned cat data/small</b>
|
||||
</pre>
|
||||
<pre class="pre-non-highlight-in-pair">
|
||||
| a | b | i | x | y |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| pan | pan | 1 | 0.346791 | 0.726802 |
|
||||
| eks | pan | 2 | 0.758679 | 0.522151 |
|
||||
| wye | wye | 3 | 0.204603 | 0.338318 |
|
||||
| eks | wye | 4 | 0.381399 | 0.134188 |
|
||||
| wye | pan | 5 | 0.573288 | 0.863624 |
|
||||
</pre>
|
||||
|
||||
## XTAB: Vertical tabular
|
||||
|
||||
This is perhaps most useful for looking a very wide and/or multi-column data which causes line-wraps on the screen (but see also
|
||||
|
|
|
|||
|
|
@ -223,6 +223,15 @@ which renders like this when dropped into various web tools (e.g. github.comment
|
|||
As of Miller 4.3.0, markdown format is supported only for output, not input; as of Miller 6.11.0, markdown format
|
||||
is supported for input as well.
|
||||
|
||||
By default, markdown cells are not padded -- which renders identically in a Markdown viewer
|
||||
but can be awkward to read or maintain as raw text. Use `--omd-aligned` to pad each column
|
||||
to a uniform width so the raw markdown source lines up. This flag implies `--omd`, so you
|
||||
do not need to pass `--omd` in addition:
|
||||
|
||||
GENMD-RUN-COMMAND
|
||||
mlr --omd-aligned cat data/small
|
||||
GENMD-EOF
|
||||
|
||||
## XTAB: Vertical tabular
|
||||
|
||||
This is perhaps most useful for looking a very wide and/or multi-column data which causes line-wraps on the screen (but see also
|
||||
|
|
|
|||
|
|
@ -178,6 +178,7 @@ This is simply a copy of what you should see on running `man mlr` at a command p
|
|||
mlr help format-conversion-keystroke-saver-flags
|
||||
mlr help json-only-flags
|
||||
mlr help legacy-flags
|
||||
mlr help markdown-only-flags
|
||||
mlr help miscellaneous-flags
|
||||
mlr help output-colorization-flags
|
||||
mlr help pprint-only-flags
|
||||
|
|
@ -560,6 +561,16 @@ This is simply a copy of what you should see on running `man mlr` at a command p
|
|||
--vflatsep Ignored as of version 6. This functionality is
|
||||
subsumed into JSON formatting.
|
||||
|
||||
1mMARKDOWN-ONLY FLAGS0m
|
||||
These are flags which are applicable to markdown-tabular format.
|
||||
|
||||
--omd-aligned or --omarkdown-aligned
|
||||
For markdown-tabular output, left-justify cells and
|
||||
pad each column to a uniform width, making the raw
|
||||
markdown source easier to read and maintain. (The
|
||||
rendered table is unaffected.) Implies --omd, so you
|
||||
do not need to also pass --omd.
|
||||
|
||||
1mMISCELLANEOUS FLAGS0m
|
||||
These are flags which don't fit into any other category.
|
||||
--fflush Force buffered output to be written after every
|
||||
|
|
@ -3835,5 +3846,5 @@ This is simply a copy of what you should see on running `man mlr` at a command p
|
|||
MIME Type for Comma-Separated Values (CSV) Files, the Miller docsite
|
||||
https://miller.readthedocs.io
|
||||
|
||||
2026-04-19 4mMILLER24m(1)
|
||||
2026-05-16 4mMILLER24m(1)
|
||||
</pre>
|
||||
|
|
|
|||
|
|
@ -157,6 +157,7 @@
|
|||
mlr help format-conversion-keystroke-saver-flags
|
||||
mlr help json-only-flags
|
||||
mlr help legacy-flags
|
||||
mlr help markdown-only-flags
|
||||
mlr help miscellaneous-flags
|
||||
mlr help output-colorization-flags
|
||||
mlr help pprint-only-flags
|
||||
|
|
@ -539,6 +540,16 @@
|
|||
--vflatsep Ignored as of version 6. This functionality is
|
||||
subsumed into JSON formatting.
|
||||
|
||||
1mMARKDOWN-ONLY FLAGS0m
|
||||
These are flags which are applicable to markdown-tabular format.
|
||||
|
||||
--omd-aligned or --omarkdown-aligned
|
||||
For markdown-tabular output, left-justify cells and
|
||||
pad each column to a uniform width, making the raw
|
||||
markdown source easier to read and maintain. (The
|
||||
rendered table is unaffected.) Implies --omd, so you
|
||||
do not need to also pass --omd.
|
||||
|
||||
1mMISCELLANEOUS FLAGS0m
|
||||
These are flags which don't fit into any other category.
|
||||
--fflush Force buffered output to be written after every
|
||||
|
|
@ -3814,4 +3825,4 @@
|
|||
MIME Type for Comma-Separated Values (CSV) Files, the Miller docsite
|
||||
https://miller.readthedocs.io
|
||||
|
||||
2026-04-19 4mMILLER24m(1)
|
||||
2026-05-16 4mMILLER24m(1)
|
||||
|
|
|
|||
|
|
@ -282,6 +282,15 @@ They are accepted as no-op flags in order to keep old scripts from breaking.
|
|||
* `--quote-original`: Ignored as of version 6. Types are inferred/retained through the processing flow now.
|
||||
* `--vflatsep`: Ignored as of version 6. This functionality is subsumed into JSON formatting.
|
||||
|
||||
## Markdown-only flags
|
||||
|
||||
These are flags which are applicable to markdown-tabular format.
|
||||
|
||||
|
||||
**Flags:**
|
||||
|
||||
* `--omd-aligned or --omarkdown-aligned`: For markdown-tabular output, left-justify cells and pad each column to a uniform width, making the raw markdown source easier to read and maintain. (The rendered table is unaffected.) Implies --omd, so you do not need to also pass --omd.
|
||||
|
||||
## Miscellaneous flags
|
||||
|
||||
These are flags which don't fit into any other category.
|
||||
|
|
|
|||
|
|
@ -157,6 +157,7 @@
|
|||
mlr help format-conversion-keystroke-saver-flags
|
||||
mlr help json-only-flags
|
||||
mlr help legacy-flags
|
||||
mlr help markdown-only-flags
|
||||
mlr help miscellaneous-flags
|
||||
mlr help output-colorization-flags
|
||||
mlr help pprint-only-flags
|
||||
|
|
@ -539,6 +540,16 @@
|
|||
--vflatsep Ignored as of version 6. This functionality is
|
||||
subsumed into JSON formatting.
|
||||
|
||||
1mMARKDOWN-ONLY FLAGS0m
|
||||
These are flags which are applicable to markdown-tabular format.
|
||||
|
||||
--omd-aligned or --omarkdown-aligned
|
||||
For markdown-tabular output, left-justify cells and
|
||||
pad each column to a uniform width, making the raw
|
||||
markdown source easier to read and maintain. (The
|
||||
rendered table is unaffected.) Implies --omd, so you
|
||||
do not need to also pass --omd.
|
||||
|
||||
1mMISCELLANEOUS FLAGS0m
|
||||
These are flags which don't fit into any other category.
|
||||
--fflush Force buffered output to be written after every
|
||||
|
|
@ -3814,4 +3825,4 @@
|
|||
MIME Type for Comma-Separated Values (CSV) Files, the Miller docsite
|
||||
https://miller.readthedocs.io
|
||||
|
||||
2026-04-19 4mMILLER24m(1)
|
||||
2026-05-16 4mMILLER24m(1)
|
||||
|
|
|
|||
23
man/mlr.1
23
man/mlr.1
|
|
@ -2,12 +2,12 @@
|
|||
.\" Title: mlr
|
||||
.\" Author: [see the "AUTHOR" section]
|
||||
.\" Generator: ./mkman.rb
|
||||
.\" Date: 2026-04-19
|
||||
.\" Date: 2026-05-16
|
||||
.\" Manual: \ \&
|
||||
.\" Source: \ \&
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "MILLER" "1" "2026-04-19" "\ \&" "\ \&"
|
||||
.TH "MILLER" "1" "2026-05-16" "\ \&" "\ \&"
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * Portability definitions
|
||||
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
|
@ -194,6 +194,7 @@ Flags:
|
|||
mlr help format-conversion-keystroke-saver-flags
|
||||
mlr help json-only-flags
|
||||
mlr help legacy-flags
|
||||
mlr help markdown-only-flags
|
||||
mlr help miscellaneous-flags
|
||||
mlr help output-colorization-flags
|
||||
mlr help pprint-only-flags
|
||||
|
|
@ -662,6 +663,24 @@ They are accepted as no-op flags in order to keep old scripts from breaking.
|
|||
.fi
|
||||
.if n \{\
|
||||
.RE
|
||||
.SH "MARKDOWN-ONLY FLAGS"
|
||||
.sp
|
||||
|
||||
.if n \{\
|
||||
.RS 0
|
||||
.\}
|
||||
.nf
|
||||
These are flags which are applicable to markdown-tabular format.
|
||||
|
||||
--omd-aligned or --omarkdown-aligned
|
||||
For markdown-tabular output, left-justify cells and
|
||||
pad each column to a uniform width, making the raw
|
||||
markdown source easier to read and maintain. (The
|
||||
rendered table is unaffected.) Implies --omd, so you
|
||||
do not need to also pass --omd.
|
||||
.fi
|
||||
.if n \{\
|
||||
.RE
|
||||
.SH "MISCELLANEOUS FLAGS"
|
||||
.sp
|
||||
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ var FLAG_TABLE = FlagTable{
|
|||
&CSVTSVOnlyFlagSection,
|
||||
&JSONOnlyFlagSection,
|
||||
&PPRINTOnlyFlagSection,
|
||||
&MarkdownOnlyFlagSection,
|
||||
&DKVPOnlyFlagSection,
|
||||
&CompressedDataFlagSection,
|
||||
&CommentsInDataFlagSection,
|
||||
|
|
@ -582,6 +583,35 @@ var PPRINTOnlyFlagSection = FlagSection{
|
|||
},
|
||||
}
|
||||
|
||||
// MARKDOWN-ONLY FLAGS
|
||||
|
||||
func MarkdownOnlyPrintInfo() {
|
||||
fmt.Println("These are flags which are applicable to markdown-tabular format.")
|
||||
}
|
||||
|
||||
func init() { MarkdownOnlyFlagSection.Sort() }
|
||||
|
||||
var MarkdownOnlyFlagSection = FlagSection{
|
||||
name: "Markdown-only flags",
|
||||
infoPrinter: MarkdownOnlyPrintInfo,
|
||||
flags: []Flag{
|
||||
|
||||
{
|
||||
name: "--omd-aligned",
|
||||
altNames: []string{"--omarkdown-aligned"},
|
||||
help: "For markdown-tabular output, left-justify cells and pad each " +
|
||||
"column to a uniform width, making the raw markdown source easier " +
|
||||
"to read and maintain. (The rendered table is unaffected.) Implies " +
|
||||
"--omd, so you do not need to also pass --omd.",
|
||||
parser: func(args []string, argc int, pargi *int, options *TOptions) {
|
||||
options.WriterOptions.OutputFileFormat = "markdown"
|
||||
options.WriterOptions.MarkdownAlignedOutput = true
|
||||
*pargi += 1
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// DKVP-ONLY FLAGS
|
||||
|
||||
func DKVPOnlyPrintInfo() {
|
||||
|
|
|
|||
|
|
@ -101,6 +101,7 @@ type TWriterOptions struct {
|
|||
BarredUseUnicode bool
|
||||
RightAlignedPPRINTOutput bool
|
||||
RightAlignedXTABOutput bool
|
||||
MarkdownAlignedOutput bool
|
||||
|
||||
// JSON output: --jlistwrap on, --jvstack on
|
||||
// JSON Lines output: --jlistwrap off, --jvstack off
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package output
|
|||
import (
|
||||
"bufio"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/johnkerl/miller/v6/pkg/cli"
|
||||
"github.com/johnkerl/miller/v6/pkg/colorizer"
|
||||
|
|
@ -13,8 +14,14 @@ import (
|
|||
type RecordWriterMarkdown struct {
|
||||
writerOptions *cli.TWriterOptions
|
||||
|
||||
// Streaming-mode state (when MarkdownAlignedOutput is false)
|
||||
numHeaderLinesOutput int
|
||||
lastJoinedHeader string
|
||||
|
||||
// Aligned-mode state (when MarkdownAlignedOutput is true)
|
||||
batch []*mlrval.Mlrmap
|
||||
batchJoinedHeader *string
|
||||
numBatchesOutput int
|
||||
}
|
||||
|
||||
func NewRecordWriterMarkdown(writerOptions *cli.TWriterOptions) (*RecordWriterMarkdown, error) {
|
||||
|
|
@ -23,6 +30,10 @@ func NewRecordWriterMarkdown(writerOptions *cli.TWriterOptions) (*RecordWriterMa
|
|||
|
||||
numHeaderLinesOutput: 0,
|
||||
lastJoinedHeader: "",
|
||||
|
||||
batch: []*mlrval.Mlrmap{},
|
||||
batchJoinedHeader: nil,
|
||||
numBatchesOutput: 0,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -31,6 +42,17 @@ func (writer *RecordWriterMarkdown) Write(
|
|||
_ *types.Context,
|
||||
bufferedOutputStream *bufio.Writer,
|
||||
outputIsStdout bool,
|
||||
) error {
|
||||
if writer.writerOptions.MarkdownAlignedOutput {
|
||||
return writer.writeAligned(outrec, bufferedOutputStream, outputIsStdout)
|
||||
}
|
||||
return writer.writeStreaming(outrec, bufferedOutputStream, outputIsStdout)
|
||||
}
|
||||
|
||||
func (writer *RecordWriterMarkdown) writeStreaming(
|
||||
outrec *mlrval.Mlrmap,
|
||||
bufferedOutputStream *bufio.Writer,
|
||||
outputIsStdout bool,
|
||||
) error {
|
||||
if outrec == nil { // end of record stream
|
||||
return nil
|
||||
|
|
@ -76,3 +98,101 @@ func (writer *RecordWriterMarkdown) Write(
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeAligned accumulates records into a same-schema batch and flushes when
|
||||
// the schema changes or the stream ends. We need the whole batch in hand
|
||||
// before emitting any row, since column widths depend on every value.
|
||||
func (writer *RecordWriterMarkdown) writeAligned(
|
||||
outrec *mlrval.Mlrmap,
|
||||
bufferedOutputStream *bufio.Writer,
|
||||
outputIsStdout bool,
|
||||
) error {
|
||||
if outrec == nil {
|
||||
if len(writer.batch) > 0 {
|
||||
writer.flushBatch(bufferedOutputStream, outputIsStdout)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
joinedHeader := outrec.GetKeysJoined()
|
||||
if writer.batchJoinedHeader == nil {
|
||||
writer.batch = append(writer.batch, outrec)
|
||||
writer.batchJoinedHeader = &joinedHeader
|
||||
} else if *writer.batchJoinedHeader != joinedHeader {
|
||||
writer.flushBatch(bufferedOutputStream, outputIsStdout)
|
||||
writer.batch = []*mlrval.Mlrmap{outrec}
|
||||
writer.batchJoinedHeader = &joinedHeader
|
||||
} else {
|
||||
writer.batch = append(writer.batch, outrec)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (writer *RecordWriterMarkdown) flushBatch(
|
||||
bufferedOutputStream *bufio.Writer,
|
||||
outputIsStdout bool,
|
||||
) {
|
||||
if writer.numBatchesOutput > 0 {
|
||||
bufferedOutputStream.WriteString(writer.writerOptions.ORS)
|
||||
}
|
||||
|
||||
first := writer.batch[0]
|
||||
|
||||
// Floor of 3 so "---" never overflows the column.
|
||||
maxWidths := make(map[string]int)
|
||||
for pe := first.Head; pe != nil; pe = pe.Next {
|
||||
maxWidths[pe.Key] = max(utf8.RuneCountInString(pe.Key), 3)
|
||||
}
|
||||
for _, rec := range writer.batch {
|
||||
for pe := rec.Head; pe != nil; pe = pe.Next {
|
||||
value := strings.ReplaceAll(pe.Value.String(), "|", "\\|")
|
||||
width := utf8.RuneCountInString(value)
|
||||
if width > maxWidths[pe.Key] {
|
||||
maxWidths[pe.Key] = width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Header
|
||||
bufferedOutputStream.WriteString("|")
|
||||
for pe := first.Head; pe != nil; pe = pe.Next {
|
||||
bufferedOutputStream.WriteString(" ")
|
||||
bufferedOutputStream.WriteString(colorizer.MaybeColorizeKey(pe.Key, outputIsStdout))
|
||||
writePadding(bufferedOutputStream, maxWidths[pe.Key]-utf8.RuneCountInString(pe.Key))
|
||||
bufferedOutputStream.WriteString(" |")
|
||||
}
|
||||
bufferedOutputStream.WriteString(writer.writerOptions.ORS)
|
||||
|
||||
// Separator
|
||||
bufferedOutputStream.WriteString("|")
|
||||
for pe := first.Head; pe != nil; pe = pe.Next {
|
||||
bufferedOutputStream.WriteString(" ---")
|
||||
writePadding(bufferedOutputStream, maxWidths[pe.Key]-3)
|
||||
bufferedOutputStream.WriteString(" |")
|
||||
}
|
||||
bufferedOutputStream.WriteString(writer.writerOptions.ORS)
|
||||
|
||||
// Data
|
||||
for _, rec := range writer.batch {
|
||||
bufferedOutputStream.WriteString("|")
|
||||
for pe := rec.Head; pe != nil; pe = pe.Next {
|
||||
value := strings.ReplaceAll(pe.Value.String(), "|", "\\|")
|
||||
bufferedOutputStream.WriteString(" ")
|
||||
bufferedOutputStream.WriteString(colorizer.MaybeColorizeValue(value, outputIsStdout))
|
||||
writePadding(bufferedOutputStream, maxWidths[pe.Key]-utf8.RuneCountInString(value))
|
||||
bufferedOutputStream.WriteString(" |")
|
||||
}
|
||||
bufferedOutputStream.WriteString(writer.writerOptions.ORS)
|
||||
}
|
||||
|
||||
writer.batch = nil
|
||||
writer.batchJoinedHeader = nil
|
||||
writer.numBatchesOutput++
|
||||
}
|
||||
|
||||
func writePadding(bufferedOutputStream *bufio.Writer, n int) {
|
||||
if n <= 0 {
|
||||
return
|
||||
}
|
||||
bufferedOutputStream.WriteString(strings.Repeat(" ", n))
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue