95 lines
2.4 KiB
TypeScript
95 lines
2.4 KiB
TypeScript
import { TextLineStream } from "https://deno.land/std@0.208.0/streams/mod.ts";
|
|
|
|
export type Channel<T> = ReturnType<typeof channel<T>>;
|
|
export function channel<T>() {
|
|
const inner = {
|
|
queue: [] as T[],
|
|
resolver: null as (() => void) | null,
|
|
};
|
|
const send = (item: T) => {
|
|
inner.queue.push(item);
|
|
if (inner.resolver === null) return;
|
|
inner.resolver();
|
|
inner.resolver = null;
|
|
};
|
|
const receive = async () => {
|
|
if (inner.queue.length < 1) await new Promise<void>((res) => inner.resolver = res);
|
|
const [head] = inner.queue.splice(0, 1);
|
|
return head;
|
|
};
|
|
return { send, receive };
|
|
}
|
|
|
|
export function* range(from: number, to: number) {
|
|
while (from < to) yield from++;
|
|
}
|
|
|
|
export async function exists(path: string) {
|
|
try {
|
|
await Deno.stat(path);
|
|
return true;
|
|
} catch (_) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export async function next<T>(readable: ReadableStream<T>) {
|
|
for await (const item of readable) return item;
|
|
}
|
|
|
|
export function lines(readable: ReadableStream<Uint8Array>) {
|
|
return readable
|
|
.pipeThrough(new TextDecoderStream())
|
|
.pipeThrough(new TextLineStream());
|
|
}
|
|
|
|
const async_noop = async () => {};
|
|
export type LoopProcess = ReturnType<typeof loop_process>;
|
|
export function loop_process(
|
|
command: Deno.Command,
|
|
opts?: { delay?: number; on_start?: () => unknown; on_stop?: () => unknown },
|
|
) {
|
|
const events = {
|
|
on_start: opts?.on_start ?? async_noop,
|
|
on_stop: opts?.on_stop ?? async_noop,
|
|
};
|
|
const kill_sig = channel<"kill">();
|
|
async function launch() {
|
|
while (true) {
|
|
await events.on_start();
|
|
const child_process = command.spawn();
|
|
const result = await Promise.any([kill_sig.receive(), child_process.output()]);
|
|
if (result === "kill") {
|
|
await events.on_stop();
|
|
child_process.kill();
|
|
break;
|
|
}
|
|
await events.on_stop();
|
|
await sleep(opts?.delay ?? 500);
|
|
}
|
|
}
|
|
const looping = launch();
|
|
return {
|
|
kill: async () => {
|
|
kill_sig.send("kill"), await looping;
|
|
},
|
|
events,
|
|
looping,
|
|
};
|
|
}
|
|
|
|
export function sleep(ms: number) {
|
|
return new Promise((resolver) => setTimeout(resolver, ms));
|
|
}
|
|
|
|
export async function run(command: string, ...args: string[]) {
|
|
const { code } = await new Deno.Command(command, { args }).output();
|
|
if (code != 0) throw new Error(`command ${command} failed`);
|
|
}
|
|
|
|
export function log_from(...prefixes: string[]) {
|
|
const prefix = prefixes.map((p) => `[${p}]`).join("").padStart(10);
|
|
return function (...args: unknown[]) {
|
|
console.log(prefix, ...args);
|
|
};
|
|
}
|