miller/pkg/output/record_writer_dkvpx.go
John Kerl 78363f1cc3
DKVPX reader: honor --ifs and --ips instead of hard-coding comma and equals (#2172)
The DKVPX record reader set dkvpxReader.Comma = ',' unconditionally,
ignoring the resolved --ifs value; the key-value separator '=' was
likewise hard-coded in the dkvpx parser, ignoring --ips. So e.g.

  printf 'a=1;b=2\n' | mlr -i dkvpx --ifs semicolon --ojson cat

produced a single field {"a": "1;b=2"} instead of {"a": 1, "b": 2}.

Fixes:

* pkg/dkvpx.Reader gains an Equals field (default '=') alongside Comma.
* The DKVPX record reader now passes the resolved IFS/IPS through,
  validating that each is a single character (mirroring the CSV
  reader's "IFS can only be a single character" error) rather than
  silently ignoring or truncating them.
* The DKVPX record writer already emitted OFS/OPS, but its quoting
  decisions were based on the default separators, so with --ofs
  semicolon a value containing ';' went out unquoted (ambiguous) while
  a value containing ',' was quoted needlessly. Quoting now keys off
  the actual OFS/OPS.

Adds unit tests, regression cases under test/cases/io-dkvpx/, and a
note in the separators reference table.

Discovered while investigating #369; tracked in #2170.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 17:37:07 -04:00

55 lines
1.4 KiB
Go

package output
import (
"bufio"
"github.com/johnkerl/miller/v6/pkg/cli"
"github.com/johnkerl/miller/v6/pkg/colorizer"
"github.com/johnkerl/miller/v6/pkg/dkvpx"
"github.com/johnkerl/miller/v6/pkg/mlrval"
"github.com/johnkerl/miller/v6/pkg/types"
)
type RecordWriterDKVPX struct {
writerOptions *cli.TWriterOptions
}
func NewRecordWriterDKVPX(writerOptions *cli.TWriterOptions) (*RecordWriterDKVPX, error) {
return &RecordWriterDKVPX{
writerOptions: writerOptions,
}, nil
}
func (writer *RecordWriterDKVPX) Write(
outrec *mlrval.Mlrmap,
_ *types.Context,
bufferedOutputStream *bufio.Writer,
outputIsStdout bool,
) error {
if outrec == nil {
return nil
}
if outrec.IsEmpty() {
bufferedOutputStream.WriteString(writer.writerOptions.ORS)
return nil
}
first := true
for pe := outrec.Head; pe != nil; pe = pe.Next {
if !first {
bufferedOutputStream.WriteString(writer.writerOptions.OFS)
}
first = false
keyStr := dkvpx.FormatFieldWithSeparators(pe.Key, writer.writerOptions.OFS, writer.writerOptions.OPS)
valStr := dkvpx.FormatFieldWithSeparators(pe.Value.String(), writer.writerOptions.OFS, writer.writerOptions.OPS)
bufferedOutputStream.WriteString(colorizer.MaybeColorizeKey(keyStr, outputIsStdout))
bufferedOutputStream.WriteString(writer.writerOptions.OPS)
bufferedOutputStream.WriteString(colorizer.MaybeColorizeValue(valStr, outputIsStdout))
}
bufferedOutputStream.WriteString(writer.writerOptions.ORS)
return nil
}