From 74ba06377265df8e19f53a0e56d4362b235622e6 Mon Sep 17 00:00:00 2001 From: smolgumball Date: Sun, 9 Jan 2022 20:26:26 -0700 Subject: [PATCH 1/3] Add simple glob support to nano/vim usage --- src/Terminal/commands/common/editor.ts | 90 +++++++++++++++++++++++++- 1 file changed, 87 insertions(+), 3 deletions(-) diff --git a/src/Terminal/commands/common/editor.ts b/src/Terminal/commands/common/editor.ts index bf5c23960..8d6b6c7c5 100644 --- a/src/Terminal/commands/common/editor.ts +++ b/src/Terminal/commands/common/editor.ts @@ -1,9 +1,12 @@ import { ITerminal } from "../../ITerminal"; +import { removeLeadingSlash, removeTrailingSlash } from '../../DirectoryHelpers' import { IRouter, ScriptEditorRouteOptions } from "../../../ui/Router"; import { IPlayer } from "../../../PersonObjects/IPlayer"; import { BaseServer } from "../../../Server/BaseServer"; import { isScriptFilename } from "../../../Script/isScriptFilename"; import { CursorPositions } from "../../../ScriptEditor/CursorPositions"; +import { Script } from "../../../Script/Script"; +import { isEmpty } from "lodash"; interface EditorParameters { terminal: ITerminal; @@ -22,6 +25,74 @@ export async function main(ns) { }`; +interface ISimpleScriptGlob { + glob: string; + preGlob: string; + postGlob: string; + globError: string; + globMatches: string[]; + globAgainst: Script[]; +} + +function containsSimpleGlob(filename: string): boolean { + return filename.includes("*"); +} + +function detectSimpleScriptGlob( + args: EditorParameters["args"], + player: IPlayer, + terminal: ITerminal, +): ISimpleScriptGlob | null { + if (args.length == 1 && containsSimpleGlob(`${args[0]}`)) { + const filename = `${args[0]}`; + const scripts = player.getCurrentServer().scripts; + const parsedGlob = parseSimpleScriptGlob(filename, scripts, terminal); + return parsedGlob; + } + return null; +} + +function parseSimpleScriptGlob(globString: string, globDatabase: Script[], terminal: ITerminal): ISimpleScriptGlob { + const parsedGlob: ISimpleScriptGlob = { + glob: globString, + preGlob: "", + postGlob: "", + globError: "", + globMatches: [], + globAgainst: globDatabase, + }; + + // Ensure deep globs are minified to simple globs, which act as deep globs in this impl + globString = globString.replace("**", "*"); + + // Ensure only a single glob is present + if (globString.split("").filter((c) => c == "*").length !== 1) { + parsedGlob.globError = "Only a single glob is supported per command.\nexample: `nano my-dir/*.js`"; + return parsedGlob; + } + + // Split arg around glob, normalize preGlob path + [parsedGlob.preGlob, parsedGlob.postGlob] = globString.split("*"); + parsedGlob.preGlob = removeLeadingSlash(parsedGlob.preGlob); + + // Add CWD to preGlob path + const cwd = removeTrailingSlash(terminal.cwd()) + parsedGlob.preGlob = `${cwd}/${parsedGlob.preGlob}` + + // For every script on the current server, filter matched scripts per glob values & persist + globDatabase.forEach((script) => { + const filename = script.filename.startsWith('/') ? script.filename : `/${script.filename}` + if (filename.startsWith(parsedGlob.preGlob) && filename.endsWith(parsedGlob.postGlob)) { + parsedGlob.globMatches.push(filename); + } + }); + + // Rebuild glob for potential error reporting + parsedGlob.glob = `${parsedGlob.preGlob}*${parsedGlob.postGlob}` + + return parsedGlob; +} + export function commonEditor( command: string, { terminal, router, player, args }: EditorParameters, @@ -32,8 +103,15 @@ export function commonEditor( return; } + let filesToLoadOrCreate = args; try { - const files = args.map((arg) => { + const globSearch = detectSimpleScriptGlob(args, player, terminal); + if (globSearch) { + if (isEmpty(globSearch.globError) === false) throw new Error(globSearch.globError); + filesToLoadOrCreate = globSearch.globMatches; + } + + const files = filesToLoadOrCreate.map((arg) => { const filename = `${arg}`; if (isScriptFilename(filename)) { @@ -55,12 +133,18 @@ export function commonEditor( if (filename.endsWith(".txt")) { const filepath = terminal.getFilepath(filename); const txt = terminal.getTextFile(player, filename); - return [filepath, txt == null ? "" : txt.text]; + return [filepath, txt === null ? "" : txt.text]; } - throw new Error(`Invalid file. Only scripts (.script, .ns, .js), or text files (.txt) can be edited with ${command}`); + throw new Error( + `Invalid file. Only scripts (.script, .ns, .js), or text files (.txt) can be edited with ${command}`, + ); }); + if (globSearch && files.length === 0) { + throw new Error(`Could not find any valid files to open with ${command} using glob: \`${globSearch.glob}\``) + } + router.toScriptEditor(Object.fromEntries(files), scriptEditorRouteOptions); } catch (e) { terminal.error(`${e}`); From b6f252cd8c488b384db9e62a4c01dcd68bdf73e1 Mon Sep 17 00:00:00 2001 From: smolgumball Date: Sun, 9 Jan 2022 20:26:56 -0700 Subject: [PATCH 2/3] Update terminal help to mention globs; refactor to dedupe nano/vim usage details --- src/Terminal/HelpText.ts | 42 +++++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/src/Terminal/HelpText.ts b/src/Terminal/HelpText.ts index af943d125..a8ac51f17 100644 --- a/src/Terminal/HelpText.ts +++ b/src/Terminal/HelpText.ts @@ -29,7 +29,7 @@ export const TerminalHelpText: string[] = [ "lscpu Displays the number of CPU cores on the machine", "mem [script] [-t n] Displays the amount of RAM required to run the script", "mv [src] [dest] Move/rename a text or script file", - "nano [file ...] Text editor - Open up and edit one or more scripts or text files", + "nano [file ...] | [glob] Text editor - Open up and edit one or more scripts or text files", "ps Display all scripts that are currently running", "rm [file] Delete a file from the server", "run [name] [-t n] [--tail] [args...] Execute a program or script", @@ -40,11 +40,33 @@ export const TerminalHelpText: string[] = [ "tail [script] [args...] Displays dynamic logs for the specified script", "top Displays all running scripts and their RAM usage", "unalias [alias name] Deletes the specified alias", - "vim [file ...] Text editor - Open up and edit one or more scripts or text files in vim mode", + "vim [file ...] | [glob] Text editor - Open up and edit one or more scripts or text files in vim mode", "weaken Reduce the security of the current machine", "wget [url] [target file] Retrieves code/text from a web server", ]; +const TemplatedHelpTexts: IMap<(command: string) => string[]> = { + textEditor: (command) => { + return [ + `${command} [file ...] | [glob]`, + ` `, + `Opens up the specified file(s) in the Text Editor. Only scripts (.js, .ns) or text files (.txt) can be `, + `edited using the Text Editor. If the file does not already exist, then a new, empty one will be created`, + ` `, + `If provided a glob as the only argument, ${command} can spider directories and open all matching `, + `files at once. ${command} cannot create files using globs, so your scripts must already exist.`, + ` `, + `Examples:`, + ` `, + `${command} test.js`, + `${command} test.js test2.js`, + ` `, + `${command} test.*`, + `${command} /my-dir/*.js`, + ] + } +} + export const HelpTexts: IMap = { alias: [ 'alias [-g] [name="value"] ', @@ -306,13 +328,7 @@ export const HelpTexts: IMap = { " ", "mv myScript.js myOldScript.js", ], - nano: [ - "nano [file ...]", - " ", - "Opens up the specified file(s) in the Text Editor. Only scripts (.script) or text files (.txt) can be ", - "edited using the Text Editor. If the file does not already exist, then a new, empty one ", - "will be created", - ], + nano: TemplatedHelpTexts.textEditor('nano'), ps: ["ps", " ", "Prints all scripts that are running on the current server"], rm: [ @@ -410,13 +426,7 @@ export const HelpTexts: IMap = { " ", "It is not necessary to differentiate between global and non-global aliases when using 'unalias'", ], - vim: [ - "vim [file ...]", - " ", - "Opens up the specified file(s) in the Text Editor in vim mode. Only scripts (.script) or text files (.txt) can be ", - "edited using the Text Editor. If the file does not already exist, then a new, empty one ", - "will be created", - ], + vim: TemplatedHelpTexts.textEditor('vim'), weaken: [ "weaken", "", From 3f032d70062d8a0cf338bcced9a4d9e201aea0ca Mon Sep 17 00:00:00 2001 From: smolgumball Date: Sun, 9 Jan 2022 20:41:48 -0700 Subject: [PATCH 3/3] Update verbiage to match UI (Script Editor vs. Text Editor) --- src/Terminal/HelpText.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Terminal/HelpText.ts b/src/Terminal/HelpText.ts index a8ac51f17..5ac9204d9 100644 --- a/src/Terminal/HelpText.ts +++ b/src/Terminal/HelpText.ts @@ -46,12 +46,12 @@ export const TerminalHelpText: string[] = [ ]; const TemplatedHelpTexts: IMap<(command: string) => string[]> = { - textEditor: (command) => { + scriptEditor: (command) => { return [ `${command} [file ...] | [glob]`, ` `, - `Opens up the specified file(s) in the Text Editor. Only scripts (.js, .ns) or text files (.txt) can be `, - `edited using the Text Editor. If the file does not already exist, then a new, empty one will be created`, + `Opens up the specified file(s) in the Script Editor. Only scripts (.js, .ns, .script) or text files (.txt) `, + `can be edited using the Script Editor. If a file does not exist a new one will be created`, ` `, `If provided a glob as the only argument, ${command} can spider directories and open all matching `, `files at once. ${command} cannot create files using globs, so your scripts must already exist.`, @@ -328,7 +328,7 @@ export const HelpTexts: IMap = { " ", "mv myScript.js myOldScript.js", ], - nano: TemplatedHelpTexts.textEditor('nano'), + nano: TemplatedHelpTexts.scriptEditor('nano'), ps: ["ps", " ", "Prints all scripts that are running on the current server"], rm: [ @@ -426,7 +426,7 @@ export const HelpTexts: IMap = { " ", "It is not necessary to differentiate between global and non-global aliases when using 'unalias'", ], - vim: TemplatedHelpTexts.textEditor('vim'), + vim: TemplatedHelpTexts.scriptEditor('vim'), weaken: [ "weaken", "",