mirror of
https://github.com/rust-lang/rustlings.git
synced 2024-12-26 15:26:29 +00:00
Compare commits
9 commits
b2f62bdad5
...
60d686f00e
Author | SHA1 | Date | |
---|---|---|---|
|
60d686f00e | ||
|
930a0ea73b | ||
|
7e2f56f41a | ||
|
e90f5f03f3 | ||
|
0e090ae112 | ||
|
99496706c5 | ||
|
f146553dea | ||
|
e59c65cf45 | ||
|
1c27aeead9 |
21 changed files with 161 additions and 66 deletions
15
Cargo.lock
generated
15
Cargo.lock
generated
|
@ -186,12 +186,6 @@ dependencies = [
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "foldhash"
|
|
||||||
version = "0.1.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fsevent-sys"
|
name = "fsevent-sys"
|
||||||
version = "4.1.0"
|
version = "4.1.0"
|
||||||
|
@ -283,9 +277,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.159"
|
version = "0.2.160"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5"
|
checksum = "f0b21006cd1874ae9e650973c565615676dc4a274c965bb0a73796dac838ce4f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libredox"
|
name = "libredox"
|
||||||
|
@ -410,9 +404,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.87"
|
version = "1.0.88"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a"
|
checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
@ -455,7 +449,6 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
"foldhash",
|
|
||||||
"notify",
|
"notify",
|
||||||
"os_pipe",
|
"os_pipe",
|
||||||
"rustix",
|
"rustix",
|
||||||
|
|
|
@ -49,7 +49,6 @@ include = [
|
||||||
anyhow = "1.0.89"
|
anyhow = "1.0.89"
|
||||||
clap = { version = "4.5.20", features = ["derive"] }
|
clap = { version = "4.5.20", features = ["derive"] }
|
||||||
crossterm = { version = "0.28.1", default-features = false, features = ["windows", "events"] }
|
crossterm = { version = "0.28.1", default-features = false, features = ["windows", "events"] }
|
||||||
foldhash = "0.1.3"
|
|
||||||
notify = { version = "6.1.1", default-features = false, features = ["macos_fsevent"] }
|
notify = { version = "6.1.1", default-features = false, features = ["macos_fsevent"] }
|
||||||
os_pipe = "1.2.1"
|
os_pipe = "1.2.1"
|
||||||
rustlings-macros = { path = "rustlings-macros", version = "=6.3.0" }
|
rustlings-macros = { path = "rustlings-macros", version = "=6.3.0" }
|
||||||
|
|
12
README.md
12
README.md
|
@ -124,14 +124,13 @@ The list allows you to…
|
||||||
|
|
||||||
- See the status of all exercises (done or pending)
|
- See the status of all exercises (done or pending)
|
||||||
- `c`: Continue at another exercise (temporarily skip some exercises or go back to a previous one)
|
- `c`: Continue at another exercise (temporarily skip some exercises or go back to a previous one)
|
||||||
- `r`: Reset status and file of an exercise (you need to _reload/reopen_ its file in your editor afterwards)
|
- `r`: Reset status and file of the selected exercise (you need to _reload/reopen_ its file in your editor afterwards)
|
||||||
|
|
||||||
See the footer of the list for all possible keys.
|
See the footer of the list for all possible keys.
|
||||||
|
|
||||||
## Continuing On
|
## Questions?
|
||||||
|
|
||||||
Once you've completed Rustlings, put your new knowledge to good use!
|
If you need any help while doing the exercises and the builtin-hints aren't helpful, feel free to ask in the [_Q&A_ category of the discussions](https://github.com/rust-lang/rustlings/discussions/categories/q-a?discussions_q=) if your question wasn't asked yet 💡
|
||||||
Continue practicing your Rust skills by building your own projects, contributing to Rustlings, or finding other open-source projects to contribute to.
|
|
||||||
|
|
||||||
## Third-Party Exercises
|
## Third-Party Exercises
|
||||||
|
|
||||||
|
@ -144,6 +143,11 @@ Do you want to create your own set of Rustlings exercises to focus on some speci
|
||||||
Or do you want to translate the original Rustlings exercises?
|
Or do you want to translate the original Rustlings exercises?
|
||||||
Then follow the the guide about [third-party exercises](https://github.com/rust-lang/rustlings/blob/main/THIRD_PARTY_EXERCISES.md)!
|
Then follow the the guide about [third-party exercises](https://github.com/rust-lang/rustlings/blob/main/THIRD_PARTY_EXERCISES.md)!
|
||||||
|
|
||||||
|
## Continuing On
|
||||||
|
|
||||||
|
Once you've completed Rustlings, put your new knowledge to good use!
|
||||||
|
Continue practicing your Rust skills by building your own projects, contributing to Rustlings, or finding other open-source projects to contribute to.
|
||||||
|
|
||||||
## Uninstalling Rustlings
|
## Uninstalling Rustlings
|
||||||
|
|
||||||
If you want to remove Rustlings from your system, run the following command:
|
If you want to remove Rustlings from your system, run the following command:
|
||||||
|
|
|
@ -5,9 +5,6 @@ disallowed-types = [
|
||||||
]
|
]
|
||||||
|
|
||||||
disallowed-methods = [
|
disallowed-methods = [
|
||||||
# We use `foldhash` instead of the default hasher.
|
|
||||||
"std::collections::HashSet::new",
|
|
||||||
"std::collections::HashSet::with_capacity",
|
|
||||||
# Inefficient. Use `.queue(…)` instead.
|
# Inefficient. Use `.queue(…)` instead.
|
||||||
"crossterm::style::style",
|
"crossterm::style::style",
|
||||||
# Use `thread::Builder::spawn` instead and handle the error.
|
# Use `thread::Builder::spawn` instead and handle the error.
|
||||||
|
|
|
@ -26,6 +26,8 @@ bin = [
|
||||||
{ name = "functions4_sol", path = "../solutions/02_functions/functions4.rs" },
|
{ name = "functions4_sol", path = "../solutions/02_functions/functions4.rs" },
|
||||||
{ name = "functions5", path = "../exercises/02_functions/functions5.rs" },
|
{ name = "functions5", path = "../exercises/02_functions/functions5.rs" },
|
||||||
{ name = "functions5_sol", path = "../solutions/02_functions/functions5.rs" },
|
{ name = "functions5_sol", path = "../solutions/02_functions/functions5.rs" },
|
||||||
|
{ name = "functions6", path = "../exercises/02_functions/functions6.rs" },
|
||||||
|
{ name = "functions6_sol", path = "../solutions/02_functions/functions6.rs" },
|
||||||
{ name = "if1", path = "../exercises/03_if/if1.rs" },
|
{ name = "if1", path = "../exercises/03_if/if1.rs" },
|
||||||
{ name = "if1_sol", path = "../solutions/03_if/if1.rs" },
|
{ name = "if1_sol", path = "../solutions/03_if/if1.rs" },
|
||||||
{ name = "if2", path = "../exercises/03_if/if2.rs" },
|
{ name = "if2", path = "../exercises/03_if/if2.rs" },
|
||||||
|
@ -60,6 +62,8 @@ bin = [
|
||||||
{ name = "move_semantics4_sol", path = "../solutions/06_move_semantics/move_semantics4.rs" },
|
{ name = "move_semantics4_sol", path = "../solutions/06_move_semantics/move_semantics4.rs" },
|
||||||
{ name = "move_semantics5", path = "../exercises/06_move_semantics/move_semantics5.rs" },
|
{ name = "move_semantics5", path = "../exercises/06_move_semantics/move_semantics5.rs" },
|
||||||
{ name = "move_semantics5_sol", path = "../solutions/06_move_semantics/move_semantics5.rs" },
|
{ name = "move_semantics5_sol", path = "../solutions/06_move_semantics/move_semantics5.rs" },
|
||||||
|
{ name = "move_semantics6", path = "../exercises/06_move_semantics/move_semantics6.rs" },
|
||||||
|
{ name = "move_semantics6_sol", path = "../solutions/06_move_semantics/move_semantics6.rs" },
|
||||||
{ name = "structs1", path = "../exercises/07_structs/structs1.rs" },
|
{ name = "structs1", path = "../exercises/07_structs/structs1.rs" },
|
||||||
{ name = "structs1_sol", path = "../solutions/07_structs/structs1.rs" },
|
{ name = "structs1_sol", path = "../solutions/07_structs/structs1.rs" },
|
||||||
{ name = "structs2", path = "../exercises/07_structs/structs2.rs" },
|
{ name = "structs2", path = "../exercises/07_structs/structs2.rs" },
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
# Functions
|
# Functions
|
||||||
|
|
||||||
Here, you'll learn how to write functions and how the Rust compiler can help you debug errors even
|
Here, you'll learn how to write functions and how the Rust compiler can help you debug errors even
|
||||||
in more complex code.
|
in more complex code. You will also learn what is the difference with closures.
|
||||||
|
|
||||||
## Further information
|
## Further information
|
||||||
|
|
||||||
- [How Functions Work](https://doc.rust-lang.org/book/ch03-03-how-functions-work.html)
|
- [How Functions Work](https://doc.rust-lang.org/book/ch03-03-how-functions-work.html)
|
||||||
|
- [Closures](https://doc.rust-lang.org/book/ch13-01-closures.html)
|
||||||
|
|
19
exercises/02_functions/functions6.rs
Normal file
19
exercises/02_functions/functions6.rs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
// functions6.rs
|
||||||
|
//
|
||||||
|
// Here you can practice special functions called `closures`, that can capture
|
||||||
|
// variables of their parent context.
|
||||||
|
// Fix the code below to make it compile, without changing the two closure
|
||||||
|
// definitions.
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint functions6` or use the `hint` watch subcommand for
|
||||||
|
// some hints.
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// TODO: ensure the definition of captured variable
|
||||||
|
let closure_1 = |input_var: u32| -> u32 {input_var + outer_var};
|
||||||
|
println!("Closure#1 returns {}", closure_1(5));
|
||||||
|
|
||||||
|
let closure_2 = |input_var| println!("Closure#2 (input_var {})", input_var);
|
||||||
|
closure_2(2);
|
||||||
|
closure_2("5"); // TODO: look at the captured variable type here
|
||||||
|
}
|
25
exercises/06_move_semantics/move_semantics6.rs
Normal file
25
exercises/06_move_semantics/move_semantics6.rs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
// move_semantics6.rs
|
||||||
|
//
|
||||||
|
// Here you will practice how mutable/immutable borrowing works in the context
|
||||||
|
// of a closure.
|
||||||
|
//
|
||||||
|
// Try to fix this code to make it compile and not panic.
|
||||||
|
// You can't change anything except removing 1 line.
|
||||||
|
//
|
||||||
|
// Execute `rustlings hint move_semantics7` or use the `hint` watch subcommand
|
||||||
|
// for a hint.
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut counter = 0;
|
||||||
|
|
||||||
|
let mut increment = || {
|
||||||
|
counter += 1;
|
||||||
|
println!("counter equals {}", counter);
|
||||||
|
};
|
||||||
|
|
||||||
|
increment();
|
||||||
|
let _reborrowed_counter = &counter; // TODO: figure out where to put this borrowing instruction
|
||||||
|
increment();
|
||||||
|
|
||||||
|
assert_eq!(counter, 2);
|
||||||
|
}
|
|
@ -17,7 +17,7 @@ struct TeamScores {
|
||||||
|
|
||||||
fn build_scores_table(results: &str) -> HashMap<&str, TeamScores> {
|
fn build_scores_table(results: &str) -> HashMap<&str, TeamScores> {
|
||||||
// The name of the team is the key and its associated struct is the value.
|
// The name of the team is the key and its associated struct is the value.
|
||||||
let mut scores = HashMap::new();
|
let mut scores = HashMap::<&str, TeamScores>::new();
|
||||||
|
|
||||||
for line in results.lines() {
|
for line in results.lines() {
|
||||||
let mut split_iterator = line.split(',');
|
let mut split_iterator = line.split(',');
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
| Exercise | Book Chapter |
|
| Exercise | Book Chapter |
|
||||||
| ---------------------- | ------------------- |
|
| ---------------------- | ------------------- |
|
||||||
| variables | §3.1 |
|
| variables | §3.1 |
|
||||||
| functions | §3.3 |
|
| functions | §3.3, §13.1 |
|
||||||
| if | §3.5 |
|
| if | §3.5 |
|
||||||
| primitive_types | §3.2, §4.3 |
|
| primitive_types | §3.2, §4.3 |
|
||||||
| vecs | §8.1 |
|
| vecs | §8.1 |
|
||||||
|
|
|
@ -187,6 +187,20 @@ There are two solutions:
|
||||||
1. Add the `return` keyword before `num * num;`
|
1. Add the `return` keyword before `num * num;`
|
||||||
2. Remove the semicolon `;` after `num * num`"""
|
2. Remove the semicolon `;` after `num * num`"""
|
||||||
|
|
||||||
|
[[exercises]]
|
||||||
|
name = "functions6"
|
||||||
|
dir = "02_functions"
|
||||||
|
test = false
|
||||||
|
hint = """
|
||||||
|
Hint FIX #1: Closures can capture variables defined in the outer context.
|
||||||
|
|
||||||
|
Hint FIX #2: Closures can infer both input and returned types, when they are not
|
||||||
|
specified in the signature. But the closure cannot be reused with different
|
||||||
|
input types.
|
||||||
|
|
||||||
|
Read more about closures in the rust book dedicated section:
|
||||||
|
https://doc.rust-lang.org/book/ch13-01-closures.html"""
|
||||||
|
|
||||||
# IF
|
# IF
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
|
@ -391,6 +405,18 @@ The first problem is that `get_char` is taking ownership of the string. So
|
||||||
Once you've fixed that, `string_uppercase`'s function signature will also need
|
Once you've fixed that, `string_uppercase`'s function signature will also need
|
||||||
to be adjusted."""
|
to be adjusted."""
|
||||||
|
|
||||||
|
[[exercises]]
|
||||||
|
name = "move_semantics6"
|
||||||
|
dir = "06_move_semantics"
|
||||||
|
test = false
|
||||||
|
hint = """
|
||||||
|
When a closure captures a variable to modify it, it actually borrows that variable
|
||||||
|
as a mutable reference. In this exercise, the closure mutably borrows the `counter`
|
||||||
|
variable, thus, any attempt to borrow `counter` between closure calls leads to an error.
|
||||||
|
|
||||||
|
You cannot immutably borrow a variable if a mutable closure is
|
||||||
|
called later in the scope."""
|
||||||
|
|
||||||
# STRUCTS
|
# STRUCTS
|
||||||
|
|
||||||
[[exercises]]
|
[[exercises]]
|
||||||
|
@ -575,12 +601,8 @@ https://doc.rust-lang.org/book/ch08-03-hash-maps.html#only-inserting-a-value-if-
|
||||||
name = "hashmaps3"
|
name = "hashmaps3"
|
||||||
dir = "11_hashmaps"
|
dir = "11_hashmaps"
|
||||||
hint = """
|
hint = """
|
||||||
Hint 1: Use the `entry()` and `or_insert()` (or `or_insert_with()`) methods of
|
Hint 1: Use the `entry()` and `or_default()` methods of `HashMap` to insert the
|
||||||
`HashMap` to insert the default value of `TeamScores` if a team doesn't
|
default value of `TeamScores` if a team doesn't exist in the table yet.
|
||||||
exist in the table yet.
|
|
||||||
|
|
||||||
Learn more in The Book:
|
|
||||||
https://doc.rust-lang.org/book/ch08-03-hash-maps.html#only-inserting-a-value-if-the-key-has-no-value
|
|
||||||
|
|
||||||
Hint 2: If there is already an entry for a given key, the value returned by
|
Hint 2: If there is already an entry for a given key, the value returned by
|
||||||
`entry()` can be updated based on the existing value.
|
`entry()` can be updated based on the existing value.
|
||||||
|
|
9
solutions/02_functions/functions6.rs
Normal file
9
solutions/02_functions/functions6.rs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
fn main() {
|
||||||
|
let outer_var = 1;
|
||||||
|
let closure_1 = |input_var: u32| -> u32 { input_var + outer_var };
|
||||||
|
println!("Closure#1 returns {}", closure_1(5));
|
||||||
|
|
||||||
|
let closure_2 = |input_var| println!("Closure#2 (input_var {})", input_var);
|
||||||
|
closure_2(2);
|
||||||
|
closure_2(5);
|
||||||
|
}
|
14
solutions/06_move_semantics/move_semantics6.rs
Normal file
14
solutions/06_move_semantics/move_semantics6.rs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
fn main() {
|
||||||
|
let mut counter = 0;
|
||||||
|
|
||||||
|
let mut increment = || {
|
||||||
|
counter += 1;
|
||||||
|
println!("counter equals {}", counter);
|
||||||
|
};
|
||||||
|
|
||||||
|
increment();
|
||||||
|
increment();
|
||||||
|
let _reborrowed_counter = &counter;
|
||||||
|
|
||||||
|
assert_eq!(counter, 2);
|
||||||
|
}
|
|
@ -17,7 +17,7 @@ struct TeamScores {
|
||||||
|
|
||||||
fn build_scores_table(results: &str) -> HashMap<&str, TeamScores> {
|
fn build_scores_table(results: &str) -> HashMap<&str, TeamScores> {
|
||||||
// The name of the team is the key and its associated struct is the value.
|
// The name of the team is the key and its associated struct is the value.
|
||||||
let mut scores = HashMap::new();
|
let mut scores = HashMap::<&str, TeamScores>::new();
|
||||||
|
|
||||||
for line in results.lines() {
|
for line in results.lines() {
|
||||||
let mut split_iterator = line.split(',');
|
let mut split_iterator = line.split(',');
|
||||||
|
@ -28,17 +28,13 @@ fn build_scores_table(results: &str) -> HashMap<&str, TeamScores> {
|
||||||
let team_2_score: u8 = split_iterator.next().unwrap().parse().unwrap();
|
let team_2_score: u8 = split_iterator.next().unwrap().parse().unwrap();
|
||||||
|
|
||||||
// Insert the default with zeros if a team doesn't exist yet.
|
// Insert the default with zeros if a team doesn't exist yet.
|
||||||
let team_1 = scores
|
let team_1 = scores.entry(team_1_name).or_default();
|
||||||
.entry(team_1_name)
|
|
||||||
.or_insert_with(TeamScores::default);
|
|
||||||
// Update the values.
|
// Update the values.
|
||||||
team_1.goals_scored += team_1_score;
|
team_1.goals_scored += team_1_score;
|
||||||
team_1.goals_conceded += team_2_score;
|
team_1.goals_conceded += team_2_score;
|
||||||
|
|
||||||
// Similarly for the second team.
|
// Similarly for the second team.
|
||||||
let team_2 = scores
|
let team_2 = scores.entry(team_2_name).or_default();
|
||||||
.entry(team_2_name)
|
|
||||||
.or_insert_with(TeamScores::default);
|
|
||||||
team_2.goals_scored += team_2_score;
|
team_2.goals_scored += team_2_score;
|
||||||
team_2.goals_conceded += team_1_score;
|
team_2.goals_conceded += team_1_score;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use anyhow::{bail, Context, Error, Result};
|
use anyhow::{bail, Context, Error, Result};
|
||||||
use crossterm::{cursor, terminal, QueueableCommand};
|
use crossterm::{cursor, terminal, QueueableCommand};
|
||||||
use std::{
|
use std::{
|
||||||
|
collections::HashSet,
|
||||||
env,
|
env,
|
||||||
fs::{File, OpenOptions},
|
fs::{File, OpenOptions},
|
||||||
io::{Read, Seek, StdoutLock, Write},
|
io::{Read, Seek, StdoutLock, Write},
|
||||||
|
@ -16,7 +17,6 @@ use std::{
|
||||||
use crate::{
|
use crate::{
|
||||||
clear_terminal,
|
clear_terminal,
|
||||||
cmd::CmdRunner,
|
cmd::CmdRunner,
|
||||||
collections::hash_set_with_capacity,
|
|
||||||
embedded::EMBEDDED_FILES,
|
embedded::EMBEDDED_FILES,
|
||||||
exercise::{Exercise, RunnableExercise},
|
exercise::{Exercise, RunnableExercise},
|
||||||
info_file::ExerciseInfo,
|
info_file::ExerciseInfo,
|
||||||
|
@ -146,7 +146,7 @@ impl AppState {
|
||||||
break 'block StateFileStatus::NotRead;
|
break 'block StateFileStatus::NotRead;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut done_exercises = hash_set_with_capacity(exercises.len());
|
let mut done_exercises = HashSet::with_capacity(exercises.len());
|
||||||
|
|
||||||
for done_exercise_name in lines {
|
for done_exercise_name in lines {
|
||||||
if done_exercise_name.is_empty() {
|
if done_exercise_name.is_empty() {
|
||||||
|
|
|
@ -125,7 +125,7 @@ pub struct CargoSubcommand<'out> {
|
||||||
output: Option<&'out mut Vec<u8>>,
|
output: Option<&'out mut Vec<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'out> CargoSubcommand<'out> {
|
impl CargoSubcommand<'_> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn args<'arg, I>(&mut self, args: I) -> &mut Self
|
pub fn args<'arg, I>(&mut self, args: I) -> &mut Self
|
||||||
where
|
where
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
use foldhash::fast::FixedState;
|
|
||||||
|
|
||||||
/// DOS attacks aren't a concern for Rustlings. Therefore, we use `foldhash` with a fixed state.
|
|
||||||
pub type HashSet<T> = std::collections::HashSet<T, FixedState>;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn hash_set_with_capacity<T>(capacity: usize) -> HashSet<T> {
|
|
||||||
HashSet::with_capacity_and_hasher(capacity, FixedState::default())
|
|
||||||
}
|
|
|
@ -1,6 +1,7 @@
|
||||||
use anyhow::{anyhow, bail, Context, Error, Result};
|
use anyhow::{anyhow, bail, Context, Error, Result};
|
||||||
use std::{
|
use std::{
|
||||||
cmp::Ordering,
|
cmp::Ordering,
|
||||||
|
collections::HashSet,
|
||||||
fs::{self, read_dir, OpenOptions},
|
fs::{self, read_dir, OpenOptions},
|
||||||
io::{self, Read, Write},
|
io::{self, Read, Write},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
|
@ -11,7 +12,6 @@ use std::{
|
||||||
use crate::{
|
use crate::{
|
||||||
cargo_toml::{append_bins, bins_start_end_ind, BINS_BUFFER_CAPACITY},
|
cargo_toml::{append_bins, bins_start_end_ind, BINS_BUFFER_CAPACITY},
|
||||||
cmd::CmdRunner,
|
cmd::CmdRunner,
|
||||||
collections::{hash_set_with_capacity, HashSet},
|
|
||||||
exercise::{RunnableExercise, OUTPUT_CAPACITY},
|
exercise::{RunnableExercise, OUTPUT_CAPACITY},
|
||||||
info_file::{ExerciseInfo, InfoFile},
|
info_file::{ExerciseInfo, InfoFile},
|
||||||
CURRENT_FORMAT_VERSION,
|
CURRENT_FORMAT_VERSION,
|
||||||
|
@ -53,8 +53,8 @@ fn check_cargo_toml(
|
||||||
|
|
||||||
// Check the info of all exercises and return their paths in a set.
|
// Check the info of all exercises and return their paths in a set.
|
||||||
fn check_info_file_exercises(info_file: &InfoFile) -> Result<HashSet<PathBuf>> {
|
fn check_info_file_exercises(info_file: &InfoFile) -> Result<HashSet<PathBuf>> {
|
||||||
let mut names = hash_set_with_capacity(info_file.exercises.len());
|
let mut names = HashSet::with_capacity(info_file.exercises.len());
|
||||||
let mut paths = hash_set_with_capacity(info_file.exercises.len());
|
let mut paths = HashSet::with_capacity(info_file.exercises.len());
|
||||||
|
|
||||||
let mut file_buf = String::with_capacity(1 << 14);
|
let mut file_buf = String::with_capacity(1 << 14);
|
||||||
for exercise_info in &info_file.exercises {
|
for exercise_info in &info_file.exercises {
|
||||||
|
@ -282,7 +282,7 @@ fn check_solutions(
|
||||||
.collect::<Result<Vec<_>, _>>()
|
.collect::<Result<Vec<_>, _>>()
|
||||||
.context("Failed to spawn a thread to check a solution")?;
|
.context("Failed to spawn a thread to check a solution")?;
|
||||||
|
|
||||||
let mut sol_paths = hash_set_with_capacity(info_file.exercises.len());
|
let mut sol_paths = HashSet::with_capacity(info_file.exercises.len());
|
||||||
let mut fmt_cmd = Command::new("rustfmt");
|
let mut fmt_cmd = Command::new("rustfmt");
|
||||||
fmt_cmd
|
fmt_cmd
|
||||||
.arg("--check")
|
.arg("--check")
|
||||||
|
|
|
@ -105,6 +105,28 @@ impl<'a> ListState<'a> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn draw_exericse_name(&self, writer: &mut MaxLenWriter, exercise: &Exercise) -> io::Result<()> {
|
||||||
|
if !self.search_query.is_empty() {
|
||||||
|
if let Some((pre_highlight, highlight, post_highlight)) = exercise
|
||||||
|
.name
|
||||||
|
.find(&self.search_query)
|
||||||
|
.and_then(|ind| exercise.name.split_at_checked(ind))
|
||||||
|
.and_then(|(pre_highlight, rest)| {
|
||||||
|
rest.split_at_checked(self.search_query.len())
|
||||||
|
.map(|x| (pre_highlight, x.0, x.1))
|
||||||
|
})
|
||||||
|
{
|
||||||
|
writer.write_str(pre_highlight)?;
|
||||||
|
writer.stdout.queue(SetForegroundColor(Color::Magenta))?;
|
||||||
|
writer.write_str(highlight)?;
|
||||||
|
writer.stdout.queue(ResetColor)?;
|
||||||
|
return writer.write_str(post_highlight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.write_str(exercise.name)
|
||||||
|
}
|
||||||
|
|
||||||
fn draw_rows(
|
fn draw_rows(
|
||||||
&self,
|
&self,
|
||||||
stdout: &mut StdoutLock,
|
stdout: &mut StdoutLock,
|
||||||
|
@ -147,10 +169,10 @@ impl<'a> ListState<'a> {
|
||||||
writer.stdout.queue(SetForegroundColor(Color::Yellow))?;
|
writer.stdout.queue(SetForegroundColor(Color::Yellow))?;
|
||||||
writer.write_ascii(b"PENDING ")?;
|
writer.write_ascii(b"PENDING ")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.stdout.queue(SetForegroundColor(Color::Reset))?;
|
writer.stdout.queue(SetForegroundColor(Color::Reset))?;
|
||||||
|
|
||||||
writer.write_str(exercise.name)?;
|
self.draw_exericse_name(&mut writer, exercise)?;
|
||||||
|
|
||||||
writer.write_ascii(&self.name_col_padding[exercise.name.len()..])?;
|
writer.write_ascii(&self.name_col_padding[exercise.name.len()..])?;
|
||||||
|
|
||||||
// The list links aren't shown correctly in VS Code on Windows.
|
// The list links aren't shown correctly in VS Code on Windows.
|
||||||
|
|
|
@ -13,7 +13,6 @@ use self::{app_state::AppState, dev::DevCommands, info_file::InfoFile};
|
||||||
mod app_state;
|
mod app_state;
|
||||||
mod cargo_toml;
|
mod cargo_toml;
|
||||||
mod cmd;
|
mod cmd;
|
||||||
mod collections;
|
|
||||||
mod dev;
|
mod dev;
|
||||||
mod embedded;
|
mod embedded;
|
||||||
mod exercise;
|
mod exercise;
|
||||||
|
|
24
src/term.rs
24
src/term.rs
|
@ -11,15 +11,15 @@ use std::{
|
||||||
|
|
||||||
use crate::app_state::CheckProgress;
|
use crate::app_state::CheckProgress;
|
||||||
|
|
||||||
pub struct MaxLenWriter<'a, 'b> {
|
pub struct MaxLenWriter<'a, 'lock> {
|
||||||
pub stdout: &'a mut StdoutLock<'b>,
|
pub stdout: &'a mut StdoutLock<'lock>,
|
||||||
len: usize,
|
len: usize,
|
||||||
max_len: usize,
|
max_len: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b> MaxLenWriter<'a, 'b> {
|
impl<'a, 'lock> MaxLenWriter<'a, 'lock> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new(stdout: &'a mut StdoutLock<'b>, max_len: usize) -> Self {
|
pub fn new(stdout: &'a mut StdoutLock<'lock>, max_len: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
stdout,
|
stdout,
|
||||||
len: 0,
|
len: 0,
|
||||||
|
@ -34,13 +34,13 @@ impl<'a, 'b> MaxLenWriter<'a, 'b> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait CountedWrite<'a> {
|
pub trait CountedWrite<'lock> {
|
||||||
fn write_ascii(&mut self, ascii: &[u8]) -> io::Result<()>;
|
fn write_ascii(&mut self, ascii: &[u8]) -> io::Result<()>;
|
||||||
fn write_str(&mut self, unicode: &str) -> io::Result<()>;
|
fn write_str(&mut self, unicode: &str) -> io::Result<()>;
|
||||||
fn stdout(&mut self) -> &mut StdoutLock<'a>;
|
fn stdout(&mut self) -> &mut StdoutLock<'lock>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b> CountedWrite<'b> for MaxLenWriter<'a, 'b> {
|
impl<'lock> CountedWrite<'lock> for MaxLenWriter<'_, 'lock> {
|
||||||
fn write_ascii(&mut self, ascii: &[u8]) -> io::Result<()> {
|
fn write_ascii(&mut self, ascii: &[u8]) -> io::Result<()> {
|
||||||
let n = ascii.len().min(self.max_len.saturating_sub(self.len));
|
let n = ascii.len().min(self.max_len.saturating_sub(self.len));
|
||||||
if n > 0 {
|
if n > 0 {
|
||||||
|
@ -65,7 +65,7 @@ impl<'a, 'b> CountedWrite<'b> for MaxLenWriter<'a, 'b> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn stdout(&mut self) -> &mut StdoutLock<'b> {
|
fn stdout(&mut self) -> &mut StdoutLock<'lock> {
|
||||||
self.stdout
|
self.stdout
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,17 +87,17 @@ impl<'a> CountedWrite<'a> for StdoutLock<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CheckProgressVisualizer<'a, 'b> {
|
pub struct CheckProgressVisualizer<'a, 'lock> {
|
||||||
stdout: &'a mut StdoutLock<'b>,
|
stdout: &'a mut StdoutLock<'lock>,
|
||||||
n_cols: usize,
|
n_cols: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b> CheckProgressVisualizer<'a, 'b> {
|
impl<'a, 'lock> CheckProgressVisualizer<'a, 'lock> {
|
||||||
const CHECKING_COLOR: Color = Color::Blue;
|
const CHECKING_COLOR: Color = Color::Blue;
|
||||||
const DONE_COLOR: Color = Color::Green;
|
const DONE_COLOR: Color = Color::Green;
|
||||||
const PENDING_COLOR: Color = Color::Red;
|
const PENDING_COLOR: Color = Color::Red;
|
||||||
|
|
||||||
pub fn build(stdout: &'a mut StdoutLock<'b>, term_width: u16) -> io::Result<Self> {
|
pub fn build(stdout: &'a mut StdoutLock<'lock>, term_width: u16) -> io::Result<Self> {
|
||||||
clear_terminal(stdout)?;
|
clear_terminal(stdout)?;
|
||||||
stdout.write_all("Checking all exercises…\n".as_bytes())?;
|
stdout.write_all("Checking all exercises…\n".as_bytes())?;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue