UNPKG

pxt-core

Version:

Microsoft MakeCode provides Blocks / JavaScript / Python tools and editors

259 lines (258 loc) • 9.51 kB
"use strict"; 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; }