fileutils-cli
Version:
A collection of powerful command line file utilities
211 lines (201 loc) • 7.02 kB
JavaScript
const async = require('async');
const clear = require('cli-clear');
const clipboardy = require('clipboardy');
const { format } = require('date-fns');
const fs = require('fs-extra');
const inquirer = require('inquirer');
const path = require('path');
const term = require('terminal-kit').terminal;
const fu = require('../fu');
const util = require('../util/util');
module.exports.History = class History {
constructor(sequelize, options) {
this.sequelize = sequelize;
this.options = options;
this.count = options.numberToDisplay || 10;
this.includeUndone = !options.noUndone;
this.onlyUndoable = options.onlyUndoable;
this.batches = [];
this.page = 1;
}
async getBatches(page) {
if (parseInt(page) > 0) this.page = page;
let queryParams = {
limit: this.count,
order: [[ 'createdAt', 'DESC' ]],
include: this.sequelize.models.Op,
where: {}
};
if (!this.includeUndone) queryParams.where = { undone: false };
if (this.onlyUndoable) queryParams.where = Object.assign(queryParams.where, { undoable: true });
if (page > 1) queryParams.offset = (this.count * (page - 1));
this.batches = await this.sequelize.models.Batch.findAll(queryParams);
}
async undo(ops, cwd, cls) {
if (cls) clear();
cwd = (cwd || process.cwd()) + path.sep;
await async.eachSeries(ops, async (o) => {
const fileExists = await fs.pathExists(o.output);
if (!fileExists) {
console.log(`${o.output} no longer exists`);
} else {
switch(o.type) {
case 'move': {
if (this.options.verbose) console.log(`Moving ${o.output.replace(cwd, '')} → ${o.input.replace(cwd, '')}`);
await fs.rename(o.output, o.input);
break;
}
default: {
if (this.options.verbose) console.log(`Deleting ${o.output.replace(cwd, '')}`);
await fs.unlink(o.output);
}
}
o.undone = true;
await o.save();
}
});
}
async undoBatch(index, cls) {
if (this.batches.length > index) {
let batch = this.batches[index];
this.undo(batch.Ops, batch.cwd, cls);
batch.undone = true;
await batch.save();
} else {
console.log('The specified index is out of bounds');
}
}
async display() {
clear();
let choices = await async.mapSeries(this.batches, async (b) => {
return {
name: `${b.type.toUpperCase()} - ${format(b.createdAt, 'MMMM d yyyy, h:mm:ss a')} (${b.Ops.length} operations)${b.undone ? ' - Undone' : ''}`,
value: this.batches.indexOf(b)
};
});
choices.push({ name: 'More...', value: -1});
if (this.page > 1) choices = [{ name: 'Previous', value: -2 }, ...choices];
const selection = await inquirer.prompt({
type: 'list',
loop: false,
message: 'Select a batch to view more information and options',
name: 'batch',
default: 0,
choices: choices,
pageSize: 20
});
clear();
if (selection.batch === -1) {
await this.getBatches(this.page + 1);
await this.display();
return;
} else if (selection.batch === -2) {
await this.getBatches(this.page - 1);
await this.display();
return;
} else {
await this.displayBatch(selection.batch);
}
}
async displayBatch(index) {
if (index < 0 || index >= this.batches.length) {
console.log('displayBatch index out of range');
process.exit(1);
}
const selectedBatch = this.batches[index];
const workingDir = selectedBatch.cwd + path.sep;
const opsText = await async.reduce(selectedBatch.Ops, '', async (collector, o) => {
collector += `${o.input.replace(workingDir, '')} → ${o.output.replace(workingDir, '')}${o.undone ? ' (undone)': ''}\n`;
return collector;
});
console.log();
term.table([
['Command: ', `${selectedBatch.commandString} `],
['Working Dir: ', `${selectedBatch.cwd} `],
['Operations: ', opsText] ], {
hasBorder: true,
borderChars: 'lightRounded',
fit: true
});
console.log();
let choices;
if (selectedBatch.undone) choices = ['Go Back', 'Re-run the Command', 'Copy the Command', 'Add to Favorites', 'Remove from History', 'Exit'];
else choices = ['Go Back', 'Undo Every Operation', 'Undo Some Operations', 'Re-run the Command', 'Copy the Command', 'Add to Favorites', 'Remove from History', 'Exit'];
const selection = await inquirer.prompt({
type: 'list',
loop: false,
message: 'What would you like to do?',
name: 'choice',
default: 0,
choices: choices
});
switch (selection.choice) {
case 'Go Back':
await this.display();
return;
case 'Undo Every Operation':
await this.undoBatch(index, true);
return;
case 'Undo Some Operations':
await this.displayOps(index);
return;
case 'Re-run the Command':
process.chdir(selectedBatch.cwd);
await this.runCommand(selectedBatch.command);
return;
case 'Copy the Command':
await clipboardy.write(selectedBatch.commandString);
return;
case 'Add to Favorites':
await this.addToFavorites(selectedBatch.command);
return;
case 'Remove from History':
await selectedBatch.destroy();
await this.getBatches(this.page);
await this.display();
return;
default: process.exit(0);
}
}
async runCommand(command) {
if (this.options.verbose) console.log('Command: ' + command.join(' '));
await fu(command);
}
async addToFavorites(command) {
let favoriteData = { command: command };
const input = await inquirer.prompt({
type: 'input',
name: 'alias',
message: 'Enter an alias for this favorite (optional)',
});
if (input.alias) favoriteData.alias = input.alias;
let favorite = this.sequelize.models.Favorites.build(favoriteData);
await favorite.save();
console.log(`Command added to favorites. ID: ${favorite.id}`);
}
async displayOps(index) {
if (index < 0 || index >= this.batches.length) {
console.log('displayBatch index out of range');
process.exit(1);
}
const selectedBatch = this.batches[index];
const workingDir = selectedBatch.cwd + path.sep;
clear();
console.log(selectedBatch.commandString);
let choices = await async.mapSeries(selectedBatch.Ops, async (o) => {
return {
name: `${o.input.replace(workingDir, '')} → ${o.output.replace(workingDir, '')}`,
disabled: o.undone ? 'undone' : false,
value: o
};
});
const selection = await inquirer.prompt({
type: 'checkbox',
message: 'Select the operations to undo',
name: 'ops',
choices: choices,
pageSize: (selectedBatch.Ops.length > 20 ? 20 : selectedBatch.Ops.length)
});
this.undo(selection.ops, selectedBatch.cwd, true);
}
};