Skip to content

Commit

Permalink
feat: add PLAIN variant progress bar
Browse files Browse the repository at this point in the history
  • Loading branch information
Thanga-Ganapathy committed Apr 5, 2024
1 parent fab1f43 commit 279dcdb
Show file tree
Hide file tree
Showing 9 changed files with 198 additions and 15 deletions.
5 changes: 5 additions & 0 deletions .changeset/clean-ladybugs-fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@opentf/cli-pbar': minor
---

Feature: Added `PLAIN` variant & fixed no progress bars on CI env.
49 changes: 48 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ multiPBar.stop();
```

> [!TIP]
> It is recommended to use the `MEDIUM` sized bars in multi progress bars for better visuals.
> It is recommended to use the `MEDIUM` sized bars in multi progress bars to get better visuals.
## Examples

Expand Down Expand Up @@ -127,6 +127,52 @@ pBar.stop();

![Count Demo](./assets/count-demo.png)

---

Rendering a plain variant progress bar.

```js
import { ProgressBar } from '@opentf/cli-pbar';

const pBar = new ProgressBar({
variant: 'PLAIN',
prefix: 'Downloading',
});

pBar.start({ total: 3 });
pBar.inc();
pBar.stop();
```

![Plain Variant Demo](./assets/plain-demo.png)

---

It does not render progress bars in non TTY terminals, like CI, etc.

```js
import { sleep, aForEach } from '@opentf/std';
import { ProgressBar } from '@opentf/cli-pbar';

const arr = ['File 1', 'File 2', 'File 3'];
const pBar = new ProgressBar({
prefix: 'Downloading',
showPercent: false,
showCount: true,
});

pBar.start({ total: arr.length });

await aForEach(arr, async (f) => {
pBar.inc({ suffix: f });
await sleep(500);
});

pBar.stop();
```

![CI Demo](./assets/ci-demo.png)

## API

### options:
Expand All @@ -143,6 +189,7 @@ pBar.stop();
| autoClear | boolean | false | If true, then it auto-clears the progress bar after the `stop` method is called. |
| showPercent | boolean | true | If false, then it hides the progress bar percent. |
| showCount | boolean | false | If true, then it show the progress bar count. |
| variant | string | 'STANDARD' | There are two variants available, `STANDARD` & `PLAIN`. |

### Instance methods:

Expand Down
59 changes: 59 additions & 0 deletions __tests__/progressBar.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { ProgressBar } from '../src/index.ts';
import {
DEFAULT_BAR_CHAR,
MEDIUM_BAR_CHAR,
PLAIN_DONE_BAR_CHAR,
PLAIN_NOT_DONE_BAR_CHAR,
SMALL_BAR_CHAR,
} from '../src/constants.ts';

Expand Down Expand Up @@ -47,6 +49,14 @@ function getBarsStr(n, done = false) {
).repeat(n);
}

function getPlainBarsStr(n, done = false, color = '') {
return style(
`$${color}.${done ? 'bol' : 'dim'}{${
done ? PLAIN_DONE_BAR_CHAR : PLAIN_NOT_DONE_BAR_CHAR
}}`
).repeat(n);
}

function getBars(complete = 0, percent = 0, opt = {}) {
const options = {
width: 30,
Expand Down Expand Up @@ -210,4 +220,53 @@ describe('Single Progress Bar', () => {
const outBars = getBarsStr(1, true) + getBarsStr(2) + ' [2/3]';
expect(output[2]).toStrictEqual(outBars);
});

it('renders plain progress bar', async () => {
const output = await run(
async (pBar) => {
pBar.start({ total: 3 });
pBar.inc();
pBar.inc();
pBar.stop();
},
{ width: 3, variant: 'PLAIN' }
);
const outBars =
'[' + getPlainBarsStr(1, true) + getPlainBarsStr(2) + ']' + ' 66%';
expect(output[2]).toStrictEqual(outBars);
});

it('renders plain progress bar with colors', async () => {
const output = await run(
async (pBar) => {
pBar.start({ total: 3 });
pBar.inc();
pBar.inc();
pBar.stop();
},
{ width: 3, variant: 'PLAIN', color: 'g', bgColor: 'r' }
);
const outBars =
'[' +
getPlainBarsStr(1, true, 'g') +
getPlainBarsStr(2, false, 'r') +
']' +
' 66%';
expect(output[2]).toStrictEqual(outBars);
});

it('renders no progress bars when the stream is not TTY', async () => {
const output = await run(
async (pBar) => {
pBar.start({ total: 3 });
pBar.inc();
pBar.inc();
pBar.inc();
pBar.stop();
},
{ width: 3, prefix: 'Downloading' },
false
);
expect(output[3]).toBe('⏳ Downloading 100%\n');
});
});
Binary file added assets/ci-demo.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/plain-demo.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 21 additions & 1 deletion demo.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import https from 'node:https';
import path from 'node:path';
import { sleep } from '@opentf/std';
import { aForEach, sleep } from '@opentf/std';
import { style } from '@opentf/cli-styles';
import { ProgressBar } from './src';

Expand Down Expand Up @@ -222,10 +222,30 @@ async function styledTexts() {
multiPBar.stop();
}

async function plain() {
const arr = ['Apple', 'Mango', 'Orange', 'Grapes', 'Pear', 'Guava'];

const pBar = template('Plain', {
variant: 'PLAIN',
prefix: 'Downloading',
showPercent: true,
showCount: false,
});
pBar.start({ total: arr.length });

await aForEach(arr, async (f) => {
await sleep(500);
pBar.inc({ suffix: f });
});

pBar.stop();
}

await defaultBar();
await mediumBar();
await smallBar();
await prefixSuffix();
// await downloading();
await autoClear();
await styledTexts();
await plain();
70 changes: 57 additions & 13 deletions src/ProgressBar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@ import {
shallowMerge,
} from '@opentf/std';
import { type Bar, type BarSize, type Options } from './types';
import { DEFAULT_BAR_CHAR, MEDIUM_BAR_CHAR, SMALL_BAR_CHAR } from './constants';
import {
DEFAULT_BAR_CHAR,
MEDIUM_BAR_CHAR,
PLAIN_DONE_BAR_CHAR,
PLAIN_NOT_DONE_BAR_CHAR,
SMALL_BAR_CHAR,
} from './constants';

class ProgressBar {
private _options: Options = {
Expand All @@ -23,11 +29,16 @@ class ProgressBar {
suffix: '',
showPercent: true,
showCount: false,
variant: 'STANDARD',
};
private _bars: Bar[];

constructor(options?: Partial<Options>) {
this._options = shallowMerge(this._options, options as object) as Options;
const opts =
options?.variant === 'PLAIN'
? { ...this._options, color: '', bgColor: '' }
: this._options;
this._options = shallowMerge(opts, options as object) as Options;
this._bars = [];
}

Expand All @@ -43,22 +54,34 @@ class ProgressBar {
}

private _getBars(bar: Bar, percent: number): string {
const barChar = this._getBarCharBySize(bar.size);
let doneBars, bgBars;
const color = bar.color || this._options.color;
const bgColor = bar.bgColor || this._options.bgColor;
const percentVal = Math.trunc(percentageOf(percent, this._options.width));
const doneBars = style(`$${color}.bol{${barChar}}`).repeat(percentVal);
const bgBars = style(`$${bgColor}.dim{${barChar}}`).repeat(
this._options.width - percentVal
);

if (this._options.variant === 'PLAIN') {
doneBars = style(`$${color}.bol{${PLAIN_DONE_BAR_CHAR}}`).repeat(
percentVal
);
bgBars = style(`$${bgColor}.dim{${PLAIN_NOT_DONE_BAR_CHAR}}`).repeat(
this._options.width - percentVal
);
} else {
const barChar = this._getBarCharBySize(bar.size);
doneBars = style(`$${color}.bol{${barChar}}`).repeat(percentVal);
bgBars = style(`$${bgColor}.dim{${barChar}}`).repeat(
this._options.width - percentVal
);
}

return doneBars + bgBars;
}

private _render() {
this._bars.forEach((b, i) => {
let str = '';

if (i > 0) {
if (i > 0 && this._options.stream.isTTY) {
this._options.stream.write(EOL);
}

Expand All @@ -74,17 +97,21 @@ class ProgressBar {
const showCount = Object.hasOwn(b, 'showCount')
? b.showCount
: this._options.showCount;
const variant = Object.hasOwn(b, 'variant')
? b.variant
: this._options.variant;

if (b.progress) {
const percent = b.total
? Math.trunc(percentage(isNaN(b.value) ? 0 : b.value, b.total))
: 0;
const percent = b.total
? Math.trunc(percentage(isNaN(b.value) ? 0 : b.value, b.total))
: 0;

if (b.progress && this._options.stream.isTTY) {
const bar = this._getBars(b, percent);
str += (
intersperse(
compact([
prefix,
bar,
variant === 'PLAIN' ? `[${bar}]` : bar,
showPercent ? percent + '%' : null,
showCount ? `[${b.value || 0}/${b.total || 0}]` : null,
suffix,
Expand All @@ -96,6 +123,23 @@ class ProgressBar {
str += prefix + ' ' + suffix;
}

if (!this._options.stream.isTTY) {
str = (
intersperse(
compact([
'⏳',
prefix,
showPercent ? percent + '%' : null,
showCount ? `[${b.value || 0}/${b.total || 0}]` : null,
suffix,
]),
' '
) as string[]
).join('');
this._options.stream.write(str + EOL);
return;
}

if (
this._options.stream.cursorTo(0) &&
this._options.stream.clearLine(0)
Expand Down
6 changes: 6 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
export const DEFAULT_BAR_CHAR = '\u{2588}';

export const SMALL_BAR_CHAR = '\u{2501}';

export const MEDIUM_BAR_CHAR = '\u{2586}';

export const PLAIN_DONE_BAR_CHAR = '=';

export const PLAIN_NOT_DONE_BAR_CHAR = '-';
2 changes: 2 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export type Options = {
showPercent: boolean;
/** Show hide progress bar count */
showCount: boolean;
/** Renders the progress bars based on the variant */
variant: 'PLAIN' | 'STANDARD';
};

export type Bar = Omit<Options, 'stream' | 'width' | 'autoClear'> & {
Expand Down

0 comments on commit 279dcdb

Please sign in to comment.