Skip to content

Commit

Permalink
File-handling improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
LorisSigrist committed Oct 12, 2023
1 parent 37cff9a commit 5f0632c
Show file tree
Hide file tree
Showing 9 changed files with 119 additions and 55 deletions.
7 changes: 3 additions & 4 deletions src/core/file-handling/fileHandler.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { readFile } from "fs/promises";
import { basename } from "path";
import { LoadingException } from "./exception.js";
import { ResultMatcher } from "../utils/resultMatcher.js";

export class FileHandler {
/** @type {import("./types.js").FormatHandler[]} */
Expand All @@ -13,19 +14,17 @@ export class FileHandler {

/**
* @param {string} filePath Absolute path to the file that needs to be handled
* @param {string} locale The locale for which the file should be handled
* @returns {Promise<Map<string,string>>} A Map of the Key-Value pairs in the file
*
* @throws {LoadingException} If the file could not be handled
*/
async handle(filePath, locale) {
async handle(filePath) {
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 keyVal = await handler.load(filePath, textContent, locale);
const keyVal = handler.load(filePath, textContent);

return keyVal;
}
Expand Down
26 changes: 12 additions & 14 deletions src/core/file-handling/fileHandler.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,19 @@ describe("FileHandler", () => {
describe("handle", () => {
it("throws LoadingException if no handler is found", async () => {
const handler = new FileHandler([]);
await expect(handler.handle("foo.bar", "en")).rejects.toThrow(
LoadingException,
);
await expect(handler.handle("foo.bar")).rejects.toThrow(LoadingException);
});

it("throws LoadingException if the file cannot be read", async () => {
const handler = new FileHandler([
{
fileExtensions: ["json"],
load: async () => new Map(),
setPath: async () => {},
load: () => new Map(),
setPath: () => "",
},
]);
await expect(handler.handle("nonexistent.json", "en")).rejects.toThrow(
LoadingException,
await expect(handler.handle("nonexistent.json")).rejects.toThrow(
LoadingException
);
});
});
Expand All @@ -35,8 +33,8 @@ describe("FileHandler", () => {
const handler = new FileHandler([
{
fileExtensions: ["json"],
load: async () => new Map(),
setPath: async () => {},
load: () => new Map(),
setPath: () => "",
},
]);
expect(handler.getSupportedFileExtensions()).toEqual(new Set(["json"]));
Expand All @@ -46,17 +44,17 @@ describe("FileHandler", () => {
const handler = new FileHandler([
{
fileExtensions: ["json"],
load: async () => new Map(),
setPath: async () => {},
load: () => new Map(),
setPath: () => "",
},
{
fileExtensions: ["yaml", "yml"],
load: async () => new Map(),
setPath: async () => {},
load: () => new Map(),
setPath: () => "",
},
]);
expect(handler.getSupportedFileExtensions()).toEqual(
new Set(["json", "yaml", "yml"]),
new Set(["json", "yaml", "yml"])
);
});
});
Expand Down
24 changes: 15 additions & 9 deletions src/core/file-handling/formats/json.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,30 @@
import { ResultMatcher } from "../../utils/resultMatcher.js";
import { LoadingException } from "../exception.js";
import { flattenTree } from "../utils.js";

/** @type {import("../types.js").FormatHandler} */
export const JsonHandler = {
fileExtensions: ["json"],
load: async (filePath, content, locale) => {
try {
content = content.trim();
if (content.length === 0) return new Map();
const tree = JSON.parse(content);
return flattenTree(tree);
} catch (e) {
if (!(e instanceof Error)) throw e;
load: (filePath, content) => {
content = content.trim();
if (content.length === 0) return new Map();

/** @param {Error} e */
const raiseLoadingException = e => {
console.warn("Raising loading exception");
throw new LoadingException(
`Could not parse JSON file ${filePath}: ${e.message}`,
{ cause: e },
);
}

return new ResultMatcher(JSON.parse)
.ok(flattenTree)
.catch(SyntaxError, raiseLoadingException)
.run(content);

},
async setPath() {
setPath() {
throw new Error("Not implemented");
},
};
41 changes: 41 additions & 0 deletions src/core/file-handling/formats/json.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { describe, expect, it } from "vitest";
import { JsonHandler } from "./json";
import { LoadingException } from "../exception";

describe("JsonHandler", () => {
it("parses a flat JSON file", () => {
const json = '{ "key1": "value1", "key2": "value2" }';
const result = JsonHandler.load("test.json", json);

const expected = new Map().set("key1", "value1").set("key2", "value2");

expect(result).toEqual(expected);
});

it("parses an empty file as an empty dictionary", () => {
const result = JsonHandler.load("test.json", "");
expect(result).toEqual(new Map());
});

it("parses a nested JSON file", () => {
const json =
'{ "common" : { "save": "Save", "cancel": "Cancel" }, "home": { "title": "Home" } }';

const result = JsonHandler.load("test.json", json);
const expected = new Map()
.set("common.save", "Save")
.set("common.cancel", "Cancel")
.set("home.title", "Home");

expect(result).toEqual(expected);
});

it("throws LoadingException if the JSON is invalid", () => {
const invalidJson = '{ "key1": "value1", "key2": "value2" ';
const parseInvalid = () => {
JsonHandler.load("test.json", invalidJson);
};

expect(parseInvalid).toThrow(LoadingException);
});
});
24 changes: 13 additions & 11 deletions src/core/file-handling/formats/yaml.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
import { load as loadYaml } from "js-yaml";
import { load as loadYaml, YAMLException } from "js-yaml";
import { flattenTree } from "../utils.js";
import { LoadingException } from "../exception.js";
import { ResultMatcher } from "../../utils/resultMatcher.js";

/** @type {import("../types.js").FormatHandler} */
export const YamlHandler = {
fileExtensions: ["yaml", "yml"],
load: async (filePath, content, locale) => {
try {
const tree = loadYaml(content, {
filename: filePath,
});
return flattenTree(tree);
} catch (e) {
if (!(e instanceof Error)) throw e;
load: (filePath, content) => {

/** @param {YAMLException} e */
const raiseLoadingException = (e) => {
throw new LoadingException(
`Could not parse YAML file ${filePath}: ${e.message}`,
{ cause: e },
{ cause: e }
);
}

return new ResultMatcher(loadYaml)
.ok(flattenTree)
.catch(YAMLException, raiseLoadingException)
.run(content, { filename: filePath });
},
async setPath() {
setPath() {
throw new Error("Not implemented");
},
};
8 changes: 5 additions & 3 deletions src/core/file-handling/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ export interface FormatHandler {
load: (
filePath: string,
fileContent: string,
locale: string,
) => Promise<Map<string, string>>;
) =>Map<string, string>;

setPath: (path: string, value: string) => Promise<void>;
/**
* Modifies the file content so that the given key has the given value.
*/
setPath: (oldContent: string, key:string, value:string) => string;
}
14 changes: 8 additions & 6 deletions src/core/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export function t18sCore(pluginConfig) {
const locale = getLocale(filePath);

try {
const keyVal = await fileHandler.handle(filePath, locale);
const keyVal = await fileHandler.handle(filePath);
const { dictionary, invalidKeys } = compileToDictionary(keyVal, locale);
if (invalidKeys) reporter.warnAboutInvalidKeys(filePath, invalidKeys);
localeDictionaries.set(locale, dictionary);
Expand All @@ -57,6 +57,7 @@ export function t18sCore(pluginConfig) {

await regenerateDTS();
triggerHMREvent("t18s:createLocale", locale);
reporter.localeCreated(locale);
}

/**
Expand All @@ -69,7 +70,7 @@ export function t18sCore(pluginConfig) {
const locale = getLocale(filePath);

try {
const keyVal = await fileHandler.handle(filePath, locale);
const keyVal = await fileHandler.handle(filePath);
const { dictionary, invalidKeys } = compileToDictionary(keyVal, locale);

if (invalidKeys) reporter.warnAboutInvalidKeys(filePath, invalidKeys);
Expand Down Expand Up @@ -100,6 +101,7 @@ export function t18sCore(pluginConfig) {

await regenerateDTS();
triggerHMREvent("t18s:removeLocale", locale);
reporter.localeDeleted(locale);
}

/**
Expand Down Expand Up @@ -131,7 +133,7 @@ export function t18sCore(pluginConfig) {
async function loadFile(path) {
const locale = getLocale(path);
try {
const keyVal = await fileHandler.handle(path, locale);
const keyVal = await fileHandler.handle(path);
const { dictionary, invalidKeys } = compileToDictionary(keyVal, locale);
if (invalidKeys) reporter.warnAboutInvalidKeys(path, invalidKeys);
localeDictionaries.set(locale, dictionary);
Expand Down Expand Up @@ -185,7 +187,7 @@ export function t18sCore(pluginConfig) {
});
} else {
logger.error(
`Could not trigger HMR event '${event}' for locale '${locale}' beacuase the viteDevServer is not available. This should never happen.`,
`Could not trigger HMR event '${event}' for locale '${locale}' beacuase the viteDevServer is not available. This should never happen.`
);
}
}
Expand All @@ -199,7 +201,7 @@ export function t18sCore(pluginConfig) {
dtsPath: resolve(resolvedConfig.root, pluginConfig.dts),
translationsDir: resolve(
resolvedConfig.root,
pluginConfig.translationsDir,
pluginConfig.translationsDir
),
verbose: pluginConfig.verbose,
};
Expand All @@ -214,7 +216,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
);
}
},
Expand Down
12 changes: 6 additions & 6 deletions src/core/utils/logger.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import colors from "kleur";
import kleur from "kleur";

/**
* Log Messages to the console in a standard format.
Expand Down Expand Up @@ -26,7 +26,7 @@ export class Logger {
log(msg) {
if (!this.#verbose) return;
msg = this.#formatMessage(msg, "ℹ");
this.#viteConfig.logger.info(colors.cyan(msg));
this.#viteConfig.logger.info(kleur.cyan(msg));
}

/**
Expand All @@ -36,8 +36,8 @@ export class Logger {
*/
warn(msg) {
if (!this.#verbose) return;
msg = this.#formatMessage(msg);
this.#viteConfig.logger.warn(colors.bold().yellow(msg));
msg = this.#formatMessage(msg, "★");
this.#viteConfig.logger.warn(kleur.bold().yellow(msg));
}

/**
Expand All @@ -46,7 +46,7 @@ export class Logger {
*/
error(msg) {
msg = this.#formatMessage(msg, "✗");
this.#viteConfig.logger.error(colors.bold().red(msg));
this.#viteConfig.logger.error(kleur.bold().red(msg));
}

/**
Expand All @@ -55,7 +55,7 @@ export class Logger {
*/
success(msg) {
msg = this.#formatMessage(msg, "✔");
this.#viteConfig.logger.info(colors.green(msg));
this.#viteConfig.logger.info(kleur.bold().green(msg));
}

/**
Expand Down
18 changes: 16 additions & 2 deletions src/core/utils/reporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,29 @@ export class Reporter {
this.#logger.log(`Locale ${kleur.italic(locale)} updated`);
}

/**
* @param {string} locale
*/
localeDeleted(locale) {
this.#logger.warn(`Locale ${kleur.italic(locale)} deleted`);
}

/**
* @param {string} locale
*/
localeCreated(locale) {
this.#logger.success(`Locale ${kleur.italic(locale)} created`);
}

/**
* @param {string} locale
* @param {string[]} filePaths
*/
warnAboutDuplicateLocaleFiles(locale, filePaths) {
this.#logger.error(
`Multiple files for locale ${locale} found:\n ${filePaths.join(
"\n ",
)}`,
"\n "
)}`
);
}
}

0 comments on commit 5f0632c

Please sign in to comment.