From 60e88afe9349fa540c54741a1b17c608aa6adbd5 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Thu, 5 Oct 2023 13:51:55 -0700 Subject: [PATCH] Read values from main section when parsing INI files (#986) --- .changeset/serious-fans-hear.md | 5 ++ .../src/parseIni.spec.ts | 64 ++++++++++++++++--- .../shared-ini-file-loader/src/parseIni.ts | 18 ++++-- 3 files changed, 70 insertions(+), 17 deletions(-) create mode 100644 .changeset/serious-fans-hear.md diff --git a/.changeset/serious-fans-hear.md b/.changeset/serious-fans-hear.md new file mode 100644 index 00000000000..b7acad90edf --- /dev/null +++ b/.changeset/serious-fans-hear.md @@ -0,0 +1,5 @@ +--- +"@smithy/shared-ini-file-loader": patch +--- + +Read values from main settings when parsing INI files diff --git a/packages/shared-ini-file-loader/src/parseIni.spec.ts b/packages/shared-ini-file-loader/src/parseIni.spec.ts index f81c5663d8a..3ba0ae3db35 100644 --- a/packages/shared-ini-file-loader/src/parseIni.spec.ts +++ b/packages/shared-ini-file-loader/src/parseIni.spec.ts @@ -12,24 +12,50 @@ describe(parseIni.name, () => { const mockProfileName = "mock_profile_name"; const mockProfileData = { key: "value" }; - const getMockProfileData = (profileName: string, profileData: Record) => - `[${profileName}]\n${Object.entries(profileData) - .map(([key, value]) => `${key} = ${value}`) - .join("\n")}\n`; + const getMockProfileDataEntries = (profileData: Record>) => + Object.entries(profileData).map(([key, value]) => { + let result = `${key}=`; + if (typeof value === "string") { + result += `${value}`; + } else { + result += `\n ${getMockProfileDataEntries(value).join("\n ")}`; + } + return result; + }); + + const getMockProfileContent = (profileName: string, profileData: Record>) => + `[${profileName}]\n${getMockProfileDataEntries(profileData).join("\n")}\n`; + + it("trims data from key/value", () => { + const mockInput = `[${mockProfileName}]\n ${Object.entries(mockProfileData) + .map(([key, value]) => ` ${key} = ${value} `) + .join("\n")}`; + expect(parseIni(mockInput)).toStrictEqual({ + [mockProfileName]: mockProfileData, + }); + }); + + it("returns value with equals sign", () => { + const mockProfileDataWithEqualsSign = { key: "value=value" }; + const mockInput = getMockProfileContent(mockProfileName, mockProfileDataWithEqualsSign); + expect(parseIni(mockInput)).toStrictEqual({ + [mockProfileName]: mockProfileDataWithEqualsSign, + }); + }); it("returns data for one profile", () => { - const mockInput = getMockProfileData(mockProfileName, mockProfileData); + const mockInput = getMockProfileContent(mockProfileName, mockProfileData); expect(parseIni(mockInput)).toStrictEqual({ [mockProfileName]: mockProfileData, }); }); it("returns data for two profiles", () => { - const mockProfile1 = getMockProfileData(mockProfileName, mockProfileData); + const mockProfile1 = getMockProfileContent(mockProfileName, mockProfileData); const mockProfileName2 = "mock_profile_name_2"; const mockProfileData2 = { key2: "value2" }; - const mockProfile2 = getMockProfileData(mockProfileName2, mockProfileData2); + const mockProfile2 = getMockProfileContent(mockProfileName2, mockProfileData2); expect(parseIni(`${mockProfile1}${mockProfile2}`)).toStrictEqual({ [mockProfileName]: mockProfileData, @@ -39,7 +65,7 @@ describe(parseIni.name, () => { it("skip section if data is not present", () => { const mockProfileNameWithoutData = "mock_profile_name_without_data"; - const mockInput = getMockProfileData(mockProfileName, mockProfileData); + const mockInput = getMockProfileContent(mockProfileName, mockProfileData); expect(parseIni(`${mockInput}[${mockProfileNameWithoutData}]`)).toStrictEqual({ [mockProfileName]: mockProfileData, }); @@ -50,7 +76,7 @@ describe(parseIni.name, () => { it("returns data profile name containing multiple words", () => { const mockProfileNameMultiWords = "foo bar baz"; - const mockInput = getMockProfileData(mockProfileNameMultiWords, mockProfileData); + const mockInput = getMockProfileContent(mockProfileNameMultiWords, mockProfileData); expect(parseIni(mockInput)).toStrictEqual({ [mockProfileNameMultiWords]: mockProfileData, }); @@ -58,10 +84,28 @@ describe(parseIni.name, () => { it("returns data for profile containing multiple entries", () => { const mockProfileDataMultipleEntries = { key1: "value1", key2: "value2", key3: "value3" }; - const mockInput = getMockProfileData(mockProfileName, mockProfileDataMultipleEntries); + const mockInput = getMockProfileContent(mockProfileName, mockProfileDataMultipleEntries); expect(parseIni(mockInput)).toStrictEqual({ [mockProfileName]: mockProfileDataMultipleEntries, }); }); + + it("returns data from main section, and not subsection", () => { + const mockMainSettings = { key: "value1" }; + const mockProfileDataWithSubSettings = { ...mockMainSettings, "sub-settings-name": { key: "subValue1" } }; + const mockInput = getMockProfileContent(mockProfileName, mockProfileDataWithSubSettings); + expect(parseIni(mockInput)).toStrictEqual({ + [mockProfileName]: mockMainSettings, + }); + + const mockProfileName2 = "mock_profile_name_2"; + const mockMainSettings2 = { key: "value2" }; + const mockProfileDataWithSubSettings2 = { ...mockMainSettings2, "sub-settings-name": { key: "subValue2" } }; + const mockInput2 = getMockProfileContent(mockProfileName2, mockProfileDataWithSubSettings2); + expect(parseIni(`${mockInput}${mockInput2}`)).toStrictEqual({ + [mockProfileName]: mockMainSettings, + [mockProfileName2]: mockMainSettings2, + }); + }); }); }); diff --git a/packages/shared-ini-file-loader/src/parseIni.ts b/packages/shared-ini-file-loader/src/parseIni.ts index 8fee695934a..933f5bcad31 100644 --- a/packages/shared-ini-file-loader/src/parseIni.ts +++ b/packages/shared-ini-file-loader/src/parseIni.ts @@ -4,29 +4,33 @@ const profileNameBlockList = ["__proto__", "profile __proto__"]; export const parseIni = (iniData: string): ParsedIniData => { const map: ParsedIniData = {}; + let currentSection: string | undefined; + let currentSubSection: string | undefined; for (let line of iniData.split(/\r?\n/)) { line = line.split(/(^|\s)[;#]/)[0].trim(); // remove comments and trim const isSection: boolean = line[0] === "[" && line[line.length - 1] === "]"; if (isSection) { + currentSubSection = undefined; currentSection = line.substring(1, line.length - 1); if (profileNameBlockList.includes(currentSection)) { throw new Error(`Found invalid profile name "${currentSection}"`); } } else if (currentSection) { const indexOfEqualsSign = line.indexOf("="); - const start = 0; - const end: number = line.length - 1; - const isAssignment: boolean = - indexOfEqualsSign !== -1 && indexOfEqualsSign !== start && indexOfEqualsSign !== end; - if (isAssignment) { + if (![0, -1].includes(indexOfEqualsSign)) { const [name, value]: [string, string] = [ line.substring(0, indexOfEqualsSign).trim(), line.substring(indexOfEqualsSign + 1).trim(), ]; - map[currentSection] = map[currentSection] || {}; - map[currentSection][name] = value; + if (value === "") { + currentSubSection = name; + } else if (currentSubSection === undefined) { + // ToDo: populate subsection in future PR, when IniSection is updated to support subsections. + map[currentSection] = map[currentSection] || {}; + map[currentSection][name] = value; + } } } }