workwatch
Version:
A Linux terminal program for honest worktime tracking and billing.
195 lines (192 loc) • 8.44 kB
JavaScript
/**
* 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 commands module used in CLI. It defines an object providing
* commands and their options. Each command has also a run function defining
* the commands code.
*
* This file defines code for reset and log commands because they are quite
* small and simple. Rest of them are moved to external files.
*/
const process = require("node:process");
const defaultCommand = require("./_.js");
const startCommand = require("./start.js");
module.exports = {
// Default command.
"_": {
description: "Usage: workwatch [options] <command>\n workwatch --help\n workwatch --version",
options: {
"--config-file": {
shortAliases: "-c",
isBooleanValue: false,
description: "Set configuration file."
},
"--data-dir": {
shortAliases: "-d",
isBooleanValue: false,
description: "Set the directory where the WorkWatch stores its data files."
},
"--measurement-filename": {
shortAliases: "-m",
isBooleanValue: false,
description: "Set the measurement filename where the current time admeasuring will be stored."
},
"--log-filename": {
shortAliases: "-l",
isBooleanValue: false,
description: "Set the log where all measurements will be stored."
},
"--help": {
shortAliases: "-h,-?",
isBooleanValue: true,
description: "Display short summary of commands and options."
},
"--version": {
shortAliases: "-v",
isBooleanValue: true,
description: "Show version and exit."
}
},
run(parameters, appData, nextCommand) {
// The parameters holds all options provided by command-line for
// specific command.
const app = new defaultCommand(parameters, appData, nextCommand);
return app.run();
}
},
"start": {
description: "Starts time admeasuring.",
options: {},
run: startCommand
},
"reset": {
description: "Resets admeasuring and optionally stores it in the log.",
options: {},
run(parameters, appData) {
return new Promise((resolve, reject) => {
try {
// Retrieves measured time in order to check if there is any measurement.
const time = appData.timeMeasurement.time;
// Create dialog asking for storing measurement in a worklog.
appData.terminal.ask(
"WorkWatch Version 0.0.2 (Mountain) - Reseting Measurement",
`Your worktime measurement is ${time} (${Number(time.split(":")[0])} hours and ${Number(time.split(":")[1])} minutes).\nIf you want, you can save your measurement in the log before it is reset. If you disagree, the measurement will be reset anyway.\nDo you want to save your measurement in the log?\nPress the key assigned to answer or press ^C to cancel.`,
{"Yes": ["Y", true], "No": ["N"]},
(saveDialogError, answer) => {
if (saveDialogError) {
reject(saveDialogError);
}
if (answer === "Yes") {
// Create dialog asking for project name to assing stored measurement.
appData.terminal.ask(
"WorkWatch Version 0.0.2 (Mountain) - Reseting Measurement",
"Enter the project name you wish to assign your measurement.",
{},
(projectDialogError, projectName) => {
if (projectDialogError) {
reject(projectDialogError);
}
// Remove newline character provided along the answer.
// If there is no project name use "None".
projectName = (
projectName.replace("\n", "").length === 0
)? "None" : projectName.replace("\n", "");
// Get the rest of fields for storage.
const {day, number, hoursRange} = appData.timeMeasurement;
appData.terminal.appArea = "Saving...";
appData.log.add(day, {time, number, hoursRange, project: projectName})
.then(savingSuccess => {
appData.terminal.appArea = `The measurement has been saved in your worklog (${appData.config.dataDir}/${appData.config.logFilename}.json).`;
// Remove measurement file after saving.
return appData.timeMeasurement.delete();
})
.then(deleteSuccess => resolve(true))
.catch(savingError => reject(savingError));
}
);
} else if (answer === "No") {
// Remove measurement file without saving.
appData.timeMeasurement.delete()
.then(deletionSuccess => {
appData.terminal.appArea = "The measurement has been reset without saving in a log.";
resolve(true);
})
.catch(deletionError => reject(deletionError));
}
}
);
} catch (error) {
// Handle error occured when getting the measured time.
if (error.code === "ERR_FILE_MEASUREMENT_NO_TIME_PROPERTY_OR_INVALID") {
appData.terminal.clearScreen();
appData.terminal.title = "WorkWatch Version 0.0.2 (Mountain) - Reseting Measurement";
appData.terminal.appArea = "There is no measurement to reset. Use start command to begin a new one.";
resolve(true);
} else {
reject(error);
}
}
});
}
},
"log": {
description: "Show the log with all measurements stored.",
options: {},
run(parameters, appData) {
return new Promise((resolve, reject) => {
// Prepare a worklog table content if there is something in a worklog.
if (appData.log.days.length > 0) {
const tableContent = [];
appData.log.days.forEach(logDay => {
let tableRow;
logDay.measurements.forEach(measurement => {
// Create a table row using measurement fields with changed names.
// The column names are changed for human readability.
tableRow = {
day: logDay.day,
worked: measurement.time,
number: measurement.number,
hours: measurement.hoursRange,
project: measurement.project
};
// The number field in measurement file is a Number type.
// Worklog table has only a text columns so that it is
// converted to string.
tableRow.number = String(tableRow.number);
// The hours field is an array of {from:"hh:mm", to:"hh:mm"}
// objects. They are converted to array of "from - to" strings.
tableRow.hours = tableRow.hours.map(range => `${range.from} - ${range.to}`);
tableContent.push(tableRow);
});
});
appData.terminal.title = "WorkWatch Version 0.0.2 (Mountain) - Worklog";
appData.terminal.displayTable(tableContent);
} else {
appData.terminal.title = "WorkWatch Version 0.0.2 (Mountain) - Worklog";
appData.terminal.appArea = "There is no worklog to display. Use start command to begin a new measurement and reset when it's finished.";
}
resolve(true);
});
}
}
};