t-cli
Version:
A daily time tracker
331 lines (301 loc) • 8.78 kB
text/troff
// vim: set filetype=javascript:
var path = require('path');
var fs = require('fs');
var program = require('commander');
var moment = require('moment');
var colors = require('colors');
var T = require('../t');
// In case want to debug
// var options = {
// pomoTimer: 1,
// shortBreakTimer: 1,
// longerBreakTimer: 1.5
// };
var config = require('../lib/config');
var addUser = require('../lib/adduser');
var Client = require('../lib/client');
var pomodoro = require('../lib/pomodoro')();
// Track tasks daily, saved as a plain text file named by today's date
// eg. 2013-10-10.t
var today = moment().format('YYYY-MM-DD');
function start(t) {
var lines = [
'\n',
'You are working on ' + t.task.text.bold.cyan + '\t',
'\n',
'\nPress Ctrl+C to stop(break or interrupt) task'
];
var timerY = 1;
var timerX = lines[timerY].length;
var lastY = lines.length - 1;
var lastX = lines[lastY].length;
// TODO: More smart to determine longer or short break
var pomos = t.getAllPomodori().length;
var mode = (pomos && pomos % pomodoro.pomodori === 0) ? 'longer' : 'short';
var charm = require('charm')(process);
// Write lines
lines.forEach(charm.write, charm);
// Init timer's output position
charm.move(timerX - lastX, timerY - lastY);
pomodoro.on('tick', function(timer) {
charm.left(timer.length);
charm.write(timer.green);
});
pomodoro.start(mode);
charm.removeAllListeners('^C');
// Once accept key to break or interrupt
charm.once('^C', function() {
var start = t.task.start.split(':');
var startTime = moment({
hours: +start[0],
minutes: +start[1]
});
// Duration is minutes
var duration = moment().diff(startTime, 'minutes');
if (duration >= pomodoro.pomoTimer) {
// console.log('Take a break: ' + mode);
// Take a break
t.task.metas.pomodoro = 1;
// Remove interruption mark on an existing task
delete t.task.metas.interruption;
// Stop here to record actual end time
t.stop();
pomodoro.break(mode);
}
else {
// console.log('Take an interruption');
t.task.metas.interruption = 1;
// Remove pomodoro mark on an existing task
delete t.task.metas.pomodoro;
pomodoro.stopTimer();
// Log the interruption
charm.move(-timerX, lastY - timerY + 1);
console.log('\nChoose a interruption reason or enter others:');
console.log('\n(Press Ctrl+C one more time to exit)');
var conf = config.load();
conf.interruptions = conf.interruptions ||
[
'Colleague Interrupted',
'Boss Interrupted',
'Phone Call'
];
var inputs = [];
var interruptions = conf.interruptions;
interruptions.forEach(function(value, i) {
console.log((i + '. ' + value).red);
});
process.stdin.on('data', function(chunk) {
var s = chunk.toString('utf8');
if (s.charCodeAt(0) === 127 && inputs.length > 0) {
// Match BACKSPACE
inputs.pop();
charm.erase('start');
charm.move(-inputs.length - 1, 0);
charm.write(inputs.join(''));
}
else if (s.match(/\r\n|[\n\r\u0085\u2028\u2029]/)) {
// Match ENTER
// Remove first '\u003'
inputs.shift();
// console.log(inputs);
var line = inputs.join('').trim();
if (line.match(/^\d+$/)) {
t.task.metas.interruption = interruptions[+line] || 1;
}
else {
t.task.metas.interruption = line || 1;
if (line && interruptions.indexOf(line) === -1) {
interruptions.push(line);
config.save(conf);
}
}
console.log('\n');
console.log('Interrupted +1'.red);
charm.emit('^C');
}
else {
inputs.push(s);
}
});
}
// Exit on next Ctrl+C
charm.once('^C', function() {
if (t.task.metas.interruption) {
// Interrupted task should be saved once exit to record Interrupt
// reason.
t.stop();
}
process.exit();
});
});
}
// date format: YYYY-MM-DD
function iniT(date, cb) {
var conf = config.load(function(err) {
console.log('\nPlease set task saved directory first.');
program.outputHelp();
process.exit(1);
});
var t = new T(path.join(conf.dir, (date || today) + '.t'));
if (cb) {
t.stream.on('error', function(e) { cb(e, t); });
t.parser.on('end', function(e) { cb(e, t); });
}
return t;
}
program
.command('set [dir]')
.description('get/set the task saved directory')
.action(function(d) {
var conf = config.load();
if (d) {
conf.dir = path.resolve(d);
config.save(conf);
console.log('Set the saved directory to ' + conf.dir + ' success!');
}
else {
// Show the saved directory without specified
console.log(conf.dir || 'You have not set task saved directory yet!');
}
});
program
.command('adduser')
.description('register/auth user')
.action(function() {
addUser(function(err) {
console.log(err ? err : 'Done');
});
});
program
.command('add <task>')
.description('add a new task(todo)')
.action(function(text) {
iniT(today, function(e, t) {
t.todo(text);
});
});
program
.command('start <id|task>')
.description('start a task by id, or start a new task')
.action(function(arg) {
iniT(today, function(e, t) {
if (/^\d+$/.test(arg)) {
// Start task by id
var id = parseInt(arg, 10);
try {
t.start(id);
start(t);
}
catch(err) {
console.log('Task id ' + arg.red + ' does not exist!');
process.exit(1);
}
}
else {
// Add new task then start
t.start(arg);
start(t);
}
});
});
program
.command('list [period]')
.description('list tasks, optional specified by days or week.\n' +
'\teg.\n' +
'\tlist\tlist today\'s tasks\n' +
'\tlist 3\tlist last 3 day\'s tasks'
)
.action(function(period) {
if (period) {
var days = parseInt(period, 10);
var date;
var _next = function(e, t) {
console.log('\n' + date.bold.magenta + '\n');
var list = t.getSortedList();
if (list.length === 0) {
console.log('You have no task today!'.cyan);
}
list.forEach(function(item, i) {
// Exclude todo items
if (item.start && item.end) {
var duration = item.start + ' - ' + item.end;
console.log(duration.green + '\t' + item.text.cyan);
}
});
if (--days >= 0) {
summary(days);
}
};
var summary = function(days) {
date = moment().subtract('d', days).format('YYYY-MM-DD');
iniT(date, _next);
};
if (days > 0 && days < 30) {
summary(--days);
}
}
else {
var t = iniT();
t.parser.on('end', function() {
var list = t.getUniqList();
if (list.length === 0) {
console.log('You have no task tracked today!');
process.exit(1);
}
// List with ids
list.forEach(function(item, i) {
console.log(i.toString().green+ '\t' + item.text.cyan);
});
});
}
});
program
.command('upload [period]')
.description('upload tasks, optional specified by days or week or filename.\n' +
'\teg.\n' +
'\tupload\tupload today\'s tasks\n' +
'\tupload 3\tupload last 3 day\'s tasks'
)
.action(function(period) {
var client = new Client();
var days, i, filepath, conf;
function done(err) {
if (err) {
console.log(err);
process.exit();
}
else {
console.log('Upload Done!');
}
}
conf = config.load(done);
if (period) {
filepath = path.resolve(period);
days = parseInt(period, 10);
if (fs.existsSync(filepath)) {
client.upload([filepath], done);
}
else if (days) {
var files = [], date;
for(i = days; i--;) {
date = moment().subtract('d', i).format('YYYY-MM-DD');
files.push(path.join(conf.dir, date + '.t'));
}
// TODO: limit files' count
client.upload(files, done);
}
else {
console.log('No file to upload');
// program.outputHelp();
}
}
else {
// default today's task
client.upload([path.join(conf.dir, today + '.t')], done);
}
});
program.parse(process.argv);
if (process.argv.length == 2) {
program.outputHelp();
}