Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions src/core/commands/config.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import {Command} from 'commander';
import Config from '../file-config.js';
import Fs from '../filesystem.js';

let config = new Config(process.cwd());

function cfgCmd() {
function cfgCmd(configLoader) {
const cfgCmd = new Command('config', 'edit the configuration file in your default editor')
.option('-l, --local', 'edit the local configuration file')
.action((options, program) => {

let config = configLoader();

if (program.opts().local) {
config.assertLocalConfig();
}
Expand Down
2 changes: 1 addition & 1 deletion src/core/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const defaults = {
token: false,
project: false,
from: "1970-01-01",
to: dayjs().format(),
to: dayjs().add(1, 'day').format('YYYY-MM-DD'),
iids: false,
closed: false,
milestone: false,
Expand Down
53 changes: 30 additions & 23 deletions src/core/file-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@ import fs from 'fs';
import path from 'path';
import os from 'os';
import Config from './config.js';
import { load as yamlLoad } from 'js-yaml';
import { load as yamlLoad, dump as yamlDump } from 'js-yaml';
import hash from 'hash-sum';
import Fs from './filesystem.js';
import envPaths from 'env-paths';
import Cli from './cli.js';

const readYaml = file => yamlLoad(fs.readFileSync(file, 'utf8'));

Expand All @@ -18,12 +17,16 @@ class FileConfig extends Config {
/**
* construct
* @param workDir
* @param load set to false to skip parsing the config files, e.g. when
* recovering from a broken config to still resolve its paths
*/
constructor(workDir) {
constructor(workDir, recover) {
super();
this.assertGlobalConfig();
this.assertGlobalConfig(recover);
this.workDir = workDir;
this.data = Object.assign(this.data, this.localExists() ? this.parseLocal() : this.parseGlobal());
if(!recover) {
this.data = Object.assign(this.data, this.localExists() ? this.parseLocal() : this.parseGlobal());
}
if (!fs.existsSync(this.frameDir)) fs.mkdirSync(this.frameDir, { recursive: true });
this.cache = {
delete: this._cacheDelete,
Expand All @@ -41,7 +44,8 @@ class FileConfig extends Config {
try {
return readYaml(this.global);
} catch (e) {
Cli.error(`Error parsing configuration: "${this.global}"`, e);
e.message = `Error parsing configuration: "${this.global}": ${e.message}`;
throw e;
}
}

Expand All @@ -50,24 +54,27 @@ class FileConfig extends Config {
* @returns {Object}
*/
parseLocal() {
let local;
try {
let local = Object.assign({extend: true}, readYaml(this.local));

if (local.extend === true) {
let global = this.parseGlobal();
local = Object.assign(global ? global : {}, local);
} else if (local.extend) {
try {
local = Object.assign(readYaml(local.extend), local);
} catch (e) {
Cli.error(`Error parsing configuration: "${local.extend}"`, e);
}
}

return local;
local = Object.assign({extend: true}, readYaml(this.local));
} catch (e) {
Cli.error(`Error parsing configuration: "${this.local}"`, e);
e.message = `Error parsing configuration: "${this.local}": ${e.message}`;
throw e;
}

if (local.extend === true) {
let global = this.parseGlobal();
local = Object.assign(global ? global : {}, local);
} else if (local.extend) {
try {
local = Object.assign(readYaml(local.extend), local);
} catch (e) {
e.message = `Error parsing configuration: "${local.extend}": ${e.message}`;
throw e;
}
}

return local;
}

localExists() {
Expand All @@ -85,7 +92,7 @@ class FileConfig extends Config {
}
}

assertGlobalConfig() {
assertGlobalConfig(recover) {
if(!fs.existsSync(this.globalDir) && fs.existsSync(this.oldGlobalDir)) {
fs.renameSync(this.oldGlobalDir, this.globalDir);
}
Expand All @@ -94,7 +101,7 @@ class FileConfig extends Config {

if (!fs.existsSync(this.globalDir)) fs.mkdirSync(this.globalDir, { recursive: true });
if (!fs.existsSync(this.cacheDir)) fs.mkdirSync(this.cacheDir, { recursive: true });
if (!fs.existsSync(this.global)) fs.appendFileSync(this.global, '');
if (recover && !fs.existsSync(this.global)) fs.appendFileSync(this.global, yamlDump(this.data));
}

assertLocalConfig() {
Expand Down
45 changes: 31 additions & 14 deletions src/gtt.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import version from './version.js';

import { program } from 'commander';

import Config from './core/file-config.js';
import Cli from './core/cli.js';
import config from './core/commands/config.js';
import start from './timekeeping/commands/start.js';
import create from './timekeeping/commands/create.js';
Expand All @@ -19,22 +21,37 @@ import delCmd from './timekeeping/commands/delete.js';
import archive from './timekeeping/commands/archive.js';
import report from './reporting/commands/report.js';

/**
* build the config, only called once a command actually runs
* @param recover if true, fall back to a config with unparsed data instead
* of exiting when the config file fails to parse (used by the config
* command itself, so it stays usable to fix a broken file)
* @returns {Config}
*/
function loadConfig(recover = false) {
try {
return new Config(process.cwd(), recover);
} catch (e) {
Cli.error(`${e.message}`);
}
}

program
.version(version)
.addCommand(start())
.addCommand(create())
.addCommand(status())
.addCommand(stop())
.addCommand(resume())
.addCommand(cancel())
.addCommand(list())
.addCommand(log())
.addCommand(sync())
.addCommand(edit())
.addCommand(delCmd())
.addCommand(archive())
.addCommand(report())
.addCommand(config())
.addCommand(start(loadConfig))
.addCommand(create(loadConfig))
.addCommand(status(loadConfig))
.addCommand(stop(loadConfig))
.addCommand(resume(loadConfig))
.addCommand(cancel(loadConfig))
.addCommand(list(loadConfig))
.addCommand(log(loadConfig))
.addCommand(sync(loadConfig))
.addCommand(edit(loadConfig))
.addCommand(delCmd(loadConfig))
.addCommand(archive(loadConfig))
.addCommand(report(loadConfig))
.addCommand(config(() => loadConfig(true)))
.parse(process.argv);


5 changes: 2 additions & 3 deletions src/reporting/commands/report.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {Command} from 'commander';
import dayjs from '../../core/dayjs.js';
import Cli from '../../core/cli.js';
import Args from '../../core/args.js';
import Config from '../../core/file-config.js';
import Report from '../api/report.js';
import Owner from '../../core/owner.js';
import ReportCollection from '../api/reportCollection.js';
Expand All @@ -28,7 +27,7 @@ function collect(val, arr) {
}


function report() {
function report(configLoader) {
const report = new Command('report', 'generate a report for the given project and issues')
.arguments('[project] [ids...]')
.option('-e --type <type>', 'specify the query type: project, user, group')
Expand Down Expand Up @@ -81,7 +80,7 @@ function report() {
.action(async (project, ids, options, program) => {

// init helpers
let config = new Config(process.cwd());
let config = configLoader();
let args = new Args(program.args);

// overwrite config with args and opts
Expand Down
5 changes: 2 additions & 3 deletions src/timekeeping/commands/archive.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
import fs from 'fs';
import {Command} from 'commander';
import Config from '../../core/file-config.js';
import Cli from '../../core/cli.js';
import Fs from '../../core/filesystem.js';
import Zip from '../../core/zip.js';
import Timekeeper from '../timekeeper.js';

function archive() {
function archive(configLoader) {
const archive = new Command('archive', 'archive synced time records into yearly zip files (filesYYYY.zip), one folder per month')
.option('--year <year>', 'only archive the given year')
.option('--verbose', 'show verbose output')
.action((options, program) => {

Cli.verbose = program.opts().verbose;

let config = new Config(process.cwd()),
let config = configLoader(),
timekeeper = new Timekeeper(config),
year = program.opts().year;

Expand Down
5 changes: 2 additions & 3 deletions src/timekeeping/commands/cancel.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import {Command} from 'commander';
import pc from 'picocolors';
import dayjs from '../../core/dayjs.js';
import Config from '../../core/file-config.js';
import Cli from '../../core/cli.js';
import Timekeeper from '../timekeeper.js';

function cancel() {
function cancel(configLoader) {
const cancel = new Command('cancel', 'cancel and discard active monitoring time')
.option('--verbose', 'show verbose output')
.action((options, program) => {

Cli.verbose = program.opts().verbose;

let config = new Config(process.cwd());
let config = configLoader();
let timekeeper = new Timekeeper(config);

timekeeper.cancel()
Expand Down
5 changes: 2 additions & 3 deletions src/timekeeping/commands/create.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import pc from 'picocolors';
import dayjs from '../../core/dayjs.js';
import {Command} from 'commander';
import Config from '../../core/file-config.js';
import Cli from '../../core/cli.js';
import Timekeeper from '../timekeeper.js';


function create() {
function create(configLoader) {
const create = new Command('create', 'start monitoring time for the given project and create a new issue or merge request with the given title')
.arguments('[project] [title]')
.option('-t, --type <type>', 'specify resource type: issue, merge_request')
Expand All @@ -15,7 +14,7 @@ function create() {

Cli.verbose = program.opts().verbose;

let config = new Config(process.cwd()),
let config = configLoader(),
timekeeper = new Timekeeper(config),
type = program.opts().type ? program.opts().type : 'issue',
title = program.args.length === 1 ? program.args[0] : program.args[1],
Expand Down
5 changes: 2 additions & 3 deletions src/timekeeping/commands/delete.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import { Command } from 'commander';
import Frame from '../storage/frame.js';
import Config from '../../core/file-config.js';
import Cli from '../../core/cli.js';
import Fs from '../../core/filesystem.js';
import pc from 'picocolors';

function delCmd() {
function delCmd(configLoader) {
const delCmd = new Command('delete', 'delete time record by the given id')
.arguments('[id]')
.action((id, opts, program) => {

let config = new Config(process.cwd());
let config = configLoader();

if (!id && -Infinity === (id = Fs.newest(config.frameDir).name))
Cli.error('No record found.');
Expand Down
5 changes: 2 additions & 3 deletions src/timekeeping/commands/edit.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {Command} from 'commander';
import Config from '../../core/file-config.js';
import Cli from '../../core/cli.js';
import Fs from '../../core/filesystem.js';
import Time from '../../core/time.js';
Expand Down Expand Up @@ -207,7 +206,7 @@ function showInteractiveMenu(frames) {
});
}

function edit() {
function edit(configLoader) {
const edit = new Command('edit', 'edit time record by the given id')
.arguments('[id]')
.option('-f, --following <number>', 'edit also the following (by ctime) of the given [id]')
Expand All @@ -217,7 +216,7 @@ function edit() {
.option('--this_week', 'only list entries for this week')
.action((id, opts ,program) => {

let config = new Config(process.cwd());
let config = configLoader();
let timeFormat = config.set('timeFormat', program.opts().time_format).get('timeFormat', 'log');
let timekeeper = new Timekeeper(config);
const listSize = program.opts().listsize;
Expand Down
9 changes: 4 additions & 5 deletions src/timekeeping/commands/list.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@ import pc from 'picocolors';
import Table from 'cli-table';


import Config from '../../core/file-config.js';
import cli from '../../core/cli.js';
import Timekeeper from '../timekeeper.js';

function list() {
function list(configLoader) {
const list = new Command('list', 'list all open issues or merge requests')
.arguments('[project]')
.option('--verbose', 'show verbose output')
Expand All @@ -20,10 +19,10 @@ function list() {
.option('--token <token>', 'API access token')
.action((aproject, options, program) => {

let config = new Config(process.cwd())
let config = configLoader()
.set('url', program.opts().url)
.set('token', program.opts().token),
timekeeper = new Timekeeper(config),
.set('token', program.opts().token);
let timekeeper = new Timekeeper(config),
type = program.opts().type ? program.opts().type : 'issue',
project = program.args[0];

Expand Down
7 changes: 3 additions & 4 deletions src/timekeeping/commands/log.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import {Command} from 'commander';
import pc from 'picocolors';
import dayjs from '../../core/dayjs.js';
import Config from '../../core/file-config.js';
import Cli from '../../core/cli.js';
import Time from '../../core/time.js';
import Timekeeper from '../timekeeper.js';
import mergeRequest from '../api/mergeRequest.js';

function log() {
function log(configLoader) {
const log = new Command('log', 'log recorded time records')
.option('--verbose', 'show verbose output')
.option('--hours_per_day <hours>', 'hours per day for human readable time formats')
Expand All @@ -17,8 +16,8 @@ function log() {

Cli.verbose = program.opts().verbose;

let config = new Config(process.cwd()).set('hoursPerDay', program.opts().hours_per_day),
timekeeper = new Timekeeper(config),
let config = configLoader().set('hoursPerDay', program.opts().hours_per_day);
let timekeeper = new Timekeeper(config),
timeFormat = config.set('timeFormat', program.opts().time_format).get('timeFormat', 'log');

function toHumanReadable(input) {
Expand Down
Loading