ENUMS: Followup for #859 (#868)

This commit is contained in:
Snarling 2023-10-17 07:19:32 -04:00 committed by GitHub
parent 9c41995e59
commit 38f693e2c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 46 additions and 149 deletions

@ -13,10 +13,8 @@ export function determineCrimeSuccess(type: CrimeType): boolean {
} }
export function findCrime(roughName: string): Crime | null { export function findCrime(roughName: string): Crime | null {
const helper = getEnumHelper("CrimeType"); const matchedName = getEnumHelper("CrimeType").getMember(roughName, { fuzzy: true });
if (helper.isMember(roughName)) return Crimes[roughName]; if (matchedName) return Crimes[matchedName];
const fuzzMatch = getEnumHelper("CrimeType").fuzzyGetMember(roughName);
if (fuzzMatch) return Crimes[fuzzMatch];
// This can probably all be removed // This can probably all be removed
roughName = roughName.toLowerCase(); roughName = roughName.toLowerCase();
if (roughName.includes("shoplift")) return Crimes[CrimeType.shoplift]; if (roughName.includes("shoplift")) return Crimes[CrimeType.shoplift];

@ -32,7 +32,7 @@ import { formatMoney, formatRam, formatReputation } from "../ui/formatNumber";
import { currentNodeMults } from "../BitNode/BitNodeMultipliers"; import { currentNodeMults } from "../BitNode/BitNodeMultipliers";
import { Companies } from "../Company/Companies"; import { Companies } from "../Company/Companies";
import { Factions } from "../Faction/Factions"; import { Factions } from "../Faction/Factions";
import { helpers, assertString } from "../Netscript/NetscriptHelpers"; import { helpers } from "../Netscript/NetscriptHelpers";
import { convertTimeMsToTimeElapsedString } from "../utils/StringHelperFunctions"; import { convertTimeMsToTimeElapsedString } from "../utils/StringHelperFunctions";
import { getServerOnNetwork } from "../Server/ServerHelpers"; import { getServerOnNetwork } from "../Server/ServerHelpers";
import { Terminal } from "../Terminal"; import { Terminal } from "../Terminal";
@ -738,22 +738,7 @@ export function NetscriptSingularity(): InternalAPI<ISingularity> {
applyToCompany: (ctx) => (_companyName, _field) => { applyToCompany: (ctx) => (_companyName, _field) => {
helpers.checkSingularityAccess(ctx); helpers.checkSingularityAccess(ctx);
const companyName = getEnumHelper("CompanyName").nsGetMember(ctx, _companyName); const companyName = getEnumHelper("CompanyName").nsGetMember(ctx, _companyName);
assertString(ctx, "field", _field); const field = getEnumHelper("JobField").nsGetMember(ctx, _field, "field", { fuzzy: true });
// capitalize each word, except for "part-time"
function capitalizeJobField(field: string) {
return field
.toLowerCase()
.split(" ")
.map((s) => {
if (s.length == 0 || s == "part-time") return s;
if (s.length == 2) return s.toUpperCase(); // Probably an acronym
return s[0].toUpperCase() + s.slice(1);
})
.join(" ");
}
const field = getEnumHelper("JobField").nsGetMember(ctx, capitalizeJobField(_field as string), "field");
Player.location = companyNameAsLocationName(companyName); Player.location = companyNameAsLocationName(companyName);
let res; let res;

@ -300,7 +300,7 @@ export class Sleeve extends Person implements SleevePerson {
}; };
if (workTypeConversion[_workType]) _workType = workTypeConversion[_workType]; if (workTypeConversion[_workType]) _workType = workTypeConversion[_workType];
const faction = Factions[factionName]; const faction = Factions[factionName];
const workType = getEnumHelper("FactionWorkType").fuzzyGetMember(_workType); const workType = getEnumHelper("FactionWorkType").getMember(_workType, { fuzzy: true });
if (!workType) return false; if (!workType) return false;
const factionInfo = faction.getInfo(); const factionInfo = faction.getInfo();

@ -67,8 +67,10 @@ export class SleeveFactionWork extends SleeveWorkClass {
/** Initializes a FactionWork object from a JSON save state. */ /** Initializes a FactionWork object from a JSON save state. */
static fromJSON(value: IReviverValue): SleeveFactionWork { static fromJSON(value: IReviverValue): SleeveFactionWork {
const factionWork = Generic_fromJSON(SleeveFactionWork, value.data); const factionWork = Generic_fromJSON(SleeveFactionWork, value.data);
factionWork.factionWorkType = getEnumHelper("FactionWorkType").fuzzyGetMember(factionWork.factionWorkType, true); factionWork.factionWorkType = getEnumHelper("FactionWorkType").getMember(factionWork.factionWorkType, {
factionWork.factionName = getEnumHelper("FactionName").fuzzyGetMember(factionWork.factionName, true); alwaysMatch: true,
});
factionWork.factionName = getEnumHelper("FactionName").getMember(factionWork.factionName, { alwaysMatch: true });
return factionWork; return factionWork;
} }
} }

@ -271,7 +271,7 @@ function getABC(sleeve: Sleeve): [string, string, string] {
return ["Workout at Gym", gymNames[work.classType as GymType], work.location]; return ["Workout at Gym", gymNames[work.classType as GymType], work.location];
} }
case SleeveWorkType.CRIME: case SleeveWorkType.CRIME:
return ["Commit Crime", getEnumHelper("CrimeType").fuzzyGetMember(work.crimeType, true), "------"]; return ["Commit Crime", getEnumHelper("CrimeType").getMember(work.crimeType, { alwaysMatch: true }), "------"];
case SleeveWorkType.SUPPORT: case SleeveWorkType.SUPPORT:
return ["Perform Bladeburner Actions", "Support main sleeve", "------"]; return ["Perform Bladeburner Actions", "Support main sleeve", "------"];
case SleeveWorkType.INFILTRATE: case SleeveWorkType.INFILTRATE:

@ -6873,9 +6873,9 @@ declare enum JobField {
security = "Security", security = "Security",
agent = "Agent", agent = "Agent",
employee = "Employee", employee = "Employee",
partTimeEmployee = "part-time Employee", partTimeEmployee = "Part-time Employee",
waiter = "Waiter", waiter = "Waiter",
partTimeWaiter = "part-time Waiter", partTimeWaiter = "Part-time Waiter",
} }
// CORP ENUMS - Changed to types // CORP ENUMS - Changed to types

@ -98,7 +98,7 @@ export class CrimeWork extends Work {
/** Initializes a CrimeWork object from a JSON save state. */ /** Initializes a CrimeWork object from a JSON save state. */
static fromJSON(value: IReviverValue): CrimeWork { static fromJSON(value: IReviverValue): CrimeWork {
const crimeWork = Generic_fromJSON(CrimeWork, value.data); const crimeWork = Generic_fromJSON(CrimeWork, value.data);
crimeWork.crimeType = getEnumHelper("CrimeType").fuzzyGetMember(crimeWork.crimeType, true); crimeWork.crimeType = getEnumHelper("CrimeType").getMember(crimeWork.crimeType, { alwaysMatch: true });
return crimeWork; return crimeWork;
} }
} }

@ -75,7 +75,7 @@ export enum JobField {
security = "Security", security = "Security",
agent = "Agent", agent = "Agent",
employee = "Employee", employee = "Employee",
partTimeEmployee = "part-time Employee", partTimeEmployee = "Part-time Employee",
waiter = "Waiter", waiter = "Waiter",
partTimeWaiter = "part-time Waiter", partTimeWaiter = "Part-time Waiter",
} }

@ -91,8 +91,10 @@ export class FactionWork extends Work {
/** Initializes a FactionWork object from a JSON save state. */ /** Initializes a FactionWork object from a JSON save state. */
static fromJSON(value: IReviverValue): FactionWork { static fromJSON(value: IReviverValue): FactionWork {
const factionWork = Generic_fromJSON(FactionWork, value.data); const factionWork = Generic_fromJSON(FactionWork, value.data);
factionWork.factionWorkType = getEnumHelper("FactionWorkType").fuzzyGetMember(factionWork.factionWorkType, true); factionWork.factionWorkType = getEnumHelper("FactionWorkType").getMember(factionWork.factionWorkType, {
factionWork.factionName = getEnumHelper("FactionName").fuzzyGetMember(factionWork.factionName, true); alwaysMatch: true,
});
factionWork.factionName = getEnumHelper("FactionName").getMember(factionWork.factionName, { alwaysMatch: true });
return factionWork; return factionWork;
} }
} }

@ -5,6 +5,13 @@ import * as allEnums from "../Enums";
import { assertString, helpers } from "../Netscript/NetscriptHelpers"; import { assertString, helpers } from "../Netscript/NetscriptHelpers";
import { getRandomInt } from "./helpers/getRandomInt"; import { getRandomInt } from "./helpers/getRandomInt";
interface GetMemberOptions {
/** Whether to use fuzzy matching on the input (case insensitive, ignore spaces and dashes) */
fuzzy?: boolean;
/** Whether to always return an enum member, even if there was no match. Will attempt fuzzy match before returning a default match. */
alwaysMatch?: boolean;
}
class EnumHelper<EnumObj extends object, EnumMember extends Member<EnumObj> & string> { class EnumHelper<EnumObj extends object, EnumMember extends Member<EnumObj> & string> {
name: string; // Name, for including in error text name: string; // Name, for including in error text
defaultArgName: string; // Used as default for for validating ns arg name defaultArgName: string; // Used as default for for validating ns arg name
@ -24,12 +31,19 @@ class EnumHelper<EnumObj extends object, EnumMember extends Member<EnumObj> & st
return (this.valueSet.has as (value: unknown) => boolean)(toValidate); return (this.valueSet.has as (value: unknown) => boolean)(toValidate);
} }
/** Take an unknown input from a player script, either return an enum member or throw */ /** Take an unknown input from a player script, either return an enum member or throw */
nsGetMember(ctx: NetscriptContext, toValidate: unknown, argName = this.defaultArgName): EnumMember { nsGetMember(
if (this.isMember(toValidate)) return toValidate; ctx: NetscriptContext,
// assertString is just called so if the user didn't even pass in a string, they get a different error message toValidate: unknown,
argName = this.defaultArgName,
options?: GetMemberOptions,
): EnumMember {
const match = this.getMember(toValidate, options);
if (match) return match;
// No match found, create error message
assertString(ctx, argName, toValidate); assertString(ctx, argName, toValidate);
// Don't display all possibilities for large enums
let allowableValues = `Allowable values: ${this.valueArray.map((val) => `"${val}"`).join(", ")}`; let allowableValues = `Allowable values: ${this.valueArray.map((val) => `"${val}"`).join(", ")}`;
// Don't display all possibilities for large enums
if (this.valueArray.length > 10) { if (this.valueArray.length > 10) {
console.warn( console.warn(
`Provided value ${toValidate} was not a valid option for enum type ${this.name}.\n${allowableValues}`, `Provided value ${toValidate} was not a valid option for enum type ${this.name}.\n${allowableValues}`,
@ -41,19 +55,16 @@ class EnumHelper<EnumObj extends object, EnumMember extends Member<EnumObj> & st
`Argument ${argName} should be a ${this.name} enum member.\nProvided value: "${toValidate}".\n${allowableValues}`, `Argument ${argName} should be a ${this.name} enum member.\nProvided value: "${toValidate}".\n${allowableValues}`,
); );
} }
/** Provides case insensitivty and ignores spaces and dashes, and can always match the input */ getMember(input: unknown, options: { alwaysMatch: true }): EnumMember;
fuzzyGetMember(input: string): EnumMember | undefined; getMember(input: unknown, options?: GetMemberOptions): EnumMember | undefined;
fuzzyGetMember(input: string, alwaysMatch: true): EnumMember; getMember(input: unknown, options?: GetMemberOptions): EnumMember | undefined {
fuzzyGetMember(input: string, alwaysMatch = false) { if (this.isMember(input)) return input;
const matchedValue = this.fuzzMap.get(input.toLowerCase().replace(/[ -]+/g, "")); if (typeof input !== "string") return options?.alwaysMatch ? this.valueArray[0] : undefined;
if (matchedValue) { if (options?.fuzzy || options?.alwaysMatch) {
return matchedValue; const fuzzMatch = this.fuzzMap.get(input.toLowerCase().replace(/[ -]+/g, ""));
if (fuzzMatch) return fuzzMatch;
} }
return alwaysMatch ? this.valueArray[0] : undefined; return undefined;
}
/** Provide a case sensitive match, or undefined if */
getMember(input: unknown): EnumMember | undefined {
return this.isMember(input) ? input : undefined;
} }
// Get a random enum member // Get a random enum member
random() { random() {

@ -1,101 +0,0 @@
import { Player, setPlayer } from "../../../src/Player";
import { getCompaniesMetadata } from "../../../src/Company/data/CompaniesMetadata";
import { getCompanyPositionMetadata } from "../../../src/Company/data/CompanyPositionsMetadata";
import { PlayerObject } from "../../../src/PersonObjects/Player/PlayerObject";
import { NetscriptSingularity } from "../../../src/NetscriptFunctions/Singularity";
import { CompanyName, JobField, JobName } from "@enums";
import { WorkerScript } from "../../../src/Netscript/WorkerScript";
import { Script } from "../../../src/Script/Script";
import { RunningScript } from "../../../src/Script/RunningScript";
import { ScriptFilePath } from "../../../src/Paths/ScriptFilePath";
import { GetServer } from "../../../src/Server/AllServers";
describe("Singularity", () => {
let ctx;
let singularity;
let positionMetadata;
let companyMetadata;
beforeAll(() => {
singularity = NetscriptSingularity();
positionMetadata = getCompanyPositionMetadata();
companyMetadata = getCompaniesMetadata();
});
beforeEach(() => {
setPlayer(new PlayerObject());
Player.init();
Player.sourceFiles.set(4, 3);
GetServer("home").writeToScriptFile("function.js", "");
let script = new Script("function.js", "", "home");
let runningScript = new RunningScript(script, 1, []);
let workerScript = new WorkerScript(runningScript, 1);
ctx = { workerScript: workerScript, function: "singularityTest", functionPath: "test.singularityTest" };
});
afterEach(() => {
Object.values(CompanyName).forEach((k) => {
companyMetadata[k].playerReputation = 0;
});
});
describe("getCompanyPositionInfo", () => {
it("returns an enum for field", () => {
let companyWithPositions = Object.values(CompanyName).find(
(cn) => companyMetadata[cn].companyPositions.length > 0,
);
let company = companyMetadata[companyWithPositions];
let positionName = company.companyPositions[0];
let position = positionMetadata[positionName];
let companyPosition = singularity.getCompanyPositionInfo(ctx)(company.name, positionName);
expect(companyPosition.field).toEqual(position.field);
});
});
describe("applyToCompany", () => {
it("throws an error if input doesn't match an enum", () => {
let anyValidCompany = Object.values(CompanyName)[0];
expect(() => singularity.applyToCompany(ctx)(anyValidCompany, "sockware")).toThrow("should be a JobField");
expect(() => singularity.applyToCompany(ctx)(anyValidCompany, "invalid-job-field")).toThrow(
"should be a JobField",
);
});
it("accepts the JobField specified by getCompanyPositionInfo", () => {
Object.values(CompanyName).forEach((cn) => {
companyMetadata[cn].companyPositions.forEach((pn) => {
let pos = positionMetadata[pn];
expect(() => singularity.applyToCompany(ctx)(cn, pos.field)).not.toThrow();
Player.quitJob(cn);
});
});
});
it("is case-insensitive to string inputs to the field parameter", () => {
Object.values(CompanyName).forEach((cn) => {
companyMetadata[cn].companyPositions.forEach((pn) => {
let pos = positionMetadata[pn];
let field = pos.field;
let upperCase = field.toUpperCase();
expect(() => singularity.applyToCompany(ctx)(cn, upperCase)).not.toThrow();
Player.quitJob(cn);
let lowerCase = field.toLowerCase();
expect(() => singularity.applyToCompany(ctx)(cn, lowerCase)).not.toThrow();
Player.quitJob(cn);
let brokenCasing = field
.split("")
.map((c, i) => (i % 2 == 0 ? c.toUpperCase() : c.toLowerCase()))
.join("");
expect(() => singularity.applyToCompany(ctx)(cn, brokenCasing)).not.toThrow();
Player.quitJob(cn);
});
});
});
});
});