Last active
November 28, 2018 01:33
-
-
Save elundmark/c5510dc45ec66bf868dab7d7736e1b81 to your computer and use it in GitHub Desktop.
vsleep - verbose sleep for when you want a progress meter (written for linux+nodejs)
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
#!/usr/bin/env node | |
/* | |
$ vs 10s | |
vsleep: 10s (current time: 2018-11-28 02:23:22 - ends: 2018-11-28 02:23:32 - length: 0.003 hours) | |
│ 10s 2ms [100%] | |
└─ Done ─ Timer reached 10s 4ms [100%] ┐ | |
^C^C 1s 25ms [10%] | |
└─ CANCELLED!! | |
# Note! it also calls xset to effectivly turn on the screen when it reaches 100% | |
*/ | |
/* jshint esversion: 6 */ | |
"use strict"; | |
process.title = "vsleep "+(process.argv.slice(2).join(" ")); | |
Date.prototype.customDate = function () { | |
let y = this.getFullYear()+"", | |
m = (this.getMonth()+1)+"", // zero-based | |
d = this.getDate()+"", | |
h = this.getHours()+"", | |
mi= this.getMinutes()+"", | |
s = this.getSeconds()+"", | |
p = function (str) { return str.length > 1 ? str : "0" + str; } | |
; | |
return y + "-" + p(m) + "-" + p(d) + " " + p(h) + ":" + p(mi) + ":" + p(s); | |
}; | |
let hrstart = process.hrtime(), | |
programHeading = "vsleep: ", | |
helpText = ` | |
NAME | |
vs - delay for a specified amount of time and show progess through stderr | |
SYNOPSIS | |
vs --time=NUMBER[SUFFIX] [--interval=NUMBER[SUFFIX]] | |
vs NUMBER[SUFFIX] | |
vs --to=HH:MM[:SS] [--interval=NUMBER[SUFFIX]] | |
DESCRIPTION | |
Pause for NUMBER seconds, or up to a specific time in a 24H FORMAT. | |
SUFFIX may be 's' for seconds (the default), 'ms' for milliseconds, | |
'm' for minutes, 'h' for hours or 'd' for days. Unlike most | |
implementations that require NUMBER be an integer, here NUMBER may be an | |
arbitrary floating point number. If you use the simpler time argument it | |
must be the ONLY argment. | |
`.replace(/^(\t{2}|\t$)/gm, "").replace(/^\n/, ""), | |
aMillion = 1E6, | |
kMs = 1E3, | |
sixty = 60, | |
twentyFour = 24, | |
ten = 10, | |
hundred = 100, | |
oneMinute = kMs * sixty, | |
suffixPatt = /[smhd]$/, | |
interrupted, | |
defaultTimeSuffix = "s", | |
notifyOptions = "", | |
paddTime = (n) => { | |
return n < ten ? "0" + n : n + ""; | |
}, | |
dateToMS = (m) => { | |
// converts 12:00 to milliseconds from NOW, always in the future | |
let ms = Date.now(), | |
future = new Date(); | |
notifyOptions = programHeading; | |
future.setHours(parseInt(m[1], ten)); | |
future.setMinutes(parseInt(m[2], ten)); | |
future.setSeconds(parseInt(m[4] || 0, ten)); | |
if (ms > future.getTime()) { | |
future.setTime(future.getTime() + (oneMinute * sixty * twentyFour)); | |
notifyOptions += "tomorrow "; | |
} else { | |
notifyOptions += "today "; | |
} | |
notifyOptions += (paddTime(future.getHours()) + | |
":" + paddTime(future.getMinutes()) + | |
":" + paddTime(future.getSeconds())); | |
return future.getTime() - ms; | |
}, | |
opts = (function (a) { | |
let m, i, y, x, o = {}, | |
timePatt = /^([0-9.]+)([smhd]?|ms)$/, | |
toPatt = /^([0-2][0-9]):([0-5][0-9])(:([0-5][0-9]))?$/, | |
cliArgs = [ | |
{ name: "help", flags: [ "--help" , "-h", "-?" ], re: false }, | |
// include shortcut-single argument check manually | |
{ name: "time", flags: [ "--time" ], re: timePatt }, | |
{ name: "to", flags: [ "--to" ], re: toPatt }, | |
{ name: "interval", flags: [ "--interval", "--output-interval" ], re: timePatt } | |
], | |
defaultDelay = 1; | |
o.delaySuffix = defaultTimeSuffix; | |
o.outputNumber = defaultDelay; | |
for (i = 0; i < a.length; i += 1) { | |
if ((m=a[i].match(timePatt))) { | |
o.time = m[0]; | |
if (a.length === 2 && timePatt.test(a[1])) { | |
o.interval = a[1]; | |
} | |
break; | |
} | |
for (y = 0; y < cliArgs.length; y += 1) { | |
for(x = 0; x < cliArgs[y].flags.length; x += 1) { | |
if (cliArgs[y].re) { | |
if ((m=a[i].replace(new RegExp("^" + cliArgs[y].flags[x] + "=", ""), "").match(cliArgs[y].re))) { | |
o[cliArgs[y].name] = m[0]; | |
break; | |
} | |
} else { | |
if (a[i] === cliArgs[y].flags[x]) { | |
o[cliArgs[y].name] = true; | |
break; | |
} | |
} | |
} | |
} | |
} | |
if (o.to !== undefined && o.to.toString().match(toPatt)) { | |
o.timeNumber = dateToMS(o.to.toString().match(toPatt)); | |
o.timeSuffix = "ms"; | |
} else if (o.time !== undefined) { | |
o.timeNumber = o.time.toString().match(timePatt); | |
o.timeNumber = o.timeNumber ? parseFloat(o.timeNumber[1]) : 0; | |
o.timeSuffix = o.time.toString().match(timePatt); | |
o.timeSuffix = o.timeSuffix ? (o.timeSuffix[2] ? o.timeSuffix[2] : defaultTimeSuffix) : defaultTimeSuffix; | |
} | |
if (o.interval !== undefined) { | |
o.outputNumber = o.interval.toString().match(timePatt); | |
o.outputNumber = o.outputNumber ? parseFloat(o.outputNumber[1]) : defaultDelay; | |
o.delaySuffix = o.interval.toString().match(timePatt); | |
o.delaySuffix = o.delaySuffix ? (o.delaySuffix[2] ? o.delaySuffix[2] : defaultTimeSuffix) : defaultTimeSuffix; | |
} | |
if (!o.timeNumber || o.timeNumber <= 0) { | |
process.stderr.write(helpText + "\n\nNo time specified\n"); | |
process.exit(1); | |
} | |
if (o.outputNumber <= 0) { | |
process.stderr.write(helpText + "\n\nNegative output interval specified\n"); | |
process.exit(1); | |
} | |
switch (o.timeSuffix) { | |
case "s": | |
o.timeNumber = o.timeNumber * kMs; | |
break; | |
case "m": | |
o.timeNumber = o.timeNumber * sixty * kMs; | |
break; | |
case "h": | |
o.timeNumber = o.timeNumber * sixty * sixty * kMs; | |
break; | |
case "d": | |
o.timeNumber = o.timeNumber * sixty * sixty * twentyFour * kMs; | |
break; | |
case "ms": | |
break; | |
default: | |
process.exit(1); | |
} | |
switch (o.delaySuffix) { | |
case "s": | |
o.outputNumber = o.outputNumber * kMs; | |
break; | |
case "m": | |
o.outputNumber = o.outputNumber * sixty * kMs; | |
break; | |
case "h": | |
o.outputNumber = o.outputNumber * sixty * sixty * kMs; | |
break; | |
case "d": | |
o.outputNumber = o.outputNumber * sixty * sixty * twentyFour * kMs; | |
break; | |
case "ms": | |
break; | |
default: | |
process.stderr.write("Invalid time suffix"); | |
process.exit(1); | |
} | |
return o; | |
}(process.argv.slice(2))), | |
loop, | |
makeLoop, | |
makeTimer, | |
nowDateObj, | |
outputInfo; | |
if (opts.help) { | |
process.stdout.write(helpText + "\n"); | |
process.exit(0); | |
} | |
outputInfo = (hrt, ms, prefix, first, append) => { | |
let lineEnding = " \r", s = ""; | |
append = append || lineEnding; | |
if (opts.timeNumber > (oneMinute * sixty)) { | |
s = `${Math.floor(hrt[0] / sixty / sixty)}h ${Math.floor(hrt[0] / sixty) % sixty}m ${hrt[0] % sixty}s `; | |
} else if (opts.timeNumber > (oneMinute)) { | |
s = `${Math.floor(hrt[0] / sixty) % sixty}m ${hrt[0] % sixty}s `; | |
} else if (opts.timeNumber > kMs) { | |
s = `${hrt[0]}s `; | |
} | |
s += `${Math.round(hrt[1] / aMillion)}ms`; | |
return process.stderr.write(`${prefix}${s} [${Math.round((ms / opts.timeNumber) * hundred)}%]${append}`); | |
}; | |
makeTimer= (first) => { | |
let elapsedHrtime = process.hrtime(hrstart), | |
elapsedMs = (elapsedHrtime[0] * kMs) + Math.round(elapsedHrtime[1] / aMillion); | |
outputInfo(elapsedHrtime, elapsedMs, " │ ", first); | |
if (elapsedMs < opts.timeNumber) { | |
return setTimeout(makeLoop, Math.min((opts.timeNumber - elapsedMs), opts.outputNumber)); | |
} | |
return false; | |
}; | |
makeLoop = (first) => { | |
loop = makeTimer(first); | |
}; | |
if (!notifyOptions) { | |
notifyOptions = programHeading + opts.time; | |
if (!suffixPatt.test(opts.time)) { | |
notifyOptions += defaultTimeSuffix; | |
} | |
} | |
process.stderr.write(`${ | |
notifyOptions | |
} (current time: ${ | |
(nowDateObj=new Date()).customDate() | |
} - ends: ${ | |
new Date(opts.timeNumber+nowDateObj.getTime()).customDate() | |
} - length: ${ | |
(opts.timeNumber / kMs / sixty / sixty).toFixed(3) | |
} hour${(opts.timeNumber / kMs / sixty / sixty) === 1 ? '' : 's'})\n`); | |
process.on("SIGINT", () => { | |
interrupted = true; | |
process.stderr.write("\n └─ CANCELLED!!\n"); | |
process.exit(4); | |
}); | |
process.on("exit", () => { | |
if (interrupted) return; | |
let hrEnd = process.hrtime(hrstart), | |
msEnd = (hrEnd[0] * kMs) + Math.round(hrEnd[1] / aMillion); | |
// Exit screensaver mode (wake up screen) | |
require("child_process").execFileSync("/usr/bin/xset", ["dpms", "force", "on"]); | |
return outputInfo(hrEnd, msEnd, "\n └─ Done ─ Timer reached ", false, " ┐\n"); | |
}); | |
makeLoop(true); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment