summaryrefslogtreecommitdiff
path: root/src/main.rs
blob: 3d691b088136be412c6a4efd8c56b2608f9d6fc4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
use anyhow::{bail, Context, Result};
use clap::{Parser, Subcommand};
use state_file::StateFile;
use std::path::Path;
use std::process::exit;
use verify::VerifyState;

mod consts;
mod embedded;
mod exercise;
mod init;
mod list;
mod run;
mod state_file;
mod verify;
mod watch;

use crate::consts::WELCOME;
use crate::embedded::{WriteStrategy, EMBEDDED_FILES};
use crate::exercise::{Exercise, ExerciseList};
use crate::run::run;
use crate::verify::verify;

/// Rustlings is a collection of small exercises to get you used to writing and reading Rust code
#[derive(Parser)]
#[command(version)]
struct Args {
    #[command(subcommand)]
    command: Option<Subcommands>,
}

#[derive(Subcommand)]
enum Subcommands {
    /// Initialize Rustlings
    Init,
    /// Verify all exercises according to the recommended order
    Verify,
    /// Same as just running `rustlings` without a subcommand.
    Watch,
    /// Run/Test a single exercise
    Run {
        /// The name of the exercise
        name: String,
    },
    /// Reset a single exercise
    Reset {
        /// The name of the exercise
        name: String,
    },
    /// Return a hint for the given exercise
    Hint {
        /// The name of the exercise
        name: String,
    },
    /// List the exercises available in Rustlings
    List,
}

fn main() -> Result<()> {
    let args = Args::parse();

    which::which("cargo").context(
        "Failed to find `cargo`.
Did you already install Rust?
Try running `cargo --version` to diagnose the problem.",
    )?;

    let exercises = ExerciseList::parse()?.exercises;

    if matches!(args.command, Some(Subcommands::Init)) {
        init::init_rustlings(&exercises).context("Initialization failed")?;
        println!(
            "\nDone initialization!\n
Run `cd rustlings` to go into the generated directory.
Then run `rustlings` for further instructions on getting started."
        );
        return Ok(());
    } else if !Path::new("exercises").is_dir() {
        println!(
            "
{WELCOME}

The `exercises` directory wasn't found in the current directory.
If you are just starting with Rustlings, run the command `rustlings init` to initialize it."
        );
        exit(1);
    }

    let mut state = StateFile::read_or_default(&exercises);

    match args.command {
        None | Some(Subcommands::Watch) => {
            watch::watch(&state, &exercises)?;
        }
        // `Init` is handled above.
        Some(Subcommands::Init) => (),
        Some(Subcommands::List) => {
            list::list(&mut state, &exercises)?;
        }
        Some(Subcommands::Run { name }) => {
            let exercise = find_exercise(&name, &exercises)?;
            run(exercise).unwrap_or_else(|_| exit(1));
        }
        Some(Subcommands::Reset { name }) => {
            let exercise = find_exercise(&name, &exercises)?;
            EMBEDDED_FILES
                .write_exercise_to_disk(&exercise.path, WriteStrategy::Overwrite)
                .with_context(|| format!("Failed to reset the exercise {exercise}"))?;
            println!("The file {} has been reset!", exercise.path.display());
        }
        Some(Subcommands::Hint { name }) => {
            let exercise = find_exercise(&name, &exercises)?;
            println!("{}", exercise.hint);
        }
        Some(Subcommands::Verify) => match verify(&exercises, 0)? {
            VerifyState::AllExercisesDone => println!("All exercises done!"),
            VerifyState::Failed(exercise) => bail!("Exercise {exercise} failed"),
        },
    }

    Ok(())
}

fn find_exercise<'a>(name: &str, exercises: &'a [Exercise]) -> Result<&'a Exercise> {
    if name == "next" {
        for exercise in exercises {
            if !exercise.looks_done()? {
                return Ok(exercise);
            }
        }

        println!("🎉 Congratulations! You have done all the exercises!");
        println!("🔚 There are no more exercises to do next!");
        exit(0);
    }

    exercises
        .iter()
        .find(|e| e.name == name)
        .with_context(|| format!("No exercise found for '{name}'!"))
}