add node.js cli to create cards, decks and files
This commit is contained in:
parent
5873ea3af5
commit
43ee5d2c22
3 changed files with 292 additions and 0 deletions
240
cli/index.js
Normal file
240
cli/index.js
Normal 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
13
cli/package.json
Normal 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
39
cli/test.json
Normal 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)))"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue