mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-22 07:33:48 +01:00
TERMINAL: Tweaks and bugfixes to grep (#1431)
This commit is contained in:
parent
21e984bda6
commit
357cc568e9
@ -13,150 +13,123 @@ const DEFAULT: string = "\x1b[0m";
|
|||||||
const GREEN: string = "\x1b[32m";
|
const GREEN: string = "\x1b[32m";
|
||||||
const MAGENTA: string = "\x1b[35m";
|
const MAGENTA: string = "\x1b[35m";
|
||||||
const CYAN: string = "\x1b[36m";
|
const CYAN: string = "\x1b[36m";
|
||||||
|
const YELLOW: string = "\x1b[33m";
|
||||||
const WHITE: string = "\x1b[37m";
|
const WHITE: string = "\x1b[37m";
|
||||||
|
|
||||||
// Options and ValidArgs key names must correlate
|
const ERR = {
|
||||||
class ArgStrings {
|
noArgs: "grep argument error. Usage: grep [OPTION]... PATTERN [FILE]... [-O] [OUTPUT FILE] [-m -B/A/C] [NUM]",
|
||||||
|
noSearchArg:
|
||||||
|
"grep argument error: At least one FILE argument must be passed, or pass -*/--search-all to search all files on server",
|
||||||
|
badArgs: (args: string[]) => "grep argument error: Invalid argument(s): " + args.join(", "),
|
||||||
|
badParameter: (option: string, arg: string) =>
|
||||||
|
`grep argument error: Incorrect ${option} argument "${arg}". Must be a number. OPTIONS with additional parameters (-O, -m, -B/A/C) must be separated from other options`,
|
||||||
|
outFileExists: (path: string) =>
|
||||||
|
`grep file output failed: Invalid output file "${path}". Output file must not already exist. Pass -f/--allow-overwrite to overwrite.`,
|
||||||
|
badOutFile: (path: string) =>
|
||||||
|
`grep file output failed: Invalid output file "${path}". Output file path must be a valid .txt file.`,
|
||||||
|
truncated: () =>
|
||||||
|
`\n${YELLOW}Terminal output truncated to ${Settings.MaxTerminalCapacity} lines (Max terminal capacity)`,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
type ArgStrings = {
|
||||||
short: readonly string[];
|
short: readonly string[];
|
||||||
long: readonly string[];
|
long: readonly string[];
|
||||||
|
};
|
||||||
|
|
||||||
constructor(validArgs: ArgStrings) {
|
type Options = {
|
||||||
this.long = validArgs.long;
|
|
||||||
this.short = validArgs.long;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Options {
|
|
||||||
isRegExpr: boolean;
|
isRegExpr: boolean;
|
||||||
|
|
||||||
isLineNum: boolean;
|
isLineNum: boolean;
|
||||||
isNamed: boolean;
|
isNamed: boolean;
|
||||||
isNotNamed: boolean;
|
isNotNamed: boolean;
|
||||||
isInvertMatch: boolean;
|
isInvertMatch: boolean;
|
||||||
isMaxMatches: boolean;
|
|
||||||
|
|
||||||
isQuiet: boolean;
|
isQuiet: boolean;
|
||||||
isVerbose: boolean;
|
isVerbose: boolean;
|
||||||
|
|
||||||
isToFile: boolean;
|
|
||||||
isOverWrite: boolean;
|
isOverWrite: boolean;
|
||||||
|
|
||||||
isPreContext: boolean;
|
|
||||||
isContext: boolean;
|
|
||||||
isPostContext: boolean;
|
|
||||||
|
|
||||||
isHelp: boolean;
|
isHelp: boolean;
|
||||||
|
|
||||||
isSearchAll: boolean;
|
isSearchAll: boolean;
|
||||||
isPipeIn: boolean;
|
isPipeIn: boolean;
|
||||||
|
|
||||||
// exceptions: these options are not explicitly checked against passed arguments
|
|
||||||
isMultiFile: boolean;
|
isMultiFile: boolean;
|
||||||
hasContextFlag: boolean;
|
hasContextFlag: boolean;
|
||||||
}
|
};
|
||||||
interface ValidArgs {
|
|
||||||
isRegExpr: ArgStrings;
|
|
||||||
|
|
||||||
isLineNum: ArgStrings;
|
type Parameters = {
|
||||||
isNamed: ArgStrings;
|
preContext: string;
|
||||||
isNotNamed: ArgStrings;
|
context: string;
|
||||||
isInvertMatch: ArgStrings;
|
postContext: string;
|
||||||
isMaxMatches: ArgStrings;
|
|
||||||
|
|
||||||
isQuiet: ArgStrings;
|
outfile: string;
|
||||||
isVerbose: ArgStrings;
|
maxMatches: string;
|
||||||
|
};
|
||||||
|
|
||||||
isToFile: ArgStrings;
|
type ValidParams<T extends keyof Parameters> = {
|
||||||
isOverWrite: ArgStrings;
|
[key in T]: ArgStrings;
|
||||||
|
};
|
||||||
|
|
||||||
isPreContext: ArgStrings;
|
const VALID_PARAMS: ValidParams<keyof Parameters> = {
|
||||||
isContext: ArgStrings;
|
preContext: { short: ["-B"], long: ["--before-context"] },
|
||||||
isPostContext: ArgStrings;
|
context: { short: ["-C"], long: ["--context"] },
|
||||||
|
postContext: { short: ["-A"], long: ["--after-context"] },
|
||||||
|
|
||||||
isHelp: ArgStrings;
|
maxMatches: { short: ["-m"], long: ["--max-count"] },
|
||||||
|
outfile: { short: ["-O"], long: ["--output"] },
|
||||||
|
};
|
||||||
|
|
||||||
isSearchAll: ArgStrings;
|
type ValidArgs<T extends keyof Options> = {
|
||||||
isPipeIn: ArgStrings;
|
[key in T]: ArgStrings;
|
||||||
}
|
};
|
||||||
const VALID_ARGS: ValidArgs = {
|
|
||||||
|
const VALID_ARGS: ValidArgs<keyof Options> = {
|
||||||
isRegExpr: { short: ["-R"], long: ["--regexp"] },
|
isRegExpr: { short: ["-R"], long: ["--regexp"] },
|
||||||
|
|
||||||
isLineNum: { short: ["-n"], long: ["--line-number"] },
|
isLineNum: { short: ["-n"], long: ["--line-number"] },
|
||||||
isNamed: { short: ["-H"], long: ["--with-filename"] },
|
isNamed: { short: ["-H"], long: ["--with-filename"] },
|
||||||
isNotNamed: { short: ["-h"], long: ["--no-filename"] },
|
isNotNamed: { short: ["-h"], long: ["--no-filename"] },
|
||||||
isInvertMatch: { short: ["-v"], long: ["--invert-match"] },
|
isInvertMatch: { short: ["-v"], long: ["--invert-match"] },
|
||||||
isMaxMatches: { short: ["-m"], long: ["--max-count"] },
|
|
||||||
|
|
||||||
isQuiet: { short: ["-q"], long: ["--silent", "--quiet"] },
|
isQuiet: { short: ["-q"], long: ["--silent", "--quiet"] },
|
||||||
isVerbose: { short: ["-V"], long: ["--verbose"] },
|
isVerbose: { short: ["-V"], long: ["--verbose"] },
|
||||||
|
|
||||||
isToFile: { short: ["-O"], long: ["--output"] },
|
|
||||||
isOverWrite: { short: ["-f"], long: ["--allow-overwrite"] },
|
isOverWrite: { short: ["-f"], long: ["--allow-overwrite"] },
|
||||||
|
|
||||||
isPreContext: { short: ["-B"], long: ["--before-context"] },
|
|
||||||
isContext: { short: ["-C"], long: ["--context"] },
|
|
||||||
isPostContext: { short: ["-A"], long: ["--after-context"] },
|
|
||||||
|
|
||||||
isSearchAll: { short: ["-*"], long: ["--search-all"] },
|
isSearchAll: { short: ["-*"], long: ["--search-all"] },
|
||||||
isPipeIn: { short: ["-p"], long: ["--pipe-terminal"] },
|
isPipeIn: { short: ["-p"], long: ["--pipe-terminal"] },
|
||||||
|
|
||||||
isHelp: { short: [], long: ["--help"] },
|
isHelp: { short: [], long: ["--help"] },
|
||||||
} as const;
|
|
||||||
//
|
|
||||||
|
|
||||||
interface Errors {
|
isMultiFile: { short: [], long: [] },
|
||||||
noArgs: string;
|
hasContextFlag: { short: [], long: [] },
|
||||||
noSearchArg: string;
|
|
||||||
badSearchFile: (str: string[]) => string;
|
|
||||||
badParameter: (opt: string, arg: string) => string;
|
|
||||||
badOutFile: (str: string) => string;
|
|
||||||
outFileExists: (str: string) => string;
|
|
||||||
truncated: () => string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ERR: Errors = {
|
|
||||||
noArgs: "grep argument error. Usage: grep [OPTION]... PATTERN [FILE]... [-O] [OUTPUT FILE] [-B/A/C] [NUM]",
|
|
||||||
noSearchArg:
|
|
||||||
"grep argument error: At least one FILE argument must be passed, or pass -*/--search-all to search all files on server",
|
|
||||||
badSearchFile: (files: string[]) =>
|
|
||||||
`grep argument error: Invalid filename(s): ${files.join(
|
|
||||||
", ",
|
|
||||||
)}. OPTIONS with additional parameters (-O, -m, -B/A/C) must be separated from other options`,
|
|
||||||
badParameter: (option: string, arg: string) =>
|
|
||||||
`grep argument error: Incorrect ${option} argument "${arg}". Must be a number.`,
|
|
||||||
outFileExists: (path: string) =>
|
|
||||||
`grep file output failed: Invalid output file "${path}". Output file must not already exist. Pass -f/--allow-overwrite to overwrite.`,
|
|
||||||
badOutFile: (path: string) =>
|
|
||||||
`grep file output failed: Invalid output file "${path}". Output file must be a text file.`,
|
|
||||||
truncated: () => `\n${RED}Terminal output truncated to ${Settings.MaxTerminalCapacity} lines (Max terminal capacity)`,
|
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
class Args {
|
class Args {
|
||||||
args: string[];
|
args: string[];
|
||||||
|
options: Options;
|
||||||
|
params: Parameters;
|
||||||
|
|
||||||
constructor(args: (string | number | boolean)[]) {
|
constructor(args: (string | number | boolean)[]) {
|
||||||
this.args = args.map(String);
|
this.args = args.map(String);
|
||||||
|
this.options = this.INIT_OPTIONS;
|
||||||
|
this.params = this.INIT_PARAMS;
|
||||||
}
|
}
|
||||||
|
|
||||||
initOptions: Options = {
|
INIT_OPTIONS: Options = {
|
||||||
isRegExpr: false,
|
isRegExpr: false,
|
||||||
|
|
||||||
isLineNum: false,
|
isLineNum: false,
|
||||||
isNamed: false,
|
isNamed: false,
|
||||||
isNotNamed: false,
|
isNotNamed: false,
|
||||||
isInvertMatch: false,
|
isInvertMatch: false,
|
||||||
isMaxMatches: false,
|
|
||||||
|
|
||||||
isQuiet: false,
|
isQuiet: false,
|
||||||
isVerbose: false,
|
isVerbose: false,
|
||||||
|
|
||||||
isToFile: false,
|
|
||||||
isOverWrite: false,
|
isOverWrite: false,
|
||||||
|
|
||||||
isPreContext: false,
|
|
||||||
isContext: false,
|
|
||||||
isPostContext: false,
|
|
||||||
|
|
||||||
isSearchAll: false,
|
isSearchAll: false,
|
||||||
isPipeIn: false,
|
isPipeIn: false,
|
||||||
|
|
||||||
@ -166,109 +139,124 @@ class Args {
|
|||||||
hasContextFlag: false,
|
hasContextFlag: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
mapArgToOpts(fullArg: string, options: Options): [Options, boolean] {
|
INIT_PARAMS: Parameters = {
|
||||||
let isOption = false;
|
preContext: "",
|
||||||
for (const key of Object.keys(VALID_ARGS)) {
|
context: "",
|
||||||
const stripDash = (arg: string) => arg.replace("-", "");
|
postContext: "",
|
||||||
if (!fullArg.startsWith("-")) break;
|
maxMatches: "",
|
||||||
// check long args
|
outfile: "",
|
||||||
const theseArgs = VALID_ARGS[key as keyof ValidArgs];
|
};
|
||||||
const allArgs = [...theseArgs.long, ...theseArgs.short];
|
|
||||||
if (allArgs.includes(fullArg)) {
|
|
||||||
options[key as keyof Options] = true;
|
|
||||||
isOption = true;
|
|
||||||
}
|
|
||||||
// check multiflag args
|
|
||||||
const multiFlag = stripDash(fullArg);
|
|
||||||
const shortArgs = theseArgs.short.map(stripDash);
|
|
||||||
if (multiFlag.length > 1 && shortArgs.some((arg) => [...multiFlag].includes(arg))) {
|
|
||||||
options[key as keyof Options] = true;
|
|
||||||
isOption = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [options, isOption];
|
|
||||||
}
|
|
||||||
|
|
||||||
splitOptsAndArgs(): [Options, string[], string, string, string] {
|
private spliceParam(validArgs: ArgStrings): string {
|
||||||
let outFile, limit, context;
|
console.log(validArgs);
|
||||||
|
|
||||||
[outFile, this.args] = this.spliceOptParam(VALID_ARGS.isToFile);
|
|
||||||
[limit, this.args] = this.spliceOptParam(VALID_ARGS.isMaxMatches);
|
|
||||||
[context, this.args] = this.spliceOptParam(VALID_ARGS.isPreContext);
|
|
||||||
if (!context) [context, this.args] = this.spliceOptParam(VALID_ARGS.isContext);
|
|
||||||
if (!context) [context, this.args] = this.spliceOptParam(VALID_ARGS.isPostContext);
|
|
||||||
|
|
||||||
const [options, otherArgs] = this.args.reduce(
|
|
||||||
([options, otherArgs]: [Options, string[]], fullArg: string): [Options, string[]] => {
|
|
||||||
let isOption = false;
|
|
||||||
[options, isOption] = this.mapArgToOpts(fullArg, options);
|
|
||||||
return isOption ? [options, otherArgs] : [options, [...otherArgs, fullArg]];
|
|
||||||
},
|
|
||||||
[this.initOptions, []],
|
|
||||||
);
|
|
||||||
const outFileStr = outFile ?? "";
|
|
||||||
const limitNum = limit ?? "";
|
|
||||||
const contextNum = context ?? "";
|
|
||||||
|
|
||||||
return [options, otherArgs, outFileStr, contextNum, limitNum];
|
|
||||||
}
|
|
||||||
|
|
||||||
spliceOptParam(validArgs: ArgStrings): [string, string[]] | [undefined, string[]] {
|
|
||||||
const argIndex = [...validArgs.long, ...validArgs.short].reduce((ret: number, arg: string) => {
|
const argIndex = [...validArgs.long, ...validArgs.short].reduce((ret: number, arg: string) => {
|
||||||
const argIndex = this.args.indexOf(arg);
|
const argIndex = this.args.indexOf(arg);
|
||||||
return argIndex > -1 ? argIndex : ret;
|
return argIndex > -1 ? argIndex : ret;
|
||||||
}, NaN);
|
}, NaN);
|
||||||
|
|
||||||
if (isNaN(argIndex)) return [undefined, this.args];
|
if (isNaN(argIndex)) return "";
|
||||||
|
|
||||||
const nextArg = this.args.splice(argIndex + 1, 1)[0];
|
return this.args.splice(argIndex + 1, 1)[0];
|
||||||
|
}
|
||||||
|
|
||||||
return [nextArg, this.args];
|
private spliceOptionalParams(): Args {
|
||||||
|
for (const [key, validArgs] of Object.entries(VALID_PARAMS)) {
|
||||||
|
this.params[key as keyof Parameters] = this.spliceParam(validArgs);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private reduceToOptionsAndFiles(): string[] {
|
||||||
|
const stripDash = (arg: string) => arg.slice(1);
|
||||||
|
const argKeys = Object.keys(VALID_ARGS).map((k) => k as keyof Options);
|
||||||
|
const paramKeys = Object.keys(VALID_PARAMS).map((k) => k as keyof Parameters);
|
||||||
|
const allValidArgs: string[] = [];
|
||||||
|
|
||||||
|
let validFlagChars = "";
|
||||||
|
for (const key of paramKeys) {
|
||||||
|
const argString = VALID_PARAMS[key];
|
||||||
|
allValidArgs.push(...argString.long, ...argString.short);
|
||||||
|
}
|
||||||
|
for (const key of argKeys) {
|
||||||
|
const argString = VALID_ARGS[key];
|
||||||
|
allValidArgs.push(...argString.long, ...argString.short);
|
||||||
|
validFlagChars += argString.short.map(stripDash).join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileArgs = this.args.reduce((fileArgs: string[], fullArg: string): string[] => {
|
||||||
|
if (!fullArg.startsWith("-")) return [...fileArgs, fullArg];
|
||||||
|
const isLongArg = fullArg.startsWith("--");
|
||||||
|
const isShortArg = fullArg.length === 2;
|
||||||
|
let isBadArg = false;
|
||||||
|
for (const key of argKeys) {
|
||||||
|
const argStrings = VALID_ARGS[key];
|
||||||
|
// check for exact matches
|
||||||
|
if (isLongArg || isShortArg) {
|
||||||
|
isBadArg = !allValidArgs.includes(fullArg);
|
||||||
|
if (!isBadArg && [...argStrings.long, ...argStrings.short].includes(fullArg)) {
|
||||||
|
this.options[key] = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// or check multiflag
|
||||||
|
const flagStr = stripDash(fullArg);
|
||||||
|
const shortArgs = argStrings.short.map(stripDash);
|
||||||
|
isBadArg = [...flagStr].some((char) => !validFlagChars.includes(char));
|
||||||
|
if (!isBadArg && shortArgs.some((arg) => [...flagStr].includes(arg))) {
|
||||||
|
this.options[key] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return !isBadArg ? fileArgs : [...fileArgs, fullArg];
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return fileArgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
splitOptsAndArgs(): [string[], Options, Parameters] {
|
||||||
|
return [this.spliceOptionalParams().reduceToOptionsAndFiles(), this.options, this.params];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LineStrings {
|
type LineStrings = {
|
||||||
rawLine: string;
|
rawLine: string;
|
||||||
prettyLine: string;
|
prettyLine: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
interface ParsedLine {
|
type ParsedLine = {
|
||||||
isPrint: boolean;
|
isPrint: boolean;
|
||||||
isMatched: boolean;
|
isMatched: boolean;
|
||||||
lines: LineStrings;
|
lines: LineStrings;
|
||||||
filename: string;
|
filename: string;
|
||||||
isFileSep: boolean;
|
isFileSep: boolean;
|
||||||
}
|
};
|
||||||
|
|
||||||
class Results {
|
class Results {
|
||||||
lines: ParsedLine[];
|
results: ParsedLine[];
|
||||||
areEdited: boolean;
|
areEdited: boolean;
|
||||||
numMatches: number;
|
numMatches: number;
|
||||||
options: Options;
|
options: Options;
|
||||||
matchCounter: number;
|
params: Parameters;
|
||||||
matchLimit: number;
|
|
||||||
|
|
||||||
constructor(results: ParsedLine[], options: Options, matchLimit: number) {
|
constructor(results: ParsedLine[], options: Options, params: Parameters) {
|
||||||
this.lines = results;
|
this.results = results;
|
||||||
this.options = options;
|
this.options = options;
|
||||||
|
this.params = params;
|
||||||
this.areEdited = results.some((line) => line.isMatched);
|
this.areEdited = results.some((line) => line.isMatched);
|
||||||
this.numMatches = results.reduce((acc, result) => acc + Number(result.isMatched), 0);
|
this.numMatches = results.reduce((acc, result) => acc + Number(result.isMatched), 0);
|
||||||
this.matchLimit = matchLimit;
|
|
||||||
this.matchCounter = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addContext(context: number): Results {
|
addContext(context: number): Results {
|
||||||
const nContext = isNaN(Number(context)) ? 0 : Number(context);
|
const nContext = isNaN(Number(context)) ? 0 : Number(context);
|
||||||
for (const [editLineIndex, line] of this.lines.entries()) {
|
for (const [editLineIndex, line] of this.results.entries()) {
|
||||||
if (!line.isMatched) continue;
|
if (!line.isMatched) continue;
|
||||||
for (let contextLineIndex = 0; contextLineIndex <= nContext; contextLineIndex++) {
|
for (let contextLineIndex = 0; contextLineIndex <= nContext; contextLineIndex++) {
|
||||||
let contextLine;
|
let contextLine;
|
||||||
if (this.options.isPreContext) {
|
if (this.params.preContext) {
|
||||||
contextLine = this.lines[editLineIndex - contextLineIndex];
|
contextLine = this.results[editLineIndex - contextLineIndex];
|
||||||
} else if (this.options.isPostContext) {
|
} else if (this.params.postContext) {
|
||||||
contextLine = this.lines[editLineIndex + contextLineIndex];
|
contextLine = this.results[editLineIndex + contextLineIndex];
|
||||||
} else if (this.options.isContext) {
|
} else if (this.params.context) {
|
||||||
contextLine = this.lines[editLineIndex - Math.floor(nContext / 2) + contextLineIndex];
|
contextLine = this.results[editLineIndex - Math.floor(nContext / 2) + contextLineIndex];
|
||||||
} else {
|
} else {
|
||||||
contextLine = line;
|
contextLine = line;
|
||||||
}
|
}
|
||||||
@ -282,7 +270,7 @@ class Results {
|
|||||||
splitAndFilter(): [string[], string[]] {
|
splitAndFilter(): [string[], string[]] {
|
||||||
const rawResult = [];
|
const rawResult = [];
|
||||||
const prettyResult = [];
|
const prettyResult = [];
|
||||||
for (const lineInfo of this.lines) {
|
for (const lineInfo of this.results) {
|
||||||
if (lineInfo.isPrint === this.options.isInvertMatch) continue;
|
if (lineInfo.isPrint === this.options.isInvertMatch) continue;
|
||||||
rawResult.push(lineInfo.lines.rawLine);
|
rawResult.push(lineInfo.lines.rawLine);
|
||||||
prettyResult.push(lineInfo.lines.prettyLine);
|
prettyResult.push(lineInfo.lines.prettyLine);
|
||||||
@ -291,10 +279,12 @@ class Results {
|
|||||||
}
|
}
|
||||||
|
|
||||||
capMatches(limit: number): Results {
|
capMatches(limit: number): Results {
|
||||||
if (!this.options.isMaxMatches) return this;
|
if (!this.params.maxMatches) return this;
|
||||||
for (const line of this.lines) {
|
|
||||||
if (line.isMatched) this.matchCounter += 1;
|
let matchCounter = 0;
|
||||||
if (this.matchCounter > limit) line.isMatched = false;
|
for (const line of this.results) {
|
||||||
|
if (line.isMatched) matchCounter += 1;
|
||||||
|
if (matchCounter > limit) line.isMatched = false;
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@ -302,7 +292,7 @@ class Results {
|
|||||||
getVerboseInfo(files: ContentFile[], pattern: string | RegExp, options: Options): string {
|
getVerboseInfo(files: ContentFile[], pattern: string | RegExp, options: Options): string {
|
||||||
if (!options.isVerbose) return "";
|
if (!options.isVerbose) return "";
|
||||||
const suffix = (pre: string, num: number) => pre + (num === 1 ? "" : "s");
|
const suffix = (pre: string, num: number) => pre + (num === 1 ? "" : "s");
|
||||||
const totalLines = this.lines.length;
|
const totalLines = this.results.length;
|
||||||
const matchCount = Math.abs((options.isInvertMatch ? totalLines : 0) - this.numMatches);
|
const matchCount = Math.abs((options.isInvertMatch ? totalLines : 0) - this.numMatches);
|
||||||
const inputStr = options.isPipeIn
|
const inputStr = options.isPipeIn
|
||||||
? "piped from terminal "
|
? "piped from terminal "
|
||||||
@ -312,7 +302,9 @@ class Results {
|
|||||||
.join(", ");
|
.join(", ");
|
||||||
|
|
||||||
return [
|
return [
|
||||||
`\n${(options.isMaxMatches ? this.matchLimit : matchCount) + (options.isInvertMatch ? " INVERTED" : "")} `,
|
`\n${
|
||||||
|
(this.params.maxMatches ? this.params.maxMatches : matchCount) + (options.isInvertMatch ? " INVERTED" : "")
|
||||||
|
} `,
|
||||||
suffix("line", matchCount) + " matched ",
|
suffix("line", matchCount) + " matched ",
|
||||||
`against PATTERN "${pattern.toString()}" `,
|
`against PATTERN "${pattern.toString()}" `,
|
||||||
`in ${totalLines} ${suffix("line", totalLines)}, `,
|
`in ${totalLines} ${suffix("line", totalLines)}, `,
|
||||||
@ -385,17 +377,15 @@ function writeToTerminal(
|
|||||||
files: ContentFile[],
|
files: ContentFile[],
|
||||||
pattern: string | RegExp,
|
pattern: string | RegExp,
|
||||||
): void {
|
): void {
|
||||||
const printResult = prettyResult.slice(prettyResult.length - Settings.MaxTerminalCapacity); // limit printing to terminal
|
const printResult = prettyResult.slice(0, Math.min(prettyResult.length, Settings.MaxTerminalCapacity)); // limit printing to terminal
|
||||||
const isTruncated = prettyResult.length !== printResult.length;
|
|
||||||
const verboseInfo = results.getVerboseInfo(files, pattern, options);
|
const verboseInfo = results.getVerboseInfo(files, pattern, options);
|
||||||
const truncateInfo = isTruncated ? ERR.truncated() : "";
|
const truncateInfo = prettyResult.length !== printResult.length ? ERR.truncated() : "";
|
||||||
|
|
||||||
if (results.areEdited) Terminal.print(printResult.join("\n") + truncateInfo);
|
if (results.areEdited) Terminal.print(printResult.join("\n") + truncateInfo);
|
||||||
if (options.isVerbose) Terminal.print(verboseInfo);
|
if (options.isVerbose) Terminal.print(verboseInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkOutFile(outFileStr: string, options: Options, server: BaseServer): ContentFilePath | void {
|
function checkOutFile(outFileStr: string, options: Options, server: BaseServer): ContentFilePath | void {
|
||||||
if (!options.isToFile) return;
|
if (!outFileStr) return;
|
||||||
const outFilePath = Terminal.getFilepath(outFileStr);
|
const outFilePath = Terminal.getFilepath(outFileStr);
|
||||||
if (!outFilePath || !hasTextExtension(outFilePath)) {
|
if (!outFilePath || !hasTextExtension(outFilePath)) {
|
||||||
return Terminal.error(ERR.badOutFile(outFileStr));
|
return Terminal.error(ERR.badOutFile(outFileStr));
|
||||||
@ -411,25 +401,26 @@ function grabTerminal(): string[] {
|
|||||||
export function grep(args: (string | number | boolean)[], server: BaseServer): void {
|
export function grep(args: (string | number | boolean)[], server: BaseServer): void {
|
||||||
if (!args.length) return Terminal.error(ERR.noArgs);
|
if (!args.length) return Terminal.error(ERR.noArgs);
|
||||||
|
|
||||||
const [options, otherArgs, outFile, context, limit] = new Args(args).splitOptsAndArgs();
|
const [otherArgs, options, params] = new Args(args).splitOptsAndArgs();
|
||||||
|
if (options.isHelp) return help(["grep"]);
|
||||||
|
options.hasContextFlag = !!params.context || !!params.preContext || !!params.postContext;
|
||||||
|
|
||||||
|
const nContext = Math.max(Number(params.preContext), Number(params.context), Number(params.postContext));
|
||||||
|
const nLimit = Number(params.maxMatches);
|
||||||
|
|
||||||
|
if (options.hasContextFlag && (!nContext || isNaN(Number(params.context))))
|
||||||
|
return Terminal.error(ERR.badParameter("context", params.context));
|
||||||
|
if (params.maxMatches && (!nLimit || isNaN(Number(params.maxMatches))))
|
||||||
|
return Terminal.error(ERR.badParameter("limit", params.maxMatches));
|
||||||
|
|
||||||
const [files, notFiles] = options.isSearchAll ? getServerFiles(server) : getArgFiles(otherArgs.slice(1));
|
const [files, notFiles] = options.isSearchAll ? getServerFiles(server) : getArgFiles(otherArgs.slice(1));
|
||||||
const outFilePath = checkOutFile(outFile, options, server);
|
|
||||||
|
if (notFiles.length) return Terminal.error(ERR.badArgs(notFiles));
|
||||||
|
if (!options.isPipeIn && !options.isSearchAll && !files.length) return Terminal.error(ERR.noSearchArg);
|
||||||
|
|
||||||
options.isMultiFile = files.length > 1;
|
options.isMultiFile = files.length > 1;
|
||||||
options.hasContextFlag = options.isContext || options.isPreContext || options.isPostContext;
|
const outFilePath = checkOutFile(params.outfile, options, server);
|
||||||
|
if (params.outfile && !outFilePath) return; // associated errors are printed in checkOutFile
|
||||||
// error checking
|
|
||||||
if (options.isToFile && !outFilePath) return; // associated errors are printed in checkOutFile
|
|
||||||
if (options.isHelp) return help(["grep"]);
|
|
||||||
if (notFiles.length) return Terminal.error(ERR.badSearchFile(notFiles));
|
|
||||||
if (!options.isPipeIn && !options.isSearchAll && !files.length) return Terminal.error(ERR.noSearchArg);
|
|
||||||
if (options.hasContextFlag && (context === "" || isNaN(Number(context))))
|
|
||||||
return Terminal.error(ERR.badParameter("context", context));
|
|
||||||
if (options.isMaxMatches && (limit === "" || isNaN(Number(limit))))
|
|
||||||
return Terminal.error(ERR.badParameter("limit", limit));
|
|
||||||
|
|
||||||
const nContext = Number(context);
|
|
||||||
const nLimit = Number(limit);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const pattern = options.isRegExpr ? new RegExp(otherArgs[0], "g") : otherArgs[0];
|
const pattern = options.isRegExpr ? new RegExp(otherArgs[0], "g") : otherArgs[0];
|
||||||
@ -437,12 +428,12 @@ export function grep(args: (string | number | boolean)[], server: BaseServer): v
|
|||||||
const termParser = lineParser.bind(null, options, "Terminal");
|
const termParser = lineParser.bind(null, options, "Terminal");
|
||||||
const fileParser = parseFile.bind(null, lineParser, options);
|
const fileParser = parseFile.bind(null, lineParser, options);
|
||||||
const contentToMatch = options.isPipeIn ? grabTerminal().map(termParser) : files.flatMap(fileParser);
|
const contentToMatch = options.isPipeIn ? grabTerminal().map(termParser) : files.flatMap(fileParser);
|
||||||
const results = new Results(contentToMatch, options, nLimit);
|
const results = new Results(contentToMatch, options, params);
|
||||||
const [rawResult, prettyResult] = results.capMatches(nLimit).addContext(nContext).splitAndFilter();
|
const [rawResult, prettyResult] = results.capMatches(nLimit).addContext(nContext).splitAndFilter();
|
||||||
|
|
||||||
if (options.isPipeIn) files.length = 0;
|
if (options.isPipeIn) files.length = 0;
|
||||||
if (!options.isQuiet) writeToTerminal(prettyResult, options, results, files, pattern);
|
if (!options.isQuiet) writeToTerminal(prettyResult, options, results, files, pattern);
|
||||||
if (options.isToFile && outFilePath) server.writeToContentFile(outFilePath, rawResult.join("\n"));
|
if (params.outfile && outFilePath) server.writeToContentFile(outFilePath, rawResult.join("\n"));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Terminal.error("grep processing error: " + e);
|
Terminal.error("grep processing error: " + e);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user