add node.js cli to create cards, decks and files

This commit is contained in:
Niko Lockenvitz 2020-05-23 17:12:14 +02:00
parent 5873ea3af5
commit 43ee5d2c22
3 changed files with 292 additions and 0 deletions

240
cli/index.js Normal file
View file

@ -0,0 +1,240 @@
const fs = require("fs");
const readline = require('readline').createInterface({
input: process.stdin,
output: process.stdout
});
let curFilename;
let curFileContent;
let saveOnClose = false;
readline.on('close', async () => {
if (saveOnClose) await saveCurrentFile();
process.exit(0);
});
async function cli () {
let cmd;
while (true) {
cmd = await readCommand(`Commands: new file <filename>, edit file <filename>, exit`);
if (cmd.startsWith("new file ")) {
curFilename = cmd.substring("new file ".length);
if (!curFilename.endsWith(".json")) curFilename += ".json";
if (await doesFileAlreadyExist(curFilename)) {
console.log(`There is already a file ${curFilename}`);
} else {
curFileContent = { meta: {}, decks: {} };
await saveCurrentFile(false);
await cliEditFile(curFilename);
}
} else if (cmd.startsWith("edit file ")) {
const filename = cmd.substring("edit file ".length)
await cliEditFile(filename);
} else if (cmd === "exit") {
if (saveOnClose) await saveCurrentFile();
break;
} else {
console.log("Your command was not understood...");
}
saveOnClose = false;
}
readline.close();
}
cli();
async function cliEditFile (filename) {
saveOnClose = true;
curFilename = filename;
if (!curFilename.endsWith(".json")) curFilename += ".json";
curFileContent = await readJSONFromFile(curFilename);
console.log(`editing ${curFilename}`);
while (true) {
const cmd = await readCommand(`Commands: new deck <name>, list decks, edit deck <name>, delete deck <name>, show meta, meta <attr> <value>, whereami, save, quit`);
if (cmd.startsWith("new deck ")) {
let deckName = cmd.substring("new deck ".length);
if (curFileContent.decks[deckName] === undefined) {
curFileContent.decks[deckName] = {
meta: { deck_name: deckName, next_id: 0 },
cards: {}
}
await cliEditDeck(deckName);
} else {
console.log("There is already a deck with that name. You might want to edit it.");
}
} else if (cmd === "list decks") {
for (let deckName of Object.keys(curFileContent.decks)) {
console.log(deckName);
}
} else if (cmd.startsWith("edit deck ")) {
const deckName = cmd.substring("edit deck ".length);
if (deckName in curFileContent.decks) {
await cliEditDeck(deckName);
} else {
console.log("There is no deck with that name. You might want to create it.");
}
} else if (cmd.startsWith("delete deck ")) {
const deckName = cmd.substring("delete deck ".length);
const conf = await readCommand(`Do you really want to delete the deck ${deckName}? (y/n)`);
if (conf.toLowerCase() === "y") {
delete curFileContent.decks[deckName];
console.log(`Deleted deck ${deckName}`);
}
} else if (cmd === "show meta") {
for (let attr in curFileContent.meta) {
console.log(`${attr}: ${curFileContent.meta[attr]}`);
}
} else if (cmd.startsWith("meta ")) {
const attr = cmd.split(" ")[1];
const value = cmd.substring(`meta ${attr} `.length);
curFileContent.meta[attr] = value;
} else if (cmd === "whereami") {
console.log(`File: ${curFilename}`);
} else if (cmd === "save") {
await saveCurrentFile();
} else if (cmd === "quit") {
await saveCurrentFile();
break;
} else {
console.log("Your command was not understood...");
}
}
}
async function cliEditDeck (deckName) {
while (true) {
const cmd = await readCommand(`Commands: add cards, list cards, edit card <id>, delete card <id>, show meta, meta <attr> <value>, whereami, save, quit`);
if (cmd === "add cards") {
await cliAddCards(deckName);
} else if (cmd === "list cards") {
for (let cardId in curFileContent.decks[deckName].cards) {
const card = curFileContent.decks[deckName].cards[cardId];
console.log(`id: ${cardId} q: ${card.q} a: ${card.a}`)
}
} else if (cmd.startsWith("edit card ")) {
const cardId = cmd.substring("edit card ".length);
const card = curFileContent.decks[deckName].cards[cardId];
console.log(`q: ${card.q} a: ${card.a}`);
console.log("If you leave a field blank, it will not be changed");
const q = await readQA("Q");
if (q) card.q = q;
const a = await readQA("A");
if (a) card.a = a;
console.log(`q: ${card.q} a: ${card.a}`);
} else if (cmd.startsWith("delete card ")) {
const cardId = cmd.substring("delete card ".length);
const card = curFileContent.decks[deckName].cards[cardId];
console.log(`id: ${cardId} q: ${card.q} a: ${card.a}`);
const conf = await readCommand(`Do you really want to delete the card ${cardId}? (y/n)`);
if (conf.toLowerCase() === "y") {
delete curFileContent.decks[deckName].cards[cardId];
console.log(`Deleted card ${cardId}`);
}
} else if (cmd === "show meta") {
for (let attr in curFileContent.decks[deckName].meta) {
console.log(`${attr}: ${curFileContent.decks[deckName].meta[attr]}`);
}
} else if (cmd.startsWith("meta ")) {
const attr = cmd.split(" ")[1];
const value = cmd.substring(`meta ${attr} `.length);
curFileContent.decks[deckName].meta[attr] = value;
} else if (cmd === "whereami") {
console.log(`Deck: ${curFilename} > ${deckName}`);
} else if (cmd === "save") {
await saveCurrentFile();
} else if (cmd === "quit") {
await saveCurrentFile();
break;
} else {
console.log("Your command was not understood...");
}
}
}
async function cliAddCards (deckName) {
console.log("\nEnter questions and answers. When you leave the question blank, you can run commands: continue, save, quit");
while (true) {
console.log("");
const q = await readQA("Q");
if (q === "") {
const cmd = await readCommand(`Commands: continue, save, quit`);
if (cmd === "continue") {
continue;
} else if (cmd === "save") {
await saveCurrentFile();
continue;
} else if (cmd === "quit") {
await saveCurrentFile();
return;
} else {
console.log("Your command was not understood...");
continue;
}
} else if (["quit", "exit"].includes(q)) {
console.log("(If you want to quit, you need to leave the question blank.)");
}
const a = await readQA("A");
const id = addCardToDeck(deckName, q, a);
console.log(`id: ${id}\n`);
}
}
function addCardToDeck (deckName, q, a) {
const id = curFileContent.decks[deckName].meta.next_id;
curFileContent.decks[deckName].cards[id] = { q, a };
curFileContent.decks[deckName].meta.next_id = id + 1;
return id;
}
async function readCommand (question) {
return new Promise((resolve, reject) => {
readline.question(`\n${question}\n>> `, answer => {
resolve(answer);
});
});
}
async function readQA (text) {
return new Promise((resolve, reject) => {
readline.question(`${text}: `, input => {
resolve(input);
});
});
}
async function saveCurrentFile (log=true) {
await writeJSONToFile(curFilename, curFileContent);
if (log) console.log(`\nSaved ${curFilename}`);
}
async function doesFileAlreadyExist (filepath) {
return new Promise((resolve, reject) => {
fs.exists(filepath, (exists) => {
resolve(exists);
});
});
}
async function readJSONFromFile (filepath) {
return new Promise((resolve, reject) => {
fs.readFile(filepath, "utf8", function (err, data) {
if (err) {
reject(err);
} else {
resolve(JSON.parse(data));
}
});
});
}
async function writeJSONToFile (filepath, content) {
return new Promise((resolve, reject) => {
fs.writeFile(filepath, JSON.stringify(content, null, 2), "utf8", function (err) {
if (err) {
reject(err);
} else {
resolve();
}
});
});
}

13
cli/package.json Normal file
View file

@ -0,0 +1,13 @@
{
"name": "cli",
"version": "0.0.1",
"description": "generate files for ffc",
"main": "index.js",
"scripts": {
"cli": "node index.js",
"start": "node index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Niko Lockenvitz",
"license": "MIT"
}

39
cli/test.json Normal file
View file

@ -0,0 +1,39 @@
{
"meta": {
"author": "Niko Lockenvitz"
},
"decks": {
"d0": {
"meta": {
"deck_name": "d0",
"next_id": 2
},
"cards": {
"0": {
"q": "2+2?",
"a": "4"
},
"1": {
"q": "3*4*10?",
"a": "5!"
}
}
},
"d1": {
"meta": {
"deck_name": "d1",
"next_id": 3
},
"cards": {
"0": {
"q": "How many seconds in a day (on earth)?",
"a": "86400"
},
"2": {
"q": "Has the year 2000 been a leap year?",
"a": "yes (because each year divisible by 4 is a leap year (except the ones divisible by 100 (except the ones divisible by 400)))"
}
}
}
}
}