diff --git a/src/core/file-handling/formats/json.js b/src/core/file-handling/formats/json.js index 7f5ccd2..2d69100 100644 --- a/src/core/file-handling/formats/json.js +++ b/src/core/file-handling/formats/json.js @@ -1,6 +1,6 @@ import { ResultMatcher } from "../../utils/resultMatcher.js"; import { LoadingException } from "../exception.js"; -import { flattenTree } from "../utils.js"; +import { flattenTree, setPathOnTree } from "../utils.js"; /** @type {import("../types.js").FormatHandler} */ export const JsonHandler = { @@ -28,29 +28,7 @@ export const JsonHandler = { const obj = JSON.parse(oldJSON); const path = key.split("."); - - /** - * @param {any} tree - * @param {string[]} path - * @param {string} value - * @returns - */ - function setOnTree(tree, path, value) { - //Split the path into the current level and the rest of the path - const [current, ...rest] = path; - if (!current) - throw new Error("There must be at least one level in the path"); - - if (rest.length === 0) { - tree[current] = value; - return; - } - - if (!tree[current]) tree[current] = {}; - setOnTree(tree[current], rest, value); - } - - setOnTree(obj, path, value); + setPathOnTree(obj, path, value); const newJSON = JSON.stringify(obj); return newJSON; diff --git a/src/core/file-handling/formats/yaml.js b/src/core/file-handling/formats/yaml.js index afe63c7..4fd6c12 100644 --- a/src/core/file-handling/formats/yaml.js +++ b/src/core/file-handling/formats/yaml.js @@ -1,5 +1,5 @@ -import { load as loadYaml, YAMLException } from "js-yaml"; -import { flattenTree } from "../utils.js"; +import { dump, load as loadYaml, YAMLException } from "js-yaml"; +import { flattenTree, setPathOnTree } from "../utils.js"; import { LoadingException } from "../exception.js"; import { ResultMatcher } from "../../utils/resultMatcher.js"; @@ -7,20 +7,45 @@ import { ResultMatcher } from "../../utils/resultMatcher.js"; export const YamlHandler = { fileExtensions: ["yaml", "yml"], load: (filePath, content) => { - /** @param {YAMLException} e */ - const raiseLoadingException = (e) => { - throw new LoadingException( - `Could not parse YAML file ${filePath}: ${e.message}`, - { cause: e }, - ); - }; - - return new ResultMatcher(loadYaml) - .ok(flattenTree) - .catch(YAMLException, raiseLoadingException) - .run(content, { filename: filePath }); + const tree = parseAsTree(content, filePath); + return new ResultMatcher(flattenTree) + .catchAll((e) => raiseLoadingException(e, filePath)) + .run(tree); }, - setPath() { - throw new Error("Not implemented"); + setPath(oldYaml, key, value) { + const tree = parseAsTree(oldYaml); + + const path = key.split("."); + setPathOnTree(tree, path, value); + + const newYaml = dump(tree); + return newYaml; }, }; + +/** + * @param {string} content + * @param {string|undefined} filePath + * @return {unknown} + */ +function parseAsTree(content, filePath = undefined) { + return new ResultMatcher(loadYaml) + .ok((res) => { + if (typeof res !== "object") return {}; + return res; + }) + .catch(YAMLException, (e) => raiseLoadingException(e, filePath)) + .run(content, { filename: filePath }); +} + +/** + * @param {unknown} e + * @param {string | undefined} filePath + */ +const raiseLoadingException = (e, filePath = undefined) => { + const msg = + e instanceof Error + ? `Could not parse YAML file ${filePath ?? ""}: ${e.message}` + : `Could not parse YAML file ${filePath ?? ""}`; + throw new LoadingException(msg, { cause: e }); +}; diff --git a/src/core/file-handling/formats/yaml.test.js b/src/core/file-handling/formats/yaml.test.js new file mode 100644 index 0000000..7762dd1 --- /dev/null +++ b/src/core/file-handling/formats/yaml.test.js @@ -0,0 +1,21 @@ +import { describe, expect, it } from "vitest"; +import { LoadingException } from "../exception"; +import { YamlHandler } from "./yaml"; + +describe("YamlHandler", () => { + it("parses an empty file as an empty dictionary", () => { + const result = YamlHandler.load("test.yaml", ""); + expect(result).toEqual(new Map()); + }); + + it("sets a path on an empty file", () => { + const newYaml = YamlHandler.setPath("", "key1.key2", "value"); + expect(newYaml).toEqual("key1:\n key2: value\n"); + }); + + it("sets a path on an existing file", () => { + const oldYaml = "key1:\n key2: value\n"; + const newYaml = YamlHandler.setPath(oldYaml, "key1.key3", "value"); + expect(newYaml).toEqual("key1:\n key2: value\n key3: value\n"); + }); +}); diff --git a/src/core/file-handling/utils.js b/src/core/file-handling/utils.js index 5b6838e..4a0bfda 100644 --- a/src/core/file-handling/utils.js +++ b/src/core/file-handling/utils.js @@ -38,3 +38,26 @@ export function flattenTree(tree) { throw new Error("Invalid tree"); } } + +/** + * Modifies a tree so that the given path has the given value. + * Will create intermediate levels as needed. + * + * @param {any} tree + * @param {string[]} path + * @param {string} value + * @returns + */ +export function setPathOnTree(tree, path, value) { + //Split the path into the current level and the rest of the path + const [current, ...rest] = path; + if (!current) throw new Error("There must be at least one level in the path"); + + if (rest.length === 0) { + tree[current] = value; + return; + } + + if (!tree[current]) tree[current] = {}; + setPathOnTree(tree[current], rest, value); +}