Add a section to the record-heterogeneity page showing how to
concatenate multiple CSV files whose headers differ, using unsparsify
(and unsparsify -f then regularize for the streaming case), with new
sample data files under docs/src/data/het/.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Add a worked-examples section to the questions-about-joins page showing
how to get SQL-style join semantics (keeping non-matching records) with
mlr join --ul/--ur followed by unsparsify, using the example data from
issue #652.
Resolves#652.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Adds a recipe section to the questions-about-joins page showing two
ways to preprocess the join verb's -f left file without creating an
intermediate file: the main-level --prepipe flag (which the left file
inherits along with the other main input options), and shell process
substitution. Also notes that the verb-level --prepipe/--prepipex
flags listed in 'mlr join --help' do not currently take effect for
the left file in Miller 6.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Documents the "TZ environment variable appears malformed" / "time:
invalid location name" startup errors: TZ set to a file path (which
Go's time library rejects, unlike C), and missing timezone databases
on Windows, Cygwin, and minimal containers. Lists remedies: IANA zone
names, installing tzdata, the ZONEINFO environment variable, and
fixed-offset formats.
Addresses #1238; also relevant to #1406 and #1758.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
With --right-align-numeric, PPRINT data cells right-align but headers
stayed left-aligned, so a header did not line up with its own column's
data -- the original ask in #380. Now a header is right-aligned when
every value in its column is numeric, for both non-barred and barred
PPRINT output. Mixed columns keep left-aligned headers.
For --omd-aligned, the raw header text of right-aligned columns is now
right-justified too, matching how Markdown viewers render the ---:
marker; this follows the same all-values-numeric per-column rule
already used for the separator markers.
Man-page regeneration also picks up previously-merged reorder help-text
edits that had not been regenerated.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The uniq verb outputs only the group-by columns, and issue #1075 asks
for a way to deduplicate on some fields while keeping the rest. Miller
already supports this via "head -n 1 -g", but that wasn't discoverable
from uniq's help text or its reference-verbs section. Add a note to
"mlr uniq --help" and a short recipe (with live examples) to the uniq
section of the verbs reference, and regenerate the derived man page,
docs, and CLI-help golden files.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
When the skip-trivial-records verb is in the then-chain, the CSV/TSV
record-readers now silently skip trivial (all-fields-empty) input lines
-- notably blank lines at the end of a CSV file -- instead of raising a
fatal header/data length mismatch. The user has explicitly asked for
trivial records to be skipped, so mlr now exits 0 in this case.
The verb's CLI parser sets a new ReaderOptions.SkipTrivialRecords flag,
which the CSV and TSV readers consult only on the would-be-fatal
mismatch path, so:
* Behavior without the verb is unchanged: blank lines still error.
* Genuinely ragged non-trivial records still error even with the verb.
* --allow-ragged-csv-input behavior is unchanged.
Fixes#1535.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
In the explicit-header ragged code paths of the csvlite, TSV, and
barred-pprint readers, the header-longer-than-data fill used
record.PutCopy with the un-deduplicated header name. With duplicate
header names (e.g. "name,4,4" deduped to "name,4,4_2"), the VOID fill
for the missing trailing field overwrote the already-stored value under
the colliding key, so a short row like "bar,sea" lost "sea".
Use record.PutReferenceMaybeDedupe, as the implicit-header paths
already do (where PR #794 had applied it, although implicit headers are
generated unique and never need dedupe).
Adds regression cases under test/cases/io-dedupe-field-names/ for the
csvlite, TSV, and barred-pprint readers.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Plain {} placeholders consume arguments sequentially as before; {N}
refers to the Nth argument (1-based) and may be used to repeat and/or
reorder arguments, e.g. format("{1}/{2}/{1}_{3}.ext", $p1, $p2, $p3).
Mixing is allowed: the {} sequence counter is independent of positional
placeholders (as in Rust format!). Out-of-range indices interpolate the
empty string, consistent with existing too-few-arguments behavior; {0}
is an error value since indices are 1-based.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
With multiple regexes, 'mlr reorder -r' previously emitted all matched
fields in record order, ignoring the order the regexes were given. Now
matched fields are grouped by regex-list order (first regex's matches
first, then the second's, etc.); within each group, fields keep their
record order. A field matching multiple regexes is claimed by the first
one. This makes 'reorder -r' consistent with 'cut -orf' and satisfies
the original request in #1325: -r '^YYY,^XXX' puts YYY-prefixed fields
first, then XXX-prefixed fields, then the rest. Applies to -e, -b, and
-a modes as well.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
For PPRINT, right-justifies data cells whose values are numeric,
leaving other cells and header lines left-justified. For Markdown,
emits right-alignment markers (---:) for numeric columns; in
--omd-aligned mode also right-justifies the raw cell text.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Honor --ors CRLF for CSV output (#1810)
The full-CSV record writer validated ORS as newline or
carriage-return/newline, but never propagated the choice to the forked
Go CSV writer's UseCRLF field, so `--ors '\r\n'` (or `--ors crlf`)
silently produced LF line endings. Set UseCRLF from the writer options
so CRLF output is honored. Default behavior (LF) is unchanged, and
other ORS values are still rejected. The CSV-lite and TSV writers
already honored CRLF ORS.
Also: fix a copy-paste "for CSV" in the TSV writer's ORS-validation
message, add unit tests asserting byte-exact line endings (the regtest
harness normalizes CR/LF, so this can't be asserted in test/cases), add
CLI-level regression cases, and update the separators documentation.
This substantially addresses #1722 as well: RFC-4180-style CRLF output
can now be requested on any platform.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Fix errcheck lint: check Flush() error in CSV writer test
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The summary verb's field_type column shows values like "string-int" or
"empty-string" for columns containing values of mixed inferred types:
all types encountered across records are printed, hyphen-joined, in the
order first encountered. Add a note to the verb's help text explaining
this, and regenerate the man page, docs, and golden test output.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Miller type-inference accepts float formats which the JSON grammar
disallows -- leading zeros like 004.56, leading plus signs, bare leading
or trailing decimal points like .56 or 4., etc. -- and the JSON writer
passed the original text through verbatim, producing invalid JSON.
Leading-zero integers such as 0123456789 were already handled (inferred
as strings), and hex/binary/octal ints were already re-rendered as
decimal for JSON per #1761; this does the same for floats: when a
float string representation is not a valid JSON number, re-render it
(e.g. 004.56 -> 4.56).
Also documents in reference-main-data-types that CSV double-quoting
does not affect type inference, with -S / --infer-none and string()
as the ways to keep such values as strings.
Addresses #1114 and #1293.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The tee verb passed the same record pointer both to its file-output
handler (which writes asynchronously on another goroutine, and whose
buffering formats like pprint/json can hold records until close) and
downstream in the verb chain, where subsequent verbs mutate records in
place. Downstream mutations could therefore leak into the tee'd output:
mlr tee -p cat then cat -n then nothing <<EOF
a=1,b=2
a=3,b=4
a=5,b=6
EOF
emitted "n=3,a=5,b=6" for the last tee'd record, and with pprint/json
tee formats every record picked up downstream fields.
Fix: give the file-writer its own deep copy of the record. Same fix
applied to the split verb when -v (emit downstream) is used, which
shared records with downstream the same way.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The join verb's help text listed only '-i {one of csv,dkvp,nidx,pprint,xtab}'
for overriding the left-file input format, but the verb also accepts the
--icsv/--ijson-style main-flag shorthands (and formats beyond the five
listed, e.g. json and tsv), since unrecognized verb flags fall through to
the main flag table. Update the usage text to say so, and regenerate the
man page and docs content that embed this help output.
Fixes#444.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The uniq verb writes its group-by fields in the order they are named
with -g or -f, not in the order they appear in the input records
(unlike cut, which preserves input order unless -o is given). Per
discussion on #962, document this in the verb help text rather than
change long-standing output ordering. Regenerate the man page and the
docs pages which embed the verb help.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* docs: add "Miller and AI agents" quick-start page (#2098)
Umbrella page for the AI-friendly feature stack: one-line MCP setup as
the fast path, plus the plain-CLI path (which, help --as-json,
describe, --explain, --errors-json, --no-shell) with live CI-tested
examples, and the discover -> constrain -> validate -> run loop.
Cross-linked with the MCP server page; listed under Getting started.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: reframe AI page as "Miller and AI" (#2098)
Rename ai-agents.md to ai.md and restructure around the pre-MCP
feature stack, organized as the loop each feature serves: Discover
(catalog/index/which, cache keys, single-sourced usage text),
Constrain (enum value-sets + describe: tool shape vs data shape),
Validate (--explain), Run and recover (--errors-json, --no-shell,
env-var trio). MCP is now one closing section pointing at the
mcp-server.md detail page. All examples are live and CI-tested,
including Miller querying its own catalog.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: retitle MCP page to "The MCP server" under the Miller-and-AI umbrella
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: add bare-minimum getting-started section to Miller-and-AI page
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: AI features land in Miller 6.20
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: hyperlink SKILL.md references to the repo
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: rename section to 'The essentials'
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Plan: flesh out PR7 (MCP server + Agent Skill) design
stdio transport (no HTTP port), mlr mcp terminal in the main binary,
SDK-vs-handroll decision, tool list, in-process vs subprocess split,
run-tool safety (--no-shell prerequisite), single-sourced skill, tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Add mlr mcp: MCP server + agent playbook; --no-shell gate (#2098 PR7)
New terminal `mlr mcp` runs a Model Context Protocol server over stdio
(spawned by MCP clients; no network port), exposing five tools --
list_capabilities, which, validate_dsl, describe_data, run -- plus an
agent playbook as MCP prompt/resource. Catalog tools are served
in-process from the help registries; the rest subprocess this same
binary with MLR_ERRORS_JSON=1, a timeout, and an output cap.
Prerequisite: a new --no-shell flag / MLR_NO_SHELL env var (one-way
gate) disables the DSL system/exec functions, piped redirects, and
--prepipe/--prepipex; the MCP server sets it on the commands it runs
unless started with --allow-shell.
Adds the github.com/modelcontextprotocol/go-sdk dependency.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Force LF checkout for the embedded SKILL.md (Windows CI fix)
go:embed embeds checkout bytes, so a CRLF checkout on Windows made the
embedded playbook differ per platform and failed
TestPlaybookHasFrontmatter. Pin the file to eol=lf in .gitattributes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Move no-shell test DSL into per-case mlr files (Windows CI fix)
Inline single-quoted DSL in cmd files is mangled by the Windows shell
(single quotes are not quote characters there); the harness's
put -f ${CASEDIR}/mlr pattern avoids shell quoting entirely.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
One output record per input field: types seen with counts, occurrence
count, null count, cardinality, min/max, and -- for fields within the
-n/--max-values cap -- the complete distinct-value list in first-seen
order. `mlr --ojson describe` is the machine-readable form; nested
types/values flatten in tabular formats.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Lets an agent type-check a DSL expression before spending a full input
pass. `mlr put --explain '...'` (and filter) runs the existing
parse -> ValidateAST -> CST build -> Resolve path, then:
- valid: prints "mlr {put,filter}: DSL expression is valid." and exits 0
- invalid: returns the build error up the normal path, so --errors-json
emits a structured document; exits 1
- -W with fatal warnings: reports and exits 1
The gate lives in the pass-two constructor, before any input file is
opened, so no input stream is read (verified with a nonexistent input
file still validating OK).
Also categorize bare "parse error: ..." messages from the DSL parser as
kind "dsl-parse-error" rather than "generic" (climain/errors_json.go),
so --explain --errors-json gives an agent a useful error kind. The CSV
reader's "parse error on line ..." is stream-time and never reaches this
command-line-parse categorizer.
Tests: dsl-explain/0001-0004 regression cases (valid put/filter, invalid
plain, invalid --errors-json) and categorize unit tests. Regenerated
verb docs, manpage, and the help usage-verbs golden case.
The older -X ("exit after parsing") still exits 0 even on a parse error;
left as-is since --explain is the correct validation path.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Tier-2 structured verb options: OptionSpec, initial migration (#2098)
PR 3 of the AI-friendly roadmap (plans/plan-2098-llm.md).
Infrastructure:
- Add OptionSpec{Flag,Arg,Type,Desc,Repeatable,Values} to
pkg/transformers/aaa_record_transformer.go alongside TransformerSetup.
Type is one of: bool, string, int, float, csv-list, regex, filename,
format, enum. For type=="enum", Values lists the valid choices.
- Add Options []OptionSpec to TransformerSetup (nil = not yet migrated).
- Emit Options in VerbInfoForJSON (omitempty so unmigrated verbs stay
backward-compatible; agents check key presence for Tier-2 availability).
UsageText is always present as the Tier-1 prose fallback.
- Add VerbOptionsNilCheck() in aaa_verb_options_check.go: progress report
of migrated vs. unmigrated verbs, analogous to FLAG_TABLE.NilCheck().
- Wire verb-options-nil-check into mlr help (internal/docgen section).
Initial migration (5/70 verbs):
- nothing: empty Options (no verb-specific options, explicitly migrated)
- cat: -n (bool), -N (string), -g (csv-list), --filename, --filenum (bool)
- head: -g (csv-list), -n (int)
- tail: -g (csv-list), -n (int)
- tee: -a, -p (bool)
Tests:
- 5 new unit tests in aaa_transformer_json_test.go covering migrated/
unmigrated paths, field population, JSON round-trip, and key-presence.
- Regression test case 0003: mlr help verb-options-nil-check golden output.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* Migrate all 70 verbs to structured OptionSpec; bump catalog schema to v2
Completes the Tier-2 migration started in the previous commit. Every verb
in TRANSFORMER_LOOKUP_TABLE now has a non-nil Options field.
- Workflow-migrated all 65 remaining verbs. Each Setup var now carries
Options: []OptionSpec{...} with Flag/Arg/Type/Desc fields. Verbs with
no verb-specific options (altkv, check, group-like, nothing, etc.) use
an empty slice to signal "migrated but no options."
- Drop `omitempty` from VerbInfoForJSON.Options: empty slices were silently
dropped, making migrated-no-option verbs indistinguishable from unmigrated
ones in JSON. Without omitempty: null=unmigrated, []=migrated-no-options,
[...]= migrated-with-options. Bump catalogSchemaVersion 1→2 for this shape
change.
- Replace the two "unmigrated-verb" unit tests (which used stats1 as an
example) with TestAllVerbsFullyMigrated (asserts every verb has non-nil
Options) and TestAllVerbsHaveOptionsKeyInJSON (asserts every migrated
verb emits the "options" key in JSON).
- Regenerate test/cases/cli-help/0003/expout: now reads
"Verb options migration: 70/70 migrated."
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* Add --errors-json structured error output (#2098)
PR 4 of the AI-friendly roadmap (plans/plan-2098-llm.md).
Agents previously had to regex-match English prose to branch on error
kind; this PR lets them do it structurally.
Interface:
- mlr --errors-json <bad command> emits a JSON object to stderr and
exits 1; same behavior as prose path but machine-readable.
- MLR_ERRORS_JSON=1 (truthy) is the env-var equivalent.
- Without the flag, prose output is byte-for-byte unchanged.
JSON shape: {error, kind, token, verb, hint, did_you_mean[]}
Error kinds: unknown-verb, unknown-flag, verb-option-error, generic.
Implementation:
- New pkg/climain/errors_json.go: CLIError typed error, StructuredError
DTO, WantErrorsJSON pre-scan, Levenshtein edit distance, topMatches,
EmitStructuredError.
- Convert two direct os.Exit sites in parseCommandLinePassOne (unknown
verb, unknown flag) to return CLIError values; verb-option-error
likewise returns CLIError from pass-one ParseCLIFunc handling.
- parseCommandLinePassOne gains an error return; ParseCommandLine
propagates it.
- entrypoint.go pre-scans os.Args for --errors-json before calling
ParseCommandLine; routes errors to EmitStructuredError or printError.
- did_you_mean: Levenshtein nearest-match over verb catalog, flag
catalog, or the verb own OptionSpec flags (PR3 catalog) for
verb-option-error. Closes the self-correction loop the catalog
enables.
- --errors-json registered in Miscellaneous flags so it appears in
mlr --help and is not treated as an unrecognized flag.
Tests: 16 unit tests covering Levenshtein, topMatches, threshold,
WantErrorsJSON, CLIError interface, and all categorize paths.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* Tier-2 structured verb options: OptionSpec, initial migration (#2098)
PR 3 of the AI-friendly roadmap (plans/plan-2098-llm.md).
Infrastructure:
- Add OptionSpec{Flag,Arg,Type,Desc,Repeatable,Values} to
pkg/transformers/aaa_record_transformer.go alongside TransformerSetup.
Type is one of: bool, string, int, float, csv-list, regex, filename,
format, enum. For type=="enum", Values lists the valid choices.
- Add Options []OptionSpec to TransformerSetup (nil = not yet migrated).
- Emit Options in VerbInfoForJSON (omitempty so unmigrated verbs stay
backward-compatible; agents check key presence for Tier-2 availability).
UsageText is always present as the Tier-1 prose fallback.
- Add VerbOptionsNilCheck() in aaa_verb_options_check.go: progress report
of migrated vs. unmigrated verbs, analogous to FLAG_TABLE.NilCheck().
- Wire verb-options-nil-check into mlr help (internal/docgen section).
Initial migration (5/70 verbs):
- nothing: empty Options (no verb-specific options, explicitly migrated)
- cat: -n (bool), -N (string), -g (csv-list), --filename, --filenum (bool)
- head: -g (csv-list), -n (int)
- tail: -g (csv-list), -n (int)
- tee: -a, -p (bool)
Tests:
- 5 new unit tests in aaa_transformer_json_test.go covering migrated/
unmigrated paths, field population, JSON round-trip, and key-presence.
- Regression test case 0003: mlr help verb-options-nil-check golden output.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* Migrate all 70 verbs to structured OptionSpec; bump catalog schema to v2
Completes the Tier-2 migration started in the previous commit. Every verb
in TRANSFORMER_LOOKUP_TABLE now has a non-nil Options field.
- Workflow-migrated all 65 remaining verbs. Each Setup var now carries
Options: []OptionSpec{...} with Flag/Arg/Type/Desc fields. Verbs with
no verb-specific options (altkv, check, group-like, nothing, etc.) use
an empty slice to signal "migrated but no options."
- Drop `omitempty` from VerbInfoForJSON.Options: empty slices were silently
dropped, making migrated-no-option verbs indistinguishable from unmigrated
ones in JSON. Without omitempty: null=unmigrated, []=migrated-no-options,
[...]= migrated-with-options. Bump catalogSchemaVersion 1→2 for this shape
change.
- Replace the two "unmigrated-verb" unit tests (which used stats1 as an
example) with TestAllVerbsFullyMigrated (asserts every verb has non-nil
Options) and TestAllVerbsHaveOptionsKeyInJSON (asserts every migrated
verb emits the "options" key in JSON).
- Regenerate test/cases/cli-help/0003/expout: now reads
"Verb options migration: 70/70 migrated."
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* remove a transitional helper
* git rms
* Render verb usage Options blocks from structured OptionSpec
Each verb's usage message and its Tier-2 OptionSpec list previously
duplicated the option text. New WriteVerbOptions (aaa_verb_usage.go)
renders the "Options:" block from the specs: aligned flag column,
descriptions word-wrapped at 80, uniform trailing -h|--help line.
- OptionSpec gains Aliases (JSON "aliases") so long-form spellings
like join's --lk|--left-keep-field-names survive in both outputs
- All 70 verbs migrated; options literals hoisted to package-level
vars (usage funcs can't reference their Setup var without a Go
init cycle)
- Hand-written per-option details the specs had condensed away are
merged into Desc, enriching the JSON catalog
- Non-option prose (examples, cross-references, dynamic accumulator
listings) kept verbatim
- Regenerated the six usage-embedding regression expectations and
the two affected doc pages
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Fix pre-existing usage-text bugs surfaced by the OptionSpec migration
- gap: usage said "One of -f or -g is required" but the parser takes
-n or -g
- seqgen: drop description line copy-pasted from cat ("Passes input
records directly to output...") which contradicted "Discards the
input record stream"
- utf8-to-latin1: description read inverted ("from Latin-1 to UTF-8")
- sec2gmtdate: usage said "../c/mlr" instead of "mlr"
- top: document the accepted-but-undocumented --max flag
- stats2: add linreg-pca to the -a enum values, matching the runtime
accumulator table
Regression expectations and docs regenerated accordingly.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Fix check usage sentence order; stats1 usage blank line to usage stream
- check: the description's second and third lines were swapped,
reading "Consumes records without printing any output, / Useful for
doing a well-formatted check on input data. / with the exception
that warnings are printed to stderr."
- stats1: a bare fmt.Println() in the usage func wrote its blank line
to process stdout instead of the usage output stream
Regression expectation and docs regenerated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* Add MT_BYTES mlrval type: foundation and disposition tables
First step toward a first-class bytes type in the DSL (#1231).
Adds MT_BYTES (payload []byte, rendered as lowercase hex in all output
formats, JSON-encoded as a hex string), extends every disposition
matrix/vector with the new row/column -- real cells for comparison,
sorting, and dot-concat of bytes with bytes; type-error stubs
elsewhere -- and adds sweep tests asserting no table has nil cells,
since Go zero-fills short array literals when MT_DIM grows.
Bytes values are not yet constructible from the DSL; b"..." literals
and constructor/codec functions follow in subsequent commits.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Add b"..." bytes-literal syntax to the DSL
Adds a bytes_literal token to the grammar (regenerating the PGPG lexer
and parser) and a BytesLiteralNode in the CST which evaluates to an
MT_BYTES mlrval. Escape handling reuses UnbackslashStringLiteral,
which is already byte-oriented: b"\xff" is the single byte 0xff.
Unlike string literals, bytes literals never participate in
regex-capture replacement. A bare identifier b is unaffected.
Part of #1231.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Add bytes DSL functions: conversions, codecs, and bytes-aware built-ins
- bytes(x) converts strings to bytes; string(b) reinterprets raw bytes
as UTF-8 text (the reverse)
- base64_decode now always returns bytes (superseding the interim
string-or-hex behavior); base64_encode accepts string or bytes
- New hex_encode/hex_decode functions
- is_bytes and asserting_bytes predicates
- md5/sha1/sha256/sha512 accept bytes, hashing the raw payload
- strlen of bytes is the byte count; substr/substr0/substr1 on bytes
slice by byte position and return bytes
The Cyrillic-LDAP scenario from #1231 now works without exec
workarounds: string(base64_decode($x)) recovers the text, and binary
payloads survive undamaged as bytes.
Closes#1231.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Add bytes-type docs and regression cases
Documents the bytes type on the data-types page, regenerates the
function-reference/man-page material, and adds regression coverage:
literal escape forms, operators (concat/compare/slice/sort and
type errors), conversions and codec round-trips, and CSV-to-JSON
output rendering of bytes fields.
Part of #1231.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Reposition MT_BYTES to sort adjacent to MT_STRING in the type enum
MT_BYTES was appended after MT_ABSENT for index stability; move it
right after MT_STRING instead, since that's where it conceptually
belongs and where it already sorts in the cmp disposition matrices.
Mechanically re-derive all ~40 disposition tables in pkg/bifs and
pkg/mlrval accordingly.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix windows CI
* fix merge
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* 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>
* plans/lintfixes.md
* plans/lintfixes.md
* Fix remaining govet lint findings
- Rename MarshalJSON -> FormatAsJSON on Mlrval and Mlrmap (govet
stdmethods): the methods shadowed json.Marshaler with an
incompatible signature.
- Remove unreachable return after exhaustive if-else in
pkg/mlrval/mlrval_collections.go (govet unreachable).
- Update plans/lintfixes.md with current status: 84 findings remain
(50 errcheck, 34 staticcheck).
Part of #2109.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Return nil on successful single-index array unset
removeIndexedOnArray removed the element on the in-bounds path but
then fell through to return an "array index out of bounds for unset"
error, so the success path never returned nil. Callers currently
ignore the error, which masked this; return nil on success so that
upcoming errcheck fixes can propagate the error meaningfully. This
matches removeIndexedOnMap, which returns nil on success.
Add unit tests for RemoveIndexed on arrays.
Part of #2109.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Found with golang.org/x/tools/cmd/deadcode (rooted at cmd/mlr + tests)
and staticcheck U1000; each finding verified by hand before deletion.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Update snapcore/action-build to node24 SHA, drop unused matrix
snapcore/action-build@v1 targets Node.js 20, which is deprecated on
GitHub Actions runners (forced to run on Node.js 24 with a warning).
Upstream has an open PR to update (https://github.com/snapcore/action-build/pull/1)
but it has been stale for a while, so we pin directly to its HEAD SHA
(edf78ca) until upstream merges and cuts a new tag.
Also removes the `strategy.matrix.node-version: [20.x]` block, which
was dead config — there was no `actions/setup-node` step consuming it.
Fixes the warning seen in:
https://github.com/johnkerl/miller/actions/runs/28449033140
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* comment
---------
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>