This repository has been archived by the owner on Feb 1, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
20 changed files
with
1,201 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
// tslint:disable | ||
import cli from '../src' | ||
import SimpleAction from '../src/action/simple' | ||
|
||
const wait = (ms = 400) => new Promise(resolve => setTimeout(resolve, ms)) | ||
|
||
async function run() { | ||
cli.action.start('x foo') | ||
await wait() | ||
cli.log('1 log') | ||
await wait() | ||
cli.action.status = 'a wild status appeared!' | ||
await wait() | ||
cli.log('2 log') | ||
await wait() | ||
cli.log('3 log') | ||
await wait() | ||
cli.action.start('4 bar') | ||
await wait() | ||
cli.action.stop('now it is done') | ||
await wait() | ||
cli.action.start('x hideme') | ||
await wait() | ||
cli.action.start('5 foo') | ||
await wait() | ||
process.stderr.write('partial') | ||
await wait() | ||
cli.action.stop() | ||
await wait() | ||
cli.action.start('6 warn') | ||
await wait() | ||
cli.warn('uh oh') | ||
await wait() | ||
cli.action.stop() | ||
await wait() | ||
} | ||
|
||
async function main() { | ||
console.log('SPINNER') | ||
console.log('=======') | ||
await run() | ||
|
||
console.log('\nSIMPLE') | ||
console.log('======') | ||
process.env.TERM = 'dumb' | ||
cli.config.action = new SimpleAction() | ||
await run() | ||
|
||
console.log('\nERROR') | ||
console.log('=====') | ||
cli.action.start('7 error out') | ||
await wait() | ||
cli.error('oh no') | ||
} | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import {cli} from '../src' | ||
|
||
const wait = (ms = 400) => new Promise(resolve => setTimeout(resolve, ms)) | ||
|
||
async function run() { | ||
cli.action.start('doing a thing') | ||
await wait() | ||
let input = await cli.confirm('yes or no') | ||
await wait() | ||
cli.log(`you entered: ${input}`) | ||
} | ||
run().catch(err => cli.error(err)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { cli } from '../src' | ||
|
||
const wait = (ms = 400) => new Promise(resolve => setTimeout(resolve, ms)) | ||
|
||
async function run() { | ||
cli.action.start('doing a thing') | ||
await wait() | ||
let input = await cli.prompt('your name (normal)') | ||
cli.action.start('working') | ||
await wait() | ||
cli.log(`you entered: ${input}`) | ||
input = await cli.prompt('your name (mask)', { type: 'mask' }) | ||
await wait() | ||
cli.log(`you entered: ${input}`) | ||
input = await cli.prompt('your name (hide)', { type: 'hide' }) | ||
await wait() | ||
cli.action.stop() | ||
cli.log(`you entered: ${input}`) | ||
} | ||
run().catch(err => cli.error(err)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import cli from '../src' | ||
|
||
function wait() { | ||
return new Promise(resolve => setTimeout(resolve, 1000)) | ||
} | ||
|
||
async function run() { | ||
cli.action.start('running') | ||
await wait() | ||
cli.action.stop() | ||
} | ||
run().catch(err => cli.error(err)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,183 @@ | ||
import * as _ from 'lodash' | ||
import {inspect} from 'util' | ||
|
||
export interface ITask { | ||
action: string | ||
status: string | undefined | ||
active: boolean | ||
} | ||
|
||
export type ActionType = 'spinner' | 'simple' | 'debug' | ||
|
||
export interface Options { | ||
stderr?: boolean | ||
} | ||
|
||
const stdmockWrite = { | ||
stdout: process.stdout.write, | ||
stderr: process.stderr.write, | ||
} | ||
|
||
export class ActionBase { | ||
type: ActionType | ||
std: 'stdout' | 'stderr' = 'stdout' | ||
protected stdmocks?: ['stdout' | 'stderr', string[]][] | ||
|
||
public start(action: string, status?: string, opts: Options = {}) { | ||
this.std = opts.stderr ? 'stderr' : 'stdout' | ||
const task = (this.task = {action, status, active: !!(this.task && this.task.active)}) | ||
this._start() | ||
task.active = true | ||
this._stdout(true) | ||
} | ||
|
||
public stop(msg: string = 'done') { | ||
const task = this.task | ||
if (!task) { | ||
return | ||
} | ||
this._stop(msg) | ||
task.active = false | ||
this.task = undefined | ||
this._stdout(false) | ||
} | ||
|
||
private get globals(): { action: { task?: ITask }; output: string | undefined } { | ||
const globals = ((global as any)['cli-ux'] = (global as any)['cli-ux'] || {}) | ||
globals.action = globals.action || {} | ||
return globals | ||
} | ||
|
||
public get task(): ITask | undefined { | ||
return this.globals.action.task | ||
} | ||
|
||
public set task(task: ITask | undefined) { | ||
this.globals.action.task = task | ||
} | ||
|
||
protected get output(): string | undefined { | ||
return this.globals.output | ||
} | ||
protected set output(output: string | undefined) { | ||
this.globals.output = output | ||
} | ||
|
||
get running(): boolean { | ||
return !!this.task | ||
} | ||
|
||
get status(): string | undefined { | ||
return this.task ? this.task.status : undefined | ||
} | ||
set status(status: string | undefined) { | ||
const task = this.task | ||
if (!task) { | ||
return | ||
} | ||
if (task.status === status) { | ||
return | ||
} | ||
this._updateStatus(status, task.status) | ||
task.status = status | ||
} | ||
|
||
public async pauseAsync(fn: () => Promise<any>, icon?: string) { | ||
const task = this.task | ||
const active = task && task.active | ||
if (task && active) { | ||
this._pause(icon) | ||
this._stdout(false) | ||
task.active = false | ||
} | ||
const ret = await fn() | ||
if (task && active) { | ||
this._resume() | ||
} | ||
return ret | ||
} | ||
|
||
public pause(fn: () => any, icon?: string) { | ||
const task = this.task | ||
const active = task && task.active | ||
if (task && active) { | ||
this._pause(icon) | ||
this._stdout(false) | ||
task.active = false | ||
} | ||
const ret = fn() | ||
if (task && active) { | ||
this._resume() | ||
} | ||
return ret | ||
} | ||
|
||
protected _start() { | ||
throw new Error('not implemented') | ||
} | ||
protected _stop(_: string) { | ||
throw new Error('not implemented') | ||
} | ||
protected _resume() { | ||
if (this.task) this.start(this.task.action, this.task.status) | ||
} | ||
protected _pause(_?: string) { | ||
throw new Error('not implemented') | ||
} | ||
protected _updateStatus(_: string | undefined, __?: string) {} | ||
|
||
/** | ||
* mock out stdout/stderr so it doesn't screw up the rendering | ||
*/ | ||
protected _stdout(toggle: boolean) { | ||
try { | ||
const outputs: ['stdout', 'stderr'] = ['stdout', 'stderr'] | ||
if (toggle) { | ||
if (this.stdmocks) return | ||
this.stdmocks = [] | ||
for (let std of outputs) { | ||
(process[std] as any).write = (...args: any[]) => { | ||
this.stdmocks!.push([std, args] as ['stdout' | 'stderr', string[]]) | ||
} | ||
} | ||
} else { | ||
if (!this.stdmocks) return | ||
// this._write('stderr', '\nresetstdmock\n\n\n') | ||
delete this.stdmocks | ||
for (let std of outputs) process[std].write = stdmockWrite[std] as any | ||
} | ||
} catch (err) { | ||
this._write('stderr', inspect(err)) | ||
} | ||
} | ||
|
||
/** | ||
* flush mocked stdout/stderr | ||
*/ | ||
protected _flushStdout() { | ||
try { | ||
let output = '' | ||
let std: 'stdout' | 'stderr' | undefined | ||
while (this.stdmocks && this.stdmocks.length) { | ||
let cur = this.stdmocks.shift() as ['stdout' | 'stderr', string[]] | ||
std = cur[0] | ||
this._write(std, cur[1]) | ||
output += cur[1].map((a: any) => a.toString('utf8')).join('') | ||
} | ||
// add newline if there isn't one already | ||
// otherwise we'll just overwrite it when we render | ||
if (output && std && output[output.length - 1] !== '\n') { | ||
this._write(std, '\n') | ||
} | ||
} catch (err) { | ||
this._write('stderr', inspect(err)) | ||
} | ||
} | ||
|
||
/** | ||
* write to the real stdout/stderr | ||
*/ | ||
protected _write(std: 'stdout' | 'stderr', s: string | string[]) { | ||
stdmockWrite[std].apply(process[std], _.castArray(s)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import {ActionBase, ActionType} from './base' | ||
|
||
export default class SimpleAction extends ActionBase { | ||
public type: ActionType = 'simple' | ||
|
||
protected _start() { | ||
const task = this.task | ||
if (!task) return | ||
this._render(task.action, task.status) | ||
} | ||
|
||
protected _pause(icon?: string) { | ||
if (icon) this._updateStatus(icon) | ||
else this._flush() | ||
} | ||
|
||
protected _resume() {} | ||
|
||
protected _updateStatus(status: string, prevStatus?: string, newline: boolean = false) { | ||
const task = this.task | ||
if (!task) return | ||
if (task.active && !prevStatus) this._write(this.std, ` ${status}`) | ||
else this._write(this.std, `${task.action}... ${status}`) | ||
if (newline || !prevStatus) this._flush() | ||
} | ||
|
||
protected _stop(status: string) { | ||
const task = this.task | ||
if (!task) return | ||
this._updateStatus(status, task.status, true) | ||
} | ||
|
||
private _render(action: string, status?: string) { | ||
const task = this.task | ||
if (!task) return | ||
if (task.active) this._flush() | ||
this._write(this.std, status ? `${action}... ${status}` : `${action}...`) | ||
} | ||
|
||
private _flush() { | ||
this._write(this.std, '\n') | ||
this._flushStdout() | ||
} | ||
} |
Oops, something went wrong.