friendlog
Version:
[Install Node](https://nodejs.org/en/download/package-manager/) (if you haven't before)
306 lines (273 loc) • 9.37 kB
JavaScript
;
var moment = require("moment");
var _ = require("lodash");
var fs = require("fs");
var os = require("os");
var rl = require("readline");
var argv = require("minimist")(process.argv.slice(2));
var columnify = require("columnify");
// Constants
var FL_DIRECTORY = os.homedir() + "/.friendlog/";
var FRIENDS_PATH = FL_DIRECTORY + "friends.json";
var EVENTS_PATH = FL_DIRECTORY + "events.json";
var CONFIG_PATH = FL_DIRECTORY + "config.json";
var DATE_STORAGE_FORMAT = "YYYY-MM-DD";
var DATE_PARSE_FORMAT = "YYYY-MM-DD";
var DATE_DISPLAY_FORMAT = "YYYY-MM-DD";
var TODAY = moment().startOf("day");
var DEFAULT_NUM_DAYS = 10;
// Config
var config = loadConfigData();
// Setup
try {
loadFriendsData();
loadEventsData();
} catch (err) {
console.log("Setting up friendlog! :-)");
fs.existsSync(FL_DIRECTORY) || fs.mkdirSync(FL_DIRECTORY);
fs.existsSync(CONFIG_PATH) || ((config = JSON.stringify({"prettyPrintJson": true})) && writeConfigData());
fs.existsSync(FRIENDS_PATH) || writeFriendsData([]);
fs.existsSync(EVENTS_PATH) || writeEventsData([]);
}
// Data transfer
function loadFriendsData() {
var rawFriends = fs.readFileSync(FRIENDS_PATH);
return JSON.parse(rawFriends);
}
function loadEventsData() {
var rawEvents = fs.readFileSync(EVENTS_PATH);
return JSON.parse(rawEvents);
}
function writeFriendsData(friends) {
var rawNewFriends = stringify(friends);
fs.writeFileSync(FRIENDS_PATH, rawNewFriends);
}
function writeEventsData(events) {
var rawNewEvents = stringify(events);
fs.writeFileSync(EVENTS_PATH, rawNewEvents);
}
function writeConfigData() {
fs.writeFileSync(CONFIG_PATH, stringify(config));
}
function stringify(jsonObject) {
if (config.prettyPrintJson) {
return JSON.stringify(jsonObject, null, 2);
}
return JSON.stringify(jsonObject);
}
function loadConfigData() {
try {
var rawConfig = fs.readFileSync(CONFIG_PATH);
return JSON.parse(rawConfig);
} catch (err) {
return {};
}
}
// Helper functions
function ask(question, callback) {
var r = rl.createInterface({
input: process.stdin,
output: process.stdout});
r.question(question + "\n", function(answer) {
r.close();
callback(answer);
});
}
function ifYesAddFriend(answer, name, callback) {
if (-1 !== ["y", "yes", "Y", "YES"].indexOf(answer)) {
addFriend(name);
return callback();
}
fail("Friend " + name + " does not exist");
}
function getFriendByName(friends, name) {
return _.filter(friends, friend => friend.name === name);
}
function fail(msg) {
throw msg;
}
// Date-related helpers
function convertDaysToNumber(days) {
var numDays = +days;
if (!numDays || numDays <= 0) {
fail("Please use a positive number of days (specified '" + days + "')");
}
return days;
}
function parseDate(input) {
var date;
if (input === "today") {
date = moment();
} else if (input === "yesterday" || input === "yday") {
date = moment().subtract(1, "days");
} else if (parseWeekday(input)) {
date = parseWeekday(input);
} else {
date = moment(input, DATE_PARSE_FORMAT, true);
if (!date.isValid()) {
fail("Invalid date (specified '" + input + "')");
}
}
return date.startOf("day");
}
function parseWeekday(s) {
const weekdays = [
["sunday", "sun"],
["monday", "mon"],
["tuesday", "tue"],
["wednesday", "wed"],
["thursday", "thu"],
["friday", "fri"],
["saturday", "sat"],
];
for (let i = 1; i <= 7; i++) {
const d = moment(TODAY).subtract(i, "days");
if (weekdays[d.day()].indexOf(s) !== -1) {
return d;
}
}
return false;
}
// Public API
function listFriends() {
var NEW_INDICATOR = "new ";
var events = loadEventsData();
var friends = loadFriendsData();
var eventsByFriend = _.groupBy(events, "name");
var nextEventByFriend = _.map(friends, friend => {
var lastEvent = _.sortBy(eventsByFriend[friend.name], "date").reverse()[0];
return {
name: friend.name,
date: lastEvent ? moment(lastEvent.date).add(friend.interval, "days").format(DATE_DISPLAY_FORMAT) : NEW_INDICATOR
};
});
var newFriends = _.filter(nextEventByFriend, x => x.date === NEW_INDICATOR);
var nonNewFriends = _.filter(nextEventByFriend, x => x.date !== NEW_INDICATOR);
_.sortBy(nonNewFriends, "date").forEach(displayFriend);
_.sortBy(newFriends, "name").forEach(displayFriend);
}
function displayFriend(friend) {
const today = TODAY.format(DATE_DISPLAY_FORMAT); // TODO - what if date display format is not Year, Month, Day?
const dateColumn = friend.date + (friend.date < today ? "*" : " ");
console.log(dateColumn + " " + friend.name);
}
function addFriend(name, days) {
var friends = loadFriendsData();
var myFriend = getFriendByName(friends, name)[0];
if (myFriend) {
fail("Friend " + name + " already exists");
}
var numDays = days !== undefined ? convertDaysToNumber(days) : DEFAULT_NUM_DAYS;
friends.push({"name": name, "interval": numDays});
writeFriendsData(friends);
console.info("Added friend " + name);
}
function editFriend(name, days) {
var friends = loadFriendsData();
var myFriend = getFriendByName(friends, name)[0];
if (!myFriend) {
return ask(
"New friend " + name + "! Add them to friendlog? [yN]",
(answer) => ifYesAddFriend(answer, name, () => editFriend(name, days))
);
}
var numDays = convertDaysToNumber(days);
var oldNumDays = myFriend.interval;
myFriend.interval = numDays;
writeFriendsData(friends);
console.info("Friend " + name + " now at " + numDays + " days (was " + oldNumDays + ")");
}
function getFriendInfo(name) {
var friends = _.sortBy(loadFriendsData(), "name");
if (!name) {
return (argv.n || argv.names)
? friends.map(f => console.log(f.name))
: friends.map(f => console.log(f));
}
var myFriend = getFriendByName(friends, name)[0];
if (!myFriend) {
return ask(
"New friend " + name + "! Add them to friendlog? [yN]",
(answer) => ifYesAddFriend(answer, name, () => getFriendInfo(name))
);
}
console.log(myFriend);
}
function addEvent(friendName, date, memo) {
var friends = loadFriendsData();
var myFriend = getFriendByName(friends, friendName)[0];
if (!myFriend) {
return ask(
"New friend " + friendName + "! Add them to friendlog? [yN]",
(answer) => ifYesAddFriend(answer, friendName, () => addEvent(friendName, date, memo))
);
}
var events = loadEventsData();
var isoDate = parseDate(date).format(DATE_STORAGE_FORMAT);
events.push({"name": friendName, "date": isoDate, "memo": memo});
writeEventsData(events);
console.info("Added event '" + memo + "' on " + isoDate + " with " + friendName);
}
function showHelp() {
console.log("welcome to friendlog :-)");
console.log(" add [friend] [interval=10] " + "Adds [friend]. Events expected every [interval] days");
console.log(" list " + "Lists each friend and their next expected event");
console.log(" info [-n] " + "View info or just [-names] of each friend");
console.log(" info [friend] " + "View info about [friend]");
console.log(" edit [friend] [interval] " + "Edits [friend]'s expected [interval]");
console.log(" hangout [f] [d] [m] " + "Records event with [friend] on [date] with [memo]");
console.log(" history [-c] " + "See history, grouped by friend or [-chronological]");
console.log(" history [friend] " + "See history with specific friend");
console.log(" shortcuts " + "Print tab-completion and flh='fl hangout' shortcut");
}
function showHistory(friendName) {
var events = loadEventsData();
if (friendName) {
events = _.filter(events, e => e.name === friendName);
}
if (argv.c || argv.chronological) {
events = _.orderBy(events, ["date", "memo", "name"], ["desc", "asc", "asc"]);
} else {
events = _.orderBy(events, ["name", "date", "memo"], ["asc", "desc", "asc"]);
}
return console.log(columnify(events));
}
function printShortcuts() {
console.log("## start friendlog shortcuts");
console.log("alias flh=\"friendlog hangout\"");
console.log("source fl-completion-setup");
console.log("## end friendlog shortcuts");
}
// Default behavior
var callDefault = listFriends;
// figure out which command you're running
function main() {
var args = argv._;
if (0 === args.length) {
callDefault();
} else if (2 === args.length && "add" === args[0]) {
addFriend(args[1]);
} else if (3 === args.length && "add" === args[0]) {
addFriend(args[1], args[2]);
} else if (1 === args.length && "list" === args[0]) {
listFriends();
} else if (1 === args.length && "info" === args[0]) {
getFriendInfo();
} else if (2 === args.length && "info" === args[0]) {
getFriendInfo(args[1]);
} else if (3 === args.length && "edit" === args[0]) {
editFriend(args[1], args[2]);
} else if (1 === args.length && "history" === args[0]) {
showHistory();
} else if (2 === args.length && "history" === args[0]) {
showHistory(args[1]);
} else if (4 === args.length && "hangout" === args[0]) {
addEvent(args[1], args[2], args[3]);
} else if (1 === args.length && "shortcuts" === args[0]) {
printShortcuts();
} else {
showHelp();
}
}
main();