Skip to content

Commit

Permalink
Option to omit CSV or TSV header line
Browse files Browse the repository at this point in the history
--csv-omit-header and --tsv-omit-header will only print records,
skipping the first line with field names.  This is useful when output
will be passed to another command like `uniq` without needing to pipe
through `tail +2` first.

When using these options, `adifmt select -fields` is a good idea to
ensure fields match expectations.
  • Loading branch information
flwyd committed Aug 21, 2024
1 parent 5d907f5 commit 2a5c8e8
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 9 deletions.
7 changes: 5 additions & 2 deletions adif/csv.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type CSVIO struct {
LazyQuotes bool
RequireFullRecord bool
TrimLeadingSpace bool
OmitHeader bool
}

func NewCSVIO() *CSVIO {
Expand Down Expand Up @@ -114,8 +115,10 @@ func (o *CSVIO) Write(l *Logfile, out io.Writer) error {
c.Comma = o.Comma
c.UseCRLF = o.CRLF
// CSV header row
if err := c.Write(order); err != nil {
return fmt.Errorf("writing CSV header to %s: %w", l, err)
if !o.OmitHeader {
if err := c.Write(order); err != nil {
return fmt.Errorf("writing CSV header to %s: %w", l, err)
}
}
row := make([]string, len(order))
for i, r := range l.Records {
Expand Down
35 changes: 35 additions & 0 deletions adif/csv_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,3 +165,38 @@ Switzerland",",comma notes,,,"
}
}
}

func TestCSVOmitHeader(t *testing.T) {
l := NewLogfile()
l.Comment = "CSV ignores comments"
l.AddRecord(NewRecord(
Field{Name: "QSO_DATE", Value: "19901031", Type: TypeDate},
Field{Name: "TIME_ON", Value: "1234", Type: TypeTime},
Field{Name: "BAND", Value: "40M"},
Field{Name: "CALLSIGN", Value: "W1AW"},
Field{Name: "NAME", Value: "Hiram Percy Maxim", Type: TypeString},
Field{Name: "FREQ", Value: "7.054"},
)).AddRecord(NewRecord(
Field{Name: "QSO_DATE", Value: "20221224"},
Field{Name: "TIME_ON", Value: "095846"},
Field{Name: "BAND", Value: "1.25cm", Type: TypeEnumeration},
Field{Name: "CALLSIGN", Value: "N0P", Type: TypeString},
Field{Name: "NAME", Value: "Santa Claus"},
Field{Name: "NOTES_INTL", Value: `Þhrough the /❄\ bringing 🎁 \to the child\re\n`},
))
l.Records[1].SetComment("Record comment")
want := `19901031,1234,40M,W1AW,Hiram Percy Maxim,7.054,
20221224,095846,1.25cm,N0P,Santa Claus,,Þhrough the /❄\ bringing 🎁 \to the child\re\n
`
csv := NewCSVIO()
csv.OmitHeader = true
out := &strings.Builder{}
if err := csv.Write(l, out); err != nil {
t.Errorf("Write(%v) got error %v", l, err)
} else {
got := out.String()
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("Write(%v) had diff with expected:\n%s", l, diff)
}
}
}
15 changes: 9 additions & 6 deletions adif/tsv.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type TSVIO struct {
CRLF bool
EscapeSpecial bool
IgnoreEmptyHeaders bool
OmitHeader bool
}

func NewTSVIO() *TSVIO { return &TSVIO{} }
Expand Down Expand Up @@ -127,12 +128,14 @@ func (o *TSVIO) Write(l *Logfile, w io.Writer) error {
}
return nil
}
for i, h := range order {
if _, err := out.WriteString(o.escape(h)); err != nil {
return fmt.Errorf("writing TSV header: %w", err)
}
if err := writeDelim(i); err != nil {
return fmt.Errorf("writing TSV header: %w", err)
if !o.OmitHeader {
for i, h := range order {
if _, err := out.WriteString(o.escape(h)); err != nil {
return fmt.Errorf("writing TSV header: %w", err)
}
if err := writeDelim(i); err != nil {
return fmt.Errorf("writing TSV header: %w", err)
}
}
}
for _, r := range l.Records {
Expand Down
35 changes: 35 additions & 0 deletions adif/tsv_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,3 +183,38 @@ func TestTSVEscapeSpecialCharacters(t *testing.T) {
}
}
}

func TestTSVOmitHeader(t *testing.T) {
l := NewLogfile()
l.Comment = "TSV ignores comments"
l.FieldOrder = []string{"QSO_DATE", "TIME_ON", "BAND", "CALL"}
l.AddRecord(NewRecord(
Field{Name: "TIME_ON", Value: "1234", Type: TypeTime},
Field{Name: "QSO_DATE", Value: "19901031", Type: TypeDate},
Field{Name: "NAME", Value: "Hiram Percy Maxim", Type: TypeString},
Field{Name: "BAND", Value: "40M"},
Field{Name: "FREQ", Value: "7.054"},
Field{Name: "CALL", Value: "W1AW"},
)).AddRecord(NewRecord(
Field{Name: "notes_intl", Value: "Þhrough the /❄\\ bringing 🎁 \\to the child\\re\\n"},
Field{Name: "qso_date", Value: "20221224"},
Field{Name: "call", Value: "N0P", Type: TypeString},
Field{Name: "time_on", Value: "095846"},
Field{Name: "band", Value: "1.25cm", Type: TypeEnumeration},
Field{Name: "name", Value: "Santa \"St. Nick\" Claus"},
))
l.Records[1].SetComment("Record comment")
want := "19901031\t1234\t40M\tW1AW\tHiram Percy Maxim\t7.054\t\n" +
"20221224\t095846\t1.25cm\tN0P\tSanta \"St. Nick\" Claus\t\tÞhrough the /❄\\ bringing 🎁 \\to the child\\re\\n\n"
tsv := NewTSVIO()
tsv.OmitHeader = true
out := &strings.Builder{}
if err := tsv.Write(l, out); err != nil {
t.Errorf("Write(%v) got error %v", l, err)
} else {
got := out.String()
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("Write(%v) had diff with expected:\n%s", l, diff)
}
}
}
4 changes: 3 additions & 1 deletion adifmt/formats.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ func (c csvConfig) AddFlags(fs *flag.FlagSet) {
fs.BoolVar(&c.io.RequireFullRecord, "csv-require-all-fields", false, "CSV files: error if fewer fields in a record than in header")
fs.BoolVar(&c.io.TrimLeadingSpace, "csv-trim-space", false, "CSV files: ignore leading space in fields")
fs.BoolVar(&c.io.CRLF, "csv-crlf", false, "CSV files: output MS Windows line endings")
fs.BoolVar(&c.io.OmitHeader, "csv-omit-header", false, "CSV files: don't output the header line")
}

type jsonConfig struct{ io *adif.JSONIO }
Expand All @@ -145,5 +146,6 @@ func (c tsvConfig) IO() adif.ReadWriter { return c.io }
func (c tsvConfig) AddFlags(fs *flag.FlagSet) {
fs.BoolVar(&c.io.CRLF, "tsv-crlf", false, "TSV files: output MS Windows line endings")
fs.BoolVar(&c.io.EscapeSpecial, "tsv-escape-special", false, "TSV files: accept and produce \\t \\r \\n and \\\\ escapes in fields")
fs.BoolVar(&c.io.IgnoreEmptyHeaders, "tsv-ignore-empty-headers", false, "TSV files: do not return error if a TSV file has an empty header field")
fs.BoolVar(&c.io.IgnoreEmptyHeaders, "tsv-ignore-empty-headers", false, "TSV files: don't return error if a TSV file has an empty header field")
fs.BoolVar(&c.io.OmitHeader, "tsv-omit-header", false, "TSV files: don't output the header line")
}

0 comments on commit 2a5c8e8

Please sign in to comment.