Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

is #5

Open
wants to merge 34 commits into
base: 1_is
Choose a base branch
from
Open

is #5

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
0ecd7c4
feat: CMError 추가
vanism2091 Dec 19, 2022
8b10008
feat: IOError 추가
vanism2091 Dec 19, 2022
5fd3cd9
feat: IOManager - 사용자 입력 받기 기능 추가
vanism2091 Dec 19, 2022
b93cad9
feat: IOManager - 입력 validity 확인
vanism2091 Dec 19, 2022
4a25221
feat: IOManager - 콘솔에 출력 기능 추가
vanism2091 Dec 19, 2022
6a05462
feat: Info Message 추가
vanism2091 Dec 19, 2022
0a398b4
refactor: Info 데이터타입 enum으로 변경
vanism2091 Dec 19, 2022
9e2b5ec
feat: Status 추가
vanism2091 Dec 19, 2022
cb549a1
feat: Status - infoMessage 추가
vanism2091 Dec 19, 2022
062ea8a
feat: Status - Result Message 추가
vanism2091 Dec 19, 2022
9efef18
feat: Status - parse method 추가
vanism2091 Dec 19, 2022
7775bf7
feat: Student 클래스 추가
vanism2091 Dec 19, 2022
ccb8d7f
feat: Student - isCourseEmpty property 추가
vanism2091 Dec 19, 2022
eef683c
feat: Student - averageScore property 추가
vanism2091 Dec 19, 2022
b99677c
feat: Student - allScoresDescription property 추가
vanism2091 Dec 19, 2022
5c672d7
feat: Student - update method 추가
vanism2091 Dec 19, 2022
c8c86c9
feat: Student - remove(course:) method 추가
vanism2091 Dec 19, 2022
b0e4581
feat: StudentList 추가
vanism2091 Dec 19, 2022
e80397b
feat: Score structure 추가
vanism2091 Dec 19, 2022
6ac2785
feat: Score - description 추가
vanism2091 Dec 19, 2022
424fa1e
feat: CreditManager 추가
vanism2091 Dec 19, 2022
f63f0f7
feat: parse method 추가
vanism2091 Dec 19, 2022
b533e30
feat: start method 추가
vanism2091 Dec 19, 2022
0b69322
feat: CreditManager - Add Student method 추가
vanism2091 Dec 19, 2022
f3026d9
feat: CreditManager - Delete Student method 추가
vanism2091 Dec 19, 2022
c808263
feat: CreditManager - Add Score method 추가
vanism2091 Dec 19, 2022
e9dc48a
feat: CreditManager - Delete Score method 추가
vanism2091 Dec 19, 2022
4dc03ba
feat: CreditManager - Show(score:) method 추가
vanism2091 Dec 19, 2022
84f38ec
feat: CreditManager - saveData method 추가
vanism2091 Dec 19, 2022
7f3c646
feat: CreditManager - loadData method 추가
vanism2091 Dec 19, 2022
0bf362f
feat: CreditManager - doWith method 추가
vanism2091 Dec 19, 2022
aa8d401
feat: CreditManager - run method 추가
vanism2091 Dec 19, 2022
9bf6ac0
feat: main 작성
vanism2091 Dec 19, 2022
6761330
chore: main 작성자 변경
vanism2091 Dec 19, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions Git_ Exercise.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@

/* Begin PBXBuildFile section */
2768CA08294F453B00990B98 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2768CA07294F453B00990B98 /* main.swift */; };
40841A53294FFE1F00E540E4 /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40841A52294FFE1F00E540E4 /* Error.swift */; };
40841A55294FFF5400E540E4 /* IOManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40841A54294FFF5300E540E4 /* IOManager.swift */; };
40841A572950008400E540E4 /* Info.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40841A562950008400E540E4 /* Info.swift */; };
40841A592950022400E540E4 /* Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40841A582950022400E540E4 /* Status.swift */; };
40841A5B2950031B00E540E4 /* Student.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40841A5A2950031B00E540E4 /* Student.swift */; };
40841A5D295004B200E540E4 /* Score.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40841A5C295004B200E540E4 /* Score.swift */; };
40841A5F2950051200E540E4 /* CreditManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40841A5E2950051200E540E4 /* CreditManager.swift */; };
/* End PBXBuildFile section */

/* Begin PBXCopyFilesBuildPhase section */
Expand All @@ -25,6 +32,13 @@
/* Begin PBXFileReference section */
2768CA04294F453B00990B98 /* Git_ Exercise */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "Git_ Exercise"; sourceTree = BUILT_PRODUCTS_DIR; };
2768CA07294F453B00990B98 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
40841A52294FFE1F00E540E4 /* Error.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Error.swift; sourceTree = "<group>"; };
40841A54294FFF5300E540E4 /* IOManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IOManager.swift; sourceTree = "<group>"; };
40841A562950008400E540E4 /* Info.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Info.swift; sourceTree = "<group>"; };
40841A582950022400E540E4 /* Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Status.swift; sourceTree = "<group>"; };
40841A5A2950031B00E540E4 /* Student.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Student.swift; sourceTree = "<group>"; };
40841A5C295004B200E540E4 /* Score.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Score.swift; sourceTree = "<group>"; };
40841A5E2950051200E540E4 /* CreditManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreditManager.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -58,6 +72,13 @@
isa = PBXGroup;
children = (
2768CA07294F453B00990B98 /* main.swift */,
40841A52294FFE1F00E540E4 /* Error.swift */,
40841A54294FFF5300E540E4 /* IOManager.swift */,
40841A562950008400E540E4 /* Info.swift */,
40841A582950022400E540E4 /* Status.swift */,
40841A5A2950031B00E540E4 /* Student.swift */,
40841A5C295004B200E540E4 /* Score.swift */,
40841A5E2950051200E540E4 /* CreditManager.swift */,
);
path = "Git_ Exercise";
sourceTree = "<group>";
Expand Down Expand Up @@ -120,6 +141,13 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
40841A5D295004B200E540E4 /* Score.swift in Sources */,
40841A53294FFE1F00E540E4 /* Error.swift in Sources */,
40841A5B2950031B00E540E4 /* Student.swift in Sources */,
40841A592950022400E540E4 /* Status.swift in Sources */,
40841A5F2950051200E540E4 /* CreditManager.swift in Sources */,
40841A572950008400E540E4 /* Info.swift in Sources */,
40841A55294FFF5400E540E4 /* IOManager.swift in Sources */,
2768CA08294F453B00990B98 /* main.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
267 changes: 267 additions & 0 deletions Git_ Exercise/CreditManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
//
// CreditManager.swift
// Git_ Exercise
//
// Created by sei_dev on 12/19/22.
//

import Foundation

/// creditManager의 DataFile 관련 정보들
enum DataFile {
/// 데이터가 저장되고 로드하는 json 파일 이름
static let name = "data.json"
/// 파일이 저장될 Directory
static let directory: String = FileManager.default.currentDirectoryPath
static let pathString = DataFile.directory.appending(DataFile.name)
static var pathUrl: URL {
if #available(macOS 13.0, *) {
return URL(filePath: DataFile.directory.appending(DataFile.name))
} else {
return URL(fileURLWithPath: DataFile.directory.appending(DataFile.name))
}
}
}

final class CreditManager {
/// CreditManager - singleton
static let shared = CreditManager()
private init() { }

/// CreditManager의 현재 status
private var status: Status = .start
/// CreditManager에 등록된 Student의 List
private var students = [Student]()

/// 1. 저장된 데이터 로드
/// 2. 프로그램 상태에 맞는 info 메세지 출력
/// 3. input 처리 및 실행
/// 4. error발생 시 설명 출력
/// - 종료 error 시 데이터 저장 및 프로그램 종료
func run() {
loadData()
while true {
do {
IOManager.writeMessage(status.infoMessage)
let input = try IOManager.getInput(isStartStatus: status == .start)
let parsedInput = try parse(input: input)
try doWith(parsedInput)
} catch {
IOManager.writeMessage(error.localizedDescription, type: .error)
switch error {
case CMError.quitProgram:
saveData()
return
default:
status = .start
}
}
}
}

/// status 별 input parse
private func parse(input: String) throws -> ParsedInput {
guard let parsedInput = status.parse(input: input) else {
throw status == .start ? CMError.invalidStartInput : IOError.wrongInput
}
return parsedInput
}


/// 입력과 상태에 따라 CreditManager 작동 분기
///
/// - result Infomation이 있다면 출력한다
/// - 실행 후 credit manager의 status는 start로 변경
private func doWith(_ input: ParsedInput) throws {
do {
switch self.status {
case .start:
try start(input)
return
case .addStudent:
try add(student: input)
case .deleteStudent:
try delete(student: input)
case .addScore:
try add(score: input)
case .deleteScore:
try delete(score: input)
case .showScoreAverage:
try show(score: input)
case .exit:
return
}
} catch {
throw error
}
if let resInfo = status.resMessage(input: input) {
IOManager.writeMessage(resInfo, type: .reaction)
}
status = .start
}
}

//MARK: - Start

extension CreditManager {
/// 입력에 따라 credit manager의 status 변경
private func start(_ input: ParsedInput) throws {
guard let nextStatus = input[0] as? Status else {
throw CMError.invalidStartInput
}
guard nextStatus != .exit else {
throw CMError.quitProgram
}
guard [Status.start, Status.addStudent].contains(nextStatus) || false == students.isEmpty else {
throw CMError.emptyStudents
}
self.status = nextStatus
}
}

//MARK: - AddStudent

extension CreditManager {
/// 입력받은 학생을 students에 추가
///
/// Error occurs when
/// - 입력받은 학생이 이미 존재
private func add(student input: ParsedInput) throws {
guard let name = input[0] as? String else {
throw IOError.wrongInput
}
guard false == exists(student: name) else {
throw CMError.studentAleadyExists(name: name)
}
let student = Student(name: name)
students.append(student)
}

/// 학생이 등록되어 있다면 true
private func exists(student name: String) -> Bool {
return students.contains { $0.name == name }
}
}

//MARK: - Delete Student

extension CreditManager {
/// 입력받은 학생을 students에서 삭제
///
/// Error occurs when
/// - 등록된 학생이 0명
/// - 입력받은 학생이 존재하지 않음
private func delete(student input: ParsedInput) throws {
guard false == students.isEmpty else {
throw CMError.emptyStudents
}
guard let name = input[0] as? String else {
throw IOError.wrongInput
}
guard exists(student: name) else {
throw CMError.studentNotFound(name: name)
}
students.removeAll { $0.name == name }
}
}


//MARK: - Add Score

extension CreditManager {
/// 등록된 학생에 course, score 정보 추가
///
/// Error occurs when
/// - 입력받은 학생이 존재하지 않음
private func add(score input: ParsedInput) throws {
guard let name = input[0] as? String,
let course = input[1] as? String,
let score = input[2] as? Score else {
throw IOError.wrongInput
}
guard let student = students.first(where: { $0.name == name}) else {
throw CMError.studentNotFound(name: name)
}
student.update(course: course, score: score)
}
}

//MARK: - Delete Score

extension CreditManager {
/// 등록된 학생의 course 정보 삭제
///
/// Error occurs when
/// - 입력받은 학생이 존재하지 않음
/// - 학생 정보에 해당 과목이 등록되어 있지 않음
private func delete(score input: ParsedInput) throws {
guard let name = input[0] as? String,
let course = input[1] as? String else {
throw IOError.wrongInput
}
guard let student = students.first(where: { $0.name == name}) else {
throw CMError.studentNotFound(name: name)
}
guard student.remove(course: course) != nil else {
throw CMError.courseNotFound(name: name, course: course)
}
}
}

//MARK: - Show Average Score

extension CreditManager {
/// 등록된 학생의 전체 과목과 평점 확인
///
/// Error occurs when
/// - 입력받은 학생이 존재하지 않음
/// - 학생 정보에 등록된 과목이 없음
private func show(score input: ParsedInput) throws {
guard let name = input[0] as? String else {
throw IOError.wrongInput
}
guard let student = students.first(where: { $0.name == name}) else {
throw CMError.studentNotFound(name: name)
}
guard false == student.scores.isEmpty else {
throw CMError.emptyCourse(name: name)
}
IOManager.writeMessage(student.allScoresDescription)
}
}


extension CreditManager {
/// students property를 json 파일에 저장
private func saveData() {
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
do {
let data = try encoder.encode(StudentList(students: students))

if false == FileManager.default.fileExists(atPath: DataFile.pathString) {
FileManager.default.createFile(atPath: DataFile.pathString, contents: nil)
}

try data.write(to: DataFile.pathUrl)
let names = students.map {$0.name}

IOManager.writeMessage(Info.Data.saved(names: names), type: .reaction)
} catch {
IOManager.writeMessage(error.localizedDescription, type: .error)
}
}

/// json 파일 데이터를 students property에 불러오기
private func loadData() {
guard let data = try? Data(contentsOf: DataFile.pathUrl),
let studentList = try? JSONDecoder().decode(StudentList.self, from: data) else {
IOManager.writeMessage(Info.Warning.failedToLoadData, type: .error)
return
}
students = studentList.students

let names = students.map {$0.name}
IOManager.writeMessage(Info.Data.loaded(names: names), type: .reaction)
}
}
56 changes: 56 additions & 0 deletions Git_ Exercise/Error.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//
// Error.swift
// Git_ Exercise
//
// Created by sei_dev on 12/19/22.
//

import Foundation

//MARK: - CMError

enum CMError: Error {
case invalidStartInput
case studentAleadyExists(name: String)
case studentNotFound(name: String)
case emptyCourse(name: String)
case courseNotFound(name: String, course:String)
case emptyStudents
case quitProgram
}

extension CMError: LocalizedError {
public var errorDescription: String? {
switch self {
case .invalidStartInput:
return "입력이 잘못되었습니다. 1~5 사이의 숫자 혹은 X를 입력해주세요."
case let .studentAleadyExists(name):
return "\(name): 이미 존재하는 학생입니다. 추가하지 않습니다."
case let .studentNotFound(name):
return "\(name) 학생을 찾지 못했습니다."
case let .courseNotFound(name, course):
return "\(name) - \(course): 등록되지 않은 과목입니다."
case let .emptyCourse(name):
return "\(name): 등록된 과목이 없습니다."
case .emptyStudents:
return "등록된 학생이 존재하지 않습니다."
case .quitProgram:
return "프로그램을 종료합니다..."
}
}
}

//MARK: - IOError

enum IOError: Error {
case wrongInput
}

extension IOError: LocalizedError {
public var errorDescription: String? {
switch self {
case .wrongInput:
return "입력이 잘못되었습니다. 다시 확인해주세요."
}
}
}
Loading