124 lines
3.8 KiB
Rust
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)
|
|
}
|
|
}
|