dotfiles/script/docker.js
2025-01-24 10:01:12 +01:00

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);
});