Skip to content

Commit

Permalink
feat: fromFileSystem
Browse files Browse the repository at this point in the history
Signed-off-by: Lexus Drumgold <[email protected]>
  • Loading branch information
unicornware committed Jan 19, 2025
1 parent 8c366ce commit 240ac3e
Show file tree
Hide file tree
Showing 43 changed files with 1,797 additions and 33 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,5 @@ codecov
# ------------------------------------------------------------------------------
**/*config.*.timestamp*
**/.temp/
**/*.scratch.*
**/scratch.*
166 changes: 159 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,25 @@ Create file system trees
- [Install](#install)
- [Use](#use)
- [API](#api)
- [`fromFileSystem([options])`](#fromfilesystemoptions)
- [`Options`](#options)
- [`Dirent`](#dirent)
- [`Extensions`](#extensions)
- [`FileSystem`](#filesystem)
- [`Filter`](#filter)
- [`Filters`](#filters)
- [`Handle<[T]>`](#handlet)
- [`Handles`](#handles)
- [`Sort`](#sort)
- [Syntax tree](#syntax-tree)
- [Types](#types)
- [Interfaces](#interfaces)
- [Contribute](#contribute)

## What is this?

**TODO**: what is this?
This package is a utility to create [file system trees][fst].

This utility that uses file system adapters to recursively read a directory, and create a tree from its contents.

## Install

Expand Down Expand Up @@ -64,7 +75,140 @@ In browsers with [`esm.sh`][esmsh]:

## API

**TODO**: api
This package exports the following identifiers:

- [`fromFileSystem`](#fromfilesystemoptions)

There is no default export.

### `fromFileSystem([options])`

Create a file system tree.

#### Parameters

- `options` ([`Options`](#options), optional) — tree options

#### Returns

([`Root`][fst-root]) file system tree

### `Options`

Options for creating a file system tree (TypeScript interface).

#### Properties

- `content` (`boolean`, optional) — include file content (populates the `value` field of each [`file` node][fst-file])
- `depth` (`number`, optional) — maximum search depth (inclusive)
- `extensions` ([`Extensions`](#extensions), optional) — list of file extensions to filter matched files by
- `filters` ([`Filters`](#filters), optional) — path filters to determine if nodes should be added to the tree
- `fs` ([`Partial<FileSystem>`](#filesystem), optional) — file system adapter
- `handles` ([`Handles`](#handles), optional) — node handlers
- `root` (`URL | string`, optional) — module id of root directory
- **default**: [`pathe.cwd() + pathe.sep`][pathe]
- `sort` ([`Sort`](#sort), optional) — function used to sort child nodes

### `Dirent`

Directory content entry (TypeScript interface).

This interface can be augmented to register custom methods and properties.

```ts
declare module '@flex-development/fst-util-from-fs' {
interface Dirent {
parentPath: string
}
}
```

#### Properties

- `isDirectory` (`(this: void) => boolean`) — check if the dirent describes a directory
- `name` (`string`) — directory content name. if the dirent refers to a file, the file extension should be included

### `Extensions`

Union of options to filter matched files by file extension (TypeScript type).

```ts
type Extensions = Set<string> | readonly string[] | string
```
### `FileSystem`
File system adapter (TypeScript interface).
#### Properties
- `readFileSync` (`(this: void, path: string, encoding: 'utf8') => string`, optional) —
get the contents of the file at `path`
- `readdirSync` (`(this: void, path: string, options: { withFileTypes: true }) => readonly Dirent[]`) —
read the contents of the directory at `path`
### `Filter`
Determine if a node for `x` should be added to a file system tree.
#### Parameters
- `x` (`string`) — path to directory or file
#### Returns
(`boolean`) `true` if node for `x` should be added, `false` otherwise
### `Filters`
Path filters to determine if nodes should be added to the tree (TypeScript type).
#### Properties
- `directory` ([`Filter`](#filter), optional) — determine if a `directory` node should be added to the tree
- `file` ([`Filter`](#filter), optional) — determine if a `file` node should be added to the tree
### `Handle<[T]>`
Handle `node`.
#### Type Parameters
- `T` ([`Child`][fst-child]) — [fst][] child node
#### Parameters
- `node` (`T`) — directory or file node
- `dirent` ([`Dirent`](#dirent)) — dirent object representing directory or file
- `parent` ([`Parent`][fst-parent]) — parent node
- `tree` ([`Root`][fst-root]) — file system tree
- `fs` ([`FileSystem`](#filesystem)) — file system adapter
#### Returns
(`null | undefined | void`) nothing
### `Handles`
Path filters to determine if nodes should be added to the tree (TypeScript type).
#### Properties
- `directory` ([`Handle<Directory>`](#handlet), optional) — [directory node][fst-directory] handler
- `file` ([`Handle<File>`](#handlet), optional) — [file node][fst-file] handler
### `Sort`
Compare node `a` to `b`.
#### Parameters
- `a` ([`Child`][fst-child]) — current child node
- `b` ([`Child`][fst-child]) — next child node
#### Returns
(`number`) comparison result
## Syntax tree
Expand All @@ -74,10 +218,6 @@ The syntax tree is [fst][].
This package is fully typed with [TypeScript][].
### Interfaces

**TODO**: interfaces

## Contribute
See [`CONTRIBUTING.md`](CONTRIBUTING.md).
Expand All @@ -89,8 +229,20 @@ community you agree to abide by its terms.
[esmsh]: https://esm.sh
[fst-child]: https://github.com/flex-development/fst#child
[fst-directory]: https://github.com/flex-development/fst#directory
[fst-file]: https://github.com/flex-development/fst#file
[fst-parent]: https://github.com/flex-development/fst#parent
[fst-root]: https://github.com/flex-development/fst#root
[fst]: https://github.com/flex-development/fst
[pathe]: https://github.com/flex-development/pathe
[typescript]: https://www.typescriptlang.org
[yarn]: https://yarnpkg.com
37 changes: 37 additions & 0 deletions __tests__/utils/build-path.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* @file Test Utilities - buildPath
* @module tests/utils/buildPath
*/

import type { Directory, DirectoryContent, Root } from '@flex-development/fst'
import pathe from '@flex-development/pathe'

/**
* Get the path to `node`.
*
* @see {@linkcode DirectoryContent}
* @see {@linkcode Directory}
* @see {@linkcode Root}
*
* @this {void}
*
* @param {DirectoryContent} node
* Current node
* @param {Directory | Root} parent
* Parent of `node`
* @param {(Directory | Root)[]} ancestors
* List of ancestor nodes where the last node is the grandparent of `node`
* @return {undefined}
*/
function buildPath(
this: void,
node: DirectoryContent,
parent: Directory | Root,
ancestors: (Directory | Root)[]
): string {
return pathe.join(...[...ancestors, parent, node].map(node => {
return 'path' in node ? node.path : node.name
}))
}

export default buildPath
90 changes: 90 additions & 0 deletions __tests__/utils/readdir.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/**
* @file Test Utilities - readdir
* @module tests/utils/readdir
*/

import toPath from '#internal/to-path'
import pathe from '@flex-development/pathe'
import fs from 'node:fs'

export { readdir as default, type ReadResult }

/**
* Read directory result.
*/
type ReadResult = {
/**
* List of direcory paths.
*/
directories: string[]

/**
* List of files.
*/
files: string[]
}

/**
* Get the contents of the directory at `dir`.
*
* @this {void}
*
* @param {string} dir
* Directory URL or path to directory
* @param {number | null | undefined} depth
* Maximum search depth (inclusive)
* @param {ReadResult | null | undefined} [ctx]
* Read directory context
* @return {ReadResult}
* Read directory result
*/
function readdir(
this: void,
dir: URL | string,
depth?: number | null | undefined,
ctx?: ReadResult | null | undefined
): ReadResult {
ctx ??= { directories: [], files: [] }

if (
depth === null ||
depth === undefined ||
typeof depth === 'number' && depth > 0
) {
dir = toPath(dir)

/**
* List of subdirectories.
*
* @const {string[]} subdirectories
*/
const subdirectories: string[] = []

for (const dirent of fs.readdirSync(dir, { withFileTypes: true })) {
/**
* Relative path to directory or file.
*
* @const {string} path
*/
const path: string = pathe.join(dir, dirent.name)

if (dirent.isDirectory()) {
subdirectories.push(path)
} else {
ctx.files.push(path)
}
}

if (typeof depth === 'number') {
depth--
if (depth <= 0) return ctx
}

for (const path of subdirectories) {
ctx.directories.push(path)
readdir(path, depth, ctx)
}
}

return ctx
}
Loading

0 comments on commit 240ac3e

Please sign in to comment.