# tools/core/readers/rule.reader.ts

> 123 lines of code and 38 definitions.

Tree: Coordination tree
Language: typescript
Canonical: https://banes-lab.com/anatomy/coordination#file-coordination-tools-core-readers-rule-reader-ts
Source text: https://banes-lab.com/assets/sources/source.caa83cea1fbca92b92aeaa5d0e4f5bcab4a26db8e13ba412005e1436eaca6d52.generated.txt

## Definitions

- `readExpandedSlugs` (lexical_declaration, line 121, exported)
- `headingSlug` (lexical_declaration, line 83)
- `declarationStart` (lexical_declaration, line 130)
- `isGateChar` (lexical_declaration, line 30)
- `spacesBefore` (lexical_declaration, line 42)
- `gateOf` (lexical_declaration, line 58)
- `slugAfter` (lexical_declaration, line 76)
- `listedRule` (lexical_declaration, line 89)
- `stripGateDeclarations` (lexical_declaration, line 145, exported)
- `readDeclaredRules` (lexical_declaration, line 102, exported)
- `withoutGate` (lexical_declaration, line 136)
- `gateRunEnd` (lexical_declaration, line 50)
- `DeclaredRule` (interface_declaration, line 3, exported)
- `HeadingState` (interface_declaration, line 10)
- `GATE_MARKER` (lexical_declaration, line 15)
- `LOCKED_MARKER` (lexical_declaration, line 17)
- `LIST_SLUG` (lexical_declaration, line 19)
- `SEPARATOR_DOT` (lexical_declaration, line 21)
- `backtickedSlug` (lexical_declaration, line 23)
- `spacesAfter` (lexical_declaration, line 34)
- `start` (lexical_declaration, line 63)
- `value` (lexical_declaration, line 64)
- `hashRun` (lexical_declaration, line 68)
- `cursor` (lexical_declaration, line 69)
- `close` (lexical_declaration, line 77)
- `tail` (lexical_declaration, line 79)
- `hashes` (lexical_declaration, line 84)
- `open` (lexical_declaration, line 85)
- `headedRule` (lexical_declaration, line 95)
- `gate` (lexical_declaration, line 96)
- `out` (lexical_declaration, line 103, exported)
- `heading` (lexical_declaration, line 104, exported)
- `rule` (lexical_declaration, line 112, exported)
- `slug` (lexical_declaration, line 125, exported)
- `before` (lexical_declaration, line 131)
- `dotted` (lexical_declaration, line 132)
- `marker` (lexical_declaration, line 137)
- `end` (lexical_declaration, line 141)

## Uses

- [tools/core/readers/document.reader.ts](https://banes-lab.com/source/coordination/tools/core/readers/document.reader.ts.md)

## Used by

- [tools/core/validators/coverage.validator.ts](https://banes-lab.com/source/coordination/tools/core/validators/coverage.validator.ts.md)

## Source

```typescript
import { unfencedLines } from "./document.reader.ts";

export interface DeclaredRule {
    readonly slug: string;
    readonly gate: string;
    readonly locked: boolean;
    readonly line: number;
}

interface HeadingState {
    readonly slug: string | null;
    readonly locked: boolean;
}

const GATE_MARKER = "gate:";

const LOCKED_MARKER = "(LOCKED)";

const LIST_SLUG = "- `";

const SEPARATOR_DOT = "·";

const backtickedSlug = function backtickedSlug(line: string): string | null {
    const open = line.indexOf("`");
    const close = open === -1 ? -1 : line.indexOf("`", open + 1);
    const slug = close === -1 ? "" : line.slice(open + 1, close);
    return slug.length > 0 ? slug : null;
};

const isGateChar = function isGateChar(char: string): boolean {
    return (char >= "a" && char <= "z") || char === "-";
};

const spacesAfter = function spacesAfter(line: string, from: number): number {
    let cursor = from;
    while (line.charAt(cursor) === " ") {
        cursor += 1;
    }
    return cursor;
};

const spacesBefore = function spacesBefore(line: string, from: number): number {
    let cursor = from;
    while (cursor > 0 && line.charAt(cursor - 1) === " ") {
        cursor -= 1;
    }
    return cursor;
};

const gateRunEnd = function gateRunEnd(line: string, from: number): number {
    let cursor = from;
    while (cursor < line.length && isGateChar(line.charAt(cursor))) {
        cursor += 1;
    }
    return cursor;
};

const gateOf = function gateOf(line: string): string | null {
    const marker = line.lastIndexOf(GATE_MARKER);
    if (marker === -1) {
        return null;
    }
    const start = spacesAfter(line, marker + GATE_MARKER.length);
    const value = line.slice(start, gateRunEnd(line, start));
    return value.length > 0 ? value : null;
};

const hashRun = function hashRun(line: string): number {
    let cursor = 0;
    while (line.charAt(cursor) === "#") {
        cursor += 1;
    }
    return cursor;
};

const slugAfter = function slugAfter(line: string, open: number): string | null {
    const close = line.indexOf("`", open + 1);
    const slug = close === -1 ? "" : line.slice(open + 1, close);
    const tail = line.slice(close + 1).trim();
    return slug.length > 0 && (tail.length === 0 || tail === LOCKED_MARKER) ? slug : null;
};

const headingSlug = function headingSlug(line: string): string | null {
    const hashes = hashRun(line);
    const open = spacesAfter(line, hashes);
    return hashes > 0 && line.charAt(open) === "`" ? slugAfter(line, open) : null;
};

const listedRule = function listedRule(line: string, number: number): DeclaredRule[] {
    const slug = backtickedSlug(line);
    const gate = slug === null ? null : gateOf(line);
    return slug === null || gate === null ? [] : [{ gate, line: number, locked: line.includes(LOCKED_MARKER), slug }];
};

const headedRule = function headedRule(heading: HeadingState, line: string, number: number): DeclaredRule | null {
    const gate = heading.slug === null ? null : gateOf(line);
    return heading.slug === null || gate === null
        ? null
        : { gate, line: number, locked: heading.locked, slug: heading.slug };
};

export const readDeclaredRules = function readDeclaredRules(source: string): DeclaredRule[] {
    const out: DeclaredRule[] = [];
    let heading: HeadingState = { locked: false, slug: null };

    for (const { text: line, number } of unfencedLines(source)) {
        if (line.startsWith("#")) {
            heading = { locked: line.includes(LOCKED_MARKER), slug: headingSlug(line) };
        } else if (line.startsWith(LIST_SLUG)) {
            out.push(...listedRule(line, number));
        } else {
            const rule = headedRule(heading, line, number);
            out.push(...(rule === null ? [] : [rule]));
            heading = rule === null ? heading : { ...heading, slug: null };
        }
    }

    return out;
};

export const readExpandedSlugs = function readExpandedSlugs(source: string): { slug: string; line: number }[] {
    return unfencedLines(source)
        .filter(({ text }) => text.startsWith("#"))
        .flatMap(({ text, number }) => {
            const slug = headingSlug(text);
            return slug === null ? [] : [{ line: number, slug }];
        });
};

const declarationStart = function declarationStart(line: string, marker: number): number {
    const before = spacesBefore(line, marker);
    const dotted = before > 0 && line.charAt(before - 1) === SEPARATOR_DOT ? before - 1 : before;
    return spacesBefore(line, dotted);
};

const withoutGate = function withoutGate(line: string): string {
    const marker = line.lastIndexOf(GATE_MARKER);
    if (marker === -1 || gateOf(line) === null) {
        return line;
    }
    const end = gateRunEnd(line, spacesAfter(line, marker + GATE_MARKER.length));
    return line.slice(0, declarationStart(line, marker)) + line.slice(end);
};

export const stripGateDeclarations = function stripGateDeclarations(source: string): string {
    return source.split("\n").map(withoutGate).join("\n");
};
```
