diff --git a/CLAUDE.md b/CLAUDE.md index 6a7a3bf4b..52c006462 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,6 +4,30 @@ Miller is a command-line data processing tool for working with CSV, TSV, JSON, and other data formats. It's written in Go (v1.18+) and provides SQL-like operations on data. +## Initial Setup + +### Setting Up staticcheck + +The `make staticcheck` target requires the staticcheck tool. To set it up: + +```bash +go install honnef.co/go/tools/cmd/staticcheck@latest +``` + +This installs staticcheck to `~/go/bin/` (the default Go binaries directory). For `make staticcheck` to work, you need `~/go/bin` in your `PATH`. + +**Add to your shell profile** (`.bashrc`, `.zshrc`, or equivalent): +```bash +export PATH="$PATH:$HOME/go/bin" +``` + +**Verify the installation:** +```bash +staticcheck -version +``` + +If this works, you can use `make staticcheck` without any further setup. + ## Build & Test ### Building @@ -23,7 +47,7 @@ make bench # Run benchmarks ### Code Quality ```bash make fmt # Format code with go fmt -make staticcheck # Run static analysis (requires: go install honnef.co/go/tools/cmd/staticcheck@latest) +make staticcheck # Run static analysis (see Initial Setup section above) ``` ### Full Developer Workflow diff --git a/pkg/go-csv/csv_reader.go b/pkg/go-csv/csv_reader.go index b09a2b558..9d9ef530d 100644 --- a/pkg/go-csv/csv_reader.go +++ b/pkg/go-csv/csv_reader.go @@ -307,32 +307,31 @@ func (r *Reader) readRecord(dst []string) ([]string, error) { // Read line (automatically skipping past empty lines and any comments). var line []byte var errRead error - for errRead == nil { - line, errRead = r.readLine() - // MILLER-SPECIFIC UPDATE: DO NOT DO THIS - // if r.Comment != 0 && nextRune(line) == r.Comment { - // line = nil - // continue // Skip comment lines - // } + line, errRead = r.readLine() - // MILLER-SPECIFIC UPDATE: DO NOT DO THIS - // if errRead == nil && len(line) == lengthNL(line) { - // line = nil - // continue // Skip empty lines - // } + // MILLER-SPECIFIC UPDATE: DO NOT DO THIS + // if r.Comment != 0 && nextRune(line) == r.Comment { + // line = nil + // continue // Skip comment lines + // } - // MILLER-SPECIFIC UPDATE: If the line starts with the comment character, - // don't attempt to CSV-parse it -- just hand it back as a single field. - // This allows two things: - // * User comments get passed through as intended, without being reformatted; - // * Users can do things like `# a"b` in their comments without getting an - // imbalanced-double-quote error. - if r.Comment != 0 && nextRune(line) == r.Comment { - return []string{string(line)}, nil - } - break + // MILLER-SPECIFIC UPDATE: DO NOT DO THIS + // if errRead == nil && len(line) == lengthNL(line) { + // line = nil + // continue // Skip empty lines + // } + + // MILLER-SPECIFIC UPDATE: If the line starts with the comment character, + // don't attempt to CSV-parse it -- just hand it back as a single field. + // This allows two things: + // * User comments get passed through as intended, without being reformatted; + // * Users can do things like `# a"b` in their comments without getting an + // imbalanced-double-quote error. + if r.Comment != 0 && nextRune(line) == r.Comment { + return []string{string(line)}, nil } + if errRead == io.EOF { return nil, errRead } diff --git a/pkg/input/fixed_width_splitter.go b/pkg/input/fixed_width_splitter.go index c7e822fb0..e3067a51d 100644 --- a/pkg/input/fixed_width_splitter.go +++ b/pkg/input/fixed_width_splitter.go @@ -41,7 +41,7 @@ func NewFixedWidthSplitter(spec, referenceRow string) (*fixedWidthSplitter, erro } else if spec == "right-align-multi-word" { indexes = parseRightAlign(referenceRow, true) } else { - return nil, fmt.Errorf("Unknown spec: %v", spec) + return nil, fmt.Errorf("unknown spec: %v", spec) } return &fixedWidthSplitter{indexes: indexes}, nil diff --git a/pkg/input/record_reader_pprint.go b/pkg/input/record_reader_pprint.go index 2f10a9196..51d967347 100644 --- a/pkg/input/record_reader_pprint.go +++ b/pkg/input/record_reader_pprint.go @@ -229,7 +229,7 @@ func (reader *RecordReaderPprintFixedSplit) getRecords( } else { if !reader.readerOptions.AllowRaggedCSVInput && len(reader.headerStrings) != len(fields) { err := fmt.Errorf( - "Fixed-width header/data length mismatch %d != %d at filename %s line %d", + "fixed-width header/data length mismatch %d != %d at filename %s line %d", len(reader.headerStrings), len(fields), filename, reader.inputLineNumber, ) errorChannel <- err diff --git a/pkg/mlrval/mlrmap_flatten_unflatten.go b/pkg/mlrval/mlrmap_flatten_unflatten.go index b8cf3e568..e62744e06 100644 --- a/pkg/mlrval/mlrmap_flatten_unflatten.go +++ b/pkg/mlrval/mlrmap_flatten_unflatten.go @@ -151,7 +151,7 @@ func (mlrmap *Mlrmap) CopyUnflattened( // Check for "" in any of the split pieces; treat the field as terminal if so. legitDots := true - for i, _ := range arrayval { + for i := range arrayval { piece := arrayval[i].String() if piece == "" { legitDots = false diff --git a/pkg/mlrval/mlrval_collections.go b/pkg/mlrval/mlrval_collections.go index 48cefd7fd..6364b8a8b 100644 --- a/pkg/mlrval/mlrval_collections.go +++ b/pkg/mlrval/mlrval_collections.go @@ -338,7 +338,7 @@ func putIndexedOnArray( return errors.New("zero indices are not supported. Indices are 1-up") } if mindex.intf.(int64) < 0 { - return errors.New("Cannot use negative indices to auto-lengthen arrays") + return errors.New("cannot use negative indices to auto-lengthen arrays") } // Array is [a,b,c] with mindices 1,2,3. Length is 3. Zindices are 0,1,2. // Given mindex is 4. @@ -371,7 +371,7 @@ func putIndexedOnArray( } else if mindex.intf.(int64) == 0 { return errors.New("zero indices are not supported. Indices are 1-up") } else if mindex.intf.(int64) < 0 { - return errors.New("Cannot use negative indices to auto-lengthen arrays") + return errors.New("cannot use negative indices to auto-lengthen arrays") } else { // Already allocated but needs to be longer LengthenMlrvalArray(baseArray, int(mindex.intf.(int64))) diff --git a/pkg/mlrval/mlrval_json.go b/pkg/mlrval/mlrval_json.go index 9fbf4fb56..4894e83ec 100644 --- a/pkg/mlrval/mlrval_json.go +++ b/pkg/mlrval/mlrval_json.go @@ -27,7 +27,7 @@ const ( JSON_MULTILINE = 2 ) -var prematureEofError error = errors.New("JSON parser: unexpected premature EOF") +var errPrematureEOF = errors.New("JSON parser: unexpected premature EOF") // The JSON decoder (https://golang.org/pkg/encoding/json/#Decoder) is quite // nice. What we can have is: @@ -192,7 +192,7 @@ func MlrvalDecodeFromJSON(decoder *json.Decoder) ( for decoder.More() { element, eof, err := MlrvalDecodeFromJSON(decoder) if eof { - return nil, false, prematureEofError + return nil, false, errPrematureEOF } if err != nil { return nil, false, err @@ -205,7 +205,7 @@ func MlrvalDecodeFromJSON(decoder *json.Decoder) ( for decoder.More() { key, eof, err := MlrvalDecodeFromJSON(decoder) if eof { - return nil, false, prematureEofError + return nil, false, errPrematureEOF } if err != nil { return nil, false, err @@ -219,7 +219,7 @@ func MlrvalDecodeFromJSON(decoder *json.Decoder) ( value, eof, err := MlrvalDecodeFromJSON(decoder) if eof { - return nil, false, prematureEofError + return nil, false, errPrematureEOF } if err != nil { return nil, false, err @@ -296,7 +296,7 @@ func (mv *Mlrval) marshalJSONAux( case MT_DIM: // MT_DIM is one past the last valid type return "", fmt.Errorf("internal coding error detected") } - return "", fmt.Errorf("Internal coding error detected") + return "", fmt.Errorf("internal coding error detected") } // TYPE-SPECIFIC MARSHALERS diff --git a/pkg/output/file_output_handlers.go b/pkg/output/file_output_handlers.go index edc952a35..afa2515b5 100644 --- a/pkg/output/file_output_handlers.go +++ b/pkg/output/file_output_handlers.go @@ -69,7 +69,7 @@ type MultiOutputHandlerManager struct { lruNodes map[string]*lruNode // filename -> node (file mode only) lruHead *lruNode // MRU lruTail *lruNode // LRU - evictedFilenames map[string]bool // filenames we closed; re-open with append + evictedFilenames map[string]bool // filenames we closed; re-open with append } func NewFileOutputHandlerManager( diff --git a/pkg/output/record_writer_dcf.go b/pkg/output/record_writer_dcf.go index 174245a3e..b03d5fe53 100644 --- a/pkg/output/record_writer_dcf.go +++ b/pkg/output/record_writer_dcf.go @@ -52,7 +52,7 @@ func dcfValueString(mv *mlrval.Mlrval) string { } if mv.IsArray() { arr := mv.GetArray() - if arr == nil || len(arr) == 0 { + if len(arr) == 0 { return "" } parts := make([]string, 0, len(arr)) diff --git a/pkg/parsing/token/token.go b/pkg/parsing/token/token.go index 50282b2c3..69fe0404c 100644 --- a/pkg/parsing/token/token.go +++ b/pkg/parsing/token/token.go @@ -123,7 +123,7 @@ func (t *Token) Int64Value() (int64, error) { func (t *Token) UTF8Rune() (rune, error) { r, _ := utf8.DecodeRune(t.Lit) if r == utf8.RuneError { - err := fmt.Errorf("Invalid rune") + err := fmt.Errorf("invalid rune") return r, err } return r, nil diff --git a/pkg/transformers/check.go b/pkg/transformers/check.go index 5af3c2f5d..46e291b15 100644 --- a/pkg/transformers/check.go +++ b/pkg/transformers/check.go @@ -58,8 +58,9 @@ func transformerCheckParseCLI( transformerCheckUsage(os.Stdout) return nil, cli.ErrHelpRequested + } else { + return nil, cli.VerbErrorf(verbNameCheck, "option \"%s\" not recognized", opt) } - return nil, cli.VerbErrorf(verbNameCheck, "option \"%s\" not recognized", opt) } *pargi = argi diff --git a/pkg/transformers/group_by.go b/pkg/transformers/group_by.go index 005aab422..e4d704c03 100644 --- a/pkg/transformers/group_by.go +++ b/pkg/transformers/group_by.go @@ -54,8 +54,9 @@ func transformerGroupByParseCLI( transformerGroupByUsage(os.Stdout) return nil, cli.ErrHelpRequested + } else { + return nil, cli.VerbErrorf(verbNameGroupBy, "option \"%s\" not recognized", opt) } - return nil, cli.VerbErrorf(verbNameGroupBy, "option \"%s\" not recognized", opt) } // Get the group-by field names from the command line diff --git a/pkg/transformers/group_like.go b/pkg/transformers/group_like.go index bc65204cb..a47d224e9 100644 --- a/pkg/transformers/group_like.go +++ b/pkg/transformers/group_like.go @@ -54,8 +54,9 @@ func transformerGroupLikeParseCLI( transformerGroupLikeUsage(os.Stdout) return nil, cli.ErrHelpRequested + } else { + return nil, cli.VerbErrorf(verbNameGroupLike, "option \"%s\" not recognized", opt) } - return nil, cli.VerbErrorf(verbNameGroupLike, "option \"%s\" not recognized", opt) } *pargi = argi diff --git a/pkg/transformers/label.go b/pkg/transformers/label.go index 9a1f35f8a..3113cd449 100644 --- a/pkg/transformers/label.go +++ b/pkg/transformers/label.go @@ -58,8 +58,9 @@ func transformerLabelParseCLI( transformerLabelUsage(os.Stdout) return nil, cli.ErrHelpRequested + } else { + return nil, cli.VerbErrorf(verbNameLabel, "option \"%s\" not recognized", opt) } - return nil, cli.VerbErrorf(verbNameLabel, "option \"%s\" not recognized", opt) } // Get the label field names from the command line diff --git a/pkg/transformers/latin1_to_utf8.go b/pkg/transformers/latin1_to_utf8.go index a93acb21d..30befec45 100644 --- a/pkg/transformers/latin1_to_utf8.go +++ b/pkg/transformers/latin1_to_utf8.go @@ -56,8 +56,9 @@ func transformerLatin1ToUTF8ParseCLI( transformerLatin1ToUTF8Usage(os.Stdout) return nil, cli.ErrHelpRequested + } else { + return nil, cli.VerbErrorf(verbNameLatin1ToUTF8, "option \"%s\" not recognized", opt) } - return nil, cli.VerbErrorf(verbNameLatin1ToUTF8, "option \"%s\" not recognized", opt) } *pargi = argi diff --git a/pkg/transformers/nothing.go b/pkg/transformers/nothing.go index 6fb0cfd69..460f25f2f 100644 --- a/pkg/transformers/nothing.go +++ b/pkg/transformers/nothing.go @@ -53,9 +53,10 @@ func transformerNothingParseCLI( if opt == "-h" || opt == "--help" { transformerNothingUsage(os.Stdout) return nil, cli.ErrHelpRequested + } else { + transformerNothingUsage(os.Stderr) + return nil, fmt.Errorf("%s %s: option \"%s\" not recognized", "mlr", verbNameNothing, opt) } - transformerNothingUsage(os.Stderr) - return nil, fmt.Errorf("%s %s: option \"%s\" not recognized", "mlr", verbNameNothing, opt) } *pargi = argi diff --git a/pkg/transformers/regularize.go b/pkg/transformers/regularize.go index c862967fa..61ca19ee0 100644 --- a/pkg/transformers/regularize.go +++ b/pkg/transformers/regularize.go @@ -55,8 +55,9 @@ func transformerRegularizeParseCLI( transformerRegularizeUsage(os.Stdout) return nil, cli.ErrHelpRequested + } else { + return nil, cli.VerbErrorf(verbNameRegularize, "option \"%s\" not recognized", opt) } - return nil, cli.VerbErrorf(verbNameRegularize, "option \"%s\" not recognized", opt) } *pargi = argi diff --git a/pkg/transformers/remove_empty_columns.go b/pkg/transformers/remove_empty_columns.go index e939ecfca..c148358df 100644 --- a/pkg/transformers/remove_empty_columns.go +++ b/pkg/transformers/remove_empty_columns.go @@ -54,8 +54,9 @@ func transformerRemoveEmptyColumnsParseCLI( transformerRemoveEmptyColumnsUsage(os.Stdout) return nil, cli.ErrHelpRequested + } else { + return nil, cli.VerbErrorf(verbNameRemoveEmptyColumns, "option \"%s\" not recognized", opt) } - return nil, cli.VerbErrorf(verbNameRemoveEmptyColumns, "option \"%s\" not recognized", opt) } *pargi = argi diff --git a/pkg/transformers/sec2gmtdate.go b/pkg/transformers/sec2gmtdate.go index 296b90a65..77c50bf23 100644 --- a/pkg/transformers/sec2gmtdate.go +++ b/pkg/transformers/sec2gmtdate.go @@ -57,8 +57,9 @@ func transformerSec2GMTDateParseCLI( transformerSec2GMTDateUsage(os.Stdout) return nil, cli.ErrHelpRequested + } else { + return nil, cli.VerbErrorf(verbNameSec2GMTDate, "option \"%s\" not recognized", opt) } - return nil, cli.VerbErrorf(verbNameSec2GMTDate, "option \"%s\" not recognized", opt) } if argi >= argc { diff --git a/pkg/transformers/shuffle.go b/pkg/transformers/shuffle.go index 6a35e36e7..688230fb4 100644 --- a/pkg/transformers/shuffle.go +++ b/pkg/transformers/shuffle.go @@ -57,8 +57,9 @@ func transformerShuffleParseCLI( transformerShuffleUsage(os.Stdout) return nil, cli.ErrHelpRequested + } else { + return nil, cli.VerbErrorf(verbNameShuffle, "option \"%s\" not recognized", opt) } - return nil, cli.VerbErrorf(verbNameShuffle, "option \"%s\" not recognized", opt) } *pargi = argi diff --git a/pkg/transformers/skip_trivial_records.go b/pkg/transformers/skip_trivial_records.go index de4a456d4..e8ddd1fe5 100644 --- a/pkg/transformers/skip_trivial_records.go +++ b/pkg/transformers/skip_trivial_records.go @@ -54,8 +54,9 @@ func transformerSkipTrivialRecordsParseCLI( transformerSkipTrivialRecordsUsage(os.Stdout) return nil, cli.ErrHelpRequested + } else { + return nil, cli.VerbErrorf(verbNameSkipTrivialRecords, "option \"%s\" not recognized", opt) } - return nil, cli.VerbErrorf(verbNameSkipTrivialRecords, "option \"%s\" not recognized", opt) } *pargi = argi diff --git a/pkg/transformers/tac.go b/pkg/transformers/tac.go index d7017aac3..237d8a23a 100644 --- a/pkg/transformers/tac.go +++ b/pkg/transformers/tac.go @@ -53,8 +53,9 @@ func transformerTacParseCLI( transformerTacUsage(os.Stdout) return nil, cli.ErrHelpRequested + } else { + return nil, cli.VerbErrorf(verbNameTac, "option \"%s\" not recognized", opt) } - return nil, cli.VerbErrorf(verbNameTac, "option \"%s\" not recognized", opt) } *pargi = argi diff --git a/pkg/transformers/utf8_to_latin1.go b/pkg/transformers/utf8_to_latin1.go index f5d5735c4..fbeebe383 100644 --- a/pkg/transformers/utf8_to_latin1.go +++ b/pkg/transformers/utf8_to_latin1.go @@ -56,8 +56,9 @@ func transformerUTF8ToLatin1ParseCLI( transformerUTF8ToLatin1Usage(os.Stdout) return nil, cli.ErrHelpRequested + } else { + return nil, cli.VerbErrorf(verbNameUTF8ToLatin1, "option \"%s\" not recognized", opt) } - return nil, cli.VerbErrorf(verbNameUTF8ToLatin1, "option \"%s\" not recognized", opt) } *pargi = argi