Add progression reporting.

This commit is contained in:
Matthieu Jolimaitre 2025-07-28 17:01:37 +02:00
parent feaef5a448
commit e94d8a0c50
4 changed files with 58 additions and 9 deletions

6
examples/run_wait Executable file
View file

@ -0,0 +1,6 @@
#!/usr/bin/bash
set -e
cd "$(dirname "$(realpath "$0")")"
../src/regar.ts --hard --progress 'sleep 2s' wdir

0
examples/wdir/work.txt Normal file
View file

6
package/deploy Normal file
View file

@ -0,0 +1,6 @@
#!/usr/bin/bash
set -e
cd "$(dirname "$(realpath "$0")")"
./release/package
./aur/package

View file

@ -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<Event>()
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 <extentions>", "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<Event>) {
@ -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<boolean>
@ -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<C extends Constructible<C, any>, 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}`,