Skip to content
This repository has been archived by the owner on Sep 19, 2024. It is now read-only.

Commit

Permalink
fix: ts equivalent
Browse files Browse the repository at this point in the history
  • Loading branch information
Keyrxng committed Nov 1, 2023
1 parent 615512f commit 0a83bf7
Show file tree
Hide file tree
Showing 3 changed files with 23 additions and 119 deletions.
5 changes: 5 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
preset: "ts-jest",
testEnvironment: "node",
moduleFileExtensions: ["ts", "js", "json", "node"],
};
87 changes: 7 additions & 80 deletions src/configs/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,86 +8,13 @@ export const GLOBAL_STRINGS = {
autopayComment: "Automatic payment for this issue is enabled:",
};

const OPEN_BRACE = "{";
const CLOSE_BRACE = "}";
const ESCAPED_OPEN_BRACE = "{{";
const ESCAPED_CLOSE_BRACE = "}}";
// Typescript equivalent of Python's f-string

type InputValues<K extends string = string> = Record<K, string>;
type ParsedFStringNode = { type: "literal"; text: string } | { type: "variable"; name: string };
type Variables = Record<string, string | number>;

const parseFString = (f_string: string): ParsedFStringNode[] => {
const nodes: ParsedFStringNode[] = [];
let currentPosition = 0;

while (currentPosition < f_string.length) {
switch (f_string[currentPosition]) {
case OPEN_BRACE: {
if (f_string.substr(currentPosition, 2) === ESCAPED_OPEN_BRACE) {
const closePosition = f_string.indexOf(ESCAPED_CLOSE_BRACE, currentPosition + 2);

if (closePosition > -1) {
nodes.push({ type: "literal", text: f_string.substring(currentPosition + 1, closePosition + 1) });
currentPosition = closePosition + 2;
} else {
nodes.push({ type: "literal", text: OPEN_BRACE });
currentPosition += 1;
}
} else {
const endBracePosition = f_string.indexOf(CLOSE_BRACE, currentPosition);
if (endBracePosition < 0) throw new Error("Unclosed '{' in f_string.");

nodes.push({
type: "variable",
name: f_string.substring(currentPosition + 1, endBracePosition),
});
currentPosition = endBracePosition + 1;
}
break;
}
case CLOSE_BRACE: {
if (f_string.substr(currentPosition, 2) === ESCAPED_CLOSE_BRACE) {
nodes.push({ type: "literal", text: CLOSE_BRACE });
currentPosition += 2;
continue;
} else {
throw new Error("Single '}' in f_string. Position: " + currentPosition);
}
break;
}
default: {
const nextOpenBracePosition = f_string.indexOf(OPEN_BRACE, currentPosition);
if (nextOpenBracePosition === -1) {
nodes.push({ type: "literal", text: f_string.substring(currentPosition) });
currentPosition = f_string.length;
} else {
nodes.push({ type: "literal", text: f_string.substring(currentPosition, nextOpenBracePosition) });
currentPosition = nextOpenBracePosition;
}
}
}
}
return nodes;
};

const interpolateFString = (f_string: string, values: InputValues) =>
parseFString(f_string).reduce((res, node) => {
if (node.type === "variable") {
if (node.name in values) {
return res + values[node.name];
}
throw new Error(`Missing value for variable: ${node.name}`);
}
return res + node.text;
}, "");

export const formatFString = (f_string: string, inputValues: InputValues) => interpolateFString(f_string, inputValues);

export const checkValidFString = (f_string: string, inputVariables: string[]) => {
try {
const dummyInputs: InputValues = inputVariables.reduce((res, input) => ({ ...res, [input]: "" }), {});
interpolateFString(f_string, dummyInputs);
} catch (e: any) {
throw new Error(`Invalid f-string: ${e.message}`);
}
export const formatFString = <T extends Variables>(template: string, variables: T): string => {
return template.replace(/{(.*?)}/g, (_match, key) => {
if (!(key.trim() in variables)) throw new Error(`Missing value for variable: ${key.trim()}`);
return String(variables[key.trim()]);
});
};
50 changes: 11 additions & 39 deletions src/tests/strings-test.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { checkValidFString, formatFString } from "../configs";
import { formatFString } from "../configs";

Check warning on line 1 in src/tests/strings-test.test.ts

View workflow job for this annotation

GitHub Actions / check-filenames

This file is not in kebab-case or snake_case

const varStrings = {
askUpdate: "Do you have any updates {username}?",
assignNotice: "The `/assign` command is disabled for this repository due to {reason}.",
askPricing: "Using the {{/ask}} command, will cost you {price}.",
askPricing: "Using the /ask command, will cost you {price}.",
manyVars: "{repo} is {status} as {reason}, it's {repoDesc}.",
};

Expand All @@ -15,20 +15,26 @@ describe("f-string utilities", () => {
};
const expectedString1 = "Do you have any updates Keyrxng?";
const result1 = formatFString(varStrings.askUpdate, values1);

console.log(`Expected: ${expectedString1}\n Result: ${result1}`);
expect(result1).toBe(expectedString1);

const values2 = {
reason: "maintenance",
};
const expectedString2 = "The `/assign` command is disabled for this repository due to maintenance.";
const result2 = formatFString(varStrings.assignNotice, values2);

console.log(`Expected: ${expectedString2}\n Result: ${result2}`);
expect(result2).toBe(expectedString2);

const values3 = {
price: "$10",
};
const expectedString3 = "Using the {/ask} command, will cost you $10.";
const expectedString3 = "Using the /ask command, will cost you $10.";
const result3 = formatFString(varStrings.askPricing, values3);

console.log(`Expected: ${expectedString3}\n Result: ${result3}`);
expect(result3).toBe(expectedString3);
});

Expand All @@ -41,47 +47,13 @@ describe("f-string utilities", () => {
};
const expectedString = "Block#Builder is no longer maintained as it's too boring, it's used for building blocks.";
const result = formatFString(varStrings.manyVars, values);

console.log(`Expected: ${expectedString}\n Result: ${result}`);
expect(result).toBe(expectedString);
});

it("should throw error for missing input values using varStrings", () => {
expect(() => formatFString(varStrings.askUpdate, {})).toThrowError("Missing value for variable: username");
});
});

describe("checkValidFString", () => {
it("should not throw error for valid f-string using varStrings", () => {
const variables1 = ["username"];
expect(() => checkValidFString(varStrings.askUpdate, variables1)).not.toThrow();

const variables2 = ["reason"];
expect(() => checkValidFString(varStrings.assignNotice, variables2)).not.toThrow();

const variables3 = ["price"];
expect(() => checkValidFString(varStrings.askPricing, variables3)).not.toThrow();
});

it("should throw error for invalid brackets", () => {
let faultyTemplate = "{unassignComment} {missingVar";
const variables = ["unassign", "keyrxng"];
expect(() => checkValidFString(faultyTemplate, variables)).toThrowError("Invalid f-string: Unclosed '{' in f_string.");

faultyTemplate = "{unassignComment} missingVar}";
expect(() => checkValidFString(faultyTemplate, variables)).toThrowError("Invalid f-string: Missing value for variable: unassignComment");

faultyTemplate = "{unassignComment} missingVar}}";

expect(() => checkValidFString(faultyTemplate, variables)).toThrowError("Invalid f-string: Missing value for variable: unassignComment");

faultyTemplate = "{unassignComment} {{missingVar";

expect(() => checkValidFString(faultyTemplate, variables)).toThrowError("Invalid f-string: Unclosed '{' in f_string.");
});

it("should throw error for missing variables using varStrings", () => {
const template = varStrings.manyVars;
const variables = ["repo", "status"];
expect(() => checkValidFString(template, variables)).toThrowError("Invalid f-string: Missing value for variable: reason");
});
});
});

0 comments on commit 0a83bf7

Please sign in to comment.