Progress indicator for CLI apps in Deno

Mayank C
Tech Tonic

--

Introduction

One of the use cases for Deno is CLI apps. These apps are different from the usual web apps. The CLI apps run in console/terminal and have a limited flexibility to interact with user & show feedback for ongoing operations.

Some CLI apps may have a need to show a progress indicator to the user. For example — A CLI app asks the user to input a file name, then the file is read & processed. For small files, it isn’t very useful to show progress because the processing of the file would finish before the user blinks. However, for big files, it’d be useful to show a progress indicator. This keeps the user engaged by giving them an idea about how much work is still remaining.

In this article, we’ll write a small progress indicator class. With just a few lines of code, and without using any external module, we can easily build three different types of progress indicators:

  • Dots: A blinking set of dots that indicates some work is in progress
  • Percent: A textual representation of how much % of work is completed
  • Bar: A bar indicator show a bar that gets filled as the work progresses

First, we’ll see the progress indicator, followed by some examples.

Progress Indicator

The code for progress indicator module is present here.

The type of progress indicator (BAR, PERCENT, or DOTS) need to be supplied at the time of creation. Along with the type, an optional number of units can be supplied. For example — number of bars in the progress bar (default is 50), number of dots (default is 5).

There is a single update function that would update the progress indicator. Each call of the update function needs a total value and a current value. The progress indicator class updates progress once every 100MS (updateInterval). This keeps Deno’s memory usage under check. If the progress indicator doesn’t throttle, it’d choke up the write buffer and there would be a sharp increase in memory usage.

The progress indicator uses Deno.stdout.write function to write raw data on the console.

To use the progress indicator, an object needs to be created, and then the update function can be called as and when the caller likes.

import {ProgressIndicatorType, ProgressIndicator} from "./progressindicator.ts";const pi=new ProgressIndicator(ProgressIndicatorType.PERCENT);
while(1) {
// ... some code ...
await pi.update(totalVal, currentVal);
}

Example

Now that we’ve seen a simple progress indicator class, let’s write a small piece of code that would read a big file (5G) in chunks, and update the user with the progress as it reads the chunks. In Deno, the maximum chunk size is 16384. For each chunk, the update function is called. But, the progress indicator would update only every 100MS. As mentioned above, throttling of writers would make sure that Deno’s memory usage remains under check.

import {ProgressIndicatorType, ProgressIndicator} from "./progressindicator.ts";const pi=new ProgressIndicator(ProgressIndicatorType.DOTS);
const fileName='./readings5G.txt';
console.log('Processing file '+fileName);
const s=(await Deno.stat(fileName)).size;
const f=await Deno.open(fileName);
let c=0;
const u=new Uint8Array(16384);
while(1) {
const chunk=await f.read(u);
c+=chunk || 0;
await pi.update(s, c);
if(!chunk)
break;
}
await f.close();
console.log('\nDone, moving to next step');

Here are some sample runs for each type of indicators.

First, a run with default dots (5), and a run with 10 dots:

const pi=new ProgressIndicator(ProgressIndicatorType.DOTS);
const pi=new ProgressIndicator(ProgressIndicatorType.DOTS, 10);

Next, a run with percentage (no customizations for percent):

const pi=new ProgressIndicator(ProgressIndicatorType.PERCENT);

Finally, a run with default bars (50), and a run with 100 bars.

const pi=new ProgressIndicator(ProgressIndicatorType.BAR);
const pi=new ProgressIndicator(ProgressIndicatorType.BAR, 100);

--

--