mirror of
https://github.com/rust-lang/rustlings.git
synced 2025-01-09 20:03:24 +03:00
Compare commits
12 commits
24539666af
...
7526c6b1f9
Author | SHA1 | Date | |
---|---|---|---|
7526c6b1f9 | |||
1cbabc3d28 | |||
bd10b154fe | |||
070a780d7f | |||
8aef915ee7 | |||
3da860927d | |||
1c90575b9f | |||
9dcc4b7df5 | |||
9831cbb139 | |||
bee62c89de | |||
5c0073a948 | |||
2a26dfcb00 |
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -4,7 +4,7 @@ target/
|
||||||
/dev/Cargo.lock
|
/dev/Cargo.lock
|
||||||
|
|
||||||
# State file
|
# State file
|
||||||
.rustlings-state.json
|
.rustlings-state.txt
|
||||||
|
|
||||||
# oranda
|
# oranda
|
||||||
public/
|
public/
|
||||||
|
|
72
Cargo.lock
generated
72
Cargo.lock
generated
|
@ -261,9 +261,9 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.10.0"
|
version = "1.11.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
|
checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
|
@ -684,12 +684,12 @@ dependencies = [
|
||||||
"assert_cmd",
|
"assert_cmd",
|
||||||
"clap",
|
"clap",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
|
"hashbrown",
|
||||||
"notify-debouncer-mini",
|
"notify-debouncer-mini",
|
||||||
"predicates",
|
"predicates",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
"rustlings-macros",
|
"rustlings-macros",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
|
||||||
"toml_edit",
|
"toml_edit",
|
||||||
"which",
|
"which",
|
||||||
]
|
]
|
||||||
|
@ -748,17 +748,6 @@ dependencies = [
|
||||||
"syn 2.0.58",
|
"syn 2.0.58",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_json"
|
|
||||||
version = "1.0.115"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd"
|
|
||||||
dependencies = [
|
|
||||||
"itoa",
|
|
||||||
"ryu",
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_spanned"
|
name = "serde_spanned"
|
||||||
version = "0.6.5"
|
version = "0.6.5"
|
||||||
|
@ -1011,7 +1000,7 @@ version = "0.52.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-targets 0.52.4",
|
"windows-targets 0.52.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1031,17 +1020,18 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-targets"
|
name = "windows-targets"
|
||||||
version = "0.52.4"
|
version = "0.52.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
|
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows_aarch64_gnullvm 0.52.4",
|
"windows_aarch64_gnullvm 0.52.5",
|
||||||
"windows_aarch64_msvc 0.52.4",
|
"windows_aarch64_msvc 0.52.5",
|
||||||
"windows_i686_gnu 0.52.4",
|
"windows_i686_gnu 0.52.5",
|
||||||
"windows_i686_msvc 0.52.4",
|
"windows_i686_gnullvm",
|
||||||
"windows_x86_64_gnu 0.52.4",
|
"windows_i686_msvc 0.52.5",
|
||||||
"windows_x86_64_gnullvm 0.52.4",
|
"windows_x86_64_gnu 0.52.5",
|
||||||
"windows_x86_64_msvc 0.52.4",
|
"windows_x86_64_gnullvm 0.52.5",
|
||||||
|
"windows_x86_64_msvc 0.52.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1052,9 +1042,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_gnullvm"
|
name = "windows_aarch64_gnullvm"
|
||||||
version = "0.52.4"
|
version = "0.52.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
|
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_msvc"
|
name = "windows_aarch64_msvc"
|
||||||
|
@ -1064,9 +1054,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_msvc"
|
name = "windows_aarch64_msvc"
|
||||||
version = "0.52.4"
|
version = "0.52.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
|
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_gnu"
|
name = "windows_i686_gnu"
|
||||||
|
@ -1076,9 +1066,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_gnu"
|
name = "windows_i686_gnu"
|
||||||
version = "0.52.4"
|
version = "0.52.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3"
|
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnullvm"
|
||||||
|
version = "0.52.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_msvc"
|
name = "windows_i686_msvc"
|
||||||
|
@ -1088,9 +1084,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_msvc"
|
name = "windows_i686_msvc"
|
||||||
version = "0.52.4"
|
version = "0.52.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
|
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnu"
|
name = "windows_x86_64_gnu"
|
||||||
|
@ -1100,9 +1096,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnu"
|
name = "windows_x86_64_gnu"
|
||||||
version = "0.52.4"
|
version = "0.52.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"
|
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnullvm"
|
name = "windows_x86_64_gnullvm"
|
||||||
|
@ -1112,9 +1108,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnullvm"
|
name = "windows_x86_64_gnullvm"
|
||||||
version = "0.52.4"
|
version = "0.52.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"
|
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_msvc"
|
name = "windows_x86_64_msvc"
|
||||||
|
@ -1124,9 +1120,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_msvc"
|
name = "windows_x86_64_msvc"
|
||||||
version = "0.52.4"
|
version = "0.52.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"
|
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "winnow"
|
||||||
|
|
|
@ -37,10 +37,10 @@ edition.workspace = true
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
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"
|
||||||
notify-debouncer-mini = "0.4.1"
|
notify-debouncer-mini = "0.4.1"
|
||||||
ratatui = "0.26.1"
|
ratatui = "0.26.1"
|
||||||
rustlings-macros = { path = "rustlings-macros" }
|
rustlings-macros = { path = "rustlings-macros" }
|
||||||
serde_json = "1.0.115"
|
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
toml_edit.workspace = true
|
toml_edit.workspace = true
|
||||||
which = "6.0.1"
|
which = "6.0.1"
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
// intro1.rs
|
// intro1.rs
|
||||||
//
|
//
|
||||||
// TODO: Update comment
|
|
||||||
// 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, remove the `I AM NOT DONE` comment below.
|
// ready for the next exercise, remove the `I AM NOT DONE` comment below.
|
||||||
|
|
|
@ -10,18 +10,18 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct Exercise {
|
struct ExerciseInfo {
|
||||||
name: String,
|
name: String,
|
||||||
path: String,
|
dir: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct InfoToml {
|
struct InfoFile {
|
||||||
exercises: Vec<Exercise>,
|
exercises: Vec<ExerciseInfo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let exercises = toml_edit::de::from_str::<InfoToml>(
|
let exercise_infos = toml_edit::de::from_str::<InfoFile>(
|
||||||
&fs::read_to_string("info.toml").context("Failed to read `info.toml`")?,
|
&fs::read_to_string("info.toml").context("Failed to read `info.toml`")?,
|
||||||
)
|
)
|
||||||
.context("Failed to deserialize `info.toml`")?
|
.context("Failed to deserialize `info.toml`")?
|
||||||
|
@ -36,12 +36,16 @@ fn main() -> Result<()> {
|
||||||
bin = [\n",
|
bin = [\n",
|
||||||
);
|
);
|
||||||
|
|
||||||
for exercise in exercises {
|
for exercise_info in exercise_infos {
|
||||||
buf.extend_from_slice(b" { name = \"");
|
buf.extend_from_slice(b" { name = \"");
|
||||||
buf.extend_from_slice(exercise.name.as_bytes());
|
buf.extend_from_slice(exercise_info.name.as_bytes());
|
||||||
buf.extend_from_slice(b"\", path = \"../");
|
buf.extend_from_slice(b"\", path = \"../exercises/");
|
||||||
buf.extend_from_slice(exercise.path.as_bytes());
|
if let Some(dir) = &exercise_info.dir {
|
||||||
buf.extend_from_slice(b"\" },\n");
|
buf.extend_from_slice(dir.as_bytes());
|
||||||
|
buf.push(b'/');
|
||||||
|
}
|
||||||
|
buf.extend_from_slice(exercise_info.name.as_bytes());
|
||||||
|
buf.extend_from_slice(b".rs\" },\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
buf.extend_from_slice(
|
buf.extend_from_slice(
|
||||||
|
|
272
info.toml
272
info.toml
|
@ -33,10 +33,11 @@ https://github.com/rust-lang/rustlings/blob/main/CONTRIBUTING.md
|
||||||
|
|
||||||
# INTRO
|
# INTRO
|
||||||
|
|
||||||
|
# TODO: Update exercise
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "intro1"
|
name = "intro1"
|
||||||
path = "exercises/00_intro/intro1.rs"
|
dir = "00_intro"
|
||||||
mode = "compile"
|
mode = "run"
|
||||||
# TODO: Fix hint
|
# TODO: Fix hint
|
||||||
hint = """
|
hint = """
|
||||||
Remove the `I AM NOT DONE` comment in the `exercises/intro00/intro1.rs` file
|
Remove the `I AM NOT DONE` comment in the `exercises/intro00/intro1.rs` file
|
||||||
|
@ -44,8 +45,8 @@ to move on to the next exercise."""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "intro2"
|
name = "intro2"
|
||||||
path = "exercises/00_intro/intro2.rs"
|
dir = "00_intro"
|
||||||
mode = "compile"
|
mode = "run"
|
||||||
hint = """
|
hint = """
|
||||||
The compiler is informing us that we've got the name of the print macro wrong, and has suggested an alternative."""
|
The compiler is informing us that we've got the name of the print macro wrong, and has suggested an alternative."""
|
||||||
|
|
||||||
|
@ -53,16 +54,16 @@ The compiler is informing us that we've got the name of the print macro wrong, a
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "variables1"
|
name = "variables1"
|
||||||
path = "exercises/01_variables/variables1.rs"
|
dir = "01_variables"
|
||||||
mode = "compile"
|
mode = "run"
|
||||||
hint = """
|
hint = """
|
||||||
The declaration in the first line in the main function is missing a keyword
|
The declaration in the first line in the main function is missing a keyword
|
||||||
that is needed in Rust to create a new variable binding."""
|
that is needed in Rust to create a new variable binding."""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "variables2"
|
name = "variables2"
|
||||||
path = "exercises/01_variables/variables2.rs"
|
dir = "01_variables"
|
||||||
mode = "compile"
|
mode = "run"
|
||||||
hint = """
|
hint = """
|
||||||
The compiler message is saying that Rust cannot infer the type that the
|
The compiler message is saying that Rust cannot infer the type that the
|
||||||
variable binding `x` has with what is given here.
|
variable binding `x` has with what is given here.
|
||||||
|
@ -80,8 +81,8 @@ What if `x` is the same type as `10`? What if it's a different type?"""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "variables3"
|
name = "variables3"
|
||||||
path = "exercises/01_variables/variables3.rs"
|
dir = "01_variables"
|
||||||
mode = "compile"
|
mode = "run"
|
||||||
hint = """
|
hint = """
|
||||||
Oops! In this exercise, we have a variable binding that we've created on in the
|
Oops! In this exercise, we have a variable binding that we've created on in the
|
||||||
first line in the `main` function, and we're trying to use it in the next line,
|
first line in the `main` function, and we're trying to use it in the next line,
|
||||||
|
@ -94,8 +95,8 @@ programming language -- thankfully the Rust compiler has caught this for us!"""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "variables4"
|
name = "variables4"
|
||||||
path = "exercises/01_variables/variables4.rs"
|
dir = "01_variables"
|
||||||
mode = "compile"
|
mode = "run"
|
||||||
hint = """
|
hint = """
|
||||||
In Rust, variable bindings are immutable by default. But here we're trying
|
In Rust, variable bindings are immutable by default. But here we're trying
|
||||||
to reassign a different value to `x`! There's a keyword we can use to make
|
to reassign a different value to `x`! There's a keyword we can use to make
|
||||||
|
@ -103,8 +104,8 @@ a variable binding mutable instead."""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "variables5"
|
name = "variables5"
|
||||||
path = "exercises/01_variables/variables5.rs"
|
dir = "01_variables"
|
||||||
mode = "compile"
|
mode = "run"
|
||||||
hint = """
|
hint = """
|
||||||
In `variables4` we already learned how to make an immutable variable mutable
|
In `variables4` we already learned how to make an immutable variable mutable
|
||||||
using a special keyword. Unfortunately this doesn't help us much in this
|
using a special keyword. Unfortunately this doesn't help us much in this
|
||||||
|
@ -121,8 +122,8 @@ Try to solve this exercise afterwards using this technique."""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "variables6"
|
name = "variables6"
|
||||||
path = "exercises/01_variables/variables6.rs"
|
dir = "01_variables"
|
||||||
mode = "compile"
|
mode = "run"
|
||||||
hint = """
|
hint = """
|
||||||
We know about variables and mutability, but there is another important type of
|
We know about variables and mutability, but there is another important type of
|
||||||
variable available: constants.
|
variable available: constants.
|
||||||
|
@ -141,8 +142,8 @@ https://doc.rust-lang.org/book/ch03-01-variables-and-mutability.html#constants
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "functions1"
|
name = "functions1"
|
||||||
path = "exercises/02_functions/functions1.rs"
|
dir = "02_functions"
|
||||||
mode = "compile"
|
mode = "run"
|
||||||
hint = """
|
hint = """
|
||||||
This main function is calling a function that it expects to exist, but the
|
This main function is calling a function that it expects to exist, but the
|
||||||
function doesn't exist. It expects this function to have the name `call_me`.
|
function doesn't exist. It expects this function to have the name `call_me`.
|
||||||
|
@ -151,24 +152,24 @@ Sounds a lot like `main`, doesn't it?"""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "functions2"
|
name = "functions2"
|
||||||
path = "exercises/02_functions/functions2.rs"
|
dir = "02_functions"
|
||||||
mode = "compile"
|
mode = "run"
|
||||||
hint = """
|
hint = """
|
||||||
Rust requires that all parts of a function's signature have type annotations,
|
Rust requires that all parts of a function's signature have type annotations,
|
||||||
but `call_me` is missing the type annotation of `num`."""
|
but `call_me` is missing the type annotation of `num`."""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "functions3"
|
name = "functions3"
|
||||||
path = "exercises/02_functions/functions3.rs"
|
dir = "02_functions"
|
||||||
mode = "compile"
|
mode = "run"
|
||||||
hint = """
|
hint = """
|
||||||
This time, the function *declaration* is okay, but there's something wrong
|
This time, the function *declaration* is okay, but there's something wrong
|
||||||
with the place where we're calling the function."""
|
with the place where we're calling the function."""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "functions4"
|
name = "functions4"
|
||||||
path = "exercises/02_functions/functions4.rs"
|
dir = "02_functions"
|
||||||
mode = "compile"
|
mode = "run"
|
||||||
hint = """
|
hint = """
|
||||||
The error message points to the function `sale_price` and says it expects a type
|
The error message points to the function `sale_price` and says it expects a type
|
||||||
after the `->`. This is where the function's return type should be -- take a
|
after the `->`. This is where the function's return type should be -- take a
|
||||||
|
@ -179,8 +180,8 @@ for the inputs of the functions here, since the original prices shouldn't be neg
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "functions5"
|
name = "functions5"
|
||||||
path = "exercises/02_functions/functions5.rs"
|
dir = "02_functions"
|
||||||
mode = "compile"
|
mode = "run"
|
||||||
hint = """
|
hint = """
|
||||||
This is a really common error that can be fixed by removing one character.
|
This is a really common error that can be fixed by removing one character.
|
||||||
It happens because Rust distinguishes between expressions and statements:
|
It happens because Rust distinguishes between expressions and statements:
|
||||||
|
@ -198,7 +199,7 @@ They are not the same. There are two solutions:
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "if1"
|
name = "if1"
|
||||||
path = "exercises/03_if/if1.rs"
|
dir = "03_if"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
It's possible to do this in one line if you would like!
|
It's possible to do this in one line if you would like!
|
||||||
|
@ -214,7 +215,7 @@ Remember in Rust that:
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "if2"
|
name = "if2"
|
||||||
path = "exercises/03_if/if2.rs"
|
dir = "03_if"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
For that first compiler error, it's important in Rust that each conditional
|
For that first compiler error, it's important in Rust that each conditional
|
||||||
|
@ -223,7 +224,7 @@ conditions checking different input values."""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "if3"
|
name = "if3"
|
||||||
path = "exercises/03_if/if3.rs"
|
dir = "03_if"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
In Rust, every arm of an `if` expression has to return the same type of value.
|
In Rust, every arm of an `if` expression has to return the same type of value.
|
||||||
|
@ -233,7 +234,6 @@ Make sure the type is consistent across all arms."""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "quiz1"
|
name = "quiz1"
|
||||||
path = "exercises/quiz1.rs"
|
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = "No hints this time ;)"
|
hint = "No hints this time ;)"
|
||||||
|
|
||||||
|
@ -241,20 +241,20 @@ hint = "No hints this time ;)"
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "primitive_types1"
|
name = "primitive_types1"
|
||||||
path = "exercises/04_primitive_types/primitive_types1.rs"
|
dir = "04_primitive_types"
|
||||||
mode = "compile"
|
mode = "run"
|
||||||
hint = "No hints this time ;)"
|
hint = "No hints this time ;)"
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "primitive_types2"
|
name = "primitive_types2"
|
||||||
path = "exercises/04_primitive_types/primitive_types2.rs"
|
dir = "04_primitive_types"
|
||||||
mode = "compile"
|
mode = "run"
|
||||||
hint = "No hints this time ;)"
|
hint = "No hints this time ;)"
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "primitive_types3"
|
name = "primitive_types3"
|
||||||
path = "exercises/04_primitive_types/primitive_types3.rs"
|
dir = "04_primitive_types"
|
||||||
mode = "compile"
|
mode = "run"
|
||||||
hint = """
|
hint = """
|
||||||
There's a shorthand to initialize Arrays with a certain size that does not
|
There's a shorthand to initialize Arrays with a certain size that does not
|
||||||
require you to type in 100 items (but you certainly can if you want!).
|
require you to type in 100 items (but you certainly can if you want!).
|
||||||
|
@ -269,7 +269,7 @@ for `a.len() >= 100`?"""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "primitive_types4"
|
name = "primitive_types4"
|
||||||
path = "exercises/04_primitive_types/primitive_types4.rs"
|
dir = "04_primitive_types"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
Take a look at the 'Understanding Ownership -> Slices -> Other Slices' section
|
Take a look at the 'Understanding Ownership -> Slices -> Other Slices' section
|
||||||
|
@ -284,8 +284,8 @@ https://doc.rust-lang.org/nomicon/coercions.html"""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "primitive_types5"
|
name = "primitive_types5"
|
||||||
path = "exercises/04_primitive_types/primitive_types5.rs"
|
dir = "04_primitive_types"
|
||||||
mode = "compile"
|
mode = "run"
|
||||||
hint = """
|
hint = """
|
||||||
Take a look at the 'Data Types -> The Tuple Type' section of the book:
|
Take a look at the 'Data Types -> The Tuple Type' section of the book:
|
||||||
https://doc.rust-lang.org/book/ch03-02-data-types.html#the-tuple-type
|
https://doc.rust-lang.org/book/ch03-02-data-types.html#the-tuple-type
|
||||||
|
@ -297,7 +297,7 @@ of the tuple. You can do it!!"""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "primitive_types6"
|
name = "primitive_types6"
|
||||||
path = "exercises/04_primitive_types/primitive_types6.rs"
|
dir = "04_primitive_types"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
While you could use a destructuring `let` for the tuple here, try
|
While you could use a destructuring `let` for the tuple here, try
|
||||||
|
@ -310,7 +310,7 @@ Now you have another tool in your toolbox!"""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "vecs1"
|
name = "vecs1"
|
||||||
path = "exercises/05_vecs/vecs1.rs"
|
dir = "05_vecs"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
In Rust, there are two ways to define a Vector.
|
In Rust, there are two ways to define a Vector.
|
||||||
|
@ -325,7 +325,7 @@ of the Rust book to learn more.
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "vecs2"
|
name = "vecs2"
|
||||||
path = "exercises/05_vecs/vecs2.rs"
|
dir = "05_vecs"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
In the first function we are looping over the Vector and getting a reference to
|
In the first function we are looping over the Vector and getting a reference to
|
||||||
|
@ -348,7 +348,7 @@ What do you think is the more commonly used pattern under Rust developers?
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "move_semantics1"
|
name = "move_semantics1"
|
||||||
path = "exercises/06_move_semantics/move_semantics1.rs"
|
dir = "06_move_semantics"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
So you've got the "cannot borrow immutable local variable `vec` as mutable"
|
So you've got the "cannot borrow immutable local variable `vec` as mutable"
|
||||||
|
@ -362,7 +362,7 @@ happens!"""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "move_semantics2"
|
name = "move_semantics2"
|
||||||
path = "exercises/06_move_semantics/move_semantics2.rs"
|
dir = "06_move_semantics"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
When running this exercise for the first time, you'll notice an error about
|
When running this exercise for the first time, you'll notice an error about
|
||||||
|
@ -383,7 +383,7 @@ try them all:
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "move_semantics3"
|
name = "move_semantics3"
|
||||||
path = "exercises/06_move_semantics/move_semantics3.rs"
|
dir = "06_move_semantics"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
The difference between this one and the previous ones is that the first line
|
The difference between this one and the previous ones is that the first line
|
||||||
|
@ -393,7 +393,7 @@ an existing binding to be a mutable binding instead of an immutable one :)"""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "move_semantics4"
|
name = "move_semantics4"
|
||||||
path = "exercises/06_move_semantics/move_semantics4.rs"
|
dir = "06_move_semantics"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
Stop reading whenever you feel like you have enough direction :) Or try
|
Stop reading whenever you feel like you have enough direction :) Or try
|
||||||
|
@ -407,7 +407,7 @@ So the end goal is to:
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "move_semantics5"
|
name = "move_semantics5"
|
||||||
path = "exercises/06_move_semantics/move_semantics5.rs"
|
dir = "06_move_semantics"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
Carefully reason about the range in which each mutable reference is in
|
Carefully reason about the range in which each mutable reference is in
|
||||||
|
@ -419,8 +419,8 @@ https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html#mutable-ref
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "move_semantics6"
|
name = "move_semantics6"
|
||||||
path = "exercises/06_move_semantics/move_semantics6.rs"
|
dir = "06_move_semantics"
|
||||||
mode = "compile"
|
mode = "run"
|
||||||
hint = """
|
hint = """
|
||||||
To find the answer, you can consult the book section "References and Borrowing":
|
To find the answer, you can consult the book section "References and Borrowing":
|
||||||
https://doc.rust-lang.org/stable/book/ch04-02-references-and-borrowing.html
|
https://doc.rust-lang.org/stable/book/ch04-02-references-and-borrowing.html
|
||||||
|
@ -440,7 +440,7 @@ Another hint: it has to do with the `&` character."""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "structs1"
|
name = "structs1"
|
||||||
path = "exercises/07_structs/structs1.rs"
|
dir = "07_structs"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
Rust has more than one type of struct. Three actually, all variants are used to
|
Rust has more than one type of struct. Three actually, all variants are used to
|
||||||
|
@ -460,7 +460,7 @@ https://doc.rust-lang.org/book/ch05-01-defining-structs.html"""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "structs2"
|
name = "structs2"
|
||||||
path = "exercises/07_structs/structs2.rs"
|
dir = "07_structs"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
Creating instances of structs is easy, all you need to do is assign some values
|
Creating instances of structs is easy, all you need to do is assign some values
|
||||||
|
@ -472,7 +472,7 @@ https://doc.rust-lang.org/stable/book/ch05-01-defining-structs.html#creating-ins
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "structs3"
|
name = "structs3"
|
||||||
path = "exercises/07_structs/structs3.rs"
|
dir = "07_structs"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
For `is_international`: What makes a package international? Seems related to
|
For `is_international`: What makes a package international? Seems related to
|
||||||
|
@ -488,21 +488,21 @@ https://doc.rust-lang.org/book/ch05-03-method-syntax.html"""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "enums1"
|
name = "enums1"
|
||||||
path = "exercises/08_enums/enums1.rs"
|
dir = "08_enums"
|
||||||
mode = "compile"
|
mode = "run"
|
||||||
hint = "No hints this time ;)"
|
hint = "No hints this time ;)"
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "enums2"
|
name = "enums2"
|
||||||
path = "exercises/08_enums/enums2.rs"
|
dir = "08_enums"
|
||||||
mode = "compile"
|
mode = "run"
|
||||||
hint = """
|
hint = """
|
||||||
You can create enumerations that have different variants with different types
|
You can create enumerations that have different variants with different types
|
||||||
such as no data, anonymous structs, a single string, tuples, ...etc"""
|
such as no data, anonymous structs, a single string, tuples, ...etc"""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "enums3"
|
name = "enums3"
|
||||||
path = "exercises/08_enums/enums3.rs"
|
dir = "08_enums"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
As a first step, you can define enums to compile this code without errors.
|
As a first step, you can define enums to compile this code without errors.
|
||||||
|
@ -516,8 +516,8 @@ to get value in the variant."""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "strings1"
|
name = "strings1"
|
||||||
path = "exercises/09_strings/strings1.rs"
|
dir = "09_strings"
|
||||||
mode = "compile"
|
mode = "run"
|
||||||
hint = """
|
hint = """
|
||||||
The `current_favorite_color` function is currently returning a string slice
|
The `current_favorite_color` function is currently returning a string slice
|
||||||
with the `'static` lifetime. We know this because the data of the string lives
|
with the `'static` lifetime. We know this because the data of the string lives
|
||||||
|
@ -530,8 +530,8 @@ another way that uses the `From` trait."""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "strings2"
|
name = "strings2"
|
||||||
path = "exercises/09_strings/strings2.rs"
|
dir = "09_strings"
|
||||||
mode = "compile"
|
mode = "run"
|
||||||
hint = """
|
hint = """
|
||||||
Yes, it would be really easy to fix this by just changing the value bound to
|
Yes, it would be really easy to fix this by just changing the value bound to
|
||||||
`word` to be a string slice instead of a `String`, wouldn't it?? There is a way
|
`word` to be a string slice instead of a `String`, wouldn't it?? There is a way
|
||||||
|
@ -545,7 +545,7 @@ https://doc.rust-lang.org/stable/book/ch15-02-deref.html#implicit-deref-coercion
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "strings3"
|
name = "strings3"
|
||||||
path = "exercises/09_strings/strings3.rs"
|
dir = "09_strings"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
There's tons of useful standard library functions for strings. Let's try and use some of them:
|
There's tons of useful standard library functions for strings. Let's try and use some of them:
|
||||||
|
@ -556,16 +556,16 @@ the string slice into an owned string, which you can then freely extend."""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "strings4"
|
name = "strings4"
|
||||||
path = "exercises/09_strings/strings4.rs"
|
dir = "09_strings"
|
||||||
mode = "compile"
|
mode = "run"
|
||||||
hint = "No hints this time ;)"
|
hint = "No hints this time ;)"
|
||||||
|
|
||||||
# MODULES
|
# MODULES
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "modules1"
|
name = "modules1"
|
||||||
path = "exercises/10_modules/modules1.rs"
|
dir = "10_modules"
|
||||||
mode = "compile"
|
mode = "run"
|
||||||
hint = """
|
hint = """
|
||||||
Everything is private in Rust by default-- but there's a keyword we can use
|
Everything is private in Rust by default-- but there's a keyword we can use
|
||||||
to make something public! The compiler error should point to the thing that
|
to make something public! The compiler error should point to the thing that
|
||||||
|
@ -573,8 +573,8 @@ needs to be public."""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "modules2"
|
name = "modules2"
|
||||||
path = "exercises/10_modules/modules2.rs"
|
dir = "10_modules"
|
||||||
mode = "compile"
|
mode = "run"
|
||||||
hint = """
|
hint = """
|
||||||
The delicious_snacks module is trying to present an external interface that is
|
The delicious_snacks module is trying to present an external interface that is
|
||||||
different than its internal structure (the `fruits` and `veggies` modules and
|
different than its internal structure (the `fruits` and `veggies` modules and
|
||||||
|
@ -585,8 +585,8 @@ Learn more at https://doc.rust-lang.org/book/ch07-04-bringing-paths-into-scope-w
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "modules3"
|
name = "modules3"
|
||||||
path = "exercises/10_modules/modules3.rs"
|
dir = "10_modules"
|
||||||
mode = "compile"
|
mode = "run"
|
||||||
hint = """
|
hint = """
|
||||||
`UNIX_EPOCH` and `SystemTime` are declared in the `std::time` module. Add a
|
`UNIX_EPOCH` and `SystemTime` are declared in the `std::time` module. Add a
|
||||||
`use` statement for these two to bring them into scope. You can use nested
|
`use` statement for these two to bring them into scope. You can use nested
|
||||||
|
@ -596,7 +596,7 @@ paths or the glob operator to bring these two in using only one line."""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "hashmaps1"
|
name = "hashmaps1"
|
||||||
path = "exercises/11_hashmaps/hashmaps1.rs"
|
dir = "11_hashmaps"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
Hint 1: Take a look at the return type of the function to figure out
|
Hint 1: Take a look at the return type of the function to figure out
|
||||||
|
@ -608,7 +608,7 @@ Hint 2: Number of fruits should be at least 5. And you have to put
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "hashmaps2"
|
name = "hashmaps2"
|
||||||
path = "exercises/11_hashmaps/hashmaps2.rs"
|
dir = "11_hashmaps"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
Use the `entry()` and `or_insert()` methods of `HashMap` to achieve this.
|
Use the `entry()` and `or_insert()` methods of `HashMap` to achieve this.
|
||||||
|
@ -617,7 +617,7 @@ Learn more at https://doc.rust-lang.org/stable/book/ch08-03-hash-maps.html#only-
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "hashmaps3"
|
name = "hashmaps3"
|
||||||
path = "exercises/11_hashmaps/hashmaps3.rs"
|
dir = "11_hashmaps"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
Hint 1: Use the `entry()` and `or_insert()` methods of `HashMap` to insert
|
Hint 1: Use the `entry()` and `or_insert()` methods of `HashMap` to insert
|
||||||
|
@ -635,7 +635,6 @@ Learn more at https://doc.rust-lang.org/book/ch08-03-hash-maps.html#updating-a-v
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "quiz2"
|
name = "quiz2"
|
||||||
path = "exercises/quiz2.rs"
|
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = "No hints this time ;)"
|
hint = "No hints this time ;)"
|
||||||
|
|
||||||
|
@ -643,7 +642,7 @@ hint = "No hints this time ;)"
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "options1"
|
name = "options1"
|
||||||
path = "exercises/12_options/options1.rs"
|
dir = "12_options"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
Options can have a `Some` value, with an inner value, or a `None` value,
|
Options can have a `Some` value, with an inner value, or a `None` value,
|
||||||
|
@ -655,7 +654,7 @@ it doesn't panic in your face later?"""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "options2"
|
name = "options2"
|
||||||
path = "exercises/12_options/options2.rs"
|
dir = "12_options"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
Check out:
|
Check out:
|
||||||
|
@ -672,8 +671,8 @@ Also see `Option::flatten`
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "options3"
|
name = "options3"
|
||||||
path = "exercises/12_options/options3.rs"
|
dir = "12_options"
|
||||||
mode = "compile"
|
mode = "run"
|
||||||
hint = """
|
hint = """
|
||||||
The compiler says a partial move happened in the `match` statement. How can
|
The compiler says a partial move happened in the `match` statement. How can
|
||||||
this be avoided? The compiler shows the correction needed.
|
this be avoided? The compiler shows the correction needed.
|
||||||
|
@ -685,7 +684,7 @@ https://doc.rust-lang.org/std/keyword.ref.html"""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "errors1"
|
name = "errors1"
|
||||||
path = "exercises/13_error_handling/errors1.rs"
|
dir = "13_error_handling"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
`Ok` and `Err` are the two variants of `Result`, so what the tests are saying
|
`Ok` and `Err` are the two variants of `Result`, so what the tests are saying
|
||||||
|
@ -701,7 +700,7 @@ To make this change, you'll need to:
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "errors2"
|
name = "errors2"
|
||||||
path = "exercises/13_error_handling/errors2.rs"
|
dir = "13_error_handling"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
One way to handle this is using a `match` statement on
|
One way to handle this is using a `match` statement on
|
||||||
|
@ -717,8 +716,8 @@ and give it a try!"""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "errors3"
|
name = "errors3"
|
||||||
path = "exercises/13_error_handling/errors3.rs"
|
dir = "13_error_handling"
|
||||||
mode = "compile"
|
mode = "run"
|
||||||
hint = """
|
hint = """
|
||||||
If other functions can return a `Result`, why shouldn't `main`? It's a fairly
|
If other functions can return a `Result`, why shouldn't `main`? It's a fairly
|
||||||
common convention to return something like `Result<(), ErrorType>` from your
|
common convention to return something like `Result<(), ErrorType>` from your
|
||||||
|
@ -729,7 +728,7 @@ positive results."""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "errors4"
|
name = "errors4"
|
||||||
path = "exercises/13_error_handling/errors4.rs"
|
dir = "13_error_handling"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
`PositiveNonzeroInteger::new` is always creating a new instance and returning
|
`PositiveNonzeroInteger::new` is always creating a new instance and returning
|
||||||
|
@ -741,8 +740,8 @@ everything is... okay :)"""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "errors5"
|
name = "errors5"
|
||||||
path = "exercises/13_error_handling/errors5.rs"
|
dir = "13_error_handling"
|
||||||
mode = "compile"
|
mode = "run"
|
||||||
hint = """
|
hint = """
|
||||||
There are two different possible `Result` types produced within `main()`, which
|
There are two different possible `Result` types produced within `main()`, which
|
||||||
are propagated using `?` operators. How do we declare a return type from
|
are propagated using `?` operators. How do we declare a return type from
|
||||||
|
@ -765,7 +764,7 @@ https://doc.rust-lang.org/stable/rust-by-example/error/multiple_error_types/reen
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "errors6"
|
name = "errors6"
|
||||||
path = "exercises/13_error_handling/errors6.rs"
|
dir = "13_error_handling"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
This exercise uses a completed version of `PositiveNonzeroInteger` from
|
This exercise uses a completed version of `PositiveNonzeroInteger` from
|
||||||
|
@ -787,8 +786,8 @@ https://doc.rust-lang.org/std/result/enum.Result.html#method.map_err"""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "generics1"
|
name = "generics1"
|
||||||
path = "exercises/14_generics/generics1.rs"
|
dir = "14_generics"
|
||||||
mode = "compile"
|
mode = "run"
|
||||||
hint = """
|
hint = """
|
||||||
Vectors in Rust make use of generics to create dynamically sized arrays of any
|
Vectors in Rust make use of generics to create dynamically sized arrays of any
|
||||||
type.
|
type.
|
||||||
|
@ -797,7 +796,7 @@ You need to tell the compiler what type we are pushing onto this vector."""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "generics2"
|
name = "generics2"
|
||||||
path = "exercises/14_generics/generics2.rs"
|
dir = "14_generics"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
Currently we are wrapping only values of type `u32`.
|
Currently we are wrapping only values of type `u32`.
|
||||||
|
@ -811,7 +810,7 @@ If you are still stuck https://doc.rust-lang.org/stable/book/ch10-01-syntax.html
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "traits1"
|
name = "traits1"
|
||||||
path = "exercises/15_traits/traits1.rs"
|
dir = "15_traits"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
A discussion about Traits in Rust can be found at:
|
A discussion about Traits in Rust can be found at:
|
||||||
|
@ -820,7 +819,7 @@ https://doc.rust-lang.org/book/ch10-02-traits.html
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "traits2"
|
name = "traits2"
|
||||||
path = "exercises/15_traits/traits2.rs"
|
dir = "15_traits"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
Notice how the trait takes ownership of `self`, and returns `Self`.
|
Notice how the trait takes ownership of `self`, and returns `Self`.
|
||||||
|
@ -833,7 +832,7 @@ the documentation at: https://doc.rust-lang.org/std/vec/struct.Vec.html"""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "traits3"
|
name = "traits3"
|
||||||
path = "exercises/15_traits/traits3.rs"
|
dir = "15_traits"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
Traits can have a default implementation for functions. Structs that implement
|
Traits can have a default implementation for functions. Structs that implement
|
||||||
|
@ -845,7 +844,7 @@ See the documentation at: https://doc.rust-lang.org/book/ch10-02-traits.html#def
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "traits4"
|
name = "traits4"
|
||||||
path = "exercises/15_traits/traits4.rs"
|
dir = "15_traits"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
Instead of using concrete types as parameters you can use traits. Try replacing
|
Instead of using concrete types as parameters you can use traits. Try replacing
|
||||||
|
@ -856,8 +855,8 @@ See the documentation at: https://doc.rust-lang.org/book/ch10-02-traits.html#tra
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "traits5"
|
name = "traits5"
|
||||||
path = "exercises/15_traits/traits5.rs"
|
dir = "15_traits"
|
||||||
mode = "compile"
|
mode = "run"
|
||||||
hint = """
|
hint = """
|
||||||
To ensure a parameter implements multiple traits use the '+ syntax'. Try
|
To ensure a parameter implements multiple traits use the '+ syntax'. Try
|
||||||
replacing the '??' with 'impl <> + <>'.
|
replacing the '??' with 'impl <> + <>'.
|
||||||
|
@ -869,7 +868,6 @@ See the documentation at: https://doc.rust-lang.org/book/ch10-02-traits.html#spe
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "quiz3"
|
name = "quiz3"
|
||||||
path = "exercises/quiz3.rs"
|
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
To find the best solution to this challenge you're going to need to think back
|
To find the best solution to this challenge you're going to need to think back
|
||||||
|
@ -881,16 +879,16 @@ You may also need this: `use std::fmt::Display;`."""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "lifetimes1"
|
name = "lifetimes1"
|
||||||
path = "exercises/16_lifetimes/lifetimes1.rs"
|
dir = "16_lifetimes"
|
||||||
mode = "compile"
|
mode = "run"
|
||||||
hint = """
|
hint = """
|
||||||
Let the compiler guide you. Also take a look at the book if you need help:
|
Let the compiler guide you. Also take a look at the book if you need help:
|
||||||
https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html"""
|
https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html"""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "lifetimes2"
|
name = "lifetimes2"
|
||||||
path = "exercises/16_lifetimes/lifetimes2.rs"
|
dir = "16_lifetimes"
|
||||||
mode = "compile"
|
mode = "run"
|
||||||
hint = """
|
hint = """
|
||||||
Remember that the generic lifetime `'a` will get the concrete lifetime that is
|
Remember that the generic lifetime `'a` will get the concrete lifetime that is
|
||||||
equal to the smaller of the lifetimes of `x` and `y`.
|
equal to the smaller of the lifetimes of `x` and `y`.
|
||||||
|
@ -903,8 +901,8 @@ inner block:
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "lifetimes3"
|
name = "lifetimes3"
|
||||||
path = "exercises/16_lifetimes/lifetimes3.rs"
|
dir = "16_lifetimes"
|
||||||
mode = "compile"
|
mode = "run"
|
||||||
hint = """
|
hint = """
|
||||||
If you use a lifetime annotation in a struct's fields, where else does it need
|
If you use a lifetime annotation in a struct's fields, where else does it need
|
||||||
to be added?"""
|
to be added?"""
|
||||||
|
@ -913,7 +911,7 @@ to be added?"""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "tests1"
|
name = "tests1"
|
||||||
path = "exercises/17_tests/tests1.rs"
|
dir = "17_tests"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
You don't even need to write any code to test -- you can just test values and
|
You don't even need to write any code to test -- you can just test values and
|
||||||
|
@ -928,7 +926,7 @@ ones pass, and which ones fail :)"""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "tests2"
|
name = "tests2"
|
||||||
path = "exercises/17_tests/tests2.rs"
|
dir = "17_tests"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
Like the previous exercise, you don't need to write any code to get this test
|
Like the previous exercise, you don't need to write any code to get this test
|
||||||
|
@ -941,7 +939,7 @@ argument comes first and which comes second!"""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "tests3"
|
name = "tests3"
|
||||||
path = "exercises/17_tests/tests3.rs"
|
dir = "17_tests"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
You can call a function right where you're passing arguments to `assert!`. So
|
You can call a function right where you're passing arguments to `assert!`. So
|
||||||
|
@ -952,7 +950,7 @@ what you're doing using `!`, like `assert!(!having_fun())`."""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "tests4"
|
name = "tests4"
|
||||||
path = "exercises/17_tests/tests4.rs"
|
dir = "17_tests"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
We expect method `Rectangle::new()` to panic for negative values.
|
We expect method `Rectangle::new()` to panic for negative values.
|
||||||
|
@ -966,7 +964,7 @@ https://doc.rust-lang.org/stable/book/ch11-01-writing-tests.html#checking-for-pa
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "iterators1"
|
name = "iterators1"
|
||||||
path = "exercises/18_iterators/iterators1.rs"
|
dir = "18_iterators"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
Step 1:
|
Step 1:
|
||||||
|
@ -989,7 +987,7 @@ https://doc.rust-lang.org/std/iter/trait.Iterator.html for some ideas.
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "iterators2"
|
name = "iterators2"
|
||||||
path = "exercises/18_iterators/iterators2.rs"
|
dir = "18_iterators"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
Step 1:
|
Step 1:
|
||||||
|
@ -1015,7 +1013,7 @@ powerful and very general. Rust just needs to know the desired type."""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "iterators3"
|
name = "iterators3"
|
||||||
path = "exercises/18_iterators/iterators3.rs"
|
dir = "18_iterators"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
The `divide` function needs to return the correct error when even division is
|
The `divide` function needs to return the correct error when even division is
|
||||||
|
@ -1034,7 +1032,7 @@ powerful! It can make the solution to this exercise infinitely easier."""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "iterators4"
|
name = "iterators4"
|
||||||
path = "exercises/18_iterators/iterators4.rs"
|
dir = "18_iterators"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
In an imperative language, you might write a `for` loop that updates a mutable
|
In an imperative language, you might write a `for` loop that updates a mutable
|
||||||
|
@ -1046,7 +1044,7 @@ Hint 2: Check out the `fold` and `rfold` methods!"""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "iterators5"
|
name = "iterators5"
|
||||||
path = "exercises/18_iterators/iterators5.rs"
|
dir = "18_iterators"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
The documentation for the `std::iter::Iterator` trait contains numerous methods
|
The documentation for the `std::iter::Iterator` trait contains numerous methods
|
||||||
|
@ -1065,7 +1063,7 @@ a different method that could make your code more compact than using `fold`."""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "box1"
|
name = "box1"
|
||||||
path = "exercises/19_smart_pointers/box1.rs"
|
dir = "19_smart_pointers"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
Step 1:
|
Step 1:
|
||||||
|
@ -1089,7 +1087,7 @@ definition and try other types!
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "rc1"
|
name = "rc1"
|
||||||
path = "exercises/19_smart_pointers/rc1.rs"
|
dir = "19_smart_pointers"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
This is a straightforward exercise to use the `Rc<T>` type. Each `Planet` has
|
This is a straightforward exercise to use the `Rc<T>` type. Each `Planet` has
|
||||||
|
@ -1108,8 +1106,8 @@ See more at: https://doc.rust-lang.org/book/ch15-04-rc.html
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "arc1"
|
name = "arc1"
|
||||||
path = "exercises/19_smart_pointers/arc1.rs"
|
dir = "19_smart_pointers"
|
||||||
mode = "compile"
|
mode = "run"
|
||||||
hint = """
|
hint = """
|
||||||
Make `shared_numbers` be an `Arc` from the numbers vector. Then, in order
|
Make `shared_numbers` be an `Arc` from the numbers vector. Then, in order
|
||||||
to avoid creating a copy of `numbers`, you'll need to create `child_numbers`
|
to avoid creating a copy of `numbers`, you'll need to create `child_numbers`
|
||||||
|
@ -1126,7 +1124,7 @@ https://doc.rust-lang.org/stable/book/ch16-00-concurrency.html
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "cow1"
|
name = "cow1"
|
||||||
path = "exercises/19_smart_pointers/cow1.rs"
|
dir = "19_smart_pointers"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
If `Cow` already owns the data it doesn't need to clone it when `to_mut()` is
|
If `Cow` already owns the data it doesn't need to clone it when `to_mut()` is
|
||||||
|
@ -1140,8 +1138,8 @@ on the `Cow` type.
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "threads1"
|
name = "threads1"
|
||||||
path = "exercises/20_threads/threads1.rs"
|
dir = "20_threads"
|
||||||
mode = "compile"
|
mode = "run"
|
||||||
hint = """
|
hint = """
|
||||||
`JoinHandle` is a struct that is returned from a spawned thread:
|
`JoinHandle` is a struct that is returned from a spawned thread:
|
||||||
https://doc.rust-lang.org/std/thread/fn.spawn.html
|
https://doc.rust-lang.org/std/thread/fn.spawn.html
|
||||||
|
@ -1158,8 +1156,8 @@ https://doc.rust-lang.org/std/thread/struct.JoinHandle.html
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "threads2"
|
name = "threads2"
|
||||||
path = "exercises/20_threads/threads2.rs"
|
dir = "20_threads"
|
||||||
mode = "compile"
|
mode = "run"
|
||||||
hint = """
|
hint = """
|
||||||
`Arc` is an Atomic Reference Counted pointer that allows safe, shared access
|
`Arc` is an Atomic Reference Counted pointer that allows safe, shared access
|
||||||
to **immutable** data. But we want to *change* the number of `jobs_completed`
|
to **immutable** data. But we want to *change* the number of `jobs_completed`
|
||||||
|
@ -1180,7 +1178,7 @@ https://doc.rust-lang.org/book/ch16-03-shared-state.html#sharing-a-mutext-betwee
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "threads3"
|
name = "threads3"
|
||||||
path = "exercises/20_threads/threads3.rs"
|
dir = "20_threads"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
An alternate way to handle concurrency between threads is to use an `mpsc`
|
An alternate way to handle concurrency between threads is to use an `mpsc`
|
||||||
|
@ -1199,8 +1197,8 @@ See https://doc.rust-lang.org/book/ch16-02-message-passing.html for more info.
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "macros1"
|
name = "macros1"
|
||||||
path = "exercises/21_macros/macros1.rs"
|
dir = "21_macros"
|
||||||
mode = "compile"
|
mode = "run"
|
||||||
hint = """
|
hint = """
|
||||||
When you call a macro, you need to add something special compared to a
|
When you call a macro, you need to add something special compared to a
|
||||||
regular function call. If you're stuck, take a look at what's inside
|
regular function call. If you're stuck, take a look at what's inside
|
||||||
|
@ -1208,8 +1206,8 @@ regular function call. If you're stuck, take a look at what's inside
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "macros2"
|
name = "macros2"
|
||||||
path = "exercises/21_macros/macros2.rs"
|
dir = "21_macros"
|
||||||
mode = "compile"
|
mode = "run"
|
||||||
hint = """
|
hint = """
|
||||||
Macros don't quite play by the same rules as the rest of Rust, in terms of
|
Macros don't quite play by the same rules as the rest of Rust, in terms of
|
||||||
what's available where.
|
what's available where.
|
||||||
|
@ -1219,8 +1217,8 @@ Unlike other things in Rust, the order of "where you define a macro" versus
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "macros3"
|
name = "macros3"
|
||||||
path = "exercises/21_macros/macros3.rs"
|
dir = "21_macros"
|
||||||
mode = "compile"
|
mode = "run"
|
||||||
hint = """
|
hint = """
|
||||||
In order to use a macro outside of its module, you need to do something
|
In order to use a macro outside of its module, you need to do something
|
||||||
special to the module to lift the macro out into its parent.
|
special to the module to lift the macro out into its parent.
|
||||||
|
@ -1230,8 +1228,8 @@ exported macros, if you've seen any of those around."""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "macros4"
|
name = "macros4"
|
||||||
path = "exercises/21_macros/macros4.rs"
|
dir = "21_macros"
|
||||||
mode = "compile"
|
mode = "run"
|
||||||
hint = """
|
hint = """
|
||||||
You only need to add a single character to make this compile.
|
You only need to add a single character to make this compile.
|
||||||
|
|
||||||
|
@ -1247,7 +1245,7 @@ https://veykril.github.io/tlborm/"""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "clippy1"
|
name = "clippy1"
|
||||||
path = "exercises/22_clippy/clippy1.rs"
|
dir = "22_clippy"
|
||||||
mode = "clippy"
|
mode = "clippy"
|
||||||
hint = """
|
hint = """
|
||||||
Rust stores the highest precision version of any long or infinite precision
|
Rust stores the highest precision version of any long or infinite precision
|
||||||
|
@ -1263,14 +1261,14 @@ appropriate replacement constant from `std::f32::consts`..."""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "clippy2"
|
name = "clippy2"
|
||||||
path = "exercises/22_clippy/clippy2.rs"
|
dir = "22_clippy"
|
||||||
mode = "clippy"
|
mode = "clippy"
|
||||||
hint = """
|
hint = """
|
||||||
`for` loops over `Option` values are more clearly expressed as an `if let`"""
|
`for` loops over `Option` values are more clearly expressed as an `if let`"""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "clippy3"
|
name = "clippy3"
|
||||||
path = "exercises/22_clippy/clippy3.rs"
|
dir = "22_clippy"
|
||||||
mode = "clippy"
|
mode = "clippy"
|
||||||
hint = "No hints this time!"
|
hint = "No hints this time!"
|
||||||
|
|
||||||
|
@ -1278,7 +1276,7 @@ hint = "No hints this time!"
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "using_as"
|
name = "using_as"
|
||||||
path = "exercises/23_conversions/using_as.rs"
|
dir = "23_conversions"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
Use the `as` operator to cast one of the operands in the last line of the
|
Use the `as` operator to cast one of the operands in the last line of the
|
||||||
|
@ -1286,14 +1284,14 @@ Use the `as` operator to cast one of the operands in the last line of the
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "from_into"
|
name = "from_into"
|
||||||
path = "exercises/23_conversions/from_into.rs"
|
dir = "23_conversions"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
Follow the steps provided right before the `From` implementation"""
|
Follow the steps provided right before the `From` implementation"""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "from_str"
|
name = "from_str"
|
||||||
path = "exercises/23_conversions/from_str.rs"
|
dir = "23_conversions"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
The implementation of `FromStr` should return an `Ok` with a `Person` object,
|
The implementation of `FromStr` should return an `Ok` with a `Person` object,
|
||||||
|
@ -1314,7 +1312,7 @@ https://doc.rust-lang.org/stable/rust-by-example/error/multiple_error_types/reen
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "try_from_into"
|
name = "try_from_into"
|
||||||
path = "exercises/23_conversions/try_from_into.rs"
|
dir = "23_conversions"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
Follow the steps provided right before the `TryFrom` implementation.
|
Follow the steps provided right before the `TryFrom` implementation.
|
||||||
|
@ -1337,7 +1335,7 @@ Challenge: Can you make the `TryFrom` implementations generic over many integer
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "as_ref_mut"
|
name = "as_ref_mut"
|
||||||
path = "exercises/23_conversions/as_ref_mut.rs"
|
dir = "23_conversions"
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """
|
hint = """
|
||||||
Add `AsRef<str>` or `AsMut<u32>` as a trait bound to the functions."""
|
Add `AsRef<str>` or `AsMut<u32>` as a trait bound to the functions."""
|
||||||
|
|
268
src/app_state.rs
268
src/app_state.rs
|
@ -4,106 +4,128 @@ use crossterm::{
|
||||||
terminal::{Clear, ClearType},
|
terminal::{Clear, ClearType},
|
||||||
ExecutableCommand,
|
ExecutableCommand,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::{
|
use std::{
|
||||||
fs,
|
fs::{self, File},
|
||||||
io::{StdoutLock, Write},
|
io::{Read, StdoutLock, Write},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{exercise::Exercise, FENISH_LINE};
|
use crate::{exercise::Exercise, info_file::ExerciseInfo, FENISH_LINE};
|
||||||
|
|
||||||
|
const STATE_FILE_NAME: &str = ".rustlings-state.txt";
|
||||||
const BAD_INDEX_ERR: &str = "The current exercise index is higher than the number of exercises";
|
const BAD_INDEX_ERR: &str = "The current exercise index is higher than the number of exercises";
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
#[serde(deny_unknown_fields)]
|
|
||||||
struct StateFile {
|
|
||||||
current_exercise_ind: usize,
|
|
||||||
progress: Vec<bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StateFile {
|
|
||||||
fn read(exercises: &[Exercise]) -> Option<Self> {
|
|
||||||
let file_content = fs::read(".rustlings-state.json").ok()?;
|
|
||||||
|
|
||||||
let slf: Self = serde_json::de::from_slice(&file_content).ok()?;
|
|
||||||
|
|
||||||
if slf.progress.len() != exercises.len() || slf.current_exercise_ind >= exercises.len() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(slf)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_or_default(exercises: &[Exercise]) -> Self {
|
|
||||||
Self::read(exercises).unwrap_or_else(|| Self {
|
|
||||||
current_exercise_ind: 0,
|
|
||||||
progress: vec![false; exercises.len()],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write(&self) -> Result<()> {
|
|
||||||
let mut buf = Vec::with_capacity(1024);
|
|
||||||
serde_json::ser::to_writer(&mut buf, self).context("Failed to serialize the state")?;
|
|
||||||
fs::write(".rustlings-state.json", buf)
|
|
||||||
.context("Failed to write the state file `.rustlings-state.json`")?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub enum ExercisesProgress {
|
pub enum ExercisesProgress {
|
||||||
AllDone,
|
AllDone,
|
||||||
Pending,
|
Pending,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum StateFileStatus {
|
||||||
|
Read,
|
||||||
|
NotRead,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
state_file: StateFile,
|
current_exercise_ind: usize,
|
||||||
exercises: &'static [Exercise],
|
exercises: Vec<Exercise>,
|
||||||
n_done: u16,
|
n_done: u16,
|
||||||
current_exercise: &'static Exercise,
|
final_message: String,
|
||||||
final_message: &'static str,
|
file_buf: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppState {
|
impl AppState {
|
||||||
pub fn new(mut exercises: Vec<Exercise>, mut final_message: String) -> Self {
|
fn update_from_file(&mut self) -> StateFileStatus {
|
||||||
// Leaking especially for sending the exercises to the debounce event handler.
|
self.file_buf.clear();
|
||||||
// Leaking is not a problem because the `AppState` instance lives until
|
self.n_done = 0;
|
||||||
// the end of the program.
|
|
||||||
exercises.shrink_to_fit();
|
|
||||||
let exercises = exercises.leak();
|
|
||||||
final_message.shrink_to_fit();
|
|
||||||
let final_message = final_message.leak();
|
|
||||||
|
|
||||||
let state_file = StateFile::read_or_default(exercises);
|
if File::open(STATE_FILE_NAME)
|
||||||
let n_done = state_file
|
.and_then(|mut file| file.read_to_end(&mut self.file_buf))
|
||||||
.progress
|
.is_err()
|
||||||
.iter()
|
{
|
||||||
.fold(0, |acc, done| acc + u16::from(*done));
|
return StateFileStatus::NotRead;
|
||||||
let current_exercise = &exercises[state_file.current_exercise_ind];
|
|
||||||
|
|
||||||
Self {
|
|
||||||
state_file,
|
|
||||||
exercises,
|
|
||||||
n_done,
|
|
||||||
current_exercise,
|
|
||||||
final_message,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// See `Self::write` for more information about the file format.
|
||||||
|
let mut lines = self.file_buf.split(|c| *c == b'\n');
|
||||||
|
let Some(current_exercise_name) = lines.next() else {
|
||||||
|
return StateFileStatus::NotRead;
|
||||||
|
};
|
||||||
|
|
||||||
|
if current_exercise_name.is_empty() || lines.next().is_none() {
|
||||||
|
return StateFileStatus::NotRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut done_exercises = hashbrown::HashSet::with_capacity(self.exercises.len());
|
||||||
|
|
||||||
|
for done_exerise_name in lines {
|
||||||
|
if done_exerise_name.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
done_exercises.insert(done_exerise_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ind, exercise) in self.exercises.iter_mut().enumerate() {
|
||||||
|
if done_exercises.contains(exercise.name.as_bytes()) {
|
||||||
|
exercise.done = true;
|
||||||
|
self.n_done += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if exercise.name.as_bytes() == current_exercise_name {
|
||||||
|
self.current_exercise_ind = ind;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StateFileStatus::Read
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(
|
||||||
|
exercise_infos: Vec<ExerciseInfo>,
|
||||||
|
final_message: String,
|
||||||
|
) -> (Self, StateFileStatus) {
|
||||||
|
let exercises = exercise_infos
|
||||||
|
.into_iter()
|
||||||
|
.map(|mut exercise_info| {
|
||||||
|
// Leaking to be able to borrow in the watch mode `Table`.
|
||||||
|
// Leaking is not a problem because the `AppState` instance lives until
|
||||||
|
// the end of the program.
|
||||||
|
let path = exercise_info.path().leak();
|
||||||
|
|
||||||
|
exercise_info.name.shrink_to_fit();
|
||||||
|
let name = exercise_info.name.leak();
|
||||||
|
|
||||||
|
let hint = exercise_info.hint.trim().to_owned();
|
||||||
|
|
||||||
|
Exercise {
|
||||||
|
name,
|
||||||
|
path,
|
||||||
|
mode: exercise_info.mode,
|
||||||
|
hint,
|
||||||
|
done: false,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let mut slf = Self {
|
||||||
|
current_exercise_ind: 0,
|
||||||
|
exercises,
|
||||||
|
n_done: 0,
|
||||||
|
final_message,
|
||||||
|
file_buf: Vec::with_capacity(2048),
|
||||||
|
};
|
||||||
|
|
||||||
|
let state_file_status = slf.update_from_file();
|
||||||
|
|
||||||
|
(slf, state_file_status)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn current_exercise_ind(&self) -> usize {
|
pub fn current_exercise_ind(&self) -> usize {
|
||||||
self.state_file.current_exercise_ind
|
self.current_exercise_ind
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn progress(&self) -> &[bool] {
|
pub fn exercises(&self) -> &[Exercise] {
|
||||||
&self.state_file.progress
|
&self.exercises
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn exercises(&self) -> &'static [Exercise] {
|
|
||||||
self.exercises
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -112,8 +134,8 @@ impl AppState {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn current_exercise(&self) -> &'static Exercise {
|
pub fn current_exercise(&self) -> &Exercise {
|
||||||
self.current_exercise
|
&self.exercises[self.current_exercise_ind]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_current_exercise_ind(&mut self, ind: usize) -> Result<()> {
|
pub fn set_current_exercise_ind(&mut self, ind: usize) -> Result<()> {
|
||||||
|
@ -121,70 +143,61 @@ impl AppState {
|
||||||
bail!(BAD_INDEX_ERR);
|
bail!(BAD_INDEX_ERR);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.state_file.current_exercise_ind = ind;
|
self.current_exercise_ind = ind;
|
||||||
self.current_exercise = &self.exercises[ind];
|
|
||||||
|
|
||||||
self.state_file.write()
|
self.write()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_current_exercise_by_name(&mut self, name: &str) -> Result<()> {
|
pub fn set_current_exercise_by_name(&mut self, name: &str) -> Result<()> {
|
||||||
let (ind, exercise) = self
|
// O(N) is fine since this method is used only once until the program exits.
|
||||||
|
// Building a hashmap would have more overhead.
|
||||||
|
self.current_exercise_ind = self
|
||||||
.exercises
|
.exercises
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.position(|exercise| exercise.name == name)
|
||||||
.find(|(_, exercise)| exercise.name == name)
|
|
||||||
.with_context(|| format!("No exercise found for '{name}'!"))?;
|
.with_context(|| format!("No exercise found for '{name}'!"))?;
|
||||||
|
|
||||||
self.state_file.current_exercise_ind = ind;
|
self.write()
|
||||||
self.current_exercise = exercise;
|
|
||||||
|
|
||||||
self.state_file.write()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_pending(&mut self, ind: usize) -> Result<()> {
|
pub fn set_pending(&mut self, ind: usize) -> Result<()> {
|
||||||
let done = self
|
let exercise = self.exercises.get_mut(ind).context(BAD_INDEX_ERR)?;
|
||||||
.state_file
|
|
||||||
.progress
|
|
||||||
.get_mut(ind)
|
|
||||||
.context(BAD_INDEX_ERR)?;
|
|
||||||
|
|
||||||
if *done {
|
if exercise.done {
|
||||||
*done = false;
|
exercise.done = false;
|
||||||
self.n_done -= 1;
|
self.n_done -= 1;
|
||||||
self.state_file.write()?;
|
self.write()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_pending_exercise_ind(&self) -> Option<usize> {
|
fn next_pending_exercise_ind(&self) -> Option<usize> {
|
||||||
let current_ind = self.state_file.current_exercise_ind;
|
if self.current_exercise_ind == self.exercises.len() - 1 {
|
||||||
|
|
||||||
if current_ind == self.state_file.progress.len() - 1 {
|
|
||||||
// The last exercise is done.
|
// The last exercise is done.
|
||||||
// Search for exercises not done from the start.
|
// Search for exercises not done from the start.
|
||||||
return self.state_file.progress[..current_ind]
|
return self.exercises[..self.current_exercise_ind]
|
||||||
.iter()
|
.iter()
|
||||||
.position(|done| !done);
|
.position(|exercise| !exercise.done);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The done exercise isn't the last one.
|
// The done exercise isn't the last one.
|
||||||
// Search for a pending exercise after the current one and then from the start.
|
// Search for a pending exercise after the current one and then from the start.
|
||||||
match self.state_file.progress[current_ind + 1..]
|
match self.exercises[self.current_exercise_ind + 1..]
|
||||||
.iter()
|
.iter()
|
||||||
.position(|done| !done)
|
.position(|exercise| !exercise.done)
|
||||||
{
|
{
|
||||||
Some(ind) => Some(current_ind + 1 + ind),
|
Some(ind) => Some(self.current_exercise_ind + 1 + ind),
|
||||||
None => self.state_file.progress[..current_ind]
|
None => self.exercises[..self.current_exercise_ind]
|
||||||
.iter()
|
.iter()
|
||||||
.position(|done| !done),
|
.position(|exercise| !exercise.done),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn done_current_exercise(&mut self, writer: &mut StdoutLock) -> Result<ExercisesProgress> {
|
pub fn done_current_exercise(&mut self, writer: &mut StdoutLock) -> Result<ExercisesProgress> {
|
||||||
let done = &mut self.state_file.progress[self.state_file.current_exercise_ind];
|
let exercise = &mut self.exercises[self.current_exercise_ind];
|
||||||
if !*done {
|
if !exercise.done {
|
||||||
*done = true;
|
exercise.done = true;
|
||||||
self.n_done += 1;
|
self.n_done += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,15 +211,14 @@ impl AppState {
|
||||||
if !exercise.run()?.status.success() {
|
if !exercise.run()?.status.success() {
|
||||||
writer.write_fmt(format_args!("{}\n\n", "FAILED".red()))?;
|
writer.write_fmt(format_args!("{}\n\n", "FAILED".red()))?;
|
||||||
|
|
||||||
self.state_file.current_exercise_ind = exercise_ind;
|
self.current_exercise_ind = exercise_ind;
|
||||||
self.current_exercise = exercise;
|
|
||||||
|
|
||||||
// No check if the exercise is done before setting it to pending
|
// No check if the exercise is done before setting it to pending
|
||||||
// because no pending exercise was found.
|
// because no pending exercise was found.
|
||||||
self.state_file.progress[exercise_ind] = false;
|
self.exercises[exercise_ind].done = false;
|
||||||
self.n_done -= 1;
|
self.n_done -= 1;
|
||||||
|
|
||||||
self.state_file.write()?;
|
self.write()?;
|
||||||
|
|
||||||
return Ok(ExercisesProgress::Pending);
|
return Ok(ExercisesProgress::Pending);
|
||||||
}
|
}
|
||||||
|
@ -216,8 +228,12 @@ impl AppState {
|
||||||
|
|
||||||
writer.execute(Clear(ClearType::All))?;
|
writer.execute(Clear(ClearType::All))?;
|
||||||
writer.write_all(FENISH_LINE.as_bytes())?;
|
writer.write_all(FENISH_LINE.as_bytes())?;
|
||||||
writer.write_all(self.final_message.as_bytes())?;
|
|
||||||
writer.write_all(b"\n")?;
|
let final_message = self.final_message.trim();
|
||||||
|
if !final_message.is_empty() {
|
||||||
|
writer.write_all(self.final_message.as_bytes())?;
|
||||||
|
writer.write_all(b"\n")?;
|
||||||
|
}
|
||||||
|
|
||||||
return Ok(ExercisesProgress::AllDone);
|
return Ok(ExercisesProgress::AllDone);
|
||||||
};
|
};
|
||||||
|
@ -226,6 +242,32 @@ impl AppState {
|
||||||
|
|
||||||
Ok(ExercisesProgress::Pending)
|
Ok(ExercisesProgress::Pending)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write the state file.
|
||||||
|
// The file's format is very simple:
|
||||||
|
// - The first line is the name of the current exercise. It must end with `\n` even if there
|
||||||
|
// are no done exercises.
|
||||||
|
// - The second line is an empty line.
|
||||||
|
// - All remaining lines are the names of done exercises.
|
||||||
|
fn write(&mut self) -> Result<()> {
|
||||||
|
self.file_buf.clear();
|
||||||
|
|
||||||
|
self.file_buf
|
||||||
|
.extend_from_slice(self.current_exercise().name.as_bytes());
|
||||||
|
self.file_buf.push(b'\n');
|
||||||
|
|
||||||
|
for exercise in &self.exercises {
|
||||||
|
if exercise.done {
|
||||||
|
self.file_buf.push(b'\n');
|
||||||
|
self.file_buf.extend_from_slice(exercise.name.as_bytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::write(STATE_FILE_NAME, &self.file_buf)
|
||||||
|
.with_context(|| format!("Failed to write the state file {STATE_FILE_NAME}"))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const RERUNNING_ALL_EXERCISES_MSG: &[u8] = b"
|
const RERUNNING_ALL_EXERCISES_MSG: &[u8] = b"
|
||||||
|
|
|
@ -91,7 +91,12 @@ impl EmbeddedFiles {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_exercise_to_disk(&self, path: &Path, strategy: WriteStrategy) -> io::Result<()> {
|
pub fn write_exercise_to_disk<P>(&self, path: P, strategy: WriteStrategy) -> io::Result<()>
|
||||||
|
where
|
||||||
|
P: AsRef<Path>,
|
||||||
|
{
|
||||||
|
let path = path.as_ref();
|
||||||
|
|
||||||
if let Some(file) = self
|
if let Some(file) = self
|
||||||
.exercises_dir
|
.exercises_dir
|
||||||
.files
|
.files
|
||||||
|
|
|
@ -1,77 +1,47 @@
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use serde::Deserialize;
|
use crossterm::style::{style, StyledContent, Stylize};
|
||||||
use std::{
|
use std::{
|
||||||
fmt::{self, Debug, Display, Formatter},
|
fmt::{self, Display, Formatter},
|
||||||
fs::{self},
|
fs,
|
||||||
path::PathBuf,
|
|
||||||
process::{Command, Output},
|
process::{Command, Output},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::embedded::{WriteStrategy, EMBEDDED_FILES};
|
use crate::{
|
||||||
|
embedded::{WriteStrategy, EMBEDDED_FILES},
|
||||||
|
info_file::Mode,
|
||||||
|
};
|
||||||
|
|
||||||
// The mode of the exercise.
|
pub struct TerminalFileLink<'a> {
|
||||||
#[derive(Deserialize, Copy, Clone)]
|
path: &'a str,
|
||||||
#[serde(rename_all = "lowercase")]
|
|
||||||
pub enum Mode {
|
|
||||||
// The exercise should be compiled as a binary
|
|
||||||
Compile,
|
|
||||||
// The exercise should be compiled as a test harness
|
|
||||||
Test,
|
|
||||||
// The exercise should be linted with clippy
|
|
||||||
Clippy,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
impl<'a> Display for TerminalFileLink<'a> {
|
||||||
#[serde(deny_unknown_fields)]
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
pub struct InfoFile {
|
if let Ok(Some(canonical_path)) = fs::canonicalize(self.path)
|
||||||
// TODO
|
.as_deref()
|
||||||
pub welcome_message: Option<String>,
|
.map(|path| path.to_str())
|
||||||
pub final_message: Option<String>,
|
{
|
||||||
pub exercises: Vec<Exercise>,
|
write!(
|
||||||
}
|
f,
|
||||||
|
"\x1b]8;;file://{}\x1b\\{}\x1b]8;;\x1b\\",
|
||||||
impl InfoFile {
|
canonical_path, self.path,
|
||||||
pub fn parse() -> Result<Self> {
|
)
|
||||||
// Read a local `info.toml` if it exists.
|
|
||||||
// Mainly to let the tests work for now.
|
|
||||||
let slf: Self = if let Ok(file_content) = fs::read_to_string("info.toml") {
|
|
||||||
toml_edit::de::from_str(&file_content)
|
|
||||||
} else {
|
} else {
|
||||||
toml_edit::de::from_str(include_str!("../info.toml"))
|
write!(f, "{}", self.path,)
|
||||||
}
|
}
|
||||||
.context("Failed to parse `info.toml`")?;
|
|
||||||
|
|
||||||
if slf.exercises.is_empty() {
|
|
||||||
panic!("{NO_EXERCISES_ERR}");
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(slf)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deserialized from the `info.toml` file.
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
#[serde(deny_unknown_fields)]
|
|
||||||
pub struct Exercise {
|
pub struct Exercise {
|
||||||
// Name of the exercise
|
// Exercise's unique name
|
||||||
pub name: String,
|
pub name: &'static str,
|
||||||
// The path to the file containing the exercise's source code
|
// Exercise's path
|
||||||
pub path: PathBuf,
|
pub path: &'static str,
|
||||||
// The mode of the exercise
|
// The mode of the exercise
|
||||||
pub mode: Mode,
|
pub mode: Mode,
|
||||||
// The hint text associated with the exercise
|
// The hint text associated with the exercise
|
||||||
pub hint: String,
|
pub hint: String,
|
||||||
}
|
pub done: bool,
|
||||||
|
|
||||||
// The context information of a pending exercise.
|
|
||||||
#[derive(PartialEq, Eq, Debug)]
|
|
||||||
pub struct ContextLine {
|
|
||||||
// The source code line
|
|
||||||
pub line: String,
|
|
||||||
// The line number
|
|
||||||
pub number: usize,
|
|
||||||
// Whether this is important and should be highlighted
|
|
||||||
pub important: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Exercise {
|
impl Exercise {
|
||||||
|
@ -90,7 +60,7 @@ impl Exercise {
|
||||||
.arg("always")
|
.arg("always")
|
||||||
.arg("-q")
|
.arg("-q")
|
||||||
.arg("--bin")
|
.arg("--bin")
|
||||||
.arg(&self.name)
|
.arg(self.name)
|
||||||
.args(args)
|
.args(args)
|
||||||
.output()
|
.output()
|
||||||
.context("Failed to run Cargo")
|
.context("Failed to run Cargo")
|
||||||
|
@ -98,7 +68,7 @@ impl Exercise {
|
||||||
|
|
||||||
pub fn run(&self) -> Result<Output> {
|
pub fn run(&self) -> Result<Output> {
|
||||||
match self.mode {
|
match self.mode {
|
||||||
Mode::Compile => self.cargo_cmd("run", &[]),
|
Mode::Run => self.cargo_cmd("run", &[]),
|
||||||
Mode::Test => self.cargo_cmd("test", &["--", "--nocapture", "--format", "pretty"]),
|
Mode::Test => self.cargo_cmd("test", &["--", "--nocapture", "--format", "pretty"]),
|
||||||
Mode::Clippy => self.cargo_cmd(
|
Mode::Clippy => self.cargo_cmd(
|
||||||
"clippy",
|
"clippy",
|
||||||
|
@ -109,16 +79,19 @@ impl Exercise {
|
||||||
|
|
||||||
pub fn reset(&self) -> Result<()> {
|
pub fn reset(&self) -> Result<()> {
|
||||||
EMBEDDED_FILES
|
EMBEDDED_FILES
|
||||||
.write_exercise_to_disk(&self.path, WriteStrategy::Overwrite)
|
.write_exercise_to_disk(self.path, WriteStrategy::Overwrite)
|
||||||
.with_context(|| format!("Failed to reset the exercise {self}"))
|
.with_context(|| format!("Failed to reset the exercise {self}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn terminal_link(&self) -> StyledContent<TerminalFileLink<'_>> {
|
||||||
|
style(TerminalFileLink { path: self.path })
|
||||||
|
.underlined()
|
||||||
|
.blue()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Exercise {
|
impl Display for Exercise {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
Display::fmt(&self.path.display(), f)
|
self.path.fmt(f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const NO_EXERCISES_ERR: &str = "There are no exercises yet!
|
|
||||||
If you are developing third-party exercises, add at least one exercise before testing.";
|
|
||||||
|
|
79
src/info_file.rs
Normal file
79
src/info_file.rs
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
use anyhow::{bail, Context, Error, Result};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
// The mode of the exercise.
|
||||||
|
#[derive(Deserialize, Copy, Clone)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
pub enum Mode {
|
||||||
|
// The exercise should be compiled as a binary
|
||||||
|
Run,
|
||||||
|
// The exercise should be compiled as a test harness
|
||||||
|
Test,
|
||||||
|
// The exercise should be linted with clippy
|
||||||
|
Clippy,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deserialized from the `info.toml` file.
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct ExerciseInfo {
|
||||||
|
// Name of the exercise
|
||||||
|
pub name: String,
|
||||||
|
// The exercise's directory inside the `exercises` directory
|
||||||
|
pub dir: Option<String>,
|
||||||
|
// The mode of the exercise
|
||||||
|
pub mode: Mode,
|
||||||
|
// The hint text associated with the exercise
|
||||||
|
pub hint: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExerciseInfo {
|
||||||
|
pub fn path(&self) -> String {
|
||||||
|
if let Some(dir) = &self.dir {
|
||||||
|
format!("exercises/{dir}/{}.rs", self.name)
|
||||||
|
} else {
|
||||||
|
format!("exercises/{}.rs", self.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct InfoFile {
|
||||||
|
pub welcome_message: Option<String>,
|
||||||
|
pub final_message: Option<String>,
|
||||||
|
pub exercises: Vec<ExerciseInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InfoFile {
|
||||||
|
pub fn parse() -> Result<Self> {
|
||||||
|
// Read a local `info.toml` if it exists.
|
||||||
|
let slf: Self = match fs::read_to_string("info.toml") {
|
||||||
|
Ok(file_content) => toml_edit::de::from_str(&file_content)
|
||||||
|
.context("Failed to parse the `info.toml` file")?,
|
||||||
|
Err(e) => match e.kind() {
|
||||||
|
std::io::ErrorKind::NotFound => {
|
||||||
|
toml_edit::de::from_str(include_str!("../info.toml"))
|
||||||
|
.context("Failed to parse the embedded `info.toml` file")?
|
||||||
|
}
|
||||||
|
_ => return Err(Error::from(e).context("Failed to read the `info.toml` file")),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if slf.exercises.is_empty() {
|
||||||
|
bail!("{NO_EXERCISES_ERR}");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut names_set = hashbrown::HashSet::with_capacity(slf.exercises.len());
|
||||||
|
for exercise in &slf.exercises {
|
||||||
|
if !names_set.insert(exercise.name.as_str()) {
|
||||||
|
bail!("Exercise names must all be unique!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
drop(names_set);
|
||||||
|
|
||||||
|
Ok(slf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const NO_EXERCISES_ERR: &str = "There are no exercises yet!
|
||||||
|
If you are developing third-party exercises, add at least one exercise before testing.";
|
25
src/init.rs
25
src/init.rs
|
@ -6,17 +6,21 @@ use std::{
|
||||||
path::Path,
|
path::Path,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{embedded::EMBEDDED_FILES, exercise::Exercise};
|
use crate::{embedded::EMBEDDED_FILES, info_file::ExerciseInfo};
|
||||||
|
|
||||||
fn create_cargo_toml(exercises: &[Exercise]) -> io::Result<()> {
|
fn create_cargo_toml(exercise_infos: &[ExerciseInfo]) -> io::Result<()> {
|
||||||
let mut cargo_toml = Vec::with_capacity(1 << 13);
|
let mut cargo_toml = Vec::with_capacity(1 << 13);
|
||||||
cargo_toml.extend_from_slice(b"bin = [\n");
|
cargo_toml.extend_from_slice(b"bin = [\n");
|
||||||
for exercise in exercises {
|
for exercise_info in exercise_infos {
|
||||||
cargo_toml.extend_from_slice(b" { name = \"");
|
cargo_toml.extend_from_slice(b" { name = \"");
|
||||||
cargo_toml.extend_from_slice(exercise.name.as_bytes());
|
cargo_toml.extend_from_slice(exercise_info.name.as_bytes());
|
||||||
cargo_toml.extend_from_slice(b"\", path = \"");
|
cargo_toml.extend_from_slice(b"\", path = \"exercises/");
|
||||||
cargo_toml.extend_from_slice(exercise.path.to_str().unwrap().as_bytes());
|
if let Some(dir) = &exercise_info.dir {
|
||||||
cargo_toml.extend_from_slice(b"\" },\n");
|
cargo_toml.extend_from_slice(dir.as_bytes());
|
||||||
|
cargo_toml.push(b'/');
|
||||||
|
}
|
||||||
|
cargo_toml.extend_from_slice(exercise_info.name.as_bytes());
|
||||||
|
cargo_toml.extend_from_slice(b".rs\" },\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
cargo_toml.extend_from_slice(
|
cargo_toml.extend_from_slice(
|
||||||
|
@ -54,7 +58,7 @@ fn create_vscode_dir() -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(exercises: &[Exercise]) -> Result<()> {
|
pub fn init(exercise_infos: &[ExerciseInfo]) -> Result<()> {
|
||||||
if Path::new("exercises").is_dir() && Path::new("Cargo.toml").is_file() {
|
if Path::new("exercises").is_dir() && Path::new("Cargo.toml").is_file() {
|
||||||
bail!(PROBABLY_IN_RUSTLINGS_DIR_ERR);
|
bail!(PROBABLY_IN_RUSTLINGS_DIR_ERR);
|
||||||
}
|
}
|
||||||
|
@ -74,7 +78,8 @@ pub fn init(exercises: &[Exercise]) -> Result<()> {
|
||||||
.init_exercises_dir()
|
.init_exercises_dir()
|
||||||
.context("Failed to initialize the `rustlings/exercises` directory")?;
|
.context("Failed to initialize the `rustlings/exercises` directory")?;
|
||||||
|
|
||||||
create_cargo_toml(exercises).context("Failed to create the file `rustlings/Cargo.toml`")?;
|
create_cargo_toml(exercise_infos)
|
||||||
|
.context("Failed to create the file `rustlings/Cargo.toml`")?;
|
||||||
|
|
||||||
create_gitignore().context("Failed to create the file `rustlings/.gitignore`")?;
|
create_gitignore().context("Failed to create the file `rustlings/.gitignore`")?;
|
||||||
|
|
||||||
|
@ -84,7 +89,7 @@ pub fn init(exercises: &[Exercise]) -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
const GITIGNORE: &[u8] = b"/target
|
const GITIGNORE: &[u8] = b"/target
|
||||||
/.rustlings-state.json
|
/.rustlings-state.txt
|
||||||
";
|
";
|
||||||
|
|
||||||
const VS_CODE_EXTENSIONS_JSON: &[u8] = br#"{"recommendations":["rust-lang.rust-analyzer"]}"#;
|
const VS_CODE_EXTENSIONS_JSON: &[u8] = br#"{"recommendations":["rust-lang.rust-analyzer"]}"#;
|
||||||
|
|
11
src/list.rs
11
src/list.rs
|
@ -5,7 +5,7 @@ use crossterm::{
|
||||||
ExecutableCommand,
|
ExecutableCommand,
|
||||||
};
|
};
|
||||||
use ratatui::{backend::CrosstermBackend, Terminal};
|
use ratatui::{backend::CrosstermBackend, Terminal};
|
||||||
use std::{fmt::Write, io};
|
use std::io;
|
||||||
|
|
||||||
mod state;
|
mod state;
|
||||||
|
|
||||||
|
@ -72,14 +72,7 @@ pub fn list(app_state: &mut AppState) -> Result<()> {
|
||||||
ui_state.message.push_str(message);
|
ui_state.message.push_str(message);
|
||||||
}
|
}
|
||||||
KeyCode::Char('r') => {
|
KeyCode::Char('r') => {
|
||||||
let Some(exercise) = ui_state.reset_selected()? else {
|
ui_state = ui_state.with_reset_selected()?;
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
ui_state = ui_state.with_updated_rows();
|
|
||||||
ui_state
|
|
||||||
.message
|
|
||||||
.write_fmt(format_args!("The exercise {exercise} has been reset!"))?;
|
|
||||||
}
|
}
|
||||||
KeyCode::Char('c') => {
|
KeyCode::Char('c') => {
|
||||||
ui_state.selected_to_current_exercise()?;
|
ui_state.selected_to_current_exercise()?;
|
||||||
|
|
|
@ -6,8 +6,9 @@ use ratatui::{
|
||||||
widgets::{Block, Borders, HighlightSpacing, Paragraph, Row, Table, TableState},
|
widgets::{Block, Borders, HighlightSpacing, Paragraph, Row, Table, TableState},
|
||||||
Frame,
|
Frame,
|
||||||
};
|
};
|
||||||
|
use std::fmt::Write;
|
||||||
|
|
||||||
use crate::{app_state::AppState, exercise::Exercise, progress_bar::progress_bar_ratatui};
|
use crate::{app_state::AppState, progress_bar::progress_bar_ratatui};
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||||
pub enum Filter {
|
pub enum Filter {
|
||||||
|
@ -34,10 +35,9 @@ impl<'a> UiState<'a> {
|
||||||
.app_state
|
.app_state
|
||||||
.exercises()
|
.exercises()
|
||||||
.iter()
|
.iter()
|
||||||
.zip(self.app_state.progress().iter().copied())
|
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter_map(|(ind, (exercise, done))| {
|
.filter_map(|(ind, exercise)| {
|
||||||
let exercise_state = if done {
|
let exercise_state = if exercise.done {
|
||||||
if self.filter == Filter::Pending {
|
if self.filter == Filter::Pending {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
@ -62,8 +62,8 @@ impl<'a> UiState<'a> {
|
||||||
Some(Row::new([
|
Some(Row::new([
|
||||||
next,
|
next,
|
||||||
exercise_state,
|
exercise_state,
|
||||||
Span::raw(&exercise.name),
|
Span::raw(exercise.name),
|
||||||
Span::raw(exercise.path.to_string_lossy()),
|
Span::raw(exercise.path),
|
||||||
]))
|
]))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -212,29 +212,30 @@ impl<'a> UiState<'a> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset_selected(&mut self) -> Result<Option<&'static Exercise>> {
|
pub fn with_reset_selected(mut self) -> Result<Self> {
|
||||||
let Some(selected) = self.table_state.selected() else {
|
let Some(selected) = self.table_state.selected() else {
|
||||||
return Ok(None);
|
return Ok(self);
|
||||||
};
|
};
|
||||||
|
|
||||||
let (ind, exercise) = self
|
let (ind, exercise) = self
|
||||||
.app_state
|
.app_state
|
||||||
.exercises()
|
.exercises()
|
||||||
.iter()
|
.iter()
|
||||||
.zip(self.app_state.progress())
|
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter_map(|(ind, (exercise, done))| match self.filter {
|
.filter_map(|(ind, exercise)| match self.filter {
|
||||||
Filter::Done => done.then_some((ind, exercise)),
|
Filter::Done => exercise.done.then_some((ind, exercise)),
|
||||||
Filter::Pending => (!done).then_some((ind, exercise)),
|
Filter::Pending => (!exercise.done).then_some((ind, exercise)),
|
||||||
Filter::None => Some((ind, exercise)),
|
Filter::None => Some((ind, exercise)),
|
||||||
})
|
})
|
||||||
.nth(selected)
|
.nth(selected)
|
||||||
.context("Invalid selection index")?;
|
.context("Invalid selection index")?;
|
||||||
|
|
||||||
self.app_state.set_pending(ind)?;
|
|
||||||
exercise.reset()?;
|
exercise.reset()?;
|
||||||
|
self.message
|
||||||
|
.write_fmt(format_args!("The exercise {exercise} has been reset!"))?;
|
||||||
|
self.app_state.set_pending(ind)?;
|
||||||
|
|
||||||
Ok(Some(exercise))
|
Ok(self.with_updated_rows())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn selected_to_current_exercise(&mut self) -> Result<()> {
|
pub fn selected_to_current_exercise(&mut self) -> Result<()> {
|
||||||
|
@ -244,12 +245,12 @@ impl<'a> UiState<'a> {
|
||||||
|
|
||||||
let ind = self
|
let ind = self
|
||||||
.app_state
|
.app_state
|
||||||
.progress()
|
.exercises()
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter_map(|(ind, done)| match self.filter {
|
.filter_map(|(ind, exercise)| match self.filter {
|
||||||
Filter::Done => done.then_some(ind),
|
Filter::Done => exercise.done.then_some(ind),
|
||||||
Filter::Pending => (!done).then_some(ind),
|
Filter::Pending => (!exercise.done).then_some(ind),
|
||||||
Filter::None => Some(ind),
|
Filter::None => Some(ind),
|
||||||
})
|
})
|
||||||
.nth(selected)
|
.nth(selected)
|
||||||
|
|
84
src/main.rs
84
src/main.rs
|
@ -1,10 +1,20 @@
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
use app_state::StateFileStatus;
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use std::{path::Path, process::exit};
|
use crossterm::{
|
||||||
|
terminal::{Clear, ClearType},
|
||||||
|
ExecutableCommand,
|
||||||
|
};
|
||||||
|
use std::{
|
||||||
|
io::{self, BufRead, Write},
|
||||||
|
path::Path,
|
||||||
|
process::exit,
|
||||||
|
};
|
||||||
|
|
||||||
mod app_state;
|
mod app_state;
|
||||||
mod embedded;
|
mod embedded;
|
||||||
mod exercise;
|
mod exercise;
|
||||||
|
mod info_file;
|
||||||
mod init;
|
mod init;
|
||||||
mod list;
|
mod list;
|
||||||
mod progress_bar;
|
mod progress_bar;
|
||||||
|
@ -13,7 +23,7 @@ mod watch;
|
||||||
|
|
||||||
use self::{
|
use self::{
|
||||||
app_state::AppState,
|
app_state::AppState,
|
||||||
exercise::InfoFile,
|
info_file::InfoFile,
|
||||||
init::init,
|
init::init,
|
||||||
list::list,
|
list::list,
|
||||||
run::run,
|
run::run,
|
||||||
|
@ -26,6 +36,10 @@ use self::{
|
||||||
struct Args {
|
struct Args {
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
command: Option<Subcommands>,
|
command: Option<Subcommands>,
|
||||||
|
/// Manually run the current exercise using `r` or `run` in the watch mode.
|
||||||
|
/// Only use this if Rustlings fails to detect exercise file changes.
|
||||||
|
#[arg(long)]
|
||||||
|
manual_run: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
|
@ -54,12 +68,10 @@ fn main() -> Result<()> {
|
||||||
|
|
||||||
which::which("cargo").context(CARGO_NOT_FOUND_ERR)?;
|
which::which("cargo").context(CARGO_NOT_FOUND_ERR)?;
|
||||||
|
|
||||||
let mut info_file = InfoFile::parse()?;
|
let info_file = InfoFile::parse()?;
|
||||||
info_file.exercises.shrink_to_fit();
|
|
||||||
let exercises = info_file.exercises;
|
|
||||||
|
|
||||||
if matches!(args.command, Some(Subcommands::Init)) {
|
if matches!(args.command, Some(Subcommands::Init)) {
|
||||||
init(&exercises).context("Initialization failed")?;
|
init(&info_file.exercises).context("Initialization failed")?;
|
||||||
|
|
||||||
println!("{POST_INIT_MSG}");
|
println!("{POST_INIT_MSG}");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
@ -68,18 +80,56 @@ fn main() -> Result<()> {
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut app_state = AppState::new(exercises, info_file.final_message.unwrap_or_default());
|
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 {
|
||||||
|
StateFileStatus::NotRead => {
|
||||||
|
let mut stdout = io::stdout().lock();
|
||||||
|
stdout.execute(Clear(ClearType::All))?;
|
||||||
|
|
||||||
|
let welcome_message = welcome_message.trim();
|
||||||
|
write!(stdout, "{welcome_message}\n\nPress ENTER to continue ")?;
|
||||||
|
stdout.flush()?;
|
||||||
|
|
||||||
|
io::stdin().lock().read_until(b'\n', &mut Vec::new())?;
|
||||||
|
|
||||||
|
stdout.execute(Clear(ClearType::All))?;
|
||||||
|
}
|
||||||
|
StateFileStatus::Read => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
match args.command {
|
match args.command {
|
||||||
None => loop {
|
None => {
|
||||||
match watch(&mut app_state)? {
|
let notify_exercise_paths: Option<&'static [&'static str]> = if args.manual_run {
|
||||||
WatchExit::Shutdown => break,
|
None
|
||||||
// It is much easier to exit the watch mode, launch the list mode and then restart
|
} else {
|
||||||
// the watch mode instead of trying to pause the watch threads and correct the
|
// For the the notify event handler thread.
|
||||||
// watch state.
|
// Leaking is not a problem because the slice lives until the end of the program.
|
||||||
WatchExit::List => list(&mut app_state)?,
|
Some(
|
||||||
|
app_state
|
||||||
|
.exercises()
|
||||||
|
.iter()
|
||||||
|
.map(|exercise| exercise.path)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.leak(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match watch(&mut app_state, notify_exercise_paths)? {
|
||||||
|
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.
|
||||||
|
WatchExit::List => list(&mut app_state)?,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
// `Init` is handled above.
|
// `Init` is handled above.
|
||||||
Some(Subcommands::Init) => (),
|
Some(Subcommands::Init) => (),
|
||||||
Some(Subcommands::Run { name }) => {
|
Some(Subcommands::Run { name }) => {
|
||||||
|
@ -90,10 +140,10 @@ fn main() -> Result<()> {
|
||||||
}
|
}
|
||||||
Some(Subcommands::Reset { name }) => {
|
Some(Subcommands::Reset { name }) => {
|
||||||
app_state.set_current_exercise_by_name(&name)?;
|
app_state.set_current_exercise_by_name(&name)?;
|
||||||
app_state.set_pending(app_state.current_exercise_ind())?;
|
|
||||||
let exercise = app_state.current_exercise();
|
let exercise = app_state.current_exercise();
|
||||||
exercise.reset()?;
|
exercise.reset()?;
|
||||||
println!("The exercise {exercise} has been reset!");
|
println!("The exercise {exercise} has been reset!");
|
||||||
|
app_state.set_pending(app_state.current_exercise_ind())?;
|
||||||
}
|
}
|
||||||
Some(Subcommands::Hint { name }) => {
|
Some(Subcommands::Hint { name }) => {
|
||||||
app_state.set_current_exercise_by_name(&name)?;
|
app_state.set_current_exercise_by_name(&name)?;
|
||||||
|
@ -124,7 +174,7 @@ const POST_INIT_MSG: &str = "
|
||||||
Done initialization!
|
Done initialization!
|
||||||
|
|
||||||
Run `cd rustlings` to go into the generated directory.
|
Run `cd rustlings` to go into the generated directory.
|
||||||
Then run `rustlings` for further instructions on getting started.";
|
Then run `rustlings` to get started.";
|
||||||
|
|
||||||
const FENISH_LINE: &str = "+----------------------------------------------------+
|
const FENISH_LINE: &str = "+----------------------------------------------------+
|
||||||
| You made it to the Fe-nish line! |
|
| You made it to the Fe-nish line! |
|
||||||
|
|
12
src/run.rs
12
src/run.rs
|
@ -17,18 +17,24 @@ pub fn run(app_state: &mut AppState) -> Result<()> {
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
app_state.set_pending(app_state.current_exercise_ind())?;
|
app_state.set_pending(app_state.current_exercise_ind())?;
|
||||||
|
|
||||||
bail!("Ran {exercise} with errors");
|
bail!(
|
||||||
|
"Ran {} with errors",
|
||||||
|
app_state.current_exercise().terminal_link(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
stdout.write_fmt(format_args!(
|
stdout.write_fmt(format_args!(
|
||||||
"{}{}\n",
|
"{}{}\n",
|
||||||
"✓ Successfully ran ".green(),
|
"✓ Successfully ran ".green(),
|
||||||
exercise.path.to_string_lossy().green(),
|
exercise.path.green(),
|
||||||
))?;
|
))?;
|
||||||
|
|
||||||
match app_state.done_current_exercise(&mut stdout)? {
|
match app_state.done_current_exercise(&mut stdout)? {
|
||||||
ExercisesProgress::AllDone => (),
|
ExercisesProgress::AllDone => (),
|
||||||
ExercisesProgress::Pending => println!("Next exercise: {}", app_state.current_exercise()),
|
ExercisesProgress::Pending => println!(
|
||||||
|
"Next exercise: {}",
|
||||||
|
app_state.current_exercise().terminal_link(),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
60
src/watch.rs
60
src/watch.rs
|
@ -11,14 +11,14 @@ use std::{
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod debounce_event;
|
mod notify_event;
|
||||||
mod state;
|
mod state;
|
||||||
mod terminal_event;
|
mod terminal_event;
|
||||||
|
|
||||||
use crate::app_state::{AppState, ExercisesProgress};
|
use crate::app_state::{AppState, ExercisesProgress};
|
||||||
|
|
||||||
use self::{
|
use self::{
|
||||||
debounce_event::DebounceEventHandler,
|
notify_event::DebounceEventHandler,
|
||||||
state::WatchState,
|
state::WatchState,
|
||||||
terminal_event::{terminal_event_handler, InputEvent},
|
terminal_event::{terminal_event_handler, InputEvent},
|
||||||
};
|
};
|
||||||
|
@ -40,24 +40,40 @@ pub enum WatchExit {
|
||||||
List,
|
List,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn watch(app_state: &mut AppState) -> Result<WatchExit> {
|
pub fn watch(
|
||||||
|
app_state: &mut AppState,
|
||||||
|
notify_exercise_paths: Option<&'static [&'static str]>,
|
||||||
|
) -> Result<WatchExit> {
|
||||||
let (tx, rx) = channel();
|
let (tx, rx) = channel();
|
||||||
let mut debouncer = new_debouncer(
|
|
||||||
Duration::from_secs(1),
|
|
||||||
DebounceEventHandler {
|
|
||||||
tx: tx.clone(),
|
|
||||||
exercises: app_state.exercises(),
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
debouncer
|
|
||||||
.watcher()
|
|
||||||
.watch(Path::new("exercises"), RecursiveMode::Recursive)?;
|
|
||||||
|
|
||||||
let mut watch_state = WatchState::new(app_state);
|
let mut manual_run = false;
|
||||||
|
// Prevent dropping the guard until the end of the function.
|
||||||
|
// Otherwise, the file watcher exits.
|
||||||
|
let _debouncer_guard = if let Some(exercise_paths) = notify_exercise_paths {
|
||||||
|
let mut debouncer = new_debouncer(
|
||||||
|
Duration::from_secs(1),
|
||||||
|
DebounceEventHandler {
|
||||||
|
tx: tx.clone(),
|
||||||
|
exercise_paths,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.inspect_err(|_| eprintln!("{NOTIFY_ERR}"))?;
|
||||||
|
debouncer
|
||||||
|
.watcher()
|
||||||
|
.watch(Path::new("exercises"), RecursiveMode::Recursive)
|
||||||
|
.inspect_err(|_| eprintln!("{NOTIFY_ERR}"))?;
|
||||||
|
|
||||||
|
Some(debouncer)
|
||||||
|
} else {
|
||||||
|
manual_run = true;
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut watch_state = WatchState::new(app_state, manual_run);
|
||||||
|
|
||||||
watch_state.run_current_exercise()?;
|
watch_state.run_current_exercise()?;
|
||||||
|
|
||||||
thread::spawn(move || terminal_event_handler(tx));
|
thread::spawn(move || terminal_event_handler(tx, manual_run));
|
||||||
|
|
||||||
while let Ok(event) = rx.recv() {
|
while let Ok(event) = rx.recv() {
|
||||||
match event {
|
match event {
|
||||||
|
@ -75,6 +91,7 @@ pub fn watch(app_state: &mut AppState) -> Result<WatchExit> {
|
||||||
watch_state.into_writer().write_all(QUIT_MSG)?;
|
watch_state.into_writer().write_all(QUIT_MSG)?;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
WatchEvent::Input(InputEvent::Run) => watch_state.run_current_exercise()?,
|
||||||
WatchEvent::Input(InputEvent::Unrecognized(cmd)) => {
|
WatchEvent::Input(InputEvent::Unrecognized(cmd)) => {
|
||||||
watch_state.handle_invalid_cmd(&cmd)?;
|
watch_state.handle_invalid_cmd(&cmd)?;
|
||||||
}
|
}
|
||||||
|
@ -85,10 +102,11 @@ pub fn watch(app_state: &mut AppState) -> Result<WatchExit> {
|
||||||
watch_state.render()?;
|
watch_state.render()?;
|
||||||
}
|
}
|
||||||
WatchEvent::NotifyErr(e) => {
|
WatchEvent::NotifyErr(e) => {
|
||||||
return Err(Error::from(e).context("Exercise file watcher failed"))
|
watch_state.into_writer().write_all(NOTIFY_ERR.as_bytes())?;
|
||||||
|
return Err(Error::from(e));
|
||||||
}
|
}
|
||||||
WatchEvent::TerminalEventErr(e) => {
|
WatchEvent::TerminalEventErr(e) => {
|
||||||
return Err(Error::from(e).context("Terminal event listener failed"))
|
return Err(Error::from(e).context("Terminal event listener failed"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -100,3 +118,11 @@ const QUIT_MSG: &[u8] = b"
|
||||||
We hope you're enjoying learning Rust!
|
We hope you're enjoying learning Rust!
|
||||||
If you want to continue working on the exercises at a later point, you can simply run `rustlings` again.
|
If you want to continue working on the exercises at a later point, you can simply run `rustlings` again.
|
||||||
";
|
";
|
||||||
|
|
||||||
|
const NOTIFY_ERR: &str = "
|
||||||
|
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.
|
||||||
|
";
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
use notify_debouncer_mini::{DebounceEventResult, DebouncedEventKind};
|
use notify_debouncer_mini::{DebounceEventResult, DebouncedEventKind};
|
||||||
use std::sync::mpsc::Sender;
|
use std::sync::mpsc::Sender;
|
||||||
|
|
||||||
use crate::exercise::Exercise;
|
|
||||||
|
|
||||||
use super::WatchEvent;
|
use super::WatchEvent;
|
||||||
|
|
||||||
pub struct DebounceEventHandler {
|
pub struct DebounceEventHandler {
|
||||||
pub tx: Sender<WatchEvent>,
|
pub tx: Sender<WatchEvent>,
|
||||||
pub exercises: &'static [Exercise],
|
pub exercise_paths: &'static [&'static str],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl notify_debouncer_mini::DebounceEventHandler for DebounceEventHandler {
|
impl notify_debouncer_mini::DebounceEventHandler for DebounceEventHandler {
|
||||||
|
@ -23,9 +21,9 @@ impl notify_debouncer_mini::DebounceEventHandler for DebounceEventHandler {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.exercises
|
self.exercise_paths
|
||||||
.iter()
|
.iter()
|
||||||
.position(|exercise| event.path.ends_with(&exercise.path))
|
.position(|path| event.path.ends_with(path))
|
||||||
})
|
})
|
||||||
.min()
|
.min()
|
||||||
else {
|
else {
|
|
@ -18,10 +18,11 @@ pub struct WatchState<'a> {
|
||||||
stderr: Option<Vec<u8>>,
|
stderr: Option<Vec<u8>>,
|
||||||
show_hint: bool,
|
show_hint: bool,
|
||||||
show_done: bool,
|
show_done: bool,
|
||||||
|
manual_run: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> WatchState<'a> {
|
impl<'a> WatchState<'a> {
|
||||||
pub fn new(app_state: &'a mut AppState) -> Self {
|
pub fn new(app_state: &'a mut AppState, manual_run: bool) -> Self {
|
||||||
let writer = io::stdout().lock();
|
let writer = io::stdout().lock();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
@ -31,6 +32,7 @@ impl<'a> WatchState<'a> {
|
||||||
stderr: None,
|
stderr: None,
|
||||||
show_hint: false,
|
show_hint: false,
|
||||||
show_done: false,
|
show_done: false,
|
||||||
|
manual_run,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,6 +80,10 @@ impl<'a> WatchState<'a> {
|
||||||
fn show_prompt(&mut self) -> io::Result<()> {
|
fn show_prompt(&mut self) -> io::Result<()> {
|
||||||
self.writer.write_all(b"\n")?;
|
self.writer.write_all(b"\n")?;
|
||||||
|
|
||||||
|
if self.manual_run {
|
||||||
|
self.writer.write_fmt(format_args!("{}un/", 'r'.bold()))?;
|
||||||
|
}
|
||||||
|
|
||||||
if self.show_done {
|
if self.show_done {
|
||||||
self.writer.write_fmt(format_args!("{}ext/", 'n'.bold()))?;
|
self.writer.write_fmt(format_args!("{}ext/", 'n'.bold()))?;
|
||||||
}
|
}
|
||||||
|
@ -136,11 +142,7 @@ When you are done experimenting, enter `n` or `next` to go to the next exercise
|
||||||
)?;
|
)?;
|
||||||
self.writer.write_fmt(format_args!(
|
self.writer.write_fmt(format_args!(
|
||||||
"{progress_bar}Current exercise: {}\n",
|
"{progress_bar}Current exercise: {}\n",
|
||||||
self.app_state
|
self.app_state.current_exercise().terminal_link(),
|
||||||
.current_exercise()
|
|
||||||
.path
|
|
||||||
.to_string_lossy()
|
|
||||||
.bold(),
|
|
||||||
))?;
|
))?;
|
||||||
|
|
||||||
self.show_prompt()?;
|
self.show_prompt()?;
|
||||||
|
|
|
@ -4,6 +4,7 @@ use std::sync::mpsc::Sender;
|
||||||
use super::WatchEvent;
|
use super::WatchEvent;
|
||||||
|
|
||||||
pub enum InputEvent {
|
pub enum InputEvent {
|
||||||
|
Run,
|
||||||
Next,
|
Next,
|
||||||
Hint,
|
Hint,
|
||||||
List,
|
List,
|
||||||
|
@ -11,7 +12,7 @@ pub enum InputEvent {
|
||||||
Unrecognized(String),
|
Unrecognized(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn terminal_event_handler(tx: Sender<WatchEvent>) {
|
pub fn terminal_event_handler(tx: Sender<WatchEvent>, manual_run: bool) {
|
||||||
let mut input = String::with_capacity(8);
|
let mut input = String::with_capacity(8);
|
||||||
|
|
||||||
let last_input_event = loop {
|
let last_input_event = loop {
|
||||||
|
@ -43,6 +44,7 @@ pub fn terminal_event_handler(tx: Sender<WatchEvent>) {
|
||||||
"h" | "hint" => InputEvent::Hint,
|
"h" | "hint" => InputEvent::Hint,
|
||||||
"l" | "list" => break InputEvent::List,
|
"l" | "list" => break InputEvent::List,
|
||||||
"q" | "quit" => break InputEvent::Quit,
|
"q" | "quit" => break InputEvent::Quit,
|
||||||
|
"r" | "run" if manual_run => InputEvent::Run,
|
||||||
_ => InputEvent::Unrecognized(input.clone()),
|
_ => InputEvent::Unrecognized(input.clone()),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -5,34 +5,39 @@ use serde::Deserialize;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct Exercise {
|
struct ExerciseInfo {
|
||||||
name: String,
|
name: String,
|
||||||
path: String,
|
dir: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct InfoToml {
|
struct InfoFile {
|
||||||
exercises: Vec<Exercise>,
|
exercises: Vec<ExerciseInfo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn dev_cargo_bins() {
|
fn dev_cargo_bins() {
|
||||||
let content = fs::read_to_string("dev/Cargo.toml").unwrap();
|
let cargo_toml = fs::read_to_string("dev/Cargo.toml").unwrap();
|
||||||
|
|
||||||
let exercises = toml_edit::de::from_str::<InfoToml>(&fs::read_to_string("info.toml").unwrap())
|
let exercise_infos =
|
||||||
.unwrap()
|
toml_edit::de::from_str::<InfoFile>(&fs::read_to_string("info.toml").unwrap())
|
||||||
.exercises;
|
.unwrap()
|
||||||
|
.exercises;
|
||||||
|
|
||||||
let mut start_ind = 0;
|
let mut start_ind = 0;
|
||||||
for exercise in exercises {
|
for exercise_info in exercise_infos {
|
||||||
let name_start = start_ind + content[start_ind..].find('"').unwrap() + 1;
|
let name_start = start_ind + cargo_toml[start_ind..].find('"').unwrap() + 1;
|
||||||
let name_end = name_start + content[name_start..].find('"').unwrap();
|
let name_end = name_start + cargo_toml[name_start..].find('"').unwrap();
|
||||||
assert_eq!(exercise.name, &content[name_start..name_end]);
|
assert_eq!(exercise_info.name, &cargo_toml[name_start..name_end]);
|
||||||
|
|
||||||
// +3 to skip `../` at the begeinning of the path.
|
let path_start = name_end + cargo_toml[name_end + 1..].find('"').unwrap() + 2;
|
||||||
let path_start = name_end + content[name_end + 1..].find('"').unwrap() + 5;
|
let path_end = path_start + cargo_toml[path_start..].find('"').unwrap();
|
||||||
let path_end = path_start + content[path_start..].find('"').unwrap();
|
let expected_path = if let Some(dir) = exercise_info.dir {
|
||||||
assert_eq!(exercise.path, &content[path_start..path_end]);
|
format!("../exercises/{dir}/{}.rs", exercise_info.name)
|
||||||
|
} else {
|
||||||
|
format!("../exercises/{}.rs", exercise_info.name)
|
||||||
|
};
|
||||||
|
assert_eq!(expected_path, &cargo_toml[path_start..path_end]);
|
||||||
|
|
||||||
start_ind = path_end + 1;
|
start_ind = path_end + 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "compFailure"
|
name = "compFailure"
|
||||||
path = "exercises/compFailure.rs"
|
mode = "run"
|
||||||
mode = "compile"
|
|
||||||
hint = ""
|
hint = ""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "testFailure"
|
name = "testFailure"
|
||||||
path = "exercises/testFailure.rs"
|
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = "Hello!"
|
hint = "Hello!"
|
||||||
|
|
|
@ -1,17 +1,14 @@
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "pending_exercise"
|
name = "pending_exercise"
|
||||||
path = "exercises/pending_exercise.rs"
|
mode = "run"
|
||||||
mode = "compile"
|
|
||||||
hint = """"""
|
hint = """"""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "pending_test_exercise"
|
name = "pending_test_exercise"
|
||||||
path = "exercises/pending_test_exercise.rs"
|
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """"""
|
hint = """"""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "finished_exercise"
|
name = "finished_exercise"
|
||||||
path = "exercises/finished_exercise.rs"
|
mode = "run"
|
||||||
mode = "compile"
|
|
||||||
hint = """"""
|
hint = """"""
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "compSuccess"
|
name = "compSuccess"
|
||||||
path = "exercises/compSuccess.rs"
|
mode = "run"
|
||||||
mode = "compile"
|
|
||||||
hint = """"""
|
hint = """"""
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
name = "testSuccess"
|
name = "testSuccess"
|
||||||
path = "exercises/testSuccess.rs"
|
|
||||||
mode = "test"
|
mode = "test"
|
||||||
hint = """"""
|
hint = """"""
|
||||||
|
|
Loading…
Reference in a new issue