167 lines
4.3 KiB
JavaScript
Executable file
167 lines
4.3 KiB
JavaScript
Executable file
#! /usr/bin/env node
|
|
const promisify = require('util').promisify;
|
|
const childProcess = require('child_process');
|
|
const execFile = promisify(childProcess.execFile);
|
|
const spawn = require('child_pty').spawn;
|
|
|
|
let pullStarted = false;
|
|
let pullEnded = false;
|
|
|
|
const oldTitlePromise =
|
|
execFile('tmux', ['display', '-pt', '?', '#{pane_title}'], {
|
|
windowsHide: true,
|
|
}).catch(() => ({ stdout: '', stderr: '' }));
|
|
|
|
const whale = '\u{1f433}';
|
|
|
|
const setTitle = async (title) => {
|
|
const oldTitle = await oldTitlePromise;
|
|
const pre = oldTitle.stdout.split(whale)[0];
|
|
let newTitle = oldTitle.stdout;
|
|
if(title) {
|
|
newTitle = pre + whale + title;
|
|
}
|
|
process.stdout.write('\x1b]2;' + newTitle + '\x1b\\');
|
|
};
|
|
|
|
const progress = {};
|
|
let lastHash = '';
|
|
let averageTotal = 0;
|
|
const handle = async (data) => {
|
|
const strData = data.toString();
|
|
|
|
process.stdout.write(strData.replace(/\r\n?/g, '\n'));
|
|
|
|
if(pullStarted && pullEnded) {
|
|
return;
|
|
}
|
|
|
|
for(const line of strData.split(/[\r\n]+/g)) {
|
|
if(!pullStarted) {
|
|
if (!/^.*:\s*Pulling\s+from\s+\S+\s*$/i.test(line)) {
|
|
continue;
|
|
}
|
|
|
|
pullStarted = true;
|
|
|
|
setTitle('pull');
|
|
|
|
continue;
|
|
}
|
|
else if(!pullEnded) {
|
|
if(/^\s*Status:\s+Downloaded\s+newer\s+image\s+for/i.test(line)) {
|
|
pullEnded = true;
|
|
|
|
setTitle('pulled');
|
|
|
|
setTitle();
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
let match;
|
|
if(match = /([0-9a-f]+):/ig.exec(line)) {
|
|
lastHash = match[1] || lastHash;
|
|
}
|
|
|
|
if(!lastHash) {
|
|
continue;
|
|
}
|
|
|
|
if(match = /(Pull\s+complete|Already\s+exists|Pulling\s+fs\s+layer|Downloading\s+\[([=>\s]+)\]\s+([0-9\.]+)\w+\/([0-9\.]+)\w+)/ig.exec(line)) {
|
|
const eventType = match[1];
|
|
const bar = match[2];
|
|
const unit = match[3];
|
|
const total = match[4];
|
|
|
|
if(eventType == 'Already exists' || eventType == 'Pull complete') {
|
|
const oldHash = progress[lastHash] || {};
|
|
progress[lastHash] = {
|
|
unit: oldHash.total,
|
|
total: oldHash.total,
|
|
}
|
|
}
|
|
else if(eventType == 'Pulling fs layer') {
|
|
progress[lastHash] = {
|
|
unit: 0,
|
|
total: averageTotal,
|
|
}
|
|
}
|
|
else if(bar && unit && total) {
|
|
progress[lastHash] = {
|
|
unit: parseFloat(unit),
|
|
total: parseFloat(total),
|
|
};
|
|
}
|
|
|
|
let units = 0;
|
|
let totals = 0;
|
|
for(const p in progress) {
|
|
const item = progress[p];
|
|
units += item.unit;
|
|
totals += item.total;
|
|
}
|
|
|
|
debugger;
|
|
|
|
averageTotal = totals / Object.keys(progress).length;
|
|
|
|
setTitle(((units / totals).toFixed(2) * 100) + '%');
|
|
}
|
|
}
|
|
};
|
|
|
|
let args = [...process.argv];
|
|
const debug = args.includes('--debug');
|
|
|
|
const main = async() => {
|
|
args.shift();
|
|
args.shift();
|
|
|
|
if(debug) {
|
|
args.shift();
|
|
|
|
try {
|
|
await execFile('docker', ['rmi', args[0]], {
|
|
windowsHide: true,
|
|
});
|
|
}
|
|
catch(e) {
|
|
console.error(e);
|
|
// Image already gone?
|
|
}
|
|
|
|
args = ['run', '-it', '--rm', ...args];
|
|
}
|
|
|
|
const commandName = args.filter(x => x && !x.startsWith("-"));
|
|
|
|
/* Output sample for pull
|
|
alpine: Pulling from library/node
|
|
6c40cc604d8e: Already exists
|
|
bf8900ab0b62: Downloading [=> ] 440kB/21.57MB
|
|
287f798ae2cd: Downloading [===============> ] 423.6kB/1.332MB
|
|
*/
|
|
|
|
const docker = spawn('docker', args, {
|
|
columns: 200,
|
|
rows: 1,
|
|
name: 'xterm',
|
|
cwd: process.cwd(),
|
|
});
|
|
|
|
!debug && process.stdin.setRawMode(true);
|
|
process.stdin.pipe(docker.stdin);
|
|
|
|
docker.stdout.on('data', data => handle(data).catch(e => debug && console.error(e)));
|
|
|
|
docker.on('close', () => process.exit(0));
|
|
};
|
|
|
|
main()
|
|
.then(() => {})
|
|
.catch(e => {
|
|
debug && console.error(e);
|
|
process.exit(1);
|
|
});
|