captain/src/main.rs
2023-04-17 19:37:00 +01:00

124 lines
3.8 KiB
Rust

use clap::{Parser, Subcommand};
use styles::Style;
use std::{env, process::Command};
use text_io::read;
mod config;
mod state;
mod styles;
#[derive(Parser)]
#[command(version, about, long_about = None)]
struct Args {
#[command(subcommand)]
command: Option<Commands>
}
#[derive(Subcommand)]
enum Commands {
/// Print the current problem
View,
/// Print the current problem's hint
Hint,
/// Check the solution of, or answer the current problem
Check,
/// Reset progress to the first problem
Reset
}
fn print_problem(config: config::Config, state: state::State) {
let problem = config.problems.get(state.problem_index).expect("Should be able to get current problem");
let name = env::args().next().unwrap_or_else(|| "captain".to_owned());
println!("{}\n{}\n\n{}",
Style::Header.as_style().paint(format!("Problem {}:", state.problem_index)),
problem.text,
Style::Hint.as_style().paint(format!("Run `{} check` once complete", name)))
}
fn print_hint(config: config::Config, state: state::State) {
let problem = config.problems.get(state.problem_index).expect("Should be able to get current problem");
let name = env::args().next().unwrap_or_else(|| "captain".to_owned());
match problem.hint.clone() {
Some(hint) => println!("{}\n{}\n\n{}",
Style::Header.as_style().paint(format!("Problem {} (Hint):", state.problem_index)),
hint,
Style::Hint.as_style().paint(format!("Run `{} check` once complete", name))),
None => println!("{}", Style::Warning.as_style().paint("There is no hint available for this problem"))
}
}
fn reset_state(mut state: state::State) {
state.problem_index = 0;
state.save();
println!("{}", Style::Good.as_style().paint("State has been reset!"))
}
fn run_command(cmd: String) -> Result<std::process::Output, std::io::Error> {
Command::new("bash").arg("-c").arg(cmd).output()
}
fn ask(expected: String) -> bool {
const PROMPT: &str = "What is the answer? ";
for _ in 1..4 {
print!("{}", Style::Prompt.as_style().paint(PROMPT));
let actual: String = read!("{}\n");
if expected.trim() == actual.trim() {
return true;
}
}
false
}
fn check(config: config::Config, mut state: state::State) {
let problem = config.problems.get(state.problem_index).expect("Should be able to get current problem");
println!("{}\n{}\n",
Style::Header.as_style().paint(format!("Problem {}:", state.problem_index)),
problem.text);
let ok = match problem.check_type.as_str() {
"cmd" => run_command(problem.check.clone()).expect("").status.success(),
"ask_cmd" => {
let cmd = run_command(problem.check.clone()).expect("Should be able to run check command successfully");
let expected = std::str::from_utf8(&cmd.stdout).expect("Should be able to parse check command output");
ask(expected.to_owned())
},
"ask" => {
ask(problem.check.clone())
}
_ => panic!("Check type {} is not implemented!", problem.check_type)
};
if !ok {
println!("{}", Style::Bad.as_style().paint("That's not quite right, try again."));
return;
}
if state.problem_index + 1 >= config.problems.len() {
println!("{}", Style::Good.as_style().paint(config.success_message));
return;
}
let name = env::args().next().unwrap_or_else(|| "captain".to_owned());
println!("{}", Style::Good.as_style().paint(format!("Great! Run `{}` for the next problem.", name)));
state.problem_index += 1;
state.save();
}
fn main() {
let args = Args::parse();
let config = config::Config::load();
let state = state::State::load();
match &args.command {
Some(Commands::View) | None => print_problem(config, state),
Some(Commands::Hint) => print_hint(config, state),
Some(Commands::Reset) => reset_state(state),
Some(Commands::Check) => check(config, state)
}
}