mirror of
https://github.com/johnkerl/miller.git
synced 2026-07-17 16:38:54 +00:00
* refine the plan
* Fix all staticcheck lint findings (uncapped)
golangci-lint's default max-same-issues=3 was hiding most of the backlog:
the true pre-fix count was 69 staticcheck findings, not 34. This fixes all
of them, driving staticcheck to zero:
- ST1023/QF1011 (37): omit explicit types inferred from the RHS
- S1009/S1031 (15): drop redundant nil checks before len()/range
- SA9003 (9): remove comment-only empty branches, keeping the comments
- QF1007 (3): merge conditional assignment into declaration
- QF1006 (3): lift break conditions into loop conditions
- QF1001 (3): apply De Morgan's law / name the negated predicate
Also updates plans/lintfixes.md with the cap discovery and the corrected
errcheck picture (1202 uncapped, ~949 of them fmt.Fprint*).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Drive errcheck to zero: config for bulk categories, propagate real errors
Adds .golangci.yml with errcheck exclude-functions for fmt.Fprint* (usage
printers), (*bufio.Writer).Write/WriteString (sticky errors, surfaced at the
now-checked final Flush), and (*strings.Builder).WriteString; pins
max-issues-per-linter/max-same-issues to 0 so CI reports true counts.
Real error paths now propagate instead of being dropped:
- Finalize{Reader,Writer}Options in join/put/filter/split/tee and the
repl/script entry points: 'mlr join -i badformat' now errors instead of
silently using wrong separators
- final output-stream Flush in pkg/stream: write failure no longer exits 0
- DSL emit/print/dump redirect writes, matching their sibling branches
- CSV writer WriteCSVRecordMaybeColorized, close-time Flush in file output
handlers, ENV[...] Setenv, REPL record-write and redirect-close errors
- termcvt write-side Close before rename (had "TODO: check return status")
The rest are deliberate ignores, marked with _ = and a comment where the
reason isn't obvious: unset-of-missing-path no-ops, read-side closes,
mid-stream FlushOnEveryRecord, init-time strftime registrations, in-memory
usage-capture pipes, and regtest-harness env/temp-file teardown.
golangci-lint now reports 0 issues on ./cmd/mlr ./pkg/... with all caps off.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
94 lines
2.9 KiB
Go
94 lines
2.9 KiB
Go
// Machine-readable (JSON) accessors over the verb (transformer) catalog, for
|
|
// `mlr help --as-json` and similar tooling.
|
|
//
|
|
// Tier-1 caveat: unlike functions and flags, verb options are not held in any
|
|
// structured form -- each verb hand-writes a UsageFunc that prints prose. So
|
|
// here we expose the verb name, a one-line summary, and the captured raw usage
|
|
// text. Structured per-verb options (flag/arg/type) are a planned follow-on
|
|
// (an optional Options field on TransformerSetup); when present they can be
|
|
// emitted alongside UsageText.
|
|
|
|
package transformers
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
// VerbInfoForJSON is the structured view of a single verb. Summary is the first
|
|
// non-"Usage:" line of the usage text; UsageText is the verb's full usage
|
|
// output verbatim (the Tier-1 fallback for not-yet-structured options).
|
|
type VerbInfoForJSON struct {
|
|
Name string `json:"name"`
|
|
Summary string `json:"summary"`
|
|
IgnoresInput bool `json:"ignores_input"`
|
|
UsageText string `json:"usage_text"`
|
|
}
|
|
|
|
// captureUsageFunc runs a verb's UsageFunc against a pipe and returns what it
|
|
// printed. The UsageFunc signature takes an *os.File, so we hand it the
|
|
// write-end of a pipe directly -- no global-stdout swap needed. A goroutine
|
|
// drains the read-end so we never block on the OS pipe buffer.
|
|
func captureUsageFunc(usageFunc TransformerUsageFunc) string {
|
|
r, w, err := os.Pipe()
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
|
|
done := make(chan string)
|
|
go func() {
|
|
var buf bytes.Buffer
|
|
_, _ = io.Copy(&buf, r)
|
|
done <- buf.String()
|
|
}()
|
|
|
|
usageFunc(w)
|
|
_ = w.Close()
|
|
s := <-done
|
|
_ = r.Close()
|
|
return s
|
|
}
|
|
|
|
// summarizeUsageText returns the first line that isn't blank or a "Usage:"
|
|
// banner -- the closest thing each verb has to a one-line description.
|
|
func summarizeUsageText(usageText string) string {
|
|
for _, line := range strings.Split(usageText, "\n") {
|
|
trimmed := strings.TrimSpace(line)
|
|
if trimmed == "" || strings.HasPrefix(trimmed, "Usage:") {
|
|
continue
|
|
}
|
|
return trimmed
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func makeVerbInfoForJSON(setup *TransformerSetup) *VerbInfoForJSON {
|
|
usageText := captureUsageFunc(setup.UsageFunc)
|
|
return &VerbInfoForJSON{
|
|
Name: setup.Verb,
|
|
Summary: summarizeUsageText(usageText),
|
|
IgnoresInput: setup.IgnoresInput,
|
|
UsageText: strings.TrimRight(usageText, "\n"),
|
|
}
|
|
}
|
|
|
|
// GetVerbInfosForJSON returns the full verb catalog in table order.
|
|
func GetVerbInfosForJSON() []*VerbInfoForJSON {
|
|
infos := make([]*VerbInfoForJSON, 0, len(TRANSFORMER_LOOKUP_TABLE))
|
|
for i := range TRANSFORMER_LOOKUP_TABLE {
|
|
infos = append(infos, makeVerbInfoForJSON(&TRANSFORMER_LOOKUP_TABLE[i]))
|
|
}
|
|
return infos
|
|
}
|
|
|
|
// GetVerbInfoForJSON returns the structured view of a single verb, or nil if
|
|
// there is no such verb.
|
|
func GetVerbInfoForJSON(verb string) *VerbInfoForJSON {
|
|
setup := LookUp(verb)
|
|
if setup == nil {
|
|
return nil
|
|
}
|
|
return makeVerbInfoForJSON(setup)
|
|
}
|