UNPKG

workwatch

Version:

A Linux terminal program for honest worktime tracking and billing.

196 lines (191 loc) 8.06 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 file is defining a default command. Default command is a replacement * of index number 1 in a process.argv array. Instead of storing the script name * it is replaced with an underscore "_" marking the default command. * * It is responsible for loading configuration, data files and validate them. */ const {Config} = require("../config.js"); const {TimeMeasurement, Log} = require("../file"); // The default command object. It accepts a third parameter with a next // command to run. It is necesarry for respective file loading. function _(parameters, appData, nextCommand) { this.loadConfiguration = () => { return new Promise((resolve, reject) => { const params = Object.keys(parameters); // The --config-file option has the highest precedence. if (params.includes("--config-file")) { resolve(Config.load(parameters["--config-file"])); } else if (!params.includes("--config-file") && params.length > 0) { // If not --config-file the rest of config options have their priority. let config; Promise.resolve({}) .then(parametersToSet => { // The config module accepts parameters as an object not the // command-line options so that they have to be converted. return Object.assign( parametersToSet, ...params.map(param => { const configParam = param.replace("--", "").split("-").map( (paramPart, paramPartIndex) => ( paramPartIndex > 0 )? paramPart.replace(paramPart[0], paramPart[0].toUpperCase()) : paramPart ).join(""); if (parameters[param] !== null && parameters[param] !== undefined) { return {[configParam]: parameters[param]}; } else { const error = new Error(`The parameter ${param} can't be null or undefined.`); error.code = "ERR_CLI_WRONG_PARAMETER_VALUE"; reject(error); } }) ); }) .then(parametersToSet => { // Check if there are any parameters in order not to set empty // values when error occured. if (Object.keys(parametersToSet).length > 0) { // Set configuration provided by command-line. config = new Config(); return config.set(parametersToSet); } }) .then(configurationSuccess => resolve(config)) .catch(cliConfigError => reject(cliConfigError)); } else { // If there are no command-line config options use predefined config // file locations including WORKWATCH_CONFIG_FILE environment variable. resolve(Config.load()); } }); }; this.loadDataFiles = () => { return new Promise((resolve, reject) => { // Loading data files using configured data directory and file-specific // filename configurations. Promise.resolve(nextCommand) .then(commandName => { // Load files according to the command needs. if (commandName === "start" || commandName === "reset") { return TimeMeasurement.load(`${appData.config.dataDir}/${appData.config.measurementFilename}.json`); } else { return {}; } }) .catch(timeMeasurementLoadError => { if (timeMeasurementLoadError.code === "ERR_FILE_NO_MEASUREMENT") { // When there is no measurement file due to beggining a new measurement // instantiate empty measurement file. return new TimeMeasurement("{}", `${appData.config.dataDir}/${appData.config.measurementFilename}.json`); } else if ( ( timeMeasurementLoadError.code === "ERR_FILE_MEASUREMENT_TOO_LONG_MEASUREMENT" || timeMeasurementLoadError.code === "ERR_FILE_MEASUREMENT_PREVIOUS_MEASUREMENT_NOT_SAVED" ) && nextCommand === "reset" ) { // Load the measurement file without validations because it will be reset. return TimeMeasurement.load(`${appData.config.dataDir}/${appData.config.measurementFilename}.json`, false); } else { // Reject any other error. reject(timeMeasurementLoadError); } }) .then(timeMeasurementObject => { if (timeMeasurementObject instanceof TimeMeasurement) { // Add measurement file to the appData for usage by another command. appData.timeMeasurement = timeMeasurementObject; // Don't load the log file when continuing a measurement. if (nextCommand === "start") { try { // Check if the measurement file is saved which means // the measurement continuation. appData.timeMeasurement.time; // If it is saved don't do anything. resolve(true); } catch (error) { // Error generally means the empty file in this context, so // handle is empty allowing the log file to be loaded. } } } // Proceed to load the worklog file. return Log.load(`${appData.config.dataDir}/${appData.config.logFilename}.json`); }) .catch(logLoadError => { if (logLoadError.code === "ERR_FILE_NO_LOG") { // Instantiate empty log when you have no worklog yet. return new Log("[]", `${appData.config.dataDir}/${appData.config.logFilename}.json`); } else { reject(logLoadError); } }) .then(logObject => { if (logObject instanceof Log) { // Add to appData for the same reason as with measurement. appData.log = logObject; resolve(true); } }); }); }; this.run = () => { return new Promise((resolve, reject) => { // Start the command with loading configuration. this.loadConfiguration() .catch(loadConfigurationError => { if (loadConfigurationError.code === "ERR_NO_CONFIG") { // If no config has been found in predefined locations instantiate // the default configuration defined in the config module. return new Config(); } else { reject(loadConfigurationError); } }) .then(configObject => { if (configObject instanceof Config) { // Add current configuration for other commands' usage. Here, the // configuration content is added, not the config object. appData.config = {}; Object.keys(configObject).forEach( key => appData.config[key] = configObject[key] ); // Verify if predefined values are existing and correct. return configObject.verify(); } }) .then(configVerifySuccess => { if (typeof configVerifySuccess === "boolean" && configVerifySuccess) { return this.loadDataFiles(); } }) .then(dataFilesLoadSuccess => resolve(0)) .catch(configVerifyOrDataLoadError => { reject(configVerifyOrDataLoadError); }); }); }; } module.exports = _;