mapm-cli 6.1.0

The command-line implementation of mapm
use std::env;
use std::io::Write;
use std::process::{Command, Stdio};

use mapm::problem::Problem;

use ansi_term::Colour::*;
use ansi_term::Style;

pub fn problems_to_string(problems: &[Problem]) -> String {
    let mut problem_display = String::new();
    let color: bool = env::var("NO_COLOR").is_err();
    let g_bold_style: Option<Style> = Some(Green.bold());
    let b_style: Option<Style> = Some(Blue.normal());
    let b_bold_style: Option<Style> = Some(Blue.bold());
    let r_style: Option<Style> = Some(Red.normal());
    let r_bold_style: Option<Style> = Some(Red.bold());
    let mut append_str = |s: &str, sty_opt: Option<Style>| {
        if color {
            match sty_opt {
                Some(sty) => {
                    problem_display.push_str(&sty.paint(s).to_string());
                }
                None => {
                    problem_display.push_str(s);
                }
            }
        } else {
            problem_display.push_str(s);
        }
    };
    for (
        problem_idx,
        Problem {
            name,
            vars,
            solutions,
            choices,
        },
    ) in problems.iter().enumerate()
    {
        if problem_idx > 0 {
            append_str("\n\n", None);
        }
        append_str(&format!("-- {} --", name), g_bold_style);
        for (key, val) in vars {
            append_str("\n", None);
            append_str(key, b_style);
            append_str(": ", None);
            for (idx, string) in val.lines().enumerate() {
                if idx == 0 {
                    append_str(string, None);
                } else {
                    append_str("\n  ", None);
                    append_str(&str::repeat(" ", key.chars().count()), None);
                    append_str(string, None);
                }
            }
        }
        if let Some(choices) = choices {
            append_str("\n", None);
            append_str("choices", b_bold_style);
            append_str(": ", None);
            if choices.is_empty() {
                append_str("empty", r_bold_style);
            } else {
                for (idx, choice) in choices.iter().enumerate() {
                    append_str("\n  ", None);
                    append_str(&((idx + 1).to_string() + "."), r_style);
                    append_str(" ", None);
                    append_str(choice, None);
                }
            }
            append_str("\n", None);
        }
        if let Some(solutions) = solutions {
            append_str("\n", None);
            append_str("solutions", b_bold_style);
            append_str(": ", None);
            if solutions.is_empty() {
                append_str("empty", r_bold_style);
            } else {
                for (idx, solution) in solutions.iter().enumerate() {
                    append_str("\n  ", None);
                    append_str(&((idx + 1).to_string() + "."), r_style);
                    for (key_idx, (key, val)) in solution.iter().enumerate() {
                        // This part assumes that the solution count is exactly one digit.
                        // Which is a reasonable assumption to make, but perhaps it could be
                        // handled better.
                        if key_idx == 0 {
                            append_str(" ", None);
                        } else {
                            append_str("\n", None);
                            append_str(&str::repeat(" ", 5), None);
                        }
                        append_str(key, b_style);
                        append_str(": ", None);
                        for (idx, string) in val.lines().enumerate() {
                            if idx > 0 {
                                append_str("\n", None);
                                append_str(
                                    &str::repeat(
                                        " ",
                                        (idx + 1).to_string().len() + key.chars().count() + 6,
                                    ),
                                    None,
                                );
                            }
                            append_str(string, None);
                        }
                    }
                }
            }
        }
    }
    problem_display
}

pub fn display(string: &str) {
    match env::var("PAGER") {
        Ok(pager) => {
            let mut process = match Command::new(&pager).stdin(Stdio::piped()).spawn() {
                Err(msg) => panic!("Couldn't spawn {pager}: {msg}"),
                Ok(process) => process,
            };
            if let Err(e) = process.stdin.as_ref().unwrap().write_all(string.as_bytes()) {
                if e.kind() == std::io::ErrorKind::BrokenPipe {
                    if matches!(std::env::var("NO_BROKEN_PIPE"), Err(_)) {
                        println!("The pipe from stdin to your pager was broken.");
                        println!("This is nothing to be concerned about and is absolutely normal if you didn't load the entire file into your pager.");
                        println!("The error message is reproduced below:");
                        println!("\t{}", e);
                        println!("If you would like to never see this input again, set the `NO_BROKEN_PIPE` environment variable.");
                        println!("(It doesn't matter what value you give it.)");
                    }
                } else {
                    panic!("Couldn't write to `{pager}` stdin: {e}");
                }
            }
            process.wait().expect("`wait` failed");
        }
        Err(_) => {
            println!("{}", string);
        }
    }
}