# core/coordinators/template.coordinator.ts

> 110 lines of code and 25 definitions.

Tree: GovLab Docs
Language: typescript
Layer: application
Canonical: https://banes-lab.com/anatomy/docs#file-docs-core-coordinators-template-coordinator-ts
Source text: https://banes-lab.com/source/docs/core/coordinators/template.coordinator.ts.txt

Listed in [core/coordinators](https://banes-lab.com/api/source/docs/core/coordinators.md), after [core/coordinators/system.coordinator.ts](https://banes-lab.com/source/docs/core/coordinators/system.coordinator.ts.md) and before [core/coordinators/validation.coordinator.ts](https://banes-lab.com/source/docs/core/coordinators/validation.coordinator.ts.md).

## Definitions

- `runNew` (lexical_declaration, line 95, exported)
- `hintFor` (lexical_declaration, line 66)
- `assertName` (lexical_declaration, line 59)
- `statusFor` (lexical_declaration, line 88)
- `locate` (lexical_declaration, line 73)
- `composedName` (lexical_declaration, line 41)
- `LIST_SEPARATOR` (lexical_declaration, line 23)
- `VERB_JOIN` (lexical_declaration, line 24)
- `DIRECTIVE_MOOD` (lexical_declaration, line 25)
- `MODULE_AXIS` (lexical_declaration, line 26)
- `CURRENT_STATUS` (lexical_declaration, line 27)
- `PLANNED_STATUS` (lexical_declaration, line 28)
- `CONCERN_REASONS` (lexical_declaration, line 29)
- `MEMBER_REASONS` (lexical_declaration, line 30)
- `resolveForm` (lexical_declaration, line 32)
- `valid` (lexical_declaration, line 35)
- `verb` (lexical_declaration, line 51)
- `activities` (lexical_declaration, line 53)
- `[defect]` (lexical_declaration, line 60)
- `result` (lexical_declaration, line 74)
- `def` (lexical_declaration, line 96, exported)
- `name` (lexical_declaration, line 97, exported)
- `path` (lexical_declaration, line 99, exported)
- `absolute` (lexical_declaration, line 100, exported)
- `summary` (lexical_declaration, line 104, exported)

## Contained in

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

## Uses

- [configuration/strings/template.strings.ts](https://banes-lab.com/source/docs/configuration/strings/template.strings.ts.md)
- [core/formatters/template.formatter.ts](https://banes-lab.com/source/docs/core/formatters/template.formatter.ts.md)
- [core/reporters/base.reporter.ts](https://banes-lab.com/source/docs/core/reporters/base.reporter.ts.md)

## Used by

- [runtime/entrypoints/document.entrypoint.ts](https://banes-lab.com/source/docs/runtime/entrypoints/document.entrypoint.ts.md)

## Linked from

- [configuration/strings](https://banes-lab.com/anatomy/docs/folder-docs-configuration-strings.md)
- [core/coordinators](https://banes-lab.com/anatomy/docs/folder-docs-core-coordinators.md)
- [core/formatters](https://banes-lab.com/anatomy/docs/folder-docs-core-formatters.md)
- [core/reporters](https://banes-lab.com/anatomy/docs/folder-docs-core-reporters.md)
- [runtime/entrypoints](https://banes-lab.com/anatomy/docs/folder-docs-runtime-entrypoints.md)

## Source

```typescript
import {
    MISSING_SUBJECT,
    alreadyExists,
    brokenName,
    concernHint,
    created,
    memberHint,
    notActivity,
    placeholderSummary,
    unknownForm,
    unplaceable,
} from "#configuration/strings/template.strings";
import type { TemplateArguments, TemplateContext } from "#types/environment.types";
import { dirname, join } from "node:path";
import { existsSync, mkdirSync } from "node:fs";
import type { DocForm } from "#types/location.types";
import { computeLocation } from "#core/resolvers/location.resolver";
import { docName } from "#core/validators/filename.validator";
import { print } from "#core/reporters/base.reporter";
import { renderTemplate } from "#core/formatters/template.formatter";
import { writeVerbatim } from "@govlab/canonical-write";

const LIST_SEPARATOR = ", ";
const VERB_JOIN = "-";
const DIRECTIVE_MOOD = "directive";
const MODULE_AXIS = "module";
const CURRENT_STATUS = "current";
const PLANNED_STATUS = "planned";
const CONCERN_REASONS: ReadonlySet<string> = new Set(["unknown-concern", "missing-concern"]);
const MEMBER_REASONS: ReadonlySet<string> = new Set(["unmarked-member", "unknown-member"]);

const resolveForm = function resolveForm(context: TemplateContext, form: string): DocForm {
    const def = context.userReg.forms[form];
    if (def === undefined || def.boundary) {
        const valid = Object.entries(context.userReg.forms).flatMap(([id, entry]) => (entry.boundary ? [] : [id]));
        throw new Error(unknownForm(form, valid.join(LIST_SEPARATOR)));
    }
    return def;
};

const composedName = function composedName(context: TemplateContext, def: DocForm, args: TemplateArguments): string {
    if (args.name.length > 0) {
        return args.name;
    }
    if (args.subject.length === 0) {
        throw new Error(MISSING_SUBJECT);
    }
    if (def.mood !== DIRECTIVE_MOOD) {
        return args.subject;
    }
    const verb = context.userReg.activityVerbs[args.concern];
    if (verb === undefined) {
        const activities = Object.keys(context.userReg.activityVerbs).join(LIST_SEPARATOR);
        throw new Error(notActivity(def.id, args.concern, activities));
    }
    return [verb, args.subject].join(VERB_JOIN);
};

const assertName = function assertName(context: TemplateContext, def: DocForm, concern: string, name: string): void {
    const [defect] = docName({ activityVerbs: context.userReg.activityVerbs, concern, form: def, name });
    if (defect !== undefined) {
        throw new Error(brokenName(name, defect.detail));
    }
};

const hintFor = function hintFor(context: TemplateContext, reason: string): string {
    if (CONCERN_REASONS.has(reason)) {
        return concernHint(context.userReg.concerns.join(LIST_SEPARATOR));
    }
    return MEMBER_REASONS.has(reason) ? memberHint((context.registries.owners ?? []).join(LIST_SEPARATOR)) : "";
};

const locate = function locate(context: TemplateContext, args: TemplateArguments, name: string): string {
    const result = computeLocation({
        concern: args.concern,
        form: args.form,
        member: args.member,
        name,
        options: context.locationOptions,
        registries: context.registries,
    });
    if (!result.ok) {
        throw new Error(unplaceable(result.reason, result.detail, hintFor(context, result.reason)));
    }
    return result.path;
};

const statusFor = function statusFor(def: DocForm, requested: string): string {
    if (requested.length > 0) {
        return requested;
    }
    return def.ownerAxis === MODULE_AXIS ? CURRENT_STATUS : PLANNED_STATUS;
};

export const runNew = function runNew(context: TemplateContext, args: TemplateArguments): void {
    const def = resolveForm(context, args.form);
    const name = composedName(context, def, args);
    assertName(context, def, args.concern, name);
    const path = locate(context, args, name);
    const absolute = join(context.root, path);
    if (existsSync(absolute)) {
        throw new Error(alreadyExists(path));
    }
    const summary = args.summary.length > 0 ? args.summary : placeholderSummary(name);
    mkdirSync(dirname(absolute), { recursive: true });
    writeVerbatim(
        absolute,
        renderTemplate({
            concern: args.concern,
            def,
            form: args.form,
            name,
            status: statusFor(def, args.status),
            summary,
        }),
    );
    print(created(path));
};
```
