practicode 0.2.5

Offline-first programming language lessons and coding practice in a Rust terminal UI.
Documentation
use super::*;

pub fn localized(map: &HashMap<String, String>, lang: &str) -> String {
    let lang = normalize_ui_language(lang);
    map.get(lang.as_str())
        .or_else(|| map.get("en"))
        .or_else(|| map.get("ko"))
        .or_else(|| map.values().next())
        .cloned()
        .unwrap_or_default()
}
pub fn render_problem(problem: &Problem, ui_language: &str) -> String {
    let lang = normalize_ui_language(ui_language);
    let examples = problem
        .examples
        .iter()
        .enumerate()
        .map(|(index, case)| {
            format!(
                "### {} {}\n\n{}\n\n{}\n\n{}\n\n{}",
                ui_text(&lang, "example"),
                index + 1,
                ui_text(&lang, "input"),
                fenced_text(&case.input),
                ui_text(&lang, "output"),
                fenced_text(&case.output)
            )
        })
        .collect::<Vec<_>>()
        .join("\n\n");
    let number = problem
        .id
        .split_once('-')
        .map(|(number, _)| number)
        .unwrap_or(&problem.id);
    format!(
        "# {number}. {}\n\n{}: {}\n{}: {}\n\n{}\n\n## {}\n\n{}\n\n## {}\n\n{}\n\n## {}\n\n{}",
        localized(&problem.title, &lang),
        ui_text(&lang, "difficulty"),
        problem.difficulty,
        ui_text(&lang, "topics"),
        problem.topics.join(", "),
        localized(&problem.statement, &lang),
        ui_text(&lang, "input"),
        localized(&problem.input, &lang),
        ui_text(&lang, "output"),
        localized(&problem.output, &lang),
        ui_text(&lang, "examples"),
        examples
    )
}

pub fn render_problem_tui(problem: &Problem, ui_language: &str) -> String {
    let lang = normalize_ui_language(ui_language);
    let number = problem
        .id
        .split_once('-')
        .map(|(number, _)| number)
        .unwrap_or(&problem.id);
    let mut lines = vec![
        format!("{number}. {}", localized(&problem.title, &lang)),
        format!(
            "{}: {}    {}: {}",
            ui_text(&lang, "difficulty"),
            problem.difficulty,
            ui_text(&lang, "topics"),
            problem.topics.join(", ")
        ),
        String::new(),
        localized(&problem.statement, &lang),
    ];
    push_tui_section(
        &mut lines,
        ui_text(&lang, "input"),
        &localized(&problem.input, &lang),
    );
    push_tui_section(
        &mut lines,
        ui_text(&lang, "output"),
        &localized(&problem.output, &lang),
    );
    lines.push(String::new());
    lines.push(ui_text(&lang, "examples").to_string());
    for (index, case) in problem.examples.iter().enumerate() {
        lines.push(format!("  {} {}", ui_text(&lang, "example"), index + 1));
        lines.push(format!("    {}:", ui_text(&lang, "input")));
        push_case_text(&mut lines, &case.input, &lang);
        lines.push(format!("    {}:", ui_text(&lang, "output")));
        push_case_text(&mut lines, &case.output, &lang);
    }
    lines.join("\n").trim_end().to_string()
}

fn push_tui_section(lines: &mut Vec<String>, title: &str, body: &str) {
    lines.push(String::new());
    lines.push(title.to_string());
    for line in body.trim_end().lines() {
        lines.push(format!("  {line}"));
    }
}

fn push_case_text(lines: &mut Vec<String>, body: &str, ui_language: &str) {
    let body = body.trim_end();
    if body.is_empty() {
        lines.push(format!("      {}", ui_text(ui_language, "empty_value")));
    } else {
        for line in body.lines() {
            lines.push(format!("      {line}"));
        }
    }
}

pub fn fenced_text(value: &str) -> String {
    let mut body = value.to_string();
    if !body.ends_with('\n') {
        body.push('\n');
    }
    format!("```text\n{body}```")
}