perftree-cli 0.2.0

Command-line interface for perftree
use perftree::{Diff, State};
use std::env;
use std::io::{self, BufRead, Write};
use std::process::exit;
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};

fn usage() -> ! {
    eprintln!("Usage: perftree <script>");
    exit(1);
}

struct Prompt<R> {
    lines: std::io::Lines<R>,
}

impl<R> Prompt<R>
where
    R: BufRead,
{
    fn new(buf_read: R) -> Self {
        Self {
            lines: buf_read.lines(),
        }
    }

    fn prompt(&mut self, ps: &str) -> io::Result<Option<String>> {
        if atty::is(atty::Stream::Stdin) {
            if atty::is(atty::Stream::Stdout) {
                print!("{}", ps);
                io::stdout().flush()?;
            } else if atty::is(atty::Stream::Stderr) {
                eprint!("{}", ps);
                io::stderr().flush()?;
            }
        }
        self.lines.next().transpose()
    }
}

fn main() -> anyhow::Result<()> {
    let input = io::stdin();
    let mut prompt = Prompt::new(input.lock());
    let mut output = StandardStream::stdout(ColorChoice::Auto);

    let mut state = State::new(env::args().nth(1).unwrap_or_else(|| usage()))?;

    while let Some(line) = prompt.prompt("> ")? {
        let mut words = line.split_whitespace();
        let cmd = match words.next() {
            Some(word) => word,
            None => continue,
        };

        match cmd {
            "fen" => {
                let fen = words.collect::<Vec<_>>().join(" ");
                if fen.is_empty() {
                    println!("{}", state.fen());
                } else {
                    state.set_fen(fen);
                }
            }
            "moves" => {
                let moves = words.map(|s| s.to_string()).collect::<Vec<_>>();
                if moves.is_empty() {
                    println!("{}", state.moves().join(" "));
                } else {
                    state.set_moves(moves);
                }
            }
            "depth" => {
                if let Some(depth) = words.next() {
                    let depth = match depth.parse() {
                        Ok(x) => x,
                        Err(e) => {
                            eprintln!("cannot parse given depth: {}", e);
                            continue;
                        }
                    };
                    state.set_depth(depth);
                } else {
                    println!("{}", state.depth());
                }
            }
            "root" => {
                state.goto_root();
            }
            "parent" | "unmove" => {
                state.goto_parent();
            }
            "child" | "move" => {
                if let Some(move_) = words.next() {
                    state.goto_child(move_);
                } else {
                    eprintln!("missing argument, expected a child move");
                }
            }
            "diff" => match state.diff() {
                Ok(diff) => write_colored(&diff, &mut output)?,
                Err(e) => eprintln!("cannot compute diff: {:#}", e),
            },
            "exit" | "quit" => {
                break;
            }
            other => {
                eprintln!("unknown command {:?}", other);
            }
        }
    }
    Ok(())
}

pub fn write_colored<W>(diff: &Diff, mut write: W) -> io::Result<()>
where
    W: WriteColor,
{
    let mut min_width = 0;
    for &(lhs, rhs) in diff.child_count().values() {
        if let Some(lhs) = lhs {
            let digits = (lhs as f64).log10().ceil().max(0.0) as usize;
            min_width = min_width.max(digits);
        }
        if let Some(rhs) = rhs {
            let digits = (rhs as f64).log10().ceil().max(0.0) as usize;
            min_width = min_width.max(digits);
        }
    }

    for (move_, &(lhs, rhs)) in diff.child_count() {
        if lhs != rhs {
            write.set_color(ColorSpec::new().set_fg(Some(Color::Red)).set_bold(true))?;
        }
        write!(write, "{}", move_)?;
        if let Some(lhs) = lhs {
            write!(write, "  {:>width$}", lhs, width = min_width)?;
        } else {
            write!(write, "  {:>width$}", "", width = min_width)?;
        }
        if let Some(rhs) = rhs {
            write!(write, "  {:>width$}", rhs, width = min_width)?;
        } else {
            write!(write, "  {:>width$}", "", width = min_width)?;
        }
        writeln!(write)?;
        write.reset()?;
    }

    writeln!(write)?;
    let (lhs, rhs) = diff.total_count();
    if lhs != rhs {
        write.set_color(ColorSpec::new().set_fg(Some(Color::Red)).set_bold(true))?;
    }
    write!(write, "total  {}  {}", lhs, rhs)?;
    write.reset()?;
    writeln!(write)?;

    Ok(())
}