diff --git a/Cargo.lock b/Cargo.lock index ee469437..aeb6c61f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -699,7 +699,6 @@ dependencies = [ "serde_json", "toml_edit", "which", - "winnow", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index da09ba18..435dfd49 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,7 +44,6 @@ serde_json = "1.0.115" serde.workspace = true toml_edit.workspace = true which = "6.0.1" -winnow = "0.6.5" [dev-dependencies] assert_cmd = "2.0.14" diff --git a/README.md b/README.md index 6b9c9833..fd76fdff 100644 --- a/README.md +++ b/README.md @@ -101,13 +101,7 @@ The task is simple. Most exercises contain an error that keeps them from compili rustlings watch ``` -This will try to verify the completion of every exercise in a predetermined order (what we think is best for newcomers). It will also rerun automatically every time you change a file in the `exercises/` directory. If you want to only run it once, you can use: - -```bash -rustlings verify -``` - -This will do the same as watch, but it'll quit after running. +This will try to verify the completion of every exercise in a predetermined order (what we think is best for newcomers). It will also rerun automatically every time you change a file in the `exercises/` directory. In case you want to go by your own order, or want to only verify a single exercise, you can run: diff --git a/exercises/00_intro/intro1.rs b/exercises/00_intro/intro1.rs index 5dd18b45..aa505a13 100644 --- a/exercises/00_intro/intro1.rs +++ b/exercises/00_intro/intro1.rs @@ -1,6 +1,6 @@ // intro1.rs // -// About this `I AM NOT DONE` thing: +// TODO: Update comment // We sometimes encourage you to keep trying things on a given exercise, even // after you already figured it out. If you got everything working and feel // ready for the next exercise, remove the `I AM NOT DONE` comment below. @@ -13,8 +13,6 @@ // Execute `rustlings hint intro1` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - fn main() { println!("Hello and"); println!(r#" welcome to... "#); diff --git a/exercises/00_intro/intro2.rs b/exercises/00_intro/intro2.rs index a28ad3dc..84e0d75c 100644 --- a/exercises/00_intro/intro2.rs +++ b/exercises/00_intro/intro2.rs @@ -5,8 +5,6 @@ // Execute `rustlings hint intro2` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - fn main() { printline!("Hello there!") } diff --git a/exercises/01_variables/variables1.rs b/exercises/01_variables/variables1.rs index b3e089a5..56408f35 100644 --- a/exercises/01_variables/variables1.rs +++ b/exercises/01_variables/variables1.rs @@ -5,8 +5,6 @@ // Execute `rustlings hint variables1` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - fn main() { x = 5; println!("x has the value {}", x); diff --git a/exercises/01_variables/variables2.rs b/exercises/01_variables/variables2.rs index e1c23edf..0f417e01 100644 --- a/exercises/01_variables/variables2.rs +++ b/exercises/01_variables/variables2.rs @@ -3,8 +3,6 @@ // Execute `rustlings hint variables2` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - fn main() { let x; if x == 10 { diff --git a/exercises/01_variables/variables3.rs b/exercises/01_variables/variables3.rs index 86bed419..421c6b15 100644 --- a/exercises/01_variables/variables3.rs +++ b/exercises/01_variables/variables3.rs @@ -3,8 +3,6 @@ // Execute `rustlings hint variables3` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - fn main() { let x: i32; println!("Number {}", x); diff --git a/exercises/01_variables/variables4.rs b/exercises/01_variables/variables4.rs index 5394f394..68f8f50b 100644 --- a/exercises/01_variables/variables4.rs +++ b/exercises/01_variables/variables4.rs @@ -3,8 +3,6 @@ // Execute `rustlings hint variables4` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - fn main() { let x = 3; println!("Number {}", x); diff --git a/exercises/01_variables/variables5.rs b/exercises/01_variables/variables5.rs index a29b38be..7014c568 100644 --- a/exercises/01_variables/variables5.rs +++ b/exercises/01_variables/variables5.rs @@ -3,8 +3,6 @@ // Execute `rustlings hint variables5` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - fn main() { let number = "T-H-R-E-E"; // don't change this line println!("Spell a Number : {}", number); diff --git a/exercises/01_variables/variables6.rs b/exercises/01_variables/variables6.rs index 853183ba..9f476825 100644 --- a/exercises/01_variables/variables6.rs +++ b/exercises/01_variables/variables6.rs @@ -3,8 +3,6 @@ // Execute `rustlings hint variables6` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - const NUMBER = 3; fn main() { println!("Number {}", NUMBER); diff --git a/exercises/02_functions/functions1.rs b/exercises/02_functions/functions1.rs index 40ed9a07..2365f91b 100644 --- a/exercises/02_functions/functions1.rs +++ b/exercises/02_functions/functions1.rs @@ -3,8 +3,6 @@ // Execute `rustlings hint functions1` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - fn main() { call_me(); } diff --git a/exercises/02_functions/functions2.rs b/exercises/02_functions/functions2.rs index 5154f34d..64dbd665 100644 --- a/exercises/02_functions/functions2.rs +++ b/exercises/02_functions/functions2.rs @@ -3,8 +3,6 @@ // Execute `rustlings hint functions2` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - fn main() { call_me(3); } diff --git a/exercises/02_functions/functions3.rs b/exercises/02_functions/functions3.rs index 74f44d6d..50371212 100644 --- a/exercises/02_functions/functions3.rs +++ b/exercises/02_functions/functions3.rs @@ -3,8 +3,6 @@ // Execute `rustlings hint functions3` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - fn main() { call_me(); } diff --git a/exercises/02_functions/functions4.rs b/exercises/02_functions/functions4.rs index 77c4b2aa..6b449edf 100644 --- a/exercises/02_functions/functions4.rs +++ b/exercises/02_functions/functions4.rs @@ -8,8 +8,6 @@ // Execute `rustlings hint functions4` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - fn main() { let original_price = 51; println!("Your sale price is {}", sale_price(original_price)); diff --git a/exercises/02_functions/functions5.rs b/exercises/02_functions/functions5.rs index f1b63f48..0c963223 100644 --- a/exercises/02_functions/functions5.rs +++ b/exercises/02_functions/functions5.rs @@ -3,8 +3,6 @@ // Execute `rustlings hint functions5` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - fn main() { let answer = square(3); println!("The square of 3 is {}", answer); diff --git a/exercises/03_if/if1.rs b/exercises/03_if/if1.rs index d2afccf8..a1df66bb 100644 --- a/exercises/03_if/if1.rs +++ b/exercises/03_if/if1.rs @@ -2,8 +2,6 @@ // // Execute `rustlings hint if1` or use the `hint` watch subcommand for a hint. -// I AM NOT DONE - pub fn bigger(a: i32, b: i32) -> i32 { // Complete this function to return the bigger number! // If both numbers are equal, any of them can be returned. diff --git a/exercises/03_if/if2.rs b/exercises/03_if/if2.rs index f512f13f..7b9c05f6 100644 --- a/exercises/03_if/if2.rs +++ b/exercises/03_if/if2.rs @@ -5,8 +5,6 @@ // // Execute `rustlings hint if2` or use the `hint` watch subcommand for a hint. -// I AM NOT DONE - pub fn foo_if_fizz(fizzish: &str) -> &str { if fizzish == "fizz" { "foo" diff --git a/exercises/03_if/if3.rs b/exercises/03_if/if3.rs index 16962740..caba172b 100644 --- a/exercises/03_if/if3.rs +++ b/exercises/03_if/if3.rs @@ -2,8 +2,6 @@ // // Execute `rustlings hint if3` or use the `hint` watch subcommand for a hint. -// I AM NOT DONE - pub fn animal_habitat(animal: &str) -> &'static str { let identifier = if animal == "crab" { 1 diff --git a/exercises/04_primitive_types/primitive_types1.rs b/exercises/04_primitive_types/primitive_types1.rs index 36633400..f9169c84 100644 --- a/exercises/04_primitive_types/primitive_types1.rs +++ b/exercises/04_primitive_types/primitive_types1.rs @@ -3,8 +3,6 @@ // Fill in the rest of the line that has code missing! No hints, there's no // tricks, just get used to typing these :) -// I AM NOT DONE - fn main() { // Booleans (`bool`) diff --git a/exercises/04_primitive_types/primitive_types2.rs b/exercises/04_primitive_types/primitive_types2.rs index f1616ed3..1911b12a 100644 --- a/exercises/04_primitive_types/primitive_types2.rs +++ b/exercises/04_primitive_types/primitive_types2.rs @@ -3,8 +3,6 @@ // Fill in the rest of the line that has code missing! No hints, there's no // tricks, just get used to typing these :) -// I AM NOT DONE - fn main() { // Characters (`char`) diff --git a/exercises/04_primitive_types/primitive_types3.rs b/exercises/04_primitive_types/primitive_types3.rs index 8b0de44e..70a8cc20 100644 --- a/exercises/04_primitive_types/primitive_types3.rs +++ b/exercises/04_primitive_types/primitive_types3.rs @@ -5,8 +5,6 @@ // Execute `rustlings hint primitive_types3` or use the `hint` watch subcommand // for a hint. -// I AM NOT DONE - fn main() { let a = ??? diff --git a/exercises/04_primitive_types/primitive_types4.rs b/exercises/04_primitive_types/primitive_types4.rs index d44d8776..8ed0a82a 100644 --- a/exercises/04_primitive_types/primitive_types4.rs +++ b/exercises/04_primitive_types/primitive_types4.rs @@ -5,8 +5,6 @@ // Execute `rustlings hint primitive_types4` or use the `hint` watch subcommand // for a hint. -// I AM NOT DONE - #[test] fn slice_out_of_array() { let a = [1, 2, 3, 4, 5]; diff --git a/exercises/04_primitive_types/primitive_types5.rs b/exercises/04_primitive_types/primitive_types5.rs index f646986e..5754a3d8 100644 --- a/exercises/04_primitive_types/primitive_types5.rs +++ b/exercises/04_primitive_types/primitive_types5.rs @@ -5,8 +5,6 @@ // Execute `rustlings hint primitive_types5` or use the `hint` watch subcommand // for a hint. -// I AM NOT DONE - fn main() { let cat = ("Furry McFurson", 3.5); let /* your pattern here */ = cat; diff --git a/exercises/04_primitive_types/primitive_types6.rs b/exercises/04_primitive_types/primitive_types6.rs index 07cc46c6..5f82f10f 100644 --- a/exercises/04_primitive_types/primitive_types6.rs +++ b/exercises/04_primitive_types/primitive_types6.rs @@ -6,8 +6,6 @@ // Execute `rustlings hint primitive_types6` or use the `hint` watch subcommand // for a hint. -// I AM NOT DONE - #[test] fn indexing_tuple() { let numbers = (1, 2, 3); diff --git a/exercises/05_vecs/vecs1.rs b/exercises/05_vecs/vecs1.rs index 65b7a7f8..c64acbbd 100644 --- a/exercises/05_vecs/vecs1.rs +++ b/exercises/05_vecs/vecs1.rs @@ -7,8 +7,6 @@ // // Execute `rustlings hint vecs1` or use the `hint` watch subcommand for a hint. -// I AM NOT DONE - fn array_and_vec() -> ([i32; 4], Vec) { let a = [10, 20, 30, 40]; // a plain array let v = // TODO: declare your vector here with the macro for vectors diff --git a/exercises/05_vecs/vecs2.rs b/exercises/05_vecs/vecs2.rs index e92c970a..d64d3d16 100644 --- a/exercises/05_vecs/vecs2.rs +++ b/exercises/05_vecs/vecs2.rs @@ -7,8 +7,6 @@ // // Execute `rustlings hint vecs2` or use the `hint` watch subcommand for a hint. -// I AM NOT DONE - fn vec_loop(mut v: Vec) -> Vec { for element in v.iter_mut() { // TODO: Fill this up so that each element in the Vec `v` is diff --git a/exercises/06_move_semantics/move_semantics1.rs b/exercises/06_move_semantics/move_semantics1.rs index e0639375..c612ba93 100644 --- a/exercises/06_move_semantics/move_semantics1.rs +++ b/exercises/06_move_semantics/move_semantics1.rs @@ -3,8 +3,6 @@ // Execute `rustlings hint move_semantics1` or use the `hint` watch subcommand // for a hint. -// I AM NOT DONE - #[test] fn main() { let vec0 = vec![22, 44, 66]; diff --git a/exercises/06_move_semantics/move_semantics2.rs b/exercises/06_move_semantics/move_semantics2.rs index dc58be50..3457d111 100644 --- a/exercises/06_move_semantics/move_semantics2.rs +++ b/exercises/06_move_semantics/move_semantics2.rs @@ -5,8 +5,6 @@ // Execute `rustlings hint move_semantics2` or use the `hint` watch subcommand // for a hint. -// I AM NOT DONE - #[test] fn main() { let vec0 = vec![22, 44, 66]; diff --git a/exercises/06_move_semantics/move_semantics3.rs b/exercises/06_move_semantics/move_semantics3.rs index 7152c716..9415eb15 100644 --- a/exercises/06_move_semantics/move_semantics3.rs +++ b/exercises/06_move_semantics/move_semantics3.rs @@ -6,8 +6,6 @@ // Execute `rustlings hint move_semantics3` or use the `hint` watch subcommand // for a hint. -// I AM NOT DONE - #[test] fn main() { let vec0 = vec![22, 44, 66]; diff --git a/exercises/06_move_semantics/move_semantics4.rs b/exercises/06_move_semantics/move_semantics4.rs index bfc917fa..1509f5d2 100644 --- a/exercises/06_move_semantics/move_semantics4.rs +++ b/exercises/06_move_semantics/move_semantics4.rs @@ -7,8 +7,6 @@ // Execute `rustlings hint move_semantics4` or use the `hint` watch subcommand // for a hint. -// I AM NOT DONE - #[test] fn main() { let vec0 = vec![22, 44, 66]; diff --git a/exercises/06_move_semantics/move_semantics5.rs b/exercises/06_move_semantics/move_semantics5.rs index 267bdccc..c84d2fea 100644 --- a/exercises/06_move_semantics/move_semantics5.rs +++ b/exercises/06_move_semantics/move_semantics5.rs @@ -6,8 +6,6 @@ // Execute `rustlings hint move_semantics5` or use the `hint` watch subcommand // for a hint. -// I AM NOT DONE - #[test] fn main() { let mut x = 100; diff --git a/exercises/06_move_semantics/move_semantics6.rs b/exercises/06_move_semantics/move_semantics6.rs index cace4ca6..6059e61c 100644 --- a/exercises/06_move_semantics/move_semantics6.rs +++ b/exercises/06_move_semantics/move_semantics6.rs @@ -5,8 +5,6 @@ // Execute `rustlings hint move_semantics6` or use the `hint` watch subcommand // for a hint. -// I AM NOT DONE - fn main() { let data = "Rust is great!".to_string(); diff --git a/exercises/07_structs/structs1.rs b/exercises/07_structs/structs1.rs index 5fa5821c..29781214 100644 --- a/exercises/07_structs/structs1.rs +++ b/exercises/07_structs/structs1.rs @@ -5,8 +5,6 @@ // Execute `rustlings hint structs1` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - struct ColorClassicStruct { // TODO: Something goes here } diff --git a/exercises/07_structs/structs2.rs b/exercises/07_structs/structs2.rs index 328567f0..a7a2deca 100644 --- a/exercises/07_structs/structs2.rs +++ b/exercises/07_structs/structs2.rs @@ -5,8 +5,6 @@ // Execute `rustlings hint structs2` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - #[derive(Debug)] struct Order { name: String, diff --git a/exercises/07_structs/structs3.rs b/exercises/07_structs/structs3.rs index 7cda5af1..9835b811 100644 --- a/exercises/07_structs/structs3.rs +++ b/exercises/07_structs/structs3.rs @@ -7,8 +7,6 @@ // Execute `rustlings hint structs3` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - #[derive(Debug)] struct Package { sender_country: String, diff --git a/exercises/08_enums/enums1.rs b/exercises/08_enums/enums1.rs index 25525b25..330269ca 100644 --- a/exercises/08_enums/enums1.rs +++ b/exercises/08_enums/enums1.rs @@ -2,8 +2,6 @@ // // No hints this time! ;) -// I AM NOT DONE - #[derive(Debug)] enum Message { // TODO: define a few types of messages as used below diff --git a/exercises/08_enums/enums2.rs b/exercises/08_enums/enums2.rs index df93fe0f..f0e4e6d3 100644 --- a/exercises/08_enums/enums2.rs +++ b/exercises/08_enums/enums2.rs @@ -3,8 +3,6 @@ // Execute `rustlings hint enums2` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - #[derive(Debug)] enum Message { // TODO: define the different variants used below diff --git a/exercises/08_enums/enums3.rs b/exercises/08_enums/enums3.rs index 92d18c46..580a553e 100644 --- a/exercises/08_enums/enums3.rs +++ b/exercises/08_enums/enums3.rs @@ -5,8 +5,6 @@ // Execute `rustlings hint enums3` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - enum Message { // TODO: implement the message variant types based on their usage below } diff --git a/exercises/09_strings/strings1.rs b/exercises/09_strings/strings1.rs index f50e1fa9..a1255a32 100644 --- a/exercises/09_strings/strings1.rs +++ b/exercises/09_strings/strings1.rs @@ -5,8 +5,6 @@ // Execute `rustlings hint strings1` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - fn main() { let answer = current_favorite_color(); println!("My current favorite color is {}", answer); diff --git a/exercises/09_strings/strings2.rs b/exercises/09_strings/strings2.rs index 4d95d16a..ba76fe65 100644 --- a/exercises/09_strings/strings2.rs +++ b/exercises/09_strings/strings2.rs @@ -5,8 +5,6 @@ // Execute `rustlings hint strings2` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - fn main() { let word = String::from("green"); // Try not changing this line :) if is_a_color_word(word) { diff --git a/exercises/09_strings/strings3.rs b/exercises/09_strings/strings3.rs index 384e7ce3..dedc081f 100644 --- a/exercises/09_strings/strings3.rs +++ b/exercises/09_strings/strings3.rs @@ -3,8 +3,6 @@ // Execute `rustlings hint strings3` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - fn trim_me(input: &str) -> String { // TODO: Remove whitespace from both ends of a string! ??? diff --git a/exercises/09_strings/strings4.rs b/exercises/09_strings/strings4.rs index e8c54acc..a034aa49 100644 --- a/exercises/09_strings/strings4.rs +++ b/exercises/09_strings/strings4.rs @@ -7,8 +7,6 @@ // // No hints this time! -// I AM NOT DONE - fn string_slice(arg: &str) { println!("{}", arg); } diff --git a/exercises/10_modules/modules1.rs b/exercises/10_modules/modules1.rs index 9eb5a48b..c750946c 100644 --- a/exercises/10_modules/modules1.rs +++ b/exercises/10_modules/modules1.rs @@ -3,8 +3,6 @@ // Execute `rustlings hint modules1` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - mod sausage_factory { // Don't let anybody outside of this module see this! fn get_secret_recipe() -> String { diff --git a/exercises/10_modules/modules2.rs b/exercises/10_modules/modules2.rs index 04154543..4d3106c4 100644 --- a/exercises/10_modules/modules2.rs +++ b/exercises/10_modules/modules2.rs @@ -7,8 +7,6 @@ // Execute `rustlings hint modules2` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - mod delicious_snacks { // TODO: Fix these use statements use self::fruits::PEAR as ??? diff --git a/exercises/10_modules/modules3.rs b/exercises/10_modules/modules3.rs index f2bb0503..c211a769 100644 --- a/exercises/10_modules/modules3.rs +++ b/exercises/10_modules/modules3.rs @@ -8,8 +8,6 @@ // Execute `rustlings hint modules3` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - // TODO: Complete this use statement use ??? diff --git a/exercises/11_hashmaps/hashmaps1.rs b/exercises/11_hashmaps/hashmaps1.rs index 80829eaa..5a52f611 100644 --- a/exercises/11_hashmaps/hashmaps1.rs +++ b/exercises/11_hashmaps/hashmaps1.rs @@ -11,8 +11,6 @@ // Execute `rustlings hint hashmaps1` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - use std::collections::HashMap; fn fruit_basket() -> HashMap { diff --git a/exercises/11_hashmaps/hashmaps2.rs b/exercises/11_hashmaps/hashmaps2.rs index a5925690..27306439 100644 --- a/exercises/11_hashmaps/hashmaps2.rs +++ b/exercises/11_hashmaps/hashmaps2.rs @@ -14,8 +14,6 @@ // Execute `rustlings hint hashmaps2` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - use std::collections::HashMap; #[derive(Hash, PartialEq, Eq)] diff --git a/exercises/11_hashmaps/hashmaps3.rs b/exercises/11_hashmaps/hashmaps3.rs index 8d9236df..775a4014 100644 --- a/exercises/11_hashmaps/hashmaps3.rs +++ b/exercises/11_hashmaps/hashmaps3.rs @@ -15,8 +15,6 @@ // Execute `rustlings hint hashmaps3` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - use std::collections::HashMap; // A structure to store the goal details of a team. diff --git a/exercises/12_options/options1.rs b/exercises/12_options/options1.rs index 3cbfecd6..ba4b1cda 100644 --- a/exercises/12_options/options1.rs +++ b/exercises/12_options/options1.rs @@ -3,8 +3,6 @@ // Execute `rustlings hint options1` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - // This function returns how much icecream there is left in the fridge. // If it's before 10PM, there's 5 scoops left. At 10PM, someone eats it // all, so there'll be no more left :( diff --git a/exercises/12_options/options2.rs b/exercises/12_options/options2.rs index 4d998e7d..73f707e3 100644 --- a/exercises/12_options/options2.rs +++ b/exercises/12_options/options2.rs @@ -3,8 +3,6 @@ // Execute `rustlings hint options2` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - #[cfg(test)] mod tests { #[test] diff --git a/exercises/12_options/options3.rs b/exercises/12_options/options3.rs index 23c15eab..7922ef92 100644 --- a/exercises/12_options/options3.rs +++ b/exercises/12_options/options3.rs @@ -3,8 +3,6 @@ // Execute `rustlings hint options3` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - struct Point { x: i32, y: i32, diff --git a/exercises/13_error_handling/errors1.rs b/exercises/13_error_handling/errors1.rs index 0ba59a57..9767f2c8 100644 --- a/exercises/13_error_handling/errors1.rs +++ b/exercises/13_error_handling/errors1.rs @@ -9,8 +9,6 @@ // Execute `rustlings hint errors1` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - pub fn generate_nametag_text(name: String) -> Option { if name.is_empty() { // Empty names aren't allowed. diff --git a/exercises/13_error_handling/errors2.rs b/exercises/13_error_handling/errors2.rs index 631fe67f..88d1bf43 100644 --- a/exercises/13_error_handling/errors2.rs +++ b/exercises/13_error_handling/errors2.rs @@ -19,8 +19,6 @@ // Execute `rustlings hint errors2` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - use std::num::ParseIntError; pub fn total_cost(item_quantity: &str) -> Result { diff --git a/exercises/13_error_handling/errors3.rs b/exercises/13_error_handling/errors3.rs index d42d3b17..56bb31b1 100644 --- a/exercises/13_error_handling/errors3.rs +++ b/exercises/13_error_handling/errors3.rs @@ -7,8 +7,6 @@ // Execute `rustlings hint errors3` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - use std::num::ParseIntError; fn main() { diff --git a/exercises/13_error_handling/errors4.rs b/exercises/13_error_handling/errors4.rs index d6d6fcb6..0e5c08bf 100644 --- a/exercises/13_error_handling/errors4.rs +++ b/exercises/13_error_handling/errors4.rs @@ -3,8 +3,6 @@ // Execute `rustlings hint errors4` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - #[derive(PartialEq, Debug)] struct PositiveNonzeroInteger(u64); diff --git a/exercises/13_error_handling/errors5.rs b/exercises/13_error_handling/errors5.rs index 92461a7e..0bcb4b8c 100644 --- a/exercises/13_error_handling/errors5.rs +++ b/exercises/13_error_handling/errors5.rs @@ -22,8 +22,6 @@ // Execute `rustlings hint errors5` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - use std::error; use std::fmt; use std::num::ParseIntError; diff --git a/exercises/13_error_handling/errors6.rs b/exercises/13_error_handling/errors6.rs index aaf0948e..de73a9a5 100644 --- a/exercises/13_error_handling/errors6.rs +++ b/exercises/13_error_handling/errors6.rs @@ -9,8 +9,6 @@ // Execute `rustlings hint errors6` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - use std::num::ParseIntError; // This is a custom error type that we will be using in `parse_pos_nonzero()`. diff --git a/exercises/14_generics/generics1.rs b/exercises/14_generics/generics1.rs index 35c1d2fe..545fd95c 100644 --- a/exercises/14_generics/generics1.rs +++ b/exercises/14_generics/generics1.rs @@ -6,8 +6,6 @@ // Execute `rustlings hint generics1` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - fn main() { let mut shopping_list: Vec = Vec::new(); shopping_list.push("milk"); diff --git a/exercises/14_generics/generics2.rs b/exercises/14_generics/generics2.rs index 074cd938..d50ed174 100644 --- a/exercises/14_generics/generics2.rs +++ b/exercises/14_generics/generics2.rs @@ -6,8 +6,6 @@ // Execute `rustlings hint generics2` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - struct Wrapper { value: u32, } diff --git a/exercises/15_traits/traits1.rs b/exercises/15_traits/traits1.rs index 37dfcbfe..c51d3b88 100644 --- a/exercises/15_traits/traits1.rs +++ b/exercises/15_traits/traits1.rs @@ -7,8 +7,6 @@ // Execute `rustlings hint traits1` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - trait AppendBar { fn append_bar(self) -> Self; } diff --git a/exercises/15_traits/traits2.rs b/exercises/15_traits/traits2.rs index 3e35f8e1..9a2bc07a 100644 --- a/exercises/15_traits/traits2.rs +++ b/exercises/15_traits/traits2.rs @@ -8,8 +8,6 @@ // // Execute `rustlings hint traits2` or use the `hint` watch subcommand for a hint. -// I AM NOT DONE - trait AppendBar { fn append_bar(self) -> Self; } diff --git a/exercises/15_traits/traits3.rs b/exercises/15_traits/traits3.rs index 4e2b06b0..357f1d7f 100644 --- a/exercises/15_traits/traits3.rs +++ b/exercises/15_traits/traits3.rs @@ -8,8 +8,6 @@ // Execute `rustlings hint traits3` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - pub trait Licensed { fn licensing_info(&self) -> String; } diff --git a/exercises/15_traits/traits4.rs b/exercises/15_traits/traits4.rs index 4bda3e57..7242c483 100644 --- a/exercises/15_traits/traits4.rs +++ b/exercises/15_traits/traits4.rs @@ -7,8 +7,6 @@ // Execute `rustlings hint traits4` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - pub trait Licensed { fn licensing_info(&self) -> String { "some information".to_string() diff --git a/exercises/15_traits/traits5.rs b/exercises/15_traits/traits5.rs index df183805..f258d327 100644 --- a/exercises/15_traits/traits5.rs +++ b/exercises/15_traits/traits5.rs @@ -7,8 +7,6 @@ // Execute `rustlings hint traits5` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - pub trait SomeTrait { fn some_function(&self) -> bool { true diff --git a/exercises/16_lifetimes/lifetimes1.rs b/exercises/16_lifetimes/lifetimes1.rs index 87bde490..4f544b41 100644 --- a/exercises/16_lifetimes/lifetimes1.rs +++ b/exercises/16_lifetimes/lifetimes1.rs @@ -8,8 +8,6 @@ // Execute `rustlings hint lifetimes1` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - fn longest(x: &str, y: &str) -> &str { if x.len() > y.len() { x diff --git a/exercises/16_lifetimes/lifetimes2.rs b/exercises/16_lifetimes/lifetimes2.rs index 4f3d8c18..33b5565f 100644 --- a/exercises/16_lifetimes/lifetimes2.rs +++ b/exercises/16_lifetimes/lifetimes2.rs @@ -6,8 +6,6 @@ // Execute `rustlings hint lifetimes2` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x diff --git a/exercises/16_lifetimes/lifetimes3.rs b/exercises/16_lifetimes/lifetimes3.rs index 9c59f9c0..de6005ec 100644 --- a/exercises/16_lifetimes/lifetimes3.rs +++ b/exercises/16_lifetimes/lifetimes3.rs @@ -5,8 +5,6 @@ // Execute `rustlings hint lifetimes3` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - struct Book { author: &str, title: &str, diff --git a/exercises/17_tests/tests1.rs b/exercises/17_tests/tests1.rs index 810277ac..bde21083 100644 --- a/exercises/17_tests/tests1.rs +++ b/exercises/17_tests/tests1.rs @@ -10,8 +10,6 @@ // Execute `rustlings hint tests1` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - #[cfg(test)] mod tests { #[test] diff --git a/exercises/17_tests/tests2.rs b/exercises/17_tests/tests2.rs index f8024e9f..aea5c0e4 100644 --- a/exercises/17_tests/tests2.rs +++ b/exercises/17_tests/tests2.rs @@ -6,8 +6,6 @@ // Execute `rustlings hint tests2` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - #[cfg(test)] mod tests { #[test] diff --git a/exercises/17_tests/tests3.rs b/exercises/17_tests/tests3.rs index 4013e384..d815e058 100644 --- a/exercises/17_tests/tests3.rs +++ b/exercises/17_tests/tests3.rs @@ -7,8 +7,6 @@ // Execute `rustlings hint tests3` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - pub fn is_even(num: i32) -> bool { num % 2 == 0 } diff --git a/exercises/17_tests/tests4.rs b/exercises/17_tests/tests4.rs index 935d0db1..0972a5b4 100644 --- a/exercises/17_tests/tests4.rs +++ b/exercises/17_tests/tests4.rs @@ -5,8 +5,6 @@ // Execute `rustlings hint tests4` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - struct Rectangle { width: i32, height: i32 diff --git a/exercises/18_iterators/iterators1.rs b/exercises/18_iterators/iterators1.rs index 31076bb9..7ec7da2c 100644 --- a/exercises/18_iterators/iterators1.rs +++ b/exercises/18_iterators/iterators1.rs @@ -9,8 +9,6 @@ // Execute `rustlings hint iterators1` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - #[test] fn main() { let my_fav_fruits = vec!["banana", "custard apple", "avocado", "peach", "raspberry"]; diff --git a/exercises/18_iterators/iterators2.rs b/exercises/18_iterators/iterators2.rs index dda82a08..4ca7742e 100644 --- a/exercises/18_iterators/iterators2.rs +++ b/exercises/18_iterators/iterators2.rs @@ -6,8 +6,6 @@ // Execute `rustlings hint iterators2` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - // Step 1. // Complete the `capitalize_first` function. // "hello" -> "Hello" diff --git a/exercises/18_iterators/iterators3.rs b/exercises/18_iterators/iterators3.rs index 29fa23a3..f7da049c 100644 --- a/exercises/18_iterators/iterators3.rs +++ b/exercises/18_iterators/iterators3.rs @@ -9,8 +9,6 @@ // Execute `rustlings hint iterators3` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - #[derive(Debug, PartialEq, Eq)] pub enum DivisionError { NotDivisible(NotDivisibleError), diff --git a/exercises/18_iterators/iterators4.rs b/exercises/18_iterators/iterators4.rs index 3c0724e9..af3958c8 100644 --- a/exercises/18_iterators/iterators4.rs +++ b/exercises/18_iterators/iterators4.rs @@ -3,8 +3,6 @@ // Execute `rustlings hint iterators4` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - pub fn factorial(num: u64) -> u64 { // Complete this function to return the factorial of num // Do not use: diff --git a/exercises/18_iterators/iterators5.rs b/exercises/18_iterators/iterators5.rs index a062ee4c..ceec5369 100644 --- a/exercises/18_iterators/iterators5.rs +++ b/exercises/18_iterators/iterators5.rs @@ -11,8 +11,6 @@ // Execute `rustlings hint iterators5` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - use std::collections::HashMap; #[derive(Clone, Copy, PartialEq, Eq)] diff --git a/exercises/19_smart_pointers/arc1.rs b/exercises/19_smart_pointers/arc1.rs index 3526ddcb..0647eea7 100644 --- a/exercises/19_smart_pointers/arc1.rs +++ b/exercises/19_smart_pointers/arc1.rs @@ -21,8 +21,6 @@ // // Execute `rustlings hint arc1` or use the `hint` watch subcommand for a hint. -// I AM NOT DONE - #![forbid(unused_imports)] // Do not change this, (or the next) line. use std::sync::Arc; use std::thread; diff --git a/exercises/19_smart_pointers/box1.rs b/exercises/19_smart_pointers/box1.rs index 513e7daa..2abc0249 100644 --- a/exercises/19_smart_pointers/box1.rs +++ b/exercises/19_smart_pointers/box1.rs @@ -18,8 +18,6 @@ // // Execute `rustlings hint box1` or use the `hint` watch subcommand for a hint. -// I AM NOT DONE - #[derive(PartialEq, Debug)] pub enum List { Cons(i32, List), diff --git a/exercises/19_smart_pointers/cow1.rs b/exercises/19_smart_pointers/cow1.rs index fcd3e0bb..b24591b7 100644 --- a/exercises/19_smart_pointers/cow1.rs +++ b/exercises/19_smart_pointers/cow1.rs @@ -12,8 +12,6 @@ // // Execute `rustlings hint cow1` or use the `hint` watch subcommand for a hint. -// I AM NOT DONE - use std::borrow::Cow; fn abs_all<'a, 'b>(input: &'a mut Cow<'b, [i32]>) -> &'a mut Cow<'b, [i32]> { diff --git a/exercises/19_smart_pointers/rc1.rs b/exercises/19_smart_pointers/rc1.rs index 1b903469..e96e6255 100644 --- a/exercises/19_smart_pointers/rc1.rs +++ b/exercises/19_smart_pointers/rc1.rs @@ -10,8 +10,6 @@ // // Execute `rustlings hint rc1` or use the `hint` watch subcommand for a hint. -// I AM NOT DONE - use std::rc::Rc; #[derive(Debug)] diff --git a/exercises/20_threads/threads1.rs b/exercises/20_threads/threads1.rs index 80b6def3..be1301d7 100644 --- a/exercises/20_threads/threads1.rs +++ b/exercises/20_threads/threads1.rs @@ -8,8 +8,6 @@ // Execute `rustlings hint threads1` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - use std::thread; use std::time::{Duration, Instant}; diff --git a/exercises/20_threads/threads2.rs b/exercises/20_threads/threads2.rs index 60d68241..13cb840e 100644 --- a/exercises/20_threads/threads2.rs +++ b/exercises/20_threads/threads2.rs @@ -7,8 +7,6 @@ // Execute `rustlings hint threads2` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - use std::sync::Arc; use std::thread; use std::time::Duration; diff --git a/exercises/20_threads/threads3.rs b/exercises/20_threads/threads3.rs index acb97b4b..35b914ac 100644 --- a/exercises/20_threads/threads3.rs +++ b/exercises/20_threads/threads3.rs @@ -3,8 +3,6 @@ // Execute `rustlings hint threads3` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - use std::sync::mpsc; use std::sync::Arc; use std::thread; diff --git a/exercises/21_macros/macros1.rs b/exercises/21_macros/macros1.rs index 678de6ee..65986db0 100644 --- a/exercises/21_macros/macros1.rs +++ b/exercises/21_macros/macros1.rs @@ -3,8 +3,6 @@ // Execute `rustlings hint macros1` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - macro_rules! my_macro { () => { println!("Check out my macro!"); diff --git a/exercises/21_macros/macros2.rs b/exercises/21_macros/macros2.rs index 788fc16a..b7c37fd9 100644 --- a/exercises/21_macros/macros2.rs +++ b/exercises/21_macros/macros2.rs @@ -3,8 +3,6 @@ // Execute `rustlings hint macros2` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - fn main() { my_macro!(); } diff --git a/exercises/21_macros/macros3.rs b/exercises/21_macros/macros3.rs index b795c149..92a19227 100644 --- a/exercises/21_macros/macros3.rs +++ b/exercises/21_macros/macros3.rs @@ -5,8 +5,6 @@ // Execute `rustlings hint macros3` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - mod macros { macro_rules! my_macro { () => { diff --git a/exercises/21_macros/macros4.rs b/exercises/21_macros/macros4.rs index 71b45a09..83a6e44f 100644 --- a/exercises/21_macros/macros4.rs +++ b/exercises/21_macros/macros4.rs @@ -3,8 +3,6 @@ // Execute `rustlings hint macros4` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - #[rustfmt::skip] macro_rules! my_macro { () => { diff --git a/exercises/22_clippy/clippy1.rs b/exercises/22_clippy/clippy1.rs index e0c6ce7c4..1e0f42e2 100644 --- a/exercises/22_clippy/clippy1.rs +++ b/exercises/22_clippy/clippy1.rs @@ -9,8 +9,6 @@ // Execute `rustlings hint clippy1` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - use std::f32; fn main() { diff --git a/exercises/22_clippy/clippy2.rs b/exercises/22_clippy/clippy2.rs index 9b87a0b7..37ac089e 100644 --- a/exercises/22_clippy/clippy2.rs +++ b/exercises/22_clippy/clippy2.rs @@ -3,8 +3,6 @@ // Execute `rustlings hint clippy2` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - fn main() { let mut res = 42; let option = Some(12); diff --git a/exercises/22_clippy/clippy3.rs b/exercises/22_clippy/clippy3.rs index 5a95f5b8..6a6a36b5 100644 --- a/exercises/22_clippy/clippy3.rs +++ b/exercises/22_clippy/clippy3.rs @@ -3,8 +3,6 @@ // Here's a couple more easy Clippy fixes, so you can see its utility. // No hints. -// I AM NOT DONE - #[allow(unused_variables, unused_assignments)] fn main() { let my_option: Option<()> = None; diff --git a/exercises/23_conversions/as_ref_mut.rs b/exercises/23_conversions/as_ref_mut.rs index 2ba9e3f0..cd2c93be 100644 --- a/exercises/23_conversions/as_ref_mut.rs +++ b/exercises/23_conversions/as_ref_mut.rs @@ -7,8 +7,6 @@ // Execute `rustlings hint as_ref_mut` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - // Obtain the number of bytes (not characters) in the given argument. // TODO: Add the AsRef trait appropriately as a trait bound. fn byte_counter(arg: T) -> usize { diff --git a/exercises/23_conversions/from_into.rs b/exercises/23_conversions/from_into.rs index 11787c37..d2a1609e 100644 --- a/exercises/23_conversions/from_into.rs +++ b/exercises/23_conversions/from_into.rs @@ -41,8 +41,6 @@ impl Default for Person { // If while parsing the age, something goes wrong, then return the default of // Person Otherwise, then return an instantiated Person object with the results -// I AM NOT DONE - impl From<&str> for Person { fn from(s: &str) -> Person {} } diff --git a/exercises/23_conversions/from_str.rs b/exercises/23_conversions/from_str.rs index e2093474..ed91ca5c 100644 --- a/exercises/23_conversions/from_str.rs +++ b/exercises/23_conversions/from_str.rs @@ -31,8 +31,6 @@ enum ParsePersonError { ParseInt(ParseIntError), } -// I AM NOT DONE - // Steps: // 1. If the length of the provided string is 0, an error should be returned // 2. Split the given string on the commas present in it diff --git a/exercises/23_conversions/try_from_into.rs b/exercises/23_conversions/try_from_into.rs index 32d6ef39..23166555 100644 --- a/exercises/23_conversions/try_from_into.rs +++ b/exercises/23_conversions/try_from_into.rs @@ -27,8 +27,6 @@ enum IntoColorError { IntConversion, } -// I AM NOT DONE - // Your task is to complete this implementation and return an Ok result of inner // type Color. You need to create an implementation for a tuple of three // integers, an array of three integers, and a slice of integers. diff --git a/exercises/23_conversions/using_as.rs b/exercises/23_conversions/using_as.rs index 414cef3a..9f617ec5 100644 --- a/exercises/23_conversions/using_as.rs +++ b/exercises/23_conversions/using_as.rs @@ -10,8 +10,6 @@ // Execute `rustlings hint using_as` or use the `hint` watch subcommand for a // hint. -// I AM NOT DONE - fn average(values: &[f64]) -> f64 { let total = values.iter().sum::(); total / values.len() diff --git a/exercises/quiz1.rs b/exercises/quiz1.rs index 4ee5ada7..b9e71f59 100644 --- a/exercises/quiz1.rs +++ b/exercises/quiz1.rs @@ -13,8 +13,6 @@ // // No hints this time ;) -// I AM NOT DONE - // Put your function here! // fn calculate_price_of_apples { diff --git a/exercises/quiz2.rs b/exercises/quiz2.rs index 29925caf..8ace3fe0 100644 --- a/exercises/quiz2.rs +++ b/exercises/quiz2.rs @@ -20,8 +20,6 @@ // // No hints this time! -// I AM NOT DONE - pub enum Command { Uppercase, Trim, diff --git a/exercises/quiz3.rs b/exercises/quiz3.rs index 3b01d313..24f70829 100644 --- a/exercises/quiz3.rs +++ b/exercises/quiz3.rs @@ -16,8 +16,6 @@ // // Execute `rustlings hint quiz3` or use the `hint` watch subcommand for a hint. -// I AM NOT DONE - pub struct ReportCard { pub grade: f32, pub student_name: String, diff --git a/info.toml b/info.toml index 36629b38..c085e89c 100644 --- a/info.toml +++ b/info.toml @@ -4,6 +4,7 @@ name = "intro1" path = "exercises/00_intro/intro1.rs" mode = "compile" +# TODO: Fix hint hint = """ Remove the `I AM NOT DONE` comment in the `exercises/intro00/intro1.rs` file to move on to the next exercise.""" @@ -129,11 +130,7 @@ path = "exercises/02_functions/functions3.rs" mode = "compile" hint = """ This time, the function *declaration* is okay, but there's something wrong -with the place where we're calling the function. - -As a reminder, you can freely play around with different solutions in Rustlings! -Watch mode will only jump to the next exercise if you remove the `I AM NOT -DONE` comment.""" +with the place where we're calling the function.""" [[exercises]] name = "functions4" diff --git a/src/app_state.rs b/src/app_state.rs new file mode 100644 index 00000000..4a0912e4 --- /dev/null +++ b/src/app_state.rs @@ -0,0 +1,185 @@ +use anyhow::{bail, Context, Result}; +use serde::{Deserialize, Serialize}; +use std::fs; + +use crate::exercise::Exercise; + +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, +} + +impl StateFile { + fn read(exercises: &[Exercise]) -> Option { + 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(()) + } +} + +pub struct AppState { + state_file: StateFile, + exercises: &'static [Exercise], + n_done: u16, + current_exercise: &'static Exercise, +} + +#[must_use] +pub enum ExercisesProgress { + AllDone, + Pending, +} + +impl AppState { + pub fn new(exercises: Vec) -> Self { + // Leaking for sending the exercises to the debounce event handler. + // Leaking is not a problem since the exercises' slice is used until the end of the program. + let exercises = exercises.leak(); + + let state_file = StateFile::read_or_default(exercises); + let n_done = state_file + .progress + .iter() + .fold(0, |acc, done| acc + u16::from(*done)); + let current_exercise = &exercises[state_file.current_exercise_ind]; + + Self { + state_file, + exercises, + n_done, + current_exercise, + } + } + + #[inline] + pub fn current_exercise_ind(&self) -> usize { + self.state_file.current_exercise_ind + } + + #[inline] + pub fn progress(&self) -> &[bool] { + &self.state_file.progress + } + + #[inline] + pub fn exercises(&self) -> &'static [Exercise] { + self.exercises + } + + #[inline] + pub fn n_done(&self) -> u16 { + self.n_done + } + + #[inline] + pub fn current_exercise(&self) -> &'static Exercise { + self.current_exercise + } + + pub fn set_current_exercise_ind(&mut self, ind: usize) -> Result<()> { + if ind >= self.exercises.len() { + bail!(BAD_INDEX_ERR); + } + + self.state_file.current_exercise_ind = ind; + self.current_exercise = &self.exercises[ind]; + + self.state_file.write() + } + + pub fn set_current_exercise_by_name(&mut self, name: &str) -> Result<()> { + let (ind, exercise) = self + .exercises + .iter() + .enumerate() + .find(|(_, exercise)| exercise.name == name) + .with_context(|| format!("No exercise found for '{name}'!"))?; + + self.state_file.current_exercise_ind = ind; + self.current_exercise = exercise; + + self.state_file.write() + } + + pub fn set_pending(&mut self, ind: usize) -> Result<()> { + let done = self + .state_file + .progress + .get_mut(ind) + .context(BAD_INDEX_ERR)?; + + if *done { + *done = false; + self.n_done -= 1; + self.state_file.write()?; + } + + Ok(()) + } + + fn next_exercise_ind(&self) -> Option { + let current_ind = self.state_file.current_exercise_ind; + + if current_ind == self.state_file.progress.len() - 1 { + // The last exercise is done. + // Search for exercises not done from the start. + return self.state_file.progress[..current_ind] + .iter() + .position(|done| !done); + } + + // The done exercise isn't the last one. + // Search for a pending exercise after the current one and then from the start. + match self.state_file.progress[current_ind + 1..] + .iter() + .position(|done| !done) + { + Some(ind) => Some(current_ind + 1 + ind), + None => self.state_file.progress[..current_ind] + .iter() + .position(|done| !done), + } + } + + pub fn done_current_exercise(&mut self) -> Result { + let done = &mut self.state_file.progress[self.state_file.current_exercise_ind]; + if !*done { + *done = true; + self.n_done += 1; + } + + let Some(ind) = self.next_exercise_ind() else { + return Ok(ExercisesProgress::AllDone); + }; + + self.set_current_exercise_ind(ind)?; + + Ok(ExercisesProgress::Pending) + } +} diff --git a/src/exercise.rs b/src/exercise.rs index ca47009d..de435d13 100644 --- a/src/exercise.rs +++ b/src/exercise.rs @@ -1,38 +1,14 @@ use anyhow::{Context, Result}; use serde::Deserialize; use std::{ - array, fmt::{self, Debug, Display, Formatter}, - fs::{self, File}, - io::{self, BufRead, BufReader}, - mem, + fs::{self}, path::PathBuf, process::{Command, Output}, }; -use winnow::{ - ascii::{space0, Caseless}, - combinator::opt, - Parser, -}; use crate::embedded::{WriteStrategy, EMBEDDED_FILES}; -// The number of context lines above and below a highlighted line. -const CONTEXT: usize = 2; - -// Check if the line contains the "I AM NOT DONE" comment. -fn contains_not_done_comment(input: &str) -> bool { - ( - space0::<_, ()>, - "//", - opt('/'), - space0, - Caseless("I AM NOT DONE"), - ) - .parse_next(&mut &*input) - .is_ok() -} - // The mode of the exercise. #[derive(Deserialize, Copy, Clone)] #[serde(rename_all = "lowercase")] @@ -78,13 +54,6 @@ pub struct Exercise { pub hint: String, } -// The state of an Exercise. -#[derive(PartialEq, Eq, Debug)] -pub enum State { - Done, - Pending(Vec), -} - // The context information of a pending exercise. #[derive(PartialEq, Eq, Debug)] pub struct ContextLine { @@ -129,105 +98,6 @@ impl Exercise { } } - pub fn state(&self) -> Result { - let source_file = File::open(&self.path) - .with_context(|| format!("Failed to open the exercise file {}", self.path.display()))?; - let mut source_reader = BufReader::new(source_file); - - // Read the next line into `buf` without the newline at the end. - let mut read_line = |buf: &mut String| -> io::Result<_> { - let n = source_reader.read_line(buf)?; - if buf.ends_with('\n') { - buf.pop(); - if buf.ends_with('\r') { - buf.pop(); - } - } - Ok(n) - }; - - let mut current_line_number: usize = 1; - // Keep the last `CONTEXT` lines while iterating over the file lines. - let mut prev_lines: [_; CONTEXT] = array::from_fn(|_| String::with_capacity(256)); - let mut line = String::with_capacity(256); - - loop { - let n = read_line(&mut line).with_context(|| { - format!("Failed to read the exercise file {}", self.path.display()) - })?; - - // Reached the end of the file and didn't find the comment. - if n == 0 { - return Ok(State::Done); - } - - if contains_not_done_comment(&line) { - let mut context = Vec::with_capacity(2 * CONTEXT + 1); - // Previous lines. - for (ind, prev_line) in prev_lines - .into_iter() - .take(current_line_number - 1) - .enumerate() - .rev() - { - context.push(ContextLine { - line: prev_line, - number: current_line_number - 1 - ind, - important: false, - }); - } - - // Current line. - context.push(ContextLine { - line, - number: current_line_number, - important: true, - }); - - // Next lines. - for ind in 0..CONTEXT { - let mut next_line = String::with_capacity(256); - let Ok(n) = read_line(&mut next_line) else { - // If an error occurs, just ignore the next lines. - break; - }; - - // Reached the end of the file. - if n == 0 { - break; - } - - context.push(ContextLine { - line: next_line, - number: current_line_number + 1 + ind, - important: false, - }); - } - - return Ok(State::Pending(context)); - } - - current_line_number += 1; - // Add the current line as a previous line and shift the older lines by one. - for prev_line in &mut prev_lines { - mem::swap(&mut line, prev_line); - } - // The current line now contains the oldest previous line. - // Recycle it for reading the next line. - line.clear(); - } - } - - // Check that the exercise looks to be solved using self.state() - // This is not the best way to check since - // the user can just remove the "I AM NOT DONE" string from the file - // without actually having solved anything. - // The only other way to truly check this would to compile and run - // the exercise; which would be both costly and counterintuitive - pub fn looks_done(&self) -> Result { - self.state().map(|state| state == State::Done) - } - pub fn reset(&self) -> Result<()> { EMBEDDED_FILES .write_exercise_to_disk(&self.path, WriteStrategy::Overwrite) @@ -240,77 +110,3 @@ impl Display for Exercise { self.path.fmt(f) } } - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_pending_state() { - let exercise = Exercise { - name: "pending_exercise".into(), - path: PathBuf::from("tests/fixture/state/exercises/pending_exercise.rs"), - mode: Mode::Compile, - hint: String::new(), - }; - - let state = exercise.state(); - let expected = vec![ - ContextLine { - line: "// fake_exercise".to_string(), - number: 1, - important: false, - }, - ContextLine { - line: "".to_string(), - number: 2, - important: false, - }, - ContextLine { - line: "// I AM NOT DONE".to_string(), - number: 3, - important: true, - }, - ContextLine { - line: "".to_string(), - number: 4, - important: false, - }, - ContextLine { - line: "fn main() {".to_string(), - number: 5, - important: false, - }, - ]; - - assert_eq!(state.unwrap(), State::Pending(expected)); - } - - #[test] - fn test_finished_exercise() { - let exercise = Exercise { - name: "finished_exercise".into(), - path: PathBuf::from("tests/fixture/state/exercises/finished_exercise.rs"), - mode: Mode::Compile, - hint: String::new(), - }; - - assert_eq!(exercise.state().unwrap(), State::Done); - } - - #[test] - fn test_not_done() { - assert!(contains_not_done_comment("// I AM NOT DONE")); - assert!(contains_not_done_comment("/// I AM NOT DONE")); - assert!(contains_not_done_comment("// I AM NOT DONE")); - assert!(contains_not_done_comment("/// I AM NOT DONE")); - assert!(contains_not_done_comment("// I AM NOT DONE ")); - assert!(contains_not_done_comment("// I AM NOT DONE!")); - assert!(contains_not_done_comment("// I am not done")); - assert!(contains_not_done_comment("// i am NOT done")); - - assert!(!contains_not_done_comment("I AM NOT DONE")); - assert!(!contains_not_done_comment("// NOT DONE")); - assert!(!contains_not_done_comment("DONE")); - } -} diff --git a/src/list.rs b/src/list.rs index 560b85a8..80b78e8d 100644 --- a/src/list.rs +++ b/src/list.rs @@ -9,11 +9,11 @@ use std::{fmt::Write, io}; mod state; -use crate::{exercise::Exercise, state_file::StateFile}; +use crate::app_state::AppState; use self::state::{Filter, UiState}; -pub fn list(state_file: &mut StateFile, exercises: &'static [Exercise]) -> Result<()> { +pub fn list(app_state: &mut AppState) -> Result<()> { let mut stdout = io::stdout().lock(); stdout.execute(EnterAlternateScreen)?; enable_raw_mode()?; @@ -21,7 +21,7 @@ pub fn list(state_file: &mut StateFile, exercises: &'static [Exercise]) -> Resul let mut terminal = Terminal::new(CrosstermBackend::new(&mut stdout))?; terminal.clear()?; - let mut ui_state = UiState::new(state_file, exercises); + let mut ui_state = UiState::new(app_state); 'outer: loop { terminal.draw(|frame| ui_state.draw(frame).unwrap())?; @@ -56,7 +56,7 @@ pub fn list(state_file: &mut StateFile, exercises: &'static [Exercise]) -> Resul "Enabled filter DONE │ Press d again to disable the filter" }; - ui_state = ui_state.with_updated_rows(state_file); + ui_state = ui_state.with_updated_rows(); ui_state.message.push_str(message); } KeyCode::Char('p') => { @@ -68,23 +68,20 @@ pub fn list(state_file: &mut StateFile, exercises: &'static [Exercise]) -> Resul "Enabled filter PENDING │ Press p again to disable the filter" }; - ui_state = ui_state.with_updated_rows(state_file); + ui_state = ui_state.with_updated_rows(); ui_state.message.push_str(message); } KeyCode::Char('r') => { - let selected = ui_state.selected(); - let exercise = &exercises[selected]; - exercise.reset()?; - state_file.reset(selected)?; + let exercise = ui_state.reset_selected()?; - ui_state = ui_state.with_updated_rows(state_file); + ui_state = ui_state.with_updated_rows(); ui_state .message .write_fmt(format_args!("The exercise {exercise} has been reset!"))?; } KeyCode::Char('c') => { - state_file.set_next_exercise_ind(ui_state.selected())?; - ui_state = ui_state.with_updated_rows(state_file); + ui_state.selected_to_current_exercise()?; + ui_state = ui_state.with_updated_rows(); } _ => (), } diff --git a/src/list/state.rs b/src/list/state.rs index 209374b1..7714268c 100644 --- a/src/list/state.rs +++ b/src/list/state.rs @@ -7,7 +7,7 @@ use ratatui::{ Frame, }; -use crate::{exercise::Exercise, progress_bar::progress_bar_ratatui, state_file::StateFile}; +use crate::{app_state::AppState, exercise::Exercise, progress_bar::progress_bar_ratatui}; #[derive(Copy, Clone, PartialEq, Eq)] pub enum Filter { @@ -16,30 +16,29 @@ pub enum Filter { None, } -pub struct UiState { +pub struct UiState<'a> { pub table: Table<'static>, pub message: String, pub filter: Filter, - exercises: &'static [Exercise], - progress: u16, - selected: usize, + app_state: &'a mut AppState, table_state: TableState, + selected: usize, last_ind: usize, } -impl UiState { - pub fn with_updated_rows(mut self, state_file: &StateFile) -> Self { +impl<'a> UiState<'a> { + pub fn with_updated_rows(mut self) -> Self { + let current_exercise_ind = self.app_state.current_exercise_ind(); + let mut rows_counter: usize = 0; - let mut progress: u16 = 0; let rows = self - .exercises + .app_state + .exercises() .iter() - .zip(state_file.progress().iter().copied()) + .zip(self.app_state.progress().iter().copied()) .enumerate() .filter_map(|(ind, (exercise, done))| { let exercise_state = if done { - progress += 1; - if self.filter == Filter::Pending { return None; } @@ -55,7 +54,7 @@ impl UiState { rows_counter += 1; - let next = if ind == state_file.next_exercise_ind() { + let next = if ind == current_exercise_ind { ">>>>".bold().red() } else { Span::default() @@ -74,15 +73,14 @@ impl UiState { self.last_ind = rows_counter.saturating_sub(1); self.select(self.selected.min(self.last_ind)); - self.progress = progress; - self } - pub fn new(state_file: &StateFile, exercises: &'static [Exercise]) -> Self { + pub fn new(app_state: &'a mut AppState) -> Self { let header = Row::new(["Next", "State", "Name", "Path"]); - let max_name_len = exercises + let max_name_len = app_state + .exercises() .iter() .map(|exercise| exercise.name.len()) .max() @@ -104,7 +102,7 @@ impl UiState { .highlight_symbol("🦀") .block(Block::default().borders(Borders::BOTTOM)); - let selected = state_file.next_exercise_ind(); + let selected = app_state.current_exercise_ind(); let table_state = TableState::default() .with_offset(selected.saturating_sub(10)) .with_selected(Some(selected)); @@ -113,19 +111,13 @@ impl UiState { table, message: String::with_capacity(128), filter: Filter::None, - exercises, - progress: 0, - selected, + app_state, table_state, + selected, last_ind: 0, }; - slf.with_updated_rows(state_file) - } - - #[inline] - pub fn selected(&self) -> usize { - self.selected + slf.with_updated_rows() } fn select(&mut self, ind: usize) { @@ -134,11 +126,13 @@ impl UiState { } pub fn select_next(&mut self) { - self.select(self.selected.saturating_add(1).min(self.last_ind)); + let next = (self.selected + 1).min(self.last_ind); + self.select(next); } pub fn select_previous(&mut self) { - self.select(self.selected.saturating_sub(1)); + let previous = self.selected.saturating_sub(1); + self.select(previous); } #[inline] @@ -167,8 +161,8 @@ impl UiState { frame.render_widget( Paragraph::new(progress_bar_ratatui( - self.progress, - self.exercises.len() as u16, + self.app_state.n_done(), + self.app_state.exercises().len() as u16, area.width, )?) .block(Block::default().borders(Borders::BOTTOM)), @@ -200,4 +194,19 @@ impl UiState { Ok(()) } + + pub fn reset_selected(&mut self) -> Result<&'static Exercise> { + self.app_state.set_pending(self.selected)?; + // TODO: Take care of filters! + let exercise = &self.app_state.exercises()[self.selected]; + exercise.reset()?; + + Ok(exercise) + } + + #[inline] + pub fn selected_to_current_exercise(&mut self) -> Result<()> { + // TODO: Take care of filters! + self.app_state.set_current_exercise_ind(self.selected) + } } diff --git a/src/main.rs b/src/main.rs index fc83e0fd..926605c1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,8 @@ -use anyhow::{bail, Context, Result}; +use anyhow::{Context, Result}; use clap::{Parser, Subcommand}; use std::{path::Path, process::exit}; +mod app_state; mod consts; mod embedded; mod exercise; @@ -9,17 +10,15 @@ mod init; mod list; mod progress_bar; mod run; -mod state_file; -mod verify; mod watch; use self::{ + app_state::AppState, consts::WELCOME, - exercise::{Exercise, InfoFile}, + exercise::InfoFile, + init::init, list::list, run::run, - state_file::StateFile, - verify::{verify, VerifyState}, watch::{watch, WatchExit}, }; @@ -35,14 +34,12 @@ struct Args { enum Subcommands { /// Initialize Rustlings Init, - /// Verify all exercises according to the recommended order - Verify, /// Same as just running `rustlings` without a subcommand. Watch, - /// Run/Test a single exercise + /// Run a single exercise. Runs the next pending exercise if the exercise name is not specified. Run { /// The name of the exercise - name: String, + name: Option, }, /// Reset a single exercise Reset { @@ -56,26 +53,6 @@ enum Subcommands { }, } -fn find_exercise(name: &str, exercises: &'static [Exercise]) -> Result<(usize, &'static Exercise)> { - if name == "next" { - for (ind, exercise) in exercises.iter().enumerate() { - if !exercise.looks_done()? { - return Ok((ind, exercise)); - } - } - - println!("🎉 Congratulations! You have done all the exercises!"); - println!("🔚 There are no more exercises to do next!"); - exit(0); - } - - exercises - .iter() - .enumerate() - .find(|(_, exercise)| exercise.name == name) - .with_context(|| format!("No exercise found for '{name}'!")) -} - fn main() -> Result<()> { let args = Args::parse(); @@ -87,11 +64,10 @@ Try running `cargo --version` to diagnose the problem.", let mut info_file = InfoFile::parse()?; info_file.exercises.shrink_to_fit(); - // Leaking is not a problem since the exercises' slice is used until the end of the program. - let exercises = info_file.exercises.leak(); + let exercises = info_file.exercises; if matches!(args.command, Some(Subcommands::Init)) { - init::init(exercises).context("Initialization failed")?; + init(&exercises).context("Initialization failed")?; println!( "\nDone initialization!\n Run `cd rustlings` to go into the generated directory. @@ -109,38 +85,37 @@ If you are just starting with Rustlings, run the command `rustlings init` to ini exit(1); } - let mut state_file = StateFile::read_or_default(exercises); + let mut app_state = AppState::new(exercises); match args.command { None | Some(Subcommands::Watch) => loop { - match watch(&mut state_file, exercises)? { + match watch(&mut app_state)? { 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 state_file, exercises)?, + WatchExit::List => list(&mut app_state)?, } }, // `Init` is handled above. Some(Subcommands::Init) => (), Some(Subcommands::Run { name }) => { - let (_, exercise) = find_exercise(&name, exercises)?; - run(exercise).unwrap_or_else(|_| exit(1)); + if let Some(name) = name { + app_state.set_current_exercise_by_name(&name)?; + } + run(&mut app_state)?; } Some(Subcommands::Reset { name }) => { - let (ind, exercise) = find_exercise(&name, exercises)?; + app_state.set_current_exercise_by_name(&name)?; + app_state.set_pending(app_state.current_exercise_ind())?; + let exercise = app_state.current_exercise(); exercise.reset()?; - state_file.reset(ind)?; println!("The exercise {exercise} has been reset!"); } Some(Subcommands::Hint { name }) => { - let (_, exercise) = find_exercise(&name, exercises)?; - println!("{}", exercise.hint); + app_state.set_current_exercise_by_name(&name)?; + println!("{}", app_state.current_exercise().hint); } - Some(Subcommands::Verify) => match verify(exercises, 0)? { - VerifyState::AllExercisesDone => println!("All exercises done!"), - VerifyState::Failed(exercise) => bail!("Exercise {exercise} failed"), - }, } Ok(()) diff --git a/src/run.rs b/src/run.rs index 2fd6f407..18da193b 100644 --- a/src/run.rs +++ b/src/run.rs @@ -2,13 +2,10 @@ use anyhow::{bail, Result}; use crossterm::style::Stylize; use std::io::{stdout, Write}; -use crate::exercise::Exercise; +use crate::app_state::{AppState, ExercisesProgress}; -// Invoke the rust compiler on the path of the given exercise, -// and run the ensuing binary. -// The verbose argument helps determine whether or not to show -// the output from the test harnesses (if the mode of the exercise is test) -pub fn run(exercise: &Exercise) -> Result<()> { +pub fn run(app_state: &mut AppState) -> Result<()> { + let exercise = app_state.current_exercise(); let output = exercise.run()?; { @@ -22,7 +19,19 @@ pub fn run(exercise: &Exercise) -> Result<()> { bail!("Ran {exercise} with errors"); } - println!("{}", "✓ Successfully ran {exercise}".green()); + println!( + "{}{}", + "✓ Successfully ran ".green(), + exercise.path.to_string_lossy().green(), + ); + + match app_state.done_current_exercise()? { + ExercisesProgress::AllDone => println!( + "🎉 Congratulations! You have done all the exercises! +🔚 There are no more exercises to do next!" + ), + ExercisesProgress::Pending => println!("Next exercise: {}", app_state.current_exercise()), + } Ok(()) } diff --git a/src/state_file.rs b/src/state_file.rs deleted file mode 100644 index 6b80354e..00000000 --- a/src/state_file.rs +++ /dev/null @@ -1,68 +0,0 @@ -use anyhow::{bail, Context, Result}; -use serde::{Deserialize, Serialize}; -use std::fs; - -use crate::exercise::Exercise; - -#[derive(Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct StateFile { - next_exercise_ind: usize, - progress: Vec, -} - -const BAD_INDEX_ERR: &str = "The next exercise index is higher than the number of exercises"; - -impl StateFile { - fn read(exercises: &[Exercise]) -> Option { - 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.next_exercise_ind >= exercises.len() { - return None; - } - - Some(slf) - } - - pub fn read_or_default(exercises: &[Exercise]) -> Self { - Self::read(exercises).unwrap_or_else(|| Self { - next_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(()) - } - - #[inline] - pub fn next_exercise_ind(&self) -> usize { - self.next_exercise_ind - } - - pub fn set_next_exercise_ind(&mut self, ind: usize) -> Result<()> { - if ind >= self.progress.len() { - bail!(BAD_INDEX_ERR); - } - self.next_exercise_ind = ind; - self.write() - } - - #[inline] - pub fn progress(&self) -> &[bool] { - &self.progress - } - - pub fn reset(&mut self, ind: usize) -> Result<()> { - let done = self.progress.get_mut(ind).context(BAD_INDEX_ERR)?; - *done = false; - self.write() - } -} diff --git a/src/verify.rs b/src/verify.rs deleted file mode 100644 index cea6bdf6..00000000 --- a/src/verify.rs +++ /dev/null @@ -1,85 +0,0 @@ -use anyhow::Result; -use crossterm::style::{Attribute, ContentStyle, Stylize}; -use std::io::{stdout, Write}; - -use crate::exercise::{Exercise, Mode, State}; - -pub enum VerifyState { - AllExercisesDone, - Failed(&'static Exercise), -} - -// Verify that the provided container of Exercise objects -// can be compiled and run without any failures. -// Any such failures will be reported to the end user. -// If the Exercise being verified is a test, the verbose boolean -// determines whether or not the test harness outputs are displayed. -pub fn verify( - exercises: &'static [Exercise], - mut current_exercise_ind: usize, -) -> Result { - while current_exercise_ind < exercises.len() { - let exercise = &exercises[current_exercise_ind]; - - println!( - "Progress: {current_exercise_ind}/{} ({:.1}%)\n", - exercises.len(), - current_exercise_ind as f32 / exercises.len() as f32 * 100.0, - ); - - let output = exercise.run()?; - - { - let mut stdout = stdout().lock(); - stdout.write_all(&output.stdout)?; - stdout.write_all(&output.stderr)?; - stdout.flush()?; - } - - if !output.status.success() { - return Ok(VerifyState::Failed(exercise)); - } - - println!(); - // TODO: Color - match exercise.mode { - Mode::Compile => println!("Successfully ran {exercise}!"), - Mode::Test => println!("Successfully tested {exercise}!"), - Mode::Clippy => println!("Successfully checked {exercise}!"), - } - - if let State::Pending(context) = exercise.state()? { - println!( - "\nYou can keep working on this exercise, -or jump into the next one by removing the {} comment:\n", - "`I AM NOT DONE`".bold() - ); - - for context_line in context { - let formatted_line = if context_line.important { - format!("{}", context_line.line.bold()) - } else { - context_line.line - }; - - println!( - "{:>2} {} {}", - ContentStyle { - foreground_color: Some(crossterm::style::Color::Blue), - background_color: None, - underline_color: None, - attributes: Attribute::Bold.into() - } - .apply(context_line.number), - "|".blue(), - formatted_line, - ); - } - return Ok(VerifyState::Failed(exercise)); - } - - current_exercise_ind += 1; - } - - Ok(VerifyState::AllExercisesDone) -} diff --git a/src/watch.rs b/src/watch.rs index b29169b3..929275f1 100644 --- a/src/watch.rs +++ b/src/watch.rs @@ -15,7 +15,7 @@ mod debounce_event; mod state; mod terminal_event; -use crate::{exercise::Exercise, state_file::StateFile}; +use crate::app_state::AppState; use self::{ debounce_event::DebounceEventHandler, @@ -39,23 +39,23 @@ pub enum WatchExit { List, } -pub fn watch(state_file: &mut StateFile, exercises: &'static [Exercise]) -> Result { +pub fn watch(app_state: &mut AppState) -> Result { let (tx, rx) = channel(); let mut debouncer = new_debouncer( Duration::from_secs(1), DebounceEventHandler { tx: tx.clone(), - exercises, + exercises: app_state.exercises(), }, )?; debouncer .watcher() .watch(Path::new("exercises"), RecursiveMode::Recursive)?; - let mut watch_state = WatchState::new(state_file, exercises); + let mut watch_state = WatchState::new(app_state); // TODO: bool - watch_state.run_exercise()?; + watch_state.run_current_exercise()?; watch_state.render()?; thread::spawn(move || terminal_event_handler(tx)); diff --git a/src/watch/state.rs b/src/watch/state.rs index 6f6d2f10..a7647d8d 100644 --- a/src/watch/state.rs +++ b/src/watch/state.rs @@ -1,26 +1,16 @@ -use anyhow::{Context, Result}; +use anyhow::Result; use crossterm::{ - style::{Attribute, ContentStyle, Stylize}, + style::Stylize, terminal::{size, Clear, ClearType}, ExecutableCommand, }; -use std::{ - fmt::Write as _, - io::{self, StdoutLock, Write}, -}; +use std::io::{self, StdoutLock, Write}; -use crate::{ - exercise::{Exercise, State}, - progress_bar::progress_bar, - state_file::StateFile, -}; +use crate::{app_state::AppState, progress_bar::progress_bar}; pub struct WatchState<'a> { writer: StdoutLock<'a>, - exercises: &'static [Exercise], - exercise: &'static Exercise, - current_exercise_ind: usize, - progress: u16, + app_state: &'a mut AppState, stdout: Option>, stderr: Option>, message: Option, @@ -28,19 +18,12 @@ pub struct WatchState<'a> { } impl<'a> WatchState<'a> { - pub fn new(state_file: &StateFile, exercises: &'static [Exercise]) -> Self { - let current_exercise_ind = state_file.next_exercise_ind(); - let progress = state_file.progress().iter().filter(|done| **done).count() as u16; - let exercise = &exercises[current_exercise_ind]; - + pub fn new(app_state: &'a mut AppState) -> Self { let writer = io::stdout().lock(); Self { writer, - exercises, - exercise, - current_exercise_ind, - progress, + app_state, stdout: None, stderr: None, message: None, @@ -53,8 +36,8 @@ impl<'a> WatchState<'a> { self.writer } - pub fn run_exercise(&mut self) -> Result { - let output = self.exercise.run()?; + pub fn run_current_exercise(&mut self) -> Result { + let output = self.app_state.current_exercise().run()?; self.stdout = Some(output.stdout); if !output.status.success() { @@ -64,55 +47,15 @@ impl<'a> WatchState<'a> { self.stderr = None; - if let State::Pending(context) = self.exercise.state()? { - let mut message = format!( - " -You can keep working on this exercise or jump into the next one by removing the {} comment: - -", - "`I AM NOT DONE`".bold(), - ); - - for context_line in context { - let formatted_line = if context_line.important { - context_line.line.bold() - } else { - context_line.line.stylize() - }; - - writeln!( - message, - "{:>2} {} {}", - ContentStyle { - foreground_color: Some(crossterm::style::Color::Blue), - background_color: None, - underline_color: None, - attributes: Attribute::Bold.into() - } - .apply(context_line.number), - "|".blue(), - formatted_line, - )?; - } - - self.message = Some(message); - return Ok(false); - } - Ok(true) } pub fn run_exercise_with_ind(&mut self, exercise_ind: usize) -> Result { - self.exercise = self - .exercises - .get(exercise_ind) - .context("Invalid exercise index")?; - self.current_exercise_ind = exercise_ind; - - self.run_exercise() + self.app_state.set_current_exercise_ind(exercise_ind)?; + self.run_current_exercise() } - pub fn show_prompt(&mut self) -> io::Result<()> { + fn show_prompt(&mut self) -> io::Result<()> { self.writer.write_all(b"\n\n")?; if !self.hint_displayed { @@ -150,18 +93,27 @@ You can keep working on this exercise or jump into the next one by removing the if self.hint_displayed { self.writer .write_fmt(format_args!("\n{}\n", "Hint".bold().cyan().underlined()))?; - self.writer.write_all(self.exercise.hint.as_bytes())?; + self.writer + .write_all(self.app_state.current_exercise().hint.as_bytes())?; self.writer.write_all(b"\n\n")?; } let line_width = size()?.0; - let progress_bar = progress_bar(self.progress, self.exercises.len() as u16, line_width)?; + let progress_bar = progress_bar( + self.app_state.n_done(), + self.app_state.exercises().len() as u16, + line_width, + )?; self.writer.write_all(progress_bar.as_bytes())?; self.writer.write_all(b"Current exercise: ")?; self.writer.write_fmt(format_args!( "{}", - self.exercise.path.to_string_lossy().bold() + self.app_state + .current_exercise() + .path + .to_string_lossy() + .bold(), ))?; self.show_prompt()?; diff --git a/tests/fixture/state/exercises/pending_exercise.rs b/tests/fixture/state/exercises/pending_exercise.rs index f579d0b4..016b827c 100644 --- a/tests/fixture/state/exercises/pending_exercise.rs +++ b/tests/fixture/state/exercises/pending_exercise.rs @@ -1,7 +1,5 @@ // fake_exercise -// I AM NOT DONE - fn main() { } diff --git a/tests/fixture/state/exercises/pending_test_exercise.rs b/tests/fixture/state/exercises/pending_test_exercise.rs index 8756f02d..2002ef17 100644 --- a/tests/fixture/state/exercises/pending_test_exercise.rs +++ b/tests/fixture/state/exercises/pending_test_exercise.rs @@ -1,4 +1,2 @@ -// I AM NOT DONE - #[test] fn it_works() {} diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index f8f4383f..51cdefb8 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -1,7 +1,6 @@ use assert_cmd::prelude::*; -use glob::glob; use predicates::boolean::PredicateBooleanExt; -use std::{fs::File, io::Read, process::Command}; +use std::process::Command; #[test] fn fails_when_in_wrong_dir() { @@ -137,31 +136,6 @@ fn get_hint_for_single_test() { .stdout("Hello!\n"); } -#[test] -fn all_exercises_require_confirmation() { - for exercise in glob("exercises/**/*.rs").unwrap() { - let path = exercise.unwrap(); - if path.file_name().unwrap() == "mod.rs" { - continue; - } - let source = { - let mut file = File::open(&path).unwrap(); - let mut s = String::new(); - file.read_to_string(&mut s).unwrap(); - s - }; - source - .matches("// I AM NOT DONE") - .next() - .unwrap_or_else(|| { - panic!( - "There should be an `I AM NOT DONE` annotation in {:?}", - path - ) - }); - } -} - #[test] fn run_compile_exercise_does_not_prompt() { Command::cargo_bin("rustlings")