Compare commits

...

16 commits

Author SHA1 Message Date
Kacper Poneta af1410cd46
Merge 59e8f70e55 into a2d1cb3b22 2024-08-21 02:42:18 +08:00
mo8it a2d1cb3b22 Push newline after running an exercise instead on each rendering 2024-08-20 16:05:52 +02:00
mo8it e7ba88f905 Highlight the solution file 2024-08-20 16:04:29 +02:00
mo8it 50f6e5232e Leak info_file and cmd_runner in dev check 2024-08-20 14:47:08 +02:00
mo8it 8854f0a5ed Use anyhow! 2024-08-20 14:32:47 +02:00
mo8it 13cc3acdfd Improve readability 2024-08-20 13:56:52 +02:00
mo8it 5b7368c46d Improve error message if no exercise exists 2024-08-20 13:54:20 +02:00
mo8it 27999f2d26 Check if exercise doesn't contain tests 2024-08-20 13:49:48 +02:00
mo8it e74f2a4274 Check for #[test] with newline at the end 2024-08-20 13:39:14 +02:00
mo8it d141a73493 threads3: Improve the test 2024-08-20 13:35:07 +02:00
mo8it 631f44331e Remove --show-output for tests and use --format pretty 2024-08-20 13:08:15 +02:00
mo8it 59e8f70e55 Format code 2024-07-12 18:31:23 +02:00
mo8it 4c8365fe88 Update dev/Cargo.toml 2024-07-12 18:25:01 +02:00
Kacper Poneta 52af0674c1 changed the task to make it more appropriate 2024-07-12 18:14:40 +02:00
Kacper Poneta 938b90e5f2 very small solution update 2024-07-11 22:55:48 +02:00
Kacper Poneta 55cc8584bd added exercise 2024-07-11 22:53:38 +02:00
12 changed files with 291 additions and 171 deletions

View file

@ -116,6 +116,8 @@ bin = [
{ name = "generics1_sol", path = "../solutions/14_generics/generics1.rs" },
{ name = "generics2", path = "../exercises/14_generics/generics2.rs" },
{ name = "generics2_sol", path = "../solutions/14_generics/generics2.rs" },
{ name = "generics3", path = "../exercises/14_generics/generics3.rs" },
{ name = "generics3_sol", path = "../solutions/14_generics/generics3.rs" },
{ name = "traits1", path = "../exercises/15_traits/traits1.rs" },
{ name = "traits1_sol", path = "../solutions/15_traits/traits1.rs" },
{ name = "traits2", path = "../exercises/15_traits/traits2.rs" },

View file

@ -0,0 +1,54 @@
// generics3.rs
// Execute `rustlings hint generics3` or use the `hint` watch subcommand for a hint.
// This function should take an array of `Option` elements and returns array of not None elements
// TODO fix this function signature
fn into_dispose_nulls(list: Vec<Option<&str>>) -> Vec<&str> {
list.into_iter().flatten().collect()
}
fn main() {
// You can optionally experiment here.
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn store_str_on_list() {
let names_list = vec![Some("maria"), Some("jacob"), None, Some("kacper"), None];
let only_values = into_dispose_nulls(names_list);
assert_eq!(only_values.len(), 3);
}
#[test]
fn store_numbers_on_list() {
let numbers_list = vec![Some(1), Some(2), None, Some(3)];
let only_values = into_dispose_nulls(numbers_list);
assert_eq!(only_values.len(), 3);
}
#[test]
fn store_custom_type_on_list() {
#[allow(dead_code)]
struct Rectangle {
width: i32,
height: i32,
}
impl Rectangle {
fn new(width: i32, height: i32) -> Self {
Self { width, height }
}
}
let custom_list = vec![
Some(Rectangle::new(1, 2)),
None,
None,
Some(Rectangle::new(3, 4)),
];
let only_values = into_dispose_nulls(custom_list);
assert_eq!(only_values.len(), 2);
}
}

View file

@ -1,7 +1,6 @@
use std::{sync::mpsc, thread, time::Duration};
struct Queue {
length: u32,
first_half: Vec<u32>,
second_half: Vec<u32>,
}
@ -9,7 +8,6 @@ struct Queue {
impl Queue {
fn new() -> Self {
Self {
length: 10,
first_half: vec![1, 2, 3, 4, 5],
second_half: vec![6, 7, 8, 9, 10],
}
@ -48,17 +46,15 @@ mod tests {
fn threads3() {
let (tx, rx) = mpsc::channel();
let queue = Queue::new();
let queue_length = queue.length;
send_tx(queue, tx);
let mut total_received: u32 = 0;
for received in rx {
println!("Got: {received}");
total_received += 1;
let mut received = Vec::with_capacity(10);
for value in rx {
received.push(value);
}
println!("Number of received values: {total_received}");
assert_eq!(total_received, queue_length);
received.sort();
assert_eq!(received, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
}
}

View file

@ -744,6 +744,17 @@ hint = """
Related section in The Book:
https://doc.rust-lang.org/book/ch10-01-syntax.html#in-method-definitions"""
[[exercises]]
name = "generics3"
dir = "14_generics"
hint = """
Vectors in Rust use generics to create dynamically-sized arrays of any type.
The `into_dispose_nulls` function takes a vector as an argument, but only accepts vectors that store the &str type.
To allow the function to accept vectors that store any type, you can leverage your knowledge about generics.
If you're unsure how to proceed, please refer to the Rust Book at:
https://doc.rust-lang.org/book/ch10-01-syntax.html#in-function-definitions.
"""
# TRAITS
[[exercises]]

View file

@ -0,0 +1,53 @@
// generics3.rs
// Execute `rustlings hint generics3` or use the `hint` watch subcommand for a hint.
// Here we added generic type `T` to function signature
// Now this function can be used with vector of any
fn into_dispose_nulls<T>(list: Vec<Option<T>>) -> Vec<T> {
list.into_iter().flatten().collect()
}
fn main() {
// You can optionally experiment here.
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn store_str_on_list() {
let names_list = vec![Some("maria"), Some("jacob"), None, Some("kacper"), None];
let only_values = into_dispose_nulls(names_list);
assert_eq!(only_values.len(), 3);
}
#[test]
fn store_numbers_on_list() {
let numbers_list = vec![Some(1), Some(2), None, Some(3)];
let only_values = into_dispose_nulls(numbers_list);
assert_eq!(only_values.len(), 3);
}
#[test]
fn store_custom_type_on_list() {
struct Rectangle {
width: i32,
height: i32,
}
impl Rectangle {
fn new(width: i32, height: i32) -> Self {
Self { width, height }
}
}
let custom_list = vec![
Some(Rectangle::new(1, 2)),
None,
None,
Some(Rectangle::new(3, 4)),
];
let only_values = into_dispose_nulls(custom_list);
assert_eq!(only_values.len(), 2);
}
}

View file

@ -1,7 +1,6 @@
use std::{sync::mpsc, thread, time::Duration};
struct Queue {
length: u32,
first_half: Vec<u32>,
second_half: Vec<u32>,
}
@ -9,7 +8,6 @@ struct Queue {
impl Queue {
fn new() -> Self {
Self {
length: 10,
first_half: vec![1, 2, 3, 4, 5],
second_half: vec![6, 7, 8, 9, 10],
}
@ -50,17 +48,15 @@ mod tests {
fn threads3() {
let (tx, rx) = mpsc::channel();
let queue = Queue::new();
let queue_length = queue.length;
send_tx(queue, tx);
let mut total_received: u32 = 0;
for received in rx {
println!("Got: {received}");
total_received += 1;
let mut received = Vec::with_capacity(10);
for value in rx {
received.push(value);
}
println!("Number of received values: {total_received}");
assert_eq!(total_received, queue_length);
received.sort();
assert_eq!(received, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
}
}

View file

@ -74,12 +74,14 @@ impl CmdRunner {
bail!("The command `cargo metadata …` failed. Are you in the `rustlings/` directory?");
}
let target_dir = serde_json::de::from_slice::<CargoMetadata>(&metadata_output.stdout)
let metadata: CargoMetadata = serde_json::de::from_slice(&metadata_output.stdout)
.context(
"Failed to read the field `target_directory` from the output of the command `cargo metadata …`",
)?.target_directory;
)?;
Ok(Self { target_dir })
Ok(Self {
target_dir: metadata.target_directory,
})
}
pub fn cargo<'out>(

View file

@ -97,7 +97,12 @@ fn check_info_file_exercises(info_file: &InfoFile) -> Result<HashSet<PathBuf>> {
bail!("Didn't find any `// TODO` comment in the file `{path}`.\nYou need to have at least one such comment to guide the user.");
}
if !exercise_info.test && file_buf.contains("#[test]") {
let contains_tests = file_buf.contains("#[test]\n");
if exercise_info.test {
if !contains_tests {
bail!("The file `{path}` doesn't contain any tests. If you don't want to add tests to this exercise, set `test = false` for this exercise in the `info.toml` file");
}
} else if contains_tests {
bail!("The file `{path}` contains tests annotated with `#[test]` but the exercise `{name}` has `test = false` in the `info.toml` file");
}
@ -160,11 +165,13 @@ fn check_unexpected_files(dir: &str, allowed_rust_files: &HashSet<PathBuf>) -> R
Ok(())
}
fn check_exercises_unsolved(info_file: &InfoFile, cmd_runner: &CmdRunner) -> Result<()> {
fn check_exercises_unsolved(
info_file: &'static InfoFile,
cmd_runner: &'static CmdRunner,
) -> Result<()> {
let mut stdout = io::stdout().lock();
stdout.write_all(b"Running all exercises to check that they aren't already solved...\n")?;
thread::scope(|s| {
let handles = info_file
.exercises
.iter()
@ -175,7 +182,7 @@ fn check_exercises_unsolved(info_file: &InfoFile, cmd_runner: &CmdRunner) -> Res
Some((
exercise_info.name.as_str(),
s.spawn(|| exercise_info.run_exercise(None, cmd_runner)),
thread::spawn(|| exercise_info.run_exercise(None, cmd_runner)),
))
})
.collect::<Vec<_>>();
@ -191,9 +198,9 @@ fn check_exercises_unsolved(info_file: &InfoFile, cmd_runner: &CmdRunner) -> Res
};
match result {
Ok(true) => bail!(
"The exercise {exercise_name} is already solved.\n{SKIP_CHECK_UNSOLVED_HINT}",
),
Ok(true) => {
bail!("The exercise {exercise_name} is already solved.\n{SKIP_CHECK_UNSOLVED_HINT}",)
}
Ok(false) => (),
Err(e) => return Err(e),
}
@ -205,26 +212,25 @@ fn check_exercises_unsolved(info_file: &InfoFile, cmd_runner: &CmdRunner) -> Res
stdout.write_all(b"\n")?;
Ok(())
})
}
fn check_exercises(info_file: &InfoFile, cmd_runner: &CmdRunner) -> Result<()> {
fn check_exercises(info_file: &'static InfoFile, cmd_runner: &'static CmdRunner) -> Result<()> {
match info_file.format_version.cmp(&CURRENT_FORMAT_VERSION) {
Ordering::Less => bail!("`format_version` < {CURRENT_FORMAT_VERSION} (supported version)\nPlease migrate to the latest format version"),
Ordering::Greater => bail!("`format_version` > {CURRENT_FORMAT_VERSION} (supported version)\nTry updating the Rustlings program"),
Ordering::Equal => (),
}
let info_file_paths = check_info_file_exercises(info_file)?;
let handle = thread::spawn(move || check_unexpected_files("exercises", &info_file_paths));
let handle = thread::spawn(move || check_exercises_unsolved(info_file, cmd_runner));
let info_file_paths = check_info_file_exercises(info_file)?;
check_unexpected_files("exercises", &info_file_paths)?;
check_exercises_unsolved(info_file, cmd_runner)?;
handle.join().unwrap()
}
enum SolutionCheck {
Success { sol_path: String },
MissingRequired,
MissingOptional,
RunFailure { output: Vec<u8> },
Err(Error),
@ -232,22 +238,24 @@ enum SolutionCheck {
fn check_solutions(
require_solutions: bool,
info_file: &InfoFile,
cmd_runner: &CmdRunner,
info_file: &'static InfoFile,
cmd_runner: &'static CmdRunner,
) -> Result<()> {
let mut stdout = io::stdout().lock();
stdout.write_all(b"Running all solutions...\n")?;
thread::scope(|s| {
let handles = info_file
.exercises
.iter()
.map(|exercise_info| {
s.spawn(|| {
thread::spawn(move || {
let sol_path = exercise_info.sol_path();
if !Path::new(&sol_path).exists() {
if require_solutions {
return SolutionCheck::MissingRequired;
return SolutionCheck::Err(anyhow!(
"The solution of the exercise {} is missing",
exercise_info.name,
));
}
return SolutionCheck::MissingOptional;
@ -291,12 +299,6 @@ fn check_solutions(
fmt_cmd.arg(&sol_path);
sol_paths.insert(PathBuf::from(sol_path));
}
SolutionCheck::MissingRequired => {
bail!(
"The solution of the exercise {} is missing",
exercise_info.name,
);
}
SolutionCheck::MissingOptional => (),
SolutionCheck::RunFailure { output } => {
stdout.write_all(b"\n\n")?;
@ -315,7 +317,7 @@ fn check_solutions(
}
stdout.write_all(b"\n")?;
let handle = s.spawn(move || check_unexpected_files("solutions", &sol_paths));
let handle = thread::spawn(move || check_unexpected_files("solutions", &sol_paths));
if !fmt_cmd
.status()
@ -326,7 +328,6 @@ fn check_solutions(
}
handle.join().unwrap()
})
}
pub fn check(require_solutions: bool) -> Result<()> {
@ -339,9 +340,12 @@ pub fn check(require_solutions: bool) -> Result<()> {
check_cargo_toml(&info_file.exercises, "Cargo.toml", b"")?;
}
let cmd_runner = CmdRunner::build()?;
check_exercises(&info_file, &cmd_runner)?;
check_solutions(require_solutions, &info_file, &cmd_runner)?;
// Leaking is fine since they are used until the end of the program.
let cmd_runner = Box::leak(Box::new(CmdRunner::build()?));
let info_file = Box::leak(Box::new(info_file));
check_exercises(info_file, cmd_runner)?;
check_solutions(require_solutions, info_file, cmd_runner)?;
println!("Everything looks fine!");

View file

@ -98,7 +98,7 @@ pub trait RunnableExercise {
let output_is_some = output.is_some();
let mut test_cmd = cmd_runner.cargo("test", bin_name, output.as_deref_mut());
if output_is_some {
test_cmd.args(["--", "--color", "always", "--show-output"]);
test_cmd.args(["--", "--color", "always", "--format", "pretty"]);
}
let test_success = test_cmd.run("cargo test …")?;
if !test_success {

View file

@ -135,4 +135,4 @@ impl InfoFile {
}
const NO_EXERCISES_ERR: &str = "There are no exercises yet!
If you are developing third-party exercises, add at least one exercise before testing.";
Add at least one exercise before testing.";

View file

@ -33,18 +33,21 @@ pub fn run(app_state: &mut AppState) -> Result<()> {
)?;
if let Some(solution_path) = app_state.current_solution_path()? {
println!(
"\nA solution file can be found at {}\n",
style(TerminalFileLink(&solution_path)).underlined().green(),
);
writeln!(
stdout,
"\n{} for comparison: {}\n",
"Solution".bold(),
style(TerminalFileLink(&solution_path)).underlined().cyan(),
)?;
}
match app_state.done_current_exercise(&mut stdout)? {
ExercisesProgress::AllDone => (),
ExercisesProgress::CurrentPending | ExercisesProgress::NewPending => println!(
ExercisesProgress::CurrentPending | ExercisesProgress::NewPending => writeln!(
stdout,
"Next exercise: {}",
app_state.current_exercise().terminal_link(),
),
)?,
}
Ok(())

View file

@ -56,10 +56,12 @@ impl<'a> WatchState<'a> {
"\nChecking the exercise `{}`. Please wait…",
self.app_state.current_exercise().name,
)?;
let success = self
.app_state
.current_exercise()
.run_exercise(Some(&mut self.output), self.app_state.cmd_runner())?;
self.output.push(b'\n');
if success {
self.done_status =
if let Some(solution_path) = self.app_state.current_solution_path()? {
@ -121,11 +123,9 @@ impl<'a> WatchState<'a> {
pub fn render(&mut self) -> Result<()> {
// Prevent having the first line shifted if clearing wasn't successful.
self.writer.write_all(b"\n")?;
clear_terminal(&mut self.writer)?;
self.writer.write_all(&self.output)?;
self.writer.write_all(b"\n")?;
if self.show_hint {
writeln!(
@ -137,21 +137,20 @@ impl<'a> WatchState<'a> {
}
if self.done_status != DoneStatus::Pending {
writeln!(
self.writer,
"{}\n",
"Exercise done ✓
When you are done experimenting, enter `n` to move on to the next exercise 🦀"
.bold()
.green(),
)?;
}
writeln!(self.writer, "{}", "Exercise done ✓".bold().green())?;
if let DoneStatus::DoneWithSolution(solution_path) = &self.done_status {
writeln!(
self.writer,
"A solution file can be found at {}\n",
style(TerminalFileLink(solution_path)).underlined().green(),
"{} for comparison: {}",
"Solution".bold(),
style(TerminalFileLink(solution_path)).underlined().cyan(),
)?;
}
writeln!(
self.writer,
"When done experimenting, enter `n` to move on to the next exercise 🦀\n",
)?;
}