diff --git a/.all-contributorsrc b/.all-contributorsrc index 97cd1b04..14ec3a98 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -2001,6 +2001,141 @@ "contributions": [ "content" ] + }, + { + "login": "poneciak57", + "name": "Kacper Poneta", + "avatar_url": "https://avatars.githubusercontent.com/u/94321164?v=4", + "profile": "https://github.com/poneciak57", + "contributions": [ + "content" + ] + }, + { + "login": "ktheory", + "name": "Aaron Suggs", + "avatar_url": "https://avatars.githubusercontent.com/u/975?v=4", + "profile": "https://ktheory.com/", + "contributions": [ + "content" + ] + }, + { + "login": "alexwh", + "name": "Alex", + "avatar_url": "https://avatars.githubusercontent.com/u/1723612?v=4", + "profile": "https://github.com/alexwh", + "contributions": [ + "content" + ] + }, + { + "login": "stornquist", + "name": "Sebastian TΓΆrnquist", + "avatar_url": "https://avatars.githubusercontent.com/u/42915664?v=4", + "profile": "https://github.com/stornquist", + "contributions": [ + "content" + ] + }, + { + "login": "smlavine", + "name": "Sebastian LaVine", + "avatar_url": "https://avatars.githubusercontent.com/u/33563640?v=4", + "profile": "http://smlavine.com", + "contributions": [ + "code" + ] + }, + { + "login": "akgerber", + "name": "Alan Gerber", + "avatar_url": "https://avatars.githubusercontent.com/u/201313?v=4", + "profile": "http://www.alangerber.us", + "contributions": [ + "content" + ] + }, + { + "login": "esotuvaka", + "name": "Eric", + "avatar_url": "https://avatars.githubusercontent.com/u/104941850?v=4", + "profile": "http://esotuvaka.github.io", + "contributions": [ + "content" + ] + }, + { + "login": "az0977776", + "name": "Aaron Wang", + "avatar_url": "https://avatars.githubusercontent.com/u/9172038?v=4", + "profile": "https://github.com/az0977776", + "contributions": [ + "content" + ] + }, + { + "login": "nmay231", + "name": "Noah", + "avatar_url": "https://avatars.githubusercontent.com/u/35386821?v=4", + "profile": "https://github.com/nmay231", + "contributions": [ + "content" + ] + }, + { + "login": "rb5014", + "name": "rb5014", + "avatar_url": "https://avatars.githubusercontent.com/u/105397317?v=4", + "profile": "https://github.com/rb5014", + "contributions": [ + "content" + ] + }, + { + "login": "deedy5", + "name": "deedy5", + "avatar_url": "https://avatars.githubusercontent.com/u/65482418?v=4", + "profile": "https://github.com/deedy5", + "contributions": [ + "content" + ] + }, + { + "login": "lionel-rowe", + "name": "lionel-rowe", + "avatar_url": "https://avatars.githubusercontent.com/u/26078826?v=4", + "profile": "https://github.com/lionel-rowe", + "contributions": [ + "content" + ] + }, + { + "login": "Ben2917", + "name": "Ben", + "avatar_url": "https://avatars.githubusercontent.com/u/10279994?v=4", + "profile": "https://github.com/Ben2917", + "contributions": [ + "content" + ] + }, + { + "login": "b1ue64", + "name": "b1ue64", + "avatar_url": "https://avatars.githubusercontent.com/u/77976308?v=4", + "profile": "https://github.com/b1ue64", + "contributions": [ + "content" + ] + }, + { + "login": "lazywalker", + "name": "lazywalker", + "avatar_url": "https://avatars.githubusercontent.com/u/53956?v=4", + "profile": "https://github.com/lazywalker", + "contributions": [ + "content" + ] } ], "contributorsPerLine": 8, diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000..67339d1b --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,18 @@ +name: Lint + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: DavidAnson/markdownlint-cli2-action@v9 + with: + globs: "exercises/**/*.md" diff --git a/.gitignore b/.gitignore index c14f9227..88bf2b6c 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,7 @@ rust-project.json !.vscode/extensions.json *.iml *.o +public/ + +# Local Netlify folder +.netlify diff --git a/.markdownlint.yml b/.markdownlint.yml new file mode 100644 index 00000000..d5f7e391 --- /dev/null +++ b/.markdownlint.yml @@ -0,0 +1,2 @@ +# MD013/line-length Line length, Expected: 80 +MD013: false diff --git a/AUTHORS.md b/AUTHORS.md index cffd25eb..50a477c0 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -284,6 +284,25 @@ authors. Keogami
Keogami

πŸ“– Alexandre Esse
Alexandre Esse

πŸ–‹ Sagar Vora
Sagar Vora

πŸ–‹ + Kacper Poneta
Kacper Poneta

πŸ–‹ + Aaron Suggs
Aaron Suggs

πŸ–‹ + Alex
Alex

πŸ–‹ + Sebastian TΓΆrnquist
Sebastian TΓΆrnquist

πŸ–‹ + Sebastian LaVine
Sebastian LaVine

πŸ’» + + + Alan Gerber
Alan Gerber

πŸ–‹ + Eric
Eric

πŸ–‹ + Aaron Wang
Aaron Wang

πŸ–‹ + Noah
Noah

πŸ–‹ + rb5014
rb5014

πŸ–‹ + deedy5
deedy5

πŸ–‹ + lionel-rowe
lionel-rowe

πŸ–‹ + Ben
Ben

πŸ–‹ + + + b1ue64
b1ue64

πŸ–‹ + lazywalker
lazywalker

πŸ–‹ diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c14e7d7..a802faaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,45 @@ + +## 5.5.1 (2023-05-17) + +#### Fixed + +- Reverted `rust-project.json` path generation due to an upstream `rust-analyzer` fix. + + +## 5.5.0 (2023-05-17) + +#### Added + +- `strings2`: Added a reference to the book chapter for reference conversion +- `lifetimes`: Added a link to the lifetimekata project +- Added a new `tests4` exercises, which teaches about testing for panics +- Added a `!` prefix command to watch mode that runs an external command +- Added a `--success-hints` option to watch mode that shows hints on exercise success + +#### Changed + +- `vecs2`: Renamed iterator variable bindings for clarify +- `lifetimes`: Changed order of book references +- `hashmaps2`: Clarified instructions in the todo block +- Moved lifetime exercises before test exercises (via the recommended book ordering) +- `options2`: Improved tests for layering options +- `modules2`: Added more information to the hint + +#### Fixed + +- `errors2`: Corrected a comment wording +- `iterators2`: Fixed a spelling mistake in the hint text +- `variables`: Wrapped the mut keyword with backticks for readability +- `move_semantics2`: Removed references to line numbers +- `cow1`: Clarified the `owned_no_mutation` comments +- `options3`: Changed exercise to panic when no match is found +- `rustlings lsp` now generates absolute paths, which should fix VSCode `rust-analyzer` usage on Windows + +#### Housekeeping + +- Added a markdown linter to run on GitHub actions +- Split quick installation section into two code blocks + ## 5.4.1 (2023-03-10) diff --git a/Cargo.lock b/Cargo.lock index 192a4ac0..a09d98f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -441,7 +441,7 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "rustlings" -version = "5.4.1" +version = "5.5.1" dependencies = [ "argh", "assert_cmd", diff --git a/Cargo.toml b/Cargo.toml index d22816ca..eca091f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "rustlings" -version = "5.4.1" +description = "Small exercises to get you used to reading and writing Rust code!" +version = "5.5.1" authors = [ "Liv ", "Carol (Nichols || Goulding) ", diff --git a/README.md b/README.md index be470fdb..12bd3925 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ +
+ # rustlings πŸ¦€β€οΈ +
+ Greetings and welcome to `rustlings`. This project contains small exercises to get you used to reading and writing Rust code. This includes reading and responding to compiler messages! _...looking for the old, web-based version of Rustlings? Try [here](https://github.com/rust-lang/rustlings/tree/rustlings-1)_ @@ -36,8 +40,8 @@ This will install Rustlings and give you access to the `rustlings` command. Run Basically: Clone the repository at the latest tag, finally run `nix develop` or `nix-shell`. ```bash -# find out the latest version at https://github.com/rust-lang/rustlings/releases/latest (on edit 5.4.1) -git clone -b 5.4.1 --depth 1 https://github.com/rust-lang/rustlings +# find out the latest version at https://github.com/rust-lang/rustlings/releases/latest (on edit 5.5.1) +git clone -b 5.5.1 --depth 1 https://github.com/rust-lang/rustlings cd rustlings # if nix version > 2.3 nix develop @@ -74,8 +78,8 @@ If you get a permission denied message, you might have to exclude the directory Basically: Clone the repository at the latest tag, run `cargo install --path .`. ```bash -# find out the latest version at https://github.com/rust-lang/rustlings/releases/latest (on edit 5.4.1) -git clone -b 5.4.1 --depth 1 https://github.com/rust-lang/rustlings +# find out the latest version at https://github.com/rust-lang/rustlings/releases/latest (on edit 5.5.1) +git clone -b 5.5.1 --depth 1 https://github.com/rust-lang/rustlings cd rustlings cargo install --force --path . ``` diff --git a/exercises/conversions/README.md b/exercises/conversions/README.md index 8d7da93e..619a78c5 100644 --- a/exercises/conversions/README.md +++ b/exercises/conversions/README.md @@ -6,6 +6,7 @@ The simplest form of type conversion is a type cast expression. It is denoted wi Rust also offers traits that facilitate type conversions upon implementation. These traits can be found under the [`convert`](https://doc.rust-lang.org/std/convert/index.html) module. The traits are the following: + - `From` and `Into` covered in [`from_into`](from_into.rs) - `TryFrom` and `TryInto` covered in [`try_from_into`](try_from_into.rs) - `AsRef` and `AsMut` covered in [`as_ref_mut`](as_ref_mut.rs) @@ -17,5 +18,6 @@ These should be the main ways ***within the standard library*** to convert data ## Further information These are not directly covered in the book, but the standard library has a great documentation for it. + - [conversions](https://doc.rust-lang.org/std/convert/index.html) -- [`FromStr` trait](https://doc.rust-lang.org/std/str/trait.FromStr.html) \ No newline at end of file +- [`FromStr` trait](https://doc.rust-lang.org/std/str/trait.FromStr.html) diff --git a/exercises/hashmaps/README.md b/exercises/hashmaps/README.md index 6afa04b2..80ec1441 100644 --- a/exercises/hashmaps/README.md +++ b/exercises/hashmaps/README.md @@ -1,4 +1,5 @@ # Hashmaps + A *hash map* allows you to associate a value with a particular key. You may also know this by the names [*unordered map* in C++](https://en.cppreference.com/w/cpp/container/unordered_map), [*dictionary* in Python](https://docs.python.org/3/tutorial/datastructures.html#dictionaries) or an *associative array* in other languages. diff --git a/exercises/iterators/iterators5.rs b/exercises/iterators/iterators5.rs index 0593d123..d0fcc8ca 100644 --- a/exercises/iterators/iterators5.rs +++ b/exercises/iterators/iterators5.rs @@ -2,13 +2,11 @@ // Let's define a simple model to track Rustlings exercise progress. Progress // will be modelled using a hash map. The name of the exercise is the key and // the progress is the value. Two counting functions were created to count the -// number of exercises with a given progress. These counting functions use -// imperative style for loops. Recreate this counting functionality using -// iterators. Only the two iterator methods (count_iterator and -// count_collection_iterator) need to be modified. +// number of exercises with a given progress. Recreate this counting +// functionality using iterators. Try not to use imperative loops (for, while). +// Only the two iterator methods (count_iterator and count_collection_iterator) +// need to be modified. // Execute `rustlings hint iterators5` or use the `hint` watch subcommand for a hint. -// -// Make the code compile and the tests pass. // I AM NOT DONE @@ -67,12 +65,27 @@ mod tests { } #[test] - fn count_equals_for() { + fn count_some() { let map = get_map(); - assert_eq!( - count_for(&map, Progress::Complete), - count_iterator(&map, Progress::Complete) - ); + assert_eq!(1, count_iterator(&map, Progress::Some)); + } + + #[test] + fn count_none() { + let map = get_map(); + assert_eq!(2, count_iterator(&map, Progress::None)); + } + + #[test] + fn count_complete_equals_for() { + let map = get_map(); + let progress_states = vec![Progress::Complete, Progress::Some, Progress::None]; + for progress_state in progress_states { + assert_eq!( + count_for(&map, progress_state), + count_iterator(&map, progress_state) + ); + } } #[test] @@ -85,12 +98,28 @@ mod tests { } #[test] - fn count_collection_equals_for() { + fn count_collection_some() { let collection = get_vec_map(); - assert_eq!( - count_collection_for(&collection, Progress::Complete), - count_collection_iterator(&collection, Progress::Complete) - ); + assert_eq!(1, count_collection_iterator(&collection, Progress::Some)); + } + + #[test] + fn count_collection_none() { + let collection = get_vec_map(); + assert_eq!(4, count_collection_iterator(&collection, Progress::None)); + } + + #[test] + fn count_collection_equals_for() { + let progress_states = vec![Progress::Complete, Progress::Some, Progress::None]; + let collection = get_vec_map(); + + for progress_state in progress_states { + assert_eq!( + count_collection_for(&collection, progress_state), + count_collection_iterator(&collection, progress_state) + ); + } } fn get_map() -> HashMap { diff --git a/exercises/move_semantics/move_semantics2.rs b/exercises/move_semantics/move_semantics2.rs index 64870850..93bb82ef 100644 --- a/exercises/move_semantics/move_semantics2.rs +++ b/exercises/move_semantics/move_semantics2.rs @@ -1,12 +1,16 @@ // move_semantics2.rs -// Make me compile without changing line 13 or moving line 10! // Execute `rustlings hint move_semantics2` or use the `hint` watch subcommand for a hint. +// Expected output: +// vec0 has length 3 content `[22, 44, 66]` +// vec1 has length 4 content `[22, 44, 66, 88]` + // I AM NOT DONE fn main() { let vec0 = Vec::new(); + // Do not move the following line! let mut vec1 = fill_vec(vec0); // Do not change the following line! diff --git a/exercises/options/README.md b/exercises/options/README.md index 5433fed8..bdd33749 100644 --- a/exercises/options/README.md +++ b/exercises/options/README.md @@ -2,6 +2,7 @@ Type Option represents an optional value: every Option is either Some and contains a value, or None, and does not. Option types are very common in Rust code, as they have a number of uses: + - Initial values - Return values for functions that are not defined over their entire input range (partial functions) - Return value for otherwise reporting simple errors, where None is returned on error diff --git a/exercises/options/options2.rs b/exercises/options/options2.rs index 4e36443f..337c4261 100644 --- a/exercises/options/options2.rs +++ b/exercises/options/options2.rs @@ -18,17 +18,22 @@ mod tests { #[test] fn layered_option() { - let mut range = 10; - let mut optional_integers: Vec> = Vec::new(); - for i in 0..(range + 1) { + let range = 10; + let mut optional_integers: Vec> = vec![None]; + + for i in 1..(range + 1) { optional_integers.push(Some(i)); } + let mut cursor = range; + // TODO: make this a while let statement - remember that vector.pop also adds another layer of Option - // You can stack `Option`'s into while let and if let + // You can stack `Option`s into while let and if let integer = optional_integers.pop() { - assert_eq!(integer, range); - range -= 1; + assert_eq!(integer, cursor); + cursor -= 1; } + + assert_eq!(cursor, 0); } } diff --git a/exercises/options/options3.rs b/exercises/options/options3.rs index 3f995c52..3ed76eeb 100644 --- a/exercises/options/options3.rs +++ b/exercises/options/options3.rs @@ -13,7 +13,7 @@ fn main() { match y { Some(p) => println!("Co-ordinates are {},{} ", p.x, p.y), - _ => println!("no match"), + _ => panic!("no match!"), } y; // Fix without deleting this line. } diff --git a/exercises/smart_pointers/README.md b/exercises/smart_pointers/README.md index c517ae31..d56d2b62 100644 --- a/exercises/smart_pointers/README.md +++ b/exercises/smart_pointers/README.md @@ -1,4 +1,5 @@ # Smart Pointers + In Rust, smart pointers are variables that contain an address in memory and reference some other data, but they also have additional metadata and capabilities. Smart pointers in Rust often own the data they point to, while references only borrow data. diff --git a/exercises/smart_pointers/cow1.rs b/exercises/smart_pointers/cow1.rs index 885138a7..bc5b28e5 100644 --- a/exercises/smart_pointers/cow1.rs +++ b/exercises/smart_pointers/cow1.rs @@ -52,7 +52,8 @@ mod tests { fn owned_no_mutation() -> Result<(), &'static str> { // We can also pass `slice` without `&` so Cow owns it directly. // In this case no mutation occurs and thus also no clone, - // but the result is still owned because it always was. + // but the result is still owned because it was never borrowed + // or mutated. let slice = vec![0, 1, 2]; let mut input = Cow::from(slice); match abs_all(&mut input) { diff --git a/exercises/tests/tests4.rs b/exercises/tests/tests4.rs new file mode 100644 index 00000000..1f34a2b2 --- /dev/null +++ b/exercises/tests/tests4.rs @@ -0,0 +1,45 @@ +// tests4.rs +// Make sure that we're testing for the correct conditions! +// Execute `rustlings hint tests4` or use the `hint` watch subcommand for a hint. + +// I AM NOT DONE + +struct Rectangle { + width: i32, + height: i32 +} + +impl Rectangle { + // Only change the test functions themselves + pub fn new(width: i32, height: i32) -> Self { + if width <= 0 || height <= 0 { + panic!("Rectangle width and height cannot be negative!") + } + Rectangle {width, height} + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn correct_width_and_height() { + // This test should check if the rectangle is the size that we pass into its constructor + let rect = Rectangle::new(10, 20); + assert_eq!(???, 10); // check width + assert_eq!(???, 20); // check height + } + + #[test] + fn negative_width() { + // This test should check if program panics when we try to create rectangle with negative width + let _rect = Rectangle::new(-10, 10); + } + + #[test] + fn negative_height() { + // This test should check if program panics when we try to create rectangle with negative height + let _rect = Rectangle::new(10, -10); + } +} diff --git a/exercises/traits/README.md b/exercises/traits/README.md index de67acd0..ac87c64e 100644 --- a/exercises/traits/README.md +++ b/exercises/traits/README.md @@ -7,13 +7,13 @@ Data types can implement traits. To do so, the methods making up the trait are d In this way, traits are somewhat similar to Java interfaces and C++ abstract classes. Some additional common Rust traits include: + - `Clone` (the `clone` method) - `Display` (which allows formatted display via `{}`) - `Debug` (which allows formatted display via `{:?}`) Because traits indicate shared behavior between data types, they are useful when writing generics. - ## Further information - [Traits](https://doc.rust-lang.org/book/ch10-02-traits.html) diff --git a/flake.nix b/flake.nix index 5815a920..5dbca5c2 100644 --- a/flake.nix +++ b/flake.nix @@ -22,7 +22,7 @@ rustlings = pkgs.rustPlatform.buildRustPackage { name = "rustlings"; - version = "5.4.1"; + version = "5.5.1"; buildInputs = cargoBuildInputs; diff --git a/info.toml b/info.toml index ef891435..2add5f0c 100644 --- a/info.toml +++ b/info.toml @@ -325,8 +325,7 @@ doing one step and then fixing the compiler errors that result! So the end goal is to: - get rid of the first line in main that creates the new vector - so then `vec0` doesn't exist, so we can't pass it to `fill_vec` - - we don't want to pass anything to `fill_vec`, so its signature should - reflect that it does not take any arguments + - `fill_vec` has had its signature changed, which our call should reflect - since we're not creating a new vec in `main` anymore, we need to create a new vec in `fill_vec`, similarly to the way we did in `main`""" @@ -479,7 +478,8 @@ hint = """ The delicious_snacks module is trying to present an external interface that is different than its internal structure (the `fruits` and `veggies` modules and associated constants). Complete the `use` statements to fit the uses in main and -find the one keyword missing for both constants.""" +find the one keyword missing for both constants. +Learn more at https://doc.rust-lang.org/book/ch07-04-bringing-paths-into-scope-with-the-use-keyword.html#re-exporting-names-with-pub-use""" [[exercises]] name = "modules3" @@ -807,6 +807,17 @@ You can call a function right where you're passing arguments to `assert!` -- so something like `assert!(having_fun())`. If you want to check that you indeed get false, you can negate the result of what you're doing using `!`, like `assert!(!having_fun())`.""" +[[exercises]] +name = "tests4" +path = "exercises/tests/tests4.rs" +mode = "test" +hint = """ +We expect method `Rectangle::new()` to panic for negative values. +To handle that you need to add a special attribute to the test function. +You can refer to the docs: +https://doc.rust-lang.org/stable/book/ch11-01-writing-tests.html#checking-for-panics-with-should_panic""" + + # STANDARD LIBRARY TYPES [[exercises]] @@ -886,9 +897,6 @@ hint = """ The documentation for the std::iter::Iterator trait contains numerous methods that would be helpful here. -Return 0 from count_collection_iterator to make the code compile in order to -test count_iterator. - The collection variable in count_collection_iterator is a slice of HashMaps. It needs to be converted into an iterator in order to use the iterator methods. diff --git a/oranda.json b/oranda.json new file mode 100644 index 00000000..18ef8c68 --- /dev/null +++ b/oranda.json @@ -0,0 +1,10 @@ +{ + "homepage": "https://rustlings.cool", + "repository": "https://github.com/rust-lang/rustlings", + "analytics": { + "plausible": { + "domain": "rustlings.cool" + } + }, + "changelog": false +} diff --git a/src/main.rs b/src/main.rs index 704398e5..0a9af2ec 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,7 +26,7 @@ mod run; mod verify; // In sync with crate version -const VERSION: &str = "5.4.1"; +const VERSION: &str = "5.5.1"; #[derive(FromArgs, PartialEq, Debug)] /// Rustlings is a collection of small exercises to get you used to writing and reading Rust code @@ -61,7 +61,11 @@ struct VerifyArgs {} #[derive(FromArgs, PartialEq, Debug)] #[argh(subcommand, name = "watch")] /// Reruns `verify` when files were edited -struct WatchArgs {} +struct WatchArgs { + /// show hints on success + #[argh(switch)] + success_hints: bool, +} #[derive(FromArgs, PartialEq, Debug)] #[argh(subcommand, name = "run")] @@ -229,7 +233,7 @@ fn main() { } Subcommands::Verify(_subargs) => { - verify(&exercises, (0, exercises.len()), verbose) + verify(&exercises, (0, exercises.len()), verbose, false) .unwrap_or_else(|_| std::process::exit(1)); } @@ -252,7 +256,7 @@ fn main() { } } - Subcommands::Watch(_subargs) => match watch(&exercises, verbose) { + Subcommands::Watch(_subargs) => match watch(&exercises, verbose, _subargs.success_hints) { Err(e) => { println!( "Error: Could not watch your progress. Error message was {:?}.", @@ -298,13 +302,21 @@ fn spawn_watch_shell( println!("Bye!"); } else if input.eq("help") { println!("Commands available to you in watch mode:"); - println!(" hint - prints the current exercise's hint"); - println!(" clear - clears the screen"); - println!(" quit - quits watch mode"); - println!(" help - displays this help message"); + println!(" hint - prints the current exercise's hint"); + println!(" clear - clears the screen"); + println!(" quit - quits watch mode"); + println!(" ! - executes a command, like `!rustc --explain E0381`"); + println!(" help - displays this help message"); println!(); println!("Watch mode automatically re-evaluates the current exercise"); println!("when you edit a file's contents.") + } else if let Some(cmd) = input.strip_prefix('!') { + let parts: Vec<&str> = cmd.split_whitespace().collect(); + if parts.is_empty() { + println!("no command provided"); + } else if let Err(e) = Command::new(parts[0]).args(&parts[1..]).status() { + println!("failed to execute command `{}`: {}", cmd, e); + } } else { println!("unknown command: {input}"); } @@ -340,7 +352,11 @@ enum WatchStatus { Unfinished, } -fn watch(exercises: &[Exercise], verbose: bool) -> notify::Result { +fn watch( + exercises: &[Exercise], + verbose: bool, + success_hints: bool, +) -> notify::Result { /* Clears the terminal with an ANSI escape code. Works in UNIX and newer Windows terminals. */ fn clear_screen() { @@ -356,7 +372,12 @@ fn watch(exercises: &[Exercise], verbose: bool) -> notify::Result { clear_screen(); let to_owned_hint = |t: &Exercise| t.hint.to_owned(); - let failed_exercise_hint = match verify(exercises.iter(), (0, exercises.len()), verbose) { + let failed_exercise_hint = match verify( + exercises.iter(), + (0, exercises.len()), + verbose, + success_hints, + ) { Ok(_) => return Ok(WatchStatus::Finished), Err(exercise) => Arc::new(Mutex::new(Some(to_owned_hint(exercise)))), }; @@ -378,7 +399,12 @@ fn watch(exercises: &[Exercise], verbose: bool) -> notify::Result { ); let num_done = exercises.iter().filter(|e| e.looks_done()).count(); clear_screen(); - match verify(pending_exercises, (num_done, exercises.len()), verbose) { + match verify( + pending_exercises, + (num_done, exercises.len()), + verbose, + success_hints, + ) { Ok(_) => return Ok(WatchStatus::Finished), Err(exercise) => { let mut failed_exercise_hint = failed_exercise_hint.lock().unwrap(); diff --git a/src/project.rs b/src/project.rs index 6e483504..ebebe27d 100644 --- a/src/project.rs +++ b/src/project.rs @@ -2,6 +2,7 @@ use glob::glob; use serde::{Deserialize, Serialize}; use std::env; use std::error::Error; +use std::path::PathBuf; use std::process::Command; /// Contains the structure of resulting rust-project.json file @@ -38,11 +39,11 @@ impl RustAnalyzerProject { } /// If path contains .rs extension, add a crate to `rust-project.json` - fn path_to_json(&mut self, path: String) { - if let Some((_, ext)) = path.split_once('.') { + fn path_to_json(&mut self, path: PathBuf) -> Result<(), Box> { + if let Some(ext) = path.extension() { if ext == "rs" { self.crates.push(Crate { - root_module: path, + root_module: path.display().to_string(), edition: "2021".to_string(), deps: Vec::new(), // This allows rust_analyzer to work inside #[test] blocks @@ -50,15 +51,16 @@ impl RustAnalyzerProject { }) } } + + Ok(()) } /// Parse the exercises folder for .rs files, any matches will create /// a new `crate` in rust-project.json which allows rust-analyzer to /// treat it like a normal binary pub fn exercises_to_json(&mut self) -> Result<(), Box> { - for e in glob("./exercises/**/*")? { - let path = e?.to_string_lossy().to_string(); - self.path_to_json(path); + for path in glob("./exercises/**/*")? { + self.path_to_json(path?)?; } Ok(()) } diff --git a/src/verify.rs b/src/verify.rs index 68ba6cef..f3f3b564 100644 --- a/src/verify.rs +++ b/src/verify.rs @@ -12,6 +12,7 @@ pub fn verify<'a>( exercises: impl IntoIterator, progress: (usize, usize), verbose: bool, + success_hints: bool, ) -> Result<(), &'a Exercise> { let (num_done, total) = progress; let bar = ProgressBar::new(total as u64); @@ -25,9 +26,9 @@ pub fn verify<'a>( for exercise in exercises { let compile_result = match exercise.mode { - Mode::Test => compile_and_test(exercise, RunMode::Interactive, verbose), - Mode::Compile => compile_and_run_interactively(exercise), - Mode::Clippy => compile_only(exercise), + Mode::Test => compile_and_test(exercise, RunMode::Interactive, verbose, success_hints), + Mode::Compile => compile_and_run_interactively(exercise, success_hints), + Mode::Clippy => compile_only(exercise, success_hints), }; if !compile_result.unwrap_or(false) { return Err(exercise); @@ -46,12 +47,12 @@ enum RunMode { // Compile and run the resulting test harness of the given Exercise pub fn test(exercise: &Exercise, verbose: bool) -> Result<(), ()> { - compile_and_test(exercise, RunMode::NonInteractive, verbose)?; + compile_and_test(exercise, RunMode::NonInteractive, verbose, false)?; Ok(()) } // Invoke the rust compiler without running the resulting binary -fn compile_only(exercise: &Exercise) -> Result { +fn compile_only(exercise: &Exercise, success_hints: bool) -> Result { let progress_bar = ProgressBar::new_spinner(); progress_bar.set_message(format!("Compiling {exercise}...")); progress_bar.enable_steady_tick(100); @@ -59,11 +60,11 @@ fn compile_only(exercise: &Exercise) -> Result { let _ = compile(exercise, &progress_bar)?; progress_bar.finish_and_clear(); - Ok(prompt_for_completion(exercise, None)) + Ok(prompt_for_completion(exercise, None, success_hints)) } // Compile the given Exercise and run the resulting binary in an interactive mode -fn compile_and_run_interactively(exercise: &Exercise) -> Result { +fn compile_and_run_interactively(exercise: &Exercise, success_hints: bool) -> Result { let progress_bar = ProgressBar::new_spinner(); progress_bar.set_message(format!("Compiling {exercise}...")); progress_bar.enable_steady_tick(100); @@ -84,12 +85,12 @@ fn compile_and_run_interactively(exercise: &Exercise) -> Result { } }; - Ok(prompt_for_completion(exercise, Some(output.stdout))) + Ok(prompt_for_completion(exercise, Some(output.stdout), success_hints)) } // Compile the given Exercise as a test harness and display // the output if verbose is set to true -fn compile_and_test(exercise: &Exercise, run_mode: RunMode, verbose: bool) -> Result { +fn compile_and_test(exercise: &Exercise, run_mode: RunMode, verbose: bool, success_hints: bool) -> Result { let progress_bar = ProgressBar::new_spinner(); progress_bar.set_message(format!("Testing {exercise}...")); progress_bar.enable_steady_tick(100); @@ -104,7 +105,7 @@ fn compile_and_test(exercise: &Exercise, run_mode: RunMode, verbose: bool) -> Re println!("{}", output.stdout); } if let RunMode::Interactive = run_mode { - Ok(prompt_for_completion(exercise, None)) + Ok(prompt_for_completion(exercise, None, success_hints)) } else { Ok(true) } @@ -142,12 +143,11 @@ fn compile<'a, 'b>( } } -fn prompt_for_completion(exercise: &Exercise, prompt_output: Option) -> bool { +fn prompt_for_completion(exercise: &Exercise, prompt_output: Option, success_hints: bool) -> bool { let context = match exercise.state() { State::Done => return true, State::Pending(context) => context, }; - match exercise.mode { Mode::Compile => success!("Successfully ran {}!", exercise), Mode::Test => success!("Successfully tested {}!", exercise), @@ -167,7 +167,6 @@ fn prompt_for_completion(exercise: &Exercise, prompt_output: Option) -> Mode::Test => "The code is compiling, and the tests pass!", Mode::Clippy => clippy_success_msg, }; - println!(); if no_emoji { println!("~*~ {success_msg} ~*~") @@ -183,6 +182,13 @@ fn prompt_for_completion(exercise: &Exercise, prompt_output: Option) -> println!("{}", separator()); println!(); } + if success_hints { + println!("Hints:"); + println!("{}", separator()); + println!("{}", exercise.hint); + println!("{}", separator()); + println!(); + } println!("You can keep working on this exercise,"); println!(