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"
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]]
name = "filetime"
version = "0.2.23"
@ -279,7 +289,7 @@ checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd"
dependencies = [
"cfg-if",
"libc",
"redox_syscall 0.4.1",
"redox_syscall",
"windows-sys 0.52.0",
]
@ -323,6 +333,15 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "indexmap"
version = "2.2.6"
@ -401,10 +420,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]]
name = "lock_api"
version = "0.4.12"
name = "linux-raw-sys"
version = "0.4.13"
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 = [
"autocfg",
"scopeguard",
@ -474,6 +499,7 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d40b221972a1fc5ef4d858a2f671fb34c75983eb385463dff3780eeff6a9d43"
dependencies = [
"crossbeam-channel",
"log",
"notify",
]
@ -505,9 +531,9 @@ dependencies = [
[[package]]
name = "parking_lot"
version = "0.12.2"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [
"lock_api",
"parking_lot_core",
@ -515,15 +541,15 @@ dependencies = [
[[package]]
name = "parking_lot_core"
version = "0.9.10"
version = "0.9.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
dependencies = [
"cfg-if",
"libc",
"redox_syscall 0.5.1",
"redox_syscall",
"smallvec",
"windows-targets 0.52.5",
"windows-targets 0.48.5",
]
[[package]]
@ -609,15 +635,6 @@ dependencies = [
"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]]
name = "regex"
version = "1.10.4"
@ -647,9 +664,22 @@ version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "rustlings"
version = "6.0.0-beta.5"
version = "6.0.0-beta.3"
dependencies = [
"anyhow",
"assert_cmd",
@ -662,13 +692,13 @@ dependencies = [
"ratatui",
"rustlings-macros",
"serde",
"serde_json",
"toml_edit",
"which",
]
[[package]]
name = "rustlings-macros"
version = "6.0.0-beta.5"
version = "6.0.0-beta.3"
dependencies = [
"quote",
"serde",
@ -704,35 +734,24 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde"
version = "1.0.199"
version = "1.0.198"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c9f6e76df036c77cd94996771fb40db98187f096dd0b9af39c6c6e452ba966a"
checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.199"
version = "1.0.198"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc"
checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9"
dependencies = [
"proc-macro2",
"quote",
"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]]
name = "serde_spanned"
version = "0.6.5"
@ -875,9 +894,9 @@ checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
[[package]]
name = "unicode-width"
version = "0.1.12"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6"
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
[[package]]
name = "utf8parse"
@ -916,6 +935,18 @@ version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "winapi"
version = "0.3.9"
@ -1088,13 +1119,19 @@ checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
[[package]]
name = "winnow"
version = "0.6.7"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14b9415ee827af173ebb3f15f9083df5a122eb93572ec28741fb153356ea2578"
checksum = "f0c976aaaa0e1f90dbb21e9587cdaf1d9679a1cde8875c0d6bd83ab96a208352"
dependencies = [
"memchr",
]
[[package]]
name = "winsafe"
version = "0.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
[[package]]
name = "zerocopy"
version = "0.7.32"

View file

@ -8,7 +8,7 @@ exclude = [
]
[workspace.package]
version = "6.0.0-beta.5"
version = "6.0.0-beta.3"
authors = [
"Liv <mokou@fastmail.com>",
"Mo Bitar <mo8it@proton.me>",
@ -20,12 +20,13 @@ license = "MIT"
edition = "2021"
[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"] }
[package]
name = "rustlings"
description = "Small exercises to get you used to reading and writing Rust code!"
default-run = "rustlings"
version.workspace = true
authors.workspace = true
repository.workspace = true
@ -51,13 +52,13 @@ anyhow = "1.0.82"
clap = { version = "4.5.4", features = ["derive"] }
crossterm = "0.27.0"
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"
ratatui = { version = "0.26.2", default-features = false, features = ["crossterm"] }
rustlings-macros = { path = "rustlings-macros", version = "=6.0.0-beta.5" }
serde_json = "1.0.116"
ratatui = "0.26.2"
rustlings-macros = { path = "rustlings-macros", version = "=6.0.0-beta.3" }
serde.workspace = true
toml_edit.workspace = true
which = "6.0.1"
[dev-dependencies]
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 -->
```bash
cargo install rustlings@6.0.0-beta.5
cargo install rustlings@6.0.0-beta.3
```
<details>
@ -44,7 +44,7 @@ cargo install rustlings@6.0.0-beta.5
<!-- TODO: Remove @6.0.0-beta.x -->
- 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)
</details>
@ -83,7 +83,7 @@ It will rerun the current exercise automatically every time you change the exerc
<details>
<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).
@ -91,7 +91,7 @@ It will rerun the current exercise automatically every time you change the exerc
### 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…

View file

@ -1,10 +1,11 @@
// 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
// 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!
// Try adding a new `println!`.
// Try removing a semicolon and see what happens in the terminal!
// If you're running this using `rustlings watch`: The exercise file will be
// reloaded when you change one of the lines below! Try adding a `println!`
// line, or try changing what it outputs in your terminal. Try removing a
// semicolon and see what happens!
fn main() {
println!("Hello and");
@ -21,6 +22,5 @@ fn main() {
println!("solve the exercises. Good luck!");
println!();
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!("You can click on the path to open the exercise file in your editor.");
println!("The current exercise path is shown under the progress bar in the watch mode.");
}

View file

@ -1,26 +1,32 @@
format_version = 1
welcome_message = """Is this your first time? Don't worry, Rustlings is made for beginners!
We are going to teach you a lot of things about Rust, but before we can
get started, here are some notes about how Rustlings operates:
welcome_message = """Is this your first time? Don't worry, Rustlings was made for beginners! We are
going to teach you a lot of things about Rust, but before we can get
started, here's a couple of notes about how Rustlings operates:
1. The central concept behind Rustlings is that you solve exercises. These
exercises usually contain some compiler or logic errors which cause the
exercise to fail compilation or testing. It's your job to find all errors
and fix them!
2. Make sure to have your editor open in the `rustlings/` directory. Rustlings
will show you the path of the current exercise under the progress bar. Open
the exercise file in your editor, fix errors and save the file. Rustlings will
automatically detect the file change and rerun the exercise. If all errors are
fixed, Rustlings will ask you to move on to the next exercise.
3. If you're stuck on an exercise, enter `h` (or `hint`) to show a hint.
exercises usually have some sort of syntax error in them, which will cause
them to fail compilation or testing. Sometimes there's a logic error instead
of a syntax error. No matter what error, it's your job to find it and fix it!
You'll know when you fixed it because then, the exercise will compile and
Rustlings will be able to move on to the next exercise.
2. If you run Rustlings in watch mode (which we recommend), it'll automatically
start with the first exercise. Don't get confused by an error message popping
up as soon as you run Rustlings! This is part of the exercise that you're
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!
(https://github.com/rust-lang/rustlings). We look at every issue, and sometimes,
other learners do too so you can help each other out!
(https://github.com/rust-lang/rustlings/issues/new). We look at every issue,
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!
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!
Before reporting an issue or contributing, please read our guidelines:
@ -35,7 +41,9 @@ name = "intro1"
dir = "00_intro"
test = false
# 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]]
name = "intro2"

View file

@ -4,11 +4,10 @@ use crossterm::{
terminal::{Clear, ClearType},
ExecutableCommand,
};
use serde::Deserialize;
use std::{
fs::{self, File},
io::{Read, StdoutLock, Write},
path::{Path, PathBuf},
path::Path,
process::{Command, Stdio},
};
@ -33,11 +32,6 @@ pub enum StateFileStatus {
NotRead,
}
#[derive(Deserialize)]
struct CargoMetadata {
target_directory: PathBuf,
}
pub struct AppState {
current_exercise_ind: usize,
exercises: Vec<Exercise>,
@ -45,7 +39,6 @@ pub struct AppState {
final_message: String,
file_buf: Vec<u8>,
official_exercises: bool,
target_dir: PathBuf,
}
impl AppState {
@ -97,24 +90,7 @@ impl AppState {
pub fn new(
exercise_infos: Vec<ExerciseInfo>,
final_message: String,
) -> Result<(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;
) -> (Self, StateFileStatus) {
let exercises = exercise_infos
.into_iter()
.map(|mut exercise_info| {
@ -151,12 +127,11 @@ impl AppState {
final_message,
file_buf: Vec::with_capacity(2048),
official_exercises: !Path::new("info.toml").exists(),
target_dir,
};
let state_file_status = slf.update_from_file();
Ok((slf, state_file_status))
(slf, state_file_status)
}
#[inline]
@ -179,11 +154,6 @@ impl AppState {
&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<()> {
if ind >= self.exercises.len() {
bail!(BAD_INDEX_ERR);
@ -343,7 +313,7 @@ impl AppState {
write!(writer, "Running {exercise} ... ")?;
writer.flush()?;
let success = exercise.run(&mut output, &self.target_dir)?;
let success = exercise.run(&mut output)?;
if !success {
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"
All exercises seem to be 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 std::{
fmt::{self, Display, Formatter},
io::Write,
path::{Path, PathBuf},
process::Command,
io::{Read, Write},
process::{Command, Stdio},
};
use crate::{
cmd::{run_cmd, CargoCmd},
in_official_repo,
terminal_link::TerminalFileLink,
DEBUG_PROFILE,
};
use crate::{in_official_repo, terminal_link::TerminalFileLink, DEBUG_PROFILE};
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 dir: Option<&'static str>,
// Exercise's unique name
@ -30,16 +66,11 @@ pub struct 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())?;
let mut bin_path =
PathBuf::with_capacity(target_dir.as_os_str().len() + 7 + self.name.len());
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)?;
let bin_path = format!("target/debug/{}", self.name);
let success = run_command(Command::new(&bin_path), &bin_path, output, true)?;
if !success {
writeln!(
@ -54,23 +85,43 @@ impl Exercise {
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();
// Developing the official Rustlings.
let dev = DEBUG_PROFILE && in_official_repo();
let build_success = CargoCmd {
subcommand: "build",
args: &[],
exercise_name: self.name,
description: "cargo build …",
hide_warnings: false,
target_dir,
output,
dev,
}
.run()?;
let build_success = self.cargo_cmd("build", &[], "cargo build …", output, dev, true)?;
if !build_success {
return Ok(false);
}
@ -83,46 +134,34 @@ impl Exercise {
} else {
&["--profile", "test"]
};
let clippy_success = CargoCmd {
subcommand: "clippy",
args: clippy_args,
exercise_name: self.name,
description: "cargo clippy …",
hide_warnings: false,
target_dir,
output,
dev,
}
.run()?;
let clippy_success =
self.cargo_cmd("clippy", clippy_args, "cargo clippy …", output, dev, true)?;
if !clippy_success {
return Ok(false);
}
if !self.test {
return self.run_bin(output, target_dir);
return self.run_bin(output);
}
let test_success = CargoCmd {
subcommand: "test",
args: &[
let test_success = self.cargo_cmd(
"test",
&[
"--",
"--color",
"always",
"--show-output",
"--nocapture",
"--format",
"pretty",
],
exercise_name: self.name,
description: "cargo test …",
// Hide warnings because they are shown by Clippy.
hide_warnings: true,
target_dir,
"cargo test …",
output,
dev,
}
.run()?;
// Hide warnings because they are shown by Clippy.
false,
)?;
let run_success = self.run_bin(output, target_dir)?;
let run_success = self.run_bin(output)?;
Ok(test_success && run_success)
}

View file

@ -42,7 +42,7 @@ pub fn list(app_state: &mut AppState) -> Result<()> {
ui_state.message.clear();
match key.code {
KeyCode::Esc | KeyCode::Char('q') => break,
KeyCode::Char('q') => break,
KeyCode::Down | KeyCode::Char('j') => ui_state.select_next(),
KeyCode::Up | KeyCode::Char('k') => ui_state.select_previous(),
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() {
// Help footer.
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 {
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 cargo_toml;
mod cmd;
mod dev;
mod embedded;
mod exercise;
@ -87,6 +86,8 @@ fn main() -> Result<()> {
bail!("{OLD_METHOD_ERR}");
}
which::which("cargo").context(CARGO_NOT_FOUND_ERR)?;
match args.command {
Some(Subcommands::Init) => {
if DEBUG_PROFILE {
@ -121,7 +122,7 @@ fn main() -> Result<()> {
let (mut app_state, state_file_status) = AppState::new(
info_file.exercises,
info_file.final_message.unwrap_or_default(),
)?;
);
if let Some(welcome_message) = info_file.welcome_message {
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:
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 =
"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.

View file

@ -11,7 +11,7 @@ use crate::{
pub fn run(app_state: &mut AppState) -> Result<()> {
let exercise = app_state.current_exercise();
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();
stdout.write_all(&output)?;

View file

@ -124,5 +124,5 @@ The automatic detection of exercise file changes failed :(
Please try running `rustlings` again.
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<()> {
self.show_hint = false;
let success = self
.app_state
.current_exercise()
.run(&mut self.output, self.app_state.target_dir())?;
let success = self.app_state.current_exercise().run(&mut self.output)?;
if success {
self.done_status =
if let Some(solution_path) = self.app_state.current_solution_path()? {
@ -130,7 +127,7 @@ impl<'a> WatchState<'a> {
self.writer,
"{}\n",
"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()
.green(),
)?;

View file

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