From 4d31ea499e107dac688f05c6df422a964b6718e3 Mon Sep 17 00:00:00 2001 From: inaemin Date: Mon, 20 Oct 2025 23:43:37 +0900 Subject: [PATCH 1/7] =?UTF-8?q?docs:=20README=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EC=B2=B4=ED=81=AC=EB=A6=AC=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EB=A1=9C=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 13420b29..2c014820 100644 --- a/README.md +++ b/README.md @@ -1 +1,27 @@ -# javascript-calculator-precourse \ No newline at end of file +# javascript-calculator-precourse + +## 기능 목록 + +### 입력받기 + +- [] 입력받은 문자열에서 구분자를 기준으로 숫자를 추출한다. + +### 구분자 정하기 + +- [] 기본 구분자: 쉼표(`,`), 콜론(`:`) +- [] 커스텀 구분자: `//`와 `\\n` 사이에 위치하는 문자 + +### 검증하기 + +- [] 올바른 커스텀 구분자인지 검증한다(문자 길이 1 확인) +- [] 올바른 숫자 형식인지 검증한다(숫자만 허용) +- [] 양수인지 검사한다(음수 불허용) + +### 계산하기 + +- [] 추출한 숫자들을 모두 더한다. + +### 출력하기 + +- [] 계산 결과를 `결과 : {숫자}` 형식으로 출력한다. +- [] 에러 발생 시 `[ERROR] {에러메시지}` 형식으로 출력한다. From b180f512590bcc5d73902fc46c016d67b07b0493 Mon Sep 17 00:00:00 2001 From: inaemin Date: Mon, 20 Oct 2025 23:43:45 +0900 Subject: [PATCH 2/7] =?UTF-8?q?feat:=20=EA=B5=AC=EB=B6=84=EC=9E=90=20?= =?UTF-8?q?=ED=8C=8C=EC=8B=B1=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Separator.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/Separator.js diff --git a/src/Separator.js b/src/Separator.js new file mode 100644 index 00000000..9e0f2413 --- /dev/null +++ b/src/Separator.js @@ -0,0 +1,14 @@ +export class Separator { + static parse(rawInput) { + const separator = [",", ":"]; // 기본 구분자 + const customSeparators = [...rawInput.matchAll(/\/\/(.+?)\\n/g)].map( + (m) => m[1] + ); // 커스텀 구분자 + if (customSeparators.length) { + separator.push(...customSeparators); + } + // 숫자 부분 추출 + const numStrs = rawInput.replace(/\/\/(.+?)\\n/, ""); + return { separator, numStrs }; + } +} From c1d7a6efa8a070a42b68b51510d125e2460e7449 Mon Sep 17 00:00:00 2001 From: inaemin Date: Mon, 20 Oct 2025 23:43:51 +0900 Subject: [PATCH 3/7] =?UTF-8?q?feat:=20=EC=9E=85=EB=A0=A5=EA=B0=92=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Validator.js | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 src/Validator.js diff --git a/src/Validator.js b/src/Validator.js new file mode 100644 index 00000000..f6957e5c --- /dev/null +++ b/src/Validator.js @@ -0,0 +1,34 @@ +export class Validator { + // 유효한 구분자인지 검사 (문자인지 확인) + static validateSeparators(separator) { + if (!Array.isArray(separator) || separator.length === 0) { + throw new Error("[ERROR] 구분자는 하나 이상이여야 합니다."); + } + + separator.forEach((sep) => { + if (typeof sep !== "string" || sep.length !== 1) { + throw new Error("[ERROR] 각 구분자는 하나의 문자여야 합니다."); + } + }); + } + + // 유효한 숫자인지 검사 (숫자 형식 검증) + static validateNumbers(numStrs) { + numStrs.forEach((numStr) => { + const trimmed = numStr.trim(); + if (trimmed === "" || isNaN(Number(trimmed))) { + throw new Error("[ERROR] 올바른 숫자 형식이 아닙니다."); + } + }); + } + + // 양수인지 검사 + static validatePositiveNumbers(numStrs) { + numStrs.forEach((numStr) => { + const num = Number(numStr.trim()); + if (num < 0) { + throw new Error("[ERROR] 음수는 허용되지 않습니다."); + } + }); + } +} From f391adac580955ca4dcf8b4706e2d6f5cede0b09 Mon Sep 17 00:00:00 2001 From: inaemin Date: Mon, 20 Oct 2025 23:43:57 +0900 Subject: [PATCH 4/7] =?UTF-8?q?feat:=20=EC=9E=85=EB=A0=A5=EB=B0=9B?= =?UTF-8?q?=EA=B8=B0=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/InputManager.js | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/InputManager.js diff --git a/src/InputManager.js b/src/InputManager.js new file mode 100644 index 00000000..3c4ddea4 --- /dev/null +++ b/src/InputManager.js @@ -0,0 +1,9 @@ +import { MissionUtils } from "@woowacourse/mission-utils"; + +export class InputManager { + static async getInput() { + return await MissionUtils.Console.readLineAsync( + "덧셈할 문자열을 입력해 주세요.\n" + ); + } +} From 0d094c5535e23092a98300d1817b28175af2f5dc Mon Sep 17 00:00:00 2001 From: inaemin Date: Mon, 20 Oct 2025 23:44:02 +0900 Subject: [PATCH 5/7] =?UTF-8?q?feat:=20=EC=B6=9C=EB=A0=A5=ED=95=98?= =?UTF-8?q?=EA=B8=B0=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/OutputManager.js | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/OutputManager.js diff --git a/src/OutputManager.js b/src/OutputManager.js new file mode 100644 index 00000000..5c4c28df --- /dev/null +++ b/src/OutputManager.js @@ -0,0 +1,11 @@ +import { MissionUtils } from "@woowacourse/mission-utils"; + +export class OutputManager { + static print(result) { + MissionUtils.Console.print(`결과 : ${result}`); + } + + static errorPrint(errMsg) { + MissionUtils.Console.print(`${errMsg}`); + } +} From 71c62ec6b391a94add5fc52010dfab299e758d97 Mon Sep 17 00:00:00 2001 From: inaemin Date: Mon, 20 Oct 2025 23:44:07 +0900 Subject: [PATCH 6/7] =?UTF-8?q?feat:=20=EA=B3=84=EC=82=B0=EA=B8=B0=20?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/App.js b/src/App.js index 091aa0a5..5d6f3e28 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,31 @@ +import { InputManager } from "./InputManager.js"; +import { Separator } from "./Separator.js"; +import { Validator } from "./Validator.js"; +import { OutputManager } from "./OutputManager.js"; +import { MissionUtils } from "@woowacourse/mission-utils"; + class App { - async run() {} + async run() { + try { + const rawInput = await InputManager.getInput(); + const { separator, numStrs } = Separator.parse(rawInput); + Validator.validateSeparators(separator); + const escapedSeparators = separator.map((sep) => + sep.replace(/[\\^$.|?*+()[\]{}-]/g, "\\$&") + ); // 정규표현식 내에서 특수문자는 이스케이프되어야 함. + const regex = new RegExp(`[${escapedSeparators.join("")}]`); + const numbers = numStrs.split(regex).filter((n) => n.trim() !== ""); + Validator.validateNumbers(numbers); + Validator.validatePositiveNumbers(numbers); + const addResult = numbers + .map((s) => +s.trim()) + .reduce((r, n) => r + n, 0); + OutputManager.print(addResult); + } catch (err) { + OutputManager.errorPrint(err.message); + throw err; + } + } } export default App; From b83fd156e5a8d6bf30705991e56b167f09fd163e Mon Sep 17 00:00:00 2001 From: inaemin Date: Mon, 20 Oct 2025 23:45:09 +0900 Subject: [PATCH 7/7] =?UTF-8?q?docs:=20=EB=AA=A8=EB=93=A0=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=99=84=EB=A3=8C=EB=A1=9C=20=EC=B2=B4=ED=81=AC?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 2c014820..2a82f8ae 100644 --- a/README.md +++ b/README.md @@ -4,24 +4,24 @@ ### 입력받기 -- [] 입력받은 문자열에서 구분자를 기준으로 숫자를 추출한다. +- [x] 입력받은 문자열에서 구분자를 기준으로 숫자를 추출한다. ### 구분자 정하기 -- [] 기본 구분자: 쉼표(`,`), 콜론(`:`) -- [] 커스텀 구분자: `//`와 `\\n` 사이에 위치하는 문자 +- [x] 기본 구분자: 쉼표(`,`), 콜론(`:`) +- [x] 커스텀 구분자: `//`와 `\\n` 사이에 위치하는 문자 ### 검증하기 -- [] 올바른 커스텀 구분자인지 검증한다(문자 길이 1 확인) -- [] 올바른 숫자 형식인지 검증한다(숫자만 허용) -- [] 양수인지 검사한다(음수 불허용) +- [x] 올바른 커스텀 구분자인지 검증한다(문자 길이 1 확인) +- [x] 올바른 숫자 형식인지 검증한다(숫자만 허용) +- [x] 양수인지 검사한다(음수 불허용) ### 계산하기 -- [] 추출한 숫자들을 모두 더한다. +- [x] 추출한 숫자들을 모두 더한다. ### 출력하기 -- [] 계산 결과를 `결과 : {숫자}` 형식으로 출력한다. -- [] 에러 발생 시 `[ERROR] {에러메시지}` 형식으로 출력한다. +- [x] 계산 결과를 `결과 : {숫자}` 형식으로 출력한다. +- [x] 에러 발생 시 `[ERROR] {에러메시지}` 형식으로 출력한다.