# core/converters/index.converter.ts

> 155 lines of code and 45 definitions.

Tree: Bane's Lab Deploy
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/deploy#file-deploy-core-converters-index-converter-ts
Source text: https://banes-lab.com/source/deploy/core/converters/index.converter.ts.txt

Listed in [core/converters](https://banes-lab.com/api/source/deploy/core/converters.md), after [core/converters/failure.converter.ts](https://banes-lab.com/source/deploy/core/converters/failure.converter.ts.md) and before [core/converters/markdown.converter.ts](https://banes-lab.com/source/deploy/core/converters/markdown.converter.ts.md).

## Definitions

- `newest` (lexical_declaration, line 113)
- `relationsOf` (lexical_declaration, line 53)
- `compareVersions` (lexical_declaration, line 102, exported)
- `upstreamOf` (lexical_declaration, line 117, exported)
- `fileOf` (lexical_declaration, line 127, exported)
- `dependsOn` (lexical_declaration, line 138, exported)
- `stanzaAt` (lexical_declaration, line 154, exported)
- `installedOf` (lexical_declaration, line 162, exported)
- `serverTarget` (lexical_declaration, line 131, exported)
- `companionOf` (lexical_declaration, line 146, exported)
- `stanzasOf` (lexical_declaration, line 61, exported)
- `FIELD_MARK` (lexical_declaration, line 10)
- `CONTINUATION` (lexical_declaration, line 11)
- `CARRIAGE` (lexical_declaration, line 12)
- `LIST_MARK` (lexical_declaration, line 13)
- `CHOICE_MARK` (lexical_declaration, line 14)
- `CONSTRAINT_OPEN` (lexical_declaration, line 15)
- `CONSTRAINT_CLOSE` (lexical_declaration, line 16)
- `EXACT_MARK` (lexical_declaration, line 17)
- `REVISION_MARK` (lexical_declaration, line 18)
- `CODENAME_MARK` (lexical_declaration, line 19)
- `PATH_MARK` (lexical_declaration, line 20)
- `DIGITS` (lexical_declaration, line 21)
- `BLOCK_MARK` (lexical_declaration, line 22)
- `indexUrl` (lexical_declaration, line 24, exported)
- `fieldsOf` (lexical_declaration, line 28)
- `relationOf` (lexical_declaration, line 39)
- `trimmed` (lexical_declaration, line 40)
- `open` (lexical_declaration, line 41)
- `close` (lexical_declaration, line 45)
- `constraint` (lexical_declaration, line 46)
- `name` (lexical_declaration, line 68, exported)
- `version` (lexical_declaration, line 69, exported)
- `filename` (lexical_declaration, line 70, exported)
- `sha256` (lexical_declaration, line 71, exported)
- `provides` (lexical_declaration, line 75, exported)
- `numbersOf` (lexical_declaration, line 89)
- `runs` (lexical_declaration, line 90)
- `last` (lexical_declaration, line 92)
- `[leftNumbers, rightNumbers]` (lexical_declaration, line 103, exported)
- `difference` (lexical_declaration, line 105, exported)
- `codenameOf` (lexical_declaration, line 122, exported)
- `at` (lexical_declaration, line 123, exported)
- `fields` (lexical_declaration, line 163, exported)
- `isCurrent` (lexical_declaration, line 171, exported)

## Contained in

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

## Used by

- [core/pipelines/setup.pipeline.ts](https://banes-lab.com/source/deploy/core/pipelines/setup.pipeline.ts.md)
- [core/steps/setup.step.ts](https://banes-lab.com/source/deploy/core/steps/setup.step.ts.md)

## Linked from

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

## Source

```typescript
import {
    CONFIGURED_STATUS,
    NGINX_PACKAGE,
    PACKAGE_STATUS_FIELD,
    PACKAGE_VERSION_FIELD,
} from "#configuration/constants/nginx.constants";
import type { InstalledPackage, PackageSource, PackageStanza, Relation } from "#types/deployment.types";
import { LINE_BREAK } from "#configuration/constants/deployment.constants";

const FIELD_MARK = ": ";
const CONTINUATION = " ";
const CARRIAGE = "\r";
const LIST_MARK = ",";
const CHOICE_MARK = "|";
const CONSTRAINT_OPEN = "(";
const CONSTRAINT_CLOSE = ")";
const EXACT_MARK = "=";
const REVISION_MARK = "-";
const CODENAME_MARK = "~";
const PATH_MARK = "/";
const DIGITS = "0123456789";
const BLOCK_MARK = LINE_BREAK + LINE_BREAK;

export const indexUrl = function indexUrl(source: PackageSource, codename: string, architecture: string): string {
    return `${source.base}dists/${codename}/${source.component}/binary-${architecture}/Packages`;
};

const fieldsOf = function fieldsOf(block: string): ReadonlyMap<string, string> {
    const fields = new Map<string, string>();
    for (const line of block.split(LINE_BREAK)) {
        const at = line.indexOf(FIELD_MARK);
        if (!line.startsWith(CONTINUATION) && at > 0) {
            fields.set(line.slice(0, at), line.slice(at + FIELD_MARK.length).trim());
        }
    }
    return fields;
};

const relationOf = function relationOf(text: string): Relation {
    const trimmed = text.trim();
    const open = trimmed.indexOf(CONSTRAINT_OPEN);
    if (open === -1) {
        return { exact: null, name: trimmed };
    }
    const close = trimmed.indexOf(CONSTRAINT_CLOSE, open);
    const constraint = trimmed.slice(open + 1, close === -1 ? trimmed.length : close).trim();
    return {
        exact: constraint.startsWith(EXACT_MARK) ? constraint.slice(EXACT_MARK.length).trim() : null,
        name: trimmed.slice(0, open).trim(),
    };
};

const relationsOf = function relationsOf(value: string | undefined): readonly Relation[] {
    return (value ?? "")
        .split(LIST_MARK)
        .flatMap((clause) => clause.split(CHOICE_MARK))
        .map(relationOf)
        .filter((relation) => relation.name.length > 0);
};

export const stanzasOf = function stanzasOf(text: string, source: PackageSource): readonly PackageStanza[] {
    return text
        .split(CARRIAGE)
        .join("")
        .split(BLOCK_MARK)
        .map(fieldsOf)
        .flatMap((fields) => {
            const name = fields.get("Package");
            const version = fields.get(PACKAGE_VERSION_FIELD);
            const filename = fields.get("Filename");
            const sha256 = fields.get("SHA256");
            if (name === undefined || version === undefined || filename === undefined || sha256 === undefined) {
                return [];
            }
            const provides = relationsOf(fields.get("Provides")).map((relation) => relation.name);
            return [
                {
                    depends: relationsOf(fields.get("Depends")),
                    name,
                    provides,
                    sha256,
                    url: source.base + filename,
                    version,
                },
            ];
        });
};

const numbersOf = function numbersOf(version: string): readonly number[] {
    const runs: string[] = [""];
    for (const character of version) {
        const last = runs.length - 1;
        if (DIGITS.includes(character)) {
            runs[last] = (runs[last] ?? "") + character;
        } else {
            runs.push("");
        }
    }
    return runs.filter((run) => run.length > 0).map(Number);
};

export const compareVersions = function compareVersions(left: string, right: string): number {
    const [leftNumbers, rightNumbers] = [numbersOf(left), numbersOf(right)];
    for (let at = 0; at < Math.max(leftNumbers.length, rightNumbers.length); at += 1) {
        const difference = (leftNumbers[at] ?? 0) - (rightNumbers[at] ?? 0);
        if (difference !== 0) {
            return difference;
        }
    }
    return 0;
};

const newest = function newest(stanzas: readonly PackageStanza[]): PackageStanza | null {
    return stanzas.toSorted((left, right) => compareVersions(right.version, left.version)).at(0) ?? null;
};

export const upstreamOf = function upstreamOf(version: string): string {
    const at = version.lastIndexOf(REVISION_MARK);
    return at === -1 ? version : version.slice(0, at);
};

export const codenameOf = function codenameOf(version: string): string {
    const at = version.lastIndexOf(CODENAME_MARK);
    return at === -1 ? "" : version.slice(at + CODENAME_MARK.length);
};

export const fileOf = function fileOf(stanza: PackageStanza): string {
    return stanza.url.slice(stanza.url.lastIndexOf(PATH_MARK) + 1);
};

export const serverTarget = function serverTarget(
    stanzas: readonly PackageStanza[],
    upstream: string,
): PackageStanza | null {
    return newest(stanzas.filter((stanza) => stanza.name === NGINX_PACKAGE && upstreamOf(stanza.version) === upstream));
};

export const dependsOn = function dependsOn(stanza: PackageStanza, target: PackageStanza): boolean {
    return stanza.depends.some((relation) =>
        relation.exact === null
            ? relation.name !== target.name && target.provides.includes(relation.name)
            : relation.name === target.name && relation.exact === target.version,
    );
};

export const companionOf = function companionOf(
    stanzas: readonly PackageStanza[],
    name: string,
    target: PackageStanza,
): PackageStanza | null {
    return newest(stanzas.filter((stanza) => stanza.name === name && dependsOn(stanza, target)));
};

export const stanzaAt = function stanzaAt(
    stanzas: readonly PackageStanza[],
    name: string,
    version: string,
): PackageStanza | null {
    return stanzas.find((stanza) => stanza.name === name && stanza.version === version) ?? null;
};

export const installedOf = function installedOf(name: string, status: string): InstalledPackage {
    const fields = fieldsOf(status);
    return {
        configured: fields.get(PACKAGE_STATUS_FIELD) === CONFIGURED_STATUS,
        name,
        version: fields.get(PACKAGE_VERSION_FIELD) ?? null,
    };
};

export const isCurrent = function isCurrent(installed: InstalledPackage, stanza: PackageStanza): boolean {
    return installed.configured && installed.version === stanza.version;
};
```
