Skip to content

Instantly share code, notes, and snippets.

@elundmark
Last active November 28, 2018 01:33
Show Gist options
  • Save elundmark/c5510dc45ec66bf868dab7d7736e1b81 to your computer and use it in GitHub Desktop.
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)
#!/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