@atomist/automation-client
Version:
Atomist API for software low-level client
166 lines (156 loc) • 5.97 kB
text/typescript
import {
defer,
ScriptedFlushable,
} from "../../internal/common/Flushable";
import { isPromise } from "../../internal/util/async";
import { toStringArray } from "../../internal/util/string";
import { File } from "../File";
import {
FileStream,
Project,
ProjectAsync,
} from "../Project";
/**
* Promise of an array of files. Usually sourced from Project.streamFiles
*/
export function toPromise(stream: FileStream): Promise<File[]> {
return new Promise((resolve, reject) => {
const files: File[] = [];
stream
.on("data", f => files.push(f))
.on("error", reject)
.on("end", _ => resolve(files));
});
}
/**
* Allows conveniently passing one or many glob patterns to utility functions
*/
export type GlobOptions = string | string[];
/**
* Does at least one file matching the given predicate exist in this project?
* If no predicate is supplied, does at least one file match the glob pattern?
* No guarantees about ordering
* @param p
* @param globPatterns positive and negative globs to match
* @param test return a boolean or promise. Defaults to true
* @return {Promise<boolean>}
*/
export async function fileExists<T>(p: ProjectAsync,
globPatterns: GlobOptions,
test: (f: File) => (boolean | Promise<boolean>) = () => true): Promise<boolean> {
return await countFiles(p, globPatterns, test) > 0;
}
/**
* Count files matching the given predicate in this project
* If no predicate is supplied, does at least one file match the glob pattern?
* No guarantees about ordering
* @param p
* @param globPatterns positive and negative globs to match
* @param test return a boolean or promise. Defaults to true
* @return {Promise<boolean>}
*/
export async function countFiles<T>(p: ProjectAsync,
globPatterns: GlobOptions,
test: (f: File) => (boolean | Promise<boolean>) = () => true): Promise<number> {
const results = await gatherFromFiles<boolean>(p,
globPatterns,
async f => await test(f) === true);
return results.length;
}
/**
* Gather values from files
* @param {ProjectAsync} project to act on
* @param {string} globPatterns glob pattern for files to match
* @param {(f: File) => Promise<T>} gather function returning a promise (of the value you're gathering) from each file.
* Undefined returns will be filtered out
* @return {Promise<T[]>}
*/
export async function gatherFromFiles<T>(project: ProjectAsync,
globPatterns: GlobOptions,
gather: (f: File) => Promise<T> | undefined): Promise<T[]> {
const allFiles = await project.getFiles(globPatterns);
const matches = allFiles.map(gather);
return (await Promise.all(matches)).filter(t => !!t);
}
/**
* Async generator to iterate over files.
* @param {Project} project to act on
* @param {string} globPatterns glob pattern for files to match
* @param filter function to determine whether this file should be included.
* Include all files if this function isn't supplied.
* @return {Promise<T[]>}
*/
export async function* fileIterator(project: Project,
globPatterns: GlobOptions,
filter: (f: File) => Promise<boolean> = async () => true): AsyncIterable<File> {
const files = await project.getFiles(globPatterns);
for (const file of files) {
if (await filter(file)) {
yield file;
}
}
}
/**
* Perform the same operation on all the files.
* @param project project to act on
* @param globPatterns glob patterns to match
* @param op operation to perform on files. Can return void or a promise.
*/
export async function doWithFiles<P extends ProjectAsync>(project: P,
globPatterns: GlobOptions,
op: (f: File) => void | Promise<any>): Promise<P> {
const files = await project.getFiles(globPatterns);
const filePromises: Array<Promise<File>> = [];
files.map(f => {
const r = op(f);
if (isPromise(r)) {
filePromises.push(r.then(_ => f.flush()));
} else {
if (f.dirty) {
filePromises.push(f.flush());
}
}
});
await Promise.all(filePromises);
return project;
}
/**
* Delete files matching the glob pattern and extra test (if supplied)
* @param project project to act on
* @param globPatterns glob patterns for files to delete
* @param test additional, optional test for files to be deleted
*/
export function deleteFiles<T>(project: ProjectAsync,
globPatterns: GlobOptions,
test: (f: File) => boolean = () => true): Promise<number> {
const fp = project as any as ScriptedFlushable<any>;
return new Promise((resolve, reject) => {
let deleted = 0;
project.streamFiles(...toStringArray(globPatterns))
.on("data", f => {
if (test(f)) {
++deleted;
defer(fp, project.deleteFile(f.path));
}
})
.on("error", reject)
.on("end", () => {
resolve(fp.flush()
.then(() => deleted));
});
});
}
/**
* Copy files from one project to another
* @param from project to copy files from
* @param to project to copy files to
*/
export function copyFiles<P extends Project = Project>(from: Project, to: P): Promise<P> {
return toPromise(from.streamFiles())
.then(files =>
Promise.all(
files.map(f =>
f.getContent().then(content => to.addFile(f.path, content))),
),
).then(() => to);
}