diff --git a/Makefile b/Makefile index c5a355b..a1f0c68 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,9 @@ -.PHONY: help prod dev clean update-npmjs-readme release publish +.PHONY: help build clean update-npmjs-readme release publish help: @echo "Available targets:" @echo " - help: Show this help message" - @echo " - prod: Build the project in production mode" - @echo " - dev: Build the project in development mode" + @echo " - build: Build the project" @echo " - clean: Remove build artifacts" @echo " - release: Create a new release version" @echo " - publish: Publish the new version created with the release target" @@ -12,17 +11,14 @@ help: node_modules: npm install -prod: node_modules - npm run prod - -dev: node_modules - npm run dev - chmod +x dist/wtc-dev.mjs +build: node_modules + npm run build + chmod +x dist/wtc clean: rm -r dist node_modules -release: prod +release: build @read -p "Enter version bump (patch, minor, major): " bump && \ version=$$(npm version $$bump | grep -oP "(?<=v)[^']+") && \ echo "Version $$version created. Run 'make publish' to push the changes and publish the package." diff --git a/README.adoc b/README.adoc index d012c0f..b4b689a 100644 --- a/README.adoc +++ b/README.adoc @@ -35,10 +35,6 @@ After installation, you should be able to run the program with wtc ---- -== Update - -To update, just run the install command again - == Rationale Don't know if it's just me but calculating my working hours sometimes @@ -60,7 +56,7 @@ needs specifically. == Configuration file -See the https://git.korhonen.cc/FunctionalHacker/work-time-calculator/src/branch/main/config/config.toml[default configuration file] +See the https://git.korhonen.cc/FunctionalHacker/work-time-calculator/src/branch/main/config.toml[default configuration file] for more information on how to override configurations. == TODO diff --git a/bin/wtc b/bin/wtc deleted file mode 100755 index 408a2ea..0000000 --- a/bin/wtc +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -DIR="$(dirname "$(readlink -f "$0")")" -node "$DIR/../dist/wtc.mjs" "$@" diff --git a/config/config.toml b/config.toml similarity index 58% rename from config/config.toml rename to config.toml index cc7ead1..895729b 100644 --- a/config/config.toml +++ b/config.toml @@ -1,33 +1,26 @@ -#:schema https://git.korhonen.cc/FunctionalHacker/work-time-calculator/raw/branch/main/config/config-schema.json - # Work Time Calculator configuration file # This is the default configuration. # You can only partially override the config, # any missing values will use the defaults described here. -# On Unix/Linux you can place your configuration file in $XDG_CONFIG_HOME/wtc/config.toml, +# You can place your configuration file in $XDG_CONFIG_HOME/wtc/config.toml, # usually ~/.config/wtc/config.toml -# For windows, I don't know. - -# Comment out or remove if you don't have an unpaid lunch break -# or if you normally log your lunch break hours -unpaidLunchBreakDuration = "00:30" # The language of the application. # Currently supported languages are "en", "fi" language = "en" -# Time format used to display timestamps (started, stopped etc.) -# Refer to the dayjs documentation on how to set this https://day.js.org/docs/en/display/format -# For example, the finnish format would be "MM.DD.YYYY [kello] HH.mm" -timestampFormat = "YYYY-MM-DD HH:mm" - # This section is for default values for inputs [defaults] +# Leave empty if you don't have an unpaid lunch break +# or if you normally log your lunch break hours +lunchBreakDuration = "00:30" + # Your work day duration workDayDuration = "07:30" # The time you start working + startTime = "08:00" # The time you stop working. Can either be "now" or a time @@ -41,3 +34,6 @@ workDayDuration = true startTime = true stopTime = true logged = true + +# It is assumed that you didn't have lunch if this is false +hadLunch = true diff --git a/config/config-schema.json b/config/config-schema.json deleted file mode 100644 index 281af39..0000000 --- a/config/config-schema.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "properties": { - "unpaidLunchBreakDuration": { - "type": "string", - "description": "Comment out or remove if you don't have an unpaid lunch break or if you normally log your lunch break hours" - }, - "language": { - "type": "string", - "enum": ["en", "fi"], - "description": "The language of the application. Currently supported languages are English (en) and Finnish (fi)" - }, - "timestampFormat": { - "type": "string", - "description": "Time format used to display timestamps (started, stopped, etc.). Refer to the dayjs documentation on how to set this https://day.js.org/docs/en/display/format" - }, - "defaults": { - "type": "object", - "properties": { - "workDayDuration": { "type": "string", "description": "Your work day duration" }, - "startTime": { "type": "string", "description": "The time you start working" }, - "stopTime": { - "type": ["string", "null"], - "description": "The time you stop working. Can either be 'now' or a time" - }, - "hadLunch": { "type": "boolean", "description": "Wether you had lunch already or not" } - }, - "additionalProperties": false, - "description": "Default values for inputs" - }, - "askInput": { - "type": "object", - "properties": { - "workDayDuration": { - "type": "boolean", - "description": "Disable prompt for work day duration if set to false" - }, - "startTime": { "type": "boolean", "description": "Disable prompt for start time if set to false" }, - "stopTime": { "type": "boolean", "description": "Disable prompt for stop time if set to false" }, - "logged": { "type": "boolean", "description": "Disable prompt for logged time if set to false" } - }, - "additionalProperties": false, - "description": "Settings to disable prompts" - } - }, - "additionalProperties": false, - "description": "Work Time Calculator configuration file. Configuration file location: $XDG_CONFIG_HOME/wtc/config.toml, usually ~/.config/wtc/config.toml" -} diff --git a/package-lock.json b/package-lock.json index a0226f1..fa354b5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,17 @@ { "name": "work-time-calculator", - "version": "1.0.2", + "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "work-time-calculator", - "version": "1.0.2", + "version": "0.1.0", "license": "MIT", "dependencies": { + "@iarna/toml": "^2.2.5", "chalk": "^5.3.0", "dayjs": "^1.11.10", - "iarna-toml-esm": "^3.0.5", "xdg-basedir": "^5.1.0", "yargs": "^17.7.2" }, @@ -19,7 +19,6 @@ "wtc": "bin/wtc" }, "devDependencies": { - "@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-typescript": "^11.1.5", "@types/node": "^20.9.0", @@ -135,6 +134,11 @@ "dev": true, "peer": true }, + "node_modules/@iarna/toml": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz", + "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==" + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", @@ -228,31 +232,6 @@ "node": ">= 8" } }, - "node_modules/@rollup/plugin-node-resolve": { - "version": "15.2.3", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz", - "integrity": "sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==", - "dev": true, - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "@types/resolve": "1.20.2", - "deepmerge": "^4.2.2", - "is-builtin-module": "^3.2.1", - "is-module": "^1.0.0", - "resolve": "^1.22.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.78.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, "node_modules/@rollup/plugin-terser": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz", @@ -500,12 +479,6 @@ "undici-types": "~5.26.4" } }, - "node_modules/@types/resolve": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", - "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", - "dev": true - }, "node_modules/@types/semver": { "version": "7.5.5", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.5.tgz", @@ -837,18 +810,6 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, - "node_modules/builtin-modules": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -956,15 +917,6 @@ "dev": true, "peer": true }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -990,14 +942,6 @@ "node": ">=6.0.0" } }, - "node_modules/emitter-component": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/emitter-component/-/emitter-component-1.1.2.tgz", - "integrity": "sha512-QdXO3nXOzZB4pAjM0n6ZE+R9/+kPpECA/XSELIcc54NeYVnBqIk+4DFiBgK+8QbV3mdvTG6nedl7dTYgO+5wDw==", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -1486,14 +1430,6 @@ "node": ">= 0.4" } }, - "node_modules/iarna-toml-esm": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/iarna-toml-esm/-/iarna-toml-esm-3.0.5.tgz", - "integrity": "sha512-CgeDbPohnFG827UoRaCqKxJ8idiIDZDWlcHf5hUReQnZ8jHnNnhN4QJFiY12fKvr0LvuDuKAimqQfrmQnacbtw==", - "dependencies": { - "stream": "^0.0.2" - } - }, "node_modules/ignore": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", @@ -1548,21 +1484,6 @@ "dev": true, "peer": true }, - "node_modules/is-builtin-module": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", - "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", - "dev": true, - "dependencies": { - "builtin-modules": "^3.3.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-core-module": { "version": "2.13.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", @@ -1604,12 +1525,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", - "dev": true - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -2209,14 +2124,6 @@ "deprecated": "Please use @jridgewell/sourcemap-codec instead", "dev": true }, - "node_modules/stream": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stream/-/stream-0.0.2.tgz", - "integrity": "sha512-gCq3NDI2P35B2n6t76YJuOp7d6cN/C7Rt0577l91wllh0sY9ZBuw9KaSGqH/b0hzn3CWWJbpbW0W0WvQ1H/Q7g==", - "dependencies": { - "emitter-component": "^1.1.1" - } - }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", diff --git a/package.json b/package.json index 5a651e7..6c5a240 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "work-time-calculator", - "version": "1.0.2", + "version": "0.1.0", "description": "An interactive CLI tool to calculate work time", "license": "MIT", "repository": { @@ -12,14 +12,13 @@ "url": "https://git.korhonen.cc/FunctionalHacker/work-time-calculator/issues", "email": "wtc@functionalhacker.korhonen.cc" }, + "main": "src/main.ts", "type": "module", "bin": { - "wtc": "bin/wtc" + "wtc": "dist/wtc" }, - "main": "dist/wtc.mjs", "scripts": { - "prod": "rollup -c ./rollup.prod.config.js", - "dev": "rollup -c ./rollup.dev.config.js" + "build": "rollup -c" }, "keywords": [ "work", @@ -28,7 +27,6 @@ ], "author": "Marko Korhonen ", "devDependencies": { - "@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-typescript": "^11.1.5", "@types/node": "^20.9.0", @@ -40,9 +38,9 @@ "tslib": "^2.6.2" }, "dependencies": { + "@iarna/toml": "^2.2.5", "chalk": "^5.3.0", "dayjs": "^1.11.10", - "iarna-toml-esm": "^3.0.5", "xdg-basedir": "^5.1.0", "yargs": "^17.7.2" } diff --git a/rollup.prod.config.js b/rollup.config.js similarity index 59% rename from rollup.prod.config.js rename to rollup.config.js index 258f57b..0e1e6ab 100644 --- a/rollup.prod.config.js +++ b/rollup.config.js @@ -1,15 +1,15 @@ import typescript from '@rollup/plugin-typescript'; -import { nodeResolve } from '@rollup/plugin-node-resolve'; import terser from '@rollup/plugin-terser'; +import shebang from 'rollup-plugin-add-shebang'; /** @type {import('rollup').RollupOptions} */ const config = { input: 'src/main.ts', output: { format: 'esm', - file: 'dist/wtc.mjs', + file: 'dist/wtc', }, - plugins: [typescript(), nodeResolve({ exportConditions: ['node'] }), terser()], + plugins: [typescript(), terser(), shebang({ include: 'dist/wtc' })], }; export default config; diff --git a/rollup.dev.config.js b/rollup.dev.config.js deleted file mode 100644 index 7d2e1fb..0000000 --- a/rollup.dev.config.js +++ /dev/null @@ -1,15 +0,0 @@ -import typescript from '@rollup/plugin-typescript'; -import { nodeResolve } from '@rollup/plugin-node-resolve'; -import shebang from 'rollup-plugin-add-shebang'; - -/** @type {import('rollup').RollupOptions} */ -const config = { - input: 'src/main.ts', - output: { - format: 'esm', - file: 'dist/wtc-dev.mjs', - }, - plugins: [typescript(), nodeResolve({ exportConditions: ['node'] }), shebang({ include: 'dist/wtc-dev.mjs' })], -}; - -export default config; diff --git a/src/config.ts b/src/config.ts index 02f288b..8049b22 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,69 +1,66 @@ import fs from 'fs'; import path from 'path'; import { xdgConfig } from 'xdg-basedir'; -import {parse} from 'iarna-toml-esm'; +import toml from '@iarna/toml'; import { parseDuration, parseTimestamp } from './parse.js'; import WtcConfig from './types/WtcConfig.js'; import Language from './types/Language.js'; -interface RawConfig extends Omit { - unpaidLunchBreakDuration: string; +interface RawConfig extends Omit { defaults: { workDayDuration: string; + lunchBreakDuration: string; startTime: string; stopTime: string; - hadLunch: boolean; }; } const defaultConfig: RawConfig = { language: Language.en, - timestampFormat: 'YYYY-MM-DD HH:mm', - unpaidLunchBreakDuration: '00:30', defaults: { workDayDuration: '07:30', + lunchBreakDuration: '00:30', startTime: '08:00', stopTime: 'now', - hadLunch: true, }, askInput: { - workDayDuration: true, + workDayLength: true, startTime: true, stopTime: true, logged: true, + hadLunch: true, }, }; const getConfig = (): WtcConfig => { const configDir = xdgConfig || path.join(process.env.HOME ?? './', '.config'); - const configFilePath = path.join(configDir, 'wtc', 'config.toml'); + let configFilePath = path.join(configDir, 'wtc', 'config.toml'); - let configData: Partial; + let configData: RawConfig; if (fs.existsSync(configFilePath)) { - configData = parse(fs.readFileSync(configFilePath, 'utf8')) as unknown as RawConfig; + configData = toml.parse(fs.readFileSync(configFilePath, 'utf8')) as unknown as RawConfig; } else { configData = defaultConfig; } - const { language, timestampFormat, unpaidLunchBreakDuration, defaults, askInput } = configData; - return { - language: language ?? defaultConfig.language, - timestampFormat: timestampFormat ?? defaultConfig.timestampFormat, - unpaidLunchBreakDuration: !unpaidLunchBreakDuration - ? undefined - : parseDuration(unpaidLunchBreakDuration), + language: configData.language ?? defaultConfig.language, defaults: { - workDayDuration: parseDuration(defaults?.workDayDuration ?? defaultConfig.defaults.workDayDuration), - startTime: parseTimestamp(defaults?.startTime ?? defaultConfig.defaults.startTime), - stopTime: parseTimestamp(defaults?.stopTime ?? defaultConfig.defaults.stopTime), - hadLunch: defaults?.hadLunch ?? defaultConfig.defaults.hadLunch, + workDayDuration: parseDuration( + configData.defaults.workDayDuration ?? defaultConfig.defaults.workDayDuration, + ), + lunchBreakDuration: parseDuration( + configData.defaults.lunchBreakDuration ?? defaultConfig.defaults.workDayDuration, + ), + startTime: parseTimestamp(configData.defaults.startTime ?? defaultConfig.defaults.startTime), + stopTime: parseTimestamp(configData.defaults.stopTime ?? defaultConfig.defaults.stopTime), }, askInput: { - workDayDuration: askInput?.workDayDuration ?? defaultConfig.askInput.workDayDuration, - startTime: askInput?.startTime ?? defaultConfig.askInput.startTime, - stopTime: askInput?.stopTime ?? defaultConfig.askInput.stopTime, - logged: askInput?.logged ?? defaultConfig.askInput.logged, + workDayLength: configData.askInput.workDayLength ?? defaultConfig.askInput.workDayLength, + startTime: configData.askInput.startTime ?? defaultConfig.askInput.startTime, + stopTime: configData.askInput.stopTime ?? defaultConfig.askInput.stopTime, + logged: configData.askInput.logged ?? defaultConfig.askInput.logged, + hadLunch: configData.askInput.hadLunch ?? defaultConfig.askInput.hadLunch, }, }; }; diff --git a/src/dayjs.ts b/src/dayjs.ts deleted file mode 100644 index 7626024..0000000 --- a/src/dayjs.ts +++ /dev/null @@ -1,10 +0,0 @@ -import dayjs, {Dayjs} from 'dayjs/esm'; -import duration, {Duration} from 'dayjs/esm/plugin/duration'; -import customParseFormat from 'dayjs/esm/plugin/customParseFormat'; - -dayjs.extend(duration); -dayjs.extend(customParseFormat); - -export default dayjs; - -export type {Dayjs, Duration}; diff --git a/src/format.ts b/src/format.ts index b097907..c831e24 100644 --- a/src/format.ts +++ b/src/format.ts @@ -1,14 +1,15 @@ -import dayjs, { Dayjs } from './dayjs'; +import dayjs, { Dayjs } from 'dayjs'; import { Duration } from 'dayjs/plugin/duration.js'; import Language from './types/Language'; import { MessageKey, message } from './i18n'; +export const formatTimestamp = (timestamp: Dayjs): string => timestamp.format('YYYY-MM-DD HH:mm'); + export const formatTime = (time: Dayjs): string => time.format('HH:mm'); export const formatDuration = (language: Language) => - (duration?: Duration, short?: boolean): string => { - duration = duration ?? dayjs.duration(0, 'minutes'); + (duration: Duration, short?: boolean): string => { if (duration.hours() === 0 && duration.minutes() === 0) { return 'none'; } @@ -20,10 +21,10 @@ export const formatDuration = } else if (language === Language.fi) { formatString = duration.hours() > 0 && duration.minutes() > 0 - ? `H [tunti${duration.hours() > 1 ? 'a' : ''} ja] m [minuuttia]` + ? `H [tunti${duration.hours() > 1 ? 'a' : ''} ja] m [minuutti${duration.minutes() > 1 ? 'a' : ''}]` : duration.hours() > 0 ? `H [tunti${duration.hours() > 1 ? 'a' : ''}]` - : 'm [minuuttia]'; + : `m [minutti${duration.minutes() > 1 ? 'a' : ''}]`; } else { formatString = duration.hours() > 0 && duration.minutes() > 0 diff --git a/src/i18n.ts b/src/i18n.ts index 074e43b..833a729 100644 --- a/src/i18n.ts +++ b/src/i18n.ts @@ -1,18 +1,12 @@ import Language from './types/Language'; export enum MessageKey { - cliHelp, - cliVersion, promptWorkDayDuration, - excludingLunch, promptStartTime, promptStopTime, parseTimeFailed, startTimeBeforeStopTimeError, promptLunchBreak, - promptYesNoYes, - promptYesNoNo, - unpaidLunch, promptLogged, none, startedWorking, @@ -28,21 +22,9 @@ export enum MessageKey { } const messages: Record> = { - [MessageKey.cliHelp]: { - [Language.en]: 'Show this help', - [Language.fi]: 'Näytä tämä ohje', - }, - [MessageKey.cliVersion]: { - [Language.en]: 'Show program version', - [Language.fi]: 'Näytä ohjelman versio', - }, [MessageKey.promptWorkDayDuration]: { - [Language.en]: 'How long is your work day today{0}? [{1}]: ', - [Language.fi]: 'Kuinka pitkä työpäiväsi on tänään{0}? [{1}]: ', - }, - [MessageKey.excludingLunch]: { - [Language.en]: ', excluding the lunch break', - [Language.fi]: ', poisluettuna lounastauko', + [Language.en]: 'How long is your work day today, excluding the lunch break? [{0}]: ', + [Language.fi]: 'Kuinka pitkä työpäiväsi on tänään, poisluettuna lounastauko? [{0}]: ', }, [MessageKey.promptStartTime]: { [Language.en]: 'What time did you start work today? [{0}]: ', @@ -61,20 +43,8 @@ const messages: Record> = { [Language.fi]: 'Aloitusaika ({0}) pitää olla ennen lopetusaikaa ({1}). Ohjelma sammuu', }, [MessageKey.promptLunchBreak]: { - [Language.en]: 'Did you have a lunch break? [{0}]: ', - [Language.fi]: 'Piditkö jo lounastauon? [{0}]: ', - }, - [MessageKey.promptYesNoYes]: { - [Language.en]: 'Y/n', - [Language.fi]: 'K/e', - }, - [MessageKey.promptYesNoNo]: { - [Language.en]: 'y/N', - [Language.fi]: 'k/E', - }, - [MessageKey.unpaidLunch]: { - [Language.en]: 'Unpaid lunch:', - [Language.fi]: 'Palkaton lounas:', + [Language.en]: 'Did you have a lunch break? [y/N]: ', + [Language.fi]: 'Piditkö jo lounastauon? [k/E]: ', }, [MessageKey.promptLogged]: { [Language.en]: 'How many hours did you log already? [00:00] ', diff --git a/src/input.ts b/src/input.ts index 386060f..e49e582 100644 --- a/src/input.ts +++ b/src/input.ts @@ -1,19 +1,23 @@ import chalk from 'chalk'; +import { Duration } from 'dayjs/plugin/duration'; +import getConfig from './config'; import { parseDuration, parseTimestamp } from './parse'; import * as readline from 'readline/promises'; import { formatDuration, formatTime } from './format'; -import { Dayjs } from 'dayjs'; +import dayjs, { Dayjs } from 'dayjs'; import { WtcPromptResult } from './types/WtcPromptResult'; -import { WtcRuntimeConfig } from './types/WtcConfig'; -import { MessageKey } from './i18n'; -import dayjs, { Duration } from './dayjs'; +import duration from 'dayjs/plugin/duration.js'; +import WtcConfig from './types/WtcConfig'; +import { MessageKey, message } from './i18n'; + +dayjs.extend(duration); const { error } = console; -const input = async (runtimeCfg: WtcRuntimeConfig): Promise => { - const { config, msg } = runtimeCfg; +const input = async (config: WtcConfig): Promise => { + const msg = message(config.language); const fmtDuration = formatDuration(config.language); - const { defaults, askInput, unpaidLunchBreakDuration: lunchBreakDuration } = config; + const { defaults, askInput } = config; const rl = readline.createInterface({ input: process.stdin, output: process.stdout, @@ -27,13 +31,9 @@ const input = async (runtimeCfg: WtcRuntimeConfig): Promise => // Get work day duration let workDayDuration: Duration | undefined = undefined; - if (askInput.workDayDuration) { + if (askInput.workDayLength) { const durationAnswer = await rl.question( - msg( - MessageKey.promptWorkDayDuration, - config.unpaidLunchBreakDuration ? msg(MessageKey.excludingLunch) : '', - fmtDuration(defaults.workDayDuration, true), - ), + msg(MessageKey.promptWorkDayDuration, fmtDuration(defaults.workDayDuration, true)), ); if (durationAnswer !== '') { workDayDuration = parseDuration(durationAnswer); @@ -92,24 +92,13 @@ const input = async (runtimeCfg: WtcRuntimeConfig): Promise => let worked = dayjs.duration(stoppedAt.diff(startedAt)); let hadLunch = false; - if (lunchBreakDuration) { - const lunchAnswer = ( - await rl.question( - msg( - MessageKey.promptLunchBreak, - msg(config.defaults.hadLunch ? MessageKey.promptYesNoYes : MessageKey.promptYesNoNo), - ), - ) - ).toLowerCase(); + if (askInput.hadLunch) { + const lunchAnswer = (await rl.question(msg(MessageKey.promptLunchBreak))).toLowerCase(); + hadLunch = lunchAnswer === 'y' || lunchAnswer === 'k'; + } - if ( - lunchAnswer === 'y' || - lunchAnswer === 'k' || - (config.defaults.hadLunch && lunchAnswer !== 'n' && lunchAnswer !== 'e') - ) { - hadLunch = true; - worked = worked.subtract(lunchBreakDuration); - } + if (hadLunch) { + worked = worked.subtract(defaults.lunchBreakDuration); } // Calculate unlogged time @@ -120,11 +109,11 @@ const input = async (runtimeCfg: WtcRuntimeConfig): Promise => const logged = parseDuration(loggedAnswer); const unLogged = worked.subtract(logged); const workLeft = workDayDuration.subtract(worked); - const workLeftMinutes = workLeft.asMinutes(); - let workedOvertime: Duration | undefined; + let workLeftMinutes = workLeft.asMinutes(); + let workedOverTime: Duration | undefined; if (workLeftMinutes < 0) { - workedOvertime = dayjs.duration(Math.round(workLeftMinutes * -1), 'minutes'); + workedOverTime = dayjs.duration(Math.round(workLeftMinutes * -1), 'minutes'); } return { @@ -136,7 +125,6 @@ const input = async (runtimeCfg: WtcRuntimeConfig): Promise => hadLunch, worked, workLeft, - workedOvertime, }; } finally { rl.close(); diff --git a/src/main.ts b/src/main.ts index 6e3b054..f244ada 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,38 +1,9 @@ import yargs from 'yargs'; import { hideBin } from 'yargs/helpers'; import ui from './ui.js'; -import update from './update.js'; -import getConfig from './config.js'; -import { MessageKey, message } from './i18n.js'; -import { WtcRuntimeConfig } from './types/WtcConfig.js'; - -// Build runtime config -const config = getConfig(); -const msg = message(config.language); -const runtimeConfig: WtcRuntimeConfig = { - config, - msg, -}; // Process args. Yargs will exit if it detects help or version -const args = await yargs(hideBin(process.argv)) - .usage('Work time calculator') - .alias('help', 'h') - .alias('version', 'v') - .options({ - help: { - description: msg(MessageKey.cliHelp), - }, - version: { - description: msg(MessageKey.cliVersion), - }, - }).argv; +yargs(hideBin(process.argv)).usage('Work time calculator').alias('help', 'h').alias('version', 'v').argv; -// Run updater if requested -if (args.update) { - update(); - process.exit(0); -} - -// Run UI if no arguments -ui(runtimeConfig); +// Run UI if help or version is not prompted +ui(); diff --git a/src/output.ts b/src/output.ts index 86e0619..5e98780 100644 --- a/src/output.ts +++ b/src/output.ts @@ -1,48 +1,40 @@ import chalk from 'chalk'; -import { formatDuration, getHoursRoundedStr } from './format'; +import { formatDuration, formatTimestamp, getHoursRoundedStr } from './format'; import { WtcPromptResult } from './types/WtcPromptResult'; -import { MessageKey } from './i18n.js'; -import { WtcRuntimeConfig } from './types/WtcConfig'; -import dayjs from './dayjs'; +import { MessageKey, message } from './i18n.js'; +import WtcConfig from './types/WtcConfig'; const { log } = console; -const output = (result: WtcPromptResult, runtimeCfg: WtcRuntimeConfig) => { - const {config, msg} = runtimeCfg; - const { language, timestampFormat } = config; - const fmtDuration = formatDuration(language); - const hoursRounded = getHoursRoundedStr(language); - const { startedAt, stoppedAt, stoppedWorking, worked, unLogged, workLeft, workedOvertime, hadLunch } = result; - +const output = (result: WtcPromptResult, config: WtcConfig) => { + const msg = message(config.language); + const fmtDuration = formatDuration(config.language); + const hoursRounded = getHoursRoundedStr(config.language); + const { startedAt, stoppedAt, stoppedWorking, worked, unLogged, workLeft, workedOverTime } = result; log(); - log(msg(MessageKey.startedWorking), startedAt.format(timestampFormat)); + log(msg(MessageKey.startedWorking), formatTimestamp(startedAt)); log( (stoppedWorking ? msg(MessageKey.stoppedWorking) : msg(MessageKey.hoursCalculated)) + ` ${msg(MessageKey.klo)}:`, - stoppedAt.format(timestampFormat), + formatTimestamp(stoppedAt), ); log(msg(MessageKey.workedToday), chalk.green(fmtDuration(worked)), chalk.yellow(hoursRounded(worked))); - if (hadLunch) { - log(msg(MessageKey.unpaidLunch), chalk.green(fmtDuration(config.unpaidLunchBreakDuration))); - } - const unLoggedMinutes = unLogged.asMinutes(); if (unLoggedMinutes >= 0) { log( msg(MessageKey.unloggedToday), unLoggedMinutes === 0 ? chalk.green(msg(MessageKey.none)) : chalk.red(fmtDuration(unLogged)), - unLoggedMinutes === 0 ? '' : chalk.yellow(hoursRounded(unLogged)), + chalk.yellow(hoursRounded(unLogged)), ); } else if (unLoggedMinutes < 0) { - const overLogged = dayjs.duration(Math.abs(unLogged.asMilliseconds()), 'milliseconds'); - log(chalk.red(msg(MessageKey.loggedOver, fmtDuration(overLogged)))); + log(chalk.red(msg(MessageKey.loggedOver, fmtDuration(unLogged))), chalk.yellow(hoursRounded(unLogged))); } if (workLeft.asMinutes() > 0) { log(msg(MessageKey.workLeft, chalk.green(fmtDuration(workLeft)))); - } else if (workedOvertime) { - log(msg(MessageKey.workedOvertime, chalk.green(fmtDuration(workedOvertime)))); + } else if (workedOverTime) { + log(msg(MessageKey.workedOvertime, chalk.green(fmtDuration(workedOverTime)))); } }; diff --git a/src/parse.ts b/src/parse.ts index 7d723fb..4abe2fd 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -1,4 +1,9 @@ -import dayjs, { Dayjs, Duration } from './dayjs'; +import dayjs, { Dayjs } from 'dayjs'; +import customParseFormat from 'dayjs/plugin/customParseFormat.js'; +import duration, { Duration } from 'dayjs/plugin/duration.js'; + +dayjs.extend(customParseFormat); +dayjs.extend(duration); export const parseTimestamp = (time: string): Dayjs => (time === 'now' ? dayjs() : dayjs(time, 'HH:mm', true)); diff --git a/src/types/WtcConfig.ts b/src/types/WtcConfig.ts index 7d822b8..65f7b4a 100644 --- a/src/types/WtcConfig.ts +++ b/src/types/WtcConfig.ts @@ -1,29 +1,20 @@ import { Dayjs } from 'dayjs'; import { Duration } from 'dayjs/plugin/duration.js'; import Language from './Language.js'; -import { message } from '../i18n.js'; export default interface WtcConfig { language: Language, - timestampFormat: string, - unpaidLunchBreakDuration?: Duration; defaults: { workDayDuration: Duration; + lunchBreakDuration: Duration; startTime: Dayjs; stopTime: Dayjs; - hadLunch: boolean; }; askInput: { - workDayDuration: boolean; + workDayLength: boolean; startTime: boolean; stopTime: boolean; logged: boolean; + hadLunch: boolean; }; } - -/** Config and current language msg function together */ -export interface WtcRuntimeConfig { - config: WtcConfig; - msg: ReturnType; -} - diff --git a/src/types/WtcPromptResult.ts b/src/types/WtcPromptResult.ts index 9e0f269..ed3ee0f 100644 --- a/src/types/WtcPromptResult.ts +++ b/src/types/WtcPromptResult.ts @@ -10,5 +10,5 @@ export interface WtcPromptResult { hadLunch: boolean; worked: Duration; workLeft: Duration; - workedOvertime?: Duration; + workedOverTime?: Duration; } diff --git a/src/ui.ts b/src/ui.ts index 9d792fe..45d0285 100644 --- a/src/ui.ts +++ b/src/ui.ts @@ -1,7 +1,11 @@ +import getConfig from './config.js'; import input from './input.js'; import output from './output.js'; -import { WtcRuntimeConfig } from './types/WtcConfig.js'; -const ui = async (config: WtcRuntimeConfig) => output(await input(config), config); +const ui = async () => { + const config = getConfig(); + const result = await input(config); + output(result, config); +}; export default ui; diff --git a/src/update.ts b/src/update.ts deleted file mode 100644 index 92ad114..0000000 --- a/src/update.ts +++ /dev/null @@ -1,7 +0,0 @@ -const { log } = console; - -const update = () => { - log('update'); -}; - -export default update;