UNPKG

workwatch

Version:

A Linux terminal program for honest worktime tracking and billing.

171 lines (160 loc) 6.05 kB
/** * This file is part of the WorkWatch, a Linux terminal program for honest * worktime tracking and billing. * * Copyright (C) 2020-2025 by Artur Rutkowski * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. * * WorkWatch is beeing developped and maintained by Artur (locust) Rutkwoski * <locust@mailbox.org> */ /** * This is a module responsible for reading and setting configuration. * It uses a config file in JSON format, environment variables and predefined * values. It is also used to set configuration passed by the CLI arguments. */ const fs = require("node:fs"); const process = require("node:process"); const utils = require("./utils.js"); // Holds a current configuration. const configuration = { dataDir: ( process.env.WORKWATCH_DATA_DIR )? process.env.WORKWATCH_DATA_DIR : `${process.env.HOME}/.workwatch`, measurementFilename: ( process.env.WORKWATCH_MEASUREMENT_FILENAME )? process.env.WORKWATCH_MEASUREMENT_FILENAME : "measurement", logFilename: ( process.env.WORKWATCH_LOG_FILENAME )? process.env.WORKWATCH_LOG_FILENAME : "log" }; // Predefined possible config file locations. const configFileLocations = [ process.env.WORKWATCH_CONFIG_FILE, `${process.env.HOME}/.config/workwatch/config.json`, `${configuration.dataDir}/config.json` ]; // Configuration management object. function Config() { // Getters to obtain the current config data from earlier defined configuration object. // Getters also verify their data but data directory is verified later in verify method. Object.defineProperty(this, "dataDir", { get: function () { return configuration.dataDir; }, enumerable: true, configurable: false }); Object.defineProperty(this, "measurementFilename", { get: function () { if (utils.verifyFilenameValue(configuration.measurementFilename)) { return configuration.measurementFilename; } else { const error = new Error("The measurement filename is wrong."); error.code = "ERR_CONFIG_MEASUREMENT_FILENAME_INCORRECT"; throw error; } }, enumerable: true, configurable: false }); Object.defineProperty(this, "logFilename", { get: function () { if (utils.verifyFilenameValue(configuration.logFilename)) { return configuration.logFilename; } else { const error = new Error("The log filename is wrong."); error.code = "ERR_CONFIG_LOG_FILENAME_INCORRECT"; throw error; } }, enumerable: true, configurable: false }); // Verifies the config data by checking data directory existence and // by invoking getters to run their verification code. Object.defineProperty(this, "verify", { value: () => { return new Promise((resolve, reject) => { fs.realpath(configuration.dataDir, "utf8", (pathError, resolvedPath) => { if (pathError && pathError.code !== "ENOENT") reject(pathError); if (resolvedPath === undefined) { const error = new Error(`The data directory doesn't exist at ${configuration.dataDir}.`); error.code = "ERR_CONFIG_DATA_DIR_NOT_EXISTS"; reject(error); } let {measurementFilename, logFilename} = this; resolve(true); }); }); }, writeable: false, enumerable: false, configurable: false }); // Sets configuration with provided object. Object.defineProperty(this, "set", { value: ({dataDir, measurementFilename, logFilename}) => { return new Promise((resolve, reject) => { const parametersToSet = {dataDir, measurementFilename, logFilename}; Object.keys(parametersToSet).forEach(key => { if (parametersToSet[key] !== configuration[key]) { configuration[key] = parametersToSet[key]; } }); this.verify() .then(verifySuccess => resolve(true)) .catch(newSettingsVerifyError => reject(newSettingsVerifyError)); }); }, writeable: false, enumerable: false, configurable: false }); Object.preventExtensions(this); } // Load a config file from predefined or given location. Config.load = function (path = null) { return new Promise((resolve, reject) => { let locations = (path)? [path] : [...configFileLocations].filter( item => item !== undefined && item !== null ); locations.forEach(location => { fs.realpath(location, "utf8", (error, resolvedPath) => { if (error && error.code !== "ENOENT") reject(error); if ( resolvedPath === undefined && locations.indexOf(location) === locations.length - 1 ) { const loadError = new Error( (locations.length === 1)? `There is no config file at the given path: ${location}` : "No config file found." ); loadError.code = (locations.length === 1)? "ERR_NO_CONFIG_FILE" : "ERR_NO_CONFIG"; reject(loadError); } else if (resolvedPath !== undefined) { fs.readFile(resolvedPath, "utf8", (err, configData) => { if (err) reject(err); const configJSON = JSON.parse(configData); const config = new Config(); config.set(configJSON) .then(successfullyConfigured => resolve(config)) .catch(configurationError => reject(configurationError)); }); } }); }); }); }; module.exports = {Config};