From 164f3757ec883819aaa4912253bd9ae9d4c0a9e0 Mon Sep 17 00:00:00 2001 From: keindev Date: Fri, 26 Nov 2021 00:08:16 +0300 Subject: [PATCH] refactor: fix eslint errors --- src/ProgressBar.ts | 123 +++++++++++++++--------------- src/Task.ts | 72 +++++++++--------- src/TaskTree.ts | 118 ++++++++++++++--------------- src/Theme.ts | 144 ++++++++++++++++++------------------ src/__tests__/Theme.test.ts | 12 +-- 5 files changed, 235 insertions(+), 234 deletions(-) diff --git a/src/ProgressBar.ts b/src/ProgressBar.ts index 6270ee0b..15d08bdf 100644 --- a/src/ProgressBar.ts +++ b/src/ProgressBar.ts @@ -31,22 +31,22 @@ export enum TemplateToken { /** ProgressBar display options */ export interface IProgressBarOptions { + /** Option to add badge */ + badges?: boolean; + /** Option to clear the bar on completion */ + clear?: boolean; + /** Completion character */ + completeChar?: string; /** Current completed index */ current?: number; + /** Option to add gradient to pending bar */ + gradient?: boolean; + /** Incomplete character */ + incompleteChar?: string; /** Total number of ticks to complete */ total?: number; /** The displayed width of the progress bar defaulting to total */ width?: number; - /** Completion character */ - completeChar?: string; - /** Incomplete character */ - incompleteChar?: string; - /** Option to clear the bar on completion */ - clear?: boolean; - /** Option to add badge */ - badges?: boolean; - /** Option to add gradient to pending bar */ - gradient?: boolean; } /** User template tokens values */ @@ -62,41 +62,42 @@ export interface IProgressBarToken { * ``` */ export class ProgressBar implements Required> { - static readonly TICK = 1; - static readonly TIME_DIMENSION = 1000; + static readonly MAX_PERCENT = 100; static readonly MAX_POINT_POSITION = 1; - static readonly MIN_POINT_POSITION = 0; + static readonly MAX_RATIO = 1; static readonly MIN_PERCENT = 0; - static readonly MAX_PERCENT = 100; + static readonly MIN_POINT_POSITION = 0; static readonly MIN_RATIO = 0; - static readonly MAX_RATIO = 1; + static readonly TICK = 1; + static readonly TIME_DIMENSION = 1000; + + #current = Progress.Start; + #end: number | undefined; + #start = new Date().getTime(); + #status = TaskStatus.Pending; + #tokens: Map = new Map(); - /** Total number of ticks to complete */ - readonly total = Progress.End; - /** Completion character */ - readonly completeChar = figures.square; - /** Incomplete character */ - readonly incompleteChar = figures.square; - /** The displayed width of the progress bar defaulting to total */ - readonly width: number = 20; - /** Option to clear the bar on completion */ - readonly clear: boolean = false; /** Option to add badge */ readonly badges: boolean = true; + /** Option to clear the bar on completion */ + readonly clear: boolean = false; + /** Completion character */ + readonly completeChar = figures.square; /** Option to add gradient to pending bar */ readonly gradient: boolean = true; + /** Incomplete character */ + readonly incompleteChar = figures.square; /** * Output template * * @default `:bar :rate/bps :percent :eta/s` */ readonly template: string; + /** Total number of ticks to complete */ + readonly total = Progress.End; - #current = Progress.Start; - #start = new Date().getTime(); - #end: number | undefined; - #status = TaskStatus.Pending; - #tokens: Map = new Map(); + /** The displayed width of the progress bar defaulting to total */ + readonly width: number = 20; constructor(template?: string, options?: IProgressBarOptions) { this.template = @@ -162,50 +163,22 @@ export class ProgressBar implements Required= this.total || !!this.#end; } - /** - * Increases current progress on step value - * @param step - Value by which the current progress will increase - * @param tokens - Add custom tokens by adding a `{'name': value}` object parameter to your method - * @example - * ```javascript - * const bar = new Progress(':bar template with custom :token'); - * - * bat.tick(10, { token: 100 }); - * ``` - */ - public tick(step?: number, tokens?: IProgressBarToken): ProgressBar { - this.#current = Math.min(this.total, this.#current + (step || ProgressBar.TICK)); - this.#tokens = typeof tokens === 'object' ? new Map(Object.entries(tokens)) : new Map(); - - if (this.isCompleted) this.complete(); - - return this; - } - /** Completes progress and marks it as successful */ - public complete(): void { + complete(): void { this.#current = this.total; this.#status = TaskStatus.Completed; this.#end = new Date().getTime(); } - /** Stops the progress and marks it as skipped */ - public skip(): void { - if (!this.isCompleted) { - this.#status = TaskStatus.Skipped; - this.#end = new Date().getTime(); - } - } - /** Stops the progress and marks it as failed */ - public fail(): void { + fail(): void { if (!this.isCompleted) { this.#status = TaskStatus.Failed; this.#end = new Date().getTime(); } } - public render(theme: Theme): string { + render(theme: Theme): string { const length = Math.round(this.width * this.ratio); const type = Theme.type(this.#status); @@ -235,6 +208,34 @@ export class ProgressBar implements Required(); #errors: string[] = []; + #id: number; + #logs = new Set(); + #status: TaskStatus; + #subtasks: Task[] = []; + #text: string; #warnings = new Set(); - public constructor(text: string, { status, autoClear }: ITaskOptions = {}) { + constructor(text: string, { status, autoClear }: ITaskOptions = {}) { this.#id = ++uid; this.#text = text; this.#autoClear = !!autoClear; @@ -84,7 +84,7 @@ export class Task { return !!this.#subtasks.length; } - public add(text: string, { status, autoClear }: ITaskOptions = {}): Task { + add(text: string, { status, autoClear }: ITaskOptions = {}): Task { const isCompleted = !this.isPending; const task = new Task(text, { status: isCompleted ? TaskStatus.Failed : status, @@ -98,15 +98,8 @@ export class Task { return task; } - /** Update task text */ - public update(text: string): Task { - if (this.isPending) this.#text = text; - - return this; - } - /** Adds a new progress bar. Returns a progress bar object */ - public bar(template?: string, options?: IProgressBarOptions): ProgressBar { + bar(template?: string, options?: IProgressBarOptions): ProgressBar { const bar = new ProgressBar(template, this.isPending ? options : { total: Progress.End }); this.#bars.push(bar); @@ -117,12 +110,12 @@ export class Task { } /** Removes all subtasks and progress bars */ - public clear(): void { + clear(): void { this.#subtasks = []; this.#bars = []; } - public complete(text?: string, clear = this.#autoClear): Task { + complete(text?: string, clear = this.#autoClear): Task { if (this.havePendingSubtasks) this.fail('Subtasks is not complete.'); this.setStatus(TaskStatus.Completed, text, clear); @@ -135,13 +128,15 @@ export class Task { return this; } - public skip(text?: string, clear = this.#autoClear): Task { - this.setStatus(TaskStatus.Skipped, text, clear); + error(error?: string | Error, fail?: boolean): Task { + if (typeof error === 'string') this.#errors.push(error); + if (error instanceof Error && error.stack) this.#errors.push(error.stack); + if (fail) this.fail(error); return this; } - public fail(error?: string | Error, clear = this.#autoClear): never { + fail(error?: string | Error, clear = this.#autoClear): never { const text = error instanceof Error ? error.name : error; this.setStatus(TaskStatus.Failed, text, clear); @@ -149,27 +144,13 @@ export class Task { return TaskTree.tree().exit(ExitCode.Error, error) as never; } - public error(error?: string | Error, fail?: boolean): Task { - if (typeof error === 'string') this.#errors.push(error); - if (error instanceof Error && error.stack) this.#errors.push(error.stack); - if (fail) this.fail(error); - - return this; - } - - public log(text: string): Task { + log(text: string): Task { if (this.isPending) this.#logs.add(text); return this; } - public warn(text: string): Task { - if (this.isPending) this.#warnings.add(text); - - return this; - } - - public render(theme: Theme, level = 0): string[] { + render(theme: Theme, level = 0): string[] { const type = level ? IndicationType.Dim : IndicationType.Default; const rows = [ theme.title(this, level), @@ -186,6 +167,25 @@ export class Task { return rows.map((row): string => theme.paint(row, type)); } + skip(text?: string, clear = this.#autoClear): Task { + this.setStatus(TaskStatus.Skipped, text, clear); + + return this; + } + + /** Update task text */ + update(text: string): Task { + if (this.isPending) this.#text = text; + + return this; + } + + warn(text: string): Task { + if (this.isPending) this.#warnings.add(text); + + return this; + } + private setStatus(status: TaskStatus, text?: string, clear?: boolean): void { if (this.isPending) { if (text) this.#text = text; diff --git a/src/TaskTree.ts b/src/TaskTree.ts index df0a7a27..0d201d1b 100644 --- a/src/TaskTree.ts +++ b/src/TaskTree.ts @@ -20,14 +20,14 @@ export class TaskTree { static readonly TIMEOUT = 100; private static instance: TaskTree; + #autoClear = false; #handle: NodeJS.Timeout | undefined; - #tasks: Task[]; - #theme: Theme; #manager: UpdateManager; + #offset = 0; #silent = false; - #autoClear = false; #started = false; - #offset = 0; + #tasks: Task[]; + #theme: Theme; private constructor(theme?: ThemeOptions) { this.#tasks = []; @@ -35,6 +35,16 @@ export class TaskTree { this.#manager = UpdateManager.getInstance(); } + /** Adds a new task to the task tree. If there are active tasks, add a new one as a subtask - to the last subtask of the first active task */ + static add(text: string): Task { + return TaskTree.tree().add(text); + } + + /** Fail active task or adds a new subtask and call fail on it */ + static fail(error: string | Error, active = true): never { + return TaskTree.tree().fail(error, active); + } + /** * Method to get the object to control the tree * @@ -74,47 +84,21 @@ export class TaskTree { return TaskTree.instance; } - /** Adds a new task to the task tree. If there are active tasks, add a new one as a subtask - to the last subtask of the first active task */ - static add(text: string): Task { - return TaskTree.tree().add(text); - } - - /** Fail active task or adds a new subtask and call fail on it */ - static fail(error: string | Error, active = true): never { - return TaskTree.tree().fail(error, active); - } - - /** Starts output a task tree in a terminal at a defined interval. In “silent mode” - the task tree only collects tasks and is not output it in a terminal */ - start({ silent, autoClear }: ITaskTreeOptions = {}): TaskTree { - this.#silent = !!silent; - this.#autoClear = !!autoClear; - this.#tasks = []; - this.#offset = 0; - this.#started = true; - - if (!this.#handle && !this.#silent) { - this.#manager.hook(); - this.#handle = setInterval((): void => { - this.log(); - }, TaskTree.TIMEOUT); - } - - return this; - } - - /** Stop output a task tree in a terminal */ - stop(): TaskTree { - this.#started = false; - - if (this.#handle) { - clearInterval(this.#handle); + /** + * Adds a new task to the task tree. If there are active tasks, add a new one as a subtask - to the last subtask of the first active task + * @param text - Text for display + */ + add(text: string): Task { + let task = this.#tasks[this.#tasks.length - 1]; - this.log(); - this.#manager.unhook(); - this.#handle = undefined; + if (task && task.isPending) { + task = task.activeSubtask; + task = task.add(text); + } else { + this.#tasks.push((task = new Task(text, { autoClear: this.#autoClear }))); } - return this; + return task; } /** Force the process to exit (see process.exit). Do nothing in "silent mode" */ @@ -133,23 +117,6 @@ export class TaskTree { } } - /** - * Adds a new task to the task tree. If there are active tasks, add a new one as a subtask - to the last subtask of the first active task - * @param text - Text for display - */ - add(text: string): Task { - let task = this.#tasks[this.#tasks.length - 1]; - - if (task && task.isPending) { - task = task.activeSubtask; - task = task.add(text); - } else { - this.#tasks.push((task = new Task(text, { autoClear: this.#autoClear }))); - } - - return task; - } - /** * Fail active task or adds a new subtask and call fail on it * @param error - Text or Error object for display @@ -192,6 +159,39 @@ export class TaskTree { return output; } + /** Starts output a task tree in a terminal at a defined interval. In “silent mode” - the task tree only collects tasks and is not output it in a terminal */ + start({ silent, autoClear }: ITaskTreeOptions = {}): TaskTree { + this.#silent = !!silent; + this.#autoClear = !!autoClear; + this.#tasks = []; + this.#offset = 0; + this.#started = true; + + if (!this.#handle && !this.#silent) { + this.#manager.hook(); + this.#handle = setInterval((): void => { + this.log(); + }, TaskTree.TIMEOUT); + } + + return this; + } + + /** Stop output a task tree in a terminal */ + stop(): TaskTree { + this.#started = false; + + if (this.#handle) { + clearInterval(this.#handle); + + this.log(); + this.#manager.unhook(); + this.#handle = undefined; + } + + return this; + } + private log(): void { const offset = this.#offset; diff --git a/src/Theme.ts b/src/Theme.ts index a913a1b4..6a8ec054 100644 --- a/src/Theme.ts +++ b/src/Theme.ts @@ -66,9 +66,9 @@ export type ThemeOptions = { export class Theme { static INDENT = ' '; + #badges: Map = new Map(); #colors: Map = new Map(); #symbols: Map = new Map(); - #badges: Map = new Map(); constructor(options?: ThemeOptions) { if (options) { @@ -95,6 +95,22 @@ export class Theme { } } + static dye(str: string, color: string): string { + return color ? chalk.hex(color)(str) : str; + } + + static format(template: string): string { + return template ? chalk(Object.assign([], { raw: [template] })) : ''; + } + + static indent(count: number, ...text: string[]): string { + return `${Theme.INDENT.padStart(count * Indent.Default)}${Theme.join(TextSeparator.Space, ...text)}`; + } + + static join(separator: string, ...text: string[]): string { + return text.filter((value): boolean => !!value?.length).join(separator); + } + static type(status: TaskStatus, isList = false): IndicationType { let type: IndicationType; @@ -117,22 +133,6 @@ export class Theme { return type; } - static join(separator: string, ...text: string[]): string { - return text.filter((value): boolean => !!value?.length).join(separator); - } - - static dye(str: string, color: string): string { - return color ? chalk.hex(color)(str) : str; - } - - static indent(count: number, ...text: string[]): string { - return `${Theme.INDENT.padStart(count * Indent.Default)}${Theme.join(TextSeparator.Space, ...text)}`; - } - - static format(template: string): string { - return template ? chalk(Object.assign([], { raw: [template] })) : ''; - } - private static getValueBy(map: Map, type: IndicationType, getDefault: () => T): T { let result = map.get(type); @@ -169,7 +169,46 @@ export class Theme { return rgb; } - public getColor(type: IndicationType): string { + badge(type: IndicationType): string { + const badge = Theme.getValueBy(this.#badges, type, () => { + if (type === IndicationType.Error) return IndicationBadge.Error; + if (type === IndicationType.Skip) return IndicationBadge.Skip; + + return this.#badges.get(IndicationType.Default) || IndicationBadge.Default; + }); + + return badge ? this.paint(badge, IndicationType.Dim) : badge; + } + + bars(list: ProgressBar[], level: number): string[] { + const symbol = this.symbol(IndicationType.Subtask); + + return list + .filter((bar): boolean => !bar.isCompleted || !bar.clear) + .map((bar): string => Theme.indent(level, symbol, bar.render(this))); + } + + errors(errors: string[], level: number): string[] { + const type = IndicationType.Error; + const sublevel = level + Indent.Long; + + return errors.reduce((acc, text): string[] => { + const [error = '', ...lines] = text.split(Terminal.EOL); + const title = Theme.join( + TextSeparator.Space, + this.symbol(IndicationType.Message), + this.symbol(type), + error.trim() + ); + + return acc.concat([ + Theme.indent(level + 1, this.paint(title, type)), + ...lines.map((line): string => Theme.indent(sublevel, this.paint(line.trim(), IndicationType.Dim))), + ]); + }, []); + } + + getColor(type: IndicationType): string { return Theme.getValueBy(this.#colors, type, (): string => { if (type === IndicationType.Active) return IndicationColor.Active; if (type === IndicationType.Success) return IndicationColor.Success; @@ -186,11 +225,7 @@ export class Theme { }); } - public paint(str: string, type: IndicationType): string { - return Theme.dye(Theme.format(str), this.getColor(type)); - } - - public gradient(str: string, gradient: IGradient): string { + gradient(str: string, gradient: IGradient): string { const begin = Theme.rgb(this.getColor(gradient.begin)); const end = Theme.rgb(this.getColor(gradient.end)); const w = gradient.position * 2 - 1; @@ -207,7 +242,19 @@ export class Theme { ); } - public symbol(type: IndicationType): string { + messages(list: string[], type: IndicationType, level: number): string[] { + const symbol = this.symbol(type); + const sign = this.symbol(IndicationType.Message); + const indent = level + 1; + + return list.map((text): string => Theme.indent(indent, sign, symbol, text)); + } + + paint(str: string, type: IndicationType): string { + return Theme.dye(Theme.format(str), this.getColor(type)); + } + + symbol(type: IndicationType): string { const symbol = Theme.getValueBy(this.#symbols, type, () => { if (type === IndicationType.Active) return frame(); if (type === IndicationType.Success) return figures.tick; @@ -225,18 +272,7 @@ export class Theme { return symbol ? this.paint(symbol, type) : symbol; } - public badge(type: IndicationType): string { - const badge = Theme.getValueBy(this.#badges, type, () => { - if (type === IndicationType.Error) return IndicationBadge.Error; - if (type === IndicationType.Skip) return IndicationBadge.Skip; - - return this.#badges.get(IndicationType.Default) || IndicationBadge.Default; - }); - - return badge ? this.paint(badge, IndicationType.Dim) : badge; - } - - public title(task: Task, level: number): string { + title(task: Task, level: number): string { const type = Theme.type(task.status, task.haveSubtasks); const badge = this.badge(type); const symbol = this.symbol(type); @@ -246,40 +282,4 @@ export class Theme { return Theme.indent(level, prefix, symbol, task.text, badge); } - - public errors(errors: string[], level: number): string[] { - const type = IndicationType.Error; - const sublevel = level + Indent.Long; - - return errors.reduce((acc, text): string[] => { - const [error = '', ...lines] = text.split(Terminal.EOL); - const title = Theme.join( - TextSeparator.Space, - this.symbol(IndicationType.Message), - this.symbol(type), - error.trim() - ); - - return acc.concat([ - Theme.indent(level + 1, this.paint(title, type)), - ...lines.map((line): string => Theme.indent(sublevel, this.paint(line.trim(), IndicationType.Dim))), - ]); - }, []); - } - - public messages(list: string[], type: IndicationType, level: number): string[] { - const symbol = this.symbol(type); - const sign = this.symbol(IndicationType.Message); - const indent = level + 1; - - return list.map((text): string => Theme.indent(indent, sign, symbol, text)); - } - - public bars(list: ProgressBar[], level: number): string[] { - const symbol = this.symbol(IndicationType.Subtask); - - return list - .filter((bar): boolean => !bar.isCompleted || !bar.clear) - .map((bar): string => Theme.indent(level, symbol, bar.render(this))); - } } diff --git a/src/__tests__/Theme.test.ts b/src/__tests__/Theme.test.ts index 660e0011..4405dfc9 100644 --- a/src/__tests__/Theme.test.ts +++ b/src/__tests__/Theme.test.ts @@ -1,4 +1,4 @@ -import * as Figures from 'figures'; +import figures from 'figures'; import stripAnsi from 'strip-ansi'; import { IndicationBadge, IndicationType, Theme } from '../Theme'; @@ -14,10 +14,10 @@ describe('Theme', (): void => { expect(stripAnsi(theme.paint(text, IndicationType.Skip))).toBe(text); expect(stripAnsi(theme.paint(text, IndicationType.Error))).toBe(text); - expect(stripAnsi(theme.symbol(IndicationType.Default))).toBe(stripAnsi(Figures.pointerSmall)); - expect(stripAnsi(theme.symbol(IndicationType.Success))).toBe(stripAnsi(Figures.tick)); - expect(stripAnsi(theme.symbol(IndicationType.Skip))).toBe(stripAnsi(Figures.arrowDown)); - expect(stripAnsi(theme.symbol(IndicationType.Error))).toBe(stripAnsi(Figures.cross)); + expect(stripAnsi(theme.symbol(IndicationType.Default))).toBe(stripAnsi(figures.pointerSmall)); + expect(stripAnsi(theme.symbol(IndicationType.Success))).toBe(stripAnsi(figures.tick)); + expect(stripAnsi(theme.symbol(IndicationType.Skip))).toBe(stripAnsi(figures.arrowDown)); + expect(stripAnsi(theme.symbol(IndicationType.Error))).toBe(stripAnsi(figures.cross)); expect(theme.badge(IndicationType.Default)).toBe(IndicationBadge.Default); expect(stripAnsi(theme.badge(IndicationType.Skip))).toBe(IndicationBadge.Skip); @@ -25,7 +25,7 @@ describe('Theme', (): void => { }); it('Custom', (): void => { - const symbol = stripAnsi(Figures.star); + const symbol = stripAnsi(figures.star); const badge = '[test]'; const theme = new Theme({ default: ['#000000', symbol],