elvn
Version:
Command line time management for nerds
555 lines (533 loc) • 19.5 kB
JavaScript
var util = require('util');
var it = require('./items');
var Command = function () {
};
Command.prototype.run = function (display, items) {
return null;
};
Command.prototype.forward = function (list, result) {
if (result) {
this.next = result;
}
if (list) {
return list;
}
return null;
};
Command.prototype.exec = function (display, items) {
if (!display || !items) {
return;
}
var current = this.run(display, items);
var next = this.next;
if (next) {
current = next.run(display, items);
}
if (current != null) {
display.currentList = current;
}
};
var SwitchList = function (plan, date) {
Command.call(this);
if (typeof plan != 'string') {
plan = '' + plan;
}
this.plan = plan;
this.date = date;
this.run = function (display, items) {
items.search = null;
var list = items.getByPlan(this.plan, this.date);
if (list != null) {
display.showList(list);
return this.forward(list.plan);
}
return this.forward();
};
};
var SwitchSync = function (command) {
Command.call(this);
this.command = command;
this.run = function (display, items) {
var sync = items.getSync();
var c = this.command;
if (c) {
if (sync != null) {
if (c === '<') {
sync.pull();
} else if (c === '>') {
sync.push();
} else if (c === '-') {
sync.restore();
} else {
display.show(this.help(c));
}
} else {
display.show("Sync is not initialized");
}
} else {
display.showItems(items.getItems());
}
return this.forward();
};
this.help = function (invalid) {
return "Wrong sync command: " + invalid + "\n" + "Available commands:\n" + "\t> push\n" + "\t< pull\n"
+ "\t- revert";
};
};
var SwitchTimer = function (command) {
Command.call(this);
this.command = command;
this.run = function (display, items) {
var reshow = true;
var timer = items.getTimer();
var c = this.command;
if (c) {
if (c === ':') {
timer.pause();
} else if (c === '>') {
if (timer.running) {
timer.resume();
} else {
var tasks = items.getByPlan(it.today[0]).items;
if (tasks.length > 0) {
var onTime = function () {
var task = tasks.shift();
if (task) {
tasks.runItem(task, onTime);
}
};
onTime();
}
reshow = false;
}
} else if (c === 'x') {
timer.cancel();
} else if (c === '^') {
timer.fire(false);
} else if (c === 'v') {
timer.fire(true);
} else {
display.show(this.help(c));
reshow = false;
}
if (reshow) {
timer.show();
}
} else {
timer.show();
}
return this.forward();
};
this.help = function (invalid) {
return "Wrong timer command: " + invalid + "\n" + "Available commands:\n" + "\t> run/resume task\n"
+ "\t: pause task\n" + "\t^ skip current stage\n" + "\tx cancel task\n" + "\tv complete task";
};
};
var SearchTask = function (query) {
Command.call(this);
this.query = query;
this.run = function (display, items) {
var tasks = items.findItems(this.query);
display.showItems(tasks);
return this.forward('all');
};
};
var EditTask = function (plan, text) {
Command.call(this);
this.plan = plan;
this.text = text;
this.run = function (display, items) {
var currentList = display.currentList;
this.updateTask({plan: 'work', text: text}, items, currentList);
return this.forward(currentList, new SwitchList(currentList));
};
this.updateTask = function (item, items, currentList) {
var id = item.id;
if (id == null) {
id = items.getId();
}
var c = this.plan;
var t = this.text;
if (!c || c.length == 0 || it.is(it.restricted, c)) {
if (!it.is(it.restricted, currentList)) {
c = currentList;
} else {
c = item.plan;
}
}
if (c == null && t.charAt(0) == ':') {
t = t.substring(1);
}
if (t != null) {
t = this.processText(item.text, t);
} else {
t = item.text;
}
var planned = it.is(it.today, item.plan) || it.is(it.today, currentList);
var completedOn = it.is(it.done, item.plan);
c = it.set(c, it.today[1], planned);
c = it.set(c, it.done[1], completedOn);
items.saveItem({id: id, plan: c, text: t});
};
this.processText = function (oldText, newText) {
var t = newText;
if (t.indexOf("%") != -1) {
var split = t.split("%");
var replace = "";
if (split.length > 1) {
replace = split[1];
}
t = oldText.replace(split[0], replace);
} else if (t.charAt(0) == "+") {
t = oldText.concat(t.substring(1));
} else if (t.charAt(0) == "-") {
t = oldText.replace(t.substring(1), "");
}
return t.trim();
};
};
var LocateTask = function (text, positions, command, plan) {
EditTask.call(this, null, text);
this.positions = positions;
this.command = command;
this.plan = plan;
this.run = function (display, items) {
var currentList = display.currentList;
var next = new SwitchList(currentList);
var its = this.getTasks(currentList, items);
var first = null;
its.forEach(function (item, i) {
var c = this.command;
if (c) {
var p = item.plan;
var save = true;
if (c == '>') {
first = item;
p = it.set(p, it.today[1], true);
p = it.set(p, it.done[1], false);
next = null;
} else if (c == 'x') {
items.removeItem(item);
save = false;
} else if (c == '+') {
if (this.plan && this.plan.length > 0) {
this.plan.forEach(function (plan) {
p = it.set(p, plan, true);
});
} else {
p = it.set(p, it.today[1], true);
p = it.set(p, it.done[1], false);
}
} else if (c == '-') {
if (this.plan && this.plan.length > 0) {
this.plan.forEach(function (plan) {
p = it.set(p, plan, false);
});
} else {
p = it.set(p, it.today[1], false);
p = it.set(p, it.done[1], false);
}
} else if (c == 'v') {
p = it.set(p, it.done[1], true);
items.journal("Completed:'" + item.text + "' in " + item.plan);
} else if (c == '^') {
p = it.set(p, it.done[1], false);
} else if (c == ':') {
p = this.plan;
} else {
c = null;
}
if (save) {
items.saveItem({id: item.id, plan: p, text: item.text});
}
if (c == null) {
next = null;
display.show(this.help(this.command));
}
} else if (!this.plan && !this.text) {
next = null;
display.showItem(item, this.positions[i]);
} else {
this.updateTask(item, items, currentList);
}
}, this);
if (first) {
items.runItem(first, null);
}
return this.forward(currentList, next);
};
this.getTasks = function (currentList, items) {
var res = [];
var list = items.getByPlan(currentList);
if (list) {
var its = items.search ? items.search : list.items;
if (this.positions.length == 0) {
return its;
}
this.positions.forEach(function (pos) {
if (its.length >= pos) {
res.push(its[pos - 1]);
}
});
}
return res;
};
this.help = function (invalid) {
return "Wrong item command: " + invalid + "\n" + "Available commands:\n" + "\t> run item\n"
+ "\t+ make item planned for today\n" + "\t- make item not planned for today\n"
+ "\tv make item completed\n" + "\t^ make item not completed\n" + "\tx delete item";
};
};
var JournalTask = function (time, entry) {
Command.call(this);
this.time = time;
this.entry = entry;
this.run = function (display, items) {
items.journal(this.entry, this.time);
return this.forward(it.journal[0], new SwitchList(it.journal[0]));
};
};
util.inherits(SwitchList, Command);
util.inherits(SwitchSync, Command);
util.inherits(SwitchTimer, Command);
util.inherits(SearchTask, Command);
util.inherits(EditTask, Command);
util.inherits(LocateTask, EditTask);
util.inherits(JournalTask, Command);
exports.Command = Command;
exports.SwitchList = SwitchList;
exports.SwitchSync = SwitchSync;
exports.SwitchTimer = SwitchTimer;
exports.SearchTask = SearchTask;
exports.EditTask = EditTask;
exports.LocateTask = LocateTask;
exports.JournalTask = JournalTask;
function calculateRange(args) {
var start = args.start;
var end = args.end;
if (start > end) {
var tmp = start;
start = end;
end = tmp;
}
var positions = [];
var i = 0;
while (start <= end) {
positions[i++] = start++;
}
return positions;
}
function calculatePositions(args) {
var positions = [];
if (typeof args.positions === 'number') {
positions = [args.positions];
} else if (args.positions == '*') {
positions = [];
} else {
var split = args.positions.split(',');
split.forEach(function (pos) {
positions.push(parseInt(pos));
});
}
return positions;
}
function calculatePlan(args) {
var plan = args.plan ? args.plan.split(',') : [];
var res = [];
plan.forEach(function (item) {
res.push(item.trim());
});
return res;
}
function validateTime(time) {
if (typeof time != 'string') {
time = time + ':00';
}
time = time.trim();
var s = time.split(":");
var h = parseInt(s[0]);
if (h < 0 || h > 23) {
return false;
}
if (h < 10) {
h = "0" + h;
}
var m = "00";
if (s.length > 1) {
m = parseInt(s[1]);
if (m < 0 || m > 59) {
return false;
}
if (m < 10) {
m = "0" + m;
}
}
return [h, m].join(":");
}
function validateDate(date) {
date = date.trim();
var s = date.split('-');
var now = new Date();
var d = parseInt(s[0]);
if (d < 1 || d > 31) {
return false;
}
if (d < 10) {
d = "0" + d;
}
var m = now.getMonth() + 1;
if (s.length > 1) {
m = parseInt(s[1]);
if (m < 1 || m > 12) {
return false;
}
}
if (m < 10) {
m = "0" + m;
}
var y = now.getFullYear();
if (s.length > 2) {
y = parseInt(s[2]);
if (y < 2011 || y > 2032) {
return false;
}
}
return [d, m, y].join("-");
}
exports.init = function (cli, display, items) {
cli.command('/{journal}{date}', 'show items by plan',
{journal: '@|journal', date: '( \\d{1,2}((-\\d{1,2})|)((-20\\d{2})|))|'}, function (input, args) {
var date = args.date;
if (date) {
date = validateDate(date);
if (!date) {
display.show('Wrong date format, expected dd-MM-yyyy');
return null;
}
}
var cmd = new SwitchList(args.journal, date);
cmd.exec(display, items);
return cmd;
});
cli.command('/{plan}', 'show items by plan', {plan: '\\w+|$'}, function (input, args) {
var cmd = new SwitchList(args.plan);
cmd.exec(display, items);
return cmd;
});
cli.command('%{command}', 'sync items and executes command if specified.' +
'\n\tSync commands:\n\t> push\n\t< pull\n\t- revert', {command: '[-<>]?'}, function (input, args) {
var cmd = new SwitchSync(args.command);
cmd.exec(display, items);
return cmd;
});
cli.command('${command}', 'show timer and executes command if specified. ' +
'\n\tTimer commands:\n\t> runs/resume task\n\t: pause task\n\t^ skip current stage\n\tx cancel task\n\tv complete task',
{command: '[:>^vx]?'}, function (input, args) {
var cmd = new SwitchTimer(args.command);
cmd.exec(display, items);
return cmd;
});
cli.command('?{query}', 'search item', {query: '.+'}, function (input, args) {
var cmd = new SearchTask(args.query);
cmd.exec(display, items);
return cmd;
});
cli.command('#{start}-{end}={search}%{replacement}', 'locate item by range (ex. #1-4) and replace search with replacement',
{start: '\\d+', end: '\\d+', search: '.+', replacement: '.+'}, function (input, args) {
var cmd = new LocateTask(args.search + '%' + args.replacement, calculateRange(args));
cmd.exec(display, items);
return cmd;
});
cli.command('#{start}-{end}={text}', 'locate item by range (ex. #1-4) and change its text',
{start: '\\d+', end: '\\d+', text: '.+'}, function (input, args) {
var cmd = new LocateTask(args.text, calculateRange(args));
cmd.exec(display, items);
return cmd;
});
cli.command('#{start}-{end}{command}', 'show item by range (ex. #1-4) and executes command if specified. ' +
'\n\tItem commands:\n\t> run item\n\t+ plan item for today\n\t- not plan item for today\n' +
'\tv make item completed\n\t^ make item not completed\n\tx delete item',
{start: '\\d+', end: '\\d+', command: '[>+-v^x]?'}, function (input, args) {
var cmd = new LocateTask('', calculateRange(args), args.command);
cmd.exec(display, items);
return cmd;
});
cli.command('#{start}-{end}:{plan}', 'locate item by range (ex. #1-4) and change its plan',
{start: '\\d+', end: '\\d+', plan: '[^:]*'}, function (input, args) {
var cmd = new LocateTask('', calculateRange(args), ':', calculatePlan(args));
cmd.exec(display, items);
return cmd;
});
cli.command('#{start}-{end}+{plan}', 'locate item by range (ex. #1-4) and appends to its plan',
{start: '\\d+', end: '\\d+', plan: '[^:]*'}, function (input, args) {
var cmd = new LocateTask('', calculateRange(args), '+', calculatePlan(args));
cmd.exec(display, items);
return cmd;
});
cli.command('#{start}-{end}-{plan}', 'locate item by range (ex. #1-4) and removes from its plan',
{start: '\\d+', end: '\\d+', plan: '[^:]*'}, function (input, args) {
var cmd = new LocateTask('', calculateRange(args), '-', calculatePlan(args));
cmd.exec(display, items);
return cmd;
});
cli.command('#{positions}={search}%{replacement}', 'locate item by positions (ex. #2 or #2,4) and replace search with replacement',
{positions: '(\\d+,?|\\*)+', search: '.+', replacement: '.+'}, function (input, args) {
var cmd = new LocateTask(args.search + '%' + args.replacement, calculatePositions(args));
cmd.exec(display, items);
return cmd;
});
cli.command('#{positions}={text}', 'locate item by positions (ex. #2 or #2,4) and change its text',
{positions: '(\\d+,?|\\*)+', text: '.+'}, function (input, args) {
var cmd = new LocateTask(args.text, calculatePositions(args));
cmd.exec(display, items);
return cmd;
});
cli.command('#{positions}{command}', 'show item by positions (ex. #2 or #2,4) and executes command if specified.' +
'\n\tItem commands:\n\t> run item\n\t+ plan item for today\n\t- not plan item for today\n' +
'\tv make item completed\n\t^ make item not completed\n\tx delete item',
{positions: '(\\d+,?|\\*)+', command: '[>+-v^x]?'}, function (input, args) {
var cmd = new LocateTask('', calculatePositions(args), args.command);
cmd.exec(display, items);
return cmd;
});
cli.command('#{positions}:{plan}', 'locate item by positions (ex. #2 or #2,4) and changes its plan',
{positions: '(\\d+,?|\\*)+', plan: '[^:]*'}, function (input, args) {
var cmd = new LocateTask('', calculatePositions(args), ':', calculatePlan(args));
cmd.exec(display, items);
return cmd;
});
cli.command('#{positions}+{plan}', 'locate item by positions (ex. #2 or #2,4) and appends tp its plan',
{positions: '(\\d+,?|\\*)+', plan: '[^:]*'}, function (input, args) {
var cmd = new LocateTask('', calculatePositions(args), '+', calculatePlan(args));
cmd.exec(display, items);
return cmd;
});
cli.command('#{positions}-{plan}', 'locate item by positions (ex. #2 or #2,4) and removes from its plan',
{positions: '(\\d+,?|\\*)+', plan: '[^:]*'}, function (input, args) {
var cmd = new LocateTask('', calculatePositions(args), '-', calculatePlan(args));
cmd.exec(display, items);
return cmd;
});
cli.command('@{time}-{entry}', 'add journal entry', {time: '(\\d{1,2}((:\\d{1,2})|))|', entry: '.+'},
function (input, args) {
var time = args.time;
if (time) {
time = validateTime(time);
if (!time) {
display.show('Wrong time format, expected hh:mm');
return null;
}
}
var cmd = new JournalTask(time, args.entry);
cmd.exec(display, items);
return cmd;
});
cli.command('{plan}:{text}', 'add item', {plan: '[^:]*', text: '.+'}, function (input, args) {
var cmd = new EditTask(calculatePlan(args), args.text);
cmd.exec(display, items);
return cmd;
});
cli.command('\\\\s', 'toggle silent state for timer', function () {
items && items.getTimer().toggleSilent();
});
};