2024-04-16 02:22:54 +03:00
|
|
|
use anyhow::{bail, Context, Result};
|
2024-04-15 04:36:12 +03:00
|
|
|
use app_state::StateFileStatus;
|
2023-08-26 00:18:01 +03:00
|
|
|
use clap::{Parser, Subcommand};
|
2024-04-14 17:03:49 +03:00
|
|
|
use std::{
|
2024-04-30 02:41:08 +03:00
|
|
|
io::{self, BufRead, StdoutLock, Write},
|
2024-04-27 18:31:51 +03:00
|
|
|
path::Path,
|
|
|
|
process::exit,
|
2024-04-14 17:03:49 +03:00
|
|
|
};
|
2019-01-09 22:33:43 +03:00
|
|
|
|
2024-04-18 12:31:08 +03:00
|
|
|
use self::{app_state::AppState, dev::DevCommands, info_file::InfoFile, watch::WatchExit};
|
|
|
|
|
2024-04-11 03:51:02 +03:00
|
|
|
mod app_state;
|
2024-04-21 21:22:01 +03:00
|
|
|
mod cargo_toml;
|
2024-04-27 05:14:59 +03:00
|
|
|
mod cmd;
|
2024-04-16 00:54:57 +03:00
|
|
|
mod dev;
|
2024-03-28 23:06:36 +03:00
|
|
|
mod embedded;
|
2019-04-11 23:41:24 +03:00
|
|
|
mod exercise;
|
2024-04-14 02:15:43 +03:00
|
|
|
mod info_file;
|
2024-03-29 03:29:41 +03:00
|
|
|
mod init;
|
2024-04-07 04:03:37 +03:00
|
|
|
mod list;
|
2024-04-09 20:37:39 +03:00
|
|
|
mod progress_bar;
|
2019-01-09 22:33:43 +03:00
|
|
|
mod run;
|
2024-04-24 03:52:30 +03:00
|
|
|
mod terminal_link;
|
2024-04-07 02:17:53 +03:00
|
|
|
mod watch;
|
2018-05-14 19:41:58 +03:00
|
|
|
|
2024-04-16 02:22:54 +03:00
|
|
|
const CURRENT_FORMAT_VERSION: u8 = 1;
|
2024-04-21 20:26:19 +03:00
|
|
|
const DEBUG_PROFILE: bool = {
|
2024-04-17 16:55:50 +03:00
|
|
|
#[allow(unused_assignments, unused_mut)]
|
|
|
|
let mut debug_profile = false;
|
|
|
|
|
|
|
|
#[cfg(debug_assertions)]
|
|
|
|
{
|
|
|
|
debug_profile = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
debug_profile
|
|
|
|
};
|
2024-04-16 02:22:54 +03:00
|
|
|
|
2024-04-29 18:01:47 +03:00
|
|
|
// The current directory is the official Rustligns repository.
|
|
|
|
fn in_official_repo() -> bool {
|
|
|
|
Path::new("dev/rustlings-repo.txt").exists()
|
|
|
|
}
|
|
|
|
|
2024-04-30 02:41:08 +03:00
|
|
|
fn clear_terminal(stdout: &mut StdoutLock) -> io::Result<()> {
|
|
|
|
stdout.write_all(b"\x1b[H\x1b[2J\x1b[3J")
|
|
|
|
}
|
|
|
|
|
2024-04-30 02:46:57 +03:00
|
|
|
fn press_enter_prompt() -> io::Result<()> {
|
|
|
|
io::stdin().lock().read_until(b'\n', &mut Vec::new())?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
feat: Replace clap with argh
I’ve been wanting to do this for a while, but always procrastinated on it. We’ve been using Clap since the 2.0 rewrite, but Clap is known to be a fairly heavy library. Since Rustlings is usually peoples’ first contact with a Rust compilation, I think it’s in our best interests that this complation is as fast as possible. In effect, replacing Clap with the smaller, structopt-style `argh` reduces the amount of crates needing to be compiled from 82 to 60.
I also think this makes the code way easier to read, we don’t need to use Clap’s methods anymore, but can switch over to using pure Rust methods, e.g., switches are booleans, options are Option<String>s or the like, and subcommands are just structs.
2021-04-20 13:46:49 +03:00
|
|
|
/// Rustlings is a collection of small exercises to get you used to writing and reading Rust code
|
2023-08-26 00:18:01 +03:00
|
|
|
#[derive(Parser)]
|
|
|
|
#[command(version)]
|
feat: Replace clap with argh
I’ve been wanting to do this for a while, but always procrastinated on it. We’ve been using Clap since the 2.0 rewrite, but Clap is known to be a fairly heavy library. Since Rustlings is usually peoples’ first contact with a Rust compilation, I think it’s in our best interests that this complation is as fast as possible. In effect, replacing Clap with the smaller, structopt-style `argh` reduces the amount of crates needing to be compiled from 82 to 60.
I also think this makes the code way easier to read, we don’t need to use Clap’s methods anymore, but can switch over to using pure Rust methods, e.g., switches are booleans, options are Option<String>s or the like, and subcommands are just structs.
2021-04-20 13:46:49 +03:00
|
|
|
struct Args {
|
2023-08-26 00:18:01 +03:00
|
|
|
#[command(subcommand)]
|
|
|
|
command: Option<Subcommands>,
|
2024-05-13 03:37:32 +03:00
|
|
|
/// Manually run the current exercise using `r` in the watch mode.
|
2024-04-14 18:10:53 +03:00
|
|
|
/// Only use this if Rustlings fails to detect exercise file changes.
|
|
|
|
#[arg(long)]
|
|
|
|
manual_run: bool,
|
feat: Replace clap with argh
I’ve been wanting to do this for a while, but always procrastinated on it. We’ve been using Clap since the 2.0 rewrite, but Clap is known to be a fairly heavy library. Since Rustlings is usually peoples’ first contact with a Rust compilation, I think it’s in our best interests that this complation is as fast as possible. In effect, replacing Clap with the smaller, structopt-style `argh` reduces the amount of crates needing to be compiled from 82 to 60.
I also think this makes the code way easier to read, we don’t need to use Clap’s methods anymore, but can switch over to using pure Rust methods, e.g., switches are booleans, options are Option<String>s or the like, and subcommands are just structs.
2021-04-20 13:46:49 +03:00
|
|
|
}
|
|
|
|
|
2023-08-26 00:18:01 +03:00
|
|
|
#[derive(Subcommand)]
|
feat: Replace clap with argh
I’ve been wanting to do this for a while, but always procrastinated on it. We’ve been using Clap since the 2.0 rewrite, but Clap is known to be a fairly heavy library. Since Rustlings is usually peoples’ first contact with a Rust compilation, I think it’s in our best interests that this complation is as fast as possible. In effect, replacing Clap with the smaller, structopt-style `argh` reduces the amount of crates needing to be compiled from 82 to 60.
I also think this makes the code way easier to read, we don’t need to use Clap’s methods anymore, but can switch over to using pure Rust methods, e.g., switches are booleans, options are Option<String>s or the like, and subcommands are just structs.
2021-04-20 13:46:49 +03:00
|
|
|
enum Subcommands {
|
2024-04-29 18:01:47 +03:00
|
|
|
/// Initialize the official Rustlings exercises
|
2024-03-29 03:29:41 +03:00
|
|
|
Init,
|
2024-04-22 01:45:16 +03:00
|
|
|
/// Run a single exercise. Runs the next pending exercise if the exercise name is not specified
|
2023-08-26 00:18:01 +03:00
|
|
|
Run {
|
|
|
|
/// The name of the exercise
|
2024-04-11 03:51:02 +03:00
|
|
|
name: Option<String>,
|
2023-08-26 00:18:01 +03:00
|
|
|
},
|
2024-03-29 00:11:16 +03:00
|
|
|
/// Reset a single exercise
|
2023-08-26 00:18:01 +03:00
|
|
|
Reset {
|
|
|
|
/// The name of the exercise
|
|
|
|
name: String,
|
|
|
|
},
|
2024-04-22 01:45:16 +03:00
|
|
|
/// Show a hint. Shows the hint of the next pending exercise if the exercise name is not specified
|
2023-08-26 00:18:01 +03:00
|
|
|
Hint {
|
|
|
|
/// The name of the exercise
|
2024-04-18 18:17:39 +03:00
|
|
|
name: Option<String>,
|
2023-08-26 00:18:01 +03:00
|
|
|
},
|
2024-04-22 01:45:16 +03:00
|
|
|
/// Commands for developing (third-party) Rustlings exercises
|
2024-04-16 00:54:57 +03:00
|
|
|
#[command(subcommand)]
|
|
|
|
Dev(DevCommands),
|
feat: Replace clap with argh
I’ve been wanting to do this for a while, but always procrastinated on it. We’ve been using Clap since the 2.0 rewrite, but Clap is known to be a fairly heavy library. Since Rustlings is usually peoples’ first contact with a Rust compilation, I think it’s in our best interests that this complation is as fast as possible. In effect, replacing Clap with the smaller, structopt-style `argh` reduces the amount of crates needing to be compiled from 82 to 60.
I also think this makes the code way easier to read, we don’t need to use Clap’s methods anymore, but can switch over to using pure Rust methods, e.g., switches are booleans, options are Option<String>s or the like, and subcommands are just structs.
2021-04-20 13:46:49 +03:00
|
|
|
}
|
|
|
|
|
2024-03-25 05:46:56 +03:00
|
|
|
fn main() -> Result<()> {
|
2023-08-26 00:18:01 +03:00
|
|
|
let args = Args::parse();
|
feat: Replace clap with argh
I’ve been wanting to do this for a while, but always procrastinated on it. We’ve been using Clap since the 2.0 rewrite, but Clap is known to be a fairly heavy library. Since Rustlings is usually peoples’ first contact with a Rust compilation, I think it’s in our best interests that this complation is as fast as possible. In effect, replacing Clap with the smaller, structopt-style `argh` reduces the amount of crates needing to be compiled from 82 to 60.
I also think this makes the code way easier to read, we don’t need to use Clap’s methods anymore, but can switch over to using pure Rust methods, e.g., switches are booleans, options are Option<String>s or the like, and subcommands are just structs.
2021-04-20 13:46:49 +03:00
|
|
|
|
2024-04-25 02:56:01 +03:00
|
|
|
if !DEBUG_PROFILE && in_official_repo() {
|
2024-04-21 20:34:55 +03:00
|
|
|
bail!("{OLD_METHOD_ERR}");
|
|
|
|
}
|
|
|
|
|
2024-04-16 04:15:14 +03:00
|
|
|
match args.command {
|
|
|
|
Some(Subcommands::Init) => {
|
2024-04-21 20:26:19 +03:00
|
|
|
if DEBUG_PROFILE {
|
|
|
|
bail!("Disabled in the debug build");
|
2024-04-18 12:28:28 +03:00
|
|
|
}
|
|
|
|
|
2024-04-22 00:24:10 +03:00
|
|
|
{
|
|
|
|
let mut stdout = io::stdout().lock();
|
|
|
|
stdout.write_all(b"This command will create the directory `rustlings/` which will contain the exercises.\nPress ENTER to continue ")?;
|
|
|
|
stdout.flush()?;
|
2024-04-30 02:46:57 +03:00
|
|
|
press_enter_prompt()?;
|
2024-04-22 00:24:10 +03:00
|
|
|
stdout.write_all(b"\n")?;
|
|
|
|
}
|
|
|
|
|
2024-04-17 16:55:50 +03:00
|
|
|
return init::init().context("Initialization failed");
|
2024-04-16 04:15:14 +03:00
|
|
|
}
|
2024-04-17 16:55:50 +03:00
|
|
|
Some(Subcommands::Dev(dev_command)) => return dev_command.run(),
|
2024-04-16 04:15:14 +03:00
|
|
|
_ => (),
|
2024-04-15 03:11:27 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if !Path::new("exercises").is_dir() {
|
2024-04-12 02:24:01 +03:00
|
|
|
println!("{PRE_INIT_MSG}");
|
2024-03-29 03:29:41 +03:00
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
2024-04-17 16:55:50 +03:00
|
|
|
let info_file = InfoFile::parse()?;
|
|
|
|
|
|
|
|
if info_file.format_version > CURRENT_FORMAT_VERSION {
|
|
|
|
bail!(FORMAT_VERSION_HIGHER_ERR);
|
|
|
|
}
|
|
|
|
|
2024-04-14 17:03:49 +03:00
|
|
|
let (mut app_state, state_file_status) = AppState::new(
|
|
|
|
info_file.exercises,
|
|
|
|
info_file.final_message.unwrap_or_default(),
|
2024-04-27 18:31:51 +03:00
|
|
|
)?;
|
2024-04-14 17:03:49 +03:00
|
|
|
|
2024-04-29 18:01:47 +03:00
|
|
|
// Show the welcome message if the state file doesn't exist yet.
|
2024-04-14 17:03:49 +03:00
|
|
|
if let Some(welcome_message) = info_file.welcome_message {
|
|
|
|
match state_file_status {
|
|
|
|
StateFileStatus::NotRead => {
|
|
|
|
let mut stdout = io::stdout().lock();
|
2024-04-30 02:41:08 +03:00
|
|
|
clear_terminal(&mut stdout)?;
|
2024-04-14 17:03:49 +03:00
|
|
|
|
|
|
|
let welcome_message = welcome_message.trim();
|
|
|
|
write!(stdout, "{welcome_message}\n\nPress ENTER to continue ")?;
|
|
|
|
stdout.flush()?;
|
2024-04-30 02:46:57 +03:00
|
|
|
press_enter_prompt()?;
|
2024-04-30 02:41:08 +03:00
|
|
|
clear_terminal(&mut stdout)?;
|
2024-04-14 17:03:49 +03:00
|
|
|
}
|
|
|
|
StateFileStatus::Read => (),
|
|
|
|
}
|
|
|
|
}
|
2024-04-07 02:17:53 +03:00
|
|
|
|
2024-04-05 04:04:53 +03:00
|
|
|
match args.command {
|
2024-04-14 02:15:43 +03:00
|
|
|
None => {
|
2024-04-25 15:44:12 +03:00
|
|
|
let notify_exercise_names = if args.manual_run {
|
2024-04-14 18:10:53 +03:00
|
|
|
None
|
|
|
|
} else {
|
|
|
|
// For the the notify event handler thread.
|
|
|
|
// Leaking is not a problem because the slice lives until the end of the program.
|
|
|
|
Some(
|
2024-04-25 15:44:12 +03:00
|
|
|
&*app_state
|
2024-04-14 18:10:53 +03:00
|
|
|
.exercises()
|
|
|
|
.iter()
|
2024-04-25 15:44:12 +03:00
|
|
|
.map(|exercise| exercise.name.as_bytes())
|
2024-04-14 18:10:53 +03:00
|
|
|
.collect::<Vec<_>>()
|
|
|
|
.leak(),
|
|
|
|
)
|
|
|
|
};
|
2024-04-14 02:15:43 +03:00
|
|
|
|
|
|
|
loop {
|
2024-04-25 15:44:12 +03:00
|
|
|
match watch::watch(&mut app_state, notify_exercise_names)? {
|
2024-04-14 02:15:43 +03:00
|
|
|
WatchExit::Shutdown => break,
|
|
|
|
// It is much easier to exit the watch mode, launch the list mode and then restart
|
|
|
|
// the watch mode instead of trying to pause the watch threads and correct the
|
|
|
|
// watch state.
|
2024-04-16 00:54:57 +03:00
|
|
|
WatchExit::List => list::list(&mut app_state)?,
|
2024-04-14 02:15:43 +03:00
|
|
|
}
|
2024-04-10 03:12:50 +03:00
|
|
|
}
|
2024-04-14 02:15:43 +03:00
|
|
|
}
|
2024-04-05 04:04:53 +03:00
|
|
|
Some(Subcommands::Run { name }) => {
|
2024-04-11 03:51:02 +03:00
|
|
|
if let Some(name) = name {
|
|
|
|
app_state.set_current_exercise_by_name(&name)?;
|
|
|
|
}
|
2024-04-16 00:54:57 +03:00
|
|
|
run::run(&mut app_state)?;
|
feat: Replace clap with argh
I’ve been wanting to do this for a while, but always procrastinated on it. We’ve been using Clap since the 2.0 rewrite, but Clap is known to be a fairly heavy library. Since Rustlings is usually peoples’ first contact with a Rust compilation, I think it’s in our best interests that this complation is as fast as possible. In effect, replacing Clap with the smaller, structopt-style `argh` reduces the amount of crates needing to be compiled from 82 to 60.
I also think this makes the code way easier to read, we don’t need to use Clap’s methods anymore, but can switch over to using pure Rust methods, e.g., switches are booleans, options are Option<String>s or the like, and subcommands are just structs.
2021-04-20 13:46:49 +03:00
|
|
|
}
|
2024-04-05 04:04:53 +03:00
|
|
|
Some(Subcommands::Reset { name }) => {
|
2024-04-11 03:51:02 +03:00
|
|
|
app_state.set_current_exercise_by_name(&name)?;
|
2024-04-18 13:41:17 +03:00
|
|
|
let exercise_path = app_state.reset_current_exercise()?;
|
|
|
|
println!("The exercise {exercise_path} has been reset");
|
2022-08-17 17:31:53 +03:00
|
|
|
}
|
2024-04-05 04:04:53 +03:00
|
|
|
Some(Subcommands::Hint { name }) => {
|
2024-04-18 18:17:39 +03:00
|
|
|
if let Some(name) = name {
|
|
|
|
app_state.set_current_exercise_by_name(&name)?;
|
|
|
|
}
|
2024-04-11 03:51:02 +03:00
|
|
|
println!("{}", app_state.current_exercise().hint);
|
feat: Replace clap with argh
I’ve been wanting to do this for a while, but always procrastinated on it. We’ve been using Clap since the 2.0 rewrite, but Clap is known to be a fairly heavy library. Since Rustlings is usually peoples’ first contact with a Rust compilation, I think it’s in our best interests that this complation is as fast as possible. In effect, replacing Clap with the smaller, structopt-style `argh` reduces the amount of crates needing to be compiled from 82 to 60.
I also think this makes the code way easier to read, we don’t need to use Clap’s methods anymore, but can switch over to using pure Rust methods, e.g., switches are booleans, options are Option<String>s or the like, and subcommands are just structs.
2021-04-20 13:46:49 +03:00
|
|
|
}
|
2024-04-16 04:15:14 +03:00
|
|
|
// Handled in an earlier match.
|
|
|
|
Some(Subcommands::Init | Subcommands::Dev(_)) => (),
|
2018-11-26 13:10:38 +03:00
|
|
|
}
|
2024-03-25 05:46:56 +03:00
|
|
|
|
|
|
|
Ok(())
|
2018-05-06 19:59:50 +03:00
|
|
|
}
|
2024-04-12 02:24:01 +03:00
|
|
|
|
2024-04-21 20:34:55 +03:00
|
|
|
const OLD_METHOD_ERR: &str = "You are trying to run Rustlings using the old method before v6.
|
|
|
|
The new method doesn't include cloning the Rustlings' repository.
|
|
|
|
Please follow the instructions in the README:
|
|
|
|
https://github.com/rust-lang/rustlings#getting-started";
|
|
|
|
|
2024-04-16 02:22:54 +03:00
|
|
|
const FORMAT_VERSION_HIGHER_ERR: &str =
|
|
|
|
"The format version specified in the `info.toml` file is higher than the last one supported.
|
|
|
|
It is possible that you have an outdated version of Rustlings.
|
|
|
|
Try to install the latest Rustlings version first.";
|
|
|
|
|
2024-04-12 02:24:01 +03:00
|
|
|
const PRE_INIT_MSG: &str = r"
|
2024-04-15 03:11:27 +03:00
|
|
|
Welcome to...
|
2024-04-12 02:24:01 +03:00
|
|
|
_ _ _
|
|
|
|
_ __ _ _ ___| |_| (_)_ __ __ _ ___
|
|
|
|
| '__| | | / __| __| | | '_ \ / _` / __|
|
|
|
|
| | | |_| \__ \ |_| | | | | | (_| \__ \
|
|
|
|
|_| \__,_|___/\__|_|_|_| |_|\__, |___/
|
|
|
|
|___/
|
|
|
|
|
|
|
|
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.";
|