diff --git a/examples/run_wait b/examples/run_wait new file mode 100755 index 0000000..dc0ff75 --- /dev/null +++ b/examples/run_wait @@ -0,0 +1,6 @@ +#!/usr/bin/bash +set -e +cd "$(dirname "$(realpath "$0")")" + + +../src/regar.ts --hard --progress 'sleep 2s' wdir \ No newline at end of file diff --git a/examples/wdir/work.txt b/examples/wdir/work.txt new file mode 100644 index 0000000..e69de29 diff --git a/package/deploy b/package/deploy new file mode 100644 index 0000000..d4d25d0 --- /dev/null +++ b/package/deploy @@ -0,0 +1,6 @@ +#!/usr/bin/bash +set -e +cd "$(dirname "$(realpath "$0")")" + +./release/package +./aur/package diff --git a/src/regar.ts b/src/regar.ts index b1b1863..0cc522a 100755 --- a/src/regar.ts +++ b/src/regar.ts @@ -5,13 +5,13 @@ import { Command } from "https://deno.land/x/cliffy@v1.0.0-rc.4/command/mod.ts" import { Channel, Constructible, InstanceOf } from "https://git.barnulf.net/mb/barnulf_ts/raw/branch/master/mod.ts" import { wait } from "https://git.barnulf.net/mb/barnulf_ts/raw/branch/master/src/lib/utils.ts" -const version = "1.0.0" +const version = "1.1.0" async function main() { - const { do_clear, command, extensions, files, strategy, go_up } = await parse_args() + const { do_clear, command, extensions, files, strategy, go_up, show_progress } = await parse_args() const channel = new Channel() files.map((path) => new Watcher(path, extensions).spin(channel)) - new Runner(command, strategy, do_clear, go_up).spin(channel) + new Runner(command, strategy, do_clear, go_up, show_progress).spin(channel) } async function parse_args() { @@ -26,6 +26,7 @@ async function parse_args() { .option("-e, --extensions ", "Comma separated whitelist of extensions to whatch.") .option("-c, --clear", "Clear the terminal on restart.", { default: false }) .option("-u, --up", "Scroll the terminal to the start of the command at each run.", { default: false }) + .option("-p, --progress", "Show estimated progress on subsequent runs.", { default: false }) .help({ colors: false }) const { args, options } = await cmd.parse() const [command, ...files] = args @@ -36,7 +37,8 @@ async function parse_args() { const extensions = options.extensions?.split(",") const do_clear = options.clear const go_up = options.up - return { command, files, strategy, extensions, do_clear, go_up } + const show_progress = options.progress + return { command, files, strategy, extensions, do_clear, go_up, show_progress } } class Watcher { @@ -67,6 +69,7 @@ class Runner { private strategy: ShutdownStrategy, private do_clear: boolean, private go_up: boolean, + private show_progress: boolean, ) {} public async spin(channel: Channel) { @@ -89,7 +92,9 @@ class Runner { log("running", this.command) console.log() const process = new Deno.Command("sh", { args }).spawn() - process.status.then(this.on_end()) + let stop: Stop | undefined = undefined + if (this.show_progress) stop = this.start_progress() + process.status.then(this.on_end(stop)) return process } @@ -101,20 +106,22 @@ class Runner { private last_event_date_ms = 0 private debouncing(event: Event) { const delay_ms = event.date_ms - this.last_event_date_ms - const threshold_ms = 100 + const threshold_ms = 150 if (delay_ms < threshold_ms) return true this.last_event_date_ms = event.date_ms return false } - private on_end() { + private on_end(stop: Stop | undefined) { if (this.go_up) this.save_cursor() const start_ms = Date.now() - return () => { + return (status: Deno.CommandStatus) => { const end_ms = Date.now() const time_ms = end_ms - start_ms + if (status.success) this.time_estimation_ms = time_ms const secs = time_ms / 1000 if (this.go_up) this.restore_cursor() + if (stop !== undefined) stop.stop = true log("terminated", `${secs} s`) } } @@ -126,8 +133,26 @@ class Runner { private restore_cursor() { console.log("\x1b[u") } + + time_estimation_ms = 0 + private start_progress() { + const stop = { stop: false } + ;(async () => { + for await (const step of progress(this.time_estimation_ms)) { + if (stop.stop) break + const secs = (Math.floor(step.ms / 100) / 10).toString().padStart(3) + const total = Math.floor(this.time_estimation_ms / 1_000).toString().padStart(3) + const perc = (Math.floor(step.relative * 1_000) / 10).toString().padStart(5) + log("progress", `${secs} s / ${total} s (${perc} %)\x1bM`) + } + })() + + return stop + } } +type Stop = { stop: boolean } + interface ShutdownStrategy { /** Returns wether the child has been shutdown. */ shutdown(process_: Deno.ChildProcess): Promise @@ -173,6 +198,18 @@ function try_signal(pid: number, sig: Deno.Signal) { ) } +async function* progress(time_ms: number, step_ms = 100) { + const start = Date.now() + yield { ms: 0., relative: 0. } + while (true) { + const ellapsed = Date.now() - start + if (ellapsed > time_ms) break + yield { ms: ellapsed, relative: ellapsed / time_ms } + await wait(step_ms) + } + yield { ms: time_ms, relative: 1. } +} + // deno-lint-ignore no-explicit-any type Catching, R = void> = [ exception: C, @@ -199,7 +236,7 @@ function log(verb: string, value: unknown) { const bold = "\x1b[1m" const reset = "\x1b[0m" console.log( - `` + + `\x1b[2K` + `${yellow}${bold}${verb.padStart(12)}${reset}` + ` ` + `${yellow}${value}${reset}`,