# core/converters/surface.converter.ts

> 88 lines of code and 21 definitions.

Tree: Bane's Lab Build Scripts
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/build-scripts#file-build-scripts-core-converters-surface-converter-ts
Source text: https://banes-lab.com/source/build-scripts/core/converters/surface.converter.ts.txt

Listed in [core/converters](https://banes-lab.com/api/source/build-scripts/core/converters.md), after [core/converters/style.converter.ts](https://banes-lab.com/source/build-scripts/core/converters/style.converter.ts.md) and before [core/converters/tab.converter.ts](https://banes-lab.com/source/build-scripts/core/converters/tab.converter.ts.md).

## Definitions

- `venueView` (lexical_declaration, line 51, exported)
- `isViewLine` (lexical_declaration, line 41)
- `appendLine` (lexical_declaration, line 84, exported)
- `QUOTE` (lexical_declaration, line 11)
- `SPACE` (lexical_declaration, line 13)
- `changedRegion` (lexical_declaration, line 15, exported)
- `shorter` (lexical_declaration, line 19, exported)
- `prefix` (lexical_declaration, line 20, exported)
- `suffix` (lexical_declaration, line 24, exported)
- `to` (lexical_declaration, line 28, exported)
- `ViewPlace` (interface_declaration, line 34)
- `tail` (lexical_declaration, line 52, exported)
- `kept` (lexical_declaration, line 53, exported)
- `fenced` (lexical_declaration, line 54, exported)
- `inRecord` (lexical_declaration, line 55, exported)
- `afterKept` (lexical_declaration, line 56, exported)
- `markedLines` (lexical_declaration, line 69, exported)
- `quoted` (lexical_declaration, line 76)
- `commandLine` (lexical_declaration, line 80, exported)
- `recordingsOf` (lexical_declaration, line 88, exported)
- `grouped` (lexical_declaration, line 92, exported)

## Contained in

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

## Used by

- [core/adapters/surface.adapter.ts](https://banes-lab.com/source/build-scripts/core/adapters/surface.adapter.ts.md)

## Linked from

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

## Source

```typescript
import {
    CODE_FENCE,
    RECORD_CLOSE,
    RECORD_OPEN,
    ROSTER_LINES,
    SECTION_BANNER,
} from "#configuration/constants/surface.constants";
import type { FigureFrame, Region, ScenarioTool } from "#types/surface.types";
import type { SurfaceFigureName, SurfaceLine, SurfaceRecording } from "@banes-lab/web/types/surface.types.ts";

const QUOTE = '"';

const SPACE = " ";

export const changedRegion = function changedRegion(
    before: readonly string[],
    after: readonly string[],
): Region | null {
    const shorter = Math.min(before.length, after.length);
    let prefix = 0;
    while (prefix < shorter && before[prefix] === after[prefix]) {
        prefix += 1;
    }
    let suffix = 0;
    while (suffix < shorter - prefix && before[before.length - 1 - suffix] === after[after.length - 1 - suffix]) {
        suffix += 1;
    }
    const to = after.length - suffix;
    return prefix === after.length && before.length === after.length
        ? null
        : { from: prefix, to: Math.max(prefix, to) };
};

interface ViewPlace {
    readonly fenced: boolean;
    readonly inRecord: boolean;
    readonly inTail: boolean;
    readonly afterKept: boolean;
}

const isViewLine = function isViewLine(line: string, place: ViewPlace): boolean {
    if (place.inTail || place.inRecord) {
        return true;
    }
    if (line === "") {
        return place.afterKept;
    }
    return !place.fenced && ROSTER_LINES.some((roster) => line.startsWith(roster));
};

export const venueView = function venueView(lines: readonly string[]): string[] {
    const tail = lines.findLastIndex((line) => line.startsWith(SECTION_BANNER));
    const kept: string[] = [];
    let fenced = false;
    let inRecord = false;
    let afterKept = false;
    for (const [index, line] of lines.entries()) {
        fenced = fenced !== line.startsWith(CODE_FENCE);
        inRecord ||= !fenced && line.startsWith(RECORD_OPEN);
        afterKept = isViewLine(line, { afterKept, fenced, inRecord, inTail: tail !== -1 && index >= tail });
        if (afterKept) {
            kept.push(line);
        }
        inRecord &&= !line.startsWith(RECORD_CLOSE);
    }
    return kept;
};

export const markedLines = function markedLines(lines: readonly string[], highlight: Region | null): SurfaceLine[] {
    return lines.map((text, at) => ({
        changed: highlight !== null && at >= highlight.from && at < highlight.to,
        text,
    }));
};

const quoted = function quoted(argument: string): string {
    return argument.includes(SPACE) ? QUOTE + argument + QUOTE : argument;
};

export const commandLine = function commandLine(tool: ScenarioTool, args: readonly string[]): string {
    return `npm run ${tool} -- ${args.map(quoted).join(SPACE)}`;
};

export const appendLine = function appendLine(text: string, file: string): string {
    return `echo ${QUOTE}${text}${QUOTE} >> ${file}`;
};

export const recordingsOf = function recordingsOf(
    frames: readonly FigureFrame[],
    inputs: string,
): Map<SurfaceFigureName, SurfaceRecording> {
    const grouped = Map.groupBy(frames, (entry) => entry.figure);
    return new Map(
        [...grouped].map(([figure, entries]) => [
            figure,
            { figure, frames: entries.map((entry) => entry.frame), inputs, opening: entries.at(0)?.before ?? [] },
        ]),
    );
};
```
