diff --git a/src/Alias.ts b/src/Alias.ts index 0586ea6e4..5ee17d199 100644 --- a/src/Alias.ts +++ b/src/Alias.ts @@ -1,4 +1,5 @@ import { Terminal } from "./Terminal"; +import { trimQuotes } from "./utils/helpers/string"; export const Aliases = new Map(); export const GlobalAliases = new Map(); @@ -34,6 +35,7 @@ export function parseAliasDeclaration(dec: string, global = false): boolean { if (matches == null || matches.length != 3) { return false; } + matches[2] = trimQuotes(matches[2]); if (global) { addGlobalAlias(matches[1], matches[2]); diff --git a/src/Corporation/Division.ts b/src/Corporation/Division.ts index a7bbfaaf2..d03939cbb 100644 --- a/src/Corporation/Division.ts +++ b/src/Corporation/Division.ts @@ -8,7 +8,7 @@ import { calculateEffectWithFactors } from "../utils/calculateEffectWithFactors" import { OfficeSpace } from "./OfficeSpace"; import { Product } from "./Product"; import { dialogBoxCreate } from "../ui/React/DialogBox"; -import { isString } from "../utils/helpers/isString"; +import { isString } from "../utils/helpers/string"; import { MaterialInfo } from "./MaterialInfo"; import { Warehouse } from "./Warehouse"; import { Corporation } from "./Corporation"; diff --git a/src/Corporation/ui/MaterialElem.tsx b/src/Corporation/ui/MaterialElem.tsx index 4b0dda01b..90e17a772 100644 --- a/src/Corporation/ui/MaterialElem.tsx +++ b/src/Corporation/ui/MaterialElem.tsx @@ -11,7 +11,7 @@ import { PurchaseMaterialModal } from "./modals/PurchaseMaterialModal"; import { formatBigNumber, formatCorpStat, formatMoney, formatQuality } from "../../ui/formatNumber"; -import { isString } from "../../utils/helpers/isString"; +import { isString } from "../../utils/helpers/string"; import { Money } from "../../ui/React/Money"; import { useCorporation, useDivision } from "./Context"; diff --git a/src/Corporation/ui/ProductElem.tsx b/src/Corporation/ui/ProductElem.tsx index 76b0c9896..662540905 100644 --- a/src/Corporation/ui/ProductElem.tsx +++ b/src/Corporation/ui/ProductElem.tsx @@ -11,7 +11,7 @@ import { CancelProductModal } from "./modals/CancelProductModal"; import { formatBigNumber, formatCorpStat, formatMoney, formatPercent } from "../../ui/formatNumber"; -import { isString } from "../../utils/helpers/isString"; +import { isString } from "../../utils/helpers/string"; import { Money } from "../../ui/React/Money"; import { useCorporation, useDivision } from "./Context"; diff --git a/src/Terminal/HelpText.ts b/src/Terminal/HelpText.ts index 02e1a7820..57a67506c 100644 --- a/src/Terminal/HelpText.ts +++ b/src/Terminal/HelpText.ts @@ -1,7 +1,7 @@ export const TerminalHelpText: string[] = [ "Type 'help name' to learn more about the command ", " ", - ' alias [-g] ["name=value"] Create or display Terminal aliases', + ' alias [-g] [name="value"] Create or display Terminal aliases', " analyze Get information about the current machine ", " backdoor Install a backdoor on the current machine ", " buy [-l/-a/program] Purchase a program through the Dark Web", @@ -69,20 +69,20 @@ const TemplatedHelpTexts: Record string[]> = { export const HelpTexts: Record = { alias: [ - 'Usage: alias [-g] ["name=value"] ', + 'Usage: alias [-g] [name="value"] ', " ", "Create or display aliases. An alias enables a replacement of a word with another string. ", "It can be used to abbreviate a commonly used command, or commonly used parts of a command. The NAME ", "of an alias defines the word that will be replaced, while the VALUE defines what it will be replaced by. For example, ", "you could create the alias 'nuke' for the Terminal command 'run NUKE.exe' using the following: ", " ", - ' alias "nuke=run NUKE.exe"', + ' alias nuke="run NUKE.exe"', " ", "Then, to run the NUKE.exe program you would just have to enter 'nuke' in Terminal rather than the full command. ", "It is important to note that 'default' aliases will only be substituted for the first word of a Terminal command. For ", "example, if the following alias was set: ", " ", - ' alias "worm=HTTPWorm.exe"', + ' alias worm="HTTPWorm.exe"', " ", "and then you tried to run the following terminal command: ", " ", @@ -91,7 +91,7 @@ export const HelpTexts: Record = { "This would fail because the worm alias is not the first word of a Terminal command. To allow an alias to be substituted ", "anywhere in a Terminal command, rather than just the first word, you must set it to be a global alias using the -g flag: ", " ", - ' alias -g "worm=HTTPWorm.exe"', + ' alias -g worm="HTTPWorm.exe"', " ", "Now, the 'worm' alias will be substituted anytime it shows up as an individual word in a Terminal command. ", " ", diff --git a/src/Terminal/Parser.ts b/src/Terminal/Parser.ts index d7b5e33ff..27774c2de 100644 --- a/src/Terminal/Parser.ts +++ b/src/Terminal/Parser.ts @@ -1,3 +1,4 @@ +import { trimQuotes } from "../utils/helpers/string"; import { substituteAliases } from "../Alias"; // Helper function to parse individual arguments into number/boolean/string as appropriate function parseArg(arg: string): string | number | boolean { @@ -5,11 +6,7 @@ function parseArg(arg: string): string | number | boolean { if (arg === "false") return false; const argAsNumber = Number(arg); if (!isNaN(argAsNumber)) return argAsNumber; - // For quoted strings just return the inner string - if ((arg.startsWith('"') && arg.endsWith('"')) || (arg.startsWith("'") && arg.endsWith("'"))) { - return arg.substring(1, arg.length - 1); - } - return arg; + return trimQuotes(arg); } /** split a commands string into a commands array */ @@ -31,7 +28,7 @@ export function parseCommands(commandsText: string): string[] { /** get a commandArgs array from a single command string */ export function parseCommand(command: string): (string | number | boolean)[] { // Match every command arg in a given command string - const argDetection = /(?:("[^"]*"|'[^']*'|[^\s]+))/g; + const argDetection = /(?:([^ ;"']*"[^"]*"|[^ ;"']*'[^']*'|[^\s]+))/g; const commandArgs = command.match(argDetection); if (!commandArgs) return []; return commandArgs.map(parseArg); diff --git a/src/Terminal/commands/alias.ts b/src/Terminal/commands/alias.ts index 02d804474..d5c7f8d37 100644 --- a/src/Terminal/commands/alias.ts +++ b/src/Terminal/commands/alias.ts @@ -20,5 +20,5 @@ export function alias(args: (string | number | boolean)[]): void { } } } - Terminal.error('Incorrect usage of alias command. Usage: alias [-g] ["aliasname=value"]'); + Terminal.error('Incorrect usage of alias command. Usage: alias [-g] [aliasname="value"]'); } diff --git a/src/utils/StringHelperFunctions.ts b/src/utils/StringHelperFunctions.ts index d848d8809..313553c04 100644 --- a/src/utils/StringHelperFunctions.ts +++ b/src/utils/StringHelperFunctions.ts @@ -1,5 +1,5 @@ import { Settings } from "../Settings/Settings"; -import { isString } from "./helpers/isString"; +import { isString } from "./helpers/string"; /* Converts a date representing time in milliseconds to a string with the format H hours M minutes and S seconds diff --git a/src/utils/helpers/isString.ts b/src/utils/helpers/isString.ts deleted file mode 100644 index c5874fd2b..000000000 --- a/src/utils/helpers/isString.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Checks whether the value passed in can be considered a string. - * @param value The value to check if it is a string. - */ -export function isString(value: unknown): value is string { - return typeof value === "string" || value instanceof String; -} diff --git a/src/utils/helpers/string.ts b/src/utils/helpers/string.ts new file mode 100644 index 000000000..b0eba1cf4 --- /dev/null +++ b/src/utils/helpers/string.ts @@ -0,0 +1,17 @@ +// We can probably get rid of isString in favor of just checking typeof value==="string". +// We are not and should not ever be using `new String()` for anything. Will remove in 2.3.1 +/** + * Checks whether the value passed in can be considered a string. + * @param value The value to check if it is a string. + */ +export function isString(value: unknown): value is string { + return typeof value === "string" || value instanceof String; +} + +/** Removes a single layer of matching single or double quotes, if present. */ +export function trimQuotes(value: string): string { + if (value.length < 2) return value; + if (value.at(0) !== value.at(-1)) return value; + if (value.at(0) !== "'" && value.at(0) !== '"') return value; + return value.substring(1, value.length - 1); +} diff --git a/test/jest/Terminal/Parsing.test.ts b/test/jest/Terminal/Parsing.test.ts new file mode 100644 index 000000000..cc1b7106c --- /dev/null +++ b/test/jest/Terminal/Parsing.test.ts @@ -0,0 +1,16 @@ +import { parseCommand } from "../../../src/Terminal/Parser"; +test("parseCommand Tests", () => { + const expectedParsings = { + // A quoted string that is not the entire argument should retain the quotes + 'alias -g n00dles="home;connect n00dles"': ["alias", "-g", 'n00dles="home;connect n00dles"'], + // Normal quoted string handling + 'alias -g "n00dles=home;connect n00dles"': ["alias", "-g", "n00dles=home;connect n00dles"], + // Check parsing even if quoted section appears within another quoted section, with differing whitespace between args and in quotes + 'run myScript.js "" " " " " hello \' whoa" " \'': ["run", "myScript.js", "", " ", " ", "hello", ' whoa" " '], + // extra whitespace at start and end of string are ignored + ' run myScript.js "" " " " " hello \' whoa" " \' ': ["run", "myScript.js", "", " ", " ", "hello", ' whoa" " '], + }; + for (const [commandString, expectedArray] of Object.entries(expectedParsings)) { + expect(parseCommand(commandString)).toEqual(expectedArray); + } +});