2022-01-11 04:40:01 +01:00
|
|
|
import { Theme } from "@mui/material/styles";
|
|
|
|
import createStyles from "@mui/styles/createStyles";
|
|
|
|
import makeStyles from "@mui/styles/makeStyles";
|
|
|
|
import { toString } from "lodash";
|
2021-10-27 02:26:05 +02:00
|
|
|
import React from "react";
|
2021-09-16 01:50:44 +02:00
|
|
|
import { IPlayer } from "../../PersonObjects/IPlayer";
|
|
|
|
import { BaseServer } from "../../Server/BaseServer";
|
2022-03-06 05:05:55 +01:00
|
|
|
import { evaluateDirectoryPath, getFirstParentDirectory, isValidDirectoryPath } from "../DirectoryHelpers";
|
2022-01-11 04:40:01 +01:00
|
|
|
import { IRouter } from "../../ui/Router";
|
|
|
|
import { ITerminal } from "../ITerminal";
|
2022-07-20 03:53:43 +02:00
|
|
|
import libarg from "arg";
|
2022-04-14 16:23:45 +02:00
|
|
|
import { showLiterature } from "../../Literature/LiteratureHelpers";
|
|
|
|
import { MessageFilenames, showMessage } from "../../Message/MessageHelpers";
|
2022-07-20 03:53:43 +02:00
|
|
|
import { ScriptArg } from "../../Netscript/ScriptArg";
|
2021-09-16 01:50:44 +02:00
|
|
|
|
2022-07-20 03:53:43 +02:00
|
|
|
export function ls(terminal: ITerminal, router: IRouter, player: IPlayer, server: BaseServer, args: ScriptArg[]): void {
|
|
|
|
interface LSFlags {
|
|
|
|
["-l"]: boolean;
|
|
|
|
["--grep"]: string;
|
|
|
|
}
|
|
|
|
let flags: LSFlags;
|
2022-02-05 16:27:08 +01:00
|
|
|
try {
|
2022-04-07 01:30:08 +02:00
|
|
|
flags = libarg(
|
|
|
|
{
|
|
|
|
"-l": Boolean,
|
|
|
|
"--grep": String,
|
|
|
|
"-g": "--grep",
|
|
|
|
},
|
|
|
|
{ argv: args },
|
|
|
|
);
|
2022-02-05 16:27:08 +01:00
|
|
|
} catch (e) {
|
|
|
|
// catch passing only -g / --grep with no string to use as the search
|
2022-04-07 01:30:08 +02:00
|
|
|
incorrectUsage();
|
2022-02-05 16:27:08 +01:00
|
|
|
return;
|
|
|
|
}
|
2022-04-07 01:30:08 +02:00
|
|
|
const filter = flags["--grep"];
|
2022-02-05 16:27:08 +01:00
|
|
|
|
2021-09-16 01:50:44 +02:00
|
|
|
const numArgs = args.length;
|
|
|
|
function incorrectUsage(): void {
|
2022-03-17 13:17:43 +01:00
|
|
|
terminal.error("Incorrect usage of ls command. Usage: ls [dir] [-l] [-g, --grep pattern]");
|
2021-09-16 01:50:44 +02:00
|
|
|
}
|
|
|
|
|
2022-02-05 17:05:48 +01:00
|
|
|
if (numArgs > 4) {
|
2021-09-16 01:50:44 +02:00
|
|
|
return incorrectUsage();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Directory path
|
|
|
|
let prefix = terminal.cwd();
|
|
|
|
if (!prefix.endsWith("/")) {
|
|
|
|
prefix += "/";
|
|
|
|
}
|
|
|
|
|
2022-02-05 17:05:48 +01:00
|
|
|
// If first arg doesn't contain a - it must be the file/folder
|
2022-04-07 01:30:08 +02:00
|
|
|
const dir = args[0] && typeof args[0] == "string" && !args[0].startsWith("-") ? args[0] : "";
|
2022-02-05 17:05:48 +01:00
|
|
|
const newPath = evaluateDirectoryPath(dir + "", terminal.cwd());
|
|
|
|
prefix = newPath || "";
|
|
|
|
if (!prefix.endsWith("/")) {
|
|
|
|
prefix += "/";
|
2021-09-16 01:50:44 +02:00
|
|
|
}
|
2022-02-05 17:05:48 +01:00
|
|
|
if (!isValidDirectoryPath(prefix)) {
|
|
|
|
return incorrectUsage();
|
2021-09-16 01:50:44 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Root directory, which is the same as no 'prefix' at all
|
|
|
|
if (prefix === "/") {
|
|
|
|
prefix = "";
|
|
|
|
}
|
|
|
|
|
|
|
|
// Display all programs and scripts
|
|
|
|
const allPrograms: string[] = [];
|
|
|
|
const allScripts: string[] = [];
|
|
|
|
const allTextFiles: string[] = [];
|
|
|
|
const allContracts: string[] = [];
|
|
|
|
const allMessages: string[] = [];
|
|
|
|
const folders: string[] = [];
|
|
|
|
|
|
|
|
function handleFn(fn: string, dest: string[]): void {
|
|
|
|
let parsedFn = fn;
|
|
|
|
if (prefix) {
|
|
|
|
if (!fn.startsWith(prefix)) {
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
parsedFn = fn.slice(prefix.length, fn.length);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (filter && !parsedFn.includes(filter)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the fn includes a forward slash, it must be in a subdirectory.
|
|
|
|
// Therefore, we only list the "first" directory in its path
|
|
|
|
if (parsedFn.includes("/")) {
|
|
|
|
const firstParentDir = getFirstParentDirectory(parsedFn);
|
|
|
|
if (filter && !firstParentDir.includes(filter)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!folders.includes(firstParentDir)) {
|
|
|
|
folders.push(firstParentDir);
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
dest.push(parsedFn);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get all of the programs and scripts on the machine into one temporary array
|
|
|
|
const s = player.getCurrentServer();
|
|
|
|
for (const program of s.programs) handleFn(program, allPrograms);
|
|
|
|
for (const script of s.scripts) handleFn(script.filename, allScripts);
|
|
|
|
for (const txt of s.textFiles) handleFn(txt.fn, allTextFiles);
|
|
|
|
for (const contract of s.contracts) handleFn(contract.fn, allContracts);
|
2021-10-14 08:07:05 +02:00
|
|
|
for (const msgOrLit of s.messages) handleFn(msgOrLit, allMessages);
|
2021-09-16 01:50:44 +02:00
|
|
|
|
|
|
|
// Sort the files/folders alphabetically then print each
|
|
|
|
allPrograms.sort();
|
|
|
|
allScripts.sort();
|
|
|
|
allTextFiles.sort();
|
|
|
|
allContracts.sort();
|
|
|
|
allMessages.sort();
|
|
|
|
folders.sort();
|
|
|
|
|
2022-04-14 16:23:45 +02:00
|
|
|
interface ClickableRowProps {
|
2022-01-11 04:40:01 +01:00
|
|
|
row: string;
|
|
|
|
prefix: string;
|
2022-01-11 05:00:52 +01:00
|
|
|
hostname: string;
|
2022-01-11 04:40:01 +01:00
|
|
|
}
|
|
|
|
|
2022-04-14 16:23:45 +02:00
|
|
|
function ClickableScriptRow({ row, prefix, hostname }: ClickableRowProps): React.ReactElement {
|
2022-01-11 04:40:01 +01:00
|
|
|
const classes = makeStyles((theme: Theme) =>
|
|
|
|
createStyles({
|
|
|
|
scriptLinksWrap: {
|
2022-01-16 05:29:12 +01:00
|
|
|
display: "inline-flex",
|
2022-01-11 04:40:01 +01:00
|
|
|
color: theme.palette.warning.main,
|
|
|
|
},
|
|
|
|
scriptLink: {
|
|
|
|
cursor: "pointer",
|
|
|
|
textDecorationLine: "underline",
|
|
|
|
paddingRight: "1.15em",
|
|
|
|
"&:last-child": { padding: 0 },
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
)();
|
|
|
|
|
2022-03-17 17:50:39 +01:00
|
|
|
const rowSplit = row.split("~");
|
|
|
|
let rowSplitArray = rowSplit.map((x) => [x.trim(), x.replace(x.trim(), "")]);
|
2022-03-17 22:25:30 +01:00
|
|
|
rowSplitArray = rowSplitArray.filter((x) => !!x[0]);
|
2022-01-11 04:40:01 +01:00
|
|
|
|
|
|
|
function onScriptLinkClick(filename: string): void {
|
2022-01-11 05:00:52 +01:00
|
|
|
if (player.getCurrentServer().hostname !== hostname) {
|
|
|
|
return terminal.error(`File is not on this server, connect to ${hostname} and try again`);
|
|
|
|
}
|
2022-01-11 04:40:01 +01:00
|
|
|
if (filename.startsWith("/")) filename = filename.slice(1);
|
|
|
|
const filepath = terminal.getFilepath(`${prefix}${filename}`);
|
|
|
|
const code = toString(terminal.getScript(player, filepath)?.code);
|
|
|
|
router.toScriptEditor({ [filepath]: code });
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<span className={classes.scriptLinksWrap}>
|
2022-03-17 17:50:39 +01:00
|
|
|
{rowSplitArray.map((rowItem) => (
|
2022-04-14 16:23:45 +02:00
|
|
|
<span key={"script_" + rowItem[0]}>
|
|
|
|
<span className={classes.scriptLink} onClick={() => onScriptLinkClick(rowItem[0])}>
|
2022-03-17 17:50:39 +01:00
|
|
|
{rowItem[0]}
|
|
|
|
</span>
|
2022-04-14 16:23:45 +02:00
|
|
|
<span>{rowItem[1]}</span>
|
2022-01-11 04:40:01 +01:00
|
|
|
</span>
|
|
|
|
))}
|
|
|
|
</span>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-04-14 16:23:45 +02:00
|
|
|
function ClickableMessageRow({ row, prefix, hostname }: ClickableRowProps): React.ReactElement {
|
|
|
|
const classes = makeStyles((theme: Theme) =>
|
|
|
|
createStyles({
|
|
|
|
linksWrap: {
|
|
|
|
display: "inline-flex",
|
|
|
|
color: theme.palette.primary.main,
|
|
|
|
},
|
|
|
|
link: {
|
|
|
|
cursor: "pointer",
|
|
|
|
textDecorationLine: "underline",
|
|
|
|
paddingRight: "1.15em",
|
|
|
|
"&:last-child": { padding: 0 },
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
)();
|
|
|
|
|
|
|
|
const rowSplit = row.split("~");
|
|
|
|
let rowSplitArray = rowSplit.map((x) => [x.trim(), x.replace(x.trim(), "")]);
|
|
|
|
rowSplitArray = rowSplitArray.filter((x) => !!x[0]);
|
|
|
|
|
|
|
|
function onMessageLinkClick(filename: string): void {
|
|
|
|
if (player.getCurrentServer().hostname !== hostname) {
|
|
|
|
return terminal.error(`File is not on this server, connect to ${hostname} and try again`);
|
|
|
|
}
|
|
|
|
if (filename.startsWith("/")) filename = filename.slice(1);
|
|
|
|
const filepath = terminal.getFilepath(`${prefix}${filename}`);
|
|
|
|
|
|
|
|
if (filepath.endsWith(".lit")) {
|
|
|
|
showLiterature(filepath);
|
|
|
|
} else if (filepath.endsWith(".msg")) {
|
|
|
|
showMessage(filepath as MessageFilenames);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<span className={classes.linksWrap}>
|
|
|
|
{rowSplitArray.map((rowItem) => (
|
|
|
|
<span key={"text_" + rowItem[0]}>
|
|
|
|
<span className={classes.link} onClick={() => onMessageLinkClick(rowItem[0])}>
|
|
|
|
{rowItem[0]}
|
|
|
|
</span>
|
|
|
|
<span>{rowItem[1]}</span>
|
|
|
|
</span>
|
|
|
|
))}
|
|
|
|
</span>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
enum FileType {
|
|
|
|
Folder,
|
|
|
|
Message,
|
|
|
|
TextFile,
|
|
|
|
Program,
|
|
|
|
Contract,
|
|
|
|
Script,
|
|
|
|
}
|
|
|
|
|
|
|
|
interface FileGroup {
|
|
|
|
type: FileType;
|
|
|
|
segments: string[];
|
|
|
|
}
|
|
|
|
|
2022-07-20 03:53:43 +02:00
|
|
|
function postSegments(group: FileGroup, flags: LSFlags): void {
|
2022-04-14 16:23:45 +02:00
|
|
|
const segments = group.segments;
|
|
|
|
const linked = group.type === FileType.Script || group.type === FileType.Message;
|
2021-09-16 01:50:44 +02:00
|
|
|
const maxLength = Math.max(...segments.map((s) => s.length)) + 1;
|
2022-03-17 18:29:18 +01:00
|
|
|
const filesPerRow = flags["-l"] === true ? 1 : Math.ceil(80 / maxLength);
|
2021-09-16 01:50:44 +02:00
|
|
|
for (let i = 0; i < segments.length; i++) {
|
|
|
|
let row = "";
|
|
|
|
for (let col = 0; col < filesPerRow; col++) {
|
|
|
|
if (!(i < segments.length)) break;
|
|
|
|
row += segments[i];
|
|
|
|
row += " ".repeat(maxLength * (col + 1) - row.length);
|
2022-04-07 01:30:08 +02:00
|
|
|
if (linked) {
|
2022-03-17 17:50:39 +01:00
|
|
|
row += "~";
|
|
|
|
}
|
2021-09-16 01:50:44 +02:00
|
|
|
i++;
|
|
|
|
}
|
|
|
|
i--;
|
2022-04-14 16:23:45 +02:00
|
|
|
|
|
|
|
switch (group.type) {
|
|
|
|
case FileType.Folder:
|
|
|
|
terminal.printRaw(<span style={{ color: "cyan" }}>{row}</span>);
|
|
|
|
break;
|
|
|
|
case FileType.Script:
|
|
|
|
terminal.printRaw(<ClickableScriptRow row={row} prefix={prefix} hostname={server.hostname} />);
|
|
|
|
break;
|
|
|
|
case FileType.Message:
|
|
|
|
terminal.printRaw(<ClickableMessageRow row={row} prefix={prefix} hostname={server.hostname} />);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
terminal.print(row);
|
2022-03-17 17:50:39 +01:00
|
|
|
}
|
2021-09-16 01:50:44 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-14 16:23:45 +02:00
|
|
|
const groups: FileGroup[] = [
|
|
|
|
{ type: FileType.Folder, segments: folders },
|
|
|
|
{ type: FileType.Message, segments: allMessages },
|
|
|
|
{ type: FileType.TextFile, segments: allTextFiles },
|
|
|
|
{ type: FileType.Program, segments: allPrograms },
|
|
|
|
{ type: FileType.Contract, segments: allContracts },
|
|
|
|
{ type: FileType.Script, segments: allScripts },
|
2021-09-16 01:50:44 +02:00
|
|
|
].filter((g) => g.segments.length > 0);
|
2022-04-14 16:23:45 +02:00
|
|
|
for (const group of groups) {
|
|
|
|
postSegments(group, flags);
|
2021-09-16 01:50:44 +02:00
|
|
|
}
|
|
|
|
}
|