# Inject custom commands

Write an argspec, validate it, and register a custom command so the parser reads its arguments correctly.

https://texform.dev/docs/inject-custom-commands

The parser knows the argument structure of every command in its packages, but real corpora are full of project macros the knowledge base has never seen. Left unknown, a macro like `\mycmd[opt]{body}` parses as a bare command and its bracketed argument leaks out as loose tokens. You fix that by registering the command yourself: describe its signature as an argspec string, add it to a knowledge base as a context item, and a parser using that knowledge base reads its arguments exactly as if the command were built in.

The workflow has three steps: write the argspec, check it with `validate_argspec`, then build a context item and pass it to the knowledge base the parser uses. The example registers `\mycmd` with the signature `o m` — one optional bracketed argument followed by one mandatory group.

**Python**

```python
import texform

# 1. Write the signature and confirm it parses to the slots you expect.
check = texform.validate_argspec("o m")
print("valid:", check["valid"], "arg_count:", check["arg_count"])

# 2. Build a context item for the command. Python keys are snake_case.
item = {
    "target": "command",
    "name": "mycmd",
    "kind": "prefix",
    "allowed_mode": "math",
    "argspec": "o m",
}

# 3. Add it to a knowledge base, build a parser on it, and parse a formula.
kb = texform.KnowledgeBase(items=[item])
parser = texform.Parser(knowledge_base=kb)
doc = parser.parse(r"\mycmd[opt]{body} + \mycmd{bare}")["document"]
print("latex:", doc.to_latex())

for node in doc.find_commands("mycmd"):
    slots = [None if s is None else s["kind"] for s in node.arg_slots()]
    print("slots:", slots)
```

```text
valid: True arg_count: 2
latex: \mycmd [ o p t ] { b o d y } + \mycmd { b a r e }
slots: ['Math', 'Math']
slots: [None, 'Math']
```

**JavaScript**

```js
import { KnowledgeBase, Parser, validateArgspec } from 'texform';

// 1. Write the signature and confirm it parses to the slots you expect.
const check = validateArgspec('o m');
console.log('valid:', check.valid, 'argCount:', check.argCount);

// 2. Build a context item for the command. JavaScript keys are camelCase.
const item = {
  target: 'command',
  name: 'mycmd',
  kind: 'prefix',
  allowedMode: 'math',
  argspec: 'o m',
};

// 3. Add it to a knowledge base, build a parser on it, and parse a formula.
const knowledgeBase = new KnowledgeBase({ items: [item] });
const parser = new Parser({ knowledgeBase });
const doc = parser.parse('\\mycmd[opt]{body} + \\mycmd{bare}').document;
console.log('latex:', doc.toLatex());

for (const node of doc.findCommands('mycmd')) {
  const slots = node.argSlots().map((s) => (s === null ? null : s.kind));
  console.log('slots:', JSON.stringify(slots));
}
```

```text
valid: true argCount: 2
latex: \mycmd [ o p t ] { b o d y } + \mycmd { b a r e }
slots: ["Math","Math"]
slots: [null,"Math"]
```

The signature is honored: `\mycmd[opt]{body}` fills both slots, while `\mycmd{bare}` leaves the optional slot empty (`None` in Python, `null` in JavaScript) and fills only the mandatory one. The parser bound `[opt]` as the optional argument instead of letting it fall through as stray characters, which is the whole point of registering the command.

Commands are not the only thing you can inject. A context item is a tagged record, and the `target` field selects one of three kinds: `"command"` as above, `"environment"` for a custom `\begin{...}` block (which also takes a `body_mode`, since an environment body can be math or text), and `"delimiter"` for a delimiter-control name usable after `\left` and `\right`. They all go into the same `items` list of the `KnowledgeBase` constructor.

```python
import texform

item = {
    "target": "environment",
    "name": "myenv",
    "allowed_mode": "math",
    "body_mode": "math",
    "argspec": "m",
}

kb = texform.KnowledgeBase(items=[item])
doc = texform.Parser(knowledge_base=kb).parse(r"\begin{myenv}{c} x \end{myenv}")["document"]
print(doc.to_latex())
```

```text
\begin {myenv} { c } x \end {myenv}
```

What you cannot inject is a rewrite rule. Context items extend the parser's knowledge of *what a command's arguments look like*; they do not tell the transform engine what to turn that command into. The rule set is compiled in, and the only user-facing control over it is switching rules off — see [Disable a rewrite rule](https://texform.dev/docs/disable-a-rule).

For the full argspec notation — every slot kind, delimited forms, and the extensions to xparse — see the [argspec reference](https://texform.dev/docs/argspec); for the context-item shape and construction options, see the [KnowledgeBase API](https://texform.dev/api/python/knowledge-base).

## Next steps [#next-steps]

- [Argspec reference](https://texform.dev/docs/argspec) — Every slot kind, delimited form, and TeXForm extension to xparse notation.

- [KnowledgeBase API](https://texform.dev/api/python/knowledge-base) — Context-item shapes and the rest of the knowledge-base construction options.

- [Disable a rewrite rule](https://texform.dev/docs/disable-a-rule) — The one piece of the transform engine you can adjust from the outside.

- [Clean OCR/MER output](https://texform.dev/docs/clean-ocr-mer-output) — Find the unknown commands worth registering in the first place.

