Merge pull request #2527 from smolgumball/add-simple-globs-nano-vim

feat: add simple glob support to open multiple files with `nano` & `vim` commands
This commit is contained in:
hydroflame 2022-01-15 17:38:39 -05:00 committed by GitHub
commit c791e8264a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 151 additions and 61 deletions

@ -3,49 +3,70 @@ import { IMap } from "../types";
export const TerminalHelpText: string[] = [
"Type 'help name' to learn more about the command ",
" ",
' 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",
" cat [file] Display a .msg, .lit, or .txt file",
" cd [dir] Change to a new directory",
" check [script] [args...] Print a script's logs to Terminal",
" clear Clear all text on the terminal ",
" cls See 'clear' command ",
" connect [hostname] Connects to a remote server",
" cp [src] [dst] Copy a file",
" download [script/text file] Downloads scripts or text files to your computer",
" expr [math expression] Evaluate a mathematical expression",
" free Check the machine's memory (RAM) usage",
" grow Spoof money in a servers bank account, increasing the amount available.",
" hack Hack the current machine",
" help [command] Display this help text, or the help text for a command",
" home Connect to home computer",
" hostname Displays the hostname of the machine",
" kill [script/pid] [args...] Stops the specified script on the current server ",
" killall Stops all running scripts on the current machine",
" ls [dir] [| grep pattern] Displays all files on the machine",
" 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",
" 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",
" scan Prints all immediately-available network connections",
" scan-analyze [d] [-a] Prints info for all servers up to <i>d</i> nodes away",
" scp [file ...] [server] Copies a file to a destination server",
" sudov Shows whether you have root access on this computer",
" 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",
" weaken Reduce the security of the current machine",
" wget [url] [target file] Retrieves code/text from a web server",
" ",
"",
'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/program] Purchase a program through the Dark Web",
"cat [file] Display a .msg, .lit, or .txt file",
"cd [dir] Change to a new directory",
"check [script] [args...] Print a script's logs to Terminal",
"clear Clear all text on the terminal ",
"cls See 'clear' command ",
"connect [hostname] Connects to a remote server",
"cp [src] [dst] Copy a file",
"download [script/text file] Downloads scripts or text files to your computer",
"expr [math expression] Evaluate a mathematical expression",
"free Check the machine's memory (RAM) usage",
"grow Spoof money in a servers bank account, increasing the amount available.",
"hack Hack the current machine",
"help [command] Display this help text, or the help text for a command",
"home Connect to home computer",
"hostname Displays the hostname of the machine",
"kill [script/pid] [args...] Stops the specified script on the current server ",
"killall Stops all running scripts on the current machine",
"ls [dir] [| grep pattern] Displays all files on the machine",
"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 ...] | [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",
"scan Prints all immediately-available network connections",
"scan-analyze [d] [-a] Prints info for all servers up to <i>d</i> nodes away",
"scp [file ...] [server] Copies a file to a destination server",
"sudov Shows whether you have root access on this computer",
"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 ...] | [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[]> = {
scriptEditor: (command) => {
return [
`${command} [file ...] | [glob]`,
` `,
`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.`,
` `,
`Examples:`,
` `,
`${command} test.js`,
`${command} test.js test2.js`,
` `,
`${command} test.*`,
`${command} /my-dir/*.js`,
]
}
}
export const HelpTexts: IMap<string[]> = {
alias: [
'Usage: alias [-g] [name="value"] ',
@ -322,16 +343,8 @@ export const HelpTexts: IMap<string[]> = {
" mv myScript.js myOldScript.js",
" ",
],
nano: [
"Usage: 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.scriptEditor('nano'),
ps: ["Usage: ps", " ", "Prints all scripts that are running on the current server", " "],
rm: [
"Usage: rm [file]",
" ",
@ -433,14 +446,7 @@ export const HelpTexts: IMap<string[]> = {
"It is not necessary to differentiate between global and non-global aliases when using 'unalias'",
" ",
],
vim: [
"Usage: 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.scriptEditor('vim'),
weaken: [
"Usage: weaken",
" ",

@ -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}`);