Commit graph

30 commits

Author SHA1 Message Date
John Kerl
72ffcb3414
Support %a %A %e %h %c %x %X in strptime (#1518) (#2179)
Miller's strftime delegates to the full-featured lestrrat-go/strftime
library, but strptime uses an in-tree fork of a small subset-only
package, so strftime output couldn't always be parsed back by
strptime with the same format string. This adds the format codes
needed to round-trip common formats like "%a %b %e %T %Y":

* %a / %A (weekday name) and %h (alias of %b): straightforward
  formatMap entries, same pattern as the existing %b/%B.
* %e (space-padded day of month): needed dedicated width-detection
  logic, since %e's own optional leading pad space is otherwise
  indistinguishable from a literal separator space when the code
  searches for the next literal text to bound a field -- this was
  the root cause of the round-trip failure in the linked issue.
* %c, %x, %X: shorthand aliases (expanding to %a %b %e %H:%M:%S %Y,
  %m/%d/%y, and %H:%M:%S respectively), same mechanism already used
  for %T/%D/%F/%R/%r.

Also documents the %D/%F/%r/%R/%T shorthands in the strptime table
in reference-dsl-time.md.in, which were already supported but
missing from the docs -- this was the exact confusion reported in
the discussion linked from #1518.

Round-trip tests added in pkg/bifs/datetime_test.go assert
strptime(strftime(t, fmt), fmt) == t across the newly-supported
formats, plus table-driven cases in
pkg/pbnjay-strptime/strptime_test.go covering %e's edge cases
(padded/unpadded single digit, double digit, adjacent to another
code with no separator, at end of string).

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-08 16:42:03 -04:00
John Kerl
a55327c7ef
Add sparkline DSL function and mlr sparkline verb (#166) (#2177)
Adds a sparkline(array|map) built-in that renders numeric values as a
Unicode block-character string (▁▂▃▄▅▆▇█), plus a new `mlr sparkline`
verb that summarizes each field's values in record order -- useful for
eyeballing trends without external plotting tools.

Also adds `-s` to `mlr histogram` to sparkline a field's binned counts
(its distribution shape) rather than emitting one record per bin. This
is a different chart from `mlr sparkline` (order-independent binning
vs. record-order values), and the docs for each cross-reference the
other to avoid conflating them.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 15:47:01 -04:00
John Kerl
1004ba5097
DSL format function: support 1-based positional placeholders like {1} (#1650) (#2160)
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>
2026-07-06 15:15:33 -04:00
John Kerl
41f5188bd0
Add mlr mcp MCP server + agent playbook, with a --no-shell gate (#2098 PR7) (#2133)
* 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>
2026-07-03 19:41:49 -04:00
John Kerl
12c96298b9
Add a first-class bytes type to the DSL, with b"..." literals and base64/hex codecs (#2122)
* 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>
2026-07-03 11:58:44 -04:00
John Kerl
91eaff1341
Lint round 5+6: staticcheck and errcheck to zero (#2130)
* 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>
2026-07-03 11:42:08 -04:00
John Kerl
2bec10fc54
Fix govet lint findings; fix masked unset-on-array error path (#2129)
* 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>
2026-07-03 10:31:08 -04:00
John Kerl
b817040747
Strip dead code from pkg/ (#2121)
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>
2026-07-01 19:32:13 -04:00
John Kerl
d56eafff97
Convert if/else-if chains to typed switch statements (staticcheck QF1003) (#2112)
Replaces 100+ if/else-if chains on a single variable with tagged switch
statements across 72 files. The bulk are transformer option-parsing loops
(switch on opt string), plus a handful of value-dispatch sites in mlrval,
dsl/cst, repl, lib, auxents, and bifs. One case (surv.go) required a
labeled break to preserve the loop-exit behavior of the original else branch.

Fixes staticcheck QF1003 findings.

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-06-28 18:27:42 -04:00
John Kerl
7e1c9b4119
Next batch of lint fixes (#2108) 2026-06-28 17:36:36 -04:00
John Kerl
1bb9ba12c2
Fix mlr -s shebang doc and reject arrays/maps in contains/index (#1658) (#2058)
The shebang example `#!/usr/bin/env mlr -s` does not work on Linux
because env(1) does not accept arguments to the interpreter; use
`#!/usr/bin/env -S mlr -s` instead.

`contains` and `index` previously stringified their inputs silently,
so `contains([1,2], $foo)` would match against the literal string
`[1, 2]` and produce surprising results. Both now return a type-error
when either argument is an array or map; help text points users at
`any(arr, func(e){return e == x})` for membership tests.
2026-05-17 11:40:39 -04:00
Balki
549864bf38
Fix crash on array compare containing absent values (#2039) 2026-04-17 09:50:34 -04:00
Balki
c88142a9e2
Add hasvalue builtin function (#2026) 2026-04-08 08:33:12 -04:00
John Kerl
56add7d1f0
Fix #1784: inconsistent mapexcept behavior (#2004)
* Fix #1784

* test/cases/dsl-mapsum-mapdiff-mapexcept/0033/
2026-03-02 23:41:32 -05:00
Stephen Kitt
f0a7c5832f
Performance and style fixes (#1981)
* Switch to integer ranges in for loops

Signed-off-by: Stephen Kitt <steve@sk2.org>

* Switch to slices functions where appropriate

A number of utility functions can be replaced outright; since Miller
can technically be used as a library, these are deprecated rather than
removed. go:fix directives ensure that they can be replaced
automatically.

Signed-off-by: Stephen Kitt <steve@sk2.org>

* Switch to reflect.TypeFor

This is slightly more efficient than TypeOf when the type is known at
compile time.

Signed-off-by: Stephen Kitt <steve@sk2.org>

* Switch to strings.SplitSeq instead of strings.Split

SplitSeq results in fewer allocations.

Signed-off-by: Stephen Kitt <steve@sk2.org>

* Drop obsolete build directives

Signed-off-by: Stephen Kitt <steve@sk2.org>

* Use min/max instead of explicit comparisons

Signed-off-by: Stephen Kitt <steve@sk2.org>

* Append slices instead of looping

Signed-off-by: Stephen Kitt <steve@sk2.org>

---------

Signed-off-by: Stephen Kitt <steve@sk2.org>
2026-02-18 09:19:31 -05:00
John Kerl
5eb40c9e7b
Multiple style updates (#1974)
* Comment style

* IRecordTransformer -> RecordTransfomer

* make fmt

* else-return style mod

* snake-case -> camel-case

* Remove redundant err = nil and similar zero-value initializations.

* redundant break;

* bugfix

* neaten

* typofix

* simplify/standardize init of zero-length slices

* Standardize fmt.Fprintf w/ errors

* fix double print of "mlr:"

* neatening

* Uniformize error messages

* make docs

* avoid shadowing package names

* shorten some receiver names
2026-02-16 15:49:21 -05:00
John Kerl
a3b5d25933
Neaten some comments (#1973)
* comment-neaten

* comment-neaten

* more
2026-02-16 13:38:49 -05:00
John Kerl
e143857b70
Genericize lib.OrderedMap (#1948) 2026-02-01 16:08:43 -05:00
John Kerl
100166532c
Fix joinv with "" separator (#1794)
* codemod per se

* unit-test coverage
2025-05-01 17:08:55 -04:00
John Kerl
41649bf4f9
Static-check fixes from @lespea #1657, batch 7/n (#1709) 2024-10-27 12:11:28 -04:00
John Kerl
8c791f5466
Static-check fixes from @lespea #1657, batch 4/n (#1706)
* Static-check fixes from @lespea #1657, batch 2/n

* Static-check fixes from @lespea #1657, batch 3/n

* Static-check fixes from @lespea #1657, batch 4/n
2024-10-27 12:00:25 -04:00
John Kerl
cc8a3c4b4e
Static-check fixes from @lespea #1657, batch 2/n (#1704) 2024-10-27 11:50:15 -04:00
Adam Lesperance
085e831668
The package version must match the major tag version (#1654)
* Update package version

* Update makefile targets

* Update readme packages

* Remaining old packages via rg/sd
2024-09-20 12:10:11 -04:00
John Kerl
f33c0b2cd6
Error in splita/splitax when field contains a single non-string value (#1629) 2024-08-25 19:00:24 -04:00
John Kerl
5ac48516f7
Add a stat DSL function (#1560)
* Add a `stat` DSL function [WIP]

* artifacts from `make dev`

* regression test
2024-05-09 18:39:44 -04:00
camcui
12480c4ab5
chore: fix function name in comment (#1543)
Signed-off-by: camcui <cuishua@sina.cn>
2024-04-12 09:38:31 -04:00
John Kerl
d2559b8387
Have clean_whitespace re-run type inference (#1464)
* Have `clean_whitespace` re-infer types

* make dev output

* unit-test files

* drive-by typofix

* make dev output
2024-01-01 18:39:27 -05:00
John Kerl
c6b745537a
New strmatch/strmatchx DSL functions (#1448)
* New `match`/`matchx` DSL functions

* unit-test cases

* match/matchx -> strmatch/strmatchx

* help strings for strmatch and strmatchx

* update regex doc page re strmatch/strmatchx

* unit-test update
2023-12-19 14:34:54 -05:00
John Kerl
1ae670fd4a
Rename internal regex functions (#1446) 2023-12-17 12:46:28 -05:00
John Kerl
268a96d002
Export library code in pkg/ (#1391)
* Export library code in `pkg/`

* new doc page
2023-09-10 17:15:13 -04:00