From 304651940616e9d0fe01d0d8ebe405153d8c274b Mon Sep 17 00:00:00 2001 From: silentrald Date: Tue, 21 Jan 2025 01:04:12 +0800 Subject: [PATCH] [feat] added a way to parse env strings --- src/__tests__/unit/env.test.ts | 61 +++++++++++++++++ src/main/helpers/env.helpers.ts | 117 +++++++++++++++++++++++++++++++- 2 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 src/__tests__/unit/env.test.ts diff --git a/src/__tests__/unit/env.test.ts b/src/__tests__/unit/env.test.ts new file mode 100644 index 000000000..fc6c80d21 --- /dev/null +++ b/src/__tests__/unit/env.test.ts @@ -0,0 +1,61 @@ +import { parseEnvString } from "main/helpers/env.helpers"; + +describe("Test parseEnvString", () => { + + it("Empty", () => { + const envVars = parseEnvString(""); + expect(envVars).toEqual({}); + }); + + it("Single test; no quotes", () => { + const envString = "HELLO=World!"; + const envVars = parseEnvString(envString); + expect(envVars).toEqual({ + HELLO: "World!", + }); + }); + + it("Single test; single quotes", () => { + const envString = "SINGLE_QOUTE='Single quote with spaces'"; + const envVars = parseEnvString(envString); + expect(envVars).toEqual({ + SINGLE_QOUTE: "Single quote with spaces", + }); + }); + + it("Single test; double quotes", () => { + const envString = 'DOUBLE_QOUTE="Some random quote."'; + const envVars = parseEnvString(envString); + expect(envVars).toEqual({ + DOUBLE_QOUTE: "Some random quote.", + }); + }); + + it("Single test; empty value", () => { + const envString = "EMPTY="; + const envVars = parseEnvString(envString); + expect(envVars).toEqual({ + EMPTY: "", + }); + }); + + it("Multiple test; combined", () => { + const envString = `HELLO=World! DOUBLE_QUOTE="Two Words" SINGLE_QUOTE='' EMPTY=` + const envVars = parseEnvString(envString); + expect(envVars).toEqual(expect.objectContaining({ + HELLO: "World!", + DOUBLE_QUOTE: "Two Words", + SINGLE_QUOTE: "", + EMPTY: "" + })); + }); + + it("Key with numbers and lower case", () => { + const envString = "H3ll0=world"; + const envVars = parseEnvString(envString); + expect(envVars).toEqual({ + H3ll0: "world", + }); + }); + +}); diff --git a/src/main/helpers/env.helpers.ts b/src/main/helpers/env.helpers.ts index 8eaffea71..677fa2eb4 100644 --- a/src/main/helpers/env.helpers.ts +++ b/src/main/helpers/env.helpers.ts @@ -1,3 +1,4 @@ +import { CustomError } from "shared/models/exceptions/custom-error.class"; import { ProviderPlatform } from "shared/models/provider-platform.enum"; export function execOnOs(executions: { [key in ProviderPlatform]?: () => T }, noError = false): T { @@ -10,4 +11,118 @@ export function execOnOs(executions: { [key in ProviderPlatform]?: () => T }, } return undefined; -} \ No newline at end of file +} + +enum EnvParserState { + NAME_START, + NAME, + VALUE_START, + VALUE, + QUOTE_VALUE, + DQUOTE_VALUE, + SPACE, + ERROR, +}; + +const isAlphaCharacter = (c: string) => + (c >= "a" && c <= "z") || (c >= "A" && c <= "Z"); +const isNumber = (c: string) => c >= "0" && c <= "9"; + +export function parseEnvString(envString: string): Record { + const envVars: Record = {}; + + let state: EnvParserState = EnvParserState.NAME_START; + let index = 0; + let newName = ""; + for (let pos = 0; pos < envString.length; ++pos) { + const c = envString[pos]; + + switch (state) { + case EnvParserState.NAME_START: + if (isAlphaCharacter(c) || c === "_") { + state = EnvParserState.NAME; + index = pos; + } else if (c !== " ") { + state = EnvParserState.ERROR; + } + break; + + case EnvParserState.NAME: + if (c === "=") { + state = EnvParserState.VALUE_START; + newName = envString.substring(index, pos); + index = pos + 1; + } else if (!isAlphaCharacter(c) && !isNumber(c) && c !== "_") { + state = EnvParserState.ERROR; + } + break; + + case EnvParserState.VALUE_START: + if (c === "'") { + ++index; + state = EnvParserState.QUOTE_VALUE; + } else if (c === '"') { + ++index; + state = EnvParserState.DQUOTE_VALUE; + } else if (c === " ") { + state = EnvParserState.NAME_START; + envVars[newName] = ""; + } else { + state = EnvParserState.VALUE; + } + break; + + case EnvParserState.VALUE: + if (c === " ") { + state = EnvParserState.NAME_START; + envVars[newName] = envString.substring(index, pos); + } + break; + + case EnvParserState.QUOTE_VALUE: + if (c === "'") { + state = EnvParserState.SPACE; + envVars[newName] = envString.substring(index, pos); + } + break; + + case EnvParserState.DQUOTE_VALUE: + if (c === '"') { + state = EnvParserState.SPACE; + envVars[newName] = envString.substring(index, pos); + } + break; + + case EnvParserState.SPACE: + if (c === " ") { + state = EnvParserState.NAME_START; + } else { + state = EnvParserState.ERROR; + } + break; + + default: + } + + if (state === EnvParserState.ERROR) { + throw new CustomError( + `parseEnvString failed: invalid character at position ${pos}`, + "env.parse" + ); + } + } + + if (state === EnvParserState.VALUE_START || state === EnvParserState.VALUE) { + envVars[newName] = envString.substring(index); + return envVars; + } + + if (state === EnvParserState.NAME_START || state === EnvParserState.SPACE) { + return envVars; + } + + throw new CustomError( + "parseEnvString failed: invalid ending state", + "env.parse" + ); +}