@freemework/hosting
Version:
Hosting library of the Freemework Project.
144 lines (124 loc) • 3.91 kB
text/typescript
import { readFile } from "fs";
import { promisify } from "util";
import { pathToFileURL } from "url";
// @ts-ignore
import type IarnaToml from "@iarna/toml";
import {
FConfiguration,
FConfigurationDictionary,
FConfigurationException,
FException,
FExceptionInvalidOperation,
FUtilUnReadonly,
} from "@freemework/common";
const readFileAsync = promisify(readFile);
let parse: ((toml: string) => IarnaToml.JsonMap) | null = null;
const parseLoadPromise: Promise<void> = import("@iarna/toml")
.then((module) => {
parse = module.parse;
})
.catch((e) => {
const ex = FException.wrapIfNeeded(e);
function raiseModuleNotFound(): never {
throw new FExceptionInvalidOperation(
"Unable to use FConfigurationToml. Please install module '@iarna/toml' before.",
ex
);
}
parse = raiseModuleNotFound;
});
export class FConfigurationToml extends FConfigurationDictionary {
public static async fromFile(
tomlConfigFile: string,
arrayIndexKey: string = FConfiguration.DEFAULT_INDEX_KEY,
arrayIndexesKey: string = FConfiguration.DEFAULT_INDEXES_KEY
): Promise<FConfigurationToml> {
await parseLoadPromise;
const tomlConfigFileURL: URL = pathToFileURL(tomlConfigFile);
const sourceURI: URL = new URL(
`configuration+file+toml://${tomlConfigFileURL.pathname}`
);
const fileContent: Buffer = await readFileAsync(tomlConfigFile);
return new FConfigurationToml(
sourceURI,
fileContent.toString("utf-8"),
arrayIndexKey,
arrayIndexesKey
);
}
public static async factory(
tomlDocument: string,
arrayIndexKey: string = FConfiguration.DEFAULT_INDEX_KEY,
arrayIndexesKey: string = FConfiguration.DEFAULT_INDEXES_KEY
): Promise<FConfigurationToml> {
await parseLoadPromise;
const encodedTomlDocument: string = encodeURIComponent(tomlDocument);
const sourceURI: URL = new URL(
`configuration:toml?data=${encodedTomlDocument}`
);
return new FConfigurationToml(
sourceURI,
tomlDocument,
arrayIndexKey,
arrayIndexesKey
);
}
protected constructor(
sourceURI: URL,
tomlDocument: string,
arrayIndexKey: string,
arrayIndexesKey: string
) {
if (parse === null) {
throw new FExceptionInvalidOperation(
"Unable to use FConfigurationToml. Please install module '@iarna/toml' before."
);
}
const dict: FUtilUnReadonly<FConfigurationDictionary.Data> = {};
const tomlData: IarnaToml.JsonMap = parse(tomlDocument);
function recursiveWalker(sourceData: any, ns: string = ""): void {
if (typeof sourceData === "string") {
dict[ns] = sourceData;
} else if (typeof sourceData === "number") {
dict[ns] = sourceData.toString();
} else if (typeof sourceData === "boolean") {
dict[ns] = sourceData ? "true" : "false";
} else if (Array.isArray(sourceData)) {
const indexes: Array<string> = [];
const indexerKey: string = `${ns}.${arrayIndexesKey}`;
for (let index = 0; index < sourceData.length; ++index) {
const innerSourceData = sourceData[index];
let indexName: string;
if (
typeof innerSourceData === "object" &&
arrayIndexKey in innerSourceData
) {
indexName = innerSourceData[arrayIndexKey];
delete innerSourceData[arrayIndexKey];
} else {
indexName = index.toString();
}
const subKey = `${ns}.${indexName}`;
recursiveWalker(innerSourceData, subKey);
if(indexes.includes(indexName)) {
throw new FConfigurationException(
`Index constraint violation, the index '${indexName}' appears twice.`,
ns,
);
}
indexes.push(indexName);
}
if (!(indexerKey in dict)) {
dict[indexerKey] = indexes.join(" ");
}
} else {
for (const [key, value] of Object.entries(sourceData)) {
const fullKey = ns !== "" ? `${ns}.${key}` : key;
recursiveWalker(value, fullKey);
}
}
}
recursiveWalker(tomlData);
super(sourceURI, dict);
}
}