gauge
Advanced tools
Comparing version 2.1.0 to 2.2.0
@@ -0,1 +1,9 @@ | ||
### v2.2.0 | ||
* All new themes API– reference themes by name and pass in custom themes and | ||
themesets (themesets get platform support autodetection done on them to | ||
select the best theme). Theme mixins let you add features to all existing | ||
themes. | ||
* Much, much improved test coverage. | ||
### v2.1.0 | ||
@@ -2,0 +10,0 @@ |
@@ -48,2 +48,6 @@ 'use strict' | ||
exports.gotoSOL = function () { | ||
return '\r' | ||
} | ||
exports.hideCursor = function hideCursor () { | ||
@@ -50,0 +54,0 @@ return prefix + '?25l' |
16
demo.js
var Gauge = require('./') | ||
var theme = require('./themes.js') | ||
var gaugeDefault = require('./themes.js') | ||
var onExit = require('signal-exit') | ||
var themes = [] | ||
;['darwin', 'linux', 'win32'].forEach(function (os) { | ||
themes.push( | ||
[os + ' unicode & color', theme({platform: os, hasUnicode: true, hasColor: true})], | ||
[os + ' unicode & nocolor', theme({platform: os, hasUnicode: true, hasColor: false})], | ||
[os + ' nounicode & color', theme({platform: os, hasUnicode: false, hasColor: true})], | ||
[os + ' nounicode & nocolor', theme({platform: os, hasUnicode: false, hasColor: false})] | ||
) | ||
}) | ||
var activeGauge | ||
@@ -21,2 +11,6 @@ | ||
var themes = Object.keys(gaugeDefault.themes).map(function (key) { | ||
return [key, gaugeDefault.themes[key]] | ||
}) | ||
nextBar() | ||
@@ -23,0 +17,0 @@ function nextBar () { |
@@ -7,3 +7,5 @@ 'use strict' | ||
var onExit = require('signal-exit') | ||
var themes = require('./themes') | ||
var defaultThemes = require('./themes') | ||
var setInterval = require('./set-interval.js') | ||
var process = require('./process.js') | ||
@@ -41,2 +43,3 @@ module.exports = Gauge | ||
var themes = options.themes || defaultThemes | ||
var theme = options.theme || themes({hasUnicode: hasUnicode(), hasColor: hasColor}) | ||
@@ -56,3 +59,3 @@ var template = options.template || [ | ||
if (options.cleanupOnExit == null || options.cealnupOnExit) { | ||
if (options.cleanupOnExit == null || options.cleanupOnExit) { | ||
onExit(callWith(this, this.disable)) | ||
@@ -59,0 +62,0 @@ } |
{ | ||
"name": "gauge", | ||
"version": "2.1.0", | ||
"version": "2.2.0", | ||
"description": "A terminal based horizontal guage", | ||
@@ -35,3 +35,3 @@ "main": "index.js", | ||
"devDependencies": { | ||
"require-inject": "^1.3.0", | ||
"require-inject": "^1.3.1", | ||
"standard": "^6.0.7", | ||
@@ -38,0 +38,0 @@ "tap": "^5.6.0", |
@@ -32,3 +32,3 @@ 'use strict' | ||
Plumbing.prototype.hide = function () { | ||
return '\r' + consoleStrings.eraseLine() | ||
return consoleStrings.gotoSOL() + consoleStrings.eraseLine() | ||
} | ||
@@ -42,6 +42,8 @@ | ||
var values = Object.create(this.theme) | ||
Object.keys(status).forEach(function (key) { values[key] = status[key] }) | ||
for (var key in status) { | ||
values[key] = status[key] | ||
} | ||
return renderTemplate(this.width, this.template, values) + | ||
consoleStrings.eraseLine() + '\r' | ||
consoleStrings.eraseLine() + consoleStrings.gotoSOL() | ||
} |
103
README.md
@@ -18,3 +18,3 @@ gauge | ||
![](example.png) | ||
![](gauge-demo.gif) | ||
@@ -66,9 +66,11 @@ | ||
expect your program to exit. | ||
* **theme**: The theme to use as output. Defaults to something usable on | ||
your combination of OS, color and unicode support. In practice this means | ||
that Windows always gets ASCII by default, Mac almost always gets unicode, | ||
and everyone almost always gets color. Unicode is detected with | ||
[has-unicode] and color is detected by checking platform and TERM env var. | ||
Theme selection is done by the internal [themes] module, which you can use | ||
directly. | ||
* **themes**: A themeset to use when selecting the theme to use. Defaults | ||
to `gauge/themes`, see the [themes] documentation for details. | ||
* **theme**: Alternatively you can specificy a specific theme to use. If you | ||
use this, there's no reason to use **themes**. | ||
The default theme is selected using **themes** to something usable on your | ||
combination of OS, color and unicode support. Unicode support is detected | ||
with [has-unicode] and color is detected by checking platform and TERM env | ||
var. | ||
* **template**: Describes what you want your gauge to look like. The | ||
@@ -150,13 +152,13 @@ default is what npm uses. Detailed [documentation] is later in this | ||
``` | ||
var theme = require('gauge/themes') | ||
var themes = require('gauge/themes') | ||
// fetch the default color unicode theme for this platform | ||
var ourTheme = theme({hasUnicode: true, hasColor: true}) | ||
var ourTheme = themes({hasUnicode: true, hasColor: true}) | ||
// fetch the default non-color unicode theme for osx | ||
var ourTheme = theme({hasUnicode: true, hasColor: false, platform: 'darwin'}) | ||
var ourTheme = themes({hasUnicode: true, hasColor: false, platform: 'darwin'}) | ||
// create a new theme based on the color ascii theme for this platform | ||
// that brackets the progress bar with arrows | ||
var ourTheme = theme.newTheme(theme(hasUnicode: false, hasColor: true}), { | ||
var ourTheme = themes.newTheme(theme(hasUnicode: false, hasColor: true}), { | ||
preProgressbar: '→', | ||
@@ -167,18 +169,66 @@ postProgressbar: '←' | ||
#### theme(opts) | ||
The object returned by `gauge/themes` is an instance of the `ThemeSet` class. | ||
Fetches a theme object based on platform settings. | ||
``` | ||
var ThemeSet = require('gauge/theme-set') | ||
var themes = new ThemeSet() | ||
// or | ||
var themes = require('gauge/themes') | ||
var mythemes = themes.newThemeset() // creates a new themeset based on the default themes | ||
``` | ||
#### themes(opts) | ||
#### themes.getDefault(opts) | ||
Theme objects are a function that fetches the default theme based on | ||
platform, unicode and color support. | ||
Options is an object with the following properties: | ||
* **hasUnicode** - If true, fetch a unicode theme. | ||
* **hasColor** - If true, fetch a color theme. | ||
* **platform** (optional) - Defaults to `process.platform`. The platform code of the theme we want. | ||
* **hasUnicode** - If true, fetch a unicode theme, if no unicode theme is | ||
available then a non-unicode theme will be used. | ||
* **hasColor** - If true, fetch a color theme, if no color theme is | ||
available a non-color theme will be used. | ||
* **platform** (optional) - Defaults to `process.platform`. If no | ||
platform match is available then `fallback` is used instead. | ||
#### theme.newTheme([parent,] newTheme) | ||
If no compatible theme can be found then an error will be thrown with a | ||
`code` of `EMISSINGTHEME`. | ||
Create a new theme object based on `parent`. If no `parent` is provided | ||
then a minimal parent that defines functions for rendering the activity | ||
indicator (spinner) and progress bar will be defined. | ||
#### themes.addTheme(themeName, themeObj) | ||
#### themes.addTheme(themeName, [parentTheme], newTheme) | ||
Adds a named theme to the themeset. You can pass in either a theme object, | ||
as returned by `themes.newTheme` or the arguments you'd pass to | ||
`themes.newTheme`. | ||
#### themes.getTheme(name) | ||
Returns the theme object from this theme set named `name`. | ||
If `name` does not exist in this themeset an error will be thrown with | ||
a `code` of `EMISSINGTHEME`. | ||
#### themes.setDefault([opts], themeName) | ||
`opts` is an object with the following properties. | ||
* **platform** - Defaults to `'fallback'`. If your theme is platform | ||
specific, specify that here with the platform from `process.platform`, eg, | ||
`win32`, `darwin`, etc. | ||
* **hasUnicode** - Defaults to `false`. If your theme uses unicode you | ||
should set this to true. | ||
* **hasColor** - Defaults to `false`. If your theme uses color you should | ||
set this to true. | ||
`themeName` is the name of the theme (as given to `addTheme`) to use for | ||
this set of `opts`. | ||
#### themes.newTheme([parentTheme,] newTheme) | ||
Create a new theme object based on `parentTheme`. If no `parentTheme` is | ||
provided then a minimal parentTheme that defines functions for rendering the | ||
activity indicator (spinner) and progress bar will be defined. (This | ||
fallback parent is defined in `gauge/base-theme`.) | ||
newTheme should be a bare object– we'll start by discussing the properties | ||
@@ -220,2 +270,13 @@ defined by the default themes: | ||
#### themes.addToAllThemes(theme) | ||
This *mixes-in* `theme` into all themes currently defined. It also adds it | ||
to the default parent theme for this themeset, so future themes added to | ||
this themeset will get the values from `theme` by default. | ||
#### themes.newThemeset() | ||
Copy the current themeset into a new one. This allows you to easily inherit | ||
one themeset from another. | ||
### TEMPLATES | ||
@@ -222,0 +283,0 @@ |
@@ -60,3 +60,3 @@ 'use strict' | ||
var length = this.getBaseLength() | ||
if (length == null) return | ||
if (length == null) return null | ||
return length + this.padLeft + this.padRight | ||
@@ -66,3 +66,3 @@ } | ||
TemplateItem.prototype.getMaxLength = function () { | ||
if (this.maxLength == null) return | ||
if (this.maxLength == null) return null | ||
return this.maxLength + this.padLeft + this.padRight | ||
@@ -72,5 +72,5 @@ } | ||
TemplateItem.prototype.getMinLength = function () { | ||
if (this.minLength == null) return | ||
if (this.minLength == null) return null | ||
return this.minLength + this.padLeft + this.padRight | ||
} | ||
@@ -43,3 +43,4 @@ 'use strict' | ||
t.is(consoleStrings.goto(10, 3), '\x1b[3;10H', 'absolute position') | ||
t.is(consoleStrings.gotoSOL(), '\r', 'goto start of line') | ||
t.done() | ||
}) |
@@ -6,3 +6,3 @@ 'use strict' | ||
var util = require('util') | ||
var EventEmitter = require('event-emitter') | ||
var EventEmitter = require('events') | ||
@@ -27,2 +27,3 @@ function Sink () { | ||
var args = Array.prototype.slice.call(arguments) | ||
results.emit('called', [name, args]) | ||
results.emit('called:' + name, args) | ||
@@ -62,3 +63,5 @@ return '' | ||
template: 'TEMPLATE', | ||
enabled: false | ||
enabled: false, | ||
updateInterval: 0, | ||
fixedFramerate: false | ||
}) | ||
@@ -69,2 +72,3 @@ t.ok(gauge) | ||
t.is(results.template, 'TEMPLATE', 'template passed through') | ||
t.done() | ||
@@ -161,1 +165,114 @@ }) | ||
}) | ||
function collectResults (time, cb) { | ||
var collected = [] | ||
function collect (called) { | ||
collected.push(called) | ||
} | ||
results.on('called', collect) | ||
setTimeout(function () { | ||
results.removeListener('called', collect) | ||
cb(collected) | ||
}, time) | ||
} | ||
test('hideCursor:true', function (t) { | ||
var output = new Sink() | ||
output.isTTY = true | ||
output.columns = 16 | ||
var gauge = new Gauge(output, { | ||
Plumbing: MockPlumbing, | ||
theme: 'THEME', | ||
template: 'TEMPLATE', | ||
enabled: true, | ||
updateInterval: 10, | ||
fixedFramerate: true, | ||
hideCursor: true | ||
}) | ||
collectResults(11, andCursorHidden) | ||
gauge.show('NAME', 0.5) | ||
function andCursorHidden (got) { | ||
var expected = [ | ||
['hideCursor', []], | ||
['show', [{ | ||
spun: 0, | ||
section: 'NAME', | ||
subsection: '', | ||
completed: 0.5 | ||
}]] | ||
] | ||
t.isDeeply(got, expected, 'hideCursor') | ||
gauge.disable() | ||
t.end() | ||
} | ||
}) | ||
test('hideCursor:false', function (t) { | ||
var output = new Sink() | ||
output.isTTY = true | ||
output.columns = 16 | ||
var gauge = new Gauge(output, { | ||
Plumbing: MockPlumbing, | ||
theme: 'THEME', | ||
template: 'TEMPLATE', | ||
enabled: true, | ||
updateInterval: 10, | ||
fixedFramerate: true, | ||
hideCursor: false | ||
}) | ||
collectResults(11, andCursorHidden) | ||
gauge.show('NAME', 0.5) | ||
function andCursorHidden (got) { | ||
var expected = [ | ||
['show', [{ | ||
spun: 0, | ||
section: 'NAME', | ||
subsection: '', | ||
completed: 0.5 | ||
}]] | ||
] | ||
t.isDeeply(got, expected, 'do not hideCursor') | ||
gauge.disable() | ||
t.end() | ||
} | ||
}) | ||
/* todo missing: | ||
constructor | ||
writeTo: process.stderr & process.stdout IS A TTY (or isn't) | ||
cleanupOnExit: explicitly set to true | ||
disable while showing then enable | ||
enable while fixedFramerate is false | ||
enable while fixedFramerate is true & redrawTracker.unref is false (0.8) | ||
disable while fixedFramerate is false | ||
hide() | ||
show() while disabled | ||
show() with object args | ||
show() with neither string nor object (undefined? number?) | ||
show() with fixedFramerate == false | ||
pulse() while disabled | ||
pulse while _showing == false | ||
pulse where fixedFramerate == false | ||
implicit: | ||
_doRedraw will get called with fixedFramerate = false if we test the above | ||
trigger a show + redraw with hideCursor = true | ||
trigger a hide + redraw with hideCursor = true | ||
trigger a _doRedraw when _needsRedraw is false | ||
TEST BACKPRESSURE | ||
*/ |
'use strict' | ||
var test = require('tap').test | ||
var stripAnsi = require('strip-ansi') | ||
var Plumbing = require('../plumbing.js') | ||
var themes = require('../themes.js') | ||
var requireInject = require('require-inject') | ||
var Plumbing = requireInject('../plumbing.js', { | ||
'../render-template.js': function (width, template, values) { | ||
if (values.x) values.x = values.x // pull in from parent object for stringify | ||
return 'w:' + width + ', t:' + JSON.stringify(template) + ', v:' + JSON.stringify(values) | ||
}, | ||
'../console-strings.js': { | ||
eraseLine: function () { return 'ERASE' }, | ||
gotoSOL: function () { return 'CR' }, | ||
hideCursor: function () { return 'HIDE' }, | ||
showCursor: function () { return 'SHOW' } | ||
} | ||
}) | ||
var stripInvisible = function (str) { | ||
return stripAnsi(str).replace(/^\s+|\s+$/mg, '') | ||
} | ||
var template = [ | ||
{type: 'name'} | ||
] | ||
var plumbing = new Plumbing(themes.fallback.noUnicode.noColor, template, 10) | ||
var theme = {} | ||
var plumbing = new Plumbing(theme, template, 10) | ||
@@ -20,11 +27,11 @@ // These three produce fixed strings and are entirely static, so as long as | ||
test('showCursor', function (t) { | ||
t.ok(plumbing.showCursor()) | ||
t.is(plumbing.showCursor(), 'SHOW') | ||
t.end() | ||
}) | ||
test('hideCursor', function (t) { | ||
t.ok(plumbing.hideCursor()) | ||
t.is(plumbing.hideCursor(), 'HIDE') | ||
t.end() | ||
}) | ||
test('hide', function (t) { | ||
t.ok(plumbing.hide()) | ||
t.is(plumbing.hide(), 'CRERASE') | ||
t.end() | ||
@@ -34,4 +41,28 @@ }) | ||
test('show', function (t) { | ||
t.is(stripInvisible(plumbing.show({name: 'test'})), 'test') | ||
t.is(plumbing.show({name: 'test'}), 'w:10, t:[{"type":"name"}], v:{"name":"test"}ERASECR') | ||
t.end() | ||
}) | ||
test('width', function (t) { | ||
var plumbing = new Plumbing(theme, template) | ||
t.is(plumbing.show({name: 'test'}), 'w:80, t:[{"type":"name"}], v:{"name":"test"}ERASECR') | ||
t.end() | ||
}) | ||
test('setTheme', function (t) { | ||
plumbing.setTheme({x: 'abc'}) | ||
t.is(plumbing.show({name: 'test'}), 'w:10, t:[{"type":"name"}], v:{"name":"test","x":"abc"}ERASECR') | ||
t.end() | ||
}) | ||
test('setTemplate', function (t) { | ||
plumbing.setTemplate([{type: 'name'}, {type: 'x'}]) | ||
t.is(plumbing.show({name: 'test'}), 'w:10, t:[{"type":"name"},{"type":"x"}], v:{"name":"test","x":"abc"}ERASECR') | ||
t.end() | ||
}) | ||
test('setWidth', function (t) { | ||
plumbing.setWidth(20) | ||
t.is(plumbing.show({name: 'test'}), 'w:20, t:[{"type":"name"},{"type":"x"}], v:{"name":"test","x":"abc"}ERASECR') | ||
t.end() | ||
}) |
@@ -81,3 +81,6 @@ 'use strict' | ||
result = renderTemplate(10, [{value: 'abc', align: 'xyzzy'}]) | ||
t.is(result, 'abc ', 'unknown aligns are align left') | ||
t.end() | ||
}) |
@@ -5,26 +5,8 @@ 'use strict' | ||
test('themes', function (t) { | ||
Object.keys(themes).forEach(function (platform) { | ||
if (typeof themes[platform] === 'function') return | ||
var platformThemeGroup = themes[platform] | ||
;['hasUnicode', 'noUnicode'].forEach(function (unicode) { | ||
var unicodeThemeGroup = platformThemeGroup[unicode] | ||
if (t.ok(unicodeThemeGroup, platform + ' has theming for ' + unicode)) { | ||
['hasColor', 'noColor'].forEach(function (color) { | ||
var colorTheme = unicodeThemeGroup[color] | ||
t.ok(colorTheme, platform + ' ' + unicode + ' has theming for ' + color) | ||
}) | ||
} | ||
}) | ||
}) | ||
t.end() | ||
}) | ||
test('selector', function (t) { | ||
t.is(themes({hasUnicode: false, hasColor: false, platform: 'unknown'}), themes.fallback.noUnicode.noColor, 'fallback') | ||
t.is(themes({hasUnicode: false, hasColor: false, platform: 'darwin'}), themes.darwin.noUnicode.noColor, 'ff darwin') | ||
t.is(themes({hasUnicode: true, hasColor: false, platform: 'darwin'}), themes.darwin.hasUnicode.noColor, 'tf drawin') | ||
t.is(themes({hasUnicode: false, hasColor: true, platform: 'darwin'}), themes.darwin.noUnicode.hasColor, 'ft darwin') | ||
var theme = themes[process.platform] || themes.fallback | ||
t.is(themes({hasUnicode: true, hasColor: true}), theme.hasUnicode.hasColor, 'tt') | ||
t.is(themes({hasUnicode: false, hasColor: false, platform: 'unknown'}), themes.getTheme('ASCII'), 'fallback') | ||
t.is(themes({hasUnicode: false, hasColor: false, platform: 'darwin'}), themes.getTheme('ASCII'), 'ff darwin') | ||
t.is(themes({hasUnicode: true, hasColor: false, platform: 'darwin'}), themes.getTheme('brailleSpinner'), 'tf drawin') | ||
t.is(themes({hasUnicode: false, hasColor: true, platform: 'darwin'}), themes.getTheme('colorASCII'), 'ft darwin') | ||
t.is(themes({hasUnicode: true, hasColor: true, platform: 'darwin'}), themes.getTheme('colorBrailleSpinner'), 'ft darwin') | ||
t.end() | ||
@@ -31,0 +13,0 @@ }) |
'use strict' | ||
var validate = require('aproba') | ||
var objectAssign = require('object-assign') | ||
var consoleStrings = require('./console-strings') | ||
var spin = require('./spin.js') | ||
var progressBar = require('./progress-bar.js') | ||
var ThemeSet = require('./theme-set.js') | ||
var themes = module.exports = function (opts) { | ||
if (!opts) opts = {} | ||
var os = themes[opts.platform || process.platform] || themes.fallback | ||
var unicode = opts.hasUnicode ? os.hasUnicode : os.noUnicode | ||
var color = opts.hasColor ? unicode.hasColor : unicode.noColor | ||
return color | ||
} | ||
var themes = module.exports = new ThemeSet() | ||
var baseTheme = { | ||
activityIndicator: function (values, theme, width) { | ||
if (values.spun == null) return | ||
return spin(theme, values.spun) | ||
}, | ||
progressbar: function (values, theme, width) { | ||
if (values.completed == null) return | ||
return progressBar(theme, width, values.completed) | ||
} | ||
} | ||
var newTheme = themes.newTheme = function (parent, theme) { | ||
if (!theme) { | ||
theme = parent | ||
parent = baseTheme | ||
} | ||
validate('OO', [parent, theme]) | ||
return objectAssign({}, parent, theme) | ||
} | ||
themes.fallback = {} | ||
themes.fallback.noUnicode = {} | ||
themes.fallback.noUnicode.noColor = newTheme({ | ||
themes.addTheme('ASCII', { | ||
preProgressbar: '[', | ||
@@ -49,3 +18,3 @@ postProgressbar: ']', | ||
themes.fallback.noUnicode.hasColor = newTheme(themes.fallback.noUnicode.noColor, { | ||
themes.addTheme('colorASCII', themes.getTheme('ASCII'), { | ||
progressbarTheme: { | ||
@@ -61,8 +30,3 @@ preComplete: consoleStrings.color('inverse'), | ||
themes.fallback.hasUnicode = themes.fallback.noUnicode | ||
themes.darwin = {} | ||
themes.darwin.noUnicode = themes.fallback.noUnicode | ||
themes.darwin.hasUnicode = {} | ||
themes.darwin.hasUnicode.noColor = newTheme({ | ||
themes.addTheme('brailleSpinner', { | ||
preProgressbar: '⸨', | ||
@@ -77,3 +41,3 @@ postProgressbar: '⸩', | ||
themes.darwin.hasUnicode.hasColor = newTheme(themes.darwin.hasUnicode.noColor, { | ||
themes.addTheme('colorBrailleSpinner', themes.getTheme('brailleSpinner'), { | ||
progressbarTheme: { | ||
@@ -88,1 +52,6 @@ preComplete: consoleStrings.color('inverse'), | ||
}) | ||
themes.setDefault({}, 'ASCII') | ||
themes.setDefault({hasColor: true}, 'colorASCII') | ||
themes.setDefault({platform: 'darwin', hasUnicode: true}, 'brailleSpinner') | ||
themes.setDefault({platform: 'darwin', hasUnicode: true, hasColor: true}, 'colorBrailleSpinner') |
Sorry, the diff of this file is not supported yet
1518
356
359895
39