# core/converters/invocation.converter.ts

> 83 lines of code and 17 definitions.

Tree: Argv Contract
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/argv#file-argv-core-converters-invocation-converter-ts
Source text: https://banes-lab.com/source/argv/core/converters/invocation.converter.ts.txt

Listed in [core/converters](https://banes-lab.com/api/source/argv/core/converters.md).

## Definitions

- `refused` (lexical_declaration, line 21)
- `withFlag` (lexical_declaration, line 25)
- `takeFlag` (lexical_declaration, line 30)
- `takeWord` (lexical_declaration, line 52)
- `restBegins` (lexical_declaration, line 47)
- `parseArgv` (lexical_declaration, line 76, exported)
- `arityRefusal` (lexical_declaration, line 65)
- `Scan` (interface_declaration, line 12)
- `seen` (lexical_declaration, line 26)
- `flag` (lexical_declaration, line 31)
- `declared` (lexical_declaration, line 66)
- `variadic` (lexical_declaration, line 67)
- `given` (lexical_declaration, line 68)
- `required` (lexical_declaration, line 72)
- `scan` (lexical_declaration, line 77, exported)
- `at` (lexical_declaration, line 78, exported)
- `reason` (lexical_declaration, line 86, exported)

## Contained in

- [core/converters](https://banes-lab.com/anatomy/argv/folder-argv-core-converters.md)

## Uses

- [configuration/strings/invocation.strings.ts](https://banes-lab.com/source/argv/configuration/strings/invocation.strings.ts.md)
- [core/formatters/invocation.formatter.ts](https://banes-lab.com/source/argv/core/formatters/invocation.formatter.ts.md)

## Linked from

- [configuration/strings](https://banes-lab.com/anatomy/argv/folder-argv-configuration-strings.md)
- [core/converters](https://banes-lab.com/anatomy/argv/folder-argv-core-converters.md)
- [core/formatters](https://banes-lab.com/anatomy/argv/folder-argv-core-formatters.md)

## Source

```typescript
import type { ArgvOutcome, ArgvSpec } from "#types/invocation.types";
import { FLAG_PREFIX, HELP_FLAGS } from "#configuration/constants/invocation.constants";
import {
    missingValue,
    repeatedFlag,
    tooFewPositionals,
    tooManyPositionals,
    undeclaredFlag,
} from "#configuration/strings/invocation.strings";
import { usageOf } from "#core/formatters/invocation.formatter";

interface Scan {
    readonly flags: ReadonlyMap<string, readonly string[]>;
    readonly positionals: readonly string[];
    readonly rest: readonly string[];
    readonly refused: string | null;
    readonly help: boolean;
    readonly consumed: number;
}

const refused = function refused(scan: Scan, reason: string): Scan {
    return { ...scan, consumed: 1, refused: scan.refused ?? reason };
};

const withFlag = function withFlag(scan: Scan, word: string, value: string, consumed: number): Scan {
    const seen = scan.flags.get(word) ?? [];
    return { ...scan, consumed, flags: new Map([...scan.flags, [word, [...seen, value]]]) };
};

const takeFlag = function takeFlag(scan: Scan, spec: ArgvSpec, word: string, next: string | undefined): Scan {
    const flag = spec.flags.find((held) => held.name === word);
    if (flag === undefined) {
        return refused(scan, undeclaredFlag(word, spec.command));
    }
    if ((scan.flags.get(word) ?? []).length > 0 && flag.repeatable !== true) {
        return refused(scan, repeatedFlag(word));
    }
    if (!flag.takesValue) {
        return withFlag(scan, word, "", 1);
    }
    if (next === undefined || next.startsWith(FLAG_PREFIX)) {
        return refused(scan, missingValue(word));
    }
    return withFlag(scan, word, next, 2);
};

const restBegins = function restBegins(scan: Scan, spec: ArgvSpec): boolean {
    const declared = spec.positionals ?? [];
    return spec.rest !== undefined && declared.at(-1)?.variadic !== true && scan.positionals.length === declared.length;
};

const takeWord = function takeWord(scan: Scan, spec: ArgvSpec, word: string, next: string | undefined): Scan {
    if (restBegins(scan, spec)) {
        return { ...scan, consumed: 1, rest: [...scan.rest, word] };
    }
    if (HELP_FLAGS.has(word)) {
        return { ...scan, consumed: 1, help: true };
    }
    if (!word.startsWith(FLAG_PREFIX)) {
        return { ...scan, consumed: 1, positionals: [...scan.positionals, word] };
    }
    return takeFlag(scan, spec, word, next);
};

const arityRefusal = function arityRefusal(scan: Scan, spec: ArgvSpec): string | null {
    const declared = spec.positionals ?? [];
    const variadic = declared.at(-1)?.variadic === true;
    const given = scan.positionals.length;
    if (!variadic && given > declared.length) {
        return tooManyPositionals(given, declared.length, scan.positionals);
    }
    const required = declared.filter((held) => held.optional !== true).length;
    return given < required ? tooFewPositionals(given, required) : null;
};

export const parseArgv = function parseArgv(spec: ArgvSpec, argv: readonly string[]): ArgvOutcome {
    let scan: Scan = { consumed: 0, flags: new Map(), help: false, positionals: [], refused: null, rest: [] };
    let at = 0;
    while (at < argv.length && !scan.help) {
        scan = takeWord(scan, spec, argv[at] ?? "", argv[at + 1]);
        at += scan.consumed;
    }
    if (scan.help) {
        return { kind: "help", usage: usageOf(spec) };
    }
    const reason = scan.refused ?? arityRefusal(scan, spec);
    if (reason !== null) {
        return { kind: "refused", reason, usage: usageOf(spec) };
    }
    return { argv: { flags: scan.flags, positionals: scan.positionals, rest: scan.rest }, kind: "parsed" };
};
```
