Skip to content
This repository has been archived by the owner on Feb 1, 2022. It is now read-only.

Commit

Permalink
fix: added actions
Browse files Browse the repository at this point in the history
  • Loading branch information
jdx committed Jan 16, 2018
1 parent f10be7d commit 8782559
Show file tree
Hide file tree
Showing 20 changed files with 1,201 additions and 26 deletions.
55 changes: 55 additions & 0 deletions examples/action.ts
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()
12 changes: 12 additions & 0 deletions examples/confirm.ts
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))
20 changes: 20 additions & 0 deletions examples/prompt.ts
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))
12 changes: 12 additions & 0 deletions examples/spinner.ts
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))
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,25 @@
"dependencies": {
"@dxcli/screen": "^0.0.0",
"@heroku/linewrap": "^1.0.0",
"ansi-styles": "^3.2.0",
"chalk": "^2.3.0",
"fs-extra": "^5.0.0",
"lodash": "^4.17.4",
"password-prompt": "^1.0.3",
"rxjs": "^5.5.6",
"semver": "^5.4.1"
"semver": "^5.4.1",
"strip-ansi": "^4.0.0",
"supports-color": "^5.1.0"
},
"devDependencies": {
"@dxcli/dev": "^1.1.3",
"@dxcli/dev-semantic-release": "^0.0.3",
"@dxcli/dev-test": "^0.2.0",
"@dxcli/dev-test": "^0.2.1",
"@dxcli/dev-tslint": "^0.0.14",
"@types/fs-extra": "^5.0.0",
"@types/node": "^9.3.0",
"@types/semver": "^5.4.0",
"@types/supports-color": "^3.1.0",
"eslint": "^4.15.0",
"eslint-config-dxcli": "^1.1.4",
"husky": "^0.14.3",
Expand Down
183 changes: 183 additions & 0 deletions src/action/base.ts
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))
}
}
44 changes: 44 additions & 0 deletions src/action/simple.ts
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()
}
}
Loading

0 comments on commit 8782559

Please sign in to comment.