Compare commits

..

No commits in common. "aedeff8b243bad9205b84a657789b59928bf6524" and "dc5c72bc19951313e80f038961fb446bd6ea02f5" have entirely different histories.

15 changed files with 234 additions and 248 deletions

119
Cargo.lock generated
View file

@ -271,6 +271,16 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "errno"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
[[package]] [[package]]
name = "filetime" name = "filetime"
version = "0.2.23" version = "0.2.23"
@ -279,7 +289,7 @@ checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"redox_syscall 0.4.1", "redox_syscall",
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
@ -323,6 +333,15 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "home"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
dependencies = [
"windows-sys 0.52.0",
]
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.2.6" version = "2.2.6"
@ -401,10 +420,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]] [[package]]
name = "lock_api" name = "linux-raw-sys"
version = "0.4.12" version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
[[package]]
name = "lock_api"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"scopeguard", "scopeguard",
@ -474,6 +499,7 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d40b221972a1fc5ef4d858a2f671fb34c75983eb385463dff3780eeff6a9d43" checksum = "5d40b221972a1fc5ef4d858a2f671fb34c75983eb385463dff3780eeff6a9d43"
dependencies = [ dependencies = [
"crossbeam-channel",
"log", "log",
"notify", "notify",
] ]
@ -505,9 +531,9 @@ dependencies = [
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
version = "0.12.2" version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [ dependencies = [
"lock_api", "lock_api",
"parking_lot_core", "parking_lot_core",
@ -515,15 +541,15 @@ dependencies = [
[[package]] [[package]]
name = "parking_lot_core" name = "parking_lot_core"
version = "0.9.10" version = "0.9.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"redox_syscall 0.5.1", "redox_syscall",
"smallvec", "smallvec",
"windows-targets 0.52.5", "windows-targets 0.48.5",
] ]
[[package]] [[package]]
@ -609,15 +635,6 @@ dependencies = [
"bitflags 1.3.2", "bitflags 1.3.2",
] ]
[[package]]
name = "redox_syscall"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e"
dependencies = [
"bitflags 2.5.0",
]
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.10.4" version = "1.10.4"
@ -647,9 +664,22 @@ version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
[[package]]
name = "rustix"
version = "0.38.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
dependencies = [
"bitflags 2.5.0",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.52.0",
]
[[package]] [[package]]
name = "rustlings" name = "rustlings"
version = "6.0.0-beta.5" version = "6.0.0-beta.3"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"assert_cmd", "assert_cmd",
@ -662,13 +692,13 @@ dependencies = [
"ratatui", "ratatui",
"rustlings-macros", "rustlings-macros",
"serde", "serde",
"serde_json",
"toml_edit", "toml_edit",
"which",
] ]
[[package]] [[package]]
name = "rustlings-macros" name = "rustlings-macros"
version = "6.0.0-beta.5" version = "6.0.0-beta.3"
dependencies = [ dependencies = [
"quote", "quote",
"serde", "serde",
@ -704,35 +734,24 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.199" version = "1.0.198"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c9f6e76df036c77cd94996771fb40db98187f096dd0b9af39c6c6e452ba966a" checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.199" version = "1.0.198"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc" checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn",
] ]
[[package]]
name = "serde_json"
version = "1.0.116"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]] [[package]]
name = "serde_spanned" name = "serde_spanned"
version = "0.6.5" version = "0.6.5"
@ -875,9 +894,9 @@ checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
[[package]] [[package]]
name = "unicode-width" name = "unicode-width"
version = "0.1.12" version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
[[package]] [[package]]
name = "utf8parse" name = "utf8parse"
@ -916,6 +935,18 @@ version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "which"
version = "6.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8211e4f58a2b2805adfbefbc07bab82958fc91e3836339b1ab7ae32465dce0d7"
dependencies = [
"either",
"home",
"rustix",
"winsafe",
]
[[package]] [[package]]
name = "winapi" name = "winapi"
version = "0.3.9" version = "0.3.9"
@ -1088,13 +1119,19 @@ checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "0.6.7" version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14b9415ee827af173ebb3f15f9083df5a122eb93572ec28741fb153356ea2578" checksum = "f0c976aaaa0e1f90dbb21e9587cdaf1d9679a1cde8875c0d6bd83ab96a208352"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "winsafe"
version = "0.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
[[package]] [[package]]
name = "zerocopy" name = "zerocopy"
version = "0.7.32" version = "0.7.32"

View file

@ -8,7 +8,7 @@ exclude = [
] ]
[workspace.package] [workspace.package]
version = "6.0.0-beta.5" version = "6.0.0-beta.3"
authors = [ authors = [
"Liv <mokou@fastmail.com>", "Liv <mokou@fastmail.com>",
"Mo Bitar <mo8it@proton.me>", "Mo Bitar <mo8it@proton.me>",
@ -20,12 +20,13 @@ license = "MIT"
edition = "2021" edition = "2021"
[workspace.dependencies] [workspace.dependencies]
serde = { version = "1.0.199", features = ["derive"] } serde = { version = "1.0.198", features = ["derive"] }
toml_edit = { version = "0.22.12", default-features = false, features = ["parse", "serde"] } toml_edit = { version = "0.22.12", default-features = false, features = ["parse", "serde"] }
[package] [package]
name = "rustlings" name = "rustlings"
description = "Small exercises to get you used to reading and writing Rust code!" description = "Small exercises to get you used to reading and writing Rust code!"
default-run = "rustlings"
version.workspace = true version.workspace = true
authors.workspace = true authors.workspace = true
repository.workspace = true repository.workspace = true
@ -51,13 +52,13 @@ anyhow = "1.0.82"
clap = { version = "4.5.4", features = ["derive"] } clap = { version = "4.5.4", features = ["derive"] }
crossterm = "0.27.0" crossterm = "0.27.0"
hashbrown = "0.14.3" hashbrown = "0.14.3"
notify-debouncer-mini = { version = "0.4.1", default-features = false } notify-debouncer-mini = "0.4.1"
os_pipe = "1.1.5" os_pipe = "1.1.5"
ratatui = { version = "0.26.2", default-features = false, features = ["crossterm"] } ratatui = "0.26.2"
rustlings-macros = { path = "rustlings-macros", version = "=6.0.0-beta.5" } rustlings-macros = { path = "rustlings-macros", version = "=6.0.0-beta.3" }
serde_json = "1.0.116"
serde.workspace = true serde.workspace = true
toml_edit.workspace = true toml_edit.workspace = true
which = "6.0.1"
[dev-dependencies] [dev-dependencies]
assert_cmd = "2.0.14" assert_cmd = "2.0.14"

View file

@ -35,7 +35,7 @@ The following command will download and compile Rustlings:
<!-- TODO: Remove @6.0.0-beta.x --> <!-- TODO: Remove @6.0.0-beta.x -->
```bash ```bash
cargo install rustlings@6.0.0-beta.5 cargo install rustlings@6.0.0-beta.3
``` ```
<details> <details>
@ -44,7 +44,7 @@ cargo install rustlings@6.0.0-beta.5
<!-- TODO: Remove @6.0.0-beta.x --> <!-- TODO: Remove @6.0.0-beta.x -->
- Make sure you have the latest Rust version by running `rustup update` - Make sure you have the latest Rust version by running `rustup update`
- Try adding the `--locked` flag: `cargo install rustlings@6.0.0-beta.5 --locked` - Try adding the `--locked` flag: `cargo install rustlings@6.0.0-beta.3 --locked`
- Otherwise, please [report the issue](https://github.com/rust-lang/rustlings/issues/new) - Otherwise, please [report the issue](https://github.com/rust-lang/rustlings/issues/new)
</details> </details>
@ -83,7 +83,7 @@ It will rerun the current exercise automatically every time you change the exerc
<details> <details>
<summary><strong>If detecting file changes in the <code>exercises/</code> directory fails…</strong> (<em>click to expand</em>)</summary> <summary><strong>If detecting file changes in the <code>exercises/</code> directory fails…</strong> (<em>click to expand</em>)</summary>
> You can add the **`--manual-run`** flag (`rustlings --manual-run`) to manually rerun the current exercise by entering `r` (or `run`) in the watch mode. > You can add the **`--manual-run`** flag (`rustlings --manual-run`) to manually rerun the current exercise by entering `r` or `run` in the watch mode.
> >
> Please [report the issue](https://github.com/rust-lang/rustlings/issues/new) with some information about your operating system and whether you run Rustlings in a container or virtual machine (e.g. WSL). > Please [report the issue](https://github.com/rust-lang/rustlings/issues/new) with some information about your operating system and whether you run Rustlings in a container or virtual machine (e.g. WSL).
@ -91,7 +91,7 @@ It will rerun the current exercise automatically every time you change the exerc
### Exercise List ### Exercise List
In the [watch mode](#watch-mode) (after launching `rustlings`), you can enter `l` (or `list`) to open the interactive exercise list. In the [watch mode](#watch-mode) (after launching `rustlings`), you can enter `l` or `list` to open the interactive exercise list.
The list allows you to… The list allows you to…

View file

@ -1,10 +1,11 @@
// We sometimes encourage you to keep trying things on a given exercise, even // We sometimes encourage you to keep trying things on a given exercise, even
// after you already figured it out. If you got everything working and feel // after you already figured it out. If you got everything working and feel
// ready for the next exercise, enter `n` (or `next`) in the terminal. // ready for the next exercise, remove the `I AM NOT DONE` comment below.
// //
// The exercise file will be reloaded when you change one of the lines below! // If you're running this using `rustlings watch`: The exercise file will be
// Try adding a new `println!`. // reloaded when you change one of the lines below! Try adding a `println!`
// Try removing a semicolon and see what happens in the terminal! // line, or try changing what it outputs in your terminal. Try removing a
// semicolon and see what happens!
fn main() { fn main() {
println!("Hello and"); println!("Hello and");
@ -21,6 +22,5 @@ fn main() {
println!("solve the exercises. Good luck!"); println!("solve the exercises. Good luck!");
println!(); println!();
println!("The file of this exercise is `exercises/00_intro/intro1.rs`. Have a look!"); println!("The file of this exercise is `exercises/00_intro/intro1.rs`. Have a look!");
println!("The current exercise path will be always shown under the progress bar."); println!("The current exercise path is shown under the progress bar in the watch mode.");
println!("You can click on the path to open the exercise file in your editor.");
} }

View file

@ -1,26 +1,32 @@
format_version = 1 format_version = 1
welcome_message = """Is this your first time? Don't worry, Rustlings is made for beginners! welcome_message = """Is this your first time? Don't worry, Rustlings was made for beginners! We are
We are going to teach you a lot of things about Rust, but before we can going to teach you a lot of things about Rust, but before we can get
get started, here are some notes about how Rustlings operates: started, here's a couple of notes about how Rustlings operates:
1. The central concept behind Rustlings is that you solve exercises. These 1. The central concept behind Rustlings is that you solve exercises. These
exercises usually contain some compiler or logic errors which cause the exercises usually have some sort of syntax error in them, which will cause
exercise to fail compilation or testing. It's your job to find all errors them to fail compilation or testing. Sometimes there's a logic error instead
and fix them! of a syntax error. No matter what error, it's your job to find it and fix it!
2. Make sure to have your editor open in the `rustlings/` directory. Rustlings You'll know when you fixed it because then, the exercise will compile and
will show you the path of the current exercise under the progress bar. Open Rustlings will be able to move on to the next exercise.
the exercise file in your editor, fix errors and save the file. Rustlings will 2. If you run Rustlings in watch mode (which we recommend), it'll automatically
automatically detect the file change and rerun the exercise. If all errors are start with the first exercise. Don't get confused by an error message popping
fixed, Rustlings will ask you to move on to the next exercise. up as soon as you run Rustlings! This is part of the exercise that you're
3. If you're stuck on an exercise, enter `h` (or `hint`) to show a hint. supposed to solve, so open the exercise file in an editor and start your
detective work!
3. If you're stuck on an exercise, there is a helpful hint you can view by typing
'hint' (in watch mode), or running `rustlings hint exercise_name`.
4. If an exercise doesn't make sense to you, feel free to open an issue on GitHub! 4. If an exercise doesn't make sense to you, feel free to open an issue on GitHub!
(https://github.com/rust-lang/rustlings). We look at every issue, and sometimes, (https://github.com/rust-lang/rustlings/issues/new). We look at every issue,
other learners do too so you can help each other out! and sometimes, other learners do too so you can help each other out!
Got all that? Great! To get started, run `rustlings watch` in order to get the first exercise.
Make sure to have your editor open in the `rustlings` directory!
""" """
final_message = """We hope you enjoyed learning about the various aspects of Rust! final_message = """We hope you enjoyed learning about the various aspects of Rust!
If you noticed any issues, don't hesitate to report them on Github. If you noticed any issues, please don't hesitate to report them to our repo.
You can also contribute your own exercises to help the greater community! You can also contribute your own exercises to help the greater community!
Before reporting an issue or contributing, please read our guidelines: Before reporting an issue or contributing, please read our guidelines:
@ -35,7 +41,9 @@ name = "intro1"
dir = "00_intro" dir = "00_intro"
test = false test = false
# TODO: Fix hint # TODO: Fix hint
hint = """Enter `n` (or `next`) followed by ENTER to move on to the next exercise""" hint = """
Remove the `I AM NOT DONE` comment in the `exercises/intro00/intro1.rs` file
to move on to the next exercise."""
[[exercises]] [[exercises]]
name = "intro2" name = "intro2"

View file

@ -4,11 +4,10 @@ use crossterm::{
terminal::{Clear, ClearType}, terminal::{Clear, ClearType},
ExecutableCommand, ExecutableCommand,
}; };
use serde::Deserialize;
use std::{ use std::{
fs::{self, File}, fs::{self, File},
io::{Read, StdoutLock, Write}, io::{Read, StdoutLock, Write},
path::{Path, PathBuf}, path::Path,
process::{Command, Stdio}, process::{Command, Stdio},
}; };
@ -33,11 +32,6 @@ pub enum StateFileStatus {
NotRead, NotRead,
} }
#[derive(Deserialize)]
struct CargoMetadata {
target_directory: PathBuf,
}
pub struct AppState { pub struct AppState {
current_exercise_ind: usize, current_exercise_ind: usize,
exercises: Vec<Exercise>, exercises: Vec<Exercise>,
@ -45,7 +39,6 @@ pub struct AppState {
final_message: String, final_message: String,
file_buf: Vec<u8>, file_buf: Vec<u8>,
official_exercises: bool, official_exercises: bool,
target_dir: PathBuf,
} }
impl AppState { impl AppState {
@ -97,24 +90,7 @@ impl AppState {
pub fn new( pub fn new(
exercise_infos: Vec<ExerciseInfo>, exercise_infos: Vec<ExerciseInfo>,
final_message: String, final_message: String,
) -> Result<(Self, StateFileStatus)> { ) -> (Self, StateFileStatus) {
let metadata_output = Command::new("cargo")
.arg("metadata")
.arg("-q")
.arg("--format-version")
.arg("1")
.arg("--no-deps")
.stdin(Stdio::null())
.stderr(Stdio::inherit())
.output()
.context(CARGO_METADATA_ERR)?
.stdout;
let target_dir = serde_json::de::from_slice::<CargoMetadata>(&metadata_output)
.context(
"Failed to read the field `target_directory` from the `cargo metadata` output",
)?
.target_directory;
let exercises = exercise_infos let exercises = exercise_infos
.into_iter() .into_iter()
.map(|mut exercise_info| { .map(|mut exercise_info| {
@ -151,12 +127,11 @@ impl AppState {
final_message, final_message,
file_buf: Vec::with_capacity(2048), file_buf: Vec::with_capacity(2048),
official_exercises: !Path::new("info.toml").exists(), official_exercises: !Path::new("info.toml").exists(),
target_dir,
}; };
let state_file_status = slf.update_from_file(); let state_file_status = slf.update_from_file();
Ok((slf, state_file_status)) (slf, state_file_status)
} }
#[inline] #[inline]
@ -179,11 +154,6 @@ impl AppState {
&self.exercises[self.current_exercise_ind] &self.exercises[self.current_exercise_ind]
} }
#[inline]
pub fn target_dir(&self) -> &Path {
&self.target_dir
}
pub fn set_current_exercise_ind(&mut self, ind: usize) -> Result<()> { pub fn set_current_exercise_ind(&mut self, ind: usize) -> Result<()> {
if ind >= self.exercises.len() { if ind >= self.exercises.len() {
bail!(BAD_INDEX_ERR); bail!(BAD_INDEX_ERR);
@ -343,7 +313,7 @@ impl AppState {
write!(writer, "Running {exercise} ... ")?; write!(writer, "Running {exercise} ... ")?;
writer.flush()?; writer.flush()?;
let success = exercise.run(&mut output, &self.target_dir)?; let success = exercise.run(&mut output)?;
if !success { if !success {
writeln!(writer, "{}\n", "FAILED".red())?; writeln!(writer, "{}\n", "FAILED".red())?;
@ -410,10 +380,6 @@ impl AppState {
} }
} }
const CARGO_METADATA_ERR: &str = "Failed to run the command `cargo metadata …`
Did you already install Rust?
Try running `cargo --version` to diagnose the problem.";
const RERUNNING_ALL_EXERCISES_MSG: &[u8] = b" const RERUNNING_ALL_EXERCISES_MSG: &[u8] = b"
All exercises seem to be done. All exercises seem to be done.
Recompiling and running all exercises to make sure that all of them are actually done. Recompiling and running all exercises to make sure that all of them are actually done.

View file

@ -1,70 +0,0 @@
use anyhow::{Context, Result};
use std::{io::Read, path::Path, process::Command};
pub fn run_cmd(mut cmd: Command, description: &str, output: &mut Vec<u8>) -> Result<bool> {
let (mut reader, writer) = os_pipe::pipe()
.with_context(|| format!("Failed to create a pipe to run the command `{description}``"))?;
let writer_clone = writer.try_clone().with_context(|| {
format!("Failed to clone the pipe writer for the command `{description}`")
})?;
let mut handle = cmd
.stdout(writer_clone)
.stderr(writer)
.spawn()
.with_context(|| format!("Failed to run the command `{description}`"))?;
// Prevent pipe deadlock.
drop(cmd);
reader
.read_to_end(output)
.with_context(|| format!("Failed to read the output of the command `{description}`"))?;
output.push(b'\n');
handle
.wait()
.with_context(|| format!("Failed to wait on the command `{description}` to exit"))
.map(|status| status.success())
}
pub struct CargoCmd<'a> {
pub subcommand: &'a str,
pub args: &'a [&'a str],
pub exercise_name: &'a str,
pub description: &'a str,
pub hide_warnings: bool,
pub target_dir: &'a Path,
pub output: &'a mut Vec<u8>,
pub dev: bool,
}
impl<'a> CargoCmd<'a> {
pub fn run(&mut self) -> Result<bool> {
let mut cmd = Command::new("cargo");
cmd.arg(self.subcommand);
// A hack to make `cargo run` work when developing Rustlings.
if self.dev {
cmd.arg("--manifest-path")
.arg("dev/Cargo.toml")
.arg("--target-dir")
.arg(self.target_dir);
}
cmd.arg("--color")
.arg("always")
.arg("-q")
.arg("--bin")
.arg(self.exercise_name)
.args(self.args);
if self.hide_warnings {
cmd.env("RUSTFLAGS", "-A warnings");
}
run_cmd(cmd, self.description, self.output)
}
}

View file

@ -1,21 +1,57 @@
use anyhow::Result; use anyhow::{Context, Result};
use crossterm::style::{style, StyledContent, Stylize}; use crossterm::style::{style, StyledContent, Stylize};
use std::{ use std::{
fmt::{self, Display, Formatter}, fmt::{self, Display, Formatter},
io::Write, io::{Read, Write},
path::{Path, PathBuf}, process::{Command, Stdio},
process::Command,
}; };
use crate::{ use crate::{in_official_repo, terminal_link::TerminalFileLink, DEBUG_PROFILE};
cmd::{run_cmd, CargoCmd},
in_official_repo,
terminal_link::TerminalFileLink,
DEBUG_PROFILE,
};
pub const OUTPUT_CAPACITY: usize = 1 << 14; pub const OUTPUT_CAPACITY: usize = 1 << 14;
fn run_command(
mut cmd: Command,
cmd_description: &str,
output: &mut Vec<u8>,
stderr: bool,
) -> Result<bool> {
let (mut reader, writer) = os_pipe::pipe().with_context(|| {
format!("Failed to create a pipe to run the command `{cmd_description}``")
})?;
let (stdout, stderr) = if stderr {
(
Stdio::from(writer.try_clone().with_context(|| {
format!("Failed to clone the pipe writer for the command `{cmd_description}`")
})?),
Stdio::from(writer),
)
} else {
(Stdio::from(writer), Stdio::null())
};
let mut handle = cmd
.stdout(stdout)
.stderr(stderr)
.spawn()
.with_context(|| format!("Failed to run the command `{cmd_description}`"))?;
// Prevent pipe deadlock.
drop(cmd);
reader
.read_to_end(output)
.with_context(|| format!("Failed to read the output of the command `{cmd_description}`"))?;
output.push(b'\n');
handle
.wait()
.with_context(|| format!("Failed to wait on the command `{cmd_description}` to exit"))
.map(|status| status.success())
}
pub struct Exercise { pub struct Exercise {
pub dir: Option<&'static str>, pub dir: Option<&'static str>,
// Exercise's unique name // Exercise's unique name
@ -30,16 +66,11 @@ pub struct Exercise {
} }
impl Exercise { impl Exercise {
fn run_bin(&self, output: &mut Vec<u8>, target_dir: &Path) -> Result<bool> { fn run_bin(&self, output: &mut Vec<u8>) -> Result<bool> {
writeln!(output, "{}", "Output".underlined())?; writeln!(output, "{}", "Output".underlined())?;
let mut bin_path = let bin_path = format!("target/debug/{}", self.name);
PathBuf::with_capacity(target_dir.as_os_str().len() + 7 + self.name.len()); let success = run_command(Command::new(&bin_path), &bin_path, output, true)?;
bin_path.push(target_dir);
bin_path.push("debug");
bin_path.push(self.name);
let success = run_cmd(Command::new(&bin_path), &bin_path.to_string_lossy(), output)?;
if !success { if !success {
writeln!( writeln!(
@ -54,23 +85,43 @@ impl Exercise {
Ok(success) Ok(success)
} }
pub fn run(&self, output: &mut Vec<u8>, target_dir: &Path) -> Result<bool> { fn cargo_cmd(
&self,
command: &str,
args: &[&str],
cmd_description: &str,
output: &mut Vec<u8>,
dev: bool,
stderr: bool,
) -> Result<bool> {
let mut cmd = Command::new("cargo");
cmd.arg(command);
// A hack to make `cargo run` work when developing Rustlings.
if dev {
cmd.arg("--manifest-path")
.arg("dev/Cargo.toml")
.arg("--target-dir")
.arg("target");
}
cmd.arg("--color")
.arg("always")
.arg("-q")
.arg("--bin")
.arg(self.name)
.args(args);
run_command(cmd, cmd_description, output, stderr)
}
pub fn run(&self, output: &mut Vec<u8>) -> Result<bool> {
output.clear(); output.clear();
// Developing the official Rustlings. // Developing the official Rustlings.
let dev = DEBUG_PROFILE && in_official_repo(); let dev = DEBUG_PROFILE && in_official_repo();
let build_success = CargoCmd { let build_success = self.cargo_cmd("build", &[], "cargo build …", output, dev, true)?;
subcommand: "build",
args: &[],
exercise_name: self.name,
description: "cargo build …",
hide_warnings: false,
target_dir,
output,
dev,
}
.run()?;
if !build_success { if !build_success {
return Ok(false); return Ok(false);
} }
@ -83,46 +134,34 @@ impl Exercise {
} else { } else {
&["--profile", "test"] &["--profile", "test"]
}; };
let clippy_success = CargoCmd { let clippy_success =
subcommand: "clippy", self.cargo_cmd("clippy", clippy_args, "cargo clippy …", output, dev, true)?;
args: clippy_args,
exercise_name: self.name,
description: "cargo clippy …",
hide_warnings: false,
target_dir,
output,
dev,
}
.run()?;
if !clippy_success { if !clippy_success {
return Ok(false); return Ok(false);
} }
if !self.test { if !self.test {
return self.run_bin(output, target_dir); return self.run_bin(output);
} }
let test_success = CargoCmd { let test_success = self.cargo_cmd(
subcommand: "test", "test",
args: &[ &[
"--", "--",
"--color", "--color",
"always", "always",
"--show-output", "--nocapture",
"--format", "--format",
"pretty", "pretty",
], ],
exercise_name: self.name, "cargo test …",
description: "cargo test …",
// Hide warnings because they are shown by Clippy.
hide_warnings: true,
target_dir,
output, output,
dev, dev,
} // Hide warnings because they are shown by Clippy.
.run()?; false,
)?;
let run_success = self.run_bin(output, target_dir)?; let run_success = self.run_bin(output)?;
Ok(test_success && run_success) Ok(test_success && run_success)
} }

View file

@ -42,7 +42,7 @@ pub fn list(app_state: &mut AppState) -> Result<()> {
ui_state.message.clear(); ui_state.message.clear();
match key.code { match key.code {
KeyCode::Esc | KeyCode::Char('q') => break, KeyCode::Char('q') => break,
KeyCode::Down | KeyCode::Char('j') => ui_state.select_next(), KeyCode::Down | KeyCode::Char('j') => ui_state.select_next(),
KeyCode::Up | KeyCode::Char('k') => ui_state.select_previous(), KeyCode::Up | KeyCode::Char('k') => ui_state.select_previous(),
KeyCode::Home | KeyCode::Char('g') => ui_state.select_first(), KeyCode::Home | KeyCode::Char('g') => ui_state.select_first(),

View file

@ -194,7 +194,7 @@ impl<'a> UiState<'a> {
let message = if self.message.is_empty() { let message = if self.message.is_empty() {
// Help footer. // Help footer.
Span::raw( Span::raw(
"↓/j ↑/k home/g end/G │ <c>ontinue at │ <r>eset │ filter <d>one/<p>ending │ <q>uit", "↓/j ↑/k home/g end/G │ filter <d>one/<p>ending │ <r>eset │ <c>ontinue at │ <q>uit",
) )
} else { } else {
self.message.as_str().light_blue() self.message.as_str().light_blue()

View file

@ -15,7 +15,6 @@ use self::{app_state::AppState, dev::DevCommands, info_file::InfoFile, watch::Wa
mod app_state; mod app_state;
mod cargo_toml; mod cargo_toml;
mod cmd;
mod dev; mod dev;
mod embedded; mod embedded;
mod exercise; mod exercise;
@ -87,6 +86,8 @@ fn main() -> Result<()> {
bail!("{OLD_METHOD_ERR}"); bail!("{OLD_METHOD_ERR}");
} }
which::which("cargo").context(CARGO_NOT_FOUND_ERR)?;
match args.command { match args.command {
Some(Subcommands::Init) => { Some(Subcommands::Init) => {
if DEBUG_PROFILE { if DEBUG_PROFILE {
@ -121,7 +122,7 @@ fn main() -> Result<()> {
let (mut app_state, state_file_status) = AppState::new( let (mut app_state, state_file_status) = AppState::new(
info_file.exercises, info_file.exercises,
info_file.final_message.unwrap_or_default(), info_file.final_message.unwrap_or_default(),
)?; );
if let Some(welcome_message) = info_file.welcome_message { if let Some(welcome_message) = info_file.welcome_message {
match state_file_status { match state_file_status {
@ -197,6 +198,10 @@ The new method doesn't include cloning the Rustlings' repository.
Please follow the instructions in the README: Please follow the instructions in the README:
https://github.com/rust-lang/rustlings#getting-started"; https://github.com/rust-lang/rustlings#getting-started";
const CARGO_NOT_FOUND_ERR: &str = "Failed to find `cargo`.
Did you already install Rust?
Try running `cargo --version` to diagnose the problem.";
const FORMAT_VERSION_HIGHER_ERR: &str = const FORMAT_VERSION_HIGHER_ERR: &str =
"The format version specified in the `info.toml` file is higher than the last one supported. "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. It is possible that you have an outdated version of Rustlings.

View file

@ -11,7 +11,7 @@ use crate::{
pub fn run(app_state: &mut AppState) -> Result<()> { pub fn run(app_state: &mut AppState) -> Result<()> {
let exercise = app_state.current_exercise(); let exercise = app_state.current_exercise();
let mut output = Vec::with_capacity(OUTPUT_CAPACITY); let mut output = Vec::with_capacity(OUTPUT_CAPACITY);
let success = exercise.run(&mut output, app_state.target_dir())?; let success = exercise.run(&mut output)?;
let mut stdout = io::stdout().lock(); let mut stdout = io::stdout().lock();
stdout.write_all(&output)?; stdout.write_all(&output)?;

View file

@ -124,5 +124,5 @@ The automatic detection of exercise file changes failed :(
Please try running `rustlings` again. Please try running `rustlings` again.
If you keep getting this error, run `rustlings --manual-run` to deactivate the file watcher. If you keep getting this error, run `rustlings --manual-run` to deactivate the file watcher.
You need to manually trigger running the current exercise using `r` (or `run`) then. You need to manually trigger running the current exercise using `r` or `run` then.
"; ";

View file

@ -50,10 +50,7 @@ impl<'a> WatchState<'a> {
pub fn run_current_exercise(&mut self) -> Result<()> { pub fn run_current_exercise(&mut self) -> Result<()> {
self.show_hint = false; self.show_hint = false;
let success = self let success = self.app_state.current_exercise().run(&mut self.output)?;
.app_state
.current_exercise()
.run(&mut self.output, self.app_state.target_dir())?;
if success { if success {
self.done_status = self.done_status =
if let Some(solution_path) = self.app_state.current_solution_path()? { if let Some(solution_path) = self.app_state.current_solution_path()? {
@ -130,7 +127,7 @@ impl<'a> WatchState<'a> {
self.writer, self.writer,
"{}\n", "{}\n",
"Exercise done ✓ "Exercise done ✓
When you are done experimenting, enter `n` (or `next`) to move on to the next exercise 🦀" When you are done experimenting, enter `n` or `next` to go to the next exercise 🦀"
.bold() .bold()
.green(), .green(),
)?; )?;

View file

@ -1,4 +1,5 @@
use assert_cmd::prelude::*; use assert_cmd::prelude::*;
use predicates::boolean::PredicateBooleanExt;
use std::process::Command; use std::process::Command;
#[test] #[test]
@ -109,7 +110,8 @@ fn run_compile_exercise_does_not_prompt() {
.args(["run", "pending_exercise"]) .args(["run", "pending_exercise"])
.current_dir("tests/fixture/state") .current_dir("tests/fixture/state")
.assert() .assert()
.code(0); .code(0)
.stdout(predicates::str::contains("I AM NOT DONE").not());
} }
#[test] #[test]
@ -119,7 +121,8 @@ fn run_test_exercise_does_not_prompt() {
.args(["run", "pending_test_exercise"]) .args(["run", "pending_test_exercise"])
.current_dir("tests/fixture/state") .current_dir("tests/fixture/state")
.assert() .assert()
.code(0); .code(0)
.stdout(predicates::str::contains("I AM NOT DONE").not());
} }
#[test] #[test]