274 lines
6.0 KiB
JavaScript
274 lines
6.0 KiB
JavaScript
|
/**
|
||
|
* Record
|
||
|
* Record your terminal and create a recording file
|
||
|
*
|
||
|
* @author Mohammad Fares <faressoft.com@gmail.com>
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* The path of the recording file
|
||
|
* @type {String}
|
||
|
*/
|
||
|
var recordingFile = null;
|
||
|
|
||
|
/**
|
||
|
* The normalized configurations
|
||
|
* @type {Object} {json, raw}
|
||
|
*/
|
||
|
var config = {};
|
||
|
|
||
|
/**
|
||
|
* To keep tracking of the timestamp
|
||
|
* of the last inserted record
|
||
|
* @type {Number}
|
||
|
*/
|
||
|
var lastRecordTimestamp = null;
|
||
|
|
||
|
/**
|
||
|
* To store the records
|
||
|
* @type {Array}
|
||
|
*/
|
||
|
var records = [];
|
||
|
|
||
|
/**
|
||
|
* Normalize the config file
|
||
|
*
|
||
|
* - Set default values in the json and raw
|
||
|
* - Change the formatting of the values in the json and raw
|
||
|
*
|
||
|
* @param {Object} config {json, raw}
|
||
|
* @return {Object} {json, raw}
|
||
|
*/
|
||
|
function normalizeConfig(config) {
|
||
|
|
||
|
// Default value for command
|
||
|
if (!config.json.command) {
|
||
|
|
||
|
// Windows OS
|
||
|
if (di.os.platform() === 'win32') {
|
||
|
di.utility.changeYAMLValue(config, 'command', 'powershell.exe');
|
||
|
} else {
|
||
|
di.utility.changeYAMLValue(config, 'command', 'bash');
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// Default value for cwd
|
||
|
if (!config.json.cwd) {
|
||
|
di.utility.changeYAMLValue(config, 'cwd', process.cwd());
|
||
|
} else {
|
||
|
di.utility.changeYAMLValue(config, 'cwd', di.path.resolve(config.json.cwd));
|
||
|
}
|
||
|
|
||
|
// Default value for cols
|
||
|
if (di.is.not.number(config.json.cols)) {
|
||
|
di.utility.changeYAMLValue(config, 'cols', process.stdout.columns);
|
||
|
}
|
||
|
|
||
|
// Default value for rows
|
||
|
if (di.is.not.number(config.json.rows)) {
|
||
|
di.utility.changeYAMLValue(config, 'rows', process.stdout.rows);
|
||
|
}
|
||
|
|
||
|
return config;
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Calculate the duration from the last inserted record in ms,
|
||
|
* and update lastRecordTimestamp
|
||
|
*
|
||
|
* @return {Number}
|
||
|
*/
|
||
|
function getDuration() {
|
||
|
|
||
|
// Calculate the duration from the last inserted record
|
||
|
var duration = di.now().toFixed() - lastRecordTimestamp;
|
||
|
|
||
|
// Update the lastRecordTimestamp
|
||
|
lastRecordTimestamp = di.now().toFixed();
|
||
|
|
||
|
return duration;
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* When an input or output is received from the PTY instance
|
||
|
*
|
||
|
* @param {Buffer} content
|
||
|
*/
|
||
|
function onData(content) {
|
||
|
|
||
|
process.stdout.write(content);
|
||
|
|
||
|
var duration = getDuration();
|
||
|
|
||
|
if (duration < 5) {
|
||
|
var lastRecord = records[records.length - 1];
|
||
|
lastRecord.content += content;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
records.push({
|
||
|
delay: duration,
|
||
|
content: content
|
||
|
});
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Executed after the command completes its task
|
||
|
* Store the output file with reserving the comments
|
||
|
*/
|
||
|
function done() {
|
||
|
|
||
|
var outputYAML = '';
|
||
|
|
||
|
// Add config parent element
|
||
|
outputYAML += '# The configurations that used for the recording, feel free to edit them\n';
|
||
|
outputYAML += 'config:\n\n';
|
||
|
|
||
|
// Add the configurations with indentation
|
||
|
outputYAML += config.raw.replace(/^/gm, ' ');
|
||
|
|
||
|
// Add the records
|
||
|
outputYAML += '\n# Records, feel free to edit them\n';
|
||
|
outputYAML += di.yaml.dump({records: records});
|
||
|
|
||
|
// Store the data into the recording file
|
||
|
try {
|
||
|
di.fs.writeFileSync(recordingFile, outputYAML, 'utf8');
|
||
|
} catch (error) {
|
||
|
di.errorHandler(error.message);
|
||
|
process.exit();
|
||
|
}
|
||
|
|
||
|
console.log(di.chalk.green('Successfully Recorded'));
|
||
|
console.log('The recording data is saved into the file:');
|
||
|
console.log(di.chalk.magenta(recordingFile));
|
||
|
console.log('You can edit the file and even change the configurations.');
|
||
|
|
||
|
// Terminate the app
|
||
|
process.exit();
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The command's main function
|
||
|
*
|
||
|
* @param {Object} argv
|
||
|
*/
|
||
|
function command(argv) {
|
||
|
|
||
|
// Normalize the configurations
|
||
|
config = normalizeConfig(argv.config);
|
||
|
|
||
|
// Store the path of the recordingFile
|
||
|
recordingFile = argv.recordingFile;
|
||
|
|
||
|
// Overwrite the command to be executed
|
||
|
if (argv.command) {
|
||
|
di.utility.changeYAMLValue(config, 'command', argv.command);
|
||
|
}
|
||
|
|
||
|
// Split the command and its arguments
|
||
|
var args = di.stringArgv(config.json.command);
|
||
|
var command = args[0];
|
||
|
var commandArguments = args.slice(1);
|
||
|
|
||
|
// PTY instance
|
||
|
var ptyProcess = di.pty.spawn(command, commandArguments, {
|
||
|
name: 'xterm-color',
|
||
|
cols: config.json.cols,
|
||
|
rows: config.json.rows,
|
||
|
cwd: config.json.pwd,
|
||
|
env: di.deepmerge(process.env, config.json.env)
|
||
|
});
|
||
|
|
||
|
// Input and output capturing and redirection
|
||
|
ptyProcess.on('data', onData);
|
||
|
ptyProcess.on('exit', done);
|
||
|
process.stdin.on('data', ptyProcess.write.bind(ptyProcess));
|
||
|
|
||
|
// Input and output normalization
|
||
|
process.stdin.setEncoding('utf8');
|
||
|
process.stdout.setDefaultEncoding('utf8');
|
||
|
process.stdin.setRawMode(true);
|
||
|
process.stdin.resume();
|
||
|
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////
|
||
|
// Command Definition //////////////////////////////
|
||
|
////////////////////////////////////////////////////
|
||
|
|
||
|
/**
|
||
|
* Command's usage
|
||
|
* @type {String}
|
||
|
*/
|
||
|
module.exports.command = 'record <recordingFile>';
|
||
|
|
||
|
/**
|
||
|
* Command's description
|
||
|
* @type {String}
|
||
|
*/
|
||
|
module.exports.describe = 'Record your terminal and create a recording file';
|
||
|
|
||
|
/**
|
||
|
* Handler
|
||
|
*
|
||
|
* @param {Object} argv
|
||
|
*/
|
||
|
module.exports.handler = function(argv) {
|
||
|
|
||
|
// The default configurations
|
||
|
var defaultConfig = di.utility.getDefaultConfig();
|
||
|
|
||
|
// Default value for the config option
|
||
|
if (typeof argv.config == 'undefined') {
|
||
|
argv.config = di.utility.getDefaultConfig();
|
||
|
}
|
||
|
|
||
|
// Execute the command
|
||
|
command(argv);
|
||
|
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Builder
|
||
|
*
|
||
|
* @param {Object} yargs
|
||
|
*/
|
||
|
module.exports.builder = function(yargs) {
|
||
|
|
||
|
// Define the recordingFile argument
|
||
|
yargs.positional('recordingFile', {
|
||
|
describe: 'A name for the recording file',
|
||
|
type: 'string',
|
||
|
coerce: di._.partial(di.utility.resolveFilePath, di._, 'yml')
|
||
|
});
|
||
|
|
||
|
// Define the config option
|
||
|
yargs.option('c', {
|
||
|
alias: 'config',
|
||
|
type: 'string',
|
||
|
describe: 'Overwrite the default configurations',
|
||
|
requiresArg: true,
|
||
|
coerce: di.utility.loadYAML
|
||
|
});
|
||
|
|
||
|
// Define the config option
|
||
|
yargs.option('d', {
|
||
|
alias: 'command',
|
||
|
type: 'string',
|
||
|
describe: 'The command to be executed',
|
||
|
requiresArg: true,
|
||
|
default: null
|
||
|
});
|
||
|
|
||
|
// Add examples
|
||
|
yargs.example('$0 record foo', 'Start recording and create a recording file called foo.yml');
|
||
|
yargs.example('$0 record foo --config config.yml', 'Start recording with with your own configurations');
|
||
|
|
||
|
};
|