UNPKG

@plugjs/plug

Version:
145 lines (115 loc) 4.44 kB
import { inspect } from 'node:util' import { assert } from './asserts' import { mkdir, writeFile } from './fs' import { assertRelativeChildPath, getAbsoluteParent, getCurrentWorkingDirectory, resolveAbsolutePath, } from './paths' import type { AbsolutePath } from './paths' /** The {@link FilesBuilder} interface defines a builder for {@link Files}. */ export interface FilesBuilder { /** The (resolved) directory the {@link Files} will be associated with */ readonly directory: AbsolutePath /** * Push files into the {@link Files} instance being built. * * This method will not check that files actually exist on disk. */ add(...files: string[]): this /** Merge orther {@link Files} instance to the {@link Files} being built */ merge(...files: Files[]): this /** Write a file and add it to the {@link Files} instance being built */ write(file: string, content: string | Uint8Array): Promise<AbsolutePath> /** Build and return a {@link Files} instance */ build(): Files } /** * The {@link Files} class represents a collection of relative path names * identifying some _files_ rooted in a given _directory_. */ export class Files { private readonly _directory: AbsolutePath private readonly _files: string[] /** * Create a new {@link Files} instance rooted in the specified `directory` * relative to the specified {@link Run}'s directory. */ constructor(directory?: AbsolutePath) { this._directory = directory || getCurrentWorkingDirectory() this._files = [] // Nicety for "console.log" / "util.inspect"... Object.defineProperty(this, inspect.custom, { value: () => ({ directory: this._directory, files: [ ...this._files ], }) }) } /** Return the _directory_ where this {@link Files} is rooted */ get directory(): AbsolutePath { return this._directory } /** Return the number of files tracked by this instance. */ get length(): number { return this._files.length } /** Return an iterator over all _relative_ files of this instance */ * [Symbol.iterator](): Generator<string> { for (const file of this._files) yield file } /** Return an iterator over all _absolute_ files of this instance */ * absolutePaths(): Generator<AbsolutePath> { for (const file of this) yield resolveAbsolutePath(this._directory, file) } /** Return an iterator over all _relative_ to _absolute_ mappings */ * pathMappings(): Generator<[ relative: string, absolute: AbsolutePath ]> { for (const file of this) yield [ file, resolveAbsolutePath(this._directory, file) ] } /** Create a new {@link FilesBuilder} creating {@link Files} instances. */ static builder(): FilesBuilder static builder(files: Files): FilesBuilder static builder(directory: AbsolutePath): FilesBuilder static builder(arg?: Files | AbsolutePath): FilesBuilder { if (! arg) arg = getCurrentWorkingDirectory() const directory = typeof arg === 'string' ? arg : arg.directory const set = typeof arg === 'string' ? new Set<string>() : new Set(arg._files) const instance = new Files(directory) let built = false return { directory: instance.directory, add(...files: string[]): FilesBuilder { assert(! built, 'FileBuilder "build()" already called') for (const file of files) { const relative = assertRelativeChildPath(instance.directory, file) set.add(relative) } return this }, merge(...args: Files[]): FilesBuilder { assert(! built, 'FileBuilder "build()" already called') for (const files of args) { for (const file of files.absolutePaths()) { this.add(file) } } return this }, async write(file: string, content: string | Uint8Array): Promise<AbsolutePath> { const relative = assertRelativeChildPath(instance.directory, file) const absolute = resolveAbsolutePath(instance.directory, relative) const directory = getAbsoluteParent(absolute) await mkdir(directory, { recursive: true }) await writeFile(absolute, content) this.add(absolute) return absolute }, build(): Files { assert(! built, 'FileBuilder "build()" already called') built = true instance._files.push(...set) instance._files.sort() return instance }, } } }