From 2cdd61294f0d9a53775ee24ad76435bec8a21e60 Mon Sep 17 00:00:00 2001 From: Roberto Vidal Date: Mon, 11 Nov 2019 13:38:24 +0100 Subject: [PATCH] feat: improve `watch` execution mode The `watch` command now requires user action to move to the next exercise. BREAKING CHANGE: this changes the behavior of `watch`. --- Cargo.lock | 8 ++ Cargo.toml | 2 + exercises/enums/enums1.rs | 2 + exercises/enums/enums2.rs | 2 + exercises/enums/enums3.rs | 2 + exercises/error_handling/errors1.rs | 2 + exercises/error_handling/errors2.rs | 2 + exercises/error_handling/errors3.rs | 2 + exercises/error_handling/errorsn.rs | 2 + exercises/error_handling/option1.rs | 2 + exercises/error_handling/result1.rs | 2 + exercises/functions/functions1.rs | 2 + exercises/functions/functions2.rs | 2 + exercises/functions/functions3.rs | 2 + exercises/functions/functions4.rs | 2 + exercises/functions/functions5.rs | 2 + exercises/if/if1.rs | 2 + exercises/macros/macros1.rs | 2 + exercises/macros/macros2.rs | 2 + exercises/macros/macros3.rs | 2 + exercises/macros/macros4.rs | 2 + exercises/modules/modules1.rs | 2 + exercises/modules/modules2.rs | 2 + exercises/move_semantics/move_semantics1.rs | 2 + exercises/move_semantics/move_semantics2.rs | 2 + exercises/move_semantics/move_semantics3.rs | 2 + exercises/move_semantics/move_semantics4.rs | 2 + exercises/primitive_types/primitive_types1.rs | 2 + exercises/primitive_types/primitive_types2.rs | 2 + exercises/primitive_types/primitive_types3.rs | 2 + exercises/primitive_types/primitive_types4.rs | 2 + exercises/primitive_types/primitive_types5.rs | 2 + exercises/primitive_types/primitive_types6.rs | 2 + exercises/standard_library_types/arc1.rs | 2 + .../standard_library_types/iterators2.rs | 2 + .../standard_library_types/iterators3.rs | 2 + .../standard_library_types/iterators4.rs | 2 + exercises/strings/strings1.rs | 2 + exercises/strings/strings2.rs | 2 + exercises/structs/structs1.rs | 2 + exercises/structs/structs2.rs | 2 + exercises/test1.rs | 2 + exercises/test2.rs | 2 + exercises/test3.rs | 2 + exercises/test4.rs | 2 + exercises/tests/tests1.rs | 2 + exercises/tests/tests2.rs | 2 + exercises/tests/tests3.rs | 2 + exercises/threads/threads1.rs | 2 + exercises/variables/variables1.rs | 7 ++ exercises/variables/variables2.rs | 2 + exercises/variables/variables3.rs | 2 + exercises/variables/variables4.rs | 2 + src/exercise.rs | 113 +++++++++++++++++- src/main.rs | 4 +- src/run.rs | 4 +- src/verify.rs | 56 ++++++++- tests/fixture/state/finished_exercise.rs | 0 tests/fixture/state/pending_exercise.rs | 7 ++ tests/integration_tests.rs | 20 ++++ 60 files changed, 309 insertions(+), 12 deletions(-) create mode 100644 tests/fixture/state/finished_exercise.rs create mode 100644 tests/fixture/state/pending_exercise.rs diff --git a/Cargo.lock b/Cargo.lock index c88baa6..19572d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -204,6 +204,11 @@ name = "fuchsia-zircon-sys" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "indicatif" version = "0.9.0" @@ -605,8 +610,10 @@ dependencies = [ "assert_cmd 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "console 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "indicatif 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "notify 4.0.12 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -848,6 +855,7 @@ dependencies = [ "checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" +"checksum glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" "checksum indicatif 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a29b2fa6f00010c268bface64c18bb0310aaa70d46a195d5382d288c477fb016" "checksum inotify 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "40b54539f3910d6f84fbf9a643efd6e3aa6e4f001426c0329576128255994718" "checksum inotify-sys 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e74a1aa87c59aeff6ef2cc2fa62d41bc43f54952f55652656b18a02fd5e356c0" diff --git a/Cargo.toml b/Cargo.toml index ab6ee81..7cbe2a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ indicatif = "0.9.0" console = "0.6.2" notify = "4.0.0" toml = "0.4.10" +regex = "1.1.6" serde = {version = "1.0.10", features = ["derive"]} [[bin]] @@ -18,3 +19,4 @@ path = "src/main.rs" [dev-dependencies] assert_cmd = "0.11.0" +glob = "0.3.0" diff --git a/exercises/enums/enums1.rs b/exercises/enums/enums1.rs index 8b14f29..f490928 100644 --- a/exercises/enums/enums1.rs +++ b/exercises/enums/enums1.rs @@ -1,6 +1,8 @@ // enums1.rs // Make me compile! Scroll down for hints! +// I AM NOT DONE + #[derive(Debug)] enum Message { // TODO: define a few types of messages as used below diff --git a/exercises/enums/enums2.rs b/exercises/enums/enums2.rs index 71ac839..8f04bd8 100644 --- a/exercises/enums/enums2.rs +++ b/exercises/enums/enums2.rs @@ -1,6 +1,8 @@ // enums2.rs // Make me compile! Scroll down for hints +// I AM NOT DONE + #[derive(Debug)] enum Message { // TODO: define the different variants used below diff --git a/exercises/enums/enums3.rs b/exercises/enums/enums3.rs index c5d81bf..bb7dfb4 100644 --- a/exercises/enums/enums3.rs +++ b/exercises/enums/enums3.rs @@ -1,6 +1,8 @@ // enums3.rs // Address all the TODOs to make the tests pass! +// I AM NOT DONE + enum Message { // TODO: implement the message variant types based on their usage below } diff --git a/exercises/error_handling/errors1.rs b/exercises/error_handling/errors1.rs index 8483234..a06f0ca 100644 --- a/exercises/error_handling/errors1.rs +++ b/exercises/error_handling/errors1.rs @@ -6,6 +6,8 @@ // this function to have. // Scroll down for hints!!! +// I AM NOT DONE + pub fn generate_nametag_text(name: String) -> Option { if name.len() > 0 { Some(format!("Hi! My name is {}", name)) diff --git a/exercises/error_handling/errors2.rs b/exercises/error_handling/errors2.rs index 8b81207..315528f 100644 --- a/exercises/error_handling/errors2.rs +++ b/exercises/error_handling/errors2.rs @@ -16,6 +16,8 @@ // There are at least two ways to implement this that are both correct-- but // one is a lot shorter! Scroll down for hints to both ways. +// I AM NOT DONE + use std::num::ParseIntError; pub fn total_cost(item_quantity: &str) -> Result { diff --git a/exercises/error_handling/errors3.rs b/exercises/error_handling/errors3.rs index 31800fc..35cd5c3 100644 --- a/exercises/error_handling/errors3.rs +++ b/exercises/error_handling/errors3.rs @@ -3,6 +3,8 @@ // `total_cost` function from the previous exercise. It's not working though! // Why not? What should we do to fix it? Scroll for hints! +// I AM NOT DONE + use std::num::ParseIntError; fn main() { diff --git a/exercises/error_handling/errorsn.rs b/exercises/error_handling/errorsn.rs index c2b16ce..cc90801 100644 --- a/exercises/error_handling/errorsn.rs +++ b/exercises/error_handling/errorsn.rs @@ -15,6 +15,8 @@ // // Scroll down for hints :) +// I AM NOT DONE + use std::error; use std::fmt; use std::io; diff --git a/exercises/error_handling/option1.rs b/exercises/error_handling/option1.rs index c5a4a64..4e432a6 100644 --- a/exercises/error_handling/option1.rs +++ b/exercises/error_handling/option1.rs @@ -4,6 +4,8 @@ // on `None`. Handle this in a more graceful way than calling `unwrap`! // Scroll down for hints :) +// I AM NOT DONE + pub fn pop_too_much() -> bool { let mut list = vec![3]; diff --git a/exercises/error_handling/result1.rs b/exercises/error_handling/result1.rs index f9596e2..52972dc 100644 --- a/exercises/error_handling/result1.rs +++ b/exercises/error_handling/result1.rs @@ -1,6 +1,8 @@ // result1.rs // Make this test pass! Scroll down for hints :) +// I AM NOT DONE + #[derive(PartialEq, Debug)] struct PositiveNonzeroInteger(u64); diff --git a/exercises/functions/functions1.rs b/exercises/functions/functions1.rs index 396dd56..b9312ae 100644 --- a/exercises/functions/functions1.rs +++ b/exercises/functions/functions1.rs @@ -1,6 +1,8 @@ // functions1.rs // Make me compile! Scroll down for hints :) +// I AM NOT DONE + fn main() { call_me(); } diff --git a/exercises/functions/functions2.rs b/exercises/functions/functions2.rs index 1cf95c3..c5b4418 100644 --- a/exercises/functions/functions2.rs +++ b/exercises/functions/functions2.rs @@ -1,6 +1,8 @@ // functions2.rs // Make me compile! Scroll down for hints :) +// I AM NOT DONE + fn main() { call_me(3); } diff --git a/exercises/functions/functions3.rs b/exercises/functions/functions3.rs index b17543b..63bb586 100644 --- a/exercises/functions/functions3.rs +++ b/exercises/functions/functions3.rs @@ -1,6 +1,8 @@ // functions3.rs // Make me compile! Scroll down for hints :) +// I AM NOT DONE + fn main() { call_me(); } diff --git a/exercises/functions/functions4.rs b/exercises/functions/functions4.rs index 5baca0e..91f6295 100644 --- a/exercises/functions/functions4.rs +++ b/exercises/functions/functions4.rs @@ -4,6 +4,8 @@ // This store is having a sale where if the price is an even number, you get // 10 (money unit) off, but if it's an odd number, it's 3 (money unit) less. +// I AM NOT DONE + fn main() { let original_price = 51; println!("Your sale price is {}", sale_price(original_price)); diff --git a/exercises/functions/functions5.rs b/exercises/functions/functions5.rs index d9227c9..696edee 100644 --- a/exercises/functions/functions5.rs +++ b/exercises/functions/functions5.rs @@ -1,6 +1,8 @@ // functions5.rs // Make me compile! Scroll down for hints :) +// I AM NOT DONE + fn main() { let answer = square(3); println!("The answer is {}", answer); diff --git a/exercises/if/if1.rs b/exercises/if/if1.rs index 6da09d3..da77e35 100644 --- a/exercises/if/if1.rs +++ b/exercises/if/if1.rs @@ -1,5 +1,7 @@ // if1.rs +// I AM NOT DONE + pub fn bigger(a: i32, b: i32) -> i32 { // Complete this function to return the bigger number! // Do not use: diff --git a/exercises/macros/macros1.rs b/exercises/macros/macros1.rs index a7c78a5..f7b89e0 100644 --- a/exercises/macros/macros1.rs +++ b/exercises/macros/macros1.rs @@ -1,6 +1,8 @@ // macros1.rs // Make me compile! Scroll down for hints :) +// I AM NOT DONE + macro_rules! my_macro { () => { println!("Check out my macro!"); diff --git a/exercises/macros/macros2.rs b/exercises/macros/macros2.rs index bc2e56b..7fe9429 100644 --- a/exercises/macros/macros2.rs +++ b/exercises/macros/macros2.rs @@ -1,6 +1,8 @@ // macros2.rs // Make me compile! Scroll down for hints :) +// I AM NOT DONE + fn main() { my_macro!(); } diff --git a/exercises/macros/macros3.rs b/exercises/macros/macros3.rs index 84c4308..e9ef7f3 100644 --- a/exercises/macros/macros3.rs +++ b/exercises/macros/macros3.rs @@ -1,6 +1,8 @@ // macros3.rs // Make me compile, without taking the macro out of the module! Scroll down for hints :) +// I AM NOT DONE + mod macros { macro_rules! my_macro { () => { diff --git a/exercises/macros/macros4.rs b/exercises/macros/macros4.rs index d844bb0..2893a15 100644 --- a/exercises/macros/macros4.rs +++ b/exercises/macros/macros4.rs @@ -1,6 +1,8 @@ // macros4.rs // Make me compile! Scroll down for hints :) +// I AM NOT DONE + macro_rules! my_macro { () => { println!("Check out my macro!"); diff --git a/exercises/modules/modules1.rs b/exercises/modules/modules1.rs index 0e092c5..3f44968 100644 --- a/exercises/modules/modules1.rs +++ b/exercises/modules/modules1.rs @@ -1,6 +1,8 @@ // modules1.rs // Make me compile! Scroll down for hints :) +// I AM NOT DONE + mod sausage_factory { fn make_sausage() { println!("sausage!"); diff --git a/exercises/modules/modules2.rs b/exercises/modules/modules2.rs index 3cfa36d..a3497cf 100644 --- a/exercises/modules/modules2.rs +++ b/exercises/modules/modules2.rs @@ -1,6 +1,8 @@ // modules2.rs // Make me compile! Scroll down for hints :) +// I AM NOT DONE + mod delicious_snacks { use self::fruits::PEAR as fruit; use self::veggies::CUCUMBER as veggie; diff --git a/exercises/move_semantics/move_semantics1.rs b/exercises/move_semantics/move_semantics1.rs index ab855fc..625096d 100644 --- a/exercises/move_semantics/move_semantics1.rs +++ b/exercises/move_semantics/move_semantics1.rs @@ -1,6 +1,8 @@ // move_semantics1.rs // Make me compile! Scroll down for hints :) +// I AM NOT DONE + fn main() { let vec0 = Vec::new(); diff --git a/exercises/move_semantics/move_semantics2.rs b/exercises/move_semantics/move_semantics2.rs index f85b3ed..28749e2 100644 --- a/exercises/move_semantics/move_semantics2.rs +++ b/exercises/move_semantics/move_semantics2.rs @@ -1,6 +1,8 @@ // move_semantics2.rs // Make me compile without changing line 10! Scroll down for hints :) +// I AM NOT DONE + fn main() { let vec0 = Vec::new(); diff --git a/exercises/move_semantics/move_semantics3.rs b/exercises/move_semantics/move_semantics3.rs index 8b91896..a5b41b4 100644 --- a/exercises/move_semantics/move_semantics3.rs +++ b/exercises/move_semantics/move_semantics3.rs @@ -3,6 +3,8 @@ // (no lines with multiple semicolons necessary!) // Scroll down for hints :) +// I AM NOT DONE + fn main() { let vec0 = Vec::new(); diff --git a/exercises/move_semantics/move_semantics4.rs b/exercises/move_semantics/move_semantics4.rs index 90930f0..b01e593 100644 --- a/exercises/move_semantics/move_semantics4.rs +++ b/exercises/move_semantics/move_semantics4.rs @@ -3,6 +3,8 @@ // in `fn main`, we instead create it within `fn fill_vec` and transfer the // freshly created vector from fill_vec to its caller. Scroll for hints! +// I AM NOT DONE + fn main() { let vec0 = Vec::new(); diff --git a/exercises/primitive_types/primitive_types1.rs b/exercises/primitive_types/primitive_types1.rs index c3d11fe..0912139 100644 --- a/exercises/primitive_types/primitive_types1.rs +++ b/exercises/primitive_types/primitive_types1.rs @@ -2,6 +2,8 @@ // 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/primitive_types/primitive_types2.rs b/exercises/primitive_types/primitive_types2.rs index f5c8f87..6576a4d 100644 --- a/exercises/primitive_types/primitive_types2.rs +++ b/exercises/primitive_types/primitive_types2.rs @@ -2,6 +2,8 @@ // 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/primitive_types/primitive_types3.rs b/exercises/primitive_types/primitive_types3.rs index 7ce2226..bf2716a 100644 --- a/exercises/primitive_types/primitive_types3.rs +++ b/exercises/primitive_types/primitive_types3.rs @@ -2,6 +2,8 @@ // Create an array with at least 100 elements in it where the ??? is. // Scroll down for hints! +// I AM NOT DONE + fn main() { let a = ??? diff --git a/exercises/primitive_types/primitive_types4.rs b/exercises/primitive_types/primitive_types4.rs index e1ccdbc..e675aa1 100644 --- a/exercises/primitive_types/primitive_types4.rs +++ b/exercises/primitive_types/primitive_types4.rs @@ -2,6 +2,8 @@ // Get a slice out of Array a where the ??? is so that the `if` statement // returns true. Scroll down for hints!! +// I AM NOT DONE + #[test] fn main() { let a = [1, 2, 3, 4, 5]; diff --git a/exercises/primitive_types/primitive_types5.rs b/exercises/primitive_types/primitive_types5.rs index 0d53c9c..d8527ee 100644 --- a/exercises/primitive_types/primitive_types5.rs +++ b/exercises/primitive_types/primitive_types5.rs @@ -2,6 +2,8 @@ // Destructure the `cat` tuple so that the println will work. // Scroll down for hints! +// I AM NOT DONE + fn main() { let cat = ("Furry McFurson", 3.5); let /* your pattern here */ = cat; diff --git a/exercises/primitive_types/primitive_types6.rs b/exercises/primitive_types/primitive_types6.rs index 854544f..8fe9a36 100644 --- a/exercises/primitive_types/primitive_types6.rs +++ b/exercises/primitive_types/primitive_types6.rs @@ -3,6 +3,8 @@ // You can put this right into the `println!` where the ??? is. // Scroll down for hints! +// I AM NOT DONE + fn main() { let numbers = (1, 2, 3); println!("The second number is {}", ???); diff --git a/exercises/standard_library_types/arc1.rs b/exercises/standard_library_types/arc1.rs index d610e5f..effd28a 100644 --- a/exercises/standard_library_types/arc1.rs +++ b/exercises/standard_library_types/arc1.rs @@ -4,6 +4,8 @@ // somewhere. Try not to create any copies of the `numbers` Vec! // Scroll down for hints :) +// I AM NOT DONE + use std::sync::Arc; use std::thread; diff --git a/exercises/standard_library_types/iterators2.rs b/exercises/standard_library_types/iterators2.rs index b6d3366..ea523e7 100644 --- a/exercises/standard_library_types/iterators2.rs +++ b/exercises/standard_library_types/iterators2.rs @@ -5,6 +5,8 @@ // Step 3. Apply the `capitalize_first` function again to a list, but try and ensure it returns a single string // As always, there are hints below! +// I AM NOT DONE + pub fn capitalize_first(input: &str) -> String { let mut c = input.chars(); match c.next() { diff --git a/exercises/standard_library_types/iterators3.rs b/exercises/standard_library_types/iterators3.rs index c012795..8fc57f2 100644 --- a/exercises/standard_library_types/iterators3.rs +++ b/exercises/standard_library_types/iterators3.rs @@ -8,6 +8,8 @@ // a major hint. // Have fun :-) +// I AM NOT DONE + #[derive(Debug, PartialEq, Eq)] pub enum DivisionError { NotDivisible(NotDivisibleError), diff --git a/exercises/standard_library_types/iterators4.rs b/exercises/standard_library_types/iterators4.rs index 13613a6..39be8bf 100644 --- a/exercises/standard_library_types/iterators4.rs +++ b/exercises/standard_library_types/iterators4.rs @@ -1,5 +1,7 @@ // iterators4.rs +// I AM NOT DONE + pub fn factorial(num: u64) -> u64 { // Complete this function to return factorial of num // Do not use: diff --git a/exercises/strings/strings1.rs b/exercises/strings/strings1.rs index 2e5088f..3335734 100644 --- a/exercises/strings/strings1.rs +++ b/exercises/strings/strings1.rs @@ -1,6 +1,8 @@ // strings1.rs // Make me compile without changing the function signature! Scroll down for hints :) +// I AM NOT DONE + fn main() { let answer = current_favorite_color(); println!("My current favorite color is {}", answer); diff --git a/exercises/strings/strings2.rs b/exercises/strings/strings2.rs index c77e16f..3890cfe 100644 --- a/exercises/strings/strings2.rs +++ b/exercises/strings/strings2.rs @@ -1,6 +1,8 @@ // strings2.rs // Make me compile without changing the function signature! Scroll down for hints :) +// 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/structs/structs1.rs b/exercises/structs/structs1.rs index 138b3fd..6d0b2f4 100644 --- a/exercises/structs/structs1.rs +++ b/exercises/structs/structs1.rs @@ -1,6 +1,8 @@ // structs1.rs // Address all the TODOs to make the tests pass! +// I AM NOT DONE + struct ColorClassicStruct { // TODO: Something goes here } diff --git a/exercises/structs/structs2.rs b/exercises/structs/structs2.rs index db381e7..0699137 100644 --- a/exercises/structs/structs2.rs +++ b/exercises/structs/structs2.rs @@ -2,6 +2,8 @@ // Address all the TODOs to make the tests pass! // No hints, just do it! +// I AM NOT DONE + #[derive(Debug)] struct Order { name: String, diff --git a/exercises/test1.rs b/exercises/test1.rs index 6c27355..3e13ce5 100644 --- a/exercises/test1.rs +++ b/exercises/test1.rs @@ -7,6 +7,8 @@ // more than 40 at once, each apple only costs 1! Write a function that calculates // the price of an order of apples given the order amount. No hints this time! +// I AM NOT DONE + // Put your function here! // fn ..... { diff --git a/exercises/test2.rs b/exercises/test2.rs index 7fe81c6..d01606c 100644 --- a/exercises/test2.rs +++ b/exercises/test2.rs @@ -7,6 +7,8 @@ // you think each value is. That is, add either `string_slice` or `string` // before the parentheses on each line. If you're right, it will compile! +// I AM NOT DONE + fn string_slice(arg: &str) { println!("{}", arg); } diff --git a/exercises/test3.rs b/exercises/test3.rs index 9a72118..f94c36f 100644 --- a/exercises/test3.rs +++ b/exercises/test3.rs @@ -7,6 +7,8 @@ // we expect to get when we call `times_two` with a negative number. // No hints, you can do this :) +// I AM NOT DONE + pub fn times_two(num: i32) -> i32 { num * 2 } diff --git a/exercises/test4.rs b/exercises/test4.rs index e50f1b0..c543a64 100644 --- a/exercises/test4.rs +++ b/exercises/test4.rs @@ -5,6 +5,8 @@ // Write a macro that passes the test! No hints this time, you can do it! +// I AM NOT DONE + fn main() { if my_macro!("world!") != "Hello world!" { panic!("Oh no! Wrong output!"); diff --git a/exercises/tests/tests1.rs b/exercises/tests/tests1.rs index b11221f..1465e37 100644 --- a/exercises/tests/tests1.rs +++ b/exercises/tests/tests1.rs @@ -6,6 +6,8 @@ // This test has a problem with it -- make the test compile! Make the test // pass! Make the test fail! Scroll down for hints :) +// I AM NOT DONE + #[cfg(test)] mod tests { #[test] diff --git a/exercises/tests/tests2.rs b/exercises/tests/tests2.rs index 6775d61..81e30e0 100644 --- a/exercises/tests/tests2.rs +++ b/exercises/tests/tests2.rs @@ -2,6 +2,8 @@ // This test has a problem with it -- make the test compile! Make the test // pass! Make the test fail! Scroll down for hints :) +// I AM NOT DONE + #[cfg(test)] mod tests { #[test] diff --git a/exercises/tests/tests3.rs b/exercises/tests/tests3.rs index e10d2aa..af48095 100644 --- a/exercises/tests/tests3.rs +++ b/exercises/tests/tests3.rs @@ -3,6 +3,8 @@ // the test passes. Then write a second test that tests whether we get the result // we expect to get when we call `is_even(5)`. Scroll down for hints! +// I AM NOT DONE + pub fn is_even(num: i32) -> bool { num % 2 == 0 } diff --git a/exercises/threads/threads1.rs b/exercises/threads/threads1.rs index 7983668..d23553e 100644 --- a/exercises/threads/threads1.rs +++ b/exercises/threads/threads1.rs @@ -5,6 +5,8 @@ // of "waiting..." and the program ends without timing out the playground, // you've got it :) +// I AM NOT DONE + use std::sync::Arc; use std::thread; use std::time::Duration; diff --git a/exercises/variables/variables1.rs b/exercises/variables/variables1.rs index 1cdd270..174ad80 100644 --- a/exercises/variables/variables1.rs +++ b/exercises/variables/variables1.rs @@ -1,6 +1,13 @@ // variables1.rs // Make me compile! Scroll down for hints :) +// About this `I AM NOT DONE` thing: +// 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, you the `I AM NOT DONE` comment below. + +// I AM NOT DONE + fn main() { x = 5; println!("x has the value {}", x); diff --git a/exercises/variables/variables2.rs b/exercises/variables/variables2.rs index a0b4a37..e3c3b76 100644 --- a/exercises/variables/variables2.rs +++ b/exercises/variables/variables2.rs @@ -1,6 +1,8 @@ // variables2.rs // Make me compile! Scroll down for hints :) +// I AM NOT DONE + fn main() { let x; if x == 10 { diff --git a/exercises/variables/variables3.rs b/exercises/variables/variables3.rs index 165a277..7e3fd93 100644 --- a/exercises/variables/variables3.rs +++ b/exercises/variables/variables3.rs @@ -1,6 +1,8 @@ // variables3.rs // Make me compile! Scroll down for hints :) +// I AM NOT DONE + fn main() { let x = 3; println!("Number {}", x); diff --git a/exercises/variables/variables4.rs b/exercises/variables/variables4.rs index 71ebf0f..33eca9e 100644 --- a/exercises/variables/variables4.rs +++ b/exercises/variables/variables4.rs @@ -1,6 +1,8 @@ // variables4.rs // Make me compile! Scroll down for hints :) +// I AM NOT DONE + fn main() { let x: i32; println!("Number {}", x); diff --git a/src/exercise.rs b/src/exercise.rs index 6f526e7..f27b545 100644 --- a/src/exercise.rs +++ b/src/exercise.rs @@ -1,16 +1,20 @@ +use regex::Regex; use serde::Deserialize; use std::fmt::{self, Display, Formatter}; -use std::fs::remove_file; +use std::fs::{remove_file, File}; +use std::io::Read; use std::path::PathBuf; use std::process::{self, Command, Output}; const RUSTC_COLOR_ARGS: &[&str] = &["--color", "always"]; +const I_AM_DONE_REGEX: &str = r"(?m)^\s*///?\s*I\s+AM\s+NOT\s+DONE"; +const CONTEXT: usize = 2; fn temp_file() -> String { format!("./temp_{}", process::id()) } -#[derive(Deserialize)] +#[derive(Deserialize, Copy, Clone)] #[serde(rename_all = "lowercase")] pub enum Mode { Compile, @@ -28,6 +32,19 @@ pub struct Exercise { pub mode: Mode, } +#[derive(PartialEq, Debug)] +pub enum State { + Done, + Pending(Vec), +} + +#[derive(PartialEq, Debug)] +pub struct ContextLine { + pub line: String, + pub number: usize, + pub important: bool, +} + impl Exercise { pub fn compile(&self) -> Output { match self.mode { @@ -52,6 +69,48 @@ impl Exercise { pub fn clean(&self) { let _ignored = remove_file(&temp_file()); } + + pub fn state(&self) -> State { + let mut source_file = + File::open(&self.path).expect("We were unable to open the exercise file!"); + + let source = { + let mut s = String::new(); + source_file + .read_to_string(&mut s) + .expect("We were unable to read the exercise file!"); + s + }; + + let re = Regex::new(I_AM_DONE_REGEX).unwrap(); + + if !re.is_match(&source) { + return State::Done; + } + + let matched_line_index = source + .lines() + .enumerate() + .filter_map(|(i, line)| if re.is_match(line) { Some(i) } else { None }) + .next() + .expect("This should not happen at all"); + + let min_line = ((matched_line_index as i32) - (CONTEXT as i32)).max(0) as usize; + let max_line = matched_line_index + CONTEXT; + + let context = source + .lines() + .enumerate() + .filter(|&(i, _)| i >= min_line && i <= max_line) + .map(|(i, line)| ContextLine { + line: line.to_string(), + number: i + 1, + important: i == matched_line_index, + }) + .collect(); + + State::Pending(context) + } } impl Display for Exercise { @@ -63,7 +122,6 @@ impl Display for Exercise { #[cfg(test)] mod test { use super::*; - use std::fs::File; use std::path::Path; #[test] @@ -76,4 +134,53 @@ mod test { exercise.clean(); assert!(!Path::new(&temp_file()).exists()); } + + #[test] + fn test_pending_state() { + let exercise = Exercise { + path: PathBuf::from("tests/fixture/state/pending_exercise.rs"), + mode: Mode::Compile, + }; + + 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, State::Pending(expected)); + } + + #[test] + fn test_finished_exercise() { + let exercise = Exercise { + path: PathBuf::from("tests/fixture/state/finished_exercise.rs"), + mode: Mode::Compile, + }; + + assert_eq!(exercise.state(), State::Done); + } } diff --git a/src/main.rs b/src/main.rs index aad4cff..e42dff4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -110,11 +110,11 @@ fn watch(exercises: &[Exercise]) -> notify::Result<()> { DebouncedEvent::Create(b) | DebouncedEvent::Chmod(b) | DebouncedEvent::Write(b) => { if b.extension() == Some(OsStr::new("rs")) && b.exists() { let filepath = b.as_path().canonicalize().unwrap(); - let exercise = exercises + let pending_exercises = exercises .iter() .skip_while(|e| !filepath.ends_with(&e.path)); clear_screen(); - let _ignored = verify(exercise); + let _ignored = verify(pending_exercises); } } _ => {} diff --git a/src/run.rs b/src/run.rs index 1484351..1f777ec 100644 --- a/src/run.rs +++ b/src/run.rs @@ -5,7 +5,9 @@ use indicatif::ProgressBar; pub fn run(exercise: &Exercise) -> Result<(), ()> { match exercise.mode { - Mode::Test => test(exercise)?, + Mode::Test => { + test(exercise)?; + } Mode::Compile => compile_and_run(exercise)?, } Ok(()) diff --git a/src/verify.rs b/src/verify.rs index d066afa..020102e 100644 --- a/src/verify.rs +++ b/src/verify.rs @@ -1,18 +1,21 @@ -use crate::exercise::{Exercise, Mode}; +use crate::exercise::{ContextLine, Exercise, Mode, State}; use console::{style, Emoji}; use indicatif::ProgressBar; pub fn verify<'a>(start_at: impl IntoIterator) -> Result<(), ()> { for exercise in start_at { - match exercise.mode { + let is_done = match exercise.mode { Mode::Test => test(&exercise)?, Mode::Compile => compile_only(&exercise)?, + }; + if !is_done { + return Err(()); } } Ok(()) } -fn compile_only(exercise: &Exercise) -> Result<(), ()> { +fn compile_only(exercise: &Exercise) -> Result { let progress_bar = ProgressBar::new_spinner(); progress_bar.set_message(format!("Compiling {}...", exercise).as_str()); progress_bar.enable_steady_tick(100); @@ -22,7 +25,12 @@ fn compile_only(exercise: &Exercise) -> Result<(), ()> { let formatstr = format!("{} Successfully compiled {}!", Emoji("✅", "✓"), exercise); println!("{}", style(formatstr).green()); exercise.clean(); - Ok(()) + if let State::Pending(context) = exercise.state() { + print_everything_looks_good(exercise.mode, context); + Ok(false) + } else { + Ok(true) + } } else { let formatstr = format!( "{} Compilation of {} failed! Compiler error message:\n", @@ -36,7 +44,7 @@ fn compile_only(exercise: &Exercise) -> Result<(), ()> { } } -pub fn test(exercise: &Exercise) -> Result<(), ()> { +pub fn test(exercise: &Exercise) -> Result { let progress_bar = ProgressBar::new_spinner(); progress_bar.set_message(format!("Testing {}...", exercise).as_str()); progress_bar.enable_steady_tick(100); @@ -52,7 +60,12 @@ pub fn test(exercise: &Exercise) -> Result<(), ()> { let formatstr = format!("{} Successfully tested {}!", Emoji("✅", "✓"), exercise); println!("{}", style(formatstr).green()); exercise.clean(); - Ok(()) + if let State::Pending(context) = exercise.state() { + print_everything_looks_good(exercise.mode, context); + Ok(false) + } else { + Ok(true) + } } else { let formatstr = format!( "{} Testing of {} failed! Please try again. Here's the output:", @@ -77,3 +90,34 @@ pub fn test(exercise: &Exercise) -> Result<(), ()> { Err(()) } } + +fn print_everything_looks_good(mode: Mode, context: Vec) { + let success_msg = match mode { + Mode::Compile => "The code is compiling!", + Mode::Test => "The code is compiling, and the tests pass!", + }; + + println!(""); + println!("🎉 🎉 {} 🎉 🎉", success_msg); + println!(""); + println!("You can keep working on this exercise,"); + println!( + "or jump into the next one by removing the {} comment:", + style("`I AM NOT DONE`").bold() + ); + println!(""); + for context_line in context { + let formatted_line = if context_line.important { + format!("{}", style(context_line.line).bold()) + } else { + format!("{}", context_line.line) + }; + + println!( + "{:>2} {} {}", + style(context_line.number).blue().bold(), + style("|").blue(), + formatted_line + ); + } +} diff --git a/tests/fixture/state/finished_exercise.rs b/tests/fixture/state/finished_exercise.rs new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixture/state/pending_exercise.rs b/tests/fixture/state/pending_exercise.rs new file mode 100644 index 0000000..f579d0b --- /dev/null +++ b/tests/fixture/state/pending_exercise.rs @@ -0,0 +1,7 @@ +// fake_exercise + +// I AM NOT DONE + +fn main() { + +} diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 3acaf90..897cd2a 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -1,4 +1,7 @@ use assert_cmd::prelude::*; +use glob::glob; +use std::fs::File; +use std::io::Read; use std::process::Command; #[test] @@ -105,3 +108,20 @@ fn run_single_test_no_exercise() { .assert() .code(1); } + +#[test] +fn all_exercises_require_confirmation() { + for exercise in glob("exercises/**/*.rs").unwrap() { + let path = exercise.unwrap(); + 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().expect(&format!( + "There should be an `I AM NOT DONE` annotation in {:?}", + path + )); + } +}