lapse
Version:
command-line progress bar
141 lines (124 loc) • 4.38 kB
JavaScript
module.exports = Lapse;
var _ = require('underscore');
// ( {} | { format: string, stream: stream, width: int, car_char: string, road_char: string, clear: bool, callback: function, refresh_rate: int } ) -> { format: string, stream: stream, width: int, car_char: string, road_char: string, clear: bool, callback: function, refresh_rate: int }
function settings( options )
{
var stream = _.has( options, "stream" ) ? options.stream : process.stderr;
return _.defaults(
_.isUndefined( options ) ? {} : options,
{
format: " #bar #eta ",
stream: stream,
width: stream.columns,
car_char: "|",
road_char: "⋅",
trail: false,
clear: false,
callback: function() {},
refresh_rate: 100
}
);
}
// int, obj ->
function Lapse( duration, options )
{
// set the start and end times
var start = new Date(); var end = new Date();
end.setSeconds( start.getSeconds() + duration );
// listen for key presses
var paused = false;
var pause_start;
var stdin = process.stdin;
stdin.setRawMode( true );
stdin.resume();
stdin.setEncoding( "utf8" );
stdin.on( "data", function( key ) {
if( key === "\u0003" ) // ctrl-c
process.exit();
else if( key === " " )
{
if( paused ) start = new Date( start.getTime() + Date.now() )
if( paused ) end = new Date( end.getTime() + Date.now() )
paused = !paused;
if( paused ) start = new Date( start.getTime() - Date.now() )
if( paused ) end = new Date( end.getTime() - Date.now() )
}
});
// parse default settings
var app = settings( options );
app.stream.write( "\n" );
// line() creates our line of text
var line = line_gen( app.stream, app.format, app.width, app.car_char, app.road_char, app.trail );
// run the update function every n milliseconds
var ticker = setInterval( update, 1000 / app.refresh_rate );
function update()
{
if( !paused && !render( app.stream, line( start, end ) ) )
{
clearInterval( ticker );
if( app.clear )
app.stream.moveCursor( 0, -1 ) || app.stream.clearScreenDown( 0 );
else
app.stream.write('\n\n');
app.callback();
}
};
}
// int -> string
function pad( n )
{
return ( n < 10 ) ? ( "0" + n ) : n;
}
// Date, bool -> string
function format_time( time, format )
{
var date = new Date( time );
var m = date.getMinutes();
var s = date.getSeconds();
return ( m && ( _.isUndefined( format ) || format ) ) ? m + ":" + pad( s ) : (date/1000).toFixed(1);
}
// int -> int
function clockify( n )
{
return ( n + 11 ) % 12 + 1;
}
// string -> string
function ansi_strip( s )
{
return s.replace( /\u001b\[.+?m/g, "" );
}
// string, int, int, string, string -> ( f: float -> string )
function line_gen( stream, format, width, car_char, road_char, trail )
{
var bar = function( width, percent )
{
var present = Math.round( percent * width );
var future = width - present;
return ( trail ? car_char : road_char ).repeat( Math.max( 0, present-1 ) ) + car_char + road_char.repeat( future );
};
return function( start, end )
{
var percent = ( Date.now() - start ) / ( end - start + 1 );
line_string = format
.replace( "#time", pad( clockify( (new Date).getHours() ) ) + ":" + pad( (new Date).getMinutes() ) )
.replace( "#mtime", pad( (new Date).getHours() ) + ":" + pad( (new Date).getMinutes() ) )
.replace( "#start", pad( clockify( start.getHours() ) ) + ":" + pad( start.getMinutes() ) )
.replace( "#mstart", pad( start.getHours() ) + ":" + pad( start.getMinutes() ) )
.replace( "#elapsed", format_time( Date.now() - start ) )
.replace( "#eta", format_time( end - Date.now() ) )
.replace( "#percent", ( percent * 100 ).toFixed(1) )
.replace( "#seconds", format_time( Date.now() - start, false ) );
var availableSpace = Math.max( 0, stream.columns - ansi_strip( line_string ).length + 2 );
var bar_width = Math.min( width, availableSpace );
return Date.now() < end ? line_string.replace( "#bar", bar( bar_width, percent ) ) : false;
};
}
// stream, string -> bool
function render( stream, line )
{
// todo we don't have to move the cursor so much...
return ( stream.isTTY && line )
&& ( stream.moveCursor( 0, -1 )
|| !stream.write( "\n" + line + "\n" )
|| !stream.moveCursor( stream.columns, -1 ) );
}