Skip to content

Commit

Permalink
Read values from main section when parsing INI files (#986)
Browse files Browse the repository at this point in the history
  • Loading branch information
trivikr authored Oct 5, 2023
1 parent 21ee16a commit 60e88af
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 17 deletions.
5 changes: 5 additions & 0 deletions .changeset/serious-fans-hear.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@smithy/shared-ini-file-loader": patch
---

Read values from main settings when parsing INI files
64 changes: 54 additions & 10 deletions packages/shared-ini-file-loader/src/parseIni.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,50 @@ describe(parseIni.name, () => {
const mockProfileName = "mock_profile_name";
const mockProfileData = { key: "value" };

const getMockProfileData = (profileName: string, profileData: Record<string, string>) =>
`[${profileName}]\n${Object.entries(profileData)
.map(([key, value]) => `${key} = ${value}`)
.join("\n")}\n`;
const getMockProfileDataEntries = (profileData: Record<string, string | Record<string, string>>) =>
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<string, string | Record<string, string>>) =>
`[${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,
Expand All @@ -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,
});
Expand All @@ -50,18 +76,36 @@ 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,
});
});

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,
});
});
});
});
18 changes: 11 additions & 7 deletions packages/shared-ini-file-loader/src/parseIni.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}
}
Expand Down

0 comments on commit 60e88af

Please sign in to comment.