Split code more into modules
This commit is contained in:
parent
3246815997
commit
b88e19e311
5 changed files with 206 additions and 157 deletions
148
src/input.ts
Normal file
148
src/input.ts
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
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, { Dayjs } from 'dayjs';
|
||||||
|
import { WtcPromptResult } from './types/WtcPromptResult';
|
||||||
|
import duration from 'dayjs/plugin/duration.js';
|
||||||
|
|
||||||
|
dayjs.extend(duration);
|
||||||
|
|
||||||
|
const { error } = console;
|
||||||
|
|
||||||
|
const input = async (): Promise<WtcPromptResult> => {
|
||||||
|
const { defaults, askInput } = getConfig();
|
||||||
|
const rl = readline.createInterface({
|
||||||
|
input: process.stdin,
|
||||||
|
output: process.stdout,
|
||||||
|
});
|
||||||
|
|
||||||
|
let startedAt: Dayjs | undefined = undefined;
|
||||||
|
let stoppedAt: Dayjs | undefined = undefined;
|
||||||
|
let stoppedWorking = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get work day duration
|
||||||
|
let workDayDuration: Duration | undefined = 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 = defaults.workDayDuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = defaults.startTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (askInput.stopTime) {
|
||||||
|
const stoppedAnswer = await rl.question(
|
||||||
|
`What time did you stop working? [${formatTime(defaults.stopTime)}] `,
|
||||||
|
);
|
||||||
|
|
||||||
|
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(
|
||||||
|
`Start time (${formatTime(startedAt)}) needs to be before stop time (${formatTime(
|
||||||
|
stoppedAt,
|
||||||
|
)}). Exiting`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let worked = dayjs.duration(stoppedAt.diff(startedAt));
|
||||||
|
|
||||||
|
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
|
||||||
|
let loggedAnswer = await rl.question('How many hours did you log already? [00:00] ');
|
||||||
|
if (loggedAnswer === '') {
|
||||||
|
loggedAnswer = '00:00';
|
||||||
|
}
|
||||||
|
const logged = parseDuration(loggedAnswer);
|
||||||
|
const unLogged = worked.subtract(logged);
|
||||||
|
const workLeft = workDayDuration.subtract(worked);
|
||||||
|
let workLeftMinutes = workLeft.asMinutes();
|
||||||
|
let workedOverTime: Duration | undefined;
|
||||||
|
|
||||||
|
if (workLeftMinutes < 0) {
|
||||||
|
workedOverTime = dayjs.duration(Math.round(workLeftMinutes * -1), 'minutes');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
logged,
|
||||||
|
unLogged,
|
||||||
|
startedAt,
|
||||||
|
stoppedAt,
|
||||||
|
stoppedWorking,
|
||||||
|
hadLunch,
|
||||||
|
worked,
|
||||||
|
workLeft,
|
||||||
|
};
|
||||||
|
} finally {
|
||||||
|
rl.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default input;
|
160
src/main.ts
160
src/main.ts
|
@ -1,163 +1,9 @@
|
||||||
import chalk from 'chalk';
|
|
||||||
import yargs from 'yargs';
|
import yargs from 'yargs';
|
||||||
import { hideBin } from 'yargs/helpers';
|
import { hideBin } from 'yargs/helpers';
|
||||||
import dayjs, { Dayjs } from 'dayjs';
|
import ui from './ui.js';
|
||||||
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 ui = async () => {
|
|
||||||
const { defaults, askInput } = getConfig();
|
|
||||||
const rl = readline.createInterface({
|
|
||||||
input: process.stdin,
|
|
||||||
output: process.stdout,
|
|
||||||
});
|
|
||||||
|
|
||||||
let startedAt: Dayjs | undefined = undefined;
|
|
||||||
let stoppedAt: Dayjs | undefined = undefined;
|
|
||||||
let stoppedWorking = false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Get work day duration
|
|
||||||
let workDayDuration: Duration | undefined = 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 = defaults.workDayDuration;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = defaults.startTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (askInput.stopTime) {
|
|
||||||
const stoppedAnswer = await rl.question(
|
|
||||||
`What time did you stop working? [${formatTime(defaults.stopTime)}] `,
|
|
||||||
);
|
|
||||||
|
|
||||||
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(
|
|
||||||
`Start time (${formatTime(startedAt)}) needs to be before stop time (${formatTime(
|
|
||||||
stoppedAt,
|
|
||||||
)}). Exiting`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let worked = dayjs.duration(stoppedAt.diff(startedAt));
|
|
||||||
|
|
||||||
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
|
|
||||||
let loggedAnswer = await rl.question('How many hours did you log already? [00:00] ');
|
|
||||||
if (loggedAnswer === '') {
|
|
||||||
loggedAnswer = '00:00';
|
|
||||||
}
|
|
||||||
const logged = parseDuration(loggedAnswer);
|
|
||||||
const unLogged = worked.subtract(logged);
|
|
||||||
|
|
||||||
// Log result
|
|
||||||
log();
|
|
||||||
log('Started working at:', formatTimestamp(startedAt));
|
|
||||||
log((stoppedWorking ? 'Stopped working' : 'Hours calculated') + ' at:', formatTimestamp(stoppedAt));
|
|
||||||
log('Worked today:', chalk.green(formatDuration(worked)), chalk.yellow(getHoursRoundedStr(worked)));
|
|
||||||
|
|
||||||
if (unLogged.asMinutes() == 0) {
|
|
||||||
log('Unlogged today:', chalk.green('none'));
|
|
||||||
} else if (unLogged.asMinutes() > 0) {
|
|
||||||
log('Unlogged today:', chalk.red(formatDuration(unLogged)), chalk.yellow(getHoursRoundedStr(unLogged)));
|
|
||||||
} else if (unLogged.asMinutes() < 0) {
|
|
||||||
log(
|
|
||||||
chalk.red(`You have logged ${formatDuration(unLogged)} more than you worked today!`),
|
|
||||||
chalk.yellow(getHoursRoundedStr(unLogged)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const workLeft = workDayDuration.subtract(worked);
|
|
||||||
const workLeftMinutes = workLeft.asMinutes();
|
|
||||||
if (workLeftMinutes > 0) {
|
|
||||||
log('You still have to work', chalk.green(formatDuration(workLeft)), 'more today');
|
|
||||||
} else if (workLeft.asMinutes() < 0) {
|
|
||||||
log(
|
|
||||||
'You worked',
|
|
||||||
chalk.green(
|
|
||||||
formatDuration(dayjs.duration(Math.round(workLeftMinutes * -1), 'minutes')),
|
|
||||||
'overtime today',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
rl.close();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
// Process args. Yargs will exit if it detects help or version
|
||||||
yargs(hideBin(process.argv)).usage('Work time calculator').alias('help', 'h').alias('version', 'v').argv;
|
yargs(hideBin(process.argv)).usage('Work time calculator').alias('help', 'h').alias('version', 'v').argv;
|
||||||
|
|
||||||
|
// Run UI if help or version is not prompted
|
||||||
ui();
|
ui();
|
||||||
|
|
32
src/output.ts
Normal file
32
src/output.ts
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import chalk from 'chalk';
|
||||||
|
import { formatDuration, formatTimestamp, getHoursRoundedStr } from './format';
|
||||||
|
import { WtcPromptResult } from './types/WtcPromptResult';
|
||||||
|
|
||||||
|
const { log } = console;
|
||||||
|
|
||||||
|
const output = (result: WtcPromptResult) => {
|
||||||
|
const { startedAt, stoppedAt, stoppedWorking, worked, unLogged, workLeft, workedOverTime } = result;
|
||||||
|
log();
|
||||||
|
log('Started working at:', formatTimestamp(startedAt));
|
||||||
|
log((stoppedWorking ? 'Stopped working' : 'Hours calculated') + ' at:', formatTimestamp(stoppedAt));
|
||||||
|
log('Worked today:', chalk.green(formatDuration(worked)), chalk.yellow(getHoursRoundedStr(worked)));
|
||||||
|
|
||||||
|
if (unLogged.asMinutes() == 0) {
|
||||||
|
log('Unlogged today:', chalk.green('none'));
|
||||||
|
} else if (unLogged.asMinutes() > 0) {
|
||||||
|
log('Unlogged today:', chalk.red(formatDuration(unLogged)), chalk.yellow(getHoursRoundedStr(unLogged)));
|
||||||
|
} else if (unLogged.asMinutes() < 0) {
|
||||||
|
log(
|
||||||
|
chalk.red(`You have logged ${formatDuration(unLogged)} more than you worked today!`),
|
||||||
|
chalk.yellow(getHoursRoundedStr(unLogged)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (workLeft.asMinutes() > 0) {
|
||||||
|
log('You still have to work', chalk.green(formatDuration(workLeft)), 'more today');
|
||||||
|
} else if (workedOverTime) {
|
||||||
|
log('You worked', chalk.green(formatDuration(workedOverTime), 'overtime today'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default output;
|
14
src/types/WtcPromptResult.ts
Normal file
14
src/types/WtcPromptResult.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import { Dayjs } from 'dayjs';
|
||||||
|
import { Duration } from 'dayjs/plugin/duration';
|
||||||
|
|
||||||
|
export interface WtcPromptResult {
|
||||||
|
startedAt: Dayjs;
|
||||||
|
stoppedAt: Dayjs;
|
||||||
|
stoppedWorking: boolean;
|
||||||
|
logged: Duration;
|
||||||
|
unLogged: Duration;
|
||||||
|
hadLunch: boolean;
|
||||||
|
worked: Duration;
|
||||||
|
workLeft: Duration;
|
||||||
|
workedOverTime?: Duration;
|
||||||
|
}
|
9
src/ui.ts
Normal file
9
src/ui.ts
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import input from './input.js';
|
||||||
|
import output from './output.js';
|
||||||
|
|
||||||
|
const ui = async () => {
|
||||||
|
const result = await input();
|
||||||
|
output(result);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ui;
|
Loading…
Add table
Add a link
Reference in a new issue