@plugjs/plug
Version:
PlugJS Build System ===================
145 lines (115 loc) • 4.44 kB
text/typescript
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
},
}
}
}