diff --git a/cli/unstable_progress_bar.ts b/cli/unstable_progress_bar.ts index 3201d277f1b5..6578848ac633 100644 --- a/cli/unstable_progress_bar.ts +++ b/cli/unstable_progress_bar.ts @@ -166,8 +166,14 @@ function getUnitEntry(max: number): [Unit, number] { * await bar.stop(); */ export class ProgressBar { + #hasUpdate = true; + + #value: number = 0; /** * The current progress that has been completed. + * + * @param value Value to set. + * * @example Usage * ```ts no-assert * import { ProgressBar } from "@std/cli/unstable-progress-bar"; @@ -180,9 +186,21 @@ export class ProgressBar { * await progressBar.stop(); * ``` */ - value: number; + set value(value: number) { + if (this.#value === value) return; + this.#value = value; + this.#hasUpdate = true; + } + get value(): number { + return this.#value; + } + + #max: number = 0; /** * The maximum progress that is expected. + * + * @param value Max to set. + * * @example Usage * ```ts no-assert * import { ProgressBar } from "@std/cli/unstable-progress-bar"; @@ -195,10 +213,20 @@ export class ProgressBar { * await progressBar.stop(); * ``` */ - max: number; + set max(value: number) { + if (this.#max === value) return; + this.#max = value; + const [unit, rate] = getUnitEntry(this.#max); + this.#unit = unit; + this.#rate = rate; + this.#hasUpdate = true; + } + get max(): number { + return this.#max; + } - #unit: Unit; - #rate: number; + #unit: Unit = "KiB"; + #rate: number = 2 ** 10; #writer: WritableStreamDefaultWriter; #id: number; #startTime: number; @@ -216,9 +244,7 @@ export class ProgressBar { * * @param options The options to configure various settings of the progress bar. */ - constructor( - options: ProgressBarOptions, - ) { + constructor(options: ProgressBarOptions) { const { writable = Deno.stderr.writable, value = 0, @@ -253,13 +279,17 @@ export class ProgressBar { this.#lastTime = this.#startTime; this.#lastValue = this.value; - this.#id = setInterval(() => this.#print(), 1000); + this.#id = setInterval(() => this.#print(), 100); this.#print(); } async #print(): Promise { const currentTime = performance.now(); + const delta = currentTime - this.#lastTime; + if (delta < 1000 && !this.#hasUpdate) return; + this.#hasUpdate = false; + const size = this.value / this.max * this.#barLength | 0; const fillChars = this.#fillChar.repeat(size); const emptyChars = this.#emptyChar.repeat(this.#barLength - size); diff --git a/cli/unstable_progress_bar_test.ts b/cli/unstable_progress_bar_test.ts index ffaf22b877c9..1aa65f637fb0 100644 --- a/cli/unstable_progress_bar_test.ts +++ b/cli/unstable_progress_bar_test.ts @@ -17,18 +17,72 @@ const decoder = new TextDecoder(); Deno.test("ProgressBar() outputs default result", async () => { const { readable, writable } = new TransformStream(); + const bar = new ProgressBar({ writable, max: 10 * 1000 }); + for (let index = 0; index < 10; index++) { + bar.value += 1000; + await new Promise((resolve) => setTimeout(resolve, 100)); + } + bar.stop().then(() => writable.close()); - for await (const a of getData(10, 1000)) bar.value += a.length; + const expected = [ + "\r\u001b[K[00:00] [--------------------------------------------------] [0.00/9.77 KiB]", + "\r\u001b[K[00:00] [#####---------------------------------------------] [0.98/9.77 KiB]", + "\r\u001b[K[00:00] [##########----------------------------------------] [1.95/9.77 KiB]", + "\r\u001b[K[00:00] [###############-----------------------------------] [2.93/9.77 KiB]", + "\r\u001b[K[00:00] [####################------------------------------] [3.91/9.77 KiB]", + "\r\u001b[K[00:00] [#########################-------------------------] [4.88/9.77 KiB]", + "\r\u001b[K[00:00] [##############################--------------------] [5.86/9.77 KiB]", + "\r\u001b[K[00:00] [###################################---------------] [6.84/9.77 KiB]", + "\r\u001b[K[00:00] [########################################----------] [7.81/9.77 KiB]", + "\r\u001b[K[00:00] [#############################################-----] [8.79/9.77 KiB]", + "\r\u001b[K[00:01] [##################################################] [9.77/9.77 KiB]", + "\n", + ]; + + const actual = []; + for await (const buffer of readable) { + actual.push(decoder.decode(buffer)); + } + assertEquals(actual, expected); +}); + +Deno.test("ProgressBar() outputs passing time", async () => { + const { readable, writable } = new TransformStream(); + + const bar = new ProgressBar({ writable, max: 10 * 1000 }); + await new Promise((resolve) => setTimeout(resolve, 3000)); + bar.stop().then(() => writable.close()); + + const expected = [ + "\r\u001b[K[00:00] [--------------------------------------------------] [0.00/9.77 KiB]", + "\r\u001b[K[00:01] [--------------------------------------------------] [0.00/9.77 KiB]", + "\r\u001b[K[00:02] [--------------------------------------------------] [0.00/9.77 KiB]", + "\n", + ]; + + const actual = []; + for await (const buffer of readable) { + actual.push(decoder.decode(buffer)); + } + assertEquals(actual, expected); +}); + +Deno.test("ProgressBar() change max", async () => { + const { readable, writable } = new TransformStream(); + + const bar = new ProgressBar({ writable, max: 2 ** 10 }); + bar.max = 2 ** 20; + await new Promise((resolve) => setTimeout(resolve, 100)); bar.stop().then(() => writable.close()); const expected = [ - "\r\x1b[K[00:00] [--------------------------------------------------] [0.00/9.77 KiB]", - "\r\x1b[K[00:00] [##################################################] [9.77/9.77 KiB]", + "\r\u001b[K[00:00] [--------------------------------------------------] [0.00/1.00 KiB]", + "\r\u001b[K[00:00] [--------------------------------------------------] [0.00/1.00 MiB]", "\n", ]; - const actual: string[] = []; + const actual = []; for await (const buffer of readable) { actual.push(decoder.decode(buffer)); } @@ -37,27 +91,42 @@ Deno.test("ProgressBar() outputs default result", async () => { Deno.test("ProgressBar() can handle a readable.cancel() correctly", async () => { const { readable, writable } = new TransformStream(); - const bar = new ProgressBar({ writable, max: 10 * 1000 }); - for await (const a of getData(10, 1000)) bar.value += a.length; + const bar = new ProgressBar({ writable, max: 10 * 1000 }); + for (let index = 0; index < 10; index++) { + bar.value += 1000; + await new Promise((resolve) => setTimeout(resolve, 100)); + } bar.stop(); - await readable.cancel(); }); Deno.test("ProgressBar() can remove itself when finished", async () => { const { readable, writable } = new TransformStream(); - const bar = new ProgressBar({ writable, max: 10 * 1000, clear: true }); - for await (const a of getData(10, 1000)) bar.value += a.length; + const bar = new ProgressBar({ writable, max: 10 * 1000, clear: true }); + for (let index = 0; index < 10; index++) { + bar.value += 1000; + await new Promise((resolve) => setTimeout(resolve, 100)); + } bar.stop().then(() => writable.close()); const expected = [ - "\r\x1b[K[00:00] [--------------------------------------------------] [0.00/9.77 KiB]", + "\r\u001b[K[00:00] [--------------------------------------------------] [0.00/9.77 KiB]", + "\r\u001b[K[00:00] [#####---------------------------------------------] [0.98/9.77 KiB]", + "\r\u001b[K[00:00] [##########----------------------------------------] [1.95/9.77 KiB]", + "\r\u001b[K[00:00] [###############-----------------------------------] [2.93/9.77 KiB]", + "\r\u001b[K[00:00] [####################------------------------------] [3.91/9.77 KiB]", + "\r\u001b[K[00:00] [#########################-------------------------] [4.88/9.77 KiB]", + "\r\u001b[K[00:00] [##############################--------------------] [5.86/9.77 KiB]", + "\r\u001b[K[00:00] [###################################---------------] [6.84/9.77 KiB]", + "\r\u001b[K[00:00] [########################################----------] [7.81/9.77 KiB]", + "\r\u001b[K[00:00] [#############################################-----] [8.79/9.77 KiB]", + "\r\u001b[K[00:01] [##################################################] [9.77/9.77 KiB]", "\r\x1b[K", ]; - const actual: string[] = []; + const actual = []; for await (const buffer of readable) { actual.push(decoder.decode(buffer)); } @@ -87,24 +156,87 @@ Deno.test("ProgressBar() passes correct values to formatter", async () => { await new Response(readable).bytes(); }); -Deno.test("ProgressBar() uses correct unit type", async () => { - const units = ["KiB", "MiB", "GiB", "TiB", "PiB"]; - let i = 0; - for (const unit of units) { +Deno.test("ProgressBar() uses correct unit type", async (t) => { + await t.step("KiB", async () => { + const expected = + "\r\x1b[K[00:00] [--------------------------------------------------] [0.00/1.00 KiB]"; const { readable, writable } = new TransformStream(); const bar = new ProgressBar({ writable, - max: 2 ** (10 * ++i), + max: 2 ** 10, keepOpen: false, }); - - const decoder = new TextDecoder(); for await (const buffer of readable) { - assertEquals(decoder.decode(buffer.subarray(-4, -1)), unit); + const actual = decoder.decode(buffer); + assertEquals(actual, expected); break; } bar.stop(); - } + }); + await t.step("MiB", async () => { + const expected = + "\r\x1b[K[00:00] [--------------------------------------------------] [0.00/1.00 MiB]"; + const { readable, writable } = new TransformStream(); + const bar = new ProgressBar({ + writable, + max: 2 ** 20, + keepOpen: false, + }); + for await (const buffer of readable) { + const actual = decoder.decode(buffer); + assertEquals(actual, expected); + break; + } + bar.stop(); + }); + await t.step("GiB", async () => { + const expected = + "\r\x1b[K[00:00] [--------------------------------------------------] [0.00/1.00 GiB]"; + const { readable, writable } = new TransformStream(); + const bar = new ProgressBar({ + writable, + max: 2 ** 30, + keepOpen: false, + }); + for await (const buffer of readable) { + const actual = decoder.decode(buffer); + assertEquals(actual, expected); + break; + } + bar.stop(); + }); + await t.step("TiB", async () => { + const expected = + "\r\x1b[K[00:00] [--------------------------------------------------] [0.00/1.00 TiB]"; + const { readable, writable } = new TransformStream(); + const bar = new ProgressBar({ + writable, + max: 2 ** 40, + keepOpen: false, + }); + for await (const buffer of readable) { + const actual = decoder.decode(buffer); + assertEquals(actual, expected); + break; + } + bar.stop(); + }); + await t.step("PiB", async () => { + const expected = + "\r\x1b[K[00:00] [--------------------------------------------------] [0.00/1.00 PiB]"; + const { readable, writable } = new TransformStream(); + const bar = new ProgressBar({ + writable, + max: 2 ** 50, + keepOpen: false, + }); + for await (const buffer of readable) { + const actual = decoder.decode(buffer); + assertEquals(actual, expected); + break; + } + bar.stop(); + }); }); Deno.test("ProgressBar() does not leak resources when immediately stopped", async () => {