From 4d7dfcb6b0f029e9e92deec145a31b09e1d75cd7 Mon Sep 17 00:00:00 2001 From: Loris Sigrist Date: Thu, 12 Oct 2023 13:12:17 +0200 Subject: [PATCH] buffer --- src/core/file-handling/fileHandler.js | 29 +++++++-- src/core/index.js | 93 ++++++++++++++++----------- src/core/utils/bufferPromise.js | 29 +++++++++ 3 files changed, 107 insertions(+), 44 deletions(-) create mode 100644 src/core/utils/bufferPromise.js diff --git a/src/core/file-handling/fileHandler.js b/src/core/file-handling/fileHandler.js index aca6ff7..2a2c42c 100644 --- a/src/core/file-handling/fileHandler.js +++ b/src/core/file-handling/fileHandler.js @@ -1,4 +1,4 @@ -import { readFile } from "fs/promises"; +import { readFile, writeFile } from "fs/promises"; import { basename } from "path"; import { LoadingException } from "./exception.js"; import { ResultMatcher } from "../utils/resultMatcher.js"; @@ -17,11 +17,11 @@ export class FileHandler { * @returns {Promise>} A Map of the Key-Value pairs in the file * @throws {LoadingException} If the file could not be handled */ - async handle(filePath) { + async read(filePath) { const handler = this.#getHandler(filePath); if (!handler) throw new LoadingException( - `Could not find handler for ${filePath}. Supported file extensions are ${this.getSupportedFileExtensions()}`, + `Could not find handler for ${filePath}. Supported file extensions are ${this.getSupportedFileExtensions()}` ); const textContent = await this.#readFileContent(filePath); const keyVal = handler.load(filePath, textContent); @@ -29,6 +29,25 @@ export class FileHandler { return keyVal; } + /** + * @param {string} filePath Absolute path to the file that needs to be handled + * @param {string} key + * @param {string} value + * @returns {Promise} A Map of the Key-Value pairs in the file + * @throws {LoadingException} If the file could not be handled + */ + async setPath(filePath, key, value) { + const handler = this.#getHandler(filePath); + if (!handler) + throw new LoadingException( + `Could not find handler for ${filePath}. Supported file extensions are ${this.getSupportedFileExtensions()}` + ); + + const textContent = await this.#readFileContent(filePath); + const newContent = handler.setPath(textContent, key, value); + await writeFile(filePath, newContent, { encoding: "utf-8" }); + } + /** * Resolved which handler should be used for the given file * @param {string} filePath @@ -39,11 +58,11 @@ export class FileHandler { const fileExtension = filename.split(".").at(-1); if (typeof fileExtension !== "string") throw new LoadingException( - "Could not determine file extension for ${filePath}", + "Could not determine file extension for ${filePath}" ); const handler = this.#handlers.find((l) => - l.fileExtensions.includes(fileExtension), + l.fileExtensions.includes(fileExtension) ); return handler ?? null; diff --git a/src/core/index.js b/src/core/index.js index 7c2cd9c..53304a1 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -14,6 +14,8 @@ import { generateDictionaryModule } from "./codegen/dictionary.js"; import { generateMainModuleCode } from "./codegen/main.js"; import { compileToDictionary } from "./compiler/index.js"; import { Reporter } from "./utils/reporter.js"; +import { ResultMatcher } from "./utils/resultMatcher.js"; +import { buffer } from "./utils/bufferPromise.js"; /** * TypeSafe translations for Svelte & SvelteKit. @@ -36,6 +38,7 @@ export function t18sCore(pluginConfig) { /** @type {import("./types.js").LocaleDictionaries} */ const localeDictionaries = new Map(); + /** Handles interactions with translation files */ const fileHandler = new FileHandler([YamlHandler, JsonHandler]); /** @@ -45,15 +48,19 @@ export function t18sCore(pluginConfig) { async function addTranslationFile(filePath) { const locale = getLocale(filePath); - try { - const keyVal = await fileHandler.handle(filePath); - const { dictionary, invalidKeys } = compileToDictionary(keyVal, locale); - if (invalidKeys) reporter.warnAboutInvalidKeys(filePath, invalidKeys); - localeDictionaries.set(locale, dictionary); - } catch (e) { - if (!(e instanceof LoadingException)) throw e; - logger.error(e.message); - } + //Try to read the file & buffer the result + const bufferedFileRead = await buffer(fileHandler.read(filePath)); + + const keyVal = new ResultMatcher(bufferedFileRead) + .catch(LoadingException, (e) => { + logger.error(e.message); + return new Map(); + }) + .run(); + + const { dictionary, invalidKeys } = compileToDictionary(keyVal, locale); + if (invalidKeys) reporter.warnAboutInvalidKeys(filePath, invalidKeys); + localeDictionaries.set(locale, dictionary); await regenerateDTS(); triggerHMREvent("t18s:createLocale", locale); @@ -69,18 +76,23 @@ export function t18sCore(pluginConfig) { async function invalidateTranslationFile(filePath) { const locale = getLocale(filePath); - try { - const keyVal = await fileHandler.handle(filePath); - const { dictionary, invalidKeys } = compileToDictionary(keyVal, locale); + //Try to read the file & buffer the result + const bufferedFileRead = await buffer(fileHandler.read(filePath)); - if (invalidKeys) reporter.warnAboutInvalidKeys(filePath, invalidKeys); - else reporter.localeUpdated(locale); + const keyVal = new ResultMatcher(bufferedFileRead) + .catch(LoadingException, (e) => { + logger.error(e.message); + return new Map(); + }) + .run(); - localeDictionaries.set(locale, dictionary); - } catch (e) { - if (!(e instanceof LoadingException)) throw e; - logger.error(e.message); - } + + const { dictionary, invalidKeys } = compileToDictionary(keyVal, locale); + + if (invalidKeys) reporter.warnAboutInvalidKeys(filePath, invalidKeys); + else reporter.localeUpdated(locale); + + localeDictionaries.set(locale, dictionary); await regenerateDTS(); triggerHMREvent("t18s:invalidateLocale", locale); @@ -119,28 +131,31 @@ export function t18sCore(pluginConfig) { * @param { import("./types.js").ResolvedPluginConfig} config */ async function loadInitialLocales(config) { - /** @type {string[]} */ - let files = []; - try { - files = await readdir(config.translationsDir); - } catch (e) { - logger.error("Could not read translation directory\n" + e); - return; - } + const readdirResult = await buffer(readdir(config.translationsDir)); + + const files = new ResultMatcher(readdirResult) + .catch(LoadingException, (e) => { + logger.error("Could not read translation directory\n" + e); + return []; + }) + .run(); + const paths = files.map((file) => resolve(config.translationsDir, file)); /** @param {string} path */ async function loadFile(path) { const locale = getLocale(path); - try { - const keyVal = await fileHandler.handle(path); - const { dictionary, invalidKeys } = compileToDictionary(keyVal, locale); - if (invalidKeys) reporter.warnAboutInvalidKeys(path, invalidKeys); - localeDictionaries.set(locale, dictionary); - } catch (e) { - if (!(e instanceof LoadingException)) throw e; - logger.error(e.message); - } + const readResult = await buffer(fileHandler.read(path)); + const keyVal = new ResultMatcher(readResult) + .catch(LoadingException, (e) => { + logger.error(e.message); + return new Map(); + }) + .run(); + + const { dictionary, invalidKeys } = compileToDictionary(keyVal, locale); + if (invalidKeys) reporter.warnAboutInvalidKeys(path, invalidKeys); + localeDictionaries.set(locale, dictionary); } //Load all locale-files @@ -187,7 +202,7 @@ export function t18sCore(pluginConfig) { }); } else { logger.error( - `Could not trigger HMR event '${event}' for locale '${locale}' because the viteDevServer is not available. This should never happen.`, + `Could not trigger HMR event '${event}' for locale '${locale}' because the viteDevServer is not available. This should never happen.` ); } } @@ -201,7 +216,7 @@ export function t18sCore(pluginConfig) { dtsPath: resolve(resolvedConfig.root, pluginConfig.dts), translationsDir: resolve( resolvedConfig.root, - pluginConfig.translationsDir, + pluginConfig.translationsDir ), verbose: pluginConfig.verbose, }; @@ -216,7 +231,7 @@ export function t18sCore(pluginConfig) { if (id.startsWith(VIRTUAL_MODULE_PREFIX)) { return id.replace( VIRTUAL_MODULE_PREFIX, - RESOLVED_VIRTUAL_MODULE_PREFIX, + RESOLVED_VIRTUAL_MODULE_PREFIX ); } }, diff --git a/src/core/utils/bufferPromise.js b/src/core/utils/bufferPromise.js new file mode 100644 index 0000000..585d3f5 --- /dev/null +++ b/src/core/utils/bufferPromise.js @@ -0,0 +1,29 @@ +/** + * Await a promise and save the result & errors so that they can be synchronously replayed later. + * + * @template {any} T + * @param {T} promise + * + * @returns {Promise<()=> Awaited>} + */ +export async function buffer(promise) { + /** @type {Awaited} */ + let resolvedWith; + + /** @type {unknown} */ + let rejectedWith; + let rejected = false; + + + try { + resolvedWith = await promise; + } catch (e) { + rejectedWith = e; + rejected = true; + } + + return () => { + if (rejected) throw rejectedWith; + return resolvedWith; + } +} \ No newline at end of file