Add configuration file support
This commit is contained in:
parent
d141fe1d36
commit
ab51b4c11d
7 changed files with 204 additions and 51 deletions
85
src/config.ts
Normal file
85
src/config.ts
Normal file
|
@ -0,0 +1,85 @@
|
|||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { xdgConfig } from 'xdg-basedir';
|
||||
import toml from '@iarna/toml';
|
||||
import { Dayjs } from 'dayjs';
|
||||
import { Duration } from 'dayjs/plugin/duration.js';
|
||||
import { parseDuration, parseTimestamp } from './parse.js';
|
||||
|
||||
const { debug } = console;
|
||||
|
||||
interface Config {
|
||||
defaults: {
|
||||
workDayDuration: Duration;
|
||||
lunchBreakDuration: Duration;
|
||||
startTime: Dayjs;
|
||||
stopTime: Dayjs;
|
||||
};
|
||||
askInput: {
|
||||
workDayLength: boolean;
|
||||
startTime: boolean;
|
||||
stopTime: boolean;
|
||||
logged: boolean;
|
||||
hadLunch: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
interface RawConfig extends Omit<Config, 'defaults'> {
|
||||
defaults: {
|
||||
workDayDuration: string;
|
||||
lunchBreakDuration: string;
|
||||
startTime: string;
|
||||
stopTime: string;
|
||||
};
|
||||
}
|
||||
|
||||
const defaultConfig: RawConfig = {
|
||||
defaults: {
|
||||
workDayDuration: '07:30',
|
||||
lunchBreakDuration: '00:30',
|
||||
startTime: '08:00',
|
||||
stopTime: 'now',
|
||||
},
|
||||
askInput: {
|
||||
workDayLength: true,
|
||||
startTime: true,
|
||||
stopTime: true,
|
||||
logged: true,
|
||||
hadLunch: true,
|
||||
},
|
||||
};
|
||||
|
||||
const getConfig = (): Config => {
|
||||
const configDir = xdgConfig || path.join(process.env.HOME ?? './', '.config');
|
||||
let configFilePath = path.join(configDir, 'wct', 'config.toml');
|
||||
|
||||
let configData: RawConfig;
|
||||
if (fs.existsSync(configFilePath)) {
|
||||
configData = toml.parse(fs.readFileSync(configFilePath, 'utf8')) as unknown as RawConfig;
|
||||
} else {
|
||||
debug('Configuration file does not exist, loading defaults');
|
||||
configData = defaultConfig;
|
||||
}
|
||||
|
||||
return {
|
||||
defaults: {
|
||||
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: {
|
||||
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,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default getConfig;
|
109
src/main.ts
109
src/main.ts
|
@ -6,83 +6,97 @@ import * as readline from 'readline/promises';
|
|||
import { formatDuration, formatTime, formatTimestamp, getHoursRoundedStr } from './format.js';
|
||||
import duration, { Duration } from 'dayjs/plugin/duration.js';
|
||||
import { parseDuration, parseTimestamp } from './parse.js';
|
||||
import getConfig from './config.js';
|
||||
|
||||
dayjs.extend(duration);
|
||||
|
||||
const { log, error } = console;
|
||||
const defaultStartTime = '08:00';
|
||||
const lunchBreakDuration = dayjs.duration({ minutes: 30 });
|
||||
const defaultWorkDayDuration = dayjs.duration({ hours: 7, minutes: 30 });
|
||||
|
||||
const ui = async () => {
|
||||
const { defaults, askInput } = getConfig();
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
});
|
||||
|
||||
let startedAt: Dayjs | undefined = undefined;
|
||||
const now = dayjs();
|
||||
let stoppedAt: Dayjs | undefined = undefined;
|
||||
let stoppedWorking = false;
|
||||
|
||||
try {
|
||||
// Get work day duration
|
||||
let workDayDuration: Duration | undefined = undefined;
|
||||
|
||||
const durationAnswer = await rl.question(
|
||||
`How long is your work day today, excluding the lunch break? [${formatDuration(
|
||||
defaultWorkDayDuration,
|
||||
true,
|
||||
)}] `,
|
||||
);
|
||||
if (durationAnswer !== '') {
|
||||
workDayDuration = parseDuration(durationAnswer);
|
||||
if (workDayDuration.asMinutes() <= 0) {
|
||||
error(
|
||||
chalk.red(
|
||||
`Failed to parse ${durationAnswer} to duration, using default work day duration ${defaultWorkDayDuration}`,
|
||||
),
|
||||
);
|
||||
workDayDuration = undefined;
|
||||
if (askInput.workDayLength) {
|
||||
const durationAnswer = await rl.question(
|
||||
`How long is your work day today, excluding the lunch break? [${formatDuration(
|
||||
defaults.workDayDuration,
|
||||
true,
|
||||
)}] `,
|
||||
);
|
||||
if (durationAnswer !== '') {
|
||||
workDayDuration = parseDuration(durationAnswer);
|
||||
if (workDayDuration.asMinutes() <= 0) {
|
||||
error(
|
||||
chalk.red(
|
||||
`Failed to parse ${durationAnswer} to duration, using default work day duration ${formatDuration(
|
||||
defaults.workDayDuration,
|
||||
true,
|
||||
)}`,
|
||||
),
|
||||
);
|
||||
workDayDuration = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!workDayDuration) {
|
||||
workDayDuration = defaultWorkDayDuration;
|
||||
workDayDuration = defaults.workDayDuration;
|
||||
}
|
||||
|
||||
// Calculate worked time
|
||||
const startTimeAnswer = await rl.question(`What time did you start work today? [${defaultStartTime}] `);
|
||||
if (startTimeAnswer !== '') {
|
||||
startedAt = parseTimestamp(startTimeAnswer);
|
||||
if (!startedAt.isValid()) {
|
||||
error(
|
||||
chalk.red(
|
||||
`Failed to parse ${startTimeAnswer} to time, using default start time ${defaultStartTime}`,
|
||||
),
|
||||
);
|
||||
if (askInput.startTime) {
|
||||
const startTimeAnswer = await rl.question(
|
||||
`What time did you start work today? [${formatTime(defaults.startTime)}] `,
|
||||
);
|
||||
if (startTimeAnswer !== '') {
|
||||
startedAt = parseTimestamp(startTimeAnswer);
|
||||
if (!startedAt.isValid()) {
|
||||
error(
|
||||
chalk.red(
|
||||
`Failed to parse ${startTimeAnswer} to time, using default start time ${formatTime(
|
||||
defaults.startTime,
|
||||
)}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!startedAt?.isValid()) {
|
||||
startedAt = parseTimestamp(defaultStartTime);
|
||||
startedAt = defaults.startTime;
|
||||
}
|
||||
|
||||
let stoppedWorking = false;
|
||||
let stoppedAt: Dayjs | undefined = undefined;
|
||||
const stoppedAnswer = await rl.question(
|
||||
`What time did you stop working (default is current time if you didn't stop yet)? [${formatTime(now)}] `,
|
||||
);
|
||||
if (askInput.stopTime) {
|
||||
const stoppedAnswer = await rl.question(
|
||||
`What time did you stop working (default is current time if you didn't stop yet)? [${formatTime(
|
||||
defaults.stopTime,
|
||||
)}] `,
|
||||
);
|
||||
|
||||
if (stoppedAnswer === '') {
|
||||
stoppedAt = now;
|
||||
} else {
|
||||
stoppedWorking = true;
|
||||
stoppedAt = parseTimestamp(stoppedAnswer);
|
||||
if (!stoppedAt.isValid()) {
|
||||
error(`Failed to parse ${stoppedAnswer} to time, using current time`);
|
||||
stoppedAt = dayjs();
|
||||
if (stoppedAnswer !== '') {
|
||||
stoppedWorking = true;
|
||||
stoppedAt = parseTimestamp(stoppedAnswer);
|
||||
if (!stoppedAt.isValid()) {
|
||||
error(`Failed to parse ${stoppedAnswer} to time, using current time`);
|
||||
stoppedAt = dayjs();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!stoppedAt) {
|
||||
stoppedAt = defaults.stopTime;
|
||||
}
|
||||
|
||||
if (stoppedAt.isSame(startedAt) || stoppedAt.isBefore(startedAt)) {
|
||||
error(
|
||||
chalk.red(
|
||||
|
@ -96,8 +110,11 @@ const ui = async () => {
|
|||
|
||||
let worked = dayjs.duration(stoppedAt.diff(startedAt));
|
||||
|
||||
if ((await rl.question('Did you have a lunch break? [Y/n] ')).toLowerCase() !== 'n') {
|
||||
worked = worked.subtract(lunchBreakDuration);
|
||||
const hadLunch =
|
||||
askInput.hadLunch && (await rl.question('Did you have a lunch break? [Y/n] ')).toLowerCase() !== 'n';
|
||||
|
||||
if (hadLunch) {
|
||||
worked = worked.subtract(defaults.lunchBreakDuration);
|
||||
}
|
||||
|
||||
// Calculate unlogged time
|
||||
|
|
|
@ -5,7 +5,7 @@ import duration, { Duration } from 'dayjs/plugin/duration.js';
|
|||
dayjs.extend(customParseFormat);
|
||||
dayjs.extend(duration);
|
||||
|
||||
export const parseTimestamp = (time: string): Dayjs => dayjs(time, 'HH:mm', true);
|
||||
export const parseTimestamp = (time: string): Dayjs => (time === 'now' ? dayjs() : dayjs(time, 'HH:mm', true));
|
||||
|
||||
export const parseDuration = (time: string): Duration => {
|
||||
const [hours, minutes] = time.split(':').map(Number);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue