2017-11-03 02:09:34 +02:00
|
|
|
const yargParser = require('yargs-parser');
|
2020-11-07 17:59:37 +02:00
|
|
|
const { _ } = require('@joplin/lib/locale');
|
|
|
|
const time = require('@joplin/lib/time').default;
|
2017-08-03 19:48:14 +02:00
|
|
|
const stringPadding = require('string-padding');
|
2020-11-07 17:59:37 +02:00
|
|
|
const Logger = require('@joplin/lib/Logger').default;
|
2017-08-03 19:48:14 +02:00
|
|
|
|
2017-08-04 19:51:01 +02:00
|
|
|
const cliUtils = {};
|
2017-08-03 19:48:14 +02:00
|
|
|
|
2019-09-13 00:16:42 +02:00
|
|
|
cliUtils.printArray = function(logFunction, rows) {
|
2017-08-04 19:51:01 +02:00
|
|
|
if (!rows.length) return '';
|
2017-08-03 19:48:14 +02:00
|
|
|
|
2017-08-04 19:51:01 +02:00
|
|
|
const ALIGN_LEFT = 0;
|
|
|
|
const ALIGN_RIGHT = 1;
|
2017-08-03 19:48:14 +02:00
|
|
|
|
2020-03-14 01:46:14 +02:00
|
|
|
const colWidths = [];
|
|
|
|
const colAligns = [];
|
2017-08-03 19:48:14 +02:00
|
|
|
|
2017-08-04 19:51:01 +02:00
|
|
|
for (let i = 0; i < rows.length; i++) {
|
2020-03-14 01:46:14 +02:00
|
|
|
const row = rows[i];
|
2019-07-30 09:35:42 +02:00
|
|
|
|
2017-08-04 19:51:01 +02:00
|
|
|
for (let j = 0; j < row.length; j++) {
|
2020-03-14 01:46:14 +02:00
|
|
|
const item = row[j];
|
|
|
|
const width = item ? item.toString().length : 0;
|
|
|
|
const align = typeof item == 'number' ? ALIGN_RIGHT : ALIGN_LEFT;
|
2017-08-04 19:51:01 +02:00
|
|
|
if (!colWidths[j] || colWidths[j] < width) colWidths[j] = width;
|
|
|
|
if (colAligns.length <= j) colAligns[j] = align;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (let row = 0; row < rows.length; row++) {
|
2020-03-14 01:46:14 +02:00
|
|
|
const line = [];
|
2017-08-04 19:51:01 +02:00
|
|
|
for (let col = 0; col < colWidths.length; col++) {
|
2020-03-14 01:46:14 +02:00
|
|
|
const item = rows[row][col];
|
|
|
|
const width = colWidths[col];
|
|
|
|
const dir = colAligns[col] == ALIGN_LEFT ? stringPadding.RIGHT : stringPadding.LEFT;
|
2017-08-04 19:51:01 +02:00
|
|
|
line.push(stringPadding(item, width, ' ', dir));
|
2017-08-03 19:48:14 +02:00
|
|
|
}
|
2017-08-04 19:51:01 +02:00
|
|
|
logFunction(line.join(' '));
|
|
|
|
}
|
2019-07-30 09:35:42 +02:00
|
|
|
};
|
2017-08-04 19:51:01 +02:00
|
|
|
|
|
|
|
cliUtils.parseFlags = function(flags) {
|
2020-03-14 01:46:14 +02:00
|
|
|
const output = {};
|
2017-08-04 19:51:01 +02:00
|
|
|
flags = flags.split(',');
|
|
|
|
for (let i = 0; i < flags.length; i++) {
|
|
|
|
let f = flags[i].trim();
|
2017-08-03 19:48:14 +02:00
|
|
|
|
2017-08-04 19:51:01 +02:00
|
|
|
if (f.substr(0, 2) == '--') {
|
|
|
|
f = f.split(' ');
|
|
|
|
output.long = f[0].substr(2).trim();
|
|
|
|
if (f.length == 2) {
|
|
|
|
output.arg = cliUtils.parseCommandArg(f[1].trim());
|
2017-08-03 19:48:14 +02:00
|
|
|
}
|
2017-08-04 19:51:01 +02:00
|
|
|
} else if (f.substr(0, 1) == '-') {
|
|
|
|
output.short = f.substr(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return output;
|
2019-07-30 09:35:42 +02:00
|
|
|
};
|
2017-08-04 19:51:01 +02:00
|
|
|
|
|
|
|
cliUtils.parseCommandArg = function(arg) {
|
2019-09-19 23:51:18 +02:00
|
|
|
if (arg.length <= 2) throw new Error(`Invalid command arg: ${arg}`);
|
2017-08-04 19:51:01 +02:00
|
|
|
|
|
|
|
const c1 = arg[0];
|
|
|
|
const c2 = arg[arg.length - 1];
|
|
|
|
const name = arg.substr(1, arg.length - 2);
|
|
|
|
|
|
|
|
if (c1 == '<' && c2 == '>') {
|
|
|
|
return { required: true, name: name };
|
|
|
|
} else if (c1 == '[' && c2 == ']') {
|
|
|
|
return { required: false, name: name };
|
|
|
|
} else {
|
2019-09-19 23:51:18 +02:00
|
|
|
throw new Error(`Invalid command arg: ${arg}`);
|
2017-08-04 19:51:01 +02:00
|
|
|
}
|
2019-07-30 09:35:42 +02:00
|
|
|
};
|
2017-08-04 19:51:01 +02:00
|
|
|
|
|
|
|
cliUtils.makeCommandArgs = function(cmd, argv) {
|
|
|
|
let cmdUsage = cmd.usage();
|
|
|
|
cmdUsage = yargParser(cmdUsage);
|
2020-03-14 01:46:14 +02:00
|
|
|
const output = {};
|
2017-08-04 19:51:01 +02:00
|
|
|
|
2020-03-14 01:46:14 +02:00
|
|
|
const options = cmd.options();
|
|
|
|
const booleanFlags = [];
|
|
|
|
const aliases = {};
|
2017-08-04 19:51:01 +02:00
|
|
|
for (let i = 0; i < options.length; i++) {
|
2019-09-19 23:51:18 +02:00
|
|
|
if (options[i].length != 2) throw new Error(`Invalid options: ${options[i]}`);
|
2017-08-04 19:51:01 +02:00
|
|
|
let flags = options[i][0];
|
|
|
|
|
|
|
|
flags = cliUtils.parseFlags(flags);
|
|
|
|
|
|
|
|
if (!flags.arg) {
|
2021-09-17 11:59:10 +02:00
|
|
|
if (flags.short) booleanFlags.push(flags.short);
|
2017-08-04 19:51:01 +02:00
|
|
|
if (flags.long) booleanFlags.push(flags.long);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (flags.short && flags.long) {
|
|
|
|
aliases[flags.long] = [flags.short];
|
2017-08-03 19:48:14 +02:00
|
|
|
}
|
2017-08-04 19:51:01 +02:00
|
|
|
}
|
|
|
|
|
2020-03-14 01:46:14 +02:00
|
|
|
const args = yargParser(argv, {
|
2017-08-04 19:51:01 +02:00
|
|
|
boolean: booleanFlags,
|
|
|
|
alias: aliases,
|
2017-08-21 19:56:40 +02:00
|
|
|
string: ['_'],
|
2017-08-04 19:51:01 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
for (let i = 1; i < cmdUsage['_'].length; i++) {
|
|
|
|
const a = cliUtils.parseCommandArg(cmdUsage['_'][i]);
|
2017-08-04 18:50:12 +02:00
|
|
|
if (a.required && !args['_'][i]) throw new Error(_('Missing required argument: %s', a.name));
|
2017-08-04 19:51:01 +02:00
|
|
|
if (i >= a.length) {
|
|
|
|
output[a.name] = null;
|
|
|
|
} else {
|
|
|
|
output[a.name] = args['_'][i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-14 01:46:14 +02:00
|
|
|
const argOptions = {};
|
|
|
|
for (const key in args) {
|
2017-08-04 19:51:01 +02:00
|
|
|
if (!args.hasOwnProperty(key)) continue;
|
|
|
|
if (key == '_') continue;
|
|
|
|
argOptions[key] = args[key];
|
|
|
|
}
|
|
|
|
|
|
|
|
output.options = argOptions;
|
2017-08-03 19:48:14 +02:00
|
|
|
|
2017-08-04 19:51:01 +02:00
|
|
|
return output;
|
2019-07-30 09:35:42 +02:00
|
|
|
};
|
2017-08-03 19:48:14 +02:00
|
|
|
|
2017-09-10 19:32:04 +02:00
|
|
|
cliUtils.promptMcq = function(message, answers) {
|
|
|
|
const readline = require('readline');
|
|
|
|
|
|
|
|
const rl = readline.createInterface({
|
|
|
|
input: process.stdin,
|
2019-07-30 09:35:42 +02:00
|
|
|
output: process.stdout,
|
2017-09-10 19:32:04 +02:00
|
|
|
});
|
|
|
|
|
2019-07-30 09:35:42 +02:00
|
|
|
message += '\n\n';
|
2020-03-14 01:46:14 +02:00
|
|
|
for (const n in answers) {
|
2017-09-10 19:32:04 +02:00
|
|
|
if (!answers.hasOwnProperty(n)) continue;
|
2019-09-19 23:51:18 +02:00
|
|
|
message += `${_('%s: %s', n, answers[n])}\n`;
|
2017-09-10 19:32:04 +02:00
|
|
|
}
|
|
|
|
|
2019-07-30 09:35:42 +02:00
|
|
|
message += '\n';
|
2017-09-10 19:32:04 +02:00
|
|
|
message += _('Your choice: ');
|
|
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
2020-05-21 10:14:33 +02:00
|
|
|
rl.question(message, answer => {
|
2017-09-10 19:32:04 +02:00
|
|
|
rl.close();
|
|
|
|
|
|
|
|
if (!(answer in answers)) {
|
|
|
|
reject(new Error(_('Invalid answer: %s', answer)));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
resolve(answer);
|
|
|
|
});
|
|
|
|
});
|
2019-07-30 09:35:42 +02:00
|
|
|
};
|
2017-09-10 19:32:04 +02:00
|
|
|
|
2017-08-04 18:50:12 +02:00
|
|
|
cliUtils.promptConfirm = function(message, answers = null) {
|
|
|
|
if (!answers) answers = [_('Y'), _('n')];
|
|
|
|
const readline = require('readline');
|
|
|
|
|
|
|
|
const rl = readline.createInterface({
|
|
|
|
input: process.stdin,
|
2019-07-30 09:35:42 +02:00
|
|
|
output: process.stdout,
|
2017-08-04 18:50:12 +02:00
|
|
|
});
|
|
|
|
|
2019-09-19 23:51:18 +02:00
|
|
|
message += ` (${answers.join('/')})`;
|
2017-08-04 18:50:12 +02:00
|
|
|
|
2019-09-13 00:16:42 +02:00
|
|
|
return new Promise((resolve) => {
|
2020-05-21 10:14:33 +02:00
|
|
|
rl.question(`${message} `, answer => {
|
2017-08-04 18:50:12 +02:00
|
|
|
const ok = !answer || answer.toLowerCase() == answers[0].toLowerCase();
|
|
|
|
rl.close();
|
|
|
|
resolve(ok);
|
|
|
|
});
|
|
|
|
});
|
2019-07-30 09:35:42 +02:00
|
|
|
};
|
2017-08-04 18:50:12 +02:00
|
|
|
|
2017-10-20 00:02:13 +02:00
|
|
|
// Note: initialText is there to have the same signature as statusBar.prompt() so that
|
|
|
|
// it can be a drop-in replacement, however initialText is not used (and cannot be
|
|
|
|
// with readline.question?).
|
2019-09-13 00:16:42 +02:00
|
|
|
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
|
2017-12-12 20:17:30 +02:00
|
|
|
cliUtils.prompt = function(initialText = '', promptString = ':', options = null) {
|
|
|
|
if (!options) options = {};
|
|
|
|
|
2017-10-20 00:02:13 +02:00
|
|
|
const readline = require('readline');
|
2017-12-12 20:17:30 +02:00
|
|
|
const Writable = require('stream').Writable;
|
|
|
|
|
|
|
|
const mutableStdout = new Writable({
|
|
|
|
write: function(chunk, encoding, callback) {
|
2019-07-30 09:35:42 +02:00
|
|
|
if (!this.muted) process.stdout.write(chunk, encoding);
|
2017-12-12 20:17:30 +02:00
|
|
|
callback();
|
2019-07-30 09:35:42 +02:00
|
|
|
},
|
2017-12-12 20:17:30 +02:00
|
|
|
});
|
2017-10-20 00:02:13 +02:00
|
|
|
|
|
|
|
const rl = readline.createInterface({
|
|
|
|
input: process.stdin,
|
2017-12-12 20:17:30 +02:00
|
|
|
output: mutableStdout,
|
|
|
|
terminal: true,
|
2017-10-20 00:02:13 +02:00
|
|
|
});
|
|
|
|
|
2019-09-13 00:16:42 +02:00
|
|
|
return new Promise((resolve) => {
|
2017-12-12 20:17:30 +02:00
|
|
|
mutableStdout.muted = false;
|
|
|
|
|
2020-05-21 10:14:33 +02:00
|
|
|
rl.question(promptString, answer => {
|
2017-10-20 00:02:13 +02:00
|
|
|
rl.close();
|
2019-07-30 09:35:42 +02:00
|
|
|
if (options.secure) this.stdout_('');
|
2017-10-20 00:02:13 +02:00
|
|
|
resolve(answer);
|
|
|
|
});
|
2017-12-12 20:17:30 +02:00
|
|
|
|
|
|
|
mutableStdout.muted = !!options.secure;
|
2017-10-20 00:02:13 +02:00
|
|
|
});
|
2019-07-30 09:35:42 +02:00
|
|
|
};
|
2017-10-20 00:02:13 +02:00
|
|
|
|
2017-08-04 18:50:12 +02:00
|
|
|
let redrawStarted_ = false;
|
|
|
|
let redrawLastLog_ = null;
|
|
|
|
let redrawLastUpdateTime_ = 0;
|
|
|
|
|
2017-10-14 20:03:23 +02:00
|
|
|
cliUtils.setStdout = function(v) {
|
|
|
|
this.stdout_ = v;
|
2019-07-30 09:35:42 +02:00
|
|
|
};
|
2017-10-14 20:03:23 +02:00
|
|
|
|
2017-08-04 18:50:12 +02:00
|
|
|
cliUtils.redraw = function(s) {
|
|
|
|
const now = time.unixMs();
|
|
|
|
|
|
|
|
if (now - redrawLastUpdateTime_ > 4000) {
|
2017-10-25 19:41:36 +02:00
|
|
|
this.stdout_(s);
|
2017-08-04 18:50:12 +02:00
|
|
|
redrawLastUpdateTime_ = now;
|
|
|
|
redrawLastLog_ = null;
|
|
|
|
} else {
|
|
|
|
redrawLastLog_ = s;
|
|
|
|
}
|
|
|
|
|
2019-07-30 09:35:42 +02:00
|
|
|
redrawStarted_ = true;
|
|
|
|
};
|
2017-08-04 18:50:12 +02:00
|
|
|
|
|
|
|
cliUtils.redrawDone = function() {
|
|
|
|
if (!redrawStarted_) return;
|
|
|
|
|
|
|
|
if (redrawLastLog_) {
|
2017-10-14 20:03:23 +02:00
|
|
|
this.stdout_(redrawLastLog_);
|
2017-08-04 18:50:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
redrawLastLog_ = null;
|
|
|
|
redrawStarted_ = false;
|
2019-07-30 09:35:42 +02:00
|
|
|
};
|
2017-08-04 18:50:12 +02:00
|
|
|
|
2020-08-02 13:28:50 +02:00
|
|
|
cliUtils.stdoutLogger = function(stdout) {
|
|
|
|
const stdoutFn = (...s) => stdout(s.join(' '));
|
|
|
|
|
|
|
|
const logger = new Logger();
|
|
|
|
logger.addTarget('console', { console: {
|
|
|
|
info: stdoutFn,
|
|
|
|
warn: stdoutFn,
|
|
|
|
error: stdoutFn,
|
|
|
|
} });
|
|
|
|
|
|
|
|
return logger;
|
|
|
|
};
|
|
|
|
|
2019-07-30 09:35:42 +02:00
|
|
|
module.exports = { cliUtils };
|