listr
Advanced tools
Comparing version 0.5.0 to 0.6.0
29
index.js
'use strict'; | ||
const Task = require('./lib/task'); | ||
const CLIRenderer = require('./lib/renderer').CLIRenderer; | ||
const renderers = require('./lib/renderer'); | ||
const getRenderer = renderer => { | ||
if (typeof renderer === 'string') { | ||
return renderers[renderer] || renderers.default; | ||
} | ||
return renderer || renderers.default; | ||
}; | ||
class Listr { | ||
@@ -17,14 +25,17 @@ | ||
this._RendererClass = CLIRenderer; | ||
this._tasks = []; | ||
this._options = Object.assign({ | ||
showSubtasks: true, | ||
concurrent: false | ||
concurrent: false, | ||
renderer: 'default' | ||
}, opts); | ||
this._tasks = []; | ||
this._RendererClass = getRenderer(this._options.renderer); | ||
this.level = 0; | ||
this.add(tasks || []); | ||
} | ||
get tasks() { | ||
return this._tasks; | ||
} | ||
setRenderer(renderer) { | ||
@@ -46,3 +57,3 @@ this._RendererClass = renderer; | ||
if (!this._renderer) { | ||
this._renderer = new this._RendererClass(this._tasks); | ||
this._renderer = new this._RendererClass(this._tasks, this._options); | ||
} | ||
@@ -68,5 +79,3 @@ | ||
.catch(err => { | ||
if (this.level === 0) { | ||
this._renderer.end(); | ||
} | ||
this._renderer.end(err); | ||
throw err; | ||
@@ -73,0 +82,0 @@ }); |
'use strict'; | ||
module.exports = { | ||
const state = { | ||
PENDING: 0, | ||
@@ -8,1 +8,18 @@ COMPLETED: 1, | ||
}; | ||
state.toString = input => { | ||
switch (input) { | ||
case state.PENDING: | ||
return 'pending'; | ||
case state.COMPLETED: | ||
return 'completed'; | ||
case state.FAILED: | ||
return 'failed'; | ||
case state.SKIPPED: | ||
return 'skipped'; | ||
default: | ||
return 'unknown'; | ||
} | ||
}; | ||
module.exports = state; |
113
lib/task.js
'use strict'; | ||
const Ora = require('ora'); | ||
const logSymbols = require('log-symbols'); | ||
const figures = require('figures'); | ||
const chalk = require('chalk'); | ||
const indentString = require('indent-string'); | ||
const isStream = require('is-stream'); | ||
const isPromise = require('is-promise'); | ||
const streamToObservable = require('stream-to-observable'); | ||
const stripAnsi = require('strip-ansi'); | ||
const cliTruncate = require('cli-truncate'); | ||
const StringRenderer = require('./renderer').StringRenderer; | ||
const Subject = require('rxjs/Subject').Subject; | ||
const renderers = require('./renderer'); | ||
const state = require('./state'); | ||
const color = 'yellow'; | ||
const pointer = chalk[color](figures.pointer); | ||
const skipped = chalk[color](figures.arrowDown); | ||
const isListr = obj => obj && obj.setRenderer && obj.add && obj.run; | ||
// https://github.com/sindresorhus/is-observable/issues/1 | ||
const isObservable = obj => obj && obj.subscribe && obj.forEach; | ||
const isObservable = obj => obj && typeof obj.subscribe === 'function' && typeof obj.constructor.create === 'function'; | ||
const getSymbol = task => { | ||
switch (task.state) { | ||
case state.PENDING: | ||
return task.showSubtasks() ? `${pointer} ` : task._spinner.frame(); | ||
case state.COMPLETED: | ||
return `${logSymbols.success} `; | ||
case state.FAILED: | ||
return task._result ? `${pointer} ` : `${logSymbols.error} `; | ||
case state.SKIPPED: | ||
return `${skipped} `; | ||
default: | ||
return ' '; | ||
} | ||
}; | ||
const defaultSkipFn = () => false; | ||
class Task { | ||
class Task extends Subject { | ||
constructor(listr, task, options) { | ||
super(); | ||
if (!task) { | ||
@@ -61,3 +38,4 @@ throw new TypeError('Expected a task'); | ||
this._options = options || {}; | ||
this._spinner = new Ora({color}); | ||
this._subtasks = []; | ||
this._output = undefined; | ||
@@ -69,28 +47,42 @@ this.title = task.title; | ||
showSubtasks() { | ||
return this._result && (this._options.showSubtasks !== false || this.state === state.FAILED); | ||
get output() { | ||
return this._output; | ||
} | ||
render() { | ||
const skipped = this.state === state.SKIPPED ? ` ${chalk.dim('[skipped]')}` : ''; | ||
let out = indentString(` ${getSymbol(this)}${this.title}${skipped}`, ' ', this._listr.level); | ||
get subtasks() { | ||
return this._subtasks; | ||
} | ||
if (this.showSubtasks()) { | ||
const output = this._result.render(); | ||
if (output !== null) { | ||
out += `\n${output}`; | ||
} | ||
} | ||
set state(state) { | ||
this._state = state; | ||
if ((this.state === state.PENDING || this.state === state.SKIPPED) && this._output) { | ||
const lastLine = stripAnsi(this._output.trim().split('\n').pop().trim()); | ||
if (lastLine) { | ||
const output = indentString(`${figures.arrowRight} ${lastLine}`, ' ', this._listr.level); | ||
out += `\n ${chalk.gray(cliTruncate(output, process.stdout.columns - 3))}`; | ||
} | ||
} | ||
this.next({ | ||
type: 'STATE' | ||
}); | ||
} | ||
return out; | ||
get state() { | ||
return state.toString(this._state); | ||
} | ||
hasSubtasks() { | ||
return this._subtasks.length > 0; | ||
} | ||
isPending() { | ||
return this._state === state.PENDING; | ||
} | ||
isSkipped() { | ||
return this._state === state.SKIPPED; | ||
} | ||
isCompleted() { | ||
return this._state === state.COMPLETED; | ||
} | ||
hasFailed() { | ||
return this._state === state.FAILED; | ||
} | ||
run() { | ||
@@ -100,5 +92,9 @@ const handleResult = result => { | ||
if (isListr(result)) { | ||
result.level = this._listr.level + 1; | ||
result.setRenderer(StringRenderer); | ||
this._result = result; | ||
result.setRenderer(renderers.silent); | ||
this._subtasks = result.tasks; | ||
this.next({ | ||
type: 'SUBTASKS' | ||
}); | ||
return result.run(); | ||
@@ -118,2 +114,7 @@ } | ||
this._output = data; | ||
this.next({ | ||
type: 'DATA', | ||
data | ||
}); | ||
}, | ||
@@ -141,6 +142,6 @@ error: reject, | ||
if (skipped) { | ||
this.state = state.SKIPPED; | ||
if (typeof skipped === 'string') { | ||
this._output = skipped; | ||
} | ||
this.state = state.SKIPPED; | ||
return; | ||
@@ -152,3 +153,3 @@ } | ||
.then(() => { | ||
if (this.state === state.PENDING) { | ||
if (this.isPending()) { | ||
this.state = state.COMPLETED; | ||
@@ -160,2 +161,6 @@ } | ||
throw err; | ||
}) | ||
.then(() => { | ||
// Mark the Observable as completed | ||
this.complete(); | ||
}); | ||
@@ -162,0 +167,0 @@ } |
{ | ||
"name": "listr", | ||
"version": "0.5.0", | ||
"version": "0.6.0", | ||
"description": "Terminal task list", | ||
@@ -46,5 +46,9 @@ "license": "MIT", | ||
"is-stream": "^1.1.0", | ||
"listr-silent-renderer": "^1.0.0", | ||
"listr-update-renderer": "^0.1.1", | ||
"listr-verbose-renderer": "^0.1.0", | ||
"log-symbols": "^1.0.2", | ||
"log-update": "^1.0.2", | ||
"ora": "^0.2.3", | ||
"rxjs": "^5.0.0-beta.11", | ||
"stream-to-observable": "^0.1.0", | ||
@@ -57,3 +61,3 @@ "strip-ansi": "^3.0.1" | ||
"delay": "^1.3.1", | ||
"rxjs": "^5.0.0-beta.9", | ||
"hook-std": "^0.2.0", | ||
"xo": "*" | ||
@@ -63,8 +67,3 @@ }, | ||
"esnext": true | ||
}, | ||
"clinton": { | ||
"rules": { | ||
"editorconfig": "off" | ||
} | ||
} | ||
} |
@@ -31,3 +31,3 @@ # listr [![Build Status](https://travis-ci.org/SamVerschueren/listr.svg?branch=master)](https://travis-ci.org/SamVerschueren/listr) | ||
} | ||
}); | ||
}) | ||
}, | ||
@@ -40,3 +40,3 @@ { | ||
} | ||
}); | ||
}) | ||
} | ||
@@ -66,3 +66,3 @@ ], {concurrent: true}); | ||
### Task | ||
## Task | ||
@@ -87,3 +87,3 @@ A `task` can return different values. If a `task` returns, it means the task was completed successfully. If a task throws an error, the task failed. | ||
#### Promises | ||
### Promises | ||
@@ -96,3 +96,3 @@ A `task` can also be async by returning a `Promise`. If the promise resolves, the task completed successfully, it it rejects, the task failed. | ||
title: 'Success', | ||
task: () => Promise.resolve('Foo'); | ||
task: () => Promise.resolve('Foo') | ||
}, | ||
@@ -106,3 +106,3 @@ { | ||
#### Observable | ||
### Observable | ||
@@ -139,3 +139,3 @@ <img src="media/observable.gif" width="250" align="right"> | ||
#### Streams | ||
### Streams | ||
@@ -145,3 +145,3 @@ It's also possible to return a `stream`. The stream will be converted to an `Observable` and handled as such. | ||
#### Skipping tasks | ||
### Skipping tasks | ||
@@ -180,3 +180,58 @@ <img src="media/skipped.png" width="250" align="right"> | ||
## Custom renderers | ||
It's possible to write custom renderers for Listr. A renderer is an ES6 class that accepts the tasks that it should renderer, and the Listr options object. It has two methods, the `render` method which is called when it should start rendering, and the `end` method. The `end` method is called all the tasks are completed or if a task failed. If a task failed, the error object is passed in via an argument. | ||
```js | ||
class CustomRenderer { | ||
constructor(tasks, options) { } | ||
render() { } | ||
end(err) { } | ||
} | ||
module.exports = CustomRenderer; | ||
``` | ||
> Note: A renderer is not passed through to the subtasks, only to the main task. It is up to you to handle that case. | ||
### Observables | ||
Every task is an observable. The task emits three different events and every event is an object with a `type` property. | ||
1. The state of the task has changed (`STATE`). | ||
2. The task outputted data (`DATA`). | ||
3. The task returns a subtask list (`SUBTASKS`). | ||
This allows you to flexibly build up your UI. Let's render every task that starts executing. | ||
```js | ||
class CustomRenderer { | ||
constructor(tasks, options) { | ||
this._tasks = tasks; | ||
this._options = Object.assign({}, options); | ||
} | ||
render() { | ||
for (const task of this._tasks) { | ||
task.subscribe(event => { | ||
if (event.type === 'STATE' && task.isPending()) { | ||
console.log(`${task.title} [started]`); | ||
} | ||
}); | ||
} | ||
} | ||
end(err) { } | ||
} | ||
module.exports = CustomRenderer; | ||
``` | ||
If you want more complex examples, take a look at the [update](https://github.com/SamVerschueren/listr-update-renderer) and [verbose](https://github.com/SamVerschueren/listr-verbose-renderer) renderers. | ||
## API | ||
@@ -212,10 +267,4 @@ | ||
##### showSubtasks | ||
Any renderer specific options. | ||
Type: `boolean`<br> | ||
Default: `true` | ||
Set to `false` if you want to disable the rendering of the subtasks. Subtasks will be rendered if | ||
an error occurred in one of them. | ||
##### concurrent | ||
@@ -228,2 +277,10 @@ | ||
##### renderer | ||
Type: `string` `object`<br> | ||
Default: `default`<br> | ||
Options: `default` `verbose` `silent` | ||
Renderer that should be used. You can either pass in the name of the known renderer, or a class of a custom renderer. | ||
### Instance | ||
@@ -230,0 +287,0 @@ |
14182
301
15
7
225
+ Addedlistr-silent-renderer@^1.0.0
+ Addedlistr-update-renderer@^0.1.1
+ Addedrxjs@^5.0.0-beta.11
+ Addedelegant-spinner@1.0.1(transitive)
+ Addedindent-string@3.2.0(transitive)
+ Addedlistr-silent-renderer@1.1.1(transitive)
+ Addedlistr-update-renderer@0.1.4(transitive)
+ Addedlistr-verbose-renderer@0.1.0(transitive)
+ Addedrxjs@5.5.12(transitive)
+ Addedsymbol-observable@1.0.1(transitive)