miller/pkg/output/record_writer_rec.go
John Kerl b7804e0791
Add GNU recutils (.rec) as a Miller I/O format (#2201)
Implements the plan in plans/recutils.md, addressing #378. Adds a
hand-rolled reader/writer pair (pkg/input/record_reader_rec.go,
pkg/output/record_writer_rec.go) supporting recutils' blank-line-separated
"Key: value" records, "+"-continuation and backslash-newline continuation,
and generic comment handling, with recutils' %rec-descriptor schema layer
left unspecialized since Miller has no schema-enforcement concept.

Registers the format under the "recutils" name (--irecutils/--orecutils/
--recutils flags, matching the --idcf/--odcf/--dcf pattern) in the
reader/writer factories and pkg/cli/separators.go, adds docs and a sample
data file, and adds unit tests plus test/cases/io-recutils regression
cases covering round-tripping, continuation lines, comments, and the
hard-error behavior on malformed input.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 11:51:16 -04:00

68 lines
1.8 KiB
Go

package output
import (
"bufio"
"strings"
"github.com/johnkerl/miller/v6/pkg/cli"
"github.com/johnkerl/miller/v6/pkg/colorizer"
"github.com/johnkerl/miller/v6/pkg/mlrval"
"github.com/johnkerl/miller/v6/pkg/types"
)
// RecordWriterREC writes GNU recutils (.rec) format: "FieldName: Value"
// lines, one record per blank-line-separated stanza. Unlike DCF, recutils
// has no hardcoded list-valued field names -- array/map values are handled
// by Miller's generic auto-flatten mechanism upstream of this writer.
type RecordWriterREC struct {
writerOptions *cli.TWriterOptions
}
func NewRecordWriterREC(writerOptions *cli.TWriterOptions) (*RecordWriterREC, error) {
return &RecordWriterREC{
writerOptions: writerOptions,
}, nil
}
func (writer *RecordWriterREC) Write(
outrec *mlrval.Mlrmap,
_ *types.Context,
bufferedOutputStream *bufio.Writer,
outputIsStdout bool,
) error {
if outrec == nil {
return nil
}
if outrec.IsEmpty() {
bufferedOutputStream.WriteString("\n")
return nil
}
for pe := outrec.Head; pe != nil; pe = pe.Next {
valueStr := pe.Value.String()
keyStr := colorizer.MaybeColorizeKey(pe.Key, outputIsStdout)
valueStr = colorizer.MaybeColorizeValue(valueStr, outputIsStdout)
writeRECField(bufferedOutputStream, keyStr, valueStr)
}
bufferedOutputStream.WriteString("\n")
return nil
}
// writeRECField writes one "Key: value" line, folding embedded newlines in
// the value into recutils continuation lines: each continuation line is
// prefixed with "+ ".
func writeRECField(b *bufio.Writer, key, value string) {
lines := strings.Split(value, "\n")
for i, line := range lines {
if i == 0 {
b.WriteString(key)
b.WriteString(": ")
b.WriteString(line)
b.WriteString("\n")
} else {
b.WriteString("+ ")
b.WriteString(line)
b.WriteString("\n")
}
}
}