이번 편의 목표: 제목과 연체 일수를 명령으로 받아 CSV에 남기고, 권마다 연체료를 출력합니다.

앞 편의 소유권, 구조체, Result, 벡터, 파일을 한 프로그램으로 모읍니다. 기록 파일 이름은 loans.csv입니다. 한 줄이 책 한 권이고, 제목과 일수를 쉼표로 구분합니다. 제목에는 쉼표를 넣지 않습니다.

src/main.rs를 다음으로 바꿉니다. 13편의 book.rs는 이 파일에 mod로 연결하지 않으면 실행에 포함되지 않습니다. 계산은 이 파일의 overdue_fee를 씁니다.

Rust
use std::env;
use std::fs;
use std::io;

struct Loan {
    title: String,
    days: i32,
}

fn overdue_fee(days: i32) -> i32 {
    if days <= 0 { 0 } else { days * 100 }
}

fn parse_loan(line: &str) -> Option<Loan> {
    let line = line.trim();
    if line.is_empty() {
        return None;
    }
    let mut parts = line.split(',');
    let title = parts.next()?.trim();
    let days: i32 = parts.next()?.trim().parse().ok()?;
    if title.is_empty() || days < 0 {
        return None;
    }
    Some(Loan {
        title: title.to_string(),
        days,
    })
}

fn load(path: &str) -> Vec<Loan> {
    let text = fs::read_to_string(path).unwrap_or_default();
    let mut loans = Vec::new();
    for line in text.lines() {
        if let Some(loan) = parse_loan(line) {
            loans.push(loan);
        }
    }
    loans
}

fn save(path: &str, loans: &[Loan]) -> io::Result<()> {
    let mut body = String::new();
    for loan in loans {
        body.push_str(&loan.title);
        body.push(',');
        body.push_str(&loan.days.to_string());
        body.push('\n');
    }
    fs::write(path, body)
}

fn main() -> io::Result<()> {
    let path = "loans.csv";
    let mut loans = load(path);
    let mut args = env::args().skip(1);
    match args.next().as_deref() {
        Some("add") => {
            let title = args.next().unwrap_or_default();
            let days: i32 = args.next().unwrap_or_default().parse().unwrap_or(-1);
            if title.is_empty() || days < 0 {
                println!("사용법: cargo run -- add 제목 연체일수");
                return Ok(());
            }
            loans.push(Loan { title, days });
            save(path, &loans)?;
            println!("기록했습니다.");
        }
        Some("list") | None => {
            if loans.is_empty() {
                println!("기록이 없습니다.");
            }
            for loan in &loans {
                println!("{} {}일 {}원", loan.title, loan.days, overdue_fee(loan.days));
            }
        }
        Some(_) => {
            println!("명령은 add 또는 list입니다.");
        }
    }
    Ok(())
}

파일이 없으면 load는 빈 목록으로 시작합니다. 잘못된 줄은 건너뜁니다. add는 목록 끝에 한 권을 붙인 뒤 파일 전체를 다시 씁니다. list와 인자가 없는 실행은 저장된 권을 출력합니다.

cargo run

기록이 없습니다.

cargo run -- add 생각나무 3

기록했습니다.

cargo run -- list

생각나무 3일 300원

cargo run -- 의 --는 그 뒤의 단어를 프로그램 인자로 넘기라는 표시입니다. add와 제목, 일수가 args로 들어옵니다. 일수가 정수가 아니거나 음수이면 사용법만 출력하고 파일은 바꾸지 않습니다.

직접 해 보기

cargo run -- add 도서관 2 를 실행한 뒤 list를 보세요. 둘째 줄은 도서관 2일 200원이어야 합니다.

해설

하루 100원이고 2일이므로 200원입니다. loans.csv에는 생각나무,3과 도서관,2가 각각 한 줄로 남습니다. 목록 순서는 추가한 순서입니다.