pxt-core
Version:
Microsoft MakeCode provides Blocks / JavaScript / Python tools and editors
259 lines (258 loc) • 9.51 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.CommandParser = void 0;
/* eslint-disable @typescript-eslint/no-for-in-array */
const nodeutil = require("./nodeutil");
const MaxColumns = 100;
const argRegex = /^(-+)?(.+)$/;
class CommandParser {
constructor() {
this.commands = [];
}
defineCommand(c, callback) {
const aliasMap = {};
for (const flag in c.flags) {
const def = c.flags[flag];
recordAlias(flag, flag);
const aliases = c.flags[flag].aliases;
if (aliases) {
aliases.forEach(alias => {
recordAlias(flag, alias);
});
}
}
c._aliasMap = aliasMap;
c._callback = callback;
this.commands.push(c);
function recordAlias(flag, alias) {
if (aliasMap[alias]) {
throw new Error(`Alias ${alias} for flag ${flag} duplicates the alias for flag ${aliasMap[alias]}`);
}
aliasMap[alias] = flag;
}
}
parseCommand(args) {
if (!args[0])
args = ["help"];
const name = args[0];
const parsedArgs = [];
const flags = {};
const filtered = this.commands.filter(c => c.name === name || c.aliases && c.aliases.indexOf(name) !== -1);
if (!filtered.length)
pxt.U.userError(`Command '${name}' not found, use "pxt help all" to see available commands.`);
const command = filtered[0];
if (command.anyArgs)
return command._callback({
name: command.name,
args: args.slice(1),
flags
});
let currentFlag;
let currentFlagDef;
for (let i = 1; i < args.length; i++) {
const match = argRegex.exec(args[i]);
if (!match) {
continue;
}
if (match[1]) {
if (currentFlag)
pxt.U.userError(`Expected value to follow flag '${currentFlag}'`);
const flagName = command._aliasMap[match[2]];
const debugFlag = flagName || match[2];
if (debugFlag == "debug" || debugFlag == "d" || debugFlag == "dbg") {
pxt.options.debug = true;
if (pxt.options.debug) {
pxt.setLogLevel(pxt.LogLevel.Debug);
}
pxt.log(`debug mode`);
if (!flagName)
continue;
}
if (!flagName)
pxt.U.userError(`Unrecognized flag '${match[2]}' for command '${command.name}'`);
const flagDefinition = command.flags[flagName];
if (flagDefinition.argument) {
currentFlag = flagName;
currentFlagDef = flagDefinition;
}
else {
flags[flagName] = true;
}
}
else if (currentFlag) {
if (currentFlagDef.possibleValues && currentFlagDef.possibleValues.length && !nodeutil.matchesAny(match[2], currentFlagDef.possibleValues)) {
pxt.U.userError(`Unknown value for flag '${currentFlag}', '${match[2]}'`);
}
if (!currentFlagDef.type || currentFlagDef.type === "string") {
flags[currentFlag] = match[2];
}
else if (currentFlagDef.type === "boolean") {
flags[currentFlag] = match[2].toLowerCase() === "true";
}
else {
try {
flags[currentFlag] = parseFloat(match[2]);
}
catch (e) {
throw new Error(`Flag '${currentFlag}' expected an argument of type number but received '${match[2]}'`);
}
}
currentFlag = undefined;
currentFlagDef = undefined;
}
else {
parsedArgs.push(match[2]);
}
}
if (currentFlag) {
pxt.U.userError(`Expected value to follow flag '${currentFlag}'`);
}
else if (!command.argString && parsedArgs.length) {
pxt.U.userError(`Command '${command.name}' expected exactly 0 argument(s) but received ${parsedArgs.length}`);
}
else if (command.numArgs && parsedArgs.length !== command.numArgs) {
pxt.U.userError(`Command '${command.name}' expected exactly ${command.numArgs} argument(s) but received ${parsedArgs.length}`);
}
return command._callback({
name: command.name,
args: parsedArgs,
flags
});
}
printHelp(args, print) {
if (args && args.length === 1) {
const name = args[0];
if (name === "all") {
this.printTopLevelHelp(true, print);
}
else {
const filtered = this.commands.filter(c => c.name === name || c.aliases && c.aliases.indexOf(name) !== -1);
if (filtered) {
this.printCommandHelp(filtered[0], print);
}
}
}
else {
this.printTopLevelHelp(false, print);
}
}
printCommandHelp(c, print) {
let usage = ` pxt ${c.name}`;
if (c.argString) {
usage += " " + c.argString;
}
if (c.flags) {
for (const flag in c.flags) {
const def = c.flags[flag];
if (def.hidden)
continue;
if (def.possibleValues && def.possibleValues.length) {
usage += ` [${dash(flag)} ${def.possibleValues.map(v => v.toString()).join("|")}]`;
}
else if (def.argument) {
usage += ` [${dash(flag)} ${def.argument}]`;
}
else {
usage += ` [${dash(flag)}]`;
}
}
}
print("");
print("Usage:");
print(usage);
print("");
print(c.help);
if (c.aliases && c.aliases.length) {
print("");
print("Aliases:");
c.aliases.forEach(a => print(" " + a));
}
const flagNames = [];
const flagDescriptions = [];
let maxWidth = 0;
for (const flag in c.flags) {
const def = c.flags[flag];
if (def.deprecated || def.hidden)
continue;
let usage = dash(flag);
if (def.aliases && def.aliases.length) {
usage += " " + def.aliases.map(dash).join(" ");
}
if (def.argument) {
if (def.possibleValues && def.possibleValues.length) {
usage += ` <${def.possibleValues.map(v => v.toString()).join("|")}>`;
}
else {
usage += def.type && def.type === "number" ? " <number>" : " <value>";
}
}
maxWidth = Math.max(maxWidth, usage.length);
flagNames.push(usage);
flagDescriptions.push(def.description);
}
if (flagNames.length) {
print("");
print("Flags:");
for (let i = 0; i < flagNames.length; i++) {
printLine(flagNames[i], maxWidth, flagDescriptions[i], print);
}
}
if (c.onlineHelp)
print(`More information at ${"https://makecode.com/cli/" + c.name} .`);
}
printTopLevelHelp(advanced, print) {
print("");
print("Usage: pxt <command>");
print("");
print("Commands:");
this.commands.sort((a, b) => a.priority - b.priority);
const toPrint = advanced ? this.commands : this.commands.filter(c => !c.advanced);
const cmdDescriptions = [];
let maxNameWidth = 0;
const names = toPrint.map(command => {
maxNameWidth = Math.max(maxNameWidth, command.name.length);
cmdDescriptions.push(command.help);
return command.name;
});
for (let i = 0; i < names.length; i++) {
printLine(names[i], maxNameWidth, cmdDescriptions[i], print);
}
print("");
print("For more information on a command, try 'pxt help <command>'");
}
}
exports.CommandParser = CommandParser;
function printLine(name, maxNameWidth, description, print) {
// Lines are of the format: name ...... description
let line = pad(` ${name} `, maxNameWidth - name.length + 3, false, ".");
const prefixLength = line.length;
// Split the description into words so that we can try and do some naive wrapping
const dWords = description.split(" ");
dWords.forEach(w => {
if (line.length + w.length < MaxColumns) {
line += " " + w;
}
else {
print(line);
line = pad(w, prefixLength + 1, true);
}
});
print(line);
}
function pad(str, len, left, char = " ") {
for (let i = 0; i < len; i++) {
if (left) {
str = char + str;
}
else {
str += char;
}
}
return str;
}
function dash(flag) {
if (flag.length === 1) {
return "-" + flag;
}
return "--" + flag;
}