@eagleoutice/flowr-dev
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
275 lines (270 loc) • 12.3 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.helpCommand = void 0;
exports.masterScriptArgs = masterScriptArgs;
exports.getReplCommands = getReplCommands;
exports.getCommandNames = getCommandNames;
exports.getCommand = getCommand;
exports.asOptionName = asOptionName;
exports.longestCommandName = longestCommandName;
exports.padCmd = padCmd;
const repl_quit_1 = require("./repl-quit");
const execute_1 = require("../execute");
const prompt_1 = require("../prompt");
const repl_version_1 = require("./repl-version");
const repl_parse_1 = require("./repl-parse");
const repl_execute_1 = require("./repl-execute");
const repl_normalize_1 = require("./repl-normalize");
const repl_dataflow_1 = require("./repl-dataflow");
const repl_cfg_1 = require("./repl-cfg");
const ansi_1 = require("../../../util/text/ansi");
const args_1 = require("../../../util/text/args");
const assert_1 = require("../../../util/assert");
const scripts_info_1 = require("../../common/scripts-info");
const repl_query_1 = require("./repl-query");
const repl_signature_1 = require("./repl-signature");
const version_1 = require("../../../util/version");
const cmd = (name, f) => (0, ansi_1.color)(name, 6 /* Colors.Cyan */, f, { style: 1 /* FontStyles.Bold */ });
const ansiSgr = new RegExp(String.fromCharCode(27) + '\\[[0-9;]*m', 'g');
const visibleLength = (s) => s.replace(ansiSgr, '').length;
/** Appends `content` after `prefix`, word-wrapping to the terminal width with a hanging indent. */
function wrapAfter(prefix, content) {
const width = process.stdout.isTTY ? (process.stdout.columns ?? 80) : Infinity;
const indent = visibleLength(prefix);
const pad = ' '.repeat(indent);
const lines = [];
let line = '';
let lineLength = indent;
for (const word of content.split(' ')) {
const wordLength = visibleLength(word);
if (line !== '' && lineLength + 1 + wordLength > width) {
lines.push(line);
line = word;
lineLength = indent + wordLength;
}
else {
line = line === '' ? word : `${line} ${word}`;
lineLength = line === word ? indent + wordLength : lineLength + 1 + wordLength;
}
}
if (line !== '') {
lines.push(line);
}
return prefix + lines.map((l, i) => i === 0 ? l : pad + l).join('\n');
}
function printHelpForScript(script, f, starredVersion) {
let content = script[1].description;
if (starredVersion) {
content += ` ${(0, ansi_1.italic)(`(star: ${starredVersion.description})`, f)}`;
}
const aliases = script[1].aliases;
if (aliases.length > 0) {
content += ` ${(0, ansi_1.italic)(`(alias${aliases.length > 1 ? 'es' : ''}:`, f)} ${aliases.map(a => cmd(':' + a, f)).join(', ')}${(0, ansi_1.italic)(')', f)}`;
}
return wrapAfter(` ${cmd(padCmd(':' + script[0] + (starredVersion ? '[*]' : '')), f)}`, content);
}
function printFamilyVariants(children, cmds, f) {
const variant = (name) => {
const aliases = cmds[name].aliases;
const suffix = aliases.length > 0 ? ' ' + (0, ansi_1.italic)(`(${aliases.map(a => ':' + a).join(', ')})`, f) : '';
return cmd(':' + name + (cmds[name + '*'] ? '[*]' : ''), f) + suffix;
};
return wrapAfter(` ${(0, ansi_1.italic)('variants:', f)} `, children.map(variant).join(', '));
}
function printCommandHelp(formatter) {
const cmds = getReplCommands();
const bases = Object.entries(cmds).filter(([name, c]) => !c.script && !name.endsWith('*'));
const names = bases.map(([name]) => name);
const shortestPrefixCommand = (name) => names.filter(n => name.startsWith(n)).reduce((a, b) => a.length <= b.length ? a : b);
const children = new Map();
for (const name of names) {
const root = shortestPrefixCommand(name);
if (root !== name) {
children.set(root, [...children.get(root) ?? [], name]);
}
}
const lines = [];
for (const entry of bases.filter(([name]) => shortestPrefixCommand(name) === name).sort((a, b) => a[0].localeCompare(b[0]))) {
lines.push(printHelpForScript(entry, formatter, cmds[entry[0] + '*']));
const variants = children.get(entry[0]);
if (variants) {
lines.push(printFamilyVariants(variants.sort(), cmds, formatter));
}
}
return lines.join('\n');
}
exports.helpCommand = {
description: 'Show help information',
isCodeCommand: false,
script: false,
usageExample: ':help',
aliases: ['h', '?'],
fn: ({ output }) => {
initCommandMapping();
output.stdout(`
If enabled ('--r-session-access' and if using the 'r-shell' engine), you can just enter R expressions which get evaluated right away:
${prompt_1.rawPrompt} ${(0, ansi_1.bold)('1 + 1', output.formatter)}
${(0, ansi_1.italic)('[1] 2', output.formatter)}
Besides that, you can use the following commands. The scripts ${(0, ansi_1.italic)('can', output.formatter)} accept further arguments. In general, those ending with [*] may be called with and without the star.
There are the following basic commands:
${printCommandHelp(output.formatter)}
Furthermore, you can directly call the following scripts which accept arguments. If you are unsure, try to add ${(0, ansi_1.italic)('--help', output.formatter)} after the command.
${Array.from(Object.entries(getReplCommands())).filter(([, { script }]) => script).map(([command, { description }]) => wrapAfter(` ${cmd(padCmd(':' + command), output.formatter)}`, description)).sort().join('\n')}
You can combine commands by separating them with a semicolon ${(0, ansi_1.bold)(';', output.formatter)}.
Commands that accept a file path support two path prefixes:
${(0, ansi_1.color)('file://<path>', 2 /* Colors.Green */, output.formatter, { style: 1 /* FontStyles.Bold */ })} run the command once on the given file or folder
${(0, ansi_1.color)('watch://<path>', 3 /* Colors.Yellow */, output.formatter, { style: 1 /* FontStyles.Bold */ })} re-run the command whenever the file (or any file in the folder) changes
Press Ctrl+C or enter any other command to leave watch mode.
You are running flowR ${(0, ansi_1.bold)('v' + (0, version_1.flowrVersion)().toString(), output.formatter)} (use ${(0, ansi_1.bold)(':version', output.formatter)} for details). Check for newer releases and per-install upgrade steps (Docker, npm, source) at:
${(0, ansi_1.color)('https://github.com/flowr-analysis/flowr/releases', 6 /* Colors.Cyan */, output.formatter, { style: 1 /* FontStyles.Bold */ })}
`);
}
};
/**
* All commands that should be available in the REPL.
*/
const _commands = {
'help': exports.helpCommand,
'quit': repl_quit_1.quitCommand,
'version': repl_version_1.versionCommand,
'execute': repl_execute_1.executeCommand,
'parse': repl_parse_1.parseCommand,
'normalize': repl_normalize_1.normalizeCommand,
'normalize*': repl_normalize_1.normalizeStarCommand,
'normalize#': repl_normalize_1.normalizeHashCommand,
'dataflow': repl_dataflow_1.dataflowCommand,
'dataflow*': repl_dataflow_1.dataflowStarCommand,
'dataflowsimple': repl_dataflow_1.dataflowSimplifiedCommand,
'dataflowsimple*': repl_dataflow_1.dataflowSimpleStarCommand,
'dataflowascii': repl_dataflow_1.dataflowAsciiCommand,
'dataflowsilent': repl_dataflow_1.dataflowSilentCommand,
'controlflow': repl_cfg_1.controlflowCommand,
'controlflow*': repl_cfg_1.controlflowStarCommand,
'controlflowbb': repl_cfg_1.controlflowBbCommand,
'controlflowbb*': repl_cfg_1.controlflowBbStarCommand,
'query': repl_query_1.queryCommand,
'query*': repl_query_1.queryStarCommand,
'signature': repl_signature_1.signatureCommand
};
let commandsInitialized = false;
function hasModule(path) {
try {
require.resolve(path);
return true;
}
catch {
return false;
}
}
/**
* The args a master script is forked with: `remainingLine` tokenized, plus `--config-json <analyzer.flowrConfig>`
* so it sees the repl's current (possibly `:query \@config`-edited) config -- unless the script does not support
* that option, or the user already named a config themselves.
*/
function masterScriptArgs(remainingLine, config, scriptOptions) {
const args = (0, args_1.splitAtEscapeSensitive)(remainingLine);
if (scriptOptions.some(o => o.name === 'config-json') && !args.includes('--config-json') && !args.includes('--config-file')) {
args.push('--config-json', JSON.stringify(config));
}
return args;
}
/**
* Retrieve all REPL commands (including those generated from master scripts)
*/
function getReplCommands() {
if (commandsInitialized) {
return _commands;
}
for (const [script, { target, description, type, options }] of Object.entries(scripts_info_1.scripts)) {
if (type === 'master script') {
_commands[script] = {
description,
aliases: [],
script: true,
usageExample: `:${script} --help`,
isCodeCommand: false,
fn: async ({ output, remainingLine, analyzer }) => {
// check if the target *module* exists in the current directory, else try two dirs up, otherwise, fail with a message
let path = `${__dirname}/${target}`;
if (!hasModule(path)) {
path = `${__dirname}/../../${target}`;
if (!hasModule(path)) {
output.stderr(`Could not find the target script ${target} in the current directory or two directories up.`);
return;
}
}
await (0, execute_1.waitOnScript)(path, masterScriptArgs(remainingLine, analyzer.flowrConfig, options), stdio => (0, execute_1.stdioCaptureProcessor)(stdio, msg => output.stdout(msg), msg => output.stderr(msg)));
}
};
}
}
commandsInitialized = true;
return _commands;
}
/**
* The names of all commands including their aliases (but without the leading `:`)
*/
function getCommandNames() {
if (commandNames === undefined) {
initCommandMapping();
}
return commandNames;
}
let commandNames = undefined;
// maps command names or aliases to the actual command name
let commandMapping = undefined;
function initCommandMapping() {
const mapping = {};
const names = [];
for (const [command, { aliases }] of Object.entries(getReplCommands())) {
(0, assert_1.guard)(mapping[command] === undefined, `Command ${command} is already registered!`);
mapping[command] = command;
for (const alias of aliases) {
(0, assert_1.guard)(mapping[alias] === undefined, `Command (alias) ${alias} is already registered!`);
mapping[alias] = command;
}
names.push(command);
names.push(...aliases);
}
commandMapping = mapping;
commandNames = names;
}
/**
* Get the command for a given command name or alias.
* @param command - The name of the command (without the leading `:`)
*/
function getCommand(command) {
if (commandMapping === undefined) {
initCommandMapping();
}
return getReplCommands()[commandMapping[command]];
}
/**
* Formats the given argument name as a command line option (with single or double dashes).
*/
function asOptionName(argument) {
if (argument.length == 1) {
return `-${argument}`;
}
else {
return `--${argument}`;
}
}
let _longestCommandName = undefined;
/**
* Retrieve the length of the longest command name (including star and brackets if applicable)
*/
function longestCommandName() {
if (_longestCommandName === undefined) {
_longestCommandName = Array.from(Object.keys(getReplCommands()), k => k.endsWith('*') ? k.length + 3 : k.length).reduce((p, n) => Math.max(p, n), 0);
}
return _longestCommandName;
}
/**
* Pad the given command string to the length of the longest command name plus two spaces.
* @see {@link longestCommandName}
*/
function padCmd(string) {
return String(string).padEnd(longestCommandName() + 2, ' ');
}
//# sourceMappingURL=repl-commands.js.map