Liking cljdoc? Tell your friends :D

hive-system.shell.search

Building an argv for a pattern-taking CLI from a Construct.

What this is for

Callers used to hand-assemble a pattern string and hand it to rg. A lookahead in that string reaches rg, which rejects it at runtime with

regex parse error: look-around, including look-ahead and look-behind, is not supported

— a failure the caller can only discover by spawning a process and then parsing English out of stderr. The dialect gate already knows the answer from the shape of the construct, so the refusal belongs here, before the fork, naming the missing capability as data.

With grep the argument is stronger still. GNU grep does not reject what it cannot do: grep -E '\d+' matches a literal d, exit 0, no warning. There the gate is not saving the caller a stderr parse, it is saving them a wrong answer they had no way to notice.

Tools are data, not code

A tool is a Tool map: which dialect it speaks, what it needs in order to speak it, and how a pattern enters its argv. Adding ugrep is a register! call, not an edit to argv — the open set gets a registry, not a case.

What is measured

An argv is a claim about a program's parser, and only that program can settle it. rg, sd and grep are driven for real by the suite, and the separator rule argv applies is what those runs established.

fd is measured too, as of fd 9.0.0: sd's positional shape holds for it, including the negative control — strip the -- and fd reads -dash-[0-9]+ as --max-depth ash-[0-9]+ and exits 2. What that run also established is that the argv was not the whole claim. Debian installs the binary as fdfind, so the shape was right, the program was present, and the command was still unrunnable. Hence executable and spawn-argv: argv states the grammar, and resolving argv[0] against PATH is a separate question with its own refusal.

A PATH grep is frequently ugrep or another superset, which would accept constructs POSIX ERE cannot express. That only ever widens what runs, never narrows it, so emitting strict ERE stays correct whichever binary answers.

Building an argv for a pattern-taking CLI from a Construct.

## What this is for

Callers used to hand-assemble a pattern string and hand it to `rg`. A
lookahead in that string reaches rg, which rejects it at runtime with

  regex parse error: look-around, including look-ahead and look-behind,
  is not supported

— a failure the caller can only discover by spawning a process and then
parsing English out of stderr. The dialect gate already knows the answer
from the shape of the construct, so the refusal belongs here, before the
fork, naming the missing capability as data.

With `grep` the argument is stronger still. GNU grep does not reject what it
cannot do: `grep -E '\d+'` matches a literal `d`, exit 0, no warning. There
the gate is not saving the caller a stderr parse, it is saving them a wrong
answer they had no way to notice.

## Tools are data, not code

A tool is a `Tool` map: which dialect it speaks, what it needs in order to
speak it, and how a pattern enters its argv. Adding `ugrep` is a `register!`
call, not an edit to `argv` — the open set gets a registry, not a `case`.

## What is measured

An argv is a claim about a program's parser, and only that program can
settle it. `rg`, `sd` and `grep` are driven for real by the suite, and the
separator rule `argv` applies is what those runs established.

`fd` is measured too, as of fd 9.0.0: sd's positional shape holds for it,
including the negative control — strip the `--` and fd reads `-dash-[0-9]+`
as `--max-depth ash-[0-9]+` and exits 2. What that run also established is
that the argv was not the whole claim. Debian installs the binary as
`fdfind`, so the shape was right, the program was present, and the command
was still unrunnable. Hence `executable` and `spawn-argv`: `argv` states the
grammar, and resolving argv[0] against PATH is a separate question with its
own refusal.

A PATH `grep` is frequently ugrep or another superset, which would accept
constructs POSIX ERE cannot express. That only ever widens what runs, never
narrows it, so emitting strict ERE stays correct whichever binary answers.
raw docstring

argvclj

(argv tool-id form)
(argv tool-id form {:keys [flags operands]})

Result<vector<string>>: the argv running TOOL-ID against FORM.

FORM is regal's authored syntax or a normalized Construct. If the tool's dialect cannot express it the Result is the dialect's own refusal — :construct/unsupported, with :missing naming the capabilities — and no process is ever spawned.

opts: :flags — argv entries placed before the pattern :operands — argv entries placed after it (rg/grep: paths; sd: the replacement, then files)

Where the separator goes

-- is placed as early as the tool's grammar allows, which is decided by whether a flag introduces the pattern:

flag-introduced pattern — the flag already protects the pattern, so -- guards the operands, and is emitted only when there are operands to guard. positional pattern — -- must precede the PATTERN, or a pattern such as -\d+ is read as a flag. One separator then covers the operands as well.

(argv :rg [:cat [:+ :digit] "-"] {:operands ["src"]}) => (ok ["rg" "--regexp" "\d+-" "--" "src"])

(argv :sd [:+ :digit] {:operands ["N" "f.txt"]}) => (ok ["sd" "--" "\d+" "N" "f.txt"])

(argv :grep [:+ [:class [\0 \9]]] {:operands ["src"]}) => (ok ["grep" "-E" "-e" "[0-9]+" "--" "src"])

Result<vector<string>>: the argv running TOOL-ID against FORM.

FORM is regal's authored syntax or a normalized Construct. If the tool's
dialect cannot express it the Result is the dialect's own refusal —
`:construct/unsupported`, with `:missing` naming the capabilities — and no
process is ever spawned.

opts:
  :flags     — argv entries placed before the pattern
  :operands  — argv entries placed after it (rg/grep: paths; sd: the
               replacement, then files)

## Where the separator goes

`--` is placed as early as the tool's grammar allows, which is decided by
whether a flag introduces the pattern:

  flag-introduced pattern — the flag already protects the pattern, so `--`
                            guards the operands, and is emitted only when
                            there are operands to guard.
  positional pattern      — `--` must precede the PATTERN, or a pattern such
                            as `-\d+` is read as a flag. One separator then
                            covers the operands as well.

(argv :rg [:cat [:+ :digit] "-"] {:operands ["src"]})
=> (ok ["rg" "--regexp" "\\d+-" "--" "src"])

(argv :sd [:+ :digit] {:operands ["N" "f.txt"]})
=> (ok ["sd" "--" "\\d+" "N" "f.txt"])

(argv :grep [:+ [:class [\0 \9]]] {:operands ["src"]})
=> (ok ["grep" "-E" "-e" "[0-9]+" "--" "src"])
sourceraw docstring

built-inclj

Every profile this namespace ships.

Every profile this namespace ships.
sourceraw docstring

executableclj

(executable tool-id)

Result<string>: the name TOOL-ID is actually installed under HERE.

:tool/bin is the canonical name and the one argv emits, because an argv is a claim about a program's GRAMMAR and that claim does not change with packaging. argv[0] does: Debian ships fd as fdfind, so the canonical name resolves to nothing and the argv argv built is correct and unrunnable.

Tries :tool/bin first, then :tool/bin-alts in order, and refuses naming every name it tried — rather than letting the caller meet it as an ENOENT from the fork.

Result<string>: the name TOOL-ID is actually installed under HERE.

`:tool/bin` is the canonical name and the one `argv` emits, because an argv
is a claim about a program's GRAMMAR and that claim does not change with
packaging. argv[0] does: Debian ships fd as `fdfind`, so the canonical name
resolves to nothing and the argv `argv` built is correct and unrunnable.

Tries `:tool/bin` first, then `:tool/bin-alts` in order, and refuses naming
every name it tried — rather than letting the caller meet it as an ENOENT
from the fork.
sourceraw docstring

explainclj

(explain form)

Result<map>: what FORM requires, and which registered TOOLS can run it. :runnable is the subset whose dialect covers the construct; :blocked maps the rest to the capabilities they lack.

Result<map>: what FORM requires, and which registered TOOLS can run it.
`:runnable` is the subset whose dialect covers the construct; `:blocked`
maps the rest to the capabilities they lack.
sourceraw docstring

fdclj

source

grepclj

source

register!clj

(register! tool)

Register TOOL under its id, replacing any prior one. Result<Tool>. A profile that does not conform to the Tool schema is refused.

Register TOOL under its id, replacing any prior one. Result<Tool>.
A profile that does not conform to the Tool schema is refused.
sourceraw docstring

register-built-in!clj

(register-built-in!)

Register every built-in profile. Returns the vector of Results.

Register every built-in profile. Returns the vector of Results.
sourceraw docstring

registeredclj

(registered)

Snapshot of {tool-id -> Tool}.

Snapshot of {tool-id -> Tool}.
sourceraw docstring

ripgrepclj

source

runnable?clj

(runnable? tool-id form)

Result<boolean>: can TOOL-ID's dialect express FORM? Answered from the shape — nothing is compiled and nothing is spawned.

Result<boolean>: can TOOL-ID's dialect express FORM? Answered from the
shape — nothing is compiled and nothing is spawned.
sourceraw docstring

sdclj

source

spawn-argvclj

(spawn-argv tool-id form)
(spawn-argv tool-id form opts)

Result<vector<string>>: argv, with argv[0] resolved against PATH — the argv to actually spawn.

Two refusals, both before the fork: the dialect cannot express the construct (:construct/unsupported), or the program is not installed under any name it goes by (:search/tool-not-installed).

The construct is checked FIRST, deliberately. That refusal is a fact about the command line and reads the same on every machine; the PATH one is a fact about this host. Resolving first would let the same call fail two different ways depending on what happens to be installed.

Use argv to reason about the command line, this to run it.

Result<vector<string>>: `argv`, with argv[0] resolved against PATH — the
argv to actually spawn.

Two refusals, both before the fork: the dialect cannot express the construct
(`:construct/unsupported`), or the program is not installed under any name
it goes by (`:search/tool-not-installed`).

The construct is checked FIRST, deliberately. That refusal is a fact about
the command line and reads the same on every machine; the PATH one is a fact
about this host. Resolving first would let the same call fail two different
ways depending on what happens to be installed.

Use `argv` to reason about the command line, this to run it.
sourceraw docstring

Toolclj

A pattern-taking CLI, as data.

:tool/flag how the pattern is introduced, or nil if it is positional. A flag such as --regexp also stops a pattern that begins with - from being read as a flag. :tool/separator the tool's end-of-flags separator, or nil if it has none. :tool/dialect-flags what the tool needs in order to speak :tool/dialect at all — grep reads BRE until -E says otherwise. Not derivable from the dialect: it is a fact about this binary's CLI. :tool/bin-alts other names the SAME program is installed under. Debian ships fd as fdfind, the name fd having already gone to fdclone. A packaging fact, derivable from neither :tool/bin nor the dialect, and the reason an argv can be perfectly shaped and still unrunnable.

Where the separator GOES is DERIVED from :tool/flag — see argv. A field declaring it would be a second copy of a decision :tool/flag already makes.

A pattern-taking CLI, as data.

:tool/flag           how the pattern is introduced, or nil if it is
                     positional. A flag such as `--regexp` also stops a
                     pattern that begins with `-` from being read as a flag.
:tool/separator      the tool's end-of-flags separator, or nil if it has none.
:tool/dialect-flags  what the tool needs in order to speak `:tool/dialect` at
                     all — `grep` reads BRE until `-E` says otherwise. Not
                     derivable from the dialect: it is a fact about this
                     binary's CLI.
:tool/bin-alts       other names the SAME program is installed under. Debian
                     ships fd as `fdfind`, the name `fd` having already gone
                     to fdclone. A packaging fact, derivable from neither
                     `:tool/bin` nor the dialect, and the reason an argv can
                     be perfectly shaped and still unrunnable.

Where the separator GOES is DERIVED from `:tool/flag` — see `argv`. A field
declaring it would be a second copy of a decision `:tool/flag` already makes.
sourceraw docstring

toolclj

(tool id)

Result<Tool> for ID.

Result<Tool> for ID.
sourceraw docstring

ToolIdclj

Stable keyword naming a registered tool (:rg, :sd, :fd).

Distinct from a DialectId even where the two spell the same word: a tool is a binary with an argv grammar, a dialect is a regex language, and one dialect serves many tools.

Stable keyword naming a registered tool (:rg, :sd, :fd).

Distinct from a DialectId even where the two spell the same word: a tool is a
binary with an argv grammar, a dialect is a regex language, and one dialect
serves many tools.
sourceraw docstring

unregister!clj

(unregister! id)

Drop the tool registered under ID. Returns it, or nil.

Drop the tool registered under ID. Returns it, or nil.
sourceraw docstring

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close