@fontoxml/fontoxml-development-tools
Version:
Development tools for Fonto.
156 lines (139 loc) • 3.9 kB
JavaScript
import fs from 'fs';
import path from 'path';
const BASE_CONFIG_FILE_NAME = Symbol('base configuration file name');
const BASE_LOCATION = Symbol('base configuration file location');
const CONFIG_FILE_NAME = Symbol('configuration file name');
const LOCATION = Symbol('configuration file location');
const SERIALIZERS = Symbol('configuration file serializers');
function suffixPath(configLocation, configFileName) {
const suffix = path.sep + configFileName;
return configLocation.substr(-suffix.length) !== suffix
? configLocation + suffix
: configLocation;
}
function readJsonOrReturnObject(jsonPath) {
try {
return JSON.parse(fs.readFileSync(jsonPath));
} catch (_error) {
return {};
}
}
/**
* Manages a unified configuration file for all modules that register their config.
*/
export default class ConfigManager {
/**
* @param {Array} configLocation The main config file location to which changes will be stored.
* @param {Array} baseConfigLocation A base config file to inherit from.
* @param {string} configFileName The name of the config file.
* @param {string} baseConfigFilename The name of the base config file.
*
* @constructor
*/
constructor(
configLocation,
baseConfigLocation,
configFileName,
baseConfigFilename
) {
this[BASE_CONFIG_FILE_NAME] = baseConfigFilename;
this[BASE_LOCATION] = baseConfigLocation;
this[CONFIG_FILE_NAME] = configFileName;
this[LOCATION] = configLocation;
this[SERIALIZERS] = {};
this.read();
}
/**
* @param {string} configName A key of the configuration object that you can share with other modules, or not.
* @param {*} defaultValue If no configuration is found, use this.
* @param {*|function(config): ?*} [serialize]
*
* @return {*}
*/
registerConfig(configName, defaultValue, serialize) {
if (!this[configName]) {
this[configName] = defaultValue || null;
}
Object.assign(this[SERIALIZERS], {
[configName]: serialize,
});
return this[configName];
}
/**
* @return {string} The location of the configuration file.
*/
getLocation() {
return this[LOCATION];
}
/**
* Checks the path & exist status for all possible configuration file locations.
*
* @return {Array}
*/
getStatus() {
return [
suffixPath(this[LOCATION], this[CONFIG_FILE_NAME]),
suffixPath(this[BASE_LOCATION], this[BASE_CONFIG_FILE_NAME]),
].map((configLocation) => ({
path: configLocation,
exists: fs.existsSync(configLocation),
}));
}
/**
* Read all possible configuration files and assign them to one new object.
*/
read() {
Object.assign(
this,
this.getStatus().reduce(
(config, file) =>
file.exists
? Object.assign(
readJsonOrReturnObject(file.path),
config
)
: config,
{}
)
);
}
/**
* Save a stringified version of the configuration object.
*
* @return {Promise} Resolves to the successful configuration location.
*/
save() {
const configLocation = suffixPath(
this[LOCATION],
this[CONFIG_FILE_NAME]
);
return new Promise((resolve, reject) =>
fs.writeFile(configLocation, this.toString(), (error) => {
return error ? reject(error) : resolve(configLocation);
})
);
}
/**
* Prepare a stringified version of the configuration object - one that can be deserialized too.
*
* @return {string}
*/
toString() {
const serializedConfig = Object.keys(this[SERIALIZERS]).reduce(
(config, serializerName) => {
const serializerFn = this[SERIALIZERS][serializerName];
const serialized =
typeof serializerFn === 'function'
? serializerFn(this[serializerName])
: this[serializerName];
return serialized !== null && serialized !== undefined
? Object.assign(config, {
[serializerName]: serialized,
})
: config;
},
{}
);
return JSON.stringify(serializedConfig, null, '\t');
}
}