diff --git a/README.md b/README.md deleted file mode 100644 index d86263b..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# todo -레벨2 diff --git a/class/app.js b/class/app.js new file mode 100644 index 0000000..2d67124 --- /dev/null +++ b/class/app.js @@ -0,0 +1,55 @@ +const readLine = require('readline').createInterface( { + input:process.stdin, + output:process.stdout, +}); +const CommandParser = require('./commandParser.js'); +const Utils = require('./utils.js'); +const Instruction = require('./instruction.js'); +const CustomException = require('./customException.js'); + +const RunTodoApp = class { + constructor() { + this.cmdArr = ['show','delete','update','add','undo','redo']; + this.commandParser = new CommandParser(); + this.utils = new Utils(); + this.instruction = new Instruction(); + this.customException = new CustomException(); + } + + runProgram (readline) { + readline.setPrompt('명령하세요: '); + readline.prompt(); + readline.on('line', (userInput) => { + + try { + + this.customException.missingSeperatorException(userInput); + const cmdList = this.commandParser.getCmdList(userInput); + + this.customException.CommandMissingException(cmdList[0], this.cmdArr); + this.commandParser.executeCmd(cmdList); + + this.utils.delay(0) + .then(() => {if (cmdList[0] === 'update') return this.utils.delay(3500);}) + .then(() => {return this.utils.delay(1000);}) + .then(() => {if (cmdList[0] !== 'show') this.instruction.show('all');}) + .catch(function(e) {console.log(e);}) + .then(() => {readLine.prompt();}); + + } catch(e) { + console.error(e.message); + readLine.prompt(); + } + + }).on('close', () => { + console.log("프로그램을 종료합니다."); + process.exit(); + }); + } + +}; + +const run = (() => { + const runTodoApp = new RunTodoApp(); + runTodoApp.runProgram(readLine); +})(); \ No newline at end of file diff --git a/class/commandParser.js b/class/commandParser.js new file mode 100644 index 0000000..8c4b88e --- /dev/null +++ b/class/commandParser.js @@ -0,0 +1,42 @@ +const Instruction = require('./instruction.js'); +const CustomException = require('./customException.js'); + +const CommandParser = class { + constructor() { + this.instruction = new Instruction(); + this.customException = new CustomException(); + } + + getCmdList(input) { + const regexp = /[^\$]+|undo|redo/g; + return input.match(regexp); + } + + executeCmd(command) { + try { + + if (command.length === 1) { + this.instruction[command[0]](); + } else if (command.length === 2) { + this.instruction[command[0]](command[1]); + } else if (command.length === 3) { + this.instruction[command[0]](command[1], command[2]); + } else { + this.customException.CommandMissingException(); + } + + } catch (e) { + console.error(e.message); + return; + } + + } + + isValidCommand(command, arr) { + let result = false; + if (arr.includes(command)) result = true; + return result; + } +}; + +module.exports = CommandParser; \ No newline at end of file diff --git a/class/customException.js b/class/customException.js new file mode 100644 index 0000000..84db372 --- /dev/null +++ b/class/customException.js @@ -0,0 +1,34 @@ +const CustomException = class { + missingSeperatorException (input) { + if (!this.isValidSeperator(input)) throw new Error("구분자 $가 존재하지 않습니다"); + } + + notExistIdException () { + throw new Error(`찾으시는 id가 존재하지 않습니다`); + } + + sameStatusException () { + throw new Error(`같은 상태로 업데이트 할 수 없습니다`); + } + + CommandMissingException (command, arr) { + if (!this.isValidCommand(command, arr)) + throw new Error(`올바른 명령어가 아닙니다`); + } + + isValidSeperator (input) { + let result = true; + const regexp = /\$|undo|redo/g; + if (input.match(regexp) == null) result = false; + + return result; + } + + isValidCommand(command, arr) { + let result = false; + if (arr.includes(command)) result = true; + return result; + } +}; + +module.exports = CustomException; \ No newline at end of file diff --git a/class/instruction.js b/class/instruction.js new file mode 100644 index 0000000..603f009 --- /dev/null +++ b/class/instruction.js @@ -0,0 +1,183 @@ +const originData = require('./todosdata.json'); +const convertedData = JSON.parse(JSON.stringify(originData)).data; +const Utils = require('./utils.js'); +const CustomException = require('./customException.js'); + +const Instruction = class { + constructor() { + this.utils = new Utils(); + this.customException = new CustomException(); + this.minIdNum = 1; + this.maxIdNum = 99999; + this.commandHistory = { + historyArr : [], + pointer : -1, + }; + } + + everyStatus (convertedData) { + let [numOfTodos, numOfDoings, numOfDones] = [0,0,0]; + convertedData.forEach((value) => { + if (value.status === 'todo') numOfTodos++; + else if (value.status === 'doing') numOfDoings++; + else if (value.status === 'done') numOfDones++; + }); + console.log(`현재상태 : todo: ${numOfTodos}개, doing: ${numOfDoings}개, done: ${numOfDones}개`); + } + + singleStatus (convertedData, status) { + const tasks = this.utils.getArrByCondition(convertedData, (val) => { return (status === val.status);}); + let message = `${status}리스트 총 ${tasks.length}건 : `; + tasks.forEach( obj => { + message += `'${obj.name}, ${obj.id}번,' `; + }); + console.log(message); + } + + show(status) { + const statusArr = ['all', 'todo', 'doing', 'done']; + if (status === 'all') { + this.everyStatus(convertedData); + } else if (statusArr.includes(status)) { + this.singleStatus(convertedData, status); + } + } + + add(name, tags) { + const id = this.utils.getRandomId(this.maxIdNum, this.minIdNum); + let obj = { + name, + tags, + status: 'todo', + id, + }; + convertedData.push(obj); + + if (arguments.length !== 3) { + this.createHistoryObj('add' ,obj); + } + + const message = `${obj.name} 1개가 추가됐습니다.(id : ${obj.id})`; + console.log(message); + } + + delete(id) { + const targetObj = this.utils.getArrByCondition(convertedData, (val) => { return id == val.id;})[0]; + try { + if (!targetObj) this.customException.notExistIdException(); + } catch (e) { + console.error(e.message); + return; + } + convertedData.splice(convertedData.indexOf(targetObj), 1); + + if (arguments.length == 1) { + this.createHistoryObj('delete', obj); + } + let message = `${targetObj.name}이 ${targetObj.status}에서 삭제되었습니다.`; + console.log(message); + } + + update(id, status) { + const targetObj = this.utils.getArrByCondition(convertedData, (val) => { return id == val.id;})[0]; + + try { + if (!targetObj) this.customException.notExistIdException(); + if (targetObj.status === status) this.customException.sameStatusException(); + } catch (e) { + console.error(e.message); + return; + } + + const preObj = this.utils.deepCopy(targetObj); + targetObj.status = status; + const nextObj = this.utils.deepCopy(targetObj); + + if (arguments.length === 2) { + this.createHistoryObj('update', preObj, nextObj); + } + + const message = `${targetObj.name}가 ${targetObj.status}로 상태가 변경되었습니다`; + setTimeout(() => { + console.log(message); + }, 3000); + } + + pushObj(arr, obj) { + if (arr.length === 3) { + arr.shift(); + arr.push(obj); + } else { + arr.push(obj); + } + } + + createHistoryObj(command, ...obj) { + let historyObj = {}; + + if (arguments.length == 3) { // update + historyObj = { + cmd: command, + pre: obj[0], + next: obj[1] + }; + + } else if (arguments.length == 2) { // add, delete + historyObj = { + cmd: command, + pre: obj[0] + }; + } + + this.pushObj(this.commandHistory.historyArr, historyObj); + this.commandHistory.pointer++; + } + + undo() { + const targetObject = this.commandHistory.historyArr[this.commandHistory.pointer]; + const [command, preObj, checkSum] = [targetObject.cmd, targetObject.pre, true]; + + let message = ``; + + if (command === 'add') { + this.delete(preObj.id, checkSum); + message += `${preObj.id}번 항목 '${preObj.name}'가 ${preObj.status} 상태에서 삭제됐습니다`; + + } else if (command === 'delete') { + convertedData.push(preObj); + message += `${preObj.id}번 항목 '${preObj.name}'가 삭제에서 ${preObj.status} 상태로 변경됐습니다`; + + } else if (command === 'update') { + this.update(preObj.id, preObj.status, checkSum); + } + + console.log(message); + + if (this.commandHistory.pointer > -1) this.commandHistory.pointer--; + } + + redo() { + if (this.commandHistory.pointer < 2) this.commandHistory.pointer++; + + const targetObject = this.commandHistory.historyArr[this.commandHistory.pointer]; + const [command, preObj, nextObj, checkSum] = [targetObject.cmd, targetObject.pre, targetObject.next, true]; + + let message = ``; + + if (command === 'add') { + convertedData.push(preObj); + message += `${preObj.id}번 항목 '${preObj.name}'가 ${preObj.status}에 추가되었습니다`; + + } else if (command === 'delete') { + this.delete(preObj.id, checkSum); + message += `${preObj.id}번 항목 '${preObj.name}'가 ${preObj.status} 상태에서 삭제됐습니다`; + + } else if (command === 'update') { + this.update(nextObj.id, nextObj.status, checkSum); + } + console.log(message); + } + +}; + +module.exports = Instruction; \ No newline at end of file diff --git a/class/todosdata.json b/class/todosdata.json new file mode 100644 index 0000000..1734fe5 --- /dev/null +++ b/class/todosdata.json @@ -0,0 +1,41 @@ +{ + "result" : true, + "data": [ + { + "name" : "자바스크립트 공부하기", + "tags" : ["programming", "javascript"], + "status" : "todo", + "id" : 12123123 + }, + { + "name" : "그림 그리기", + "tags" : ["picture", "favorite"], + "status" : "doing", + "id" : 123 + }, + { + "name" : "파이썬 공부하기", + "tags" : ["programming", "javascript"], + "status" : "done", + "id" : 213232 + }, + { + "name" : "자바 공부하기", + "tags" : ["programming", "java"], + "status" : "todo", + "id" : 123345 + }, + { + "name" : "음악 듣기", + "tags" : ["music", "favorite"], + "status" : "doing", + "id" : 12123 + }, + { + "name" : "정규식 공부하기", + "tags" : ["programming", "RegExp"], + "status" : "done", + "id" : 1234 + } + ] +} diff --git a/class/utils.js b/class/utils.js new file mode 100644 index 0000000..4645791 --- /dev/null +++ b/class/utils.js @@ -0,0 +1,31 @@ +const Utils = class { + + getArrByCondition (arr, condition) { + return arr.reduce((acc, val) => { + if (condition(val)) acc.push(val); + return acc; + }, []); + } + + getRandomId (max, min) { + return Math.floor(Math.random() * (max-min)) + 1; + } + + delay (time) { + return new Promise(function(resolve, reject){ + setTimeout(resolve, time); + }); + } + + deepCopy (obj) { + if (obj === null || typeof(obj) !== "object") return obj; + + let copy = {}; + for(let key in obj) { + copy[key] = this.deepCopy(obj[key]); + } + return copy; + } +}; + +module.exports = Utils; \ No newline at end of file