# core/converters/folder.converter.ts

> 32 lines of code and 10 definitions.

Tree: GovLab Patterns
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/patterns#file-patterns-core-converters-folder-converter-ts
Source text: https://banes-lab.com/source/patterns/core/converters/folder.converter.ts.txt

Listed in [core/converters](https://banes-lab.com/api/source/patterns/core/converters.md), after [core/converters/finding.vector.converter.ts](https://banes-lab.com/source/patterns/core/converters/finding.vector.converter.ts.md) and before [core/converters/math.converter.ts](https://banes-lab.com/source/patterns/core/converters/math.converter.ts.md).

## Definitions

- `relParts` (lexical_declaration, line 6)
- `subtreeFiles` (lexical_declaration, line 35, exported)
- `leafOf` (lexical_declaration, line 10)
- `PATH_SEP` (lexical_declaration, line 4)
- `buildGroup` (lexical_declaration, line 20, exported)
- `leaves` (lexical_declaration, line 21, exported)
- `deeper` (lexical_declaration, line 22, exported)
- `dirNames` (lexical_declaration, line 23, exported)
- `dirs` (lexical_declaration, line 24, exported)
- `children` (lexical_declaration, line 31, exported)

## Contained in

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

## Used by

- [core/converters/page.converter.ts](https://banes-lab.com/source/patterns/core/converters/page.converter.ts.md)

## Linked from

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

## Source

```typescript
import type { TreeGroup, TreeLeaf, TreeNode } from "#types/page.types";
import type { FileEntry } from "#types/package.types";

const PATH_SEP = "/";

const relParts = function relParts(rel: string, root: string): string[] {
    return (root === "" ? rel : rel.slice(root.length + 1)).split(PATH_SEP);
};

const leafOf = function leafOf(entry: FileEntry, rel: string): TreeLeaf {
    return {
        count: entry.walk.length,
        entry,
        kind: "file",
        name: relParts(entry.rel, rel)[0] ?? entry.rel,
        rel: entry.rel,
    };
};

export const buildGroup = function buildGroup(entries: readonly FileEntry[], rel: string, name: string): TreeGroup {
    const leaves = entries.filter((entry) => relParts(entry.rel, rel).length === 1).map((entry) => leafOf(entry, rel));
    const deeper = entries.filter((entry) => relParts(entry.rel, rel).length > 1);
    const dirNames = [...new Set(deeper.map((entry) => relParts(entry.rel, rel)[0] ?? ""))];
    const dirs = dirNames.map((dirName) =>
        buildGroup(
            deeper.filter((entry) => (relParts(entry.rel, rel)[0] ?? "") === dirName),
            rel === "" ? dirName : `${rel}${PATH_SEP}${dirName}`,
            dirName,
        ),
    );
    const children: TreeNode[] = [...leaves, ...dirs];
    return { children, count: children.reduce((sum, child) => sum + child.count, 0), kind: "dir", name, rel };
};

export const subtreeFiles = function subtreeFiles(node: TreeNode): FileEntry[] {
    return node.kind === "file" ? [node.entry] : node.children.flatMap(subtreeFiles);
};
```
