From 2378eaf053289efc8893144da4d7b72607ba6d3e Mon Sep 17 00:00:00 2001 From: Tommy Date: Fri, 24 Mar 2023 02:18:51 -0500 Subject: [PATCH] Add `suffixText` option (#223) --- example.js | 28 ++++++++++++++++++++++++++-- index.d.ts | 21 +++++++++++++++++++++ index.js | 45 ++++++++++++++++++++++++++++++++++++++++----- index.test-d.ts | 4 ++++ readme.md | 21 ++++++++++++++++++++- test.js | 48 ++++++++++++++++++++++++++++++++++++++++++------ 6 files changed, 153 insertions(+), 14 deletions(-) diff --git a/example.js b/example.js index f782a5f..9a0cc80 100644 --- a/example.js +++ b/example.js @@ -1,5 +1,6 @@ import process from 'node:process'; import chalk from 'chalk'; +import logSymbols from 'log-symbols'; import ora from './index.js'; const spinner = ora({ @@ -46,7 +47,30 @@ setTimeout(() => { }, 6000); setTimeout(() => { - spinner.succeed(); -}, 7000); + spinner.prefixText = chalk.dim('[info]'); + spinner.spinner = 'dots'; + spinner.text = 'Loading with prefix text'; +}, 8000); + +setTimeout(() => { + spinner.prefixText = ''; + spinner.suffixText = chalk.dim('[info]'); + spinner.text = 'Loading with suffix text'; +}, 10_000); + +setTimeout(() => { + spinner.prefixText = chalk.dim('[info]'); + spinner.suffixText = chalk.dim('[info]'); + spinner.text = 'Loading with prefix and suffix text'; +}, 12_000); + +setTimeout(() => { + spinner.stopAndPersist({ + prefixText: '', + suffixText: '', + symbol: logSymbols.info, + text: 'Stopping with different text!', + }); +}, 14_000); // $ node example.js nameOfSpinner diff --git a/index.d.ts b/index.d.ts index 35000c8..400cea2 100644 --- a/index.d.ts +++ b/index.d.ts @@ -18,6 +18,8 @@ export type Color = export type PrefixTextGenerator = () => string; +export type SuffixTextGenerator = () => string; + export interface Options { /** Text to display after the spinner. @@ -29,6 +31,11 @@ export interface Options { */ readonly prefixText?: string | PrefixTextGenerator; + /** + Text or a function that returns text to display after the spinner text. No suffix text will be displayed if set to an empty string. + */ + readonly suffixText?: string | SuffixTextGenerator; + /** Name of one of the provided spinners. See [`example.js`](https://github.com/BendingBender/ora/blob/main/example.js) in this repo if you want to test out different spinners. On Windows, it will always use the line spinner as the Windows command-line doesn't have proper Unicode support. @@ -130,6 +137,13 @@ export interface PersistOptions { Default: Current `prefixText`. */ readonly prefixText?: string | PrefixTextGenerator; + + /** + Text or a function that returns text to be persisted after the text after the symbol. No suffix text will be displayed if set to an empty string. + + Default: Current `suffixText`. + */ + readonly suffixText?: string | SuffixTextGenerator; } export interface PromiseOptions extends Options { @@ -161,6 +175,13 @@ export interface Ora { */ prefixText: string; + /** + Change the text or function that returns text after the spinner text. + + No suffix text will be displayed if set to an empty string. + */ + suffixText: string; + /** Change the spinner color. */ diff --git a/index.js b/index.js index f6e94b2..ea5d9bb 100644 --- a/index.js +++ b/index.js @@ -24,6 +24,7 @@ class Ora { #indent; #text; #prefixText; + #suffixText; color; @@ -57,6 +58,7 @@ class Ora { // It's important that these use the public setters. this.text = this.#options.text; this.prefixText = this.#options.prefixText; + this.suffixText = this.#options.suffixText; this.indent = this.#options.indent; if (process.env.NODE_ENV === 'test') { @@ -147,6 +149,15 @@ class Ora { this.updateLineCount(); } + get suffixText() { + return this.#suffixText; + } + + set suffixText(value) { + this.#suffixText = value || ''; + this.updateLineCount(); + } + get isSpinning() { return this.#id !== undefined; } @@ -164,12 +175,26 @@ class Ora { return ''; } + getFullSuffixText(suffixText = this.#suffixText, prefix = ' ') { + if (typeof suffixText === 'string' && suffixText !== '') { + return prefix + suffixText; + } + + if (typeof suffixText === 'function') { + return prefix + suffixText(); + } + + return ''; + } + updateLineCount() { const columns = this.#stream.columns || 80; const fullPrefixText = this.getFullPrefixText(this.#prefixText, '-'); + const fullSuffixText = this.getFullSuffixText(this.#suffixText, '-'); + const fullText = ' '.repeat(this.#indent) + fullPrefixText + '--' + this.#text + '--' + fullSuffixText; this.#lineCount = 0; - for (const line of stripAnsi(' '.repeat(this.#indent) + fullPrefixText + '--' + this.#text).split('\n')) { + for (const line of stripAnsi(fullText).split('\n')) { this.#lineCount += Math.max(1, Math.ceil(wcwidth(line) / columns)); } } @@ -209,8 +234,9 @@ class Ora { this.#frameIndex = ++this.#frameIndex % frames.length; const fullPrefixText = (typeof this.#prefixText === 'string' && this.#prefixText !== '') ? this.#prefixText + ' ' : ''; const fullText = typeof this.text === 'string' ? ' ' + this.text : ''; + const fullSuffixText = (typeof this.#suffixText === 'string' && this.#suffixText !== '') ? ' ' + this.#suffixText : ''; - return fullPrefixText + frame + fullText; + return fullPrefixText + frame + fullText + fullSuffixText; } clear() { @@ -328,12 +354,21 @@ class Ora { return this; } - const prefixText = options.prefixText || this.#prefixText; - const text = options.text || this.text; + const prefixText = options.prefixText ?? this.#prefixText; + const fullPrefixText = this.getFullPrefixText(prefixText, ' '); + + const symbolText = options.symbol ?? ' '; + + const text = options.text ?? this.text; const fullText = (typeof text === 'string') ? ' ' + text : ''; + const suffixText = options.suffixText ?? this.#suffixText; + const fullSuffixText = this.getFullSuffixText(suffixText, ' '); + + const textToWrite = fullPrefixText + symbolText + fullText + fullSuffixText + '\n'; + this.stop(); - this.#stream.write(`${this.getFullPrefixText(prefixText, ' ')}${options.symbol || ' '}${fullText}\n`); + this.#stream.write(textToWrite); return this; } diff --git a/index.test-d.ts b/index.test-d.ts index 7219e22..ea954ea 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -7,6 +7,8 @@ const spinner = ora('Loading unicorns'); ora({text: 'Loading unicorns'}); ora({prefixText: 'Loading unicorns'}); ora({prefixText: () => 'Loading unicorns dynamically'}); +ora({suffixText: 'Loading unicorns'}); +ora({suffixText: () => 'Loading unicorns dynamically'}); ora({spinner: 'squish'}); ora({spinner: {frames: ['-', '+', '-']}}); ora({spinner: {interval: 80, frames: ['-', '+', '-']}}); @@ -40,6 +42,7 @@ spinner.stopAndPersist(); spinner.stopAndPersist({text: 'all done'}); spinner.stopAndPersist({symbol: '@', text: 'all done'}); spinner.stopAndPersist({prefixText: 'all done'}); +spinner.stopAndPersist({suffixText: 'all done'}); spinner.clear(); spinner.render(); spinner.frame(); @@ -58,6 +61,7 @@ void oraPromise(async () => { }, 'foo'); void oraPromise(async spinner => { spinner.prefixText = 'foo'; + spinner.suffixText = '[loading]'; await resolves; return 7; }, { diff --git a/readme.md b/readme.md index 311b898..169d485 100644 --- a/readme.md +++ b/readme.md @@ -50,6 +50,12 @@ Type: `string | () => string` Text or a function that returns text to display before the spinner. No prefix text will be displayed if set to an empty string. +##### suffixText + +Type: `string | () => string` + +Text or a function that returns text to display after the spinner text. No suffix text will be displayed if set to an empty string. + ##### spinner Type: `string | object`\ @@ -142,6 +148,12 @@ Change the text before the spinner. No prefix text will be displayed if set to an empty string. +#### .suffixText get/set + +Change the text after the spinner text. + +No suffix text will be displayed if set to an empty string. + #### .color get/set Change the spinner color. @@ -208,7 +220,7 @@ Symbol to replace the spinner with. Type: `string`\ Default: Current `'text'` -Text to be persisted after the symbol +Text to be persisted after the symbol. ###### prefixText @@ -217,6 +229,13 @@ Default: Current `prefixText` Text to be persisted before the symbol. No prefix text will be displayed if set to an empty string. +###### suffixText + +Type: `string`\ +Default: Current `suffixText` + +Text to be persisted after the text after the symbol. No suffix text will be displayed if set to an empty string. + #### .clear() diff --git a/test.js b/test.js index 276bd5b..c91dee3 100644 --- a/test.js +++ b/test.js @@ -239,6 +239,16 @@ test('erases wrapped lines', t => { t.is(clearedLines, 3); // Cleared 'foo\n\nbar' t.is(cursorAtRow, -2); + spinner.clear(); + reset(); + spinner.prefixText = 'foo\n'; + spinner.text = '\nbar'; + spinner.suffixText = '\nbaz'; + spinner.render(); + spinner.render(); + t.is(clearedLines, 4); // Cleared 'foo\n\nbar \nbaz' + t.is(cursorAtRow, -3); + spinner.stop(); }); @@ -398,6 +408,30 @@ test('.stopAndPersist() with dynamic prefixText', macro, spinner => { spinner.stopAndPersist({symbol: '&', prefixText: () => 'babeee', text: 'yorkie'}); }, /babeee & yorkie\n$/, {prefixText: () => 'babeee'}); +test('.stopAndPersist() with suffixText', macro, spinner => { + spinner.stopAndPersist({symbol: '@', text: 'foo'}); +}, /@ foo bar\n$/, {suffixText: 'bar'}); + +test('.stopAndPersist() with empty suffixText', macro, spinner => { + spinner.stopAndPersist({symbol: '@', text: 'foo'}); +}, /@ foo\n$/, {suffixText: ''}); + +test('.stopAndPersist() with manual suffixText', macro, spinner => { + spinner.stopAndPersist({symbol: '@', suffixText: 'baz', text: 'foo'}); +}, /@ foo baz\n$/, {suffixText: 'bar'}); + +test('.stopAndPersist() with manual empty suffixText', macro, spinner => { + spinner.stopAndPersist({symbol: '@', suffixText: '', text: 'foo'}); +}, /@ foo\n$/, {suffixText: 'bar'}); + +test('.stopAndPersist() with dynamic suffixText', macro, spinner => { + spinner.stopAndPersist({symbol: '&', suffixText: () => 'babeee', text: 'yorkie'}); +}, /& yorkie babeee\n$/, {suffixText: () => 'babeee'}); + +test('.stopAndPersist() with prefixText and suffixText', macro, spinner => { + spinner.stopAndPersist({symbol: '@', text: 'foo'}); +}, /bar @ foo baz\n$/, {prefixText: 'bar', suffixText: 'baz'}); + // New clear method tests const currentClearMethod = transFormTTY => { @@ -596,21 +630,23 @@ test('new clear method test, erases wrapped lines', t => { currentOra.clear(); currentOra.prefixText = 'foo\n'; currentOra.text = '\nbar'; + currentOra.suffixText = '\nbaz'; currentOra.render(); currentOra.render(); spinner.clear(); spinner.prefixText = 'foo\n'; spinner.text = '\nbar'; + spinner.suffixText = '\nbaz'; spinner.render(); spinner.render(); - t.is(clearedLines(), 3); // Cleared 'foo\n\nbar' - t.is(cursorAtRow(), -2); + t.is(clearedLines(), 4); // Cleared 'foo\n\nbar \nbaz' + t.is(cursorAtRow(), -3); const [sequenceString, clearedSequenceString] = transformTTY.getSequenceStrings(); const [frames, clearedFrames] = transformTTY.getFrames(); - t.is(sequenceString, 'foo\n - \nbar'); + t.is(sequenceString, 'foo\n - \nbar \nbaz'); t.is(sequenceString, clearedSequenceString); t.deepEqual(clearedFrames, [ @@ -630,14 +666,14 @@ test('new clear method test, erases wrapped lines', t => { '- 🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄\n' + '🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄🦄\n' + 'foo', - 'foo\n - \nbar', - 'foo\n - \nbar', + 'foo\n - \nbar \nbaz', + 'foo\n - \nbar \nbaz', ]); t.deepEqual(frames, clearedFrames); const currentClearString = currentClearTTY.toString(); - t.is(currentClearString, 'foo\n - \nbar'); + t.is(currentClearString, 'foo\n - \nbar \nbaz'); const currentFrames = currentClearTTY.getFrames(); t.deepEqual(frames, currentFrames);