Skip to content

Commit

Permalink
refactor(runtime): implement process class (#42)
Browse files Browse the repository at this point in the history
  • Loading branch information
c4spar authored Jul 10, 2022
1 parent e02294b commit 2059567
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 62 deletions.
9 changes: 5 additions & 4 deletions src/runtime/deps.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
export * as async from "https://deno.land/std@0.140.0/async/mod.ts";
export type {
DebouncedFunction,
Deferred,
DelayOptions,
export {
type DebouncedFunction,
type Deferred,
deferred,
type DelayOptions,
} from "https://deno.land/std@0.140.0/async/mod.ts";
export * as path from "https://deno.land/std@0.140.0/path/mod.ts";
export type {
Expand Down
61 changes: 3 additions & 58 deletions src/runtime/exec.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
/// <reference path="../../types.d.ts" />

import { Process } from "./process.ts";
import { ProcessError } from "./process_error.ts";
import { ProcessOutput } from "./process_output.ts";
import { quote } from "./quote.ts";

let runningProcesses = 0;

export async function exec(
export function exec(
pieces: TemplateStringsArray,
...args: Array<string | number | ProcessOutput>
): Promise<ProcessOutput> {
Expand All @@ -22,45 +23,7 @@ export async function exec(
console.log($.brightBlue("$ %s"), cmd);
}

const stdout: Array<string> = [];
const stderr: Array<string> = [];
const combined: Array<string> = [];
const process = Deno.run({
cmd: [$.shell, "-c", $.prefix + " " + cmd],
env: Deno.env.toObject(),
stdout: $.stdout,
stderr: $.stderr,
});

const [status] = await Promise.all([
process.status(),
process.stdout && read(process.stdout, [stdout, combined], Deno.stdout),
process.stderr && read(process.stderr, [stderr, combined], Deno.stderr),
]);

process.stdout?.close();
process.stderr?.close();
process.close();

if (--runningProcesses === 0) {
$.stdout = "piped";
}

if (status.success) {
return new ProcessOutput({
stdout: stdout.join(""),
stderr: stderr.join(""),
combined: combined.join(""),
status,
});
}

throw new ProcessError({
stdout: stdout.join(""),
stderr: stderr.join(""),
combined: combined.join(""),
status,
});
return new Process(cmd);
}

/**
Expand Down Expand Up @@ -122,21 +85,3 @@ export const stderrOnly = async (
await exec(pieces, ...args)
.then((o) => (o instanceof ProcessOutput ? o.stderr.trim() : ""))
.catch((e) => (e instanceof ProcessError ? e.stderr.trim() : ""));

async function read(
reader: Deno.Reader,
results: Array<Array<string>>,
outputStream: Deno.Writer,
): Promise<Error | void> {
for await (const line of io.readLines(reader)) {
for (const result of results) {
result.push(line + "\n");
}
if ($.verbose > 1) {
await io.writeAll(
outputStream,
new TextEncoder().encode(line + "\n"),
);
}
}
}
126 changes: 126 additions & 0 deletions src/runtime/process.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/// <reference path="../../types.d.ts" />

import { Deferred, deferred } from "./deps.ts";
import { ProcessError } from "./process_error.ts";
import { ProcessOutput } from "./process_output.ts";

export class Process implements Promise<ProcessOutput> {
readonly [Symbol.toStringTag] = "Process";
readonly #cmd: string;
#proc?: Deno.Process;
#deferred?: Deferred<ProcessOutput>;
#stdin: Deno.RunOptions["stdin"] = "inherit";
#stdout: Deno.RunOptions["stdout"] = $.stdout;
#stderr: Deno.RunOptions["stderr"] = $.stderr;

constructor(cmd: string) {
this.#cmd = cmd;
}

get #process(): Deno.Process {
if (!this.#proc) {
this.#proc = Deno.run({
cmd: [$.shell, "-c", $.prefix + " " + this.#cmd],
env: Deno.env.toObject(),
stdin: this.#stdin,
stdout: this.#stdout,
stderr: this.#stderr,
});
}
return this.#proc;
}

then<TResult1 = ProcessOutput, TResult2 = never>(
onfulfilled?:
| ((value: ProcessOutput) => TResult1 | PromiseLike<TResult1>)
| undefined
| null,
onrejected?:
| ((reason: unknown) => TResult2 | PromiseLike<TResult2>)
| undefined
| null,
): Promise<TResult1 | TResult2> {
return this.#resolve().then(onfulfilled).catch(onrejected);
}

catch<TResult = never>(
onrejected?:
| ((reason: unknown) => TResult | PromiseLike<TResult>)
| undefined
| null,
): Promise<ProcessOutput | TResult> {
return this.#resolve().catch(onrejected);
}

finally(onfinally?: (() => void) | undefined | null): Promise<ProcessOutput> {
return this.#resolve().finally(onfinally);
}

#resolve(): Promise<ProcessOutput> {
if (this.#deferred) {
return this.#deferred;
}
this.#deferred = deferred();

this.#run()
.then((output) => this.#deferred!.resolve(output))
.catch((error) => this.#deferred!.reject(error));

return this.#deferred;
}

async #run() {
const stdout: Array<string> = [];
const stderr: Array<string> = [];
const combined: Array<string> = [];

try {
const [status] = await Promise.all([
this.#process.status(),
this.#process.stdout &&
read(this.#process.stdout, [stdout, combined], Deno.stdout),
this.#process.stderr &&
read(this.#process.stderr, [stderr, combined], Deno.stderr),
]);

if (!status.success) {
throw new ProcessError({
stdout: stdout.join(""),
stderr: stderr.join(""),
combined: combined.join(""),
status,
});
}

return new ProcessOutput({
stdout: stdout.join(""),
stderr: stderr.join(""),
combined: combined.join(""),
status,
});
} finally {
this.#process.close();
this.#process.stdin?.close();
this.#process.stdout?.close();
this.#process.stderr?.close();
}
}
}

async function read(
reader: Deno.Reader,
results: Array<Array<string>>,
outputStream: Deno.Writer,
): Promise<Error | void> {
for await (const line of io.readLines(reader)) {
for (const result of results) {
result.push(line + "\n");
}
if ($.verbose > 1) {
await io.writeAll(
outputStream,
new TextEncoder().encode(line + "\n"),
);
}
}
}

0 comments on commit 2059567

Please sign in to comment.