Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: export scan function #136

Merged
merged 8 commits into from
Mar 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 56 additions & 31 deletions workspace/aubade/src/compass/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,50 +14,75 @@ export function visit(entry) {
}

/**
* @template {'all' | 'files' | 'directories'} T
* @param {T} type
* @param {string} entry
* @param {{
* depth?: number;
* files?(path: string): boolean;
* }} [options]
* @returns {T extends 'all'
* ? import('../types.js').HydrateChunk['siblings'] : T extends 'files'
* ? import('../types.js').FileChunk[] : import('../types.js').DirChunk[]}
*/
export function traverse(entry, { depth: level = 0, files = (v) => v.endsWith('.md') } = {}) {
export function scan(type, entry) {
/** @type {import('../types.js').HydrateChunk['siblings']} */
const tree = fs.readdirSync(entry).map((name) => {
const entries = [];
for (const name of fs.statSync(entry).isDirectory() ? fs.readdirSync(entry) : []) {
const path = join(entry, name);
return {
/** @type {any} - trick TS to enable discriminated union */
type: fs.statSync(path).isDirectory() ? 'directory' : 'file',
/** @type {any} - trick TS to enable discriminated union */
const stat = fs.statSync(path).isDirectory() ? 'directory' : 'file';
if (type === 'files' && stat === 'directory') continue;
if (type === 'directories' && stat === 'file') continue;
entries.push({
type: stat,
path,
breadcrumb: path.split(/[/\\]/).reverse(),
get buffer() {
return this.type === 'file' ? fs.readFileSync(path) : void 0;
return stat === 'file' ? fs.readFileSync(path) : void 0;
},
};
});
});
}
return /** @type {any} */ (entries);
}

/**
* @param {string} entry
* @param {{
* depth?: number;
* }} [options]
*/
export function traverse(entry, { depth: level = 0 } = {}) {
const entries = scan('files', entry);
for (const { path } of level ? scan('directories', entry) : []) {
entries.push(...traverse(path, { depth: level - 1 }).files);
}

return {
files: entries,

/**
* @template {object} Output
* @template Transformed
* Hydrate `files` scanned on to the shelf with the `load` function.
*
* @template {object} Output
* @param {(chunk: import('../types.js').HydrateChunk) => undefined | Output} load
* @param {(items: Output[]) => Transformed} [transform]
* @returns {Transformed}
* @param {(path: string) => boolean} [files] filter item to process with `load`
* @returns {Output[]}
*/
hydrate(load, transform = (v) => /** @type {Transformed} */ (v)) {
const backpack = tree.flatMap(({ type, breadcrumb, buffer }) => {
const path = [...breadcrumb].reverse().join('/');
if (type === 'file') {
if (!files(path)) return [];
const siblings = tree.filter(({ breadcrumb: [name] }) => name !== breadcrumb[0]);
return load({ breadcrumb, buffer, marker, parse, siblings }) ?? [];
} else if (level !== 0) {
const depth = level < 0 ? level : level - 1;
return traverse(path, { depth, files }).hydrate(load);
}
return [];
});

return transform(/** @type {any} */ (backpack));
hydrate(load, files = (v) => v.endsWith('.md')) {
const items = [];
for (const { path, breadcrumb, buffer } of entries) {
if (!files(path)) continue;
const item = load({
breadcrumb,
buffer,
marker,
parse,
get siblings() {
const parent = breadcrumb.slice(1).reverse();
const tree = scan('all', parent.join('/'));
return tree.filter(({ path: file }) => file !== path);
},
});
item && items.push(item);
}
return items;
},
};
}
Expand Down
19 changes: 15 additions & 4 deletions workspace/aubade/src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,26 @@ export interface FrontMatter {
[key: string]: Primitives | Primitives[] | FrontMatter | FrontMatter[];
}

export interface FileChunk {
type: 'file';
path: string;
breadcrumb: string[];
buffer: Buffer;
}

export interface DirChunk {
type: 'directory';
path: string;
breadcrumb: string[];
buffer: undefined;
}

export interface HydrateChunk {
breadcrumb: string[];
buffer: Buffer;
marker: typeof marker;
parse: typeof parse;
siblings: Array<
| { type: 'file'; breadcrumb: string[]; buffer: Buffer }
| { type: 'directory'; breadcrumb: string[]; buffer: undefined }
>;
siblings: Array<FileChunk | DirChunk>;
}

export interface Metadata {
Expand Down
45 changes: 35 additions & 10 deletions workspace/content/10-modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,26 +162,49 @@ export function visit<Output>(entry: string): Output & {

The first argument of `visit` is the source entry point.

### scan

```typescript
interface FileChunk {
type: 'file';
path: string;
breadcrumb: string[];
buffer: Buffer;
}

interface DirChunk {
type: 'directory';
path: string;
breadcrumb: string[];
buffer: undefined;
}

export function scan<T extends 'all' | 'files' | 'directories'>(
type: T,
entry: string,
): FileChunk[] | DirChunk[];
```

The first argument of `scan` is the type of item to scan, and the second argument is the entrypoint. It returns an array of `FileChunk` when `type` is `'files'`, `DirChunk` when `type` is `'directories'`, and both when `type` is `'all'`. The `entry` argument can be anything as long as it exists in the filesystem, though it would return an empty array if it's not a directory.

### traverse

```typescript
export function traverse(
entry: string,
options: {
options?: {
depth?: number;
files?(path: string): boolean;
},
): {
files: FileChunk[];
hydrate(
load: (chunk: HydrateChunk) => undefined | Output,
transform?: (items: Output[]) => Transformed,
): Transformed;
filter?: (path: string) => boolean,
): Output[];
};
```

The first argument of `traverse` is the directory entrypoint, and the second argument is its `options`. It returns an object with the `hydrate` method that accepts a `load` callback and an optional `transform` callback function.

The `files` property in `options` is an optional function that takes the full path of a file and returns a boolean. If the function returns `true`, the `load` callback will be called upon the file, else it will ignored and filtered out from the final output.
The first argument of `traverse` is the directory entrypoint, and the second argument is its `options`. It returns an object with the `hydrate` method that accepts a `load` callback and an optional `files` function to filter item to process into `load`, it takes the full path of a file and returns a boolean. If the function returns `true`, the `load` callback will be called upon the file, else it will ignored and filtered out from the final output.

```
content
Expand Down Expand Up @@ -247,13 +270,15 @@ const data = traverse('content/reviews', { depth: -1 }).hydrate(

## /transform

This module provides a set of transformer function for the [`traverse(...).hydrate(..., /* transform */)` parameter](/docs/modules#compass-traverse). These function can be used in conjunction with each other, by utilizing the `pipe` function provided from the `'mauss'` package and re-exported by this module, you can do the following
This is a standalone module which provides a set of transformer function. They can be used in conjunction with each other by utilizing the `pipe` function provided from the `'mauss'` package and re-exported by this module, you can do the following

```typescript
import { traverse } from 'aubade/compass';
import { pipe } from 'aubade/transform';
import { chain } from 'aubade/transform';

const items = traverse('path/to/content').hydrate(() => {});

traverse('path/to/content').hydrate(() => {}, pipe(/* ... */));
chain(items, { ... });
```

### chain
Expand Down
5 changes: 3 additions & 2 deletions workspace/website/src/lib/content/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const ROOT = `${process.cwd()}/static/uploads`;

export const DATA = {
get 'docs/'() {
return traverse('../content').hydrate(
const items = traverse('../content').hydrate(
({ breadcrumb: [filename], buffer, marker, parse, siblings }) => {
const { body, metadata } = parse(buffer.toString('utf-8'));

Expand All @@ -25,7 +25,8 @@ export const DATA = {
content: marker.render(content),
};
},
(items) => chain(items, { base: '/docs/' }),
);

return chain(items, { base: '/docs/' });
},
};
Loading