mirror of
https://github.com/johnkerl/miller.git
synced 2026-07-17 16:38:54 +00:00
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>
68 lines
1.8 KiB
Go
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")
|
|
}
|
|
}
|
|
}
|