mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2026-05-05 23:27:55 +02:00
FILES: Path rework & typesafety (#479)
* Added new types for various file paths, all in the Paths folder. * TypeSafety and other helper functions related to these types * Added basic globbing support with * and ?. Currently only implemented for Script/Text, on nano and download terminal commands * Enforcing the new types throughout the codebase, plus whatever rewrites happened along the way * Server.textFiles is now a map * TextFile no longer uses a fn property, now it is filename * Added a shared ContentFile interface for shared functionality between TextFile and Script. * related to ContentFile change above, the player is now allowed to move a text file to a script file and vice versa. * File paths no longer conditionally start with slashes, and all directory names other than root have ending slashes. The player is still able to provide paths starting with / but this now indicates that the player is specifying an absolute path instead of one relative to root. * Singularized the MessageFilename and LiteratureName enums * Because they now only accept correct types, server.writeToXFile functions now always succeed (the only reasons they could fail before were invalid filepath). * Fix several issues with tab completion, which included pretty much a complete rewrite * Changed the autocomplete display options so there's less chance it clips outside the display area. * Turned CompletedProgramName into an enum. * Got rid of programsMetadata, and programs and DarkWebItems are now initialized immediately instead of relying on initializers called from the engine. * For any executable (program, cct, or script file) pathing can be used directly to execute without using the run command (previously the command had to start with ./ and it wasn't actually using pathing).
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
import { Terminal } from "../../../Terminal";
|
||||
import { removeLeadingSlash, removeTrailingSlash } from "../../DirectoryHelpers";
|
||||
import { ScriptEditorRouteOptions } from "../../../ui/Router";
|
||||
import { Router } from "../../../ui/GameRoot";
|
||||
import { BaseServer } from "../../../Server/BaseServer";
|
||||
import { isScriptFilename } from "../../../Script/isScriptFilename";
|
||||
import { CursorPositions } from "../../../ScriptEditor/CursorPositions";
|
||||
import { Script } from "../../../Script/Script";
|
||||
import { isEmpty } from "lodash";
|
||||
import { ScriptFilename } from "src/Types/strings";
|
||||
import { ScriptFilePath, hasScriptExtension } from "../../../Paths/ScriptFilePath";
|
||||
import { TextFilePath, hasTextExtension } from "../../../Paths/TextFilePath";
|
||||
import { getGlobbedFileMap } from "../../../Paths/GlobbedFiles";
|
||||
|
||||
// 2.3: Globbing implementation was removed from this file. Globbing will be reintroduced as broader functionality and integrated here.
|
||||
|
||||
interface EditorParameters {
|
||||
args: (string | number | boolean)[];
|
||||
@@ -23,126 +23,34 @@ export async function main(ns) {
|
||||
|
||||
}`;
|
||||
|
||||
interface ISimpleScriptGlob {
|
||||
glob: string;
|
||||
preGlob: string;
|
||||
postGlob: string;
|
||||
globError: string;
|
||||
globMatches: string[];
|
||||
globAgainst: Map<ScriptFilename, Script>;
|
||||
}
|
||||
|
||||
function containsSimpleGlob(filename: string): boolean {
|
||||
return filename.includes("*");
|
||||
}
|
||||
|
||||
function detectSimpleScriptGlob({ args, server }: EditorParameters): ISimpleScriptGlob | null {
|
||||
if (args.length == 1 && containsSimpleGlob(`${args[0]}`)) {
|
||||
const filename = `${args[0]}`;
|
||||
const scripts = server.scripts;
|
||||
const parsedGlob = parseSimpleScriptGlob(filename, scripts);
|
||||
return parsedGlob;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function parseSimpleScriptGlob(globString: string, globDatabase: Map<ScriptFilename, Script>): 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,
|
||||
{ args, server }: EditorParameters,
|
||||
scriptEditorRouteOptions?: ScriptEditorRouteOptions,
|
||||
): void {
|
||||
if (args.length < 1) {
|
||||
Terminal.error(`Incorrect usage of ${command} command. Usage: ${command} [scriptname]`);
|
||||
return;
|
||||
}
|
||||
if (args.length < 1) return Terminal.error(`Incorrect usage of ${command} command. Usage: ${command} [scriptname]`);
|
||||
const filesToOpen: Map<ScriptFilePath | TextFilePath, string> = new Map();
|
||||
for (const arg of args) {
|
||||
const pattern = String(arg);
|
||||
|
||||
let filesToLoadOrCreate = args;
|
||||
try {
|
||||
const globSearch = detectSimpleScriptGlob({ args, server });
|
||||
if (globSearch) {
|
||||
if (isEmpty(globSearch.globError) === false) throw new Error(globSearch.globError);
|
||||
filesToLoadOrCreate = globSearch.globMatches;
|
||||
// Glob of existing files
|
||||
if (pattern.includes("*") || pattern.includes("?")) {
|
||||
for (const [path, file] of getGlobbedFileMap(pattern, server, Terminal.currDir)) {
|
||||
filesToOpen.set(path, file.content);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const files = filesToLoadOrCreate.map((arg) => {
|
||||
const filename = `${arg}`;
|
||||
|
||||
if (isScriptFilename(filename)) {
|
||||
const filepath = Terminal.getFilepath(filename);
|
||||
if (!filepath) throw `Invalid filename: ${filename}`;
|
||||
const script = Terminal.getScript(filename);
|
||||
const fileIsNs2 = isNs2(filename);
|
||||
const code = script !== null ? script.code : fileIsNs2 ? newNs2Template : "";
|
||||
|
||||
if (code === newNs2Template) {
|
||||
CursorPositions.saveCursor(filename, {
|
||||
row: 3,
|
||||
column: 5,
|
||||
});
|
||||
}
|
||||
|
||||
return [filepath, code];
|
||||
}
|
||||
|
||||
if (filename.endsWith(".txt")) {
|
||||
const filepath = Terminal.getFilepath(filename);
|
||||
if (!filepath) throw `Invalid filename: ${filename}`;
|
||||
const txt = Terminal.getTextFile(filename);
|
||||
return [filepath, txt === null ? "" : txt.text];
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Invalid file. Only scripts (.script or .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}\``);
|
||||
// Non-glob, files do not need to already exist
|
||||
const path = Terminal.getFilepath(pattern);
|
||||
if (!path) return Terminal.error(`Invalid file path ${arg}`);
|
||||
if (!hasScriptExtension(path) && !hasTextExtension(path)) {
|
||||
return Terminal.error(`${command}: Only scripts or text files can be edited. Invalid file type: ${arg}`);
|
||||
}
|
||||
|
||||
Router.toScriptEditor(Object.fromEntries(files), scriptEditorRouteOptions);
|
||||
} catch (e) {
|
||||
Terminal.error(`${e}`);
|
||||
const file = server.getContentFile(path);
|
||||
const content = file ? file.content : isNs2(path) ? newNs2Template : "";
|
||||
filesToOpen.set(path, content);
|
||||
if (content === newNs2Template) CursorPositions.saveCursor(path, { row: 3, column: 5 });
|
||||
}
|
||||
Router.toScriptEditor(filesToOpen, scriptEditorRouteOptions);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user