From 9f6a68a9e938ded1829672abdfb5577de94cc7ad Mon Sep 17 00:00:00 2001 From: dem4ron Date: Tue, 21 Feb 2023 17:43:48 +0100 Subject: [PATCH 001/436] Add secret-handshake exercise --- config.json | 9 +++ .../secret-handshake/.docs/instructions.md | 24 +++++++ .../practice/secret-handshake/.gitignore | 8 +++ .../secret-handshake/.meta/config.json | 20 ++++++ .../secret-handshake/.meta/example.rs | 16 +++++ .../secret-handshake/.meta/tests.toml | 43 +++++++++++ .../practice/secret-handshake/Cargo.toml | 4 ++ .../practice/secret-handshake/src/lib.rs | 3 + .../tests/secret-handshake.rs | 72 +++++++++++++++++++ 9 files changed, 199 insertions(+) create mode 100644 exercises/practice/secret-handshake/.docs/instructions.md create mode 100644 exercises/practice/secret-handshake/.gitignore create mode 100644 exercises/practice/secret-handshake/.meta/config.json create mode 100644 exercises/practice/secret-handshake/.meta/example.rs create mode 100644 exercises/practice/secret-handshake/.meta/tests.toml create mode 100644 exercises/practice/secret-handshake/Cargo.toml create mode 100644 exercises/practice/secret-handshake/src/lib.rs create mode 100644 exercises/practice/secret-handshake/tests/secret-handshake.rs diff --git a/config.json b/config.json index bf3f8b70c..05ec651f9 100644 --- a/config.json +++ b/config.json @@ -1466,6 +1466,15 @@ "unsafe" ] }, + { + "slug": "secret-handshake", + "name": "Secret Handshake", + "uuid": "8c044530-9deb-4ff7-a638-98d6c05ebbb8", + "practices": [], + "prerequisites": [], + "difficulty": 4, + "topics": [] + }, { "slug": "hexadecimal", "name": "Hexadecimal", diff --git a/exercises/practice/secret-handshake/.docs/instructions.md b/exercises/practice/secret-handshake/.docs/instructions.md new file mode 100644 index 000000000..2d6937ae9 --- /dev/null +++ b/exercises/practice/secret-handshake/.docs/instructions.md @@ -0,0 +1,24 @@ +# Instructions + +> There are 10 types of people in the world: Those who understand +> binary, and those who don't. + +You and your fellow cohort of those in the "know" when it comes to binary decide to come up with a secret "handshake". + +```text +00001 = wink +00010 = double blink +00100 = close your eyes +01000 = jump + +10000 = Reverse the order of the operations in the secret handshake. +``` + +Given a decimal number, convert it to the appropriate sequence of events for a secret handshake. + +Here's a couple of examples: + +Given the decimal input 3, the function would return the array ["wink", "double blink"] because the decimal number 3 is 2+1 in powers of two and thus `11` in binary. + +Let's now examine the input 19 which is 16+2+1 in powers of two and thus `10011` in binary. +Recalling that the addition of 16 (`10000` in binary) reverses an array and that we already know what array is returned given input 3, the array returned for input 19 is ["double blink", "wink"]. diff --git a/exercises/practice/secret-handshake/.gitignore b/exercises/practice/secret-handshake/.gitignore new file mode 100644 index 000000000..db7f315c0 --- /dev/null +++ b/exercises/practice/secret-handshake/.gitignore @@ -0,0 +1,8 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ +**/*.rs.bk + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock diff --git a/exercises/practice/secret-handshake/.meta/config.json b/exercises/practice/secret-handshake/.meta/config.json new file mode 100644 index 000000000..5507f49c0 --- /dev/null +++ b/exercises/practice/secret-handshake/.meta/config.json @@ -0,0 +1,20 @@ +{ + "authors": [ + "dem4ron" + ], + "files": { + "solution": [ + "src/lib.rs", + "Cargo.toml" + ], + "test": [ + "tests/secret-handshake.rs" + ], + "example": [ + ".meta/example.rs" + ] + }, + "blurb": "Given a decimal number, convert it to the appropriate sequence of events for a secret handshake.", + "source": "Bert, in Mary Poppins", + "source_url": "https://www.imdb.com/title/tt0058331/quotes/qt0437047" +} diff --git a/exercises/practice/secret-handshake/.meta/example.rs b/exercises/practice/secret-handshake/.meta/example.rs new file mode 100644 index 000000000..272600417 --- /dev/null +++ b/exercises/practice/secret-handshake/.meta/example.rs @@ -0,0 +1,16 @@ +const COMMANDS = ["wink", "double blink", "close your eyes", "jump"]; + +pub fn commands(n: u8) -> Option> { + let result: Vec<&str> = COMMANDS.iter().enumerate() + .filter(|(i, _)| (1u8 << *i) & n != 0) + .map(|(_, &c)| c) + .collect(); + + if result.is_empty() { + None + } else if n & 16 != 0 { + Some(result.into_iter().rev().collect()) + } else { + Some(result) + } +} \ No newline at end of file diff --git a/exercises/practice/secret-handshake/.meta/tests.toml b/exercises/practice/secret-handshake/.meta/tests.toml new file mode 100644 index 000000000..f318e5283 --- /dev/null +++ b/exercises/practice/secret-handshake/.meta/tests.toml @@ -0,0 +1,43 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[b8496fbd-6778-468c-8054-648d03c4bb23] +description = "wink for 1" + +[83ec6c58-81a9-4fd1-bfaf-0160514fc0e3] +description = "double blink for 10" + +[0e20e466-3519-4134-8082-5639d85fef71] +description = "close your eyes for 100" + +[b339ddbb-88b7-4b7d-9b19-4134030d9ac0] +description = "jump for 1000" + +[40499fb4-e60c-43d7-8b98-0de3ca44e0eb] +description = "combine two actions" + +[9730cdd5-ef27-494b-afd3-5c91ad6c3d9d] +description = "reverse two actions" + +[0b828205-51ca-45cd-90d5-f2506013f25f] +description = "reversing one action gives the same action" + +[9949e2ac-6c9c-4330-b685-2089ab28b05f] +description = "reversing no actions still gives no actions" + +[23fdca98-676b-4848-970d-cfed7be39f81] +description = "all possible actions" + +[ae8fe006-d910-4d6f-be00-54b7c3799e79] +description = "reverse all possible actions" + +[3d36da37-b31f-4cdb-a396-d93a2ee1c4a5] +description = "do nothing for zero" diff --git a/exercises/practice/secret-handshake/Cargo.toml b/exercises/practice/secret-handshake/Cargo.toml new file mode 100644 index 000000000..029d1c91e --- /dev/null +++ b/exercises/practice/secret-handshake/Cargo.toml @@ -0,0 +1,4 @@ +[package] +edition = "2021" +name = "secret-handshake" +version = "1.1.0" diff --git a/exercises/practice/secret-handshake/src/lib.rs b/exercises/practice/secret-handshake/src/lib.rs new file mode 100644 index 000000000..409892e6c --- /dev/null +++ b/exercises/practice/secret-handshake/src/lib.rs @@ -0,0 +1,3 @@ +pub fn commands(n: u8) -> Option> { + unimplemented!("What is the secret handshake for {n}?") +} diff --git a/exercises/practice/secret-handshake/tests/secret-handshake.rs b/exercises/practice/secret-handshake/tests/secret-handshake.rs new file mode 100644 index 000000000..c7ab9182b --- /dev/null +++ b/exercises/practice/secret-handshake/tests/secret-handshake.rs @@ -0,0 +1,72 @@ +use secret_handshake::*; + +#[test] +fn wink_for_1() { + assert_eq!(commands(1), Some(vec!["wink"])) +} + +#[test] +#[ignore] +fn double_blink_for_10() { + assert_eq!(commands(2), Some(vec!["double blink"])) +} + +#[test] +#[ignore] +fn close_your_eyes_for_100() { + assert_eq!(commands(4), Some(vec!["close your eyes"])) +} + +#[test] +#[ignore] +fn jump_for_1000() { + assert_eq!(commands(8), Some(vec!["jump"])) +} + +#[test] +#[ignore] +fn combine_two_actions() { + assert_eq!(commands(3), Some(vec!["wink", "double blink"])) +} + +#[test] +#[ignore] +fn reverse_two_actions() { + assert_eq!(commands(19), Some(vec!["double blink", "wink"])) +} + +#[test] +#[ignore] +fn reversing_one_action_gives_the_same_action() { + assert_eq!(commands(24), Some(vec!["jump"])) +} + +#[test] +#[ignore] +fn reversing_no_actions_still_gives_no_actions() { + assert_eq!(commands(16), None) +} + +#[test] +#[ignore] +fn all_possible_actions() { + assert_eq!( + commands(15), + Some(vec!["wink", "double blink", "close your eyes", "jump"]) + ) +} + +#[test] +#[ignore] +fn reverse_all_possible_actions() { + assert_eq!( + commands(31), + Some(vec!["jump", "close your eyes", "double blink", "wink"]) + ) +} + +#[test] +#[ignore] +fn do_nothing_for_zero() { + assert_eq!(commands(0), None) +} From 89459bf584f5154e7ec59f8f36b1d603b1a68177 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oddmund=20Str=C3=B8mme?= Date: Mon, 27 Feb 2023 20:22:49 +0100 Subject: [PATCH 002/436] Update broken links to learning-rust (#1634) Closes https://github.com/exercism/rust/issues/1627 Note line 39 of exercises/concept/health-statistics/.meta/design.md: The link text and link href were inconsistent. I made a guess on which to keep. --- concepts/structs/links.json | 4 ++-- exercises/concept/health-statistics/.meta/design.md | 4 ++-- exercises/concept/role-playing-game/.meta/design.md | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/concepts/structs/links.json b/concepts/structs/links.json index 15cb1cccb..9dea0f3e1 100644 --- a/concepts/structs/links.json +++ b/concepts/structs/links.json @@ -1,6 +1,6 @@ [ { - "url": "https://learning-rust.github.io/docs/b2.structs.html#Tuple-structs", + "url": "https://learning-rust.github.io/docs/structs/#tuple-structs", "description": "more information about tuple structs" }, { @@ -8,7 +8,7 @@ "description": "examples of tuple and unit structs" }, { - "url": "https://learning-rust.github.io/docs/b2.structs.html#C-like-structs", + "url": "https://learning-rust.github.io/docs/structs/#c-like-structs", "description": "good overview of the different types of structs and their syntax" } ] diff --git a/exercises/concept/health-statistics/.meta/design.md b/exercises/concept/health-statistics/.meta/design.md index 1ad4f4b22..d987a10d2 100644 --- a/exercises/concept/health-statistics/.meta/design.md +++ b/exercises/concept/health-statistics/.meta/design.md @@ -30,13 +30,13 @@ ### Hints -- [https://learning-rust.github.io/docs/b2.structs.html#C-like-structs](https://learning-rust.github.io/docs/b2.structs.html#C-like-structs) +- [https://learning-rust.github.io/docs/structs/#c-like-structs](https://learning-rust.github.io/docs/structs/#c-like-structs) - [https://doc.rust-lang.org/book/ch05-01-defining-structs.html](https://doc.rust-lang.org/book/ch05-01-defining-structs.html) - [https://doc.rust-lang.org/book/ch05-03-method-syntax.html](https://doc.rust-lang.org/book/ch05-03-method-syntax.html) ### After -- [https://learning-rust.github.io/docs/b2.structs.html#C-like-structs](https://learning-rust.github.io/docs/b2.structs.html#Tuple-structs) +- [https://learning-rust.github.io/docs/structs/#tuple-structs](https://learning-rust.github.io/docs/structs/#tuple-structs) - [https://doc.rust-lang.org/stable/rust-by-example/custom_types/structs.html](https://doc.rust-lang.org/stable/rust-by-example/custom_types/structs.html) ## Representer diff --git a/exercises/concept/role-playing-game/.meta/design.md b/exercises/concept/role-playing-game/.meta/design.md index a4d584d8e..c10916351 100644 --- a/exercises/concept/role-playing-game/.meta/design.md +++ b/exercises/concept/role-playing-game/.meta/design.md @@ -34,7 +34,7 @@ The concepts this exercise unlocks are: - - -- +- - [The Billion-Dollar Mistake](https://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare/) ### After From 0516ebb1978db9f53cb40af4f043dc2f54f996dd Mon Sep 17 00:00:00 2001 From: Aron Demeter <66035744+dem4ron@users.noreply.github.com> Date: Tue, 28 Feb 2023 12:44:23 +0100 Subject: [PATCH 003/436] Add secret handshake exercise (#1626) --- config.json | 9 +++ .../secret-handshake/.docs/instructions.md | 24 +++++++ .../practice/secret-handshake/.gitignore | 8 +++ .../secret-handshake/.meta/config.json | 20 ++++++ .../secret-handshake/.meta/example.rs | 16 +++++ .../secret-handshake/.meta/tests.toml | 43 +++++++++++ .../practice/secret-handshake/Cargo.toml | 4 ++ .../practice/secret-handshake/src/lib.rs | 3 + .../tests/secret-handshake.rs | 72 +++++++++++++++++++ 9 files changed, 199 insertions(+) create mode 100644 exercises/practice/secret-handshake/.docs/instructions.md create mode 100644 exercises/practice/secret-handshake/.gitignore create mode 100644 exercises/practice/secret-handshake/.meta/config.json create mode 100644 exercises/practice/secret-handshake/.meta/example.rs create mode 100644 exercises/practice/secret-handshake/.meta/tests.toml create mode 100644 exercises/practice/secret-handshake/Cargo.toml create mode 100644 exercises/practice/secret-handshake/src/lib.rs create mode 100644 exercises/practice/secret-handshake/tests/secret-handshake.rs diff --git a/config.json b/config.json index bf3f8b70c..05ec651f9 100644 --- a/config.json +++ b/config.json @@ -1466,6 +1466,15 @@ "unsafe" ] }, + { + "slug": "secret-handshake", + "name": "Secret Handshake", + "uuid": "8c044530-9deb-4ff7-a638-98d6c05ebbb8", + "practices": [], + "prerequisites": [], + "difficulty": 4, + "topics": [] + }, { "slug": "hexadecimal", "name": "Hexadecimal", diff --git a/exercises/practice/secret-handshake/.docs/instructions.md b/exercises/practice/secret-handshake/.docs/instructions.md new file mode 100644 index 000000000..2d6937ae9 --- /dev/null +++ b/exercises/practice/secret-handshake/.docs/instructions.md @@ -0,0 +1,24 @@ +# Instructions + +> There are 10 types of people in the world: Those who understand +> binary, and those who don't. + +You and your fellow cohort of those in the "know" when it comes to binary decide to come up with a secret "handshake". + +```text +00001 = wink +00010 = double blink +00100 = close your eyes +01000 = jump + +10000 = Reverse the order of the operations in the secret handshake. +``` + +Given a decimal number, convert it to the appropriate sequence of events for a secret handshake. + +Here's a couple of examples: + +Given the decimal input 3, the function would return the array ["wink", "double blink"] because the decimal number 3 is 2+1 in powers of two and thus `11` in binary. + +Let's now examine the input 19 which is 16+2+1 in powers of two and thus `10011` in binary. +Recalling that the addition of 16 (`10000` in binary) reverses an array and that we already know what array is returned given input 3, the array returned for input 19 is ["double blink", "wink"]. diff --git a/exercises/practice/secret-handshake/.gitignore b/exercises/practice/secret-handshake/.gitignore new file mode 100644 index 000000000..db7f315c0 --- /dev/null +++ b/exercises/practice/secret-handshake/.gitignore @@ -0,0 +1,8 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ +**/*.rs.bk + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock diff --git a/exercises/practice/secret-handshake/.meta/config.json b/exercises/practice/secret-handshake/.meta/config.json new file mode 100644 index 000000000..5507f49c0 --- /dev/null +++ b/exercises/practice/secret-handshake/.meta/config.json @@ -0,0 +1,20 @@ +{ + "authors": [ + "dem4ron" + ], + "files": { + "solution": [ + "src/lib.rs", + "Cargo.toml" + ], + "test": [ + "tests/secret-handshake.rs" + ], + "example": [ + ".meta/example.rs" + ] + }, + "blurb": "Given a decimal number, convert it to the appropriate sequence of events for a secret handshake.", + "source": "Bert, in Mary Poppins", + "source_url": "https://www.imdb.com/title/tt0058331/quotes/qt0437047" +} diff --git a/exercises/practice/secret-handshake/.meta/example.rs b/exercises/practice/secret-handshake/.meta/example.rs new file mode 100644 index 000000000..88afcfad5 --- /dev/null +++ b/exercises/practice/secret-handshake/.meta/example.rs @@ -0,0 +1,16 @@ +const ACTIONS: [&str; 4] = ["wink", "double blink", "close your eyes", "jump"]; + +pub fn actions(n: u8) -> Vec<&'static str> { + let result: Vec<&str> = ACTIONS + .iter() + .enumerate() + .filter(|(i, _)| (1u8 << *i) & n != 0) + .map(|(_, &c)| c) + .collect(); + + if n & 16 != 0 { + result.into_iter().rev().collect() + } else { + result + } +} diff --git a/exercises/practice/secret-handshake/.meta/tests.toml b/exercises/practice/secret-handshake/.meta/tests.toml new file mode 100644 index 000000000..f318e5283 --- /dev/null +++ b/exercises/practice/secret-handshake/.meta/tests.toml @@ -0,0 +1,43 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[b8496fbd-6778-468c-8054-648d03c4bb23] +description = "wink for 1" + +[83ec6c58-81a9-4fd1-bfaf-0160514fc0e3] +description = "double blink for 10" + +[0e20e466-3519-4134-8082-5639d85fef71] +description = "close your eyes for 100" + +[b339ddbb-88b7-4b7d-9b19-4134030d9ac0] +description = "jump for 1000" + +[40499fb4-e60c-43d7-8b98-0de3ca44e0eb] +description = "combine two actions" + +[9730cdd5-ef27-494b-afd3-5c91ad6c3d9d] +description = "reverse two actions" + +[0b828205-51ca-45cd-90d5-f2506013f25f] +description = "reversing one action gives the same action" + +[9949e2ac-6c9c-4330-b685-2089ab28b05f] +description = "reversing no actions still gives no actions" + +[23fdca98-676b-4848-970d-cfed7be39f81] +description = "all possible actions" + +[ae8fe006-d910-4d6f-be00-54b7c3799e79] +description = "reverse all possible actions" + +[3d36da37-b31f-4cdb-a396-d93a2ee1c4a5] +description = "do nothing for zero" diff --git a/exercises/practice/secret-handshake/Cargo.toml b/exercises/practice/secret-handshake/Cargo.toml new file mode 100644 index 000000000..029d1c91e --- /dev/null +++ b/exercises/practice/secret-handshake/Cargo.toml @@ -0,0 +1,4 @@ +[package] +edition = "2021" +name = "secret-handshake" +version = "1.1.0" diff --git a/exercises/practice/secret-handshake/src/lib.rs b/exercises/practice/secret-handshake/src/lib.rs new file mode 100644 index 000000000..1666587dd --- /dev/null +++ b/exercises/practice/secret-handshake/src/lib.rs @@ -0,0 +1,3 @@ +pub fn actions(n: u8) -> Vec<&'static str> { + unimplemented!("What is the secret handshake for {n}?") +} diff --git a/exercises/practice/secret-handshake/tests/secret-handshake.rs b/exercises/practice/secret-handshake/tests/secret-handshake.rs new file mode 100644 index 000000000..4fb6b59c5 --- /dev/null +++ b/exercises/practice/secret-handshake/tests/secret-handshake.rs @@ -0,0 +1,72 @@ +use secret_handshake::*; + +#[test] +fn wink_for_1() { + assert_eq!(actions(1), vec!["wink"]) +} + +#[test] +#[ignore] +fn double_blink_for_10() { + assert_eq!(actions(2), vec!["double blink"]) +} + +#[test] +#[ignore] +fn close_your_eyes_for_100() { + assert_eq!(actions(4), vec!["close your eyes"]) +} + +#[test] +#[ignore] +fn jump_for_1000() { + assert_eq!(actions(8), vec!["jump"]) +} + +#[test] +#[ignore] +fn combine_two_actions() { + assert_eq!(actions(3), vec!["wink", "double blink"]) +} + +#[test] +#[ignore] +fn reverse_two_actions() { + assert_eq!(actions(19), vec!["double blink", "wink"]) +} + +#[test] +#[ignore] +fn reversing_one_action_gives_the_same_action() { + assert_eq!(actions(24), vec!["jump"]) +} + +#[test] +#[ignore] +fn reversing_no_actions_still_gives_no_actions() { + assert_eq!(actions(16), Vec::<&'static str>::new()) +} + +#[test] +#[ignore] +fn all_possible_actions() { + assert_eq!( + actions(15), + vec!["wink", "double blink", "close your eyes", "jump"] + ) +} + +#[test] +#[ignore] +fn reverse_all_possible_actions() { + assert_eq!( + actions(31), + vec!["jump", "close your eyes", "double blink", "wink"] + ) +} + +#[test] +#[ignore] +fn do_nothing_for_zero() { + assert_eq!(actions(0), Vec::<&'static str>::new()) +} From 3a468ae6b66379dafc955a9e006e0f3296934434 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Wed, 1 Mar 2023 09:01:02 +0100 Subject: [PATCH 004/436] Add a basic working script --- bin/generate_practice_exercise.sh | 104 ++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100755 bin/generate_practice_exercise.sh diff --git a/bin/generate_practice_exercise.sh b/bin/generate_practice_exercise.sh new file mode 100755 index 000000000..c0b03dd4a --- /dev/null +++ b/bin/generate_practice_exercise.sh @@ -0,0 +1,104 @@ +#!/usr/bin/env bash + +# Exit if anything fails. +set -euo pipefail + +# If argument not provided, print usage and exit +if [ $# -ne 1 ]; then + echo "Usage: bin/generate_practice_exercise.sh " + exit 1 +fi + +# Check if sed is gnu-sed +if ! sed --version | grep -q "GNU sed"; then + echo "GNU sed is required. Please install it and make sure it's in your PATH." + exit 1 +fi + +# Check if jq and curl are installed +command -v jq >/dev/null 2>&1 || { echo >&2 "jq is required but not installed. Aborting."; exit 1; } +command -v curl >/dev/null 2>&1 || { echo >&2 "curl is required but not installed. Aborting."; exit 1; } + +# Shows a success message if process is successful +function success() { + if [ $? -eq 0 ]; then + printf "\033[32m%s\033[0m\n" "[success]: $1" + fi +} + +function all_done() { + if [ $? -eq 0 ]; then + printf "\033[1;32m%s\033[0m\n" "[done]: $1" + fi +} + + + +SLUG="$1" +EXERCISE_DIR="exercises/practice/${SLUG}" + + +echo "Creating Rust files" +cargo new --lib "$EXERCISE_DIR" -q +mkdir -p ${EXERCISE_DIR}/tests + +cat < "$EXERCISE_DIR"/.gitignore +# Generated by Cargo +# Will have compiled files and executables +/target/ +**/*.rs.bk + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock +EOT +success "Created Rust files, tests dir and updated gitignore!" + +download() { + local FILE="$1" + local URL="$2" + curl --silent --show-error --fail --retry 3 --max-time 3 \ + --output "$FILE" "$URL" +} + +# build configlet +echo "Fetching configlet" +./bin/fetch-configlet +success "Fetched configlet successfully!" + + +# Preparing config.json +echo "Adding instructions and configuration files..." +UUID=$(bin/configlet uuid) +jq --arg slug "$SLUG" --arg uuid "$UUID" \ +'.exercises.practice += [{slug: $slug, name: "TODO", uuid: $uuid, practices: [], prerequisites: [], difficulty: 5}]' \ +config.json > config.json.tmp +# jq always rounds whole numbers, but average_run_time needs to be a float +sed -i 's/"average_run_time": \([0-9]\+\)$/"average_run_time": \1.0/' config.json.tmp +mv config.json.tmp config.json +success "Added instructions and configuration files successfully!" + +# Create instructions and config files +echo "Creating instructions and config files" +./bin/configlet sync --update --yes --docs --metadata --exercise "$SLUG" +./bin/configlet sync --update --yes --filepaths --exercise "$SLUG" +./bin/configlet sync --update --tests include --exercise "$SLUG" +success "Created instructions and config files" + + + +NAME=$(echo $SLUG | sed 's/-/_/g' ) +sed -i "s/name = \".*\"/name = \"$NAME\"/" "$EXERCISE_DIR"/Cargo.toml + + + +echo +# Prints a line of dashes that's as wide as the screen +cols=$(tput cols) +printf "%*s\n" $cols | tr " " "-" +echo + +all_done "All stub files were created." + +echo "After implementing the solution, tests and configuration, please run:" +echo "./bin/configlet fmt --update --yes --exercise ${SLUG}" \ No newline at end of file From cc19e31b32091d733951fb00355f74788b5dc2b7 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Wed, 1 Mar 2023 09:23:09 +0100 Subject: [PATCH 005/436] Add trailing empty lines --- bin/generate_practice_exercise.sh | 2 +- exercises/practice/secret-handshake/.meta/example.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/bin/generate_practice_exercise.sh b/bin/generate_practice_exercise.sh index c0b03dd4a..0e5b4a89c 100755 --- a/bin/generate_practice_exercise.sh +++ b/bin/generate_practice_exercise.sh @@ -101,4 +101,4 @@ echo all_done "All stub files were created." echo "After implementing the solution, tests and configuration, please run:" -echo "./bin/configlet fmt --update --yes --exercise ${SLUG}" \ No newline at end of file +echo "./bin/configlet fmt --update --yes --exercise ${SLUG}" diff --git a/exercises/practice/secret-handshake/.meta/example.rs b/exercises/practice/secret-handshake/.meta/example.rs index a881543cd..00a25e6b1 100644 --- a/exercises/practice/secret-handshake/.meta/example.rs +++ b/exercises/practice/secret-handshake/.meta/example.rs @@ -14,5 +14,4 @@ pub fn actions(n: u8) -> Vec<&'static str> { result } } - \ No newline at end of file From f2e997956476119d948eaf7c639a30456e538580 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Wed, 1 Mar 2023 09:48:37 +0100 Subject: [PATCH 006/436] Improve message util function --- bin/generate_practice_exercise.sh | 57 ++++++++++++------- .../secret-handshake/.meta/example.rs | 1 - 2 files changed, 35 insertions(+), 23 deletions(-) diff --git a/bin/generate_practice_exercise.sh b/bin/generate_practice_exercise.sh index 0e5b4a89c..d7a45aada 100755 --- a/bin/generate_practice_exercise.sh +++ b/bin/generate_practice_exercise.sh @@ -19,21 +19,40 @@ fi command -v jq >/dev/null 2>&1 || { echo >&2 "jq is required but not installed. Aborting."; exit 1; } command -v curl >/dev/null 2>&1 || { echo >&2 "curl is required but not installed. Aborting."; exit 1; } -# Shows a success message if process is successful -function success() { - if [ $? -eq 0 ]; then - printf "\033[32m%s\033[0m\n" "[success]: $1" - fi -} -function all_done() { - if [ $? -eq 0 ]; then - printf "\033[1;32m%s\033[0m\n" "[done]: $1" - fi +function message() { + local flag=$1 + local message=$2 + + case "$flag" in + "success") + if [ $? -eq 0 ]; then + printf "\033[32m%s\033[0m\n" "[success]: $message" + fi + ;; + "info") + if [ $? -eq 0 ]; then + printf "\033[34m%s\033[0m\n" "[info]: $message" + fi + ;; + "done") + echo + cols=$(tput cols) + printf "%*s\n" $cols | tr " " "-" + echo + if [ $? -eq 0 ]; then + printf "\033[1;32m%s\033[0m\n" "[done]: $message" + fi + ;; + *) + echo "Invalid flag: $flag" + ;; + esac } + SLUG="$1" EXERCISE_DIR="exercises/practice/${SLUG}" @@ -52,7 +71,7 @@ cat < "$EXERCISE_DIR"/.gitignore # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock Cargo.lock EOT -success "Created Rust files, tests dir and updated gitignore!" +message "success" "Created Rust files, tests dir and updated gitignore!" download() { local FILE="$1" @@ -62,9 +81,8 @@ download() { } # build configlet -echo "Fetching configlet" ./bin/fetch-configlet -success "Fetched configlet successfully!" +message "success" "Fetched configlet successfully!" # Preparing config.json @@ -76,14 +94,14 @@ config.json > config.json.tmp # jq always rounds whole numbers, but average_run_time needs to be a float sed -i 's/"average_run_time": \([0-9]\+\)$/"average_run_time": \1.0/' config.json.tmp mv config.json.tmp config.json -success "Added instructions and configuration files successfully!" +message "success" "Added instructions and configuration files successfully!" # Create instructions and config files echo "Creating instructions and config files" ./bin/configlet sync --update --yes --docs --metadata --exercise "$SLUG" ./bin/configlet sync --update --yes --filepaths --exercise "$SLUG" ./bin/configlet sync --update --tests include --exercise "$SLUG" -success "Created instructions and config files" +message "success" "Created instructions and config files" @@ -92,13 +110,8 @@ sed -i "s/name = \".*\"/name = \"$NAME\"/" "$EXERCISE_DIR"/Cargo.toml -echo -# Prints a line of dashes that's as wide as the screen -cols=$(tput cols) -printf "%*s\n" $cols | tr " " "-" -echo -all_done "All stub files were created." +message "done" "All stub files were created." -echo "After implementing the solution, tests and configuration, please run:" +message "info" "After implementing the solution, tests and configuration, please run:" echo "./bin/configlet fmt --update --yes --exercise ${SLUG}" diff --git a/exercises/practice/secret-handshake/.meta/example.rs b/exercises/practice/secret-handshake/.meta/example.rs index 00a25e6b1..88afcfad5 100644 --- a/exercises/practice/secret-handshake/.meta/example.rs +++ b/exercises/practice/secret-handshake/.meta/example.rs @@ -14,4 +14,3 @@ pub fn actions(n: u8) -> Vec<&'static str> { result } } - \ No newline at end of file From ea5874b693d49b6c75a99d046d4c4fdabf606d5c Mon Sep 17 00:00:00 2001 From: dem4ron Date: Wed, 1 Mar 2023 10:13:22 +0100 Subject: [PATCH 007/436] Improve message fn more --- bin/generate_practice_exercise.sh | 34 ++++++++++++++----------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/bin/generate_practice_exercise.sh b/bin/generate_practice_exercise.sh index d7a45aada..cd5128e06 100755 --- a/bin/generate_practice_exercise.sh +++ b/bin/generate_practice_exercise.sh @@ -23,31 +23,27 @@ command -v curl >/dev/null 2>&1 || { echo >&2 "curl is required but not installe function message() { local flag=$1 local message=$2 - - case "$flag" in - "success") - if [ $? -eq 0 ]; then + + if [ $? -eq 0 ]; then + case "$flag" in + "success") printf "\033[32m%s\033[0m\n" "[success]: $message" - fi ;; - "info") - if [ $? -eq 0 ]; then + "info") printf "\033[34m%s\033[0m\n" "[info]: $message" - fi ;; - "done") - echo - cols=$(tput cols) - printf "%*s\n" $cols | tr " " "-" - echo - if [ $? -eq 0 ]; then + "done") + echo + cols=$(tput cols) + printf "%*s\n" "$cols" | tr " " "-" + echo printf "\033[1;32m%s\033[0m\n" "[done]: $message" - fi ;; - *) - echo "Invalid flag: $flag" + *) + echo "Invalid flag: $flag" ;; - esac + esac + fi } @@ -105,7 +101,7 @@ message "success" "Created instructions and config files" -NAME=$(echo $SLUG | sed 's/-/_/g' ) +NAME=$(echo "$SLUG" | sed 's/-/_/g' ) sed -i "s/name = \".*\"/name = \"$NAME\"/" "$EXERCISE_DIR"/Cargo.toml From 94afa403d4893c2df9366d75312285d1d750532e Mon Sep 17 00:00:00 2001 From: dem4ron Date: Wed, 1 Mar 2023 10:57:29 +0100 Subject: [PATCH 008/436] Obey some lint rules, disable others --- bin/generate_practice_exercise.sh | 41 +++++++++++++++---------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/bin/generate_practice_exercise.sh b/bin/generate_practice_exercise.sh index cd5128e06..e4f18b763 100755 --- a/bin/generate_practice_exercise.sh +++ b/bin/generate_practice_exercise.sh @@ -24,26 +24,24 @@ function message() { local flag=$1 local message=$2 - if [ $? -eq 0 ]; then - case "$flag" in - "success") - printf "\033[32m%s\033[0m\n" "[success]: $message" - ;; - "info") - printf "\033[34m%s\033[0m\n" "[info]: $message" - ;; - "done") - echo - cols=$(tput cols) - printf "%*s\n" "$cols" | tr " " "-" - echo - printf "\033[1;32m%s\033[0m\n" "[done]: $message" - ;; - *) - echo "Invalid flag: $flag" - ;; - esac - fi + case "$flag" in + "success") + printf "\033[32m%s\033[0m\n" "[success]: $message" + ;; + "info") + printf "\033[34m%s\033[0m\n" "[info]: $message" + ;; + "done") + echo + cols=$(tput cols) + printf "%*s\n" "$cols" "" | tr " " "-" + echo + printf "\033[1;32m%s\033[0m\n" "[done]: $message" + ;; + *) + echo "Invalid flag: $flag" + ;; + esac } @@ -55,7 +53,7 @@ EXERCISE_DIR="exercises/practice/${SLUG}" echo "Creating Rust files" cargo new --lib "$EXERCISE_DIR" -q -mkdir -p ${EXERCISE_DIR}/tests +mkdir -p "${EXERCISE_DIR}"/tests cat < "$EXERCISE_DIR"/.gitignore # Generated by Cargo @@ -101,6 +99,7 @@ message "success" "Created instructions and config files" +# shellcheck disable=SC2001 NAME=$(echo "$SLUG" | sed 's/-/_/g' ) sed -i "s/name = \".*\"/name = \"$NAME\"/" "$EXERCISE_DIR"/Cargo.toml From 2dbee83bfaaa2135a18de836687f03e1c5307206 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Wed, 1 Mar 2023 11:36:31 +0100 Subject: [PATCH 009/436] Add remove trailing whitespace script --- bin/generate_practice_exercise.sh | 2 +- bin/remove_trailing_whitespace.sh | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100755 bin/remove_trailing_whitespace.sh diff --git a/bin/generate_practice_exercise.sh b/bin/generate_practice_exercise.sh index e4f18b763..dc93d3840 100755 --- a/bin/generate_practice_exercise.sh +++ b/bin/generate_practice_exercise.sh @@ -23,7 +23,7 @@ command -v curl >/dev/null 2>&1 || { echo >&2 "curl is required but not installe function message() { local flag=$1 local message=$2 - + case "$flag" in "success") printf "\033[32m%s\033[0m\n" "[success]: $message" diff --git a/bin/remove_trailing_whitespace.sh b/bin/remove_trailing_whitespace.sh new file mode 100755 index 000000000..5a93912f7 --- /dev/null +++ b/bin/remove_trailing_whitespace.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +# removes all trailing whitespaces from *.sh and *.py files in this folder +find . -type f \( -name "*.sh" -o -name "*.py" \) -exec sed -i 's/[[:space:]]\+$//' {} \; From a7591575ac778ce2f920685dddd34c0fbdead1e2 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Wed, 1 Mar 2023 13:59:46 +0100 Subject: [PATCH 010/436] Add difficulty, exercise name prompts, create example file, tests file --- bin/generate_practice_exercise.sh | 85 +++++++++++++++++++++++++++---- 1 file changed, 74 insertions(+), 11 deletions(-) diff --git a/bin/generate_practice_exercise.sh b/bin/generate_practice_exercise.sh index dc93d3840..b1736f506 100755 --- a/bin/generate_practice_exercise.sh +++ b/bin/generate_practice_exercise.sh @@ -44,16 +44,71 @@ function message() { esac } +function dash_to_underscore(){ + # shellcheck disable=SC2001 + echo "$1" | sed 's/-/_/g' +} + + +# by default capitalizes the words and replaces dashes with whitespace +default_exercise_name=$(echo "$1" | sed 's/-/ /g; s/\b\(.\)/\u\1/g') +printf "Please provide a display name for the exercise or press enter if default is fine for you.\nDefault: %s\nNew name: " "${default_exercise_name}" +read -er exercise_name +function prompt_exercise_name(){ + + # If the user didn't input anything, set exercise_name to a pregenerated default + if [[ -z "$exercise_name" ]]; then + exercise_name="$default_exercise_name" + fi + message "info" "You entered: $exercise_name. You can edit this later in config.json" + + echo "$exercise_name" + +} + +function prompt_exercise_difficulty(){ + valid_input=false + while ! $valid_input; do + read -rp "Difficulty of exercise (1-10): " difficulty + if [[ "$difficulty" =~ ^[1-9]$|^10$ ]]; then + valid_input=true + else + printf "Invalid input. Please enter an integer between 1 and 10." + fi + done + + message "info" "Difficulty is set to $difficulty. You can edit this later in config.json" + + echo "$difficulty" +} + SLUG="$1" +UNDERSCORED_SLUG=$(dash_to_underscore "$SLUG") EXERCISE_DIR="exercises/practice/${SLUG}" +AUTHOR_NAME="$(git config --get-regexp user.name | sed 's/user.name //g')" + +echo "Author name: ${AUTHOR_NAME}" echo "Creating Rust files" cargo new --lib "$EXERCISE_DIR" -q -mkdir -p "${EXERCISE_DIR}"/tests +mkdir -p "$EXERCISE_DIR"/tests +touch "${EXERCISE_DIR}/tests/${SLUG}.rs" + +cat < "${EXERCISE_DIR}/tests/${SLUG}.rs" +use ${UNDERSCORED_SLUG}::* +// Add tests here +EOT + + +cat < "${EXERCISE_DIR}/src/lib.rs" +fn ${UNDERSCORED_SLUG}(){ + unimplemented!("implement ${SLUG} exercise") +} +EOT cat < "$EXERCISE_DIR"/.gitignore # Generated by Cargo @@ -67,6 +122,17 @@ Cargo.lock EOT message "success" "Created Rust files, tests dir and updated gitignore!" + +mkdir "${EXERCISE_DIR}/.meta" +touch "${EXERCISE_DIR}/.meta/example.rs" +cat < "${EXERCISE_DIR}/.meta/example.rs" +// Create a solution that passes all the tests +EOT +message "success" "Created example.rs file" + + +# ================================================== + download() { local FILE="$1" local URL="$2" @@ -82,13 +148,16 @@ message "success" "Fetched configlet successfully!" # Preparing config.json echo "Adding instructions and configuration files..." UUID=$(bin/configlet uuid) -jq --arg slug "$SLUG" --arg uuid "$UUID" \ -'.exercises.practice += [{slug: $slug, name: "TODO", uuid: $uuid, practices: [], prerequisites: [], difficulty: 5}]' \ +EXERCISE_NAME=$(prompt_exercise_name "$SLUG") +EXERCISE_DIFFICULTY=$(prompt_exercise_difficulty) + +jq --arg slug "$SLUG" --arg uuid "$UUID" --arg name "$EXERCISE_NAME" --arg difficulty "$EXERCISE_DIFFICULTY" \ +'.exercises.practice += [{slug: $slug, name: $name, uuid: $uuid, practices: [], prerequisites: [], difficulty: $difficulty}]' \ config.json > config.json.tmp # jq always rounds whole numbers, but average_run_time needs to be a float sed -i 's/"average_run_time": \([0-9]\+\)$/"average_run_time": \1.0/' config.json.tmp mv config.json.tmp config.json -message "success" "Added instructions and configuration files successfully!" +message "success" "Added instructions and configuration files" # Create instructions and config files echo "Creating instructions and config files" @@ -98,13 +167,7 @@ echo "Creating instructions and config files" message "success" "Created instructions and config files" - -# shellcheck disable=SC2001 -NAME=$(echo "$SLUG" | sed 's/-/_/g' ) -sed -i "s/name = \".*\"/name = \"$NAME\"/" "$EXERCISE_DIR"/Cargo.toml - - - +sed -i "s/name = \".*\"/name = \"$UNDERSCORED_SLUG\"/" "$EXERCISE_DIR"/Cargo.toml message "done" "All stub files were created." From 1a06a456c49303ec7d1d389027653e54d342056b Mon Sep 17 00:00:00 2001 From: dem4ron Date: Wed, 1 Mar 2023 14:13:45 +0100 Subject: [PATCH 011/436] Remove fns for now --- bin/generate_practice_exercise.sh | 55 +++++++++++++++---------------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/bin/generate_practice_exercise.sh b/bin/generate_practice_exercise.sh index b1736f506..90874ca3f 100755 --- a/bin/generate_practice_exercise.sh +++ b/bin/generate_practice_exercise.sh @@ -23,7 +23,7 @@ command -v curl >/dev/null 2>&1 || { echo >&2 "curl is required but not installe function message() { local flag=$1 local message=$2 - + case "$flag" in "success") printf "\033[32m%s\033[0m\n" "[success]: $message" @@ -51,36 +51,34 @@ function dash_to_underscore(){ # by default capitalizes the words and replaces dashes with whitespace -default_exercise_name=$(echo "$1" | sed 's/-/ /g; s/\b\(.\)/\u\1/g') -printf "Please provide a display name for the exercise or press enter if default is fine for you.\nDefault: %s\nNew name: " "${default_exercise_name}" -read -er exercise_name -function prompt_exercise_name(){ - - # If the user didn't input anything, set exercise_name to a pregenerated default - if [[ -z "$exercise_name" ]]; then - exercise_name="$default_exercise_name" - fi - message "info" "You entered: $exercise_name. You can edit this later in config.json" +#TODO: turn it into a function +DEFAULT_EXERCISE_NAME=$(echo "$1" | sed 's/-/ /g; s/\b\(.\)/\u\1/g') +printf "Please provide a display name for the exercise or press enter if default is fine for you.\nDefault: %s\nNew name: " "${DEFAULT_EXERCISE_NAME}" +read -er EXERCISE_NAME + +# If the user didn't input anything, set EXERCISE_NAME to a pregenerated default +if [[ -z "$EXERCISE_NAME" ]]; then + EXERCISE_NAME="$DEFAULT_EXERCISE_NAME" +fi +message "info" "You entered: $EXERCISE_NAME. You can edit this later in config.json" - echo "$exercise_name" -} -function prompt_exercise_difficulty(){ - valid_input=false - while ! $valid_input; do - read -rp "Difficulty of exercise (1-10): " difficulty - if [[ "$difficulty" =~ ^[1-9]$|^10$ ]]; then - valid_input=true - else - printf "Invalid input. Please enter an integer between 1 and 10." - fi - done +# function prompt_exercise_difficulty(){ +#TODO: fix this function +VALID_INPUT=false +while ! $VALID_INPUT; do + read -rp "Difficulty of exercise (1-10): " EXERCISE_DIFFICULTY + if [[ "$EXERCISE_DIFFICULTY" =~ ^[1-9]$|^10$ ]]; then + VALID_INPUT=true + else + printf "Invalid input. Please enter an integer between 1 and 10." + fi +done - message "info" "Difficulty is set to $difficulty. You can edit this later in config.json" +message "info" "EXERCISE_DIFFICULTY is set to $EXERCISE_DIFFICULTY. You can edit this later in config.json" - echo "$difficulty" -} +# } @@ -148,8 +146,9 @@ message "success" "Fetched configlet successfully!" # Preparing config.json echo "Adding instructions and configuration files..." UUID=$(bin/configlet uuid) -EXERCISE_NAME=$(prompt_exercise_name "$SLUG") -EXERCISE_DIFFICULTY=$(prompt_exercise_difficulty) +# FIX this +# EXERCISE_NAME=$(prompt_exercise_name "$SLUG") +# EXERCISE_DIFFICULTY=$(prompt_exercise_difficulty) jq --arg slug "$SLUG" --arg uuid "$UUID" --arg name "$EXERCISE_NAME" --arg difficulty "$EXERCISE_DIFFICULTY" \ '.exercises.practice += [{slug: $slug, name: $name, uuid: $uuid, practices: [], prerequisites: [], difficulty: $difficulty}]' \ From 6aed89431aa917352aea1686f998805a7a2bbfee Mon Sep 17 00:00:00 2001 From: dem4ron Date: Wed, 1 Mar 2023 14:16:02 +0100 Subject: [PATCH 012/436] Remove tr ws --- bin/generate_practice_exercise.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/generate_practice_exercise.sh b/bin/generate_practice_exercise.sh index 90874ca3f..94f5cf168 100755 --- a/bin/generate_practice_exercise.sh +++ b/bin/generate_practice_exercise.sh @@ -23,7 +23,7 @@ command -v curl >/dev/null 2>&1 || { echo >&2 "curl is required but not installe function message() { local flag=$1 local message=$2 - + case "$flag" in "success") printf "\033[32m%s\033[0m\n" "[success]: $message" From 2c61ef40ed510fc8755a26fd0660933ccb1c9660 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Thu, 2 Mar 2023 17:35:30 +0100 Subject: [PATCH 013/436] Clean up code --- bin/generate_practice_exercise.sh | 86 ++++--------------------------- bin/generator-utils/colors.sh | 38 ++++++++++++++ bin/generator-utils/prompts.sh | 44 ++++++++++++++++ bin/generator-utils/utils.sh | 35 +++++++++++++ 4 files changed, 128 insertions(+), 75 deletions(-) create mode 100644 bin/generator-utils/colors.sh create mode 100644 bin/generator-utils/prompts.sh create mode 100755 bin/generator-utils/utils.sh diff --git a/bin/generate_practice_exercise.sh b/bin/generate_practice_exercise.sh index 94f5cf168..a2616f1d8 100755 --- a/bin/generate_practice_exercise.sh +++ b/bin/generate_practice_exercise.sh @@ -1,5 +1,9 @@ #!/usr/bin/env bash +# shellcheck source=/dev/null +source ./bin/generator-utils/utils.sh; +source ./bin/generator-utils/prompts.sh; + # Exit if anything fails. set -euo pipefail @@ -19,76 +23,18 @@ fi command -v jq >/dev/null 2>&1 || { echo >&2 "jq is required but not installed. Aborting."; exit 1; } command -v curl >/dev/null 2>&1 || { echo >&2 "curl is required but not installed. Aborting."; exit 1; } - -function message() { - local flag=$1 - local message=$2 - - case "$flag" in - "success") - printf "\033[32m%s\033[0m\n" "[success]: $message" - ;; - "info") - printf "\033[34m%s\033[0m\n" "[info]: $message" - ;; - "done") - echo - cols=$(tput cols) - printf "%*s\n" "$cols" "" | tr " " "-" - echo - printf "\033[1;32m%s\033[0m\n" "[done]: $message" - ;; - *) - echo "Invalid flag: $flag" - ;; - esac -} - -function dash_to_underscore(){ - # shellcheck disable=SC2001 - echo "$1" | sed 's/-/_/g' -} - - -# by default capitalizes the words and replaces dashes with whitespace -#TODO: turn it into a function -DEFAULT_EXERCISE_NAME=$(echo "$1" | sed 's/-/ /g; s/\b\(.\)/\u\1/g') -printf "Please provide a display name for the exercise or press enter if default is fine for you.\nDefault: %s\nNew name: " "${DEFAULT_EXERCISE_NAME}" -read -er EXERCISE_NAME - -# If the user didn't input anything, set EXERCISE_NAME to a pregenerated default -if [[ -z "$EXERCISE_NAME" ]]; then - EXERCISE_NAME="$DEFAULT_EXERCISE_NAME" -fi -message "info" "You entered: $EXERCISE_NAME. You can edit this later in config.json" - - - -# function prompt_exercise_difficulty(){ -#TODO: fix this function -VALID_INPUT=false -while ! $VALID_INPUT; do - read -rp "Difficulty of exercise (1-10): " EXERCISE_DIFFICULTY - if [[ "$EXERCISE_DIFFICULTY" =~ ^[1-9]$|^10$ ]]; then - VALID_INPUT=true - else - printf "Invalid input. Please enter an integer between 1 and 10." - fi -done - -message "info" "EXERCISE_DIFFICULTY is set to $EXERCISE_DIFFICULTY. You can edit this later in config.json" - -# } - - +# ================================================== SLUG="$1" UNDERSCORED_SLUG=$(dash_to_underscore "$SLUG") EXERCISE_DIR="exercises/practice/${SLUG}" -AUTHOR_NAME="$(git config --get-regexp user.name | sed 's/user.name //g')" - -echo "Author name: ${AUTHOR_NAME}" +AUTHOR_NAME=$(get_author_name) +message "info" "You entered: $AUTHOR_NAME. You can edit this later in .meta/config.json.authors" +EXERCISE_NAME=$(get_exercise_name "$SLUG") +message "info" "You entered: $EXERCISE_NAME. You can edit this later in config.json" +EXERCISE_DIFFICULTY=$(get_exercise_difficulty) +message "info" "EXERCISE_DIFFICULTY is set to $EXERCISE_DIFFICULTY. You can edit this later in config.json" echo "Creating Rust files" @@ -131,13 +77,6 @@ message "success" "Created example.rs file" # ================================================== -download() { - local FILE="$1" - local URL="$2" - curl --silent --show-error --fail --retry 3 --max-time 3 \ - --output "$FILE" "$URL" -} - # build configlet ./bin/fetch-configlet message "success" "Fetched configlet successfully!" @@ -146,9 +85,6 @@ message "success" "Fetched configlet successfully!" # Preparing config.json echo "Adding instructions and configuration files..." UUID=$(bin/configlet uuid) -# FIX this -# EXERCISE_NAME=$(prompt_exercise_name "$SLUG") -# EXERCISE_DIFFICULTY=$(prompt_exercise_difficulty) jq --arg slug "$SLUG" --arg uuid "$UUID" --arg name "$EXERCISE_NAME" --arg difficulty "$EXERCISE_DIFFICULTY" \ '.exercises.practice += [{slug: $slug, name: $name, uuid: $uuid, practices: [], prerequisites: [], difficulty: $difficulty}]' \ diff --git a/bin/generator-utils/colors.sh b/bin/generator-utils/colors.sh new file mode 100644 index 000000000..3ae992587 --- /dev/null +++ b/bin/generator-utils/colors.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + +# shellcheck disable=SC2034 +# Reset +RESET=$(echo -e '\033[0m') + +# Regular Colors +BLACK=$(echo -e '\033[0;30m') +RED=$(echo -e '\033[0;31m') +GREEN=$(echo -e '\033[0;32m') +YELLOW=$(echo -e '\033[0;33m') +BLUE=$(echo -e '\033[0;34m') +PURPLE=$(echo -e '\033[0;35m') +CYAN=$(echo -e '\033[0;36m') +WHITE=$(echo -e '\033[0;37m') + +# Bold +BOLD_BLACK=$(echo -e '\033[1;30m') +BOLD_RED=$(echo -e '\033[1;31m') +BOLD_GREEN=$(echo -e '\033[1;32m') +BOLD_YELLOW=$(echo -e '\033[1;33m') +BOLD_BLUE=$(echo -e '\033[1;34m') +BOLD_PURPLE=$(echo -e '\033[1;35m') +BOLD_CYAN=$(echo -e '\033[1;36m') +BOLD_WHITE=$(echo -e '\033[1;37m') + +# Underline +UNDERLINE=$(echo -e ='\033[4m') + +# Background +BG_BLACK=$(echo -e ='\033[40m') +BG_RED=$(echo -e ='\033[41m') +BG_GREEN=$(echo -e ='\033[42m') +BG_YELLOW=$(echo -e ='\033[43m') +BG_BLUE=$(echo -e ='\033[44m') +BG_PURPLE=$(echo -e ='\033[45m') +BG_CYAN=$(echo -e ='\033[46m') +BG_WHITE=$(echo -e ='\033[47m') diff --git a/bin/generator-utils/prompts.sh b/bin/generator-utils/prompts.sh new file mode 100644 index 000000000..935ccde91 --- /dev/null +++ b/bin/generator-utils/prompts.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash + +# shellcheck source=/dev/null +source ./bin/generator-utils/colors.sh + +function get_exercise_difficulty() { + local VALID_INPUT=false + while ! $VALID_INPUT; do + read -rp "Difficulty of exercise (1-10): " EXERCISE_DIFFICULTY + if [[ "$EXERCISE_DIFFICULTY" =~ ^[1-9]$|^10$ ]]; then + VALID_INPUT=true + else + printf "Invalid input. Please enter an integer between 1 and 10." + fi + done + echo "$EXERCISE_DIFFICULTY" +} + + + +function get_exercise_name { + DEFAULT_EXERCISE_NAME=$(echo "$1" | sed 's/-/ /g; s/\b\(.\)/\u\1/g') + read -rp "Enter exercise name or use default (${YELLOW}${DEFAULT_EXERCISE_NAME}${RESET}): " EXERCISE_NAME + + # If the user didn't input anything, set EXERCISE_NAME to a pregenerated default + if [[ -z "$EXERCISE_NAME" ]]; then + EXERCISE_NAME="$DEFAULT_EXERCISE_NAME" + fi + + echo "$EXERCISE_NAME" +} + + +function get_author_name { + DEFAULT_AUTHOR_NAME="$(git config --get-regexp user.name | sed 's/user.name //g')" + read -rp "Hey! Are you ${YELLOW}${DEFAULT_AUTHOR_NAME}${RESET}? If not, please enter your github handle: " AUTHOR_NAME + + # If the user didn't input anything, set AUTHOR_NAME to a pregenerated default + if [[ -z "$AUTHOR_NAME" ]]; then + AUTHOR_NAME="$DEFAULT_AUTHOR_NAME" + fi + + echo "$AUTHOR_NAME" +} diff --git a/bin/generator-utils/utils.sh b/bin/generator-utils/utils.sh new file mode 100755 index 000000000..33dd60841 --- /dev/null +++ b/bin/generator-utils/utils.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash + +# shellcheck source=/dev/null +source ./bin/generator-utils/colors.sh + +function message() { + local flag=$1 + local message=$2 + + case "$flag" in + "success") + printf "${GREEN}%s${RESET}\n" "[success]: $message" + ;; + "info") + printf "${BLUE}%s${RESET}\n" "[info]: $message" + ;; + "done") + echo + cols=$(tput cols) + printf "%*s\n" "$cols" "" | tr " " "-" + echo + printf "${BOLD_GREEN}%s${RESET}\n" "[done]: $message" + ;; + *) + echo "Invalid flag: $flag" + ;; + esac +} + + + +function dash_to_underscore(){ + # shellcheck disable=SC2001 + echo "$1" | sed 's/-/_/g' +} \ No newline at end of file From 19d835205d5aa233ca88b03fa444be7e6feb2a18 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Thu, 2 Mar 2023 17:36:53 +0100 Subject: [PATCH 014/436] Remove trailing whitespace --- bin/generator-utils/prompts.sh | 8 ++++---- bin/generator-utils/utils.sh | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bin/generator-utils/prompts.sh b/bin/generator-utils/prompts.sh index 935ccde91..ea32a5f8f 100644 --- a/bin/generator-utils/prompts.sh +++ b/bin/generator-utils/prompts.sh @@ -21,12 +21,12 @@ function get_exercise_difficulty() { function get_exercise_name { DEFAULT_EXERCISE_NAME=$(echo "$1" | sed 's/-/ /g; s/\b\(.\)/\u\1/g') read -rp "Enter exercise name or use default (${YELLOW}${DEFAULT_EXERCISE_NAME}${RESET}): " EXERCISE_NAME - + # If the user didn't input anything, set EXERCISE_NAME to a pregenerated default if [[ -z "$EXERCISE_NAME" ]]; then EXERCISE_NAME="$DEFAULT_EXERCISE_NAME" fi - + echo "$EXERCISE_NAME" } @@ -34,11 +34,11 @@ function get_exercise_name { function get_author_name { DEFAULT_AUTHOR_NAME="$(git config --get-regexp user.name | sed 's/user.name //g')" read -rp "Hey! Are you ${YELLOW}${DEFAULT_AUTHOR_NAME}${RESET}? If not, please enter your github handle: " AUTHOR_NAME - + # If the user didn't input anything, set AUTHOR_NAME to a pregenerated default if [[ -z "$AUTHOR_NAME" ]]; then AUTHOR_NAME="$DEFAULT_AUTHOR_NAME" fi - + echo "$AUTHOR_NAME" } diff --git a/bin/generator-utils/utils.sh b/bin/generator-utils/utils.sh index 33dd60841..037af3ac6 100755 --- a/bin/generator-utils/utils.sh +++ b/bin/generator-utils/utils.sh @@ -6,7 +6,7 @@ source ./bin/generator-utils/colors.sh function message() { local flag=$1 local message=$2 - + case "$flag" in "success") printf "${GREEN}%s${RESET}\n" "[success]: $message" From 454f650a92ed151ee9a9a487016301ebd66bf632 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Thu, 2 Mar 2023 23:22:48 +0100 Subject: [PATCH 015/436] Add canonical-data parser --- bin/generate_practice_exercise.sh | 10 ++++- bin/generator-utils/parse_canonical_data.sh | 50 +++++++++++++++++++++ 2 files changed, 58 insertions(+), 2 deletions(-) create mode 100755 bin/generator-utils/parse_canonical_data.sh diff --git a/bin/generate_practice_exercise.sh b/bin/generate_practice_exercise.sh index a2616f1d8..fbddbe19d 100755 --- a/bin/generate_practice_exercise.sh +++ b/bin/generate_practice_exercise.sh @@ -3,6 +3,7 @@ # shellcheck source=/dev/null source ./bin/generator-utils/utils.sh; source ./bin/generator-utils/prompts.sh; +source ./bin/generator-utils/parse_canonical_data.sh; # Exit if anything fails. set -euo pipefail @@ -40,13 +41,15 @@ message "info" "EXERCISE_DIFFICULTY is set to $EXERCISE_DIFFICULTY. You can edit echo "Creating Rust files" cargo new --lib "$EXERCISE_DIR" -q mkdir -p "$EXERCISE_DIR"/tests -touch "${EXERCISE_DIR}/tests/${SLUG}.rs" +TEST_FILE="${EXERCISE_DIR}/tests/${SLUG}.rs" +touch "$TEST_FILE" -cat < "${EXERCISE_DIR}/tests/${SLUG}.rs" +cat < "$TEST_FILE" use ${UNDERSCORED_SLUG}::* // Add tests here EOT +fill_test_file_with_canonical_data "$SLUG" "$TEST_FILE" cat < "${EXERCISE_DIR}/src/lib.rs" fn ${UNDERSCORED_SLUG}(){ @@ -101,6 +104,9 @@ echo "Creating instructions and config files" ./bin/configlet sync --update --tests include --exercise "$SLUG" message "success" "Created instructions and config files" +jq '.authors += ["$AUTHOR_NAME"]' "$EXERCISE_DIR"/.meta/config.json +message "success" "You've been added as the author of this exercise." + sed -i "s/name = \".*\"/name = \"$UNDERSCORED_SLUG\"/" "$EXERCISE_DIR"/Cargo.toml diff --git a/bin/generator-utils/parse_canonical_data.sh b/bin/generator-utils/parse_canonical_data.sh new file mode 100755 index 000000000..628ee3ce1 --- /dev/null +++ b/bin/generator-utils/parse_canonical_data.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash + +function fill_test_file_with_canonical_data() { + local slug=$1 + local test_dir=$2 + # fetches canonical_data + canonical_json=$(curl https://raw.githubusercontent.com/exercism/problem-specifications/main/exercises/"$slug"/canonical-data.json) + + if [ "${canonical_json}" == "404: Not Found" ]; then + canonical_json=$(jq --null-input '{cases: []}') + + cat <>"$test_dir" + +// This exercise doesn't have a canonical data file, which means you need to come up with tests +// If you came up with excellent tests, consider contributing to this repo: +// https://github.com/exercism/problem-specifications/tree/main/exercises/${slug} +EOT + fi + + # sometimes canonical data has multiple levels with multiple `cases` arrays. + # this "flattens" it + cases=$(echo "$canonical_json" | jq '[ .. | objects | with_entries(select(.key | IN("uuid", "description", "input", "expected"))) | select(. != {}) | select(has("uuid")) ]') + + first_iteration=true + jq -c '.[]' <<<"$cases" | while read -r case; do + desc=$(echo "$case" | jq '.description' | tr '[:upper:]' '[:lower:]' | tr ' ' '_' | tr -cd '[:alnum:]_' | sed 's/^/test_/') + input=$(echo "$case" | jq '.input') + expected=$(echo "$case" | jq '.expected') + + cat <>"$test_dir" +#[test] $([[ "$first_iteration" == false ]] && printf "\n#[ignore]") +fn ${desc}(){ + /* + + Input: + ${input} + + Expected output: + ${expected} + + */ + + // TODO: Add assertion + assert_eq!(1, 1) +} + +EOT + first_iteration=false + done +} From 7e5a033eeaf024a4a27a795576b7b2a56192a067 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Thu, 2 Mar 2023 23:48:11 +0100 Subject: [PATCH 016/436] Break out noisy templates into functions --- bin/generate_practice_exercise.sh | 56 ++++++------------- bin/generator-utils/prompts.sh | 3 - .../{parse_canonical_data.sh => templates.sh} | 42 ++++++++++++-- bin/generator-utils/utils.sh | 30 +++++----- 4 files changed, 70 insertions(+), 61 deletions(-) rename bin/generator-utils/{parse_canonical_data.sh => templates.sh} (60%) diff --git a/bin/generate_practice_exercise.sh b/bin/generate_practice_exercise.sh index fbddbe19d..788371b16 100755 --- a/bin/generate_practice_exercise.sh +++ b/bin/generate_practice_exercise.sh @@ -1,9 +1,9 @@ #!/usr/bin/env bash # shellcheck source=/dev/null -source ./bin/generator-utils/utils.sh; -source ./bin/generator-utils/prompts.sh; -source ./bin/generator-utils/parse_canonical_data.sh; +source ./bin/generator-utils/utils.sh +source ./bin/generator-utils/prompts.sh +source ./bin/generator-utils/templates.sh # Exit if anything fails. set -euo pipefail @@ -21,12 +21,17 @@ if ! sed --version | grep -q "GNU sed"; then fi # Check if jq and curl are installed -command -v jq >/dev/null 2>&1 || { echo >&2 "jq is required but not installed. Aborting."; exit 1; } -command -v curl >/dev/null 2>&1 || { echo >&2 "curl is required but not installed. Aborting."; exit 1; } +command -v jq >/dev/null 2>&1 || { + echo >&2 "jq is required but not installed. Aborting." + exit 1 +} +command -v curl >/dev/null 2>&1 || { + echo >&2 "curl is required but not installed. Aborting." + exit 1 +} # ================================================== - SLUG="$1" UNDERSCORED_SLUG=$(dash_to_underscore "$SLUG") EXERCISE_DIR="exercises/practice/${SLUG}" @@ -37,61 +42,37 @@ message "info" "You entered: $EXERCISE_NAME. You can edit this later in config.j EXERCISE_DIFFICULTY=$(get_exercise_difficulty) message "info" "EXERCISE_DIFFICULTY is set to $EXERCISE_DIFFICULTY. You can edit this later in config.json" - echo "Creating Rust files" cargo new --lib "$EXERCISE_DIR" -q mkdir -p "$EXERCISE_DIR"/tests -TEST_FILE="${EXERCISE_DIR}/tests/${SLUG}.rs" -touch "$TEST_FILE" - -cat < "$TEST_FILE" -use ${UNDERSCORED_SLUG}::* -// Add tests here -EOT +touch "${EXERCISE_DIR}/tests/${SLUG}.rs" -fill_test_file_with_canonical_data "$SLUG" "$TEST_FILE" +create_test_file_template "$SLUG" "$EXERCISE_DIR" +create_lib_rs_template "$SLUG" "$EXERCISE_DIR" +create_gitignore_template "$EXERCISE_DIR" -cat < "${EXERCISE_DIR}/src/lib.rs" -fn ${UNDERSCORED_SLUG}(){ - unimplemented!("implement ${SLUG} exercise") -} -EOT - -cat < "$EXERCISE_DIR"/.gitignore -# Generated by Cargo -# Will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock -EOT message "success" "Created Rust files, tests dir and updated gitignore!" - mkdir "${EXERCISE_DIR}/.meta" touch "${EXERCISE_DIR}/.meta/example.rs" -cat < "${EXERCISE_DIR}/.meta/example.rs" +cat <"${EXERCISE_DIR}/.meta/example.rs" // Create a solution that passes all the tests EOT message "success" "Created example.rs file" - # ================================================== # build configlet ./bin/fetch-configlet message "success" "Fetched configlet successfully!" - # Preparing config.json echo "Adding instructions and configuration files..." UUID=$(bin/configlet uuid) jq --arg slug "$SLUG" --arg uuid "$UUID" --arg name "$EXERCISE_NAME" --arg difficulty "$EXERCISE_DIFFICULTY" \ -'.exercises.practice += [{slug: $slug, name: $name, uuid: $uuid, practices: [], prerequisites: [], difficulty: $difficulty}]' \ -config.json > config.json.tmp + '.exercises.practice += [{slug: $slug, name: $name, uuid: $uuid, practices: [], prerequisites: [], difficulty: $difficulty}]' \ + config.json >config.json.tmp # jq always rounds whole numbers, but average_run_time needs to be a float sed -i 's/"average_run_time": \([0-9]\+\)$/"average_run_time": \1.0/' config.json.tmp mv config.json.tmp config.json @@ -107,7 +88,6 @@ message "success" "Created instructions and config files" jq '.authors += ["$AUTHOR_NAME"]' "$EXERCISE_DIR"/.meta/config.json message "success" "You've been added as the author of this exercise." - sed -i "s/name = \".*\"/name = \"$UNDERSCORED_SLUG\"/" "$EXERCISE_DIR"/Cargo.toml message "done" "All stub files were created." diff --git a/bin/generator-utils/prompts.sh b/bin/generator-utils/prompts.sh index ea32a5f8f..bf6453f72 100644 --- a/bin/generator-utils/prompts.sh +++ b/bin/generator-utils/prompts.sh @@ -16,8 +16,6 @@ function get_exercise_difficulty() { echo "$EXERCISE_DIFFICULTY" } - - function get_exercise_name { DEFAULT_EXERCISE_NAME=$(echo "$1" | sed 's/-/ /g; s/\b\(.\)/\u\1/g') read -rp "Enter exercise name or use default (${YELLOW}${DEFAULT_EXERCISE_NAME}${RESET}): " EXERCISE_NAME @@ -30,7 +28,6 @@ function get_exercise_name { echo "$EXERCISE_NAME" } - function get_author_name { DEFAULT_AUTHOR_NAME="$(git config --get-regexp user.name | sed 's/user.name //g')" read -rp "Hey! Are you ${YELLOW}${DEFAULT_AUTHOR_NAME}${RESET}? If not, please enter your github handle: " AUTHOR_NAME diff --git a/bin/generator-utils/parse_canonical_data.sh b/bin/generator-utils/templates.sh similarity index 60% rename from bin/generator-utils/parse_canonical_data.sh rename to bin/generator-utils/templates.sh index 628ee3ce1..cc22e019b 100755 --- a/bin/generator-utils/parse_canonical_data.sh +++ b/bin/generator-utils/templates.sh @@ -1,15 +1,25 @@ #!/usr/bin/env bash -function fill_test_file_with_canonical_data() { +# shellcheck source=/dev/null +source ./bin/generator-utils/utils.sh + +function create_test_file_template() { local slug=$1 - local test_dir=$2 + local exercise_dir=$2 + local test_file="${exercise_dir}/tests/${slug}.rs" + + cat <"$test_file" +use $(dash_to_underscore "$slug")::* +// Add tests here +EOT + # fetches canonical_data canonical_json=$(curl https://raw.githubusercontent.com/exercism/problem-specifications/main/exercises/"$slug"/canonical-data.json) if [ "${canonical_json}" == "404: Not Found" ]; then canonical_json=$(jq --null-input '{cases: []}') - cat <>"$test_dir" + cat <>"$test_file" // This exercise doesn't have a canonical data file, which means you need to come up with tests // If you came up with excellent tests, consider contributing to this repo: @@ -27,7 +37,7 @@ EOT input=$(echo "$case" | jq '.input') expected=$(echo "$case" | jq '.expected') - cat <>"$test_dir" + cat <>"$test_file" #[test] $([[ "$first_iteration" == false ]] && printf "\n#[ignore]") fn ${desc}(){ /* @@ -48,3 +58,27 @@ EOT first_iteration=false done } + +function create_lib_rs_template() { + local slug=$1 + local exercise_dir=$2 + cat <"${exercise_dir}/src/lib.rs" +fn $(dash_to_underscore "$slug")(){ + unimplemented!("implement ${slug} exercise") +} +EOT +} + +function create_gitignore_template() { + exercise_dir=$1 + cat <"$exercise_dir"/.gitignore +# Generated by Cargo +# Will have compiled files and executables +/target/ +**/*.rs.bk + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock +EOT +} diff --git a/bin/generator-utils/utils.sh b/bin/generator-utils/utils.sh index 037af3ac6..e28b8a7fd 100755 --- a/bin/generator-utils/utils.sh +++ b/bin/generator-utils/utils.sh @@ -8,28 +8,26 @@ function message() { local message=$2 case "$flag" in - "success") - printf "${GREEN}%s${RESET}\n" "[success]: $message" + "success") + printf "${GREEN}%s${RESET}\n" "[success]: $message" ;; - "info") - printf "${BLUE}%s${RESET}\n" "[info]: $message" + "info") + printf "${BLUE}%s${RESET}\n" "[info]: $message" ;; - "done") - echo - cols=$(tput cols) - printf "%*s\n" "$cols" "" | tr " " "-" - echo - printf "${BOLD_GREEN}%s${RESET}\n" "[done]: $message" + "done") + echo + cols=$(tput cols) + printf "%*s\n" "$cols" "" | tr " " "-" + echo + printf "${BOLD_GREEN}%s${RESET}\n" "[done]: $message" ;; - *) - echo "Invalid flag: $flag" + *) + echo "Invalid flag: $flag" ;; esac } - - -function dash_to_underscore(){ +function dash_to_underscore() { # shellcheck disable=SC2001 echo "$1" | sed 's/-/_/g' -} \ No newline at end of file +} From 65cb14d40aa3fff519786b54b4f50c98c7ac625b Mon Sep 17 00:00:00 2001 From: dem4ron Date: Fri, 3 Mar 2023 00:05:04 +0100 Subject: [PATCH 017/436] Add author automatically --- bin/generate_practice_exercise.sh | 7 ++++--- bin/generator-utils/templates.sh | 12 ++++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/bin/generate_practice_exercise.sh b/bin/generate_practice_exercise.sh index 788371b16..3a66c4938 100755 --- a/bin/generate_practice_exercise.sh +++ b/bin/generate_practice_exercise.sh @@ -47,8 +47,8 @@ cargo new --lib "$EXERCISE_DIR" -q mkdir -p "$EXERCISE_DIR"/tests touch "${EXERCISE_DIR}/tests/${SLUG}.rs" -create_test_file_template "$SLUG" "$EXERCISE_DIR" -create_lib_rs_template "$SLUG" "$EXERCISE_DIR" +create_test_file_template "$EXERCISE_DIR" "$SLUG" +create_lib_rs_template "$EXERCISE_DIR" "$SLUG" create_gitignore_template "$EXERCISE_DIR" message "success" "Created Rust files, tests dir and updated gitignore!" @@ -85,7 +85,8 @@ echo "Creating instructions and config files" ./bin/configlet sync --update --tests include --exercise "$SLUG" message "success" "Created instructions and config files" -jq '.authors += ["$AUTHOR_NAME"]' "$EXERCISE_DIR"/.meta/config.json +META_CONFIG="$EXERCISE_DIR"/.meta/config.json +jq --arg author "$AUTHOR_NAME" '.authors += [$author]' "$META_CONFIG" >"$META_CONFIG".tmp && mv "$META_CONFIG".tmp "$META_CONFIG" message "success" "You've been added as the author of this exercise." sed -i "s/name = \".*\"/name = \"$UNDERSCORED_SLUG\"/" "$EXERCISE_DIR"/Cargo.toml diff --git a/bin/generator-utils/templates.sh b/bin/generator-utils/templates.sh index cc22e019b..4052ffb96 100755 --- a/bin/generator-utils/templates.sh +++ b/bin/generator-utils/templates.sh @@ -4,12 +4,12 @@ source ./bin/generator-utils/utils.sh function create_test_file_template() { - local slug=$1 - local exercise_dir=$2 + local exercise_dir=$1 + local slug=$2 local test_file="${exercise_dir}/tests/${slug}.rs" cat <"$test_file" -use $(dash_to_underscore "$slug")::* +use $(dash_to_underscore "$slug")::*; // Add tests here EOT @@ -60,8 +60,8 @@ EOT } function create_lib_rs_template() { - local slug=$1 - local exercise_dir=$2 + local exercise_dir=$1 + local slug=$2 cat <"${exercise_dir}/src/lib.rs" fn $(dash_to_underscore "$slug")(){ unimplemented!("implement ${slug} exercise") @@ -70,7 +70,7 @@ EOT } function create_gitignore_template() { - exercise_dir=$1 + local exercise_dir=$1 cat <"$exercise_dir"/.gitignore # Generated by Cargo # Will have compiled files and executables From 558e4f270489d9f805f7fb640d187c1583aa8850 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Fri, 3 Mar 2023 00:11:11 +0100 Subject: [PATCH 018/436] Adjust template a bit --- bin/generator-utils/templates.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/bin/generator-utils/templates.sh b/bin/generator-utils/templates.sh index 4052ffb96..354c3ee11 100755 --- a/bin/generator-utils/templates.sh +++ b/bin/generator-utils/templates.sh @@ -41,13 +41,11 @@ EOT #[test] $([[ "$first_iteration" == false ]] && printf "\n#[ignore]") fn ${desc}(){ /* - Input: ${input} Expected output: ${expected} - */ // TODO: Add assertion From 87318f54a3dea116e0f3d9f5b23410be21bef960 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Fri, 3 Mar 2023 00:41:50 +0100 Subject: [PATCH 019/436] Break-out example creator, update no-canonical comment --- bin/generate_practice_exercise.sh | 11 +++-------- bin/generator-utils/templates.sh | 29 +++++++++++++++++++++++------ 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/bin/generate_practice_exercise.sh b/bin/generate_practice_exercise.sh index 3a66c4938..cfffe3a8f 100755 --- a/bin/generate_practice_exercise.sh +++ b/bin/generate_practice_exercise.sh @@ -49,16 +49,11 @@ touch "${EXERCISE_DIR}/tests/${SLUG}.rs" create_test_file_template "$EXERCISE_DIR" "$SLUG" create_lib_rs_template "$EXERCISE_DIR" "$SLUG" -create_gitignore_template "$EXERCISE_DIR" +create_example_rs_template "$EXERCISE_DIR" +overwrite_gitignore "$EXERCISE_DIR" -message "success" "Created Rust files, tests dir and updated gitignore!" +message "success" "Created Rust files succesfully!" -mkdir "${EXERCISE_DIR}/.meta" -touch "${EXERCISE_DIR}/.meta/example.rs" -cat <"${EXERCISE_DIR}/.meta/example.rs" -// Create a solution that passes all the tests -EOT -message "success" "Created example.rs file" # ================================================== diff --git a/bin/generator-utils/templates.sh b/bin/generator-utils/templates.sh index 354c3ee11..fffa5832e 100755 --- a/bin/generator-utils/templates.sh +++ b/bin/generator-utils/templates.sh @@ -11,32 +11,35 @@ function create_test_file_template() { cat <"$test_file" use $(dash_to_underscore "$slug")::*; // Add tests here + EOT - # fetches canonical_data + # fetch canonical_data canonical_json=$(curl https://raw.githubusercontent.com/exercism/problem-specifications/main/exercises/"$slug"/canonical-data.json) if [ "${canonical_json}" == "404: Not Found" ]; then canonical_json=$(jq --null-input '{cases: []}') cat <>"$test_file" - -// This exercise doesn't have a canonical data file, which means you need to come up with tests -// If you came up with excellent tests, consider contributing to this repo: +// As there isn't a canonical data file for this exercise, you will need to craft your own tests. +// If you happen to devise some outstanding tests, do contemplate sharing them with the community by contributing to this repository: // https://github.com/exercism/problem-specifications/tree/main/exercises/${slug} EOT fi # sometimes canonical data has multiple levels with multiple `cases` arrays. - # this "flattens" it + #(see kindergarten-garden https://github.com/exercism/problem-specifications/blob/main/exercises/kindergarten-garden/canonical-data.json) + # so let's flatten it cases=$(echo "$canonical_json" | jq '[ .. | objects | with_entries(select(.key | IN("uuid", "description", "input", "expected"))) | select(. != {}) | select(has("uuid")) ]') first_iteration=true + # loop through each object jq -c '.[]' <<<"$cases" | while read -r case; do desc=$(echo "$case" | jq '.description' | tr '[:upper:]' '[:lower:]' | tr ' ' '_' | tr -cd '[:alnum:]_' | sed 's/^/test_/') input=$(echo "$case" | jq '.input') expected=$(echo "$case" | jq '.expected') + # append each test fn to the test file cat <>"$test_file" #[test] $([[ "$first_iteration" == false ]] && printf "\n#[ignore]") fn ${desc}(){ @@ -55,6 +58,8 @@ fn ${desc}(){ EOT first_iteration=false done + + message "success" "Created test file template successfully!" } function create_lib_rs_template() { @@ -65,9 +70,10 @@ fn $(dash_to_underscore "$slug")(){ unimplemented!("implement ${slug} exercise") } EOT + message "success" "Created lib.rs template successfully!" } -function create_gitignore_template() { +function overwrite_gitignore() { local exercise_dir=$1 cat <"$exercise_dir"/.gitignore # Generated by Cargo @@ -79,4 +85,15 @@ function create_gitignore_template() { # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock Cargo.lock EOT + message "success" "Overwrote .gitignore successfully!" +} + +function create_example_rs_template() { + exercise_dir=$1 + mkdir "${exercise_dir}/.meta" + touch "${exercise_dir}/.meta/example.rs" + cat <"${exercise_dir}/.meta/example.rs" +// Create a solution that passes all the tests +EOT + message "success" "Created example.rs file" } From e14c7104e72493b205683589403836bb1241cac0 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Fri, 3 Mar 2023 09:54:55 +0100 Subject: [PATCH 020/436] Add exercise existence checker --- bin/generate_practice_exercise.sh | 4 +++- bin/generator-utils/utils.sh | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/bin/generate_practice_exercise.sh b/bin/generate_practice_exercise.sh index cfffe3a8f..a99cb7aa7 100755 --- a/bin/generate_practice_exercise.sh +++ b/bin/generate_practice_exercise.sh @@ -30,6 +30,9 @@ command -v curl >/dev/null 2>&1 || { exit 1 } +# Check if exercise exists in configlet info +check_exercise_existence "$1" + # ================================================== SLUG="$1" @@ -54,7 +57,6 @@ overwrite_gitignore "$EXERCISE_DIR" message "success" "Created Rust files succesfully!" - # ================================================== # build configlet diff --git a/bin/generator-utils/utils.sh b/bin/generator-utils/utils.sh index e28b8a7fd..4b0e9b658 100755 --- a/bin/generator-utils/utils.sh +++ b/bin/generator-utils/utils.sh @@ -14,6 +14,9 @@ function message() { "info") printf "${BLUE}%s${RESET}\n" "[info]: $message" ;; + "error") + printf "${RED}%s${RESET}\n" "[error]: $message" + ;; "done") echo cols=$(tput cols) @@ -31,3 +34,17 @@ function dash_to_underscore() { # shellcheck disable=SC2001 echo "$1" | sed 's/-/_/g' } + +function check_exercise_existence() { + message "info" "Looking for exercise" + slug="$1" + unimplemented_exercises=$(bin/configlet info | sed -n '/With canonical data:/,/Track summary:/p' | sed -e '/\(With\(out\)\? canonical data:\|Track summary:\)/d' -e '/^$/d') + if echo "$unimplemented_exercises" | grep -q "^$slug$"; then + message "success" "Found exercise successfully" + else + message "error" "Exercise is either implemented or doesn't exist" + message "info" "These are the unimplemented practice exercises: +${unimplemented_exercises}" + exit 1 + fi +} From 0bb3d7c01b7d0d238e40b9713f95613572bee5fc Mon Sep 17 00:00:00 2001 From: Aron Demeter <66035744+dem4ron@users.noreply.github.com> Date: Fri, 3 Mar 2023 10:59:09 +0100 Subject: [PATCH 021/436] Reword some texts Co-authored-by: Erik Schierboom --- bin/generate_practice_exercise.sh | 6 +++--- bin/generator-utils/prompts.sh | 4 ++-- bin/generator-utils/templates.sh | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bin/generate_practice_exercise.sh b/bin/generate_practice_exercise.sh index a99cb7aa7..1fb89456d 100755 --- a/bin/generate_practice_exercise.sh +++ b/bin/generate_practice_exercise.sh @@ -39,11 +39,11 @@ SLUG="$1" UNDERSCORED_SLUG=$(dash_to_underscore "$SLUG") EXERCISE_DIR="exercises/practice/${SLUG}" AUTHOR_NAME=$(get_author_name) -message "info" "You entered: $AUTHOR_NAME. You can edit this later in .meta/config.json.authors" +message "info" "You entered: $AUTHOR_NAME. You can edit this later in the 'authors' field in the .meta/config.json file" EXERCISE_NAME=$(get_exercise_name "$SLUG") -message "info" "You entered: $EXERCISE_NAME. You can edit this later in config.json" +message "info" "You entered: $EXERCISE_NAME. You can edit this later in the config.json file" EXERCISE_DIFFICULTY=$(get_exercise_difficulty) -message "info" "EXERCISE_DIFFICULTY is set to $EXERCISE_DIFFICULTY. You can edit this later in config.json" +message "info" "EXERCISE_DIFFICULTY is set to $EXERCISE_DIFFICULTY. You can edit this later in the config.json file" echo "Creating Rust files" cargo new --lib "$EXERCISE_DIR" -q diff --git a/bin/generator-utils/prompts.sh b/bin/generator-utils/prompts.sh index bf6453f72..4f693a1ff 100644 --- a/bin/generator-utils/prompts.sh +++ b/bin/generator-utils/prompts.sh @@ -29,8 +29,8 @@ function get_exercise_name { } function get_author_name { - DEFAULT_AUTHOR_NAME="$(git config --get-regexp user.name | sed 's/user.name //g')" - read -rp "Hey! Are you ${YELLOW}${DEFAULT_AUTHOR_NAME}${RESET}? If not, please enter your github handle: " AUTHOR_NAME + DEFAULT_AUTHOR_NAME="$(git config user.name)" + read -rp "Hey! Is your GitHub handle ${YELLOW}${DEFAULT_AUTHOR_NAME}${RESET}? If not, please enter your Github handle: " AUTHOR_NAME # If the user didn't input anything, set AUTHOR_NAME to a pregenerated default if [[ -z "$AUTHOR_NAME" ]]; then diff --git a/bin/generator-utils/templates.sh b/bin/generator-utils/templates.sh index fffa5832e..3b8e45597 100755 --- a/bin/generator-utils/templates.sh +++ b/bin/generator-utils/templates.sh @@ -42,7 +42,7 @@ EOT # append each test fn to the test file cat <>"$test_file" #[test] $([[ "$first_iteration" == false ]] && printf "\n#[ignore]") -fn ${desc}(){ +fn ${desc}() { /* Input: ${input} From 2b717efee74345069a550c6309a6e15f91d0dee7 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Fri, 3 Mar 2023 06:35:45 -0600 Subject: [PATCH 022/436] secret-handshake: add approach (#1636) Add approach to `secret-handshake` --- .../secret-handshake/.approaches/config.json | 15 +++ .../.approaches/introduction.md | 39 ++++++++ .../.approaches/iterate-once/content.md | 97 +++++++++++++++++++ .../.approaches/iterate-once/snippet.txt | 4 + 4 files changed, 155 insertions(+) create mode 100644 exercises/practice/secret-handshake/.approaches/config.json create mode 100644 exercises/practice/secret-handshake/.approaches/introduction.md create mode 100644 exercises/practice/secret-handshake/.approaches/iterate-once/content.md create mode 100644 exercises/practice/secret-handshake/.approaches/iterate-once/snippet.txt diff --git a/exercises/practice/secret-handshake/.approaches/config.json b/exercises/practice/secret-handshake/.approaches/config.json new file mode 100644 index 000000000..484a72695 --- /dev/null +++ b/exercises/practice/secret-handshake/.approaches/config.json @@ -0,0 +1,15 @@ +{ + "introduction": { + "authors": ["bobahop"], + "contributors": [] + }, + "approaches": [ + { + "uuid": "cd0c0a31-0905-47f0-815c-02597a7cdf40", + "slug": "iterate-once", + "title": "Iterate once", + "blurb": "Iterate once even when reversed.", + "authors": ["bobahop"] + } + ] +} diff --git a/exercises/practice/secret-handshake/.approaches/introduction.md b/exercises/practice/secret-handshake/.approaches/introduction.md new file mode 100644 index 000000000..7c7d34ff8 --- /dev/null +++ b/exercises/practice/secret-handshake/.approaches/introduction.md @@ -0,0 +1,39 @@ +# Introduction + +There are many ways to solve Secret Handshake. +One approach is to iterate only once, even when the signs are to be reversed. + +## General guidance + +Something to consider is to keep the number of iterations at a minimum to get the best performance. +However, if that is felt to adversely impact readability, then to use a series of `if` statements and then reverse is also valid. + +## Approach: Iterate once + +```rust +const SIGNS: [&'static str; 4] = ["wink", "double blink", "close your eyes", "jump"]; +const REVERSE_SIGNS: u8 = 16; + +pub fn actions(n: u8) -> Vec<&'static str> { + let (mut action, action_incr, end) = match n & REVERSE_SIGNS { + 0 => (0, 1, 4), + _ => (3, -1, -1), + }; + let mut output: Vec<&'static str> = Vec::new(); + + loop { + if action == end { + break; + } + if (n & (1 << action)) != 0 { + output.push(SIGNS[action as usize]) + } + action += action_incr + } + output +} +``` + +For more information, check the [Iterate once approach][approach-iterate-once]. + +[approach-iterate-once]: https://exercism.org/tracks/rust/exercises/secret-handshake/approaches/iterate-once diff --git a/exercises/practice/secret-handshake/.approaches/iterate-once/content.md b/exercises/practice/secret-handshake/.approaches/iterate-once/content.md new file mode 100644 index 000000000..f6430f2ab --- /dev/null +++ b/exercises/practice/secret-handshake/.approaches/iterate-once/content.md @@ -0,0 +1,97 @@ +# Iterate once + +```rust +const SIGNS: [&'static str; 4] = ["wink", "double blink", "close your eyes", "jump"]; +const REVERSE_SIGNS: u8 = 16; + +pub fn actions(n: u8) -> Vec<&'static str> { + let (mut action, action_incr, end) = match n & REVERSE_SIGNS { + 0 => (0, 1, 4), + _ => (3, -1, -1), + }; + let mut output: Vec<&'static str> = Vec::new(); + + loop { + if action == end { + break; + } + if (n & (1 << action)) != 0 { + output.push(SIGNS[action as usize]) + } + action += action_incr + } + output +} +``` + +This approach starts by defining a fixed-size [array][array] to hold the signal values in normal order. + +The `[&'static str; 4]` is used to give the type and length of the array. +To be a [`const`][const], the size of the array must be known at compile time, so setting the type and length must be done explicitly, +so the size in bytes of the fixed array can be deduced by the compiler from that and not by inspecting the element types and counting +the elements itself. + +The value of `16` is defined as a `const` with a meaningful name so it won't be used as a [magic number][magic-number]. + +The `actions` function uses multiple assignment with a `match` expression to define the variables that control iterating through the signals array, +setting their values to iterate in either the normal or reverse order. + +The [bitwise AND operator][bitand] is used to check if the input number contains the signal for reversing the order of the other signals. + +For example, if the number passed in is `19`, which is `10011` in binary, then it is ANDed with `16`, which is `10000` in binary. +The `1` in `10000` is also at the same position in `10011`, so the two values ANDed will not be `0`. +- `10011` AND +- `10000` = +- `10000` + +If the number passed in is `3`, which is `00011` in binary, then it is ANDed with `16`, which is `10000` in binary. +The `1` in `10000` is not at the same position in `00011`, so the two values ANDed will be `0`. +- `00011` AND +- `10000` = +- `00000` + +If the number passed in does not contain the signal for reverse, then the iteration variables are set to iterate through the array of signals +in their normal order, otherwise they are set to iterate through the arrray backwards.. + +The output `vector` is defined, and then the [`loop`][loop] begins. + +Normal iteration will start at index `0`. +Reverse iteration will start at index `3`. + +Normal iteration will terminate when the index equals `4`. +Reverse iteration will terminate when the index equals `-1`. + +Normal iteration will increase the index by `1` for each iteration. +Reverse iteration will decrease the index by `1` for each iteration. + +For each iteration of the `loop`, the AND operator is used to check if the number passed in contains `1` [shifted left][shl] (`<<`) for the number of positions +as the value being iterated. + +```rust +if (n & (1 << action)) != 0 { + output.push(SIGNS[action as usize]) +} +``` + +For example, if the number being iterated is `0`, then `1` is shifted left `0` times (so not shifted at all), and the number passed in is ANDed with `00001`. +If the number passed in is `3`, which is `00011` in binary, then it is ANDed with `00001`. +`00011` ANDed with `00001` is not equal to `0`, so the signal at the index of the array of signals is added to the output `vector`. +The index used is the number being iterated, which is `0`, so the element at index `0` (`"wink"`) would be added to the output `vector` +using the [push][push] function. + +If the number being iterated is `1`, then `1` is shifted left `1` time, and the number passed in is ANDed with `00010`. +If the number passed in is `3`, which is `00011` in binary, then it is ANDed with `00010`. +`00011` ANDed with `00010` is not equal to `0`, so the signal at the index of the array of signals is added to the output `vector`. +The index used is the number being iterated, which is `1`, so the element at index `1` (`"double blink"`) would be added to the output `vector`. + +If the number passed in ANDed with the number being iterated is equal to `0`, then the signal in the array for that index is not added to the output `vector`. + +After iterating through the array of signals is done, the output `vector` is returned from the function. + +[array]: https://doc.rust-lang.org/std/primitive.array.html +[const]: https://doc.rust-lang.org/std/keyword.const.html +[magic-number]: https://en.wikipedia.org/wiki/Magic_number_(programming) +[bitand]: https://doc.rust-lang.org/std/ops/trait.BitAnd.html +[shl]: https://doc.rust-lang.org/std/ops/trait.Shl.html +[loop]: https://doc.rust-lang.org/rust-by-example/flow_control/loop.html +[push]: https://doc.rust-lang.org/std/vec/struct.Vec.html#method.push diff --git a/exercises/practice/secret-handshake/.approaches/iterate-once/snippet.txt b/exercises/practice/secret-handshake/.approaches/iterate-once/snippet.txt new file mode 100644 index 000000000..e42302ba8 --- /dev/null +++ b/exercises/practice/secret-handshake/.approaches/iterate-once/snippet.txt @@ -0,0 +1,4 @@ +let (mut action, action_incr, end) = match n & REVERSE_SIGNS { + 0 => (0, 1, 4), + _ => (3, -1, -1), +}; From a9478260f8dec77980c9ba4f2a5a020ff2f6638b Mon Sep 17 00:00:00 2001 From: dem4ron Date: Fri, 3 Mar 2023 17:13:35 +0100 Subject: [PATCH 023/436] Add curl opts --- bin/generator-utils/templates.sh | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/bin/generator-utils/templates.sh b/bin/generator-utils/templates.sh index fffa5832e..9b9a6651d 100755 --- a/bin/generator-utils/templates.sh +++ b/bin/generator-utils/templates.sh @@ -14,8 +14,16 @@ use $(dash_to_underscore "$slug")::*; EOT + curlopts=( + --silent + --show-error + --fail + --location + --retry 3 + --max-time 4 + ) # fetch canonical_data - canonical_json=$(curl https://raw.githubusercontent.com/exercism/problem-specifications/main/exercises/"$slug"/canonical-data.json) + canonical_json=$(curl "${curlopts[@]}" "https://raw.githubusercontent.com/exercism/problem-specifications/main/exercises/$%7Bslug%7D/canonical-data.json") if [ "${canonical_json}" == "404: Not Found" ]; then canonical_json=$(jq --null-input '{cases: []}') From 3522f46d026d884f9e556526ef20708323314153 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Fri, 3 Mar 2023 21:24:44 +0100 Subject: [PATCH 024/436] Accept optional params, reword most of the messages, remove couple prompts, validate exercise difficulty, etc --- bin/generate_practice_exercise.sh | 28 ++++++++------- bin/generator-utils/prompts.sh | 48 ++++++++++++++----------- bin/generator-utils/templates.sh | 60 +++++++++++++++++-------------- bin/generator-utils/utils.sh | 21 ++++++----- 4 files changed, 85 insertions(+), 72 deletions(-) diff --git a/bin/generate_practice_exercise.sh b/bin/generate_practice_exercise.sh index 1fb89456d..6b12a3df9 100755 --- a/bin/generate_practice_exercise.sh +++ b/bin/generate_practice_exercise.sh @@ -9,8 +9,8 @@ source ./bin/generator-utils/templates.sh set -euo pipefail # If argument not provided, print usage and exit -if [ $# -ne 1 ]; then - echo "Usage: bin/generate_practice_exercise.sh " +if [ $# -ne 1 ] && [ $# -ne 2 ] && [ $# -ne 3 ]; then + echo "Usage: bin/generate_practice_exercise.sh [difficulty] [author-github-handle]" exit 1 fi @@ -38,21 +38,23 @@ check_exercise_existence "$1" SLUG="$1" UNDERSCORED_SLUG=$(dash_to_underscore "$SLUG") EXERCISE_DIR="exercises/practice/${SLUG}" -AUTHOR_NAME=$(get_author_name) -message "info" "You entered: $AUTHOR_NAME. You can edit this later in the 'authors' field in the .meta/config.json file" -EXERCISE_NAME=$(get_exercise_name "$SLUG") -message "info" "You entered: $EXERCISE_NAME. You can edit this later in the config.json file" -EXERCISE_DIFFICULTY=$(get_exercise_difficulty) -message "info" "EXERCISE_DIFFICULTY is set to $EXERCISE_DIFFICULTY. You can edit this later in the config.json file" - -echo "Creating Rust files" +EXERCISE_NAME=$(format_exercise_name "$SLUG") +message "info" "Using ${YELLOW}${EXERCISE_NAME}${BLUE} as a default exercise name. You can edit this later in the config.json file" +# using default value for difficulty +EXERCISE_DIFFICULTY=$(validate_difficulty_input "${2:-$(get_exercise_difficulty)}") +message "info" "The exercise difficulty has been set to ${YELLOW}${EXERCISE_DIFFICULTY}${BLUE}. You can edit this later in the config.json file" +# using default value for author +AUTHOR_HANDLE=${3:-$(get_author_handle)} +message "info" "Using ${YELLOW}${AUTHOR_HANDLE}${BLUE} as author's handle. You can edit this later in the 'authors' field in the ${EXERCISE_DIR}/.meta/config.json file" + +message "info" "Creating Rust files" cargo new --lib "$EXERCISE_DIR" -q mkdir -p "$EXERCISE_DIR"/tests touch "${EXERCISE_DIR}/tests/${SLUG}.rs" create_test_file_template "$EXERCISE_DIR" "$SLUG" create_lib_rs_template "$EXERCISE_DIR" "$SLUG" -create_example_rs_template "$EXERCISE_DIR" +create_example_rs_template "$EXERCISE_DIR" "$SLUG" overwrite_gitignore "$EXERCISE_DIR" message "success" "Created Rust files succesfully!" @@ -64,7 +66,7 @@ message "success" "Created Rust files succesfully!" message "success" "Fetched configlet successfully!" # Preparing config.json -echo "Adding instructions and configuration files..." +message "info" "Adding instructions and configuration files..." UUID=$(bin/configlet uuid) jq --arg slug "$SLUG" --arg uuid "$UUID" --arg name "$EXERCISE_NAME" --arg difficulty "$EXERCISE_DIFFICULTY" \ @@ -83,7 +85,7 @@ echo "Creating instructions and config files" message "success" "Created instructions and config files" META_CONFIG="$EXERCISE_DIR"/.meta/config.json -jq --arg author "$AUTHOR_NAME" '.authors += [$author]' "$META_CONFIG" >"$META_CONFIG".tmp && mv "$META_CONFIG".tmp "$META_CONFIG" +jq --arg author "$AUTHOR_HANDLE" '.authors += [$author]' "$META_CONFIG" >"$META_CONFIG".tmp && mv "$META_CONFIG".tmp "$META_CONFIG" message "success" "You've been added as the author of this exercise." sed -i "s/name = \".*\"/name = \"$UNDERSCORED_SLUG\"/" "$EXERCISE_DIR"/Cargo.toml diff --git a/bin/generator-utils/prompts.sh b/bin/generator-utils/prompts.sh index 4f693a1ff..554ce7f21 100644 --- a/bin/generator-utils/prompts.sh +++ b/bin/generator-utils/prompts.sh @@ -4,38 +4,44 @@ source ./bin/generator-utils/colors.sh function get_exercise_difficulty() { - local VALID_INPUT=false - while ! $VALID_INPUT; do - read -rp "Difficulty of exercise (1-10): " EXERCISE_DIFFICULTY - if [[ "$EXERCISE_DIFFICULTY" =~ ^[1-9]$|^10$ ]]; then - VALID_INPUT=true + local valid_input=false + while ! $valid_input; do + read -rp "Difficulty of exercise (1-10): " exercise_difficulty + if [[ "$exercise_difficulty" =~ ^[1-9]$|^10$ ]]; then + valid_input=true else printf "Invalid input. Please enter an integer between 1 and 10." fi done - echo "$EXERCISE_DIFFICULTY" + echo "$exercise_difficulty" } -function get_exercise_name { - DEFAULT_EXERCISE_NAME=$(echo "$1" | sed 's/-/ /g; s/\b\(.\)/\u\1/g') - read -rp "Enter exercise name or use default (${YELLOW}${DEFAULT_EXERCISE_NAME}${RESET}): " EXERCISE_NAME +function validate_difficulty_input() { - # If the user didn't input anything, set EXERCISE_NAME to a pregenerated default - if [[ -z "$EXERCISE_NAME" ]]; then - EXERCISE_NAME="$DEFAULT_EXERCISE_NAME" - fi + valid_input=false + while ! $valid_input; do + if [[ "$1" =~ ^[1-9]$|^10$ ]]; then + exercise_difficulty=$1 + valid_input=true + else + read -rp "${RED}Invalid input. ${RESET}Please enter an integer between 1 and 10. " exercise_difficulty + [[ "$exercise_difficulty" =~ ^[1-9]$|^10$ ]] && valid_input=true - echo "$EXERCISE_NAME" + fi + done + echo "$exercise_difficulty" } -function get_author_name { - DEFAULT_AUTHOR_NAME="$(git config user.name)" - read -rp "Hey! Is your GitHub handle ${YELLOW}${DEFAULT_AUTHOR_NAME}${RESET}? If not, please enter your Github handle: " AUTHOR_NAME - # If the user didn't input anything, set AUTHOR_NAME to a pregenerated default - if [[ -z "$AUTHOR_NAME" ]]; then - AUTHOR_NAME="$DEFAULT_AUTHOR_NAME" +function get_author_handle { + DEFAULT_AUTHOR_HANDLE="$(git config user.name)" + + if [ -z "$DEFAULT_AUTHOR_HANDLE" ]; then + read -rp "Hey! Couldn't find your Github handle. Add it now or skip with enter and add it later in the .meta.config.json file: " AUTHOR_HANDLE + else + AUTHOR_HANDLE="$DEFAULT_AUTHOR_HANDLE" + fi + echo "$AUTHOR_HANDLE" - echo "$AUTHOR_NAME" } diff --git a/bin/generator-utils/templates.sh b/bin/generator-utils/templates.sh index 5221ff1b4..4ea8eeed5 100755 --- a/bin/generator-utils/templates.sh +++ b/bin/generator-utils/templates.sh @@ -20,7 +20,7 @@ EOT --fail --location --retry 3 - --max-time 4 + --max-time 4 ) # fetch canonical_data canonical_json=$(curl "${curlopts[@]}" "https://raw.githubusercontent.com/exercism/problem-specifications/main/exercises/$%7Bslug%7D/canonical-data.json") @@ -33,22 +33,23 @@ EOT // If you happen to devise some outstanding tests, do contemplate sharing them with the community by contributing to this repository: // https://github.com/exercism/problem-specifications/tree/main/exercises/${slug} EOT - fi - - # sometimes canonical data has multiple levels with multiple `cases` arrays. - #(see kindergarten-garden https://github.com/exercism/problem-specifications/blob/main/exercises/kindergarten-garden/canonical-data.json) - # so let's flatten it - cases=$(echo "$canonical_json" | jq '[ .. | objects | with_entries(select(.key | IN("uuid", "description", "input", "expected"))) | select(. != {}) | select(has("uuid")) ]') - - first_iteration=true - # loop through each object - jq -c '.[]' <<<"$cases" | while read -r case; do - desc=$(echo "$case" | jq '.description' | tr '[:upper:]' '[:lower:]' | tr ' ' '_' | tr -cd '[:alnum:]_' | sed 's/^/test_/') - input=$(echo "$case" | jq '.input') - expected=$(echo "$case" | jq '.expected') - - # append each test fn to the test file - cat <>"$test_file" + message "info" "This exercise doesn't have canonical data." + message "success" "Stub file for tests has been created!" + else + # sometimes canonical data has multiple levels with multiple `cases` arrays. + #(see kindergarten-garden https://github.com/exercism/problem-specifications/blob/main/exercises/kindergarten-garden/canonical-data.json) + # so let's flatten it + cases=$(echo "$canonical_json" | jq '[ .. | objects | with_entries(select(.key | IN("uuid", "description", "input", "expected"))) | select(. != {}) | select(has("uuid")) ]') + + first_iteration=true + # loop through each object + jq -c '.[]' <<<"$cases" | while read -r case; do + desc=$(echo "$case" | jq '.description' | tr '[:upper:]' '[:lower:]' | tr ' ' '_' | tr -cd '[:alnum:]_' | sed 's/^/test_/') + input=$(echo "$case" | jq '.input') + expected=$(echo "$case" | jq '.expected') + + # append each test fn to the test file + cat <>"$test_file" #[test] $([[ "$first_iteration" == false ]] && printf "\n#[ignore]") fn ${desc}() { /* @@ -64,21 +65,22 @@ fn ${desc}() { } EOT - first_iteration=false - done + first_iteration=false + done + message "success" "Stub file for tests has been created and populated with canonical data!" + fi - message "success" "Created test file template successfully!" } function create_lib_rs_template() { local exercise_dir=$1 local slug=$2 cat <"${exercise_dir}/src/lib.rs" -fn $(dash_to_underscore "$slug")(){ - unimplemented!("implement ${slug} exercise") +fn $(dash_to_underscore "$slug")() { + unimplemented!("implement "$slug" exercise") } EOT - message "success" "Created lib.rs template successfully!" + message "success" "Stub file for lib.rs has been created!" } function overwrite_gitignore() { @@ -93,15 +95,19 @@ function overwrite_gitignore() { # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock Cargo.lock EOT - message "success" "Overwrote .gitignore successfully!" + message "success" ".gitignore has been overwritten!" } function create_example_rs_template() { exercise_dir=$1 + slug=$2 mkdir "${exercise_dir}/.meta" - touch "${exercise_dir}/.meta/example.rs" cat <"${exercise_dir}/.meta/example.rs" -// Create a solution that passes all the tests +fn $(dash_to_underscore "$slug")() { + // TODO: Create a solution that passes all the tests + unimplemented!("implement ${slug} exercise") +} + EOT - message "success" "Created example.rs file" + message "success" "Stub file for example.rs has been created!" } diff --git a/bin/generator-utils/utils.sh b/bin/generator-utils/utils.sh index 4b0e9b658..414c16802 100755 --- a/bin/generator-utils/utils.sh +++ b/bin/generator-utils/utils.sh @@ -8,15 +8,9 @@ function message() { local message=$2 case "$flag" in - "success") - printf "${GREEN}%s${RESET}\n" "[success]: $message" - ;; - "info") - printf "${BLUE}%s${RESET}\n" "[info]: $message" - ;; - "error") - printf "${RED}%s${RESET}\n" "[error]: $message" - ;; + "success") printf "${GREEN}%s${RESET}\n" "[success]: $message" ;; + "info") printf "${BLUE}%s${RESET}\n" "[info]: $message" ;; + "error") printf "${RED}%s${RESET}\n" "[error]: $message" ;; "done") echo cols=$(tput cols) @@ -35,12 +29,17 @@ function dash_to_underscore() { echo "$1" | sed 's/-/_/g' } +# exercise_name -> Exercise Name +function format_exercise_name { + echo "$1" | sed 's/-/ /g; s/\b\(.\)/\u\1/g' +} + function check_exercise_existence() { - message "info" "Looking for exercise" + message "info" "Looking for exercise.." slug="$1" unimplemented_exercises=$(bin/configlet info | sed -n '/With canonical data:/,/Track summary:/p' | sed -e '/\(With\(out\)\? canonical data:\|Track summary:\)/d' -e '/^$/d') if echo "$unimplemented_exercises" | grep -q "^$slug$"; then - message "success" "Found exercise successfully" + message "success" "Exercise has been found!" else message "error" "Exercise is either implemented or doesn't exist" message "info" "These are the unimplemented practice exercises: From 7901beff197883e0c1cbeda50e2296093b1b0205 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Fri, 3 Mar 2023 21:34:20 +0100 Subject: [PATCH 025/436] Remove duplicate code from difficulty setting --- bin/generator-utils/prompts.sh | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/bin/generator-utils/prompts.sh b/bin/generator-utils/prompts.sh index 554ce7f21..c785a2636 100644 --- a/bin/generator-utils/prompts.sh +++ b/bin/generator-utils/prompts.sh @@ -4,15 +4,7 @@ source ./bin/generator-utils/colors.sh function get_exercise_difficulty() { - local valid_input=false - while ! $valid_input; do - read -rp "Difficulty of exercise (1-10): " exercise_difficulty - if [[ "$exercise_difficulty" =~ ^[1-9]$|^10$ ]]; then - valid_input=true - else - printf "Invalid input. Please enter an integer between 1 and 10." - fi - done + read -rp "Difficulty of exercise (1-10): " exercise_difficulty echo "$exercise_difficulty" } @@ -32,7 +24,6 @@ function validate_difficulty_input() { echo "$exercise_difficulty" } - function get_author_handle { DEFAULT_AUTHOR_HANDLE="$(git config user.name)" From 42196d781e346a5f93e8bcf54b35cce23fc5ca07 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Fri, 3 Mar 2023 22:09:47 +0100 Subject: [PATCH 026/436] Check if exercise has already been implemented --- bin/generate_practice_exercise.sh | 10 ++++++++-- bin/generator-utils/utils.sh | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/bin/generate_practice_exercise.sh b/bin/generate_practice_exercise.sh index 6b12a3df9..637b8f828 100755 --- a/bin/generate_practice_exercise.sh +++ b/bin/generate_practice_exercise.sh @@ -22,14 +22,20 @@ fi # Check if jq and curl are installed command -v jq >/dev/null 2>&1 || { - echo >&2 "jq is required but not installed. Aborting." + echo >&2 "jq is required but not installed. Please install it and make sure it's in your PATH." exit 1 } command -v curl >/dev/null 2>&1 || { - echo >&2 "curl is required but not installed. Aborting." + echo >&2 "curl is required but not installed. Please install it and make sure it's in your PATH." exit 1 } +# Check if exercise is already in config.json +if jq '.exercises.practice | map(.slug)' config.json | grep -q "$1"; then + echo "${1} has already been implemented." + exit 1 +fi + # Check if exercise exists in configlet info check_exercise_existence "$1" diff --git a/bin/generator-utils/utils.sh b/bin/generator-utils/utils.sh index 414c16802..59651a4cb 100755 --- a/bin/generator-utils/utils.sh +++ b/bin/generator-utils/utils.sh @@ -41,7 +41,7 @@ function check_exercise_existence() { if echo "$unimplemented_exercises" | grep -q "^$slug$"; then message "success" "Exercise has been found!" else - message "error" "Exercise is either implemented or doesn't exist" + message "error" "Exercise doesn't exist!" message "info" "These are the unimplemented practice exercises: ${unimplemented_exercises}" exit 1 From 2db77379bbfc81668ba4025fc5dc0de3cc3965fb Mon Sep 17 00:00:00 2001 From: dem4ron Date: Fri, 3 Mar 2023 22:16:33 +0100 Subject: [PATCH 027/436] Move config checker into exercise checker function --- bin/generate_practice_exercise.sh | 8 +------- bin/generator-utils/utils.sh | 7 +++++++ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/bin/generate_practice_exercise.sh b/bin/generate_practice_exercise.sh index 637b8f828..1eb9b882a 100755 --- a/bin/generate_practice_exercise.sh +++ b/bin/generate_practice_exercise.sh @@ -30,13 +30,7 @@ command -v curl >/dev/null 2>&1 || { exit 1 } -# Check if exercise is already in config.json -if jq '.exercises.practice | map(.slug)' config.json | grep -q "$1"; then - echo "${1} has already been implemented." - exit 1 -fi - -# Check if exercise exists in configlet info +# Check if exercise exists in configlet info or in config.json check_exercise_existence "$1" # ================================================== diff --git a/bin/generator-utils/utils.sh b/bin/generator-utils/utils.sh index 59651a4cb..66f2afe07 100755 --- a/bin/generator-utils/utils.sh +++ b/bin/generator-utils/utils.sh @@ -37,6 +37,13 @@ function format_exercise_name { function check_exercise_existence() { message "info" "Looking for exercise.." slug="$1" + # Check if exercise is already in config.json + if jq '.exercises.practice | map(.slug)' config.json | grep -q "$slug"; then + echo "${1} has already been implemented." + exit 1 + fi + + # fetch configlet and crop out exercise list unimplemented_exercises=$(bin/configlet info | sed -n '/With canonical data:/,/Track summary:/p' | sed -e '/\(With\(out\)\? canonical data:\|Track summary:\)/d' -e '/^$/d') if echo "$unimplemented_exercises" | grep -q "^$slug$"; then message "success" "Exercise has been found!" From 01fe8176f9623ae348f849b22c3c875fadf2daf4 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Fri, 3 Mar 2023 23:55:53 +0100 Subject: [PATCH 028/436] Add ngram util fn, and check for spellchecks --- bin/generator-utils/ngram | Bin 0 -> 533779 bytes bin/generator-utils/utils.sh | 5 +++++ 2 files changed, 5 insertions(+) create mode 100755 bin/generator-utils/ngram diff --git a/bin/generator-utils/ngram b/bin/generator-utils/ngram new file mode 100755 index 0000000000000000000000000000000000000000..6adf0eb63353213d140c5ab65db658c6b0cf063a GIT binary patch literal 533779 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 (Q>BHd0JAqYpB4h5CItNiKsT=9NK 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≷1U;_($d1o77+XpOh~*Uh$Ih&y6=2?=aqBJXx+| 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 literal 0 HcmV?d00001 diff --git a/bin/generator-utils/utils.sh b/bin/generator-utils/utils.sh index 66f2afe07..1db04eb26 100755 --- a/bin/generator-utils/utils.sh +++ b/bin/generator-utils/utils.sh @@ -51,6 +51,11 @@ function check_exercise_existence() { message "error" "Exercise doesn't exist!" message "info" "These are the unimplemented practice exercises: ${unimplemented_exercises}" + + # this is a compiled, tiny Rust program + # this fn uses the ngram algorithm to find the closest match to an exercise + # I'm using the ngrammatic crate to achieve this + bin/generator-utils/ngram "${unimplemented_exercises}" "$slug" exit 1 fi } From e82278fbfd7fed51ad16a44470a23354ff6b245a Mon Sep 17 00:00:00 2001 From: dem4ron Date: Sat, 4 Mar 2023 12:29:23 +0100 Subject: [PATCH 029/436] Add source for ngram --- bin/generator-utils/ngram | Bin 533779 -> 533779 bytes bin/generator-utils/utils.sh | 8 ++++---- util/ngram/Cargo.lock | 16 ++++++++++++++++ util/ngram/Cargo.toml | 9 +++++++++ util/ngram/build | 2 ++ util/ngram/src/main.rs | 30 ++++++++++++++++++++++++++++++ 6 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 util/ngram/Cargo.lock create mode 100644 util/ngram/Cargo.toml create mode 100755 util/ngram/build create mode 100644 util/ngram/src/main.rs diff --git a/bin/generator-utils/ngram b/bin/generator-utils/ngram index 6adf0eb63353213d140c5ab65db658c6b0cf063a..38dcd43c9491927167f64e1234d79c8ee7b015f5 100755 GIT binary patch delta 54385 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 w+cv#_DKX`Fpx^zpx*0vZGExp(E-TS*)XR delta 54357 zcmaHT30zgh8}^ww_pk}dE}(1|mBkJBT@lT|Jv4Jm0hhoPx3W@MTmp9md(F%cwE%a~ zu3HLZg{GyYrv6<3sc;R;O3USY-!n6r^?jeepLg!_ywAL|oH;Xd&b-|06G7Q0f>xe( z+toqSn5JpfU+DO$X}eZcbaPv)SxhoUaN64J7Ob0Fxh~k#ubN7&yeOT0X>pBz`^w&q zS6jO-+vaQce(x7oWe>G$&$Za1)poU8Wv{e%Ez0eX>C7Cm!+Uk&VHMdCy{y@pz1p+h z*(JR^*{!1Uy?QZTc_up`#*det$?h0)o|lvrxySzL%;K|MhOFS_vx+th>B@Ni%%ZEW zEN4!+BmE--gYnP53(J;tY6kO@_tWv+CsuQZBe1wf>zn2k`TJ9~Y=>t^eRpA@7BGqBWr{*B!9lK7}2wX%0Dc(2(&oFuTY6U}h~EH-Ekl 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 YGx>P = exercises + .split(|c: char| c.is_whitespace() || c == '\n') + .collect(); + let mut corpus = CorpusBuilder::new().arity(2).pad_full(Pad::Auto).finish(); + + for exercise in exercises.iter() { + corpus.add_text(exercise); + } + + if let Some(top_result) = corpus.search(&slug, 0.25).first() { + if top_result.similarity > 0.99 { + println!("{}", top_result.text); + } else { + println!( + "{} - There is an exercise with a similar name: '{}' [{:.0}% match]", + slug, + top_result.text, + top_result.similarity * 100.0 + ); + } + } else { + println!("{}", slug); + } +} From cbe999d4aed703ba4eaa63d374d518fb646fa0eb Mon Sep 17 00:00:00 2001 From: dem4ron Date: Sat, 4 Mar 2023 12:39:40 +0100 Subject: [PATCH 030/436] Add message if slug is not found at all --- bin/generator-utils/ngram | Bin 533779 -> 533779 bytes util/ngram/src/main.rs | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/generator-utils/ngram b/bin/generator-utils/ngram index 38dcd43c9491927167f64e1234d79c8ee7b015f5..8991327e7786edf57bf9d0a3b361bc9de2f948c5 100755 GIT binary patch delta 55797 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 DzVC@BiM z%*+tYIH*LTZ8cIL^F=dLGc!{=AQ@gFHS?C2|5|%yGJC(jzn@PI&$FJj=CWtc%$^yn z;6Px(fx!I3o<&b6imE6|<30`lR+OS;b-g@u6pKkFa!$!9=&JVeJgjln@gGyE9IlwB zW~yqB3RQbX^N4GDTYKUAm|=Ny-tV#Iw7XF-%&CWZxnh~q&rt>W&XE;Up2%@yIcE!Q zKOMtL>M*et4C~v&=fKZF9_<3P?@KyG+jEqxlnd=!+Cwf?Z0y@tWobtXnx64z2}cX8 ziN{&|{)+m<+iuJ;yWq_8OIYg6ij1MLDhqk7qU?nwsx|OGZ6hg=t$#(|7wer_VrWI! z=v}O3QOmYw250XFnrXwmz=9iNwgjZAZPzGj)>EOFscS3cmQ~G4vEH@V+kK?lU9VKs zj16FH)n66$5N+vbOAX*QoA4u>AKFHv?czY?R%%<*-uZi*$czM@D0mSC z32r=Koz}8+9&$sFD{fYBZ=TQ4G-0ChlDYAWQkaNlhvr>c<1F1=N++XR0`f(Eyc}uG z9+dG9^m!6(RleMoZ7rCXZOcq;EsE^=Kw5{=P?To*a9e`Q1KUZo*(i%vA`6i{)#N+0 zIlMVv?DW9r8{}J|PmRJkhvtIS@GX7fXT`a&1sa%A;5FZ-72j`H@%a3CzN~IV#jf{f zTiCjQg6N`Hwys^lOiY5>6?|57o8@>HY+Yw#si6hO*9BOMCOvJXm)OX&~D1uB8ZihTSdsY3Pt<+jn5tP11ccGt|bS^vz$uk_}AeAsVs zjtY@B=b!f0rI*p3{W|BU5CaY%ep6~(O8X3h+pMXt_?f+}#(^vm#&PUVGK5u`B*y?W88KJYPueq^@R>-o}Ma>JpVT_Ji4>YKATS z>DE)IL%UkCQX1~xZ(ZAu<9=FJiUV;-5?I636qcR{Bg52*ETk``KHvybd$Rfq5YSm2 z%F=#?nVr?+%vKM{;c8Uax+hwj54KTLmeH%7hIRXMg;MxO3*}at!{4m475mP-9#9&t z`ZJpY_Jykvmi?D}%m(&0+`6b6G?rasY>iaw1DRtJtb7hLDzZbRT!%qBi%pl}N%D1N)RFVTKbiNZQA6C`I-0FW&eMd#AWtv1lKO_WFsopj^YBNV?R~?2h+40+feVeRFRC_`U7iCzFQMmeZyB*K zss1fkPM|S-q8h9FXYcWSsNuJ)R|<16M)n@qI$6!mOug!D+Us|7T@ZGo*HJ@rY}OTS z%U=J$HU(|N(H3}(+tMn9%{eP&0@?T-6e zz__qV&30x@&qGF$YP0lzzU36CDpGx6bCK%74i1LhMQWHOJVnrkWt}?6)xY?>&qM8e zuTUvGhE*y)4;iIulz+mn58E%Iy&CNazrwCk^?-kIowQTc+m!m@+ea@$y#v;6QvH2u zPIEi;gXU;^3vFpX!=X*8PeAb*>DvZ<{YokCDQI`#&)Gi#+pNa0`V$blS@mJI6EJkM zYKyA*LHbY=`j-BPKF?Bljz7$Qhde!+?C+rz{g~~0IJ8+!VpWI1e~TJpE%I(@z6)u` zVB{7xAS$(`z(;u;dN(>^3|EYi>Z%+`6ZMEd&VpR=X`^h5x`?rO!-y%TfiAApIPj@j zMbYE8B>n~`_u*ij_zT$ntJ=H*8@NyVUc8PC-Z1pPYMD>tG>&QQ+}O#< zem~RMo)ZQBwQ6vHiyOBkp{=Cz6ZSosG4>LT9VzMbqhuA8Ri;s~lPMOmkDyxJk3;oP+V-s5bEaMh)}|`SoGv5Lf8?4fcx0dJ^?zbumWS zH|lt1E$Ymp+KxcLVf9`@YI~0BYk7d`W?3mQi*ZSftNhV<_=VB&L5&+bsra;*y|&Sl zmm*WM*L8rANAT@x?C`KI<+N}h&qkE_7*rD#d$RDP2QsIFn;4wp8pK&Rn(hO zMQ=)7$hNpFgS4Y+Y?3mI>vby67Y{1h*ok3zG<)J{nU}?0SIO;;`N}dktOq@TxJFO2 zm(*0kg`;XVD_U!0e5dYKnd2oR;(N8P=B0e$8A;29vxVGyLq04#p)O=0?}Gb}>UFPO zxt#xLDbJ~Lso`@HpPi3wy2uWjYgZ0msj(Dbr`2SO>+7D8w65~TB^OLQtp)_EgL!2a zOcq}2m1WuZxL-}fspPEZ6e_}o$xwz`HlZF4omTy=@q>g<23$R@hFM1^bKEcyg3qYI z)}S-oZ|o!(dPYrVb{E)qMh&nA_2aq@b~tnfj?G84P-rf_9h zDQmHjL-z0{3`M*evCD2=u6~GDB2Mi5K);-D*aN(T^pzYhqMtwK4~%@@K^(G+$F}2V z_I$!SJu}V9>_;M#9CmOAJ372c0)yjRw7-rx@iShS35chYeftAFeP6q56B(fYD}*b! z{XFVlCjC}kILdDf*+0$X4x)h#NQYV4*mN)Aw~rDYu`; z%myK@wR22`rODsLF7$}$gFCT=XWkESAF>A`&ZNaH28crOMgMTbE&*O1=HeOkA*3(j{u3}>2gEradU|A%fC2`R!y`OH zDo`85wI7HK5&I%8StBw;>`nIfML_iTAp2_WzaQ;xgx}*WVn@3R;>K0nJ^?XJhqd6@ zE|Ll`0Uc-*XvDYOzVlDB)Qul2_QbA6af52ptW}~ZY)`|XUvWZ)R{KRE!?-wo;hYffv6h{0T& zf%Ov3`bAX`aft@qJ&=4!J!3uV#bY&iLiBHHvVX}9o<~0#;XB3P?DnL+qEky zZ@55pUwM%WZr~S6@l`m~q#knrtll$Z z?(=XrVFi@^rDm|jxe#zi-NjfgoV=rsw`|Jt@PNQOs<$!huDVTix6R^yk7eOU+brj9uFaLz}ZGmbC*GHD~^`oY~l%4P_N& za1Yb4A~%mg)OPjGJVRRQ{{E&?SoGf(=8Jc>s->BZZGyS(=&zK+YIinM%dvYDRICeP zskX4L=Vg`IjGpx)ei zYEULSGUO-oM6TN;at`J8SV&pvS4=p4_NET64-t#)O)g# z%;mC?;Kh7YmY^8JTe1TxtLXv3tynB8e%u(}img=Hw$5~ zk;YD%oz3FUaqne=`LQO|D{BmQ{&EC&=(E{ik1!_|Tb05MT7O|!!_#7o9ibr-W+{c{ zv?F-l@H#Hdoa`aF(6=o%SVwA>Fqb}HwPC^5rNPJ`Omj!U>>!rn z6EgdMeGk+7A@syuP!TYuO7KR1Pj%f4=w#(gajLFV&{)RRD0|pNk?IM zdsf4WC&17SY&Abx9d+r#dT*kFNgaQ;h; z^L>HH5uz*I5(odYUK+NA#U9cf z_I1L(8sY;^A?Ta>4Mc}vnb-A)l_6|4i~kY4LfLqxK7*TUI54d{0#0Gf#^%LCSQtxT zb-iG27&d%eEUXPQziwYR#%{!FK0lv<- zxn8%|FLkp2aaMhzXf539%zW7GUEmYWqHT@c{#jn^RQH|ii5{Yx-*>hLSs^1FpPQ>Q zEDFam-PjIg;Vjvj-i&KPtHG%Y)+rMqL(`^5B{6zwZUpyReST zB^*|FVH;UeCrF4u=iHOVv~!ka43>R`-FYV{p{MM$xBxBPPDxrY8pE^ z+tXi#xn0@ZnDl)Q^0nG{J|!P_x0ik#ZKs2gI4$m$OY?uU6#7!xO6njw3VTIkH7tq3 z!MfTVc15w_5vd>X7@G#+na&qHw(HY>m;*7zr83AmCXS*yw1JV2F?XL3(e0#>G8y^< zWIu-8;B^I#KgQCS%XWzA#={{bGtDg|Mb=FW)?Wq;&zHlDZ|m@tHHQ_ zd>P8fUOSF=ZfUQ__1;djmyCroWKSH!UlwUkK>sb|p9b#T(cYNaB9cZEY0pOgb!cxK z1+k{R;D7U9LH;8l!?cfhIR6Cn&qaIfOR$#gwZnO{$@pZSgZ7*k;Skx=hdu01`A=lv z#`h$9ADBz_+TIVBuO96>+S3!DjO>Z=|69IWDOmnkI79aIUJvJAjQ+o&Juw#CdyxI9 zf7$Uhsv~<(i0#1^TI{8wM;fPkutE!SnP^OVin+6-+Kmt1FS#-=nd#2ohs>V#cwsM^ zuO9hfGA>@`pX^zrebCd9?QV9s`Hyq|y&u7WUO19ge`;9cXc_Qo8XS*jyWA7=xuEe6 z&0cZ`)+XTML46`=guaoqP79y2Ic z+^3lHrPv&5$bv(Cm`_AnEZ5M6ePxDHNMBJjKQ?w!ZUr{+iuAT}jb*Ok^)y?~*u;&n z@fjQyawfphXV_4$+LyV2zudY#rz^xH;wYBb42CDN7_Zdp&CM2B|2zL2>(I;Cn8>13 zRy!OblCb2Xhr{)LSn~82IaXgZl9Jd3m7PBU(f!#HuPlGwUhNO^2o3$=cz2f9faFrEmkT8aY z`8ZTQmeTh6i7`qcMSD>-vd6F_7dCwl9D0SG=}xadKK`{gf*u{>qS~ z`Oq|n#V|`YIr&4Mxh$PE%z?7GSjzf-;GT(7qpcqVXX2!vRt?FSI2|vTVTz4wnXISE z_D(RO=22Z5M#7S8HZ-8a2%cXdepJ%pF$iCZ@{wYzYQ4ecGhE1Zj)jd2Sq9q_WB9zo z+?)B^K5b!^l7>23dd#lSP{B9h7k7aAN+G9L6?`I0mRH{AXd0xs!++U182D3k4hgkL@ zBWW!wb#u2*yJ7;FlbVoigl>5}UL-A)UDz?5kk1KTvsH07etYEIQxp6zi-jA{U(5AF; zi=;lnT*1p=w%{ro*QU7$ty5Ntfm+&)3=H5kO>i-W8`yx%b_1^OWYbu}HJG@I4Yy2~ z+KkVe#;INGvX*SS&EMbpfw(NhhgW=y<1>g;5!(bliCEEj@R}Q3L(9p1#lAzR(_2OD>iQ>EcymlT=mP0s6%X{%8D1j$!}RQ%bo{ehj9#=mjy|OaVciY zgxQFL6XrHETbxcz*xY7jUp3F|=QJ~W0Kc37yAQLB=+x=lUYzovQJ#1fpQ`4+xSBn( zc!S_=SoyQq-*=#GZwjOx!Q32EdAu=6+_&L5p4Q`s2sQ33)b@En)|rlC{YoiL5ycqx zFi(4Fj^{bI=pXr828oz>!bJHmLtBTTCI{M4=IOs-$isooVkU2!B8WK3{4@IubH3qS51x~Ls@Mg;2oQuir zK#oO1_76B8r9Equ{lMm`*3`$ugX;j%KjMJU(B5!5$?7$>K!*#bQQ5^Ae9qv~GuHwW z&#<|zE%*5hMd!%WlFub#MejC;`)Am5UW!|@LG+SpKXQ@f4y65zvz5ckSp740W^0Hm z?^C({aHt+f-%}1&%d(iUv5u^@H?3xm3$>E9o&{btww^^pv-C!8*ni6k-_|ofxYoc< zu@lwC(O(#z)D<0rv|q8Tfeo@s+w(17+uNcvp5q%mvohGNjTv2oAbvfw18MJIIu;VA@4&<l~8sG z-M86c-zA*gm5*TfZ|nd|{RD!W@QeETd$79+i+y}Kr8b!L2lItfzq5n>FXUOx*QYx^ z-JfvJ+IJDhJ9CYi%dAFasd=#QDvPitzQHSMUj)0aV#w&%{ti{*R+M2iTbHI6IGnEe zLZ53agk`+~8P~A3%A2s2(%<^wmK#oHS;JuHpR{fC8Z7#g%?`l(b7mpp(Y>|>Q02{C4fa&N5{x?{xHDm}cSME4SyMes&92~!a zA0qRTA?7Adl<@-~?IzpG;+_GYTP)1xc!B3N5A(X!9#?Z$oFAtfVU%Ant{ohGAng{e z;sSd^@hzN<Z7O&B;3KurS^n{ckq$M zb%N`6FymKUA>b|!Ua7$ldlxI~=m691;u{jz9*Xa>F!#{TydXh1uk~vVCy`@SUf^^O zeU)|)aSxxT!vp%>W83rr zF`A9VwOcV#i)B^KR;<-9W}6lJwAIX^Lz+s)8?aE-%2<&HL^Cae4Q+yr3}tb*;Sj?Z zfz6xqz6Ew!wR%>y71lP>Vyq$MqCC}5-%K0Hinc(48`d_f60+S8J3fYb zH?1!#%f+oqEru0+0OOl$N39_r2){B&a7SI%DkyfR_IMxmxob(xu?oCeU^^56#88^L z64EFQTmg$(pj~+vjh~t>^1|nnIvY-UX~DL*H+VbIyOWEt9N#%Vz6Do%GT*p~U~8!*v%ncJt);fyr(rN( zB)C0^yU*C?v$4-tWrDxAwuHq^f>Lj7xch{uJSKI`+`-`0N{g^orEo4G8IoFIIa0^K zqE^~+)}c29_+UAU7DBR*HkSoXghM{saQ4JgMp$djOZ5uu$t%3RCC_-QJ0$yJgJlnZ zg}xYIL8RgDr$y^5QHPWOobtwAF@oD^_~9_%ckl|rM-$RyL!={C$WPiamw8hXAjq`AW^l6Fnvv(=OX)qOiE#E!N+pzu4 zqWwj*jXlC`G(GOEfeSXxKRC6?#q5V;KIgU#M|nznf9q)Aq?lC@)?V`u*zoPcvC~BC z9;n&72h!SWS6S*^+zZr#-5ZZ{Ev<(aU4}&+@Y&|zO_B~8-ef3&eT3C3aIS-v5>|D| z#jO9ArQF9N%2}_e%j$5IxfttfZcqFMX&tpNTgX3R_9>!BclVj-pdH*d4G%lJAb0o8c>A@1H6>KFv>mcc zxM;z}->E3Y_&rJsZJv_gAuZI!T9&^1C|*$=AUUqiK`!BM4wph0VLo!j{iUvz$<0NM z^@oXZ`1p&P!@@W%*qT+(+p)MGd={rgWfnc_GKJce>D+f7mNp6fs{X~VL%t94N`GFf zf9hkA5BwKjja{e*=?CzxF7?#4qKHp6%Rw0%6XP{^#%jA5vwLf0F3GIPYW7TeGpTC1 z;drvNO>(?h79dx1iF2gMLYyEl9bFOkCizvs%>G(%rqbkWRx@o2_n~V32kn9X;;EW1 zAfMXgY?k4l`sa|(`WH{te3taTI}f5}lzOV>)5t6H%;rMFIS3e_g;|T{@b<51Fj59+ z__ce!-ngEObBZLLD_PsQ$!-h{n zL+YuA9fEZ}2vyN*Bk!hv_;I?ID)rL=?Ex$8JAN*xXbr4F89_AgRpd5 z4xPUWGt+U33VI$^reoCj6u6$QC2R8rIYVZ(){2cD2qPzGORObHJW%MfaD4(gRwqKj zL|o^buY-(9nm?3J)ZCcb2X;@yiJ&?jf?vU?bTlvV1sk7;IxDH&;ur6BZPiO zP7zG)4r?dj6CVATaeb18zxea`F^J930x~!G@s{rI#~Y%Zs!WAX7q9Mt4MIBwzH{0Q ze#Q5J?e$M#bvoKcHCLu4cFpm3X&blvz-}jVd|0d~9{nez_jtwCZE>pF?l?7SiRLw{ z#D~|2ex*TjjjhZs13zVGzXXK1^Fq+ltmK_2`=0qNXKmBGZ<0evb2u|u8*i<$iZ^J3 zkvxTJFmorYoT>#hyKv9R9L*NrbAmlnH9uB*7fzxJZ3bmjy%2LU7XBVURZS9=OI^Tvx?`Spb>#Ig`j)u4SOB!K!u2$@GqCuZR!tCf5PqjT^t;kkURHF)Q1-pqyD zSYLa;Xnx0*Jaf%)I09z0z41|=m+EA}o zxAEdNHOum9;gaQ5KGxW^7FV?_z9+=2(sfcPLwjWuDL-e2OvW3F{%bSQ{FU z9?1je(zEEq&%sN2t9a_}Y@gQ^YKpalfRN5yGvWWxRChLP>v64Xt(vNv57riJ^xdEZ zV+*#0jE`_t5Z}&N^pWPSS{);~BB3=DZ`6_#=4I=HsA<1kz|*(iEcX%sC<|eJ0`6Qg? z7Fh}n)y1b^Xc_kEw4Y#Z8IJIEGhkPl7GRxMtC=OX{0Q}B+EA@7Q0Jphbvq;F6Kx}2 z$!H0;%W>ssYXM6>#WdRrwyl`f>+l(+2`VI2U|M_!&Q#!Y&AkPEx8Y}Q+8^Mw9Y@F9 zE6{g4ev8zshKbv;Lrl2@7q)A+Sy}^}`wT~3V2j%cUbXYNZyUTzxpQ3-mT&98})PJ>=;qU+cf;n4mj&?mIMN<5&eUlORUcc62gc8xxG~$^n0*%aZM@QM^QzGh)&h<7 zw~XL(S`!-Txq)`w2kL03GlI`!*Ysis9vJd0__@E|h`OX*Rg;RpkOr61W@xDY8@rY| zBQCC#cKXc+JgP^o=5LOESI(vMu74)?k{7zX4?#V^R9xuY4zBItedJ z&!BXiqSRtqGG0+mjj?1Z*CwHGtVIdL5>#Tk;|)bQhUu)g6{W{Gi*jut2Ea5mM^T1h zT9}7{2rtEqFwI({D4Q|8jQ7y1Fty-`>@iFi6f24Y(}oRLAxwvSgf$zFej62~4W>Ol zR+RAZnHI&RL{Z|9NZf=+xtJa+Q+&YWOTB4?^f zPfQE08HDB@a`_52~^m(`zSG5OJf=|R|yDg7}mX~C4unEHA$r3%xKmP|Q;X+His^(9PQS~JCxff@KQ zB@)v~k1!<>(+vSk$;Y%q5c*?!3DdopZnLpWpO`eB;WpDF2>h9@&+4W?5DGGzm%MZ=lm zG8H@NOSlV;Y3)16Ps4(*$ix~>#|&36r4ObXRxxD|rg86KAWUarIt9}qo0yV?Y2ps1 zT*lP0lPPyFZ4A|vHZ!m#I%`T#OxuKO$_PvgyJ*TROcNtDr3BL%U!e{=b@)L|>4Rzi zBbqV^(;2?UakRr9+-{%)HsPh)n&M3@VthKw(#uty1y|--p6Qo}Z=s?@DtI5>{3FwF zllM?O{+0fvVgE#ZGojZp4Z{Eaf_d4N*3UUibD%IP$|Dw%2jYE7{^#HJHo_hu>`4OS zIAQ)70Iu^box;MC2;nakh(9viARH_3T~L&9GKJyuEoXv?@K;6tFa5!9$kRg^0YUFr zTKgs9i$(BqJx_a_<*6{Q66Tx2{MN%}^L0dTZ$VpxoO^q^(c6mVHo{y5o^M-TR!2bf zLQ8A)pt0<2OAA#!2I~;Ij>R{N7mTc~56X$q*JYF@j4GDe^;(sLR0-Ef@AG_kw(3pe z#djSd$A=M#YYxnsQhm1pBXiptw^oqiDFk@ z91p3C+*dtj^vkpKQq@Q}@~)+|r!T(RWIgr)SM7yQ-?g+8RzG_2$x34>rG1KcDx7J2 zAex3;N*5TRODr$^U5UJPoIVBXmRcScRkLmrtal5zz0{Hs`uG;UK+ z2ju2k?u!SIxeX6oi1mGzr*DaLfk@qcS514PNX?-P^^xTiEmFZ>Kr#QwTsr*1^&3Rm z0mnv)UnJ6Sk-A(pd4|OaT5frVKM7x#znl8XVoSTwQ#dpHUsa)DjDpfIM(GGJdqfV7 zW&{sH*8<269t_QM+z*HWyf*+T_Wm7__W_B zw7jgTsZa;_=FGxjjY6d2F!oStnmrc?^CV%OWIVA7JA>b)k4gT&qs;=C`kv+EekJ(J zqW_mUwT9=?rdp%||G)HyVf1Rt%bC)QG0i{J@bsr>H-CZtU-rC?|Nr(vvzQ)=^p|mE zT3crN8_&FNnWy7GJ9Qm4U>4LBS(Xaq>Kn|nFpa*3eVwJ9#cxAbBL8=k%YnL&EThy7 z5Wf*0*UdfLl|yw0ys^>pF&cwD#^<7p6LF{Wbjpl>ie2&R3v=*LxUXlqu<=A^&X{N_Ow z*Lo#5&f=5MuJr?s6vmy8%wqVWd^>IwdL5Gib2#!Th3>-0CKcY7^k!Yt)% zGhC8Ed^3HhdIDB8)8knFZ8+IX@7w&;T^Dpdw$K&-z5`v{^cbgA%kiyZFe=EY11xjX zgRoQ;ZhEigc}*=)w7H3LI|cXM^s&B|))UxVM1NY?{hZ$h$%J`(4VEgt%# ztneC~@z77GwP1MaU7U(;x>H6iTyd}0)l2XAh~u^y2me;sV-sGT(p@R>>8?2ZF(>Hf zrT1f1b?{qD-5<)l^fnmuYvi1c)uUX4uAazuYpM5A8(~IE)aIXqJuUTcO3^3U=}v>0 z+sQDec97_;cVruWg~{G}xZ8$bDcAgSruj~1d?x$7^$_9b56xTYZAja>m7eUvwRv?g zua!Q_x$;tT^xU@6M^9i2jzb?Gy}i?dZ<|x4CPz3u0y#dq4PSvxK6_d=Z|wMlvb7^F zSZ|SN2U!^eb#)4WMZS7-^$x7^)t~gg^bUEASHAPkcBHCgHt%5;q^~9EYzo0C)p$jfbx6;oiU{hPYv)TYZ zwAGWHj(usi?uS>L+rTpcy3M&332MA40eTm$B+J7n3eY3e?rY2l3)WGDeDmk>BKrB% zhty`>CuEErH!Dgx-!=Nxaj%X^pQaq@hPi?ksHZsBen7%n7!jzCa@%&FUKqzh^NqL| z;}inF2kKGQyahybRo;z;xlcN^(+eNRwKNP2(w}j@`Hh(md=RA1ch0*@?gychP4DM> z><)n)_vr8ZgVZ!dcd<0i(Df;&fyNIu{W;Zn0lv7@{*QLhpKVDG61xnJBseyhUF{~k z+d==3l^lnNj`|l~Yfeybbi6Y$;h2+@1G6&)w;)X(PFICVD;80Ed4IzwrQK9rTz01MT>U?tx|b*R3>J?|*h zBmX>AaLpcgCrmGQ-dsz?x@^RD)}K+muFa#0HeBj&>D#LNST>$v+ z%HVsnZ3T^UmC&qY;QN$$ z?QXN?WzRabffJAF?cEA%%<}Ir=|}oIJqpp0dbqXGB)Tf!41sk>>c2dX&kc$q^~aqn zzaWnWJg(QPSjmS*QCEG2>Xi4j*#b*XIzJ2J9@C%mD$l1%G=4|byTtqKz^l&xfsWnu zu`Ti766q7yC{p7Zs=+a1O*j3J+LAkL+e3Z2_77^Q%P=Pz+xxOn6|GmQY}J04+e1I^ zysDUt*C68w{kVD%o{qt3BjL5yT~0IYxY3g)O&+6^Oqw=%%BU%$GbW5tzQPae4Kl$+f(o6dvhx(w(T*?e@ZS3i>4ckdg|^LbH|3(FeefeB>;b* zof^WUdMk=M{xJ3_j_rua{#Go|04p^HznaNPjfwedlKHrai-E= z5GRqK46g~ipfAU?WrVcy>N6aVz=9IqCNOO!5xy_*K`bcYdB{_|27w18a;#v%$iCgg z_#6KV1aVdf)(9Mn-YXlx9@NWXsW*XUhC3F8{ z$P+F=oJkp87Q{^c`3Z{g=7kG`p0|#;YVS(o$rWR@x_!nWPFLm;N@;R<= zGlAo{6eZKF@i0N8;6G+W1?n-83-X3=yjNh^B9{fehTDUrcbUZXDML996u1&G6=;aS z@h@;pyM$DrvKR3AlVEQKC)(mavOxIQWKjTt`%mTgq`-Fs4tbI5E2nY$s{+&ZBW18v z;PTfvrbkBcEi*a3pD6@w1o7B#Q6Pcm3B2HS?(nU^fwMT4kLQEHAsD%V+-!cl0R**lK(t`X`_|!J%MS% zm2lEXuFqd&;!Lv_Q-D*8xj^>PYXZv}dgO8YN)do|ZYf|}+zKXt+QB8fLEtsZI6f`# z9sHL<$nKNM^=}mt#^+DHmO9bl6#|cc^I%F>sm^Oh3-w^niz;R=_e}lle z0^bz)n80|~PNDHfX@~D8)xZM@!mkKSn?Hp23rssTgzpMW+dhQ5j_3Zg(L;Egz`pZg^!vFkh7WVTV@ePa*ZJT=W)R{fqP|h?7f9Mygr{}Ux6zYaC}VQ zzux9Jr;O|47jm2@@FIcB1wKxg-ak4bq8fB~hdWIAgyR_k=Lx)5V3%@k_s-#Z+MOi- z@d76byhGs40yoR$`hx%QDxb0%DzbWv( zcR9A;*%uWsYzfC90xwx2=Kr38xGDs30>7}7JJ8`71*jCbwu0mK`P@EY8^?PlT5qR%1(J3Og^8gXcxq$X!DZ^C))2SU{t$^FZ4UQ)Z94YWm0+$P%RLJ$m z1l}p|DS^W>S8&1D9X#L~fpY}*T*>YE0xuM}NZ>mHI|QB%TyNRQ<6RUuT;Or5xZQUb zw`cw=h)6*U$61bElQ@Bo3w&8%`UknlP8;vkB8LPH5%^i0WXL{D;Qa#c5V$|iA!MiX zPfRkEZv~Oh3B?m96%x#-;u-W6ctJJC?+Z)^rzlen3QPy6gzpMW2dRW3a8japsdXIB z6?jcOVSN6d3u3QHD9TNN8wKw7ArC+YMij7EU^*cp{FlIVBt&>f5!cfh5aBHX_YjyS zIVupHESdBFOM<8pf~^A6*%Im$*JAEKCs%}11*W4a!iKSFc{J()a&>1s1yd`kld5(7oyhGq?0*74S_Q;R8|1g0k3G5KKMBuR(x&FAoZ(YRp zrwlDPOHq^L3qh2?YXqiQjO-->7Ydwpi3h9~c+hX6Kp%5`U=zo41$GF0P~f89@%d8$ z{7bk)>Sd181rEHz@n-^G7T99v`pT=^o+9wDKR7NDm`>{{zC++1H#qh$<^Gd0?{Q*? zAY9y3(_yW^vjnaac!VdnciO}Q91}Q0;Qn6RzFpvM-W*?(dV#y)Bu6cpdC7+h-Vj8f zFUO?3#&^coXCEZ~TN4bA+JH zR*ts|d}=$#mQQ(raQvsMC_s$B4S4!N_+5dA;W-FlhrpIY9Cz8u{V#vZ@hgPs{iB-} zjW zY|j65+)2cKA)w<*!rEs%0A1}M{FuOWEJ}Ecz;p~u_=Lc8vP*b?!TsqVm~f52m;d5e z-67_G@4K8x!dZ+;Ojkt6VXeU4cvXb(ErA_g91q1=jr4SNgzOap(-jiJ5jbg){jv|o za|EWVC1kJOh0mV^J$*T0!C8th-8msalE8H5gz!58(-jlKCk3XfC4{@-Bu4%xA~=3c zU`r&&hXr2HgX7MbUvP&JPjVtv;A^oQgTQn*g)*oUnC_?$4&BZD=_H#l2%IPId4cIJ z3)vst!}W&;aGaSf2)g7#g2MvSWf#J&tGEMQdLjIRz;yYA@M?kc-{!bcV7e4T_ULNv zzoUTTg#y!IGTJkhdO^_fF%n8_4R^>Fc$L6(sfO&$zU21Ql^nk#@K}Mj3p_>OvjXP` z9J-hL)1@49{+}iYI@YEP4hu}@*n}g#;tn0wbDSpd9f8XQUV!J^q`xIFoqH2rvXA>; z7Wj$(a;%h~9s8fsCE&(GXGQ{W8(?-aPaj@$1DTzQt`UPrlqRpu|8SSpA~ zzjAy=;G#y3+kD3zA}?_~T;P%>j@JsD_dCb+0#CZkaquzjANL2x69vu;yvB)AL8RX1 z_=>=N?r_}odmdnrs+o^usldmaI6fLoK~E;BLs9y zf}Z))xPGUhG)v$m0yhf$gTSesxZe8&*H037kifG9UMlcg0-rW9zJG;+c=SgeV6(t; z1WpX$8GI@+-om2--V}He-rykI?Iib~CGZr1vjpBOaGt=8glYa?C5R3`@c?usgaUpd z@R}Dnww~hl1tU1_De&Qu9KRuOqrjgDd`;j#1Xf0IedK8||GNodB@)!+IRc*+c*Yp+ z(BcfY=L_6d;0>>E`+R}By~^xh_UCn!2LWA zFy#`*^btxKY;NLsn!x$LbG$*|$}1fIB(Td>j=e5$|3rab71-quvg7lw6~qaXP?Yu; zxkJe{9$>D(eg5S5M}a-Aa~yez>*=yBwIF?zQh{&&#qrkyr{3W>_&099L%0n({6`S| z@s}Vdzz%^Y8MpqUKkDMCEa3mYGwn7$3TH*}HJ%u+f1-NI1BWsdZ)T(82DtUI{%A)@ zQ>Y01OP)hCXIy4tXr8V=0zD?^9!Jg5EAj(o!bRH~INlO1dD6#PJ9eW@AC+GnkB`=^h>(kFM6w3NvcpA2Un&BSo}75$MQX_C@DMZD(4LfQw;Pr^kSCFKoOdf^3fICps!V4gD3O0$P zrL50-UZ(~j>jM*};7crNDa#VFWzBiCto2}>t+xvlw3KZSI0y$&$`psRDOfg1OPM@o zYuxkEoJgJF}oFy z*aL2%%qD3mlZT9t`*4{{3B=6M18kC)I(himBy`>EuxKe}Yya1KBXDVJ9YEO?W?<g37&QlT@kxyB5VCMlJN z`j>_DCj2-@e-v`&;>02k_-nfI>`csOP-&83gBclsHs%_*XP}&0>d;nV5evNt28yZ(wwK zf=4tl{Prp?`lLxp<=u=`LTX|@NlTNI%4-|e7#as~TVChL6gtH4*Fjv; z|4S#Yd0Z2^BFLSO{Z-O3TY38<_eq|uiTTPznxs@-3{iS=sbwech?18&d0}Lh(1q`W z;R~=Ll9xJpk>sk-`R+2uUz_BmPTnt>^c2rGau;6?OJ3^ay^~*sE)M+O#ttEADU;Vx z#>Db)mqoUcmpXY(N7hk@Th zeN>R7Wf*zO#SzED?11+cV(BFH*4h8X_(1c#u1g{uR8N_flNy~8ZvQnEwo~xw+k``lkki68%OH9*+4nGs} zuscXv%H-Xq146b6a#3cJw3Nv!PeJ{7u5Q1;fjk_gBrkRHD%32YTks1cp)N@BQYWuS zeJgah)_s@Oy10u@5-FpbQ}lD6B%W;}hRellB`F50xL%VNwVL;0s+FsI9Ca-pVB4mi6^JzVx18$a+M(Xg&m-+K)p))aG{z;RR%FAHQ z2Jk}WurK?`<_%%DKDW-7s|jU2%CbPMef{}_O+puV1;Uo$+Um-~)sR=;ZV8!*q3u-K z>iw5g-imu>5HDdRoL`PFucT$h^0M3tAshAwFQDY5PF|urD|Ej2KawcGOLU~26(-1D4 z1wR(zcq?fslQ;J!3K?SfI!|xgUK*rOUg;|rLd2#pNE)P2-u1g7g#F>Y6}XizX(^Le z00$1`<--YhB~8HI;u02i)d&)Y7-oF1II^8l8R}tzQ z2T|dg6EZg#GTwwMbSX0HnjN!bDB~_uA(S#_C`qP7nIl6fDmV!92{u!+Ke!+OUs=4c)yR5MZsGcYWJv>GnM zkrt_zzhtFF9&){td;OnFqna1P395Cnu~Y^gBciBYW5i=qvZ>;ZS<$Y z3=9i6S2AXGRJd)UKNWUxSlD@vxh%0#2?sMUtoN)sTDEF;L0nbB!46I-xTS3OWNxD{ z+pY=L!3+$`MTd@&;Yf>B%6$M1fLKQw%OGhLw1ESlQaDK2E@d!XwBJD|GR(lR4t24Q zY}oj^2-=}>(7_H4>r;O)M_Lrpquk=ge@|q)n^JcW{lhi2gbU3uh*{zEuB22PQ-Y2A zTQ)RY3=P-R*TalD+}Gm{L|v*}lkFu~?~KFayI{;7YzSTE2L&Q`1ieGccuel(K!CG14M!lBTW}4uDGa zHf6iApN!|5tzHXS*ug2)-IVP$%r$-@uI{4Q0NBA{ZF0HsGG2q{qV{f$gAMH9ux42> z7b*O9Q!ff;U|7oBc>;%fLA#;8{pjEVidD`R86~Y=4th8MV)?VPzl^DB`9231P^Ffg zvVDqCRf``Hv}OPzBrhHVMTi^V>Nz=Em8Ev!47UvsciKlbA!YK3TFc|u*7n573Sa5tToLOz^5|uWc*1lr1B-1eqfKXwv~XWV zM-L8wN?3q4zzocV^J&`5m+?r8 zm@sM>4uD9!>B=B!(LtH%=K$c4kh6q2tSs+mD$G5i5@>7do4PA)lwi z0=a*rMTZFb^56iN+Fr)n%wVc8J4WXW%)pQiRM8;g4HvG2X<-JYRIpXHyD(O@qX;@i z&vV$pWpOsqH0DUF_aPfN03z*ZCxgYpERNcR8CXSU+3tPDDmn|dIHfbhc5q5%U1d9H zq5LxjiRd^@KRwLAklfUCk;GIj>PAsF3@)I^dkSE*s;lriPTw2s;FL@0qHqC4n${df zRjm%H>EQzE$r)VfjH+6l(GYaLcps#?t7N+Y>&0mY*HzZmrq@k&IPfnlwFIAf|7*^v|xE}&S( ze~Zyr(LRyR0+@keb$_`vavwv)Yz2cE7#97vW-L*}C(`2sW?;w#2x5%1DDu!BP$#0ch!#L%;J0mBR|ozo)@Fh*MKE$ZO_h&+m7234&t zZl}7}U(Ra6O{#G+sZmO}+_hOEI)zRHm=Osnrd=Z>Mp|}X-~fo+(;W2tEA<~02}~idMzcaFi2W- z2%tj<2SB9Vj$@Fts1QQuY1n@sA4tcjhi{(o>vk$ok$E>i9xX|Xj*QDdnn_ z?Q4vwTAgRLfeR?IbS)3ch*84wIyDS4FeLAmFcvHP2-Ct0Oexo`Y;QRvqgkC3jx-so zgZ+OTr}BOPcb?xltk?k%Ilyj*WkAwmuF|!J1K>vf;7$fNirF{li-j2&a)!Sjk@4(O z#LFAhcZD4sa*553NzPfg-lXdWW?;=z<=<-^C$Z)t`X;R_g&COX9f_?xE-}+PqRA~< z2>?4dWGpW_A-UvC`XMQr;fEa@GMTS1r)tsBK+D(Q0_yuvHf?rNhE%nRYT*Kk^k)}F zhd)xkJU#6H<2VgE2%MsoZgU-*;p_s6jOojaQdQa8bYFoP7!s*RoRT|9TD{}izyUBa zNB%XD3`UBfca(u;HUmTAb<21ePt{`L9oj>1fXM9jWH3TRQ#>1(fg$00Gh@v~fzm9@z>o)?!5C@LA(3vk zZ~#PtxW#F?f275*vvfJZ0T7wv!x{7!(RZm~n1LaoJc2ROYCJ6*0Fh{($DnnwXmXFb z53qwn0(yf)xr5%q?;af&n1LZLeF9_MMYMv!3=HY&F^qYL0tJH^7_!*S&d5e-U2rD# zb72OCNkB0-Gbpr{uJ}a9gE!-#5*T!~mcC05c zXV*YDKA`&+?BJB2Sx~mqnNzj;Tq;A^Sr0V*szop{>6~maLVS5Z7dXraRK(at##F7o z4pT*DzlWNB)vI!$fQA=jpsEeU)rT5~vh@Gghnl`MQD|VxMd??y+OO2Zg~m{W;2EQ; zmid)%0Y!NNyGt@EP2fGE`yI@{P_dF41N(#m39lI>E&G6Q07T^k zr^|AuNUNiOdN=@D(+?jh+fx`+wU|GJ27urKI-Jq{j1IRIUeD-?W-~Bf)*=X{bZyJYwLoy7S>+d;lv-YrqQ+^{#*)Cv?w5YI6Q&$fMz|>|EY;;w&t!niO z)WZcdi&0-jRV_Lmq}v@_K$V}SQnoKK>LRY>Dz`f}1AEK5Vw!B6w0Ja!9#e1tL@xXU z21$$4G*zkP9C>j0tW%gHEnk=509edmDTAbi`%HQ*C>8}zG~wo`;&Jwx+%wYhLgQi_ zh=b}L- z9YfWSLdHnT*%CMqFN#0-&kebU)ZfpeelpC!>{`liRDV-qb}dCvo~Ex2?BJrhNG^}L zDDfqau1A=GZR;a3yIV5cHqrh$b*Etl<~vwoX^i=bu;=t3h8Y-2qWpfFqbVg)#Qfbf zxd9hYt6?(YR7OdQG`cWs-~foSDhC-{Du({6>1+uzFjQOlT}*v}`)BA2`sPt~W#ApT zPo()&{7dcuBA^~i2!o_02nRqEYPrTBX$iss5CvSyXUL{W%gYK5fT-*;g+bE7-Ip$_ zk^kMrZLCi+M_Nu8z=42q4A!KzHx0;0b71+J&U%=Eg?h{Qml#vETs#UFP@ho}{q3HN z=p!r(=oShyuvEr^8B?`*HJ?WA*#k0z^(`hFC<(JlrSZ`yFIwvpb#t1XQp~Q~yec3W;F>ET`zTp6f+BPi z9021O3}H~!;@UoH94?^7TxaMSqohTJ&9umz?ch+4sKP_Jld2XSbLiz5E}(h-ayMNV zRkhlU)WHSRl&cyoW|Xv?-i8Ap${!^$NQ)j{(ZVs9fuSnWnn!XkSBrvIbmN2>7>Xrj zGB!)tzNR}O%)n4WsX~^FrfRW$30)6dJP9O=Lfdk;YSu&u}W7+OJVf&U=e8UV3RiqX%Mp~pPQ%Z0ET#a&546YVe-qO1m%)n5E z>h~Pk>_uVujxd;kp*Yoa#@-6QcXX6t23E90{t+h_Bdrc3>fiwQoxvgoNsGdh^vcF| zaP5}LfZANSd+nBr@sFur!De9HS$i@@TJ}@m0O-NsCI&s0irUndZtzRI;jC{nrfPWu zg9{Nu8Ljw4?jdQp7z_@8`3&}8khGW(Lw6-O05)GH|K|A&l9r>fZ~$~>Fq=VVQSgDT zI+%fZuaxl`Je7N>YB|gb7tk4uPGoe3@GGXv9cEzLR>_FR8QUhJ6%1xz8LK5$%2D5t z&R<_B&EnDeEG--W8-z*FpTP!U!tJv%3c_}9$vY&skGW*A^0PA9$!1^|S<{c4tGy=D zvab#Yz*GjE7)%wGCG>Z|3@kB1Hk-&8X*n4V2f(7860G||HcZ(Y6wd}`V5xf~Hi)rQ z@t}l`GR(kA_Dbw9VbEMUo z2t6DClNtQZpsLk{j(WI&4mu)Z4u2`zR<%Uo0-DcgBBQF7C|p429hFh*7RsoqmMbmc z0$TN$L>DkhS`;pzJ{8-+MY7IePSt8i8@Pa?n4)u$Y`vYB{hgjGFatx4#qEq;JTAsl zzKj-jaHzfbjk%(eDyM@TT#W?zmkfI)nsGz)F{K6%( zUcp?li2q5KAj}Atdseo3lQGijb59QkKoqL9d@I{kwR~BE3n*$>j%HNV5`_yWs$0e| zs%p6w5iX!8d|AvWX}PCx07OO1&hO-&s#=a?!37k>GJ_aZwdk{h#H|P;bvkYQTFwO2Vv+Ni!|Xz$%`XSUh7D&kL_ICJuVo!FgYhoXrOrPSt7^ zJw04NU4BZ`k5N^NGbz-~XAj6hthY0%YW0uS!3A_RqiKwi=0*3v#LQy-Td~|x(sE50 z9EjMiRPLrTgQV44r49~&{tQlIkhJW%!2vLi!Dt3a%ckJ~sQkvMvYpK!X*p;D2S6(Z zbsyy)Lt27x0Bp?QI0i|ppdJo@tr(1EP}TB~!v)lz(LzQ^tMB_|{KXYxlTyRR!E$-7 z6a6Js=tDPsq@KZg)>x^jR+H+FVok+QRqg+`^f#o>DxY6CNa_)+Gx|#{Skq5c%JW|8 z(MH~EFfA(sbRQuBT5+ld^!Ac^I%`ZEQB85xkBOsatT6*svz+wPbfSq#eFO5J9Yot^>VvXDhQ!3Ts)Wl!(HB}p^`aXB79ID63G(Vapn$WMf$y^JrKkYmCf2WsLzin;KO9S-Cf0plcWz{(rrh zHO9AYv&Ktx`I?f)+juwDc#WUW8sjV{S!3X!xF(ffRvN^}L5tsIKn$deV%?5k?*(g& za9m@J5s-4VRezL|~iEC}%F`jx^!x~S4bFA?mR>B(ZtIg}EpEnrcy>zNH7~#e85No_Y=Cj6Q zrwP@WR*p2@SN~>>_vS6EF{+)*8Uy6oy2|J2YQeC(k7}S_YT@DSeXQ|FEMSdiseL`U z19+dG#u^XhQ>-yCP|6x33NH2O^UBqNVT(Y;KpJBcr&*(e^oBLM2RduXqeC@;HTq&F zS)&j4ku_dsTiWo|g07A~8I&DB=P8Oc2G^gnMrX@hD|z&Qda%Yag{7<`l4RHEK5OLe zIq3Lm!BEv&oeYSxzlb$bfO|BMehjCEv&NkDQ`VSqZmgF)W|sX}W9BqYZ;$|{R$noI zDfwm%r61GCQ&?jL`XFme>F2YKO^{2v>>J6?XR-EYopDO~_Ziq=94`&eSi3M_&6l%t zjP!f69>jVlYiri`SYtA~rLEinBt5TXja*>ETQ(r+cR&;Q0i**RVvSj{N=>C7t6(Ou z#%%Xh)_A|LwUazn1qQO_G!=u<2R0y&WRSi50P!Hmge~NV?YdH->-@m@O1TfeV$Qs243Rz=0t+s{av7FY6_3&+S2X?XcW&MQpsqNC= z_z(H{#2uumeWPJ)@ZlRzGHWD;Hf$+Buv#xKqt&djAoD$IEXnX`C3#H9rL#uf$p+-DRB0mtr0g14qk*riF(vBXR`N*sddeC}dBfUCKl1q=unvvnAIllk zUIIvQz0MkIS==3^A31rKStFUxrGxY%c{rUl1{HfdNq@v1`4!BD)*Iv@O=g3-Q4*2u@SW$kM#?&U=y5RGyM<|-Qe<7m1xk;7&-PydEF{E!s zvqqZc1=d)?{(!X$f4+b*E|BdOJuwabbx<|g#- zH}S>X^r5w}&)Fm*O8xO3w7 zVR!#54IY2(cyZO*rqyN}J)Tppe%6pGSCgVw{ygSe;<>|Gb8q3wftz;ByEkRjEA!7L zv7K$q)`X^51?F5i(&+dk^LIHTZ}fTpF=B9V$)+h6J*}IhJ^XFxvC)g>wD!9`KYmH& zJlBTvx}@1?<{T@0sC#+;zW>j&9*#yH^)8I=YUn@GYy9i9H%Zw$7fqUX{&3-d@!{J` z`aDdu_qqOu%gx#gm(-gYb!M60;r+cj{S59F(LX0VXzyllpny-$1hZeoH%)pL?b@nkHOrB%^|uV3yeBXrHq&|Ju7-iy|dA>1)9X{5%Z*SN$sb+f6pz5yST}sz=v0HNN)3MeAD<3`F ztkK-u_qQUWADyq#sZ)h#1HaUqvu542j~?Aa{hy6Kw7}P*uH!B=>DDzmyTbva zgO|>X{^38(`O1t}sXbp5XI~FmZ~NiV*K@k#UCVh4{ny2+QsMPg`)*7hk~dO&^+vOC znrTnsUuz9fhO+C9hkx9>wddm6xfgrHH_EUbyEk}Ul{vl{!QKxSn0&HJdDJqisO8d0 z$6L)Tj&<}ZJmE0qOL&c|&q6fkR-05GKDp=kJ)M{LJhW-@v=MW=o_AZ6w8*x6$F~BS!|M4J7DO$&xcza+;wb7_0yZZ--iWcd|KVB=h8b%m*gz(?Y70R@>%Me+FPCS zvYUn?eB=;Rq)FRgwx)HHN* zj~a#R8kVkKFmwH>X6Gv23;z3X;O2|d3|EuV8)X)c_VjIcsnUYvVcYK)ndXVFwTAe# zpD{G%XvSTyNYq`jzt5Ww)%rGa$eh@>#zAdD3x|DSdsj`0eziSh&EV(* zeP(pqYH7#~b6&Oc(!ec-oYm)#R}So-n0aQg&%)YOOgsCd)f+uw333ZGIj zEI-nsbGO88hc|7{NLW3$%wNr(_j}@5ZqBLTwGIobhLqn}n!f1P@pF?8224M5!pmC! zOPrIj?Qh$78!cP#oBx%6A0|G2kvV?mqkDd99eSr5TJ)Ic^lAE;>wo_? z>UO^iKNddCZvXN0i^ZmejdF5BivkB93#m7=Xk4eppSpG$@V(e?P=_4N;8E_y8P&%u z^C*|OWAe_Fp}9@IU8+39Xtf+qDMak!`emOmCkM7vpH~Ea_ zM?asBZC;=C9GvrbWAC6B9o{YaKE){F+seKcIVGb{^s{dByUX_jXHLiZe?5OKZ{wU3 z+L0qO+ix>+?N_nS@;coEYW@{hW6vS;DknmsoY&}A-SZGTChuO>c0k~y%_|NU#d^8F z$}DX)(a`x>SUamlC!NZcwEo<9?T4$D+sl?oyZSZ2er~_gy&J3Ltk=yd=Mi(q{?FRE z#>UrA9@#Q4#biKj+sxooKYX56y7b}PtWb;W3(Y>ac=f6N zDt~&Wzb^QR~6;L7_1XN;+&f zb-awV*{fK$WUaQ@mzM$Qfq%Z)Xo##+b8-A>w=0D$?MKB%&T1YRd#2*1l@9YCEi7s7 zc<)^kzoM;g{I$OBf%P9QaJzKt-wUO#CI=@}t1N1b4*4T>R?&#Kqi#j5W|#(d62WWR zuJpL?3fk3r*Y%BY3%-4E{4~z2WxsQtvyDDJ z{9;&T-(>u<%RfVpR%%-L>#X?nR?{ON`BPGIjzXRHq z>*lB(*U@uNwEM*&Zfm~zwC_7TI_Iv>r?nSXjr-%(zx8ALKdj#A+x;5vYnX325E2=f zyJ_V7ylMlMdPb#`?w`{9Xo83MU}c(Y$o2WMW`%7P@8Mr=Uhxi}7+f|q@Wj@8iTV+Z zzZgwT|7!nk+j*z*r@LId7nqymSfP1Hh5buLN8UBrrVVFM7;v&~eP`?n`cl zT@5H(!{Xj8i`Nb>bFMr#ZxOxZfZ0rkk#`rnHyz&MuR7%i&s%o1XX4&AecE1I;;=^h ztzXgQkpX2Mik9_EBMe3Z3!`5?C@io!zqP}=PR1!iz0X#3HQDy^*~&I&N?fhW-kCCY zoBQJGCpz0&k6N&`@Waa3;a%&UczyEih3r3Xr=2sqJ@?g(m+rw)JJOSD@7%nrQ>0zY z;7xX3Lkm4eelfD|7@xM<#A)&$*Yfip#$P%-aN@Jsm2%cQX193iJ1|n0JlM<-RLkrA z)18^UjHbu6I#4k1#I;T@Z7NjO?0OwncSz`Vb#7Lm21@QIsWnf33s&{+U%`*&2wMNoyQN}9NTA2Qg+{@ZhiL` z->}{5{9*s`BX#yns@AIQ^hy=J-t{;+z~S+=u9ppoX)RChZ0h|ZxK5RcQ*!rNd!Kb` z^`XnHH@#}R{d>3f+v`8hR}NpNzdvzx=i&)H-=)=T7WLiX_5FB%!z`z>|At&#?R4Su z&*tY3^b361JmGUw`@|2Xi_(k3K3SMVoDJRZ$kDH*=i7BD({I=PCv^DQpNn4icfQvv zR6pj;*Ea8;^>&D_Xt-$H^r!2cfV=xW=N%iAX!vd5)(zd)zVP*F+hpps4EL1L&dsw6 zXT&^@9^lM5W3qbQ9iQ!3`N7i}&s#KXyUugq zDqsI9e+t`a*>!_vHf*b1=x8Lodkp-zW}KnGzx#xl)}qTlnKj2+SDuo6w)=jQ-mCLw z)ioYarQR3!Lp~A316=H#PfT9^;?bZ_4*Kku9$Lq~zm=c%Vs*fStVTI*J~ Date: Sat, 4 Mar 2023 12:51:59 +0100 Subject: [PATCH 031/436] Simplify ngram conditionals --- bin/generator-utils/ngram | Bin 533779 -> 533779 bytes util/ngram/src/main.rs | 16 ++++++---------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/bin/generator-utils/ngram b/bin/generator-utils/ngram index 8991327e7786edf57bf9d0a3b361bc9de2f948c5..662b7b8ee98bf700eaf005a69f2ddfd10cf3d700 100755 GIT binary patch delta 12476 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 delta 12522 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 diff --git a/util/ngram/src/main.rs b/util/ngram/src/main.rs index ecd7c7fdd..156b39cba 100644 --- a/util/ngram/src/main.rs +++ b/util/ngram/src/main.rs @@ -14,16 +14,12 @@ fn main() { } if let Some(top_result) = corpus.search(&slug, 0.25).first() { - if top_result.similarity > 0.99 { - println!("{}", top_result.text); - } else { - println!( - "{} - There is an exercise with a similar name: '{}' [{:.0}% match]", - slug, - top_result.text, - top_result.similarity * 100.0 - ); - } + println!( + "{} - There is an exercise with a similar name: '{}' [{:.0}% match]", + slug, + top_result.text, + top_result.similarity * 100.0 + ); } else { println!("Couldn't find any exercise similar to this: {}", slug); } From 6562ae9a7b831da6edb4029e00866c0ab1f97b2d Mon Sep 17 00:00:00 2001 From: dem4ron Date: Sat, 4 Mar 2023 17:34:27 +0100 Subject: [PATCH 032/436] Slightly improve templates --- bin/generator-utils/templates.sh | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/bin/generator-utils/templates.sh b/bin/generator-utils/templates.sh index 4ea8eeed5..6e9cb1f88 100755 --- a/bin/generator-utils/templates.sh +++ b/bin/generator-utils/templates.sh @@ -45,23 +45,19 @@ EOT # loop through each object jq -c '.[]' <<<"$cases" | while read -r case; do desc=$(echo "$case" | jq '.description' | tr '[:upper:]' '[:lower:]' | tr ' ' '_' | tr -cd '[:alnum:]_' | sed 's/^/test_/') - input=$(echo "$case" | jq '.input') - expected=$(echo "$case" | jq '.expected') + input=$(echo "$case" | jq -c '.input') + expected=$(echo "$case" | jq -c '.expected') # append each test fn to the test file cat <>"$test_file" #[test] $([[ "$first_iteration" == false ]] && printf "\n#[ignore]") fn ${desc}() { - /* - Input: - ${input} - Expected output: - ${expected} - */ + let input = ${input}; + let expected = ${expected}; // TODO: Add assertion - assert_eq!(1, 1) + assert_eq!(input, expected) } EOT @@ -77,7 +73,7 @@ function create_lib_rs_template() { local slug=$2 cat <"${exercise_dir}/src/lib.rs" fn $(dash_to_underscore "$slug")() { - unimplemented!("implement "$slug" exercise") + unimplemented!("implement ${slug} exercise") } EOT message "success" "Stub file for lib.rs has been created!" From 871618baf2abae65e497a85795dfb7f1c9ef690b Mon Sep 17 00:00:00 2001 From: dem4ron Date: Sun, 5 Mar 2023 00:56:31 +0100 Subject: [PATCH 033/436] Add template generator --- bin/generator-utils/generate_tests | 38 ++++++++++++++++++++++++++++++ bin/generator-utils/templates.sh | 4 +++- bin/generator-utils/test_template | 9 +++++++ 3 files changed, 50 insertions(+), 1 deletion(-) create mode 100755 bin/generator-utils/generate_tests create mode 100644 bin/generator-utils/test_template diff --git a/bin/generator-utils/generate_tests b/bin/generator-utils/generate_tests new file mode 100755 index 000000000..883eeaf21 --- /dev/null +++ b/bin/generator-utils/generate_tests @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + +# shellcheck source=/dev/null +source ./bin/generator-utils/utils + +function digest_template() { + template=$(cat bin/generator-utils/test_template) + # shellcheck disable=SC2001 + # turn every token into a jq command + echo "$template" | sed 's/${\([^}]*\)\}\$/$(echo $case | jq -r '\''.\1'\'')/g' +} + +canonical_json=$(cat canonical_data.json) +SLUG=$(echo "$canonical_json" | jq '.exercise') +EXERCISE_DIR="exercises/practice/${SLUG}" +EXERCISE_DIR="exercises/practice/${SLUG}" +TEST_FILE="${EXERCISE_DIR}/tests/${SLUG}.rs" + cat <"$TEST_FILE" +use $(dash_to_underscore "$SLUG")::*; +// Add tests here + +EOT +rm "$TEST_FILE" +cases=$(echo "$canonical_json" | jq '[ .. | objects | with_entries(select(.key | IN("uuid", "description", "input", "expected", "property"))) | select(. != {}) | select(has("uuid")) ]') + +# shellcheck disable=SC2034 +jq -c '.[]' <<<"$cases" | while read -r case; do + + # Evaluate the bash parts and replace them with their return values + eval_template="$(digest_template | sed -e "s/\$(\(.*\))/\$\(\1\)/g")" + eval_template="$(eval "echo \"$eval_template\"")" + + # Turn function name unto snake_case + formatted_template=$(echo "$eval_template" | sed -e ':loop' -e 's/\(fn[^(]*\)[ -]/\1_/g' -e 't loop' | sed 's/fn_/fn /') + # Push to file + echo "$formatted_template" >>tests.rs + +done diff --git a/bin/generator-utils/templates.sh b/bin/generator-utils/templates.sh index 6e9cb1f88..8b85d96b4 100755 --- a/bin/generator-utils/templates.sh +++ b/bin/generator-utils/templates.sh @@ -24,6 +24,7 @@ EOT ) # fetch canonical_data canonical_json=$(curl "${curlopts[@]}" "https://raw.githubusercontent.com/exercism/problem-specifications/main/exercises/$%7Bslug%7D/canonical-data.json") + $canonical_json >>canonical_data.json if [ "${canonical_json}" == "404: Not Found" ]; then canonical_json=$(jq --null-input '{cases: []}') @@ -40,6 +41,7 @@ EOT #(see kindergarten-garden https://github.com/exercism/problem-specifications/blob/main/exercises/kindergarten-garden/canonical-data.json) # so let's flatten it cases=$(echo "$canonical_json" | jq '[ .. | objects | with_entries(select(.key | IN("uuid", "description", "input", "expected"))) | select(. != {}) | select(has("uuid")) ]') + fn_name=$(echo "$canonical_json" | jq -r 'first(.. | .property? // empty)') first_iteration=true # loop through each object @@ -57,7 +59,7 @@ fn ${desc}() { let expected = ${expected}; // TODO: Add assertion - assert_eq!(input, expected) + assert_eq!(${fn_name}(input), expected) } EOT diff --git a/bin/generator-utils/test_template b/bin/generator-utils/test_template new file mode 100644 index 000000000..fbcebaa36 --- /dev/null +++ b/bin/generator-utils/test_template @@ -0,0 +1,9 @@ +#[test] +#[ignore] +fn ${description}$() { + + let expected = vec!${expected}$; + + assert_eq!(${property}$(${input.startVerse}$, ${input.endVerse}$), expected) +} + From 4c0bb6599369c0ee30616dffd7e340129d7c91a0 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Sun, 5 Mar 2023 01:24:23 +0100 Subject: [PATCH 034/436] Fix a couple things --- .gitignore | 1 + bin/generator-utils/generate_tests | 21 ++++++++++++++------- bin/generator-utils/templates.sh | 2 +- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 6723c90a8..d5db98f12 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ bin/exercise bin/exercise.exe exercises/*/*/Cargo.lock exercises/*/*/clippy.log +canonical_data.json diff --git a/bin/generator-utils/generate_tests b/bin/generator-utils/generate_tests index 883eeaf21..9fc7f933e 100755 --- a/bin/generator-utils/generate_tests +++ b/bin/generator-utils/generate_tests @@ -1,7 +1,7 @@ #!/usr/bin/env bash # shellcheck source=/dev/null -source ./bin/generator-utils/utils +source ./bin/generator-utils/utils.sh function digest_template() { template=$(cat bin/generator-utils/test_template) @@ -12,15 +12,18 @@ function digest_template() { canonical_json=$(cat canonical_data.json) SLUG=$(echo "$canonical_json" | jq '.exercise') -EXERCISE_DIR="exercises/practice/${SLUG}" -EXERCISE_DIR="exercises/practice/${SLUG}" -TEST_FILE="${EXERCISE_DIR}/tests/${SLUG}.rs" - cat <"$TEST_FILE" +# shellcheck disable=SC2001 +# Remove double quotes +SLUG=$(echo "$SLUG" | sed 's/"//g') +EXERCISE_DIR="exercises/practice/$SLUG" +TEST_FILE="$EXERCISE_DIR/tests/$SLUG.rs" +rm "$TEST_FILE" + +cat <"$TEST_FILE" use $(dash_to_underscore "$SLUG")::*; // Add tests here EOT -rm "$TEST_FILE" cases=$(echo "$canonical_json" | jq '[ .. | objects | with_entries(select(.key | IN("uuid", "description", "input", "expected", "property"))) | select(. != {}) | select(has("uuid")) ]') # shellcheck disable=SC2034 @@ -33,6 +36,10 @@ jq -c '.[]' <<<"$cases" | while read -r case; do # Turn function name unto snake_case formatted_template=$(echo "$eval_template" | sed -e ':loop' -e 's/\(fn[^(]*\)[ -]/\1_/g' -e 't loop' | sed 's/fn_/fn /') # Push to file - echo "$formatted_template" >>tests.rs + + echo "$formatted_template" >>"$TEST_FILE" + printf "\\n" >> "$TEST_FILE" done + +rustfmt "$TEST_FILE" diff --git a/bin/generator-utils/templates.sh b/bin/generator-utils/templates.sh index 8b85d96b4..da0e13aad 100755 --- a/bin/generator-utils/templates.sh +++ b/bin/generator-utils/templates.sh @@ -24,7 +24,7 @@ EOT ) # fetch canonical_data canonical_json=$(curl "${curlopts[@]}" "https://raw.githubusercontent.com/exercism/problem-specifications/main/exercises/$%7Bslug%7D/canonical-data.json") - $canonical_json >>canonical_data.json + echo "$canonical_json" >>canonical_data.json if [ "${canonical_json}" == "404: Not Found" ]; then canonical_json=$(jq --null-input '{cases: []}') From 877dc7a32c953084f151365e0c1ef8c8139b3144 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Sun, 5 Mar 2023 20:23:41 +0100 Subject: [PATCH 035/436] Add pub to fns --- bin/generator-utils/generate_tests | 4 +++- bin/generator-utils/templates.sh | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/bin/generator-utils/generate_tests b/bin/generator-utils/generate_tests index 9fc7f933e..6ba98e4e4 100755 --- a/bin/generator-utils/generate_tests +++ b/bin/generator-utils/generate_tests @@ -24,6 +24,8 @@ use $(dash_to_underscore "$SLUG")::*; // Add tests here EOT + +# Flattens canonical json, extracts only the objects with a uuid cases=$(echo "$canonical_json" | jq '[ .. | objects | with_entries(select(.key | IN("uuid", "description", "input", "expected", "property"))) | select(. != {}) | select(has("uuid")) ]') # shellcheck disable=SC2034 @@ -38,7 +40,7 @@ jq -c '.[]' <<<"$cases" | while read -r case; do # Push to file echo "$formatted_template" >>"$TEST_FILE" - printf "\\n" >> "$TEST_FILE" + printf "\\n" >>"$TEST_FILE" done diff --git a/bin/generator-utils/templates.sh b/bin/generator-utils/templates.sh index da0e13aad..accb39f45 100755 --- a/bin/generator-utils/templates.sh +++ b/bin/generator-utils/templates.sh @@ -74,7 +74,7 @@ function create_lib_rs_template() { local exercise_dir=$1 local slug=$2 cat <"${exercise_dir}/src/lib.rs" -fn $(dash_to_underscore "$slug")() { +pub fn $(dash_to_underscore "$slug")() { unimplemented!("implement ${slug} exercise") } EOT @@ -101,7 +101,7 @@ function create_example_rs_template() { slug=$2 mkdir "${exercise_dir}/.meta" cat <"${exercise_dir}/.meta/example.rs" -fn $(dash_to_underscore "$slug")() { +pub fn $(dash_to_underscore "$slug")() { // TODO: Create a solution that passes all the tests unimplemented!("implement ${slug} exercise") } From 00d2b9265d233c5f1cf1dfa26cf991064e8eabc3 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Sun, 5 Mar 2023 20:43:32 +0100 Subject: [PATCH 036/436] Break out canonical data fetching --- bin/generator-utils/fetch_canonical_data | 24 ++++++++++++++++++++++++ bin/generator-utils/templates.sh | 13 ++----------- 2 files changed, 26 insertions(+), 11 deletions(-) create mode 100755 bin/generator-utils/fetch_canonical_data diff --git a/bin/generator-utils/fetch_canonical_data b/bin/generator-utils/fetch_canonical_data new file mode 100755 index 000000000..bffb7db24 --- /dev/null +++ b/bin/generator-utils/fetch_canonical_data @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +if [ $# -ne 1 ]; then + echo "Usage: bin/generator-utils/fetch_canonical_data " + exit 1 +fi + +# check if curl is installed +command -v curl >/dev/null 2>&1 || { + echo >&2 "curl is required but not installed. Please install it and make sure it's in your PATH." + exit 1 +} + +slug=$1 + +curlopts=( + --silent + --show-error + --fail + --location + --retry 3 + --max-time 4 +) +curl "${curlopts[@]}" "https://raw.githubusercontent.com/exercism/problem-specifications/main/exercises/$%7Bslug%7D/canonical-data.json" diff --git a/bin/generator-utils/templates.sh b/bin/generator-utils/templates.sh index accb39f45..056821a68 100755 --- a/bin/generator-utils/templates.sh +++ b/bin/generator-utils/templates.sh @@ -14,17 +14,8 @@ use $(dash_to_underscore "$slug")::*; EOT - curlopts=( - --silent - --show-error - --fail - --location - --retry 3 - --max-time 4 - ) - # fetch canonical_data - canonical_json=$(curl "${curlopts[@]}" "https://raw.githubusercontent.com/exercism/problem-specifications/main/exercises/$%7Bslug%7D/canonical-data.json") - echo "$canonical_json" >>canonical_data.json + canonical_json=$(bin/generator-utils/fetch_canonical_data "$slug") + echo "$canonical_json" >canonical_data.json if [ "${canonical_json}" == "404: Not Found" ]; then canonical_json=$(jq --null-input '{cases: []}') From 1f8c5092d805f65f83393cbb3098812ede903ad2 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Sun, 5 Mar 2023 21:09:55 +0100 Subject: [PATCH 037/436] Remove couple curl flags --- bin/generator-utils/fetch_canonical_data | 3 --- 1 file changed, 3 deletions(-) diff --git a/bin/generator-utils/fetch_canonical_data b/bin/generator-utils/fetch_canonical_data index bffb7db24..c2de88d53 100755 --- a/bin/generator-utils/fetch_canonical_data +++ b/bin/generator-utils/fetch_canonical_data @@ -15,9 +15,6 @@ slug=$1 curlopts=( --silent - --show-error - --fail - --location --retry 3 --max-time 4 ) From 90455d23331a53313bc8abc75e7a8c3331cf0e2b Mon Sep 17 00:00:00 2001 From: nhawkes Date: Mon, 6 Mar 2023 09:50:15 +0000 Subject: [PATCH 038/436] paasio: Add PhantomData explanation (#1637) Same as the explanation present in fizzy --- exercises/practice/paasio/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/exercises/practice/paasio/src/lib.rs b/exercises/practice/paasio/src/lib.rs index 8196a6c96..f530460da 100644 --- a/exercises/practice/paasio/src/lib.rs +++ b/exercises/practice/paasio/src/lib.rs @@ -1,5 +1,8 @@ use std::io::{Read, Result, Write}; +// the PhantomData instances in this file are just to stop compiler complaints +// about missing generics; feel free to remove them + pub struct ReadStats(::std::marker::PhantomData); impl ReadStats { From 90a54dbeed3e349bb7527d7e87e81a0ba6ccb46f Mon Sep 17 00:00:00 2001 From: dem4ron Date: Mon, 6 Mar 2023 13:07:29 +0100 Subject: [PATCH 039/436] Don't commit ngram, build it instead --- .gitignore | 1 + bin/generator-utils/ngram | Bin 533779 -> 0 bytes bin/generator-utils/utils.sh | 7 ++++++- util/ngram/build | 2 +- 4 files changed, 8 insertions(+), 2 deletions(-) delete mode 100755 bin/generator-utils/ngram diff --git a/.gitignore b/.gitignore index d5db98f12..e7c68a4ee 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ bin/configlet bin/configlet.exe bin/exercise bin/exercise.exe +bin/generator-utils/ngram exercises/*/*/Cargo.lock exercises/*/*/clippy.log canonical_data.json diff --git a/bin/generator-utils/ngram b/bin/generator-utils/ngram deleted file mode 100755 index 662b7b8ee98bf700eaf005a69f2ddfd10cf3d700..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 533779 zcmeFa34E00wfO(M^JaM`VKplONdi?8a4pKB;bszC0Nn8R! z+fk~w!CL~f_hv?8TMJfu+e?5hNZcaTwsv6xww*AzusVa_{J!U1lF5*u?d|V#KmXsK zd_I$z_gT+#p7WgNob#OLyssa9?~@@)X^KA=pG-bCq$>50YUP(wqxe+tsj9lR?D9)* zx^&JplG^{ztlU58gePg|cNDCus=V}u%Kq7AeQ#3#MEg0JROcfFs;ZXWcF)qjqKWPO zY`Qg=r+=lJh2&Yr)%%yEyuH8qQB}2M>0R@d^d=>?cY{hYkyBwcr{p=lJ;QDf9GZVs zRrBw>V@V%?iS4!OR(m6EvC7MH!pQBGoc6vle}2_%_sqZTt4phv*33(6?}s&3dzl$l zd3lb9uU}(4R8`$|$JgubxV5V8j(@$=eC|IVzgTLuSCVekl;@MRC-WLI+qtByvaD+6 zHCNBIW|#Rh=hb7iqj)y|>}Nrze5CCWe5$Ixa%Ugb5#pQM+jFV^c&trW#;}rPhud3?Snx!?? zZ|hm$>U&mpd)GSSdHmaHGW?afVct3wdJL#)~Kdr z*_~u79-2d`3pC_$=k2NMb*M^$E&CzNX=o87a#>Wr-peL`i z(;>)<`;&gB@sT`hNlXA6S|2RHGxy#lOK)3rPWd^&RX5M7d}2L;q<_7y-L~Y;+wVSS z`pN6D8LHHN{nf$Z?>^`A@zvCsl#=l@l_V?n1XXN5%lydS>Gt!rnTFaHKX5bm)W1_0 zIE8^z7&wK2Qy4gffm0Ybg@IETIE8^z7&wK2Qy4gffm0Ybg@IETIE8^z7&wK2Qy4gf zfm0Ybg@IETIE8^z7&wK2Qy4gffm0Ybg@IETIE8^z7&wK2Qy4gffm0Ybg@IETIE8^z z7&wK2Qy4gffm0Ybg@OMUF<>NlqHRiTRLT?8-n(z7OY_d@(p6@MUiEgY(baYA&LscQ zuO#~;RVn@>*QfdqU1RulyBemL6CBB27jAi))}6 z1s;DWl`@7W8p=@7+=6ods9aBUs`Ads>v-hNi&Vz}chEDvCr7VAFk%bKc8cYCHUaKOU7B?zY8Pqca!*;9H z+$YN9+R>b~6V>7l(nI>_z!ApL8cm(TJ*7+;mpm)OJ4XXY_v&hO^+>fbl;MdQT1KVl)X3gm#Nso?2C+TmVS=H47nv`NqC?4f=! z^^by=*(0->XB8^{rWEBnoUfvX($tiLdUilyIcDRu+BgxXM-p&)_W(HU8~~@+QSNSV zIt85WvT=Hr;1QfoNx=u(xt!0R3pui1eYyVdFo zD6L?`>C7SH6}T_QqnBlzkIg>?+bb-Z4h< zxohlPW9YKSx4Vqai&9mWR)0&R`*>`l8hT4aV4|(nVaD3DciB#(UintIjOM~K)Q>!@ zw>sb`(2P#uOUIX8!uVz~&T__E2A+S}_8%^lIZ(REzqV=cFO708%InliqJA>?N%0?V z)O-d!&q!k}>dK!r#1s8uD!;*dc5?r*=hx!STo>gG#?g~{-}8HQPqg9;W6dnh!gmj2_k#0c#@~nY%fQ*C;O-J|I1^l! zL)&Hk*fz~~xrzJ1^JNZ>-T71MXw-GlhE7}1<}&7Brsm58{~J@(Mxj%||6SnfZs>F% zx|6kdFLdYGpmkjW?Po!w{poK+FaCx81pguM|2Ft62LHv-Vez6dbBl-G`}oD+=^dB5 za})Gf)_6mt3|jQ)H<-9Q4IKSdXmR(wI}Od%ON-S)i@?^0mrEJTC5&k%V=HHjW#Hvk zZ7vfh1Lp}ojxXEH+EhN-Z})3zwd@_omu+cF%-frgC;3ON|2chX;Lip9D&Ha96%gE2 zDYgHIUf(i~HF;Rq0=2Ap_TY^*?CssM_UUUk`_&%S^B<&LmtuLU%qJ-mB(0mgousqg zHr@Ru`!_Jvx`eNl(_a~5+-Aa;rMz#-zGv`#2tHL!*@4O_kMb3Q!$a5nH?RWlOl9&S z8FMW-G&E0CX{B$=Zw*@8A}~VJF1Lq1%jk1!+YdEa*Vb?wN>YR@nD;H`VeOBr#IcKaYzE<&t{LSU}}cA>FRHf*~|mpwrC$RPW0UYE)|cIVHa zcMTe3jV)s@-KKo8ZAKS+gzw5*)#}h5%@^eN5BMD{g^qL7#_pL`+kz7pa5`oFL&{U8 zD4TWG`0J8jIx@rM$ikyl)G71tc9~F?T_(dWGf71cBIAiXQ^$T0+mPx%xWVwpUU&O@ zu2<0_`U=h0?hUbz1%WB+cH|0zi zNIQozhbPn5#hz#;IzWiEoe7S!%al)Zd;F`*mDyfrGIih=QPxHGczDVrbcdOmnWsif z96LKjt=_2L9C<6w@tdx-?meAt+;lynKp4xq50i5VT!+raqJzR;cxojbTh3^#y%m_FXIeN$npmn z!x?pI^>ApIx%SQ4Z?zkmkl_#Qvf7zNowf-V_%|=V&@Xrp8C2R9d}LEs@Rb4Gho)Nk zkmy1|@F4UhJg0ktMf*X0N)Y$ zU+NP9B2(lq`Auwwe&6wZtTWrxeD9cTXudbiXXSgtd{(}%nco=!H*MbsoUumLy{nXn1kb$@6WfW)v_;9$CT~+_VZHqc55GRyGvzmwLhsZs>~C4 zperEz^dWc-{K}-CLg?oJ^ml}@wIWvvO-Wh~@J->{+NZMuyGhRj?kBHS{wLB-+@odh zEpwxj^4-XH;y(2E?7^=?e*^8ox5MLvc3uKDlmEj*oxRJ^Hx&F<*2I6)hLrhxbd(=~ zheRFaSm+PMo!odZ;-kE7v?%qPWX!ShnyhaQszR+wCy`lPMBfSKq4)kY^>*f%2!7ehZFITz|-kCy~cSeoEhN$v=nvlC@h$-;S>KS>(#2@E%jH1a{%; z0(-n%DSWSb++}^T-4si<>z1%T+S~7WjB&LWzSa`Z(gLfr<;=*^)lS6P2{IF$&wCE4+g8#f-~cprSI)^zW6U6Q=DLva!s6yEde``PI%H?V7v! zIW4`}zvbl}tLk5Q;w;ivQf5W_%R4%M-nk?FdoKUW^{+jV_C3v?IKFYwSmn?kmivc2 zTjoEvwl=a)8?xgyZD{kWufDpYnKr-5?}b}l+3^bXrN76t;X78fckUSa->>dSZ+PPg znKu(ppGZ3{+R^-iuQu$J={8;*eC=i|w=>6Qfty8)e^ueDEx;LA2yT{x&-vu1eNXqx zIBO|aR9ic3p-CH^Eop)iJ5Ox+;YqGVJ>&I7C$s4ro`SIlP1!^RvdnxNKWr2;0h-de!wdcEDQfEu*#3E9;SA8`4GB z$_zXLZ#oR0>PyQA2*29wQengU?Ur0ExA{8g@#5pLP12{>7(X&?jCrPwk^bM@1D_Yz z+#}8z>&2E?kG@>$Rr}|Gldp7Hw)w(0tbUBT;<4@#g=4EJS7Vn$SF8Lo<&1la$2P&| z2y%ArW|t9`^cjrnH0mbi?MYFU`N{}y;2WLLe>h!5*T9!A22Xjw@@?>R485Vr#*^4h zkASCxoRtW!vcOeQ0P^G`Je9PViL0kt z^0d6>o59s|a3%c;t~Qyt%I>wt3a$jco(&%M7I$-xsZY2&Ios$ou-hwb{C73*sMQsY zb%Cd%d&Z5`^zq+yjrf4`SuT2tFsxSaqQlmKY1H_rAw{s*`}U1?su<|bEuaZRM*cI+!GOC54gcckyf-4nmSO!+&86;S((r8GF?@^jece5+0D6}RqO|c4lRHDNvx0N zhgUSqSw?Y=(OHarp;XQfSldTAOR5Mj+gZ4QfZa3#6lK$>qwYoyjZ4OxnE}cCJp*o)m>UIV3%0kq=>z*n2s??=Mgen< z3G+{YxkzBfrV||9F+*%TVh^fEUJFu2bfEi>VOL-rj*iql*UrDjl3VYReDL0zk6p#A zKf%ghM1GM=eQ1=qeQs>0Vyh~^ewy~6ClWAqMeA9Ej_${ozPfOlenSsz_ zdSvY<$^I1&B>CI!wrnU(6IJv{m*Bah#M|I43BgmEhVh)gp%I*YzGl=a5>$x-mK%as?N#d>kdI#;6DhzSqE>Faf!?$ zc$rH2@nv(M<1awZ*Fx9VK;Ktmd$@}8g(OqI7TR^@u8Vnon7NCHt%|uE!#=w6$OAhs zrY(bc&dpH1n}mmQj$$zWqQ-peP|(=(cPlNN;j zhK^uuik&Xb1}FV@>yn=n__4Q>?-T8vTO|X$X}oD>9w6i#U2pE z*6<9t5_xMJA4!|Qx6FZ+ulY8RZ`($!G{*E@(gZK@<-U_x4tPh9CbpPi@(thk7T?5R z?XA}e4@@Pkb-nT>^DX>d`W2gt**|?9V$5RK`Kz=6%_W)No~Y2i*e;%9OocKoJ|{a9 ziZ!Nm9kF@(1qI3%8t(%4nSpNh&o40lJE5JnZP+(SyNc%y=rRO;W}Cmal>~L~jE-@d zuWhE@)trPr%6ID{_qGR9liOSMu9D!z-Wh&2AD~w!U4`vrx)p!`r1yV@Yt5d_P#C z)jw1sFs%d5_4I#<&<(c2THy(#|D3u1Ei%JLa`wo&%PsaqH(_^=LW7QcC40D^x);?e z-^a)gW3aagkKKZOaC^fok)nbzYg+Z4B?`Lp>y;6=TSd2%Cv?BPA)hnbjKD{nX9|7h z>uP@wXJPm73}5J)s~Ial;`~eI(UGlkbveHsZORMu{id68lcgN;o1yhT$2Hj36!J=_ z(dFB>k$Gp_rfr+`Abe18-px40etJb3bC9|OJL_*sUeLc7sQ{kV#xF)%^(RUUX#Iup z7x=4Kn}Vkr?8`z=LbKiAO4d!A;4E(4Oayn*zRdrt^u0Z0xxW;7tmdOwWyd_#^?T^> zC#iP@+lGle{L^hN=E*u=+)W$%1TQIaF3Wm*cx_8CwPS|(vvd#d znBkv)q2JS3U(&{Udg+;|{<6l#k{5nE-M^_ZFCwtE($;3qIJ)^Qa?J-Y0X@Hs94Ha=^yM>mn~SULO&^s*mQY=uh+!k+;n`$u`IH zCEyYLp$7$2{|uJ@66W>7Lv%qdKKyMz^lFSEtN`S0=T8baR^j$jz+fn>>Dz zy^M#}wuG4LyTSJ^=CA;nuN7KrgKva?jGZwL9snDWtxj^nFczO z{cjpDh2Wn@CM)DX#s=T*ZhX09cjJzdnzZEhZua5b&}NM*sXYjM^IUp+i22T9+?Bea$|EB(%6$w0B^l^M^y3iT-AJ&^% z#5dw2gZ*cc=I>#>9|X6xbCCIt#Wq&67s;Cc3;UMnQr`$v_^;FBbg3K_-N-(?3H@~M za@~L97WM-6PF^tm)IYvI4KLmOIe5YX5c2 z8aMiu0sW-0ml|iJt?`dP&mS6(te}r%KT7I?{(a{m({7;ep7B@ti`h#f@G99Ck|XBnWOO6J<%b+9iW|OC!FsW`gmsi=l!9Xny+Ur?M|4?zL&(=ac_B?zC_isQ)su0^-x{AtkH~e1Yi@|JtoDo&_+}UU6yEO_=qW}Nk zJ2o?Q(0xz%+>gg4`^48OQ%mlWeND~{S99*}Jr=91VP3_z$T^3+n)bwAU65tb?tIOX zQ*Tw)w~S>AzKy}Zs@2k#@O$B*bv(=YpSfRBMq_>S?zg~soOVT?aq{mJ_`&}ozJ=~3 zU1ak6sORR{No$4Yg(xF3f|QXyt+KlBCCUm8ZZq4;42)#%9|R6Ln+&>6e_CWEa~`u) zG!0y*9Z&M-e;>W*K!JI#?7q;l7oKQ?9{9X2g?>a{VJ>>l>n71(;(6UUz%kf)o!B}O z{n8w{K=k=3oUQfax#L(T>@|-)55GfK6M0wGUI@Aoo?DCE-QjnMekOm2#Gbl41zwB1 zTAe~YXtDM*7;%zwXD501)i8v-tJ9-SHkzbdGJcw z@RDBRDr>KIY3=d$4Dl5cy0hjfD{x5Kp|2Kup8gY@H|tq+!sqbK?Z>xcE`R7hl|T3R7?VC?do;gOexUl_O{l;86Su$iKT`e6cN=~$ zc9&}OY%jK#>V|s0xANV<_cp#8OIC2^bGNOJcQuj{{PtU3G#TCojIiiiw1aGubE$V#HWmeRKHYgV>+<>aI@C7*>jH zRl|4H3x75^hf$oR9RKL#XW-%onTcIEgSKm-QRJ;3wPH&Vf4gkXDe;Hw%w;c%=6mPJ zyo#UqN3QJ7d_5~%&AiH*ch}uDtyFU@=Ipc5KTdTmpq*Kod!?KYjjFqF+Vny-UCJpf zr`Zer*W!a_0Cz1mAK7c2zR~-9#rUaCWDcp{nO}(Pcm9f&Iw{X{ZJPKT41AWI$Tu^9 zUHU14HVwui;}pNcU9QY<=&7}NewQ&({E3QPYHGfg89qF9ZC>^nO)Yi6Ik7G@4L`T3 z#mFoZv+&bQ_C(8|$q;+If$vH|U9Bm@{l`bKrv(1QGf+2u6;TeE$2lYWk?fgnU~`bIfHLweiCWJ#4U4CkDlVxn@l~?c|)u(p|`*1T>W#@HP6*K-w~a;+NAG3 z-{*Mx7-f#R|CxDgrjEXziq2@DJG#*!)6gZ;(btFIudDlVpeLBh#t3UK_Q4I|E$N(p zV%tA*Cih76>}Se(7Cs{Tu<%#mopRsf_z`#wvU&k{5&kOtZTxeVY?Ob$<}co6?JfCd zD1RAi0~v+m(NWDtZ01fkYY;iQGd2#L`SVRJAL;Bz$lDv&TlZ>y%owFj(c`k z#wYpZtSJNi`v`g4ZU6l-$g5HGg`?OiJ|_Jbd!OWq?ywB`%)EC4@1We9%XPK?BlLrh z8E;da+MF`nCG(q=^Od+y};vC}mvMa&+72tn1^Dv9~s6dCm+|T@& zGKAo(Zp#-xgib2{_=h=TyBoe2?~k8&7ovr`5SyU2lih`=aA?TB{~$Wv zXmm!c-ns)(jb0>tV=_3Xg|^J~4}Fv%%f!nQPtWg@C+evqdWsiYztsI4b!GimLWgqq zq&5RSfX%&{cBBpG4n!X1c2dr8?m$3;@pm9ts|kMogP+T#nm8XkU-0h6wl`7JR;Jf2 zh%Dqx5!tnJ2y1*cegU)DpCx@2XRl?kkG#_dMZA@*q!_woe3Co}tFo zT2jZ1g42zw3myz!t&+Ux5k}yD6J9s-G3&T{jmS?Y!2Qs_w0jKn0Bvo_r@?>XKI74VROW2RIKW)c=CoDVL(tVudxbqs;nM0Ysv%r6S-26FnEb}XK=*(yDT%MlRDLS-D`WMsY zs*+0iW{oD5@Lk6D4EDnQGK!@?X9QZ&(PWIj=N$b?K8f;JH}oeyU&YGfFGQ!v*Hp9= z8?FcW_m(BreSlh*!nU6g;DvpW8f1D||Bk#SH08*23b{x4>nn!__7(g`?jZg25Vq0* z?jSu+y62yC2PrmF>x#b%S)8PP^=wwX3;O9p|h;j`d2C9}C0nO5wt zD<9agCLOqAdRlmQMze|M3J1^Z*9PSV!Zmy3-;L|J1K@hNJ)Tm=bK;%>jj(Q*&%f)A zk`DcvItaYl5FLbh80d~t^x%p5YCr#6`yT!Wl#{jC!#zLF^q4c%8PwhIHX|@iD7h5{|Ea)PjySgUjT;g&o<1b->~yv9S!@aCs?&fM>t+(NQ z=7qI+`@qS<`c-+$!HJi3EwaLLaN;c?o&j*|gP*L*GMcOKfAtn|=QsJ5pA{4ojBTgx z`_yf(e=Khywwle<_mJOC{Q}Mv1+O)X_gQe6NjsB)P0r!ujJ#kd{Qz$m9OWFTTGY(BC5F`!V{Ieq=5S=w~neR?%nh^QtmKQ{nN` zpeuaBil(Z{}=>}0cY5)d=BpuLnG5+Xbn-0ltOZvcpQByYP&NH5{W!MCnICK+m2!UZ z1v%TEiXL<>d}O57d>tkncX~_^t13)uF{5tdw{=m1iT;z-GsWU>c14Z zf;{oPc9S=YwIg-+i2clN(`iHY(3zx9F;v%Yv5l0foN#d-?WyeW3clSVbD9f~mks(% zEA;p?w6x~C@LSz*I`fGg*%&K+w7UNS;JKJNjg0q1?_+*na%Faw>Y3s7>?;>xXOXj@ zZsIiT#)t5oAm;^oUh`V$VIg*x(p0ry*5#x$+7f*)E2BAz9x47UPs{qHUIFu}WM1_# z;q~A`_)e9c8=l2)bjY8T@ezLIXMOEueHGWQ%qtas#rzdBZ>7weCree{WVV;rue}Y> zQiwkG0;Bj1x0t*|;2mwk`&=BnlklnBjQvyKJDaf;=VUYs?7Sm6v$;|Eqz#EVQOj?Y z>S?b|Rqa(;Uf2bXh(Lo97v&f+HDpfb0lOLvU*RLLx7n~8z@9IB1-J}g&llK7z>jD% zJV4vB7Nl)~!5N2xKZzkCFo`}Rc_L3ZX;MaDFW1x9mkh?68(6|vg?45BIj1)1vuDyw z{O>cHO&Y+?b&&k!uAFesFe_g{BO3SBYO!gHjOAi(Zl#?X^mvEXBk1$%L$S*L;O>I( zH96aH)=f3BGcJTim#{YW>XQNrJN^xmix+zdLj#iCRlA+_boF_`(Jebr~5tb9$}qH zimyiLF17kz@a`Jvq-6&#B5f&YyW`R>B<)VpItnbCV#8@x`I=j6eZ!Ftn;JXz&)g2P5SQMbn!JI zw#uK?>I%|@|CYO*c8tKClrJZ}V6@fm?je0JAEXZV)H6MKR-P*^Zy$LVkXMA9Vjv61 zdJf@B63n&XPwQ)^o3h1}Rd(K1yMGzmJLJheP>^HyZ}ipMNnUlfl|C*m{Z-Pls8jB; z$~v?^ow3Lr#cfguTZuW7dVXCsM9}^YS`BI)E*g{2Tpb zQdeYt*%ux(^}dY2&6Jh3YNpS0`5qu$WHou#?eC9Ss}|1`pXd@A$W?S45 zx7)moHeaGY=RRvUZK3yk>ja-;^uZmnMi`s;9w~e@oqMlNnmpfpESB^4-1)qKHIaC( zaxQ60&-~wuOEaAQ|Iu?5wg0o9s~rB&lJjFTCEk{f%{vL(cQQ8c6l~$C=#s?T(p~rr zTK<9(OY0Njl*oM!=G?z*SXo5;bne4P=ipq;FE-oWdk@H{|Arp&9c)us*w}V1xX<*_ zsdb&`pOb0df2d8#Fnx2nkzsEduY67HwPU!yBljPg;K`X<=H4LtVw21A3vDv*Ka5^! z9M4+wTWq@_*DVn_PY}5~==yR*_J!T}(f2@K?57vXSzymIW&Q%KV}=LcTJhnT^(W1L z5E*)ImWozra>iJ}*e?eMmw}5*!O10@1I*+cz>2A~`+ASRVuUApaBdm#0s8ze-ruQK zdtLYyvZg%A*v%Ms#Yh$XVzRPgCNKtb-N1*wi(V*e?GfPm%(w@iYaQG-Cg6T&AMSIw zbK>CrEpXoE$~ob_NrsK@wv;Rr-&+#!&3$mtfNL*! zcX0g}V?sW+a6PHKKd!sLHFDBt#`SM)T<`3U>$$JnxJC~6G+fWLaebEH`cw4OP_xVL z+(EkpT#trcg3$HTHl05SogdmS;~UI*#O3Nbv;coJe9M{!p!F;p z_f098Chpgv4=;n(x4TB;e>TRX^)_&yg&p-UHkvjY_m=#k3g?l$lGn7(Jk z4ARWIK5}OyFK{+v4dQ!du_q{jW;I=e5l3YIo0o?$1|It$qvXTT9B& z9klvwC1v=zh);SUx{|7Ipk94RK{7T$e9Q~nmqrvZ1?hw`ja}|1Z zAv#hG`0wEC`G>%puhin6oV9r&SH?=eQWuJUf}8n}=i=kB3tom!7ogvlYH8th*fhj1 z&Y{CH+LF06I1>^+U!>!|q2;a=y~crC&adR$wfmbMznuMU!&hZFew{(iY(I@}Cfaqc zgAT;T)X)B8-`lyy&igd`SSEfJ7Xq*7xWw-;txKXee8Vt zO|*T-w&`6x;|=bvy4efT*b~y(8-}n)3}t^D#vM4_#8)nR!ZhM58n=4ZBZ=P$zD(x*h*I6UA8{|wY3T0PaW<2yTlXU>Z2#Kn*n=c4pqDc` z=YGT#DTnRMaC~it-v2oB)tOaa6{*%eKVQMeI*Z5(6d+5eg5t4PFy99D6YL#6IX{28 zjdOc+iy(VeFyA|adjuPU`PQ10wI*v1f9jvd?o=MBXK2h%AB z{pS}DKk|p8&G?Zwh;J|Ln{utn@6tZ~sNgr0JKW8JF}p9|6XyBo_*-@qlEUv@RLbQLspCA4(~G&UP~gS%eiD;7a}rVZc0|MP!{ z^FIK`li1rtKeps?&4*6;l&l5w8F(d@h48gSe}p$tSA0GPi%BTFCtqn{g}yKHp7@t; z&+eG!Mvt%M94dkiaXM`m5eGFb$us>1VsVxd2UYAlX@+O|Y@X9{@+OxO2Xz~~gjfL6 zr;&C8@j7#f*O@=_lEw9;H-W$9pC~ha+s(9>Z75$Zu{&iP*+aNHNIX=r2?(uZk5Im+ zhC#2QpC2#ZSx4P`?6C%CC!^50V;|obF2we7;YSZ(r^yP`lV2({ia!B&o?i<7&59`} z@nXgI3!6seg_IGW-XQavd(VY0ici~NC*QnVogGNa>X4)~d zWABT96546hx}5llV*d|3x9ZIW!=5`3c=pu;dl$WbfO`Tvo_X)lH=AE+KQQUa*A9dq z-G>;d`zLAXJI=cDu{WP3{n>r5960Nn?;RLI+=4~afApLm9Jp%LD+k(Fy?-DyLE`rg z-4Wwnz`k?wH{w25k(>d-OM|+M%?RvMtUG)W+YaVWlQd+fE?JL%mGlYa{_vzS|Gud+ z{m*o#2Z(>LV(V{P_}5&9W%{8{mPw&0fe5Ij3Leh(Z^2FEqX5A%>K1gE3e zD@`0Xj9y>uZH;5OcL?rDcfresO8Ng!;@(;h&KmeE^c@?oqKPy<82$y% zwa|DIG+qIXw?X4Y+)Z(CFaC6bM~SB}5KW)|{{T(PS>~xYFaJM@^D^_~HYDtN(M*ZU zj?A={eN+*T$D$8(I`&^@V* zU+Vv2oSqe$931tql@PRX1~*?i)%Gq80hl95*;j6XZKEw5(61Rcz1SP;j~)tVjwSw zOd#h$t;A=4;WE#Q5(i!4&(uEbG4D0kiwzCg@jm>UYp5f*hz(aS8R!Drv(@BXu7bt$ zDF5;UDRYaH^FH4*Sxpa3RnyJ8K!284FFC}*$PT-ZfzG6VH}zgVCxy7}p2czww&Q^z zb7#9pe||6ZdZ>6hmb(Mpla)_&&gk#qm#*w)2Y!)*#8#a));nj% zoYN*tdujBw@d2LRL%ZDboc?4&+vS`+$@r{z8rgx5iMjK2#`&&^`~LR~9A6sI zS6jd4dsB6N|A6}a8wWg@M-E6@b=h~{EV%xA2ZAsD`ann4M`oJ3ZjZxRBDQ)%VqTXN&WDb za-NY9s2C*wRWo0F(faEA;nxx~OK{0v|NX)HJa3RT|77<$X^{M*1N3?8cDql`UtL47 z)v0`Z2DL!VQ=`8sI%7F=awc&q`L5VOtR_G#B_pS98u z*ZwU|`@|}*easW7HP2u&0<(#6vD9VSqzCL%@?QTPdV?)?w4z!ppMYWf@`7Ynx1dlx6dyjKEur zvCWmy+yp;W)QOK}%h*l)JGK~!!?gGG1eZtzX!`{=C1l{*#wlD&VziiQn@R?TDY7J)@#hJPmF75 zS^KdZJ}V3{D*VbTxNRm#^2RFaH`goVY!#WdY~qi$`B8d&jstt$C2M z^i<$=`s%?C{qZlU%&B~wGvy7l`{D&-4}aXy>LU25?5~IaY@V*aCHu`sBdxgck*X8p zunYcWY!U;rILqBU1s_ptm>V+-lY9#M?%U&%x>DeWyt^lGlyzMT&cVTc1HUu5{~<9l z#fIqMUE*9vs;oHIGEbsMC+0oY6U&kF1?lIhaY<$$GB-CS%lzh;{Z#W_f@{zd#dfn4 z9Q=*33!K}jqj8U>247GG%rf`ipgm*`for-(8e`w6c$T~+FxZ5xOIq@-1g?6OgIKeN!@p;-`##{?NiEEc>1k0yaC}Zja_Ped(*Nf1V!jIu5 z=CJmCt8O)QtLLS1|A;hKnb}^wNjrV-q^NdD%yjE{E&i6Wex*(jd#~iZp;_zr1Lz^K zzus|hb|T~ON`E#D?hje_CtFRL$_N-kW=7)a0UVj@<*&>|HFxwmW7`;#KKZQ?i@~B} z3$8zh1mAryn=n%Dj*)7$%&YmlRr!ViYxQpBn`^_o6__uy=~!^NjXsS`lWzArYstZ{ z;><<*+(uu%ci+XI~^sUq~w&&ydY>$zI_+|XSXXXs)_U%gfHZQ82s z_nP~DRip`8YXVoYwjDYMLTB9=2dPwZ+tk-wxW4kM>Gi!^{6=WQ2NE@>|3~0~jGRvU% zek^nazsIM`+@mAlX(1ozW@F(W@7Y;n^DjmF!h3*gqXC?S_Ze%Pyy)Moyc2i=@5VD} zn)6qaw?XfS`L1S-Nd29X_b;>yooavi#!i8~`XZ~I!$WoeyYqXXy^1lKylL}rYpokYt{#*h zyx^3v_QU;#yKkQ^qaA7A;p@-RZ!gaTHWNPyWj4f@vG@DLc3R@f#N#xv+_xwveH&lp z9dXHOvGG;)lE>~Zzd%I~yv3dZ+=555oDI9sT0M0I_Qd&>a>A?chu&mOC-Ukg)QiWh z$(t#s!{>#LzYLuj#QzdL<-mI<^q9y`W&djfu6Uj$@R;%w>(;8W0*&+Gp>c8D ziH)&ooVLG!wcFJ2CE>H1#J@DbeX|X_>|I6By^O(uJrTx@ zz?e9Wt-rDCR`Ie#J>zwBgOPaR8Ly`cm($X z-%Y@qShhp<2i9Ud{MXXItUuWc2E*-hHvSXWWZ3|)XGmKGp6PA)Ly6wlwx-~Q{HVu_ zw_W|ouI=8q^}2?67d(CEO-sj>_rX0$+N(Ubev&%1h`p+IoSH8FktUB2T?%@Zc@Z8f zx>SJ;gUOo*;ogtp${X6m5 zSh@hbek~5pFW5Q=aIUrCtheFx+HgK(!)eyN!Ls(MGk$eGx=xVxCYvXE@mFg^;n?$ES^#v`2pkq zG(NL>sKw_OQZ{i+|0r$c{*!1cWf0ox)sY-}{|t0=@Lh}E<8>Sd|3dF39l@t9Ix6o+ zN1uzMqi))fxLX6!QAqY9_*JX$JNB;{zGbcb=3Q&Q8BFJMbpLpGyuxePcZ4>6L7%2A z0=g1eO88Rz9#u_QDfd%>?Hr4ritb)d|DvZ$-j5{@eM4lax+&}zwyu1E_UXE^fsSe3 zy_Wj2&ZsZCvb39M)B6r%O-vJd9Y{z18gYAjb>xRH^XC#DRP21;V12#EyF(s-I`Lc# z{IbTdRcv(lb37gf+N*04_G*FotAdM!Jv}(UKK-wZMb=LUJc9ER_Q6~1c@>zpzj@;N z5k4>Y7Jeu+Yx3sLK&utN=iGHS@eR*#Xv4yJA1|0;;=Av;-~Qt0y3mKD3!I{ho<|*p zP402-<;nOe7~eOK4=fXj>~sEN0~1Huvcf7)MCNCLJwJ&!>BplP zzdJQ<|HtNN?*FU-bN`q5v-i~>*kco3;@J7Tackf|$rpbJM?R>K`J!#H33b!XK(as! zZAzmt^;H-d+?X`Jz}9-5>bO*PATlAKwtrR^ukbLd)NSYI{PJV)c* zwI|q^Wi5GVU-0U{e$xOrt*5W}HWPJ$tAKr=Gg5=|t`PFP;Ca#I@I}t1ss-zspg3SZ$*IOD;m zr~)7M@ML1lasT84?y@W=Ze}%iiuX zAJHkcZ;7+83V&3mTzMvU-RtV6y?n;YEyc0aDfuq$zc)0u1o20GkWaGa4*zBHLoK}T zdE;7qwC820%9Kpcw9D&mo%Udo@_nB>p1V})Ld6{cY5R7S)GRj3pgw~8+A7?@9c)Dz zd4E&`ckSJ3hzZ}kk*f0Dy6S1t_gdv%*b0rW(ndCi@g4d-^+V}p(`$!=XU4gkF@*H8 z>8uI!j!7-_D01OMzTs7K%6adZ3;6Jr2y^$OGOc#^v?@(Ie*wQYP_B$IRsnm*5bpXb z7wO$ zJd?DQL#;A9@GoD0e>wWYR4?@x;7d;bQ}5^Z9r%*(CBDS0;c7}%*(Hm`=d6lYrStI_ zs?zXZ0=C(}{sv>MD)TH(yRfO{Rep!i#c=VnY=eWDxEn_Z(gq4&r~z6 zhluOCnLfAD?imZ}rlr)@O%pw5vr1Vg`>^;Z%iYjf)OnW}D7WG>SFI&ATk{L7_)G=q z;|$Wi|6!HT2XH1Y>@e$&XqI=w6a$mo36}FP=Pax^MNJV{6Vr$1&Q7!$LF-5#hEe%B$Oy35ESGZF$(749gWyF8HnR1`I+^VyObM+X{ zgWVOLN}P*5z$h`Xg+CbRj?&25;%8`I+Lm@J>@h5j8^hZP?HaTj zlDj0p`HcbEj*mwkshSBLlykp__c(KQF7y!7Q^9vO={e9zF86>&aYmhojy0O|^%Qwu zI&m;9ACOYwCkf9w%K86kE{PwV#62D2&9QFfJw5V1stfom)icu~(51XL@xsg11fis`Gsbz zPTALJFQyF(zft(jmW5|Dw|t>+Rm;l4l`Z>d|2fLMMwwm2foZ2b$$y1*)@mcdv(JCj zd>%r3?5DnAgS0o2_L>TtKD9l=X%8B+ViSxC-2Q9c3k9yY|MP`J+*I&RAK6E*q~BG* zDEN`FuLU>H(Wk(A4S2bTv8)6)Zt~AE^V7oa^S{|rOa999SGH6!cJXJ+KbyE$e2YJU z$dn=OXma;64Owi;?Z9-@jL*=Qj$Sn7Ur8?_UR3dX-ZxPralUf{9^}L;=qJ1oIgR&( z6!1KW=POvxo`>p7JbKtti*)2+VmIYhk69DtK6TagYX4T^xLkm*g+o6d*!0tJICaXc zq#tB{K4P3zdQNyfZ_BB;X3Uyucyb-M?Xh7~z~%jQ7oMVh*)}yV~{2cZm4+ONoD<1`Z_-lF*n!4m7}FDYCraQ2cCl zXvitM5Z<59zMFPleM{)z{m<_kH)c%;{vb30O@?2FuPj<{dt?=O-b|b2v>|&&h`V@_ zKfYMvV`I{hU=)&ew)@&O0`9r#x>XR*{rf z$U{=ri*Ne|ZA9mT+VIZhDknSbn(odWy26V~Pl?0@0=L6to5Hs$JCi>$C=L}QEO zFFw!LQg~it%WcQXcFtGHo#cJ({L>m+3P(I-=9dw-r3{%s2iNc6Q~Dt??Ypdl#!NNs zHs)Q2#*iQ3qYt)>V4nBr$$?Up9PUQu`;hX|cMb3OmHVeweJgFt;aH{5Zg-!q`|d1M zT_RID?e2N#mWZ^ws_?-UvrSiWSc5J;plsqC=!asPRx)mBXTbb}u}ux+%XrI~OaJkz zX=hCzv!)Ju^Rh0!$-4M~J!jSRe=fO`wf;@kg_m_9bo4BAbbEb%q`LmQC1K&0w6h0X z^X`Mr;`@KICMfUp;+{v|h*`}G?Df^dI~e`wI3lMXBF6s>_L>wsGdV&jw_Akpazjj3Ni@LH2kT zJ6#%Q?ML7VN8t$vS!W;Fd_?vf*EZ`8X%F)#bF-Uw5j_e%-qX(xw6l&1z{fe@<2f52 ztvt*Ad=B_1fX@p)o&z5aPm?xu=KAO0*LuemaUe`sc(*CA{Eb*U{rcZ*t!?31Rm5U! zNB_kmCXk`wbrEihP7Q$nMXOt2ftb?(ieO20;XMs&*2iYHq ziC%dQX&t~Ud(^pJd58-4xG9J9ncv&NjhUC!=qMuxfxS?We)svD54u1Ds& z4qYeJl(`x@t#=9qnG>n^&?{>7yw|NX;h7C@#OLP(?k9i!4l7N{za!7DIc1E%?|GKB z<+9U6*Z3`I1;kY<$jv)X&K>8yY?ZG`^3J)LJh9sZw_$^_-_Mrxs`%WBESL+tqkuaP z_(wC2G01|a`;YT(rNLyuQHio3KCuJIf_GJXMp=-3rJWe*gUW*Uk6ANx;iAZAmIZ0= zHQF0g7Nosl3-6%6&nOEH&|W`T@FeXGBnvkD;xovCv^iwq?UA$Xu@58*y2(phSVvs- zN0I%8^vZ&>k!wFq7OYKDQ@#ofCCY+r$b#5VIICiPA`ebXAJxqJ?sujm&kfPjnjZzP z)vT#S=pXgkpG&;;|6WqfJ7Q~yZ&a`C;`zHJi?E&65X(-+Jcw*4^-S3iKH331 zuLIWyHjF>ucdMC(YzRyOJeX{lrpp-RT@tEYWW$-~_m>UJkqxKD$%Yet zWzmz!h6@s8!+FFLHDyC$Zr5S^Hs1#e{@lQ(fUS=AVpZc4FR;nGU){i_=yw5fUo~~a zULG1P@Tm6COpy(x)RU!oy6t@D=#ctMF<^RuuWMwZ5Zd9kIw{A93VG8A}J__#xwZ zgE0*6l^1XBCoh(p^5UOLxyx_ zylMx}c6m8hyOL*-8Ry++)f1UVEJXP*smRA3r%qu39B!h4uRUX**7wR%QcXp(wKY@pF} z6+K2gqa)Dy$D`Ftq02olHDOO&lcS~-VNcA!o)~&P#lMu;i-&fl`eSodRN}e$=bz{A zX>|Es;@zZTYh1yZgd07;ujiWY2@l;2t#Aito$l(|y+QNs+2-=iBaT}w_Ubmu)Ve&+ z_bao_Dsv-cn(Q)s`}&pHW0kp9-o;7V-L$(~>XCkh#E6@V{Bpk9FSe4S7mF-o+1oPF zcX}pyULrO}S`)I?D&|D&ZjxR+Kl23J+cUhgHHht|0GX?w?G4+_GrWWN1lt>Zh4i9c z`$I!wgmVMf&Q%&cBX#sAOP&aq;fEi?LwnFvmM1oA;X=jo@zr^9yaRA;!U4*XVFL z=QCpW?%}NKi8Hx3uztD!t=@Boyg*JIJZ-?U9os#Zd3>fX9Wk>dX22n0zM3}q`No>3 z&34CxUGTrrHN1Ce-2?ueaqa1G?bXqq#CNXyB=!@t{w>BD1&+mD=4YG%Xw-vFSk2h$ zkarxLnTIkm)>>$_TKCQnc%1QUK(GE2Yu!s)p2%=H*!UW~C8i8_fi>3LYNOZ2cZ@h) zM>0IT$2zI&rOD*Wer^n=~`1+gFQog_BO+^{}TRYvD)e&HuooYQPaPLw-50)GY{Gp>iw9&tUUe8E_= z_h#k4neU;b4a255oX-f_llOkBLYea$BSmh@<_w+rWK3&y;K8 z|5(ch=ku;e&X|Yuet}`wAtk+4?3C0a_82iM#&r5!x!f5S+Y#q;#G6~8d!`oySAoVm zm)tV|velY*#_fy~n%iH19dJdyXWAt4DL=9J zPxm}8brw*znz99y6+3(yWt{c~YDZ+y=b+u!@xS`;4@v&_xsxgS-AkBWrm$Y@ro>y}pK;ZbmhGP?Wxo1A{et`qOrRi$JCvBtRXzmDfeH6LqAhw?O z&EI|S$a&}{6PC=tF5cfDV||hK1Qvng1K>%VpCaVX#o$Bwm9i(~ABb(LBVXqE8S=-; z9DhhLT%6P0}-h)g4KgouOwR2BPG z4LXp>cPD#)p>y6NeKu~x*9kZpisNOyQGvTS3z~O!|Fo}?R(?)jnw&S5pJhEO)|$rW zE2Qn7V5P~pej(43oH}X1Iy$hGH1Q=F*PAZ3&a}X0(oOr~U6!mk?@X(n$ci(`GiAj_ zm+vdQchK~0B2CWr2aEqW4IJGQS^ndE-4$*?&W}ImuZ=9oSZd|4 z-Z_6CH`c*7ac$YdT=ZMso0e^+9Y;U#;7=y!^-fHI;O(Q#I0W;S+jvJG`pW?2B({xr zp;fkqGJm9u_{V)CV8yqIy)S-&n~AfIU5xaX@TZ49@FCq|`in@~O$lY5A;x8VnfRC$ z2IWKe3>08{2tLvm_oBwI$2ne^aW6K0PjsGv<6aEb#+KCnZEW}l+E|wW$K(G%8(MOI zIO-GHi1&At`yb*5cvhLjO;CXj<}p5w&A;)!jHRylI5vI$x{^K?dPwvu75^9U3lhKO zrgwyY^Gj~T2_ znVU>>=uhkS+`psWSK|5|2zOsD9~gJDJ_UELL+fU2Ct#L4PNMhz_CJZUq@NfQNMe-< z&%%Guj4Rd~hm3P+;a~Vftd=-r=!Vfu;+Pe2?s*?W!dyl1s)9|9@CJ_xLEQJO4k=49H9_ z5J*V4ngnXe1wje|snAS_mvHe0QLMBDq}}EMD%Do-mV|6;U<4ba+D+_+i(NB=QCmRG z+Ag;C?j*UX&fJm2#@=X}rid~fG_ zD(S;a#%J~8MxfzwGa!80WK$vs)?n~6o|_RN9_Q3<_GQH!3eOtaRk`F}l?%RPRr%D6 z$dKnM;r*4sa{96dc|-Yn1MK(D&<{@nIbVM^BeEa38O*(Y%l9EzvcCv_)qT&3lQDt~ zn~lKg{zl{nE+g=L_7vqv-vyi$#wV5WQOqkiB1@zN(_UwhUi zJ{ik1gqzMqoBPA$oO2QX_CERIuP}bGk+wKT`Kh*uH@DhSe@yNRiEXc?M|;B8 z*9*Rz9DGyk_kYcM+OvD&mm>VYOT2dwYf;}$-25xx*z-P^t$!`voB@c%`)57y?um!a zWrVu+{MWSB+h?@6P-@+5-pWlgi}m&0^Zs zIpPPSW2Cl~M?(1D7cE1+vieibe`kD3`2E?v$p|hqj8kXbRdwWc+^)M>OXyn>`sHHU zGWlM_IuS2B%D#9PeM_f)$o-Q#@hdLW2 z**-r8whx;z^!(1E&w;DCZ)5Er7H=*5?A@)a(CbCh9{O?FyIcQ1+%LLSxk}LK_Wcic zxrWe@+mIEHE-`9#kBr`_iMh-5{5qY7@I}V_26_LB_9rfSUn;!lbMH$f_r&GyOMU1I z^oRQxKhvMR(f*uf5A3Z!uD@n}{`;{*-RCcJw=TZ)-tK$7=?mch)n4#_I*R|^{|o-y z>(_nqGWc^&x;OmOz5xE@()!H!FNxy+^#29_FL1Z;->!6#t2r!T+z~PyM6o+511NXY!?7 zZar6jf&ToUm;M}FYxAdW?B%_!=X?H&^{o6$YaaL3uYP=g^e$&_^e*Q$bw%tyrO3N+ z)bTtPSKr&(xa|w{rLdR2JQwZDTbJp}5&T}g?HzIKv0vog>`_r1kQx5Rd$YeE#qH;p z!R@c&oB#Fjq3-Zy_zt;{dK=pa_PGD;x;++sCwAT55XJxd!r!gj;x2r$ZuFi$=skVW zdoJZ5)EH0BAh$U8=tz%zKh-yN2)fVYMMmJEBxCzo*01D)0@mo1caf_GqZc3-pi9h+ z<9jHdw!=o?qwa>e=#x65`wlVA=!})`M!ZuPdy>pw8qH+6tJ-oNf^=sABjmKUHK?c^?0*)dCiQ&H$UCI3RF z+h{J!HwG;v&(kNI38-)Efw;~Mf6qJuEKM#?_=RLc9ZQO@jJ@ypUH3@aYkc{gN@ zEqZYD%y^~xL?31V`}u#-ovLfIhE>P78@0m_fX$oo7<;Wy96C`lfgR>o;?v zc1yt{7Vmas(fzVYWx8Zvfoog&_<=!LdNe{6$Iw_YuB{pZQ>J?4%%qyK{49ZC~ zZeREl<_CRg^%Fy^GrNAnNE*AcY(=>@!N15%e{U>n$Ad0Dn!B`DU*%izeej#rRy~({ z!pax%Ue2HF-zKz`p2<99lB1ck%h{RO*Lf%5hrX^je4VVDxjGw=mE{Y>)6bh(8)fv* z3p|TvGxygWGZTW@tR0O*IiDu#jLV!lc_;B2KF)~dPBH@bo?1AS`;n~s#1FGJvROBE zz^IY^fw3A#JNH_8f^9c(Y$jGe26ApMmww#fd%ci%EIjGkX6}2|n$~#daPD;BNOO4` zW#hrys>6G(^v~AuQTAo(WHUx_^mi|JOOK_$HPb!wva^i9n(0Y{{EsbK-vn%Iw<*E_ zzq+M&tl@lP=a-F1;uBh%RoEP+0!zGyvlBWu`%cbSU!XtyDCt_JzZ~gMr$rYTB@r^c$Z!$Av_vK8{1dI zHyG>Iu^C1n4jkOP@8=Ad@Xw9G|7zM_9UU+42xEH!?}2w~)|bfB%32Z5&bVzHPTp?S zWu9}W>!q%I#96@WjMkma7*{aHyQ!v^4{RP}#${342av4L>h!@(^IFFC^uZpuS4xGKbBRG%nEPIkKu$D9C zvEw$DG1gkiW_?^q9qT>l+MRl7d=pr^ny;G2JY2CEt;2m;M;S*87JNI0=U#Yrg6Ug{ zJ8GhZ&FQ{Np92C%BC9DuoyK zwk92zdl|Ef@S@|QFXCx>N{<$rM`0hIk+EXZ*$&t>IjdU!9%fhPCdm&_gW8H|7A?QCB!)PR__4t z8ignAm&WiZ?V(P8w1#fxgYKWEeM9>fd}OZpth44SxnI1j$DSg3E@40HZH{95bbxwK z0J{htvlN(1KW8nhfse$)Q*y={fyKzW$M8#9^Z3il&*M_l(;`{IMSZoQewKbt8?m?@ z2e(Dw2Cv~h`Rt(1i0ir9clhve#&AA$#`0{Anc(NVd<*qj#!_cawEkXlyGZvqH{XRE z>BfGJ4c#sKc^}KKqy4j!GgPIw+52VDbndnz=4WA(v3wtR*RX%)vVVHK@qUB-vvHg+ z&~l@({TMRNI@akj*6E9rjli4m@^OF3pM3LN_$hlmGMH7~ANfoBVJEWJxIgp!cAm2~ z%aN&GM85N|KDp;^dDgedqkufsMt{6*)q{u=h)=HqheG1=Z?SP(W8qS=Vivggz(us6 zb7GA-_8zP=xy~MJ%dh+E_L}Zb?ZG-{d7yCW#-I*phCS`}^pVE$k_E_} zHx03Tv65@%tSq@G*JuoK=G%L*){i^7Pc`Nkfqx#{--8lexRB}=CUZZsw_XUG)9 zZX;7n2BsrZs7?TSluV($ELNsSa3y?7rnv8hs7z606ko&IaAb;CZv3oFu{An2C56WJ zrQ{xwOtEDYdkHw~rH^Ig6e(fu${)S$P>{ZsKDuaq4((Tho5mvWRn9Lm{*oy)77hXebjmjvc`hU zuVQe8JMk{3w$Kl!o}0Z~bXxMLXP)YfbLwe-CYF%Cy<^dp>R98RWYzl;^#ag~WK}mY zZ=)FtXI=u--Ti+Um&D2TxERp;<#~k2y4=A_BnlUWAs`oMMq@HL_Yc5W*9J)lgk9au7cp0=Uy57n-inbHr z17Y;<-%;P8#dh=swISVT^eDT}e_>3VIaz|N(Q{6$wZgu&L-?VyrT@@a-j2NM%$uHz z-kNyMyniA7zAPuof0>8N%^fkT7xA2d-0sL^(`i?m+B^@Q$N@ zi}yNx*^G_K*%N;dEzgJc3z3{`i;dKa<6H0MMs@Nk@Qbat&@`4GMJLap9|WJJIJi4|^hNmS zA!~pIeZFJQThctyh46{BUtLWd=^D!{8_lJ=^pLr&^VhL+{w8gz&!xx@+_4+h9@UN> zb(Ayy)|y^t1V-|_3>jW$YL9~Z8e|pS?OSdpSh9T(nRzs24qRe>)12R;t4{Fw7BFJV zg0%bf7`XSKGjayRFIidozxr@l{68r=RQ`#*7sN5Ak`ZHJ3&sq}oN>lH`i(C=t$6%L&8Z>(5fI_>`TS2@pFXM31)7r1JEx$`OKf`Bq;1?eR5V{LOnI zcV^J|bHyRO6C0P|#Gz$f<6G(d^54$IZdcB}R}4P4r)RsCXCXIa|7V(INA0P5)!=tZ zC$^~1(4q^(85Um&|BNl$L|;~K_64NxdKljlYkgdbqmllw8y_t3EvGWzpZ$41J)3)~ zGSNB7ftAf1GZ*Nqm)^;a;<#}HI-&C$o>v zV5lwcW!l;g9FOER^y9_Y*cJ~mR_-JIx+gsK5k9=Zmu$CLbD28NQYT}OU8krQyaZ<* zb&P>g+%E&?n&=qp#=aO*onFTLQQ++AZ^OyDOj{37XJevW$8(vs$W5@JG9g;07dSOq z6VTq`czY~YUuG=t0*)`vhSPYNwr-_PhBu1yW$N5W9dZR(b6eC)9S0xI%3E{08lKqO zS`f~%3u^uYz2NM?*#vJ;oteGV(R>}i=Ox-+44-b)`1F_y3l4PdI}E+k=B_rrMgRXn zek;-aD4%8Hs~O~5sd&YjIA60{-?XXocO5o=?y;tnk4E?W*?i)^uaM4liKh}vTb#Y` z@;o))=yR$K|D?lDKlLA;`lq1NGDG#{O5k zvH#U>?EfC^mZ-m__8wBg9C>&5-rq9b%g(qFeoSr)Xo53e{$1E-wV&mdmQD317?WmM zww5@{)uqNl40a!F+LuHya4P! zv$q|EUa?JSf684Ctz@7P}f;^8&-b`#^e(EN|v$vZ5y7I>o>Ss}J2JMwXbKZ?c zAQqP0pBpTk6zg0<9ku86*uGGwO%MGmZNy*E$X*o1!@{3^>mvSrK8?Qx>-kT&=is=B z_ZC98OE`0_oD+(ZU5Ku8l=Bz-Z_csCm^cmUIXK1jz-cD-0?g1}#2U2%nv@W@I z?YiX`>-yo7?^Aaf`DVusrtaWq-GNTsZ(poi4&Qx?x{3I(#tx&}KJBJ-r-+uA;MBy~qd>kf75-g&WZE^)y+2X-@dh_UFE%g`&WzKwM1-g>caJh8aM zRHmWRZ6A9Tb+3xn&2;J(U920X?ndga#mRu5LtkFT zx5zBm*24D&>UW@vIkX&0H)DCO^_CE&yAc1i2378qiEh1&DKeL3$Ie3k$a&7@jegm& z;f;=e9$7Rnmhqu|`BC4AkJ=EkuEC}}a>aVqO|WdFZ$0Zb=+K=->t5C)yk76c%QpzE zmQnxn-pOWt?4_^qQD0fvH<9nB-~ajVuQ}O)_71V8u@`YR676 zT7`qRz}F>RN^KXicPwKMxtj8;p?w!T9lkpEKH4}A|2;uFA7KMYzmnJw+8)pgzwaNz z@8#=W%DOqpp6$H1m{^o`dFg`=WAitGuQAxn_bZH3A?LG~F*dKjbCl2S1nV@Nb0-C? zLFLJmooX?-kmNErj4{BLlatMVFL-F5Q9qqNzQXwIB?r$k_MBJ#+`XeWKBM~aG58Lo zjr-UilH!cka`uLH@E#4HsTg4{-vkcARrZQn#w+J5YliJM`^QhRPu`>Sm?9EZ0df~gjBG$?z z7SQ(LUX9Fjd?fezCi$APZ}a7K4)DyLmdhzvY+vM`g`{Ol;!rfa+$u%JU-99JuV5q*p|I^${YP&-L?3x-n-HmNE}bCa&q6*XX@m;h}~&0 z`iBkl*`e`9?LV4n{=5=$bNBP*9sUdQ40M(rk6jK~Vl#Gt!O*R86|4QCuiEWD`|zdq z|2D_I6IFOz&)m;~4w|r`!)^WaxOuKq3f%btHc zss5}(VCK|BB4$GH9Q2>OZN^>4`|uf9I?@ZVhQ>yB%U?R{zI3dZYi4PV|@wVuhB zzU0f;EY$u$--=nt9!AfWUUSBebdFS<6$Q4b<71lf0y4GRq z>iOPU&gv^Ztsw*1a+LL6*DUt;CiZvhJ?v5Iut)WL@4%_<9kRFXjH5mLXTl%cSN`bH zqC>2?l%0csH;(=qzGlg;J4ewU`+wv|*!zBny>AwCCSBl!dtC5^)5V0?=4hWRCig2JlBLgH_m=<fU5dUS*Q2G97eJ^}T_I&YE(YWT~=d3012d(QDkYPW}_~*J4M*rYHk1z)6{*4Ko z2|V6^>g1c@Uk%2F)${&p!TD#1G?*V^LID`H1W$+&lZCLn6V+?mjV+KOS1wcoNrndj<4fgU#>B8}0de3cCMsY5CNX-XwpUnf~7O@M_|bYa5~2anf(1 zen8=>@s>VR#JJrWhi*F3S8OZfzvw`=Y=;I;4>1NkFzTP{mbnrw8qoZU4qix`I{Bt34OHS= zJMA%QCqe@s_PIm@-5xs@eC+=M?uUaf!QBOa_&m6&zy()}ZXJA`xYGXY0Zv@0=6$?- z>f~GT6==?V_$5DLjILqMPcl9|<4QFq+3cC#%oW>0cU@YCl9{r!HW=65)^Jt=b`oqE z*)H!I4?Hx>)A(3cMCc=2hxi9VL~uN0lOV~T=6Z?U?Y2(=rkRA z6|eJB*P>DOx_J1=Ez!M8{C3s^--=r(dy+O95{y$F@Jl^!WN))zh}R-F6|v`e;jQuC zT(N#mbPtn0JL`7e>$j+kXNm`H7-5{+k1TczbsE{{EEw?Id!R4jEPU(0SMs^F4+C4} z!aE?mMepD_8=P`fhdsBDu@_DO;bcF5eKu_t(PpOVu)mI?P2gB|6pfX}snQvz=(>ND z|B@r*r)qgDslF52)E?;59_NIh_EJ5c0Y6lk14nY}1H7v~1=J@ShpyQlfCGDm=vU)= zkan(tPW7%ASfhp+gJxtLsGDQhu}qSwPVymlXltv^VQ{|K2QK4%M&J}NY@%Vw?D?D@ zP#n3&MQh@gXg}ugUCOx3WPi%W)>6Y>@O8>MXz#z!b@s8x2ifD}=sS031U=|A*}M-f zz0&8!F@Q##G1v<{=DN#EzvAiNoEz9DZ?N~tS+%~dTU5rga`4djkgqZ*T=wz52)sPR z%ufPF|0sW@&$l0jzwTq4{j96yBkVOa>TGw?=(2mK&hWVXJ|q3TGVD6yrII5`$%8+d z@#_hr5&9O4gkd&}0jIm+)AwHF)3dPQ=zY<92{5JuLvn_41_(BASLT2xb`8tkr+loG zHRpVD#rkp3lFmKIF6g$)BRL;XPG4VvR*sXy`yO<{a>iP42hhJTznRb5yXl_?nt9Sp z^1m|J#*G9iNv^W{rcQ`+`7@BYWit{_)}B!VZro#L;kJeOkK((Md0Vf%VU{gYwnuP_ z12?TZ$$i3YGvyAQ-^Dm9ew2EVIB?aR$zBr2Jjl+Hh1}Ol{3!e2DYc{aX?vx3Kr--l zvbK)`kFsX1>qOc~R5|UqsIPX^Uz0M;HScYUf5iwWCY5(0qL0LAo5-8QY2tIW>^s

fNZ#(x{N3*1H||0CPZQ!+d4e(~S{x@5^e*z2LxrE}Xi!vAkThPxi!?6Y^> zoWV}xdC<2qy(-(Xr+o~}O3tXTj4V7D^og6kLZgZ=~ICP`ko)^2~pR|^u z?a!or*@PQsr(Gj_O8Ngs{(FgI&7RQk?>k+-|Mr-x>xyWn#zjo+ui*voM)_sDBZx)Z zZQP!pZ7i1^qMi1SkPG4{If~u_kI+n;<{aD6YTC{=tLiGdy0@PLKj}QH<9vB*T%!M^ ziKSoq(cF%{8^5;@fAMaojcJ$K7)9Bi+(zwx63e*BYAcPlQiBulS2f<$ds`W2?Y2Ii z@3Uh5g};2rsnCxBKJxLn_}%B5#s=Z}o~x?v@QwD~a?i10Ng5N$0&Un>ow^bF)@B$x zdcrtOc_nkLzU5z!Z3Y_Fnf#Kw&A=mfx&jq*Iak2g9tqiSb)S<-+rxGk8+yCD^*nT@ z`-x6NXUYel`5mSCjkXbT;h*6?ks0(y`TtV*XkL_qskb{)L(tc2yyMh4LjDbn-_Zrp z+%(Y5X>bp@QvHq8-Ne~{`JmKq<<@zFwhhYijpT46eN$c0lG>IHeDhJKUs?mn!9Q*P z?76BA-cfA0hqY2ddzshSKBW-zs(aDyp}n&|;hYxd%Z{|$@Wf-xuiEtK)SE%QaJ1g$ zXg!_jZ(o94>nY}k-;eX{jDvH&DT{Vzo=COkbq%~QH^zUKjs4@I?e}@&nxFr)pC*gA z2oJWFBHBiWvHVWhN>+N3jFly^W%$*wMK#yNlnHjD{+wGn^FJ%vzU*=r z+lscI6;md-Mfx8Dx1`7anjZffd;H(m3N5sMhsgsJy2q^T{POjI;Fu|at)r&~c9KW)%zZ{= z|M!gTL+P{fNp;UQA{)rT=mAz}K4U_eZ2QYhU*z7a3i1x)1764fr+9wCTvykGFTM#~ zv%I-5(1Lus&5Uo!<+;uS-OQZ4gl^FAXXF9qbts-H(GTso9C0UYinUO;i`SJU@{oQLWz zAf}4>9lvrJ_px}wCyDz!S=(?ovUKf}yUo^aaNR{e!^q+-Ze!9`>?OfT;J1YRxzT-Bmusf4`8%n;mER_2r43r%ROo7bo?LOEYMzfZ zc04dccQ2<}xrfyU<%Lu~vWFWx6m!g-qb<(3Eh7GF2knJkhId?NM5fS>y97d42MwHlK*LSu~D(357FOrr%*MU&Q>R&oq`JXBMa5z}RD7Z!7Fu z+fIJ)znlffF?R&W$8;)m6EPVkxovME%UD_i*Ee$(J6i9I>TN{^Ag^ya@p4tfJXrWm zvF#us=1snGr+&zRyVxH4^sjT49R09nb75ZiA1%x1p!W)c5-yRUf`F_!O*-QmT`H~TwC`fcTkrSCtE$n zD1OhCU_Eb#_VP2)OR&%HXO5(kiN2HrI3Imwv|X+o=>AZl5t$6E7kG9U-_>ID!}H(} zn(J%!64RLvo^ivlDGnh{mOBppqujMm-s7$<=Z>3l=>D0qvaaKW^vO&Nf3mQI!)nG+!UaIb&#j+G3q$H6oet>W|^eo2pZ~guC!{;QSy8XWy0305p`&I#%r0 z`6b*3+ z*g*TmqLYs6fQ$d}957a~mId!HdCrAL0sjS0IUY-Z`DzrNVt55l@3cdcp?n)R>z#J! zX$o+GoeCY~o2gdLRlyaGRn)g(4iC-)<{MF%vj5<#nPkBPcg6bJ>ub2RzGkA!i=S>~ zjRxKGyIw|zRC_1*cE)wzsG;?p(LTjpRn^!J7|yej(L?KnllSXYjZPb;X+++`_W3Gf z9~w;`h8hv$PsV*lmC=L`eR>h|ae8!8ps{*xm+tM|4J~eDzq^?nrowHLdtR3-if5yH zE;-NPn;G8vwwuV+-j`hHv-$S&eJkIImD4-6DCWvwe)$i4&3!w2!zOofmw1R5-r&>*W)+ID+SNrz7bqp ztj}S;>3((_{cEJZ9`=m~U1qI^d^>5>lRU9ulv#UzlzA!xnyOj#jV^Sp9W}0nUHgDh zx$0Y89&&97reO60Rt@X8guGvss}^>d1YhrbiI@j;JM>=i+fwiR z-tK3!qU+>C_-)r8ZVTuR?4`_Q8{@i&`#U^~jn*(eli*}ydl@|c7~@qw0huDF(yJim2%QhghDd9)Rh1Dib08=0T`*cb65L=JP##tpB1X()bL z>}q;fcfI9ePf=an>vu18@8u4crp4q4gs0xiKBjs{X+!ss3s0@TpY!`J#(bkyc1K_n z-|d3BxUtNox+%y|=X$Ju)4 zD~wAQdl_T3<6&YZb%$>9LF??%J6#I~2fDWzkxu$KJJW2HOw&nZU0J$w32b71(mdtk7dG1PjSsJoi>uphuvvi2IqRjD&`SCq;-GlOME{`ADJU!3^>noVgT^Yz)+%1X!0sIDl-+v!-^Atahx$2H#wnR@De5^)ISBS9^li>o+SB9 zv^xH4Hmz=S*G^MkcUgV3?~SeKZ`)9Q53R~?s8|l&CA1ltL1W%V{Wj?JB;)fG^ol)} zJJ$P$v27GH?zKOLZmqkA81uEPsg3Sd?qRE(b_!Z6XWZJDkBS`jV{(;>_nm^~V#iH9 zQgV}W=4?5}KIkVWYc+AI$sT_vG?Q~PF`JB2$ZuG3wrDCc-iU0bZ{=DO)}AulqBYT= z%QRa=<2k>;uC-OMuEYEt>^C#PEz8Z_Fyq-%;p-W|H#zGVDntf3{^%iB6}jzsNBdw0 zZAnK!mbLakt)GsL?&3uH9-wYE_ulPC78H)TRp08$PEPb^_w_|8*cZLg z8~D)4$BazAkh$xS943DV&&KlZlU3j7n$5EYWN9zYrqCbjUB<+L>A=9&7A~aRVBN&A z59ofnz(!)%q?c`yOg$u+l^o|M*F|I;?e6D0!o30GpYnBeL0`%4gmB^WwC_$07j1zq z-nb$V&h-S2&o&~%z|q6FYM(fUJh$O0PyILMdFmf>4QuJ<&Zy0-na~w!{#1C&X~w(F z8uJm@no@(me}#Ok?o|Im_V;q+C&>z$qjKoDgEi(IlU%QTS97De>SUg0!&8rupMEXh z70~H(6P~N{AYbVoX&>|^-7?2Zt|RVsZg3&{PPpqr5%kqgAJ6cfm9wGWFbkja7x9tp zD>e9HEI!~}0sdo?*BUoE1p4nn=3&jms=6@o;EyifRv+fRfyI5d z)jK$?;hb0HkE*6=Z(lWW=7mCV`gRNr3OBX&tsZ6CJDv9Q{0^Rrhn$ASV%NfA?qHeD zGszL#kyA?e{-xvYi{2n5QnBG0gevr-k+9MXRM{MmwAE>Xrp%EF+5ML;) zoW31-?94^DDdE-7u?06O7u1k1RB*Qu7wo`I2CiV%0n@6hv2b~6LranZ=iCDWXP(9$ z&Ymy5+TsuWikcR2PhjY2tDRq7id85-&}jg1ur~Vb@hKA^IF35IOwoFt^m3) z!>$_k&-;)Wt|;KVR^{B8k9q1%gIJ0ueOo$RmpqOc3$hc4u4lI zA7k4KT96}_0tbIl%L(oWBtEAl5k2FCdq|6ycWwXon{^%+_iROD9;EYX4?8$e`P<^d zyWmr=m_z)n@E_$uz^C8(%1u)O3$YWJ^uHTB!CqvTec&M5OI5l(e_M8UKRbl}JPw^^ zM$7)5GUU^_>nMwNC$y9xpICiDukM5g$d;=ys)9B;+{rns|6|3rEZ}AthBcP>cjs%zQAGT{794rB=hONXh3w< zfNU9duc?#&Z!GmI;Ja%lG}O6)A)Q11xSr4UFt#tTFOq-8!l4N|Itecp4sU_O32?~f z{dIkB@&6t<=9SX>yV6~QEnAxE$(E+L(WR{Gb&5O;l-`Gq*?3vHPa@R`$Vm>ex;0L{AFAV~XOj;iVZ|$yWK!GuTf`$f+tF#jn-3G=%96#Nk0byT(u z_5u08CXiP=Y&}m7HoovcAP7IRY$9p0*JBd_k6g~8SUN0uYJALX`fsr>(l&DF4yTPE zu-oJB2w3oe`zK)bWgXm}j?e4m2e1PccXa@(1e(}L-iln8u|v2fi;knVmcNU|5B@9s zyx*p;Z+{Z`@mQV_2w!UkJp2~l$sx~+^vR@@IRAi@c>i(uKzy7plE7T)UfF8a$#K@u z=Gr+I?t$M-aL2Xu=d4>I`O(|B6B0eC=_BSN9~pfQbwkXH%G>(pHzlni_5+<#ZJz?J zVhl&aHzvSKMy;Q70Uvwuzf=Ew`t^CC;YMxhsascsh`miYb?fV4WB}>!S&Y5(>2~;x zqq{4=X3j3r%aEYPLi%+P^&Fkts*7%~yAZtUq`?jy!W88*3+7{JENjzuC zk^5JftsU{$%vYg@4o4mUmov<#qkBfs%fxGsUZ=f>JQHJ$?Q4)r4f3*epc_;YGv1OK zN1jk$q$Snxm-DUp7!6-Z2t9J>t+J(Ew=gbmm)&dK`@M(xQ4G`ZczeqhNP?Tkyh;zxNtZpbD3$0*{~Web_{u z2`;*mCbn!ba**uEdS5cTQ+E^V&z3n}2fwx%E(kH_xv;U(R*6d6^zt8kR=TAK7(-E|B4X<1V5y6b43E#=*wy6cbVkE6Rf zFdZ1Nx@(%HyQYP+$J)AUfbWu|G=DPmR7#sU_~NFTasFZ48COV-sC4d=EE>l!?}}?`#f2D&x9X2bio=^-CJwxgISExEcDX~^wThW zyn*kne1}*k-oZogFWS1PA6_yC+vr;Ckh-r|@?8;hCfnZtJ}T21RNlK)(Ek}|TX&Bi zK!Rdg~9s@e>P2-OtATZtTn5FB%(aqBzc` z9}DS^@Jc+%{Vuu8U+NHlC+|oO2TyDzalwpgqqdT}n=|^+F8Zho{kD^Jf^vk8%(_F@ zxre>d&$^vs@sxz{Xx7rr^kp6EShlG>tlw(zE2xqkDuwqx3wH_66(i{ML4B57?9i_8 zyp8ALwMDG0pTqO@yM*7HqrW%u{TUsWwV*xZseiI{So9!kFRId>u+@H6!?XWUe~sFT z_r<09Q_wRXL`Jdo%#`3t-j}~ly5`Yx>__QrJX@Z8V* zOE=a2n8uvVXU*7f(}GKYD;R<$KB2vC67t+Nv~h-a^uIr2R29YLPUTo!Mcx+V{nn<6q*?t?GL+H01=)BT7I{fsJXGK4^s*&*P`D(bCoNw<|=of<~w}?Kg~8G&wkx#Zp1fLv%4>TxB0Kv ze8+~Hv32lsmB8)Z>-pUK8uzh|9UVEFarg;yyXwWER?Jiz>r^&X?^a{OTJUqqRn~{H z8p=j(b?WM_Nl)-EL1P2F?P9sgB9!U;gX=Y?Zd~vTWqWqm|4;FIRI~F8S&s2LM%n$m zvpL|D>mJ9{;4IoLqx?vtQ=UNikl;;}i}prcET{e$U*v0)e~a>aUU2Fw*TazDb(E7s z1357zIOBQySv&jRwUp1b%9Dd8G`R^pWRn+->Hp98Ka2m1{%i1Un!i!o?ROVL6TeVD zHeG@>3Rw8aQdVtr+V;}+(BM$Y^C;ibaH(z@Ws@iy6)jVIoci0J@^O@VkgaqESLXn# zp{>n-|7n<-k1s{*or=~w0{!0eGpF7_;CX|821lKx-29wVPCM)mnvWF9H6DuRTf%?I zTjRfPY!Dy1ns3D{JI@#JTzZTCYh32@U-Gi<`O^P+{LkQSc6)vHslAhXhNPqZ>|Hw- zdkXkIK^^CLDdosUR<7qN%0~gqd3Ks-vTNzT#z^1ltG-oFa!_{k|Jdla*6saji#BuL z>wZ?{qe<#)%x_}w8JemZ>Rn0DOPT-xuZyz<;z0uRCqpXNS@rSoqM96_J%c-m-g8(ld)oZByOY$A4k z_7mOpQba$_p_83Ye!9+lm;3Q37cGc}|KV5N&+cOXd>$X6k9yks6%U*GFVsmkBWKdg zh;#qr0s4AoxEXnXePhMbzQ_>vO!*Z&75nPF{f79jW#5vnF8|~?{F3LBe^OWQ4t|fA z_SVo|8||&5J)JK#sPD8nxs^84%}Cd1wJH0^;NUB?*)@hXpXGk926!O-{cOK}Qnwrb zXKcUjkM>Kx&)9yw_cprlyvYFv{!f9woxSdXExyRdQ;f(XTYZu5fB5jB3k!_qH#C0X z1(vV%Jmc4;{v0rr_nz?+&r1v5NjvADOO4@oXe%|^pL0B$@|Lmv{83MB7jx?w-KREm zs}Y%2v1psd*J*zR?O&MUi_Clfj=l=_jV#6!?fejQwqHn;+lIl76+F=-i!0UA40^$-kD1 zeqR05w0yU5s?)upE<7Cj`xBILXKmX}(At;+e1h*yGpc_$?M3#w@Y6=**dLMu;X>;D z!H67f*fqVqdTvwOQwdG&ODd=P8*V%FWcOW%j!dbf?f!v}9zo`3d|ujZ)}Fu?@bSr% zK#$k}*PvCor2 zAK#dlj!o(?cN_|i?4}vopU+G0^5o@HU%GsEH|w&WColANAKfLn@%oO5jA&WT3i`LeP3>z;VvSvLQ-Mv+gJa{?PHkdJ6P zOxts!Ij;+lqZ){VlRTA!?L~Lfs}0%CpX!f)caZKbG*WK0&QV^v(?alH2LAng$n7#Y z-5e2aE5t^EJtU0HV~uNse4lyw_!BaLy%2xGQFMucw6Tt7UUNiCK03&fO~hoHBmAqm zFC-gSod3Z;GNPp;Iz|bbka>aQWo+`Xzm||EK=0Quj`vsCW3+}b8t`x_dBF!*dBHo5 z-k6u8u^h=*{)17x>~XuE&Lh7Va~DX-^Oy3B?Bl%UmB{a3Xna$niu)N05Ams* z`NjtMB(>L!{wX$e=6yeB=|apw2ed*yt`!d69UmeGn;!rB(1cU=F*JVz?>qlH-8bds zJZDVG9cj!BoxU-zaV#j-c zCvQXyjv;qZp2q|~)^951pKQA&z`3~f4d~5F{>CVNj`PzQe6Ml)^4@Ikp0p6$m8(n}3Cjh!JnhViVoH~-RoZ3S>vvmR~* zx6nNo>2EIUm^?$isq()`4%o?h*^`AIiFNmO=9_ix{>0)<=$+tQhUQ&O~F<1K`{Qe&u}9&E#+HnN#_YF@Lw z#Oz$6jnGLW@=w%do)yE)xtDnSah{<3S@^)`Dp$x!_D3U{M`9Fbjb~)E*4<_#1t&!F zNEpb^o8U1!v8iT!*C;+>_V*W%mwk(A_>VBYN1@UDXda1t&Krx*Ok`}@sOSAl(V-{l zH}TQj#Sj-Rz-HHBrnF@9O!Jk_eD8S&Umw2c4*2)e;QHojdt6GO^LX!@b)^%$^(C8h zzuJg^pXXX9OIF$E?rIooPhX#P_D=F5KHpQ-RjeVEXZpIFb9IU>RP4u=%8Solch2VN z%$?4?>AV|jD;Q$mTCR4Ot4d&L?;XZCtu~We#z*;m4sFj`AK&kvuy-JXS>wx^=|C>a9%}kKku!6! zlTIu(1J{1X6_DL1gBZ#z?2VzVMr0)88Ge|9o;|5%QUn6 z;)OTdi48W&vslB+H_@)muafIEFX~svL&l`=)^7x6vBu`GZfdUf)US@Nn^1gFUJbG2 zq3>b?BMzZ$AwE9*oo84Zpf5z_@gv^s$`;-;P z^v-5@kQEC`okjTanyQRn7?j;R(1s~Lp3Zij!Kc%GdVaVDznSReG&<8+^bGZ3@iL?M z>@CtWbQf@Pa2jL&34FAYGL6fx&|!9>x5&2GJ<*K3iO+1m3%^;e8L<3j$(H}DwxgdB zIg2iGCfAH~k2fRd$KYc_Hh4qtieebjGNye9VX*m%t?jG_EmN7*m)&9rc6iS5_gi$BMT)lB|; zpO$PN;$xO?XMBMbpT3E`TDdL6=S~dpMcxLl3dX*Y9KHwf(TTss#$(3DVs5-5>LX)M z6F&PE5sTRmd){EfKiKk<4Gz2UUn&lBHT`}&b+BK)x2K?q1AMwoY?0u8kaEQci!S6F z6J5wxunW47U(AjF>G#k@Y~1BN*qyDoOX|tbD;|6?-V!_gkD(F8TOMW1)<$Vo@s>@{ z>{@8{ENjsAj}5l`W3o577>BdaMn`mPc8ix5daant8(E8?i3O1}xdoBcgFOCg9p4!= zqkZn|yuN`M^hNPL?Zh;kA0ykmVk`#-TmJ!GIYv1_Q~X}Hp*yfGe^Ur~vl3WlkNC=c z)R*qm0c^)#b#5ZO3%(dQcF9Mzy3F=b2_N~WRuB5DkIIejb^g(5$mIhi^f-G7eswjeAf)JV=I43n_jmsa)G_#7UH}P5nH*^l@NB%gdSXJ z;TpbAOzek#i;g_S8%XKvi@ckf;C~e#>2>au7M(*o!gxMp+3Dp2O2v+m7JQtssd1PBd?4slT57KMODUnE!2jY`aZbaA8c_x!@*0mfBm3-&8hmweQURo7kM4SjjiP zV#i9p2TxS2WF|2VKHA@SMb(Siv-f;u=7nn7e=tfr!a=ksSc1Qda;;m-ry8ZrcKoFB zHxkNUjuWBzn$@}MMud(`0EAB`Bo}On2{);*u>hA2G%(;hD>+FZ;-GY_r z$lZHbOC>vPn+(4F;(M^YsO^%ive^WCYBd>~XWG!)=-cb`QTwlVv9~_RdzO5|o~bxlYy>Ga z=+HHsks1MC6YrLMGy?f3c3)gf%#6l;{`91IQyyEiEp0maHbxD$`~}iex{;%&klQO% zO@D}c`_vtv74{vVTNn%OCHid4so(b5)$4hEG+vcEM5~yywrA~kH|W_5(P!MLYdvFp zG!JX_Y)LP0kvsJKhde($kLQvbzQ8VCD2^g9Dt�`NCYYjoFL@t5 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

t9=XMI)mn!Oqn_`Gl~}!Ld&{ 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 Date: Mon, 6 Mar 2023 13:20:28 +0100 Subject: [PATCH 040/436] Add info text --- bin/generator-utils/utils.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bin/generator-utils/utils.sh b/bin/generator-utils/utils.sh index 0d5adbaa2..794951520 100755 --- a/bin/generator-utils/utils.sh +++ b/bin/generator-utils/utils.sh @@ -58,6 +58,8 @@ ${unimplemented_exercises}" if [ -e bin/generator-utils/ngram ]; then echo "${YELLOW}$(bin/generator-utils/ngram "${unimplemented_exercises}" "$slug")${RESET}" else + message "info" "Building typo-checker binary for $(uname -m) system." + cd util/ngram && ./build && cd ../.. && echo "${YELLOW}$(bin/generator-utils/ngram "${unimplemented_exercises}" "$slug")${RESET}" fi From c0bb33630cfa3958b20da460a1e82e5e5e9aa90b Mon Sep 17 00:00:00 2001 From: dem4ron Date: Mon, 6 Mar 2023 13:21:53 +0100 Subject: [PATCH 041/436] Remove trailing ws --- bin/generator-utils/utils.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/generator-utils/utils.sh b/bin/generator-utils/utils.sh index 794951520..d0370f130 100755 --- a/bin/generator-utils/utils.sh +++ b/bin/generator-utils/utils.sh @@ -59,7 +59,7 @@ ${unimplemented_exercises}" echo "${YELLOW}$(bin/generator-utils/ngram "${unimplemented_exercises}" "$slug")${RESET}" else message "info" "Building typo-checker binary for $(uname -m) system." - + cd util/ngram && ./build && cd ../.. && echo "${YELLOW}$(bin/generator-utils/ngram "${unimplemented_exercises}" "$slug")${RESET}" fi From 4ec2492a4bd05be409ac27c5bb5ae0fb73159753 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Mon, 6 Mar 2023 19:36:40 +0100 Subject: [PATCH 042/436] Move test generator and template a level higher --- .gitignore | 1 + bin/{generator-utils => }/generate_tests | 0 bin/generator-utils/test_template | 9 --------- bin/test_template | 7 +++++++ 4 files changed, 8 insertions(+), 9 deletions(-) rename bin/{generator-utils => }/generate_tests (100%) delete mode 100644 bin/generator-utils/test_template create mode 100644 bin/test_template diff --git a/.gitignore b/.gitignore index e7c68a4ee..8f4f224b4 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ bin/generator-utils/ngram exercises/*/*/Cargo.lock exercises/*/*/clippy.log canonical_data.json +.vscode diff --git a/bin/generator-utils/generate_tests b/bin/generate_tests similarity index 100% rename from bin/generator-utils/generate_tests rename to bin/generate_tests diff --git a/bin/generator-utils/test_template b/bin/generator-utils/test_template deleted file mode 100644 index fbcebaa36..000000000 --- a/bin/generator-utils/test_template +++ /dev/null @@ -1,9 +0,0 @@ -#[test] -#[ignore] -fn ${description}$() { - - let expected = vec!${expected}$; - - assert_eq!(${property}$(${input.startVerse}$, ${input.endVerse}$), expected) -} - diff --git a/bin/test_template b/bin/test_template new file mode 100644 index 000000000..ec6ee9785 --- /dev/null +++ b/bin/test_template @@ -0,0 +1,7 @@ +#[test] +#[ignore] +fn ${description}$() { + + assert_eq!(${property}$(${input}$), ${expected}$) +} + From 1b3cdeef8bbca15f23d5f9ce802e71895fc71b98 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Mon, 6 Mar 2023 20:34:16 +0100 Subject: [PATCH 043/436] Use property as fn name if there is canonical data, use slug if there isn't --- .gitignore | 1 + .../fetch_canonical_data | 3 +- ...exercise.sh => generate_practice_exercise} | 19 ++++++++-- bin/generate_tests | 3 +- bin/generator-utils/templates.sh | 35 ++++++++++++++----- bin/generator-utils/utils.sh | 1 + 6 files changed, 49 insertions(+), 13 deletions(-) rename bin/{generator-utils => }/fetch_canonical_data (78%) rename bin/{generate_practice_exercise.sh => generate_practice_exercise} (86%) diff --git a/.gitignore b/.gitignore index 8f4f224b4..e7f03fe4f 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ bin/configlet.exe bin/exercise bin/exercise.exe bin/generator-utils/ngram +bin/test_template exercises/*/*/Cargo.lock exercises/*/*/clippy.log canonical_data.json diff --git a/bin/generator-utils/fetch_canonical_data b/bin/fetch_canonical_data similarity index 78% rename from bin/generator-utils/fetch_canonical_data rename to bin/fetch_canonical_data index c2de88d53..a487581f2 100755 --- a/bin/generator-utils/fetch_canonical_data +++ b/bin/fetch_canonical_data @@ -1,7 +1,8 @@ #!/usr/bin/env bash +# This script fetches the canonical data of the exercise. if [ $# -ne 1 ]; then - echo "Usage: bin/generator-utils/fetch_canonical_data " + echo "Usage: bin/fetch_canonical_data " exit 1 fi diff --git a/bin/generate_practice_exercise.sh b/bin/generate_practice_exercise similarity index 86% rename from bin/generate_practice_exercise.sh rename to bin/generate_practice_exercise index 1eb9b882a..781021432 100755 --- a/bin/generate_practice_exercise.sh +++ b/bin/generate_practice_exercise @@ -36,6 +36,19 @@ check_exercise_existence "$1" # ================================================== SLUG="$1" +HAS_CANONICAL_DATA=true +# Fetch canonical data +canonical_json=$(bin/fetch_canonical_data "$SLUG") + +if [ "${canonical_json}" == "404: Not Found" ]; then + HAS_CANONICAL_DATA=false + message "warning" "This exercise doesn't have canonical data" + +else + echo "$canonical_json" >canonical_data.json + message "success" "Fetched canonical data successfully!" +fi + UNDERSCORED_SLUG=$(dash_to_underscore "$SLUG") EXERCISE_DIR="exercises/practice/${SLUG}" EXERCISE_NAME=$(format_exercise_name "$SLUG") @@ -52,9 +65,9 @@ cargo new --lib "$EXERCISE_DIR" -q mkdir -p "$EXERCISE_DIR"/tests touch "${EXERCISE_DIR}/tests/${SLUG}.rs" -create_test_file_template "$EXERCISE_DIR" "$SLUG" -create_lib_rs_template "$EXERCISE_DIR" "$SLUG" -create_example_rs_template "$EXERCISE_DIR" "$SLUG" +create_test_file_template "$EXERCISE_DIR" "$SLUG" "$HAS_CANONICAL_DATA" +create_lib_rs_template "$EXERCISE_DIR" "$SLUG" "$HAS_CANONICAL_DATA" +create_example_rs_template "$EXERCISE_DIR" "$SLUG" "$HAS_CANONICAL_DATA" overwrite_gitignore "$EXERCISE_DIR" message "success" "Created Rust files succesfully!" diff --git a/bin/generate_tests b/bin/generate_tests index 6ba98e4e4..74c137d7f 100755 --- a/bin/generate_tests +++ b/bin/generate_tests @@ -1,10 +1,11 @@ #!/usr/bin/env bash +set -euo pipefail # shellcheck source=/dev/null source ./bin/generator-utils/utils.sh function digest_template() { - template=$(cat bin/generator-utils/test_template) + template=$(cat bin/test_template) # shellcheck disable=SC2001 # turn every token into a jq command echo "$template" | sed 's/${\([^}]*\)\}\$/$(echo $case | jq -r '\''.\1'\'')/g' diff --git a/bin/generator-utils/templates.sh b/bin/generator-utils/templates.sh index 056821a68..d1264f27c 100755 --- a/bin/generator-utils/templates.sh +++ b/bin/generator-utils/templates.sh @@ -6,6 +6,7 @@ source ./bin/generator-utils/utils.sh function create_test_file_template() { local exercise_dir=$1 local slug=$2 + local has_canonical_data=$3 local test_file="${exercise_dir}/tests/${slug}.rs" cat <"$test_file" @@ -14,11 +15,7 @@ use $(dash_to_underscore "$slug")::*; EOT - canonical_json=$(bin/generator-utils/fetch_canonical_data "$slug") - echo "$canonical_json" >canonical_data.json - - if [ "${canonical_json}" == "404: Not Found" ]; then - canonical_json=$(jq --null-input '{cases: []}') + if [ "$has_canonical_data" == false ]; then cat <>"$test_file" // As there isn't a canonical data file for this exercise, you will need to craft your own tests. @@ -28,6 +25,8 @@ EOT message "info" "This exercise doesn't have canonical data." message "success" "Stub file for tests has been created!" else + canonical_json=$(cat canonical_data.json) + # sometimes canonical data has multiple levels with multiple `cases` arrays. #(see kindergarten-garden https://github.com/exercism/problem-specifications/blob/main/exercises/kindergarten-garden/canonical-data.json) # so let's flatten it @@ -64,6 +63,8 @@ EOT function create_lib_rs_template() { local exercise_dir=$1 local slug=$2 + local has_canonical_data=$3 + fn_name=$(create_fn_name "$slug" "$has_canonical_data") cat <"${exercise_dir}/src/lib.rs" pub fn $(dash_to_underscore "$slug")() { unimplemented!("implement ${slug} exercise") @@ -88,11 +89,15 @@ EOT } function create_example_rs_template() { - exercise_dir=$1 - slug=$2 + local exercise_dir=$1 + local slug=$2 + local has_canonical_data=$3 + + fn_name=$(create_fn_name "$slug" "$has_canonical_data") + mkdir "${exercise_dir}/.meta" cat <"${exercise_dir}/.meta/example.rs" -pub fn $(dash_to_underscore "$slug")() { +pub fn ${fn_name}() { // TODO: Create a solution that passes all the tests unimplemented!("implement ${slug} exercise") } @@ -100,3 +105,17 @@ pub fn $(dash_to_underscore "$slug")() { EOT message "success" "Stub file for example.rs has been created!" } + +function create_fn_name() { + slug=$1 + has_canonical_data=$2 + + if [ "$has_canonical_data" == true ]; then + fn_name=$(dash_to_underscore "$slug") + else + fn_name=$(jq -r 'first(.. | .property? // empty)' canonical_data.json) + fi + + echo "$fn_name" + +} diff --git a/bin/generator-utils/utils.sh b/bin/generator-utils/utils.sh index d0370f130..e89b33e67 100755 --- a/bin/generator-utils/utils.sh +++ b/bin/generator-utils/utils.sh @@ -10,6 +10,7 @@ function message() { case "$flag" in "success") printf "${GREEN}%s${RESET}\n" "[success]: $message" ;; "info") printf "${BLUE}%s${RESET}\n" "[info]: $message" ;; + "warning") printf "${YELLOW}%s${RESET}\n" "[warning]: $message" ;; "error") printf "${RED}%s${RESET}\n" "[error]: $message" ;; "done") echo From ee7929e25376747afbc64b18fb1fd4c780e48a33 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Mon, 6 Mar 2023 20:45:26 +0100 Subject: [PATCH 044/436] Refactor code a bit --- bin/generate_practice_exercise | 10 +------ bin/generator-utils/templates.sh | 45 +++++++++++++++++++++++--------- 2 files changed, 33 insertions(+), 22 deletions(-) diff --git a/bin/generate_practice_exercise b/bin/generate_practice_exercise index 781021432..c2bae5562 100755 --- a/bin/generate_practice_exercise +++ b/bin/generate_practice_exercise @@ -60,17 +60,9 @@ message "info" "The exercise difficulty has been set to ${YELLOW}${EXERCISE_DIFF AUTHOR_HANDLE=${3:-$(get_author_handle)} message "info" "Using ${YELLOW}${AUTHOR_HANDLE}${BLUE} as author's handle. You can edit this later in the 'authors' field in the ${EXERCISE_DIR}/.meta/config.json file" -message "info" "Creating Rust files" -cargo new --lib "$EXERCISE_DIR" -q -mkdir -p "$EXERCISE_DIR"/tests -touch "${EXERCISE_DIR}/tests/${SLUG}.rs" -create_test_file_template "$EXERCISE_DIR" "$SLUG" "$HAS_CANONICAL_DATA" -create_lib_rs_template "$EXERCISE_DIR" "$SLUG" "$HAS_CANONICAL_DATA" -create_example_rs_template "$EXERCISE_DIR" "$SLUG" "$HAS_CANONICAL_DATA" -overwrite_gitignore "$EXERCISE_DIR" +create_rust_files "$EXERCISE_DIR" "$SLUG" "$HAS_CANONICAL_DATA" -message "success" "Created Rust files succesfully!" # ================================================== diff --git a/bin/generator-utils/templates.sh b/bin/generator-utils/templates.sh index d1264f27c..5c957314a 100755 --- a/bin/generator-utils/templates.sh +++ b/bin/generator-utils/templates.sh @@ -3,6 +3,20 @@ # shellcheck source=/dev/null source ./bin/generator-utils/utils.sh +function create_fn_name() { + slug=$1 + has_canonical_data=$2 + + if [ "$has_canonical_data" == false ]; then + fn_name=$(dash_to_underscore "$slug") + else + fn_name=$(jq -r 'first(.. | .property? // empty)' canonical_data.json) + fi + + echo "$fn_name" + +} + function create_test_file_template() { local exercise_dir=$1 local slug=$2 @@ -49,7 +63,7 @@ fn ${desc}() { let expected = ${expected}; // TODO: Add assertion - assert_eq!(${fn_name}(input), expected) + assert_eq!(${fn_name}(input), expected); } EOT @@ -66,8 +80,8 @@ function create_lib_rs_template() { local has_canonical_data=$3 fn_name=$(create_fn_name "$slug" "$has_canonical_data") cat <"${exercise_dir}/src/lib.rs" -pub fn $(dash_to_underscore "$slug")() { - unimplemented!("implement ${slug} exercise") +pub fn ${fn_name}() { + unimplemented!("implement ${slug} exercise"); } EOT message "success" "Stub file for lib.rs has been created!" @@ -99,23 +113,28 @@ function create_example_rs_template() { cat <"${exercise_dir}/.meta/example.rs" pub fn ${fn_name}() { // TODO: Create a solution that passes all the tests - unimplemented!("implement ${slug} exercise") + unimplemented!("implement ${slug} exercise"); } EOT message "success" "Stub file for example.rs has been created!" } -function create_fn_name() { - slug=$1 - has_canonical_data=$2 +function create_rust_files() { + local exercise_dir=$1 + local slug=$2 + local has_canonical_data=$3 - if [ "$has_canonical_data" == true ]; then - fn_name=$(dash_to_underscore "$slug") - else - fn_name=$(jq -r 'first(.. | .property? // empty)' canonical_data.json) - fi + message "info" "Creating Rust files" + cargo new --lib "$exercise_dir" -q + mkdir -p "$exercise_dir"/tests + touch "${exercise_dir}/tests/${slug}.rs" - echo "$fn_name" + create_test_file_template "$exercise_dir" "$slug" "$has_canonical_data" + create_lib_rs_template "$exercise_dir" "$slug" "$has_canonical_data" + create_example_rs_template "$exercise_dir" "$slug" "$has_canonical_data" + overwrite_gitignore "$exercise_dir" + + message "success" "Created Rust files succesfully!" } From 6e47a518e00bd5bdfd44a2e509f8713bf4d36852 Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Mon, 6 Mar 2023 21:54:38 +0100 Subject: [PATCH 045/436] Sync bob docs with problem-specifications (#1633) The bob exercise has been overhauled as part of a project to make practice exercises more consistent and friendly. For more context, please see the discussion in the forum, as well as the pull request that updated the exercise in the problem-specifications repository: - https://forum.exercism.org/t/new-project-making-practice-exercises-more-consistent-and-human-across-exercism/3943 - https://github.com/exercism/problem-specifications/pull/2205 --- exercises/practice/bob/.docs/instructions.md | 27 +++++++++++--------- exercises/practice/bob/.docs/introduction.md | 10 ++++++++ 2 files changed, 25 insertions(+), 12 deletions(-) create mode 100644 exercises/practice/bob/.docs/introduction.md diff --git a/exercises/practice/bob/.docs/instructions.md b/exercises/practice/bob/.docs/instructions.md index edddb1413..bb702f7bb 100644 --- a/exercises/practice/bob/.docs/instructions.md +++ b/exercises/practice/bob/.docs/instructions.md @@ -1,16 +1,19 @@ # Instructions -Bob is a lackadaisical teenager. In conversation, his responses are very limited. +Your task is to determine what Bob will reply to someone when they say something to him or ask him a question. -Bob answers 'Sure.' if you ask him a question, such as "How are you?". +Bob only ever answers one of five things: -He answers 'Whoa, chill out!' if you YELL AT HIM (in all capitals). - -He answers 'Calm down, I know what I'm doing!' if you yell a question at him. - -He says 'Fine. Be that way!' if you address him without actually saying -anything. - -He answers 'Whatever.' to anything else. - -Bob's conversational partner is a purist when it comes to written communication and always follows normal rules regarding sentence punctuation in English. +- **"Sure."** + This is his response if you ask him a question, such as "How are you?" + The convention used for questions is that it ends with a question mark. +- **"Whoa, chill out!"** + This is his answer if you YELL AT HIM. + The convention used for yelling is ALL CAPITAL LETTERS. +- **"Calm down, I know what I'm doing!"** + This is what he says if you yell a question at him. +- **"Fine. Be that way!"** + This is how he responds to silence. + The convention used for silence is nothing, or various combinations of whitespace characters. +- **"Whatever."** + This is what he answers to anything else. diff --git a/exercises/practice/bob/.docs/introduction.md b/exercises/practice/bob/.docs/introduction.md new file mode 100644 index 000000000..ea4a80776 --- /dev/null +++ b/exercises/practice/bob/.docs/introduction.md @@ -0,0 +1,10 @@ +# Introduction + +Bob is a [lackadaisical][] teenager. +He likes to think that he's very cool. +And he definitely doesn't get excited about things. +That wouldn't be cool. + +When people talk to him, his responses are pretty limited. + +[lackadaisical]: https://www.collinsdictionary.com/dictionary/english/lackadaisical From b0aaf687178e85834fc0c9b1585ba74b4b9c0faa Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Mon, 6 Mar 2023 21:54:54 +0100 Subject: [PATCH 046/436] Sync gigasecond docs with problem-specifications (#1632) The gigasecond exercise has been overhauled as part of a project to make practice exercises more consistent and friendly. For more context, please see the discussion in the forum, as well as the pull request that updated the exercise in the problem-specifications repository: - https://forum.exercism.org/t/new-project-making-practice-exercises-more-consistent-and-human-across-exercism/3943 - https://github.com/exercism/problem-specifications/pull/2206 --- .../practice/gigasecond/.docs/instructions.md | 8 ++++--- .../practice/gigasecond/.docs/introduction.md | 24 +++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 exercises/practice/gigasecond/.docs/introduction.md diff --git a/exercises/practice/gigasecond/.docs/instructions.md b/exercises/practice/gigasecond/.docs/instructions.md index 680870f3a..1e20f0022 100644 --- a/exercises/practice/gigasecond/.docs/instructions.md +++ b/exercises/practice/gigasecond/.docs/instructions.md @@ -1,6 +1,8 @@ # Instructions -Given a moment, determine the moment that would be after a gigasecond -has passed. +Your task is to determine the date and time one gigasecond after a certain date. -A gigasecond is 10^9 (1,000,000,000) seconds. +A gigasecond is one thousand million seconds. +That is a one with nine zeros after it. + +If you were born on _January 24th, 2015 at 22:00 (10:00:00pm)_, then you would be a gigasecond old on _October 2nd, 2046 at 23:46:40 (11:46:40pm)_. diff --git a/exercises/practice/gigasecond/.docs/introduction.md b/exercises/practice/gigasecond/.docs/introduction.md new file mode 100644 index 000000000..74afaa994 --- /dev/null +++ b/exercises/practice/gigasecond/.docs/introduction.md @@ -0,0 +1,24 @@ +# Introduction + +The way we measure time is kind of messy. +We have 60 seconds in a minute, and 60 minutes in an hour. +This comes from ancient Babylon, where they used 60 as the basis for their number system. +We have 24 hours in a day, 7 days in a week, and how many days in a month? +Well, for days in a month it depends not only on which month it is, but also on what type of calendar is used in the country you live in. + +What if, instead, we only use seconds to express time intervals? +Then we can use metric system prefixes for writing large numbers of seconds in more easily comprehensible quantities. + +- A food recipe might explain that you need to let the brownies cook in the oven for two kiloseconds (that's two thousand seconds). +- Perhaps you and your family would travel to somewhere exotic for two megaseconds (that's two million seconds). +- And if you and your spouse were married for _a thousand million_ seconds, you would celebrate your one gigasecond anniversary. + +```exercism/note +If we ever colonize Mars or some other planet, measuring time is going to get even messier. +If someone says "year" do they mean a year on Earth or a year on Mars? + +The idea for this exercise came from the science fiction novel ["A Deepness in the Sky"][vinge-novel] by author Vernor Vinge. +In it the author uses the metric system as the basis for time measurements. + +[vinge-novel]: https://www.tor.com/2017/08/03/science-fiction-with-something-for-everyone-a-deepness-in-the-sky-by-vernor-vinge/ +``` From 0a56964fc9e2e04912dd1d625be44466bd572175 Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Mon, 6 Mar 2023 21:58:13 +0100 Subject: [PATCH 047/436] Sync pangram docs with problem-specifications (#1638) The pangram exercise has been overhauled as part of a project to make practice exercises more consistent and friendly. For more context, please see the discussion in the forum, as well as the pull request that updated the exercise in the problem-specifications repository: - https://forum.exercism.org/t/new-project-making-practice-exercises-more-consistent-and-human-across-exercism/3943 - https://github.com/exercism/problem-specifications/pull/2215 --- exercises/practice/pangram/.docs/instructions.md | 11 +++++------ exercises/practice/pangram/.docs/introduction.md | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 6 deletions(-) create mode 100644 exercises/practice/pangram/.docs/introduction.md diff --git a/exercises/practice/pangram/.docs/instructions.md b/exercises/practice/pangram/.docs/instructions.md index 5c3bbde35..d5698bc2a 100644 --- a/exercises/practice/pangram/.docs/instructions.md +++ b/exercises/practice/pangram/.docs/instructions.md @@ -1,9 +1,8 @@ # Instructions -Determine if a sentence is a pangram. A pangram (Greek: παν γράμμα, pan gramma, -"every letter") is a sentence using every letter of the alphabet at least once. -The best known English pangram is: -> The quick brown fox jumps over the lazy dog. +Your task is to figure out if a sentence is a pangram. -The alphabet used consists of ASCII letters `a` to `z`, inclusive, and is case -insensitive. Any characters which are not an ASCII letter should be ignored. +A pangram is a sentence using every letter of the alphabet at least once. +It is case insensitive, so it doesn't matter if a letter is lower-case (e.g. `k`) or upper-case (e.g. `K`). + +For this exercise we only use the basic letters used in the English alphabet: `a` to `z`. diff --git a/exercises/practice/pangram/.docs/introduction.md b/exercises/practice/pangram/.docs/introduction.md new file mode 100644 index 000000000..d38fa341d --- /dev/null +++ b/exercises/practice/pangram/.docs/introduction.md @@ -0,0 +1,16 @@ +# Introduction + +You work for a company that sells fonts through their website. +They'd like to show a different sentence each time someone views a font on their website. +To give a comprehensive sense of the font, the random sentences should use **all** the letters in the English alphabet. + +They're running a competition to get suggestions for sentences that they can use. +You're in charge of checking the submissions to see if they are valid. + +```exercism/note +Pangram comes from Greek, παν γράμμα, pan gramma, which means "every letter". + +The best known English pangram is: + +> The quick brown fox jumps over the lazy dog. +``` From b57a1f20e50b9a798a8b437d98652b32fa6d2f26 Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Tue, 7 Mar 2023 11:06:54 +0100 Subject: [PATCH 048/436] Sync sieve docs with problem-specifications (#1640) The sieve exercise has been overhauled as part of a project to make practice exercises more consistent and friendly. For more context, please see the discussion in the forum, as well as the pull request that updated the exercise in the problem-specifications repository: - https://forum.exercism.org/t/new-project-making-practice-exercises-more-consistent-and-human-across-exercism/3943 - https://github.com/exercism/problem-specifications/pull/2216 --- .../practice/sieve/.docs/instructions.md | 38 +++++++++---------- .../practice/sieve/.docs/introduction.md | 7 ++++ 2 files changed, 25 insertions(+), 20 deletions(-) create mode 100644 exercises/practice/sieve/.docs/introduction.md diff --git a/exercises/practice/sieve/.docs/instructions.md b/exercises/practice/sieve/.docs/instructions.md index 7228737a2..ec14620ce 100644 --- a/exercises/practice/sieve/.docs/instructions.md +++ b/exercises/practice/sieve/.docs/instructions.md @@ -1,30 +1,28 @@ # Instructions -Use the Sieve of Eratosthenes to find all the primes from 2 up to a given -number. +Your task is to create a program that implements the Sieve of Eratosthenes algorithm to find prime numbers. -The Sieve of Eratosthenes is a simple, ancient algorithm for finding all -prime numbers up to any given limit. It does so by iteratively marking as -composite (i.e. not prime) the multiples of each prime, starting with the -multiples of 2. It does not use any division or remainder operation. +A prime number is a number that is only divisible by 1 and itself. +For example, 2, 3, 5, 7, 11, and 13 are prime numbers. -Create your range, starting at two and continuing up to and including the given limit. (i.e. [2, limit]) +The Sieve of Eratosthenes is an ancient algorithm that works by taking a list of numbers and crossing out all the numbers that aren't prime. -The algorithm consists of repeating the following over and over: +A number that is **not** prime is called a "composite number". -- take the next available unmarked number in your list (it is prime) -- mark all the multiples of that number (they are not prime) +To use the Sieve of Eratosthenes, you first create a list of all the numbers between 2 and your given number. +Then you repeat the following steps: -Repeat until you have processed each number in your range. +1. Find the next unmarked number in your list. This is a prime number. +2. Mark all the multiples of that prime number as composite (not prime). -When the algorithm terminates, all the numbers in the list that have not -been marked are prime. +You keep repeating these steps until you've gone through every number in your list. +At the end, all the unmarked numbers are prime. -The wikipedia article has a useful graphic that explains the algorithm: -https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes +```exercism/note +[Wikipedia's Sieve of Eratosthenes article][eratosthenes] has a useful graphic that explains the algorithm. -Notice that this is a very specific algorithm, and the tests don't check -that you've implemented the algorithm, only that you've come up with the -correct list of primes. A good first test is to check that you do not use -division or remainder operations (div, /, mod or % depending on the -language). +The tests don't check that you've implemented the algorithm, only that you've come up with the correct list of primes. +A good first test is to check that you do not use division or remainder operations. + +[eratosthenes]: https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes +``` diff --git a/exercises/practice/sieve/.docs/introduction.md b/exercises/practice/sieve/.docs/introduction.md new file mode 100644 index 000000000..f6c1cf79a --- /dev/null +++ b/exercises/practice/sieve/.docs/introduction.md @@ -0,0 +1,7 @@ +# Introduction + +You bought a big box of random computer parts at a garage sale. +You've started putting the parts together to build custom computers. + +You want to test the performance of different combinations of parts, and decide to create your own benchmarking program to see how your computers compare. +You choose the famous "Sieve of Eratosthenes" algorithm, an ancient algorithm, but one that should push your computers to the limits. From d960c7e7976854d13108c713349241c14b7812bf Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Tue, 7 Mar 2023 11:08:23 +0100 Subject: [PATCH 049/436] Sync binary-search docs with problem-specifications (#1639) The binary-search exercise has been overhauled as part of a project to make practice exercises more consistent and friendly. For more context, please see the discussion in the forum, as well as the pull request that updated the exercise in the problem-specifications repository: - https://forum.exercism.org/t/new-project-making-practice-exercises-more-consistent-and-human-across-exercism/3943 - https://github.com/exercism/problem-specifications/pull/2220 --- .../binary-search/.docs/instructions.md | 45 ++++++++----------- .../binary-search/.docs/introduction.md | 9 ++++ 2 files changed, 28 insertions(+), 26 deletions(-) create mode 100644 exercises/practice/binary-search/.docs/introduction.md diff --git a/exercises/practice/binary-search/.docs/instructions.md b/exercises/practice/binary-search/.docs/instructions.md index 4dcaba726..175c4c4ba 100644 --- a/exercises/practice/binary-search/.docs/instructions.md +++ b/exercises/practice/binary-search/.docs/instructions.md @@ -1,35 +1,28 @@ # Instructions -Implement a binary search algorithm. +Your task is to implement a binary search algorithm. -Searching a sorted collection is a common task. A dictionary is a sorted -list of word definitions. Given a word, one can find its definition. A -telephone book is a sorted list of people's names, addresses, and -telephone numbers. Knowing someone's name allows one to quickly find -their telephone number and address. +A binary search algorithm finds an item in a list by repeatedly splitting it in half, only keeping the half which contains the item we're looking for. +It allows us to quickly narrow down the possible locations of our item until we find it, or until we've eliminated all possible locations. -If the list to be searched contains more than a few items (a dozen, say) -a binary search will require far fewer comparisons than a linear search, -but it imposes the requirement that the list be sorted. +```exercism/caution +Binary search only works when a list has been sorted. +``` -In computer science, a binary search or half-interval search algorithm -finds the position of a specified input value (the search "key") within -an array sorted by key value. +The algorithm looks like this: -In each step, the algorithm compares the search key value with the key -value of the middle element of the array. +- Divide the sorted list in half and compare the middle element with the item we're looking for. +- If the middle element is our item, then we're done. +- If the middle element is greater than our item, we can eliminate that number and all the numbers **after** it. +- If the middle element is less than our item, we can eliminate that number and all the numbers **before** it. +- Repeat the process on the part of the list that we kept. -If the keys match, then a matching element has been found and its index, -or position, is returned. +Here's an example: -Otherwise, if the search key is less than the middle element's key, then -the algorithm repeats its action on the sub-array to the left of the -middle element or, if the search key is greater, on the sub-array to the -right. +Let's say we're looking for the number 23 in the following sorted list: `[4, 8, 12, 16, 23, 28, 32]`. -If the remaining array to be searched is empty, then the key cannot be -found in the array and a special "not found" indication is returned. - -A binary search halves the number of items to check with each iteration, -so locating an item (or determining its absence) takes logarithmic time. -A binary search is a dichotomic divide and conquer search algorithm. +- We start by comparing 23 with the middle element, 16. +- Since 23 is greater than 16, we can eliminate the left half of the list, leaving us with `[23, 28, 32]`. +- We then compare 23 with the new middle element, 28. +- Since 23 is less than 28, we can eliminate the right half of the list: `[23]`. +- We've found our item. diff --git a/exercises/practice/binary-search/.docs/introduction.md b/exercises/practice/binary-search/.docs/introduction.md new file mode 100644 index 000000000..66c4b8a45 --- /dev/null +++ b/exercises/practice/binary-search/.docs/introduction.md @@ -0,0 +1,9 @@ +# Introduction + +You have stumbled upon a group of mathematicians who are also singer-songwriters. +They have written a song for each of their favorite numbers, and, as you can imagine, they have a lot of favorite numbers. + +You are curious to hear the song for your favorite number, but with so many songs to wade through, finding the right song could take a while. +Fortunately, they have organized their songs in a playlist sorted by the title — which is simply the number that the song is about. + +You realize that you can use a binary search algorithm to quickly find a song given the title. From f8848f2f7e447455a8ccdf3304a81b487bfbfb01 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Tue, 7 Mar 2023 14:03:30 +0100 Subject: [PATCH 050/436] Remove `util/exercise` --- util/exercise/Cargo.lock | 2078 ----------------- util/exercise/Cargo.toml | 34 - util/exercise/src/cmd/configure.rs | 332 --- util/exercise/src/cmd/defaults/README.md | 8 - util/exercise/src/cmd/defaults/description.md | 6 - util/exercise/src/cmd/defaults/example.rs | 10 - util/exercise/src/cmd/defaults/gitignore | 8 - util/exercise/src/cmd/defaults/metadata.yml | 4 - util/exercise/src/cmd/fetch_configlet.rs | 54 - util/exercise/src/cmd/generate.rs | 198 -- util/exercise/src/cmd/mod.rs | 4 - util/exercise/src/cmd/templates/macros.rs | 46 - .../exercise/src/cmd/templates/property_fn.rs | 18 - util/exercise/src/cmd/templates/test_file.rs | 49 - util/exercise/src/cmd/templates/test_fn.rs | 19 - util/exercise/src/cmd/update.rs | 134 -- util/exercise/src/errors.rs | 76 - util/exercise/src/lib.rs | 282 --- util/exercise/src/main.rs | 119 - util/exercise/src/structs.rs | 84 - 20 files changed, 3563 deletions(-) delete mode 100644 util/exercise/Cargo.lock delete mode 100644 util/exercise/Cargo.toml delete mode 100644 util/exercise/src/cmd/configure.rs delete mode 100644 util/exercise/src/cmd/defaults/README.md delete mode 100644 util/exercise/src/cmd/defaults/description.md delete mode 100644 util/exercise/src/cmd/defaults/example.rs delete mode 100644 util/exercise/src/cmd/defaults/gitignore delete mode 100644 util/exercise/src/cmd/defaults/metadata.yml delete mode 100644 util/exercise/src/cmd/fetch_configlet.rs delete mode 100644 util/exercise/src/cmd/generate.rs delete mode 100644 util/exercise/src/cmd/mod.rs delete mode 100644 util/exercise/src/cmd/templates/macros.rs delete mode 100644 util/exercise/src/cmd/templates/property_fn.rs delete mode 100644 util/exercise/src/cmd/templates/test_file.rs delete mode 100644 util/exercise/src/cmd/templates/test_fn.rs delete mode 100644 util/exercise/src/cmd/update.rs delete mode 100644 util/exercise/src/errors.rs delete mode 100644 util/exercise/src/lib.rs delete mode 100644 util/exercise/src/main.rs delete mode 100644 util/exercise/src/structs.rs diff --git a/util/exercise/Cargo.lock b/util/exercise/Cargo.lock deleted file mode 100644 index c6e419cc8..000000000 --- a/util/exercise/Cargo.lock +++ /dev/null @@ -1,2078 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "adler32" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2" - -[[package]] -name = "aho-corasick" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d" -dependencies = [ - "memchr", -] - -[[package]] -name = "ansi_term" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" -dependencies = [ - "winapi 0.3.8", -] - -[[package]] -name = "atty" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" -dependencies = [ - "libc", - "winapi 0.3.8", -] - -[[package]] -name = "autocfg" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b671c8fb71b457dd4ae18c4ba1e59aa81793daacc361d82fcd410cef0d491875" - -[[package]] -name = "autocfg" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" - -[[package]] -name = "backtrace" -version = "0.3.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "924c76597f0d9ca25d762c25a4d369d51267536465dc5064bdf0eb073ed477ea" -dependencies = [ - "backtrace-sys", - "cfg-if 0.1.10", - "libc", - "rustc-demangle", -] - -[[package]] -name = "backtrace-sys" -version = "0.1.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "base64" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" -dependencies = [ - "byteorder", -] - -[[package]] -name = "bitflags" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" - -[[package]] -name = "block-buffer" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" -dependencies = [ - "block-padding", - "byte-tools", - "byteorder", - "generic-array", -] - -[[package]] -name = "block-padding" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" -dependencies = [ - "byte-tools", -] - -[[package]] -name = "bstr" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ede750122d9d1f87919570cb2cccee38c84fbc8c5599b25c289af40625b7030" -dependencies = [ - "memchr", -] - -[[package]] -name = "bumpalo" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad807f2fc2bf185eeb98ff3a901bd46dc5ad58163d0fa4577ba0d25674d71708" - -[[package]] -name = "byte-tools" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" - -[[package]] -name = "byteorder" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" - -[[package]] -name = "bytes" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" -dependencies = [ - "byteorder", - "either", - "iovec", -] - -[[package]] -name = "c2-chacha" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb" -dependencies = [ - "ppv-lite86", -] - -[[package]] -name = "cc" -version = "1.0.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0213d356d3c4ea2c18c40b037c3be23cd639825c18f25ee670ac7813beeef99c" - -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "chrono" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31850b4a4d6bae316f7a09e691c944c28299298837edc0a03f755618c23cbc01" -dependencies = [ - "num-integer", - "num-traits", - "time", -] - -[[package]] -name = "chrono-tz" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0e430fad0384e4defc3dc6b1223d1b886087a8bf9b7080e5ae027f73851ea15" -dependencies = [ - "chrono", - "parse-zoneinfo", -] - -[[package]] -name = "clap" -version = "2.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" -dependencies = [ - "ansi_term", - "atty", - "bitflags", - "strsim", - "textwrap", - "unicode-width", - "vec_map", -] - -[[package]] -name = "cloudabi" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" -dependencies = [ - "bitflags", -] - -[[package]] -name = "cookie" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "888604f00b3db336d2af898ec3c1d5d0ddf5e6d462220f2ededc33a87ac4bbd5" -dependencies = [ - "time", - "url 1.7.2", -] - -[[package]] -name = "cookie_store" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46750b3f362965f197996c4448e4a0935e791bf7d6631bfce9ee0af3d24c919c" -dependencies = [ - "cookie", - "failure", - "idna 0.1.5", - "log", - "publicsuffix", - "serde", - "serde_json", - "time", - "try_from", - "url 1.7.2", -] - -[[package]] -name = "crc32fast" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" -dependencies = [ - "cfg-if 0.1.10", -] - -[[package]] -name = "crossbeam-channel" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87" -dependencies = [ - "crossbeam-utils 0.7.0", - "maybe-uninit", -] - -[[package]] -name = "crossbeam-deque" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20ff29ded3204c5106278a81a38f4b482636ed4fa1e6cfbeef193291beb29ed" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils 0.7.0", - "maybe-uninit", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" -dependencies = [ - "autocfg 1.0.0", - "cfg-if 0.1.10", - "crossbeam-utils 0.7.0", - "lazy_static", - "maybe-uninit", - "memoffset", - "scopeguard", -] - -[[package]] -name = "crossbeam-queue" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c979cd6cfe72335896575c6b5688da489e420d36a27a0b9eb0c73db574b4a4b" -dependencies = [ - "crossbeam-utils 0.6.6", -] - -[[package]] -name = "crossbeam-utils" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6" -dependencies = [ - "cfg-if 0.1.10", - "lazy_static", -] - -[[package]] -name = "crossbeam-utils" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce446db02cdc3165b94ae73111e570793400d0794e46125cc4056c81cbb039f4" -dependencies = [ - "autocfg 0.1.6", - "cfg-if 0.1.10", - "lazy_static", -] - -[[package]] -name = "ct-logs" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d3686f5fa27dbc1d76c751300376e167c5a43387f44bb451fd1c24776e49113" -dependencies = [ - "sct", -] - -[[package]] -name = "deunicode" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "850878694b7933ca4c9569d30a34b55031b9b139ee1fc7b94a527c4ef960d690" - -[[package]] -name = "digest" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" -dependencies = [ - "generic-array", -] - -[[package]] -name = "dtoa" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea57b42383d091c85abcc2706240b94ab2a8fa1fc81c10ff23c4de06e2a90b5e" - -[[package]] -name = "either" -version = "1.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" - -[[package]] -name = "encoding_rs" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87240518927716f79692c2ed85bfe6e98196d18c6401ec75355760233a7e12e9" -dependencies = [ - "cfg-if 0.1.10", -] - -[[package]] -name = "error-chain" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab49e9dcb602294bc42f9a7dfc9bc6e936fca4418ea300dbfb84fe16de0b7d9" -dependencies = [ - "backtrace", - "version_check", -] - -[[package]] -name = "exercise" -version = "1.6.2" -dependencies = [ - "clap", - "failure", - "failure_derive", - "flate2", - "indexmap", - "lazy_static", - "reqwest", - "serde", - "serde_json", - "tar", - "tera", - "toml", - "uuid", -] - -[[package]] -name = "failure" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8273f13c977665c5db7eb2b99ae520952fe5ac831ae4cd09d80c4c7042b5ed9" -dependencies = [ - "backtrace", - "failure_derive", -] - -[[package]] -name = "failure_derive" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bc225b78e0391e4b8683440bf2e63c2deeeb2ce5189eab46e2b68c6d3725d08" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "fake-simd" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" - -[[package]] -name = "filetime" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0408e2626025178a6a7f7ffc05a25bc47103229f19c113755de7bf63816290c" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "redox_syscall 0.2.13", - "winapi 0.3.8", -] - -[[package]] -name = "flate2" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad3c5233c9a940c8719031b423d7e6c16af66e031cb0420b0896f5245bf181d3" -dependencies = [ - "cfg-if 0.1.10", - "crc32fast", - "libc", - "miniz_oxide", -] - -[[package]] -name = "fnv" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" - -[[package]] -name = "fuchsia-cprng" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" - -[[package]] -name = "fuchsia-zircon" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" -dependencies = [ - "bitflags", - "fuchsia-zircon-sys", -] - -[[package]] -name = "fuchsia-zircon-sys" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" - -[[package]] -name = "futures" -version = "0.1.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b980f2816d6ee8673b6517b52cb0e808a180efc92e5c19d02cdda79066703ef" - -[[package]] -name = "futures-cpupool" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4" -dependencies = [ - "futures", - "num_cpus", -] - -[[package]] -name = "generic-array" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" -dependencies = [ - "typenum", -] - -[[package]] -name = "getrandom" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" -dependencies = [ - "cfg-if 0.1.10", - "libc", - "wasi", -] - -[[package]] -name = "globset" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925aa2cac82d8834e2b2a4415b6f6879757fb5c0928fc445ae76461a12eed8f2" -dependencies = [ - "aho-corasick", - "bstr", - "fnv", - "log", - "regex", -] - -[[package]] -name = "globwalk" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53cbcf0368596897b0a3b8ff2110acf2400e80ffad4ca9238b52ff282a9b267b" -dependencies = [ - "ignore", - "walkdir", -] - -[[package]] -name = "h2" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5b34c246847f938a410a03c5458c7fee2274436675e76d8b903c08efc29c462" -dependencies = [ - "byteorder", - "bytes", - "fnv", - "futures", - "http", - "indexmap", - "log", - "slab", - "string", - "tokio-io", -] - -[[package]] -name = "heck" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "http" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6ccf5ede3a895d8856620237b2f02972c1bbc78d2965ad7fe8838d4a0ed41f0" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6741c859c1b2463a423a1dbce98d418e6c3c3fc720fb0d45528657320920292d" -dependencies = [ - "bytes", - "futures", - "http", - "tokio-buf", -] - -[[package]] -name = "httparse" -version = "1.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" - -[[package]] -name = "humansize" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6cab2627acfc432780848602f3f558f7e9dd427352224b0d9324025796d2a5e" - -[[package]] -name = "hyper" -version = "0.12.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c843caf6296fc1f93444735205af9ed4e109a539005abb2564ae1d6fad34c52" -dependencies = [ - "bytes", - "futures", - "futures-cpupool", - "h2", - "http", - "http-body", - "httparse", - "iovec", - "itoa", - "log", - "net2", - "rustc_version", - "time", - "tokio", - "tokio-buf", - "tokio-executor", - "tokio-io", - "tokio-reactor", - "tokio-tcp", - "tokio-threadpool", - "tokio-timer", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719d85c7df4a7f309a77d145340a063ea929dcb2e025bae46a80345cffec2952" -dependencies = [ - "bytes", - "ct-logs", - "futures", - "hyper", - "rustls", - "tokio-io", - "tokio-rustls", - "webpki", - "webpki-roots", -] - -[[package]] -name = "idna" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "idna" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "ignore" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "522daefc3b69036f80c7d2990b28ff9e0471c683bad05ca258e0a01dd22c5a1e" -dependencies = [ - "crossbeam-channel", - "globset", - "lazy_static", - "log", - "memchr", - "regex", - "same-file", - "thread_local 1.0.1", - "walkdir", - "winapi-util", -] - -[[package]] -name = "indexmap" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712d7b3ea5827fcb9d4fda14bf4da5f136f0db2ae9c8f4bd4e2d1c6fde4e6db2" -dependencies = [ - "autocfg 0.1.6", - "serde", -] - -[[package]] -name = "iovec" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" -dependencies = [ - "libc", -] - -[[package]] -name = "itoa" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" - -[[package]] -name = "js-sys" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc9a97d7cec30128fd8b28a7c1f9df1c001ceb9b441e2b755e24130a6b43c79" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "kernel32-sys" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -dependencies = [ - "winapi 0.2.8", - "winapi-build", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "libc" -version = "0.2.65" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a31a0627fdf1f6a39ec0dd577e101440b7db22672c0901fe00a9a6fbb5c24e8" - -[[package]] -name = "lock_api" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8912e782533a93a167888781b836336a6ca5da6175c05944c86cf28c31104dc" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" -dependencies = [ - "cfg-if 0.1.10", -] - -[[package]] -name = "maplit" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" - -[[package]] -name = "matches" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" - -[[package]] -name = "maybe-uninit" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" - -[[package]] -name = "memchr" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" - -[[package]] -name = "memoffset" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6075db033bbbb7ee5a0bbd3a3186bbae616f57fb001c485c7ff77955f8177f" -dependencies = [ - "rustc_version", -] - -[[package]] -name = "mime" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd1d63acd1b78403cc0c325605908475dd9b9a3acbf65ed8bcab97e27014afcf" - -[[package]] -name = "mime_guess" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a0ed03949aef72dbdf3116a383d7b38b4768e6f960528cd6a6044aa9ed68599" -dependencies = [ - "mime", - "unicase", -] - -[[package]] -name = "miniz_oxide" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "304f66c19be2afa56530fa7c39796192eef38618da8d19df725ad7c6d6b2aaae" -dependencies = [ - "adler32", -] - -[[package]] -name = "mio" -version = "0.6.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83f51996a3ed004ef184e16818edc51fadffe8e7ca68be67f9dee67d84d0ff23" -dependencies = [ - "fuchsia-zircon", - "fuchsia-zircon-sys", - "iovec", - "kernel32-sys", - "libc", - "log", - "miow", - "net2", - "slab", - "winapi 0.2.8", -] - -[[package]] -name = "miow" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" -dependencies = [ - "kernel32-sys", - "net2", - "winapi 0.2.8", - "ws2_32-sys", -] - -[[package]] -name = "net2" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" -dependencies = [ - "cfg-if 0.1.10", - "libc", - "winapi 0.3.8", -] - -[[package]] -name = "nom" -version = "4.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" -dependencies = [ - "memchr", - "version_check", -] - -[[package]] -name = "num-integer" -version = "0.1.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" -dependencies = [ - "autocfg 1.0.0", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" -dependencies = [ - "autocfg 1.0.0", -] - -[[package]] -name = "num_cpus" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273" -dependencies = [ - "libc", -] - -[[package]] -name = "opaque-debug" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" - -[[package]] -name = "parking_lot" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252" -dependencies = [ - "lock_api", - "parking_lot_core", - "rustc_version", -] - -[[package]] -name = "parking_lot_core" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" -dependencies = [ - "cfg-if 0.1.10", - "cloudabi", - "libc", - "redox_syscall 0.1.56", - "rustc_version", - "smallvec", - "winapi 0.3.8", -] - -[[package]] -name = "parse-zoneinfo" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feece9d0113b400182a7d00adcff81ccf29158c49c5abd11e2eed8589bf6ff07" -dependencies = [ - "regex", -] - -[[package]] -name = "percent-encoding" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" - -[[package]] -name = "percent-encoding" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" - -[[package]] -name = "pest" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e4fb201c5c22a55d8b24fef95f78be52738e5e1361129be1b5e862ecdb6894a" -dependencies = [ - "ucd-trie", -] - -[[package]] -name = "pest_derive" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" -dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b9fcf299b5712d06ee128a556c94709aaa04512c4dffb8ead07c5c998447fc0" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "pest_meta" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df43fd99896fd72c485fe47542c7b500e4ac1e8700bf995544d1317a60ded547" -dependencies = [ - "maplit", - "pest", - "sha-1", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" - -[[package]] -name = "proc-macro2" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90cf5f418035b98e655e9cdb225047638296b862b42411c4e45bb88d700f7fc0" -dependencies = [ - "unicode-xid", -] - -[[package]] -name = "publicsuffix" -version = "1.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bf259a81de2b2eb9850ec990ec78e6a25319715584fd7652b9b26f96fcb1510" -dependencies = [ - "error-chain", - "idna 0.2.0", - "lazy_static", - "regex", - "url 2.1.0", -] - -[[package]] -name = "quote" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" -dependencies = [ - "autocfg 0.1.6", - "libc", - "rand_chacha 0.1.1", - "rand_core 0.4.2", - "rand_hc 0.1.0", - "rand_isaac", - "rand_jitter", - "rand_os", - "rand_pcg", - "rand_xorshift", - "winapi 0.3.8", -] - -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom", - "libc", - "rand_chacha 0.2.1", - "rand_core 0.5.1", - "rand_hc 0.2.0", -] - -[[package]] -name = "rand_chacha" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" -dependencies = [ - "autocfg 0.1.6", - "rand_core 0.3.1", -] - -[[package]] -name = "rand_chacha" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" -dependencies = [ - "c2-chacha", - "rand_core 0.5.1", -] - -[[package]] -name = "rand_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -dependencies = [ - "rand_core 0.4.2", -] - -[[package]] -name = "rand_core" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom", -] - -[[package]] -name = "rand_hc" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "rand_isaac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "rand_jitter" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" -dependencies = [ - "libc", - "rand_core 0.4.2", - "winapi 0.3.8", -] - -[[package]] -name = "rand_os" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" -dependencies = [ - "cloudabi", - "fuchsia-cprng", - "libc", - "rand_core 0.4.2", - "rdrand", - "winapi 0.3.8", -] - -[[package]] -name = "rand_pcg" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" -dependencies = [ - "autocfg 0.1.6", - "rand_core 0.4.2", -] - -[[package]] -name = "rand_xorshift" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "rdrand" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "redox_syscall" -version = "0.1.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" - -[[package]] -name = "redox_syscall" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" -dependencies = [ - "bitflags", -] - -[[package]] -name = "regex" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc220bd33bdce8f093101afe22a037b8eb0e5af33592e6a9caafff0d4cb81cbd" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", - "thread_local 0.3.6", -] - -[[package]] -name = "regex-syntax" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716" - -[[package]] -name = "reqwest" -version = "0.9.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c2064233e442ce85c77231ebd67d9eca395207dec2127fe0bbedde4bd29a650" -dependencies = [ - "base64", - "bytes", - "cookie", - "cookie_store", - "encoding_rs", - "flate2", - "futures", - "http", - "hyper", - "hyper-rustls", - "log", - "mime", - "mime_guess", - "rustls", - "serde", - "serde_json", - "serde_urlencoded", - "time", - "tokio", - "tokio-executor", - "tokio-io", - "tokio-rustls", - "tokio-threadpool", - "tokio-timer", - "url 1.7.2", - "uuid", - "webpki-roots", - "winreg", -] - -[[package]] -name = "ring" -version = "0.16.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6747f8da1f2b1fabbee1aaa4eb8a11abf9adef0bf58a41cee45db5d59cecdfac" -dependencies = [ - "cc", - "lazy_static", - "libc", - "spin", - "untrusted", - "web-sys", - "winapi 0.3.8", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" - -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -dependencies = [ - "semver", -] - -[[package]] -name = "rustls" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b25a18b1bf7387f0145e7f8324e700805aade3842dd3db2e74e4cdeb4677c09e" -dependencies = [ - "base64", - "log", - "ring", - "sct", - "webpki", -] - -[[package]] -name = "ryu" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "scopeguard" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d" - -[[package]] -name = "sct" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3042af939fca8c3453b7af0f1c66e533a15a86169e39de2657310ade8f98d3c" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" - -[[package]] -name = "serde" -version = "1.0.102" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b39bd9b0b087684013a792c59e3e07a46a01d2322518d8a1104641a0b1be0" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.101" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b133a43a1ecd55d4086bd5b4dc6c1751c68b1bfbeba7a5040442022c7e7c02e" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f72eb2a68a7dc3f9a691bfda9305a1c017a6215e5a4545c258500d2099a37c2" -dependencies = [ - "indexmap", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "642dd69105886af2efd227f75a520ec9b44a820d65bc133a9131f7d229fd165a" -dependencies = [ - "dtoa", - "itoa", - "serde", - "url 1.7.2", -] - -[[package]] -name = "sha-1" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" -dependencies = [ - "block-buffer", - "digest", - "fake-simd", - "opaque-debug", -] - -[[package]] -name = "slab" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" - -[[package]] -name = "slug" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3bc762e6a4b6c6fcaade73e77f9ebc6991b676f88bb2358bddb56560f073373" -dependencies = [ - "deunicode", -] - -[[package]] -name = "smallvec" -version = "0.6.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0" -dependencies = [ - "maybe-uninit", -] - -[[package]] -name = "sourcefile" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bf77cb82ba8453b42b6ae1d692e4cdc92f9a47beaf89a847c8be83f4e328ad3" - -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - -[[package]] -name = "string" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d24114bfcceb867ca7f71a0d3fe45d45619ec47a6fbfa98cb14e14250bfa5d6d" -dependencies = [ - "bytes", -] - -[[package]] -name = "strsim" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" - -[[package]] -name = "syn" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66850e97125af79138385e9b88339cbcd037e3f28ceab8c5ad98e64f0f1f80bf" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "synstructure" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f085a5855930c0441ca1288cf044ea4aecf4f43a91668abdb870b4ba546a203" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "unicode-xid", -] - -[[package]] -name = "tar" -version = "0.4.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8190d9cdacf6ee1b080605fd719b58d80a9fcbcea64db6744b26f743da02e447" -dependencies = [ - "filetime", - "libc", - "xattr", -] - -[[package]] -name = "tera" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8501ae034d1c2d2e8c29f3c259d919c11489220d8ee8a268e7e1fe3eff94c86a" -dependencies = [ - "chrono", - "chrono-tz", - "globwalk", - "humansize", - "lazy_static", - "percent-encoding 2.1.0", - "pest", - "pest_derive", - "rand 0.7.3", - "regex", - "serde", - "serde_json", - "slug", - "unic-segment", -] - -[[package]] -name = "textwrap" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -dependencies = [ - "unicode-width", -] - -[[package]] -name = "thread_local" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "thread_local" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "time" -version = "0.1.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" -dependencies = [ - "libc", - "redox_syscall 0.1.56", - "winapi 0.3.8", -] - -[[package]] -name = "tokio" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a09c0b5bb588872ab2f09afa13ee6e9dac11e10a0ec9e8e3ba39a5a5d530af6" -dependencies = [ - "bytes", - "futures", - "mio", - "num_cpus", - "tokio-current-thread", - "tokio-executor", - "tokio-io", - "tokio-reactor", - "tokio-tcp", - "tokio-threadpool", - "tokio-timer", -] - -[[package]] -name = "tokio-buf" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb220f46c53859a4b7ec083e41dec9778ff0b1851c0942b211edb89e0ccdc46" -dependencies = [ - "bytes", - "either", - "futures", -] - -[[package]] -name = "tokio-current-thread" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16217cad7f1b840c5a97dfb3c43b0c871fef423a6e8d2118c604e843662a443" -dependencies = [ - "futures", - "tokio-executor", -] - -[[package]] -name = "tokio-executor" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f27ee0e6db01c5f0b2973824547ce7e637b2ed79b891a9677b0de9bd532b6ac" -dependencies = [ - "crossbeam-utils 0.6.6", - "futures", -] - -[[package]] -name = "tokio-io" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5090db468dad16e1a7a54c8c67280c5e4b544f3d3e018f0b913b400261f85926" -dependencies = [ - "bytes", - "futures", - "log", -] - -[[package]] -name = "tokio-reactor" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56391be9805bc80163151c0b9e5164ee64f4b0200962c346fea12773158f22d" -dependencies = [ - "crossbeam-utils 0.6.6", - "futures", - "lazy_static", - "log", - "mio", - "num_cpus", - "parking_lot", - "slab", - "tokio-executor", - "tokio-io", - "tokio-sync", -] - -[[package]] -name = "tokio-rustls" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df2fa53ac211c136832f530ccb081af9af891af22d685a9493e232c7a359bc2" -dependencies = [ - "bytes", - "futures", - "iovec", - "rustls", - "tokio-io", - "webpki", -] - -[[package]] -name = "tokio-sync" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d06554cce1ae4a50f42fba8023918afa931413aded705b560e29600ccf7c6d76" -dependencies = [ - "fnv", - "futures", -] - -[[package]] -name = "tokio-tcp" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d14b10654be682ac43efee27401d792507e30fd8d26389e1da3b185de2e4119" -dependencies = [ - "bytes", - "futures", - "iovec", - "mio", - "tokio-io", - "tokio-reactor", -] - -[[package]] -name = "tokio-threadpool" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bd2c6a3885302581f4401c82af70d792bb9df1700e7437b0aeb4ada94d5388c" -dependencies = [ - "crossbeam-deque", - "crossbeam-queue", - "crossbeam-utils 0.6.6", - "futures", - "lazy_static", - "log", - "num_cpus", - "slab", - "tokio-executor", -] - -[[package]] -name = "tokio-timer" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2106812d500ed25a4f38235b9cae8f78a09edf43203e16e59c3b769a342a60e" -dependencies = [ - "crossbeam-utils 0.6.6", - "futures", - "slab", - "tokio-executor", -] - -[[package]] -name = "toml" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f" -dependencies = [ - "serde", -] - -[[package]] -name = "try-lock" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" - -[[package]] -name = "try_from" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "283d3b89e1368717881a9d51dad843cc435380d8109c9e47d38780a324698d8b" -dependencies = [ - "cfg-if 0.1.10", -] - -[[package]] -name = "typenum" -version = "1.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9" - -[[package]] -name = "ucd-trie" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f00ed7be0c1ff1e24f46c3d2af4859f7e863672ba3a6e92e7cff702bf9f06c2" - -[[package]] -name = "unic-char-property" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" -dependencies = [ - "unic-char-range", -] - -[[package]] -name = "unic-char-range" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" - -[[package]] -name = "unic-common" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" - -[[package]] -name = "unic-segment" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23" -dependencies = [ - "unic-ucd-segment", -] - -[[package]] -name = "unic-ucd-segment" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700" -dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", -] - -[[package]] -name = "unic-ucd-version" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" -dependencies = [ - "unic-common", -] - -[[package]] -name = "unicase" -version = "2.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e2e6bd1e59e56598518beb94fd6db628ded570326f0a98c679a304bd9f00150" -dependencies = [ - "version_check", -] - -[[package]] -name = "unicode-bidi" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" -dependencies = [ - "matches", -] - -[[package]] -name = "unicode-normalization" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "141339a08b982d942be2ca06ff8b076563cbe223d1befd5450716790d44e2426" -dependencies = [ - "smallvec", -] - -[[package]] -name = "unicode-segmentation" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1967f4cdfc355b37fd76d2a954fb2ed3871034eb4f26d60537d88795cfc332a9" - -[[package]] -name = "unicode-width" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7007dbd421b92cc6e28410fe7362e2e0a2503394908f417b68ec8d1c364c4e20" - -[[package]] -name = "unicode-xid" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" - -[[package]] -name = "untrusted" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60369ef7a31de49bcb3f6ca728d4ba7300d9a1658f94c727d4cab8c8d9f4aece" - -[[package]] -name = "url" -version = "1.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" -dependencies = [ - "idna 0.1.5", - "matches", - "percent-encoding 1.0.1", -] - -[[package]] -name = "url" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b414f6c464c879d7f9babf951f23bc3743fb7313c081b2e6ca719067ea9d61" -dependencies = [ - "idna 0.2.0", - "matches", - "percent-encoding 2.1.0", -] - -[[package]] -name = "uuid" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a" -dependencies = [ - "rand 0.6.5", -] - -[[package]] -name = "vec_map" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" - -[[package]] -name = "version_check" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" - -[[package]] -name = "walkdir" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" -dependencies = [ - "same-file", - "winapi 0.3.8", - "winapi-util", -] - -[[package]] -name = "want" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6395efa4784b027708f7451087e647ec73cc74f5d9bc2e418404248d679a230" -dependencies = [ - "futures", - "log", - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - -[[package]] -name = "wasm-bindgen" -version = "0.2.51" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd34c5ba0d228317ce388e87724633c57edca3e7531feb4e25e35aaa07a656af" -dependencies = [ - "cfg-if 0.1.10", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.51" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927196b315c23eed2748442ba675a4c54a1a079d90d9bdc5ad16ce31cf90b15b" -dependencies = [ - "bumpalo", - "lazy_static", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.51" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92c2442bf04d89792816650820c3fb407af8da987a9f10028d5317f5b04c2b4a" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.51" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c075d27b7991c68ca0f77fe628c3513e64f8c477d422b859e03f28751b46fc5" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.51" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83d61fe986a7af038dd8b5ec660e5849cbd9f38e7492b9404cc48b2b4df731d1" - -[[package]] -name = "wasm-bindgen-webidl" -version = "0.2.51" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b979afb0535fe4749906a674082db1211de8aef466331d43232f63accb7c07c" -dependencies = [ - "failure", - "heck", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "weedle", -] - -[[package]] -name = "web-sys" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c84440699cd02ca23bed6f045ffb1497bc18a3c2628bd13e2093186faaaacf6b" -dependencies = [ - "failure", - "js-sys", - "sourcefile", - "wasm-bindgen", - "wasm-bindgen-webidl", -] - -[[package]] -name = "webpki" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7e664e770ac0110e2384769bcc59ed19e329d81f555916a6e072714957b81b4" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "webpki-roots" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a262ae37dd9d60f60dd473d1158f9fbebf110ba7b6a5051c8160460f6043718b" -dependencies = [ - "webpki", -] - -[[package]] -name = "weedle" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bb43f70885151e629e2a19ce9e50bd730fd436cfd4b666894c9ce4de9141164" -dependencies = [ - "nom", -] - -[[package]] -name = "winapi" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" - -[[package]] -name = "winapi" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-build" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ccfbf554c6ad11084fb7517daca16cfdcaccbdadba4fc336f032a8b12c2ad80" -dependencies = [ - "winapi 0.3.8", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "winreg" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" -dependencies = [ - "winapi 0.3.8", -] - -[[package]] -name = "ws2_32-sys" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" -dependencies = [ - "winapi 0.2.8", - "winapi-build", -] - -[[package]] -name = "xattr" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c" -dependencies = [ - "libc", -] diff --git a/util/exercise/Cargo.toml b/util/exercise/Cargo.toml deleted file mode 100644 index 0a7db15a2..000000000 --- a/util/exercise/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -[package] -name = "exercise" -version = "1.6.2" -description = "An utility for creating or updating the exercises on the Exercism Rust track" -edition = "2021" - -[dependencies] -clap = "2.32.0" -failure = "0.1.5" -failure_derive = "0.1.5" -flate2 = "1.0.7" -lazy_static = "1.2.0" -tar = "0.4.36" -tera = "1.0.2" -toml = "0.4.10" -uuid = { version = "0.7", features = ["v4"] } - -[dependencies.indexmap] -version = "1.3.0" -# To implement `Deserialize` on `IndexMap`. -features = ["serde-1"] - -[dependencies.reqwest] -version = "0.9.16" -default-features = false -features = ["rustls-tls"] - -[dependencies.serde] -version = "1.0.102" -features = ["derive"] - -[dependencies.serde_json] -version = "1.0.38" -features = ["preserve_order"] diff --git a/util/exercise/src/cmd/configure.rs b/util/exercise/src/cmd/configure.rs deleted file mode 100644 index a74845def..000000000 --- a/util/exercise/src/cmd/configure.rs +++ /dev/null @@ -1,332 +0,0 @@ -use crate::{self as exercise, errors::Result, get, get_mut, val_as}; -use failure::format_err; -use serde_json::{self, json, Value}; -use std::{ - fs, - io::{stdin, stdout, Write}, - path::Path, -}; -use uuid::Uuid; - -fn get_user_input(prompt: &str) -> Result { - print!("{}", prompt); - - let mut buffer = String::new(); - - stdout().flush()?; - stdin().read_line(&mut buffer)?; - - Ok(buffer.trim().to_string()) -} - -fn get_user_config(exercise_name: &str, config_content: &Value) -> Result { - let existing_config = get!(config_content, "exercises", as_array) - .iter() - .find(|exercise| exercise["slug"] == exercise_name); - - let uuid = if let Some(existing_config) = existing_config { - get!(existing_config, "uuid", as_str).to_string() - } else { - Uuid::new_v4().to_hyphenated().to_string() - }; - - let unlocked_by: Option = loop { - let default_value = if let Some(existing_config) = existing_config { - if let Value::String(s) = get!(existing_config, "unlocked_by") { - s - } else { - "null" - } - } else { - "hello-world" - }; - - let user_input = get_user_input(&format!( - "Exercise slug which unlocks this (blank for '{}'): ", - default_value - ))?; - - if user_input.is_empty() { - break Some(default_value.to_string()); - } else if user_input == "null" { - break None; - } else if !get!(config_content, "exercises", as_array) - .iter() - .any(|exercise| exercise["slug"] == user_input) - { - println!("{} is not an existing exercise slug", user_input); - continue; - } else { - break Some(user_input); - }; - }; - - let core = unlocked_by.is_none(); - - let difficulty = loop { - let unlocked_by_difficulty = match unlocked_by { - Some(ref unlocked_by) => { - let unlocked_by_exercise = get!(config_content, "exercises", as_array) - .iter() - .find(|exercise| exercise["slug"] == unlocked_by.as_str()) - .ok_or_else(|| format_err!("exercise '{}' not found in config", unlocked_by))?; - - get!(unlocked_by_exercise, "difficulty", as_u64) - } - - None => 1, - }; - - let available_difficulties: Vec = [1, 4, 7, 10] - .iter() - .skip_while(|&&difficulty| difficulty < unlocked_by_difficulty) - .cloned() - .collect(); - - let default_value = if let Some(existing_config) = existing_config { - get!(existing_config, "difficulty", as_u64) - } else { - *available_difficulties - .first() - .ok_or_else(|| format_err!("no available difficulties"))? - }; - - let user_input = get_user_input(&format!( - "Difficulty for this exercise {:?} (blank for {}): ", - available_difficulties, default_value - ))?; - - if user_input.is_empty() { - break default_value; - } else if let Ok(difficulty) = user_input.parse::() { - if !available_difficulties.contains(&difficulty) { - println!( - "Difficulty should be {:?}, not '{}'.", - available_difficulties, difficulty - ); - - continue; - } - - break difficulty; - } else { - println!("Difficulty should be a number, not '{}'.", user_input); - - continue; - } - }; - - let topics = loop { - let default_value = if let Some(existing_config) = existing_config { - use serde_json::Value::*; - use std::string; - match get!(existing_config, "topics") { - String(s) => vec![s.to_string()], - Array(topics_values) => { - let mut topics: Vec = Vec::with_capacity(topics_values.len()); - for topic in topics_values.iter() { - topics.push(val_as!(topic, as_str).to_string()) - } - topics - } - _ => { - return Err(exercise::errors::Error::SchemaTypeError { - file: "config.json".to_string(), - field: "topics".to_string(), - as_type: "array or string".to_string(), - }); - } - } - } else { - vec![exercise_name.to_string()] - }; - - let user_input = get_user_input(&format!( - "List of topics for this exercise, comma-separated (blank for {:?}): ", - default_value, - ))?; - - if user_input.is_empty() { - break default_value; - } - - let topics = user_input - .split(',') - .map(|topic| topic.trim().to_string()) - .filter(|topic| !topic.is_empty()) - .collect::>(); - - if topics.is_empty() { - println!("Must enter at least one topic"); - - continue; - } - - break topics; - }; - - Ok(json!({ - "slug": exercise_name, - "uuid": uuid, - "core": core, - "unlocked_by": unlocked_by, - "difficulty": difficulty, - "topics": topics - })) -} - -fn choose_exercise_insert_index( - exercise_name: &str, - exercises: &[Value], - difficulty: &Value, -) -> Result { - loop { - let mut exercises_with_similar_difficulty = Vec::with_capacity(exercises.len()); - for (index, exercise) in exercises - .iter() - .enumerate() - .filter(|(_, exercise)| exercise["difficulty"] == *difficulty) - { - exercises_with_similar_difficulty.push((index, get!(exercise, "slug", as_str))); - } - - let mut start_index = 0; - - let mut end_index = exercises_with_similar_difficulty.len() - 1; - - let insert_index = loop { - if start_index == end_index { - break start_index; - } - - let middle_index = start_index + ((end_index - start_index) / 2); - - let user_input = get_user_input(&format!( - "Is {} easier then {}? (y/N): ", - exercise_name, exercises_with_similar_difficulty[middle_index].1 - ))?; - - if user_input.to_lowercase().starts_with('y') { - end_index = middle_index; - } else { - start_index = middle_index + 1; - } - }; - - let insert_index = exercises_with_similar_difficulty[insert_index].0; - - let prompt = if insert_index == 0 { - format!( - "{} is the easiest exercise of difficulty {}.", - exercise_name, *difficulty - ) - } else if insert_index == exercises.len() - 1 { - format!( - "{} is the hardest exercise of difficulty {}.", - exercise_name, *difficulty - ) - } else { - format!( - "{} is placed between {} and {} exercises in difficulty.", - exercise_name, - get!(exercises[insert_index - 1], "slug", as_str), - get!(exercises[insert_index], "slug", as_str), - ) - }; - - let user_input = get_user_input(&format!( - "You have configured that {}.\nIs this correct? (y/N): ", - prompt - ))?; - - if user_input.to_lowercase().starts_with('y') { - break Ok(insert_index); - } - } -} - -fn insert_user_config( - exercise_name: &str, - config_content: &mut Value, - user_config: Value, -) -> Result<()> { - let exercises = get_mut!(config_content, "exercises", as_array_mut); - - let insert_index = - choose_exercise_insert_index(exercise_name, exercises, &user_config["difficulty"])?; - - exercises.insert(insert_index, user_config); - - Ok(()) -} - -fn update_existing_config( - exercise_name: &str, - config_content: &mut Value, - user_config: Value, -) -> Result<()> { - let exercises = get_mut!(config_content, "exercises", as_array_mut); - - let existing_exercise_index = exercises - .iter() - .position(|exercise| exercise["slug"] == exercise_name) - .ok_or_else(|| format_err!("exercise '{}' not found in config.json", exercise_name))?; - - let insert_index = - if exercises[existing_exercise_index]["difficulty"] == user_config["difficulty"] { - existing_exercise_index - } else { - choose_exercise_insert_index(exercise_name, &exercises, &user_config["difficulty"])? - }; - - exercises.remove(existing_exercise_index); - - exercises.insert(insert_index, user_config); - - Ok(()) -} - -pub fn configure_exercise(exercise_name: &str) -> Result<()> { - println!( - "Configuring config.json for the {} exercise.", - exercise_name - ); - - let config_path = Path::new(&*exercise::TRACK_ROOT).join("config.json"); - - let config_content_string = fs::read_to_string(&config_path)?; - - let mut config_content: Value = serde_json::from_str(&config_content_string)?; - - let config_exists = get!(config_content, "exercises", as_array) - .iter() - .any(|exercise| exercise["slug"] == exercise_name); - - let user_config: Value = loop { - let user_config = get_user_config(exercise_name, &config_content)?; - - let user_input = get_user_input(&format!( - "You have configured the {} exercise as follows:\n{}\nIs this correct? (y/N):", - exercise_name, - serde_json::to_string_pretty(&user_config)? - ))?; - - if user_input.to_lowercase().starts_with('y') { - break user_config; - } - }; - - if config_exists { - update_existing_config(exercise_name, &mut config_content, user_config)?; - } else { - insert_user_config(exercise_name, &mut config_content, user_config)?; - } - - fs::write(&config_path, serde_json::to_string_pretty(&config_content)?)?; - - println!("Formatting the config.json file via 'bin/configlet fmt'"); - - exercise::run_configlet_command("fmt", &["."])?; - - Ok(()) -} diff --git a/util/exercise/src/cmd/defaults/README.md b/util/exercise/src/cmd/defaults/README.md deleted file mode 100644 index 2ff7d26cb..000000000 --- a/util/exercise/src/cmd/defaults/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Defaults - -The files in this directory are used in appropriate places for defaults. They -are included at build-time in the compiled executable: if you change one, you -will need to recompile to see those changes reflected in the output. - -Using external files makes it easier to ensure that formatting etc is correct, -without needing to worry about proper escaping within a Rust source file. diff --git a/util/exercise/src/cmd/defaults/description.md b/util/exercise/src/cmd/defaults/description.md deleted file mode 100644 index 6986282dc..000000000 --- a/util/exercise/src/cmd/defaults/description.md +++ /dev/null @@ -1,6 +0,0 @@ -# Description - -Describe your exercise here. - -Don't forget that `README.md` is automatically generated; update this within -`.meta/description.md`. diff --git a/util/exercise/src/cmd/defaults/example.rs b/util/exercise/src/cmd/defaults/example.rs deleted file mode 100644 index 768f0813a..000000000 --- a/util/exercise/src/cmd/defaults/example.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! Example implementation -//! -//! - Implement the solution to your exercise here. -//! - Put the stubs for any tested functions in `src/lib.rs`, -//! whose variable names are `_` and -//! whose contents are `unimplemented!()`. -//! - If your example implementation has dependencies, copy -//! `Cargo.toml` into `Cargo-example.toml` and then make -//! any modifications necessary to the latter so your example will run. -//! - Test your example by running `../../bin/test-exercise` diff --git a/util/exercise/src/cmd/defaults/gitignore b/util/exercise/src/cmd/defaults/gitignore deleted file mode 100644 index e130ceb2d..000000000 --- a/util/exercise/src/cmd/defaults/gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Generated by exercism rust track exercise tool -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock diff --git a/util/exercise/src/cmd/defaults/metadata.yml b/util/exercise/src/cmd/defaults/metadata.yml deleted file mode 100644 index c44a80f49..000000000 --- a/util/exercise/src/cmd/defaults/metadata.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -blurb: "" -source: "" -source_url: "" diff --git a/util/exercise/src/cmd/fetch_configlet.rs b/util/exercise/src/cmd/fetch_configlet.rs deleted file mode 100644 index 8351c9232..000000000 --- a/util/exercise/src/cmd/fetch_configlet.rs +++ /dev/null @@ -1,54 +0,0 @@ -use crate::errors::Result; -use failure::format_err; -use flate2::read::GzDecoder; -use serde_json::Value; -use std::{ - path::{Path, PathBuf}, - string::ToString, -}; -use tar::Archive; - -// Returns the "os-arch" string, according to the host machine parameters -fn get_os_arch() -> String { - let os = if cfg!(target_os = "windows") { - "windows" - } else if cfg!(target_os = "macos") { - "mac" - } else { - "linux" - }; - let arch = if cfg!(target_pointer_width = "32") { - "32bit" - } else { - "64bit" - }; - format!("{}-{}", os, arch) -} - -// Makes a request to the Github API to get and return the download url for the latest configlet release -fn get_download_url() -> Result { - reqwest::get("https://api.github.com/repos/exercism/configlet/releases/latest")? - .json::()? - .get("assets") - .and_then(Value::as_array) - .ok_or_else(|| format_err!("failed to get the 'assets' field from the Github response"))? - .iter() - .filter_map(|asset| asset.get("browser_download_url").and_then(Value::as_str)) - .find(|url| url.contains(&get_os_arch())) - .map(ToString::to_string) - .ok_or_else(|| format_err!("failed to get the configlet release url")) - .map_err(|e| e.into()) -} - -// download and extract configlet into the repo's /bin folder -// -// returns the path into which the bin was extracted on success -pub fn download() -> Result { - let response = reqwest::get(&get_download_url()?)?; - let mut archive = Archive::new(GzDecoder::new(response)); - let download_path = Path::new(&*crate::TRACK_ROOT).join("bin"); - archive - .unpack(&download_path) - .map(|_| download_path) - .map_err(|e| e.into()) -} diff --git a/util/exercise/src/cmd/generate.rs b/util/exercise/src/cmd/generate.rs deleted file mode 100644 index 8810b05ab..000000000 --- a/util/exercise/src/cmd/generate.rs +++ /dev/null @@ -1,198 +0,0 @@ -/// This module contains source for the `generate` command. -use crate::{self as exercise, errors::Result, structs::CanonicalData, TEMPLATES}; -use failure::format_err; -use std::{ - fs::{self, File, OpenOptions}, - io::Write, - path::Path, - process::{Command, Stdio}, -}; -use tera::Context; - -const GITIGNORE_CONTENT: &str = include_str!("defaults/gitignore"); -const EXAMPLE_RS_CONTENT: &str = include_str!("defaults/example.rs"); -const DESCRIPTION_MD_CONTENT: &str = include_str!("defaults/description.md"); -const METADATA_YML_CONTENT: &str = include_str!("defaults/metadata.yml"); - -// Generate .meta directory and its contents without using the canonical data -fn generate_meta(exercise_name: &str, exercise_path: &Path) -> Result<()> { - let meta_dir = exercise_path.join(".meta"); - fs::create_dir(&meta_dir)?; - - for (file, content) in [ - ("description.md", DESCRIPTION_MD_CONTENT), - ("metadata.yml", METADATA_YML_CONTENT), - ] - .iter() - { - if !exercise::canonical_file_exists(exercise_name, file)? { - fs::write(exercise_path.join(".meta").join(file), content)?; - } - } - - if fs::read_dir(&meta_dir)?.count() == 0 { - fs::remove_dir(meta_dir)?; - } - - Ok(()) -} - -// Generate test suite using the canonical data -fn generate_tests_from_canonical_data( - exercise_name: &str, - tests_path: &Path, - canonical_data: &CanonicalData, - use_maplit: bool, -) -> Result<()> { - exercise::update_cargo_toml_version(exercise_name, canonical_data)?; - - let mut context = Context::from_serialize(canonical_data)?; - context.insert("use_maplit", &use_maplit); - context.insert("properties", &canonical_data.properties()); - - let test_file = TEMPLATES.render("test_file.rs", &context)?; - fs::write(&tests_path, test_file)?; - - exercise::rustfmt(&tests_path)?; - - Ok(()) -} - -// Run bin/configlet generate command to generate README for the exercise -fn generate_readme(exercise_name: &str) -> Result<()> { - println!( - "Generating README for {} via 'bin/configlet generate'", - exercise_name - ); - - let problem_specifications_path = Path::new(&*exercise::TRACK_ROOT) - .join("..") - .join("problem-specifications"); - - if !problem_specifications_path.exists() { - let problem_specifications_url = "https://github.com/exercism/problem-specifications.git"; - println!( - "problem-specifications repository not found. Cloning the repository from {}", - problem_specifications_url - ); - - Command::new("git") - .current_dir(&*exercise::TRACK_ROOT) - .stdout(Stdio::inherit()) - .arg("clone") - .arg(problem_specifications_url) - .arg(&problem_specifications_path) - .output()?; - } - - exercise::run_configlet_command( - "generate", - &[ - ".", - "--only", - exercise_name, - "--spec-path", - problem_specifications_path - .to_str() - .ok_or_else(|| format_err!("path inexpressable as str"))?, - ], - )?; - - Ok(()) -} - -// Generate a new exercise with specified name and flags -pub fn generate_exercise(exercise_name: &str, use_maplit: bool) -> Result<()> { - if exercise::exercise_exists(exercise_name) { - return Err(format_err!("exercise with the name {} already exists", exercise_name,).into()); - } - - let exercise_path = Path::new(&*exercise::TRACK_ROOT) - .join("exercises") - .join(exercise_name); - - println!( - "Generating a new exercise at path: {}", - exercise_path - .to_str() - .ok_or_else(|| format_err!("path inexpressable as str"))? - ); - - let _cargo_new_output = Command::new("cargo") - .arg("new") - .arg("--lib") - .arg( - exercise_path - .to_str() - .ok_or_else(|| format_err!("path inexpressable as str"))?, - ) - .output()?; - - fs::write(exercise_path.join(".gitignore"), GITIGNORE_CONTENT)?; - - if use_maplit { - let mut cargo_toml_file = OpenOptions::new() - .append(true) - .open(exercise_path.join("Cargo.toml"))?; - - cargo_toml_file.write_all(b"maplit = \"1.0.1\"")?; - } - - fs::create_dir(exercise_path.join("tests"))?; - - let test_file_name = exercise_path - .join("tests") - .join(format!("{}.rs", exercise_name)); - - fs::write(exercise_path.join("example.rs"), EXAMPLE_RS_CONTENT)?; - - match exercise::get_canonical_data(exercise_name) { - Ok(canonical_data) => { - println!("Generating tests from canonical data"); - - generate_tests_from_canonical_data( - &exercise_name, - &test_file_name, - &canonical_data, - use_maplit, - )?; - } - Err(_) => { - println!( - "No canonical data for exercise '{}' found. Generating standard exercise template.", - &exercise_name - ); - - let mut test_file = File::create(test_file_name)?; - - test_file.write_all( - &format!( - "//! Tests for {exercise_name} \n\ - //! \n\ - //! Generated by [utility][utility]\n\ - //! \n\ - //! [utility]: https://github.com/exercism/rust/tree/main/util/exercise\n\ - \n", - exercise_name = exercise_name, - ) - .into_bytes(), - )?; - if use_maplit { - test_file.write_all(b"use maplit::hashmap;\n")?; - } - - test_file.write_all( - &format!( - "use {escaped_exercise_name}::*;\n\n\n", - escaped_exercise_name = exercise_name.replace("-", "_"), - ) - .into_bytes(), - )?; - } - } - - generate_meta(&exercise_name, &exercise_path)?; - generate_readme(&exercise_name)?; - - Ok(()) -} diff --git a/util/exercise/src/cmd/mod.rs b/util/exercise/src/cmd/mod.rs deleted file mode 100644 index 84607911c..000000000 --- a/util/exercise/src/cmd/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod configure; -pub mod fetch_configlet; -pub mod generate; -pub mod update; diff --git a/util/exercise/src/cmd/templates/macros.rs b/util/exercise/src/cmd/templates/macros.rs deleted file mode 100644 index c7a41b7bb..000000000 --- a/util/exercise/src/cmd/templates/macros.rs +++ /dev/null @@ -1,46 +0,0 @@ -{% macro to_literal(value, use_maplit=false) -%} - {% if value is object -%} - {% if use_maplit -%} - hashmap! { - {% for k, v in value -%} - {{ self::to_literal(value=k, use_maplit=use_maplit) }} => - {{ self::to_literal(value=v, use_maplit=use_maplit) }}, - {% endfor -%} - } - {% else -%} - { - let mut hm = ::std::collections::HashMap::new(); - {% for k, v in value -%} - hm.insert( - {{ self::to_literal(value=k, use_maplit=use_maplit) }}, - {{ self::to_literal(value=v, use_maplit=use_maplit) }} - ); - {% endfor -%} - hm - } - {% endif -%} - {% elif value is iterable -%} - vec![ - {% for element in value -%} - {{ self::to_literal(value=element, use_maplit=use_maplit) }}, - {% endfor -%} - ] - {% elif value is string -%} - "{{ value }}" - {% elif value is number -%} - {{ value }} - {% else -%} - None - {% endif -%} -{% endmacro -%} - -{% macro gen_test_fn(case, first_test_case=false) -%} - {# Need to set up the variables for the template. #} - {% set description = case.description -%} - {% set comments = case.comments -%} - {% set property = case.property -%} - {% set input = case.input -%} - {% set expected = case.expected -%} - - {% include "test_fn.rs" %} -{% endmacro generate_test_fn -%} \ No newline at end of file diff --git a/util/exercise/src/cmd/templates/property_fn.rs b/util/exercise/src/cmd/templates/property_fn.rs deleted file mode 100644 index 1bac5233e..000000000 --- a/util/exercise/src/cmd/templates/property_fn.rs +++ /dev/null @@ -1,18 +0,0 @@ -/// Process a single test case for the property `{{ property }}` -/// -/// All cases for the `{{ property }}` property are implemented in terms of -/// this function. -/// -/// Note that you'll need to both name the expected transform which the student -/// needs to write, and name the types of the inputs and outputs. -/// While rustc _may_ be able to handle things properly given a working example, -/// students will face confusing errors if the `I` and `O` types are not -/// concrete. -fn process_{{ format_property(property=property) }}_case(input: I, expected: O) { - // typical implementation: - // assert_eq!( - // student_{{ format_property(property=property) }}_func(input), - // expected, - // ) - unimplemented!() -} diff --git a/util/exercise/src/cmd/templates/test_file.rs b/util/exercise/src/cmd/templates/test_file.rs deleted file mode 100644 index e4c7ecb84..000000000 --- a/util/exercise/src/cmd/templates/test_file.rs +++ /dev/null @@ -1,49 +0,0 @@ -{% import "macros.rs" as macros -%} - -//! Tests for {{ exercise }} -//! -//! Generated by [utility][utility] using [canonical data][canonical_data] -//! -//! [utility]: https://github.com/exercism/rust/tree/main/util/exercise -//! [canonical_data]: https://raw.githubusercontent.com/exercism/problem-specifications/main/exercises/{{ exercise }}/canonical-data.json - -{% for comment in comments -%} -/// {{ comment }} -{% endfor %} - -{% if use_maplit -%} -use maplit::hashmap; -{% endif %} - -{% for property in properties | sort -%} -{% include "property_fn.rs" %} -{% endfor -%} - -{# Don't ignore the first case. -#} -{% set first_test_case = true -%} - -{% for item in cases -%} - {# Check if we're dealing with a group of cases. #} - {% if item.cases -%} - /// {{ item.description }} - {% if item.optional -%} - /// {{ item.optional }} - {% endif -%} - - {% if item.comments -%} - {% for comment in item.comments -%} - /// {{ comment }} - {% endfor -%} - {% endif -%} - - {% for case in item.cases -%} - {{ macros::gen_test_fn(case=case, first_test_case=first_test_case) }} - {% set_global first_test_case = false -%} - {% endfor -%} - - {# Or just a single one. #} - {% else -%} - {{ macros::gen_test_fn(case=item, first_test_case=first_test_case) }} - {% set_global first_test_case = false -%} - {% endif -%} -{% endfor -%} diff --git a/util/exercise/src/cmd/templates/test_fn.rs b/util/exercise/src/cmd/templates/test_fn.rs deleted file mode 100644 index 3232bf4eb..000000000 --- a/util/exercise/src/cmd/templates/test_fn.rs +++ /dev/null @@ -1,19 +0,0 @@ -{% import "macros.rs" as macros -%} - -#[test] -{% if not first_test_case -%} -#[ignore] -{% endif -%} -/// {{ description }} -{% if comments -%} - /// - {% for comment in comments %} - /// {{ comment }} - {% endfor %} -{% endif -%} -fn test_{{ format_description(description=description) }}() { - process_{{ format_property(property=property) }}_case( - {{ macros::to_literal(value=input, use_maplit=use_maplit) }}, - {{ macros::to_literal(value=expected, use_maplit=use_maplit) }} - ); -} diff --git a/util/exercise/src/cmd/update.rs b/util/exercise/src/cmd/update.rs deleted file mode 100644 index f1c1a28b2..000000000 --- a/util/exercise/src/cmd/update.rs +++ /dev/null @@ -1,134 +0,0 @@ -use crate::{ - self as exercise, - errors::Result, - structs::{LabeledTest, LabeledTestItem}, -}; -use failure::format_err; -use std::{collections::HashSet, fs, path::Path}; - -enum DiffType { - NEW, - UPDATED, -} - -fn generate_diff_test( - case: &LabeledTest, - diff_type: &DiffType, - use_maplit: bool, -) -> Result { - Ok(format!( - "//{}\n{}", - match diff_type { - DiffType::NEW => "NEW", - DiffType::UPDATED => "UPDATED", - }, - exercise::generate_test_function(case, use_maplit)? - )) -} - -fn generate_diff_property(property: &str) -> Result { - Ok(format!( - "//{}\n{}", - "NEW", - exercise::generate_property_body(property)? - )) -} - -fn generate_diffs( - case: &LabeledTest, - tests_content: &str, - diffs: &mut HashSet, - use_maplit: bool, -) -> Result<()> { - let description = &case.description; - let description_formatted = exercise::format_exercise_description(description); - - let diff_type = if !tests_content.contains(&format!("test_{}", description_formatted)) { - DiffType::NEW - } else { - DiffType::UPDATED - }; - - if diffs.insert(generate_diff_test(case, &diff_type, use_maplit)?) { - match diff_type { - DiffType::NEW => println!("New test case detected: {}.", description_formatted), - DiffType::UPDATED => println!("Updated test case: {}.", description_formatted), - } - } - - let property = &case.property; - let property_formatted = exercise::format_exercise_property(property); - - if !tests_content.contains(&format!("process_{}_case", property_formatted)) - && diffs.insert(generate_diff_property(property)?) - { - println!("New property detected: {}.", property); - } - - Ok(()) -} - -fn get_diffs( - case: &LabeledTestItem, - diffs: &mut HashSet, - tests_content: &str, - use_maplit: bool, -) -> Result<()> { - match case { - LabeledTestItem::Single(case) => generate_diffs(case, &tests_content, diffs, use_maplit)?, - LabeledTestItem::Array(group) => { - for case in &group.cases { - get_diffs(case, diffs, tests_content, use_maplit)?; - } - } - } - - Ok(()) -} - -fn apply_diffs(exercise_name: &str, diffs: &HashSet, tests_content: &str) -> Result<()> { - let updated_tests_content = format!( - "{}\n{}", - tests_content, - diffs - .iter() - .map(|diff| format!("\n{}", diff)) - .collect::() - ); - - let tests_path = Path::new(&*exercise::TRACK_ROOT) - .join("exercises") - .join(exercise_name) - .join("tests") - .join(format!("{}.rs", exercise_name)); - - fs::write(&tests_path, updated_tests_content.as_bytes())?; - - exercise::rustfmt(&tests_path)?; - - Ok(()) -} - -pub fn update_exercise(exercise_name: &str, use_maplit: bool) -> Result<()> { - if !exercise::exercise_exists(exercise_name) { - return Err( - format_err!("exercise with the name '{}' does not exist", exercise_name).into(), - ); - } - - let tests_content = exercise::get_tests_content(exercise_name)?; - - let canonical_data = exercise::get_canonical_data(exercise_name)?; - - let mut diffs: HashSet = HashSet::new(); - - for case in &canonical_data.cases { - get_diffs(case, &mut diffs, &tests_content, use_maplit)?; - } - - apply_diffs(exercise_name, &diffs, &tests_content)?; - - exercise::update_cargo_toml_version(exercise_name, &canonical_data)?; - - Ok(()) -} diff --git a/util/exercise/src/errors.rs b/util/exercise/src/errors.rs deleted file mode 100644 index a0fb6d125..000000000 --- a/util/exercise/src/errors.rs +++ /dev/null @@ -1,76 +0,0 @@ -use failure; -use reqwest; -use serde_json; -use std::{convert::From, io, result}; -use tera; -use toml; - -#[derive(Debug, failure::Fail)] -pub enum Error { - #[fail(display = "IO error: {}", _0)] - IoError(#[cause] io::Error), - #[fail( - display = "config.json malformed: '{}' must have field '{}'", - parent, field - )] - ConfigJsonSchemaError { parent: String, field: String }, - #[fail( - display = "{} malformed: field '{}' must have type '{}'", - file, field, as_type - )] - SchemaTypeError { - file: String, - field: String, - as_type: String, - }, - #[fail(display = "json error: {}", _0)] - JsonError(#[cause] serde_json::Error), - #[fail(display = "config.toml parse error: {}", _0)] - ConfigTomlParseError(#[cause] toml::de::Error), - #[fail(display = "HTTP error: {}", _0)] - HTTPError(reqwest::Error), - #[fail(display = "Tera rendering error: {}", _0)] - TeraError(tera::Error), - #[fail(display = "{}", _0)] - Failure(#[cause] failure::Error), - #[fail(display = "Unknown Failure: {}", _0)] - UnknownError(String), -} - -impl From for Error { - fn from(err: io::Error) -> Self { - Error::IoError(err) - } -} - -impl From for Error { - fn from(err: toml::de::Error) -> Self { - Error::ConfigTomlParseError(err) - } -} - -impl From for Error { - fn from(err: reqwest::Error) -> Self { - Error::HTTPError(err) - } -} - -impl From for Error { - fn from(err: failure::Error) -> Self { - Error::Failure(err) - } -} - -impl From for Error { - fn from(err: serde_json::Error) -> Self { - Error::JsonError(err) - } -} - -impl From for Error { - fn from(err: tera::Error) -> Self { - Error::TeraError(err) - } -} - -pub type Result = result::Result; diff --git a/util/exercise/src/lib.rs b/util/exercise/src/lib.rs deleted file mode 100644 index d7609a39e..000000000 --- a/util/exercise/src/lib.rs +++ /dev/null @@ -1,282 +0,0 @@ -pub mod cmd; -pub mod errors; -pub mod structs; - -use errors::Result; -use failure::format_err; -use lazy_static::lazy_static; -use reqwest; -use serde_json::Value; -use std::{ - collections::HashMap, - env, fs, io, - path::Path, - process::{Command, Stdio}, -}; -use structs::{CanonicalData, LabeledTest}; -use tera::{Context, Tera}; -use toml; -use toml::Value as TomlValue; - -// we look for the track root in various places, but it's never going to change -// we therefore cache the value for efficiency -lazy_static! { - pub static ref TRACK_ROOT: String = { - let rev_parse_output = Command::new("git") - .arg("rev-parse") - .arg("--show-toplevel") - .output() - .expect("Failed to get the path to the track repo."); - - String::from_utf8(rev_parse_output.stdout) - .expect("git rev-parse produced non-utf8 output") - .trim() - .to_string() - }; -} - -// Create a static `Tera` struct so we can access the templates from anywhere. -lazy_static! { - pub static ref TEMPLATES: Tera = { - let templates = Path::new(&*TRACK_ROOT) - .join("util") - .join("exercise") - .join("src") - .join("cmd") - .join("templates") - .join("**") - .join("*.rs"); - - // Since `TRACK_ROOT` already checks for UTF-8 and nothing added is not - // UTF-8, unwrapping is fine. - let mut tera = match Tera::new(templates.to_str().unwrap()) { - Ok(t) => t, - Err(e) => { - println!("Parsing error(s): {}", e); - ::std::process::exit(1); - } - }; - - // Build wrappers around the formatting functions. - let format_description = |args: &HashMap| - args.get("description") - .and_then(Value::as_str) - .map(format_exercise_description) - .map(Value::from) - .ok_or(tera::Error::from("Problem formatting the description.")) - ; - - let format_property = |args: &HashMap| - args.get("property") - .and_then(Value::as_str) - .map(format_exercise_property) - .map(Value::from) - .ok_or(tera::Error::from("Problem formatting the property.")) - ; - - tera.register_function("format_description", format_description); - tera.register_function("format_property", format_property); - tera - }; -} - -#[macro_export] -macro_rules! val_as { - ($value:expr, $as:ident) => { - $value - .$as() - .ok_or_else(|| $crate::errors::Error::SchemaTypeError { - file: "config.json".to_string(), - field: stringify!($name).to_string(), - as_type: stringify!($as)[3..].to_string(), - })? - }; -} - -#[macro_export] -macro_rules! get { - ($value:expr, $name:expr) => { - $value - .get($name) - .ok_or_else(|| $crate::errors::Error::ConfigJsonSchemaError { - parent: stringify!($value).to_string(), - field: stringify!($name).to_string(), - })? - }; - ($value:expr, $name:expr, $as:ident) => { - val_as!(get!($value, $name), $as) - }; -} - -#[macro_export] -macro_rules! get_mut { - ($value:expr, $name:expr) => { - $value - .get_mut($name) - .ok_or_else(|| $crate::errors::Error::ConfigJsonSchemaError { - parent: stringify!($value).to_string(), - field: stringify!($name).to_string(), - })? - }; - ($value:expr, $name:expr, $as:ident) => { - val_as!(get_mut!($value, $name), $as) - }; -} - -pub fn run_configlet_command(command: &str, args: &[&str]) -> Result<()> { - let track_root = &*TRACK_ROOT; - - let bin_path = Path::new(track_root).join("bin"); - - let configlet_name_unix = "configlet"; - - let configlet_name_windows = "configlet.exe"; - - let configlet_name = if bin_path.join(configlet_name_unix).exists() { - configlet_name_unix - } else if bin_path.join(configlet_name_windows).exists() { - configlet_name_windows - } else { - println!("Configlet not found in the bin directory. Running bin/fetch-configlet."); - - let bin_path = crate::cmd::fetch_configlet::download()?; - - if bin_path.join(configlet_name_unix).exists() { - configlet_name_unix - } else if bin_path.join(configlet_name_windows).exists() { - configlet_name_windows - } else { - return Err(format_err!( - "could not locate configlet after running bin/fetch-configlet" - ) - .into()); - } - }; - - Command::new(&bin_path.join(configlet_name)) - .current_dir(track_root) - .stdout(Stdio::inherit()) - .arg(command) - .args(args) - .output()?; - - Ok(()) -} - -fn url_for(exercise: &str, file: &str) -> String { - format!( - "https://raw.githubusercontent.com/exercism/problem-specifications/main/exercises/%7B%7D/%7B%7D", - exercise, file, - ) -} - -fn get_canonical(exercise: &str, file: &str) -> Result { - reqwest::get(&url_for(exercise, file)).map_err(|e| e.into()) -} - -// Try to get the canonical data for the exercise of the given name -pub fn get_canonical_data(exercise_name: &str) -> Result { - let mut response = get_canonical(exercise_name, "canonical-data.json")?.error_for_status()?; - response.json().map_err(|e| e.into()) -} - -pub fn canonical_file_exists(exercise: &str, file: &str) -> Result { - Ok(get_canonical(exercise, file)?.status().is_success()) -} - -pub fn get_tests_content(exercise_name: &str) -> io::Result { - let tests_path = Path::new(&*TRACK_ROOT) - .join("exercises") - .join(exercise_name) - .join("tests") - .join(format!("{}.rs", exercise_name)); - - fs::read_to_string(tests_path) -} - -pub fn format_exercise_description(description: &str) -> String { - description - .chars() - .filter(|c| c.is_alphanumeric() || *c == ' ') - .collect::() - .replace(" ", "_") - .to_lowercase() -} - -pub fn format_exercise_property(property: &str) -> String { - property.replace(" ", "_").to_lowercase() -} - -pub fn generate_property_body(property: &str) -> Result { - let mut context = Context::new(); - context.insert("property", property); - TEMPLATES - .render("property_fn.rs", &context) - .map_err(|e| e.into()) -} - -pub fn generate_test_function(case: &LabeledTest, use_maplit: bool) -> Result { - let mut context = Context::from_serialize(case)?; - context.insert("use_maplit", &use_maplit); - TEMPLATES - .render("test_fn.rs", &context) - .map_err(|e| e.into()) -} - -pub fn rustfmt(file_path: &Path) -> Result<()> { - let rustfmt_is_available = { - if let Some(path_var) = env::var_os("PATH") { - env::split_paths(&path_var).any(|path| path.join("rustfmt").exists()) - } else { - false - } - }; - - if rustfmt_is_available { - Command::new("rustfmt").arg(file_path).output()?; - } - - Ok(()) -} - -pub fn exercise_exists(exercise_name: &str) -> bool { - Path::new(&*TRACK_ROOT) - .join("exercises") - .join(exercise_name) - .exists() -} - -// Update the version of the specified exercise in the Cargo.toml file according to the passed canonical data -pub fn update_cargo_toml_version( - exercise_name: &str, - canonical_data: &CanonicalData, -) -> Result<()> { - let cargo_toml_path = Path::new(&*TRACK_ROOT) - .join("exercises") - .join(exercise_name) - .join("Cargo.toml"); - - let cargo_toml_content = fs::read_to_string(&cargo_toml_path)?; - - let mut cargo_toml: TomlValue = cargo_toml_content.parse()?; - - { - let package_table = - cargo_toml["package"] - .as_table_mut() - .ok_or_else(|| errors::Error::SchemaTypeError { - file: "Config.toml".to_string(), - field: "package".to_string(), - as_type: "table".to_string(), - })?; - - package_table.insert( - "version".to_string(), - TomlValue::String(canonical_data.version.to_string()), - ); - } - - fs::write(&cargo_toml_path, cargo_toml.to_string())?; - - Ok(()) -} diff --git a/util/exercise/src/main.rs b/util/exercise/src/main.rs deleted file mode 100644 index ea9f1fa7a..000000000 --- a/util/exercise/src/main.rs +++ /dev/null @@ -1,119 +0,0 @@ -use clap::{App, Arg, ArgMatches, SubCommand}; -use exercise::{ - cmd::{configure, fetch_configlet, generate, update}, - errors::Result, -}; -use failure::format_err; - -// Creates a new CLI app with appropriate matches -// and returns the initialized matches. -fn init_app<'a>() -> ArgMatches<'a> { - App::new(env!("CARGO_PKG_NAME")) - .version(env!("CARGO_PKG_VERSION")) - .about(env!("CARGO_PKG_DESCRIPTION")) - .subcommand( - SubCommand::with_name("generate") - .about("Generates new exercise") - .arg(Arg::with_name("exercise_name").required(true).help("The name of the generated exercise")) - .arg(Arg::with_name("configure").long("configure").short("c").help( - "If set, the command will edit the config.json file after generating the exercise", - )) - .arg(Arg::with_name("use_maplit").long("use-maplit").short("m").help("Use the maplit crate to improve the readability of the generated test suite")), - ) - .subcommand( - SubCommand::with_name("update") - .about("Updates the specified exercise") - .arg(Arg::with_name("exercise_name").help("The name of the updated exercise")) - .arg(Arg::with_name("use_maplit").long("use-maplit").short("m").help("Use the maplit crate to improve the readability of the updated test suite")) - .arg(Arg::with_name("dont_update_readme").long("dont-update-readme").short("r").help("If set, the README of the exercise would not be updated")) - .arg(Arg::with_name("configure").long("configure").short("c").help( - "If set, the command will edit the config.json file after updating the exercise", - )) - ) - .subcommand( - SubCommand::with_name("configure") - .about("Edits config.json for the specified exercise") - .arg(Arg::with_name("exercise_name").required(true).help("The name of the configured exercise")), - ) - .subcommand( - SubCommand::with_name("fetch_configlet") - .about("Downloads and extracts configlet utility into the repo's /bin directory") - ) - .get_matches() -} - -// Determine which subcommand was used -// and call the appropriate function. -fn process_matches(matches: &ArgMatches<'_>) -> Result<()> { - match matches.subcommand() { - ("generate", Some(generate_matches)) => { - let exercise_name = generate_matches - .value_of("exercise_name") - .ok_or_else(|| format_err!("exercise name not present in args"))?; - let run_configure = generate_matches.is_present("configure"); - let use_maplit = generate_matches.is_present("use_maplit"); - - generate::generate_exercise(exercise_name, use_maplit)?; - - if run_configure { - configure::configure_exercise(exercise_name)?; - } - } - - ("update", Some(update_matches)) => { - let exercise_name = update_matches - .value_of("exercise_name") - .ok_or_else(|| format_err!("exercise name not present in args"))?; - - let run_configure = update_matches.is_present("configure"); - - let use_maplit = update_matches.is_present("use_maplit"); - - let update_readme = !update_matches.is_present("dont_update_readme"); - - update::update_exercise(exercise_name, use_maplit)?; - - if run_configure { - configure::configure_exercise(exercise_name)?; - } - - if update_readme { - exercise::run_configlet_command("generate", &[".", "-o", exercise_name])?; - } - } - - ("configure", Some(configure_matches)) => { - configure::configure_exercise( - configure_matches - .value_of("exercise_name") - .ok_or_else(|| format_err!("exercise name not present in args"))?, - )?; - } - - ("fetch_configlet", Some(_fetch_configlet_matches)) => { - if let Ok(fetch_path) = fetch_configlet::download() { - println!( - "Downloaded and moved the configlet utility to the {:?} path", - fetch_path - ); - } else { - println!("Failed to fetch the configlet utility"); - } - } - - ("", None) => { - println!("No subcommand was used.\nUse exercise --help to learn about the possible subcommands."); - } - - _ => unreachable!(), - }; - - Ok(()) -} - -fn main() -> Result<()> { - let matches = init_app(); - - process_matches(&matches)?; - Ok(()) -} diff --git a/util/exercise/src/structs.rs b/util/exercise/src/structs.rs deleted file mode 100644 index dd4e4bd5c..000000000 --- a/util/exercise/src/structs.rs +++ /dev/null @@ -1,84 +0,0 @@ -//! Rust struct for canonical test data. -//! -//! See https://github.com/exercism/problem-specifications/blob/main/canonical-schema.json -//! for more details on the JSON schema, which makes it possible to implement -//! `serde::Deserialize`. - -use serde::{Deserialize, Serialize}; -use serde_json::Value; -use std::collections::HashSet; - -#[derive(Serialize, Deserialize, Debug)] -pub struct CanonicalData { - pub exercise: Exercise, - pub version: Version, - pub comments: Option, - pub cases: TestGroup, -} - -type Exercise = String; -type Version = String; -type Comments = Vec; -type TestGroup = Vec; - -#[derive(Serialize, Deserialize, Debug)] -#[serde(untagged)] -pub enum LabeledTestItem { - Single(LabeledTest), - Array(LabeledTestGroup), -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct LabeledTest { - pub description: Description, - pub optional: Option, - pub comments: Option, - pub property: Property, - pub input: Input, - pub expected: Expected, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct LabeledTestGroup { - pub description: Description, - pub optional: Option, - pub comments: Option, - pub cases: TestGroup, -} - -type Description = String; -type Optional = String; -type Property = String; -type Input = Value; -type Expected = Value; - -impl CanonicalData { - pub fn properties(&self) -> HashSet<&str> { - self.cases - .iter() - .flat_map(LabeledTestItem::iter) - .map(|case| case.property.as_str()) - .collect() - } -} - -impl LabeledTestItem { - fn iter(&self) -> Box + '_> { - match self { - LabeledTestItem::Single(case) => Box::new(case.iter()), - LabeledTestItem::Array(cases) => Box::new(cases.iter()), - } - } -} - -impl LabeledTest { - fn iter(&self) -> impl Iterator { - std::iter::once(self) - } -} - -impl LabeledTestGroup { - fn iter(&self) -> impl Iterator { - self.cases.iter().flat_map(LabeledTestItem::iter) - } -} From e9cd6414933d569b65b1fc3526016081b33bc84f Mon Sep 17 00:00:00 2001 From: dem4ron Date: Tue, 7 Mar 2023 14:05:32 +0100 Subject: [PATCH 051/436] Remove test_template from gitignore --- .gitignore | 1 - bin/test_template | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index e7f03fe4f..8f4f224b4 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,6 @@ bin/configlet.exe bin/exercise bin/exercise.exe bin/generator-utils/ngram -bin/test_template exercises/*/*/Cargo.lock exercises/*/*/clippy.log canonical_data.json diff --git a/bin/test_template b/bin/test_template index ec6ee9785..ad458d212 100644 --- a/bin/test_template +++ b/bin/test_template @@ -2,6 +2,6 @@ #[ignore] fn ${description}$() { - assert_eq!(${property}$(${input}$), ${expected}$) + assert_eq!(${property}$(${input}$), ${expected}$); } From 0f1aee1176988df022a652b66b66fb544a773595 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Tue, 7 Mar 2023 14:08:55 +0100 Subject: [PATCH 052/436] Remove `util/exercise` related tests --- _test/cargo_clean_all.sh | 16 --------------- _test/check_exercise_crate.sh | 37 ----------------------------------- 2 files changed, 53 deletions(-) delete mode 100755 _test/cargo_clean_all.sh delete mode 100755 _test/check_exercise_crate.sh diff --git a/_test/cargo_clean_all.sh b/_test/cargo_clean_all.sh deleted file mode 100755 index c50bb91c2..000000000 --- a/_test/cargo_clean_all.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/sh - -status=0 -repo="$(cd "$(dirname "$0")/.." && pwd)" - -for ex in "$repo"/exercises/*/*/; do - name=$(grep '^name =' "$ex/Cargo.toml" | cut -d\" -f2) - if [ -z "$name" ]; then - echo "don't know name of $ex" - status=1 - continue - fi - cargo clean --manifest-path "$ex/Cargo.toml" --package "$name" -done - -exit $status diff --git a/_test/check_exercise_crate.sh b/_test/check_exercise_crate.sh deleted file mode 100755 index 3870db865..000000000 --- a/_test/check_exercise_crate.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail - -# A script to ensure that the util/exercise crate builds after it was modified. - -EXERCISE_CRATE_PATH="util/exercise" - -if [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then - # Check the changes on the current branch against main branch - if ! git diff --name-only remotes/origin/main | grep -q "$EXERCISE_CRATE_PATH"; then - echo "exercise crate was not modified. The script is aborted." - exit 0 - fi -fi -# If it's not a pull request, just always run it. -# Two scenarios: -# 1. It's being run locally, -# in which case we assume the person running it really does want to run it. -# 2. It's being run on CI for main, -# in which case we should check regardless of changes to exercise crate, -# in case there's a new toolchain release, etc. - - -TRACK_ROOT="$(git rev-parse --show-toplevel)" - -if ! (cd "$TRACK_ROOT/$EXERCISE_CRATE_PATH" && cargo check); then - echo - echo "An error has occurred while building the exercise crate." - echo "Please make it compile." - - exit 1 -else - echo - echo "exercise crate has been successfully built." - - exit 0 -fi From 13f177b5a254dbd66ab1db651dcf33d41ce0d6f7 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Tue, 7 Mar 2023 14:13:02 +0100 Subject: [PATCH 053/436] update tests.yml --- .github/workflows/tests.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 91db73990..8d3c9a2de 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -123,20 +123,12 @@ jobs: run: ./_test/check_exercises.sh continue-on-error: ${{ matrix.rust == 'beta' && matrix.deny_warnings == '1' }} - - name: Cargo clean (to prevent previous compilation from unintentionally interfering with later ones) - run: ./_test/cargo_clean_all.sh - - name: Ensure stubs compile env: DENYWARNINGS: ${{ matrix.deny_warnings }} run: ./_test/ensure_stubs_compile.sh continue-on-error: ${{ matrix.rust == 'beta' && matrix.deny_warnings == '1' }} - - name: Check exercise crate - env: - DENYWARNINGS: ${{ matrix.deny_warnings }} - run: ./_test/check_exercise_crate.sh - continue-on-error: ${{ matrix.rust == 'beta' && matrix.deny_warnings == '1' }} rustformat: name: Check Rust Formatting From 7dd46263b0c4835a74e632262e6b98dd3e84680d Mon Sep 17 00:00:00 2001 From: dem4ron Date: Tue, 7 Mar 2023 19:46:49 +0100 Subject: [PATCH 054/436] Rename script --- bin/{generate_practice_exercise => add_practice_exercise} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename bin/{generate_practice_exercise => add_practice_exercise} (97%) diff --git a/bin/generate_practice_exercise b/bin/add_practice_exercise similarity index 97% rename from bin/generate_practice_exercise rename to bin/add_practice_exercise index c2bae5562..08e9f4dbf 100755 --- a/bin/generate_practice_exercise +++ b/bin/add_practice_exercise @@ -10,7 +10,7 @@ set -euo pipefail # If argument not provided, print usage and exit if [ $# -ne 1 ] && [ $# -ne 2 ] && [ $# -ne 3 ]; then - echo "Usage: bin/generate_practice_exercise.sh [difficulty] [author-github-handle]" + echo "Usage: bin/add_practice_exercise [difficulty] [author-github-handle]" exit 1 fi From 975bbcd5ac93058dccd8d33017ec0efc275d82e6 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Tue, 7 Mar 2023 19:58:10 +0100 Subject: [PATCH 055/436] Remove redundant loc - sorry! last commit to this PR --- bin/generate_tests | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bin/generate_tests b/bin/generate_tests index 74c137d7f..ce8875285 100755 --- a/bin/generate_tests +++ b/bin/generate_tests @@ -11,6 +11,7 @@ function digest_template() { echo "$template" | sed 's/${\([^}]*\)\}\$/$(echo $case | jq -r '\''.\1'\'')/g' } +message "info" "Generating tests.." canonical_json=$(cat canonical_data.json) SLUG=$(echo "$canonical_json" | jq '.exercise') # shellcheck disable=SC2001 @@ -18,7 +19,6 @@ SLUG=$(echo "$canonical_json" | jq '.exercise') SLUG=$(echo "$SLUG" | sed 's/"//g') EXERCISE_DIR="exercises/practice/$SLUG" TEST_FILE="$EXERCISE_DIR/tests/$SLUG.rs" -rm "$TEST_FILE" cat <"$TEST_FILE" use $(dash_to_underscore "$SLUG")::*; @@ -46,3 +46,5 @@ jq -c '.[]' <<<"$cases" | while read -r case; do done rustfmt "$TEST_FILE" + +message "success" "Generated tests successfully! Check out ${TEST_FILE}" From a4d8b8846a899a11223512e73057d3db05d5180f Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Wed, 8 Mar 2023 15:17:17 +0100 Subject: [PATCH 056/436] Sync secret-handshake docs with problem-specifications (#1643) The secret-handshake exercise has been overhauled as part of a project to make practice exercises more consistent and friendly. For more context, please see the discussion in the forum, as well as the pull request that updated the exercise in the problem-specifications repository: - https://forum.exercism.org/t/new-project-making-practice-exercises-more-consistent-and-human-across-exercism/3943 - https://github.com/exercism/problem-specifications/pull/2219 --- .../secret-handshake/.docs/instructions.md | 43 ++++++++++++++----- .../secret-handshake/.docs/introduction.md | 7 +++ 2 files changed, 40 insertions(+), 10 deletions(-) create mode 100644 exercises/practice/secret-handshake/.docs/introduction.md diff --git a/exercises/practice/secret-handshake/.docs/instructions.md b/exercises/practice/secret-handshake/.docs/instructions.md index 2d6937ae9..77136cf0f 100644 --- a/exercises/practice/secret-handshake/.docs/instructions.md +++ b/exercises/practice/secret-handshake/.docs/instructions.md @@ -1,24 +1,47 @@ # Instructions -> There are 10 types of people in the world: Those who understand -> binary, and those who don't. +Your task is to convert a number between 1 and 31 to a sequence of actions in the secret handshake. -You and your fellow cohort of those in the "know" when it comes to binary decide to come up with a secret "handshake". +The sequence of actions is chosen by looking at the rightmost five digits of the number once it's been converted to binary. +Start at the right-most digit and move left. -```text +The actions for each number place are: + +```plaintext 00001 = wink 00010 = double blink 00100 = close your eyes 01000 = jump - 10000 = Reverse the order of the operations in the secret handshake. ``` -Given a decimal number, convert it to the appropriate sequence of events for a secret handshake. +Let's use the number `9` as an example: + +- 9 in binary is `1001`. +- The digit that is farthest to the right is 1, so the first action is `wink`. +- Going left, the next digit is 0, so there is no double-blink. +- Going left again, the next digit is 0, so you leave your eyes open. +- Going left again, the next digit is 1, so you jump. -Here's a couple of examples: +That was the last digit, so the final code is: + +```plaintext +wink, jump +``` -Given the decimal input 3, the function would return the array ["wink", "double blink"] because the decimal number 3 is 2+1 in powers of two and thus `11` in binary. +Given the number 26, which is `11010` in binary, we get the following actions: + +- double blink +- jump +- reverse actions + +The secret handshake for 26 is therefore: + +```plaintext +jump, double blink +``` -Let's now examine the input 19 which is 16+2+1 in powers of two and thus `10011` in binary. -Recalling that the addition of 16 (`10000` in binary) reverses an array and that we already know what array is returned given input 3, the array returned for input 19 is ["double blink", "wink"]. +~~~~exercism/note +If you aren't sure what binary is or how it works, check out [this binary tutorial][intro-to-binary]. +[intro-to-binary]: https://medium.com/basecs/bits-bytes-building-with-binary-13cb4289aafa +~~~~ diff --git a/exercises/practice/secret-handshake/.docs/introduction.md b/exercises/practice/secret-handshake/.docs/introduction.md new file mode 100644 index 000000000..176b92e8c --- /dev/null +++ b/exercises/practice/secret-handshake/.docs/introduction.md @@ -0,0 +1,7 @@ +# Introduction + +You are starting a secret coding club with some friends and friends-of-friends. +Not everyone knows each other, so you and your friends have decided to create a secret handshake that you can use to recognize that someone is a member. +You don't want anyone who isn't in the know to be able to crack the code. + +You've designed the code so that one person says a number between 1 and 31, and the other person turns it into a series of actions. From 79893a160887bdf7ba38235c424e2382e88baf72 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Thu, 9 Mar 2023 21:21:18 +0100 Subject: [PATCH 057/436] Change casing, fix shellcheck disables, add .shekkcheckrc --- bin/.shellcheckrc | 3 ++ bin/add_practice_exercise | 54 +++++++++++++++++--------------- bin/build_exercise_crate.sh | 27 ---------------- bin/fetch_canonical_data | 3 ++ bin/generate_tests | 24 +++++++------- bin/generator-utils/colors.sh | 40 +++++------------------ bin/generator-utils/prompts.sh | 27 ++++++++-------- bin/generator-utils/templates.sh | 15 ++++++--- bin/generator-utils/utils.sh | 29 +++++++++-------- 9 files changed, 95 insertions(+), 127 deletions(-) create mode 100644 bin/.shellcheckrc delete mode 100755 bin/build_exercise_crate.sh diff --git a/bin/.shellcheckrc b/bin/.shellcheckrc new file mode 100644 index 000000000..5ec9f679b --- /dev/null +++ b/bin/.shellcheckrc @@ -0,0 +1,3 @@ +shell=bash +external-sources=true +disable=SC2001 # https://www.shellcheck.net/wiki/SC2001 - effects most of the sed commands \ No newline at end of file diff --git a/bin/add_practice_exercise b/bin/add_practice_exercise index 08e9f4dbf..ad43c4331 100755 --- a/bin/add_practice_exercise +++ b/bin/add_practice_exercise @@ -1,6 +1,8 @@ #!/usr/bin/env bash -# shellcheck source=/dev/null +# shellcheck source=./generator-utils/utils.sh +# shellcheck source=./generator-utils/prompts.sh +# shellcheck source=./generator-utils/templates.sh source ./bin/generator-utils/utils.sh source ./bin/generator-utils/prompts.sh source ./bin/generator-utils/templates.sh @@ -35,13 +37,13 @@ check_exercise_existence "$1" # ================================================== -SLUG="$1" -HAS_CANONICAL_DATA=true +slug="$1" # Fetch canonical data -canonical_json=$(bin/fetch_canonical_data "$SLUG") +canonical_json=$(bin/fetch_canonical_data "$slug") +has_canonical_data=true if [ "${canonical_json}" == "404: Not Found" ]; then - HAS_CANONICAL_DATA=false + has_canonical_data=false message "warning" "This exercise doesn't have canonical data" else @@ -49,32 +51,31 @@ else message "success" "Fetched canonical data successfully!" fi -UNDERSCORED_SLUG=$(dash_to_underscore "$SLUG") -EXERCISE_DIR="exercises/practice/${SLUG}" -EXERCISE_NAME=$(format_exercise_name "$SLUG") -message "info" "Using ${YELLOW}${EXERCISE_NAME}${BLUE} as a default exercise name. You can edit this later in the config.json file" +underscored_slug=$(dash_to_underscore "$slug") +exercise_dir="exercises/practice/${slug}" +exercise_name=$(format_exercise_name "$slug") +message "info" "Using ${yellow}${exercise_name}${blue} as a default exercise name. You can edit this later in the config.json file" # using default value for difficulty -EXERCISE_DIFFICULTY=$(validate_difficulty_input "${2:-$(get_exercise_difficulty)}") -message "info" "The exercise difficulty has been set to ${YELLOW}${EXERCISE_DIFFICULTY}${BLUE}. You can edit this later in the config.json file" +exercise_difficulty=$(validate_difficulty_input "${2:-$(get_exercise_difficulty)}") +message "info" "The exercise difficulty has been set to ${yellow}${exercise_difficulty}${blue}. You can edit this later in the config.json file" # using default value for author -AUTHOR_HANDLE=${3:-$(get_author_handle)} -message "info" "Using ${YELLOW}${AUTHOR_HANDLE}${BLUE} as author's handle. You can edit this later in the 'authors' field in the ${EXERCISE_DIR}/.meta/config.json file" - - -create_rust_files "$EXERCISE_DIR" "$SLUG" "$HAS_CANONICAL_DATA" +author_handle=${3:-$(get_author_handle)} +message "info" "Using ${yellow}${author_handle}${blue} as author's handle. You can edit this later in the 'authors' field in the ${exercise_dir}/.meta/config.json file" +create_rust_files "$exercise_dir" "$slug" "$has_canonical_data" # ================================================== -# build configlet +# Build configlet ./bin/fetch-configlet message "success" "Fetched configlet successfully!" # Preparing config.json message "info" "Adding instructions and configuration files..." -UUID=$(bin/configlet uuid) +uuid=$(bin/configlet uuid) -jq --arg slug "$SLUG" --arg uuid "$UUID" --arg name "$EXERCISE_NAME" --arg difficulty "$EXERCISE_DIFFICULTY" \ +# Add exercise-data to global config.json +jq --arg slug "$slug" --arg uuid "$uuid" --arg name "$exercise_name" --arg difficulty "$exercise_difficulty" \ '.exercises.practice += [{slug: $slug, name: $name, uuid: $uuid, practices: [], prerequisites: [], difficulty: $difficulty}]' \ config.json >config.json.tmp # jq always rounds whole numbers, but average_run_time needs to be a float @@ -84,18 +85,19 @@ message "success" "Added instructions and configuration files" # Create instructions and config files echo "Creating instructions and config files" -./bin/configlet sync --update --yes --docs --metadata --exercise "$SLUG" -./bin/configlet sync --update --yes --filepaths --exercise "$SLUG" -./bin/configlet sync --update --tests include --exercise "$SLUG" +./bin/configlet sync --update --yes --docs --metadata --exercise "$slug" +./bin/configlet sync --update --yes --filepaths --exercise "$slug" +./bin/configlet sync --update --tests include --exercise "$slug" message "success" "Created instructions and config files" -META_CONFIG="$EXERCISE_DIR"/.meta/config.json -jq --arg author "$AUTHOR_HANDLE" '.authors += [$author]' "$META_CONFIG" >"$META_CONFIG".tmp && mv "$META_CONFIG".tmp "$META_CONFIG" +# Push author to "authors" array in ./meta/config.json +meta_config="$exercise_dir"/.meta/config.json +jq --arg author "$author_handle" '.authors += [$author]' "$meta_config" >"$meta_config".tmp && mv "$meta_config".tmp "$meta_config" message "success" "You've been added as the author of this exercise." -sed -i "s/name = \".*\"/name = \"$UNDERSCORED_SLUG\"/" "$EXERCISE_DIR"/Cargo.toml +sed -i "s/name = \".*\"/name = \"$underscored_slug\"/" "$exercise_dir"/Cargo.toml message "done" "All stub files were created." message "info" "After implementing the solution, tests and configuration, please run:" -echo "./bin/configlet fmt --update --yes --exercise ${SLUG}" +echo "./bin/configlet fmt --update --yes --exercise ${slug}" diff --git a/bin/build_exercise_crate.sh b/bin/build_exercise_crate.sh deleted file mode 100755 index ca92b77f4..000000000 --- a/bin/build_exercise_crate.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash -set -e -# Compile the 'exercise' crate and put it in the 'bin/' folder - -TRACK_ROOT="$(git rev-parse --show-toplevel)" - -EXERCISE_CRATE_PATH="$TRACK_ROOT/util/exercise" - -BIN_DIR_PATH="$TRACK_ROOT/bin" - -( - cd "$EXERCISE_CRATE_PATH" - - echo "Building exercise crate" - - cargo build --release - - RELEASE_PATH="$EXERCISE_CRATE_PATH/target/release/exercise" - - if [ -f "$RELEASE_PATH.exe" ]; then - RELEASE_PATH="$RELEASE_PATH.exe" - fi - - echo "Copying exercise crate from $RELEASE_PATH into $BIN_DIR_PATH" - - cp "$RELEASE_PATH" "$BIN_DIR_PATH" -) diff --git a/bin/fetch_canonical_data b/bin/fetch_canonical_data index a487581f2..69ca5fde0 100755 --- a/bin/fetch_canonical_data +++ b/bin/fetch_canonical_data @@ -1,6 +1,9 @@ #!/usr/bin/env bash # This script fetches the canonical data of the exercise. +# Exit if anything fails. +set -euo pipefail + if [ $# -ne 1 ]; then echo "Usage: bin/fetch_canonical_data " exit 1 diff --git a/bin/generate_tests b/bin/generate_tests index ce8875285..809406d77 100755 --- a/bin/generate_tests +++ b/bin/generate_tests @@ -1,19 +1,21 @@ #!/usr/bin/env bash +# Exit if anything fails. set -euo pipefail -# shellcheck source=/dev/null + + +# shellcheck source=./generator-utils/utils.sh source ./bin/generator-utils/utils.sh -function digest_template() { +digest_template() { template=$(cat bin/test_template) - # shellcheck disable=SC2001 # turn every token into a jq command echo "$template" | sed 's/${\([^}]*\)\}\$/$(echo $case | jq -r '\''.\1'\'')/g' } message "info" "Generating tests.." -canonical_json=$(cat canonical_data.json) -SLUG=$(echo "$canonical_json" | jq '.exercise') +CANONICAL_JSON=$(cat canonical_data.json) +SLUG=$(echo "$CANONICAL_JSON" | jq '.exercise') # shellcheck disable=SC2001 # Remove double quotes SLUG=$(echo "$SLUG" | sed 's/"//g') @@ -27,20 +29,20 @@ use $(dash_to_underscore "$SLUG")::*; EOT # Flattens canonical json, extracts only the objects with a uuid -cases=$(echo "$canonical_json" | jq '[ .. | objects | with_entries(select(.key | IN("uuid", "description", "input", "expected", "property"))) | select(. != {}) | select(has("uuid")) ]') +CASES=$(echo "$CANONICAL_JSON" | jq '[ .. | objects | with_entries(select(.key | IN("uuid", "description", "input", "expected", "property"))) | select(. != {}) | select(has("uuid")) ]') # shellcheck disable=SC2034 -jq -c '.[]' <<<"$cases" | while read -r case; do +jq -c '.[]' <<<"$CASES" | while read -r case; do # Evaluate the bash parts and replace them with their return values - eval_template="$(digest_template | sed -e "s/\$(\(.*\))/\$\(\1\)/g")" - eval_template="$(eval "echo \"$eval_template\"")" + EVAL_TEMPLATE="$(digest_template | sed -e "s/\$(\(.*\))/\$\(\1\)/g")" + EVAL_TEMPLATE="$(eval "echo \"$EVAL_TEMPLATE\"")" # Turn function name unto snake_case - formatted_template=$(echo "$eval_template" | sed -e ':loop' -e 's/\(fn[^(]*\)[ -]/\1_/g' -e 't loop' | sed 's/fn_/fn /') + FORMATTED_TEMPLATE=$(echo "$EVAL_TEMPLATE" | sed -e ':loop' -e 's/\(fn[^(]*\)[ -]/\1_/g' -e 't loop' | sed 's/fn_/fn /') # Push to file - echo "$formatted_template" >>"$TEST_FILE" + echo "$FORMATTED_TEMPLATE" >>"$TEST_FILE" printf "\\n" >>"$TEST_FILE" done diff --git a/bin/generator-utils/colors.sh b/bin/generator-utils/colors.sh index 3ae992587..c3b2f4079 100644 --- a/bin/generator-utils/colors.sh +++ b/bin/generator-utils/colors.sh @@ -1,38 +1,14 @@ #!/usr/bin/env bash -# shellcheck disable=SC2034 -# Reset -RESET=$(echo -e '\033[0m') +reset_color=$(echo -e '\033[0m') -# Regular Colors -BLACK=$(echo -e '\033[0;30m') -RED=$(echo -e '\033[0;31m') -GREEN=$(echo -e '\033[0;32m') -YELLOW=$(echo -e '\033[0;33m') -BLUE=$(echo -e '\033[0;34m') -PURPLE=$(echo -e '\033[0;35m') -CYAN=$(echo -e '\033[0;36m') -WHITE=$(echo -e '\033[0;37m') +red=$(echo -e '\033[0;31m') +green=$(echo -e '\033[0;32m') +yellow=$(echo -e '\033[0;33m') +blue=$(echo -e '\033[0;34m') +cyan=$(echo -e '\033[0;36m') -# Bold -BOLD_BLACK=$(echo -e '\033[1;30m') -BOLD_RED=$(echo -e '\033[1;31m') -BOLD_GREEN=$(echo -e '\033[1;32m') -BOLD_YELLOW=$(echo -e '\033[1;33m') -BOLD_BLUE=$(echo -e '\033[1;34m') -BOLD_PURPLE=$(echo -e '\033[1;35m') -BOLD_CYAN=$(echo -e '\033[1;36m') -BOLD_WHITE=$(echo -e '\033[1;37m') +bold_green=$(echo -e '\033[1;32m') -# Underline -UNDERLINE=$(echo -e ='\033[4m') -# Background -BG_BLACK=$(echo -e ='\033[40m') -BG_RED=$(echo -e ='\033[41m') -BG_GREEN=$(echo -e ='\033[42m') -BG_YELLOW=$(echo -e ='\033[43m') -BG_BLUE=$(echo -e ='\033[44m') -BG_PURPLE=$(echo -e ='\033[45m') -BG_CYAN=$(echo -e ='\033[46m') -BG_WHITE=$(echo -e ='\033[47m') +export red green blue yellow bold_green reset_color cyan \ No newline at end of file diff --git a/bin/generator-utils/prompts.sh b/bin/generator-utils/prompts.sh index c785a2636..9835e286f 100644 --- a/bin/generator-utils/prompts.sh +++ b/bin/generator-utils/prompts.sh @@ -1,22 +1,22 @@ #!/usr/bin/env bash -# shellcheck source=/dev/null +# shellcheck source=./colors.sh source ./bin/generator-utils/colors.sh -function get_exercise_difficulty() { +get_exercise_difficulty() { read -rp "Difficulty of exercise (1-10): " exercise_difficulty echo "$exercise_difficulty" } -function validate_difficulty_input() { +validate_difficulty_input() { - valid_input=false + local valid_input=false while ! $valid_input; do if [[ "$1" =~ ^[1-9]$|^10$ ]]; then - exercise_difficulty=$1 - valid_input=true + local exercise_difficulty=$1 + local valid_input=true else - read -rp "${RED}Invalid input. ${RESET}Please enter an integer between 1 and 10. " exercise_difficulty + read -rp "${red}Invalid input. ${reset_color}Please enter an integer between 1 and 10. " exercise_difficulty [[ "$exercise_difficulty" =~ ^[1-9]$|^10$ ]] && valid_input=true fi @@ -24,15 +24,16 @@ function validate_difficulty_input() { echo "$exercise_difficulty" } -function get_author_handle { - DEFAULT_AUTHOR_HANDLE="$(git config user.name)" +get_author_handle() { + default_author_handle="$(git config user.name)" + local default_author_handle - if [ -z "$DEFAULT_AUTHOR_HANDLE" ]; then - read -rp "Hey! Couldn't find your Github handle. Add it now or skip with enter and add it later in the .meta.config.json file: " AUTHOR_HANDLE + if [ -z "$default_author_handle" ]; then + read -rp "Hey! Couldn't find your Github handle. Add it now or skip with enter and add it later in the .meta.config.json file: " author_handle else - AUTHOR_HANDLE="$DEFAULT_AUTHOR_HANDLE" + local author_handle="$default_author_handle" fi - echo "$AUTHOR_HANDLE" + echo "$author_handle" } diff --git a/bin/generator-utils/templates.sh b/bin/generator-utils/templates.sh index 5c957314a..ed51a2319 100755 --- a/bin/generator-utils/templates.sh +++ b/bin/generator-utils/templates.sh @@ -1,14 +1,15 @@ #!/usr/bin/env bash -# shellcheck source=/dev/null +# shellcheck source=./utils.sh source ./bin/generator-utils/utils.sh -function create_fn_name() { - slug=$1 - has_canonical_data=$2 +create_fn_name() { + local slug=$1 + local has_canonical_data=$2 if [ "$has_canonical_data" == false ]; then fn_name=$(dash_to_underscore "$slug") + local fn_name else fn_name=$(jq -r 'first(.. | .property? // empty)' canonical_data.json) fi @@ -17,7 +18,7 @@ function create_fn_name() { } -function create_test_file_template() { +create_test_file_template() { local exercise_dir=$1 local slug=$2 local has_canonical_data=$3 @@ -40,12 +41,15 @@ EOT message "success" "Stub file for tests has been created!" else canonical_json=$(cat canonical_data.json) + local canonical_json # sometimes canonical data has multiple levels with multiple `cases` arrays. #(see kindergarten-garden https://github.com/exercism/problem-specifications/blob/main/exercises/kindergarten-garden/canonical-data.json) # so let's flatten it cases=$(echo "$canonical_json" | jq '[ .. | objects | with_entries(select(.key | IN("uuid", "description", "input", "expected"))) | select(. != {}) | select(has("uuid")) ]') + local cases fn_name=$(echo "$canonical_json" | jq -r 'first(.. | .property? // empty)') + local fn_name first_iteration=true # loop through each object @@ -108,6 +112,7 @@ function create_example_rs_template() { local has_canonical_data=$3 fn_name=$(create_fn_name "$slug" "$has_canonical_data") + local fn_name mkdir "${exercise_dir}/.meta" cat <"${exercise_dir}/.meta/example.rs" diff --git a/bin/generator-utils/utils.sh b/bin/generator-utils/utils.sh index e89b33e67..f0c2713c0 100755 --- a/bin/generator-utils/utils.sh +++ b/bin/generator-utils/utils.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# shellcheck source=/dev/null +# shellcheck source=./colors.sh source ./bin/generator-utils/colors.sh function message() { @@ -8,16 +8,19 @@ function message() { local message=$2 case "$flag" in - "success") printf "${GREEN}%s${RESET}\n" "[success]: $message" ;; - "info") printf "${BLUE}%s${RESET}\n" "[info]: $message" ;; - "warning") printf "${YELLOW}%s${RESET}\n" "[warning]: $message" ;; - "error") printf "${RED}%s${RESET}\n" "[error]: $message" ;; + "success") printf "${green}%s${reset_color}\n" "[success]: $message" ;; + "task") printf "${cyan}%s${reset_color}\n" "[task]: $message" ;; + "info") printf "${blue}%s${reset_color}\n" "[info]: $message" ;; + "warning") printf "${yellow}%s${reset_color}\n" "[warning]: $message" ;; + "error") printf "${red}%s${reset_color}\n" "[error]: $message" ;; "done") echo + # Create a dashed-line as wide as the screen cols=$(tput cols) + local cols printf "%*s\n" "$cols" "" | tr " " "-" echo - printf "${BOLD_GREEN}%s${RESET}\n" "[done]: $message" + printf "${bold_green}%s${reset_color}\n" "[done]: $message" ;; *) echo "Invalid flag: $flag" @@ -25,19 +28,18 @@ function message() { esac } -function dash_to_underscore() { - # shellcheck disable=SC2001 +dash_to_underscore() { echo "$1" | sed 's/-/_/g' } # exercise_name -> Exercise Name -function format_exercise_name { +format_exercise_name() { echo "$1" | sed 's/-/ /g; s/\b\(.\)/\u\1/g' } -function check_exercise_existence() { +check_exercise_existence() { message "info" "Looking for exercise.." - slug="$1" + local slug="$1" # Check if exercise is already in config.json if jq '.exercises.practice | map(.slug)' config.json | grep -q "$slug"; then echo "${1} has already been implemented." @@ -45,6 +47,7 @@ function check_exercise_existence() { fi # fetch configlet and crop out exercise list + local unimplemented_exercises unimplemented_exercises=$(bin/configlet info | sed -n '/With canonical data:/,/Track summary:/p' | sed -e '/\(With\(out\)\? canonical data:\|Track summary:\)/d' -e '/^$/d') if echo "$unimplemented_exercises" | grep -q "^$slug$"; then message "success" "Exercise has been found!" @@ -57,11 +60,11 @@ ${unimplemented_exercises}" # see util/ngram for source # First it builds a binary for the system of the contributor if [ -e bin/generator-utils/ngram ]; then - echo "${YELLOW}$(bin/generator-utils/ngram "${unimplemented_exercises}" "$slug")${RESET}" + echo "${yellow}$(bin/generator-utils/ngram "${unimplemented_exercises}" "$slug")${reset_color}" else message "info" "Building typo-checker binary for $(uname -m) system." - cd util/ngram && ./build && cd ../.. && echo "${YELLOW}$(bin/generator-utils/ngram "${unimplemented_exercises}" "$slug")${RESET}" + cd util/ngram && ./build && cd ../.. && echo "${yellow}$(bin/generator-utils/ngram "${unimplemented_exercises}" "$slug")${reset_color}" fi exit 1 From 1a2657ce6f3cd57b5f0e0b37aa24a30ffe170351 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Thu, 9 Mar 2023 21:29:57 +0100 Subject: [PATCH 058/436] Fix local variables --- bin/generator-utils/prompts.sh | 2 +- bin/generator-utils/templates.sh | 9 +++++---- bin/generator-utils/utils.sh | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/bin/generator-utils/prompts.sh b/bin/generator-utils/prompts.sh index 9835e286f..30ac172ae 100644 --- a/bin/generator-utils/prompts.sh +++ b/bin/generator-utils/prompts.sh @@ -25,8 +25,8 @@ validate_difficulty_input() { } get_author_handle() { - default_author_handle="$(git config user.name)" local default_author_handle + default_author_handle="$(git config user.name)" if [ -z "$default_author_handle" ]; then read -rp "Hey! Couldn't find your Github handle. Add it now or skip with enter and add it later in the .meta.config.json file: " author_handle diff --git a/bin/generator-utils/templates.sh b/bin/generator-utils/templates.sh index ed51a2319..c96576d8d 100755 --- a/bin/generator-utils/templates.sh +++ b/bin/generator-utils/templates.sh @@ -40,16 +40,16 @@ EOT message "info" "This exercise doesn't have canonical data." message "success" "Stub file for tests has been created!" else - canonical_json=$(cat canonical_data.json) local canonical_json + canonical_json=$(cat canonical_data.json) # sometimes canonical data has multiple levels with multiple `cases` arrays. #(see kindergarten-garden https://github.com/exercism/problem-specifications/blob/main/exercises/kindergarten-garden/canonical-data.json) # so let's flatten it - cases=$(echo "$canonical_json" | jq '[ .. | objects | with_entries(select(.key | IN("uuid", "description", "input", "expected"))) | select(. != {}) | select(has("uuid")) ]') local cases - fn_name=$(echo "$canonical_json" | jq -r 'first(.. | .property? // empty)') + cases=$(echo "$canonical_json" | jq '[ .. | objects | with_entries(select(.key | IN("uuid", "description", "input", "expected"))) | select(. != {}) | select(has("uuid")) ]') local fn_name + fn_name=$(echo "$canonical_json" | jq -r 'first(.. | .property? // empty)') first_iteration=true # loop through each object @@ -82,6 +82,7 @@ function create_lib_rs_template() { local exercise_dir=$1 local slug=$2 local has_canonical_data=$3 + local fn_name fn_name=$(create_fn_name "$slug" "$has_canonical_data") cat <"${exercise_dir}/src/lib.rs" pub fn ${fn_name}() { @@ -111,8 +112,8 @@ function create_example_rs_template() { local slug=$2 local has_canonical_data=$3 - fn_name=$(create_fn_name "$slug" "$has_canonical_data") local fn_name + fn_name=$(create_fn_name "$slug" "$has_canonical_data") mkdir "${exercise_dir}/.meta" cat <"${exercise_dir}/.meta/example.rs" diff --git a/bin/generator-utils/utils.sh b/bin/generator-utils/utils.sh index f0c2713c0..02876ee3a 100755 --- a/bin/generator-utils/utils.sh +++ b/bin/generator-utils/utils.sh @@ -16,8 +16,8 @@ function message() { "done") echo # Create a dashed-line as wide as the screen - cols=$(tput cols) local cols + cols=$(tput cols) printf "%*s\n" "$cols" "" | tr " " "-" echo printf "${bold_green}%s${reset_color}\n" "[done]: $message" From e7605c32d95b08349e8d263ceb65debb3e9abe2c Mon Sep 17 00:00:00 2001 From: dem4ron Date: Thu, 9 Mar 2023 21:38:44 +0100 Subject: [PATCH 059/436] Lowercase var names in `bin/generate_tests`, remove redundant shellcheck disable --- bin/generate_tests | 40 ++++++++++++++++++------------------ bin/generator-utils/utils.sh | 6 +++--- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/bin/generate_tests b/bin/generate_tests index 809406d77..91283d567 100755 --- a/bin/generate_tests +++ b/bin/generate_tests @@ -9,44 +9,44 @@ source ./bin/generator-utils/utils.sh digest_template() { template=$(cat bin/test_template) - # turn every token into a jq command + # Turn every token into a jq command echo "$template" | sed 's/${\([^}]*\)\}\$/$(echo $case | jq -r '\''.\1'\'')/g' } message "info" "Generating tests.." -CANONICAL_JSON=$(cat canonical_data.json) -SLUG=$(echo "$CANONICAL_JSON" | jq '.exercise') -# shellcheck disable=SC2001 +canonical_json=$(cat canonical_data.json) +slug=$(echo "$canonical_json" | jq '.exercise') # Remove double quotes -SLUG=$(echo "$SLUG" | sed 's/"//g') -EXERCISE_DIR="exercises/practice/$SLUG" -TEST_FILE="$EXERCISE_DIR/tests/$SLUG.rs" +slug=$(echo "$slug" | sed 's/"//g') +exercise_dir="exercises/practice/$slug" +test_file="$exercise_dir/tests/$slug.rs" -cat <"$TEST_FILE" -use $(dash_to_underscore "$SLUG")::*; +cat <"$test_file" +use $(dash_to_underscore "$slug")::*; // Add tests here EOT # Flattens canonical json, extracts only the objects with a uuid -CASES=$(echo "$CANONICAL_JSON" | jq '[ .. | objects | with_entries(select(.key | IN("uuid", "description", "input", "expected", "property"))) | select(. != {}) | select(has("uuid")) ]') +cases=$(echo "$canonical_json" | jq '[ .. | objects | with_entries(select(.key | IN("uuid", "description", "input", "expected", "property"))) | select(. != {}) | select(has("uuid")) ]') +# Shellcheck doesn't recognize that `case` is not unused # shellcheck disable=SC2034 -jq -c '.[]' <<<"$CASES" | while read -r case; do +jq -c '.[]' <<<"$cases" | while read -r case; do # Evaluate the bash parts and replace them with their return values - EVAL_TEMPLATE="$(digest_template | sed -e "s/\$(\(.*\))/\$\(\1\)/g")" - EVAL_TEMPLATE="$(eval "echo \"$EVAL_TEMPLATE\"")" + eval_template="$(digest_template | sed -e "s/\$(\(.*\))/\$\(\1\)/g")" + eval_template="$(eval "echo \"$eval_template\"")" - # Turn function name unto snake_case - FORMATTED_TEMPLATE=$(echo "$EVAL_TEMPLATE" | sed -e ':loop' -e 's/\(fn[^(]*\)[ -]/\1_/g' -e 't loop' | sed 's/fn_/fn /') - # Push to file + # Turn function name into snake_case + formatted_template=$(echo "$eval_template" | sed -e ':loop' -e 's/\(fn[^(]*\)[ -]/\1_/g' -e 't loop' | sed 's/fn_/fn /') - echo "$FORMATTED_TEMPLATE" >>"$TEST_FILE" - printf "\\n" >>"$TEST_FILE" + # Push to test file + echo "$formatted_template" >>"$test_file" + printf "\\n" >>"$test_file" done -rustfmt "$TEST_FILE" +rustfmt "$test_file" -message "success" "Generated tests successfully! Check out ${TEST_FILE}" +message "success" "Generated tests successfully! Check out ${test_file}" diff --git a/bin/generator-utils/utils.sh b/bin/generator-utils/utils.sh index 02876ee3a..8e4cec1f7 100755 --- a/bin/generator-utils/utils.sh +++ b/bin/generator-utils/utils.sh @@ -15,7 +15,7 @@ function message() { "error") printf "${red}%s${reset_color}\n" "[error]: $message" ;; "done") echo - # Create a dashed-line as wide as the screen + # Generate a dashed line that spans the entire width of the screen. local cols cols=$(tput cols) printf "%*s\n" "$cols" "" | tr " " "-" @@ -46,7 +46,7 @@ check_exercise_existence() { exit 1 fi - # fetch configlet and crop out exercise list + # Fetch configlet and crop out exercise list local unimplemented_exercises unimplemented_exercises=$(bin/configlet info | sed -n '/With canonical data:/,/Track summary:/p' | sed -e '/\(With\(out\)\? canonical data:\|Track summary:\)/d' -e '/^$/d') if echo "$unimplemented_exercises" | grep -q "^$slug$"; then @@ -57,7 +57,7 @@ check_exercise_existence() { ${unimplemented_exercises}" # Find closest match to typed-in not-found slug - # see util/ngram for source + # See util/ngram for source # First it builds a binary for the system of the contributor if [ -e bin/generator-utils/ngram ]; then echo "${yellow}$(bin/generator-utils/ngram "${unimplemented_exercises}" "$slug")${reset_color}" From d49f6ef59027d7e707d33798ac09f75dcd410c11 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Thu, 9 Mar 2023 21:41:45 +0100 Subject: [PATCH 060/436] remove `function` from functions --- bin/generate_tests | 1 + bin/generator-utils/templates.sh | 8 ++++---- bin/generator-utils/utils.sh | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/bin/generate_tests b/bin/generate_tests index 91283d567..3a0992c07 100755 --- a/bin/generate_tests +++ b/bin/generate_tests @@ -8,6 +8,7 @@ set -euo pipefail source ./bin/generator-utils/utils.sh digest_template() { + local template template=$(cat bin/test_template) # Turn every token into a jq command echo "$template" | sed 's/${\([^}]*\)\}\$/$(echo $case | jq -r '\''.\1'\'')/g' diff --git a/bin/generator-utils/templates.sh b/bin/generator-utils/templates.sh index c96576d8d..922bc3c42 100755 --- a/bin/generator-utils/templates.sh +++ b/bin/generator-utils/templates.sh @@ -78,7 +78,7 @@ EOT } -function create_lib_rs_template() { +create_lib_rs_template() { local exercise_dir=$1 local slug=$2 local has_canonical_data=$3 @@ -92,7 +92,7 @@ EOT message "success" "Stub file for lib.rs has been created!" } -function overwrite_gitignore() { +overwrite_gitignore() { local exercise_dir=$1 cat <"$exercise_dir"/.gitignore # Generated by Cargo @@ -107,7 +107,7 @@ EOT message "success" ".gitignore has been overwritten!" } -function create_example_rs_template() { +create_example_rs_template() { local exercise_dir=$1 local slug=$2 local has_canonical_data=$3 @@ -126,7 +126,7 @@ EOT message "success" "Stub file for example.rs has been created!" } -function create_rust_files() { +create_rust_files() { local exercise_dir=$1 local slug=$2 local has_canonical_data=$3 diff --git a/bin/generator-utils/utils.sh b/bin/generator-utils/utils.sh index 8e4cec1f7..bae45efb9 100755 --- a/bin/generator-utils/utils.sh +++ b/bin/generator-utils/utils.sh @@ -3,7 +3,7 @@ # shellcheck source=./colors.sh source ./bin/generator-utils/colors.sh -function message() { +message() { local flag=$1 local message=$2 From 109691d0f2ccb79f8d18e6bdb3a5198fa39ba5c9 Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Fri, 3 Mar 2023 06:35:45 -0600 Subject: [PATCH 061/436] secret-handshake: add approach (#1636) Add approach to `secret-handshake` --- .../secret-handshake/.approaches/config.json | 15 +++ .../.approaches/introduction.md | 39 ++++++++ .../.approaches/iterate-once/content.md | 97 +++++++++++++++++++ .../.approaches/iterate-once/snippet.txt | 4 + 4 files changed, 155 insertions(+) create mode 100644 exercises/practice/secret-handshake/.approaches/config.json create mode 100644 exercises/practice/secret-handshake/.approaches/introduction.md create mode 100644 exercises/practice/secret-handshake/.approaches/iterate-once/content.md create mode 100644 exercises/practice/secret-handshake/.approaches/iterate-once/snippet.txt diff --git a/exercises/practice/secret-handshake/.approaches/config.json b/exercises/practice/secret-handshake/.approaches/config.json new file mode 100644 index 000000000..484a72695 --- /dev/null +++ b/exercises/practice/secret-handshake/.approaches/config.json @@ -0,0 +1,15 @@ +{ + "introduction": { + "authors": ["bobahop"], + "contributors": [] + }, + "approaches": [ + { + "uuid": "cd0c0a31-0905-47f0-815c-02597a7cdf40", + "slug": "iterate-once", + "title": "Iterate once", + "blurb": "Iterate once even when reversed.", + "authors": ["bobahop"] + } + ] +} diff --git a/exercises/practice/secret-handshake/.approaches/introduction.md b/exercises/practice/secret-handshake/.approaches/introduction.md new file mode 100644 index 000000000..7c7d34ff8 --- /dev/null +++ b/exercises/practice/secret-handshake/.approaches/introduction.md @@ -0,0 +1,39 @@ +# Introduction + +There are many ways to solve Secret Handshake. +One approach is to iterate only once, even when the signs are to be reversed. + +## General guidance + +Something to consider is to keep the number of iterations at a minimum to get the best performance. +However, if that is felt to adversely impact readability, then to use a series of `if` statements and then reverse is also valid. + +## Approach: Iterate once + +```rust +const SIGNS: [&'static str; 4] = ["wink", "double blink", "close your eyes", "jump"]; +const REVERSE_SIGNS: u8 = 16; + +pub fn actions(n: u8) -> Vec<&'static str> { + let (mut action, action_incr, end) = match n & REVERSE_SIGNS { + 0 => (0, 1, 4), + _ => (3, -1, -1), + }; + let mut output: Vec<&'static str> = Vec::new(); + + loop { + if action == end { + break; + } + if (n & (1 << action)) != 0 { + output.push(SIGNS[action as usize]) + } + action += action_incr + } + output +} +``` + +For more information, check the [Iterate once approach][approach-iterate-once]. + +[approach-iterate-once]: https://exercism.org/tracks/rust/exercises/secret-handshake/approaches/iterate-once diff --git a/exercises/practice/secret-handshake/.approaches/iterate-once/content.md b/exercises/practice/secret-handshake/.approaches/iterate-once/content.md new file mode 100644 index 000000000..f6430f2ab --- /dev/null +++ b/exercises/practice/secret-handshake/.approaches/iterate-once/content.md @@ -0,0 +1,97 @@ +# Iterate once + +```rust +const SIGNS: [&'static str; 4] = ["wink", "double blink", "close your eyes", "jump"]; +const REVERSE_SIGNS: u8 = 16; + +pub fn actions(n: u8) -> Vec<&'static str> { + let (mut action, action_incr, end) = match n & REVERSE_SIGNS { + 0 => (0, 1, 4), + _ => (3, -1, -1), + }; + let mut output: Vec<&'static str> = Vec::new(); + + loop { + if action == end { + break; + } + if (n & (1 << action)) != 0 { + output.push(SIGNS[action as usize]) + } + action += action_incr + } + output +} +``` + +This approach starts by defining a fixed-size [array][array] to hold the signal values in normal order. + +The `[&'static str; 4]` is used to give the type and length of the array. +To be a [`const`][const], the size of the array must be known at compile time, so setting the type and length must be done explicitly, +so the size in bytes of the fixed array can be deduced by the compiler from that and not by inspecting the element types and counting +the elements itself. + +The value of `16` is defined as a `const` with a meaningful name so it won't be used as a [magic number][magic-number]. + +The `actions` function uses multiple assignment with a `match` expression to define the variables that control iterating through the signals array, +setting their values to iterate in either the normal or reverse order. + +The [bitwise AND operator][bitand] is used to check if the input number contains the signal for reversing the order of the other signals. + +For example, if the number passed in is `19`, which is `10011` in binary, then it is ANDed with `16`, which is `10000` in binary. +The `1` in `10000` is also at the same position in `10011`, so the two values ANDed will not be `0`. +- `10011` AND +- `10000` = +- `10000` + +If the number passed in is `3`, which is `00011` in binary, then it is ANDed with `16`, which is `10000` in binary. +The `1` in `10000` is not at the same position in `00011`, so the two values ANDed will be `0`. +- `00011` AND +- `10000` = +- `00000` + +If the number passed in does not contain the signal for reverse, then the iteration variables are set to iterate through the array of signals +in their normal order, otherwise they are set to iterate through the arrray backwards.. + +The output `vector` is defined, and then the [`loop`][loop] begins. + +Normal iteration will start at index `0`. +Reverse iteration will start at index `3`. + +Normal iteration will terminate when the index equals `4`. +Reverse iteration will terminate when the index equals `-1`. + +Normal iteration will increase the index by `1` for each iteration. +Reverse iteration will decrease the index by `1` for each iteration. + +For each iteration of the `loop`, the AND operator is used to check if the number passed in contains `1` [shifted left][shl] (`<<`) for the number of positions +as the value being iterated. + +```rust +if (n & (1 << action)) != 0 { + output.push(SIGNS[action as usize]) +} +``` + +For example, if the number being iterated is `0`, then `1` is shifted left `0` times (so not shifted at all), and the number passed in is ANDed with `00001`. +If the number passed in is `3`, which is `00011` in binary, then it is ANDed with `00001`. +`00011` ANDed with `00001` is not equal to `0`, so the signal at the index of the array of signals is added to the output `vector`. +The index used is the number being iterated, which is `0`, so the element at index `0` (`"wink"`) would be added to the output `vector` +using the [push][push] function. + +If the number being iterated is `1`, then `1` is shifted left `1` time, and the number passed in is ANDed with `00010`. +If the number passed in is `3`, which is `00011` in binary, then it is ANDed with `00010`. +`00011` ANDed with `00010` is not equal to `0`, so the signal at the index of the array of signals is added to the output `vector`. +The index used is the number being iterated, which is `1`, so the element at index `1` (`"double blink"`) would be added to the output `vector`. + +If the number passed in ANDed with the number being iterated is equal to `0`, then the signal in the array for that index is not added to the output `vector`. + +After iterating through the array of signals is done, the output `vector` is returned from the function. + +[array]: https://doc.rust-lang.org/std/primitive.array.html +[const]: https://doc.rust-lang.org/std/keyword.const.html +[magic-number]: https://en.wikipedia.org/wiki/Magic_number_(programming) +[bitand]: https://doc.rust-lang.org/std/ops/trait.BitAnd.html +[shl]: https://doc.rust-lang.org/std/ops/trait.Shl.html +[loop]: https://doc.rust-lang.org/rust-by-example/flow_control/loop.html +[push]: https://doc.rust-lang.org/std/vec/struct.Vec.html#method.push diff --git a/exercises/practice/secret-handshake/.approaches/iterate-once/snippet.txt b/exercises/practice/secret-handshake/.approaches/iterate-once/snippet.txt new file mode 100644 index 000000000..e42302ba8 --- /dev/null +++ b/exercises/practice/secret-handshake/.approaches/iterate-once/snippet.txt @@ -0,0 +1,4 @@ +let (mut action, action_incr, end) = match n & REVERSE_SIGNS { + 0 => (0, 1, 4), + _ => (3, -1, -1), +}; From 1c371c986e128f29cadd2d7e212b4fcadf59bd03 Mon Sep 17 00:00:00 2001 From: nhawkes Date: Mon, 6 Mar 2023 09:50:15 +0000 Subject: [PATCH 062/436] paasio: Add PhantomData explanation (#1637) Same as the explanation present in fizzy --- exercises/practice/paasio/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/exercises/practice/paasio/src/lib.rs b/exercises/practice/paasio/src/lib.rs index 8196a6c96..f530460da 100644 --- a/exercises/practice/paasio/src/lib.rs +++ b/exercises/practice/paasio/src/lib.rs @@ -1,5 +1,8 @@ use std::io::{Read, Result, Write}; +// the PhantomData instances in this file are just to stop compiler complaints +// about missing generics; feel free to remove them + pub struct ReadStats(::std::marker::PhantomData); impl ReadStats { From bae97ee3ecea4e5e9f7c66fe906aa685dc8a6929 Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Mon, 6 Mar 2023 21:54:38 +0100 Subject: [PATCH 063/436] Sync bob docs with problem-specifications (#1633) The bob exercise has been overhauled as part of a project to make practice exercises more consistent and friendly. For more context, please see the discussion in the forum, as well as the pull request that updated the exercise in the problem-specifications repository: - https://forum.exercism.org/t/new-project-making-practice-exercises-more-consistent-and-human-across-exercism/3943 - https://github.com/exercism/problem-specifications/pull/2205 --- exercises/practice/bob/.docs/instructions.md | 27 +++++++++++--------- exercises/practice/bob/.docs/introduction.md | 10 ++++++++ 2 files changed, 25 insertions(+), 12 deletions(-) create mode 100644 exercises/practice/bob/.docs/introduction.md diff --git a/exercises/practice/bob/.docs/instructions.md b/exercises/practice/bob/.docs/instructions.md index edddb1413..bb702f7bb 100644 --- a/exercises/practice/bob/.docs/instructions.md +++ b/exercises/practice/bob/.docs/instructions.md @@ -1,16 +1,19 @@ # Instructions -Bob is a lackadaisical teenager. In conversation, his responses are very limited. +Your task is to determine what Bob will reply to someone when they say something to him or ask him a question. -Bob answers 'Sure.' if you ask him a question, such as "How are you?". +Bob only ever answers one of five things: -He answers 'Whoa, chill out!' if you YELL AT HIM (in all capitals). - -He answers 'Calm down, I know what I'm doing!' if you yell a question at him. - -He says 'Fine. Be that way!' if you address him without actually saying -anything. - -He answers 'Whatever.' to anything else. - -Bob's conversational partner is a purist when it comes to written communication and always follows normal rules regarding sentence punctuation in English. +- **"Sure."** + This is his response if you ask him a question, such as "How are you?" + The convention used for questions is that it ends with a question mark. +- **"Whoa, chill out!"** + This is his answer if you YELL AT HIM. + The convention used for yelling is ALL CAPITAL LETTERS. +- **"Calm down, I know what I'm doing!"** + This is what he says if you yell a question at him. +- **"Fine. Be that way!"** + This is how he responds to silence. + The convention used for silence is nothing, or various combinations of whitespace characters. +- **"Whatever."** + This is what he answers to anything else. diff --git a/exercises/practice/bob/.docs/introduction.md b/exercises/practice/bob/.docs/introduction.md new file mode 100644 index 000000000..ea4a80776 --- /dev/null +++ b/exercises/practice/bob/.docs/introduction.md @@ -0,0 +1,10 @@ +# Introduction + +Bob is a [lackadaisical][] teenager. +He likes to think that he's very cool. +And he definitely doesn't get excited about things. +That wouldn't be cool. + +When people talk to him, his responses are pretty limited. + +[lackadaisical]: https://www.collinsdictionary.com/dictionary/english/lackadaisical From 28e766306477c98afd59d59c43c2b1a3d79c8524 Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Mon, 6 Mar 2023 21:54:54 +0100 Subject: [PATCH 064/436] Sync gigasecond docs with problem-specifications (#1632) The gigasecond exercise has been overhauled as part of a project to make practice exercises more consistent and friendly. For more context, please see the discussion in the forum, as well as the pull request that updated the exercise in the problem-specifications repository: - https://forum.exercism.org/t/new-project-making-practice-exercises-more-consistent-and-human-across-exercism/3943 - https://github.com/exercism/problem-specifications/pull/2206 --- .../practice/gigasecond/.docs/instructions.md | 8 ++++--- .../practice/gigasecond/.docs/introduction.md | 24 +++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 exercises/practice/gigasecond/.docs/introduction.md diff --git a/exercises/practice/gigasecond/.docs/instructions.md b/exercises/practice/gigasecond/.docs/instructions.md index 680870f3a..1e20f0022 100644 --- a/exercises/practice/gigasecond/.docs/instructions.md +++ b/exercises/practice/gigasecond/.docs/instructions.md @@ -1,6 +1,8 @@ # Instructions -Given a moment, determine the moment that would be after a gigasecond -has passed. +Your task is to determine the date and time one gigasecond after a certain date. -A gigasecond is 10^9 (1,000,000,000) seconds. +A gigasecond is one thousand million seconds. +That is a one with nine zeros after it. + +If you were born on _January 24th, 2015 at 22:00 (10:00:00pm)_, then you would be a gigasecond old on _October 2nd, 2046 at 23:46:40 (11:46:40pm)_. diff --git a/exercises/practice/gigasecond/.docs/introduction.md b/exercises/practice/gigasecond/.docs/introduction.md new file mode 100644 index 000000000..74afaa994 --- /dev/null +++ b/exercises/practice/gigasecond/.docs/introduction.md @@ -0,0 +1,24 @@ +# Introduction + +The way we measure time is kind of messy. +We have 60 seconds in a minute, and 60 minutes in an hour. +This comes from ancient Babylon, where they used 60 as the basis for their number system. +We have 24 hours in a day, 7 days in a week, and how many days in a month? +Well, for days in a month it depends not only on which month it is, but also on what type of calendar is used in the country you live in. + +What if, instead, we only use seconds to express time intervals? +Then we can use metric system prefixes for writing large numbers of seconds in more easily comprehensible quantities. + +- A food recipe might explain that you need to let the brownies cook in the oven for two kiloseconds (that's two thousand seconds). +- Perhaps you and your family would travel to somewhere exotic for two megaseconds (that's two million seconds). +- And if you and your spouse were married for _a thousand million_ seconds, you would celebrate your one gigasecond anniversary. + +```exercism/note +If we ever colonize Mars or some other planet, measuring time is going to get even messier. +If someone says "year" do they mean a year on Earth or a year on Mars? + +The idea for this exercise came from the science fiction novel ["A Deepness in the Sky"][vinge-novel] by author Vernor Vinge. +In it the author uses the metric system as the basis for time measurements. + +[vinge-novel]: https://www.tor.com/2017/08/03/science-fiction-with-something-for-everyone-a-deepness-in-the-sky-by-vernor-vinge/ +``` From 802d52912614d565b1698c09b852ef40f73483a5 Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Mon, 6 Mar 2023 21:58:13 +0100 Subject: [PATCH 065/436] Sync pangram docs with problem-specifications (#1638) The pangram exercise has been overhauled as part of a project to make practice exercises more consistent and friendly. For more context, please see the discussion in the forum, as well as the pull request that updated the exercise in the problem-specifications repository: - https://forum.exercism.org/t/new-project-making-practice-exercises-more-consistent-and-human-across-exercism/3943 - https://github.com/exercism/problem-specifications/pull/2215 --- exercises/practice/pangram/.docs/instructions.md | 11 +++++------ exercises/practice/pangram/.docs/introduction.md | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 6 deletions(-) create mode 100644 exercises/practice/pangram/.docs/introduction.md diff --git a/exercises/practice/pangram/.docs/instructions.md b/exercises/practice/pangram/.docs/instructions.md index 5c3bbde35..d5698bc2a 100644 --- a/exercises/practice/pangram/.docs/instructions.md +++ b/exercises/practice/pangram/.docs/instructions.md @@ -1,9 +1,8 @@ # Instructions -Determine if a sentence is a pangram. A pangram (Greek: παν γράμμα, pan gramma, -"every letter") is a sentence using every letter of the alphabet at least once. -The best known English pangram is: -> The quick brown fox jumps over the lazy dog. +Your task is to figure out if a sentence is a pangram. -The alphabet used consists of ASCII letters `a` to `z`, inclusive, and is case -insensitive. Any characters which are not an ASCII letter should be ignored. +A pangram is a sentence using every letter of the alphabet at least once. +It is case insensitive, so it doesn't matter if a letter is lower-case (e.g. `k`) or upper-case (e.g. `K`). + +For this exercise we only use the basic letters used in the English alphabet: `a` to `z`. diff --git a/exercises/practice/pangram/.docs/introduction.md b/exercises/practice/pangram/.docs/introduction.md new file mode 100644 index 000000000..d38fa341d --- /dev/null +++ b/exercises/practice/pangram/.docs/introduction.md @@ -0,0 +1,16 @@ +# Introduction + +You work for a company that sells fonts through their website. +They'd like to show a different sentence each time someone views a font on their website. +To give a comprehensive sense of the font, the random sentences should use **all** the letters in the English alphabet. + +They're running a competition to get suggestions for sentences that they can use. +You're in charge of checking the submissions to see if they are valid. + +```exercism/note +Pangram comes from Greek, παν γράμμα, pan gramma, which means "every letter". + +The best known English pangram is: + +> The quick brown fox jumps over the lazy dog. +``` From 3eb6c5417efc1393caf1a3a7d66b82a6310a4c69 Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Tue, 7 Mar 2023 11:06:54 +0100 Subject: [PATCH 066/436] Sync sieve docs with problem-specifications (#1640) The sieve exercise has been overhauled as part of a project to make practice exercises more consistent and friendly. For more context, please see the discussion in the forum, as well as the pull request that updated the exercise in the problem-specifications repository: - https://forum.exercism.org/t/new-project-making-practice-exercises-more-consistent-and-human-across-exercism/3943 - https://github.com/exercism/problem-specifications/pull/2216 --- .../practice/sieve/.docs/instructions.md | 38 +++++++++---------- .../practice/sieve/.docs/introduction.md | 7 ++++ 2 files changed, 25 insertions(+), 20 deletions(-) create mode 100644 exercises/practice/sieve/.docs/introduction.md diff --git a/exercises/practice/sieve/.docs/instructions.md b/exercises/practice/sieve/.docs/instructions.md index 7228737a2..ec14620ce 100644 --- a/exercises/practice/sieve/.docs/instructions.md +++ b/exercises/practice/sieve/.docs/instructions.md @@ -1,30 +1,28 @@ # Instructions -Use the Sieve of Eratosthenes to find all the primes from 2 up to a given -number. +Your task is to create a program that implements the Sieve of Eratosthenes algorithm to find prime numbers. -The Sieve of Eratosthenes is a simple, ancient algorithm for finding all -prime numbers up to any given limit. It does so by iteratively marking as -composite (i.e. not prime) the multiples of each prime, starting with the -multiples of 2. It does not use any division or remainder operation. +A prime number is a number that is only divisible by 1 and itself. +For example, 2, 3, 5, 7, 11, and 13 are prime numbers. -Create your range, starting at two and continuing up to and including the given limit. (i.e. [2, limit]) +The Sieve of Eratosthenes is an ancient algorithm that works by taking a list of numbers and crossing out all the numbers that aren't prime. -The algorithm consists of repeating the following over and over: +A number that is **not** prime is called a "composite number". -- take the next available unmarked number in your list (it is prime) -- mark all the multiples of that number (they are not prime) +To use the Sieve of Eratosthenes, you first create a list of all the numbers between 2 and your given number. +Then you repeat the following steps: -Repeat until you have processed each number in your range. +1. Find the next unmarked number in your list. This is a prime number. +2. Mark all the multiples of that prime number as composite (not prime). -When the algorithm terminates, all the numbers in the list that have not -been marked are prime. +You keep repeating these steps until you've gone through every number in your list. +At the end, all the unmarked numbers are prime. -The wikipedia article has a useful graphic that explains the algorithm: -https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes +```exercism/note +[Wikipedia's Sieve of Eratosthenes article][eratosthenes] has a useful graphic that explains the algorithm. -Notice that this is a very specific algorithm, and the tests don't check -that you've implemented the algorithm, only that you've come up with the -correct list of primes. A good first test is to check that you do not use -division or remainder operations (div, /, mod or % depending on the -language). +The tests don't check that you've implemented the algorithm, only that you've come up with the correct list of primes. +A good first test is to check that you do not use division or remainder operations. + +[eratosthenes]: https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes +``` diff --git a/exercises/practice/sieve/.docs/introduction.md b/exercises/practice/sieve/.docs/introduction.md new file mode 100644 index 000000000..f6c1cf79a --- /dev/null +++ b/exercises/practice/sieve/.docs/introduction.md @@ -0,0 +1,7 @@ +# Introduction + +You bought a big box of random computer parts at a garage sale. +You've started putting the parts together to build custom computers. + +You want to test the performance of different combinations of parts, and decide to create your own benchmarking program to see how your computers compare. +You choose the famous "Sieve of Eratosthenes" algorithm, an ancient algorithm, but one that should push your computers to the limits. From c7986545481006810da97ea42db37433cf048eda Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Tue, 7 Mar 2023 11:08:23 +0100 Subject: [PATCH 067/436] Sync binary-search docs with problem-specifications (#1639) The binary-search exercise has been overhauled as part of a project to make practice exercises more consistent and friendly. For more context, please see the discussion in the forum, as well as the pull request that updated the exercise in the problem-specifications repository: - https://forum.exercism.org/t/new-project-making-practice-exercises-more-consistent-and-human-across-exercism/3943 - https://github.com/exercism/problem-specifications/pull/2220 --- .../binary-search/.docs/instructions.md | 45 ++++++++----------- .../binary-search/.docs/introduction.md | 9 ++++ 2 files changed, 28 insertions(+), 26 deletions(-) create mode 100644 exercises/practice/binary-search/.docs/introduction.md diff --git a/exercises/practice/binary-search/.docs/instructions.md b/exercises/practice/binary-search/.docs/instructions.md index 4dcaba726..175c4c4ba 100644 --- a/exercises/practice/binary-search/.docs/instructions.md +++ b/exercises/practice/binary-search/.docs/instructions.md @@ -1,35 +1,28 @@ # Instructions -Implement a binary search algorithm. +Your task is to implement a binary search algorithm. -Searching a sorted collection is a common task. A dictionary is a sorted -list of word definitions. Given a word, one can find its definition. A -telephone book is a sorted list of people's names, addresses, and -telephone numbers. Knowing someone's name allows one to quickly find -their telephone number and address. +A binary search algorithm finds an item in a list by repeatedly splitting it in half, only keeping the half which contains the item we're looking for. +It allows us to quickly narrow down the possible locations of our item until we find it, or until we've eliminated all possible locations. -If the list to be searched contains more than a few items (a dozen, say) -a binary search will require far fewer comparisons than a linear search, -but it imposes the requirement that the list be sorted. +```exercism/caution +Binary search only works when a list has been sorted. +``` -In computer science, a binary search or half-interval search algorithm -finds the position of a specified input value (the search "key") within -an array sorted by key value. +The algorithm looks like this: -In each step, the algorithm compares the search key value with the key -value of the middle element of the array. +- Divide the sorted list in half and compare the middle element with the item we're looking for. +- If the middle element is our item, then we're done. +- If the middle element is greater than our item, we can eliminate that number and all the numbers **after** it. +- If the middle element is less than our item, we can eliminate that number and all the numbers **before** it. +- Repeat the process on the part of the list that we kept. -If the keys match, then a matching element has been found and its index, -or position, is returned. +Here's an example: -Otherwise, if the search key is less than the middle element's key, then -the algorithm repeats its action on the sub-array to the left of the -middle element or, if the search key is greater, on the sub-array to the -right. +Let's say we're looking for the number 23 in the following sorted list: `[4, 8, 12, 16, 23, 28, 32]`. -If the remaining array to be searched is empty, then the key cannot be -found in the array and a special "not found" indication is returned. - -A binary search halves the number of items to check with each iteration, -so locating an item (or determining its absence) takes logarithmic time. -A binary search is a dichotomic divide and conquer search algorithm. +- We start by comparing 23 with the middle element, 16. +- Since 23 is greater than 16, we can eliminate the left half of the list, leaving us with `[23, 28, 32]`. +- We then compare 23 with the new middle element, 28. +- Since 23 is less than 28, we can eliminate the right half of the list: `[23]`. +- We've found our item. diff --git a/exercises/practice/binary-search/.docs/introduction.md b/exercises/practice/binary-search/.docs/introduction.md new file mode 100644 index 000000000..66c4b8a45 --- /dev/null +++ b/exercises/practice/binary-search/.docs/introduction.md @@ -0,0 +1,9 @@ +# Introduction + +You have stumbled upon a group of mathematicians who are also singer-songwriters. +They have written a song for each of their favorite numbers, and, as you can imagine, they have a lot of favorite numbers. + +You are curious to hear the song for your favorite number, but with so many songs to wade through, finding the right song could take a while. +Fortunately, they have organized their songs in a playlist sorted by the title — which is simply the number that the song is about. + +You realize that you can use a binary search algorithm to quickly find a song given the title. From fe94c6c714a066278fb11700271141da08366656 Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Wed, 8 Mar 2023 15:17:17 +0100 Subject: [PATCH 068/436] Sync secret-handshake docs with problem-specifications (#1643) The secret-handshake exercise has been overhauled as part of a project to make practice exercises more consistent and friendly. For more context, please see the discussion in the forum, as well as the pull request that updated the exercise in the problem-specifications repository: - https://forum.exercism.org/t/new-project-making-practice-exercises-more-consistent-and-human-across-exercism/3943 - https://github.com/exercism/problem-specifications/pull/2219 --- .../secret-handshake/.docs/instructions.md | 43 ++++++++++++++----- .../secret-handshake/.docs/introduction.md | 7 +++ 2 files changed, 40 insertions(+), 10 deletions(-) create mode 100644 exercises/practice/secret-handshake/.docs/introduction.md diff --git a/exercises/practice/secret-handshake/.docs/instructions.md b/exercises/practice/secret-handshake/.docs/instructions.md index 2d6937ae9..77136cf0f 100644 --- a/exercises/practice/secret-handshake/.docs/instructions.md +++ b/exercises/practice/secret-handshake/.docs/instructions.md @@ -1,24 +1,47 @@ # Instructions -> There are 10 types of people in the world: Those who understand -> binary, and those who don't. +Your task is to convert a number between 1 and 31 to a sequence of actions in the secret handshake. -You and your fellow cohort of those in the "know" when it comes to binary decide to come up with a secret "handshake". +The sequence of actions is chosen by looking at the rightmost five digits of the number once it's been converted to binary. +Start at the right-most digit and move left. -```text +The actions for each number place are: + +```plaintext 00001 = wink 00010 = double blink 00100 = close your eyes 01000 = jump - 10000 = Reverse the order of the operations in the secret handshake. ``` -Given a decimal number, convert it to the appropriate sequence of events for a secret handshake. +Let's use the number `9` as an example: + +- 9 in binary is `1001`. +- The digit that is farthest to the right is 1, so the first action is `wink`. +- Going left, the next digit is 0, so there is no double-blink. +- Going left again, the next digit is 0, so you leave your eyes open. +- Going left again, the next digit is 1, so you jump. -Here's a couple of examples: +That was the last digit, so the final code is: + +```plaintext +wink, jump +``` -Given the decimal input 3, the function would return the array ["wink", "double blink"] because the decimal number 3 is 2+1 in powers of two and thus `11` in binary. +Given the number 26, which is `11010` in binary, we get the following actions: + +- double blink +- jump +- reverse actions + +The secret handshake for 26 is therefore: + +```plaintext +jump, double blink +``` -Let's now examine the input 19 which is 16+2+1 in powers of two and thus `10011` in binary. -Recalling that the addition of 16 (`10000` in binary) reverses an array and that we already know what array is returned given input 3, the array returned for input 19 is ["double blink", "wink"]. +~~~~exercism/note +If you aren't sure what binary is or how it works, check out [this binary tutorial][intro-to-binary]. +[intro-to-binary]: https://medium.com/basecs/bits-bytes-building-with-binary-13cb4289aafa +~~~~ diff --git a/exercises/practice/secret-handshake/.docs/introduction.md b/exercises/practice/secret-handshake/.docs/introduction.md new file mode 100644 index 000000000..176b92e8c --- /dev/null +++ b/exercises/practice/secret-handshake/.docs/introduction.md @@ -0,0 +1,7 @@ +# Introduction + +You are starting a secret coding club with some friends and friends-of-friends. +Not everyone knows each other, so you and your friends have decided to create a secret handshake that you can use to recognize that someone is a member. +You don't want anyone who isn't in the know to be able to crack the code. + +You've designed the code so that one person says a number between 1 and 31, and the other person turns it into a series of actions. From a4c275b447f77215ec98147b1652be8be34ba47a Mon Sep 17 00:00:00 2001 From: dem4ron Date: Thu, 9 Mar 2023 21:21:18 +0100 Subject: [PATCH 069/436] Change casing, fix shellcheck disables, add .shekkcheckrc --- bin/.shellcheckrc | 3 ++ bin/build_exercise_crate.sh | 27 ---------------- bin/fetch_canonical_data | 3 ++ bin/generate_practice_exercise | 54 +++++++++++++++++--------------- bin/generate_tests | 20 ++++++------ bin/generator-utils/colors.sh | 40 +++++------------------ bin/generator-utils/prompts.sh | 27 ++++++++-------- bin/generator-utils/templates.sh | 15 ++++++--- bin/generator-utils/utils.sh | 29 +++++++++-------- 9 files changed, 93 insertions(+), 125 deletions(-) create mode 100644 bin/.shellcheckrc delete mode 100755 bin/build_exercise_crate.sh diff --git a/bin/.shellcheckrc b/bin/.shellcheckrc new file mode 100644 index 000000000..5ec9f679b --- /dev/null +++ b/bin/.shellcheckrc @@ -0,0 +1,3 @@ +shell=bash +external-sources=true +disable=SC2001 # https://www.shellcheck.net/wiki/SC2001 - effects most of the sed commands \ No newline at end of file diff --git a/bin/build_exercise_crate.sh b/bin/build_exercise_crate.sh deleted file mode 100755 index ca92b77f4..000000000 --- a/bin/build_exercise_crate.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash -set -e -# Compile the 'exercise' crate and put it in the 'bin/' folder - -TRACK_ROOT="$(git rev-parse --show-toplevel)" - -EXERCISE_CRATE_PATH="$TRACK_ROOT/util/exercise" - -BIN_DIR_PATH="$TRACK_ROOT/bin" - -( - cd "$EXERCISE_CRATE_PATH" - - echo "Building exercise crate" - - cargo build --release - - RELEASE_PATH="$EXERCISE_CRATE_PATH/target/release/exercise" - - if [ -f "$RELEASE_PATH.exe" ]; then - RELEASE_PATH="$RELEASE_PATH.exe" - fi - - echo "Copying exercise crate from $RELEASE_PATH into $BIN_DIR_PATH" - - cp "$RELEASE_PATH" "$BIN_DIR_PATH" -) diff --git a/bin/fetch_canonical_data b/bin/fetch_canonical_data index a487581f2..69ca5fde0 100755 --- a/bin/fetch_canonical_data +++ b/bin/fetch_canonical_data @@ -1,6 +1,9 @@ #!/usr/bin/env bash # This script fetches the canonical data of the exercise. +# Exit if anything fails. +set -euo pipefail + if [ $# -ne 1 ]; then echo "Usage: bin/fetch_canonical_data " exit 1 diff --git a/bin/generate_practice_exercise b/bin/generate_practice_exercise index c2bae5562..15a0e2969 100755 --- a/bin/generate_practice_exercise +++ b/bin/generate_practice_exercise @@ -1,6 +1,8 @@ #!/usr/bin/env bash -# shellcheck source=/dev/null +# shellcheck source=./generator-utils/utils.sh +# shellcheck source=./generator-utils/prompts.sh +# shellcheck source=./generator-utils/templates.sh source ./bin/generator-utils/utils.sh source ./bin/generator-utils/prompts.sh source ./bin/generator-utils/templates.sh @@ -35,13 +37,13 @@ check_exercise_existence "$1" # ================================================== -SLUG="$1" -HAS_CANONICAL_DATA=true +slug="$1" # Fetch canonical data -canonical_json=$(bin/fetch_canonical_data "$SLUG") +canonical_json=$(bin/fetch_canonical_data "$slug") +has_canonical_data=true if [ "${canonical_json}" == "404: Not Found" ]; then - HAS_CANONICAL_DATA=false + has_canonical_data=false message "warning" "This exercise doesn't have canonical data" else @@ -49,32 +51,31 @@ else message "success" "Fetched canonical data successfully!" fi -UNDERSCORED_SLUG=$(dash_to_underscore "$SLUG") -EXERCISE_DIR="exercises/practice/${SLUG}" -EXERCISE_NAME=$(format_exercise_name "$SLUG") -message "info" "Using ${YELLOW}${EXERCISE_NAME}${BLUE} as a default exercise name. You can edit this later in the config.json file" +underscored_slug=$(dash_to_underscore "$slug") +exercise_dir="exercises/practice/${slug}" +exercise_name=$(format_exercise_name "$slug") +message "info" "Using ${yellow}${exercise_name}${blue} as a default exercise name. You can edit this later in the config.json file" # using default value for difficulty -EXERCISE_DIFFICULTY=$(validate_difficulty_input "${2:-$(get_exercise_difficulty)}") -message "info" "The exercise difficulty has been set to ${YELLOW}${EXERCISE_DIFFICULTY}${BLUE}. You can edit this later in the config.json file" +exercise_difficulty=$(validate_difficulty_input "${2:-$(get_exercise_difficulty)}") +message "info" "The exercise difficulty has been set to ${yellow}${exercise_difficulty}${blue}. You can edit this later in the config.json file" # using default value for author -AUTHOR_HANDLE=${3:-$(get_author_handle)} -message "info" "Using ${YELLOW}${AUTHOR_HANDLE}${BLUE} as author's handle. You can edit this later in the 'authors' field in the ${EXERCISE_DIR}/.meta/config.json file" - - -create_rust_files "$EXERCISE_DIR" "$SLUG" "$HAS_CANONICAL_DATA" +author_handle=${3:-$(get_author_handle)} +message "info" "Using ${yellow}${author_handle}${blue} as author's handle. You can edit this later in the 'authors' field in the ${exercise_dir}/.meta/config.json file" +create_rust_files "$exercise_dir" "$slug" "$has_canonical_data" # ================================================== -# build configlet +# Build configlet ./bin/fetch-configlet message "success" "Fetched configlet successfully!" # Preparing config.json message "info" "Adding instructions and configuration files..." -UUID=$(bin/configlet uuid) +uuid=$(bin/configlet uuid) -jq --arg slug "$SLUG" --arg uuid "$UUID" --arg name "$EXERCISE_NAME" --arg difficulty "$EXERCISE_DIFFICULTY" \ +# Add exercise-data to global config.json +jq --arg slug "$slug" --arg uuid "$uuid" --arg name "$exercise_name" --arg difficulty "$exercise_difficulty" \ '.exercises.practice += [{slug: $slug, name: $name, uuid: $uuid, practices: [], prerequisites: [], difficulty: $difficulty}]' \ config.json >config.json.tmp # jq always rounds whole numbers, but average_run_time needs to be a float @@ -84,18 +85,19 @@ message "success" "Added instructions and configuration files" # Create instructions and config files echo "Creating instructions and config files" -./bin/configlet sync --update --yes --docs --metadata --exercise "$SLUG" -./bin/configlet sync --update --yes --filepaths --exercise "$SLUG" -./bin/configlet sync --update --tests include --exercise "$SLUG" +./bin/configlet sync --update --yes --docs --metadata --exercise "$slug" +./bin/configlet sync --update --yes --filepaths --exercise "$slug" +./bin/configlet sync --update --tests include --exercise "$slug" message "success" "Created instructions and config files" -META_CONFIG="$EXERCISE_DIR"/.meta/config.json -jq --arg author "$AUTHOR_HANDLE" '.authors += [$author]' "$META_CONFIG" >"$META_CONFIG".tmp && mv "$META_CONFIG".tmp "$META_CONFIG" +# Push author to "authors" array in ./meta/config.json +meta_config="$exercise_dir"/.meta/config.json +jq --arg author "$author_handle" '.authors += [$author]' "$meta_config" >"$meta_config".tmp && mv "$meta_config".tmp "$meta_config" message "success" "You've been added as the author of this exercise." -sed -i "s/name = \".*\"/name = \"$UNDERSCORED_SLUG\"/" "$EXERCISE_DIR"/Cargo.toml +sed -i "s/name = \".*\"/name = \"$underscored_slug\"/" "$exercise_dir"/Cargo.toml message "done" "All stub files were created." message "info" "After implementing the solution, tests and configuration, please run:" -echo "./bin/configlet fmt --update --yes --exercise ${SLUG}" +echo "./bin/configlet fmt --update --yes --exercise ${slug}" diff --git a/bin/generate_tests b/bin/generate_tests index 74c137d7f..b0a10c4db 100755 --- a/bin/generate_tests +++ b/bin/generate_tests @@ -1,12 +1,14 @@ #!/usr/bin/env bash +# Exit if anything fails. set -euo pipefail -# shellcheck source=/dev/null + + +# shellcheck source=./generator-utils/utils.sh source ./bin/generator-utils/utils.sh -function digest_template() { +digest_template() { template=$(cat bin/test_template) - # shellcheck disable=SC2001 # turn every token into a jq command echo "$template" | sed 's/${\([^}]*\)\}\$/$(echo $case | jq -r '\''.\1'\'')/g' } @@ -27,20 +29,20 @@ use $(dash_to_underscore "$SLUG")::*; EOT # Flattens canonical json, extracts only the objects with a uuid -cases=$(echo "$canonical_json" | jq '[ .. | objects | with_entries(select(.key | IN("uuid", "description", "input", "expected", "property"))) | select(. != {}) | select(has("uuid")) ]') +CASES=$(echo "$CANONICAL_JSON" | jq '[ .. | objects | with_entries(select(.key | IN("uuid", "description", "input", "expected", "property"))) | select(. != {}) | select(has("uuid")) ]') # shellcheck disable=SC2034 -jq -c '.[]' <<<"$cases" | while read -r case; do +jq -c '.[]' <<<"$CASES" | while read -r case; do # Evaluate the bash parts and replace them with their return values - eval_template="$(digest_template | sed -e "s/\$(\(.*\))/\$\(\1\)/g")" - eval_template="$(eval "echo \"$eval_template\"")" + EVAL_TEMPLATE="$(digest_template | sed -e "s/\$(\(.*\))/\$\(\1\)/g")" + EVAL_TEMPLATE="$(eval "echo \"$EVAL_TEMPLATE\"")" # Turn function name unto snake_case - formatted_template=$(echo "$eval_template" | sed -e ':loop' -e 's/\(fn[^(]*\)[ -]/\1_/g' -e 't loop' | sed 's/fn_/fn /') + FORMATTED_TEMPLATE=$(echo "$EVAL_TEMPLATE" | sed -e ':loop' -e 's/\(fn[^(]*\)[ -]/\1_/g' -e 't loop' | sed 's/fn_/fn /') # Push to file - echo "$formatted_template" >>"$TEST_FILE" + echo "$FORMATTED_TEMPLATE" >>"$TEST_FILE" printf "\\n" >>"$TEST_FILE" done diff --git a/bin/generator-utils/colors.sh b/bin/generator-utils/colors.sh index 3ae992587..c3b2f4079 100644 --- a/bin/generator-utils/colors.sh +++ b/bin/generator-utils/colors.sh @@ -1,38 +1,14 @@ #!/usr/bin/env bash -# shellcheck disable=SC2034 -# Reset -RESET=$(echo -e '\033[0m') +reset_color=$(echo -e '\033[0m') -# Regular Colors -BLACK=$(echo -e '\033[0;30m') -RED=$(echo -e '\033[0;31m') -GREEN=$(echo -e '\033[0;32m') -YELLOW=$(echo -e '\033[0;33m') -BLUE=$(echo -e '\033[0;34m') -PURPLE=$(echo -e '\033[0;35m') -CYAN=$(echo -e '\033[0;36m') -WHITE=$(echo -e '\033[0;37m') +red=$(echo -e '\033[0;31m') +green=$(echo -e '\033[0;32m') +yellow=$(echo -e '\033[0;33m') +blue=$(echo -e '\033[0;34m') +cyan=$(echo -e '\033[0;36m') -# Bold -BOLD_BLACK=$(echo -e '\033[1;30m') -BOLD_RED=$(echo -e '\033[1;31m') -BOLD_GREEN=$(echo -e '\033[1;32m') -BOLD_YELLOW=$(echo -e '\033[1;33m') -BOLD_BLUE=$(echo -e '\033[1;34m') -BOLD_PURPLE=$(echo -e '\033[1;35m') -BOLD_CYAN=$(echo -e '\033[1;36m') -BOLD_WHITE=$(echo -e '\033[1;37m') +bold_green=$(echo -e '\033[1;32m') -# Underline -UNDERLINE=$(echo -e ='\033[4m') -# Background -BG_BLACK=$(echo -e ='\033[40m') -BG_RED=$(echo -e ='\033[41m') -BG_GREEN=$(echo -e ='\033[42m') -BG_YELLOW=$(echo -e ='\033[43m') -BG_BLUE=$(echo -e ='\033[44m') -BG_PURPLE=$(echo -e ='\033[45m') -BG_CYAN=$(echo -e ='\033[46m') -BG_WHITE=$(echo -e ='\033[47m') +export red green blue yellow bold_green reset_color cyan \ No newline at end of file diff --git a/bin/generator-utils/prompts.sh b/bin/generator-utils/prompts.sh index c785a2636..9835e286f 100644 --- a/bin/generator-utils/prompts.sh +++ b/bin/generator-utils/prompts.sh @@ -1,22 +1,22 @@ #!/usr/bin/env bash -# shellcheck source=/dev/null +# shellcheck source=./colors.sh source ./bin/generator-utils/colors.sh -function get_exercise_difficulty() { +get_exercise_difficulty() { read -rp "Difficulty of exercise (1-10): " exercise_difficulty echo "$exercise_difficulty" } -function validate_difficulty_input() { +validate_difficulty_input() { - valid_input=false + local valid_input=false while ! $valid_input; do if [[ "$1" =~ ^[1-9]$|^10$ ]]; then - exercise_difficulty=$1 - valid_input=true + local exercise_difficulty=$1 + local valid_input=true else - read -rp "${RED}Invalid input. ${RESET}Please enter an integer between 1 and 10. " exercise_difficulty + read -rp "${red}Invalid input. ${reset_color}Please enter an integer between 1 and 10. " exercise_difficulty [[ "$exercise_difficulty" =~ ^[1-9]$|^10$ ]] && valid_input=true fi @@ -24,15 +24,16 @@ function validate_difficulty_input() { echo "$exercise_difficulty" } -function get_author_handle { - DEFAULT_AUTHOR_HANDLE="$(git config user.name)" +get_author_handle() { + default_author_handle="$(git config user.name)" + local default_author_handle - if [ -z "$DEFAULT_AUTHOR_HANDLE" ]; then - read -rp "Hey! Couldn't find your Github handle. Add it now or skip with enter and add it later in the .meta.config.json file: " AUTHOR_HANDLE + if [ -z "$default_author_handle" ]; then + read -rp "Hey! Couldn't find your Github handle. Add it now or skip with enter and add it later in the .meta.config.json file: " author_handle else - AUTHOR_HANDLE="$DEFAULT_AUTHOR_HANDLE" + local author_handle="$default_author_handle" fi - echo "$AUTHOR_HANDLE" + echo "$author_handle" } diff --git a/bin/generator-utils/templates.sh b/bin/generator-utils/templates.sh index 5c957314a..ed51a2319 100755 --- a/bin/generator-utils/templates.sh +++ b/bin/generator-utils/templates.sh @@ -1,14 +1,15 @@ #!/usr/bin/env bash -# shellcheck source=/dev/null +# shellcheck source=./utils.sh source ./bin/generator-utils/utils.sh -function create_fn_name() { - slug=$1 - has_canonical_data=$2 +create_fn_name() { + local slug=$1 + local has_canonical_data=$2 if [ "$has_canonical_data" == false ]; then fn_name=$(dash_to_underscore "$slug") + local fn_name else fn_name=$(jq -r 'first(.. | .property? // empty)' canonical_data.json) fi @@ -17,7 +18,7 @@ function create_fn_name() { } -function create_test_file_template() { +create_test_file_template() { local exercise_dir=$1 local slug=$2 local has_canonical_data=$3 @@ -40,12 +41,15 @@ EOT message "success" "Stub file for tests has been created!" else canonical_json=$(cat canonical_data.json) + local canonical_json # sometimes canonical data has multiple levels with multiple `cases` arrays. #(see kindergarten-garden https://github.com/exercism/problem-specifications/blob/main/exercises/kindergarten-garden/canonical-data.json) # so let's flatten it cases=$(echo "$canonical_json" | jq '[ .. | objects | with_entries(select(.key | IN("uuid", "description", "input", "expected"))) | select(. != {}) | select(has("uuid")) ]') + local cases fn_name=$(echo "$canonical_json" | jq -r 'first(.. | .property? // empty)') + local fn_name first_iteration=true # loop through each object @@ -108,6 +112,7 @@ function create_example_rs_template() { local has_canonical_data=$3 fn_name=$(create_fn_name "$slug" "$has_canonical_data") + local fn_name mkdir "${exercise_dir}/.meta" cat <"${exercise_dir}/.meta/example.rs" diff --git a/bin/generator-utils/utils.sh b/bin/generator-utils/utils.sh index e89b33e67..f0c2713c0 100755 --- a/bin/generator-utils/utils.sh +++ b/bin/generator-utils/utils.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# shellcheck source=/dev/null +# shellcheck source=./colors.sh source ./bin/generator-utils/colors.sh function message() { @@ -8,16 +8,19 @@ function message() { local message=$2 case "$flag" in - "success") printf "${GREEN}%s${RESET}\n" "[success]: $message" ;; - "info") printf "${BLUE}%s${RESET}\n" "[info]: $message" ;; - "warning") printf "${YELLOW}%s${RESET}\n" "[warning]: $message" ;; - "error") printf "${RED}%s${RESET}\n" "[error]: $message" ;; + "success") printf "${green}%s${reset_color}\n" "[success]: $message" ;; + "task") printf "${cyan}%s${reset_color}\n" "[task]: $message" ;; + "info") printf "${blue}%s${reset_color}\n" "[info]: $message" ;; + "warning") printf "${yellow}%s${reset_color}\n" "[warning]: $message" ;; + "error") printf "${red}%s${reset_color}\n" "[error]: $message" ;; "done") echo + # Create a dashed-line as wide as the screen cols=$(tput cols) + local cols printf "%*s\n" "$cols" "" | tr " " "-" echo - printf "${BOLD_GREEN}%s${RESET}\n" "[done]: $message" + printf "${bold_green}%s${reset_color}\n" "[done]: $message" ;; *) echo "Invalid flag: $flag" @@ -25,19 +28,18 @@ function message() { esac } -function dash_to_underscore() { - # shellcheck disable=SC2001 +dash_to_underscore() { echo "$1" | sed 's/-/_/g' } # exercise_name -> Exercise Name -function format_exercise_name { +format_exercise_name() { echo "$1" | sed 's/-/ /g; s/\b\(.\)/\u\1/g' } -function check_exercise_existence() { +check_exercise_existence() { message "info" "Looking for exercise.." - slug="$1" + local slug="$1" # Check if exercise is already in config.json if jq '.exercises.practice | map(.slug)' config.json | grep -q "$slug"; then echo "${1} has already been implemented." @@ -45,6 +47,7 @@ function check_exercise_existence() { fi # fetch configlet and crop out exercise list + local unimplemented_exercises unimplemented_exercises=$(bin/configlet info | sed -n '/With canonical data:/,/Track summary:/p' | sed -e '/\(With\(out\)\? canonical data:\|Track summary:\)/d' -e '/^$/d') if echo "$unimplemented_exercises" | grep -q "^$slug$"; then message "success" "Exercise has been found!" @@ -57,11 +60,11 @@ ${unimplemented_exercises}" # see util/ngram for source # First it builds a binary for the system of the contributor if [ -e bin/generator-utils/ngram ]; then - echo "${YELLOW}$(bin/generator-utils/ngram "${unimplemented_exercises}" "$slug")${RESET}" + echo "${yellow}$(bin/generator-utils/ngram "${unimplemented_exercises}" "$slug")${reset_color}" else message "info" "Building typo-checker binary for $(uname -m) system." - cd util/ngram && ./build && cd ../.. && echo "${YELLOW}$(bin/generator-utils/ngram "${unimplemented_exercises}" "$slug")${RESET}" + cd util/ngram && ./build && cd ../.. && echo "${yellow}$(bin/generator-utils/ngram "${unimplemented_exercises}" "$slug")${reset_color}" fi exit 1 From 97d9eddf2de33d67cdc003a2bcf403f24621de4f Mon Sep 17 00:00:00 2001 From: dem4ron Date: Thu, 9 Mar 2023 21:29:57 +0100 Subject: [PATCH 070/436] Fix local variables --- bin/generator-utils/prompts.sh | 2 +- bin/generator-utils/templates.sh | 9 +++++---- bin/generator-utils/utils.sh | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/bin/generator-utils/prompts.sh b/bin/generator-utils/prompts.sh index 9835e286f..30ac172ae 100644 --- a/bin/generator-utils/prompts.sh +++ b/bin/generator-utils/prompts.sh @@ -25,8 +25,8 @@ validate_difficulty_input() { } get_author_handle() { - default_author_handle="$(git config user.name)" local default_author_handle + default_author_handle="$(git config user.name)" if [ -z "$default_author_handle" ]; then read -rp "Hey! Couldn't find your Github handle. Add it now or skip with enter and add it later in the .meta.config.json file: " author_handle diff --git a/bin/generator-utils/templates.sh b/bin/generator-utils/templates.sh index ed51a2319..c96576d8d 100755 --- a/bin/generator-utils/templates.sh +++ b/bin/generator-utils/templates.sh @@ -40,16 +40,16 @@ EOT message "info" "This exercise doesn't have canonical data." message "success" "Stub file for tests has been created!" else - canonical_json=$(cat canonical_data.json) local canonical_json + canonical_json=$(cat canonical_data.json) # sometimes canonical data has multiple levels with multiple `cases` arrays. #(see kindergarten-garden https://github.com/exercism/problem-specifications/blob/main/exercises/kindergarten-garden/canonical-data.json) # so let's flatten it - cases=$(echo "$canonical_json" | jq '[ .. | objects | with_entries(select(.key | IN("uuid", "description", "input", "expected"))) | select(. != {}) | select(has("uuid")) ]') local cases - fn_name=$(echo "$canonical_json" | jq -r 'first(.. | .property? // empty)') + cases=$(echo "$canonical_json" | jq '[ .. | objects | with_entries(select(.key | IN("uuid", "description", "input", "expected"))) | select(. != {}) | select(has("uuid")) ]') local fn_name + fn_name=$(echo "$canonical_json" | jq -r 'first(.. | .property? // empty)') first_iteration=true # loop through each object @@ -82,6 +82,7 @@ function create_lib_rs_template() { local exercise_dir=$1 local slug=$2 local has_canonical_data=$3 + local fn_name fn_name=$(create_fn_name "$slug" "$has_canonical_data") cat <"${exercise_dir}/src/lib.rs" pub fn ${fn_name}() { @@ -111,8 +112,8 @@ function create_example_rs_template() { local slug=$2 local has_canonical_data=$3 - fn_name=$(create_fn_name "$slug" "$has_canonical_data") local fn_name + fn_name=$(create_fn_name "$slug" "$has_canonical_data") mkdir "${exercise_dir}/.meta" cat <"${exercise_dir}/.meta/example.rs" diff --git a/bin/generator-utils/utils.sh b/bin/generator-utils/utils.sh index f0c2713c0..02876ee3a 100755 --- a/bin/generator-utils/utils.sh +++ b/bin/generator-utils/utils.sh @@ -16,8 +16,8 @@ function message() { "done") echo # Create a dashed-line as wide as the screen - cols=$(tput cols) local cols + cols=$(tput cols) printf "%*s\n" "$cols" "" | tr " " "-" echo printf "${bold_green}%s${reset_color}\n" "[done]: $message" From fe700c13a18907eae2c40c84bc98f8ee0db925b4 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Thu, 9 Mar 2023 21:38:44 +0100 Subject: [PATCH 071/436] Lowercase var names in `bin/generate_tests`, remove redundant shellcheck disable --- bin/generate_tests | 40 +++++++++++++++++++----------------- bin/generator-utils/utils.sh | 6 +++--- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/bin/generate_tests b/bin/generate_tests index b0a10c4db..91283d567 100755 --- a/bin/generate_tests +++ b/bin/generate_tests @@ -9,42 +9,44 @@ source ./bin/generator-utils/utils.sh digest_template() { template=$(cat bin/test_template) - # turn every token into a jq command + # Turn every token into a jq command echo "$template" | sed 's/${\([^}]*\)\}\$/$(echo $case | jq -r '\''.\1'\'')/g' } +message "info" "Generating tests.." canonical_json=$(cat canonical_data.json) -SLUG=$(echo "$canonical_json" | jq '.exercise') -# shellcheck disable=SC2001 +slug=$(echo "$canonical_json" | jq '.exercise') # Remove double quotes -SLUG=$(echo "$SLUG" | sed 's/"//g') -EXERCISE_DIR="exercises/practice/$SLUG" -TEST_FILE="$EXERCISE_DIR/tests/$SLUG.rs" -rm "$TEST_FILE" +slug=$(echo "$slug" | sed 's/"//g') +exercise_dir="exercises/practice/$slug" +test_file="$exercise_dir/tests/$slug.rs" -cat <"$TEST_FILE" -use $(dash_to_underscore "$SLUG")::*; +cat <"$test_file" +use $(dash_to_underscore "$slug")::*; // Add tests here EOT # Flattens canonical json, extracts only the objects with a uuid -CASES=$(echo "$CANONICAL_JSON" | jq '[ .. | objects | with_entries(select(.key | IN("uuid", "description", "input", "expected", "property"))) | select(. != {}) | select(has("uuid")) ]') +cases=$(echo "$canonical_json" | jq '[ .. | objects | with_entries(select(.key | IN("uuid", "description", "input", "expected", "property"))) | select(. != {}) | select(has("uuid")) ]') +# Shellcheck doesn't recognize that `case` is not unused # shellcheck disable=SC2034 -jq -c '.[]' <<<"$CASES" | while read -r case; do +jq -c '.[]' <<<"$cases" | while read -r case; do # Evaluate the bash parts and replace them with their return values - EVAL_TEMPLATE="$(digest_template | sed -e "s/\$(\(.*\))/\$\(\1\)/g")" - EVAL_TEMPLATE="$(eval "echo \"$EVAL_TEMPLATE\"")" + eval_template="$(digest_template | sed -e "s/\$(\(.*\))/\$\(\1\)/g")" + eval_template="$(eval "echo \"$eval_template\"")" - # Turn function name unto snake_case - FORMATTED_TEMPLATE=$(echo "$EVAL_TEMPLATE" | sed -e ':loop' -e 's/\(fn[^(]*\)[ -]/\1_/g' -e 't loop' | sed 's/fn_/fn /') - # Push to file + # Turn function name into snake_case + formatted_template=$(echo "$eval_template" | sed -e ':loop' -e 's/\(fn[^(]*\)[ -]/\1_/g' -e 't loop' | sed 's/fn_/fn /') - echo "$FORMATTED_TEMPLATE" >>"$TEST_FILE" - printf "\\n" >>"$TEST_FILE" + # Push to test file + echo "$formatted_template" >>"$test_file" + printf "\\n" >>"$test_file" done -rustfmt "$TEST_FILE" +rustfmt "$test_file" + +message "success" "Generated tests successfully! Check out ${test_file}" diff --git a/bin/generator-utils/utils.sh b/bin/generator-utils/utils.sh index 02876ee3a..8e4cec1f7 100755 --- a/bin/generator-utils/utils.sh +++ b/bin/generator-utils/utils.sh @@ -15,7 +15,7 @@ function message() { "error") printf "${red}%s${reset_color}\n" "[error]: $message" ;; "done") echo - # Create a dashed-line as wide as the screen + # Generate a dashed line that spans the entire width of the screen. local cols cols=$(tput cols) printf "%*s\n" "$cols" "" | tr " " "-" @@ -46,7 +46,7 @@ check_exercise_existence() { exit 1 fi - # fetch configlet and crop out exercise list + # Fetch configlet and crop out exercise list local unimplemented_exercises unimplemented_exercises=$(bin/configlet info | sed -n '/With canonical data:/,/Track summary:/p' | sed -e '/\(With\(out\)\? canonical data:\|Track summary:\)/d' -e '/^$/d') if echo "$unimplemented_exercises" | grep -q "^$slug$"; then @@ -57,7 +57,7 @@ check_exercise_existence() { ${unimplemented_exercises}" # Find closest match to typed-in not-found slug - # see util/ngram for source + # See util/ngram for source # First it builds a binary for the system of the contributor if [ -e bin/generator-utils/ngram ]; then echo "${yellow}$(bin/generator-utils/ngram "${unimplemented_exercises}" "$slug")${reset_color}" From e2d7c1feefa2edb24206c10ccc9bfef8ccab0d41 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Thu, 9 Mar 2023 21:41:45 +0100 Subject: [PATCH 072/436] remove `function` from functions --- bin/generate_tests | 1 + bin/generator-utils/templates.sh | 8 ++++---- bin/generator-utils/utils.sh | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/bin/generate_tests b/bin/generate_tests index 91283d567..3a0992c07 100755 --- a/bin/generate_tests +++ b/bin/generate_tests @@ -8,6 +8,7 @@ set -euo pipefail source ./bin/generator-utils/utils.sh digest_template() { + local template template=$(cat bin/test_template) # Turn every token into a jq command echo "$template" | sed 's/${\([^}]*\)\}\$/$(echo $case | jq -r '\''.\1'\'')/g' diff --git a/bin/generator-utils/templates.sh b/bin/generator-utils/templates.sh index c96576d8d..922bc3c42 100755 --- a/bin/generator-utils/templates.sh +++ b/bin/generator-utils/templates.sh @@ -78,7 +78,7 @@ EOT } -function create_lib_rs_template() { +create_lib_rs_template() { local exercise_dir=$1 local slug=$2 local has_canonical_data=$3 @@ -92,7 +92,7 @@ EOT message "success" "Stub file for lib.rs has been created!" } -function overwrite_gitignore() { +overwrite_gitignore() { local exercise_dir=$1 cat <"$exercise_dir"/.gitignore # Generated by Cargo @@ -107,7 +107,7 @@ EOT message "success" ".gitignore has been overwritten!" } -function create_example_rs_template() { +create_example_rs_template() { local exercise_dir=$1 local slug=$2 local has_canonical_data=$3 @@ -126,7 +126,7 @@ EOT message "success" "Stub file for example.rs has been created!" } -function create_rust_files() { +create_rust_files() { local exercise_dir=$1 local slug=$2 local has_canonical_data=$3 diff --git a/bin/generator-utils/utils.sh b/bin/generator-utils/utils.sh index 8e4cec1f7..bae45efb9 100755 --- a/bin/generator-utils/utils.sh +++ b/bin/generator-utils/utils.sh @@ -3,7 +3,7 @@ # shellcheck source=./colors.sh source ./bin/generator-utils/colors.sh -function message() { +message() { local flag=$1 local message=$2 From 54d0930e84e578eea4a2e1d934358b85c8bb0e84 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Thu, 9 Mar 2023 23:36:57 +0100 Subject: [PATCH 073/436] Reset --- .github/workflows/tests.yml | 8 -- ...ractice_exercise => add_practice_exercise} | 2 +- .../binary-search/.docs/instructions.md | 45 ++++----- .../binary-search/.docs/introduction.md | 9 ++ exercises/practice/bob/.docs/instructions.md | 27 +++--- exercises/practice/bob/.docs/introduction.md | 10 ++ .../practice/gigasecond/.docs/instructions.md | 8 +- .../practice/gigasecond/.docs/introduction.md | 24 +++++ exercises/practice/paasio/src/lib.rs | 3 + .../practice/pangram/.docs/instructions.md | 11 +-- .../practice/pangram/.docs/introduction.md | 16 +++ .../secret-handshake/.approaches/config.json | 15 +++ .../.approaches/introduction.md | 39 ++++++++ .../.approaches/iterate-once/content.md | 97 +++++++++++++++++++ .../.approaches/iterate-once/snippet.txt | 4 + .../secret-handshake/.docs/instructions.md | 43 ++++++-- .../secret-handshake/.docs/introduction.md | 7 ++ .../practice/sieve/.docs/instructions.md | 38 ++++---- .../practice/sieve/.docs/introduction.md | 7 ++ 19 files changed, 327 insertions(+), 86 deletions(-) rename bin/{generate_practice_exercise => add_practice_exercise} (97%) create mode 100644 exercises/practice/binary-search/.docs/introduction.md create mode 100644 exercises/practice/bob/.docs/introduction.md create mode 100644 exercises/practice/gigasecond/.docs/introduction.md create mode 100644 exercises/practice/pangram/.docs/introduction.md create mode 100644 exercises/practice/secret-handshake/.approaches/config.json create mode 100644 exercises/practice/secret-handshake/.approaches/introduction.md create mode 100644 exercises/practice/secret-handshake/.approaches/iterate-once/content.md create mode 100644 exercises/practice/secret-handshake/.approaches/iterate-once/snippet.txt create mode 100644 exercises/practice/secret-handshake/.docs/introduction.md create mode 100644 exercises/practice/sieve/.docs/introduction.md diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 91db73990..8d3c9a2de 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -123,20 +123,12 @@ jobs: run: ./_test/check_exercises.sh continue-on-error: ${{ matrix.rust == 'beta' && matrix.deny_warnings == '1' }} - - name: Cargo clean (to prevent previous compilation from unintentionally interfering with later ones) - run: ./_test/cargo_clean_all.sh - - name: Ensure stubs compile env: DENYWARNINGS: ${{ matrix.deny_warnings }} run: ./_test/ensure_stubs_compile.sh continue-on-error: ${{ matrix.rust == 'beta' && matrix.deny_warnings == '1' }} - - name: Check exercise crate - env: - DENYWARNINGS: ${{ matrix.deny_warnings }} - run: ./_test/check_exercise_crate.sh - continue-on-error: ${{ matrix.rust == 'beta' && matrix.deny_warnings == '1' }} rustformat: name: Check Rust Formatting diff --git a/bin/generate_practice_exercise b/bin/add_practice_exercise similarity index 97% rename from bin/generate_practice_exercise rename to bin/add_practice_exercise index 15a0e2969..ad43c4331 100755 --- a/bin/generate_practice_exercise +++ b/bin/add_practice_exercise @@ -12,7 +12,7 @@ set -euo pipefail # If argument not provided, print usage and exit if [ $# -ne 1 ] && [ $# -ne 2 ] && [ $# -ne 3 ]; then - echo "Usage: bin/generate_practice_exercise.sh [difficulty] [author-github-handle]" + echo "Usage: bin/add_practice_exercise [difficulty] [author-github-handle]" exit 1 fi diff --git a/exercises/practice/binary-search/.docs/instructions.md b/exercises/practice/binary-search/.docs/instructions.md index 4dcaba726..175c4c4ba 100644 --- a/exercises/practice/binary-search/.docs/instructions.md +++ b/exercises/practice/binary-search/.docs/instructions.md @@ -1,35 +1,28 @@ # Instructions -Implement a binary search algorithm. +Your task is to implement a binary search algorithm. -Searching a sorted collection is a common task. A dictionary is a sorted -list of word definitions. Given a word, one can find its definition. A -telephone book is a sorted list of people's names, addresses, and -telephone numbers. Knowing someone's name allows one to quickly find -their telephone number and address. +A binary search algorithm finds an item in a list by repeatedly splitting it in half, only keeping the half which contains the item we're looking for. +It allows us to quickly narrow down the possible locations of our item until we find it, or until we've eliminated all possible locations. -If the list to be searched contains more than a few items (a dozen, say) -a binary search will require far fewer comparisons than a linear search, -but it imposes the requirement that the list be sorted. +```exercism/caution +Binary search only works when a list has been sorted. +``` -In computer science, a binary search or half-interval search algorithm -finds the position of a specified input value (the search "key") within -an array sorted by key value. +The algorithm looks like this: -In each step, the algorithm compares the search key value with the key -value of the middle element of the array. +- Divide the sorted list in half and compare the middle element with the item we're looking for. +- If the middle element is our item, then we're done. +- If the middle element is greater than our item, we can eliminate that number and all the numbers **after** it. +- If the middle element is less than our item, we can eliminate that number and all the numbers **before** it. +- Repeat the process on the part of the list that we kept. -If the keys match, then a matching element has been found and its index, -or position, is returned. +Here's an example: -Otherwise, if the search key is less than the middle element's key, then -the algorithm repeats its action on the sub-array to the left of the -middle element or, if the search key is greater, on the sub-array to the -right. +Let's say we're looking for the number 23 in the following sorted list: `[4, 8, 12, 16, 23, 28, 32]`. -If the remaining array to be searched is empty, then the key cannot be -found in the array and a special "not found" indication is returned. - -A binary search halves the number of items to check with each iteration, -so locating an item (or determining its absence) takes logarithmic time. -A binary search is a dichotomic divide and conquer search algorithm. +- We start by comparing 23 with the middle element, 16. +- Since 23 is greater than 16, we can eliminate the left half of the list, leaving us with `[23, 28, 32]`. +- We then compare 23 with the new middle element, 28. +- Since 23 is less than 28, we can eliminate the right half of the list: `[23]`. +- We've found our item. diff --git a/exercises/practice/binary-search/.docs/introduction.md b/exercises/practice/binary-search/.docs/introduction.md new file mode 100644 index 000000000..66c4b8a45 --- /dev/null +++ b/exercises/practice/binary-search/.docs/introduction.md @@ -0,0 +1,9 @@ +# Introduction + +You have stumbled upon a group of mathematicians who are also singer-songwriters. +They have written a song for each of their favorite numbers, and, as you can imagine, they have a lot of favorite numbers. + +You are curious to hear the song for your favorite number, but with so many songs to wade through, finding the right song could take a while. +Fortunately, they have organized their songs in a playlist sorted by the title — which is simply the number that the song is about. + +You realize that you can use a binary search algorithm to quickly find a song given the title. diff --git a/exercises/practice/bob/.docs/instructions.md b/exercises/practice/bob/.docs/instructions.md index edddb1413..bb702f7bb 100644 --- a/exercises/practice/bob/.docs/instructions.md +++ b/exercises/practice/bob/.docs/instructions.md @@ -1,16 +1,19 @@ # Instructions -Bob is a lackadaisical teenager. In conversation, his responses are very limited. +Your task is to determine what Bob will reply to someone when they say something to him or ask him a question. -Bob answers 'Sure.' if you ask him a question, such as "How are you?". +Bob only ever answers one of five things: -He answers 'Whoa, chill out!' if you YELL AT HIM (in all capitals). - -He answers 'Calm down, I know what I'm doing!' if you yell a question at him. - -He says 'Fine. Be that way!' if you address him without actually saying -anything. - -He answers 'Whatever.' to anything else. - -Bob's conversational partner is a purist when it comes to written communication and always follows normal rules regarding sentence punctuation in English. +- **"Sure."** + This is his response if you ask him a question, such as "How are you?" + The convention used for questions is that it ends with a question mark. +- **"Whoa, chill out!"** + This is his answer if you YELL AT HIM. + The convention used for yelling is ALL CAPITAL LETTERS. +- **"Calm down, I know what I'm doing!"** + This is what he says if you yell a question at him. +- **"Fine. Be that way!"** + This is how he responds to silence. + The convention used for silence is nothing, or various combinations of whitespace characters. +- **"Whatever."** + This is what he answers to anything else. diff --git a/exercises/practice/bob/.docs/introduction.md b/exercises/practice/bob/.docs/introduction.md new file mode 100644 index 000000000..ea4a80776 --- /dev/null +++ b/exercises/practice/bob/.docs/introduction.md @@ -0,0 +1,10 @@ +# Introduction + +Bob is a [lackadaisical][] teenager. +He likes to think that he's very cool. +And he definitely doesn't get excited about things. +That wouldn't be cool. + +When people talk to him, his responses are pretty limited. + +[lackadaisical]: https://www.collinsdictionary.com/dictionary/english/lackadaisical diff --git a/exercises/practice/gigasecond/.docs/instructions.md b/exercises/practice/gigasecond/.docs/instructions.md index 680870f3a..1e20f0022 100644 --- a/exercises/practice/gigasecond/.docs/instructions.md +++ b/exercises/practice/gigasecond/.docs/instructions.md @@ -1,6 +1,8 @@ # Instructions -Given a moment, determine the moment that would be after a gigasecond -has passed. +Your task is to determine the date and time one gigasecond after a certain date. -A gigasecond is 10^9 (1,000,000,000) seconds. +A gigasecond is one thousand million seconds. +That is a one with nine zeros after it. + +If you were born on _January 24th, 2015 at 22:00 (10:00:00pm)_, then you would be a gigasecond old on _October 2nd, 2046 at 23:46:40 (11:46:40pm)_. diff --git a/exercises/practice/gigasecond/.docs/introduction.md b/exercises/practice/gigasecond/.docs/introduction.md new file mode 100644 index 000000000..74afaa994 --- /dev/null +++ b/exercises/practice/gigasecond/.docs/introduction.md @@ -0,0 +1,24 @@ +# Introduction + +The way we measure time is kind of messy. +We have 60 seconds in a minute, and 60 minutes in an hour. +This comes from ancient Babylon, where they used 60 as the basis for their number system. +We have 24 hours in a day, 7 days in a week, and how many days in a month? +Well, for days in a month it depends not only on which month it is, but also on what type of calendar is used in the country you live in. + +What if, instead, we only use seconds to express time intervals? +Then we can use metric system prefixes for writing large numbers of seconds in more easily comprehensible quantities. + +- A food recipe might explain that you need to let the brownies cook in the oven for two kiloseconds (that's two thousand seconds). +- Perhaps you and your family would travel to somewhere exotic for two megaseconds (that's two million seconds). +- And if you and your spouse were married for _a thousand million_ seconds, you would celebrate your one gigasecond anniversary. + +```exercism/note +If we ever colonize Mars or some other planet, measuring time is going to get even messier. +If someone says "year" do they mean a year on Earth or a year on Mars? + +The idea for this exercise came from the science fiction novel ["A Deepness in the Sky"][vinge-novel] by author Vernor Vinge. +In it the author uses the metric system as the basis for time measurements. + +[vinge-novel]: https://www.tor.com/2017/08/03/science-fiction-with-something-for-everyone-a-deepness-in-the-sky-by-vernor-vinge/ +``` diff --git a/exercises/practice/paasio/src/lib.rs b/exercises/practice/paasio/src/lib.rs index 8196a6c96..f530460da 100644 --- a/exercises/practice/paasio/src/lib.rs +++ b/exercises/practice/paasio/src/lib.rs @@ -1,5 +1,8 @@ use std::io::{Read, Result, Write}; +// the PhantomData instances in this file are just to stop compiler complaints +// about missing generics; feel free to remove them + pub struct ReadStats(::std::marker::PhantomData); impl ReadStats { diff --git a/exercises/practice/pangram/.docs/instructions.md b/exercises/practice/pangram/.docs/instructions.md index 5c3bbde35..d5698bc2a 100644 --- a/exercises/practice/pangram/.docs/instructions.md +++ b/exercises/practice/pangram/.docs/instructions.md @@ -1,9 +1,8 @@ # Instructions -Determine if a sentence is a pangram. A pangram (Greek: παν γράμμα, pan gramma, -"every letter") is a sentence using every letter of the alphabet at least once. -The best known English pangram is: -> The quick brown fox jumps over the lazy dog. +Your task is to figure out if a sentence is a pangram. -The alphabet used consists of ASCII letters `a` to `z`, inclusive, and is case -insensitive. Any characters which are not an ASCII letter should be ignored. +A pangram is a sentence using every letter of the alphabet at least once. +It is case insensitive, so it doesn't matter if a letter is lower-case (e.g. `k`) or upper-case (e.g. `K`). + +For this exercise we only use the basic letters used in the English alphabet: `a` to `z`. diff --git a/exercises/practice/pangram/.docs/introduction.md b/exercises/practice/pangram/.docs/introduction.md new file mode 100644 index 000000000..d38fa341d --- /dev/null +++ b/exercises/practice/pangram/.docs/introduction.md @@ -0,0 +1,16 @@ +# Introduction + +You work for a company that sells fonts through their website. +They'd like to show a different sentence each time someone views a font on their website. +To give a comprehensive sense of the font, the random sentences should use **all** the letters in the English alphabet. + +They're running a competition to get suggestions for sentences that they can use. +You're in charge of checking the submissions to see if they are valid. + +```exercism/note +Pangram comes from Greek, παν γράμμα, pan gramma, which means "every letter". + +The best known English pangram is: + +> The quick brown fox jumps over the lazy dog. +``` diff --git a/exercises/practice/secret-handshake/.approaches/config.json b/exercises/practice/secret-handshake/.approaches/config.json new file mode 100644 index 000000000..484a72695 --- /dev/null +++ b/exercises/practice/secret-handshake/.approaches/config.json @@ -0,0 +1,15 @@ +{ + "introduction": { + "authors": ["bobahop"], + "contributors": [] + }, + "approaches": [ + { + "uuid": "cd0c0a31-0905-47f0-815c-02597a7cdf40", + "slug": "iterate-once", + "title": "Iterate once", + "blurb": "Iterate once even when reversed.", + "authors": ["bobahop"] + } + ] +} diff --git a/exercises/practice/secret-handshake/.approaches/introduction.md b/exercises/practice/secret-handshake/.approaches/introduction.md new file mode 100644 index 000000000..7c7d34ff8 --- /dev/null +++ b/exercises/practice/secret-handshake/.approaches/introduction.md @@ -0,0 +1,39 @@ +# Introduction + +There are many ways to solve Secret Handshake. +One approach is to iterate only once, even when the signs are to be reversed. + +## General guidance + +Something to consider is to keep the number of iterations at a minimum to get the best performance. +However, if that is felt to adversely impact readability, then to use a series of `if` statements and then reverse is also valid. + +## Approach: Iterate once + +```rust +const SIGNS: [&'static str; 4] = ["wink", "double blink", "close your eyes", "jump"]; +const REVERSE_SIGNS: u8 = 16; + +pub fn actions(n: u8) -> Vec<&'static str> { + let (mut action, action_incr, end) = match n & REVERSE_SIGNS { + 0 => (0, 1, 4), + _ => (3, -1, -1), + }; + let mut output: Vec<&'static str> = Vec::new(); + + loop { + if action == end { + break; + } + if (n & (1 << action)) != 0 { + output.push(SIGNS[action as usize]) + } + action += action_incr + } + output +} +``` + +For more information, check the [Iterate once approach][approach-iterate-once]. + +[approach-iterate-once]: https://exercism.org/tracks/rust/exercises/secret-handshake/approaches/iterate-once diff --git a/exercises/practice/secret-handshake/.approaches/iterate-once/content.md b/exercises/practice/secret-handshake/.approaches/iterate-once/content.md new file mode 100644 index 000000000..f6430f2ab --- /dev/null +++ b/exercises/practice/secret-handshake/.approaches/iterate-once/content.md @@ -0,0 +1,97 @@ +# Iterate once + +```rust +const SIGNS: [&'static str; 4] = ["wink", "double blink", "close your eyes", "jump"]; +const REVERSE_SIGNS: u8 = 16; + +pub fn actions(n: u8) -> Vec<&'static str> { + let (mut action, action_incr, end) = match n & REVERSE_SIGNS { + 0 => (0, 1, 4), + _ => (3, -1, -1), + }; + let mut output: Vec<&'static str> = Vec::new(); + + loop { + if action == end { + break; + } + if (n & (1 << action)) != 0 { + output.push(SIGNS[action as usize]) + } + action += action_incr + } + output +} +``` + +This approach starts by defining a fixed-size [array][array] to hold the signal values in normal order. + +The `[&'static str; 4]` is used to give the type and length of the array. +To be a [`const`][const], the size of the array must be known at compile time, so setting the type and length must be done explicitly, +so the size in bytes of the fixed array can be deduced by the compiler from that and not by inspecting the element types and counting +the elements itself. + +The value of `16` is defined as a `const` with a meaningful name so it won't be used as a [magic number][magic-number]. + +The `actions` function uses multiple assignment with a `match` expression to define the variables that control iterating through the signals array, +setting their values to iterate in either the normal or reverse order. + +The [bitwise AND operator][bitand] is used to check if the input number contains the signal for reversing the order of the other signals. + +For example, if the number passed in is `19`, which is `10011` in binary, then it is ANDed with `16`, which is `10000` in binary. +The `1` in `10000` is also at the same position in `10011`, so the two values ANDed will not be `0`. +- `10011` AND +- `10000` = +- `10000` + +If the number passed in is `3`, which is `00011` in binary, then it is ANDed with `16`, which is `10000` in binary. +The `1` in `10000` is not at the same position in `00011`, so the two values ANDed will be `0`. +- `00011` AND +- `10000` = +- `00000` + +If the number passed in does not contain the signal for reverse, then the iteration variables are set to iterate through the array of signals +in their normal order, otherwise they are set to iterate through the arrray backwards.. + +The output `vector` is defined, and then the [`loop`][loop] begins. + +Normal iteration will start at index `0`. +Reverse iteration will start at index `3`. + +Normal iteration will terminate when the index equals `4`. +Reverse iteration will terminate when the index equals `-1`. + +Normal iteration will increase the index by `1` for each iteration. +Reverse iteration will decrease the index by `1` for each iteration. + +For each iteration of the `loop`, the AND operator is used to check if the number passed in contains `1` [shifted left][shl] (`<<`) for the number of positions +as the value being iterated. + +```rust +if (n & (1 << action)) != 0 { + output.push(SIGNS[action as usize]) +} +``` + +For example, if the number being iterated is `0`, then `1` is shifted left `0` times (so not shifted at all), and the number passed in is ANDed with `00001`. +If the number passed in is `3`, which is `00011` in binary, then it is ANDed with `00001`. +`00011` ANDed with `00001` is not equal to `0`, so the signal at the index of the array of signals is added to the output `vector`. +The index used is the number being iterated, which is `0`, so the element at index `0` (`"wink"`) would be added to the output `vector` +using the [push][push] function. + +If the number being iterated is `1`, then `1` is shifted left `1` time, and the number passed in is ANDed with `00010`. +If the number passed in is `3`, which is `00011` in binary, then it is ANDed with `00010`. +`00011` ANDed with `00010` is not equal to `0`, so the signal at the index of the array of signals is added to the output `vector`. +The index used is the number being iterated, which is `1`, so the element at index `1` (`"double blink"`) would be added to the output `vector`. + +If the number passed in ANDed with the number being iterated is equal to `0`, then the signal in the array for that index is not added to the output `vector`. + +After iterating through the array of signals is done, the output `vector` is returned from the function. + +[array]: https://doc.rust-lang.org/std/primitive.array.html +[const]: https://doc.rust-lang.org/std/keyword.const.html +[magic-number]: https://en.wikipedia.org/wiki/Magic_number_(programming) +[bitand]: https://doc.rust-lang.org/std/ops/trait.BitAnd.html +[shl]: https://doc.rust-lang.org/std/ops/trait.Shl.html +[loop]: https://doc.rust-lang.org/rust-by-example/flow_control/loop.html +[push]: https://doc.rust-lang.org/std/vec/struct.Vec.html#method.push diff --git a/exercises/practice/secret-handshake/.approaches/iterate-once/snippet.txt b/exercises/practice/secret-handshake/.approaches/iterate-once/snippet.txt new file mode 100644 index 000000000..e42302ba8 --- /dev/null +++ b/exercises/practice/secret-handshake/.approaches/iterate-once/snippet.txt @@ -0,0 +1,4 @@ +let (mut action, action_incr, end) = match n & REVERSE_SIGNS { + 0 => (0, 1, 4), + _ => (3, -1, -1), +}; diff --git a/exercises/practice/secret-handshake/.docs/instructions.md b/exercises/practice/secret-handshake/.docs/instructions.md index 2d6937ae9..77136cf0f 100644 --- a/exercises/practice/secret-handshake/.docs/instructions.md +++ b/exercises/practice/secret-handshake/.docs/instructions.md @@ -1,24 +1,47 @@ # Instructions -> There are 10 types of people in the world: Those who understand -> binary, and those who don't. +Your task is to convert a number between 1 and 31 to a sequence of actions in the secret handshake. -You and your fellow cohort of those in the "know" when it comes to binary decide to come up with a secret "handshake". +The sequence of actions is chosen by looking at the rightmost five digits of the number once it's been converted to binary. +Start at the right-most digit and move left. -```text +The actions for each number place are: + +```plaintext 00001 = wink 00010 = double blink 00100 = close your eyes 01000 = jump - 10000 = Reverse the order of the operations in the secret handshake. ``` -Given a decimal number, convert it to the appropriate sequence of events for a secret handshake. +Let's use the number `9` as an example: + +- 9 in binary is `1001`. +- The digit that is farthest to the right is 1, so the first action is `wink`. +- Going left, the next digit is 0, so there is no double-blink. +- Going left again, the next digit is 0, so you leave your eyes open. +- Going left again, the next digit is 1, so you jump. -Here's a couple of examples: +That was the last digit, so the final code is: + +```plaintext +wink, jump +``` -Given the decimal input 3, the function would return the array ["wink", "double blink"] because the decimal number 3 is 2+1 in powers of two and thus `11` in binary. +Given the number 26, which is `11010` in binary, we get the following actions: + +- double blink +- jump +- reverse actions + +The secret handshake for 26 is therefore: + +```plaintext +jump, double blink +``` -Let's now examine the input 19 which is 16+2+1 in powers of two and thus `10011` in binary. -Recalling that the addition of 16 (`10000` in binary) reverses an array and that we already know what array is returned given input 3, the array returned for input 19 is ["double blink", "wink"]. +~~~~exercism/note +If you aren't sure what binary is or how it works, check out [this binary tutorial][intro-to-binary]. +[intro-to-binary]: https://medium.com/basecs/bits-bytes-building-with-binary-13cb4289aafa +~~~~ diff --git a/exercises/practice/secret-handshake/.docs/introduction.md b/exercises/practice/secret-handshake/.docs/introduction.md new file mode 100644 index 000000000..176b92e8c --- /dev/null +++ b/exercises/practice/secret-handshake/.docs/introduction.md @@ -0,0 +1,7 @@ +# Introduction + +You are starting a secret coding club with some friends and friends-of-friends. +Not everyone knows each other, so you and your friends have decided to create a secret handshake that you can use to recognize that someone is a member. +You don't want anyone who isn't in the know to be able to crack the code. + +You've designed the code so that one person says a number between 1 and 31, and the other person turns it into a series of actions. diff --git a/exercises/practice/sieve/.docs/instructions.md b/exercises/practice/sieve/.docs/instructions.md index 7228737a2..ec14620ce 100644 --- a/exercises/practice/sieve/.docs/instructions.md +++ b/exercises/practice/sieve/.docs/instructions.md @@ -1,30 +1,28 @@ # Instructions -Use the Sieve of Eratosthenes to find all the primes from 2 up to a given -number. +Your task is to create a program that implements the Sieve of Eratosthenes algorithm to find prime numbers. -The Sieve of Eratosthenes is a simple, ancient algorithm for finding all -prime numbers up to any given limit. It does so by iteratively marking as -composite (i.e. not prime) the multiples of each prime, starting with the -multiples of 2. It does not use any division or remainder operation. +A prime number is a number that is only divisible by 1 and itself. +For example, 2, 3, 5, 7, 11, and 13 are prime numbers. -Create your range, starting at two and continuing up to and including the given limit. (i.e. [2, limit]) +The Sieve of Eratosthenes is an ancient algorithm that works by taking a list of numbers and crossing out all the numbers that aren't prime. -The algorithm consists of repeating the following over and over: +A number that is **not** prime is called a "composite number". -- take the next available unmarked number in your list (it is prime) -- mark all the multiples of that number (they are not prime) +To use the Sieve of Eratosthenes, you first create a list of all the numbers between 2 and your given number. +Then you repeat the following steps: -Repeat until you have processed each number in your range. +1. Find the next unmarked number in your list. This is a prime number. +2. Mark all the multiples of that prime number as composite (not prime). -When the algorithm terminates, all the numbers in the list that have not -been marked are prime. +You keep repeating these steps until you've gone through every number in your list. +At the end, all the unmarked numbers are prime. -The wikipedia article has a useful graphic that explains the algorithm: -https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes +```exercism/note +[Wikipedia's Sieve of Eratosthenes article][eratosthenes] has a useful graphic that explains the algorithm. -Notice that this is a very specific algorithm, and the tests don't check -that you've implemented the algorithm, only that you've come up with the -correct list of primes. A good first test is to check that you do not use -division or remainder operations (div, /, mod or % depending on the -language). +The tests don't check that you've implemented the algorithm, only that you've come up with the correct list of primes. +A good first test is to check that you do not use division or remainder operations. + +[eratosthenes]: https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes +``` diff --git a/exercises/practice/sieve/.docs/introduction.md b/exercises/practice/sieve/.docs/introduction.md new file mode 100644 index 000000000..f6c1cf79a --- /dev/null +++ b/exercises/practice/sieve/.docs/introduction.md @@ -0,0 +1,7 @@ +# Introduction + +You bought a big box of random computer parts at a garage sale. +You've started putting the parts together to build custom computers. + +You want to test the performance of different combinations of parts, and decide to create your own benchmarking program to see how your computers compare. +You choose the famous "Sieve of Eratosthenes" algorithm, an ancient algorithm, but one that should push your computers to the limits. From 90d5cab57f4a55dea9080b3c495b2a24faedf0c7 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Thu, 9 Mar 2023 21:21:18 +0100 Subject: [PATCH 074/436] Change casing, fix shellcheck disables, add .shekkcheckrc --- bin/.shellcheckrc | 3 ++ bin/add_practice_exercise | 54 +++++++++++++++++--------------- bin/build_exercise_crate.sh | 27 ---------------- bin/fetch_canonical_data | 3 ++ bin/generate_tests | 20 ++++++------ bin/generator-utils/colors.sh | 40 +++++------------------ bin/generator-utils/prompts.sh | 27 ++++++++-------- bin/generator-utils/templates.sh | 15 ++++++--- bin/generator-utils/utils.sh | 29 +++++++++-------- 9 files changed, 93 insertions(+), 125 deletions(-) create mode 100644 bin/.shellcheckrc delete mode 100755 bin/build_exercise_crate.sh diff --git a/bin/.shellcheckrc b/bin/.shellcheckrc new file mode 100644 index 000000000..5ec9f679b --- /dev/null +++ b/bin/.shellcheckrc @@ -0,0 +1,3 @@ +shell=bash +external-sources=true +disable=SC2001 # https://www.shellcheck.net/wiki/SC2001 - effects most of the sed commands \ No newline at end of file diff --git a/bin/add_practice_exercise b/bin/add_practice_exercise index 08e9f4dbf..ad43c4331 100755 --- a/bin/add_practice_exercise +++ b/bin/add_practice_exercise @@ -1,6 +1,8 @@ #!/usr/bin/env bash -# shellcheck source=/dev/null +# shellcheck source=./generator-utils/utils.sh +# shellcheck source=./generator-utils/prompts.sh +# shellcheck source=./generator-utils/templates.sh source ./bin/generator-utils/utils.sh source ./bin/generator-utils/prompts.sh source ./bin/generator-utils/templates.sh @@ -35,13 +37,13 @@ check_exercise_existence "$1" # ================================================== -SLUG="$1" -HAS_CANONICAL_DATA=true +slug="$1" # Fetch canonical data -canonical_json=$(bin/fetch_canonical_data "$SLUG") +canonical_json=$(bin/fetch_canonical_data "$slug") +has_canonical_data=true if [ "${canonical_json}" == "404: Not Found" ]; then - HAS_CANONICAL_DATA=false + has_canonical_data=false message "warning" "This exercise doesn't have canonical data" else @@ -49,32 +51,31 @@ else message "success" "Fetched canonical data successfully!" fi -UNDERSCORED_SLUG=$(dash_to_underscore "$SLUG") -EXERCISE_DIR="exercises/practice/${SLUG}" -EXERCISE_NAME=$(format_exercise_name "$SLUG") -message "info" "Using ${YELLOW}${EXERCISE_NAME}${BLUE} as a default exercise name. You can edit this later in the config.json file" +underscored_slug=$(dash_to_underscore "$slug") +exercise_dir="exercises/practice/${slug}" +exercise_name=$(format_exercise_name "$slug") +message "info" "Using ${yellow}${exercise_name}${blue} as a default exercise name. You can edit this later in the config.json file" # using default value for difficulty -EXERCISE_DIFFICULTY=$(validate_difficulty_input "${2:-$(get_exercise_difficulty)}") -message "info" "The exercise difficulty has been set to ${YELLOW}${EXERCISE_DIFFICULTY}${BLUE}. You can edit this later in the config.json file" +exercise_difficulty=$(validate_difficulty_input "${2:-$(get_exercise_difficulty)}") +message "info" "The exercise difficulty has been set to ${yellow}${exercise_difficulty}${blue}. You can edit this later in the config.json file" # using default value for author -AUTHOR_HANDLE=${3:-$(get_author_handle)} -message "info" "Using ${YELLOW}${AUTHOR_HANDLE}${BLUE} as author's handle. You can edit this later in the 'authors' field in the ${EXERCISE_DIR}/.meta/config.json file" - - -create_rust_files "$EXERCISE_DIR" "$SLUG" "$HAS_CANONICAL_DATA" +author_handle=${3:-$(get_author_handle)} +message "info" "Using ${yellow}${author_handle}${blue} as author's handle. You can edit this later in the 'authors' field in the ${exercise_dir}/.meta/config.json file" +create_rust_files "$exercise_dir" "$slug" "$has_canonical_data" # ================================================== -# build configlet +# Build configlet ./bin/fetch-configlet message "success" "Fetched configlet successfully!" # Preparing config.json message "info" "Adding instructions and configuration files..." -UUID=$(bin/configlet uuid) +uuid=$(bin/configlet uuid) -jq --arg slug "$SLUG" --arg uuid "$UUID" --arg name "$EXERCISE_NAME" --arg difficulty "$EXERCISE_DIFFICULTY" \ +# Add exercise-data to global config.json +jq --arg slug "$slug" --arg uuid "$uuid" --arg name "$exercise_name" --arg difficulty "$exercise_difficulty" \ '.exercises.practice += [{slug: $slug, name: $name, uuid: $uuid, practices: [], prerequisites: [], difficulty: $difficulty}]' \ config.json >config.json.tmp # jq always rounds whole numbers, but average_run_time needs to be a float @@ -84,18 +85,19 @@ message "success" "Added instructions and configuration files" # Create instructions and config files echo "Creating instructions and config files" -./bin/configlet sync --update --yes --docs --metadata --exercise "$SLUG" -./bin/configlet sync --update --yes --filepaths --exercise "$SLUG" -./bin/configlet sync --update --tests include --exercise "$SLUG" +./bin/configlet sync --update --yes --docs --metadata --exercise "$slug" +./bin/configlet sync --update --yes --filepaths --exercise "$slug" +./bin/configlet sync --update --tests include --exercise "$slug" message "success" "Created instructions and config files" -META_CONFIG="$EXERCISE_DIR"/.meta/config.json -jq --arg author "$AUTHOR_HANDLE" '.authors += [$author]' "$META_CONFIG" >"$META_CONFIG".tmp && mv "$META_CONFIG".tmp "$META_CONFIG" +# Push author to "authors" array in ./meta/config.json +meta_config="$exercise_dir"/.meta/config.json +jq --arg author "$author_handle" '.authors += [$author]' "$meta_config" >"$meta_config".tmp && mv "$meta_config".tmp "$meta_config" message "success" "You've been added as the author of this exercise." -sed -i "s/name = \".*\"/name = \"$UNDERSCORED_SLUG\"/" "$EXERCISE_DIR"/Cargo.toml +sed -i "s/name = \".*\"/name = \"$underscored_slug\"/" "$exercise_dir"/Cargo.toml message "done" "All stub files were created." message "info" "After implementing the solution, tests and configuration, please run:" -echo "./bin/configlet fmt --update --yes --exercise ${SLUG}" +echo "./bin/configlet fmt --update --yes --exercise ${slug}" diff --git a/bin/build_exercise_crate.sh b/bin/build_exercise_crate.sh deleted file mode 100755 index ca92b77f4..000000000 --- a/bin/build_exercise_crate.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash -set -e -# Compile the 'exercise' crate and put it in the 'bin/' folder - -TRACK_ROOT="$(git rev-parse --show-toplevel)" - -EXERCISE_CRATE_PATH="$TRACK_ROOT/util/exercise" - -BIN_DIR_PATH="$TRACK_ROOT/bin" - -( - cd "$EXERCISE_CRATE_PATH" - - echo "Building exercise crate" - - cargo build --release - - RELEASE_PATH="$EXERCISE_CRATE_PATH/target/release/exercise" - - if [ -f "$RELEASE_PATH.exe" ]; then - RELEASE_PATH="$RELEASE_PATH.exe" - fi - - echo "Copying exercise crate from $RELEASE_PATH into $BIN_DIR_PATH" - - cp "$RELEASE_PATH" "$BIN_DIR_PATH" -) diff --git a/bin/fetch_canonical_data b/bin/fetch_canonical_data index a487581f2..69ca5fde0 100755 --- a/bin/fetch_canonical_data +++ b/bin/fetch_canonical_data @@ -1,6 +1,9 @@ #!/usr/bin/env bash # This script fetches the canonical data of the exercise. +# Exit if anything fails. +set -euo pipefail + if [ $# -ne 1 ]; then echo "Usage: bin/fetch_canonical_data " exit 1 diff --git a/bin/generate_tests b/bin/generate_tests index ce8875285..939853584 100755 --- a/bin/generate_tests +++ b/bin/generate_tests @@ -1,12 +1,14 @@ #!/usr/bin/env bash +# Exit if anything fails. set -euo pipefail -# shellcheck source=/dev/null + + +# shellcheck source=./generator-utils/utils.sh source ./bin/generator-utils/utils.sh -function digest_template() { +digest_template() { template=$(cat bin/test_template) - # shellcheck disable=SC2001 # turn every token into a jq command echo "$template" | sed 's/${\([^}]*\)\}\$/$(echo $case | jq -r '\''.\1'\'')/g' } @@ -27,20 +29,20 @@ use $(dash_to_underscore "$SLUG")::*; EOT # Flattens canonical json, extracts only the objects with a uuid -cases=$(echo "$canonical_json" | jq '[ .. | objects | with_entries(select(.key | IN("uuid", "description", "input", "expected", "property"))) | select(. != {}) | select(has("uuid")) ]') +CASES=$(echo "$CANONICAL_JSON" | jq '[ .. | objects | with_entries(select(.key | IN("uuid", "description", "input", "expected", "property"))) | select(. != {}) | select(has("uuid")) ]') # shellcheck disable=SC2034 -jq -c '.[]' <<<"$cases" | while read -r case; do +jq -c '.[]' <<<"$CASES" | while read -r case; do # Evaluate the bash parts and replace them with their return values - eval_template="$(digest_template | sed -e "s/\$(\(.*\))/\$\(\1\)/g")" - eval_template="$(eval "echo \"$eval_template\"")" + EVAL_TEMPLATE="$(digest_template | sed -e "s/\$(\(.*\))/\$\(\1\)/g")" + EVAL_TEMPLATE="$(eval "echo \"$EVAL_TEMPLATE\"")" # Turn function name unto snake_case - formatted_template=$(echo "$eval_template" | sed -e ':loop' -e 's/\(fn[^(]*\)[ -]/\1_/g' -e 't loop' | sed 's/fn_/fn /') + FORMATTED_TEMPLATE=$(echo "$EVAL_TEMPLATE" | sed -e ':loop' -e 's/\(fn[^(]*\)[ -]/\1_/g' -e 't loop' | sed 's/fn_/fn /') # Push to file - echo "$formatted_template" >>"$TEST_FILE" + echo "$FORMATTED_TEMPLATE" >>"$TEST_FILE" printf "\\n" >>"$TEST_FILE" done diff --git a/bin/generator-utils/colors.sh b/bin/generator-utils/colors.sh index 3ae992587..c3b2f4079 100644 --- a/bin/generator-utils/colors.sh +++ b/bin/generator-utils/colors.sh @@ -1,38 +1,14 @@ #!/usr/bin/env bash -# shellcheck disable=SC2034 -# Reset -RESET=$(echo -e '\033[0m') +reset_color=$(echo -e '\033[0m') -# Regular Colors -BLACK=$(echo -e '\033[0;30m') -RED=$(echo -e '\033[0;31m') -GREEN=$(echo -e '\033[0;32m') -YELLOW=$(echo -e '\033[0;33m') -BLUE=$(echo -e '\033[0;34m') -PURPLE=$(echo -e '\033[0;35m') -CYAN=$(echo -e '\033[0;36m') -WHITE=$(echo -e '\033[0;37m') +red=$(echo -e '\033[0;31m') +green=$(echo -e '\033[0;32m') +yellow=$(echo -e '\033[0;33m') +blue=$(echo -e '\033[0;34m') +cyan=$(echo -e '\033[0;36m') -# Bold -BOLD_BLACK=$(echo -e '\033[1;30m') -BOLD_RED=$(echo -e '\033[1;31m') -BOLD_GREEN=$(echo -e '\033[1;32m') -BOLD_YELLOW=$(echo -e '\033[1;33m') -BOLD_BLUE=$(echo -e '\033[1;34m') -BOLD_PURPLE=$(echo -e '\033[1;35m') -BOLD_CYAN=$(echo -e '\033[1;36m') -BOLD_WHITE=$(echo -e '\033[1;37m') +bold_green=$(echo -e '\033[1;32m') -# Underline -UNDERLINE=$(echo -e ='\033[4m') -# Background -BG_BLACK=$(echo -e ='\033[40m') -BG_RED=$(echo -e ='\033[41m') -BG_GREEN=$(echo -e ='\033[42m') -BG_YELLOW=$(echo -e ='\033[43m') -BG_BLUE=$(echo -e ='\033[44m') -BG_PURPLE=$(echo -e ='\033[45m') -BG_CYAN=$(echo -e ='\033[46m') -BG_WHITE=$(echo -e ='\033[47m') +export red green blue yellow bold_green reset_color cyan \ No newline at end of file diff --git a/bin/generator-utils/prompts.sh b/bin/generator-utils/prompts.sh index c785a2636..9835e286f 100644 --- a/bin/generator-utils/prompts.sh +++ b/bin/generator-utils/prompts.sh @@ -1,22 +1,22 @@ #!/usr/bin/env bash -# shellcheck source=/dev/null +# shellcheck source=./colors.sh source ./bin/generator-utils/colors.sh -function get_exercise_difficulty() { +get_exercise_difficulty() { read -rp "Difficulty of exercise (1-10): " exercise_difficulty echo "$exercise_difficulty" } -function validate_difficulty_input() { +validate_difficulty_input() { - valid_input=false + local valid_input=false while ! $valid_input; do if [[ "$1" =~ ^[1-9]$|^10$ ]]; then - exercise_difficulty=$1 - valid_input=true + local exercise_difficulty=$1 + local valid_input=true else - read -rp "${RED}Invalid input. ${RESET}Please enter an integer between 1 and 10. " exercise_difficulty + read -rp "${red}Invalid input. ${reset_color}Please enter an integer between 1 and 10. " exercise_difficulty [[ "$exercise_difficulty" =~ ^[1-9]$|^10$ ]] && valid_input=true fi @@ -24,15 +24,16 @@ function validate_difficulty_input() { echo "$exercise_difficulty" } -function get_author_handle { - DEFAULT_AUTHOR_HANDLE="$(git config user.name)" +get_author_handle() { + default_author_handle="$(git config user.name)" + local default_author_handle - if [ -z "$DEFAULT_AUTHOR_HANDLE" ]; then - read -rp "Hey! Couldn't find your Github handle. Add it now or skip with enter and add it later in the .meta.config.json file: " AUTHOR_HANDLE + if [ -z "$default_author_handle" ]; then + read -rp "Hey! Couldn't find your Github handle. Add it now or skip with enter and add it later in the .meta.config.json file: " author_handle else - AUTHOR_HANDLE="$DEFAULT_AUTHOR_HANDLE" + local author_handle="$default_author_handle" fi - echo "$AUTHOR_HANDLE" + echo "$author_handle" } diff --git a/bin/generator-utils/templates.sh b/bin/generator-utils/templates.sh index 5c957314a..ed51a2319 100755 --- a/bin/generator-utils/templates.sh +++ b/bin/generator-utils/templates.sh @@ -1,14 +1,15 @@ #!/usr/bin/env bash -# shellcheck source=/dev/null +# shellcheck source=./utils.sh source ./bin/generator-utils/utils.sh -function create_fn_name() { - slug=$1 - has_canonical_data=$2 +create_fn_name() { + local slug=$1 + local has_canonical_data=$2 if [ "$has_canonical_data" == false ]; then fn_name=$(dash_to_underscore "$slug") + local fn_name else fn_name=$(jq -r 'first(.. | .property? // empty)' canonical_data.json) fi @@ -17,7 +18,7 @@ function create_fn_name() { } -function create_test_file_template() { +create_test_file_template() { local exercise_dir=$1 local slug=$2 local has_canonical_data=$3 @@ -40,12 +41,15 @@ EOT message "success" "Stub file for tests has been created!" else canonical_json=$(cat canonical_data.json) + local canonical_json # sometimes canonical data has multiple levels with multiple `cases` arrays. #(see kindergarten-garden https://github.com/exercism/problem-specifications/blob/main/exercises/kindergarten-garden/canonical-data.json) # so let's flatten it cases=$(echo "$canonical_json" | jq '[ .. | objects | with_entries(select(.key | IN("uuid", "description", "input", "expected"))) | select(. != {}) | select(has("uuid")) ]') + local cases fn_name=$(echo "$canonical_json" | jq -r 'first(.. | .property? // empty)') + local fn_name first_iteration=true # loop through each object @@ -108,6 +112,7 @@ function create_example_rs_template() { local has_canonical_data=$3 fn_name=$(create_fn_name "$slug" "$has_canonical_data") + local fn_name mkdir "${exercise_dir}/.meta" cat <"${exercise_dir}/.meta/example.rs" diff --git a/bin/generator-utils/utils.sh b/bin/generator-utils/utils.sh index e89b33e67..f0c2713c0 100755 --- a/bin/generator-utils/utils.sh +++ b/bin/generator-utils/utils.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# shellcheck source=/dev/null +# shellcheck source=./colors.sh source ./bin/generator-utils/colors.sh function message() { @@ -8,16 +8,19 @@ function message() { local message=$2 case "$flag" in - "success") printf "${GREEN}%s${RESET}\n" "[success]: $message" ;; - "info") printf "${BLUE}%s${RESET}\n" "[info]: $message" ;; - "warning") printf "${YELLOW}%s${RESET}\n" "[warning]: $message" ;; - "error") printf "${RED}%s${RESET}\n" "[error]: $message" ;; + "success") printf "${green}%s${reset_color}\n" "[success]: $message" ;; + "task") printf "${cyan}%s${reset_color}\n" "[task]: $message" ;; + "info") printf "${blue}%s${reset_color}\n" "[info]: $message" ;; + "warning") printf "${yellow}%s${reset_color}\n" "[warning]: $message" ;; + "error") printf "${red}%s${reset_color}\n" "[error]: $message" ;; "done") echo + # Create a dashed-line as wide as the screen cols=$(tput cols) + local cols printf "%*s\n" "$cols" "" | tr " " "-" echo - printf "${BOLD_GREEN}%s${RESET}\n" "[done]: $message" + printf "${bold_green}%s${reset_color}\n" "[done]: $message" ;; *) echo "Invalid flag: $flag" @@ -25,19 +28,18 @@ function message() { esac } -function dash_to_underscore() { - # shellcheck disable=SC2001 +dash_to_underscore() { echo "$1" | sed 's/-/_/g' } # exercise_name -> Exercise Name -function format_exercise_name { +format_exercise_name() { echo "$1" | sed 's/-/ /g; s/\b\(.\)/\u\1/g' } -function check_exercise_existence() { +check_exercise_existence() { message "info" "Looking for exercise.." - slug="$1" + local slug="$1" # Check if exercise is already in config.json if jq '.exercises.practice | map(.slug)' config.json | grep -q "$slug"; then echo "${1} has already been implemented." @@ -45,6 +47,7 @@ function check_exercise_existence() { fi # fetch configlet and crop out exercise list + local unimplemented_exercises unimplemented_exercises=$(bin/configlet info | sed -n '/With canonical data:/,/Track summary:/p' | sed -e '/\(With\(out\)\? canonical data:\|Track summary:\)/d' -e '/^$/d') if echo "$unimplemented_exercises" | grep -q "^$slug$"; then message "success" "Exercise has been found!" @@ -57,11 +60,11 @@ ${unimplemented_exercises}" # see util/ngram for source # First it builds a binary for the system of the contributor if [ -e bin/generator-utils/ngram ]; then - echo "${YELLOW}$(bin/generator-utils/ngram "${unimplemented_exercises}" "$slug")${RESET}" + echo "${yellow}$(bin/generator-utils/ngram "${unimplemented_exercises}" "$slug")${reset_color}" else message "info" "Building typo-checker binary for $(uname -m) system." - cd util/ngram && ./build && cd ../.. && echo "${YELLOW}$(bin/generator-utils/ngram "${unimplemented_exercises}" "$slug")${RESET}" + cd util/ngram && ./build && cd ../.. && echo "${yellow}$(bin/generator-utils/ngram "${unimplemented_exercises}" "$slug")${reset_color}" fi exit 1 From e93023b9e60d9201cbbd870f6d973785294bf66e Mon Sep 17 00:00:00 2001 From: dem4ron Date: Thu, 9 Mar 2023 21:29:57 +0100 Subject: [PATCH 075/436] Fix local variables --- bin/generator-utils/prompts.sh | 2 +- bin/generator-utils/templates.sh | 9 +++++---- bin/generator-utils/utils.sh | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/bin/generator-utils/prompts.sh b/bin/generator-utils/prompts.sh index 9835e286f..30ac172ae 100644 --- a/bin/generator-utils/prompts.sh +++ b/bin/generator-utils/prompts.sh @@ -25,8 +25,8 @@ validate_difficulty_input() { } get_author_handle() { - default_author_handle="$(git config user.name)" local default_author_handle + default_author_handle="$(git config user.name)" if [ -z "$default_author_handle" ]; then read -rp "Hey! Couldn't find your Github handle. Add it now or skip with enter and add it later in the .meta.config.json file: " author_handle diff --git a/bin/generator-utils/templates.sh b/bin/generator-utils/templates.sh index ed51a2319..c96576d8d 100755 --- a/bin/generator-utils/templates.sh +++ b/bin/generator-utils/templates.sh @@ -40,16 +40,16 @@ EOT message "info" "This exercise doesn't have canonical data." message "success" "Stub file for tests has been created!" else - canonical_json=$(cat canonical_data.json) local canonical_json + canonical_json=$(cat canonical_data.json) # sometimes canonical data has multiple levels with multiple `cases` arrays. #(see kindergarten-garden https://github.com/exercism/problem-specifications/blob/main/exercises/kindergarten-garden/canonical-data.json) # so let's flatten it - cases=$(echo "$canonical_json" | jq '[ .. | objects | with_entries(select(.key | IN("uuid", "description", "input", "expected"))) | select(. != {}) | select(has("uuid")) ]') local cases - fn_name=$(echo "$canonical_json" | jq -r 'first(.. | .property? // empty)') + cases=$(echo "$canonical_json" | jq '[ .. | objects | with_entries(select(.key | IN("uuid", "description", "input", "expected"))) | select(. != {}) | select(has("uuid")) ]') local fn_name + fn_name=$(echo "$canonical_json" | jq -r 'first(.. | .property? // empty)') first_iteration=true # loop through each object @@ -82,6 +82,7 @@ function create_lib_rs_template() { local exercise_dir=$1 local slug=$2 local has_canonical_data=$3 + local fn_name fn_name=$(create_fn_name "$slug" "$has_canonical_data") cat <"${exercise_dir}/src/lib.rs" pub fn ${fn_name}() { @@ -111,8 +112,8 @@ function create_example_rs_template() { local slug=$2 local has_canonical_data=$3 - fn_name=$(create_fn_name "$slug" "$has_canonical_data") local fn_name + fn_name=$(create_fn_name "$slug" "$has_canonical_data") mkdir "${exercise_dir}/.meta" cat <"${exercise_dir}/.meta/example.rs" diff --git a/bin/generator-utils/utils.sh b/bin/generator-utils/utils.sh index f0c2713c0..02876ee3a 100755 --- a/bin/generator-utils/utils.sh +++ b/bin/generator-utils/utils.sh @@ -16,8 +16,8 @@ function message() { "done") echo # Create a dashed-line as wide as the screen - cols=$(tput cols) local cols + cols=$(tput cols) printf "%*s\n" "$cols" "" | tr " " "-" echo printf "${bold_green}%s${reset_color}\n" "[done]: $message" From 961639a5abfed605cfc8dbe2ed2bb25e273e3b26 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Thu, 9 Mar 2023 21:38:44 +0100 Subject: [PATCH 076/436] Lowercase var names in `bin/generate_tests`, remove redundant shellcheck disable --- bin/generate_tests | 38 ++++++++++++++++++------------------ bin/generator-utils/utils.sh | 6 +++--- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/bin/generate_tests b/bin/generate_tests index 939853584..91283d567 100755 --- a/bin/generate_tests +++ b/bin/generate_tests @@ -9,44 +9,44 @@ source ./bin/generator-utils/utils.sh digest_template() { template=$(cat bin/test_template) - # turn every token into a jq command + # Turn every token into a jq command echo "$template" | sed 's/${\([^}]*\)\}\$/$(echo $case | jq -r '\''.\1'\'')/g' } message "info" "Generating tests.." canonical_json=$(cat canonical_data.json) -SLUG=$(echo "$canonical_json" | jq '.exercise') -# shellcheck disable=SC2001 +slug=$(echo "$canonical_json" | jq '.exercise') # Remove double quotes -SLUG=$(echo "$SLUG" | sed 's/"//g') -EXERCISE_DIR="exercises/practice/$SLUG" -TEST_FILE="$EXERCISE_DIR/tests/$SLUG.rs" +slug=$(echo "$slug" | sed 's/"//g') +exercise_dir="exercises/practice/$slug" +test_file="$exercise_dir/tests/$slug.rs" -cat <"$TEST_FILE" -use $(dash_to_underscore "$SLUG")::*; +cat <"$test_file" +use $(dash_to_underscore "$slug")::*; // Add tests here EOT # Flattens canonical json, extracts only the objects with a uuid -CASES=$(echo "$CANONICAL_JSON" | jq '[ .. | objects | with_entries(select(.key | IN("uuid", "description", "input", "expected", "property"))) | select(. != {}) | select(has("uuid")) ]') +cases=$(echo "$canonical_json" | jq '[ .. | objects | with_entries(select(.key | IN("uuid", "description", "input", "expected", "property"))) | select(. != {}) | select(has("uuid")) ]') +# Shellcheck doesn't recognize that `case` is not unused # shellcheck disable=SC2034 -jq -c '.[]' <<<"$CASES" | while read -r case; do +jq -c '.[]' <<<"$cases" | while read -r case; do # Evaluate the bash parts and replace them with their return values - EVAL_TEMPLATE="$(digest_template | sed -e "s/\$(\(.*\))/\$\(\1\)/g")" - EVAL_TEMPLATE="$(eval "echo \"$EVAL_TEMPLATE\"")" + eval_template="$(digest_template | sed -e "s/\$(\(.*\))/\$\(\1\)/g")" + eval_template="$(eval "echo \"$eval_template\"")" - # Turn function name unto snake_case - FORMATTED_TEMPLATE=$(echo "$EVAL_TEMPLATE" | sed -e ':loop' -e 's/\(fn[^(]*\)[ -]/\1_/g' -e 't loop' | sed 's/fn_/fn /') - # Push to file + # Turn function name into snake_case + formatted_template=$(echo "$eval_template" | sed -e ':loop' -e 's/\(fn[^(]*\)[ -]/\1_/g' -e 't loop' | sed 's/fn_/fn /') - echo "$FORMATTED_TEMPLATE" >>"$TEST_FILE" - printf "\\n" >>"$TEST_FILE" + # Push to test file + echo "$formatted_template" >>"$test_file" + printf "\\n" >>"$test_file" done -rustfmt "$TEST_FILE" +rustfmt "$test_file" -message "success" "Generated tests successfully! Check out ${TEST_FILE}" +message "success" "Generated tests successfully! Check out ${test_file}" diff --git a/bin/generator-utils/utils.sh b/bin/generator-utils/utils.sh index 02876ee3a..8e4cec1f7 100755 --- a/bin/generator-utils/utils.sh +++ b/bin/generator-utils/utils.sh @@ -15,7 +15,7 @@ function message() { "error") printf "${red}%s${reset_color}\n" "[error]: $message" ;; "done") echo - # Create a dashed-line as wide as the screen + # Generate a dashed line that spans the entire width of the screen. local cols cols=$(tput cols) printf "%*s\n" "$cols" "" | tr " " "-" @@ -46,7 +46,7 @@ check_exercise_existence() { exit 1 fi - # fetch configlet and crop out exercise list + # Fetch configlet and crop out exercise list local unimplemented_exercises unimplemented_exercises=$(bin/configlet info | sed -n '/With canonical data:/,/Track summary:/p' | sed -e '/\(With\(out\)\? canonical data:\|Track summary:\)/d' -e '/^$/d') if echo "$unimplemented_exercises" | grep -q "^$slug$"; then @@ -57,7 +57,7 @@ check_exercise_existence() { ${unimplemented_exercises}" # Find closest match to typed-in not-found slug - # see util/ngram for source + # See util/ngram for source # First it builds a binary for the system of the contributor if [ -e bin/generator-utils/ngram ]; then echo "${yellow}$(bin/generator-utils/ngram "${unimplemented_exercises}" "$slug")${reset_color}" From 93509714f63487a2b0afd8a04e5ece41cb07f1d7 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Thu, 9 Mar 2023 21:41:45 +0100 Subject: [PATCH 077/436] remove `function` from functions --- bin/generate_tests | 1 + bin/generator-utils/templates.sh | 8 ++++---- bin/generator-utils/utils.sh | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/bin/generate_tests b/bin/generate_tests index 91283d567..3a0992c07 100755 --- a/bin/generate_tests +++ b/bin/generate_tests @@ -8,6 +8,7 @@ set -euo pipefail source ./bin/generator-utils/utils.sh digest_template() { + local template template=$(cat bin/test_template) # Turn every token into a jq command echo "$template" | sed 's/${\([^}]*\)\}\$/$(echo $case | jq -r '\''.\1'\'')/g' diff --git a/bin/generator-utils/templates.sh b/bin/generator-utils/templates.sh index c96576d8d..922bc3c42 100755 --- a/bin/generator-utils/templates.sh +++ b/bin/generator-utils/templates.sh @@ -78,7 +78,7 @@ EOT } -function create_lib_rs_template() { +create_lib_rs_template() { local exercise_dir=$1 local slug=$2 local has_canonical_data=$3 @@ -92,7 +92,7 @@ EOT message "success" "Stub file for lib.rs has been created!" } -function overwrite_gitignore() { +overwrite_gitignore() { local exercise_dir=$1 cat <"$exercise_dir"/.gitignore # Generated by Cargo @@ -107,7 +107,7 @@ EOT message "success" ".gitignore has been overwritten!" } -function create_example_rs_template() { +create_example_rs_template() { local exercise_dir=$1 local slug=$2 local has_canonical_data=$3 @@ -126,7 +126,7 @@ EOT message "success" "Stub file for example.rs has been created!" } -function create_rust_files() { +create_rust_files() { local exercise_dir=$1 local slug=$2 local has_canonical_data=$3 diff --git a/bin/generator-utils/utils.sh b/bin/generator-utils/utils.sh index 8e4cec1f7..bae45efb9 100755 --- a/bin/generator-utils/utils.sh +++ b/bin/generator-utils/utils.sh @@ -3,7 +3,7 @@ # shellcheck source=./colors.sh source ./bin/generator-utils/colors.sh -function message() { +message() { local flag=$1 local message=$2 From 4bf3cd80b7496473c0778d62a47004c2b9228205 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Thu, 9 Mar 2023 21:21:18 +0100 Subject: [PATCH 078/436] Change casing, fix shellcheck disables, add .shekkcheckrc --- bin/generate_tests | 8 ++++---- bin/generator-utils/templates.sh | 2 ++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/bin/generate_tests b/bin/generate_tests index 3a0992c07..bf40142c7 100755 --- a/bin/generate_tests +++ b/bin/generate_tests @@ -29,15 +29,15 @@ use $(dash_to_underscore "$slug")::*; EOT # Flattens canonical json, extracts only the objects with a uuid -cases=$(echo "$canonical_json" | jq '[ .. | objects | with_entries(select(.key | IN("uuid", "description", "input", "expected", "property"))) | select(. != {}) | select(has("uuid")) ]') +CASES=$(echo "$CANONICAL_JSON" | jq '[ .. | objects | with_entries(select(.key | IN("uuid", "description", "input", "expected", "property"))) | select(. != {}) | select(has("uuid")) ]') # Shellcheck doesn't recognize that `case` is not unused # shellcheck disable=SC2034 -jq -c '.[]' <<<"$cases" | while read -r case; do +jq -c '.[]' <<<"$CASES" | while read -r case; do # Evaluate the bash parts and replace them with their return values - eval_template="$(digest_template | sed -e "s/\$(\(.*\))/\$\(\1\)/g")" - eval_template="$(eval "echo \"$eval_template\"")" + EVAL_TEMPLATE="$(digest_template | sed -e "s/\$(\(.*\))/\$\(\1\)/g")" + EVAL_TEMPLATE="$(eval "echo \"$EVAL_TEMPLATE\"")" # Turn function name into snake_case formatted_template=$(echo "$eval_template" | sed -e ':loop' -e 's/\(fn[^(]*\)[ -]/\1_/g' -e 't loop' | sed 's/fn_/fn /') diff --git a/bin/generator-utils/templates.sh b/bin/generator-utils/templates.sh index 922bc3c42..f783c7d4e 100755 --- a/bin/generator-utils/templates.sh +++ b/bin/generator-utils/templates.sh @@ -42,6 +42,7 @@ EOT else local canonical_json canonical_json=$(cat canonical_data.json) + local canonical_json # sometimes canonical data has multiple levels with multiple `cases` arrays. #(see kindergarten-garden https://github.com/exercism/problem-specifications/blob/main/exercises/kindergarten-garden/canonical-data.json) @@ -114,6 +115,7 @@ create_example_rs_template() { local fn_name fn_name=$(create_fn_name "$slug" "$has_canonical_data") + local fn_name mkdir "${exercise_dir}/.meta" cat <"${exercise_dir}/.meta/example.rs" From f24c72443eb79114d54d6fa34b514ecae555a5e7 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Thu, 9 Mar 2023 21:29:57 +0100 Subject: [PATCH 079/436] Fix local variables --- bin/generator-utils/templates.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/bin/generator-utils/templates.sh b/bin/generator-utils/templates.sh index f783c7d4e..922bc3c42 100755 --- a/bin/generator-utils/templates.sh +++ b/bin/generator-utils/templates.sh @@ -42,7 +42,6 @@ EOT else local canonical_json canonical_json=$(cat canonical_data.json) - local canonical_json # sometimes canonical data has multiple levels with multiple `cases` arrays. #(see kindergarten-garden https://github.com/exercism/problem-specifications/blob/main/exercises/kindergarten-garden/canonical-data.json) @@ -115,7 +114,6 @@ create_example_rs_template() { local fn_name fn_name=$(create_fn_name "$slug" "$has_canonical_data") - local fn_name mkdir "${exercise_dir}/.meta" cat <"${exercise_dir}/.meta/example.rs" From 2228c1569c1d043b3e3eedb1892a5c51d1302bd5 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Thu, 9 Mar 2023 21:38:44 +0100 Subject: [PATCH 080/436] Lowercase var names in `bin/generate_tests`, remove redundant shellcheck disable --- bin/generate_tests | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bin/generate_tests b/bin/generate_tests index bf40142c7..3a0992c07 100755 --- a/bin/generate_tests +++ b/bin/generate_tests @@ -29,15 +29,15 @@ use $(dash_to_underscore "$slug")::*; EOT # Flattens canonical json, extracts only the objects with a uuid -CASES=$(echo "$CANONICAL_JSON" | jq '[ .. | objects | with_entries(select(.key | IN("uuid", "description", "input", "expected", "property"))) | select(. != {}) | select(has("uuid")) ]') +cases=$(echo "$canonical_json" | jq '[ .. | objects | with_entries(select(.key | IN("uuid", "description", "input", "expected", "property"))) | select(. != {}) | select(has("uuid")) ]') # Shellcheck doesn't recognize that `case` is not unused # shellcheck disable=SC2034 -jq -c '.[]' <<<"$CASES" | while read -r case; do +jq -c '.[]' <<<"$cases" | while read -r case; do # Evaluate the bash parts and replace them with their return values - EVAL_TEMPLATE="$(digest_template | sed -e "s/\$(\(.*\))/\$\(\1\)/g")" - EVAL_TEMPLATE="$(eval "echo \"$EVAL_TEMPLATE\"")" + eval_template="$(digest_template | sed -e "s/\$(\(.*\))/\$\(\1\)/g")" + eval_template="$(eval "echo \"$eval_template\"")" # Turn function name into snake_case formatted_template=$(echo "$eval_template" | sed -e ':loop' -e 's/\(fn[^(]*\)[ -]/\1_/g' -e 't loop' | sed 's/fn_/fn /') From a531c61a9f7292c944e6ce18f0867f8d2287c893 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Fri, 10 Mar 2023 00:30:11 +0100 Subject: [PATCH 081/436] trying something out --- bin/.shellcheckrc | 3 ++ bin/add_practice_exercise | 54 +++++++++++++++++--------------- bin/fetch_canonical_data | 3 ++ bin/generate_tests | 37 ++++++++++++---------- bin/generator-utils/colors.sh | 40 +++++------------------ bin/generator-utils/prompts.sh | 27 ++++++++-------- bin/generator-utils/templates.sh | 24 ++++++++------ bin/generator-utils/utils.sh | 35 +++++++++++---------- 8 files changed, 110 insertions(+), 113 deletions(-) create mode 100644 bin/.shellcheckrc diff --git a/bin/.shellcheckrc b/bin/.shellcheckrc new file mode 100644 index 000000000..5ec9f679b --- /dev/null +++ b/bin/.shellcheckrc @@ -0,0 +1,3 @@ +shell=bash +external-sources=true +disable=SC2001 # https://www.shellcheck.net/wiki/SC2001 - effects most of the sed commands \ No newline at end of file diff --git a/bin/add_practice_exercise b/bin/add_practice_exercise index 08e9f4dbf..ad43c4331 100755 --- a/bin/add_practice_exercise +++ b/bin/add_practice_exercise @@ -1,6 +1,8 @@ #!/usr/bin/env bash -# shellcheck source=/dev/null +# shellcheck source=./generator-utils/utils.sh +# shellcheck source=./generator-utils/prompts.sh +# shellcheck source=./generator-utils/templates.sh source ./bin/generator-utils/utils.sh source ./bin/generator-utils/prompts.sh source ./bin/generator-utils/templates.sh @@ -35,13 +37,13 @@ check_exercise_existence "$1" # ================================================== -SLUG="$1" -HAS_CANONICAL_DATA=true +slug="$1" # Fetch canonical data -canonical_json=$(bin/fetch_canonical_data "$SLUG") +canonical_json=$(bin/fetch_canonical_data "$slug") +has_canonical_data=true if [ "${canonical_json}" == "404: Not Found" ]; then - HAS_CANONICAL_DATA=false + has_canonical_data=false message "warning" "This exercise doesn't have canonical data" else @@ -49,32 +51,31 @@ else message "success" "Fetched canonical data successfully!" fi -UNDERSCORED_SLUG=$(dash_to_underscore "$SLUG") -EXERCISE_DIR="exercises/practice/${SLUG}" -EXERCISE_NAME=$(format_exercise_name "$SLUG") -message "info" "Using ${YELLOW}${EXERCISE_NAME}${BLUE} as a default exercise name. You can edit this later in the config.json file" +underscored_slug=$(dash_to_underscore "$slug") +exercise_dir="exercises/practice/${slug}" +exercise_name=$(format_exercise_name "$slug") +message "info" "Using ${yellow}${exercise_name}${blue} as a default exercise name. You can edit this later in the config.json file" # using default value for difficulty -EXERCISE_DIFFICULTY=$(validate_difficulty_input "${2:-$(get_exercise_difficulty)}") -message "info" "The exercise difficulty has been set to ${YELLOW}${EXERCISE_DIFFICULTY}${BLUE}. You can edit this later in the config.json file" +exercise_difficulty=$(validate_difficulty_input "${2:-$(get_exercise_difficulty)}") +message "info" "The exercise difficulty has been set to ${yellow}${exercise_difficulty}${blue}. You can edit this later in the config.json file" # using default value for author -AUTHOR_HANDLE=${3:-$(get_author_handle)} -message "info" "Using ${YELLOW}${AUTHOR_HANDLE}${BLUE} as author's handle. You can edit this later in the 'authors' field in the ${EXERCISE_DIR}/.meta/config.json file" - - -create_rust_files "$EXERCISE_DIR" "$SLUG" "$HAS_CANONICAL_DATA" +author_handle=${3:-$(get_author_handle)} +message "info" "Using ${yellow}${author_handle}${blue} as author's handle. You can edit this later in the 'authors' field in the ${exercise_dir}/.meta/config.json file" +create_rust_files "$exercise_dir" "$slug" "$has_canonical_data" # ================================================== -# build configlet +# Build configlet ./bin/fetch-configlet message "success" "Fetched configlet successfully!" # Preparing config.json message "info" "Adding instructions and configuration files..." -UUID=$(bin/configlet uuid) +uuid=$(bin/configlet uuid) -jq --arg slug "$SLUG" --arg uuid "$UUID" --arg name "$EXERCISE_NAME" --arg difficulty "$EXERCISE_DIFFICULTY" \ +# Add exercise-data to global config.json +jq --arg slug "$slug" --arg uuid "$uuid" --arg name "$exercise_name" --arg difficulty "$exercise_difficulty" \ '.exercises.practice += [{slug: $slug, name: $name, uuid: $uuid, practices: [], prerequisites: [], difficulty: $difficulty}]' \ config.json >config.json.tmp # jq always rounds whole numbers, but average_run_time needs to be a float @@ -84,18 +85,19 @@ message "success" "Added instructions and configuration files" # Create instructions and config files echo "Creating instructions and config files" -./bin/configlet sync --update --yes --docs --metadata --exercise "$SLUG" -./bin/configlet sync --update --yes --filepaths --exercise "$SLUG" -./bin/configlet sync --update --tests include --exercise "$SLUG" +./bin/configlet sync --update --yes --docs --metadata --exercise "$slug" +./bin/configlet sync --update --yes --filepaths --exercise "$slug" +./bin/configlet sync --update --tests include --exercise "$slug" message "success" "Created instructions and config files" -META_CONFIG="$EXERCISE_DIR"/.meta/config.json -jq --arg author "$AUTHOR_HANDLE" '.authors += [$author]' "$META_CONFIG" >"$META_CONFIG".tmp && mv "$META_CONFIG".tmp "$META_CONFIG" +# Push author to "authors" array in ./meta/config.json +meta_config="$exercise_dir"/.meta/config.json +jq --arg author "$author_handle" '.authors += [$author]' "$meta_config" >"$meta_config".tmp && mv "$meta_config".tmp "$meta_config" message "success" "You've been added as the author of this exercise." -sed -i "s/name = \".*\"/name = \"$UNDERSCORED_SLUG\"/" "$EXERCISE_DIR"/Cargo.toml +sed -i "s/name = \".*\"/name = \"$underscored_slug\"/" "$exercise_dir"/Cargo.toml message "done" "All stub files were created." message "info" "After implementing the solution, tests and configuration, please run:" -echo "./bin/configlet fmt --update --yes --exercise ${SLUG}" +echo "./bin/configlet fmt --update --yes --exercise ${slug}" diff --git a/bin/fetch_canonical_data b/bin/fetch_canonical_data index a487581f2..69ca5fde0 100755 --- a/bin/fetch_canonical_data +++ b/bin/fetch_canonical_data @@ -1,6 +1,9 @@ #!/usr/bin/env bash # This script fetches the canonical data of the exercise. +# Exit if anything fails. +set -euo pipefail + if [ $# -ne 1 ]; then echo "Usage: bin/fetch_canonical_data " exit 1 diff --git a/bin/generate_tests b/bin/generate_tests index ce8875285..3a0992c07 100755 --- a/bin/generate_tests +++ b/bin/generate_tests @@ -1,27 +1,29 @@ #!/usr/bin/env bash +# Exit if anything fails. set -euo pipefail -# shellcheck source=/dev/null + + +# shellcheck source=./generator-utils/utils.sh source ./bin/generator-utils/utils.sh -function digest_template() { +digest_template() { + local template template=$(cat bin/test_template) - # shellcheck disable=SC2001 - # turn every token into a jq command + # Turn every token into a jq command echo "$template" | sed 's/${\([^}]*\)\}\$/$(echo $case | jq -r '\''.\1'\'')/g' } message "info" "Generating tests.." canonical_json=$(cat canonical_data.json) -SLUG=$(echo "$canonical_json" | jq '.exercise') -# shellcheck disable=SC2001 +slug=$(echo "$canonical_json" | jq '.exercise') # Remove double quotes -SLUG=$(echo "$SLUG" | sed 's/"//g') -EXERCISE_DIR="exercises/practice/$SLUG" -TEST_FILE="$EXERCISE_DIR/tests/$SLUG.rs" +slug=$(echo "$slug" | sed 's/"//g') +exercise_dir="exercises/practice/$slug" +test_file="$exercise_dir/tests/$slug.rs" -cat <"$TEST_FILE" -use $(dash_to_underscore "$SLUG")::*; +cat <"$test_file" +use $(dash_to_underscore "$slug")::*; // Add tests here EOT @@ -29,6 +31,7 @@ EOT # Flattens canonical json, extracts only the objects with a uuid cases=$(echo "$canonical_json" | jq '[ .. | objects | with_entries(select(.key | IN("uuid", "description", "input", "expected", "property"))) | select(. != {}) | select(has("uuid")) ]') +# Shellcheck doesn't recognize that `case` is not unused # shellcheck disable=SC2034 jq -c '.[]' <<<"$cases" | while read -r case; do @@ -36,15 +39,15 @@ jq -c '.[]' <<<"$cases" | while read -r case; do eval_template="$(digest_template | sed -e "s/\$(\(.*\))/\$\(\1\)/g")" eval_template="$(eval "echo \"$eval_template\"")" - # Turn function name unto snake_case + # Turn function name into snake_case formatted_template=$(echo "$eval_template" | sed -e ':loop' -e 's/\(fn[^(]*\)[ -]/\1_/g' -e 't loop' | sed 's/fn_/fn /') - # Push to file - echo "$formatted_template" >>"$TEST_FILE" - printf "\\n" >>"$TEST_FILE" + # Push to test file + echo "$formatted_template" >>"$test_file" + printf "\\n" >>"$test_file" done -rustfmt "$TEST_FILE" +rustfmt "$test_file" -message "success" "Generated tests successfully! Check out ${TEST_FILE}" +message "success" "Generated tests successfully! Check out ${test_file}" diff --git a/bin/generator-utils/colors.sh b/bin/generator-utils/colors.sh index 3ae992587..c7e5a7d55 100644 --- a/bin/generator-utils/colors.sh +++ b/bin/generator-utils/colors.sh @@ -1,38 +1,14 @@ #!/usr/bin/env bash -# shellcheck disable=SC2034 -# Reset -RESET=$(echo -e '\033[0m') +reset_color=$(echo -e '\033[0m') -# Regular Colors -BLACK=$(echo -e '\033[0;30m') -RED=$(echo -e '\033[0;31m') -GREEN=$(echo -e '\033[0;32m') -YELLOW=$(echo -e '\033[0;33m') -BLUE=$(echo -e '\033[0;34m') -PURPLE=$(echo -e '\033[0;35m') -CYAN=$(echo -e '\033[0;36m') -WHITE=$(echo -e '\033[0;37m') +red=$(echo -e '\033[0;31m') +green=$(echo -e '\033[0;32m') +yellow=$(echo -e '\033[0;33m') +blue=$(echo -e '\033[0;34m') +cyan=$(echo -e '\033[0;36m') -# Bold -BOLD_BLACK=$(echo -e '\033[1;30m') -BOLD_RED=$(echo -e '\033[1;31m') -BOLD_GREEN=$(echo -e '\033[1;32m') -BOLD_YELLOW=$(echo -e '\033[1;33m') -BOLD_BLUE=$(echo -e '\033[1;34m') -BOLD_PURPLE=$(echo -e '\033[1;35m') -BOLD_CYAN=$(echo -e '\033[1;36m') -BOLD_WHITE=$(echo -e '\033[1;37m') +bold_green=$(echo -e '\033[1;32m') -# Underline -UNDERLINE=$(echo -e ='\033[4m') -# Background -BG_BLACK=$(echo -e ='\033[40m') -BG_RED=$(echo -e ='\033[41m') -BG_GREEN=$(echo -e ='\033[42m') -BG_YELLOW=$(echo -e ='\033[43m') -BG_BLUE=$(echo -e ='\033[44m') -BG_PURPLE=$(echo -e ='\033[45m') -BG_CYAN=$(echo -e ='\033[46m') -BG_WHITE=$(echo -e ='\033[47m') +export red green blue yellow bold_green reset_color cyan diff --git a/bin/generator-utils/prompts.sh b/bin/generator-utils/prompts.sh index c785a2636..30ac172ae 100644 --- a/bin/generator-utils/prompts.sh +++ b/bin/generator-utils/prompts.sh @@ -1,22 +1,22 @@ #!/usr/bin/env bash -# shellcheck source=/dev/null +# shellcheck source=./colors.sh source ./bin/generator-utils/colors.sh -function get_exercise_difficulty() { +get_exercise_difficulty() { read -rp "Difficulty of exercise (1-10): " exercise_difficulty echo "$exercise_difficulty" } -function validate_difficulty_input() { +validate_difficulty_input() { - valid_input=false + local valid_input=false while ! $valid_input; do if [[ "$1" =~ ^[1-9]$|^10$ ]]; then - exercise_difficulty=$1 - valid_input=true + local exercise_difficulty=$1 + local valid_input=true else - read -rp "${RED}Invalid input. ${RESET}Please enter an integer between 1 and 10. " exercise_difficulty + read -rp "${red}Invalid input. ${reset_color}Please enter an integer between 1 and 10. " exercise_difficulty [[ "$exercise_difficulty" =~ ^[1-9]$|^10$ ]] && valid_input=true fi @@ -24,15 +24,16 @@ function validate_difficulty_input() { echo "$exercise_difficulty" } -function get_author_handle { - DEFAULT_AUTHOR_HANDLE="$(git config user.name)" +get_author_handle() { + local default_author_handle + default_author_handle="$(git config user.name)" - if [ -z "$DEFAULT_AUTHOR_HANDLE" ]; then - read -rp "Hey! Couldn't find your Github handle. Add it now or skip with enter and add it later in the .meta.config.json file: " AUTHOR_HANDLE + if [ -z "$default_author_handle" ]; then + read -rp "Hey! Couldn't find your Github handle. Add it now or skip with enter and add it later in the .meta.config.json file: " author_handle else - AUTHOR_HANDLE="$DEFAULT_AUTHOR_HANDLE" + local author_handle="$default_author_handle" fi - echo "$AUTHOR_HANDLE" + echo "$author_handle" } diff --git a/bin/generator-utils/templates.sh b/bin/generator-utils/templates.sh index 5c957314a..922bc3c42 100755 --- a/bin/generator-utils/templates.sh +++ b/bin/generator-utils/templates.sh @@ -1,14 +1,15 @@ #!/usr/bin/env bash -# shellcheck source=/dev/null +# shellcheck source=./utils.sh source ./bin/generator-utils/utils.sh -function create_fn_name() { - slug=$1 - has_canonical_data=$2 +create_fn_name() { + local slug=$1 + local has_canonical_data=$2 if [ "$has_canonical_data" == false ]; then fn_name=$(dash_to_underscore "$slug") + local fn_name else fn_name=$(jq -r 'first(.. | .property? // empty)' canonical_data.json) fi @@ -17,7 +18,7 @@ function create_fn_name() { } -function create_test_file_template() { +create_test_file_template() { local exercise_dir=$1 local slug=$2 local has_canonical_data=$3 @@ -39,12 +40,15 @@ EOT message "info" "This exercise doesn't have canonical data." message "success" "Stub file for tests has been created!" else + local canonical_json canonical_json=$(cat canonical_data.json) # sometimes canonical data has multiple levels with multiple `cases` arrays. #(see kindergarten-garden https://github.com/exercism/problem-specifications/blob/main/exercises/kindergarten-garden/canonical-data.json) # so let's flatten it + local cases cases=$(echo "$canonical_json" | jq '[ .. | objects | with_entries(select(.key | IN("uuid", "description", "input", "expected"))) | select(. != {}) | select(has("uuid")) ]') + local fn_name fn_name=$(echo "$canonical_json" | jq -r 'first(.. | .property? // empty)') first_iteration=true @@ -74,10 +78,11 @@ EOT } -function create_lib_rs_template() { +create_lib_rs_template() { local exercise_dir=$1 local slug=$2 local has_canonical_data=$3 + local fn_name fn_name=$(create_fn_name "$slug" "$has_canonical_data") cat <"${exercise_dir}/src/lib.rs" pub fn ${fn_name}() { @@ -87,7 +92,7 @@ EOT message "success" "Stub file for lib.rs has been created!" } -function overwrite_gitignore() { +overwrite_gitignore() { local exercise_dir=$1 cat <"$exercise_dir"/.gitignore # Generated by Cargo @@ -102,11 +107,12 @@ EOT message "success" ".gitignore has been overwritten!" } -function create_example_rs_template() { +create_example_rs_template() { local exercise_dir=$1 local slug=$2 local has_canonical_data=$3 + local fn_name fn_name=$(create_fn_name "$slug" "$has_canonical_data") mkdir "${exercise_dir}/.meta" @@ -120,7 +126,7 @@ EOT message "success" "Stub file for example.rs has been created!" } -function create_rust_files() { +create_rust_files() { local exercise_dir=$1 local slug=$2 local has_canonical_data=$3 diff --git a/bin/generator-utils/utils.sh b/bin/generator-utils/utils.sh index e89b33e67..bae45efb9 100755 --- a/bin/generator-utils/utils.sh +++ b/bin/generator-utils/utils.sh @@ -1,23 +1,26 @@ #!/usr/bin/env bash -# shellcheck source=/dev/null +# shellcheck source=./colors.sh source ./bin/generator-utils/colors.sh -function message() { +message() { local flag=$1 local message=$2 case "$flag" in - "success") printf "${GREEN}%s${RESET}\n" "[success]: $message" ;; - "info") printf "${BLUE}%s${RESET}\n" "[info]: $message" ;; - "warning") printf "${YELLOW}%s${RESET}\n" "[warning]: $message" ;; - "error") printf "${RED}%s${RESET}\n" "[error]: $message" ;; + "success") printf "${green}%s${reset_color}\n" "[success]: $message" ;; + "task") printf "${cyan}%s${reset_color}\n" "[task]: $message" ;; + "info") printf "${blue}%s${reset_color}\n" "[info]: $message" ;; + "warning") printf "${yellow}%s${reset_color}\n" "[warning]: $message" ;; + "error") printf "${red}%s${reset_color}\n" "[error]: $message" ;; "done") echo + # Generate a dashed line that spans the entire width of the screen. + local cols cols=$(tput cols) printf "%*s\n" "$cols" "" | tr " " "-" echo - printf "${BOLD_GREEN}%s${RESET}\n" "[done]: $message" + printf "${bold_green}%s${reset_color}\n" "[done]: $message" ;; *) echo "Invalid flag: $flag" @@ -25,26 +28,26 @@ function message() { esac } -function dash_to_underscore() { - # shellcheck disable=SC2001 +dash_to_underscore() { echo "$1" | sed 's/-/_/g' } # exercise_name -> Exercise Name -function format_exercise_name { +format_exercise_name() { echo "$1" | sed 's/-/ /g; s/\b\(.\)/\u\1/g' } -function check_exercise_existence() { +check_exercise_existence() { message "info" "Looking for exercise.." - slug="$1" + local slug="$1" # Check if exercise is already in config.json if jq '.exercises.practice | map(.slug)' config.json | grep -q "$slug"; then echo "${1} has already been implemented." exit 1 fi - # fetch configlet and crop out exercise list + # Fetch configlet and crop out exercise list + local unimplemented_exercises unimplemented_exercises=$(bin/configlet info | sed -n '/With canonical data:/,/Track summary:/p' | sed -e '/\(With\(out\)\? canonical data:\|Track summary:\)/d' -e '/^$/d') if echo "$unimplemented_exercises" | grep -q "^$slug$"; then message "success" "Exercise has been found!" @@ -54,14 +57,14 @@ function check_exercise_existence() { ${unimplemented_exercises}" # Find closest match to typed-in not-found slug - # see util/ngram for source + # See util/ngram for source # First it builds a binary for the system of the contributor if [ -e bin/generator-utils/ngram ]; then - echo "${YELLOW}$(bin/generator-utils/ngram "${unimplemented_exercises}" "$slug")${RESET}" + echo "${yellow}$(bin/generator-utils/ngram "${unimplemented_exercises}" "$slug")${reset_color}" else message "info" "Building typo-checker binary for $(uname -m) system." - cd util/ngram && ./build && cd ../.. && echo "${YELLOW}$(bin/generator-utils/ngram "${unimplemented_exercises}" "$slug")${RESET}" + cd util/ngram && ./build && cd ../.. && echo "${yellow}$(bin/generator-utils/ngram "${unimplemented_exercises}" "$slug")${reset_color}" fi exit 1 From 39bcf548db12d227170b9edcf1ddd4493dd7e538 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Fri, 10 Mar 2023 00:30:11 +0100 Subject: [PATCH 082/436] Lowercase vars, remove `function`, add .shellcheckrc --- bin/.shellcheckrc | 3 ++ bin/add_practice_exercise | 54 +++++++++++++++++--------------- bin/fetch_canonical_data | 3 ++ bin/generate_tests | 37 ++++++++++++---------- bin/generator-utils/colors.sh | 40 +++++------------------ bin/generator-utils/prompts.sh | 27 ++++++++-------- bin/generator-utils/templates.sh | 24 ++++++++------ bin/generator-utils/utils.sh | 35 +++++++++++---------- 8 files changed, 110 insertions(+), 113 deletions(-) create mode 100644 bin/.shellcheckrc diff --git a/bin/.shellcheckrc b/bin/.shellcheckrc new file mode 100644 index 000000000..5ec9f679b --- /dev/null +++ b/bin/.shellcheckrc @@ -0,0 +1,3 @@ +shell=bash +external-sources=true +disable=SC2001 # https://www.shellcheck.net/wiki/SC2001 - effects most of the sed commands \ No newline at end of file diff --git a/bin/add_practice_exercise b/bin/add_practice_exercise index 08e9f4dbf..ad43c4331 100755 --- a/bin/add_practice_exercise +++ b/bin/add_practice_exercise @@ -1,6 +1,8 @@ #!/usr/bin/env bash -# shellcheck source=/dev/null +# shellcheck source=./generator-utils/utils.sh +# shellcheck source=./generator-utils/prompts.sh +# shellcheck source=./generator-utils/templates.sh source ./bin/generator-utils/utils.sh source ./bin/generator-utils/prompts.sh source ./bin/generator-utils/templates.sh @@ -35,13 +37,13 @@ check_exercise_existence "$1" # ================================================== -SLUG="$1" -HAS_CANONICAL_DATA=true +slug="$1" # Fetch canonical data -canonical_json=$(bin/fetch_canonical_data "$SLUG") +canonical_json=$(bin/fetch_canonical_data "$slug") +has_canonical_data=true if [ "${canonical_json}" == "404: Not Found" ]; then - HAS_CANONICAL_DATA=false + has_canonical_data=false message "warning" "This exercise doesn't have canonical data" else @@ -49,32 +51,31 @@ else message "success" "Fetched canonical data successfully!" fi -UNDERSCORED_SLUG=$(dash_to_underscore "$SLUG") -EXERCISE_DIR="exercises/practice/${SLUG}" -EXERCISE_NAME=$(format_exercise_name "$SLUG") -message "info" "Using ${YELLOW}${EXERCISE_NAME}${BLUE} as a default exercise name. You can edit this later in the config.json file" +underscored_slug=$(dash_to_underscore "$slug") +exercise_dir="exercises/practice/${slug}" +exercise_name=$(format_exercise_name "$slug") +message "info" "Using ${yellow}${exercise_name}${blue} as a default exercise name. You can edit this later in the config.json file" # using default value for difficulty -EXERCISE_DIFFICULTY=$(validate_difficulty_input "${2:-$(get_exercise_difficulty)}") -message "info" "The exercise difficulty has been set to ${YELLOW}${EXERCISE_DIFFICULTY}${BLUE}. You can edit this later in the config.json file" +exercise_difficulty=$(validate_difficulty_input "${2:-$(get_exercise_difficulty)}") +message "info" "The exercise difficulty has been set to ${yellow}${exercise_difficulty}${blue}. You can edit this later in the config.json file" # using default value for author -AUTHOR_HANDLE=${3:-$(get_author_handle)} -message "info" "Using ${YELLOW}${AUTHOR_HANDLE}${BLUE} as author's handle. You can edit this later in the 'authors' field in the ${EXERCISE_DIR}/.meta/config.json file" - - -create_rust_files "$EXERCISE_DIR" "$SLUG" "$HAS_CANONICAL_DATA" +author_handle=${3:-$(get_author_handle)} +message "info" "Using ${yellow}${author_handle}${blue} as author's handle. You can edit this later in the 'authors' field in the ${exercise_dir}/.meta/config.json file" +create_rust_files "$exercise_dir" "$slug" "$has_canonical_data" # ================================================== -# build configlet +# Build configlet ./bin/fetch-configlet message "success" "Fetched configlet successfully!" # Preparing config.json message "info" "Adding instructions and configuration files..." -UUID=$(bin/configlet uuid) +uuid=$(bin/configlet uuid) -jq --arg slug "$SLUG" --arg uuid "$UUID" --arg name "$EXERCISE_NAME" --arg difficulty "$EXERCISE_DIFFICULTY" \ +# Add exercise-data to global config.json +jq --arg slug "$slug" --arg uuid "$uuid" --arg name "$exercise_name" --arg difficulty "$exercise_difficulty" \ '.exercises.practice += [{slug: $slug, name: $name, uuid: $uuid, practices: [], prerequisites: [], difficulty: $difficulty}]' \ config.json >config.json.tmp # jq always rounds whole numbers, but average_run_time needs to be a float @@ -84,18 +85,19 @@ message "success" "Added instructions and configuration files" # Create instructions and config files echo "Creating instructions and config files" -./bin/configlet sync --update --yes --docs --metadata --exercise "$SLUG" -./bin/configlet sync --update --yes --filepaths --exercise "$SLUG" -./bin/configlet sync --update --tests include --exercise "$SLUG" +./bin/configlet sync --update --yes --docs --metadata --exercise "$slug" +./bin/configlet sync --update --yes --filepaths --exercise "$slug" +./bin/configlet sync --update --tests include --exercise "$slug" message "success" "Created instructions and config files" -META_CONFIG="$EXERCISE_DIR"/.meta/config.json -jq --arg author "$AUTHOR_HANDLE" '.authors += [$author]' "$META_CONFIG" >"$META_CONFIG".tmp && mv "$META_CONFIG".tmp "$META_CONFIG" +# Push author to "authors" array in ./meta/config.json +meta_config="$exercise_dir"/.meta/config.json +jq --arg author "$author_handle" '.authors += [$author]' "$meta_config" >"$meta_config".tmp && mv "$meta_config".tmp "$meta_config" message "success" "You've been added as the author of this exercise." -sed -i "s/name = \".*\"/name = \"$UNDERSCORED_SLUG\"/" "$EXERCISE_DIR"/Cargo.toml +sed -i "s/name = \".*\"/name = \"$underscored_slug\"/" "$exercise_dir"/Cargo.toml message "done" "All stub files were created." message "info" "After implementing the solution, tests and configuration, please run:" -echo "./bin/configlet fmt --update --yes --exercise ${SLUG}" +echo "./bin/configlet fmt --update --yes --exercise ${slug}" diff --git a/bin/fetch_canonical_data b/bin/fetch_canonical_data index a487581f2..69ca5fde0 100755 --- a/bin/fetch_canonical_data +++ b/bin/fetch_canonical_data @@ -1,6 +1,9 @@ #!/usr/bin/env bash # This script fetches the canonical data of the exercise. +# Exit if anything fails. +set -euo pipefail + if [ $# -ne 1 ]; then echo "Usage: bin/fetch_canonical_data " exit 1 diff --git a/bin/generate_tests b/bin/generate_tests index ce8875285..3a0992c07 100755 --- a/bin/generate_tests +++ b/bin/generate_tests @@ -1,27 +1,29 @@ #!/usr/bin/env bash +# Exit if anything fails. set -euo pipefail -# shellcheck source=/dev/null + + +# shellcheck source=./generator-utils/utils.sh source ./bin/generator-utils/utils.sh -function digest_template() { +digest_template() { + local template template=$(cat bin/test_template) - # shellcheck disable=SC2001 - # turn every token into a jq command + # Turn every token into a jq command echo "$template" | sed 's/${\([^}]*\)\}\$/$(echo $case | jq -r '\''.\1'\'')/g' } message "info" "Generating tests.." canonical_json=$(cat canonical_data.json) -SLUG=$(echo "$canonical_json" | jq '.exercise') -# shellcheck disable=SC2001 +slug=$(echo "$canonical_json" | jq '.exercise') # Remove double quotes -SLUG=$(echo "$SLUG" | sed 's/"//g') -EXERCISE_DIR="exercises/practice/$SLUG" -TEST_FILE="$EXERCISE_DIR/tests/$SLUG.rs" +slug=$(echo "$slug" | sed 's/"//g') +exercise_dir="exercises/practice/$slug" +test_file="$exercise_dir/tests/$slug.rs" -cat <"$TEST_FILE" -use $(dash_to_underscore "$SLUG")::*; +cat <"$test_file" +use $(dash_to_underscore "$slug")::*; // Add tests here EOT @@ -29,6 +31,7 @@ EOT # Flattens canonical json, extracts only the objects with a uuid cases=$(echo "$canonical_json" | jq '[ .. | objects | with_entries(select(.key | IN("uuid", "description", "input", "expected", "property"))) | select(. != {}) | select(has("uuid")) ]') +# Shellcheck doesn't recognize that `case` is not unused # shellcheck disable=SC2034 jq -c '.[]' <<<"$cases" | while read -r case; do @@ -36,15 +39,15 @@ jq -c '.[]' <<<"$cases" | while read -r case; do eval_template="$(digest_template | sed -e "s/\$(\(.*\))/\$\(\1\)/g")" eval_template="$(eval "echo \"$eval_template\"")" - # Turn function name unto snake_case + # Turn function name into snake_case formatted_template=$(echo "$eval_template" | sed -e ':loop' -e 's/\(fn[^(]*\)[ -]/\1_/g' -e 't loop' | sed 's/fn_/fn /') - # Push to file - echo "$formatted_template" >>"$TEST_FILE" - printf "\\n" >>"$TEST_FILE" + # Push to test file + echo "$formatted_template" >>"$test_file" + printf "\\n" >>"$test_file" done -rustfmt "$TEST_FILE" +rustfmt "$test_file" -message "success" "Generated tests successfully! Check out ${TEST_FILE}" +message "success" "Generated tests successfully! Check out ${test_file}" diff --git a/bin/generator-utils/colors.sh b/bin/generator-utils/colors.sh index 3ae992587..c7e5a7d55 100644 --- a/bin/generator-utils/colors.sh +++ b/bin/generator-utils/colors.sh @@ -1,38 +1,14 @@ #!/usr/bin/env bash -# shellcheck disable=SC2034 -# Reset -RESET=$(echo -e '\033[0m') +reset_color=$(echo -e '\033[0m') -# Regular Colors -BLACK=$(echo -e '\033[0;30m') -RED=$(echo -e '\033[0;31m') -GREEN=$(echo -e '\033[0;32m') -YELLOW=$(echo -e '\033[0;33m') -BLUE=$(echo -e '\033[0;34m') -PURPLE=$(echo -e '\033[0;35m') -CYAN=$(echo -e '\033[0;36m') -WHITE=$(echo -e '\033[0;37m') +red=$(echo -e '\033[0;31m') +green=$(echo -e '\033[0;32m') +yellow=$(echo -e '\033[0;33m') +blue=$(echo -e '\033[0;34m') +cyan=$(echo -e '\033[0;36m') -# Bold -BOLD_BLACK=$(echo -e '\033[1;30m') -BOLD_RED=$(echo -e '\033[1;31m') -BOLD_GREEN=$(echo -e '\033[1;32m') -BOLD_YELLOW=$(echo -e '\033[1;33m') -BOLD_BLUE=$(echo -e '\033[1;34m') -BOLD_PURPLE=$(echo -e '\033[1;35m') -BOLD_CYAN=$(echo -e '\033[1;36m') -BOLD_WHITE=$(echo -e '\033[1;37m') +bold_green=$(echo -e '\033[1;32m') -# Underline -UNDERLINE=$(echo -e ='\033[4m') -# Background -BG_BLACK=$(echo -e ='\033[40m') -BG_RED=$(echo -e ='\033[41m') -BG_GREEN=$(echo -e ='\033[42m') -BG_YELLOW=$(echo -e ='\033[43m') -BG_BLUE=$(echo -e ='\033[44m') -BG_PURPLE=$(echo -e ='\033[45m') -BG_CYAN=$(echo -e ='\033[46m') -BG_WHITE=$(echo -e ='\033[47m') +export red green blue yellow bold_green reset_color cyan diff --git a/bin/generator-utils/prompts.sh b/bin/generator-utils/prompts.sh index c785a2636..30ac172ae 100644 --- a/bin/generator-utils/prompts.sh +++ b/bin/generator-utils/prompts.sh @@ -1,22 +1,22 @@ #!/usr/bin/env bash -# shellcheck source=/dev/null +# shellcheck source=./colors.sh source ./bin/generator-utils/colors.sh -function get_exercise_difficulty() { +get_exercise_difficulty() { read -rp "Difficulty of exercise (1-10): " exercise_difficulty echo "$exercise_difficulty" } -function validate_difficulty_input() { +validate_difficulty_input() { - valid_input=false + local valid_input=false while ! $valid_input; do if [[ "$1" =~ ^[1-9]$|^10$ ]]; then - exercise_difficulty=$1 - valid_input=true + local exercise_difficulty=$1 + local valid_input=true else - read -rp "${RED}Invalid input. ${RESET}Please enter an integer between 1 and 10. " exercise_difficulty + read -rp "${red}Invalid input. ${reset_color}Please enter an integer between 1 and 10. " exercise_difficulty [[ "$exercise_difficulty" =~ ^[1-9]$|^10$ ]] && valid_input=true fi @@ -24,15 +24,16 @@ function validate_difficulty_input() { echo "$exercise_difficulty" } -function get_author_handle { - DEFAULT_AUTHOR_HANDLE="$(git config user.name)" +get_author_handle() { + local default_author_handle + default_author_handle="$(git config user.name)" - if [ -z "$DEFAULT_AUTHOR_HANDLE" ]; then - read -rp "Hey! Couldn't find your Github handle. Add it now or skip with enter and add it later in the .meta.config.json file: " AUTHOR_HANDLE + if [ -z "$default_author_handle" ]; then + read -rp "Hey! Couldn't find your Github handle. Add it now or skip with enter and add it later in the .meta.config.json file: " author_handle else - AUTHOR_HANDLE="$DEFAULT_AUTHOR_HANDLE" + local author_handle="$default_author_handle" fi - echo "$AUTHOR_HANDLE" + echo "$author_handle" } diff --git a/bin/generator-utils/templates.sh b/bin/generator-utils/templates.sh index 5c957314a..922bc3c42 100755 --- a/bin/generator-utils/templates.sh +++ b/bin/generator-utils/templates.sh @@ -1,14 +1,15 @@ #!/usr/bin/env bash -# shellcheck source=/dev/null +# shellcheck source=./utils.sh source ./bin/generator-utils/utils.sh -function create_fn_name() { - slug=$1 - has_canonical_data=$2 +create_fn_name() { + local slug=$1 + local has_canonical_data=$2 if [ "$has_canonical_data" == false ]; then fn_name=$(dash_to_underscore "$slug") + local fn_name else fn_name=$(jq -r 'first(.. | .property? // empty)' canonical_data.json) fi @@ -17,7 +18,7 @@ function create_fn_name() { } -function create_test_file_template() { +create_test_file_template() { local exercise_dir=$1 local slug=$2 local has_canonical_data=$3 @@ -39,12 +40,15 @@ EOT message "info" "This exercise doesn't have canonical data." message "success" "Stub file for tests has been created!" else + local canonical_json canonical_json=$(cat canonical_data.json) # sometimes canonical data has multiple levels with multiple `cases` arrays. #(see kindergarten-garden https://github.com/exercism/problem-specifications/blob/main/exercises/kindergarten-garden/canonical-data.json) # so let's flatten it + local cases cases=$(echo "$canonical_json" | jq '[ .. | objects | with_entries(select(.key | IN("uuid", "description", "input", "expected"))) | select(. != {}) | select(has("uuid")) ]') + local fn_name fn_name=$(echo "$canonical_json" | jq -r 'first(.. | .property? // empty)') first_iteration=true @@ -74,10 +78,11 @@ EOT } -function create_lib_rs_template() { +create_lib_rs_template() { local exercise_dir=$1 local slug=$2 local has_canonical_data=$3 + local fn_name fn_name=$(create_fn_name "$slug" "$has_canonical_data") cat <"${exercise_dir}/src/lib.rs" pub fn ${fn_name}() { @@ -87,7 +92,7 @@ EOT message "success" "Stub file for lib.rs has been created!" } -function overwrite_gitignore() { +overwrite_gitignore() { local exercise_dir=$1 cat <"$exercise_dir"/.gitignore # Generated by Cargo @@ -102,11 +107,12 @@ EOT message "success" ".gitignore has been overwritten!" } -function create_example_rs_template() { +create_example_rs_template() { local exercise_dir=$1 local slug=$2 local has_canonical_data=$3 + local fn_name fn_name=$(create_fn_name "$slug" "$has_canonical_data") mkdir "${exercise_dir}/.meta" @@ -120,7 +126,7 @@ EOT message "success" "Stub file for example.rs has been created!" } -function create_rust_files() { +create_rust_files() { local exercise_dir=$1 local slug=$2 local has_canonical_data=$3 diff --git a/bin/generator-utils/utils.sh b/bin/generator-utils/utils.sh index e89b33e67..bae45efb9 100755 --- a/bin/generator-utils/utils.sh +++ b/bin/generator-utils/utils.sh @@ -1,23 +1,26 @@ #!/usr/bin/env bash -# shellcheck source=/dev/null +# shellcheck source=./colors.sh source ./bin/generator-utils/colors.sh -function message() { +message() { local flag=$1 local message=$2 case "$flag" in - "success") printf "${GREEN}%s${RESET}\n" "[success]: $message" ;; - "info") printf "${BLUE}%s${RESET}\n" "[info]: $message" ;; - "warning") printf "${YELLOW}%s${RESET}\n" "[warning]: $message" ;; - "error") printf "${RED}%s${RESET}\n" "[error]: $message" ;; + "success") printf "${green}%s${reset_color}\n" "[success]: $message" ;; + "task") printf "${cyan}%s${reset_color}\n" "[task]: $message" ;; + "info") printf "${blue}%s${reset_color}\n" "[info]: $message" ;; + "warning") printf "${yellow}%s${reset_color}\n" "[warning]: $message" ;; + "error") printf "${red}%s${reset_color}\n" "[error]: $message" ;; "done") echo + # Generate a dashed line that spans the entire width of the screen. + local cols cols=$(tput cols) printf "%*s\n" "$cols" "" | tr " " "-" echo - printf "${BOLD_GREEN}%s${RESET}\n" "[done]: $message" + printf "${bold_green}%s${reset_color}\n" "[done]: $message" ;; *) echo "Invalid flag: $flag" @@ -25,26 +28,26 @@ function message() { esac } -function dash_to_underscore() { - # shellcheck disable=SC2001 +dash_to_underscore() { echo "$1" | sed 's/-/_/g' } # exercise_name -> Exercise Name -function format_exercise_name { +format_exercise_name() { echo "$1" | sed 's/-/ /g; s/\b\(.\)/\u\1/g' } -function check_exercise_existence() { +check_exercise_existence() { message "info" "Looking for exercise.." - slug="$1" + local slug="$1" # Check if exercise is already in config.json if jq '.exercises.practice | map(.slug)' config.json | grep -q "$slug"; then echo "${1} has already been implemented." exit 1 fi - # fetch configlet and crop out exercise list + # Fetch configlet and crop out exercise list + local unimplemented_exercises unimplemented_exercises=$(bin/configlet info | sed -n '/With canonical data:/,/Track summary:/p' | sed -e '/\(With\(out\)\? canonical data:\|Track summary:\)/d' -e '/^$/d') if echo "$unimplemented_exercises" | grep -q "^$slug$"; then message "success" "Exercise has been found!" @@ -54,14 +57,14 @@ function check_exercise_existence() { ${unimplemented_exercises}" # Find closest match to typed-in not-found slug - # see util/ngram for source + # See util/ngram for source # First it builds a binary for the system of the contributor if [ -e bin/generator-utils/ngram ]; then - echo "${YELLOW}$(bin/generator-utils/ngram "${unimplemented_exercises}" "$slug")${RESET}" + echo "${yellow}$(bin/generator-utils/ngram "${unimplemented_exercises}" "$slug")${reset_color}" else message "info" "Building typo-checker binary for $(uname -m) system." - cd util/ngram && ./build && cd ../.. && echo "${YELLOW}$(bin/generator-utils/ngram "${unimplemented_exercises}" "$slug")${RESET}" + cd util/ngram && ./build && cd ../.. && echo "${yellow}$(bin/generator-utils/ngram "${unimplemented_exercises}" "$slug")${reset_color}" fi exit 1 From 60ccdc160465c5cc8b4f479d429e21835c0215a7 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Fri, 10 Mar 2023 00:47:14 +0100 Subject: [PATCH 083/436] Add missing end line --- bin/.shellcheckrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/.shellcheckrc b/bin/.shellcheckrc index 5ec9f679b..1a7ffeea1 100644 --- a/bin/.shellcheckrc +++ b/bin/.shellcheckrc @@ -1,3 +1,3 @@ shell=bash external-sources=true -disable=SC2001 # https://www.shellcheck.net/wiki/SC2001 - effects most of the sed commands \ No newline at end of file +disable=SC2001 # https://www.shellcheck.net/wiki/SC2001 - effects most of the sed commands From 847baf5cda4a8923d837981dde5c5f99de75247c Mon Sep 17 00:00:00 2001 From: Bob Hoeppner <32035397+bobahop@users.noreply.github.com> Date: Fri, 3 Mar 2023 06:35:45 -0600 Subject: [PATCH 084/436] secret-handshake: add approach (#1636) Add approach to `secret-handshake` --- .../secret-handshake/.approaches/config.json | 15 +++ .../.approaches/introduction.md | 39 ++++++++ .../.approaches/iterate-once/content.md | 97 +++++++++++++++++++ .../.approaches/iterate-once/snippet.txt | 4 + 4 files changed, 155 insertions(+) create mode 100644 exercises/practice/secret-handshake/.approaches/config.json create mode 100644 exercises/practice/secret-handshake/.approaches/introduction.md create mode 100644 exercises/practice/secret-handshake/.approaches/iterate-once/content.md create mode 100644 exercises/practice/secret-handshake/.approaches/iterate-once/snippet.txt diff --git a/exercises/practice/secret-handshake/.approaches/config.json b/exercises/practice/secret-handshake/.approaches/config.json new file mode 100644 index 000000000..484a72695 --- /dev/null +++ b/exercises/practice/secret-handshake/.approaches/config.json @@ -0,0 +1,15 @@ +{ + "introduction": { + "authors": ["bobahop"], + "contributors": [] + }, + "approaches": [ + { + "uuid": "cd0c0a31-0905-47f0-815c-02597a7cdf40", + "slug": "iterate-once", + "title": "Iterate once", + "blurb": "Iterate once even when reversed.", + "authors": ["bobahop"] + } + ] +} diff --git a/exercises/practice/secret-handshake/.approaches/introduction.md b/exercises/practice/secret-handshake/.approaches/introduction.md new file mode 100644 index 000000000..7c7d34ff8 --- /dev/null +++ b/exercises/practice/secret-handshake/.approaches/introduction.md @@ -0,0 +1,39 @@ +# Introduction + +There are many ways to solve Secret Handshake. +One approach is to iterate only once, even when the signs are to be reversed. + +## General guidance + +Something to consider is to keep the number of iterations at a minimum to get the best performance. +However, if that is felt to adversely impact readability, then to use a series of `if` statements and then reverse is also valid. + +## Approach: Iterate once + +```rust +const SIGNS: [&'static str; 4] = ["wink", "double blink", "close your eyes", "jump"]; +const REVERSE_SIGNS: u8 = 16; + +pub fn actions(n: u8) -> Vec<&'static str> { + let (mut action, action_incr, end) = match n & REVERSE_SIGNS { + 0 => (0, 1, 4), + _ => (3, -1, -1), + }; + let mut output: Vec<&'static str> = Vec::new(); + + loop { + if action == end { + break; + } + if (n & (1 << action)) != 0 { + output.push(SIGNS[action as usize]) + } + action += action_incr + } + output +} +``` + +For more information, check the [Iterate once approach][approach-iterate-once]. + +[approach-iterate-once]: https://exercism.org/tracks/rust/exercises/secret-handshake/approaches/iterate-once diff --git a/exercises/practice/secret-handshake/.approaches/iterate-once/content.md b/exercises/practice/secret-handshake/.approaches/iterate-once/content.md new file mode 100644 index 000000000..f6430f2ab --- /dev/null +++ b/exercises/practice/secret-handshake/.approaches/iterate-once/content.md @@ -0,0 +1,97 @@ +# Iterate once + +```rust +const SIGNS: [&'static str; 4] = ["wink", "double blink", "close your eyes", "jump"]; +const REVERSE_SIGNS: u8 = 16; + +pub fn actions(n: u8) -> Vec<&'static str> { + let (mut action, action_incr, end) = match n & REVERSE_SIGNS { + 0 => (0, 1, 4), + _ => (3, -1, -1), + }; + let mut output: Vec<&'static str> = Vec::new(); + + loop { + if action == end { + break; + } + if (n & (1 << action)) != 0 { + output.push(SIGNS[action as usize]) + } + action += action_incr + } + output +} +``` + +This approach starts by defining a fixed-size [array][array] to hold the signal values in normal order. + +The `[&'static str; 4]` is used to give the type and length of the array. +To be a [`const`][const], the size of the array must be known at compile time, so setting the type and length must be done explicitly, +so the size in bytes of the fixed array can be deduced by the compiler from that and not by inspecting the element types and counting +the elements itself. + +The value of `16` is defined as a `const` with a meaningful name so it won't be used as a [magic number][magic-number]. + +The `actions` function uses multiple assignment with a `match` expression to define the variables that control iterating through the signals array, +setting their values to iterate in either the normal or reverse order. + +The [bitwise AND operator][bitand] is used to check if the input number contains the signal for reversing the order of the other signals. + +For example, if the number passed in is `19`, which is `10011` in binary, then it is ANDed with `16`, which is `10000` in binary. +The `1` in `10000` is also at the same position in `10011`, so the two values ANDed will not be `0`. +- `10011` AND +- `10000` = +- `10000` + +If the number passed in is `3`, which is `00011` in binary, then it is ANDed with `16`, which is `10000` in binary. +The `1` in `10000` is not at the same position in `00011`, so the two values ANDed will be `0`. +- `00011` AND +- `10000` = +- `00000` + +If the number passed in does not contain the signal for reverse, then the iteration variables are set to iterate through the array of signals +in their normal order, otherwise they are set to iterate through the arrray backwards.. + +The output `vector` is defined, and then the [`loop`][loop] begins. + +Normal iteration will start at index `0`. +Reverse iteration will start at index `3`. + +Normal iteration will terminate when the index equals `4`. +Reverse iteration will terminate when the index equals `-1`. + +Normal iteration will increase the index by `1` for each iteration. +Reverse iteration will decrease the index by `1` for each iteration. + +For each iteration of the `loop`, the AND operator is used to check if the number passed in contains `1` [shifted left][shl] (`<<`) for the number of positions +as the value being iterated. + +```rust +if (n & (1 << action)) != 0 { + output.push(SIGNS[action as usize]) +} +``` + +For example, if the number being iterated is `0`, then `1` is shifted left `0` times (so not shifted at all), and the number passed in is ANDed with `00001`. +If the number passed in is `3`, which is `00011` in binary, then it is ANDed with `00001`. +`00011` ANDed with `00001` is not equal to `0`, so the signal at the index of the array of signals is added to the output `vector`. +The index used is the number being iterated, which is `0`, so the element at index `0` (`"wink"`) would be added to the output `vector` +using the [push][push] function. + +If the number being iterated is `1`, then `1` is shifted left `1` time, and the number passed in is ANDed with `00010`. +If the number passed in is `3`, which is `00011` in binary, then it is ANDed with `00010`. +`00011` ANDed with `00010` is not equal to `0`, so the signal at the index of the array of signals is added to the output `vector`. +The index used is the number being iterated, which is `1`, so the element at index `1` (`"double blink"`) would be added to the output `vector`. + +If the number passed in ANDed with the number being iterated is equal to `0`, then the signal in the array for that index is not added to the output `vector`. + +After iterating through the array of signals is done, the output `vector` is returned from the function. + +[array]: https://doc.rust-lang.org/std/primitive.array.html +[const]: https://doc.rust-lang.org/std/keyword.const.html +[magic-number]: https://en.wikipedia.org/wiki/Magic_number_(programming) +[bitand]: https://doc.rust-lang.org/std/ops/trait.BitAnd.html +[shl]: https://doc.rust-lang.org/std/ops/trait.Shl.html +[loop]: https://doc.rust-lang.org/rust-by-example/flow_control/loop.html +[push]: https://doc.rust-lang.org/std/vec/struct.Vec.html#method.push diff --git a/exercises/practice/secret-handshake/.approaches/iterate-once/snippet.txt b/exercises/practice/secret-handshake/.approaches/iterate-once/snippet.txt new file mode 100644 index 000000000..e42302ba8 --- /dev/null +++ b/exercises/practice/secret-handshake/.approaches/iterate-once/snippet.txt @@ -0,0 +1,4 @@ +let (mut action, action_incr, end) = match n & REVERSE_SIGNS { + 0 => (0, 1, 4), + _ => (3, -1, -1), +}; From 2d7f4aaa02dc43fc7dca034925de6aeb1e2e0c9e Mon Sep 17 00:00:00 2001 From: nhawkes Date: Mon, 6 Mar 2023 09:50:15 +0000 Subject: [PATCH 085/436] paasio: Add PhantomData explanation (#1637) Same as the explanation present in fizzy --- exercises/practice/paasio/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/exercises/practice/paasio/src/lib.rs b/exercises/practice/paasio/src/lib.rs index 8196a6c96..f530460da 100644 --- a/exercises/practice/paasio/src/lib.rs +++ b/exercises/practice/paasio/src/lib.rs @@ -1,5 +1,8 @@ use std::io::{Read, Result, Write}; +// the PhantomData instances in this file are just to stop compiler complaints +// about missing generics; feel free to remove them + pub struct ReadStats(::std::marker::PhantomData); impl ReadStats { From e1bc575d5bf9d5b2e6c3d27ad61bb6a4ab57bdfe Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Mon, 6 Mar 2023 21:54:38 +0100 Subject: [PATCH 086/436] Sync bob docs with problem-specifications (#1633) The bob exercise has been overhauled as part of a project to make practice exercises more consistent and friendly. For more context, please see the discussion in the forum, as well as the pull request that updated the exercise in the problem-specifications repository: - https://forum.exercism.org/t/new-project-making-practice-exercises-more-consistent-and-human-across-exercism/3943 - https://github.com/exercism/problem-specifications/pull/2205 --- exercises/practice/bob/.docs/instructions.md | 27 +++++++++++--------- exercises/practice/bob/.docs/introduction.md | 10 ++++++++ 2 files changed, 25 insertions(+), 12 deletions(-) create mode 100644 exercises/practice/bob/.docs/introduction.md diff --git a/exercises/practice/bob/.docs/instructions.md b/exercises/practice/bob/.docs/instructions.md index edddb1413..bb702f7bb 100644 --- a/exercises/practice/bob/.docs/instructions.md +++ b/exercises/practice/bob/.docs/instructions.md @@ -1,16 +1,19 @@ # Instructions -Bob is a lackadaisical teenager. In conversation, his responses are very limited. +Your task is to determine what Bob will reply to someone when they say something to him or ask him a question. -Bob answers 'Sure.' if you ask him a question, such as "How are you?". +Bob only ever answers one of five things: -He answers 'Whoa, chill out!' if you YELL AT HIM (in all capitals). - -He answers 'Calm down, I know what I'm doing!' if you yell a question at him. - -He says 'Fine. Be that way!' if you address him without actually saying -anything. - -He answers 'Whatever.' to anything else. - -Bob's conversational partner is a purist when it comes to written communication and always follows normal rules regarding sentence punctuation in English. +- **"Sure."** + This is his response if you ask him a question, such as "How are you?" + The convention used for questions is that it ends with a question mark. +- **"Whoa, chill out!"** + This is his answer if you YELL AT HIM. + The convention used for yelling is ALL CAPITAL LETTERS. +- **"Calm down, I know what I'm doing!"** + This is what he says if you yell a question at him. +- **"Fine. Be that way!"** + This is how he responds to silence. + The convention used for silence is nothing, or various combinations of whitespace characters. +- **"Whatever."** + This is what he answers to anything else. diff --git a/exercises/practice/bob/.docs/introduction.md b/exercises/practice/bob/.docs/introduction.md new file mode 100644 index 000000000..ea4a80776 --- /dev/null +++ b/exercises/practice/bob/.docs/introduction.md @@ -0,0 +1,10 @@ +# Introduction + +Bob is a [lackadaisical][] teenager. +He likes to think that he's very cool. +And he definitely doesn't get excited about things. +That wouldn't be cool. + +When people talk to him, his responses are pretty limited. + +[lackadaisical]: https://www.collinsdictionary.com/dictionary/english/lackadaisical From 002c845be6ebcb9ac2494b0877fca6b3f43823fc Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Mon, 6 Mar 2023 21:54:54 +0100 Subject: [PATCH 087/436] Sync gigasecond docs with problem-specifications (#1632) The gigasecond exercise has been overhauled as part of a project to make practice exercises more consistent and friendly. For more context, please see the discussion in the forum, as well as the pull request that updated the exercise in the problem-specifications repository: - https://forum.exercism.org/t/new-project-making-practice-exercises-more-consistent-and-human-across-exercism/3943 - https://github.com/exercism/problem-specifications/pull/2206 --- .../practice/gigasecond/.docs/instructions.md | 8 ++++--- .../practice/gigasecond/.docs/introduction.md | 24 +++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 exercises/practice/gigasecond/.docs/introduction.md diff --git a/exercises/practice/gigasecond/.docs/instructions.md b/exercises/practice/gigasecond/.docs/instructions.md index 680870f3a..1e20f0022 100644 --- a/exercises/practice/gigasecond/.docs/instructions.md +++ b/exercises/practice/gigasecond/.docs/instructions.md @@ -1,6 +1,8 @@ # Instructions -Given a moment, determine the moment that would be after a gigasecond -has passed. +Your task is to determine the date and time one gigasecond after a certain date. -A gigasecond is 10^9 (1,000,000,000) seconds. +A gigasecond is one thousand million seconds. +That is a one with nine zeros after it. + +If you were born on _January 24th, 2015 at 22:00 (10:00:00pm)_, then you would be a gigasecond old on _October 2nd, 2046 at 23:46:40 (11:46:40pm)_. diff --git a/exercises/practice/gigasecond/.docs/introduction.md b/exercises/practice/gigasecond/.docs/introduction.md new file mode 100644 index 000000000..74afaa994 --- /dev/null +++ b/exercises/practice/gigasecond/.docs/introduction.md @@ -0,0 +1,24 @@ +# Introduction + +The way we measure time is kind of messy. +We have 60 seconds in a minute, and 60 minutes in an hour. +This comes from ancient Babylon, where they used 60 as the basis for their number system. +We have 24 hours in a day, 7 days in a week, and how many days in a month? +Well, for days in a month it depends not only on which month it is, but also on what type of calendar is used in the country you live in. + +What if, instead, we only use seconds to express time intervals? +Then we can use metric system prefixes for writing large numbers of seconds in more easily comprehensible quantities. + +- A food recipe might explain that you need to let the brownies cook in the oven for two kiloseconds (that's two thousand seconds). +- Perhaps you and your family would travel to somewhere exotic for two megaseconds (that's two million seconds). +- And if you and your spouse were married for _a thousand million_ seconds, you would celebrate your one gigasecond anniversary. + +```exercism/note +If we ever colonize Mars or some other planet, measuring time is going to get even messier. +If someone says "year" do they mean a year on Earth or a year on Mars? + +The idea for this exercise came from the science fiction novel ["A Deepness in the Sky"][vinge-novel] by author Vernor Vinge. +In it the author uses the metric system as the basis for time measurements. + +[vinge-novel]: https://www.tor.com/2017/08/03/science-fiction-with-something-for-everyone-a-deepness-in-the-sky-by-vernor-vinge/ +``` From 58636ee8e698ba3be3a270b86427f8c2ba07bab6 Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Mon, 6 Mar 2023 21:58:13 +0100 Subject: [PATCH 088/436] Sync pangram docs with problem-specifications (#1638) The pangram exercise has been overhauled as part of a project to make practice exercises more consistent and friendly. For more context, please see the discussion in the forum, as well as the pull request that updated the exercise in the problem-specifications repository: - https://forum.exercism.org/t/new-project-making-practice-exercises-more-consistent-and-human-across-exercism/3943 - https://github.com/exercism/problem-specifications/pull/2215 --- exercises/practice/pangram/.docs/instructions.md | 11 +++++------ exercises/practice/pangram/.docs/introduction.md | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 6 deletions(-) create mode 100644 exercises/practice/pangram/.docs/introduction.md diff --git a/exercises/practice/pangram/.docs/instructions.md b/exercises/practice/pangram/.docs/instructions.md index 5c3bbde35..d5698bc2a 100644 --- a/exercises/practice/pangram/.docs/instructions.md +++ b/exercises/practice/pangram/.docs/instructions.md @@ -1,9 +1,8 @@ # Instructions -Determine if a sentence is a pangram. A pangram (Greek: παν γράμμα, pan gramma, -"every letter") is a sentence using every letter of the alphabet at least once. -The best known English pangram is: -> The quick brown fox jumps over the lazy dog. +Your task is to figure out if a sentence is a pangram. -The alphabet used consists of ASCII letters `a` to `z`, inclusive, and is case -insensitive. Any characters which are not an ASCII letter should be ignored. +A pangram is a sentence using every letter of the alphabet at least once. +It is case insensitive, so it doesn't matter if a letter is lower-case (e.g. `k`) or upper-case (e.g. `K`). + +For this exercise we only use the basic letters used in the English alphabet: `a` to `z`. diff --git a/exercises/practice/pangram/.docs/introduction.md b/exercises/practice/pangram/.docs/introduction.md new file mode 100644 index 000000000..d38fa341d --- /dev/null +++ b/exercises/practice/pangram/.docs/introduction.md @@ -0,0 +1,16 @@ +# Introduction + +You work for a company that sells fonts through their website. +They'd like to show a different sentence each time someone views a font on their website. +To give a comprehensive sense of the font, the random sentences should use **all** the letters in the English alphabet. + +They're running a competition to get suggestions for sentences that they can use. +You're in charge of checking the submissions to see if they are valid. + +```exercism/note +Pangram comes from Greek, παν γράμμα, pan gramma, which means "every letter". + +The best known English pangram is: + +> The quick brown fox jumps over the lazy dog. +``` From 8d9fbc0795409151f23dd69308dda4c9fcc2ef84 Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Tue, 7 Mar 2023 11:06:54 +0100 Subject: [PATCH 089/436] Sync sieve docs with problem-specifications (#1640) The sieve exercise has been overhauled as part of a project to make practice exercises more consistent and friendly. For more context, please see the discussion in the forum, as well as the pull request that updated the exercise in the problem-specifications repository: - https://forum.exercism.org/t/new-project-making-practice-exercises-more-consistent-and-human-across-exercism/3943 - https://github.com/exercism/problem-specifications/pull/2216 --- .../practice/sieve/.docs/instructions.md | 38 +++++++++---------- .../practice/sieve/.docs/introduction.md | 7 ++++ 2 files changed, 25 insertions(+), 20 deletions(-) create mode 100644 exercises/practice/sieve/.docs/introduction.md diff --git a/exercises/practice/sieve/.docs/instructions.md b/exercises/practice/sieve/.docs/instructions.md index 7228737a2..ec14620ce 100644 --- a/exercises/practice/sieve/.docs/instructions.md +++ b/exercises/practice/sieve/.docs/instructions.md @@ -1,30 +1,28 @@ # Instructions -Use the Sieve of Eratosthenes to find all the primes from 2 up to a given -number. +Your task is to create a program that implements the Sieve of Eratosthenes algorithm to find prime numbers. -The Sieve of Eratosthenes is a simple, ancient algorithm for finding all -prime numbers up to any given limit. It does so by iteratively marking as -composite (i.e. not prime) the multiples of each prime, starting with the -multiples of 2. It does not use any division or remainder operation. +A prime number is a number that is only divisible by 1 and itself. +For example, 2, 3, 5, 7, 11, and 13 are prime numbers. -Create your range, starting at two and continuing up to and including the given limit. (i.e. [2, limit]) +The Sieve of Eratosthenes is an ancient algorithm that works by taking a list of numbers and crossing out all the numbers that aren't prime. -The algorithm consists of repeating the following over and over: +A number that is **not** prime is called a "composite number". -- take the next available unmarked number in your list (it is prime) -- mark all the multiples of that number (they are not prime) +To use the Sieve of Eratosthenes, you first create a list of all the numbers between 2 and your given number. +Then you repeat the following steps: -Repeat until you have processed each number in your range. +1. Find the next unmarked number in your list. This is a prime number. +2. Mark all the multiples of that prime number as composite (not prime). -When the algorithm terminates, all the numbers in the list that have not -been marked are prime. +You keep repeating these steps until you've gone through every number in your list. +At the end, all the unmarked numbers are prime. -The wikipedia article has a useful graphic that explains the algorithm: -https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes +```exercism/note +[Wikipedia's Sieve of Eratosthenes article][eratosthenes] has a useful graphic that explains the algorithm. -Notice that this is a very specific algorithm, and the tests don't check -that you've implemented the algorithm, only that you've come up with the -correct list of primes. A good first test is to check that you do not use -division or remainder operations (div, /, mod or % depending on the -language). +The tests don't check that you've implemented the algorithm, only that you've come up with the correct list of primes. +A good first test is to check that you do not use division or remainder operations. + +[eratosthenes]: https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes +``` diff --git a/exercises/practice/sieve/.docs/introduction.md b/exercises/practice/sieve/.docs/introduction.md new file mode 100644 index 000000000..f6c1cf79a --- /dev/null +++ b/exercises/practice/sieve/.docs/introduction.md @@ -0,0 +1,7 @@ +# Introduction + +You bought a big box of random computer parts at a garage sale. +You've started putting the parts together to build custom computers. + +You want to test the performance of different combinations of parts, and decide to create your own benchmarking program to see how your computers compare. +You choose the famous "Sieve of Eratosthenes" algorithm, an ancient algorithm, but one that should push your computers to the limits. From b7597f1e378bf5af366d4aecc0bec8b686cb69b8 Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Tue, 7 Mar 2023 11:08:23 +0100 Subject: [PATCH 090/436] Sync binary-search docs with problem-specifications (#1639) The binary-search exercise has been overhauled as part of a project to make practice exercises more consistent and friendly. For more context, please see the discussion in the forum, as well as the pull request that updated the exercise in the problem-specifications repository: - https://forum.exercism.org/t/new-project-making-practice-exercises-more-consistent-and-human-across-exercism/3943 - https://github.com/exercism/problem-specifications/pull/2220 --- .../binary-search/.docs/instructions.md | 45 ++++++++----------- .../binary-search/.docs/introduction.md | 9 ++++ 2 files changed, 28 insertions(+), 26 deletions(-) create mode 100644 exercises/practice/binary-search/.docs/introduction.md diff --git a/exercises/practice/binary-search/.docs/instructions.md b/exercises/practice/binary-search/.docs/instructions.md index 4dcaba726..175c4c4ba 100644 --- a/exercises/practice/binary-search/.docs/instructions.md +++ b/exercises/practice/binary-search/.docs/instructions.md @@ -1,35 +1,28 @@ # Instructions -Implement a binary search algorithm. +Your task is to implement a binary search algorithm. -Searching a sorted collection is a common task. A dictionary is a sorted -list of word definitions. Given a word, one can find its definition. A -telephone book is a sorted list of people's names, addresses, and -telephone numbers. Knowing someone's name allows one to quickly find -their telephone number and address. +A binary search algorithm finds an item in a list by repeatedly splitting it in half, only keeping the half which contains the item we're looking for. +It allows us to quickly narrow down the possible locations of our item until we find it, or until we've eliminated all possible locations. -If the list to be searched contains more than a few items (a dozen, say) -a binary search will require far fewer comparisons than a linear search, -but it imposes the requirement that the list be sorted. +```exercism/caution +Binary search only works when a list has been sorted. +``` -In computer science, a binary search or half-interval search algorithm -finds the position of a specified input value (the search "key") within -an array sorted by key value. +The algorithm looks like this: -In each step, the algorithm compares the search key value with the key -value of the middle element of the array. +- Divide the sorted list in half and compare the middle element with the item we're looking for. +- If the middle element is our item, then we're done. +- If the middle element is greater than our item, we can eliminate that number and all the numbers **after** it. +- If the middle element is less than our item, we can eliminate that number and all the numbers **before** it. +- Repeat the process on the part of the list that we kept. -If the keys match, then a matching element has been found and its index, -or position, is returned. +Here's an example: -Otherwise, if the search key is less than the middle element's key, then -the algorithm repeats its action on the sub-array to the left of the -middle element or, if the search key is greater, on the sub-array to the -right. +Let's say we're looking for the number 23 in the following sorted list: `[4, 8, 12, 16, 23, 28, 32]`. -If the remaining array to be searched is empty, then the key cannot be -found in the array and a special "not found" indication is returned. - -A binary search halves the number of items to check with each iteration, -so locating an item (or determining its absence) takes logarithmic time. -A binary search is a dichotomic divide and conquer search algorithm. +- We start by comparing 23 with the middle element, 16. +- Since 23 is greater than 16, we can eliminate the left half of the list, leaving us with `[23, 28, 32]`. +- We then compare 23 with the new middle element, 28. +- Since 23 is less than 28, we can eliminate the right half of the list: `[23]`. +- We've found our item. diff --git a/exercises/practice/binary-search/.docs/introduction.md b/exercises/practice/binary-search/.docs/introduction.md new file mode 100644 index 000000000..66c4b8a45 --- /dev/null +++ b/exercises/practice/binary-search/.docs/introduction.md @@ -0,0 +1,9 @@ +# Introduction + +You have stumbled upon a group of mathematicians who are also singer-songwriters. +They have written a song for each of their favorite numbers, and, as you can imagine, they have a lot of favorite numbers. + +You are curious to hear the song for your favorite number, but with so many songs to wade through, finding the right song could take a while. +Fortunately, they have organized their songs in a playlist sorted by the title — which is simply the number that the song is about. + +You realize that you can use a binary search algorithm to quickly find a song given the title. From 7002cf18872586e64b1cefa4c4896524121169d2 Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Wed, 8 Mar 2023 15:17:17 +0100 Subject: [PATCH 091/436] Sync secret-handshake docs with problem-specifications (#1643) The secret-handshake exercise has been overhauled as part of a project to make practice exercises more consistent and friendly. For more context, please see the discussion in the forum, as well as the pull request that updated the exercise in the problem-specifications repository: - https://forum.exercism.org/t/new-project-making-practice-exercises-more-consistent-and-human-across-exercism/3943 - https://github.com/exercism/problem-specifications/pull/2219 --- .../secret-handshake/.docs/instructions.md | 43 ++++++++++++++----- .../secret-handshake/.docs/introduction.md | 7 +++ 2 files changed, 40 insertions(+), 10 deletions(-) create mode 100644 exercises/practice/secret-handshake/.docs/introduction.md diff --git a/exercises/practice/secret-handshake/.docs/instructions.md b/exercises/practice/secret-handshake/.docs/instructions.md index 2d6937ae9..77136cf0f 100644 --- a/exercises/practice/secret-handshake/.docs/instructions.md +++ b/exercises/practice/secret-handshake/.docs/instructions.md @@ -1,24 +1,47 @@ # Instructions -> There are 10 types of people in the world: Those who understand -> binary, and those who don't. +Your task is to convert a number between 1 and 31 to a sequence of actions in the secret handshake. -You and your fellow cohort of those in the "know" when it comes to binary decide to come up with a secret "handshake". +The sequence of actions is chosen by looking at the rightmost five digits of the number once it's been converted to binary. +Start at the right-most digit and move left. -```text +The actions for each number place are: + +```plaintext 00001 = wink 00010 = double blink 00100 = close your eyes 01000 = jump - 10000 = Reverse the order of the operations in the secret handshake. ``` -Given a decimal number, convert it to the appropriate sequence of events for a secret handshake. +Let's use the number `9` as an example: + +- 9 in binary is `1001`. +- The digit that is farthest to the right is 1, so the first action is `wink`. +- Going left, the next digit is 0, so there is no double-blink. +- Going left again, the next digit is 0, so you leave your eyes open. +- Going left again, the next digit is 1, so you jump. -Here's a couple of examples: +That was the last digit, so the final code is: + +```plaintext +wink, jump +``` -Given the decimal input 3, the function would return the array ["wink", "double blink"] because the decimal number 3 is 2+1 in powers of two and thus `11` in binary. +Given the number 26, which is `11010` in binary, we get the following actions: + +- double blink +- jump +- reverse actions + +The secret handshake for 26 is therefore: + +```plaintext +jump, double blink +``` -Let's now examine the input 19 which is 16+2+1 in powers of two and thus `10011` in binary. -Recalling that the addition of 16 (`10000` in binary) reverses an array and that we already know what array is returned given input 3, the array returned for input 19 is ["double blink", "wink"]. +~~~~exercism/note +If you aren't sure what binary is or how it works, check out [this binary tutorial][intro-to-binary]. +[intro-to-binary]: https://medium.com/basecs/bits-bytes-building-with-binary-13cb4289aafa +~~~~ diff --git a/exercises/practice/secret-handshake/.docs/introduction.md b/exercises/practice/secret-handshake/.docs/introduction.md new file mode 100644 index 000000000..176b92e8c --- /dev/null +++ b/exercises/practice/secret-handshake/.docs/introduction.md @@ -0,0 +1,7 @@ +# Introduction + +You are starting a secret coding club with some friends and friends-of-friends. +Not everyone knows each other, so you and your friends have decided to create a secret handshake that you can use to recognize that someone is a member. +You don't want anyone who isn't in the know to be able to crack the code. + +You've designed the code so that one person says a number between 1 and 31, and the other person turns it into a series of actions. From 6465978045ef49147d5230101a87b15f42b2f515 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Fri, 10 Mar 2023 00:30:11 +0100 Subject: [PATCH 092/436] Lowercase vars, remove `function`, add .shellcheckrc --- bin/.shellcheckrc | 3 ++ bin/add_practice_exercise | 54 +++++++++++++++++--------------- bin/fetch_canonical_data | 3 ++ bin/generate_tests | 37 ++++++++++++---------- bin/generator-utils/colors.sh | 40 +++++------------------ bin/generator-utils/prompts.sh | 27 ++++++++-------- bin/generator-utils/templates.sh | 24 ++++++++------ bin/generator-utils/utils.sh | 35 +++++++++++---------- 8 files changed, 110 insertions(+), 113 deletions(-) create mode 100644 bin/.shellcheckrc diff --git a/bin/.shellcheckrc b/bin/.shellcheckrc new file mode 100644 index 000000000..5ec9f679b --- /dev/null +++ b/bin/.shellcheckrc @@ -0,0 +1,3 @@ +shell=bash +external-sources=true +disable=SC2001 # https://www.shellcheck.net/wiki/SC2001 - effects most of the sed commands \ No newline at end of file diff --git a/bin/add_practice_exercise b/bin/add_practice_exercise index 08e9f4dbf..ad43c4331 100755 --- a/bin/add_practice_exercise +++ b/bin/add_practice_exercise @@ -1,6 +1,8 @@ #!/usr/bin/env bash -# shellcheck source=/dev/null +# shellcheck source=./generator-utils/utils.sh +# shellcheck source=./generator-utils/prompts.sh +# shellcheck source=./generator-utils/templates.sh source ./bin/generator-utils/utils.sh source ./bin/generator-utils/prompts.sh source ./bin/generator-utils/templates.sh @@ -35,13 +37,13 @@ check_exercise_existence "$1" # ================================================== -SLUG="$1" -HAS_CANONICAL_DATA=true +slug="$1" # Fetch canonical data -canonical_json=$(bin/fetch_canonical_data "$SLUG") +canonical_json=$(bin/fetch_canonical_data "$slug") +has_canonical_data=true if [ "${canonical_json}" == "404: Not Found" ]; then - HAS_CANONICAL_DATA=false + has_canonical_data=false message "warning" "This exercise doesn't have canonical data" else @@ -49,32 +51,31 @@ else message "success" "Fetched canonical data successfully!" fi -UNDERSCORED_SLUG=$(dash_to_underscore "$SLUG") -EXERCISE_DIR="exercises/practice/${SLUG}" -EXERCISE_NAME=$(format_exercise_name "$SLUG") -message "info" "Using ${YELLOW}${EXERCISE_NAME}${BLUE} as a default exercise name. You can edit this later in the config.json file" +underscored_slug=$(dash_to_underscore "$slug") +exercise_dir="exercises/practice/${slug}" +exercise_name=$(format_exercise_name "$slug") +message "info" "Using ${yellow}${exercise_name}${blue} as a default exercise name. You can edit this later in the config.json file" # using default value for difficulty -EXERCISE_DIFFICULTY=$(validate_difficulty_input "${2:-$(get_exercise_difficulty)}") -message "info" "The exercise difficulty has been set to ${YELLOW}${EXERCISE_DIFFICULTY}${BLUE}. You can edit this later in the config.json file" +exercise_difficulty=$(validate_difficulty_input "${2:-$(get_exercise_difficulty)}") +message "info" "The exercise difficulty has been set to ${yellow}${exercise_difficulty}${blue}. You can edit this later in the config.json file" # using default value for author -AUTHOR_HANDLE=${3:-$(get_author_handle)} -message "info" "Using ${YELLOW}${AUTHOR_HANDLE}${BLUE} as author's handle. You can edit this later in the 'authors' field in the ${EXERCISE_DIR}/.meta/config.json file" - - -create_rust_files "$EXERCISE_DIR" "$SLUG" "$HAS_CANONICAL_DATA" +author_handle=${3:-$(get_author_handle)} +message "info" "Using ${yellow}${author_handle}${blue} as author's handle. You can edit this later in the 'authors' field in the ${exercise_dir}/.meta/config.json file" +create_rust_files "$exercise_dir" "$slug" "$has_canonical_data" # ================================================== -# build configlet +# Build configlet ./bin/fetch-configlet message "success" "Fetched configlet successfully!" # Preparing config.json message "info" "Adding instructions and configuration files..." -UUID=$(bin/configlet uuid) +uuid=$(bin/configlet uuid) -jq --arg slug "$SLUG" --arg uuid "$UUID" --arg name "$EXERCISE_NAME" --arg difficulty "$EXERCISE_DIFFICULTY" \ +# Add exercise-data to global config.json +jq --arg slug "$slug" --arg uuid "$uuid" --arg name "$exercise_name" --arg difficulty "$exercise_difficulty" \ '.exercises.practice += [{slug: $slug, name: $name, uuid: $uuid, practices: [], prerequisites: [], difficulty: $difficulty}]' \ config.json >config.json.tmp # jq always rounds whole numbers, but average_run_time needs to be a float @@ -84,18 +85,19 @@ message "success" "Added instructions and configuration files" # Create instructions and config files echo "Creating instructions and config files" -./bin/configlet sync --update --yes --docs --metadata --exercise "$SLUG" -./bin/configlet sync --update --yes --filepaths --exercise "$SLUG" -./bin/configlet sync --update --tests include --exercise "$SLUG" +./bin/configlet sync --update --yes --docs --metadata --exercise "$slug" +./bin/configlet sync --update --yes --filepaths --exercise "$slug" +./bin/configlet sync --update --tests include --exercise "$slug" message "success" "Created instructions and config files" -META_CONFIG="$EXERCISE_DIR"/.meta/config.json -jq --arg author "$AUTHOR_HANDLE" '.authors += [$author]' "$META_CONFIG" >"$META_CONFIG".tmp && mv "$META_CONFIG".tmp "$META_CONFIG" +# Push author to "authors" array in ./meta/config.json +meta_config="$exercise_dir"/.meta/config.json +jq --arg author "$author_handle" '.authors += [$author]' "$meta_config" >"$meta_config".tmp && mv "$meta_config".tmp "$meta_config" message "success" "You've been added as the author of this exercise." -sed -i "s/name = \".*\"/name = \"$UNDERSCORED_SLUG\"/" "$EXERCISE_DIR"/Cargo.toml +sed -i "s/name = \".*\"/name = \"$underscored_slug\"/" "$exercise_dir"/Cargo.toml message "done" "All stub files were created." message "info" "After implementing the solution, tests and configuration, please run:" -echo "./bin/configlet fmt --update --yes --exercise ${SLUG}" +echo "./bin/configlet fmt --update --yes --exercise ${slug}" diff --git a/bin/fetch_canonical_data b/bin/fetch_canonical_data index a487581f2..69ca5fde0 100755 --- a/bin/fetch_canonical_data +++ b/bin/fetch_canonical_data @@ -1,6 +1,9 @@ #!/usr/bin/env bash # This script fetches the canonical data of the exercise. +# Exit if anything fails. +set -euo pipefail + if [ $# -ne 1 ]; then echo "Usage: bin/fetch_canonical_data " exit 1 diff --git a/bin/generate_tests b/bin/generate_tests index ce8875285..3a0992c07 100755 --- a/bin/generate_tests +++ b/bin/generate_tests @@ -1,27 +1,29 @@ #!/usr/bin/env bash +# Exit if anything fails. set -euo pipefail -# shellcheck source=/dev/null + + +# shellcheck source=./generator-utils/utils.sh source ./bin/generator-utils/utils.sh -function digest_template() { +digest_template() { + local template template=$(cat bin/test_template) - # shellcheck disable=SC2001 - # turn every token into a jq command + # Turn every token into a jq command echo "$template" | sed 's/${\([^}]*\)\}\$/$(echo $case | jq -r '\''.\1'\'')/g' } message "info" "Generating tests.." canonical_json=$(cat canonical_data.json) -SLUG=$(echo "$canonical_json" | jq '.exercise') -# shellcheck disable=SC2001 +slug=$(echo "$canonical_json" | jq '.exercise') # Remove double quotes -SLUG=$(echo "$SLUG" | sed 's/"//g') -EXERCISE_DIR="exercises/practice/$SLUG" -TEST_FILE="$EXERCISE_DIR/tests/$SLUG.rs" +slug=$(echo "$slug" | sed 's/"//g') +exercise_dir="exercises/practice/$slug" +test_file="$exercise_dir/tests/$slug.rs" -cat <"$TEST_FILE" -use $(dash_to_underscore "$SLUG")::*; +cat <"$test_file" +use $(dash_to_underscore "$slug")::*; // Add tests here EOT @@ -29,6 +31,7 @@ EOT # Flattens canonical json, extracts only the objects with a uuid cases=$(echo "$canonical_json" | jq '[ .. | objects | with_entries(select(.key | IN("uuid", "description", "input", "expected", "property"))) | select(. != {}) | select(has("uuid")) ]') +# Shellcheck doesn't recognize that `case` is not unused # shellcheck disable=SC2034 jq -c '.[]' <<<"$cases" | while read -r case; do @@ -36,15 +39,15 @@ jq -c '.[]' <<<"$cases" | while read -r case; do eval_template="$(digest_template | sed -e "s/\$(\(.*\))/\$\(\1\)/g")" eval_template="$(eval "echo \"$eval_template\"")" - # Turn function name unto snake_case + # Turn function name into snake_case formatted_template=$(echo "$eval_template" | sed -e ':loop' -e 's/\(fn[^(]*\)[ -]/\1_/g' -e 't loop' | sed 's/fn_/fn /') - # Push to file - echo "$formatted_template" >>"$TEST_FILE" - printf "\\n" >>"$TEST_FILE" + # Push to test file + echo "$formatted_template" >>"$test_file" + printf "\\n" >>"$test_file" done -rustfmt "$TEST_FILE" +rustfmt "$test_file" -message "success" "Generated tests successfully! Check out ${TEST_FILE}" +message "success" "Generated tests successfully! Check out ${test_file}" diff --git a/bin/generator-utils/colors.sh b/bin/generator-utils/colors.sh index 3ae992587..c7e5a7d55 100644 --- a/bin/generator-utils/colors.sh +++ b/bin/generator-utils/colors.sh @@ -1,38 +1,14 @@ #!/usr/bin/env bash -# shellcheck disable=SC2034 -# Reset -RESET=$(echo -e '\033[0m') +reset_color=$(echo -e '\033[0m') -# Regular Colors -BLACK=$(echo -e '\033[0;30m') -RED=$(echo -e '\033[0;31m') -GREEN=$(echo -e '\033[0;32m') -YELLOW=$(echo -e '\033[0;33m') -BLUE=$(echo -e '\033[0;34m') -PURPLE=$(echo -e '\033[0;35m') -CYAN=$(echo -e '\033[0;36m') -WHITE=$(echo -e '\033[0;37m') +red=$(echo -e '\033[0;31m') +green=$(echo -e '\033[0;32m') +yellow=$(echo -e '\033[0;33m') +blue=$(echo -e '\033[0;34m') +cyan=$(echo -e '\033[0;36m') -# Bold -BOLD_BLACK=$(echo -e '\033[1;30m') -BOLD_RED=$(echo -e '\033[1;31m') -BOLD_GREEN=$(echo -e '\033[1;32m') -BOLD_YELLOW=$(echo -e '\033[1;33m') -BOLD_BLUE=$(echo -e '\033[1;34m') -BOLD_PURPLE=$(echo -e '\033[1;35m') -BOLD_CYAN=$(echo -e '\033[1;36m') -BOLD_WHITE=$(echo -e '\033[1;37m') +bold_green=$(echo -e '\033[1;32m') -# Underline -UNDERLINE=$(echo -e ='\033[4m') -# Background -BG_BLACK=$(echo -e ='\033[40m') -BG_RED=$(echo -e ='\033[41m') -BG_GREEN=$(echo -e ='\033[42m') -BG_YELLOW=$(echo -e ='\033[43m') -BG_BLUE=$(echo -e ='\033[44m') -BG_PURPLE=$(echo -e ='\033[45m') -BG_CYAN=$(echo -e ='\033[46m') -BG_WHITE=$(echo -e ='\033[47m') +export red green blue yellow bold_green reset_color cyan diff --git a/bin/generator-utils/prompts.sh b/bin/generator-utils/prompts.sh index c785a2636..30ac172ae 100644 --- a/bin/generator-utils/prompts.sh +++ b/bin/generator-utils/prompts.sh @@ -1,22 +1,22 @@ #!/usr/bin/env bash -# shellcheck source=/dev/null +# shellcheck source=./colors.sh source ./bin/generator-utils/colors.sh -function get_exercise_difficulty() { +get_exercise_difficulty() { read -rp "Difficulty of exercise (1-10): " exercise_difficulty echo "$exercise_difficulty" } -function validate_difficulty_input() { +validate_difficulty_input() { - valid_input=false + local valid_input=false while ! $valid_input; do if [[ "$1" =~ ^[1-9]$|^10$ ]]; then - exercise_difficulty=$1 - valid_input=true + local exercise_difficulty=$1 + local valid_input=true else - read -rp "${RED}Invalid input. ${RESET}Please enter an integer between 1 and 10. " exercise_difficulty + read -rp "${red}Invalid input. ${reset_color}Please enter an integer between 1 and 10. " exercise_difficulty [[ "$exercise_difficulty" =~ ^[1-9]$|^10$ ]] && valid_input=true fi @@ -24,15 +24,16 @@ function validate_difficulty_input() { echo "$exercise_difficulty" } -function get_author_handle { - DEFAULT_AUTHOR_HANDLE="$(git config user.name)" +get_author_handle() { + local default_author_handle + default_author_handle="$(git config user.name)" - if [ -z "$DEFAULT_AUTHOR_HANDLE" ]; then - read -rp "Hey! Couldn't find your Github handle. Add it now or skip with enter and add it later in the .meta.config.json file: " AUTHOR_HANDLE + if [ -z "$default_author_handle" ]; then + read -rp "Hey! Couldn't find your Github handle. Add it now or skip with enter and add it later in the .meta.config.json file: " author_handle else - AUTHOR_HANDLE="$DEFAULT_AUTHOR_HANDLE" + local author_handle="$default_author_handle" fi - echo "$AUTHOR_HANDLE" + echo "$author_handle" } diff --git a/bin/generator-utils/templates.sh b/bin/generator-utils/templates.sh index 5c957314a..922bc3c42 100755 --- a/bin/generator-utils/templates.sh +++ b/bin/generator-utils/templates.sh @@ -1,14 +1,15 @@ #!/usr/bin/env bash -# shellcheck source=/dev/null +# shellcheck source=./utils.sh source ./bin/generator-utils/utils.sh -function create_fn_name() { - slug=$1 - has_canonical_data=$2 +create_fn_name() { + local slug=$1 + local has_canonical_data=$2 if [ "$has_canonical_data" == false ]; then fn_name=$(dash_to_underscore "$slug") + local fn_name else fn_name=$(jq -r 'first(.. | .property? // empty)' canonical_data.json) fi @@ -17,7 +18,7 @@ function create_fn_name() { } -function create_test_file_template() { +create_test_file_template() { local exercise_dir=$1 local slug=$2 local has_canonical_data=$3 @@ -39,12 +40,15 @@ EOT message "info" "This exercise doesn't have canonical data." message "success" "Stub file for tests has been created!" else + local canonical_json canonical_json=$(cat canonical_data.json) # sometimes canonical data has multiple levels with multiple `cases` arrays. #(see kindergarten-garden https://github.com/exercism/problem-specifications/blob/main/exercises/kindergarten-garden/canonical-data.json) # so let's flatten it + local cases cases=$(echo "$canonical_json" | jq '[ .. | objects | with_entries(select(.key | IN("uuid", "description", "input", "expected"))) | select(. != {}) | select(has("uuid")) ]') + local fn_name fn_name=$(echo "$canonical_json" | jq -r 'first(.. | .property? // empty)') first_iteration=true @@ -74,10 +78,11 @@ EOT } -function create_lib_rs_template() { +create_lib_rs_template() { local exercise_dir=$1 local slug=$2 local has_canonical_data=$3 + local fn_name fn_name=$(create_fn_name "$slug" "$has_canonical_data") cat <"${exercise_dir}/src/lib.rs" pub fn ${fn_name}() { @@ -87,7 +92,7 @@ EOT message "success" "Stub file for lib.rs has been created!" } -function overwrite_gitignore() { +overwrite_gitignore() { local exercise_dir=$1 cat <"$exercise_dir"/.gitignore # Generated by Cargo @@ -102,11 +107,12 @@ EOT message "success" ".gitignore has been overwritten!" } -function create_example_rs_template() { +create_example_rs_template() { local exercise_dir=$1 local slug=$2 local has_canonical_data=$3 + local fn_name fn_name=$(create_fn_name "$slug" "$has_canonical_data") mkdir "${exercise_dir}/.meta" @@ -120,7 +126,7 @@ EOT message "success" "Stub file for example.rs has been created!" } -function create_rust_files() { +create_rust_files() { local exercise_dir=$1 local slug=$2 local has_canonical_data=$3 diff --git a/bin/generator-utils/utils.sh b/bin/generator-utils/utils.sh index e89b33e67..bae45efb9 100755 --- a/bin/generator-utils/utils.sh +++ b/bin/generator-utils/utils.sh @@ -1,23 +1,26 @@ #!/usr/bin/env bash -# shellcheck source=/dev/null +# shellcheck source=./colors.sh source ./bin/generator-utils/colors.sh -function message() { +message() { local flag=$1 local message=$2 case "$flag" in - "success") printf "${GREEN}%s${RESET}\n" "[success]: $message" ;; - "info") printf "${BLUE}%s${RESET}\n" "[info]: $message" ;; - "warning") printf "${YELLOW}%s${RESET}\n" "[warning]: $message" ;; - "error") printf "${RED}%s${RESET}\n" "[error]: $message" ;; + "success") printf "${green}%s${reset_color}\n" "[success]: $message" ;; + "task") printf "${cyan}%s${reset_color}\n" "[task]: $message" ;; + "info") printf "${blue}%s${reset_color}\n" "[info]: $message" ;; + "warning") printf "${yellow}%s${reset_color}\n" "[warning]: $message" ;; + "error") printf "${red}%s${reset_color}\n" "[error]: $message" ;; "done") echo + # Generate a dashed line that spans the entire width of the screen. + local cols cols=$(tput cols) printf "%*s\n" "$cols" "" | tr " " "-" echo - printf "${BOLD_GREEN}%s${RESET}\n" "[done]: $message" + printf "${bold_green}%s${reset_color}\n" "[done]: $message" ;; *) echo "Invalid flag: $flag" @@ -25,26 +28,26 @@ function message() { esac } -function dash_to_underscore() { - # shellcheck disable=SC2001 +dash_to_underscore() { echo "$1" | sed 's/-/_/g' } # exercise_name -> Exercise Name -function format_exercise_name { +format_exercise_name() { echo "$1" | sed 's/-/ /g; s/\b\(.\)/\u\1/g' } -function check_exercise_existence() { +check_exercise_existence() { message "info" "Looking for exercise.." - slug="$1" + local slug="$1" # Check if exercise is already in config.json if jq '.exercises.practice | map(.slug)' config.json | grep -q "$slug"; then echo "${1} has already been implemented." exit 1 fi - # fetch configlet and crop out exercise list + # Fetch configlet and crop out exercise list + local unimplemented_exercises unimplemented_exercises=$(bin/configlet info | sed -n '/With canonical data:/,/Track summary:/p' | sed -e '/\(With\(out\)\? canonical data:\|Track summary:\)/d' -e '/^$/d') if echo "$unimplemented_exercises" | grep -q "^$slug$"; then message "success" "Exercise has been found!" @@ -54,14 +57,14 @@ function check_exercise_existence() { ${unimplemented_exercises}" # Find closest match to typed-in not-found slug - # see util/ngram for source + # See util/ngram for source # First it builds a binary for the system of the contributor if [ -e bin/generator-utils/ngram ]; then - echo "${YELLOW}$(bin/generator-utils/ngram "${unimplemented_exercises}" "$slug")${RESET}" + echo "${yellow}$(bin/generator-utils/ngram "${unimplemented_exercises}" "$slug")${reset_color}" else message "info" "Building typo-checker binary for $(uname -m) system." - cd util/ngram && ./build && cd ../.. && echo "${YELLOW}$(bin/generator-utils/ngram "${unimplemented_exercises}" "$slug")${RESET}" + cd util/ngram && ./build && cd ../.. && echo "${yellow}$(bin/generator-utils/ngram "${unimplemented_exercises}" "$slug")${reset_color}" fi exit 1 From 51f6b9f188f2d650bce52ba0b5258be432ed7d5c Mon Sep 17 00:00:00 2001 From: dem4ron Date: Fri, 10 Mar 2023 00:47:14 +0100 Subject: [PATCH 093/436] Add missing end line --- bin/.shellcheckrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/.shellcheckrc b/bin/.shellcheckrc index 5ec9f679b..1a7ffeea1 100644 --- a/bin/.shellcheckrc +++ b/bin/.shellcheckrc @@ -1,3 +1,3 @@ shell=bash external-sources=true -disable=SC2001 # https://www.shellcheck.net/wiki/SC2001 - effects most of the sed commands \ No newline at end of file +disable=SC2001 # https://www.shellcheck.net/wiki/SC2001 - effects most of the sed commands From 29afef966b90236d62b7df15ff07adf104016bab Mon Sep 17 00:00:00 2001 From: Aron Demeter <66035744+dem4ron@users.noreply.github.com> Date: Tue, 14 Mar 2023 09:22:55 +0100 Subject: [PATCH 094/436] Add script to help add new practice exercise (#1635) Add a `bin/add_practice_exercise ` script that helps with adding a new practice exercise. The script will: 1. Add an entry to the `config.json`'s `exercises.practice[]` array 2. Create exercism-specific files, like `.meta/config.json` and the documentation 3. Create Rust-specific files, like `Cargo.toml` and `src/lib.rb` 4. Create a tests files with functions for the tests cases as defined in the exercise's canonical data --- .github/workflows/tests.yml | 8 - .gitignore | 3 + _test/cargo_clean_all.sh | 16 - _test/check_exercise_crate.sh | 37 - bin/add_practice_exercise | 101 + bin/fetch_canonical_data | 22 + bin/generate_tests | 50 + bin/generator-utils/colors.sh | 38 + bin/generator-utils/prompts.sh | 38 + bin/generator-utils/templates.sh | 140 ++ bin/generator-utils/utils.sh | 69 + bin/remove_trailing_whitespace.sh | 4 + bin/test_template | 7 + util/exercise/Cargo.lock | 2078 ----------------- util/exercise/Cargo.toml | 34 - util/exercise/src/cmd/configure.rs | 332 --- util/exercise/src/cmd/defaults/README.md | 8 - util/exercise/src/cmd/defaults/description.md | 6 - util/exercise/src/cmd/defaults/example.rs | 10 - util/exercise/src/cmd/defaults/gitignore | 8 - util/exercise/src/cmd/defaults/metadata.yml | 4 - util/exercise/src/cmd/fetch_configlet.rs | 54 - util/exercise/src/cmd/generate.rs | 198 -- util/exercise/src/cmd/mod.rs | 4 - util/exercise/src/cmd/templates/macros.rs | 46 - .../exercise/src/cmd/templates/property_fn.rs | 18 - util/exercise/src/cmd/templates/test_file.rs | 49 - util/exercise/src/cmd/templates/test_fn.rs | 19 - util/exercise/src/cmd/update.rs | 134 -- util/exercise/src/errors.rs | 76 - util/exercise/src/lib.rs | 282 --- util/exercise/src/main.rs | 119 - util/exercise/src/structs.rs | 84 - util/ngram/Cargo.lock | 16 + util/ngram/Cargo.toml | 9 + util/ngram/build | 2 + util/ngram/src/main.rs | 26 + 37 files changed, 525 insertions(+), 3624 deletions(-) delete mode 100755 _test/cargo_clean_all.sh delete mode 100755 _test/check_exercise_crate.sh create mode 100755 bin/add_practice_exercise create mode 100755 bin/fetch_canonical_data create mode 100755 bin/generate_tests create mode 100644 bin/generator-utils/colors.sh create mode 100644 bin/generator-utils/prompts.sh create mode 100755 bin/generator-utils/templates.sh create mode 100755 bin/generator-utils/utils.sh create mode 100755 bin/remove_trailing_whitespace.sh create mode 100644 bin/test_template delete mode 100644 util/exercise/Cargo.lock delete mode 100644 util/exercise/Cargo.toml delete mode 100644 util/exercise/src/cmd/configure.rs delete mode 100644 util/exercise/src/cmd/defaults/README.md delete mode 100644 util/exercise/src/cmd/defaults/description.md delete mode 100644 util/exercise/src/cmd/defaults/example.rs delete mode 100644 util/exercise/src/cmd/defaults/gitignore delete mode 100644 util/exercise/src/cmd/defaults/metadata.yml delete mode 100644 util/exercise/src/cmd/fetch_configlet.rs delete mode 100644 util/exercise/src/cmd/generate.rs delete mode 100644 util/exercise/src/cmd/mod.rs delete mode 100644 util/exercise/src/cmd/templates/macros.rs delete mode 100644 util/exercise/src/cmd/templates/property_fn.rs delete mode 100644 util/exercise/src/cmd/templates/test_file.rs delete mode 100644 util/exercise/src/cmd/templates/test_fn.rs delete mode 100644 util/exercise/src/cmd/update.rs delete mode 100644 util/exercise/src/errors.rs delete mode 100644 util/exercise/src/lib.rs delete mode 100644 util/exercise/src/main.rs delete mode 100644 util/exercise/src/structs.rs create mode 100644 util/ngram/Cargo.lock create mode 100644 util/ngram/Cargo.toml create mode 100755 util/ngram/build create mode 100644 util/ngram/src/main.rs diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 91db73990..8d3c9a2de 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -123,20 +123,12 @@ jobs: run: ./_test/check_exercises.sh continue-on-error: ${{ matrix.rust == 'beta' && matrix.deny_warnings == '1' }} - - name: Cargo clean (to prevent previous compilation from unintentionally interfering with later ones) - run: ./_test/cargo_clean_all.sh - - name: Ensure stubs compile env: DENYWARNINGS: ${{ matrix.deny_warnings }} run: ./_test/ensure_stubs_compile.sh continue-on-error: ${{ matrix.rust == 'beta' && matrix.deny_warnings == '1' }} - - name: Check exercise crate - env: - DENYWARNINGS: ${{ matrix.deny_warnings }} - run: ./_test/check_exercise_crate.sh - continue-on-error: ${{ matrix.rust == 'beta' && matrix.deny_warnings == '1' }} rustformat: name: Check Rust Formatting diff --git a/.gitignore b/.gitignore index 6723c90a8..8f4f224b4 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,8 @@ bin/configlet bin/configlet.exe bin/exercise bin/exercise.exe +bin/generator-utils/ngram exercises/*/*/Cargo.lock exercises/*/*/clippy.log +canonical_data.json +.vscode diff --git a/_test/cargo_clean_all.sh b/_test/cargo_clean_all.sh deleted file mode 100755 index c50bb91c2..000000000 --- a/_test/cargo_clean_all.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/sh - -status=0 -repo="$(cd "$(dirname "$0")/.." && pwd)" - -for ex in "$repo"/exercises/*/*/; do - name=$(grep '^name =' "$ex/Cargo.toml" | cut -d\" -f2) - if [ -z "$name" ]; then - echo "don't know name of $ex" - status=1 - continue - fi - cargo clean --manifest-path "$ex/Cargo.toml" --package "$name" -done - -exit $status diff --git a/_test/check_exercise_crate.sh b/_test/check_exercise_crate.sh deleted file mode 100755 index 3870db865..000000000 --- a/_test/check_exercise_crate.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail - -# A script to ensure that the util/exercise crate builds after it was modified. - -EXERCISE_CRATE_PATH="util/exercise" - -if [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then - # Check the changes on the current branch against main branch - if ! git diff --name-only remotes/origin/main | grep -q "$EXERCISE_CRATE_PATH"; then - echo "exercise crate was not modified. The script is aborted." - exit 0 - fi -fi -# If it's not a pull request, just always run it. -# Two scenarios: -# 1. It's being run locally, -# in which case we assume the person running it really does want to run it. -# 2. It's being run on CI for main, -# in which case we should check regardless of changes to exercise crate, -# in case there's a new toolchain release, etc. - - -TRACK_ROOT="$(git rev-parse --show-toplevel)" - -if ! (cd "$TRACK_ROOT/$EXERCISE_CRATE_PATH" && cargo check); then - echo - echo "An error has occurred while building the exercise crate." - echo "Please make it compile." - - exit 1 -else - echo - echo "exercise crate has been successfully built." - - exit 0 -fi diff --git a/bin/add_practice_exercise b/bin/add_practice_exercise new file mode 100755 index 000000000..08e9f4dbf --- /dev/null +++ b/bin/add_practice_exercise @@ -0,0 +1,101 @@ +#!/usr/bin/env bash + +# shellcheck source=/dev/null +source ./bin/generator-utils/utils.sh +source ./bin/generator-utils/prompts.sh +source ./bin/generator-utils/templates.sh + +# Exit if anything fails. +set -euo pipefail + +# If argument not provided, print usage and exit +if [ $# -ne 1 ] && [ $# -ne 2 ] && [ $# -ne 3 ]; then + echo "Usage: bin/add_practice_exercise [difficulty] [author-github-handle]" + exit 1 +fi + +# Check if sed is gnu-sed +if ! sed --version | grep -q "GNU sed"; then + echo "GNU sed is required. Please install it and make sure it's in your PATH." + exit 1 +fi + +# Check if jq and curl are installed +command -v jq >/dev/null 2>&1 || { + echo >&2 "jq is required but not installed. Please install it and make sure it's in your PATH." + exit 1 +} +command -v curl >/dev/null 2>&1 || { + echo >&2 "curl is required but not installed. Please install it and make sure it's in your PATH." + exit 1 +} + +# Check if exercise exists in configlet info or in config.json +check_exercise_existence "$1" + +# ================================================== + +SLUG="$1" +HAS_CANONICAL_DATA=true +# Fetch canonical data +canonical_json=$(bin/fetch_canonical_data "$SLUG") + +if [ "${canonical_json}" == "404: Not Found" ]; then + HAS_CANONICAL_DATA=false + message "warning" "This exercise doesn't have canonical data" + +else + echo "$canonical_json" >canonical_data.json + message "success" "Fetched canonical data successfully!" +fi + +UNDERSCORED_SLUG=$(dash_to_underscore "$SLUG") +EXERCISE_DIR="exercises/practice/${SLUG}" +EXERCISE_NAME=$(format_exercise_name "$SLUG") +message "info" "Using ${YELLOW}${EXERCISE_NAME}${BLUE} as a default exercise name. You can edit this later in the config.json file" +# using default value for difficulty +EXERCISE_DIFFICULTY=$(validate_difficulty_input "${2:-$(get_exercise_difficulty)}") +message "info" "The exercise difficulty has been set to ${YELLOW}${EXERCISE_DIFFICULTY}${BLUE}. You can edit this later in the config.json file" +# using default value for author +AUTHOR_HANDLE=${3:-$(get_author_handle)} +message "info" "Using ${YELLOW}${AUTHOR_HANDLE}${BLUE} as author's handle. You can edit this later in the 'authors' field in the ${EXERCISE_DIR}/.meta/config.json file" + + +create_rust_files "$EXERCISE_DIR" "$SLUG" "$HAS_CANONICAL_DATA" + + +# ================================================== + +# build configlet +./bin/fetch-configlet +message "success" "Fetched configlet successfully!" + +# Preparing config.json +message "info" "Adding instructions and configuration files..." +UUID=$(bin/configlet uuid) + +jq --arg slug "$SLUG" --arg uuid "$UUID" --arg name "$EXERCISE_NAME" --arg difficulty "$EXERCISE_DIFFICULTY" \ + '.exercises.practice += [{slug: $slug, name: $name, uuid: $uuid, practices: [], prerequisites: [], difficulty: $difficulty}]' \ + config.json >config.json.tmp +# jq always rounds whole numbers, but average_run_time needs to be a float +sed -i 's/"average_run_time": \([0-9]\+\)$/"average_run_time": \1.0/' config.json.tmp +mv config.json.tmp config.json +message "success" "Added instructions and configuration files" + +# Create instructions and config files +echo "Creating instructions and config files" +./bin/configlet sync --update --yes --docs --metadata --exercise "$SLUG" +./bin/configlet sync --update --yes --filepaths --exercise "$SLUG" +./bin/configlet sync --update --tests include --exercise "$SLUG" +message "success" "Created instructions and config files" + +META_CONFIG="$EXERCISE_DIR"/.meta/config.json +jq --arg author "$AUTHOR_HANDLE" '.authors += [$author]' "$META_CONFIG" >"$META_CONFIG".tmp && mv "$META_CONFIG".tmp "$META_CONFIG" +message "success" "You've been added as the author of this exercise." + +sed -i "s/name = \".*\"/name = \"$UNDERSCORED_SLUG\"/" "$EXERCISE_DIR"/Cargo.toml + +message "done" "All stub files were created." + +message "info" "After implementing the solution, tests and configuration, please run:" +echo "./bin/configlet fmt --update --yes --exercise ${SLUG}" diff --git a/bin/fetch_canonical_data b/bin/fetch_canonical_data new file mode 100755 index 000000000..a487581f2 --- /dev/null +++ b/bin/fetch_canonical_data @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# This script fetches the canonical data of the exercise. + +if [ $# -ne 1 ]; then + echo "Usage: bin/fetch_canonical_data " + exit 1 +fi + +# check if curl is installed +command -v curl >/dev/null 2>&1 || { + echo >&2 "curl is required but not installed. Please install it and make sure it's in your PATH." + exit 1 +} + +slug=$1 + +curlopts=( + --silent + --retry 3 + --max-time 4 +) +curl "${curlopts[@]}" "https://raw.githubusercontent.com/exercism/problem-specifications/main/exercises/$%7Bslug%7D/canonical-data.json" diff --git a/bin/generate_tests b/bin/generate_tests new file mode 100755 index 000000000..ce8875285 --- /dev/null +++ b/bin/generate_tests @@ -0,0 +1,50 @@ +#!/usr/bin/env bash + +set -euo pipefail +# shellcheck source=/dev/null +source ./bin/generator-utils/utils.sh + +function digest_template() { + template=$(cat bin/test_template) + # shellcheck disable=SC2001 + # turn every token into a jq command + echo "$template" | sed 's/${\([^}]*\)\}\$/$(echo $case | jq -r '\''.\1'\'')/g' +} + +message "info" "Generating tests.." +canonical_json=$(cat canonical_data.json) +SLUG=$(echo "$canonical_json" | jq '.exercise') +# shellcheck disable=SC2001 +# Remove double quotes +SLUG=$(echo "$SLUG" | sed 's/"//g') +EXERCISE_DIR="exercises/practice/$SLUG" +TEST_FILE="$EXERCISE_DIR/tests/$SLUG.rs" + +cat <"$TEST_FILE" +use $(dash_to_underscore "$SLUG")::*; +// Add tests here + +EOT + +# Flattens canonical json, extracts only the objects with a uuid +cases=$(echo "$canonical_json" | jq '[ .. | objects | with_entries(select(.key | IN("uuid", "description", "input", "expected", "property"))) | select(. != {}) | select(has("uuid")) ]') + +# shellcheck disable=SC2034 +jq -c '.[]' <<<"$cases" | while read -r case; do + + # Evaluate the bash parts and replace them with their return values + eval_template="$(digest_template | sed -e "s/\$(\(.*\))/\$\(\1\)/g")" + eval_template="$(eval "echo \"$eval_template\"")" + + # Turn function name unto snake_case + formatted_template=$(echo "$eval_template" | sed -e ':loop' -e 's/\(fn[^(]*\)[ -]/\1_/g' -e 't loop' | sed 's/fn_/fn /') + # Push to file + + echo "$formatted_template" >>"$TEST_FILE" + printf "\\n" >>"$TEST_FILE" + +done + +rustfmt "$TEST_FILE" + +message "success" "Generated tests successfully! Check out ${TEST_FILE}" diff --git a/bin/generator-utils/colors.sh b/bin/generator-utils/colors.sh new file mode 100644 index 000000000..3ae992587 --- /dev/null +++ b/bin/generator-utils/colors.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + +# shellcheck disable=SC2034 +# Reset +RESET=$(echo -e '\033[0m') + +# Regular Colors +BLACK=$(echo -e '\033[0;30m') +RED=$(echo -e '\033[0;31m') +GREEN=$(echo -e '\033[0;32m') +YELLOW=$(echo -e '\033[0;33m') +BLUE=$(echo -e '\033[0;34m') +PURPLE=$(echo -e '\033[0;35m') +CYAN=$(echo -e '\033[0;36m') +WHITE=$(echo -e '\033[0;37m') + +# Bold +BOLD_BLACK=$(echo -e '\033[1;30m') +BOLD_RED=$(echo -e '\033[1;31m') +BOLD_GREEN=$(echo -e '\033[1;32m') +BOLD_YELLOW=$(echo -e '\033[1;33m') +BOLD_BLUE=$(echo -e '\033[1;34m') +BOLD_PURPLE=$(echo -e '\033[1;35m') +BOLD_CYAN=$(echo -e '\033[1;36m') +BOLD_WHITE=$(echo -e '\033[1;37m') + +# Underline +UNDERLINE=$(echo -e ='\033[4m') + +# Background +BG_BLACK=$(echo -e ='\033[40m') +BG_RED=$(echo -e ='\033[41m') +BG_GREEN=$(echo -e ='\033[42m') +BG_YELLOW=$(echo -e ='\033[43m') +BG_BLUE=$(echo -e ='\033[44m') +BG_PURPLE=$(echo -e ='\033[45m') +BG_CYAN=$(echo -e ='\033[46m') +BG_WHITE=$(echo -e ='\033[47m') diff --git a/bin/generator-utils/prompts.sh b/bin/generator-utils/prompts.sh new file mode 100644 index 000000000..c785a2636 --- /dev/null +++ b/bin/generator-utils/prompts.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + +# shellcheck source=/dev/null +source ./bin/generator-utils/colors.sh + +function get_exercise_difficulty() { + read -rp "Difficulty of exercise (1-10): " exercise_difficulty + echo "$exercise_difficulty" +} + +function validate_difficulty_input() { + + valid_input=false + while ! $valid_input; do + if [[ "$1" =~ ^[1-9]$|^10$ ]]; then + exercise_difficulty=$1 + valid_input=true + else + read -rp "${RED}Invalid input. ${RESET}Please enter an integer between 1 and 10. " exercise_difficulty + [[ "$exercise_difficulty" =~ ^[1-9]$|^10$ ]] && valid_input=true + + fi + done + echo "$exercise_difficulty" +} + +function get_author_handle { + DEFAULT_AUTHOR_HANDLE="$(git config user.name)" + + if [ -z "$DEFAULT_AUTHOR_HANDLE" ]; then + read -rp "Hey! Couldn't find your Github handle. Add it now or skip with enter and add it later in the .meta.config.json file: " AUTHOR_HANDLE + else + AUTHOR_HANDLE="$DEFAULT_AUTHOR_HANDLE" + + fi + echo "$AUTHOR_HANDLE" + +} diff --git a/bin/generator-utils/templates.sh b/bin/generator-utils/templates.sh new file mode 100755 index 000000000..5c957314a --- /dev/null +++ b/bin/generator-utils/templates.sh @@ -0,0 +1,140 @@ +#!/usr/bin/env bash + +# shellcheck source=/dev/null +source ./bin/generator-utils/utils.sh + +function create_fn_name() { + slug=$1 + has_canonical_data=$2 + + if [ "$has_canonical_data" == false ]; then + fn_name=$(dash_to_underscore "$slug") + else + fn_name=$(jq -r 'first(.. | .property? // empty)' canonical_data.json) + fi + + echo "$fn_name" + +} + +function create_test_file_template() { + local exercise_dir=$1 + local slug=$2 + local has_canonical_data=$3 + local test_file="${exercise_dir}/tests/${slug}.rs" + + cat <"$test_file" +use $(dash_to_underscore "$slug")::*; +// Add tests here + +EOT + + if [ "$has_canonical_data" == false ]; then + + cat <>"$test_file" +// As there isn't a canonical data file for this exercise, you will need to craft your own tests. +// If you happen to devise some outstanding tests, do contemplate sharing them with the community by contributing to this repository: +// https://github.com/exercism/problem-specifications/tree/main/exercises/${slug} +EOT + message "info" "This exercise doesn't have canonical data." + message "success" "Stub file for tests has been created!" + else + canonical_json=$(cat canonical_data.json) + + # sometimes canonical data has multiple levels with multiple `cases` arrays. + #(see kindergarten-garden https://github.com/exercism/problem-specifications/blob/main/exercises/kindergarten-garden/canonical-data.json) + # so let's flatten it + cases=$(echo "$canonical_json" | jq '[ .. | objects | with_entries(select(.key | IN("uuid", "description", "input", "expected"))) | select(. != {}) | select(has("uuid")) ]') + fn_name=$(echo "$canonical_json" | jq -r 'first(.. | .property? // empty)') + + first_iteration=true + # loop through each object + jq -c '.[]' <<<"$cases" | while read -r case; do + desc=$(echo "$case" | jq '.description' | tr '[:upper:]' '[:lower:]' | tr ' ' '_' | tr -cd '[:alnum:]_' | sed 's/^/test_/') + input=$(echo "$case" | jq -c '.input') + expected=$(echo "$case" | jq -c '.expected') + + # append each test fn to the test file + cat <>"$test_file" +#[test] $([[ "$first_iteration" == false ]] && printf "\n#[ignore]") +fn ${desc}() { + + let input = ${input}; + let expected = ${expected}; + + // TODO: Add assertion + assert_eq!(${fn_name}(input), expected); +} + +EOT + first_iteration=false + done + message "success" "Stub file for tests has been created and populated with canonical data!" + fi + +} + +function create_lib_rs_template() { + local exercise_dir=$1 + local slug=$2 + local has_canonical_data=$3 + fn_name=$(create_fn_name "$slug" "$has_canonical_data") + cat <"${exercise_dir}/src/lib.rs" +pub fn ${fn_name}() { + unimplemented!("implement ${slug} exercise"); +} +EOT + message "success" "Stub file for lib.rs has been created!" +} + +function overwrite_gitignore() { + local exercise_dir=$1 + cat <"$exercise_dir"/.gitignore +# Generated by Cargo +# Will have compiled files and executables +/target/ +**/*.rs.bk + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock +EOT + message "success" ".gitignore has been overwritten!" +} + +function create_example_rs_template() { + local exercise_dir=$1 + local slug=$2 + local has_canonical_data=$3 + + fn_name=$(create_fn_name "$slug" "$has_canonical_data") + + mkdir "${exercise_dir}/.meta" + cat <"${exercise_dir}/.meta/example.rs" +pub fn ${fn_name}() { + // TODO: Create a solution that passes all the tests + unimplemented!("implement ${slug} exercise"); +} + +EOT + message "success" "Stub file for example.rs has been created!" +} + +function create_rust_files() { + local exercise_dir=$1 + local slug=$2 + local has_canonical_data=$3 + + message "info" "Creating Rust files" + cargo new --lib "$exercise_dir" -q + mkdir -p "$exercise_dir"/tests + touch "${exercise_dir}/tests/${slug}.rs" + + create_test_file_template "$exercise_dir" "$slug" "$has_canonical_data" + create_lib_rs_template "$exercise_dir" "$slug" "$has_canonical_data" + create_example_rs_template "$exercise_dir" "$slug" "$has_canonical_data" + overwrite_gitignore "$exercise_dir" + + message "success" "Created Rust files succesfully!" + +} diff --git a/bin/generator-utils/utils.sh b/bin/generator-utils/utils.sh new file mode 100755 index 000000000..e89b33e67 --- /dev/null +++ b/bin/generator-utils/utils.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash + +# shellcheck source=/dev/null +source ./bin/generator-utils/colors.sh + +function message() { + local flag=$1 + local message=$2 + + case "$flag" in + "success") printf "${GREEN}%s${RESET}\n" "[success]: $message" ;; + "info") printf "${BLUE}%s${RESET}\n" "[info]: $message" ;; + "warning") printf "${YELLOW}%s${RESET}\n" "[warning]: $message" ;; + "error") printf "${RED}%s${RESET}\n" "[error]: $message" ;; + "done") + echo + cols=$(tput cols) + printf "%*s\n" "$cols" "" | tr " " "-" + echo + printf "${BOLD_GREEN}%s${RESET}\n" "[done]: $message" + ;; + *) + echo "Invalid flag: $flag" + ;; + esac +} + +function dash_to_underscore() { + # shellcheck disable=SC2001 + echo "$1" | sed 's/-/_/g' +} + +# exercise_name -> Exercise Name +function format_exercise_name { + echo "$1" | sed 's/-/ /g; s/\b\(.\)/\u\1/g' +} + +function check_exercise_existence() { + message "info" "Looking for exercise.." + slug="$1" + # Check if exercise is already in config.json + if jq '.exercises.practice | map(.slug)' config.json | grep -q "$slug"; then + echo "${1} has already been implemented." + exit 1 + fi + + # fetch configlet and crop out exercise list + unimplemented_exercises=$(bin/configlet info | sed -n '/With canonical data:/,/Track summary:/p' | sed -e '/\(With\(out\)\? canonical data:\|Track summary:\)/d' -e '/^$/d') + if echo "$unimplemented_exercises" | grep -q "^$slug$"; then + message "success" "Exercise has been found!" + else + message "error" "Exercise doesn't exist!" + message "info" "These are the unimplemented practice exercises: +${unimplemented_exercises}" + + # Find closest match to typed-in not-found slug + # see util/ngram for source + # First it builds a binary for the system of the contributor + if [ -e bin/generator-utils/ngram ]; then + echo "${YELLOW}$(bin/generator-utils/ngram "${unimplemented_exercises}" "$slug")${RESET}" + else + message "info" "Building typo-checker binary for $(uname -m) system." + + cd util/ngram && ./build && cd ../.. && echo "${YELLOW}$(bin/generator-utils/ngram "${unimplemented_exercises}" "$slug")${RESET}" + fi + + exit 1 + fi +} diff --git a/bin/remove_trailing_whitespace.sh b/bin/remove_trailing_whitespace.sh new file mode 100755 index 000000000..5a93912f7 --- /dev/null +++ b/bin/remove_trailing_whitespace.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +# removes all trailing whitespaces from *.sh and *.py files in this folder +find . -type f \( -name "*.sh" -o -name "*.py" \) -exec sed -i 's/[[:space:]]\+$//' {} \; diff --git a/bin/test_template b/bin/test_template new file mode 100644 index 000000000..ad458d212 --- /dev/null +++ b/bin/test_template @@ -0,0 +1,7 @@ +#[test] +#[ignore] +fn ${description}$() { + + assert_eq!(${property}$(${input}$), ${expected}$); +} + diff --git a/util/exercise/Cargo.lock b/util/exercise/Cargo.lock deleted file mode 100644 index c6e419cc8..000000000 --- a/util/exercise/Cargo.lock +++ /dev/null @@ -1,2078 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "adler32" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2" - -[[package]] -name = "aho-corasick" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d" -dependencies = [ - "memchr", -] - -[[package]] -name = "ansi_term" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" -dependencies = [ - "winapi 0.3.8", -] - -[[package]] -name = "atty" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" -dependencies = [ - "libc", - "winapi 0.3.8", -] - -[[package]] -name = "autocfg" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b671c8fb71b457dd4ae18c4ba1e59aa81793daacc361d82fcd410cef0d491875" - -[[package]] -name = "autocfg" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" - -[[package]] -name = "backtrace" -version = "0.3.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "924c76597f0d9ca25d762c25a4d369d51267536465dc5064bdf0eb073ed477ea" -dependencies = [ - "backtrace-sys", - "cfg-if 0.1.10", - "libc", - "rustc-demangle", -] - -[[package]] -name = "backtrace-sys" -version = "0.1.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "base64" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" -dependencies = [ - "byteorder", -] - -[[package]] -name = "bitflags" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" - -[[package]] -name = "block-buffer" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" -dependencies = [ - "block-padding", - "byte-tools", - "byteorder", - "generic-array", -] - -[[package]] -name = "block-padding" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" -dependencies = [ - "byte-tools", -] - -[[package]] -name = "bstr" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ede750122d9d1f87919570cb2cccee38c84fbc8c5599b25c289af40625b7030" -dependencies = [ - "memchr", -] - -[[package]] -name = "bumpalo" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad807f2fc2bf185eeb98ff3a901bd46dc5ad58163d0fa4577ba0d25674d71708" - -[[package]] -name = "byte-tools" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" - -[[package]] -name = "byteorder" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" - -[[package]] -name = "bytes" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" -dependencies = [ - "byteorder", - "either", - "iovec", -] - -[[package]] -name = "c2-chacha" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb" -dependencies = [ - "ppv-lite86", -] - -[[package]] -name = "cc" -version = "1.0.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0213d356d3c4ea2c18c40b037c3be23cd639825c18f25ee670ac7813beeef99c" - -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "chrono" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31850b4a4d6bae316f7a09e691c944c28299298837edc0a03f755618c23cbc01" -dependencies = [ - "num-integer", - "num-traits", - "time", -] - -[[package]] -name = "chrono-tz" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0e430fad0384e4defc3dc6b1223d1b886087a8bf9b7080e5ae027f73851ea15" -dependencies = [ - "chrono", - "parse-zoneinfo", -] - -[[package]] -name = "clap" -version = "2.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" -dependencies = [ - "ansi_term", - "atty", - "bitflags", - "strsim", - "textwrap", - "unicode-width", - "vec_map", -] - -[[package]] -name = "cloudabi" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" -dependencies = [ - "bitflags", -] - -[[package]] -name = "cookie" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "888604f00b3db336d2af898ec3c1d5d0ddf5e6d462220f2ededc33a87ac4bbd5" -dependencies = [ - "time", - "url 1.7.2", -] - -[[package]] -name = "cookie_store" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46750b3f362965f197996c4448e4a0935e791bf7d6631bfce9ee0af3d24c919c" -dependencies = [ - "cookie", - "failure", - "idna 0.1.5", - "log", - "publicsuffix", - "serde", - "serde_json", - "time", - "try_from", - "url 1.7.2", -] - -[[package]] -name = "crc32fast" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" -dependencies = [ - "cfg-if 0.1.10", -] - -[[package]] -name = "crossbeam-channel" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87" -dependencies = [ - "crossbeam-utils 0.7.0", - "maybe-uninit", -] - -[[package]] -name = "crossbeam-deque" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20ff29ded3204c5106278a81a38f4b482636ed4fa1e6cfbeef193291beb29ed" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils 0.7.0", - "maybe-uninit", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" -dependencies = [ - "autocfg 1.0.0", - "cfg-if 0.1.10", - "crossbeam-utils 0.7.0", - "lazy_static", - "maybe-uninit", - "memoffset", - "scopeguard", -] - -[[package]] -name = "crossbeam-queue" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c979cd6cfe72335896575c6b5688da489e420d36a27a0b9eb0c73db574b4a4b" -dependencies = [ - "crossbeam-utils 0.6.6", -] - -[[package]] -name = "crossbeam-utils" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6" -dependencies = [ - "cfg-if 0.1.10", - "lazy_static", -] - -[[package]] -name = "crossbeam-utils" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce446db02cdc3165b94ae73111e570793400d0794e46125cc4056c81cbb039f4" -dependencies = [ - "autocfg 0.1.6", - "cfg-if 0.1.10", - "lazy_static", -] - -[[package]] -name = "ct-logs" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d3686f5fa27dbc1d76c751300376e167c5a43387f44bb451fd1c24776e49113" -dependencies = [ - "sct", -] - -[[package]] -name = "deunicode" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "850878694b7933ca4c9569d30a34b55031b9b139ee1fc7b94a527c4ef960d690" - -[[package]] -name = "digest" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" -dependencies = [ - "generic-array", -] - -[[package]] -name = "dtoa" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea57b42383d091c85abcc2706240b94ab2a8fa1fc81c10ff23c4de06e2a90b5e" - -[[package]] -name = "either" -version = "1.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" - -[[package]] -name = "encoding_rs" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87240518927716f79692c2ed85bfe6e98196d18c6401ec75355760233a7e12e9" -dependencies = [ - "cfg-if 0.1.10", -] - -[[package]] -name = "error-chain" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab49e9dcb602294bc42f9a7dfc9bc6e936fca4418ea300dbfb84fe16de0b7d9" -dependencies = [ - "backtrace", - "version_check", -] - -[[package]] -name = "exercise" -version = "1.6.2" -dependencies = [ - "clap", - "failure", - "failure_derive", - "flate2", - "indexmap", - "lazy_static", - "reqwest", - "serde", - "serde_json", - "tar", - "tera", - "toml", - "uuid", -] - -[[package]] -name = "failure" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8273f13c977665c5db7eb2b99ae520952fe5ac831ae4cd09d80c4c7042b5ed9" -dependencies = [ - "backtrace", - "failure_derive", -] - -[[package]] -name = "failure_derive" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bc225b78e0391e4b8683440bf2e63c2deeeb2ce5189eab46e2b68c6d3725d08" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "fake-simd" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" - -[[package]] -name = "filetime" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0408e2626025178a6a7f7ffc05a25bc47103229f19c113755de7bf63816290c" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "redox_syscall 0.2.13", - "winapi 0.3.8", -] - -[[package]] -name = "flate2" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad3c5233c9a940c8719031b423d7e6c16af66e031cb0420b0896f5245bf181d3" -dependencies = [ - "cfg-if 0.1.10", - "crc32fast", - "libc", - "miniz_oxide", -] - -[[package]] -name = "fnv" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" - -[[package]] -name = "fuchsia-cprng" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" - -[[package]] -name = "fuchsia-zircon" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" -dependencies = [ - "bitflags", - "fuchsia-zircon-sys", -] - -[[package]] -name = "fuchsia-zircon-sys" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" - -[[package]] -name = "futures" -version = "0.1.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b980f2816d6ee8673b6517b52cb0e808a180efc92e5c19d02cdda79066703ef" - -[[package]] -name = "futures-cpupool" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4" -dependencies = [ - "futures", - "num_cpus", -] - -[[package]] -name = "generic-array" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" -dependencies = [ - "typenum", -] - -[[package]] -name = "getrandom" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" -dependencies = [ - "cfg-if 0.1.10", - "libc", - "wasi", -] - -[[package]] -name = "globset" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925aa2cac82d8834e2b2a4415b6f6879757fb5c0928fc445ae76461a12eed8f2" -dependencies = [ - "aho-corasick", - "bstr", - "fnv", - "log", - "regex", -] - -[[package]] -name = "globwalk" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53cbcf0368596897b0a3b8ff2110acf2400e80ffad4ca9238b52ff282a9b267b" -dependencies = [ - "ignore", - "walkdir", -] - -[[package]] -name = "h2" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5b34c246847f938a410a03c5458c7fee2274436675e76d8b903c08efc29c462" -dependencies = [ - "byteorder", - "bytes", - "fnv", - "futures", - "http", - "indexmap", - "log", - "slab", - "string", - "tokio-io", -] - -[[package]] -name = "heck" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "http" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6ccf5ede3a895d8856620237b2f02972c1bbc78d2965ad7fe8838d4a0ed41f0" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6741c859c1b2463a423a1dbce98d418e6c3c3fc720fb0d45528657320920292d" -dependencies = [ - "bytes", - "futures", - "http", - "tokio-buf", -] - -[[package]] -name = "httparse" -version = "1.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" - -[[package]] -name = "humansize" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6cab2627acfc432780848602f3f558f7e9dd427352224b0d9324025796d2a5e" - -[[package]] -name = "hyper" -version = "0.12.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c843caf6296fc1f93444735205af9ed4e109a539005abb2564ae1d6fad34c52" -dependencies = [ - "bytes", - "futures", - "futures-cpupool", - "h2", - "http", - "http-body", - "httparse", - "iovec", - "itoa", - "log", - "net2", - "rustc_version", - "time", - "tokio", - "tokio-buf", - "tokio-executor", - "tokio-io", - "tokio-reactor", - "tokio-tcp", - "tokio-threadpool", - "tokio-timer", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719d85c7df4a7f309a77d145340a063ea929dcb2e025bae46a80345cffec2952" -dependencies = [ - "bytes", - "ct-logs", - "futures", - "hyper", - "rustls", - "tokio-io", - "tokio-rustls", - "webpki", - "webpki-roots", -] - -[[package]] -name = "idna" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "idna" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "ignore" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "522daefc3b69036f80c7d2990b28ff9e0471c683bad05ca258e0a01dd22c5a1e" -dependencies = [ - "crossbeam-channel", - "globset", - "lazy_static", - "log", - "memchr", - "regex", - "same-file", - "thread_local 1.0.1", - "walkdir", - "winapi-util", -] - -[[package]] -name = "indexmap" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712d7b3ea5827fcb9d4fda14bf4da5f136f0db2ae9c8f4bd4e2d1c6fde4e6db2" -dependencies = [ - "autocfg 0.1.6", - "serde", -] - -[[package]] -name = "iovec" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" -dependencies = [ - "libc", -] - -[[package]] -name = "itoa" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" - -[[package]] -name = "js-sys" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc9a97d7cec30128fd8b28a7c1f9df1c001ceb9b441e2b755e24130a6b43c79" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "kernel32-sys" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -dependencies = [ - "winapi 0.2.8", - "winapi-build", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "libc" -version = "0.2.65" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a31a0627fdf1f6a39ec0dd577e101440b7db22672c0901fe00a9a6fbb5c24e8" - -[[package]] -name = "lock_api" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8912e782533a93a167888781b836336a6ca5da6175c05944c86cf28c31104dc" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" -dependencies = [ - "cfg-if 0.1.10", -] - -[[package]] -name = "maplit" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" - -[[package]] -name = "matches" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" - -[[package]] -name = "maybe-uninit" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" - -[[package]] -name = "memchr" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" - -[[package]] -name = "memoffset" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6075db033bbbb7ee5a0bbd3a3186bbae616f57fb001c485c7ff77955f8177f" -dependencies = [ - "rustc_version", -] - -[[package]] -name = "mime" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd1d63acd1b78403cc0c325605908475dd9b9a3acbf65ed8bcab97e27014afcf" - -[[package]] -name = "mime_guess" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a0ed03949aef72dbdf3116a383d7b38b4768e6f960528cd6a6044aa9ed68599" -dependencies = [ - "mime", - "unicase", -] - -[[package]] -name = "miniz_oxide" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "304f66c19be2afa56530fa7c39796192eef38618da8d19df725ad7c6d6b2aaae" -dependencies = [ - "adler32", -] - -[[package]] -name = "mio" -version = "0.6.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83f51996a3ed004ef184e16818edc51fadffe8e7ca68be67f9dee67d84d0ff23" -dependencies = [ - "fuchsia-zircon", - "fuchsia-zircon-sys", - "iovec", - "kernel32-sys", - "libc", - "log", - "miow", - "net2", - "slab", - "winapi 0.2.8", -] - -[[package]] -name = "miow" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" -dependencies = [ - "kernel32-sys", - "net2", - "winapi 0.2.8", - "ws2_32-sys", -] - -[[package]] -name = "net2" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" -dependencies = [ - "cfg-if 0.1.10", - "libc", - "winapi 0.3.8", -] - -[[package]] -name = "nom" -version = "4.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" -dependencies = [ - "memchr", - "version_check", -] - -[[package]] -name = "num-integer" -version = "0.1.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" -dependencies = [ - "autocfg 1.0.0", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" -dependencies = [ - "autocfg 1.0.0", -] - -[[package]] -name = "num_cpus" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273" -dependencies = [ - "libc", -] - -[[package]] -name = "opaque-debug" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" - -[[package]] -name = "parking_lot" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252" -dependencies = [ - "lock_api", - "parking_lot_core", - "rustc_version", -] - -[[package]] -name = "parking_lot_core" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" -dependencies = [ - "cfg-if 0.1.10", - "cloudabi", - "libc", - "redox_syscall 0.1.56", - "rustc_version", - "smallvec", - "winapi 0.3.8", -] - -[[package]] -name = "parse-zoneinfo" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feece9d0113b400182a7d00adcff81ccf29158c49c5abd11e2eed8589bf6ff07" -dependencies = [ - "regex", -] - -[[package]] -name = "percent-encoding" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" - -[[package]] -name = "percent-encoding" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" - -[[package]] -name = "pest" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e4fb201c5c22a55d8b24fef95f78be52738e5e1361129be1b5e862ecdb6894a" -dependencies = [ - "ucd-trie", -] - -[[package]] -name = "pest_derive" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" -dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b9fcf299b5712d06ee128a556c94709aaa04512c4dffb8ead07c5c998447fc0" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "pest_meta" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df43fd99896fd72c485fe47542c7b500e4ac1e8700bf995544d1317a60ded547" -dependencies = [ - "maplit", - "pest", - "sha-1", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" - -[[package]] -name = "proc-macro2" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90cf5f418035b98e655e9cdb225047638296b862b42411c4e45bb88d700f7fc0" -dependencies = [ - "unicode-xid", -] - -[[package]] -name = "publicsuffix" -version = "1.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bf259a81de2b2eb9850ec990ec78e6a25319715584fd7652b9b26f96fcb1510" -dependencies = [ - "error-chain", - "idna 0.2.0", - "lazy_static", - "regex", - "url 2.1.0", -] - -[[package]] -name = "quote" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" -dependencies = [ - "autocfg 0.1.6", - "libc", - "rand_chacha 0.1.1", - "rand_core 0.4.2", - "rand_hc 0.1.0", - "rand_isaac", - "rand_jitter", - "rand_os", - "rand_pcg", - "rand_xorshift", - "winapi 0.3.8", -] - -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom", - "libc", - "rand_chacha 0.2.1", - "rand_core 0.5.1", - "rand_hc 0.2.0", -] - -[[package]] -name = "rand_chacha" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" -dependencies = [ - "autocfg 0.1.6", - "rand_core 0.3.1", -] - -[[package]] -name = "rand_chacha" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" -dependencies = [ - "c2-chacha", - "rand_core 0.5.1", -] - -[[package]] -name = "rand_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -dependencies = [ - "rand_core 0.4.2", -] - -[[package]] -name = "rand_core" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom", -] - -[[package]] -name = "rand_hc" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "rand_isaac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "rand_jitter" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" -dependencies = [ - "libc", - "rand_core 0.4.2", - "winapi 0.3.8", -] - -[[package]] -name = "rand_os" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" -dependencies = [ - "cloudabi", - "fuchsia-cprng", - "libc", - "rand_core 0.4.2", - "rdrand", - "winapi 0.3.8", -] - -[[package]] -name = "rand_pcg" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" -dependencies = [ - "autocfg 0.1.6", - "rand_core 0.4.2", -] - -[[package]] -name = "rand_xorshift" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "rdrand" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "redox_syscall" -version = "0.1.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" - -[[package]] -name = "redox_syscall" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" -dependencies = [ - "bitflags", -] - -[[package]] -name = "regex" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc220bd33bdce8f093101afe22a037b8eb0e5af33592e6a9caafff0d4cb81cbd" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", - "thread_local 0.3.6", -] - -[[package]] -name = "regex-syntax" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716" - -[[package]] -name = "reqwest" -version = "0.9.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c2064233e442ce85c77231ebd67d9eca395207dec2127fe0bbedde4bd29a650" -dependencies = [ - "base64", - "bytes", - "cookie", - "cookie_store", - "encoding_rs", - "flate2", - "futures", - "http", - "hyper", - "hyper-rustls", - "log", - "mime", - "mime_guess", - "rustls", - "serde", - "serde_json", - "serde_urlencoded", - "time", - "tokio", - "tokio-executor", - "tokio-io", - "tokio-rustls", - "tokio-threadpool", - "tokio-timer", - "url 1.7.2", - "uuid", - "webpki-roots", - "winreg", -] - -[[package]] -name = "ring" -version = "0.16.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6747f8da1f2b1fabbee1aaa4eb8a11abf9adef0bf58a41cee45db5d59cecdfac" -dependencies = [ - "cc", - "lazy_static", - "libc", - "spin", - "untrusted", - "web-sys", - "winapi 0.3.8", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" - -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -dependencies = [ - "semver", -] - -[[package]] -name = "rustls" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b25a18b1bf7387f0145e7f8324e700805aade3842dd3db2e74e4cdeb4677c09e" -dependencies = [ - "base64", - "log", - "ring", - "sct", - "webpki", -] - -[[package]] -name = "ryu" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "scopeguard" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d" - -[[package]] -name = "sct" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3042af939fca8c3453b7af0f1c66e533a15a86169e39de2657310ade8f98d3c" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" - -[[package]] -name = "serde" -version = "1.0.102" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b39bd9b0b087684013a792c59e3e07a46a01d2322518d8a1104641a0b1be0" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.101" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b133a43a1ecd55d4086bd5b4dc6c1751c68b1bfbeba7a5040442022c7e7c02e" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f72eb2a68a7dc3f9a691bfda9305a1c017a6215e5a4545c258500d2099a37c2" -dependencies = [ - "indexmap", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "642dd69105886af2efd227f75a520ec9b44a820d65bc133a9131f7d229fd165a" -dependencies = [ - "dtoa", - "itoa", - "serde", - "url 1.7.2", -] - -[[package]] -name = "sha-1" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" -dependencies = [ - "block-buffer", - "digest", - "fake-simd", - "opaque-debug", -] - -[[package]] -name = "slab" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" - -[[package]] -name = "slug" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3bc762e6a4b6c6fcaade73e77f9ebc6991b676f88bb2358bddb56560f073373" -dependencies = [ - "deunicode", -] - -[[package]] -name = "smallvec" -version = "0.6.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0" -dependencies = [ - "maybe-uninit", -] - -[[package]] -name = "sourcefile" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bf77cb82ba8453b42b6ae1d692e4cdc92f9a47beaf89a847c8be83f4e328ad3" - -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - -[[package]] -name = "string" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d24114bfcceb867ca7f71a0d3fe45d45619ec47a6fbfa98cb14e14250bfa5d6d" -dependencies = [ - "bytes", -] - -[[package]] -name = "strsim" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" - -[[package]] -name = "syn" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66850e97125af79138385e9b88339cbcd037e3f28ceab8c5ad98e64f0f1f80bf" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "synstructure" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f085a5855930c0441ca1288cf044ea4aecf4f43a91668abdb870b4ba546a203" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "unicode-xid", -] - -[[package]] -name = "tar" -version = "0.4.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8190d9cdacf6ee1b080605fd719b58d80a9fcbcea64db6744b26f743da02e447" -dependencies = [ - "filetime", - "libc", - "xattr", -] - -[[package]] -name = "tera" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8501ae034d1c2d2e8c29f3c259d919c11489220d8ee8a268e7e1fe3eff94c86a" -dependencies = [ - "chrono", - "chrono-tz", - "globwalk", - "humansize", - "lazy_static", - "percent-encoding 2.1.0", - "pest", - "pest_derive", - "rand 0.7.3", - "regex", - "serde", - "serde_json", - "slug", - "unic-segment", -] - -[[package]] -name = "textwrap" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -dependencies = [ - "unicode-width", -] - -[[package]] -name = "thread_local" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "thread_local" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "time" -version = "0.1.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" -dependencies = [ - "libc", - "redox_syscall 0.1.56", - "winapi 0.3.8", -] - -[[package]] -name = "tokio" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a09c0b5bb588872ab2f09afa13ee6e9dac11e10a0ec9e8e3ba39a5a5d530af6" -dependencies = [ - "bytes", - "futures", - "mio", - "num_cpus", - "tokio-current-thread", - "tokio-executor", - "tokio-io", - "tokio-reactor", - "tokio-tcp", - "tokio-threadpool", - "tokio-timer", -] - -[[package]] -name = "tokio-buf" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb220f46c53859a4b7ec083e41dec9778ff0b1851c0942b211edb89e0ccdc46" -dependencies = [ - "bytes", - "either", - "futures", -] - -[[package]] -name = "tokio-current-thread" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16217cad7f1b840c5a97dfb3c43b0c871fef423a6e8d2118c604e843662a443" -dependencies = [ - "futures", - "tokio-executor", -] - -[[package]] -name = "tokio-executor" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f27ee0e6db01c5f0b2973824547ce7e637b2ed79b891a9677b0de9bd532b6ac" -dependencies = [ - "crossbeam-utils 0.6.6", - "futures", -] - -[[package]] -name = "tokio-io" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5090db468dad16e1a7a54c8c67280c5e4b544f3d3e018f0b913b400261f85926" -dependencies = [ - "bytes", - "futures", - "log", -] - -[[package]] -name = "tokio-reactor" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56391be9805bc80163151c0b9e5164ee64f4b0200962c346fea12773158f22d" -dependencies = [ - "crossbeam-utils 0.6.6", - "futures", - "lazy_static", - "log", - "mio", - "num_cpus", - "parking_lot", - "slab", - "tokio-executor", - "tokio-io", - "tokio-sync", -] - -[[package]] -name = "tokio-rustls" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df2fa53ac211c136832f530ccb081af9af891af22d685a9493e232c7a359bc2" -dependencies = [ - "bytes", - "futures", - "iovec", - "rustls", - "tokio-io", - "webpki", -] - -[[package]] -name = "tokio-sync" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d06554cce1ae4a50f42fba8023918afa931413aded705b560e29600ccf7c6d76" -dependencies = [ - "fnv", - "futures", -] - -[[package]] -name = "tokio-tcp" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d14b10654be682ac43efee27401d792507e30fd8d26389e1da3b185de2e4119" -dependencies = [ - "bytes", - "futures", - "iovec", - "mio", - "tokio-io", - "tokio-reactor", -] - -[[package]] -name = "tokio-threadpool" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bd2c6a3885302581f4401c82af70d792bb9df1700e7437b0aeb4ada94d5388c" -dependencies = [ - "crossbeam-deque", - "crossbeam-queue", - "crossbeam-utils 0.6.6", - "futures", - "lazy_static", - "log", - "num_cpus", - "slab", - "tokio-executor", -] - -[[package]] -name = "tokio-timer" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2106812d500ed25a4f38235b9cae8f78a09edf43203e16e59c3b769a342a60e" -dependencies = [ - "crossbeam-utils 0.6.6", - "futures", - "slab", - "tokio-executor", -] - -[[package]] -name = "toml" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f" -dependencies = [ - "serde", -] - -[[package]] -name = "try-lock" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" - -[[package]] -name = "try_from" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "283d3b89e1368717881a9d51dad843cc435380d8109c9e47d38780a324698d8b" -dependencies = [ - "cfg-if 0.1.10", -] - -[[package]] -name = "typenum" -version = "1.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9" - -[[package]] -name = "ucd-trie" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f00ed7be0c1ff1e24f46c3d2af4859f7e863672ba3a6e92e7cff702bf9f06c2" - -[[package]] -name = "unic-char-property" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" -dependencies = [ - "unic-char-range", -] - -[[package]] -name = "unic-char-range" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" - -[[package]] -name = "unic-common" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" - -[[package]] -name = "unic-segment" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23" -dependencies = [ - "unic-ucd-segment", -] - -[[package]] -name = "unic-ucd-segment" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700" -dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", -] - -[[package]] -name = "unic-ucd-version" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" -dependencies = [ - "unic-common", -] - -[[package]] -name = "unicase" -version = "2.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e2e6bd1e59e56598518beb94fd6db628ded570326f0a98c679a304bd9f00150" -dependencies = [ - "version_check", -] - -[[package]] -name = "unicode-bidi" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" -dependencies = [ - "matches", -] - -[[package]] -name = "unicode-normalization" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "141339a08b982d942be2ca06ff8b076563cbe223d1befd5450716790d44e2426" -dependencies = [ - "smallvec", -] - -[[package]] -name = "unicode-segmentation" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1967f4cdfc355b37fd76d2a954fb2ed3871034eb4f26d60537d88795cfc332a9" - -[[package]] -name = "unicode-width" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7007dbd421b92cc6e28410fe7362e2e0a2503394908f417b68ec8d1c364c4e20" - -[[package]] -name = "unicode-xid" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" - -[[package]] -name = "untrusted" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60369ef7a31de49bcb3f6ca728d4ba7300d9a1658f94c727d4cab8c8d9f4aece" - -[[package]] -name = "url" -version = "1.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" -dependencies = [ - "idna 0.1.5", - "matches", - "percent-encoding 1.0.1", -] - -[[package]] -name = "url" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b414f6c464c879d7f9babf951f23bc3743fb7313c081b2e6ca719067ea9d61" -dependencies = [ - "idna 0.2.0", - "matches", - "percent-encoding 2.1.0", -] - -[[package]] -name = "uuid" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a" -dependencies = [ - "rand 0.6.5", -] - -[[package]] -name = "vec_map" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" - -[[package]] -name = "version_check" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" - -[[package]] -name = "walkdir" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" -dependencies = [ - "same-file", - "winapi 0.3.8", - "winapi-util", -] - -[[package]] -name = "want" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6395efa4784b027708f7451087e647ec73cc74f5d9bc2e418404248d679a230" -dependencies = [ - "futures", - "log", - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - -[[package]] -name = "wasm-bindgen" -version = "0.2.51" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd34c5ba0d228317ce388e87724633c57edca3e7531feb4e25e35aaa07a656af" -dependencies = [ - "cfg-if 0.1.10", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.51" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927196b315c23eed2748442ba675a4c54a1a079d90d9bdc5ad16ce31cf90b15b" -dependencies = [ - "bumpalo", - "lazy_static", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.51" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92c2442bf04d89792816650820c3fb407af8da987a9f10028d5317f5b04c2b4a" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.51" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c075d27b7991c68ca0f77fe628c3513e64f8c477d422b859e03f28751b46fc5" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.51" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83d61fe986a7af038dd8b5ec660e5849cbd9f38e7492b9404cc48b2b4df731d1" - -[[package]] -name = "wasm-bindgen-webidl" -version = "0.2.51" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b979afb0535fe4749906a674082db1211de8aef466331d43232f63accb7c07c" -dependencies = [ - "failure", - "heck", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "weedle", -] - -[[package]] -name = "web-sys" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c84440699cd02ca23bed6f045ffb1497bc18a3c2628bd13e2093186faaaacf6b" -dependencies = [ - "failure", - "js-sys", - "sourcefile", - "wasm-bindgen", - "wasm-bindgen-webidl", -] - -[[package]] -name = "webpki" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7e664e770ac0110e2384769bcc59ed19e329d81f555916a6e072714957b81b4" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "webpki-roots" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a262ae37dd9d60f60dd473d1158f9fbebf110ba7b6a5051c8160460f6043718b" -dependencies = [ - "webpki", -] - -[[package]] -name = "weedle" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bb43f70885151e629e2a19ce9e50bd730fd436cfd4b666894c9ce4de9141164" -dependencies = [ - "nom", -] - -[[package]] -name = "winapi" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" - -[[package]] -name = "winapi" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-build" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ccfbf554c6ad11084fb7517daca16cfdcaccbdadba4fc336f032a8b12c2ad80" -dependencies = [ - "winapi 0.3.8", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "winreg" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" -dependencies = [ - "winapi 0.3.8", -] - -[[package]] -name = "ws2_32-sys" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" -dependencies = [ - "winapi 0.2.8", - "winapi-build", -] - -[[package]] -name = "xattr" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c" -dependencies = [ - "libc", -] diff --git a/util/exercise/Cargo.toml b/util/exercise/Cargo.toml deleted file mode 100644 index 0a7db15a2..000000000 --- a/util/exercise/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -[package] -name = "exercise" -version = "1.6.2" -description = "An utility for creating or updating the exercises on the Exercism Rust track" -edition = "2021" - -[dependencies] -clap = "2.32.0" -failure = "0.1.5" -failure_derive = "0.1.5" -flate2 = "1.0.7" -lazy_static = "1.2.0" -tar = "0.4.36" -tera = "1.0.2" -toml = "0.4.10" -uuid = { version = "0.7", features = ["v4"] } - -[dependencies.indexmap] -version = "1.3.0" -# To implement `Deserialize` on `IndexMap`. -features = ["serde-1"] - -[dependencies.reqwest] -version = "0.9.16" -default-features = false -features = ["rustls-tls"] - -[dependencies.serde] -version = "1.0.102" -features = ["derive"] - -[dependencies.serde_json] -version = "1.0.38" -features = ["preserve_order"] diff --git a/util/exercise/src/cmd/configure.rs b/util/exercise/src/cmd/configure.rs deleted file mode 100644 index a74845def..000000000 --- a/util/exercise/src/cmd/configure.rs +++ /dev/null @@ -1,332 +0,0 @@ -use crate::{self as exercise, errors::Result, get, get_mut, val_as}; -use failure::format_err; -use serde_json::{self, json, Value}; -use std::{ - fs, - io::{stdin, stdout, Write}, - path::Path, -}; -use uuid::Uuid; - -fn get_user_input(prompt: &str) -> Result { - print!("{}", prompt); - - let mut buffer = String::new(); - - stdout().flush()?; - stdin().read_line(&mut buffer)?; - - Ok(buffer.trim().to_string()) -} - -fn get_user_config(exercise_name: &str, config_content: &Value) -> Result { - let existing_config = get!(config_content, "exercises", as_array) - .iter() - .find(|exercise| exercise["slug"] == exercise_name); - - let uuid = if let Some(existing_config) = existing_config { - get!(existing_config, "uuid", as_str).to_string() - } else { - Uuid::new_v4().to_hyphenated().to_string() - }; - - let unlocked_by: Option = loop { - let default_value = if let Some(existing_config) = existing_config { - if let Value::String(s) = get!(existing_config, "unlocked_by") { - s - } else { - "null" - } - } else { - "hello-world" - }; - - let user_input = get_user_input(&format!( - "Exercise slug which unlocks this (blank for '{}'): ", - default_value - ))?; - - if user_input.is_empty() { - break Some(default_value.to_string()); - } else if user_input == "null" { - break None; - } else if !get!(config_content, "exercises", as_array) - .iter() - .any(|exercise| exercise["slug"] == user_input) - { - println!("{} is not an existing exercise slug", user_input); - continue; - } else { - break Some(user_input); - }; - }; - - let core = unlocked_by.is_none(); - - let difficulty = loop { - let unlocked_by_difficulty = match unlocked_by { - Some(ref unlocked_by) => { - let unlocked_by_exercise = get!(config_content, "exercises", as_array) - .iter() - .find(|exercise| exercise["slug"] == unlocked_by.as_str()) - .ok_or_else(|| format_err!("exercise '{}' not found in config", unlocked_by))?; - - get!(unlocked_by_exercise, "difficulty", as_u64) - } - - None => 1, - }; - - let available_difficulties: Vec = [1, 4, 7, 10] - .iter() - .skip_while(|&&difficulty| difficulty < unlocked_by_difficulty) - .cloned() - .collect(); - - let default_value = if let Some(existing_config) = existing_config { - get!(existing_config, "difficulty", as_u64) - } else { - *available_difficulties - .first() - .ok_or_else(|| format_err!("no available difficulties"))? - }; - - let user_input = get_user_input(&format!( - "Difficulty for this exercise {:?} (blank for {}): ", - available_difficulties, default_value - ))?; - - if user_input.is_empty() { - break default_value; - } else if let Ok(difficulty) = user_input.parse::() { - if !available_difficulties.contains(&difficulty) { - println!( - "Difficulty should be {:?}, not '{}'.", - available_difficulties, difficulty - ); - - continue; - } - - break difficulty; - } else { - println!("Difficulty should be a number, not '{}'.", user_input); - - continue; - } - }; - - let topics = loop { - let default_value = if let Some(existing_config) = existing_config { - use serde_json::Value::*; - use std::string; - match get!(existing_config, "topics") { - String(s) => vec![s.to_string()], - Array(topics_values) => { - let mut topics: Vec = Vec::with_capacity(topics_values.len()); - for topic in topics_values.iter() { - topics.push(val_as!(topic, as_str).to_string()) - } - topics - } - _ => { - return Err(exercise::errors::Error::SchemaTypeError { - file: "config.json".to_string(), - field: "topics".to_string(), - as_type: "array or string".to_string(), - }); - } - } - } else { - vec![exercise_name.to_string()] - }; - - let user_input = get_user_input(&format!( - "List of topics for this exercise, comma-separated (blank for {:?}): ", - default_value, - ))?; - - if user_input.is_empty() { - break default_value; - } - - let topics = user_input - .split(',') - .map(|topic| topic.trim().to_string()) - .filter(|topic| !topic.is_empty()) - .collect::>(); - - if topics.is_empty() { - println!("Must enter at least one topic"); - - continue; - } - - break topics; - }; - - Ok(json!({ - "slug": exercise_name, - "uuid": uuid, - "core": core, - "unlocked_by": unlocked_by, - "difficulty": difficulty, - "topics": topics - })) -} - -fn choose_exercise_insert_index( - exercise_name: &str, - exercises: &[Value], - difficulty: &Value, -) -> Result { - loop { - let mut exercises_with_similar_difficulty = Vec::with_capacity(exercises.len()); - for (index, exercise) in exercises - .iter() - .enumerate() - .filter(|(_, exercise)| exercise["difficulty"] == *difficulty) - { - exercises_with_similar_difficulty.push((index, get!(exercise, "slug", as_str))); - } - - let mut start_index = 0; - - let mut end_index = exercises_with_similar_difficulty.len() - 1; - - let insert_index = loop { - if start_index == end_index { - break start_index; - } - - let middle_index = start_index + ((end_index - start_index) / 2); - - let user_input = get_user_input(&format!( - "Is {} easier then {}? (y/N): ", - exercise_name, exercises_with_similar_difficulty[middle_index].1 - ))?; - - if user_input.to_lowercase().starts_with('y') { - end_index = middle_index; - } else { - start_index = middle_index + 1; - } - }; - - let insert_index = exercises_with_similar_difficulty[insert_index].0; - - let prompt = if insert_index == 0 { - format!( - "{} is the easiest exercise of difficulty {}.", - exercise_name, *difficulty - ) - } else if insert_index == exercises.len() - 1 { - format!( - "{} is the hardest exercise of difficulty {}.", - exercise_name, *difficulty - ) - } else { - format!( - "{} is placed between {} and {} exercises in difficulty.", - exercise_name, - get!(exercises[insert_index - 1], "slug", as_str), - get!(exercises[insert_index], "slug", as_str), - ) - }; - - let user_input = get_user_input(&format!( - "You have configured that {}.\nIs this correct? (y/N): ", - prompt - ))?; - - if user_input.to_lowercase().starts_with('y') { - break Ok(insert_index); - } - } -} - -fn insert_user_config( - exercise_name: &str, - config_content: &mut Value, - user_config: Value, -) -> Result<()> { - let exercises = get_mut!(config_content, "exercises", as_array_mut); - - let insert_index = - choose_exercise_insert_index(exercise_name, exercises, &user_config["difficulty"])?; - - exercises.insert(insert_index, user_config); - - Ok(()) -} - -fn update_existing_config( - exercise_name: &str, - config_content: &mut Value, - user_config: Value, -) -> Result<()> { - let exercises = get_mut!(config_content, "exercises", as_array_mut); - - let existing_exercise_index = exercises - .iter() - .position(|exercise| exercise["slug"] == exercise_name) - .ok_or_else(|| format_err!("exercise '{}' not found in config.json", exercise_name))?; - - let insert_index = - if exercises[existing_exercise_index]["difficulty"] == user_config["difficulty"] { - existing_exercise_index - } else { - choose_exercise_insert_index(exercise_name, &exercises, &user_config["difficulty"])? - }; - - exercises.remove(existing_exercise_index); - - exercises.insert(insert_index, user_config); - - Ok(()) -} - -pub fn configure_exercise(exercise_name: &str) -> Result<()> { - println!( - "Configuring config.json for the {} exercise.", - exercise_name - ); - - let config_path = Path::new(&*exercise::TRACK_ROOT).join("config.json"); - - let config_content_string = fs::read_to_string(&config_path)?; - - let mut config_content: Value = serde_json::from_str(&config_content_string)?; - - let config_exists = get!(config_content, "exercises", as_array) - .iter() - .any(|exercise| exercise["slug"] == exercise_name); - - let user_config: Value = loop { - let user_config = get_user_config(exercise_name, &config_content)?; - - let user_input = get_user_input(&format!( - "You have configured the {} exercise as follows:\n{}\nIs this correct? (y/N):", - exercise_name, - serde_json::to_string_pretty(&user_config)? - ))?; - - if user_input.to_lowercase().starts_with('y') { - break user_config; - } - }; - - if config_exists { - update_existing_config(exercise_name, &mut config_content, user_config)?; - } else { - insert_user_config(exercise_name, &mut config_content, user_config)?; - } - - fs::write(&config_path, serde_json::to_string_pretty(&config_content)?)?; - - println!("Formatting the config.json file via 'bin/configlet fmt'"); - - exercise::run_configlet_command("fmt", &["."])?; - - Ok(()) -} diff --git a/util/exercise/src/cmd/defaults/README.md b/util/exercise/src/cmd/defaults/README.md deleted file mode 100644 index 2ff7d26cb..000000000 --- a/util/exercise/src/cmd/defaults/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Defaults - -The files in this directory are used in appropriate places for defaults. They -are included at build-time in the compiled executable: if you change one, you -will need to recompile to see those changes reflected in the output. - -Using external files makes it easier to ensure that formatting etc is correct, -without needing to worry about proper escaping within a Rust source file. diff --git a/util/exercise/src/cmd/defaults/description.md b/util/exercise/src/cmd/defaults/description.md deleted file mode 100644 index 6986282dc..000000000 --- a/util/exercise/src/cmd/defaults/description.md +++ /dev/null @@ -1,6 +0,0 @@ -# Description - -Describe your exercise here. - -Don't forget that `README.md` is automatically generated; update this within -`.meta/description.md`. diff --git a/util/exercise/src/cmd/defaults/example.rs b/util/exercise/src/cmd/defaults/example.rs deleted file mode 100644 index 768f0813a..000000000 --- a/util/exercise/src/cmd/defaults/example.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! Example implementation -//! -//! - Implement the solution to your exercise here. -//! - Put the stubs for any tested functions in `src/lib.rs`, -//! whose variable names are `_` and -//! whose contents are `unimplemented!()`. -//! - If your example implementation has dependencies, copy -//! `Cargo.toml` into `Cargo-example.toml` and then make -//! any modifications necessary to the latter so your example will run. -//! - Test your example by running `../../bin/test-exercise` diff --git a/util/exercise/src/cmd/defaults/gitignore b/util/exercise/src/cmd/defaults/gitignore deleted file mode 100644 index e130ceb2d..000000000 --- a/util/exercise/src/cmd/defaults/gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Generated by exercism rust track exercise tool -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock diff --git a/util/exercise/src/cmd/defaults/metadata.yml b/util/exercise/src/cmd/defaults/metadata.yml deleted file mode 100644 index c44a80f49..000000000 --- a/util/exercise/src/cmd/defaults/metadata.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -blurb: "" -source: "" -source_url: "" diff --git a/util/exercise/src/cmd/fetch_configlet.rs b/util/exercise/src/cmd/fetch_configlet.rs deleted file mode 100644 index 8351c9232..000000000 --- a/util/exercise/src/cmd/fetch_configlet.rs +++ /dev/null @@ -1,54 +0,0 @@ -use crate::errors::Result; -use failure::format_err; -use flate2::read::GzDecoder; -use serde_json::Value; -use std::{ - path::{Path, PathBuf}, - string::ToString, -}; -use tar::Archive; - -// Returns the "os-arch" string, according to the host machine parameters -fn get_os_arch() -> String { - let os = if cfg!(target_os = "windows") { - "windows" - } else if cfg!(target_os = "macos") { - "mac" - } else { - "linux" - }; - let arch = if cfg!(target_pointer_width = "32") { - "32bit" - } else { - "64bit" - }; - format!("{}-{}", os, arch) -} - -// Makes a request to the Github API to get and return the download url for the latest configlet release -fn get_download_url() -> Result { - reqwest::get("https://api.github.com/repos/exercism/configlet/releases/latest")? - .json::()? - .get("assets") - .and_then(Value::as_array) - .ok_or_else(|| format_err!("failed to get the 'assets' field from the Github response"))? - .iter() - .filter_map(|asset| asset.get("browser_download_url").and_then(Value::as_str)) - .find(|url| url.contains(&get_os_arch())) - .map(ToString::to_string) - .ok_or_else(|| format_err!("failed to get the configlet release url")) - .map_err(|e| e.into()) -} - -// download and extract configlet into the repo's /bin folder -// -// returns the path into which the bin was extracted on success -pub fn download() -> Result { - let response = reqwest::get(&get_download_url()?)?; - let mut archive = Archive::new(GzDecoder::new(response)); - let download_path = Path::new(&*crate::TRACK_ROOT).join("bin"); - archive - .unpack(&download_path) - .map(|_| download_path) - .map_err(|e| e.into()) -} diff --git a/util/exercise/src/cmd/generate.rs b/util/exercise/src/cmd/generate.rs deleted file mode 100644 index 8810b05ab..000000000 --- a/util/exercise/src/cmd/generate.rs +++ /dev/null @@ -1,198 +0,0 @@ -/// This module contains source for the `generate` command. -use crate::{self as exercise, errors::Result, structs::CanonicalData, TEMPLATES}; -use failure::format_err; -use std::{ - fs::{self, File, OpenOptions}, - io::Write, - path::Path, - process::{Command, Stdio}, -}; -use tera::Context; - -const GITIGNORE_CONTENT: &str = include_str!("defaults/gitignore"); -const EXAMPLE_RS_CONTENT: &str = include_str!("defaults/example.rs"); -const DESCRIPTION_MD_CONTENT: &str = include_str!("defaults/description.md"); -const METADATA_YML_CONTENT: &str = include_str!("defaults/metadata.yml"); - -// Generate .meta directory and its contents without using the canonical data -fn generate_meta(exercise_name: &str, exercise_path: &Path) -> Result<()> { - let meta_dir = exercise_path.join(".meta"); - fs::create_dir(&meta_dir)?; - - for (file, content) in [ - ("description.md", DESCRIPTION_MD_CONTENT), - ("metadata.yml", METADATA_YML_CONTENT), - ] - .iter() - { - if !exercise::canonical_file_exists(exercise_name, file)? { - fs::write(exercise_path.join(".meta").join(file), content)?; - } - } - - if fs::read_dir(&meta_dir)?.count() == 0 { - fs::remove_dir(meta_dir)?; - } - - Ok(()) -} - -// Generate test suite using the canonical data -fn generate_tests_from_canonical_data( - exercise_name: &str, - tests_path: &Path, - canonical_data: &CanonicalData, - use_maplit: bool, -) -> Result<()> { - exercise::update_cargo_toml_version(exercise_name, canonical_data)?; - - let mut context = Context::from_serialize(canonical_data)?; - context.insert("use_maplit", &use_maplit); - context.insert("properties", &canonical_data.properties()); - - let test_file = TEMPLATES.render("test_file.rs", &context)?; - fs::write(&tests_path, test_file)?; - - exercise::rustfmt(&tests_path)?; - - Ok(()) -} - -// Run bin/configlet generate command to generate README for the exercise -fn generate_readme(exercise_name: &str) -> Result<()> { - println!( - "Generating README for {} via 'bin/configlet generate'", - exercise_name - ); - - let problem_specifications_path = Path::new(&*exercise::TRACK_ROOT) - .join("..") - .join("problem-specifications"); - - if !problem_specifications_path.exists() { - let problem_specifications_url = "https://github.com/exercism/problem-specifications.git"; - println!( - "problem-specifications repository not found. Cloning the repository from {}", - problem_specifications_url - ); - - Command::new("git") - .current_dir(&*exercise::TRACK_ROOT) - .stdout(Stdio::inherit()) - .arg("clone") - .arg(problem_specifications_url) - .arg(&problem_specifications_path) - .output()?; - } - - exercise::run_configlet_command( - "generate", - &[ - ".", - "--only", - exercise_name, - "--spec-path", - problem_specifications_path - .to_str() - .ok_or_else(|| format_err!("path inexpressable as str"))?, - ], - )?; - - Ok(()) -} - -// Generate a new exercise with specified name and flags -pub fn generate_exercise(exercise_name: &str, use_maplit: bool) -> Result<()> { - if exercise::exercise_exists(exercise_name) { - return Err(format_err!("exercise with the name {} already exists", exercise_name,).into()); - } - - let exercise_path = Path::new(&*exercise::TRACK_ROOT) - .join("exercises") - .join(exercise_name); - - println!( - "Generating a new exercise at path: {}", - exercise_path - .to_str() - .ok_or_else(|| format_err!("path inexpressable as str"))? - ); - - let _cargo_new_output = Command::new("cargo") - .arg("new") - .arg("--lib") - .arg( - exercise_path - .to_str() - .ok_or_else(|| format_err!("path inexpressable as str"))?, - ) - .output()?; - - fs::write(exercise_path.join(".gitignore"), GITIGNORE_CONTENT)?; - - if use_maplit { - let mut cargo_toml_file = OpenOptions::new() - .append(true) - .open(exercise_path.join("Cargo.toml"))?; - - cargo_toml_file.write_all(b"maplit = \"1.0.1\"")?; - } - - fs::create_dir(exercise_path.join("tests"))?; - - let test_file_name = exercise_path - .join("tests") - .join(format!("{}.rs", exercise_name)); - - fs::write(exercise_path.join("example.rs"), EXAMPLE_RS_CONTENT)?; - - match exercise::get_canonical_data(exercise_name) { - Ok(canonical_data) => { - println!("Generating tests from canonical data"); - - generate_tests_from_canonical_data( - &exercise_name, - &test_file_name, - &canonical_data, - use_maplit, - )?; - } - Err(_) => { - println!( - "No canonical data for exercise '{}' found. Generating standard exercise template.", - &exercise_name - ); - - let mut test_file = File::create(test_file_name)?; - - test_file.write_all( - &format!( - "//! Tests for {exercise_name} \n\ - //! \n\ - //! Generated by [utility][utility]\n\ - //! \n\ - //! [utility]: https://github.com/exercism/rust/tree/main/util/exercise\n\ - \n", - exercise_name = exercise_name, - ) - .into_bytes(), - )?; - if use_maplit { - test_file.write_all(b"use maplit::hashmap;\n")?; - } - - test_file.write_all( - &format!( - "use {escaped_exercise_name}::*;\n\n\n", - escaped_exercise_name = exercise_name.replace("-", "_"), - ) - .into_bytes(), - )?; - } - } - - generate_meta(&exercise_name, &exercise_path)?; - generate_readme(&exercise_name)?; - - Ok(()) -} diff --git a/util/exercise/src/cmd/mod.rs b/util/exercise/src/cmd/mod.rs deleted file mode 100644 index 84607911c..000000000 --- a/util/exercise/src/cmd/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod configure; -pub mod fetch_configlet; -pub mod generate; -pub mod update; diff --git a/util/exercise/src/cmd/templates/macros.rs b/util/exercise/src/cmd/templates/macros.rs deleted file mode 100644 index c7a41b7bb..000000000 --- a/util/exercise/src/cmd/templates/macros.rs +++ /dev/null @@ -1,46 +0,0 @@ -{% macro to_literal(value, use_maplit=false) -%} - {% if value is object -%} - {% if use_maplit -%} - hashmap! { - {% for k, v in value -%} - {{ self::to_literal(value=k, use_maplit=use_maplit) }} => - {{ self::to_literal(value=v, use_maplit=use_maplit) }}, - {% endfor -%} - } - {% else -%} - { - let mut hm = ::std::collections::HashMap::new(); - {% for k, v in value -%} - hm.insert( - {{ self::to_literal(value=k, use_maplit=use_maplit) }}, - {{ self::to_literal(value=v, use_maplit=use_maplit) }} - ); - {% endfor -%} - hm - } - {% endif -%} - {% elif value is iterable -%} - vec![ - {% for element in value -%} - {{ self::to_literal(value=element, use_maplit=use_maplit) }}, - {% endfor -%} - ] - {% elif value is string -%} - "{{ value }}" - {% elif value is number -%} - {{ value }} - {% else -%} - None - {% endif -%} -{% endmacro -%} - -{% macro gen_test_fn(case, first_test_case=false) -%} - {# Need to set up the variables for the template. #} - {% set description = case.description -%} - {% set comments = case.comments -%} - {% set property = case.property -%} - {% set input = case.input -%} - {% set expected = case.expected -%} - - {% include "test_fn.rs" %} -{% endmacro generate_test_fn -%} \ No newline at end of file diff --git a/util/exercise/src/cmd/templates/property_fn.rs b/util/exercise/src/cmd/templates/property_fn.rs deleted file mode 100644 index 1bac5233e..000000000 --- a/util/exercise/src/cmd/templates/property_fn.rs +++ /dev/null @@ -1,18 +0,0 @@ -/// Process a single test case for the property `{{ property }}` -/// -/// All cases for the `{{ property }}` property are implemented in terms of -/// this function. -/// -/// Note that you'll need to both name the expected transform which the student -/// needs to write, and name the types of the inputs and outputs. -/// While rustc _may_ be able to handle things properly given a working example, -/// students will face confusing errors if the `I` and `O` types are not -/// concrete. -fn process_{{ format_property(property=property) }}_case(input: I, expected: O) { - // typical implementation: - // assert_eq!( - // student_{{ format_property(property=property) }}_func(input), - // expected, - // ) - unimplemented!() -} diff --git a/util/exercise/src/cmd/templates/test_file.rs b/util/exercise/src/cmd/templates/test_file.rs deleted file mode 100644 index e4c7ecb84..000000000 --- a/util/exercise/src/cmd/templates/test_file.rs +++ /dev/null @@ -1,49 +0,0 @@ -{% import "macros.rs" as macros -%} - -//! Tests for {{ exercise }} -//! -//! Generated by [utility][utility] using [canonical data][canonical_data] -//! -//! [utility]: https://github.com/exercism/rust/tree/main/util/exercise -//! [canonical_data]: https://raw.githubusercontent.com/exercism/problem-specifications/main/exercises/{{ exercise }}/canonical-data.json - -{% for comment in comments -%} -/// {{ comment }} -{% endfor %} - -{% if use_maplit -%} -use maplit::hashmap; -{% endif %} - -{% for property in properties | sort -%} -{% include "property_fn.rs" %} -{% endfor -%} - -{# Don't ignore the first case. -#} -{% set first_test_case = true -%} - -{% for item in cases -%} - {# Check if we're dealing with a group of cases. #} - {% if item.cases -%} - /// {{ item.description }} - {% if item.optional -%} - /// {{ item.optional }} - {% endif -%} - - {% if item.comments -%} - {% for comment in item.comments -%} - /// {{ comment }} - {% endfor -%} - {% endif -%} - - {% for case in item.cases -%} - {{ macros::gen_test_fn(case=case, first_test_case=first_test_case) }} - {% set_global first_test_case = false -%} - {% endfor -%} - - {# Or just a single one. #} - {% else -%} - {{ macros::gen_test_fn(case=item, first_test_case=first_test_case) }} - {% set_global first_test_case = false -%} - {% endif -%} -{% endfor -%} diff --git a/util/exercise/src/cmd/templates/test_fn.rs b/util/exercise/src/cmd/templates/test_fn.rs deleted file mode 100644 index 3232bf4eb..000000000 --- a/util/exercise/src/cmd/templates/test_fn.rs +++ /dev/null @@ -1,19 +0,0 @@ -{% import "macros.rs" as macros -%} - -#[test] -{% if not first_test_case -%} -#[ignore] -{% endif -%} -/// {{ description }} -{% if comments -%} - /// - {% for comment in comments %} - /// {{ comment }} - {% endfor %} -{% endif -%} -fn test_{{ format_description(description=description) }}() { - process_{{ format_property(property=property) }}_case( - {{ macros::to_literal(value=input, use_maplit=use_maplit) }}, - {{ macros::to_literal(value=expected, use_maplit=use_maplit) }} - ); -} diff --git a/util/exercise/src/cmd/update.rs b/util/exercise/src/cmd/update.rs deleted file mode 100644 index f1c1a28b2..000000000 --- a/util/exercise/src/cmd/update.rs +++ /dev/null @@ -1,134 +0,0 @@ -use crate::{ - self as exercise, - errors::Result, - structs::{LabeledTest, LabeledTestItem}, -}; -use failure::format_err; -use std::{collections::HashSet, fs, path::Path}; - -enum DiffType { - NEW, - UPDATED, -} - -fn generate_diff_test( - case: &LabeledTest, - diff_type: &DiffType, - use_maplit: bool, -) -> Result { - Ok(format!( - "//{}\n{}", - match diff_type { - DiffType::NEW => "NEW", - DiffType::UPDATED => "UPDATED", - }, - exercise::generate_test_function(case, use_maplit)? - )) -} - -fn generate_diff_property(property: &str) -> Result { - Ok(format!( - "//{}\n{}", - "NEW", - exercise::generate_property_body(property)? - )) -} - -fn generate_diffs( - case: &LabeledTest, - tests_content: &str, - diffs: &mut HashSet, - use_maplit: bool, -) -> Result<()> { - let description = &case.description; - let description_formatted = exercise::format_exercise_description(description); - - let diff_type = if !tests_content.contains(&format!("test_{}", description_formatted)) { - DiffType::NEW - } else { - DiffType::UPDATED - }; - - if diffs.insert(generate_diff_test(case, &diff_type, use_maplit)?) { - match diff_type { - DiffType::NEW => println!("New test case detected: {}.", description_formatted), - DiffType::UPDATED => println!("Updated test case: {}.", description_formatted), - } - } - - let property = &case.property; - let property_formatted = exercise::format_exercise_property(property); - - if !tests_content.contains(&format!("process_{}_case", property_formatted)) - && diffs.insert(generate_diff_property(property)?) - { - println!("New property detected: {}.", property); - } - - Ok(()) -} - -fn get_diffs( - case: &LabeledTestItem, - diffs: &mut HashSet, - tests_content: &str, - use_maplit: bool, -) -> Result<()> { - match case { - LabeledTestItem::Single(case) => generate_diffs(case, &tests_content, diffs, use_maplit)?, - LabeledTestItem::Array(group) => { - for case in &group.cases { - get_diffs(case, diffs, tests_content, use_maplit)?; - } - } - } - - Ok(()) -} - -fn apply_diffs(exercise_name: &str, diffs: &HashSet, tests_content: &str) -> Result<()> { - let updated_tests_content = format!( - "{}\n{}", - tests_content, - diffs - .iter() - .map(|diff| format!("\n{}", diff)) - .collect::() - ); - - let tests_path = Path::new(&*exercise::TRACK_ROOT) - .join("exercises") - .join(exercise_name) - .join("tests") - .join(format!("{}.rs", exercise_name)); - - fs::write(&tests_path, updated_tests_content.as_bytes())?; - - exercise::rustfmt(&tests_path)?; - - Ok(()) -} - -pub fn update_exercise(exercise_name: &str, use_maplit: bool) -> Result<()> { - if !exercise::exercise_exists(exercise_name) { - return Err( - format_err!("exercise with the name '{}' does not exist", exercise_name).into(), - ); - } - - let tests_content = exercise::get_tests_content(exercise_name)?; - - let canonical_data = exercise::get_canonical_data(exercise_name)?; - - let mut diffs: HashSet = HashSet::new(); - - for case in &canonical_data.cases { - get_diffs(case, &mut diffs, &tests_content, use_maplit)?; - } - - apply_diffs(exercise_name, &diffs, &tests_content)?; - - exercise::update_cargo_toml_version(exercise_name, &canonical_data)?; - - Ok(()) -} diff --git a/util/exercise/src/errors.rs b/util/exercise/src/errors.rs deleted file mode 100644 index a0fb6d125..000000000 --- a/util/exercise/src/errors.rs +++ /dev/null @@ -1,76 +0,0 @@ -use failure; -use reqwest; -use serde_json; -use std::{convert::From, io, result}; -use tera; -use toml; - -#[derive(Debug, failure::Fail)] -pub enum Error { - #[fail(display = "IO error: {}", _0)] - IoError(#[cause] io::Error), - #[fail( - display = "config.json malformed: '{}' must have field '{}'", - parent, field - )] - ConfigJsonSchemaError { parent: String, field: String }, - #[fail( - display = "{} malformed: field '{}' must have type '{}'", - file, field, as_type - )] - SchemaTypeError { - file: String, - field: String, - as_type: String, - }, - #[fail(display = "json error: {}", _0)] - JsonError(#[cause] serde_json::Error), - #[fail(display = "config.toml parse error: {}", _0)] - ConfigTomlParseError(#[cause] toml::de::Error), - #[fail(display = "HTTP error: {}", _0)] - HTTPError(reqwest::Error), - #[fail(display = "Tera rendering error: {}", _0)] - TeraError(tera::Error), - #[fail(display = "{}", _0)] - Failure(#[cause] failure::Error), - #[fail(display = "Unknown Failure: {}", _0)] - UnknownError(String), -} - -impl From for Error { - fn from(err: io::Error) -> Self { - Error::IoError(err) - } -} - -impl From for Error { - fn from(err: toml::de::Error) -> Self { - Error::ConfigTomlParseError(err) - } -} - -impl From for Error { - fn from(err: reqwest::Error) -> Self { - Error::HTTPError(err) - } -} - -impl From for Error { - fn from(err: failure::Error) -> Self { - Error::Failure(err) - } -} - -impl From for Error { - fn from(err: serde_json::Error) -> Self { - Error::JsonError(err) - } -} - -impl From for Error { - fn from(err: tera::Error) -> Self { - Error::TeraError(err) - } -} - -pub type Result = result::Result; diff --git a/util/exercise/src/lib.rs b/util/exercise/src/lib.rs deleted file mode 100644 index d7609a39e..000000000 --- a/util/exercise/src/lib.rs +++ /dev/null @@ -1,282 +0,0 @@ -pub mod cmd; -pub mod errors; -pub mod structs; - -use errors::Result; -use failure::format_err; -use lazy_static::lazy_static; -use reqwest; -use serde_json::Value; -use std::{ - collections::HashMap, - env, fs, io, - path::Path, - process::{Command, Stdio}, -}; -use structs::{CanonicalData, LabeledTest}; -use tera::{Context, Tera}; -use toml; -use toml::Value as TomlValue; - -// we look for the track root in various places, but it's never going to change -// we therefore cache the value for efficiency -lazy_static! { - pub static ref TRACK_ROOT: String = { - let rev_parse_output = Command::new("git") - .arg("rev-parse") - .arg("--show-toplevel") - .output() - .expect("Failed to get the path to the track repo."); - - String::from_utf8(rev_parse_output.stdout) - .expect("git rev-parse produced non-utf8 output") - .trim() - .to_string() - }; -} - -// Create a static `Tera` struct so we can access the templates from anywhere. -lazy_static! { - pub static ref TEMPLATES: Tera = { - let templates = Path::new(&*TRACK_ROOT) - .join("util") - .join("exercise") - .join("src") - .join("cmd") - .join("templates") - .join("**") - .join("*.rs"); - - // Since `TRACK_ROOT` already checks for UTF-8 and nothing added is not - // UTF-8, unwrapping is fine. - let mut tera = match Tera::new(templates.to_str().unwrap()) { - Ok(t) => t, - Err(e) => { - println!("Parsing error(s): {}", e); - ::std::process::exit(1); - } - }; - - // Build wrappers around the formatting functions. - let format_description = |args: &HashMap| - args.get("description") - .and_then(Value::as_str) - .map(format_exercise_description) - .map(Value::from) - .ok_or(tera::Error::from("Problem formatting the description.")) - ; - - let format_property = |args: &HashMap| - args.get("property") - .and_then(Value::as_str) - .map(format_exercise_property) - .map(Value::from) - .ok_or(tera::Error::from("Problem formatting the property.")) - ; - - tera.register_function("format_description", format_description); - tera.register_function("format_property", format_property); - tera - }; -} - -#[macro_export] -macro_rules! val_as { - ($value:expr, $as:ident) => { - $value - .$as() - .ok_or_else(|| $crate::errors::Error::SchemaTypeError { - file: "config.json".to_string(), - field: stringify!($name).to_string(), - as_type: stringify!($as)[3..].to_string(), - })? - }; -} - -#[macro_export] -macro_rules! get { - ($value:expr, $name:expr) => { - $value - .get($name) - .ok_or_else(|| $crate::errors::Error::ConfigJsonSchemaError { - parent: stringify!($value).to_string(), - field: stringify!($name).to_string(), - })? - }; - ($value:expr, $name:expr, $as:ident) => { - val_as!(get!($value, $name), $as) - }; -} - -#[macro_export] -macro_rules! get_mut { - ($value:expr, $name:expr) => { - $value - .get_mut($name) - .ok_or_else(|| $crate::errors::Error::ConfigJsonSchemaError { - parent: stringify!($value).to_string(), - field: stringify!($name).to_string(), - })? - }; - ($value:expr, $name:expr, $as:ident) => { - val_as!(get_mut!($value, $name), $as) - }; -} - -pub fn run_configlet_command(command: &str, args: &[&str]) -> Result<()> { - let track_root = &*TRACK_ROOT; - - let bin_path = Path::new(track_root).join("bin"); - - let configlet_name_unix = "configlet"; - - let configlet_name_windows = "configlet.exe"; - - let configlet_name = if bin_path.join(configlet_name_unix).exists() { - configlet_name_unix - } else if bin_path.join(configlet_name_windows).exists() { - configlet_name_windows - } else { - println!("Configlet not found in the bin directory. Running bin/fetch-configlet."); - - let bin_path = crate::cmd::fetch_configlet::download()?; - - if bin_path.join(configlet_name_unix).exists() { - configlet_name_unix - } else if bin_path.join(configlet_name_windows).exists() { - configlet_name_windows - } else { - return Err(format_err!( - "could not locate configlet after running bin/fetch-configlet" - ) - .into()); - } - }; - - Command::new(&bin_path.join(configlet_name)) - .current_dir(track_root) - .stdout(Stdio::inherit()) - .arg(command) - .args(args) - .output()?; - - Ok(()) -} - -fn url_for(exercise: &str, file: &str) -> String { - format!( - "https://raw.githubusercontent.com/exercism/problem-specifications/main/exercises/%7B%7D/%7B%7D", - exercise, file, - ) -} - -fn get_canonical(exercise: &str, file: &str) -> Result { - reqwest::get(&url_for(exercise, file)).map_err(|e| e.into()) -} - -// Try to get the canonical data for the exercise of the given name -pub fn get_canonical_data(exercise_name: &str) -> Result { - let mut response = get_canonical(exercise_name, "canonical-data.json")?.error_for_status()?; - response.json().map_err(|e| e.into()) -} - -pub fn canonical_file_exists(exercise: &str, file: &str) -> Result { - Ok(get_canonical(exercise, file)?.status().is_success()) -} - -pub fn get_tests_content(exercise_name: &str) -> io::Result { - let tests_path = Path::new(&*TRACK_ROOT) - .join("exercises") - .join(exercise_name) - .join("tests") - .join(format!("{}.rs", exercise_name)); - - fs::read_to_string(tests_path) -} - -pub fn format_exercise_description(description: &str) -> String { - description - .chars() - .filter(|c| c.is_alphanumeric() || *c == ' ') - .collect::() - .replace(" ", "_") - .to_lowercase() -} - -pub fn format_exercise_property(property: &str) -> String { - property.replace(" ", "_").to_lowercase() -} - -pub fn generate_property_body(property: &str) -> Result { - let mut context = Context::new(); - context.insert("property", property); - TEMPLATES - .render("property_fn.rs", &context) - .map_err(|e| e.into()) -} - -pub fn generate_test_function(case: &LabeledTest, use_maplit: bool) -> Result { - let mut context = Context::from_serialize(case)?; - context.insert("use_maplit", &use_maplit); - TEMPLATES - .render("test_fn.rs", &context) - .map_err(|e| e.into()) -} - -pub fn rustfmt(file_path: &Path) -> Result<()> { - let rustfmt_is_available = { - if let Some(path_var) = env::var_os("PATH") { - env::split_paths(&path_var).any(|path| path.join("rustfmt").exists()) - } else { - false - } - }; - - if rustfmt_is_available { - Command::new("rustfmt").arg(file_path).output()?; - } - - Ok(()) -} - -pub fn exercise_exists(exercise_name: &str) -> bool { - Path::new(&*TRACK_ROOT) - .join("exercises") - .join(exercise_name) - .exists() -} - -// Update the version of the specified exercise in the Cargo.toml file according to the passed canonical data -pub fn update_cargo_toml_version( - exercise_name: &str, - canonical_data: &CanonicalData, -) -> Result<()> { - let cargo_toml_path = Path::new(&*TRACK_ROOT) - .join("exercises") - .join(exercise_name) - .join("Cargo.toml"); - - let cargo_toml_content = fs::read_to_string(&cargo_toml_path)?; - - let mut cargo_toml: TomlValue = cargo_toml_content.parse()?; - - { - let package_table = - cargo_toml["package"] - .as_table_mut() - .ok_or_else(|| errors::Error::SchemaTypeError { - file: "Config.toml".to_string(), - field: "package".to_string(), - as_type: "table".to_string(), - })?; - - package_table.insert( - "version".to_string(), - TomlValue::String(canonical_data.version.to_string()), - ); - } - - fs::write(&cargo_toml_path, cargo_toml.to_string())?; - - Ok(()) -} diff --git a/util/exercise/src/main.rs b/util/exercise/src/main.rs deleted file mode 100644 index ea9f1fa7a..000000000 --- a/util/exercise/src/main.rs +++ /dev/null @@ -1,119 +0,0 @@ -use clap::{App, Arg, ArgMatches, SubCommand}; -use exercise::{ - cmd::{configure, fetch_configlet, generate, update}, - errors::Result, -}; -use failure::format_err; - -// Creates a new CLI app with appropriate matches -// and returns the initialized matches. -fn init_app<'a>() -> ArgMatches<'a> { - App::new(env!("CARGO_PKG_NAME")) - .version(env!("CARGO_PKG_VERSION")) - .about(env!("CARGO_PKG_DESCRIPTION")) - .subcommand( - SubCommand::with_name("generate") - .about("Generates new exercise") - .arg(Arg::with_name("exercise_name").required(true).help("The name of the generated exercise")) - .arg(Arg::with_name("configure").long("configure").short("c").help( - "If set, the command will edit the config.json file after generating the exercise", - )) - .arg(Arg::with_name("use_maplit").long("use-maplit").short("m").help("Use the maplit crate to improve the readability of the generated test suite")), - ) - .subcommand( - SubCommand::with_name("update") - .about("Updates the specified exercise") - .arg(Arg::with_name("exercise_name").help("The name of the updated exercise")) - .arg(Arg::with_name("use_maplit").long("use-maplit").short("m").help("Use the maplit crate to improve the readability of the updated test suite")) - .arg(Arg::with_name("dont_update_readme").long("dont-update-readme").short("r").help("If set, the README of the exercise would not be updated")) - .arg(Arg::with_name("configure").long("configure").short("c").help( - "If set, the command will edit the config.json file after updating the exercise", - )) - ) - .subcommand( - SubCommand::with_name("configure") - .about("Edits config.json for the specified exercise") - .arg(Arg::with_name("exercise_name").required(true).help("The name of the configured exercise")), - ) - .subcommand( - SubCommand::with_name("fetch_configlet") - .about("Downloads and extracts configlet utility into the repo's /bin directory") - ) - .get_matches() -} - -// Determine which subcommand was used -// and call the appropriate function. -fn process_matches(matches: &ArgMatches<'_>) -> Result<()> { - match matches.subcommand() { - ("generate", Some(generate_matches)) => { - let exercise_name = generate_matches - .value_of("exercise_name") - .ok_or_else(|| format_err!("exercise name not present in args"))?; - let run_configure = generate_matches.is_present("configure"); - let use_maplit = generate_matches.is_present("use_maplit"); - - generate::generate_exercise(exercise_name, use_maplit)?; - - if run_configure { - configure::configure_exercise(exercise_name)?; - } - } - - ("update", Some(update_matches)) => { - let exercise_name = update_matches - .value_of("exercise_name") - .ok_or_else(|| format_err!("exercise name not present in args"))?; - - let run_configure = update_matches.is_present("configure"); - - let use_maplit = update_matches.is_present("use_maplit"); - - let update_readme = !update_matches.is_present("dont_update_readme"); - - update::update_exercise(exercise_name, use_maplit)?; - - if run_configure { - configure::configure_exercise(exercise_name)?; - } - - if update_readme { - exercise::run_configlet_command("generate", &[".", "-o", exercise_name])?; - } - } - - ("configure", Some(configure_matches)) => { - configure::configure_exercise( - configure_matches - .value_of("exercise_name") - .ok_or_else(|| format_err!("exercise name not present in args"))?, - )?; - } - - ("fetch_configlet", Some(_fetch_configlet_matches)) => { - if let Ok(fetch_path) = fetch_configlet::download() { - println!( - "Downloaded and moved the configlet utility to the {:?} path", - fetch_path - ); - } else { - println!("Failed to fetch the configlet utility"); - } - } - - ("", None) => { - println!("No subcommand was used.\nUse exercise --help to learn about the possible subcommands."); - } - - _ => unreachable!(), - }; - - Ok(()) -} - -fn main() -> Result<()> { - let matches = init_app(); - - process_matches(&matches)?; - Ok(()) -} diff --git a/util/exercise/src/structs.rs b/util/exercise/src/structs.rs deleted file mode 100644 index dd4e4bd5c..000000000 --- a/util/exercise/src/structs.rs +++ /dev/null @@ -1,84 +0,0 @@ -//! Rust struct for canonical test data. -//! -//! See https://github.com/exercism/problem-specifications/blob/main/canonical-schema.json -//! for more details on the JSON schema, which makes it possible to implement -//! `serde::Deserialize`. - -use serde::{Deserialize, Serialize}; -use serde_json::Value; -use std::collections::HashSet; - -#[derive(Serialize, Deserialize, Debug)] -pub struct CanonicalData { - pub exercise: Exercise, - pub version: Version, - pub comments: Option, - pub cases: TestGroup, -} - -type Exercise = String; -type Version = String; -type Comments = Vec; -type TestGroup = Vec; - -#[derive(Serialize, Deserialize, Debug)] -#[serde(untagged)] -pub enum LabeledTestItem { - Single(LabeledTest), - Array(LabeledTestGroup), -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct LabeledTest { - pub description: Description, - pub optional: Option, - pub comments: Option, - pub property: Property, - pub input: Input, - pub expected: Expected, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct LabeledTestGroup { - pub description: Description, - pub optional: Option, - pub comments: Option, - pub cases: TestGroup, -} - -type Description = String; -type Optional = String; -type Property = String; -type Input = Value; -type Expected = Value; - -impl CanonicalData { - pub fn properties(&self) -> HashSet<&str> { - self.cases - .iter() - .flat_map(LabeledTestItem::iter) - .map(|case| case.property.as_str()) - .collect() - } -} - -impl LabeledTestItem { - fn iter(&self) -> Box + '_> { - match self { - LabeledTestItem::Single(case) => Box::new(case.iter()), - LabeledTestItem::Array(cases) => Box::new(cases.iter()), - } - } -} - -impl LabeledTest { - fn iter(&self) -> impl Iterator { - std::iter::once(self) - } -} - -impl LabeledTestGroup { - fn iter(&self) -> impl Iterator { - self.cases.iter().flat_map(LabeledTestItem::iter) - } -} diff --git a/util/ngram/Cargo.lock b/util/ngram/Cargo.lock new file mode 100644 index 000000000..b704c48b2 --- /dev/null +++ b/util/ngram/Cargo.lock @@ -0,0 +1,16 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ngram" +version = "0.1.0" +dependencies = [ + "ngrammatic", +] + +[[package]] +name = "ngrammatic" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c6f2f987e82da7fb8a290e959bba528638bc0e2629e38647591e845ecb5f6fe" diff --git a/util/ngram/Cargo.toml b/util/ngram/Cargo.toml new file mode 100644 index 000000000..5b48fcc38 --- /dev/null +++ b/util/ngram/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "ngram" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +ngrammatic = "0.4.0" diff --git a/util/ngram/build b/util/ngram/build new file mode 100755 index 000000000..8e428879f --- /dev/null +++ b/util/ngram/build @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +cargo build --release --quiet && cp ./target/release/ngram ../../bin/generator-utils && rm -rf ./target diff --git a/util/ngram/src/main.rs b/util/ngram/src/main.rs new file mode 100644 index 000000000..156b39cba --- /dev/null +++ b/util/ngram/src/main.rs @@ -0,0 +1,26 @@ +use ngrammatic::{CorpusBuilder, Pad}; + +fn main() { + let mut args = std::env::args(); + let exercises = args.nth(1).expect("Missing exercises argument"); + let slug = args.nth(0).expect("Missing slug argument"); + let exercises: Vec<&str> = exercises + .split(|c: char| c.is_whitespace() || c == '\n') + .collect(); + let mut corpus = CorpusBuilder::new().arity(2).pad_full(Pad::Auto).finish(); + + for exercise in exercises.iter() { + corpus.add_text(exercise); + } + + if let Some(top_result) = corpus.search(&slug, 0.25).first() { + println!( + "{} - There is an exercise with a similar name: '{}' [{:.0}% match]", + slug, + top_result.text, + top_result.similarity * 100.0 + ); + } else { + println!("Couldn't find any exercise similar to this: {}", slug); + } +} From 7718f6fbf2059b51128aeb522177a0c5ef72eb1a Mon Sep 17 00:00:00 2001 From: dem4ron Date: Tue, 14 Mar 2023 09:55:57 +0100 Subject: [PATCH 095/436] move shellcheck to root --- bin/.shellcheckrc => .shellcheckrc | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename bin/.shellcheckrc => .shellcheckrc (100%) diff --git a/bin/.shellcheckrc b/.shellcheckrc similarity index 100% rename from bin/.shellcheckrc rename to .shellcheckrc From b7b0cb40fe114ac517c56e29e5b9e1546dd78be5 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Tue, 14 Mar 2023 10:07:56 +0100 Subject: [PATCH 096/436] Try shellcheck setup in job --- .github/workflows/tests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8d3c9a2de..25d30b957 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -88,6 +88,8 @@ jobs: - name: Run shellcheck uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # v2.0.0 + env: + SHELLCHECK_OPTS: -C ./.shellcheckrc compilation: name: Check compilation From ab855f51bcd6fcf7a9f22b5377ff5ebf4df2400a Mon Sep 17 00:00:00 2001 From: dem4ron Date: Tue, 14 Mar 2023 10:19:05 +0100 Subject: [PATCH 097/436] Move shellcheckrc --- .github/workflows/tests.yml | 2 +- .shellcheckrc => bin/.shellcheckrc | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename .shellcheckrc => bin/.shellcheckrc (100%) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 25d30b957..1ffeb27ff 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -89,7 +89,7 @@ jobs: - name: Run shellcheck uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # v2.0.0 env: - SHELLCHECK_OPTS: -C ./.shellcheckrc + SHELLCHECK_OPTS: -C ./bin/.shellcheckrc compilation: name: Check compilation diff --git a/.shellcheckrc b/bin/.shellcheckrc similarity index 100% rename from .shellcheckrc rename to bin/.shellcheckrc From 98b6e304649a495997463cbb0c81aa3632b7780c Mon Sep 17 00:00:00 2001 From: dem4ron Date: Tue, 14 Mar 2023 10:21:57 +0100 Subject: [PATCH 098/436] Change job version --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1ffeb27ff..493a90fc8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -87,7 +87,7 @@ jobs: uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 - name: Run shellcheck - uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # v2.0.0 + uses: ludeeus/action-shellcheck@master env: SHELLCHECK_OPTS: -C ./bin/.shellcheckrc From d1812822387395ea8a20be60b804e2ea8e72dc3a Mon Sep 17 00:00:00 2001 From: dem4ron Date: Tue, 14 Mar 2023 10:25:10 +0100 Subject: [PATCH 099/436] Change rcfile --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 493a90fc8..7750c4534 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -89,7 +89,7 @@ jobs: - name: Run shellcheck uses: ludeeus/action-shellcheck@master env: - SHELLCHECK_OPTS: -C ./bin/.shellcheckrc + SHELLCHECK_OPTS: --rcfile ./bin/.shellcheckrc compilation: name: Check compilation From 7496be503e465abeeebd427831a5e3633d48b849 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Tue, 14 Mar 2023 10:33:06 +0100 Subject: [PATCH 100/436] Try echoing content of shellcheckrc --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7750c4534..6aadc8bfa 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -89,7 +89,7 @@ jobs: - name: Run shellcheck uses: ludeeus/action-shellcheck@master env: - SHELLCHECK_OPTS: --rcfile ./bin/.shellcheckrc + SHELLCHECK_OPTS: echo $(cat ./bin/.shellcheckrc) compilation: name: Check compilation From b0792c1ac53495a1c2b9f672c6e9016a4cf3671a Mon Sep 17 00:00:00 2001 From: dem4ron Date: Tue, 14 Mar 2023 10:33:52 +0100 Subject: [PATCH 101/436] Remove comment from shellcheck rc --- bin/.shellcheckrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/.shellcheckrc b/bin/.shellcheckrc index 1a7ffeea1..f229171c6 100644 --- a/bin/.shellcheckrc +++ b/bin/.shellcheckrc @@ -1,3 +1,3 @@ shell=bash external-sources=true -disable=SC2001 # https://www.shellcheck.net/wiki/SC2001 - effects most of the sed commands +disable=SC2001 From 4c2a58be009cb16913786cc79298582d019e362a Mon Sep 17 00:00:00 2001 From: dem4ron Date: Tue, 14 Mar 2023 10:36:52 +0100 Subject: [PATCH 102/436] Change path --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6aadc8bfa..a3825571b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -89,7 +89,7 @@ jobs: - name: Run shellcheck uses: ludeeus/action-shellcheck@master env: - SHELLCHECK_OPTS: echo $(cat ./bin/.shellcheckrc) + SHELLCHECK_OPTS: echo $(cat ../../bin/.shellcheckrc) compilation: name: Check compilation From 3264d4cc53a5f1d6a5008738425a5a39abb24c4d Mon Sep 17 00:00:00 2001 From: dem4ron Date: Tue, 14 Mar 2023 10:38:47 +0100 Subject: [PATCH 103/436] See dir --- .github/workflows/tests.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a3825571b..fa1b60926 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -88,8 +88,9 @@ jobs: - name: Run shellcheck uses: ludeeus/action-shellcheck@master - env: - SHELLCHECK_OPTS: echo $(cat ../../bin/.shellcheckrc) + run: ls ./ + # env: + # SHELLCHECK_OPTS: echo $(cat ../../bin/.shellcheckrc) compilation: name: Check compilation From e3cdcecb38595decfa307ba3dba3e7bb2c231811 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Tue, 14 Mar 2023 10:43:09 +0100 Subject: [PATCH 104/436] Log things --- .github/workflows/tests.yml | 399 ++++++++++++++++++------------------ 1 file changed, 205 insertions(+), 194 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index fa1b60926..9dfdc8fdd 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,215 +12,226 @@ on: - cron: "0 0 * * 0" jobs: - ensure-conventions: - name: Ensure conventions are followed - runs-on: ubuntu-latest +# ensure-conventions: +# name: Ensure conventions are followed +# runs-on: ubuntu-latest - steps: - # Checks out a copy of your repository on the ubuntu-latest machine - - name: Checkout code - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 +# steps: +# # Checks out a copy of your repository on the ubuntu-latest machine +# - name: Checkout code +# uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 - - name: Ensure tool names are snake cased - run: ./bin/lint_tool_file_names.sh +# - name: Ensure tool names are snake cased +# run: ./bin/lint_tool_file_names.sh - - name: Ensure src/lib.rs files exist - run: ./_test/ensure_lib_src_rs_exist.sh +# - name: Ensure src/lib.rs files exist +# run: ./_test/ensure_lib_src_rs_exist.sh - - name: Count ignores - run: ./_test/count_ignores.sh +# - name: Count ignores +# run: ./_test/count_ignores.sh - - name: Check UUIDs - run: ./_test/check_uuids.sh +# - name: Check UUIDs +# run: ./_test/check_uuids.sh - - name: Verify exercise difficulties - run: ./_test/verify_exercise_difficulties.sh +# - name: Verify exercise difficulties +# run: ./_test/verify_exercise_difficulties.sh - - name: Check exercises for authors - run: ./_test/check_exercises_for_authors.sh +# - name: Check exercises for authors +# run: ./_test/check_exercises_for_authors.sh - - name: Ensure relevant files do not have trailing whitespace - run: ./bin/lint_trailing_spaces.sh +# - name: Ensure relevant files do not have trailing whitespace +# run: ./bin/lint_trailing_spaces.sh - configlet: - name: configlet lint - runs-on: ubuntu-latest +# configlet: +# name: configlet lint +# runs-on: ubuntu-latest - steps: - # Checks out default branch locally so that it is available to the scripts. - - name: Checkout main - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 - with: - ref: main +# steps: +# # Checks out default branch locally so that it is available to the scripts. +# - name: Checkout main +# uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 +# with: +# ref: main - # Checks out a copy of your repository on the ubuntu-latest machine - - name: Checkout code - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 +# # Checks out a copy of your repository on the ubuntu-latest machine +# - name: Checkout code +# uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 - - name: Fetch configlet - run: ./bin/fetch-configlet +# - name: Fetch configlet +# run: ./bin/fetch-configlet - - name: Lint configlet - run: ./bin/configlet lint +# - name: Lint configlet +# run: ./bin/configlet lint - markdownlint: - name: markdown lint - runs-on: ubuntu-latest +# markdownlint: +# name: markdown lint +# runs-on: ubuntu-latest - steps: - - name: Checkout main - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 - with: - ref: main - - name: Checkout code - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 +# steps: +# - name: Checkout main +# uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 +# with: +# ref: main +# - name: Checkout code +# uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 - - name: Run markdown lint - run: ./bin/lint_markdown.sh +# - name: Run markdown lint +# run: ./bin/lint_markdown.sh - # stolen from https://raw.githubusercontent.com/exercism/github-actions/main/.github/workflows/shellcheck.yml +# # stolen from https://raw.githubusercontent.com/exercism/github-actions/main/.github/workflows/shellcheck.yml shellcheck: - name: shellcheck internal tooling lint - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 - - - name: Run shellcheck - uses: ludeeus/action-shellcheck@master - run: ls ./ - # env: - # SHELLCHECK_OPTS: echo $(cat ../../bin/.shellcheckrc) - - compilation: - name: Check compilation - runs-on: ubuntu-latest - - strategy: - # Allows running the job multiple times with different configurations - matrix: - rust: ["stable", "beta"] - deny_warnings: ['', '1'] - - steps: - # Checks out main locally so that it is available to the scripts. - - name: Checkout main - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 - with: - ref: main - - # Checks out a copy of your repository on the ubuntu-latest machine - - name: Checkout code - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 - - - name: Setup toolchain - uses: actions-rs/toolchain@v1.0.7 - with: - toolchain: ${{ matrix.rust }} - default: true - - # run scripts as steps - - name: Check exercises - env: - DENYWARNINGS: ${{ matrix.deny_warnings }} - run: ./_test/check_exercises.sh - continue-on-error: ${{ matrix.rust == 'beta' && matrix.deny_warnings == '1' }} - - - name: Ensure stubs compile - env: - DENYWARNINGS: ${{ matrix.deny_warnings }} - run: ./_test/ensure_stubs_compile.sh - continue-on-error: ${{ matrix.rust == 'beta' && matrix.deny_warnings == '1' }} - - - rustformat: - name: Check Rust Formatting - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 - - - name: Setup toolchain - uses: actions-rs/toolchain@v1.0.7 - with: - toolchain: stable - default: true - - - name: Rust Format Version - run: rustfmt --version - - - name: Format - run: bin/format_exercises - - - name: Diff - run: | - if ! git diff --color --exit-code; then - echo "Please run cargo fmt, or see the diff above:" - exit 1 - fi - - clippy: - name: Clippy - runs-on: ubuntu-latest - - strategy: - matrix: - rust: ["stable", "beta"] - - steps: - - name: Checkout main - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 - with: - ref: main - - - name: Checkout code - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 - - - name: Setup toolchain - uses: actions-rs/toolchain@v1.0.7 - with: - toolchain: ${{ matrix.rust }} - default: true - - # Clippy already installed on Stable, but not Beta. - # So, we must install here. - - name: Install Clippy - run: rustup component add clippy - - - name: Clippy tests - env: - CLIPPY: true - run: ./_test/check_exercises.sh - - - name: Clippy stubs - env: - CLIPPY: true - run: ./_test/ensure_stubs_compile.sh - - nightly-compilation: - name: Check exercises on nightly (benchmark enabled) - runs-on: ubuntu-latest - continue-on-error: true # It's okay if the nightly job fails - - steps: - # Checks out main locally so that it is available to the scripts. - - name: Checkout main - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 - with: - ref: main - - # Checks out a copy of your repository on the ubuntu-latest machine - - name: Checkout code - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 - - - name: Setup nightly toolchain - uses: actions-rs/toolchain@v1.0.7 - with: - toolchain: nightly - default: true - - - name: Check exercises - env: - BENCHMARK: '1' - run: ./_test/check_exercises.sh + name: show directory + runs-on: ubuntu-latest + steps: + - name: show sibling dir + run: ls ./ + + - name: show parent dir + run: ls ../ + + - name: show grandparent dir + run: ls ../../ +# name: shellcheck internal tooling lint +# runs-on: ubuntu-latest +# steps: +# - name: Checkout +# uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + +# - name: Run shellcheck +# uses: ludeeus/action-shellcheck@master +# run: ls ./ +# env: +# SHELLCHECK_OPTS: echo $(cat ../../bin/.shellcheckrc) + +# compilation: +# name: Check compilation +# runs-on: ubuntu-latest + +# strategy: +# # Allows running the job multiple times with different configurations +# matrix: +# rust: ["stable", "beta"] +# deny_warnings: ['', '1'] + +# steps: +# # Checks out main locally so that it is available to the scripts. +# - name: Checkout main +# uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 +# with: +# ref: main + +# # Checks out a copy of your repository on the ubuntu-latest machine +# - name: Checkout code +# uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + +# - name: Setup toolchain +# uses: actions-rs/toolchain@v1.0.7 +# with: +# toolchain: ${{ matrix.rust }} +# default: true + +# # run scripts as steps +# - name: Check exercises +# env: +# DENYWARNINGS: ${{ matrix.deny_warnings }} +# run: ./_test/check_exercises.sh +# continue-on-error: ${{ matrix.rust == 'beta' && matrix.deny_warnings == '1' }} + +# - name: Ensure stubs compile +# env: +# DENYWARNINGS: ${{ matrix.deny_warnings }} +# run: ./_test/ensure_stubs_compile.sh +# continue-on-error: ${{ matrix.rust == 'beta' && matrix.deny_warnings == '1' }} + + +# rustformat: +# name: Check Rust Formatting +# runs-on: ubuntu-latest + +# steps: +# - name: Checkout code +# uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + +# - name: Setup toolchain +# uses: actions-rs/toolchain@v1.0.7 +# with: +# toolchain: stable +# default: true + +# - name: Rust Format Version +# run: rustfmt --version + +# - name: Format +# run: bin/format_exercises + +# - name: Diff +# run: | +# if ! git diff --color --exit-code; then +# echo "Please run cargo fmt, or see the diff above:" +# exit 1 +# fi + +# clippy: +# name: Clippy +# runs-on: ubuntu-latest + +# strategy: +# matrix: +# rust: ["stable", "beta"] + +# steps: +# - name: Checkout main +# uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 +# with: +# ref: main + +# - name: Checkout code +# uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + +# - name: Setup toolchain +# uses: actions-rs/toolchain@v1.0.7 +# with: +# toolchain: ${{ matrix.rust }} +# default: true + +# # Clippy already installed on Stable, but not Beta. +# # So, we must install here. +# - name: Install Clippy +# run: rustup component add clippy + +# - name: Clippy tests +# env: +# CLIPPY: true +# run: ./_test/check_exercises.sh + +# - name: Clippy stubs +# env: +# CLIPPY: true +# run: ./_test/ensure_stubs_compile.sh + +# nightly-compilation: +# name: Check exercises on nightly (benchmark enabled) +# runs-on: ubuntu-latest +# continue-on-error: true # It's okay if the nightly job fails + +# steps: +# # Checks out main locally so that it is available to the scripts. +# - name: Checkout main +# uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 +# with: +# ref: main + +# # Checks out a copy of your repository on the ubuntu-latest machine +# - name: Checkout code +# uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + +# - name: Setup nightly toolchain +# uses: actions-rs/toolchain@v1.0.7 +# with: +# toolchain: nightly +# default: true + +# - name: Check exercises +# env: +# BENCHMARK: '1' +# run: ./_test/check_exercises.sh From 3eedb55a12d917c6999425e8340e8b7fc8f2c592 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Tue, 14 Mar 2023 10:45:10 +0100 Subject: [PATCH 105/436] Log things 2 --- .github/workflows/tests.yml | 397 ++++++++++++++++++------------------ 1 file changed, 204 insertions(+), 193 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index fa1b60926..46d8f5286 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,215 +12,226 @@ on: - cron: "0 0 * * 0" jobs: - ensure-conventions: - name: Ensure conventions are followed - runs-on: ubuntu-latest +# ensure-conventions: +# name: Ensure conventions are followed +# runs-on: ubuntu-latest - steps: - # Checks out a copy of your repository on the ubuntu-latest machine - - name: Checkout code - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 +# steps: +# # Checks out a copy of your repository on the ubuntu-latest machine +# - name: Checkout code +# uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 - - name: Ensure tool names are snake cased - run: ./bin/lint_tool_file_names.sh +# - name: Ensure tool names are snake cased +# run: ./bin/lint_tool_file_names.sh - - name: Ensure src/lib.rs files exist - run: ./_test/ensure_lib_src_rs_exist.sh +# - name: Ensure src/lib.rs files exist +# run: ./_test/ensure_lib_src_rs_exist.sh - - name: Count ignores - run: ./_test/count_ignores.sh +# - name: Count ignores +# run: ./_test/count_ignores.sh - - name: Check UUIDs - run: ./_test/check_uuids.sh +# - name: Check UUIDs +# run: ./_test/check_uuids.sh - - name: Verify exercise difficulties - run: ./_test/verify_exercise_difficulties.sh +# - name: Verify exercise difficulties +# run: ./_test/verify_exercise_difficulties.sh - - name: Check exercises for authors - run: ./_test/check_exercises_for_authors.sh +# - name: Check exercises for authors +# run: ./_test/check_exercises_for_authors.sh - - name: Ensure relevant files do not have trailing whitespace - run: ./bin/lint_trailing_spaces.sh +# - name: Ensure relevant files do not have trailing whitespace +# run: ./bin/lint_trailing_spaces.sh - configlet: - name: configlet lint - runs-on: ubuntu-latest +# configlet: +# name: configlet lint +# runs-on: ubuntu-latest - steps: - # Checks out default branch locally so that it is available to the scripts. - - name: Checkout main - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 - with: - ref: main +# steps: +# # Checks out default branch locally so that it is available to the scripts. +# - name: Checkout main +# uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 +# with: +# ref: main - # Checks out a copy of your repository on the ubuntu-latest machine - - name: Checkout code - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 +# # Checks out a copy of your repository on the ubuntu-latest machine +# - name: Checkout code +# uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 - - name: Fetch configlet - run: ./bin/fetch-configlet +# - name: Fetch configlet +# run: ./bin/fetch-configlet - - name: Lint configlet - run: ./bin/configlet lint +# - name: Lint configlet +# run: ./bin/configlet lint - markdownlint: - name: markdown lint - runs-on: ubuntu-latest +# markdownlint: +# name: markdown lint +# runs-on: ubuntu-latest - steps: - - name: Checkout main - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 - with: - ref: main - - name: Checkout code - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 +# steps: +# - name: Checkout main +# uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 +# with: +# ref: main +# - name: Checkout code +# uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 - - name: Run markdown lint - run: ./bin/lint_markdown.sh +# - name: Run markdown lint +# run: ./bin/lint_markdown.sh # stolen from https://raw.githubusercontent.com/exercism/github-actions/main/.github/workflows/shellcheck.yml shellcheck: - name: shellcheck internal tooling lint - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 - - - name: Run shellcheck - uses: ludeeus/action-shellcheck@master - run: ls ./ - # env: - # SHELLCHECK_OPTS: echo $(cat ../../bin/.shellcheckrc) - - compilation: - name: Check compilation - runs-on: ubuntu-latest - - strategy: - # Allows running the job multiple times with different configurations - matrix: - rust: ["stable", "beta"] - deny_warnings: ['', '1'] - - steps: - # Checks out main locally so that it is available to the scripts. - - name: Checkout main - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 - with: - ref: main - - # Checks out a copy of your repository on the ubuntu-latest machine - - name: Checkout code - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 - - - name: Setup toolchain - uses: actions-rs/toolchain@v1.0.7 - with: - toolchain: ${{ matrix.rust }} - default: true - - # run scripts as steps - - name: Check exercises - env: - DENYWARNINGS: ${{ matrix.deny_warnings }} - run: ./_test/check_exercises.sh - continue-on-error: ${{ matrix.rust == 'beta' && matrix.deny_warnings == '1' }} - - - name: Ensure stubs compile - env: - DENYWARNINGS: ${{ matrix.deny_warnings }} - run: ./_test/ensure_stubs_compile.sh - continue-on-error: ${{ matrix.rust == 'beta' && matrix.deny_warnings == '1' }} - - - rustformat: - name: Check Rust Formatting - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 - - - name: Setup toolchain - uses: actions-rs/toolchain@v1.0.7 - with: - toolchain: stable - default: true - - - name: Rust Format Version - run: rustfmt --version - - - name: Format - run: bin/format_exercises - - - name: Diff - run: | - if ! git diff --color --exit-code; then - echo "Please run cargo fmt, or see the diff above:" - exit 1 - fi - - clippy: - name: Clippy - runs-on: ubuntu-latest - - strategy: - matrix: - rust: ["stable", "beta"] - - steps: - - name: Checkout main - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 - with: - ref: main - - - name: Checkout code - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 - - - name: Setup toolchain - uses: actions-rs/toolchain@v1.0.7 - with: - toolchain: ${{ matrix.rust }} - default: true - - # Clippy already installed on Stable, but not Beta. - # So, we must install here. - - name: Install Clippy - run: rustup component add clippy - - - name: Clippy tests - env: - CLIPPY: true - run: ./_test/check_exercises.sh - - - name: Clippy stubs - env: - CLIPPY: true - run: ./_test/ensure_stubs_compile.sh - - nightly-compilation: - name: Check exercises on nightly (benchmark enabled) - runs-on: ubuntu-latest - continue-on-error: true # It's okay if the nightly job fails - - steps: - # Checks out main locally so that it is available to the scripts. - - name: Checkout main - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 - with: - ref: main - - # Checks out a copy of your repository on the ubuntu-latest machine - - name: Checkout code - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 - - - name: Setup nightly toolchain - uses: actions-rs/toolchain@v1.0.7 - with: - toolchain: nightly - default: true - - - name: Check exercises - env: - BENCHMARK: '1' - run: ./_test/check_exercises.sh + name: show directory + runs-on: ubuntu-latest + steps: + - name: show sibling dir + run: ls ./ + + - name: show parent dir + run: ls ../ + + - name: show grandparent dir + run: ls ../../ +# name: shellcheck internal tooling lint +# runs-on: ubuntu-latest +# steps: +# - name: Checkout +# uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + +# - name: Run shellcheck +# uses: ludeeus/action-shellcheck@master +# run: ls ./ +# env: +# SHELLCHECK_OPTS: echo $(cat ../../bin/.shellcheckrc) + +# compilation: +# name: Check compilation +# runs-on: ubuntu-latest + +# strategy: +# # Allows running the job multiple times with different configurations +# matrix: +# rust: ["stable", "beta"] +# deny_warnings: ['', '1'] + +# steps: +# # Checks out main locally so that it is available to the scripts. +# - name: Checkout main +# uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 +# with: +# ref: main + +# # Checks out a copy of your repository on the ubuntu-latest machine +# - name: Checkout code +# uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + +# - name: Setup toolchain +# uses: actions-rs/toolchain@v1.0.7 +# with: +# toolchain: ${{ matrix.rust }} +# default: true + +# # run scripts as steps +# - name: Check exercises +# env: +# DENYWARNINGS: ${{ matrix.deny_warnings }} +# run: ./_test/check_exercises.sh +# continue-on-error: ${{ matrix.rust == 'beta' && matrix.deny_warnings == '1' }} + +# - name: Ensure stubs compile +# env: +# DENYWARNINGS: ${{ matrix.deny_warnings }} +# run: ./_test/ensure_stubs_compile.sh +# continue-on-error: ${{ matrix.rust == 'beta' && matrix.deny_warnings == '1' }} + + +# rustformat: +# name: Check Rust Formatting +# runs-on: ubuntu-latest + +# steps: +# - name: Checkout code +# uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + +# - name: Setup toolchain +# uses: actions-rs/toolchain@v1.0.7 +# with: +# toolchain: stable +# default: true + +# - name: Rust Format Version +# run: rustfmt --version + +# - name: Format +# run: bin/format_exercises + +# - name: Diff +# run: | +# if ! git diff --color --exit-code; then +# echo "Please run cargo fmt, or see the diff above:" +# exit 1 +# fi + +# clippy: +# name: Clippy +# runs-on: ubuntu-latest + +# strategy: +# matrix: +# rust: ["stable", "beta"] + +# steps: +# - name: Checkout main +# uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 +# with: +# ref: main + +# - name: Checkout code +# uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + +# - name: Setup toolchain +# uses: actions-rs/toolchain@v1.0.7 +# with: +# toolchain: ${{ matrix.rust }} +# default: true + +# # Clippy already installed on Stable, but not Beta. +# # So, we must install here. +# - name: Install Clippy +# run: rustup component add clippy + +# - name: Clippy tests +# env: +# CLIPPY: true +# run: ./_test/check_exercises.sh + +# - name: Clippy stubs +# env: +# CLIPPY: true +# run: ./_test/ensure_stubs_compile.sh + +# nightly-compilation: +# name: Check exercises on nightly (benchmark enabled) +# runs-on: ubuntu-latest +# continue-on-error: true # It's okay if the nightly job fails + +# steps: +# # Checks out main locally so that it is available to the scripts. +# - name: Checkout main +# uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 +# with: +# ref: main + +# # Checks out a copy of your repository on the ubuntu-latest machine +# - name: Checkout code +# uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + +# - name: Setup nightly toolchain +# uses: actions-rs/toolchain@v1.0.7 +# with: +# toolchain: nightly +# default: true + +# - name: Check exercises +# env: +# BENCHMARK: '1' +# run: ./_test/check_exercises.sh From cadab2b13ab65826486a5beeeef20fcd6a55ca12 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Tue, 14 Mar 2023 10:48:35 +0100 Subject: [PATCH 106/436] Update dir --- .github/workflows/tests.yml | 396 +++++++++++++++++------------------- 1 file changed, 192 insertions(+), 204 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9dfdc8fdd..6692d7a13 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,226 +12,214 @@ on: - cron: "0 0 * * 0" jobs: -# ensure-conventions: -# name: Ensure conventions are followed -# runs-on: ubuntu-latest + ensure-conventions: + name: Ensure conventions are followed + runs-on: ubuntu-latest -# steps: -# # Checks out a copy of your repository on the ubuntu-latest machine -# - name: Checkout code -# uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + steps: + # Checks out a copy of your repository on the ubuntu-latest machine + - name: Checkout code + uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 -# - name: Ensure tool names are snake cased -# run: ./bin/lint_tool_file_names.sh + - name: Ensure tool names are snake cased + run: ./bin/lint_tool_file_names.sh -# - name: Ensure src/lib.rs files exist -# run: ./_test/ensure_lib_src_rs_exist.sh + - name: Ensure src/lib.rs files exist + run: ./_test/ensure_lib_src_rs_exist.sh -# - name: Count ignores -# run: ./_test/count_ignores.sh + - name: Count ignores + run: ./_test/count_ignores.sh -# - name: Check UUIDs -# run: ./_test/check_uuids.sh + - name: Check UUIDs + run: ./_test/check_uuids.sh -# - name: Verify exercise difficulties -# run: ./_test/verify_exercise_difficulties.sh + - name: Verify exercise difficulties + run: ./_test/verify_exercise_difficulties.sh -# - name: Check exercises for authors -# run: ./_test/check_exercises_for_authors.sh + - name: Check exercises for authors + run: ./_test/check_exercises_for_authors.sh -# - name: Ensure relevant files do not have trailing whitespace -# run: ./bin/lint_trailing_spaces.sh + - name: Ensure relevant files do not have trailing whitespace + run: ./bin/lint_trailing_spaces.sh -# configlet: -# name: configlet lint -# runs-on: ubuntu-latest + configlet: + name: configlet lint + runs-on: ubuntu-latest -# steps: -# # Checks out default branch locally so that it is available to the scripts. -# - name: Checkout main -# uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 -# with: -# ref: main + steps: + # Checks out default branch locally so that it is available to the scripts. + - name: Checkout main + uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + with: + ref: main -# # Checks out a copy of your repository on the ubuntu-latest machine -# - name: Checkout code -# uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + # Checks out a copy of your repository on the ubuntu-latest machine + - name: Checkout code + uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 -# - name: Fetch configlet -# run: ./bin/fetch-configlet + - name: Fetch configlet + run: ./bin/fetch-configlet -# - name: Lint configlet -# run: ./bin/configlet lint + - name: Lint configlet + run: ./bin/configlet lint -# markdownlint: -# name: markdown lint -# runs-on: ubuntu-latest + markdownlint: + name: markdown lint + runs-on: ubuntu-latest -# steps: -# - name: Checkout main -# uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 -# with: -# ref: main -# - name: Checkout code -# uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + steps: + - name: Checkout main + uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + with: + ref: main + - name: Checkout code + uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 -# - name: Run markdown lint -# run: ./bin/lint_markdown.sh + - name: Run markdown lint + run: ./bin/lint_markdown.sh # # stolen from https://raw.githubusercontent.com/exercism/github-actions/main/.github/workflows/shellcheck.yml shellcheck: - name: show directory - runs-on: ubuntu-latest - steps: - - name: show sibling dir - run: ls ./ - - - name: show parent dir - run: ls ../ - - - name: show grandparent dir - run: ls ../../ -# name: shellcheck internal tooling lint -# runs-on: ubuntu-latest -# steps: -# - name: Checkout -# uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 - -# - name: Run shellcheck -# uses: ludeeus/action-shellcheck@master -# run: ls ./ -# env: -# SHELLCHECK_OPTS: echo $(cat ../../bin/.shellcheckrc) - -# compilation: -# name: Check compilation -# runs-on: ubuntu-latest - -# strategy: -# # Allows running the job multiple times with different configurations -# matrix: -# rust: ["stable", "beta"] -# deny_warnings: ['', '1'] - -# steps: -# # Checks out main locally so that it is available to the scripts. -# - name: Checkout main -# uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 -# with: -# ref: main - -# # Checks out a copy of your repository on the ubuntu-latest machine -# - name: Checkout code -# uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 - -# - name: Setup toolchain -# uses: actions-rs/toolchain@v1.0.7 -# with: -# toolchain: ${{ matrix.rust }} -# default: true - -# # run scripts as steps -# - name: Check exercises -# env: -# DENYWARNINGS: ${{ matrix.deny_warnings }} -# run: ./_test/check_exercises.sh -# continue-on-error: ${{ matrix.rust == 'beta' && matrix.deny_warnings == '1' }} - -# - name: Ensure stubs compile -# env: -# DENYWARNINGS: ${{ matrix.deny_warnings }} -# run: ./_test/ensure_stubs_compile.sh -# continue-on-error: ${{ matrix.rust == 'beta' && matrix.deny_warnings == '1' }} - - -# rustformat: -# name: Check Rust Formatting -# runs-on: ubuntu-latest - -# steps: -# - name: Checkout code -# uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 - -# - name: Setup toolchain -# uses: actions-rs/toolchain@v1.0.7 -# with: -# toolchain: stable -# default: true - -# - name: Rust Format Version -# run: rustfmt --version - -# - name: Format -# run: bin/format_exercises - -# - name: Diff -# run: | -# if ! git diff --color --exit-code; then -# echo "Please run cargo fmt, or see the diff above:" -# exit 1 -# fi - -# clippy: -# name: Clippy -# runs-on: ubuntu-latest - -# strategy: -# matrix: -# rust: ["stable", "beta"] - -# steps: -# - name: Checkout main -# uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 -# with: -# ref: main - -# - name: Checkout code -# uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 - -# - name: Setup toolchain -# uses: actions-rs/toolchain@v1.0.7 -# with: -# toolchain: ${{ matrix.rust }} -# default: true - -# # Clippy already installed on Stable, but not Beta. -# # So, we must install here. -# - name: Install Clippy -# run: rustup component add clippy - -# - name: Clippy tests -# env: -# CLIPPY: true -# run: ./_test/check_exercises.sh - -# - name: Clippy stubs -# env: -# CLIPPY: true -# run: ./_test/ensure_stubs_compile.sh - -# nightly-compilation: -# name: Check exercises on nightly (benchmark enabled) -# runs-on: ubuntu-latest -# continue-on-error: true # It's okay if the nightly job fails - -# steps: -# # Checks out main locally so that it is available to the scripts. -# - name: Checkout main -# uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 -# with: -# ref: main - -# # Checks out a copy of your repository on the ubuntu-latest machine -# - name: Checkout code -# uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 - -# - name: Setup nightly toolchain -# uses: actions-rs/toolchain@v1.0.7 -# with: -# toolchain: nightly -# default: true - -# - name: Check exercises -# env: -# BENCHMARK: '1' -# run: ./_test/check_exercises.sh + name: shellcheck internal tooling lint + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + + - name: Run shellcheck + uses: ludeeus/action-shellcheck@master + env: + SHELLCHECK_OPTS: echo $(cat ../rust/bin/.shellcheckrc) + + compilation: + name: Check compilation + runs-on: ubuntu-latest + + strategy: + # Allows running the job multiple times with different configurations + matrix: + rust: ["stable", "beta"] + deny_warnings: ['', '1'] + + steps: + # Checks out main locally so that it is available to the scripts. + - name: Checkout main + uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + with: + ref: main + + # Checks out a copy of your repository on the ubuntu-latest machine + - name: Checkout code + uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + + - name: Setup toolchain + uses: actions-rs/toolchain@v1.0.7 + with: + toolchain: ${{ matrix.rust }} + default: true + + # run scripts as steps + - name: Check exercises + env: + DENYWARNINGS: ${{ matrix.deny_warnings }} + run: ./_test/check_exercises.sh + continue-on-error: ${{ matrix.rust == 'beta' && matrix.deny_warnings == '1' }} + + - name: Ensure stubs compile + env: + DENYWARNINGS: ${{ matrix.deny_warnings }} + run: ./_test/ensure_stubs_compile.sh + continue-on-error: ${{ matrix.rust == 'beta' && matrix.deny_warnings == '1' }} + + + rustformat: + name: Check Rust Formatting + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + + - name: Setup toolchain + uses: actions-rs/toolchain@v1.0.7 + with: + toolchain: stable + default: true + + - name: Rust Format Version + run: rustfmt --version + + - name: Format + run: bin/format_exercises + + - name: Diff + run: | + if ! git diff --color --exit-code; then + echo "Please run cargo fmt, or see the diff above:" + exit 1 + fi + + clippy: + name: Clippy + runs-on: ubuntu-latest + + strategy: + matrix: + rust: ["stable", "beta"] + + steps: + - name: Checkout main + uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + with: + ref: main + + - name: Checkout code + uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + + - name: Setup toolchain + uses: actions-rs/toolchain@v1.0.7 + with: + toolchain: ${{ matrix.rust }} + default: true + + # Clippy already installed on Stable, but not Beta. + # So, we must install here. + - name: Install Clippy + run: rustup component add clippy + + - name: Clippy tests + env: + CLIPPY: true + run: ./_test/check_exercises.sh + + - name: Clippy stubs + env: + CLIPPY: true + run: ./_test/ensure_stubs_compile.sh + + nightly-compilation: + name: Check exercises on nightly (benchmark enabled) + runs-on: ubuntu-latest + continue-on-error: true # It's okay if the nightly job fails + + steps: + # Checks out main locally so that it is available to the scripts. + - name: Checkout main + uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + with: + ref: main + + # Checks out a copy of your repository on the ubuntu-latest machine + - name: Checkout code + uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + + - name: Setup nightly toolchain + uses: actions-rs/toolchain@v1.0.7 + with: + toolchain: nightly + default: true + + - name: Check exercises + env: + BENCHMARK: '1' + run: ./_test/check_exercises.sh From 9f930d07094a0ad6c439b733ada11173c82be59c Mon Sep 17 00:00:00 2001 From: dem4ron Date: Tue, 14 Mar 2023 10:59:24 +0100 Subject: [PATCH 107/436] Update shellcheck job with flags --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6692d7a13..b8c963b76 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -89,7 +89,7 @@ jobs: - name: Run shellcheck uses: ludeeus/action-shellcheck@master env: - SHELLCHECK_OPTS: echo $(cat ../rust/bin/.shellcheckrc) + SHELLCHECK_OPTS: --shell:bash --external-sources:true --disable:SC2001 compilation: name: Check compilation From 8f58e8546672166fe89ef9423eaab35c834795e4 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Tue, 14 Mar 2023 11:01:05 +0100 Subject: [PATCH 108/436] Update flags --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b8c963b76..58a120b76 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -89,7 +89,7 @@ jobs: - name: Run shellcheck uses: ludeeus/action-shellcheck@master env: - SHELLCHECK_OPTS: --shell:bash --external-sources:true --disable:SC2001 + SHELLCHECK_OPTS= --shell=bash --external-sources=true --disable=SC2001 compilation: name: Check compilation From 82451dabc19ef241659af12607bec241112d1a11 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Tue, 14 Mar 2023 11:03:35 +0100 Subject: [PATCH 109/436] Update more flags --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 58a120b76..af5a81194 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -89,7 +89,7 @@ jobs: - name: Run shellcheck uses: ludeeus/action-shellcheck@master env: - SHELLCHECK_OPTS= --shell=bash --external-sources=true --disable=SC2001 + SHELLCHECK_OPTS= --shell=bash --external-sources=true --exclude=SC2001 --norc compilation: name: Check compilation From 387ba4e25035a6495fde058528730db3faa02573 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Tue, 14 Mar 2023 11:05:49 +0100 Subject: [PATCH 110/436] Comment out shellcheck env var --- .github/workflows/tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index af5a81194..f2c4e2868 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -88,8 +88,8 @@ jobs: - name: Run shellcheck uses: ludeeus/action-shellcheck@master - env: - SHELLCHECK_OPTS= --shell=bash --external-sources=true --exclude=SC2001 --norc + # env: + # SHELLCHECK_OPTS= --shell=bash --external-sources=true --exclude=SC2001 --norc compilation: name: Check compilation From 838132841054cebfe49992402b1dbd5f568c5b9a Mon Sep 17 00:00:00 2001 From: dem4ron Date: Tue, 14 Mar 2023 11:06:56 +0100 Subject: [PATCH 111/436] Fix accidental substitution --- .github/workflows/tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f2c4e2868..30d6140c5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -88,8 +88,8 @@ jobs: - name: Run shellcheck uses: ludeeus/action-shellcheck@master - # env: - # SHELLCHECK_OPTS= --shell=bash --external-sources=true --exclude=SC2001 --norc + env: + SHELLCHECK_OPTS: --shell=bash --external-sources=true --exclude=SC2001 --norc compilation: name: Check compilation From d0094f626b5c46927888202aa39717d23ded36f2 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Tue, 14 Mar 2023 11:08:42 +0100 Subject: [PATCH 112/436] Try again with shorter flags --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 30d6140c5..9b3c4e2a7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -89,7 +89,7 @@ jobs: - name: Run shellcheck uses: ludeeus/action-shellcheck@master env: - SHELLCHECK_OPTS: --shell=bash --external-sources=true --exclude=SC2001 --norc + SHELLCHECK_OPTS: -shell bash -x -e SC2001 --norc compilation: name: Check compilation From 4742bd4c40143d086c39cc03f3814df606a85bd0 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Tue, 14 Mar 2023 11:09:12 +0100 Subject: [PATCH 113/436] Shell -> s --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9b3c4e2a7..e3938980d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -89,7 +89,7 @@ jobs: - name: Run shellcheck uses: ludeeus/action-shellcheck@master env: - SHELLCHECK_OPTS: -shell bash -x -e SC2001 --norc + SHELLCHECK_OPTS: -s bash -x -e SC2001 --norc compilation: name: Check compilation From 02e0cb02e7233ce5a65874af3fa7d45a75f37c37 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Tue, 14 Mar 2023 11:44:11 +0100 Subject: [PATCH 114/436] Update source paths in scripts --- .github/workflows/tests.yml | 2 +- bin/add_practice_exercise | 4 ++++ bin/generate_tests | 2 ++ bin/generator-utils/prompts.sh | 2 ++ bin/generator-utils/templates.sh | 2 ++ bin/generator-utils/utils.sh | 6 +++++- 6 files changed, 16 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e3938980d..8b568427e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -89,7 +89,7 @@ jobs: - name: Run shellcheck uses: ludeeus/action-shellcheck@master env: - SHELLCHECK_OPTS: -s bash -x -e SC2001 --norc + SHELLCHECK_OPTS: -x -s bash -e SC2001 --norc compilation: name: Check compilation diff --git a/bin/add_practice_exercise b/bin/add_practice_exercise index 840ed76cc..4b7ccce43 100755 --- a/bin/add_practice_exercise +++ b/bin/add_practice_exercise @@ -1,6 +1,10 @@ #!/usr/bin/env bash +# see comment in generator-utils/utils.sh +# shellcheck source=bin/generator-utils/utils.sh +# shellcheck source=bin/generator-utils/templates.sh +# shellcheck source=bin/generator-utils/prompts.sh # shellcheck source=./generator-utils/utils.sh # shellcheck source=./generator-utils/prompts.sh # shellcheck source=./generator-utils/templates.sh diff --git a/bin/generate_tests b/bin/generate_tests index 929420279..451b91bcc 100755 --- a/bin/generate_tests +++ b/bin/generate_tests @@ -5,6 +5,8 @@ set -euo pipefail +# see comment in generator-utils/utils.sh +# shellcheck source=bin/generator-utils/utils.sh # shellcheck source=./generator-utils/utils.sh source ./bin/generator-utils/utils.sh diff --git a/bin/generator-utils/prompts.sh b/bin/generator-utils/prompts.sh index 0bcff97a6..7a1fd055a 100644 --- a/bin/generator-utils/prompts.sh +++ b/bin/generator-utils/prompts.sh @@ -1,6 +1,8 @@ #!/usr/bin/env bash +# see comment in utils.sh +# shellcheck source=bin/generator-utils/colors.sh # shellcheck source=./colors.sh source ./bin/generator-utils/colors.sh diff --git a/bin/generator-utils/templates.sh b/bin/generator-utils/templates.sh index e47537cfd..06ce46aeb 100755 --- a/bin/generator-utils/templates.sh +++ b/bin/generator-utils/templates.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash +# see comment in utils.sh +# shellcheck source=bin/generator-utils/utils.sh # shellcheck source=./utils.sh source ./bin/generator-utils/utils.sh diff --git a/bin/generator-utils/utils.sh b/bin/generator-utils/utils.sh index 6ee3ee526..e3611e439 100755 --- a/bin/generator-utils/utils.sh +++ b/bin/generator-utils/utils.sh @@ -1,6 +1,10 @@ #!/usr/bin/env bash - +# top one gets evaluated +# relative one is needed for .shellcheckrc to test if files exist in local env +# absolute one is needed for CI to test if files exist +# before commit - swap these accordingly +# shellcheck source=bin/generator-utils/colors.sh # shellcheck source=./colors.sh source ./bin/generator-utils/colors.sh From c56fba462837a3210f6bddaf6dcda5cafae4afc5 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Tue, 14 Mar 2023 12:21:57 +0100 Subject: [PATCH 115/436] Add configlet fetching if it doesn't exist --- bin/add_practice_exercise | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/bin/add_practice_exercise b/bin/add_practice_exercise index 4b7ccce43..b9c30eb95 100755 --- a/bin/add_practice_exercise +++ b/bin/add_practice_exercise @@ -1,13 +1,12 @@ #!/usr/bin/env bash - # see comment in generator-utils/utils.sh -# shellcheck source=bin/generator-utils/utils.sh -# shellcheck source=bin/generator-utils/templates.sh -# shellcheck source=bin/generator-utils/prompts.sh -# shellcheck source=./generator-utils/utils.sh -# shellcheck source=./generator-utils/prompts.sh -# shellcheck source=./generator-utils/templates.sh +# shellcheck source=bin/generator-utils/utils.sh +# shellcheck source=bin/generator-utils/templates.sh +# shellcheck source=bin/generator-utils/prompts.sh +# shellcheck source=./generator-utils/utils.sh +# shellcheck source=./generator-utils/prompts.sh +# shellcheck source=./generator-utils/templates.sh source ./bin/generator-utils/utils.sh source ./bin/generator-utils/prompts.sh @@ -38,12 +37,19 @@ command -v curl >/dev/null 2>&1 || { exit 1 } +if [ -e bin/configlet ]; then + message "success" "Configlet found!" +else + message "info" "Fetching configlet" + bin/fetch-configlet + message "success" "Fetched configlet successfully!" +fi + # Check if exercise exists in configlet info or in config.json check_exercise_existence "$1" # ================================================== - slug="$1" # Fetch canonical data canonical_json=$(bin/fetch_canonical_data "$slug") @@ -59,7 +65,6 @@ else message "success" "Fetched canonical data successfully!" fi - underscored_slug=$(dash_to_underscore "$slug") exercise_dir="exercises/practice/${slug}" exercise_name=$(format_exercise_name "$slug") @@ -87,7 +92,6 @@ uuid=$(bin/configlet uuid) # Add exercise-data to global config.json jq --arg slug "$slug" --arg uuid "$uuid" --arg name "$exercise_name" --arg difficulty "$exercise_difficulty" \ - '.exercises.practice += [{slug: $slug, name: $name, uuid: $uuid, practices: [], prerequisites: [], difficulty: $difficulty}]' \ config.json >config.json.tmp # jq always rounds whole numbers, but average_run_time needs to be a float @@ -110,10 +114,8 @@ message "success" "You've been added as the author of this exercise." sed -i "s/name = \".*\"/name = \"$underscored_slug\"/" "$exercise_dir"/Cargo.toml - message "done" "All stub files were created." message "info" "After implementing the solution, tests and configuration, please run:" echo "./bin/configlet fmt --update --yes --exercise ${slug}" - From 47c9d8cb46ab3600b60d39e3d5f2b7b65372cad8 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Tue, 14 Mar 2023 12:32:22 +0100 Subject: [PATCH 116/436] Update comments, initial template --- bin/generate_tests | 2 -- bin/generator-utils/templates.sh | 3 +-- bin/generator-utils/utils.sh | 2 +- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/bin/generate_tests b/bin/generate_tests index 451b91bcc..fab91d3ce 100755 --- a/bin/generate_tests +++ b/bin/generate_tests @@ -30,8 +30,6 @@ test_file="$exercise_dir/tests/$slug.rs" cat <"$test_file" use $(dash_to_underscore "$slug")::*; -// Add tests here - EOT # Flattens canonical json, extracts only the objects with a uuid diff --git a/bin/generator-utils/templates.sh b/bin/generator-utils/templates.sh index 06ce46aeb..8492804d2 100755 --- a/bin/generator-utils/templates.sh +++ b/bin/generator-utils/templates.sh @@ -70,11 +70,10 @@ EOT cat <>"$test_file" #[test] $([[ "$first_iteration" == false ]] && printf "\n#[ignore]") fn ${desc}() { - let input = ${input}; let expected = ${expected}; - // TODO: Add assertion + // TODO: Verify assertion assert_eq!(${fn_name}(input), expected); } diff --git a/bin/generator-utils/utils.sh b/bin/generator-utils/utils.sh index e3611e439..b5d39f7e1 100755 --- a/bin/generator-utils/utils.sh +++ b/bin/generator-utils/utils.sh @@ -3,7 +3,7 @@ # top one gets evaluated # relative one is needed for .shellcheckrc to test if files exist in local env # absolute one is needed for CI to test if files exist -# before commit - swap these accordingly +# before commit swap these accordingly # shellcheck source=bin/generator-utils/colors.sh # shellcheck source=./colors.sh source ./bin/generator-utils/colors.sh From 98e9faef586e046fcf804d790608ed83bd8e9a4f Mon Sep 17 00:00:00 2001 From: dem4ron Date: Tue, 14 Mar 2023 12:39:44 +0100 Subject: [PATCH 117/436] Remove extra hash --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8b568427e..b39355d96 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -78,7 +78,7 @@ jobs: - name: Run markdown lint run: ./bin/lint_markdown.sh -# # stolen from https://raw.githubusercontent.com/exercism/github-actions/main/.github/workflows/shellcheck.yml + # stolen from https://raw.githubusercontent.com/exercism/github-actions/main/.github/workflows/shellcheck.yml shellcheck: name: shellcheck internal tooling lint runs-on: ubuntu-latest From 5a183b76954c22033bf269e7b65a6881fb16d9ad Mon Sep 17 00:00:00 2001 From: dem4ron Date: Tue, 14 Mar 2023 15:04:21 +0100 Subject: [PATCH 118/436] Remove empty lines --- bin/add_practice_exercise | 3 +-- bin/generator-utils/colors.sh | 1 - bin/generator-utils/prompts.sh | 5 ----- bin/generator-utils/templates.sh | 4 ---- bin/generator-utils/utils.sh | 9 --------- 5 files changed, 1 insertion(+), 21 deletions(-) diff --git a/bin/add_practice_exercise b/bin/add_practice_exercise index b9c30eb95..00c5039da 100755 --- a/bin/add_practice_exercise +++ b/bin/add_practice_exercise @@ -37,6 +37,7 @@ command -v curl >/dev/null 2>&1 || { exit 1 } +# Check if configlet is already fetched if [ -e bin/configlet ]; then message "success" "Configlet found!" else @@ -57,7 +58,6 @@ canonical_json=$(bin/fetch_canonical_data "$slug") has_canonical_data=true if [ "${canonical_json}" == "404: Not Found" ]; then has_canonical_data=false - message "warning" "This exercise doesn't have canonical data" else @@ -81,7 +81,6 @@ create_rust_files "$exercise_dir" "$slug" "$has_canonical_data" # ================================================== # Build configlet - ./bin/fetch-configlet message "success" "Fetched configlet successfully!" diff --git a/bin/generator-utils/colors.sh b/bin/generator-utils/colors.sh index c7e5a7d55..d3c5f9339 100644 --- a/bin/generator-utils/colors.sh +++ b/bin/generator-utils/colors.sh @@ -10,5 +10,4 @@ cyan=$(echo -e '\033[0;36m') bold_green=$(echo -e '\033[1;32m') - export red green blue yellow bold_green reset_color cyan diff --git a/bin/generator-utils/prompts.sh b/bin/generator-utils/prompts.sh index 7a1fd055a..fdec283f8 100644 --- a/bin/generator-utils/prompts.sh +++ b/bin/generator-utils/prompts.sh @@ -1,20 +1,16 @@ #!/usr/bin/env bash - # see comment in utils.sh # shellcheck source=bin/generator-utils/colors.sh # shellcheck source=./colors.sh source ./bin/generator-utils/colors.sh get_exercise_difficulty() { - read -rp "Difficulty of exercise (1-10): " exercise_difficulty echo "$exercise_difficulty" } - validate_difficulty_input() { - local valid_input=false while ! $valid_input; do if [[ "$1" =~ ^[1-9]$|^10$ ]]; then @@ -30,7 +26,6 @@ validate_difficulty_input() { echo "$exercise_difficulty" } - get_author_handle() { local default_author_handle default_author_handle="$(git config user.name)" diff --git a/bin/generator-utils/templates.sh b/bin/generator-utils/templates.sh index 8492804d2..8be909592 100755 --- a/bin/generator-utils/templates.sh +++ b/bin/generator-utils/templates.sh @@ -22,7 +22,6 @@ create_fn_name() { } create_test_file_template() { - local exercise_dir=$1 local slug=$2 local has_canonical_data=$3 @@ -116,14 +115,12 @@ EOT message "success" ".gitignore has been overwritten!" } - create_example_rs_template() { local exercise_dir=$1 local slug=$2 local has_canonical_data=$3 - local fn_name fn_name=$(create_fn_name "$slug" "$has_canonical_data") @@ -139,7 +136,6 @@ EOT message "success" "Stub file for example.rs has been created!" } - create_rust_files() { local exercise_dir=$1 diff --git a/bin/generator-utils/utils.sh b/bin/generator-utils/utils.sh index b5d39f7e1..d01185723 100755 --- a/bin/generator-utils/utils.sh +++ b/bin/generator-utils/utils.sh @@ -9,12 +9,10 @@ source ./bin/generator-utils/colors.sh message() { - local flag=$1 local message=$2 case "$flag" in - "success") printf "${green}%s${reset_color}\n" "[success]: $message" ;; "task") printf "${cyan}%s${reset_color}\n" "[task]: $message" ;; "info") printf "${blue}%s${reset_color}\n" "[info]: $message" ;; @@ -36,9 +34,7 @@ message() { esac } - dash_to_underscore() { - echo "$1" | sed 's/-/_/g' } @@ -58,10 +54,8 @@ check_exercise_existence() { exit 1 fi - # Fetch configlet and crop out exercise list local unimplemented_exercises - unimplemented_exercises=$(bin/configlet info | sed -n '/With canonical data:/,/Track summary:/p' | sed -e '/\(With\(out\)\? canonical data:\|Track summary:\)/d' -e '/^$/d') if echo "$unimplemented_exercises" | grep -q "^$slug$"; then message "success" "Exercise has been found!" @@ -78,11 +72,8 @@ ${unimplemented_exercises}" echo "${yellow}$(bin/generator-utils/ngram "${unimplemented_exercises}" "$slug")${reset_color}" else message "info" "Building typo-checker binary for $(uname -m) system." - cd util/ngram && ./build && cd ../.. && echo "${yellow}$(bin/generator-utils/ngram "${unimplemented_exercises}" "$slug")${reset_color}" - fi - exit 1 fi } From e4f2e6442072b8eea0c7b7d6a23393a1704ce44c Mon Sep 17 00:00:00 2001 From: dem4ron Date: Tue, 14 Mar 2023 15:05:39 +0100 Subject: [PATCH 119/436] Remove one more redundant empty line --- bin/generator-utils/utils.sh | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/bin/generator-utils/utils.sh b/bin/generator-utils/utils.sh index d01185723..227a9bcd8 100755 --- a/bin/generator-utils/utils.sh +++ b/bin/generator-utils/utils.sh @@ -26,11 +26,8 @@ message() { printf "%*s\n" "$cols" "" | tr " " "-" echo printf "${bold_green}%s${reset_color}\n" "[done]: $message" - - ;; - *) - echo "Invalid flag: $flag" ;; + *) echo "Invalid flag: $flag" ;; esac } From 72080cb79a644c95f4bb39862a153b3c448b21c0 Mon Sep 17 00:00:00 2001 From: dem4ron Date: Tue, 14 Mar 2023 15:45:09 +0100 Subject: [PATCH 120/436] Revert shellcheck job version, always fetch configlet, but only once --- .github/workflows/tests.yml | 2 +- bin/add_practice_exercise | 14 +++----------- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b39355d96..7577b6d02 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -87,7 +87,7 @@ jobs: uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 - name: Run shellcheck - uses: ludeeus/action-shellcheck@master + uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # v2.0.0 env: SHELLCHECK_OPTS: -x -s bash -e SC2001 --norc diff --git a/bin/add_practice_exercise b/bin/add_practice_exercise index 00c5039da..056bd41b0 100755 --- a/bin/add_practice_exercise +++ b/bin/add_practice_exercise @@ -37,14 +37,9 @@ command -v curl >/dev/null 2>&1 || { exit 1 } -# Check if configlet is already fetched -if [ -e bin/configlet ]; then - message "success" "Configlet found!" -else - message "info" "Fetching configlet" - bin/fetch-configlet - message "success" "Fetched configlet successfully!" -fi +# Build configlet +bin/fetch-configlet +message "success" "Fetched configlet successfully!" # Check if exercise exists in configlet info or in config.json check_exercise_existence "$1" @@ -80,9 +75,6 @@ create_rust_files "$exercise_dir" "$slug" "$has_canonical_data" # ================================================== -# Build configlet -./bin/fetch-configlet -message "success" "Fetched configlet successfully!" # Preparing config.json message "info" "Adding instructions and configuration files..." From 83b3d92cccd181d8d9f598cd72064360e42a13fb Mon Sep 17 00:00:00 2001 From: Alexey Kolgan <70635996+bashkovich@users.noreply.github.com> Date: Mon, 20 Mar 2023 20:19:48 +0700 Subject: [PATCH 121/436] concepts/integers: Fix maximum value of u8 to 255 (#1653) --- concepts/integers/about.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/concepts/integers/about.md b/concepts/integers/about.md index 03b145017..10449a89b 100644 --- a/concepts/integers/about.md +++ b/concepts/integers/about.md @@ -9,7 +9,7 @@ Integers all have a **bit width**, which is just the number of bits making up th the maximum value which can be represented by that integer type. For example, one of the most common integer types is a `u8`: an unsigned, 8-bit integer. You may -recognize this type as a single byte. This has a minimum value of 0 and a maximum value of 256. +recognize this type as a single byte. This has a minimum value of 0 and a maximum value of 255. Rust has 12 integer primitive types, broken out by bit width and signedness: From c7a89f4a3ce82366309176510137a06ca3e282fa Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Tue, 28 Mar 2023 11:14:48 +0200 Subject: [PATCH 122/436] Sync sum-of-multiples docs with problem-specifications (#1649) The sum-of-multiples exercise has been improved upstream in the problem-specifications repository. For more context, please see the pull request that updated the exercise: - https://github.com/exercism/problem-specifications/pull/2231 --- .../sum-of-multiples/.docs/instructions.md | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/exercises/practice/sum-of-multiples/.docs/instructions.md b/exercises/practice/sum-of-multiples/.docs/instructions.md index bb512396a..7b7ec006e 100644 --- a/exercises/practice/sum-of-multiples/.docs/instructions.md +++ b/exercises/practice/sum-of-multiples/.docs/instructions.md @@ -1,9 +1,18 @@ # Instructions -Given a number, find the sum of all the unique multiples of particular numbers up to -but not including that number. +Given a list of factors and a limit, add up all the unique multiples of the factors that are less than the limit. +All inputs will be greater than or equal to zero. -If we list all the natural numbers below 20 that are multiples of 3 or 5, -we get 3, 5, 6, 9, 10, 12, 15, and 18. +## Example -The sum of these multiples is 78. +Suppose the limit is 20 and the list of factors is [3, 5]. +We need to find the sum of all unique multiples of 3 and 5 that are less than 20. + +Multiples of 3 less than 20: 3, 6, 9, 12, 15, 18 +Multiples of 5 less than 20: 5, 10, 15 + +The unique multiples are: 3, 5, 6, 9, 10, 12, 15, 18 + +The sum of the unique multiples is: 3 + 5 + 6 + 9 + 10 + 12 + 15 + 18 = 78 + +So, the answer is 78. From f37443818191f607ac0ca0401761a89925e53989 Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Tue, 28 Mar 2023 11:19:54 +0200 Subject: [PATCH 123/436] Sync binary-search docs with problem-specifications (#1651) * Sync binary-search docs with problem-specifications There were a few follow-on tweaks to binary-search. For context, this is part of the project to overhaul all the practice exercises. You can read about that here: https://forum.exercism.org/t/new-project-making-practice-exercises-more-consistent-and-human-across-exercism/3943 ---- If you approve this pull request, I will eventually merge it. However, if you are happy with this change **please merge the pull request**, as it will get the changes into the hands of the students much more quickly. If this pull request contradicts the exercise on your track, **please add a review with _request changes_**. This will block the pull request from getting merged. Otherwise we aim to take an optimistic merging approach. If you wish to suggest tweaks to these changes, please open a pull request to the exercism/problem-specifications repository to discuss, so that everyone who has an interest in the shared exercise descriptions can participate. * Run configlet sync on binary-search --- .../practice/binary-search/.docs/instructions.md | 15 ++++++++------- .../practice/binary-search/.docs/introduction.md | 6 +++++- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/exercises/practice/binary-search/.docs/instructions.md b/exercises/practice/binary-search/.docs/instructions.md index 175c4c4ba..aa1946cfb 100644 --- a/exercises/practice/binary-search/.docs/instructions.md +++ b/exercises/practice/binary-search/.docs/instructions.md @@ -5,17 +5,18 @@ Your task is to implement a binary search algorithm. A binary search algorithm finds an item in a list by repeatedly splitting it in half, only keeping the half which contains the item we're looking for. It allows us to quickly narrow down the possible locations of our item until we find it, or until we've eliminated all possible locations. -```exercism/caution +~~~~exercism/caution Binary search only works when a list has been sorted. -``` +~~~~ The algorithm looks like this: -- Divide the sorted list in half and compare the middle element with the item we're looking for. -- If the middle element is our item, then we're done. -- If the middle element is greater than our item, we can eliminate that number and all the numbers **after** it. -- If the middle element is less than our item, we can eliminate that number and all the numbers **before** it. -- Repeat the process on the part of the list that we kept. +- Find the middle element of a *sorted* list and compare it with the item we're looking for. +- If the middle element is our item, then we're done! +- If the middle element is greater than our item, we can eliminate that element and all the elements **after** it. +- If the middle element is less than our item, we can eliminate that element and all the elements **before** it. +- If every element of the list has been eliminated then the item is not in the list. +- Otherwise, repeat the process on the part of the list that has not been eliminated. Here's an example: diff --git a/exercises/practice/binary-search/.docs/introduction.md b/exercises/practice/binary-search/.docs/introduction.md index 66c4b8a45..03496599e 100644 --- a/exercises/practice/binary-search/.docs/introduction.md +++ b/exercises/practice/binary-search/.docs/introduction.md @@ -1,9 +1,13 @@ # Introduction You have stumbled upon a group of mathematicians who are also singer-songwriters. -They have written a song for each of their favorite numbers, and, as you can imagine, they have a lot of favorite numbers. +They have written a song for each of their favorite numbers, and, as you can imagine, they have a lot of favorite numbers (like [0][zero] or [73][seventy-three] or [6174][kaprekars-constant]). You are curious to hear the song for your favorite number, but with so many songs to wade through, finding the right song could take a while. Fortunately, they have organized their songs in a playlist sorted by the title — which is simply the number that the song is about. You realize that you can use a binary search algorithm to quickly find a song given the title. + +[zero]: https://en.wikipedia.org/wiki/0 +[seventy-three]: https://en.wikipedia.org/wiki/73_(number) +[kaprekars-constant]: https://en.wikipedia.org/wiki/6174_(number) From af8e3cfa23a179644d6321df43a92c7915676885 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Wed, 29 Mar 2023 19:55:26 +0200 Subject: [PATCH 124/436] Disable learning mode (#1660) --- config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.json b/config.json index 05ec651f9..63a7dc83a 100644 --- a/config.json +++ b/config.json @@ -3,7 +3,7 @@ "slug": "rust", "active": true, "status": { - "concept_exercises": true, + "concept_exercises": false, "test_runner": true, "representer": true, "analyzer": true From c527bbaac4ff32adbadef470182f0ec7b92bbc57 Mon Sep 17 00:00:00 2001 From: Aron Demeter <66035744+dem4ron@users.noreply.github.com> Date: Fri, 7 Apr 2023 13:20:35 +0200 Subject: [PATCH 125/436] Generator script improvements (#1663) * Add difficulty as int, further format fn name * Add util fn to escape double quotes * Simplify escape double quotes algo * Add missing end line --- .gitignore | 1 + bin/add_practice_exercise | 2 +- bin/generate_tests | 14 ++++---- bin/test_template | 5 ++- util/escape_double_quotes/Cargo.lock | 7 ++++ util/escape_double_quotes/Cargo.toml | 8 +++++ util/escape_double_quotes/build | 2 ++ util/escape_double_quotes/src/lib.rs | 2 ++ util/escape_double_quotes/src/main.rs | 30 ++++++++++++++++ .../src/utils/escape_double_quotes.rs | 22 ++++++++++++ util/escape_double_quotes/src/utils/mod.rs | 1 + util/escape_double_quotes/tests/test.rs | 36 +++++++++++++++++++ 12 files changed, 119 insertions(+), 11 deletions(-) create mode 100644 util/escape_double_quotes/Cargo.lock create mode 100644 util/escape_double_quotes/Cargo.toml create mode 100755 util/escape_double_quotes/build create mode 100644 util/escape_double_quotes/src/lib.rs create mode 100644 util/escape_double_quotes/src/main.rs create mode 100644 util/escape_double_quotes/src/utils/escape_double_quotes.rs create mode 100644 util/escape_double_quotes/src/utils/mod.rs create mode 100644 util/escape_double_quotes/tests/test.rs diff --git a/.gitignore b/.gitignore index 8f4f224b4..edfd68168 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ bin/configlet.exe bin/exercise bin/exercise.exe bin/generator-utils/ngram +bin/generator-utils/escape_double_quotes exercises/*/*/Cargo.lock exercises/*/*/clippy.log canonical_data.json diff --git a/bin/add_practice_exercise b/bin/add_practice_exercise index 056bd41b0..a9068e431 100755 --- a/bin/add_practice_exercise +++ b/bin/add_practice_exercise @@ -82,7 +82,7 @@ message "info" "Adding instructions and configuration files..." uuid=$(bin/configlet uuid) # Add exercise-data to global config.json -jq --arg slug "$slug" --arg uuid "$uuid" --arg name "$exercise_name" --arg difficulty "$exercise_difficulty" \ +jq --arg slug "$slug" --arg uuid "$uuid" --arg name "$exercise_name" --argjson difficulty "$exercise_difficulty" \ '.exercises.practice += [{slug: $slug, name: $name, uuid: $uuid, practices: [], prerequisites: [], difficulty: $difficulty}]' \ config.json >config.json.tmp # jq always rounds whole numbers, but average_run_time needs to be a float diff --git a/bin/generate_tests b/bin/generate_tests index fab91d3ce..1065148a2 100755 --- a/bin/generate_tests +++ b/bin/generate_tests @@ -1,18 +1,21 @@ #!/usr/bin/env bash - # Exit if anything fails. set -euo pipefail - # see comment in generator-utils/utils.sh # shellcheck source=bin/generator-utils/utils.sh # shellcheck source=./generator-utils/utils.sh source ./bin/generator-utils/utils.sh +if [ ! -e bin/generator-utils/escape_double_quotes ]; then + message "info" "Building util function" + cd util/escape_double_quotes && ./build && cd ../.. +fi + digest_template() { local template - template=$(cat bin/test_template) + template=$(bin/generator-utils/escape_double_quotes bin/test_template) # Turn every token into a jq command echo "$template" | sed 's/${\([^}]*\)\}\$/$(echo $case | jq -r '\''.\1'\'')/g' @@ -35,7 +38,6 @@ EOT # Flattens canonical json, extracts only the objects with a uuid cases=$(echo "$canonical_json" | jq '[ .. | objects | with_entries(select(.key | IN("uuid", "description", "input", "expected", "property"))) | select(. != {}) | select(has("uuid")) ]') - # Shellcheck doesn't recognize that `case` is not unused # shellcheck disable=SC2034 @@ -45,9 +47,8 @@ jq -c '.[]' <<<"$cases" | while read -r case; do eval_template="$(digest_template | sed -e "s/\$(\(.*\))/\$\(\1\)/g")" eval_template="$(eval "echo \"$eval_template\"")" - # Turn function name into snake_case - formatted_template=$(echo "$eval_template" | sed -e ':loop' -e 's/\(fn[^(]*\)[ -]/\1_/g' -e 't loop' | sed 's/fn_/fn /') + formatted_template=$(echo "$eval_template" | sed -E -e '/^fn/!b' -e 's/[^a-zA-Z0-9_{}()[:space:]-]//g' -e 's/([[:upper:]])/ \L\1/g' -e 's/(fn[[:space:]]+)([a-z0-9_-]+)/\1\L\2/g' -e 's/ /_/g' -e 's/_\{/\{/g' -e 's/-/_/g' | sed 's/fn_/fn /' | sed 's/__\+/_/g') # Push to test file echo "$formatted_template" >>"$test_file" @@ -58,4 +59,3 @@ done rustfmt "$test_file" message "success" "Generated tests successfully! Check out ${test_file}" - diff --git a/bin/test_template b/bin/test_template index ad458d212..93b513457 100644 --- a/bin/test_template +++ b/bin/test_template @@ -1,7 +1,6 @@ #[test] #[ignore] fn ${description}$() { - - assert_eq!(${property}$(${input}$), ${expected}$); +let expected = "${expected}$"; + assert_eq!(${property}$(${input}$), expected); } - diff --git a/util/escape_double_quotes/Cargo.lock b/util/escape_double_quotes/Cargo.lock new file mode 100644 index 000000000..54ec08296 --- /dev/null +++ b/util/escape_double_quotes/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "escape_double_quotes" +version = "0.1.0" diff --git a/util/escape_double_quotes/Cargo.toml b/util/escape_double_quotes/Cargo.toml new file mode 100644 index 000000000..c61c6ddbc --- /dev/null +++ b/util/escape_double_quotes/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "escape_double_quotes" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/util/escape_double_quotes/build b/util/escape_double_quotes/build new file mode 100755 index 000000000..b74a56633 --- /dev/null +++ b/util/escape_double_quotes/build @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +cargo build --release --quiet && cp ./target/release/escape_double_quotes ../../bin/generator-utils && rm -rf ./target diff --git a/util/escape_double_quotes/src/lib.rs b/util/escape_double_quotes/src/lib.rs new file mode 100644 index 000000000..e91f3a79b --- /dev/null +++ b/util/escape_double_quotes/src/lib.rs @@ -0,0 +1,2 @@ +pub mod utils; +pub use utils::escape_double_quotes::*; diff --git a/util/escape_double_quotes/src/main.rs b/util/escape_double_quotes/src/main.rs new file mode 100644 index 000000000..3890e92bf --- /dev/null +++ b/util/escape_double_quotes/src/main.rs @@ -0,0 +1,30 @@ +use std::env; +use std::fs::File; +use std::io::{self, BufRead, BufReader, BufWriter, Write}; +mod utils; +use utils::escape_double_quotes::*; + + +fn main() -> io::Result<()> { + let args: Vec = env::args().collect(); + if args.len() != 2 { + eprintln!("Usage: {} ", args[0]); + std::process::exit(1); + } + + let file_path = &args[1]; + let file = File::open(file_path)?; + let reader = BufReader::new(file); + + let stdout = io::stdout(); + let mut writer = BufWriter::new(stdout.lock()); + + for line in reader.lines() { + let input = line?; + let output = escape_double_quotes(&input); + writeln!(writer, "{}", output)?; + } + + Ok(()) +} + diff --git a/util/escape_double_quotes/src/utils/escape_double_quotes.rs b/util/escape_double_quotes/src/utils/escape_double_quotes.rs new file mode 100644 index 000000000..83fb7b907 --- /dev/null +++ b/util/escape_double_quotes/src/utils/escape_double_quotes.rs @@ -0,0 +1,22 @@ +pub fn escape_double_quotes(input: &str) -> String { + let mut output = String::new(); + let mut escape = true; + + let mut chars = input.chars().peekable(); + while let Some(char) = chars.next() { + match char { + '$' if chars.peek() == Some(&'{') => { + escape = false; + output.push(char) + } + '}' if chars.peek() == Some(&'$') => { + escape = true; + output.push(char) + } + '"' if escape => output.push_str("\\\""), + _ => output.push(char), + } + } + + output +} diff --git a/util/escape_double_quotes/src/utils/mod.rs b/util/escape_double_quotes/src/utils/mod.rs new file mode 100644 index 000000000..a352b9aaf --- /dev/null +++ b/util/escape_double_quotes/src/utils/mod.rs @@ -0,0 +1 @@ +pub mod escape_double_quotes; diff --git a/util/escape_double_quotes/tests/test.rs b/util/escape_double_quotes/tests/test.rs new file mode 100644 index 000000000..99e82935e --- /dev/null +++ b/util/escape_double_quotes/tests/test.rs @@ -0,0 +1,36 @@ +use escape_double_quotes::utils::escape_double_quotes::escape_double_quotes; + +#[test] +fn test_no_double_quotes() { + let input = "let x = 5;"; + let expected = "let x = 5;"; + assert_eq!(escape_double_quotes(input), expected); +} + +#[test] +fn test_simple_double_quotes() { + let input = "let something = \"string\";"; + let expected = "let something = \\\"string\\\";"; + assert_eq!(escape_double_quotes(input), expected); +} + +#[test] +fn test_braces_with_double_quotes() { + let input = "let expected = \"${expected | join(\\\"\\n\\\")}$\";"; + let expected = "let expected = \\\"${expected | join(\\\"\\n\\\")}$\\\";"; + assert_eq!(escape_double_quotes(input), expected); +} + +#[test] +fn test_mixed_double_quotes() { + let input = "let a = \"value\"; let b = \"${value | filter(\\\"text\\\")}$\";"; + let expected = "let a = \\\"value\\\"; let b = \\\"${value | filter(\\\"text\\\")}$\\\";"; + assert_eq!(escape_double_quotes(input), expected); +} + +#[test] +fn test_nested_braces() { + let input = "let nested = \"${outer {inner | escape(\\\"\\n\\\")}}$\";"; + let expected = "let nested = \\\"${outer {inner | escape(\\\"\\n\\\")}}$\\\";"; + assert_eq!(escape_double_quotes(input), expected); +} From 399488bb3f384a686cd48a4632df53d10f9e7f1b Mon Sep 17 00:00:00 2001 From: Aron Demeter <66035744+dem4ron@users.noreply.github.com> Date: Fri, 7 Apr 2023 14:26:51 +0200 Subject: [PATCH 126/436] Add Kindergarten Garden exercise (#1662) * Add kindergarten garden exercise * Format test file, remove first ignore * Format example --- config.json | 8 + .../kindergarten-garden/.docs/instructions.md | 58 ++++++ .../practice/kindergarten-garden/.gitignore | 8 + .../kindergarten-garden/.meta/config.json | 20 +++ .../kindergarten-garden/.meta/example.rs | 35 ++++ .../kindergarten-garden/.meta/tests.toml | 61 +++++++ .../practice/kindergarten-garden/Cargo.toml | 8 + .../practice/kindergarten-garden/src/lib.rs | 3 + .../tests/kindergarten-garden.rs | 170 ++++++++++++++++++ 9 files changed, 371 insertions(+) create mode 100644 exercises/practice/kindergarten-garden/.docs/instructions.md create mode 100644 exercises/practice/kindergarten-garden/.gitignore create mode 100644 exercises/practice/kindergarten-garden/.meta/config.json create mode 100644 exercises/practice/kindergarten-garden/.meta/example.rs create mode 100644 exercises/practice/kindergarten-garden/.meta/tests.toml create mode 100644 exercises/practice/kindergarten-garden/Cargo.toml create mode 100644 exercises/practice/kindergarten-garden/src/lib.rs create mode 100644 exercises/practice/kindergarten-garden/tests/kindergarten-garden.rs diff --git a/config.json b/config.json index 63a7dc83a..3ad25420c 100644 --- a/config.json +++ b/config.json @@ -1507,6 +1507,14 @@ "strings" ], "status": "deprecated" + }, + { + "slug": "kindergarten-garden", + "name": "Kindergarten Garden", + "uuid": "c27e4878-28a4-4637-bde2-2af681a7ff0d", + "practices": [], + "prerequisites": [], + "difficulty": 1 } ], "foregone": [ diff --git a/exercises/practice/kindergarten-garden/.docs/instructions.md b/exercises/practice/kindergarten-garden/.docs/instructions.md new file mode 100644 index 000000000..472ee26f6 --- /dev/null +++ b/exercises/practice/kindergarten-garden/.docs/instructions.md @@ -0,0 +1,58 @@ +# Instructions + +Given a diagram, determine which plants each child in the kindergarten class is +responsible for. + +The kindergarten class is learning about growing plants. +The teacher thought it would be a good idea to give them actual seeds, plant them in actual dirt, and grow actual plants. + +They've chosen to grow grass, clover, radishes, and violets. + +To this end, the children have put little cups along the window sills, and +planted one type of plant in each cup, choosing randomly from the available +types of seeds. + +```text +[window][window][window] +........................ # each dot represents a cup +........................ +``` + +There are 12 children in the class: + +- Alice, Bob, Charlie, David, +- Eve, Fred, Ginny, Harriet, +- Ileana, Joseph, Kincaid, and Larry. + +Each child gets 4 cups, two on each row. +Their teacher assigns cups to the children alphabetically by their names. + +The following diagram represents Alice's plants: + +```text +[window][window][window] +VR...................... +RG...................... +``` + +In the first row, nearest the windows, she has a violet and a radish. +In the second row she has a radish and some grass. + +Your program will be given the plants from left-to-right starting with the row nearest the windows. +From this, it should be able to determine which plants belong to each student. + +For example, if it's told that the garden looks like so: + +```text +[window][window][window] +VRCGVVRVCGGCCGVRGCVCGCGV +VRCCCGCRRGVCGCRVVCVGCGCV +``` + +Then if asked for Alice's plants, it should provide: + +- Violets, radishes, violets, radishes + +While asking for Bob's plants would yield: + +- Clover, grass, clover, clover diff --git a/exercises/practice/kindergarten-garden/.gitignore b/exercises/practice/kindergarten-garden/.gitignore new file mode 100644 index 000000000..e24ae5981 --- /dev/null +++ b/exercises/practice/kindergarten-garden/.gitignore @@ -0,0 +1,8 @@ +# Generated by Cargo +# Will have compiled files and executables +/target/ +**/*.rs.bk + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock diff --git a/exercises/practice/kindergarten-garden/.meta/config.json b/exercises/practice/kindergarten-garden/.meta/config.json new file mode 100644 index 000000000..b1737f26a --- /dev/null +++ b/exercises/practice/kindergarten-garden/.meta/config.json @@ -0,0 +1,20 @@ +{ + "authors": [ + "dem4ron" + ], + "files": { + "solution": [ + "src/lib.rs", + "Cargo.toml" + ], + "test": [ + "tests/kindergarten-garden.rs" + ], + "example": [ + ".meta/example.rs" + ] + }, + "blurb": "Given a diagram, determine which plants each child in the kindergarten class is responsible for.", + "source": "Exercise by the JumpstartLab team for students at The Turing School of Software and Design.", + "source_url": "https://turing.edu/" +} diff --git a/exercises/practice/kindergarten-garden/.meta/example.rs b/exercises/practice/kindergarten-garden/.meta/example.rs new file mode 100644 index 000000000..96c753372 --- /dev/null +++ b/exercises/practice/kindergarten-garden/.meta/example.rs @@ -0,0 +1,35 @@ +pub fn plants(diagram: &str, student: &str) -> Vec<&'static str> { + let student_names = [ + "Alice", "Bob", "Charlie", "David", "Eve", "Fred", "Ginny", "Harriet", "Ileana", "Joseph", + "Kincaid", "Larry", + ]; + let index = student_names + .iter() + .position(|&name| name == student) + .unwrap(); + + let mut lines = diagram + .lines() + .map(|line| line.chars().map(plant_from_char)); + + let start = index * 2; + let mut first_row = lines.next().unwrap(); + let mut second_row = lines.next().unwrap(); + + vec![ + first_row.nth(start).unwrap(), + first_row.next().unwrap(), + second_row.nth(start).unwrap(), + second_row.next().unwrap(), + ] +} + +fn plant_from_char(c: char) -> &'static str { + match c { + 'R' => "radishes", + 'C' => "clover", + 'G' => "grass", + 'V' => "violets", + _ => "No such plant", + } +} diff --git a/exercises/practice/kindergarten-garden/.meta/tests.toml b/exercises/practice/kindergarten-garden/.meta/tests.toml new file mode 100644 index 000000000..0cdd9ad64 --- /dev/null +++ b/exercises/practice/kindergarten-garden/.meta/tests.toml @@ -0,0 +1,61 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[1fc316ed-17ab-4fba-88ef-3ae78296b692] +description = "partial garden -> garden with single student" + +[acd19dc1-2200-4317-bc2a-08f021276b40] +description = "partial garden -> different garden with single student" + +[c376fcc8-349c-446c-94b0-903947315757] +description = "partial garden -> garden with two students" + +[2d620f45-9617-4924-9d27-751c80d17db9] +description = "partial garden -> multiple students for the same garden with three students -> second student's garden" + +[57712331-4896-4364-89f8-576421d69c44] +description = "partial garden -> multiple students for the same garden with three students -> third student's garden" + +[149b4290-58e1-40f2-8ae4-8b87c46e765b] +description = "full garden -> for Alice, first student's garden" + +[ba25dbbc-10bd-4a37-b18e-f89ecd098a5e] +description = "full garden -> for Bob, second student's garden" + +[566b621b-f18e-4c5f-873e-be30544b838c] +description = "full garden -> for Charlie" + +[3ad3df57-dd98-46fc-9269-1877abf612aa] +description = "full garden -> for David" + +[0f0a55d1-9710-46ed-a0eb-399ba8c72db2] +description = "full garden -> for Eve" + +[a7e80c90-b140-4ea1-aee3-f4625365c9a4] +description = "full garden -> for Fred" + +[9d94b273-2933-471b-86e8-dba68694c615] +description = "full garden -> for Ginny" + +[f55bc6c2-ade8-4844-87c4-87196f1b7258] +description = "full garden -> for Harriet" + +[759070a3-1bb1-4dd4-be2c-7cce1d7679ae] +description = "full garden -> for Ileana" + +[78578123-2755-4d4a-9c7d-e985b8dda1c6] +description = "full garden -> for Joseph" + +[6bb66df7-f433-41ab-aec2-3ead6e99f65b] +description = "full garden -> for Kincaid, second to last student's garden" + +[d7edec11-6488-418a-94e6-ed509e0fa7eb] +description = "full garden -> for Larry, last student's garden" diff --git a/exercises/practice/kindergarten-garden/Cargo.toml b/exercises/practice/kindergarten-garden/Cargo.toml new file mode 100644 index 000000000..e29f1102b --- /dev/null +++ b/exercises/practice/kindergarten-garden/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "kindergarten_garden" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/exercises/practice/kindergarten-garden/src/lib.rs b/exercises/practice/kindergarten-garden/src/lib.rs new file mode 100644 index 000000000..2c9fd777d --- /dev/null +++ b/exercises/practice/kindergarten-garden/src/lib.rs @@ -0,0 +1,3 @@ +pub fn plants(_diagram: &str, _student: &str) -> Vec<&'static str> { + unimplemented!("Solve kindergarten-garden exercise"); +} diff --git a/exercises/practice/kindergarten-garden/tests/kindergarten-garden.rs b/exercises/practice/kindergarten-garden/tests/kindergarten-garden.rs new file mode 100644 index 000000000..054d5c72e --- /dev/null +++ b/exercises/practice/kindergarten-garden/tests/kindergarten-garden.rs @@ -0,0 +1,170 @@ +use kindergarten_garden::*; + +#[test] +fn test_garden_with_single_student() { + let diagram = "RC +GG"; + let student = "Alice"; + let expected = vec!["radishes", "clover", "grass", "grass"]; + assert_eq!(plants(diagram, student), expected); +} + +#[test] +#[ignore] +fn test_different_garden_with_single_student() { + let diagram = "VC +RC"; + let student = "Alice"; + let expected = vec!["violets", "clover", "radishes", "clover"]; + assert_eq!(plants(diagram, student), expected); +} + +#[test] +#[ignore] +fn test_garden_with_two_students() { + let diagram = "VVCG +VVRC"; + let student = "Bob"; + let expected = vec!["clover", "grass", "radishes", "clover"]; + assert_eq!(plants(diagram, student), expected); +} + +#[test] +#[ignore] +fn test_second_students_garden() { + let diagram = "VVCCGG +VVCCGG"; + let student = "Bob"; + let expected = vec!["clover", "clover", "clover", "clover"]; + assert_eq!(plants(diagram, student), expected); +} + +#[test] +#[ignore] +fn test_third_students_garden() { + let diagram = "VVCCGG +VVCCGG"; + let student = "Charlie"; + let expected = vec!["grass", "grass", "grass", "grass"]; + assert_eq!(plants(diagram, student), expected); +} + +#[test] +#[ignore] +fn test_for_alice_first_students_garden() { + let diagram = "VRCGVVRVCGGCCGVRGCVCGCGV +VRCCCGCRRGVCGCRVVCVGCGCV"; + let student = "Alice"; + let expected = vec!["violets", "radishes", "violets", "radishes"]; + assert_eq!(plants(diagram, student), expected); +} + +#[test] +#[ignore] +fn test_for_bob_second_students_garden() { + let diagram = "VRCGVVRVCGGCCGVRGCVCGCGV +VRCCCGCRRGVCGCRVVCVGCGCV"; + let student = "Bob"; + let expected = vec!["clover", "grass", "clover", "clover"]; + assert_eq!(plants(diagram, student), expected); +} + +#[test] +#[ignore] +fn test_for_charlie() { + let diagram = "VRCGVVRVCGGCCGVRGCVCGCGV +VRCCCGCRRGVCGCRVVCVGCGCV"; + let student = "Charlie"; + let expected = vec!["violets", "violets", "clover", "grass"]; + assert_eq!(plants(diagram, student), expected); +} + +#[test] +#[ignore] +fn test_for_david() { + let diagram = "VRCGVVRVCGGCCGVRGCVCGCGV +VRCCCGCRRGVCGCRVVCVGCGCV"; + let student = "David"; + let expected = vec!["radishes", "violets", "clover", "radishes"]; + assert_eq!(plants(diagram, student), expected); +} + +#[test] +#[ignore] +fn test_for_eve() { + let diagram = "VRCGVVRVCGGCCGVRGCVCGCGV +VRCCCGCRRGVCGCRVVCVGCGCV"; + let student = "Eve"; + let expected = vec!["clover", "grass", "radishes", "grass"]; + assert_eq!(plants(diagram, student), expected); +} + +#[test] +#[ignore] +fn test_for_fred() { + let diagram = "VRCGVVRVCGGCCGVRGCVCGCGV +VRCCCGCRRGVCGCRVVCVGCGCV"; + let student = "Fred"; + let expected = vec!["grass", "clover", "violets", "clover"]; + assert_eq!(plants(diagram, student), expected); +} + +#[test] +#[ignore] +fn test_for_ginny() { + let diagram = "VRCGVVRVCGGCCGVRGCVCGCGV +VRCCCGCRRGVCGCRVVCVGCGCV"; + let student = "Ginny"; + let expected = vec!["clover", "grass", "grass", "clover"]; + assert_eq!(plants(diagram, student), expected); +} + +#[test] +#[ignore] +fn test_for_harriet() { + let diagram = "VRCGVVRVCGGCCGVRGCVCGCGV +VRCCCGCRRGVCGCRVVCVGCGCV"; + let student = "Harriet"; + let expected = vec!["violets", "radishes", "radishes", "violets"]; + assert_eq!(plants(diagram, student), expected); +} + +#[test] +#[ignore] +fn test_for_ileana() { + let diagram = "VRCGVVRVCGGCCGVRGCVCGCGV +VRCCCGCRRGVCGCRVVCVGCGCV"; + let student = "Ileana"; + let expected = vec!["grass", "clover", "violets", "clover"]; + assert_eq!(plants(diagram, student), expected); +} + +#[test] +#[ignore] +fn test_for_joseph() { + let diagram = "VRCGVVRVCGGCCGVRGCVCGCGV +VRCCCGCRRGVCGCRVVCVGCGCV"; + let student = "Joseph"; + let expected = vec!["violets", "clover", "violets", "grass"]; + assert_eq!(plants(diagram, student), expected); +} + +#[test] +#[ignore] +fn test_for_kincaid_second_to_last_students_garden() { + let diagram = "VRCGVVRVCGGCCGVRGCVCGCGV +VRCCCGCRRGVCGCRVVCVGCGCV"; + let student = "Kincaid"; + let expected = vec!["grass", "clover", "clover", "grass"]; + assert_eq!(plants(diagram, student), expected); +} + +#[test] +#[ignore] +fn test_for_larry_last_students_garden() { + let diagram = "VRCGVVRVCGGCCGVRGCVCGCGV +VRCCCGCRRGVCGCRVVCVGCGCV"; + let student = "Larry"; + let expected = vec!["grass", "violets", "clover", "violets"]; + assert_eq!(plants(diagram, student), expected); +} From 796f2cac587a0ec50f9b410832b3580e635a31eb Mon Sep 17 00:00:00 2001 From: Aron Demeter <66035744+dem4ron@users.noreply.github.com> Date: Fri, 7 Apr 2023 22:57:42 +0200 Subject: [PATCH 127/436] Add Knapsack exercise (#1661) * Add knapsack exercise * Underscore unused params, turn string diff to int diff --- config.json | 8 + .../practice/knapsack/.docs/instructions.md | 32 +++ exercises/practice/knapsack/.gitignore | 8 + exercises/practice/knapsack/.meta/config.json | 20 ++ exercises/practice/knapsack/.meta/example.rs | 26 +++ exercises/practice/knapsack/.meta/tests.toml | 31 +++ exercises/practice/knapsack/Cargo.toml | 8 + exercises/practice/knapsack/src/lib.rs | 8 + exercises/practice/knapsack/tests/knapsack.rs | 219 ++++++++++++++++++ 9 files changed, 360 insertions(+) create mode 100644 exercises/practice/knapsack/.docs/instructions.md create mode 100644 exercises/practice/knapsack/.gitignore create mode 100644 exercises/practice/knapsack/.meta/config.json create mode 100644 exercises/practice/knapsack/.meta/example.rs create mode 100644 exercises/practice/knapsack/.meta/tests.toml create mode 100644 exercises/practice/knapsack/Cargo.toml create mode 100644 exercises/practice/knapsack/src/lib.rs create mode 100644 exercises/practice/knapsack/tests/knapsack.rs diff --git a/config.json b/config.json index 3ad25420c..5609f8e10 100644 --- a/config.json +++ b/config.json @@ -1508,6 +1508,14 @@ ], "status": "deprecated" }, + { + "slug": "knapsack", + "name": "Knapsack", + "uuid": "cbccd0c5-eb15-4705-9a4c-0209861f078c", + "practices": [], + "prerequisites": [], + "difficulty": 4 + }, { "slug": "kindergarten-garden", "name": "Kindergarten Garden", diff --git a/exercises/practice/knapsack/.docs/instructions.md b/exercises/practice/knapsack/.docs/instructions.md new file mode 100644 index 000000000..1dbbca91c --- /dev/null +++ b/exercises/practice/knapsack/.docs/instructions.md @@ -0,0 +1,32 @@ +# Instructions + +In this exercise, let's try to solve a classic problem. + +Bob is a thief. +After months of careful planning, he finally manages to crack the security systems of a high-class apartment. + +In front of him are many items, each with a value (v) and weight (w). +Bob, of course, wants to maximize the total value he can get; he would gladly take all of the items if he could. +However, to his horror, he realizes that the knapsack he carries with him can only hold so much weight (W). + +Given a knapsack with a specific carrying capacity (W), help Bob determine the maximum value he can get from the items in the house. +Note that Bob can take only one of each item. + +All values given will be strictly positive. +Items will be represented as a list of pairs, `wi` and `vi`, where the first element `wi` is the weight of the *i*th item and `vi` is the value for that item. + +For example: + +Items: [ + { "weight": 5, "value": 10 }, + { "weight": 4, "value": 40 }, + { "weight": 6, "value": 30 }, + { "weight": 4, "value": 50 } +] + +Knapsack Limit: 10 + +For the above, the first item has weight 5 and value 10, the second item has weight 4 and value 40, and so on. + +In this example, Bob should take the second and fourth item to maximize his value, which, in this case, is 90. +He cannot get more than 90 as his knapsack has a weight limit of 10. diff --git a/exercises/practice/knapsack/.gitignore b/exercises/practice/knapsack/.gitignore new file mode 100644 index 000000000..e24ae5981 --- /dev/null +++ b/exercises/practice/knapsack/.gitignore @@ -0,0 +1,8 @@ +# Generated by Cargo +# Will have compiled files and executables +/target/ +**/*.rs.bk + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock diff --git a/exercises/practice/knapsack/.meta/config.json b/exercises/practice/knapsack/.meta/config.json new file mode 100644 index 000000000..760ae0258 --- /dev/null +++ b/exercises/practice/knapsack/.meta/config.json @@ -0,0 +1,20 @@ +{ + "authors": [ + "dem4ron" + ], + "files": { + "solution": [ + "src/lib.rs", + "Cargo.toml" + ], + "test": [ + "tests/knapsack.rs" + ], + "example": [ + ".meta/example.rs" + ] + }, + "blurb": "Given a knapsack that can only carry a certain weight, determine which items to put in the knapsack in order to maximize their combined value.", + "source": "Wikipedia", + "source_url": "https://en.wikipedia.org/wiki/Knapsack_problem" +} diff --git a/exercises/practice/knapsack/.meta/example.rs b/exercises/practice/knapsack/.meta/example.rs new file mode 100644 index 000000000..74f431bbf --- /dev/null +++ b/exercises/practice/knapsack/.meta/example.rs @@ -0,0 +1,26 @@ +pub struct Item { + pub weight: u32, + pub value: u32, +} + +pub fn maximum_value(max_weight: u32, items: Vec) -> u32 { + let mut max_values = vec![vec![0; (max_weight + 1) as usize]; items.len() + 1]; + + for i in 1..=items.len() { + let item_weight = items[i - 1].weight as usize; + let item_value = items[i - 1].value; + + for w in 0..=(max_weight as usize) { + if item_weight <= w { + max_values[i][w] = std::cmp::max( + max_values[i - 1][w], + max_values[i - 1][w - item_weight] + item_value, + ); + } else { + max_values[i][w] = max_values[i - 1][w]; + } + } + } + + max_values[items.len()][max_weight as usize] +} diff --git a/exercises/practice/knapsack/.meta/tests.toml b/exercises/practice/knapsack/.meta/tests.toml new file mode 100644 index 000000000..febc7b26b --- /dev/null +++ b/exercises/practice/knapsack/.meta/tests.toml @@ -0,0 +1,31 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[a4d7d2f0-ad8a-460c-86f3-88ba709d41a7] +description = "no items" + +[1d39e98c-6249-4a8b-912f-87cb12e506b0] +description = "one item, too heavy" + +[833ea310-6323-44f2-9d27-a278740ffbd8] +description = "five items (cannot be greedy by weight)" + +[277cdc52-f835-4c7d-872b-bff17bab2456] +description = "five items (cannot be greedy by value)" + +[81d8e679-442b-4f7a-8a59-7278083916c9] +description = "example knapsack" + +[f23a2449-d67c-4c26-bf3e-cde020f27ecc] +description = "8 items" + +[7c682ae9-c385-4241-a197-d2fa02c81a11] +description = "15 items" diff --git a/exercises/practice/knapsack/Cargo.toml b/exercises/practice/knapsack/Cargo.toml new file mode 100644 index 000000000..7b1d5d915 --- /dev/null +++ b/exercises/practice/knapsack/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "knapsack" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/exercises/practice/knapsack/src/lib.rs b/exercises/practice/knapsack/src/lib.rs new file mode 100644 index 000000000..f55b38500 --- /dev/null +++ b/exercises/practice/knapsack/src/lib.rs @@ -0,0 +1,8 @@ +pub struct Item { + pub weight: u32, + pub value: u32, +} + +pub fn maximum_value(_max_weight: u32, _items: Vec) -> u32 { + unimplemented!("Solve the knapsack exercise"); +} diff --git a/exercises/practice/knapsack/tests/knapsack.rs b/exercises/practice/knapsack/tests/knapsack.rs new file mode 100644 index 000000000..1432f7474 --- /dev/null +++ b/exercises/practice/knapsack/tests/knapsack.rs @@ -0,0 +1,219 @@ +use knapsack::*; + +#[test] +fn test_example_knapsack() { + let max_weight = 10; + let items = vec![ + Item { + weight: 5, + value: 10, + }, + Item { + weight: 4, + value: 40, + }, + Item { + weight: 6, + value: 30, + }, + Item { + weight: 4, + value: 50, + }, + ]; + + assert_eq!(maximum_value(max_weight, items), 90); +} + +#[test] +#[ignore] +fn test_no_items() { + let max_weight = 100; + let items = vec![]; + + assert_eq!(maximum_value(max_weight, items), 0); +} + +#[test] +#[ignore] +fn test_one_item_too_heavy() { + let max_weight = 10; + let items = vec![Item { + weight: 100, + value: 1, + }]; + + assert_eq!(maximum_value(max_weight, items), 0); +} + +#[test] +#[ignore] +fn test_five_items_cannot_be_greedy_by_weight() { + let max_weight = 10; + let items = vec![ + Item { + weight: 2, + value: 5, + }, + Item { + weight: 2, + value: 5, + }, + Item { + weight: 2, + value: 5, + }, + Item { + weight: 2, + value: 5, + }, + Item { + weight: 10, + value: 21, + }, + ]; + + assert_eq!(maximum_value(max_weight, items), 21); +} + +#[test] +#[ignore] +fn test_five_items_cannot_be_greedy_by_value() { + let max_weight = 10; + let items = vec![ + Item { + weight: 2, + value: 20, + }, + Item { + weight: 2, + value: 20, + }, + Item { + weight: 2, + value: 20, + }, + Item { + weight: 2, + value: 20, + }, + Item { + weight: 10, + value: 50, + }, + ]; + + assert_eq!(maximum_value(max_weight, items), 80); +} + +#[test] +#[ignore] +fn test_8_items() { + let max_weight = 104; + let items = vec![ + Item { + weight: 25, + value: 350, + }, + Item { + weight: 35, + value: 400, + }, + Item { + weight: 45, + value: 450, + }, + Item { + weight: 5, + value: 20, + }, + Item { + weight: 25, + value: 70, + }, + Item { + weight: 3, + value: 8, + }, + Item { + weight: 2, + value: 5, + }, + Item { + weight: 2, + value: 5, + }, + ]; + + assert_eq!(maximum_value(max_weight, items), 900); +} + +#[test] +#[ignore] +fn test_15_items() { + let max_weight = 750; + let items = vec![ + Item { + weight: 70, + value: 135, + }, + Item { + weight: 73, + value: 139, + }, + Item { + weight: 77, + value: 149, + }, + Item { + weight: 80, + value: 150, + }, + Item { + weight: 82, + value: 156, + }, + Item { + weight: 87, + value: 163, + }, + Item { + weight: 90, + value: 173, + }, + Item { + weight: 94, + value: 184, + }, + Item { + weight: 98, + value: 192, + }, + Item { + weight: 106, + value: 201, + }, + Item { + weight: 110, + value: 210, + }, + Item { + weight: 113, + value: 214, + }, + Item { + weight: 115, + value: 221, + }, + Item { + weight: 118, + value: 229, + }, + Item { + weight: 120, + value: 240, + }, + ]; + + assert_eq!(maximum_value(max_weight, items), 1458); +} From cc54440c5db9f9f6548208c66a5969b81ccde862 Mon Sep 17 00:00:00 2001 From: Aron Demeter <66035744+dem4ron@users.noreply.github.com> Date: Tue, 11 Apr 2023 11:17:43 +0200 Subject: [PATCH 128/436] Add Yacht exercise (#1671) * Add files, generate tests * Solve exercise --- config.json | 8 + .../practice/yacht/.docs/instructions.md | 35 +++ exercises/practice/yacht/.gitignore | 8 + exercises/practice/yacht/.meta/config.json | 20 ++ exercises/practice/yacht/.meta/example.rs | 86 ++++++++ exercises/practice/yacht/.meta/tests.toml | 97 +++++++++ exercises/practice/yacht/Cargo.toml | 8 + exercises/practice/yacht/src/lib.rs | 19 ++ exercises/practice/yacht/tests/yacht.rs | 203 ++++++++++++++++++ 9 files changed, 484 insertions(+) create mode 100644 exercises/practice/yacht/.docs/instructions.md create mode 100644 exercises/practice/yacht/.gitignore create mode 100644 exercises/practice/yacht/.meta/config.json create mode 100644 exercises/practice/yacht/.meta/example.rs create mode 100644 exercises/practice/yacht/.meta/tests.toml create mode 100644 exercises/practice/yacht/Cargo.toml create mode 100644 exercises/practice/yacht/src/lib.rs create mode 100644 exercises/practice/yacht/tests/yacht.rs diff --git a/config.json b/config.json index 5609f8e10..9778c6af1 100644 --- a/config.json +++ b/config.json @@ -1523,6 +1523,14 @@ "practices": [], "prerequisites": [], "difficulty": 1 + }, + { + "slug": "yacht", + "name": "Yacht", + "uuid": "1a0e8e34-f578-4a53-91b0-8a1260446553", + "practices": [], + "prerequisites": [], + "difficulty": 4 } ], "foregone": [ diff --git a/exercises/practice/yacht/.docs/instructions.md b/exercises/practice/yacht/.docs/instructions.md new file mode 100644 index 000000000..163ba3792 --- /dev/null +++ b/exercises/practice/yacht/.docs/instructions.md @@ -0,0 +1,35 @@ +# Instructions + +The dice game [Yacht][yacht] is from the same family as Poker Dice, Generala and particularly Yahtzee, of which it is a precursor. +In the game, five dice are rolled and the result can be entered in any of twelve categories. +The score of a throw of the dice depends on category chosen. + +## Scores in Yacht + +| Category | Score | Description | Example | +| -------- | ----- | ----------- | ------- | +| Ones | 1 × number of ones | Any combination | 1 1 1 4 5 scores 3 | +| Twos | 2 × number of twos | Any combination | 2 2 3 4 5 scores 4 | +| Threes | 3 × number of threes | Any combination | 3 3 3 3 3 scores 15 | +| Fours | 4 × number of fours | Any combination | 1 2 3 3 5 scores 0 | +| Fives | 5 × number of fives| Any combination | 5 1 5 2 5 scores 15 | +| Sixes | 6 × number of sixes | Any combination | 2 3 4 5 6 scores 6 | +| Full House | Total of the dice | Three of one number and two of another | 3 3 3 5 5 scores 19 | +| Four of a Kind | Total of the four dice | At least four dice showing the same face | 4 4 4 4 6 scores 16 | +| Little Straight | 30 points | 1-2-3-4-5 | 1 2 3 4 5 scores 30 | +| Big Straight | 30 points | 2-3-4-5-6 | 2 3 4 5 6 scores 30 | +| Choice | Sum of the dice | Any combination | 2 3 3 4 6 scores 18 | +| Yacht | 50 points | All five dice showing the same face | 4 4 4 4 4 scores 50 | + +If the dice do not satisfy the requirements of a category, the score is zero. +If, for example, *Four Of A Kind* is entered in the *Yacht* category, zero points are scored. +A *Yacht* scores zero if entered in the *Full House* category. + +## Task + +Given a list of values for five dice and a category, your solution should return the score of the dice for that category. +If the dice do not satisfy the requirements of the category your solution should return 0. +You can assume that five values will always be presented, and the value of each will be between one and six inclusively. +You should not assume that the dice are ordered. + +[yacht]: https://en.wikipedia.org/wiki/Yacht_(dice_game) diff --git a/exercises/practice/yacht/.gitignore b/exercises/practice/yacht/.gitignore new file mode 100644 index 000000000..e24ae5981 --- /dev/null +++ b/exercises/practice/yacht/.gitignore @@ -0,0 +1,8 @@ +# Generated by Cargo +# Will have compiled files and executables +/target/ +**/*.rs.bk + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock diff --git a/exercises/practice/yacht/.meta/config.json b/exercises/practice/yacht/.meta/config.json new file mode 100644 index 000000000..91d47758d --- /dev/null +++ b/exercises/practice/yacht/.meta/config.json @@ -0,0 +1,20 @@ +{ + "authors": [ + "dem4ron" + ], + "files": { + "solution": [ + "src/lib.rs", + "Cargo.toml" + ], + "test": [ + "tests/yacht.rs" + ], + "example": [ + ".meta/example.rs" + ] + }, + "blurb": "Score a single throw of dice in the game Yacht.", + "source": "James Kilfiger, using wikipedia", + "source_url": "https://en.wikipedia.org/wiki/Yacht_(dice_game)" +} diff --git a/exercises/practice/yacht/.meta/example.rs b/exercises/practice/yacht/.meta/example.rs new file mode 100644 index 000000000..f603fe731 --- /dev/null +++ b/exercises/practice/yacht/.meta/example.rs @@ -0,0 +1,86 @@ +pub enum Category { + Ones, + Twos, + Threes, + Fours, + Fives, + Sixes, + FullHouse, + FourOfAKind, + LittleStraight, + BigStraight, + Choice, + Yacht, +} + +type Dice = [u8; 5]; +pub fn score(dice: Dice, category: Category) -> u8 { + match category { + Category::Ones => get_score(dice, 1), + Category::Twos => get_score(dice, 2), + Category::Threes => get_score(dice, 3), + Category::Fours => get_score(dice, 4), + Category::Fives => get_score(dice, 5), + Category::Sixes => get_score(dice, 6), + Category::FullHouse => get_full_house(dice), + Category::FourOfAKind => get_poker(dice), + Category::LittleStraight => get_straight(dice, 'l'), + Category::BigStraight => get_straight(dice, 'b'), + Category::Choice => dice.iter().sum(), + Category::Yacht => get_yacht(dice), + } +} + +fn collect_scores(dice: Dice) -> [u8; 6] { + dice.iter().fold([0; 6], |mut scores, num| { + scores[(*num - 1) as usize] += 1; + scores + }) +} + +fn get_score(dice: Dice, num: usize) -> u8 { + collect_scores(dice)[num - 1] * num as u8 +} + +fn get_full_house(dice: Dice) -> u8 { + let scores = collect_scores(dice); + let two_of_n = scores.iter().position(|&n| n == 2); + let three_of_n = scores.iter().position(|&n| n == 3); + match (two_of_n, three_of_n) { + (Some(two_index), Some(three_index)) => { + scores[two_index] * (two_index as u8 + 1) + + scores[three_index] * (three_index as u8 + 1) + } + _ => 0, + } +} + +fn get_poker(dice: Dice) -> u8 { + let scores = collect_scores(dice); + match scores.iter().max() { + Some(5) => (scores.iter().position(|&n| n == 5).unwrap() as u8 + 1) * 4, + Some(4) => (scores.iter().position(|&n| n == 4).unwrap() as u8 + 1) * 4, + _ => 0, + } +} + +fn get_straight(dice: Dice, straight_type: char) -> u8 { + let scores = collect_scores(dice); + if !scores.iter().all(|&n| n == 0 || n == 1) { + return 0; + } + let index: usize = if straight_type == 'l' { 5 } else { 0 }; + + if scores[index] == 0 { + 30 + } else { + 0 + } +} + +fn get_yacht(dice: Dice) -> u8 { + match collect_scores(dice).iter().max() { + Some(5) => 50, + _ => 0, + } +} diff --git a/exercises/practice/yacht/.meta/tests.toml b/exercises/practice/yacht/.meta/tests.toml new file mode 100644 index 000000000..b9d920379 --- /dev/null +++ b/exercises/practice/yacht/.meta/tests.toml @@ -0,0 +1,97 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[3060e4a5-4063-4deb-a380-a630b43a84b6] +description = "Yacht" + +[15026df2-f567-482f-b4d5-5297d57769d9] +description = "Not Yacht" + +[36b6af0c-ca06-4666-97de-5d31213957a4] +description = "Ones" + +[023a07c8-6c6e-44d0-bc17-efc5e1b8205a] +description = "Ones, out of order" + +[7189afac-cccd-4a74-8182-1cb1f374e496] +description = "No ones" + +[793c4292-dd14-49c4-9707-6d9c56cee725] +description = "Twos" + +[dc41bceb-d0c5-4634-a734-c01b4233a0c6] +description = "Fours" + +[f6125417-5c8a-4bca-bc5b-b4b76d0d28c8] +description = "Yacht counted as threes" + +[464fc809-96ed-46e4-acb8-d44e302e9726] +description = "Yacht of 3s counted as fives" + +[d054227f-3a71-4565-a684-5c7e621ec1e9] +description = "Fives" + +[e8a036e0-9d21-443a-8b5f-e15a9e19a761] +description = "Sixes" + +[51cb26db-6b24-49af-a9ff-12f53b252eea] +description = "Full house two small, three big" + +[1822ca9d-f235-4447-b430-2e8cfc448f0c] +description = "Full house three small, two big" + +[b208a3fc-db2e-4363-a936-9e9a71e69c07] +description = "Two pair is not a full house" + +[b90209c3-5956-445b-8a0b-0ac8b906b1c2] +description = "Four of a kind is not a full house" + +[32a3f4ee-9142-4edf-ba70-6c0f96eb4b0c] +description = "Yacht is not a full house" + +[b286084d-0568-4460-844a-ba79d71d79c6] +description = "Four of a Kind" + +[f25c0c90-5397-4732-9779-b1e9b5f612ca] +description = "Yacht can be scored as Four of a Kind" + +[9f8ef4f0-72bb-401a-a871-cbad39c9cb08] +description = "Full house is not Four of a Kind" + +[b4743c82-1eb8-4a65-98f7-33ad126905cd] +description = "Little Straight" + +[7ac08422-41bf-459c-8187-a38a12d080bc] +description = "Little Straight as Big Straight" + +[97bde8f7-9058-43ea-9de7-0bc3ed6d3002] +description = "Four in order but not a little straight" + +[cef35ff9-9c5e-4fd2-ae95-6e4af5e95a99] +description = "No pairs but not a little straight" + +[fd785ad2-c060-4e45-81c6-ea2bbb781b9d] +description = "Minimum is 1, maximum is 5, but not a little straight" + +[35bd74a6-5cf6-431a-97a3-4f713663f467] +description = "Big Straight" + +[87c67e1e-3e87-4f3a-a9b1-62927822b250] +description = "Big Straight as little straight" + +[c1fa0a3a-40ba-4153-a42d-32bc34d2521e] +description = "No pairs but not a big straight" + +[207e7300-5d10-43e5-afdd-213e3ac8827d] +description = "Choice" + +[b524c0cf-32d2-4b40-8fb3-be3500f3f135] +description = "Yacht as choice" diff --git a/exercises/practice/yacht/Cargo.toml b/exercises/practice/yacht/Cargo.toml new file mode 100644 index 000000000..138c413e0 --- /dev/null +++ b/exercises/practice/yacht/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "yacht" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/exercises/practice/yacht/src/lib.rs b/exercises/practice/yacht/src/lib.rs new file mode 100644 index 000000000..58a29766d --- /dev/null +++ b/exercises/practice/yacht/src/lib.rs @@ -0,0 +1,19 @@ +pub enum Category { + Ones, + Twos, + Threes, + Fours, + Fives, + Sixes, + FullHouse, + FourOfAKind, + LittleStraight, + BigStraight, + Choice, + Yacht, +} + +type Dice = [u8; 5]; +pub fn score(_dice: Dice, _category: Category) -> u8 { + unimplemented!("Solve the Yacht exercise"); +} diff --git a/exercises/practice/yacht/tests/yacht.rs b/exercises/practice/yacht/tests/yacht.rs new file mode 100644 index 000000000..116b170f0 --- /dev/null +++ b/exercises/practice/yacht/tests/yacht.rs @@ -0,0 +1,203 @@ +use yacht::*; + +#[test] +fn test_yacht() { + let expected = 50; + assert_eq!(score([5, 5, 5, 5, 5], Category::Yacht), expected); +} + +#[test] +#[ignore] +fn test_not_yacht() { + let expected = 0; + assert_eq!(score([1, 3, 3, 2, 5], Category::Yacht), expected); +} + +#[test] +#[ignore] +fn test_ones() { + let expected = 3; + assert_eq!(score([1, 1, 1, 3, 5], Category::Ones), expected); +} + +#[test] +#[ignore] +fn test_ones_out_of_order() { + let expected = 3; + assert_eq!(score([3, 1, 1, 5, 1], Category::Ones), expected); +} + +#[test] +#[ignore] +fn test_no_ones() { + let expected = 0; + assert_eq!(score([4, 3, 6, 5, 5], Category::Ones), expected); +} + +#[test] +#[ignore] +fn test_twos() { + let expected = 2; + assert_eq!(score([2, 3, 4, 5, 6], Category::Twos), expected); +} + +#[test] +#[ignore] +fn test_fours() { + let expected = 8; + assert_eq!(score([1, 4, 1, 4, 1], Category::Fours), expected); +} + +#[test] +#[ignore] +fn test_yacht_counted_as_threes() { + let expected = 15; + assert_eq!(score([3, 3, 3, 3, 3], Category::Threes), expected); +} + +#[test] +#[ignore] +fn test_yacht_of_3s_counted_as_fives() { + let expected = 0; + assert_eq!(score([3, 3, 3, 3, 3], Category::Fives), expected); +} + +#[test] +#[ignore] +fn test_fives() { + let expected = 10; + assert_eq!(score([1, 5, 3, 5, 3], Category::Fives), expected); +} + +#[test] +#[ignore] +fn test_sixes() { + let expected = 6; + assert_eq!(score([2, 3, 4, 5, 6], Category::Sixes), expected); +} + +#[test] +#[ignore] +fn test_full_house_two_small_three_big() { + let expected = 16; + assert_eq!(score([2, 2, 4, 4, 4], Category::FullHouse), expected); +} + +#[test] +#[ignore] +fn test_full_house_three_small_two_big() { + let expected = 19; + assert_eq!(score([5, 3, 3, 5, 3], Category::FullHouse), expected); +} + +#[test] +#[ignore] +fn test_two_pair_is_not_a_full_house() { + let expected = 0; + assert_eq!(score([2, 2, 4, 4, 5], Category::FullHouse), expected); +} + +#[test] +#[ignore] +fn test_four_of_a_kind_is_not_a_full_house() { + let expected = 0; + assert_eq!(score([1, 4, 4, 4, 4], Category::FullHouse), expected); +} + +#[test] +#[ignore] +fn test_yacht_is_not_a_full_house() { + let expected = 0; + assert_eq!(score([2, 2, 2, 2, 2], Category::FullHouse), expected); +} + +#[test] +#[ignore] +fn test_four_of_a_kind() { + let expected = 24; + assert_eq!(score([6, 6, 4, 6, 6], Category::FourOfAKind), expected); +} + +#[test] +#[ignore] +fn test_yacht_can_be_scored_as_four_of_a_kind() { + let expected = 12; + assert_eq!(score([3, 3, 3, 3, 3], Category::FourOfAKind), expected); +} + +#[test] +#[ignore] +fn test_full_house_is_not_four_of_a_kind() { + let expected = 0; + assert_eq!(score([3, 3, 3, 5, 5], Category::FourOfAKind), expected); +} + +#[test] +#[ignore] +fn test_little_straight() { + let expected = 30; + assert_eq!(score([3, 5, 4, 1, 2], Category::LittleStraight), expected); +} + +#[test] +#[ignore] +fn test_little_straight_as_big_straight() { + let expected = 0; + assert_eq!(score([1, 2, 3, 4, 5], Category::BigStraight), expected); +} + +#[test] +#[ignore] +fn test_four_in_order_but_not_a_little_straight() { + let expected = 0; + assert_eq!(score([1, 1, 2, 3, 4], Category::LittleStraight), expected); +} + +#[test] +#[ignore] +fn test_no_pairs_but_not_a_little_straight() { + let expected = 0; + assert_eq!(score([1, 2, 3, 4, 6], Category::LittleStraight), expected); +} + +#[test] +#[ignore] +fn test_minimum_is_1_maximum_is_5_but_not_a_little_straight() { + let expected = 0; + assert_eq!(score([1, 1, 3, 4, 5], Category::LittleStraight), expected); +} + +#[test] +#[ignore] +fn test_big_straight() { + let expected = 30; + assert_eq!(score([4, 6, 2, 5, 3], Category::BigStraight), expected); +} + +#[test] +#[ignore] +fn test_big_straight_as_little_straight() { + let expected = 0; + assert_eq!(score([6, 5, 4, 3, 2], Category::LittleStraight), expected); +} + +#[test] +#[ignore] +fn test_no_pairs_but_not_a_big_straight() { + let expected = 0; + assert_eq!(score([6, 5, 4, 3, 1], Category::BigStraight), expected); +} + +#[test] +#[ignore] +fn test_choice() { + let expected = 23; + assert_eq!(score([3, 3, 5, 6, 6], Category::Choice), expected); +} + +#[test] +#[ignore] +fn test_yacht_as_choice() { + let expected = 10; + assert_eq!(score([2, 2, 2, 2, 2], Category::Choice), expected); +} From ef74a61dfd7783037df1e4eb801772008eb17182 Mon Sep 17 00:00:00 2001 From: victor-prokhorov Date: Tue, 11 Apr 2023 21:42:41 +0200 Subject: [PATCH 129/436] knapsack accept slices (#1680) * knapsack accept slices * knapsack example accept slices * Update exercises/practice/knapsack/tests/knapsack.rs Co-authored-by: Aron Demeter <66035744+dem4ron@users.noreply.github.com> * knapsack use array instead of vector * knapsack meta add contributors --------- Co-authored-by: Aron Demeter <66035744+dem4ron@users.noreply.github.com> --- exercises/practice/knapsack/.meta/config.json | 3 ++ exercises/practice/knapsack/.meta/example.rs | 2 +- exercises/practice/knapsack/src/lib.rs | 2 +- exercises/practice/knapsack/tests/knapsack.rs | 28 +++++++++---------- 4 files changed, 19 insertions(+), 16 deletions(-) diff --git a/exercises/practice/knapsack/.meta/config.json b/exercises/practice/knapsack/.meta/config.json index 760ae0258..8560394a0 100644 --- a/exercises/practice/knapsack/.meta/config.json +++ b/exercises/practice/knapsack/.meta/config.json @@ -2,6 +2,9 @@ "authors": [ "dem4ron" ], + "contributors": [ + "victor-prokhorov" + ], "files": { "solution": [ "src/lib.rs", diff --git a/exercises/practice/knapsack/.meta/example.rs b/exercises/practice/knapsack/.meta/example.rs index 74f431bbf..085f21358 100644 --- a/exercises/practice/knapsack/.meta/example.rs +++ b/exercises/practice/knapsack/.meta/example.rs @@ -3,7 +3,7 @@ pub struct Item { pub value: u32, } -pub fn maximum_value(max_weight: u32, items: Vec) -> u32 { +pub fn maximum_value(max_weight: u32, items: &[Item]) -> u32 { let mut max_values = vec![vec![0; (max_weight + 1) as usize]; items.len() + 1]; for i in 1..=items.len() { diff --git a/exercises/practice/knapsack/src/lib.rs b/exercises/practice/knapsack/src/lib.rs index f55b38500..6f156ccbf 100644 --- a/exercises/practice/knapsack/src/lib.rs +++ b/exercises/practice/knapsack/src/lib.rs @@ -3,6 +3,6 @@ pub struct Item { pub value: u32, } -pub fn maximum_value(_max_weight: u32, _items: Vec) -> u32 { +pub fn maximum_value(_max_weight: u32, _items: &[Item]) -> u32 { unimplemented!("Solve the knapsack exercise"); } diff --git a/exercises/practice/knapsack/tests/knapsack.rs b/exercises/practice/knapsack/tests/knapsack.rs index 1432f7474..b351715bd 100644 --- a/exercises/practice/knapsack/tests/knapsack.rs +++ b/exercises/practice/knapsack/tests/knapsack.rs @@ -3,7 +3,7 @@ use knapsack::*; #[test] fn test_example_knapsack() { let max_weight = 10; - let items = vec![ + let items = [ Item { weight: 5, value: 10, @@ -22,35 +22,35 @@ fn test_example_knapsack() { }, ]; - assert_eq!(maximum_value(max_weight, items), 90); + assert_eq!(maximum_value(max_weight, &items), 90); } #[test] #[ignore] fn test_no_items() { let max_weight = 100; - let items = vec![]; + let items = []; - assert_eq!(maximum_value(max_weight, items), 0); + assert_eq!(maximum_value(max_weight, &items), 0); } #[test] #[ignore] fn test_one_item_too_heavy() { let max_weight = 10; - let items = vec![Item { + let items = [Item { weight: 100, value: 1, }]; - assert_eq!(maximum_value(max_weight, items), 0); + assert_eq!(maximum_value(max_weight, &items), 0); } #[test] #[ignore] fn test_five_items_cannot_be_greedy_by_weight() { let max_weight = 10; - let items = vec![ + let items = [ Item { weight: 2, value: 5, @@ -73,14 +73,14 @@ fn test_five_items_cannot_be_greedy_by_weight() { }, ]; - assert_eq!(maximum_value(max_weight, items), 21); + assert_eq!(maximum_value(max_weight, &items), 21); } #[test] #[ignore] fn test_five_items_cannot_be_greedy_by_value() { let max_weight = 10; - let items = vec![ + let items = [ Item { weight: 2, value: 20, @@ -103,14 +103,14 @@ fn test_five_items_cannot_be_greedy_by_value() { }, ]; - assert_eq!(maximum_value(max_weight, items), 80); + assert_eq!(maximum_value(max_weight, &items), 80); } #[test] #[ignore] fn test_8_items() { let max_weight = 104; - let items = vec![ + let items = [ Item { weight: 25, value: 350, @@ -145,14 +145,14 @@ fn test_8_items() { }, ]; - assert_eq!(maximum_value(max_weight, items), 900); + assert_eq!(maximum_value(max_weight, &items), 900); } #[test] #[ignore] fn test_15_items() { let max_weight = 750; - let items = vec![ + let items = [ Item { weight: 70, value: 135, @@ -215,5 +215,5 @@ fn test_15_items() { }, ]; - assert_eq!(maximum_value(max_weight, items), 1458); + assert_eq!(maximum_value(max_weight, &items), 1458); } From da2cfb123e9057638eb746832ef06c7bdc788d96 Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Tue, 16 May 2023 10:25:09 +0200 Subject: [PATCH 130/436] Sync etl docs with problem-specifications (#1674) The etl exercise has been overhauled as part of a project to make practice exercises more consistent and friendly. For more context, please see the discussion in the forum, as well as the pull request that updated the exercise in the problem-specifications repository: - https://forum.exercism.org/t/new-project-making-practice-exercises-more-consistent-and-human-across-exercism/3943 - https://github.com/exercism/problem-specifications/pull/2250 --- exercises/practice/etl/.docs/instructions.md | 36 +++++--------------- exercises/practice/etl/.docs/introduction.md | 16 +++++++++ 2 files changed, 24 insertions(+), 28 deletions(-) create mode 100644 exercises/practice/etl/.docs/introduction.md diff --git a/exercises/practice/etl/.docs/instructions.md b/exercises/practice/etl/.docs/instructions.md index ff96906c6..802863b54 100644 --- a/exercises/practice/etl/.docs/instructions.md +++ b/exercises/practice/etl/.docs/instructions.md @@ -1,21 +1,8 @@ # Instructions -We are going to do the `Transform` step of an Extract-Transform-Load. +Your task is to change the data format of letters and their point values in the game. -## ETL - -Extract-Transform-Load (ETL) is a fancy way of saying, "We have some crufty, legacy data over in this system, and now we need it in this shiny new system over here, so -we're going to migrate this." - -(Typically, this is followed by, "We're only going to need to run this -once." That's then typically followed by much forehead slapping and -moaning about how stupid we could possibly be.) - -## The goal - -We're going to extract some Scrabble scores from a legacy system. - -The old system stored a list of letters per score: +Currently, letters are stored in groups based on their score, in a one-to-many mapping. - 1 point: "A", "E", "I", "O", "U", "L", "N", "R", "S", "T", - 2 points: "D", "G", @@ -25,23 +12,16 @@ The old system stored a list of letters per score: - 8 points: "J", "X", - 10 points: "Q", "Z", -The shiny new Scrabble system instead stores the score per letter, which -makes it much faster and easier to calculate the score for a word. It -also stores the letters in lower-case regardless of the case of the -input letters: +This needs to be changed to store each individual letter with its score in a one-to-one mapping. - "a" is worth 1 point. - "b" is worth 3 points. - "c" is worth 3 points. - "d" is worth 2 points. -- Etc. - -Your mission, should you choose to accept it, is to transform the legacy data -format to the shiny new format. +- etc. -## Notes +As part of this change, the team has also decided to change the letters to be lower-case rather than upper-case. -A final note about scoring, Scrabble is played around the world in a -variety of languages, each with its own unique scoring table. For -example, an "E" is scored at 2 in the Māori-language version of the -game while being scored at 4 in the Hawaiian-language version. +~~~~exercism/note +If you want to look at how the data was previously structured and how it needs to change, take a look at the examples in the test suite. +~~~~ diff --git a/exercises/practice/etl/.docs/introduction.md b/exercises/practice/etl/.docs/introduction.md new file mode 100644 index 000000000..5be65147d --- /dev/null +++ b/exercises/practice/etl/.docs/introduction.md @@ -0,0 +1,16 @@ +# Introduction + +You work for a company that makes an online multiplayer game called Lexiconia. + +To play the game, each player is given 13 letters, which they must rearrange to create words. +Different letters have different point values, since it's easier to create words with some letters than others. + +The game was originally launched in English, but it is very popular, and now the company wants to expand to other languages as well. + +Different languages need to support different point values for letters. +The point values are determined by how often letters are used, compared to other letters in that language. + +For example, the letter 'C' is quite common in English, and is only worth 3 points. +But in Norwegian it's a very rare letter, and is worth 10 points. + +To make it easier to add new languages, your team needs to change the way letters and their point values are stored in the game. From 43e087e47673de3bb6c45aadb42ca2d8191f321b Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Tue, 16 May 2023 10:31:38 +0200 Subject: [PATCH 131/436] Sync word-count docs with problem-specifications (#1677) The word-count exercise has been overhauled as part of a project to make practice exercises more consistent and friendly. For more context, please see the discussion in the forum, as well as the pull request that updated the exercise in the problem-specifications repository: - https://forum.exercism.org/t/new-project-making-practice-exercises-more-consistent-and-human-across-exercism/3943 - https://github.com/exercism/problem-specifications/pull/2247 --- .../practice/word-count/.docs/instructions.md | 61 +++++++++++-------- .../practice/word-count/.docs/introduction.md | 8 +++ 2 files changed, 42 insertions(+), 27 deletions(-) create mode 100644 exercises/practice/word-count/.docs/introduction.md diff --git a/exercises/practice/word-count/.docs/instructions.md b/exercises/practice/word-count/.docs/instructions.md index 1221efd38..064393c8a 100644 --- a/exercises/practice/word-count/.docs/instructions.md +++ b/exercises/practice/word-count/.docs/instructions.md @@ -1,40 +1,47 @@ # Instructions -Given a phrase, count the occurrences of each _word_ in that phrase. +Your task is to count how many times each word occurs in a subtitle of a drama. -For the purposes of this exercise you can expect that a _word_ will always be one of: +The subtitles from these dramas use only ASCII characters. -1. A _number_ composed of one or more ASCII digits (i.e. "0" or "1234") OR -2. A _simple word_ composed of one or more ASCII letters (i.e. "a" or "they") OR -3. A _contraction_ of two _simple words_ joined by a single apostrophe (i.e. "it's" or "they're") +The characters often speak in casual English, using contractions like _they're_ or _it's_. +Though these contractions come from two words (e.g. _we are_), the contraction (_we're_) is considered a single word. -When counting words you can assume the following rules: +Words can be separated by any form of punctuation (e.g. ":", "!", or "?") or whitespace (e.g. "\t", "\n", or " "). +The only punctuation that does not separate words is the apostrophe in contractions. -1. The count is _case insensitive_ (i.e. "You", "you", and "YOU" are 3 uses of the same word) -2. The count is _unordered_; the tests will ignore how words and counts are ordered -3. Other than the apostrophe in a _contraction_ all forms of _punctuation_ are ignored -4. The words can be separated by _any_ form of whitespace (i.e. "\t", "\n", " "), or - external punctuation. +Numbers are considered words. +If the subtitles say _It costs 100 dollars._ then _100_ will be its own word. -For example, for the phrase `"That's the password: 'PASSWORD 123'!", cried the Special Agent.\nSo I fled.` the count would be: +Words are case insensitive. +For example, the word _you_ occurs three times in the following sentence: + +> You come back, you hear me? DO YOU HEAR ME? + +The ordering of the word counts in the results doesn't matter. + +Here's an example that incorporates several of the elements discussed above: + +- simple words +- contractions +- numbers +- case insensitive words +- punctuation (including apostrophes) to separate words +- different forms of whitespace to separate words + +`"That's the password: 'PASSWORD 123'!", cried the Special Agent.\nSo I fled.` + +The mapping for this subtitle would be: ```text -that's: 1 -the: 2 -password: 2 123: 1 -cried: 1 -special: 1 agent: 1 -so: 1 -i: 1 +cried: 1 fled: 1 -``` - -For the phrase `"one,two,three"` the count would be: - -```text -one: 1 -two: 1 -three: 1 +i: 1 +password: 2 +so: 1 +special: 1 +that's: 1 +the: 2 ``` diff --git a/exercises/practice/word-count/.docs/introduction.md b/exercises/practice/word-count/.docs/introduction.md new file mode 100644 index 000000000..1654508e7 --- /dev/null +++ b/exercises/practice/word-count/.docs/introduction.md @@ -0,0 +1,8 @@ +# Introduction + +You teach English as a foreign language to high school students. + +You've decided to base your entire curriculum on TV shows. +You need to analyze which words are used, and how often they're repeated. + +This will let you choose the simplest shows to start with, and to gradually increase the difficulty as time passes. From 64298fd730cd4b2999bd1d9984d0eebd34095e78 Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Tue, 16 May 2023 10:37:39 +0200 Subject: [PATCH 132/436] Sync sum-of-multiples docs with problem-specifications (#1681) * Sync sum-of-multiples docs with problem-specifications The sum-of-multiples exercise has been overhauled as part of a project to make practice exercises more consistent and friendly. For more context, please see the discussion in the forum, as well as the pull request that updated the exercise in the problem-specifications repository: - https://forum.exercism.org/t/new-project-making-practice-exercises-more-consistent-and-human-across-exercism/3943 - https://github.com/exercism/problem-specifications/pull/2249 * Run configlet sync on sum-of-multiples --- .../sum-of-multiples/.docs/instructions.md | 29 ++++++++++++------- .../sum-of-multiples/.docs/introduction.md | 6 ++++ 2 files changed, 25 insertions(+), 10 deletions(-) create mode 100644 exercises/practice/sum-of-multiples/.docs/introduction.md diff --git a/exercises/practice/sum-of-multiples/.docs/instructions.md b/exercises/practice/sum-of-multiples/.docs/instructions.md index 7b7ec006e..d69f890e9 100644 --- a/exercises/practice/sum-of-multiples/.docs/instructions.md +++ b/exercises/practice/sum-of-multiples/.docs/instructions.md @@ -1,18 +1,27 @@ # Instructions -Given a list of factors and a limit, add up all the unique multiples of the factors that are less than the limit. -All inputs will be greater than or equal to zero. +Your task is to write the code that calculates the energy points that get awarded to players when they complete a level. -## Example +The points awarded depend on two things: -Suppose the limit is 20 and the list of factors is [3, 5]. -We need to find the sum of all unique multiples of 3 and 5 that are less than 20. +- The level (a number) that the player completed. +- The base value of each magical item collected by the player during that level. -Multiples of 3 less than 20: 3, 6, 9, 12, 15, 18 -Multiples of 5 less than 20: 5, 10, 15 +The energy points are awarded according to the following rules: -The unique multiples are: 3, 5, 6, 9, 10, 12, 15, 18 +1. For each magical item, take the base value and find all the multiples of that value that are less than the level number. +2. Combine the sets of numbers. +3. Remove any duplicates. +4. Calculate the sum of all the numbers that are left. -The sum of the unique multiples is: 3 + 5 + 6 + 9 + 10 + 12 + 15 + 18 = 78 +Let's look at an example: -So, the answer is 78. +**The player completed level 20 and found two magical items with base values of 3 and 5.** + +To calculate the energy points earned by the player, we need to find all the unique multiples of these base values that are less than level 20. + +- Multiples of 3 less than 20: `{3, 6, 9, 12, 15, 18}` +- Multiples of 5 less than 20: `{5, 10, 15}` +- Combine the sets and remove duplicates: `{3, 5, 6, 9, 10, 12, 15, 18}` +- Sum the unique multiples: `3 + 5 + 6 + 9 + 10 + 12 + 15 + 18 = 78` +- Therefore, the player earns **78** energy points for completing level 20 and finding the two magical items with base values of 3 and 5. diff --git a/exercises/practice/sum-of-multiples/.docs/introduction.md b/exercises/practice/sum-of-multiples/.docs/introduction.md new file mode 100644 index 000000000..69cabeed5 --- /dev/null +++ b/exercises/practice/sum-of-multiples/.docs/introduction.md @@ -0,0 +1,6 @@ +# Introduction + +You work for a company that makes an online, fantasy-survival game. + +When a player finishes a level, they are awarded energy points. +The amount of energy awarded depends on which magical items the player found while exploring that level. From d7a01bf5e0b2466748b9266b1c92194b1d4fb9d9 Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Tue, 16 May 2023 11:43:11 +0200 Subject: [PATCH 133/436] Sync saddle-points docs with problem-specifications (#1686) The saddle-points exercise has been overhauled as part of a project to make practice exercises more consistent and friendly. For more context, please see the discussion in the forum, as well as the pull request that updated the exercise in the problem-specifications repository: - https://forum.exercism.org/t/new-project-making-practice-exercises-more-consistent-and-human-across-exercism/3943 --- .../saddle-points/.docs/instructions.md | 35 ++++++++----------- .../saddle-points/.docs/introduction.md | 9 +++++ 2 files changed, 24 insertions(+), 20 deletions(-) create mode 100644 exercises/practice/saddle-points/.docs/introduction.md diff --git a/exercises/practice/saddle-points/.docs/instructions.md b/exercises/practice/saddle-points/.docs/instructions.md index aa11e0571..d861388e4 100644 --- a/exercises/practice/saddle-points/.docs/instructions.md +++ b/exercises/practice/saddle-points/.docs/instructions.md @@ -1,29 +1,24 @@ # Instructions -Detect saddle points in a matrix. +Your task is to find the potential trees where you could build your tree house. -So say you have a matrix like so: +The data company provides the data as grids that show the heights of the trees. +The rows of the grid represent the east-west direction, and the columns represent the north-south direction. -```text - 1 2 3 - |--------- -1 | 9 8 7 -2 | 5 3 2 <--- saddle point at column 1, row 2, with value 5 -3 | 6 6 7 -``` +An acceptable tree will be the the largest in its row, while being the smallest in its column. -It has a saddle point at column 1, row 2. +A grid might not have any good trees at all. +Or it might have one, or even several. -It's called a "saddle point" because it is greater than or equal to -every element in its row and less than or equal to every element in -its column. +Here is a grid that has exactly one candidate tree. -A matrix may have zero or more saddle points. + 1 2 3 4 + |----------- +1 | 9 8 7 8 +2 | 5 3 2 4 <--- potential tree house at row 2, column 1, for tree with height 5 +3 | 6 6 7 1 -Your code should be able to provide the (possibly empty) list of all the -saddle points for any given matrix. +- Row 2 has values 5, 3, and 1. The largest value is 5. +- Column 1 has values 9, 5, and 6. The smallest value is 5. -The matrix can have a different number of rows and columns (Non square). - -Note that you may find other definitions of matrix saddle points online, -but the tests for this exercise follow the above unambiguous definition. +So the point at `[2, 1]` (row: 2, column: 1) is a great spot for a tree house. diff --git a/exercises/practice/saddle-points/.docs/introduction.md b/exercises/practice/saddle-points/.docs/introduction.md new file mode 100644 index 000000000..b582efbd2 --- /dev/null +++ b/exercises/practice/saddle-points/.docs/introduction.md @@ -0,0 +1,9 @@ +# Introduction + +You are planning on building a tree house in the woods near your house so that you can watch the sun rise and set. + +You've obtained data from a local survey company that shows the heights of all the trees in each rectangular section of the map. +You need to analyze each grid on the map to find the perfect tree for your tree house. + +The best tree will be the tallest tree compared to all the other trees to the east and west, so that you have the best possible view of the sunrises and sunsets. +You don't like climbing too much, so the perfect tree will also be the shortest among all the trees to the north and to the south. From a24bd6442657327e2e0a62101fc5b199faacb116 Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Tue, 16 May 2023 11:59:15 +0200 Subject: [PATCH 134/436] Sync simple-linked-list docs with problem-specifications (#1688) * Sync simple-linked-list docs with problem-specifications The simple-linked-list exercise has been overhauled as part of a project to make practice exercises more consistent and friendly. For more context, please see the discussion in the forum, as well as the pull request that updated the exercise in the problem-specifications repository: - https://forum.exercism.org/t/new-project-making-practice-exercises-more-consistent-and-human-across-exercism/3943 * Sync simple-linked-list docs with problem-specifications The simple-linked-list exercise has been overhauled as part of a project to make practice exercises more consistent and friendly. For more context, please see the discussion in the forum, as well as the pull request that updated the exercise in the problem-specifications repository: - https://forum.exercism.org/t/new-project-making-practice-exercises-more-consistent-and-human-across-exercism/3943 --- .../simple-linked-list/.docs/instructions.md | 27 +++++++++---------- .../simple-linked-list/.docs/introduction.md | 5 ++++ 2 files changed, 17 insertions(+), 15 deletions(-) create mode 100644 exercises/practice/simple-linked-list/.docs/introduction.md diff --git a/exercises/practice/simple-linked-list/.docs/instructions.md b/exercises/practice/simple-linked-list/.docs/instructions.md index 1c9d0b3de..04640b1fb 100644 --- a/exercises/practice/simple-linked-list/.docs/instructions.md +++ b/exercises/practice/simple-linked-list/.docs/instructions.md @@ -1,22 +1,19 @@ # Instructions -Write a simple linked list implementation that uses Elements and a List. +Write a prototype of the music player application. -The linked list is a fundamental data structure in computer science, -often used in the implementation of other data structures. They're -pervasive in functional programming languages, such as Clojure, Erlang, -or Haskell, but far less common in imperative languages such as Ruby or -Python. +For the prototype, each song will simply be represented by a number. +Given a range of numbers (the song IDs), create a singly linked list. -The simplest kind of linked list is a singly linked list. Each element in the -list contains data and a "next" field pointing to the next element in the list -of elements. +Given a singly linked list, you should be able to reverse the list to play the songs in the opposite order. -This variant of linked lists is often used to represent sequences or -push-down stacks (also called a LIFO stack; Last In, First Out). +~~~~exercism/note +The linked list is a fundamental data structure in computer science, often used in the implementation of other data structures. -As a first take, lets create a singly linked list to contain the range (1..10), -and provide functions to reverse a linked list and convert to and from arrays. +The simplest kind of linked list is a **singly** linked list. +That means that each element (or "node") contains data, along with something that points to the next node in the list. -When implementing this in a language with built-in linked lists, -implement your own abstract data type. +If you want to dig deeper into linked lists, check out [this article][intro-linked-list] that explains it using nice drawings. + +[intro-linked-list]: https://medium.com/basecs/whats-a-linked-list-anyway-part-1-d8b7e6508b9d +~~~~ diff --git a/exercises/practice/simple-linked-list/.docs/introduction.md b/exercises/practice/simple-linked-list/.docs/introduction.md new file mode 100644 index 000000000..0e1df72f9 --- /dev/null +++ b/exercises/practice/simple-linked-list/.docs/introduction.md @@ -0,0 +1,5 @@ +# Introduction + +You work for a music streaming company. + +You've been tasked with creating a playlist feature for your music player application. From 7e7d31ad280f2d72658125a273d1500beac2f97b Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Tue, 16 May 2023 11:59:28 +0200 Subject: [PATCH 135/436] Sync largest-series-product docs with problem-specifications (#1683) * Sync largest-series-product docs with problem-specifications The largest-series-product exercise has been overhauled as part of a project to make practice exercises more consistent and friendly. For more context, please see the discussion in the forum, as well as the pull request that updated the exercise in the problem-specifications repository: - https://forum.exercism.org/t/new-project-making-practice-exercises-more-consistent-and-human-across-exercism/3943 - https://github.com/exercism/problem-specifications/pull/2246 * Delete test cases from largest-series-product This deletes two deprecated test cases so that we can dramatically simplify the instructions for this exercise. --- .../.docs/instructions.md | 30 +++++++++++++------ .../.docs/introduction.md | 5 ++++ .../tests/largest-series-product.rs | 24 --------------- 3 files changed, 26 insertions(+), 33 deletions(-) create mode 100644 exercises/practice/largest-series-product/.docs/introduction.md diff --git a/exercises/practice/largest-series-product/.docs/instructions.md b/exercises/practice/largest-series-product/.docs/instructions.md index 8ddbc6024..f297b57f7 100644 --- a/exercises/practice/largest-series-product/.docs/instructions.md +++ b/exercises/practice/largest-series-product/.docs/instructions.md @@ -1,14 +1,26 @@ # Instructions -Given a string of digits, calculate the largest product for a contiguous -substring of digits of length n. +Your task is to look for patterns in the long sequence of digits in the encrypted signal. -For example, for the input `'1027839564'`, the largest product for a -series of 3 digits is 270 (9 * 5 * 6), and the largest product for a -series of 5 digits is 7560 (7 * 8 * 3 * 9 * 5). +The technique you're going to use here is called the largest series product. -Note that these series are only required to occupy *adjacent positions* -in the input; the digits need not be *numerically consecutive*. +Let's define a few terms, first. -For the input `'73167176531330624919225119674426574742355349194934'`, -the largest product for a series of 6 digits is 23520. +- **input**: the sequence of digits that you need to analyze +- **series**: a sequence of adjacent digits (those that are next to each other) that is contained within the input +- **span**: how many digits long each series is +- **product**: what you get when you multiply numbers together + +Let's work through an example, with the input `"63915"`. + +- To form a series, take adjacent digits in the original input. +- If you are working with a span of `3`, there will be three possible series: + - `"639"` + - `"391"` + - `"915"` +- Then we need to calculate the product of each series: + - The product of the series `"639"` is 162 (`6 × 3 × 9 = 162`) + - The product of the series `"391"` is 27 (`3 × 9 × 1 = 27`) + - The product of the series `"915"` is 45 (`9 × 1 × 5 = 45`) +- 162 is bigger than both 27 and 45, so the largest series product of `"63915"` is from the series `"639"`. + So the answer is **162**. diff --git a/exercises/practice/largest-series-product/.docs/introduction.md b/exercises/practice/largest-series-product/.docs/introduction.md new file mode 100644 index 000000000..597bb5fa1 --- /dev/null +++ b/exercises/practice/largest-series-product/.docs/introduction.md @@ -0,0 +1,5 @@ +# Introduction + +You work for a government agency that has intercepted a series of encrypted communication signals from a group of bank robbers. +The signals contain a long sequence of digits. +Your team needs to use various digital signal processing techniques to analyze the signals and identify any patterns that may indicate the planning of a heist. diff --git a/exercises/practice/largest-series-product/tests/largest-series-product.rs b/exercises/practice/largest-series-product/tests/largest-series-product.rs index 4811071d0..c40c75b02 100644 --- a/exercises/practice/largest-series-product/tests/largest-series-product.rs +++ b/exercises/practice/largest-series-product/tests/largest-series-product.rs @@ -68,30 +68,6 @@ fn a_span_is_longer_than_number_is_an_error() { assert_eq!(Err(Error::SpanTooLong), lsp("123", 4)); } -// There may be some confusion about whether this should be 1 or error. -// The reasoning for it being 1 is this: -// There is one 0-character string contained in the empty string. -// That's the empty string itself. -// The empty product is 1 (the identity for multiplication). -// Therefore LSP('', 0) is 1. -// It's NOT the case that LSP('', 0) takes max of an empty list. -// So there is no error. -// Compare against LSP('123', 4): -// There are zero 4-character strings in '123'. -// So LSP('123', 4) really DOES take the max of an empty list. -// So LSP('123', 4) errors and LSP('', 0) does NOT. -#[test] -#[ignore] -fn an_empty_string_and_no_span_returns_one() { - assert_eq!(Ok(1), lsp("", 0)); -} - -#[test] -#[ignore] -fn a_non_empty_string_and_no_span_returns_one() { - assert_eq!(Ok(1), lsp("123", 0)); -} - #[test] #[ignore] fn empty_string_and_non_zero_span_is_an_error() { From 2b3d4f91eec7624938fd73e60065347c5ab1c258 Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Tue, 16 May 2023 12:01:18 +0200 Subject: [PATCH 136/436] Sync rna-transcription docs with problem-specifications (#1684) The rna-transcription exercise has been overhauled as part of a project to make practice exercises more consistent and friendly. For more context, please see the discussion in the forum, as well as the pull request that updated the exercise in the problem-specifications repository: - https://forum.exercism.org/t/new-project-making-practice-exercises-more-consistent-and-human-across-exercism/3943 - https://github.com/exercism/problem-specifications/pull/2251 --- .../rna-transcription/.docs/instructions.md | 23 ++++++++++--------- .../rna-transcription/.docs/introduction.md | 16 +++++++++++++ 2 files changed, 28 insertions(+), 11 deletions(-) create mode 100644 exercises/practice/rna-transcription/.docs/introduction.md diff --git a/exercises/practice/rna-transcription/.docs/instructions.md b/exercises/practice/rna-transcription/.docs/instructions.md index 9e86efea9..36da381f5 100644 --- a/exercises/practice/rna-transcription/.docs/instructions.md +++ b/exercises/practice/rna-transcription/.docs/instructions.md @@ -1,19 +1,20 @@ # Instructions -Given a DNA strand, return its RNA complement (per RNA transcription). +Your task is determine the RNA complement of a given DNA sequence. Both DNA and RNA strands are a sequence of nucleotides. -The four nucleotides found in DNA are adenine (**A**), cytosine (**C**), -guanine (**G**) and thymine (**T**). +The four nucleotides found in DNA are adenine (**A**), cytosine (**C**), guanine (**G**) and thymine (**T**). -The four nucleotides found in RNA are adenine (**A**), cytosine (**C**), -guanine (**G**) and uracil (**U**). +The four nucleotides found in RNA are adenine (**A**), cytosine (**C**), guanine (**G**) and uracil (**U**). -Given a DNA strand, its transcribed RNA strand is formed by replacing -each nucleotide with its complement: +Given a DNA strand, its transcribed RNA strand is formed by replacing each nucleotide with its complement: -* `G` -> `C` -* `C` -> `G` -* `T` -> `A` -* `A` -> `U` +- `G` -> `C` +- `C` -> `G` +- `T` -> `A` +- `A` -> `U` + +~~~~exercism/note +If you want to look at how the inputs and outputs are structured, take a look at the examples in the test suite. +~~~~ diff --git a/exercises/practice/rna-transcription/.docs/introduction.md b/exercises/practice/rna-transcription/.docs/introduction.md new file mode 100644 index 000000000..6b3f44b53 --- /dev/null +++ b/exercises/practice/rna-transcription/.docs/introduction.md @@ -0,0 +1,16 @@ +# Introduction + +You work for a bioengineering company that specializes in developing therapeutic solutions. + +Your team has just been given a new project to develop a targeted therapy for a rare type of cancer. + +~~~~exercism/note +It's all very complicated, but the basic idea is that sometimes people's bodies produce too much of a given protein. +That can cause all sorts of havoc. + +But if you can create a very specific molecule (called a micro-RNA), it can prevent the protein from being produced. + +This technique is called [RNA Interference][rnai]. + +[rnai]: https://admin.acceleratingscience.com/ask-a-scientist/what-is-rnai/ +~~~~ From dd0ea4e6c165ebe67d8d2257333cc9bd4e9c6a4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Machist=C3=A9=20N=2E=20Quintana?= Date: Fri, 2 Jun 2023 02:33:32 -0400 Subject: [PATCH 137/436] Clarify constructor return types for RNA Transcription exercise (#1695) --- .../practice/rna-transcription/.docs/instructions.append.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/exercises/practice/rna-transcription/.docs/instructions.append.md b/exercises/practice/rna-transcription/.docs/instructions.append.md index 05c469325..ae0f7abed 100644 --- a/exercises/practice/rna-transcription/.docs/instructions.append.md +++ b/exercises/practice/rna-transcription/.docs/instructions.append.md @@ -7,3 +7,7 @@ string has a valid RNA string, we don't need to return a `Result`/`Option` from `into_rna`. This explains the type signatures you will see in the tests. + +The return types of both `DNA::new()` and `RNA::new()` are `Result`, +where the error type `usize` represents the index of the first invalid character +(char index, not utf8). From d82d68cb943c72d2926e4d75d8d6ca38d4dc003a Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Tue, 18 Jul 2023 10:32:03 +0200 Subject: [PATCH 138/436] Convert `average_run_time` to an integer. (#1705) There are two reasons for this change: 1. Having the average run time as a float gives the impression of being exact, whereas the actual run time wildly varies due to a wide variety of reasons (e.g. how busy it is on the server). That fractional component will almost never actually conform the real situation. 2. jq is often used to work with track config.json config files (e.g. to add elements to it), and it will remove any trailing .0 fractional part from a number, which caused configlet lint to fail. Those JQ scripts then have to work around this by manually adding .0 to it. --- config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.json b/config.json index 9778c6af1..dd533207c 100644 --- a/config.json +++ b/config.json @@ -16,7 +16,7 @@ "highlightjs_language": "rust" }, "test_runner": { - "average_run_time": 2.0 + "average_run_time": 2 }, "files": { "solution": [ From ae5cc143ccfbf534fca735b64c1fa31b0a46af03 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Jul 2023 17:31:06 +0200 Subject: [PATCH 139/436] Bump actions/checkout from 3.3.0 to 3.5.3 (#1698) Bumps [actions/checkout](https://github.com/actions/checkout) from 3.3.0 to 3.5.3. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/ac593985615ec2ede58e132d2e21d2b1cbd6127c...c85c95e3d7251135ab7dc9ce3241c5835cc595a9) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/tests.yml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7577b6d02..87692839e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -19,7 +19,7 @@ jobs: steps: # Checks out a copy of your repository on the ubuntu-latest machine - name: Checkout code - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: Ensure tool names are snake cased run: ./bin/lint_tool_file_names.sh @@ -49,13 +49,13 @@ jobs: steps: # Checks out default branch locally so that it is available to the scripts. - name: Checkout main - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 with: ref: main # Checks out a copy of your repository on the ubuntu-latest machine - name: Checkout code - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: Fetch configlet run: ./bin/fetch-configlet @@ -69,11 +69,11 @@ jobs: steps: - name: Checkout main - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 with: ref: main - name: Checkout code - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: Run markdown lint run: ./bin/lint_markdown.sh @@ -84,7 +84,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: Run shellcheck uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # v2.0.0 @@ -104,13 +104,13 @@ jobs: steps: # Checks out main locally so that it is available to the scripts. - name: Checkout main - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 with: ref: main # Checks out a copy of your repository on the ubuntu-latest machine - name: Checkout code - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: Setup toolchain uses: actions-rs/toolchain@v1.0.7 @@ -138,7 +138,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: Setup toolchain uses: actions-rs/toolchain@v1.0.7 @@ -169,12 +169,12 @@ jobs: steps: - name: Checkout main - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 with: ref: main - name: Checkout code - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: Setup toolchain uses: actions-rs/toolchain@v1.0.7 @@ -205,13 +205,13 @@ jobs: steps: # Checks out main locally so that it is available to the scripts. - name: Checkout main - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 with: ref: main # Checks out a copy of your repository on the ubuntu-latest machine - name: Checkout code - uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: Setup nightly toolchain uses: actions-rs/toolchain@v1.0.7 From 433c48abdc14e09461effcaea6382ca84b59c07f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Fischer?= Date: Thu, 20 Jul 2023 05:36:21 -0400 Subject: [PATCH 140/436] Added digit 9 test case from the canonical test suite (#1655) 9 is an edge case that is very important to keep testing in the advanced Luhn iteration exercises --- exercises/practice/luhn-from/tests/luhn-from.rs | 6 ++++++ exercises/practice/luhn-trait/tests/luhn-trait.rs | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/exercises/practice/luhn-from/tests/luhn-from.rs b/exercises/practice/luhn-from/tests/luhn-from.rs index a6224b646..a94e16da4 100644 --- a/exercises/practice/luhn-from/tests/luhn-from.rs +++ b/exercises/practice/luhn-from/tests/luhn-from.rs @@ -97,3 +97,9 @@ fn invalid_credit_card_is_invalid() { fn strings_that_contain_non_digits_are_invalid() { assert!(!Luhn::from("046a 454 286").is_valid()); } + +#[test] +#[ignore] +fn test_input_digit_9_is_still_correctly_converted_to_output_digit_9() { + assert!(Luhn::from("091").is_valid()); +} diff --git a/exercises/practice/luhn-trait/tests/luhn-trait.rs b/exercises/practice/luhn-trait/tests/luhn-trait.rs index 1fbc60fad..bc5bd9cc4 100644 --- a/exercises/practice/luhn-trait/tests/luhn-trait.rs +++ b/exercises/practice/luhn-trait/tests/luhn-trait.rs @@ -55,3 +55,9 @@ fn you_can_validate_from_a_usize() { assert!(valid.valid_luhn()); assert!(!invalid.valid_luhn()); } + +#[test] +#[ignore] +fn test_input_digit_9_is_still_correctly_converted_to_output_digit_9() { + assert!("091".valid_luhn()); +} From e9d19064e65cca18faaa8f9e7397e70cf0a02bd9 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Thu, 20 Jul 2023 12:38:50 +0200 Subject: [PATCH 141/436] Fix typo (#1706) [no important files changed] --- exercises/practice/doubly-linked-list/src/pre_implemented.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/doubly-linked-list/src/pre_implemented.rs b/exercises/practice/doubly-linked-list/src/pre_implemented.rs index 8e2d09070..03d145587 100644 --- a/exercises/practice/doubly-linked-list/src/pre_implemented.rs +++ b/exercises/practice/doubly-linked-list/src/pre_implemented.rs @@ -1,4 +1,4 @@ -//! Everything in thes file is implemented in terms of required functionality. +//! Everything in this file is implemented in terms of required functionality. //! You are free to use anything, if it suits you. //! They are useful for the test framework, but the implementation is trivial. //! We supply them to reduce work both for you and the mentors. From d91a3ca9e02f60641c538e3622da553010e6bbc9 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Thu, 20 Jul 2023 16:49:10 +0200 Subject: [PATCH 142/436] Fix new clippy warnings (#1708) --- exercises/practice/paasio/tests/paasio.rs | 21 ++++++++++++++------- exercises/practice/proverb/tests/proverb.rs | 8 ++++---- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/exercises/practice/paasio/tests/paasio.rs b/exercises/practice/paasio/tests/paasio.rs index d13c0d2df..59fdb9999 100644 --- a/exercises/practice/paasio/tests/paasio.rs +++ b/exercises/practice/paasio/tests/paasio.rs @@ -11,7 +11,8 @@ macro_rules! test_read { #[test] fn test_read_passthrough() { let data = $input; - let size = $len(&data); + let len = $len; + let size = len(&data); let mut reader = ReadStats::new(data); let mut buffer = Vec::with_capacity(size); @@ -31,7 +32,8 @@ macro_rules! test_read { #[test] fn test_read_chunks() { let data = $input; - let size = $len(&data); + let len = $len; + let size = len(&data); let mut reader = ReadStats::new(data); let mut buffer = [0_u8; CHUNK_SIZE]; @@ -51,7 +53,8 @@ macro_rules! test_read { #[test] fn test_read_buffered_chunks() { let data = $input; - let size = $len(&data); + let len = $len; + let size = len(&data); let mut reader = BufReader::new(ReadStats::new(data)); let mut buffer = [0_u8; CHUNK_SIZE]; @@ -84,7 +87,8 @@ macro_rules! test_write { #[test] fn test_write_passthrough() { let data = $input; - let size = $len(&data); + let len = $len; + let size = len(&data); let mut writer = WriteStats::new(Vec::with_capacity(size)); let written = writer.write(data); assert!(written.is_ok()); @@ -98,7 +102,8 @@ macro_rules! test_write { #[test] fn test_sink_oneshot() { let data = $input; - let size = $len(&data); + let len = $len; + let size = len(&data); let mut writer = WriteStats::new(io::sink()); let written = writer.write(data); assert!(written.is_ok()); @@ -111,7 +116,8 @@ macro_rules! test_write { #[test] fn test_sink_windowed() { let data = $input; - let size = $len(&data); + let len = $len; + let size = len(&data); let mut writer = WriteStats::new(io::sink()); let mut chunk_count = 0; @@ -129,7 +135,8 @@ macro_rules! test_write { #[test] fn test_sink_buffered_windowed() { let data = $input; - let size = $len(&data); + let len = $len; + let size = len(&data); let mut writer = BufWriter::new(WriteStats::new(io::sink())); for chunk in data.chunks(CHUNK_SIZE) { diff --git a/exercises/practice/proverb/tests/proverb.rs b/exercises/practice/proverb/tests/proverb.rs index fbf8e8cea..a19c2cae5 100644 --- a/exercises/practice/proverb/tests/proverb.rs +++ b/exercises/practice/proverb/tests/proverb.rs @@ -3,7 +3,7 @@ use proverb::build_proverb; #[test] fn test_two_pieces() { let input = vec!["nail", "shoe"]; - let expected = vec![ + let expected = [ "For want of a nail the shoe was lost.", "And all for the want of a nail.", ] @@ -16,7 +16,7 @@ fn test_two_pieces() { #[ignore] fn test_three_pieces() { let input = vec!["nail", "shoe", "horse"]; - let expected = vec![ + let expected = [ "For want of a nail the shoe was lost.", "For want of a shoe the horse was lost.", "And all for the want of a nail.", @@ -47,7 +47,7 @@ fn test_full() { let input = vec![ "nail", "shoe", "horse", "rider", "message", "battle", "kingdom", ]; - let expected = vec![ + let expected = [ "For want of a nail the shoe was lost.", "For want of a shoe the horse was lost.", "For want of a horse the rider was lost.", @@ -64,7 +64,7 @@ fn test_full() { #[ignore] fn test_three_pieces_modernized() { let input = vec!["pin", "gun", "soldier", "battle"]; - let expected = vec![ + let expected = [ "For want of a pin the gun was lost.", "For want of a gun the soldier was lost.", "For want of a soldier the battle was lost.", From e149c4064df27ee53ca1d43794cc323a6c4ed3e1 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Thu, 20 Jul 2023 20:18:34 +0200 Subject: [PATCH 143/436] Remove impls of deprecated exercises (#1709) --- exercises/practice/hexadecimal/src/lib.rs | 36 ++-------- .../practice/nucleotide-codons/src/lib.rs | 65 ++++++++----------- 2 files changed, 33 insertions(+), 68 deletions(-) diff --git a/exercises/practice/hexadecimal/src/lib.rs b/exercises/practice/hexadecimal/src/lib.rs index f539b3c9c..17fd514a1 100644 --- a/exercises/practice/hexadecimal/src/lib.rs +++ b/exercises/practice/hexadecimal/src/lib.rs @@ -1,33 +1,9 @@ -fn parse_hex_digit(c: char) -> Option { - match c { - '0' => Some(0), - '1' => Some(1), - '2' => Some(2), - '3' => Some(3), - '4' => Some(4), - '5' => Some(5), - '6' => Some(6), - '7' => Some(7), - '8' => Some(8), - '9' => Some(9), - 'a' => Some(10), - 'b' => Some(11), - 'c' => Some(12), - 'd' => Some(13), - 'e' => Some(14), - 'f' => Some(15), - _ => None, - } -} +// This exercise is deprecated. +// Consider working on all-your-base instead. pub fn hex_to_int(string: &str) -> Option { - let base: i64 = 16; - - string - .chars() - .rev() - .enumerate() - .fold(Some(0), |acc, (pos, c)| { - parse_hex_digit(c).and_then(|n| acc.map(|acc| acc + n * base.pow(pos as u32))) - }) + unimplemented!( + "what integer is represented by the base-16 digits {}?", + string + ); } diff --git a/exercises/practice/nucleotide-codons/src/lib.rs b/exercises/practice/nucleotide-codons/src/lib.rs index 0577d8166..f76ce4a97 100644 --- a/exercises/practice/nucleotide-codons/src/lib.rs +++ b/exercises/practice/nucleotide-codons/src/lib.rs @@ -1,46 +1,35 @@ -use std::collections::HashMap; +// This exercise is deprecated. +// Consider working on protein-translation instead. -pub struct CodonInfo<'a> { - actual_codons: HashMap<&'a str, &'a str>, -} +use std::marker::PhantomData; -pub fn parse<'a>(pairs: Vec<(&'a str, &'a str)>) -> CodonInfo<'a> { - CodonInfo { - actual_codons: pairs.into_iter().collect(), - } +pub struct CodonsInfo<'a> { + // This field is here to make the template compile and not to + // complain about unused type lifetime parameter "'a". Once you start + // solving the exercise, delete this field and the 'std::marker::PhantomData' + // import. + phantom: PhantomData<&'a ()>, } -impl<'a> CodonInfo<'a> { - pub fn name_for(&self, codon: &str) -> Result<&'a str, &'static str> { - if codon.len() != 3 { - return Err("invalid length"); - } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Error; - let mut valid = true; - let lookup: String = codon - .chars() - .map(|l| { - // Get an example of a "letter" represented by the possibly encoded letter. - // Since every codon represented by the compressed notation has to be of - // the desired amino acid just picking one at random will do. - match l { - 'A' | 'W' | 'M' | 'R' | 'D' | 'H' | 'V' | 'N' => 'A', - 'C' | 'S' | 'Y' | 'B' => 'C', - 'G' | 'K' => 'G', - 'T' => 'T', - _ => { - valid = false; - ' ' - } - } - }) - .collect(); - if !valid { - return Err("invalid char"); - } +impl<'a> CodonsInfo<'a> { + pub fn name_for(&self, codon: &str) -> Result<&'a str, Error> { + unimplemented!( + "Return the protein name for a '{}' codon or Err, if codon string is invalid", + codon + ); + } - // If the input table is correct (which it is) every valid codon is in it - // so unwrap() shouldn't panic. - Ok(self.actual_codons.get(&lookup.as_ref()).unwrap()) + pub fn of_rna(&self, rna: &str) -> Result, Error> { + unimplemented!("Return a list of protein names that correspond to the '{}' RNA string or Err if the RNA string is invalid", rna); } } + +pub fn parse<'a>(pairs: Vec<(&'a str, &'a str)>) -> CodonsInfo<'a> { + unimplemented!( + "Construct a new CodonsInfo struct from given pairs: {:?}", + pairs + ); +} From 5065be15fad74370a63b561f12a8496e53aad21a Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Thu, 20 Jul 2023 20:24:36 +0200 Subject: [PATCH 144/436] Clarify reason for disabled lint (#1707) [no important files changed] --- exercises/practice/binary-search/tests/binary-search.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/exercises/practice/binary-search/tests/binary-search.rs b/exercises/practice/binary-search/tests/binary-search.rs index af0cbae26..6795429e7 100644 --- a/exercises/practice/binary-search/tests/binary-search.rs +++ b/exercises/practice/binary-search/tests/binary-search.rs @@ -1,6 +1,8 @@ -// 1.65.0 says these &[] borrows are needless borrows, -// but 1.64.0 requires them. -// The Rust track will reevaluate this after 1.65.0 is released. +// The &[] borrows are required for the base exercise, +// where `find` is not generic. Once `find` is made generic, +// the borrows become needless. Since we want the tests to work +// without clippy warnings for both people who take on the +// additional challenge and people who don't, we disable this lint. #![allow(clippy::needless_borrow)] use binary_search::find; From 28bfac6349d0b0bcd615117363045768e044b791 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Fri, 21 Jul 2023 14:05:12 +0200 Subject: [PATCH 145/436] Remove dev-dependency hexlit (#1711) If we use dev-dependencies, breaking changes of those dependencies can break stundents' old solutions. https://github.com/exercism/rust/issues/1422 --- exercises/practice/xorcism/Cargo.toml | 3 --- exercises/practice/xorcism/tests/xorcism.rs | 21 ++++++++++----------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/exercises/practice/xorcism/Cargo.toml b/exercises/practice/xorcism/Cargo.toml index 268e17ce2..f72738b0d 100644 --- a/exercises/practice/xorcism/Cargo.toml +++ b/exercises/practice/xorcism/Cargo.toml @@ -8,6 +8,3 @@ edition = "2021" [features] io = [] - -[dev-dependencies] -hexlit = "0.5.0" diff --git a/exercises/practice/xorcism/tests/xorcism.rs b/exercises/practice/xorcism/tests/xorcism.rs index df5580249..ccd1337eb 100644 --- a/exercises/practice/xorcism/tests/xorcism.rs +++ b/exercises/practice/xorcism/tests/xorcism.rs @@ -1,4 +1,3 @@ -use hexlit::hex; #[cfg(feature = "io")] use std::io::{Read, Write}; use xorcism::Xorcism; @@ -79,13 +78,13 @@ fn statefulness() { } macro_rules! test_cases { - ($($name:ident, $key:literal, $input:literal, $expect:literal);+) => { + ($($name:ident, $key:literal, $input:literal, $expect:expr);+) => { $(mod $name { use super::*; const KEY: &str = $key; const INPUT: &str = $input; - const EXPECT: &[u8] = &hex!($expect); + const EXPECT: &[u8] = $expect; /// tests where the key is expressed as `&str`, and input is expressed as `&[u8]` mod str_slice { @@ -290,42 +289,42 @@ macro_rules! test_cases { } test_cases!( - key_shorter_than_data, "abcde", "123455", "5050 5050 5054" + key_shorter_than_data, "abcde", "123455", &[80,80,80,80,80,84] ; key_len_equal_to_data, "The quick brown fox jumped over the lazy dog.", "Wait, oops, this is not the pangram exercise!", - "0309 0c54 5d55 060c 1b53 4e52 1b1f 0753 4606 0b00 041a 1950 110c 454f 0604 1c47 0609 0800 0919 1f0b 430d 1c02 0f" + &[3,9,12,84,93,85,6,12,27,83,78,82,27,31,7,83,70,6,11,0,4,26,25,80,17,12,69,79,6,4,28,71,6,9,8,0,9,25,31,11,67,13,28,2,15] ; key_longer_than_data, "A properly cryptographically random key longer than the data can actually be fairly secure.", "Text is not cryptographically random.", - "1545 0806 4f19 1652 0216 5443 110b 0904 1b08 1513 1118 010a 020d 0015 5952 130f 0a0b 024d 45" + &[21,69,8,6,79,25,22,82,2,22,84,67,17,11,9,4,27,8,21,19,17,24,1,10,2,13,0,21,89,82,19,15,10,11,2,77,69] ; shakespearean, "Forsooth, let us never break our trust!", "The sacred brothership in which we share shall never from our hearts be lost.", - "1207 1753 1c0e 171a 4944 4c07 064f 011b 451c 161e 0c02 000b 1c45 1603 490c 1d52 5711 5206 1b15 5323 4f01 1b0e 0318 4842 451a 0006 0013 014f 0345 1910 0000 0a17 0413 1f53 4f17 1700 181d 0607 5a" + &[18,7,23,83,28,14,23,26,73,68,76,7,6,79,1,27,69,28,22,30,12,2,0,11,28,69,22,3,73,12,29,82,87,17,82,6,27,21,83,35,79,1,27,14,3,24,72,66,69,26,0,6,0,19,1,79,3,69,25,16,0,0,10,23,4,19,31,83,79,23,23,0,24,29,6,7,90] ; comics, "Who knows what evil lurks in the hearts of men?", "Spiderman! It's spiderman! Not a bird, or a plane, or a fireman! Just spiderman!", - "0418 0644 0e1c 0216 1d01 5721 1553 5345 0519 0544 0907 1f0a 1d01 4920 4f00 4804 000a 0c13 1658 534f 1d46 414d 1502 5e39 0d43 0004 1c4f 1653 461e 1a04 1941 0b57 4926 551f 0152 1803 490d 0b52 1909 0b01" + &[4,24,6,68,14,28,2,22,29,1,87,33,21,83,83,69,5,25,5,68,9,7,31,10,29,1,73,32,79,0,72,4,0,10,12,19,22,88,83,79,29,70,65,77,21,2,94,57,13,67,0,4,28,79,22,83,70,30,26,4,25,65,11,87,73,38,85,31,1,82,24,3,73,13,11,82,25,9,11,1] ; mad_science, "TRANSMUTATION_NOTES_1", "If wishes were horses, beggars would ride.", - "1d34 6139 3a3e 3d31 3274 3e2a 3c3a 6e27 3b37 203a 4278 7223 2b34 2a34 2632 743e 203b 332a 6f26 2c37 3a1f" + &[29,52,97,57,58,62,61,49,50,116,62,42,60,58,110,39,59,55,32,58,66,120,114,35,43,52,42,52,38,50,116,62,32,59,51,42,111,38,44,55,58,31] ; metaphor, "Contextualism", "The globe is text, its people prose; all the world's a page.", - "1707 0b54 0214 1b17 044c 0000 4d37 0a16 0049 581d 0112 4c19 1602 3303 0b54 150a 1b06 0457 4912 012f 4f1a 1c00 5803 1a13 000d 541e 630e 4e04 041f 115b" + &[23,7,11,84,2,20,27,23,4,76,0,0,77,55,10,22,0,73,88,29,1,18,76,25,22,2,51,3,11,84,21,10,27,6,4,87,73,18,1,47,79,26,28,0,88,3,26,19,0,13,84,30,99,14,78,4,4,31,17,91] ; emoji, "🔑🗝️… 🎹?", "⌨️! 🔒+💻+🧠=🔓", - "1213 3c7e 4810 b6bd 1f27 1b70 ab56 bf62 24a5 49a0 573f a961 6f0b 04" + &[18,19,60,126,72,16,182,189,31,39,27,112,171,86,191,98,36,165,73,160,87,63,169,97,111,11,4] ); // note the emoji case above. Most exercism tests don't test emoji, because we like to use strings // as input, but in this case, they're fine: they're arbitrary binary data that _just happen_ to From 12c32c43c7c2320a06e1bbb78daa3e7e6b8ab548 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Fri, 21 Jul 2023 14:19:38 +0200 Subject: [PATCH 146/436] Improve instructions of forth (#1710) motivation: https://github.com/exercism/rust-test-runner/issues/30 --- exercises/practice/forth/.docs/instructions.append.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 exercises/practice/forth/.docs/instructions.append.md diff --git a/exercises/practice/forth/.docs/instructions.append.md b/exercises/practice/forth/.docs/instructions.append.md new file mode 100644 index 000000000..42b8ccdf0 --- /dev/null +++ b/exercises/practice/forth/.docs/instructions.append.md @@ -0,0 +1,3 @@ +Note the additional test case in `tests/alloc-attack.rs`. It tests against +algorithmically inefficient implementations. Because of that, it usually times +out online instead of outright failing, leading to a less helpful error message. From 346a8b9ab65db00a48978bdaaaa67d0ad4b62d4f Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Fri, 21 Jul 2023 16:10:18 +0200 Subject: [PATCH 147/436] Document available crates (#1712) --- docs/TESTS.md | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/docs/TESTS.md b/docs/TESTS.md index 8529aaee1..69a3b7f94 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -1,8 +1,12 @@ # Writing the Code -Write your code in `src/lib.rs`. Some exercises come with a stub file in `src/lib.rs` that will show you the signatures of the code you'll need to write. If the exercise does not come with a `src/lib.rs` file, create one. +Write your code in `src/lib.rs`. +The exercises come with a stub file in `src/lib.rs` that will show you the signatures of the code you'll need to write. -The directory must be named `src` and the file must be named `lib.rs` otherwise your code will not compile. For more details, check out the rustlang book [chapter on modules](https://doc.rust-lang.org/book/ch07-02-defining-modules-to-control-scope-and-privacy.html) +For most exercises, it is best to write all your code in the file `src/lib.rs`. +If you would like to split your solution into several files, consult the Rust book's [chapter on modules][chapter-modules]. + +[chapter-modules]: https://doc.rust-lang.org/book/ch07-02-defining-modules-to-control-scope-and-privacy.html ## Running Tests @@ -12,8 +16,27 @@ To run the tests, all you need to do is run the following command: $ cargo test ``` -Only the first test is enabled by default. After you are ready to pass the next test, remove the ignore flag from the next test (`#[ignore]`). You can also remove the flag from all the tests at once if you prefer. +Only the first test is enabled by default. +After you are ready to pass the next test, remove the ignore flag from the next test (`#[ignore]`). +You can also remove the flag from all the tests at once if you prefer. + +Feel free to write as little code as possible to get the tests to pass. +The test failures will guide you to what should be written next. + +Because Rust checks all code at compile time, you may find that your tests won't compile until you write the required code. +Even `ignore`d tests are checked at compile time. +You can [comment out][comments] tests that won't compile by starting each line with a `//`. +Then, when you're ready to work on that test, you can un-comment it. +Rust also has a special macro called `todo!()`, which you can use on unfinished code paths to make your program compile. + +[comments]: https://doc.rust-lang.org/book/ch03-04-comments.html + +## Using libraries + +You should be able to solve most exercises without using any external libraries. +Nevertheless, you can add libraries (the Rust community calls them **crates**) by running `cargo add crate-name` in the directory of an exercise. -You should try to write as little code possible to get the tests to pass. Let the test failures guide you to what should be written next. +The automatic tests on the website only support a predefined set of crates, which can be found [here][local-registry] under the section `[dependencies]`. +Feel free to open an issue or pull request if you would like support for a specific crate to be added. -Because Rust checks all code at compile time you may find that your tests won't compile until you write the required code. Even `ignore`d tests are checked at compile time. You can [comment out](https://doc.rust-lang.org/book/ch03-04-comments.html) tests that won't compile by starting each line with a `//`. Then, when you're ready to work on that test, you can un-comment it. +[local-registry]: https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml From ece81a49a46efa8cf1755fbdfd42fe498c0c934a Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Mon, 21 Aug 2023 16:23:12 +0200 Subject: [PATCH 148/436] Replace actions-rs with dtolnay/rust-toolchain (#1714) actions-rs is unmaintained, the last commit is from March 2020. https://github.com/actions-rs/toolchain The Rust community recommends dtolnay/rust-toolchain these days, e.g. https://blessed.rs/crates#section-tooling --- .github/workflows/tests.yml | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 87692839e..5659dda86 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -113,10 +113,9 @@ jobs: uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: Setup toolchain - uses: actions-rs/toolchain@v1.0.7 + uses: dtolnay/rust-toolchain@0e66bd3e6b38ec0ad5312288c83e47c143e6b09e with: toolchain: ${{ matrix.rust }} - default: true # run scripts as steps - name: Check exercises @@ -141,10 +140,9 @@ jobs: uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: Setup toolchain - uses: actions-rs/toolchain@v1.0.7 + uses: dtolnay/rust-toolchain@0e66bd3e6b38ec0ad5312288c83e47c143e6b09e with: toolchain: stable - default: true - name: Rust Format Version run: rustfmt --version @@ -177,10 +175,9 @@ jobs: uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: Setup toolchain - uses: actions-rs/toolchain@v1.0.7 + uses: dtolnay/rust-toolchain@0e66bd3e6b38ec0ad5312288c83e47c143e6b09e with: toolchain: ${{ matrix.rust }} - default: true # Clippy already installed on Stable, but not Beta. # So, we must install here. @@ -214,10 +211,9 @@ jobs: uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - name: Setup nightly toolchain - uses: actions-rs/toolchain@v1.0.7 + uses: dtolnay/rust-toolchain@0e66bd3e6b38ec0ad5312288c83e47c143e6b09e with: toolchain: nightly - default: true - name: Check exercises env: From 5dde23a4b5a9e428aea8ab533c640a6d861f0dd2 Mon Sep 17 00:00:00 2001 From: Jack Deeth Date: Thu, 24 Aug 2023 14:39:13 +0100 Subject: [PATCH 149/436] Saddle-points: fix formatting of instructions (#1716) Fix formatting of demo grid --- exercises/practice/saddle-points/.docs/instructions.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/exercises/practice/saddle-points/.docs/instructions.md b/exercises/practice/saddle-points/.docs/instructions.md index d861388e4..e2d746764 100644 --- a/exercises/practice/saddle-points/.docs/instructions.md +++ b/exercises/practice/saddle-points/.docs/instructions.md @@ -12,11 +12,13 @@ Or it might have one, or even several. Here is a grid that has exactly one candidate tree. +``` 1 2 3 4 |----------- 1 | 9 8 7 8 2 | 5 3 2 4 <--- potential tree house at row 2, column 1, for tree with height 5 3 | 6 6 7 1 +``` - Row 2 has values 5, 3, and 1. The largest value is 5. - Column 1 has values 9, 5, and 6. The smallest value is 5. From 190c772a72f951a60adb898b2e977c9addd62ca6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Aug 2023 17:34:22 +0200 Subject: [PATCH 150/436] Bump actions/checkout from 3.5.3 to 3.6.0 (#1717) Bumps [actions/checkout](https://github.com/actions/checkout) from 3.5.3 to 3.6.0. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/c85c95e3d7251135ab7dc9ce3241c5835cc595a9...f43a0e5ff2bd294095638e18286ca9a3d1956744) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/tests.yml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5659dda86..b3618e495 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -19,7 +19,7 @@ jobs: steps: # Checks out a copy of your repository on the ubuntu-latest machine - name: Checkout code - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 - name: Ensure tool names are snake cased run: ./bin/lint_tool_file_names.sh @@ -49,13 +49,13 @@ jobs: steps: # Checks out default branch locally so that it is available to the scripts. - name: Checkout main - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 with: ref: main # Checks out a copy of your repository on the ubuntu-latest machine - name: Checkout code - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 - name: Fetch configlet run: ./bin/fetch-configlet @@ -69,11 +69,11 @@ jobs: steps: - name: Checkout main - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 with: ref: main - name: Checkout code - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 - name: Run markdown lint run: ./bin/lint_markdown.sh @@ -84,7 +84,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 - name: Run shellcheck uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # v2.0.0 @@ -104,13 +104,13 @@ jobs: steps: # Checks out main locally so that it is available to the scripts. - name: Checkout main - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 with: ref: main # Checks out a copy of your repository on the ubuntu-latest machine - name: Checkout code - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 - name: Setup toolchain uses: dtolnay/rust-toolchain@0e66bd3e6b38ec0ad5312288c83e47c143e6b09e @@ -137,7 +137,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 - name: Setup toolchain uses: dtolnay/rust-toolchain@0e66bd3e6b38ec0ad5312288c83e47c143e6b09e @@ -167,12 +167,12 @@ jobs: steps: - name: Checkout main - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 with: ref: main - name: Checkout code - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 - name: Setup toolchain uses: dtolnay/rust-toolchain@0e66bd3e6b38ec0ad5312288c83e47c143e6b09e @@ -202,13 +202,13 @@ jobs: steps: # Checks out main locally so that it is available to the scripts. - name: Checkout main - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 with: ref: main # Checks out a copy of your repository on the ubuntu-latest machine - name: Checkout code - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 - name: Setup nightly toolchain uses: dtolnay/rust-toolchain@0e66bd3e6b38ec0ad5312288c83e47c143e6b09e From 8cf385823a7b8ccdc03755691ed1b9e8ba7cc641 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Sep 2023 21:03:24 +0200 Subject: [PATCH 151/436] Bump actions/checkout from 3.6.0 to 4.0.0 (#1719) Bumps [actions/checkout](https://github.com/actions/checkout) from 3.6.0 to 4.0.0. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/f43a0e5ff2bd294095638e18286ca9a3d1956744...3df4ab11eba7bda6032a0b82a6bb43b11571feac) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/tests.yml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b3618e495..d8a06e2ad 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -19,7 +19,7 @@ jobs: steps: # Checks out a copy of your repository on the ubuntu-latest machine - name: Checkout code - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 - name: Ensure tool names are snake cased run: ./bin/lint_tool_file_names.sh @@ -49,13 +49,13 @@ jobs: steps: # Checks out default branch locally so that it is available to the scripts. - name: Checkout main - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 with: ref: main # Checks out a copy of your repository on the ubuntu-latest machine - name: Checkout code - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 - name: Fetch configlet run: ./bin/fetch-configlet @@ -69,11 +69,11 @@ jobs: steps: - name: Checkout main - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 with: ref: main - name: Checkout code - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 - name: Run markdown lint run: ./bin/lint_markdown.sh @@ -84,7 +84,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 - name: Run shellcheck uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # v2.0.0 @@ -104,13 +104,13 @@ jobs: steps: # Checks out main locally so that it is available to the scripts. - name: Checkout main - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 with: ref: main # Checks out a copy of your repository on the ubuntu-latest machine - name: Checkout code - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 - name: Setup toolchain uses: dtolnay/rust-toolchain@0e66bd3e6b38ec0ad5312288c83e47c143e6b09e @@ -137,7 +137,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 - name: Setup toolchain uses: dtolnay/rust-toolchain@0e66bd3e6b38ec0ad5312288c83e47c143e6b09e @@ -167,12 +167,12 @@ jobs: steps: - name: Checkout main - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 with: ref: main - name: Checkout code - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 - name: Setup toolchain uses: dtolnay/rust-toolchain@0e66bd3e6b38ec0ad5312288c83e47c143e6b09e @@ -202,13 +202,13 @@ jobs: steps: # Checks out main locally so that it is available to the scripts. - name: Checkout main - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 with: ref: main # Checks out a copy of your repository on the ubuntu-latest machine - name: Checkout code - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 - name: Setup nightly toolchain uses: dtolnay/rust-toolchain@0e66bd3e6b38ec0ad5312288c83e47c143e6b09e From 9800892c98e47869810198670f360e89811f35cd Mon Sep 17 00:00:00 2001 From: Magnus Balzer <35585143+UnlimitedHummus@users.noreply.github.com> Date: Tue, 12 Sep 2023 19:54:03 +0200 Subject: [PATCH 152/436] all-your-base: Update outdated comment (#1730) Update instructional comment to match changes from #469. --- exercises/practice/all-your-base/src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/exercises/practice/all-your-base/src/lib.rs b/exercises/practice/all-your-base/src/lib.rs index fd9996b9d..0a1189323 100644 --- a/exercises/practice/all-your-base/src/lib.rs +++ b/exercises/practice/all-your-base/src/lib.rs @@ -12,8 +12,7 @@ pub enum Error { /// A digit is any unsigned integer (e.g. u8, u16, u32, u64, or usize). /// Bases are specified as unsigned integers. /// -/// Return an `Err(.)` if the conversion is impossible. -/// The tests do not test for specific values inside the `Err(.)`. +/// Return the corresponding Error enum if the conversion is impossible. /// /// /// You are allowed to change the function signature as long as all test still pass. From 5ebc065549e9902cc22d1a1cc5f54ad8e56ffb8b Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Wed, 13 Sep 2023 18:54:31 +0200 Subject: [PATCH 153/436] Add test generator (#1722) * cleanup dev tooling * move all scripts into `bin/` * turn several scripts into Rust tests: * `check_exercises_for_authors.sh` * `count_ignores.sh` * `verify_exercise_difficulty.sh` * `lint_tool_file_names.sh` * `lint_trailing_spaces.sh` * remove several scripts: * `check_uuids.sh`: `configlet lint` covers that already * `ensure_lib_src_rs_exists.sh`: this is not an issue if exercise stubs are generated * `clean_topics_vs_practices.py`: applies undocumented standards (max 10 practice exercises per topic) * `fetch_canonical_data`: we now have the submodule for this * cleanup documentation * remove generic instructions about WSL * merge `CONTRIBUTING.md` and `maintaining.md` * move contributing docs in `README.md` to `CONTRIBUTING.md` * outsource instructions for making good PRs * outsource instructions for installing Rust * steal beautiful `REAMDE.md` from Python track * apply conventions and guidelines in a couple places where the new Rust tests found deficiencies * replace Bash-based exercise generation with Rust-based one (deleting the related `util/` Rust crates) * can be used to sync with problem-specifications too * uses the Tera templating engine for customizable test generation inspired by the python track using Jinja for this purpose * apply test generation to acronym exercise to validate MVP --- .editorconfig | 10 - .github/workflows/tests.yml | 112 +- .gitignore | 8 +- .gitmodules | 3 + README.md | 151 +-- _test/WINDOWS_README.md | 44 - _test/check_exercises_for_authors.sh | 8 - _test/check_uuids.sh | 15 - _test/count_ignores.sh | 40 - _test/ensure_lib_src_rs_exist.sh | 51 - _test/verify_exercise_difficulties.sh | 56 - bin/.shellcheckrc | 3 - bin/add_practice_exercise | 112 -- {_test => bin}/check_exercises.sh | 19 +- bin/clean_topics_vs_practices.py | 42 - {_test => bin}/ensure_stubs_compile.sh | 2 +- bin/fetch_canonical_data | 27 - bin/{format_exercises => format_exercises.sh} | 6 +- bin/generate_tests | 61 - bin/generator-utils/colors.sh | 13 - bin/generator-utils/prompts.sh | 40 - bin/generator-utils/templates.sh | 157 --- bin/generator-utils/utils.sh | 76 -- bin/lint_markdown.sh | 8 +- bin/lint_tool_file_names.sh | 17 - bin/lint_trailing_spaces.sh | 14 - bin/remove_trailing_whitespace.sh | 4 - bin/{test-exercise => test_exercise.sh} | 2 +- bin/test_template | 6 - concepts/methods/about.md | 2 +- concepts/references/about.md | 4 +- config.json | 25 +- docs/CONTRIBUTING.md | 110 +- docs/INSTALLATION.md | 42 +- docs/RESOURCES.md | 2 +- docs/maintaining.md | 52 - .../magazine-cutout/.docs/instructions.md | 2 +- .../role-playing-game/.docs/introduction.md | 8 +- .../short-fibonacci/.docs/instructions.md | 4 +- .../practice/acronym/.docs/instructions.md | 13 +- .../practice/acronym/.meta/test_template.tera | 12 + exercises/practice/acronym/.meta/tests.toml | 13 +- exercises/practice/acronym/tests/acronym.rs | 77 +- .../allergies/.approaches/introduction.md | 5 +- .../.approaches/vec-when-requested/content.md | 6 +- .../.approaches/looping/content.md | 3 +- .../.approaches/recursion/content.md | 11 +- .../collatz-conjecture/.docs/instructions.md | 4 +- .../dot-dsl/.docs/instructions.append.md | 2 +- .../isogram/.approaches/filter-all/content.md | 19 +- .../ocr-numbers/.docs/instructions.md | 20 +- .../.docs/instructions.append.md | 2 +- .../.approaches/introduction.md | 2 +- .../.approaches/iterate-once/content.md | 4 +- .../do-not-keep-track-of-length/content.md | 12 +- .../.approaches/introduction.md | 14 +- .../.docs/instructions.append.md | 21 +- .../space-age/.docs/instructions.append.md | 9 +- justfile | 28 + problem-specifications | 1 + rust-tooling/Cargo.lock | 1106 +++++++++++++++++ rust-tooling/Cargo.toml | 17 + rust-tooling/src/bin/generate_exercise.rs | 219 ++++ rust-tooling/src/default_test_template.tera | 12 + rust-tooling/src/exercise_config.rs | 127 ++ rust-tooling/src/exercise_generation.rs | 104 ++ rust-tooling/src/fs_utils.rs | 12 + rust-tooling/src/lib.rs | 5 + rust-tooling/src/problem_spec.rs | 69 + rust-tooling/src/track_config.rs | 145 +++ rust-tooling/tests/bash_script_conventions.rs | 72 ++ rust-tooling/tests/count_ignores.rs | 34 + rust-tooling/tests/difficulties.rs | 18 + .../tests/no_authors_in_cargo_toml.rs | 16 + rust-tooling/tests/no_trailing_whitespace.rs | 34 + util/escape_double_quotes/Cargo.lock | 7 - util/escape_double_quotes/Cargo.toml | 8 - util/escape_double_quotes/build | 2 - util/escape_double_quotes/src/lib.rs | 2 - util/escape_double_quotes/src/main.rs | 30 - .../src/utils/escape_double_quotes.rs | 22 - util/escape_double_quotes/src/utils/mod.rs | 1 - util/escape_double_quotes/tests/test.rs | 36 - util/ngram/Cargo.lock | 16 - util/ngram/Cargo.toml | 9 - util/ngram/build | 2 - util/ngram/src/main.rs | 26 - 87 files changed, 2385 insertions(+), 1402 deletions(-) delete mode 100644 .editorconfig create mode 100644 .gitmodules delete mode 100644 _test/WINDOWS_README.md delete mode 100755 _test/check_exercises_for_authors.sh delete mode 100755 _test/check_uuids.sh delete mode 100755 _test/count_ignores.sh delete mode 100755 _test/ensure_lib_src_rs_exist.sh delete mode 100755 _test/verify_exercise_difficulties.sh delete mode 100644 bin/.shellcheckrc delete mode 100755 bin/add_practice_exercise rename {_test => bin}/check_exercises.sh (79%) delete mode 100755 bin/clean_topics_vs_practices.py rename {_test => bin}/ensure_stubs_compile.sh (98%) delete mode 100755 bin/fetch_canonical_data rename bin/{format_exercises => format_exercises.sh} (86%) delete mode 100755 bin/generate_tests delete mode 100644 bin/generator-utils/colors.sh delete mode 100644 bin/generator-utils/prompts.sh delete mode 100755 bin/generator-utils/templates.sh delete mode 100755 bin/generator-utils/utils.sh delete mode 100755 bin/lint_tool_file_names.sh delete mode 100755 bin/lint_trailing_spaces.sh delete mode 100755 bin/remove_trailing_whitespace.sh rename bin/{test-exercise => test_exercise.sh} (98%) delete mode 100644 bin/test_template delete mode 100644 docs/maintaining.md create mode 100644 exercises/practice/acronym/.meta/test_template.tera create mode 100644 justfile create mode 160000 problem-specifications create mode 100644 rust-tooling/Cargo.lock create mode 100644 rust-tooling/Cargo.toml create mode 100644 rust-tooling/src/bin/generate_exercise.rs create mode 100644 rust-tooling/src/default_test_template.tera create mode 100644 rust-tooling/src/exercise_config.rs create mode 100644 rust-tooling/src/exercise_generation.rs create mode 100644 rust-tooling/src/fs_utils.rs create mode 100644 rust-tooling/src/lib.rs create mode 100644 rust-tooling/src/problem_spec.rs create mode 100644 rust-tooling/src/track_config.rs create mode 100644 rust-tooling/tests/bash_script_conventions.rs create mode 100644 rust-tooling/tests/count_ignores.rs create mode 100644 rust-tooling/tests/difficulties.rs create mode 100644 rust-tooling/tests/no_authors_in_cargo_toml.rs create mode 100644 rust-tooling/tests/no_trailing_whitespace.rs delete mode 100644 util/escape_double_quotes/Cargo.lock delete mode 100644 util/escape_double_quotes/Cargo.toml delete mode 100755 util/escape_double_quotes/build delete mode 100644 util/escape_double_quotes/src/lib.rs delete mode 100644 util/escape_double_quotes/src/main.rs delete mode 100644 util/escape_double_quotes/src/utils/escape_double_quotes.rs delete mode 100644 util/escape_double_quotes/src/utils/mod.rs delete mode 100644 util/escape_double_quotes/tests/test.rs delete mode 100644 util/ngram/Cargo.lock delete mode 100644 util/ngram/Cargo.toml delete mode 100755 util/ngram/build delete mode 100644 util/ngram/src/main.rs diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 2b1788677..000000000 --- a/.editorconfig +++ /dev/null @@ -1,10 +0,0 @@ -root = true - -[*] -indent_style = space -indent_size = 4 -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true -end_of_line = lf - diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d8a06e2ad..7efc36b87 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -4,7 +4,7 @@ name: CI on: push: branches: - - main + - main pull_request: workflow_dispatch: schedule: @@ -12,48 +12,11 @@ on: - cron: "0 0 * * 0" jobs: - ensure-conventions: - name: Ensure conventions are followed - runs-on: ubuntu-latest - - steps: - # Checks out a copy of your repository on the ubuntu-latest machine - - name: Checkout code - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 - - - name: Ensure tool names are snake cased - run: ./bin/lint_tool_file_names.sh - - - name: Ensure src/lib.rs files exist - run: ./_test/ensure_lib_src_rs_exist.sh - - - name: Count ignores - run: ./_test/count_ignores.sh - - - name: Check UUIDs - run: ./_test/check_uuids.sh - - - name: Verify exercise difficulties - run: ./_test/verify_exercise_difficulties.sh - - - name: Check exercises for authors - run: ./_test/check_exercises_for_authors.sh - - - name: Ensure relevant files do not have trailing whitespace - run: ./bin/lint_trailing_spaces.sh - configlet: name: configlet lint runs-on: ubuntu-latest steps: - # Checks out default branch locally so that it is available to the scripts. - - name: Checkout main - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 - with: - ref: main - - # Checks out a copy of your repository on the ubuntu-latest machine - name: Checkout code uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 @@ -68,10 +31,6 @@ jobs: runs-on: ubuntu-latest steps: - - name: Checkout main - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 - with: - ref: main - name: Checkout code uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 @@ -80,7 +39,7 @@ jobs: # stolen from https://raw.githubusercontent.com/exercism/github-actions/main/.github/workflows/shellcheck.yml shellcheck: - name: shellcheck internal tooling lint + name: Run shellcheck on scripts runs-on: ubuntu-latest steps: - name: Checkout @@ -88,27 +47,16 @@ jobs: - name: Run shellcheck uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # v2.0.0 - env: - SHELLCHECK_OPTS: -x -s bash -e SC2001 --norc compilation: name: Check compilation runs-on: ubuntu-latest strategy: - # Allows running the job multiple times with different configurations matrix: rust: ["stable", "beta"] - deny_warnings: ['', '1'] steps: - # Checks out main locally so that it is available to the scripts. - - name: Checkout main - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 - with: - ref: main - - # Checks out a copy of your repository on the ubuntu-latest machine - name: Checkout code uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 @@ -117,19 +65,31 @@ jobs: with: toolchain: ${{ matrix.rust }} - # run scripts as steps - name: Check exercises env: - DENYWARNINGS: ${{ matrix.deny_warnings }} - run: ./_test/check_exercises.sh - continue-on-error: ${{ matrix.rust == 'beta' && matrix.deny_warnings == '1' }} + DENYWARNINGS: "1" + run: ./bin/check_exercises.sh - name: Ensure stubs compile env: - DENYWARNINGS: ${{ matrix.deny_warnings }} - run: ./_test/ensure_stubs_compile.sh - continue-on-error: ${{ matrix.rust == 'beta' && matrix.deny_warnings == '1' }} + DENYWARNINGS: "1" + run: ./bin/ensure_stubs_compile.sh + + tests: + name: Run repository tests + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 + + - name: Setup toolchain + uses: dtolnay/rust-toolchain@0e66bd3e6b38ec0ad5312288c83e47c143e6b09e + with: + toolchain: stable + - name: Run tests + run: cd rust-tooling && cargo test rustformat: name: Check Rust Formatting @@ -144,11 +104,8 @@ jobs: with: toolchain: stable - - name: Rust Format Version - run: rustfmt --version - - name: Format - run: bin/format_exercises + run: ./bin/format_exercises.sh - name: Diff run: | @@ -166,11 +123,6 @@ jobs: rust: ["stable", "beta"] steps: - - name: Checkout main - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 - with: - ref: main - - name: Checkout code uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 @@ -179,20 +131,15 @@ jobs: with: toolchain: ${{ matrix.rust }} - # Clippy already installed on Stable, but not Beta. - # So, we must install here. - - name: Install Clippy - run: rustup component add clippy - - name: Clippy tests env: CLIPPY: true - run: ./_test/check_exercises.sh + run: ./bin/check_exercises.sh - name: Clippy stubs env: CLIPPY: true - run: ./_test/ensure_stubs_compile.sh + run: ./bin/ensure_stubs_compile.sh nightly-compilation: name: Check exercises on nightly (benchmark enabled) @@ -200,13 +147,6 @@ jobs: continue-on-error: true # It's okay if the nightly job fails steps: - # Checks out main locally so that it is available to the scripts. - - name: Checkout main - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 - with: - ref: main - - # Checks out a copy of your repository on the ubuntu-latest machine - name: Checkout code uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 @@ -217,5 +157,5 @@ jobs: - name: Check exercises env: - BENCHMARK: '1' - run: ./_test/check_exercises.sh + BENCHMARK: "1" + run: ./bin/check_exercises.sh diff --git a/.gitignore b/.gitignore index edfd68168..edcc454ae 100644 --- a/.gitignore +++ b/.gitignore @@ -3,13 +3,7 @@ .DS_Store **/target tmp -bin/configlet -bin/configlet.exe -bin/exercise -bin/exercise.exe -bin/generator-utils/ngram -bin/generator-utils/escape_double_quotes +/bin/configlet exercises/*/*/Cargo.lock exercises/*/*/clippy.log -canonical_data.json .vscode diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..bf863ee5b --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "problem-specifications"] + path = problem-specifications + url = git@github.com:exercism/problem-specifications diff --git a/README.md b/README.md index 31a9c76eb..6487c01f5 100644 --- a/README.md +++ b/README.md @@ -1,106 +1,87 @@ -# Exercism Rust Track +
-[![CI](https://github.com/exercism/rust/workflows/CI/badge.svg?branch=main)](https://github.com/exercism/rust/actions?query=workflow%3ACI+branch%3Amain) + +

Exercism Rust Track

-Exercism exercises in Rust +                          [![Discourse topics](https://img.shields.io/discourse/topics?color=8A08E6&label=Connect%20&labelColor=FFDF58&logo=Discourse&logoColor=8A08E6&server=https%3A%2F%2Fforum.exercism.org&style=social)](https://forum.exercism.org) +  [![Exercism_III](https://img.shields.io/badge/PAUSED-C73D4E?labelColor=3D454D&label=Contributions)](https://exercism.org/blog/freeing-our-maintainers) +  [![CI](https://github.com/exercism/rust/workflows/CI/badge.svg?branch=main)](https://github.com/exercism/rust/actions?query=workflow%3ACI+branch%3Amain) -## Contributing +
-Check out our [contributor documentation](docs/CONTRIBUTING.md). +Hi.  👋🏽  👋  **We are happy you are here.**  🎉 🌟 -## Exercise Tests +
-At the most basic level, Exercism is all about the tests. You can read more about how we think about test suites in [the Exercism documentation](https://github.com/exercism/legacy-docs/blob/main/language-tracks/exercises/anatomy/test-suites.md). +**`exercism/rust`** is one of many programming language tracks on [exercism(dot)org][exercism-website]. +This repo holds all the instructions, tests, code, & support files for Rust _exercises_ currently under development or implemented & available for students. -Test files should use the following format: +Some Exercism language tracks have a **syllabus** which is meant to teach the language step-by-step. +The Rust track's syllabus is a work in progress and it's not activated yet. +All exercises presented to students are **practice exercises**. +Students are exepcted to learn the language themselves, for example with the [official book][the-rust-programming-language], and practice with our exercises. -``` -extern crate exercise_name; +

-use exercise_name::*; +
+ + + + -#[test] -fn test_descriptive_name() { - assert_eq!(exercise_function(1), 1); -} +🌟🌟  Please take a moment to read our [Code of Conduct][exercism-code-of-conduct]  🌟🌟
+It might also be helpful to look at [Being a Good Community Member][being-a-good-community-member] & [The words that we use][the-words-that-we-use].
+Some defined roles in our community: [Contributors][exercism-contributors] **|** [Mentors][exercism-mentors] **|** [Maintainers][exercism-track-maintainers] **|** [Admins][exercism-admins] -#[test] -#[ignore] -fn test_second_and_past_tests_ignored() { - assert_ne!(exercise_function(1), 2); -} -``` +
-## Opening an Issue +
+ -If you plan to make significant or breaking changes, please open an issue so we can discuss it first. If this is a discussion that is relevant to more than just the Rust track, please open an issue in [exercism/discussions](https://github.com/exercism/discussions/issues). +We 💛 💙   our community.
+**`But our maintainers are not accepting community contributions at this time.`**
+Please read this [community blog post][freeing-maintainers] for details. -## Submitting a Pull Request +
+ -Pull requests should be focused on a single exercise, issue, or conceptually cohesive change. Please refer to Exercism's [pull request guidelines](https://github.com/exercism/legacy-docs/blob/main/contributing/pull-request-guidelines.md). +Here to suggest a new feature or new exercise?? **Hooray!**  🎉  
+We'd love if you did that via our [Exercism Community Forum](https://forum.exercism.org/).
+Please read [Suggesting Exercise Improvements][suggesting-improvements] & [Chesterton's Fence][chestertons-fence].
+_Thoughtful suggestions will likely result faster & more enthusiastic responses from volunteers._ -Please follow the coding standards for Rust. [rustfmt](https://github.com/nrc/rustfmt) may help with this -and can be installed with `cargo install rustfmt`. +
+ -### Verifying your Change +✨ 🦄  _**Want to jump directly into Exercism specifications & detail?**_
+     [Structure][exercism-track-structure] **|** [Tasks][exercism-tasks] **|** [Concepts][exercism-concepts] **|** [Concept Exercises][concept-exercises] **|** [Practice Exercises][practice-exercises] **|** [Presentation][exercise-presentation]
+     [Writing Style Guide][exercism-writing-style] **|** [Markdown Specification][exercism-markdown-specification] (_✨ version in [contributing][website-contributing-section] on exercism.org_) -Before submitting your pull request, you'll want to verify the changes in two ways: +
+
-* Run all the tests for the Rust exercises -* Run an Exercism-specific linter to verify the track +## Exercism Rust Track License -All the tests for Rust exercises can be run from the top level of the repo with `_test/check_exercises.sh`. If you are on a Windows machine, there are additional [Windows-specific instructions](_test/WINDOWS_README.md) for running this. +This repository uses the [MIT License](/LICENSE). -### On modifying the exercises' README - -Please note that the README of every exercise is formed using several templates, not all of which are necessarily present on this repo. The most important of these: - -- The `description.md` file in the exercise directory from the [problem-specifications repository](https://github.com/exercism/problem-specifications/tree/main/exercises) - -- The `.meta/hints.md` file in the exercise directory on this repository - -- The [Rust-specific instructions](https://github.com/exercism/rust/blob/main/exercises/shared/.docs/tests.md) - -If you are modifying the section of the README that belongs to the template not from this repository, please consider [opening a PR](https://github.com/exercism/problem-specifications/pulls) on the `problem-specifications` repository first. - -## Contributing a New Exercise - -Please see the documentation about [adding new exercises](https://github.com/exercism/legacy-docs/blob/main/you-can-help/make-up-new-exercises.md). - -Note that: - -- The simplest way to generate, update or configure an exercise is to use the [exercise](https://github.com/exercism/rust/tree/main/util/exercise) utility provided in this repository. To compile the utility you can use the [bin/build_exercise_crate.sh](https://github.com/exercism/rust/tree/main/bin/build_exercise_crate.sh) script or, if the script does not work for you, use the `cargo build --release` command in the `util/exercise/` directory and then copy the `exercise` binary from the `util/exercise/target/release/` directory into the `bin/` directory. Use `bin/exercise --help` to learn about the existing commands and their possible usage. - -- Each exercise must stand on its own. Do not reference files outside the exercise directory. They will not be included when the user fetches the exercise. - -- Exercises must conform to the Exercism-wide standards described in [the documentation](https://github.com/exercism/legacy-docs/tree/main/language-tracks/exercises). - -- Each exercise should have: - - exercises/exercise-name/ - tests/exercise-name.rs <- a test suite - src/lib.rs <- an empty file or with exercise stubs - example.rs <- example solution that satisfies tests - Cargo.toml <- with version equal to exercise definition - Cargo.lock <- Auto generated - README.md <- Instructions for the exercise (see notes below) - -- The stub file and test suite should use only the Rust core libraries. `Cargo.toml` should not list any external dependencies as we don't want to make the student assume required crates. If an `example.rs` uses external crates, include `Cargo-example.toml` so that `_tests/check_exercises.sh` can compile with these when testing. - -- Except in extraordinary circumstances, the stub file should compile under `cargo test --no-run`. - This allows us to check that the signatures in the stub file match the signatures expected by the tests. - Use `unimplemented!()` as the body of each function to achieve this. - If there is a justified reason why this is not possible, instead include a `.custom."allowed-to-not-compile"` key in the exercise's `.meta/config.json` containing the reason. - -- If porting an existing exercise from problem-specifications that has a `canonical-data.json` file, use the version in `canonical-data.json` for that exercise as your `Cargo.toml` version. Otherwise, use "0.0.0". - -- An exercise may contain `.meta/hints.md`. This is optional and will appear after the normal exercise - instructions if present. Rust is different in many ways from other languages. This is a place where the differences required for Rust are explained. If it is a large change, you may want to call this out as a comment at the top of `src/lib.rs`, so the user recognizes to read this section before starting. - -- If the test suite is appreciably sped up by running in release mode, and there is reason to be confident that the test suite appropriately detects any overflow errors, consider adding a marker to the exercise's `.meta/config.json`: `.custom."test-in-release-mode"` should be `true`. This can particularly impact the online editor experience. - -- If your exercise implements macro-based testing (see [#392](https://github.com/exercism/rust/issues/392#issuecomment-343865993) and [`perfect-numbers.rs`](https://github.com/exercism/rust/blob/main/exercises/practice/perfect-numbers/tests/perfect-numbers.rs)), you will likely run afoul of a CI check which counts the `#[ignore]` lines and compares the result to the number of `#[test]` lines. To fix this, add a marker to the exercise's `.meta/config.json`: `.custom."ignore-count-ignores"` should be `true` to disable that check for your exercise. - -- `README.md` may be [regenerated](https://github.com/exercism/legacy-docs/blob/main/maintaining-a-track/regenerating-exercise-readmes.md) from Exercism data. The generator will use the `description.md` from the exercise directory in the [problem-specifications repository](https://github.com/exercism/problem-specifications/tree/main/exercises), then any hints in `.meta/hints.md`, then the [Rust-specific instructions](https://github.com/exercism/rust/blob/main/exercises/shared/.docs/tests.md). The `## Source` section comes from the `metadata.yml` in the same directory. Convention is that the description of the source remains text and the link is both name and hyperlink of the markdown link. - -- Be sure to add the exercise to an appropriate place in the `config.json` file. The position in the file determines the order exercises are sent. Generate a unique UUID for the exercise. Current difficulty levels in use are 1, 4, 7 and 10. +[being-a-good-community-member]: https://github.com/exercism/docs/tree/main/community/good-member +[chestertons-fence]: https://github.com/exercism/docs/blob/main/community/good-member/chestertons-fence.md +[concept-exercises]: https://github.com/exercism/docs/blob/main/building/tracks/concept-exercises.md +[exercise-presentation]: https://github.com/exercism/docs/blob/main/building/tracks/presentation.md +[exercism-admins]: https://github.com/exercism/docs/blob/main/community/administrators.md +[exercism-code-of-conduct]: https://exercism.org/docs/using/legal/code-of-conduct +[exercism-concepts]: https://github.com/exercism/docs/blob/main/building/tracks/concepts.md +[exercism-contributors]: https://github.com/exercism/docs/blob/main/community/contributors.md +[exercism-markdown-specification]: https://github.com/exercism/docs/blob/main/building/markdown/markdown.md +[exercism-mentors]: https://github.com/exercism/docs/tree/main/mentoring +[exercism-tasks]: https://exercism.org/docs/building/product/tasks +[exercism-track-maintainers]: https://github.com/exercism/docs/blob/main/community/maintainers.md +[exercism-track-structure]: https://github.com/exercism/docs/tree/main/building/tracks +[exercism-website]: https://exercism.org/ +[exercism-writing-style]: https://github.com/exercism/docs/blob/main/building/markdown/style-guide.md +[freeing-maintainers]: https://exercism.org/blog/freeing-our-maintainers +[practice-exercises]: https://github.com/exercism/docs/blob/main/building/tracks/practice-exercises.md +[suggesting-improvements]: https://github.com/exercism/docs/blob/main/community/good-member/suggesting-exercise-improvements.md +[the-words-that-we-use]: https://github.com/exercism/docs/blob/main/community/good-member/words.md +[website-contributing-section]: https://exercism.org/docs/building +[the-rust-programming-language]: https://doc.rust-lang.org/book/ diff --git a/_test/WINDOWS_README.md b/_test/WINDOWS_README.md deleted file mode 100644 index 79555a8b5..000000000 --- a/_test/WINDOWS_README.md +++ /dev/null @@ -1,44 +0,0 @@ -# check_exercises.sh for Windows Rust Developers - -It is possible to run `check_exercises.sh` on Windows 10, pointing to the Windows location for your GitHub repository. This is done with the Ubuntu on Windows subsystem. - -## Enable Developer Mode -To run Ubuntu on Windows, you need to be in Developer Mode. - - - Open Settings - - Open Update and Security - - Select For Developers on Left Side - - Change to Developer Mode from Sideload Apps - -## Install - -Start a PowerShell as Administrator. - -Run the following: - - Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux - -## Run bash - -The `bash` command now gives you a terminal in a Ubuntu Linux instance. You have access to Windows files via /mnt/[drive_letter] - -Example: Windows user directory would be - - /mnt/c/Users/username - -## Installing Rust - -Inside bash, you will not have access to Window's Rust. You need to install the Linux version of Rust. - - curl -sf -L https://static.rust-lang.org/rustup.sh | sh - -You also need to install a cc linker for Rust. - - sudo apt-get install build-essential - -## Running Tests - - cd /mnt/c/[path of github project] - _test/check_exercises.sh - -This will re-download and build any crates needed, as they only existed in your Windows Rust. diff --git a/_test/check_exercises_for_authors.sh b/_test/check_exercises_for_authors.sh deleted file mode 100755 index d3c3fb10a..000000000 --- a/_test/check_exercises_for_authors.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash - -repo=$(cd "$(dirname "$0")/.." && pwd) - -if grep -rnw "$repo/exercises/" --include="*.toml" -e "authors"; then - echo "Found 'authors' field in exercises"; - exit 1; -fi diff --git a/_test/check_uuids.sh b/_test/check_uuids.sh deleted file mode 100755 index ae2ae60c1..000000000 --- a/_test/check_uuids.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail - -repo=$(cd "$(dirname "$0")/.." && pwd) - -# Check for invalid UUIDs. -# can be removed once `configlet lint` gains this ability. -# Check issue https://github.com/exercism/configlet/issues/99 - -bad_uuid=$(jq --raw-output '.exercises | .concept[], .practice[] | .uuid' "$repo"/config.json | grep -vE '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' || test 1) -if [ -n "$bad_uuid" ]; then - echo "invalid UUIDs found! please correct these to be valid UUIDs:" - echo "$bad_uuid" - exit 1 -fi diff --git a/_test/count_ignores.sh b/_test/count_ignores.sh deleted file mode 100755 index 22bbba105..000000000 --- a/_test/count_ignores.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env bash - -repo=$(cd "$(dirname "$0")/.." && pwd) -exitcode=0 - -for e in "$repo"/exercises/*/*; do - # An exercise must have a .meta/config.json - metaconf="$e/.meta/config.json" - if [ ! -f "$metaconf" ]; then - continue - fi - - if jq --exit-status '.custom?."ignore-count-ignores"?' "$metaconf"; then - continue - fi - if [ -d "$e/tests" ]; then - total_tests=0 - total_ignores=0 - for t in "$e"/tests/*.rs; do - tests=$(grep -c "\#\[test\]" "$t" | tr -d '[:space:]') - ignores=$(grep -c "\#\[ignore\]" "$t" | tr -d '[:space:]') - - total_tests=$((total_tests + tests)) - total_ignores=$((total_ignores + ignores)) - done - want_ignores=$((total_tests - 1)) - if [ "$total_ignores" != "$want_ignores" ]; then - # ShellCheck wants us to use printf, - # but there are no other uses of printf in this repo, - # so printf hasn't been tested to work yet. - # (We would not be opposed to using printf and removing this disable; - # we just haven't tested it to confirm it works yet). - # shellcheck disable=SC2028 - echo "\033[1;31m$e: Has $total_tests tests and $total_ignores ignores (should be $want_ignores)\033[0m" - exitcode=1 - fi - fi -done - -exit $exitcode diff --git a/_test/ensure_lib_src_rs_exist.sh b/_test/ensure_lib_src_rs_exist.sh deleted file mode 100755 index 0c0205577..000000000 --- a/_test/ensure_lib_src_rs_exist.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env bash - -repo=$(cd "$(dirname "$0")/.." && pwd) - -missing="" - -empty_stub="" - -check_status=0 - -IGNORED_EXERCISES=( - "two-fer" #deprecated - "nucleotide-codons" #deprecated - "hexadecimal" #deprecated -) - -for dir in "$repo"/exercises/*/*/; do - exercise=$(basename "$dir") - - if [ ! -f "$dir/src/lib.rs" ]; then - echo "$exercise is missing a src/lib.rs stub file. Please create the missing file with the template, that is necessary for the exercise, present in it." - missing="$missing\n$exercise" - else - #Check if the stub file is empty - if [ ! -s "$dir/src/lib.rs" ] || [ "$(cat "$dir/src/lib.rs")" == "" ] && [[ " ${IGNORED_EXERCISES[*]} " != *"$exercise"* ]]; then - echo "$exercise has src/lib.rs stub file, but it is empty." - empty_stub="$empty_stub\n$exercise" - fi - fi -done - -if [ -n "$missing" ]; then - # extra echo to generate a new line - echo - echo "Exercises missing src/lib.rs:$missing" - - check_status=1 -fi - -if [ -n "$empty_stub" ]; then - echo - echo "Exercises with empty src/lib.rs stub file:$empty_stub" - - check_status=1 -fi - -if [ "$check_status" -ne 0 ]; then - exit 1 -else - exit 0 -fi diff --git a/_test/verify_exercise_difficulties.sh b/_test/verify_exercise_difficulties.sh deleted file mode 100755 index e7bb6179f..000000000 --- a/_test/verify_exercise_difficulties.sh +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env bash - -set -e - -repo=$(cd "$(dirname "$0")/.." && pwd) -config=$repo/config.json - -es=0 - -# ensure every exercise has a difficulty -no_difficulty=$( - jq --raw-output ' - .exercises | - .concept[], .practice[] | - select((.status != "deprecated") and (has("difficulty") | not)) | - .slug - ' "$config" -) -if [ -n "$no_difficulty" ]; then - echo "Exercises without a difficulty in config.json:" - echo "$no_difficulty" - es=1 -fi - -# ensure that all difficulties are one of 1, 4, 7, 10 -invalid_difficulty=$( - jq --raw-output ' - .exercises | - .concept[], .practice[] | - select( - (.status != "deprecated") and - has("difficulty") and - ( - .difficulty | tostring | - in({"1":null,"4":null,"7":null,"10":null}) | - not - ) - ) | - "\(.slug) (\(.difficulty))" - ' "$config" -) -if [ -n "$invalid_difficulty" ]; then - echo "Exercises with invalid difficulty (must be in {1, 4, 7, 10})" - echo "$invalid_difficulty" - es=1 -fi - -# ensure difficulties are sorted -#exercise_order=$(jq --raw-output '.exercises[] | select(.deprecated | not) | .slug' $config) -#sorted_order=$(jq --raw-output '.exercises | sort_by(.difficulty) | .[] | select(.deprecated | not) | .slug' $config) -#if [ "$exercise_order" != "$sorted_order" ]; then -# echo "Exercises are not in sorted order in config.json" -# es=1 -#fi - -exit $es diff --git a/bin/.shellcheckrc b/bin/.shellcheckrc deleted file mode 100644 index f229171c6..000000000 --- a/bin/.shellcheckrc +++ /dev/null @@ -1,3 +0,0 @@ -shell=bash -external-sources=true -disable=SC2001 diff --git a/bin/add_practice_exercise b/bin/add_practice_exercise deleted file mode 100755 index a9068e431..000000000 --- a/bin/add_practice_exercise +++ /dev/null @@ -1,112 +0,0 @@ -#!/usr/bin/env bash - -# see comment in generator-utils/utils.sh -# shellcheck source=bin/generator-utils/utils.sh -# shellcheck source=bin/generator-utils/templates.sh -# shellcheck source=bin/generator-utils/prompts.sh -# shellcheck source=./generator-utils/utils.sh -# shellcheck source=./generator-utils/prompts.sh -# shellcheck source=./generator-utils/templates.sh - -source ./bin/generator-utils/utils.sh -source ./bin/generator-utils/prompts.sh -source ./bin/generator-utils/templates.sh - -# Exit if anything fails. -set -euo pipefail - -# If argument not provided, print usage and exit -if [ $# -ne 1 ] && [ $# -ne 2 ] && [ $# -ne 3 ]; then - echo "Usage: bin/add_practice_exercise [difficulty] [author-github-handle]" - exit 1 -fi - -# Check if sed is gnu-sed -if ! sed --version | grep -q "GNU sed"; then - echo "GNU sed is required. Please install it and make sure it's in your PATH." - exit 1 -fi - -# Check if jq and curl are installed -command -v jq >/dev/null 2>&1 || { - echo >&2 "jq is required but not installed. Please install it and make sure it's in your PATH." - exit 1 -} -command -v curl >/dev/null 2>&1 || { - echo >&2 "curl is required but not installed. Please install it and make sure it's in your PATH." - exit 1 -} - -# Build configlet -bin/fetch-configlet -message "success" "Fetched configlet successfully!" - -# Check if exercise exists in configlet info or in config.json -check_exercise_existence "$1" - -# ================================================== - -slug="$1" -# Fetch canonical data -canonical_json=$(bin/fetch_canonical_data "$slug") - -has_canonical_data=true -if [ "${canonical_json}" == "404: Not Found" ]; then - has_canonical_data=false - message "warning" "This exercise doesn't have canonical data" - -else - echo "$canonical_json" >canonical_data.json - message "success" "Fetched canonical data successfully!" -fi - -underscored_slug=$(dash_to_underscore "$slug") -exercise_dir="exercises/practice/${slug}" -exercise_name=$(format_exercise_name "$slug") -message "info" "Using ${yellow}${exercise_name}${blue} as a default exercise name. You can edit this later in the config.json file" -# using default value for difficulty -exercise_difficulty=$(validate_difficulty_input "${2:-$(get_exercise_difficulty)}") -message "info" "The exercise difficulty has been set to ${yellow}${exercise_difficulty}${blue}. You can edit this later in the config.json file" -# using default value for author -author_handle=${3:-$(get_author_handle)} -message "info" "Using ${yellow}${author_handle}${blue} as author's handle. You can edit this later in the 'authors' field in the ${exercise_dir}/.meta/config.json file" - -create_rust_files "$exercise_dir" "$slug" "$has_canonical_data" - -# ================================================== - - -# Preparing config.json -message "info" "Adding instructions and configuration files..." - -uuid=$(bin/configlet uuid) - -# Add exercise-data to global config.json -jq --arg slug "$slug" --arg uuid "$uuid" --arg name "$exercise_name" --argjson difficulty "$exercise_difficulty" \ - '.exercises.practice += [{slug: $slug, name: $name, uuid: $uuid, practices: [], prerequisites: [], difficulty: $difficulty}]' \ - config.json >config.json.tmp -# jq always rounds whole numbers, but average_run_time needs to be a float -sed -i 's/"average_run_time": \([0-9]\+\)$/"average_run_time": \1.0/' config.json.tmp -mv config.json.tmp config.json -message "success" "Added instructions and configuration files" - -# Create instructions and config files -echo "Creating instructions and config files" - -./bin/configlet sync --update --yes --docs --metadata --exercise "$slug" -./bin/configlet sync --update --yes --filepaths --exercise "$slug" -./bin/configlet sync --update --tests include --exercise "$slug" -message "success" "Created instructions and config files" - -# Push author to "authors" array in ./meta/config.json -meta_config="$exercise_dir"/.meta/config.json -jq --arg author "$author_handle" '.authors += [$author]' "$meta_config" >"$meta_config".tmp && mv "$meta_config".tmp "$meta_config" -message "success" "You've been added as the author of this exercise." - -sed -i "s/name = \".*\"/name = \"$underscored_slug\"/" "$exercise_dir"/Cargo.toml - -message "done" "All stub files were created." - -message "info" "After implementing the solution, tests and configuration, please run:" - -echo "./bin/configlet fmt --update --yes --exercise ${slug}" diff --git a/_test/check_exercises.sh b/bin/check_exercises.sh similarity index 79% rename from _test/check_exercises.sh rename to bin/check_exercises.sh index 5ec830c38..0b69cfea7 100755 --- a/_test/check_exercises.sh +++ b/bin/check_exercises.sh @@ -1,16 +1,5 @@ #!/usr/bin/env bash -# test for existence and executability of the test-exercise script -# this depends on that -if [ ! -f "./bin/test-exercise" ]; then - echo "bin/test-exercise does not exist" - exit 1 -fi -if [ ! -x "./bin/test-exercise" ]; then - echo "bin/test-exercise does not have its executable bit set" - exit 1 -fi - # In DENYWARNINGS or CLIPPY mode, do not set -e so that we run all tests. # This allows us to see all warnings. # If we are in neither DENYWARNINGS nor CLIPPY mode, do set -e. @@ -19,14 +8,14 @@ if [ -z "$DENYWARNINGS" ] && [ -z "$CLIPPY" ]; then fi # can't benchmark with a stable compiler; to bench, use -# $ BENCHMARK=1 rustup run nightly _test/check_exercises.sh +# $ BENCHMARK=1 rustup run nightly bin/check_exercises.sh if [ -n "$BENCHMARK" ]; then target_dir=benches else target_dir=tests fi -repo=$(cd "$(dirname "$0")/.." && pwd) +repo=$(git rev-parse --show-toplevel) if [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then files="$( @@ -70,11 +59,11 @@ for exercise in $files; do # (such as "Compiling"/"Downloading"). # Compiler errors will still be shown though. # Both flags are necessary to keep things quiet. - ./bin/test-exercise "$directory" --quiet --no-run + ./bin/test_exercise.sh "$directory" --quiet --no-run return_code=$((return_code | $?)) else # Run the test and get the status - ./bin/test-exercise "$directory" $release + ./bin/test_exercise.sh "$directory" $release return_code=$((return_code | $?)) fi done diff --git a/bin/clean_topics_vs_practices.py b/bin/clean_topics_vs_practices.py deleted file mode 100755 index ed3b55d01..000000000 --- a/bin/clean_topics_vs_practices.py +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env python3 -import json - - -def main(): - with open("config.json", encoding="utf-8") as f: - config = json.load(f) - - concepts = {c['slug'] for c in config['concepts']} - - for practice_exercise in config['exercises']['practice']: - if practice_exercise['topics'] is None: - continue - - practice_exercise['practices'].extend((topic for topic in practice_exercise['topics'] if topic in concepts)) - practice_exercise['topics'] = [topic for topic in practice_exercise['topics'] if topic not in concepts] - - for concept in concepts: - count = 0 - for practice_exercise in config['exercises']['practice']: - if concept in practice_exercise['practices']: - count += 1 - if count > 10: - practice_exercise['practices'].remove(concept) - practice_exercise['topics'].append(concept) - - for practice_exercise in config['exercises']['practice']: - practice_exercise['practices'].sort() - - if practice_exercise['topics'] is not None: - practice_exercise['topics'].sort() - - - with open("config.json", 'w', encoding="utf-8") as f: - json.dump(config, f, indent=2, ensure_ascii=False) - f.write('\n') - - print("Updated config.json") - - -if __name__ == '__main__': - main() diff --git a/_test/ensure_stubs_compile.sh b/bin/ensure_stubs_compile.sh similarity index 98% rename from _test/ensure_stubs_compile.sh rename to bin/ensure_stubs_compile.sh index 6f154ae31..623c2d1cd 100755 --- a/_test/ensure_stubs_compile.sh +++ b/bin/ensure_stubs_compile.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -repo="$(cd "$(dirname "$0")/.." && pwd)" +repo="$(git rev-parse --show-toplevel)" if [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then changed_exercises="$( diff --git a/bin/fetch_canonical_data b/bin/fetch_canonical_data deleted file mode 100755 index 4226a4b92..000000000 --- a/bin/fetch_canonical_data +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash -# This script fetches the canonical data of the exercise. - - -# Exit if anything fails. -set -euo pipefail - - -if [ $# -ne 1 ]; then - echo "Usage: bin/fetch_canonical_data " - exit 1 -fi - -# check if curl is installed -command -v curl >/dev/null 2>&1 || { - echo >&2 "curl is required but not installed. Please install it and make sure it's in your PATH." - exit 1 -} - -slug=$1 - -curlopts=( - --silent - --retry 3 - --max-time 4 -) -curl "${curlopts[@]}" "https://raw.githubusercontent.com/exercism/problem-specifications/main/exercises/$%7Bslug%7D/canonical-data.json" diff --git a/bin/format_exercises b/bin/format_exercises.sh similarity index 86% rename from bin/format_exercises rename to bin/format_exercises.sh index 7798baea7..8d8fa2d71 100755 --- a/bin/format_exercises +++ b/bin/format_exercises.sh @@ -2,14 +2,14 @@ # Format existing exercises using rustfmt set -e -RUST_TRACK_REPO_PATH=$(cd "$(dirname "$0")/.." && pwd) +repo=$(git rev-parse --show-toplevel) # traverse either concept or practice exercise # directory and format Rust files format_exercises() { - EXERCISES_PATH="${RUST_TRACK_REPO_PATH}/exercises/$1" + exercises_path="$repo/exercises/$1" source_file_name="$2" - for exercise_dir in "${EXERCISES_PATH}"/*; do + for exercise_dir in "$exercises_path"/*; do ( cd "$exercise_dir" config_file="$exercise_dir/.meta/config.json" diff --git a/bin/generate_tests b/bin/generate_tests deleted file mode 100755 index 1065148a2..000000000 --- a/bin/generate_tests +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env bash - -# Exit if anything fails. -set -euo pipefail - -# see comment in generator-utils/utils.sh -# shellcheck source=bin/generator-utils/utils.sh -# shellcheck source=./generator-utils/utils.sh -source ./bin/generator-utils/utils.sh - -if [ ! -e bin/generator-utils/escape_double_quotes ]; then - message "info" "Building util function" - cd util/escape_double_quotes && ./build && cd ../.. -fi - -digest_template() { - local template - template=$(bin/generator-utils/escape_double_quotes bin/test_template) - # Turn every token into a jq command - - echo "$template" | sed 's/${\([^}]*\)\}\$/$(echo $case | jq -r '\''.\1'\'')/g' -} - -message "info" "Generating tests.." -canonical_json=$(cat canonical_data.json) - -slug=$(echo "$canonical_json" | jq '.exercise') -# Remove double quotes -slug=$(echo "$slug" | sed 's/"//g') -exercise_dir="exercises/practice/$slug" -test_file="$exercise_dir/tests/$slug.rs" - -cat <"$test_file" -use $(dash_to_underscore "$slug")::*; - -EOT - -# Flattens canonical json, extracts only the objects with a uuid -cases=$(echo "$canonical_json" | jq '[ .. | objects | with_entries(select(.key | IN("uuid", "description", "input", "expected", "property"))) | select(. != {}) | select(has("uuid")) ]') - -# Shellcheck doesn't recognize that `case` is not unused - -# shellcheck disable=SC2034 -jq -c '.[]' <<<"$cases" | while read -r case; do - - # Evaluate the bash parts and replace them with their return values - eval_template="$(digest_template | sed -e "s/\$(\(.*\))/\$\(\1\)/g")" - eval_template="$(eval "echo \"$eval_template\"")" - - # Turn function name into snake_case - formatted_template=$(echo "$eval_template" | sed -E -e '/^fn/!b' -e 's/[^a-zA-Z0-9_{}()[:space:]-]//g' -e 's/([[:upper:]])/ \L\1/g' -e 's/(fn[[:space:]]+)([a-z0-9_-]+)/\1\L\2/g' -e 's/ /_/g' -e 's/_\{/\{/g' -e 's/-/_/g' | sed 's/fn_/fn /' | sed 's/__\+/_/g') - - # Push to test file - echo "$formatted_template" >>"$test_file" - printf "\\n" >>"$test_file" - -done - -rustfmt "$test_file" - -message "success" "Generated tests successfully! Check out ${test_file}" diff --git a/bin/generator-utils/colors.sh b/bin/generator-utils/colors.sh deleted file mode 100644 index d3c5f9339..000000000 --- a/bin/generator-utils/colors.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env bash - -reset_color=$(echo -e '\033[0m') - -red=$(echo -e '\033[0;31m') -green=$(echo -e '\033[0;32m') -yellow=$(echo -e '\033[0;33m') -blue=$(echo -e '\033[0;34m') -cyan=$(echo -e '\033[0;36m') - -bold_green=$(echo -e '\033[1;32m') - -export red green blue yellow bold_green reset_color cyan diff --git a/bin/generator-utils/prompts.sh b/bin/generator-utils/prompts.sh deleted file mode 100644 index fdec283f8..000000000 --- a/bin/generator-utils/prompts.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env bash - -# see comment in utils.sh -# shellcheck source=bin/generator-utils/colors.sh -# shellcheck source=./colors.sh -source ./bin/generator-utils/colors.sh - -get_exercise_difficulty() { - read -rp "Difficulty of exercise (1-10): " exercise_difficulty - echo "$exercise_difficulty" -} - -validate_difficulty_input() { - local valid_input=false - while ! $valid_input; do - if [[ "$1" =~ ^[1-9]$|^10$ ]]; then - local exercise_difficulty=$1 - local valid_input=true - else - read -rp "${red}Invalid input. ${reset_color}Please enter an integer between 1 and 10. " exercise_difficulty - - [[ "$exercise_difficulty" =~ ^[1-9]$|^10$ ]] && valid_input=true - - fi - done - echo "$exercise_difficulty" -} - -get_author_handle() { - local default_author_handle - default_author_handle="$(git config user.name)" - - if [ -z "$default_author_handle" ]; then - read -rp "Hey! Couldn't find your Github handle. Add it now or skip with enter and add it later in the .meta.config.json file: " author_handle - else - local author_handle="$default_author_handle" - - fi - echo "$author_handle" -} diff --git a/bin/generator-utils/templates.sh b/bin/generator-utils/templates.sh deleted file mode 100755 index 8be909592..000000000 --- a/bin/generator-utils/templates.sh +++ /dev/null @@ -1,157 +0,0 @@ -#!/usr/bin/env bash - -# see comment in utils.sh -# shellcheck source=bin/generator-utils/utils.sh -# shellcheck source=./utils.sh -source ./bin/generator-utils/utils.sh - -create_fn_name() { - local slug=$1 - local has_canonical_data=$2 - - if [ "$has_canonical_data" == false ]; then - fn_name=$(dash_to_underscore "$slug") - local fn_name - - else - fn_name=$(jq -r 'first(.. | .property? // empty)' canonical_data.json) - fi - - echo "$fn_name" - -} - -create_test_file_template() { - local exercise_dir=$1 - local slug=$2 - local has_canonical_data=$3 - local test_file="${exercise_dir}/tests/${slug}.rs" - - cat <"$test_file" -use $(dash_to_underscore "$slug")::*; -// Add tests here - -EOT - - if [ "$has_canonical_data" == false ]; then - - cat <>"$test_file" -// As there isn't a canonical data file for this exercise, you will need to craft your own tests. -// If you happen to devise some outstanding tests, do contemplate sharing them with the community by contributing to this repository: -// https://github.com/exercism/problem-specifications/tree/main/exercises/${slug} -EOT - message "info" "This exercise doesn't have canonical data." - message "success" "Stub file for tests has been created!" - else - - local canonical_json - - canonical_json=$(cat canonical_data.json) - - # sometimes canonical data has multiple levels with multiple `cases` arrays. - #(see kindergarten-garden https://github.com/exercism/problem-specifications/blob/main/exercises/kindergarten-garden/canonical-data.json) - # so let's flatten it - - local cases - cases=$(echo "$canonical_json" | jq '[ .. | objects | with_entries(select(.key | IN("uuid", "description", "input", "expected"))) | select(. != {}) | select(has("uuid")) ]') - local fn_name - - fn_name=$(echo "$canonical_json" | jq -r 'first(.. | .property? // empty)') - - first_iteration=true - # loop through each object - jq -c '.[]' <<<"$cases" | while read -r case; do - desc=$(echo "$case" | jq '.description' | tr '[:upper:]' '[:lower:]' | tr ' ' '_' | tr -cd '[:alnum:]_' | sed 's/^/test_/') - input=$(echo "$case" | jq -c '.input') - expected=$(echo "$case" | jq -c '.expected') - - # append each test fn to the test file - cat <>"$test_file" -#[test] $([[ "$first_iteration" == false ]] && printf "\n#[ignore]") -fn ${desc}() { - let input = ${input}; - let expected = ${expected}; - - // TODO: Verify assertion - assert_eq!(${fn_name}(input), expected); -} - -EOT - first_iteration=false - done - message "success" "Stub file for tests has been created and populated with canonical data!" - fi - -} - -create_lib_rs_template() { - local exercise_dir=$1 - local slug=$2 - local has_canonical_data=$3 - local fn_name - - fn_name=$(create_fn_name "$slug" "$has_canonical_data") - cat <"${exercise_dir}/src/lib.rs" -pub fn ${fn_name}() { - unimplemented!("implement ${slug} exercise"); -} -EOT - message "success" "Stub file for lib.rs has been created!" -} - -overwrite_gitignore() { - - local exercise_dir=$1 - cat <"$exercise_dir"/.gitignore -# Generated by Cargo -# Will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock -EOT - message "success" ".gitignore has been overwritten!" -} - -create_example_rs_template() { - - local exercise_dir=$1 - local slug=$2 - local has_canonical_data=$3 - - local fn_name - - fn_name=$(create_fn_name "$slug" "$has_canonical_data") - - mkdir "${exercise_dir}/.meta" - cat <"${exercise_dir}/.meta/example.rs" -pub fn ${fn_name}() { - // TODO: Create a solution that passes all the tests - unimplemented!("implement ${slug} exercise"); -} - -EOT - message "success" "Stub file for example.rs has been created!" -} - -create_rust_files() { - - local exercise_dir=$1 - local slug=$2 - local has_canonical_data=$3 - - message "info" "Creating Rust files" - cargo new --lib "$exercise_dir" -q - mkdir -p "$exercise_dir"/tests - touch "${exercise_dir}/tests/${slug}.rs" - - create_test_file_template "$exercise_dir" "$slug" "$has_canonical_data" - create_lib_rs_template "$exercise_dir" "$slug" "$has_canonical_data" - create_example_rs_template "$exercise_dir" "$slug" "$has_canonical_data" - overwrite_gitignore "$exercise_dir" - - message "success" "Created Rust files succesfully!" - -} diff --git a/bin/generator-utils/utils.sh b/bin/generator-utils/utils.sh deleted file mode 100755 index 227a9bcd8..000000000 --- a/bin/generator-utils/utils.sh +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/env bash - -# top one gets evaluated -# relative one is needed for .shellcheckrc to test if files exist in local env -# absolute one is needed for CI to test if files exist -# before commit swap these accordingly -# shellcheck source=bin/generator-utils/colors.sh -# shellcheck source=./colors.sh -source ./bin/generator-utils/colors.sh - -message() { - local flag=$1 - local message=$2 - - case "$flag" in - "success") printf "${green}%s${reset_color}\n" "[success]: $message" ;; - "task") printf "${cyan}%s${reset_color}\n" "[task]: $message" ;; - "info") printf "${blue}%s${reset_color}\n" "[info]: $message" ;; - "warning") printf "${yellow}%s${reset_color}\n" "[warning]: $message" ;; - "error") printf "${red}%s${reset_color}\n" "[error]: $message" ;; - "done") - echo - # Generate a dashed line that spans the entire width of the screen. - local cols - cols=$(tput cols) - printf "%*s\n" "$cols" "" | tr " " "-" - echo - printf "${bold_green}%s${reset_color}\n" "[done]: $message" - ;; - *) echo "Invalid flag: $flag" ;; - esac -} - -dash_to_underscore() { - echo "$1" | sed 's/-/_/g' -} - -# exercise_name -> Exercise Name - -format_exercise_name() { - echo "$1" | sed 's/-/ /g; s/\b\(.\)/\u\1/g' -} - -check_exercise_existence() { - message "info" "Looking for exercise.." - local slug="$1" - - # Check if exercise is already in config.json - if jq '.exercises.practice | map(.slug)' config.json | grep -q "$slug"; then - echo "${1} has already been implemented." - exit 1 - fi - - # Fetch configlet and crop out exercise list - local unimplemented_exercises - unimplemented_exercises=$(bin/configlet info | sed -n '/With canonical data:/,/Track summary:/p' | sed -e '/\(With\(out\)\? canonical data:\|Track summary:\)/d' -e '/^$/d') - if echo "$unimplemented_exercises" | grep -q "^$slug$"; then - message "success" "Exercise has been found!" - else - message "error" "Exercise doesn't exist!" - message "info" "These are the unimplemented practice exercises: -${unimplemented_exercises}" - - # Find closest match to typed-in not-found slug - - # See util/ngram for source - # First it builds a binary for the system of the contributor - if [ -e bin/generator-utils/ngram ]; then - echo "${yellow}$(bin/generator-utils/ngram "${unimplemented_exercises}" "$slug")${reset_color}" - else - message "info" "Building typo-checker binary for $(uname -m) system." - cd util/ngram && ./build && cd ../.. && echo "${yellow}$(bin/generator-utils/ngram "${unimplemented_exercises}" "$slug")${reset_color}" - fi - exit 1 - fi -} diff --git a/bin/lint_markdown.sh b/bin/lint_markdown.sh index 6f3592c7c..52b75c2ab 100755 --- a/bin/lint_markdown.sh +++ b/bin/lint_markdown.sh @@ -1,3 +1,7 @@ #!/usr/bin/env bash -set -e -npx markdownlint-cli concepts/**/*.md exercises/**/*.md docs/maintaining.md docs/CONTRIBUTING.md +set -eo pipefail + +npx markdownlint-cli \ + docs/*.md \ + concepts/**/*.md \ + exercises/**/*.md diff --git a/bin/lint_tool_file_names.sh b/bin/lint_tool_file_names.sh deleted file mode 100755 index 0c0ef67e7..000000000 --- a/bin/lint_tool_file_names.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash - -# Require that internal shell script file names use snake_case -set -eo pipefail - -# find a list of files whose names do not match our convention -errant_files=$(find bin/ _test/ -iname '*\.sh' -exec basename {} \; | grep '[^a-z_.]' || test 1) - -if [ -n "$errant_files" ]; then - echo "These file names do not follow our snake case convention:" - # find them again to print the whole relative path - while IFS= read -r file_name; do - find . -name "$file_name" - done <<< "$errant_files" - echo "Please correct them!" - exit 1 -fi diff --git a/bin/lint_trailing_spaces.sh b/bin/lint_trailing_spaces.sh deleted file mode 100755 index 16f021a14..000000000 --- a/bin/lint_trailing_spaces.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash - -# Report any .toml files that have trailing white space -# so user can fix them -set -eo pipefail - -files=$(find . \( -iname '*.toml' -o -iname '*.sh' -o -iname '*.rs' \) -exec grep -l '[[:space:]]$' {} \;) - -if [ -n "$files" ]; then - echo "These files have trailing whitespace:" - echo "$files" - echo "Our conventions disallow this so please remove the trailing whitespace." - exit 1 -fi diff --git a/bin/remove_trailing_whitespace.sh b/bin/remove_trailing_whitespace.sh deleted file mode 100755 index 5a93912f7..000000000 --- a/bin/remove_trailing_whitespace.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env bash - -# removes all trailing whitespaces from *.sh and *.py files in this folder -find . -type f \( -name "*.sh" -o -name "*.py" \) -exec sed -i 's/[[:space:]]\+$//' {} \; diff --git a/bin/test-exercise b/bin/test_exercise.sh similarity index 98% rename from bin/test-exercise rename to bin/test_exercise.sh index ee8d3040e..2e1c04b26 100755 --- a/bin/test-exercise +++ b/bin/test_exercise.sh @@ -15,7 +15,7 @@ if [ $# -ge 1 ]; then # so if you are in the exercise directory and want to pass any # arguments to cargo, you need to include the local path first. # I.e. to test in release mode: - # $ test-exercise . --release + # $ test_exercise.sh . --release shift 1 else exercise='.' diff --git a/bin/test_template b/bin/test_template deleted file mode 100644 index 93b513457..000000000 --- a/bin/test_template +++ /dev/null @@ -1,6 +0,0 @@ -#[test] -#[ignore] -fn ${description}$() { -let expected = "${expected}$"; - assert_eq!(${property}$(${input}$), expected); -} diff --git a/concepts/methods/about.md b/concepts/methods/about.md index 14eaafdfb..81280e238 100644 --- a/concepts/methods/about.md +++ b/concepts/methods/about.md @@ -59,7 +59,7 @@ If we wish to implement an `info` method to display the basic information of the we could define this method inside an `impl` block for `Wizard`: ```rust -impl Wizard { +impl Wizard { fn info(&self) { println!( "A wizard of age {} who studies in House {:?} at Hogwarts", diff --git a/concepts/references/about.md b/concepts/references/about.md index 654ff59ad..63c3917ab 100644 --- a/concepts/references/about.md +++ b/concepts/references/about.md @@ -65,7 +65,7 @@ fn check_shapes(constant: &[u8], linear: &[u8], superlinear: &[u8]) -> (bool, bo // understanding the implementations of the following functions is not necessary for this example // but are provided should you be interested -fn is_constant(slice: &[u8]) -> bool { +fn is_constant(slice: &[u8]) -> bool { slice .first() .map(|first| slice.iter().all(|v| v == first)) @@ -146,7 +146,7 @@ pub fn main() { } ``` -This works, because the compiler knows that the mutable borrows do not overlap +This works, because the compiler knows that the mutable borrows do not overlap ```rust fn add_five(counter: &mut i32) { diff --git a/config.json b/config.json index dd533207c..66e160a0e 100644 --- a/config.json +++ b/config.json @@ -76,9 +76,9 @@ }, { "slug": "semi-structured-logs", + "uuid": "1924b87a-9246-456f-8fc1-111f922a8cf3", "name": "Semi Structured Logs", "difficulty": 1, - "uuid": "1924b87a-9246-456f-8fc1-111f922a8cf3", "concepts": [ "enums" ], @@ -89,8 +89,8 @@ }, { "slug": "resistor-color", - "name": "Resistor Color", "uuid": "51c31e6a-b7ec-469d-8a28-dd821fd857d2", + "name": "Resistor Color", "difficulty": 1, "concepts": [ "external-crates" @@ -132,9 +132,9 @@ }, { "slug": "low-power-embedded-game", + "uuid": "7f064e9b-f631-48b1-9ed0-a66e8393ceba", "name": "Low-Power Embedded Game", "difficulty": 1, - "uuid": "7f064e9b-f631-48b1-9ed0-a66e8393ceba", "concepts": [ "tuples", "destructuring" @@ -146,9 +146,9 @@ }, { "slug": "short-fibonacci", + "uuid": "c481e318-ddd7-4f8a-91eb-dadb7315e304", "name": "A Short Fibonacci Sequence", "difficulty": 1, - "uuid": "c481e318-ddd7-4f8a-91eb-dadb7315e304", "concepts": [ "vec-macro" ], @@ -160,9 +160,9 @@ }, { "slug": "rpn-calculator", + "uuid": "25cc722b-211d-4271-9381-fdfe16b41301", "name": "RPN Calculator", "difficulty": 4, - "uuid": "25cc722b-211d-4271-9381-fdfe16b41301", "concepts": [ "vec-stack" ], @@ -176,9 +176,9 @@ }, { "slug": "csv-builder", + "uuid": "10c9f505-9aef-479f-b689-cb7959572482", "name": "CSV builder", "difficulty": 1, - "uuid": "10c9f505-9aef-479f-b689-cb7959572482", "concepts": [ "string-vs-str" ], @@ -1482,7 +1482,7 @@ "practices": [], "prerequisites": [], "difficulty": 1, - "topics": null, + "topics": [], "status": "deprecated" }, { @@ -1492,7 +1492,7 @@ "practices": [], "prerequisites": [], "difficulty": 1, - "topics": null, + "topics": [], "status": "deprecated" }, { @@ -1514,7 +1514,8 @@ "uuid": "cbccd0c5-eb15-4705-9a4c-0209861f078c", "practices": [], "prerequisites": [], - "difficulty": 4 + "difficulty": 4, + "topics": [] }, { "slug": "kindergarten-garden", @@ -1522,7 +1523,8 @@ "uuid": "c27e4878-28a4-4637-bde2-2af681a7ff0d", "practices": [], "prerequisites": [], - "difficulty": 1 + "difficulty": 1, + "topics": [] }, { "slug": "yacht", @@ -1530,7 +1532,8 @@ "uuid": "1a0e8e34-f578-4a53-91b0-8a1260446553", "practices": [], "prerequisites": [], - "difficulty": 4 + "difficulty": 4, + "topics": [] } ], "foregone": [ diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 94af02384..a0eb8e3a8 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -1,39 +1,99 @@ # Contributing to the Rust Exercism Track -This track is a work in progress. -Please take all the value you can from it, but if you notice any way to improve it, we are eager for pull requests. -Fixing a typo in documentation is every bit as valid a contribution as a massive addition of code. +Issues and pull requests are currently being auto-closed. +Please make a post on [the Exercism forum] to propose changes. +Contributions are very welcome if they are coordinated on the forum. -> A work of art is never finished, merely abandoned. -> -> -- Paul Valéry +[the Exercism forum]: https://forum.exercism.org/ -Nonetheless, feel free to peruse what we have written thus far! +## General policies -*Contributions welcome :)* +- [Code of Conduct](https://exercism.org/code-of-conduct) +- [Exercism's PR guidelines](https://exercism.org/docs/community/being-a-good-community-member/pull-requests). -As one of the many tracks in Exercism, contributions here should observe Exercism standards like the [Code of Conduct](https://exercism.org/code-of-conduct). -This document introduces the ways you can help and what maintainers expect of contributors. +## Tooling -## Ways to Contribute +Some tooling is present as bash scripts in `bin/`. +A lot more is present in `rust-tooling/`, +which should be preferred for anything non-trivial. -As with many Open Source projects, work abounds. -Here are a few categories of welcome contribution: -- improving existing exercises -- creating new exercises -- improving internal tooling -- updating documentation -- fixing typos, misspellings, and grammatical errors +There is also a [`justfile`](https://github.com/casey/just) +with a couple useful commands to interact with the repo. +Feel free to extend it. -## Merging Philosophy +If you want to run CI tests locally, `just test` will get you quite far. -A [pull request](https://docs.github.com/en/github/getting-started-with-github/github-glossary#pull-request) should address one logical change. -This could be small or big. +## Creating a new exercise -For example, [#1175](https://github.com/exercism/rust/pull/1175) fixed a single typo in a single file after minutes of collaboration. +Please familiarize yourself with the [Exercism documentation about practice exercises]. -It was a small, short pull request with one logical change. +[Exercism documentation about practice exercises]: https://exercism.org/docs/building/tracks/practice-exercises -[#653](https://github.com/exercism/rust/pull/653) introduced the doubly linked list exercise after months of collaboration. +Run `just add-practice-exercise` and you'll be prompted for the minimal +information required to generate the exercise stub for you. +After that, jump in the generated exercise and fill in any todos you find. +This includes most notably: +- adding an example solution in `.meta/example.rs` +- Adjusting `.meta/test_template.tera` -It was a big, long-running pull request with one logical change. +The tests are generated using the template engine [Tera]. +The input of the template is the canonical data from [`problem-specifications`]. +if you want to exclude certain tests from being generated, +you have to set `include = false` in `.meta/tests.toml`. + +[Tera]: https://keats.github.io/tera/docs/ +[`problem-specifications`]: https://github.com/exercism/problem-specifications/ + +Many aspects of a correctly implemented exercises are checked in CI. +I recommend that instead of spending lots of time studying and writing +documentation about the process, *just do it*. +If something breaks, fix it and add a test / automation +so it won't happen anymore. + +If you are creating a practice exercise from scratch, +one that is not present in `problem-specifications`, +you have to write your tests manually. +Tests should be sorted by increasing complexity, +so students can un-ignore them one by one and solve the exercise with TDD. +See [the Exercism documentation](https://github.com/exercism/legacy-docs/blob/main/language-tracks/exercises/anatomy/test-suites.md) +for more thoughts on writing good tests. + +Except for extraordinary circumstances, +the stub file should compile under `cargo test --no-run`. +This allows us to check that the signatures in the stub file +match the signatures expected by the tests. +Use `todo!()` as the body of each function to achieve this. +If there is a justified reason why this is not possible, +include a `.custom."allowed-to-not-compile"` key +in the exercise's `.meta/config.json` containing the reason. + +If your exercise implements macro-based testing +(see [#392](https://github.com/exercism/rust/issues/392#issuecomment-343865993) +and [`perfect-numbers.rs`](https://github.com/exercism/rust/blob/main/exercises/practice/perfect-numbers/tests/perfect-numbers.rs)), +you will likely run afoul of a CI check which counts the `#[ignore]` lines +and compares the result to the number of `#[test]` lines. +To fix this, add a marker to the exercise's `.meta/config.json`: +`.custom."ignore-count-ignores"` should be `true` +to disable that check for your exercise. + +## Updating an exercise + +Many exercises are derived from [`problem-specifications`]. +This includes their test suite and user-facing documentation. +Before proposing changes here, +check if they should be made `problem-specifications` instead. + +Run `just update-practice-exercise` to update an exercise. +This outsources most work to `configlet sync --update` +and runs the test generator again. + +## Syllabus + +The syllabus is currently deactivated due to low quality. +see [this forum post](https://forum.exercism.org/t/feeling-lost-and-frustrated-in-rust/4882) +for some background on the desicion. + +Creating a better syllabus would be very benefitial, +but it's a lot of work and requires good communication and coordination. +Make sure to discuss any plans you have on the forum +with people who have experience building syllabi. diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md index 4e16399e4..8c86bce10 100644 --- a/docs/INSTALLATION.md +++ b/docs/INSTALLATION.md @@ -1,42 +1,6 @@ # Installation -Methods for installing Rust change as the language evolves. To get up-to-date installation instructions head to the [Rust install page](https://www.rust-lang.org/tools/install). +To get the recommended installation instructions for your platform, +head over to [the official Rust website]. -## Additional utilities - -### Rustfmt - Writing well-formatted code - -When you are solving the exercise, you are free to choose any coding format you want. -However when you are writing a real-world application or a library, your source code will -be read by other people, not just you. To solve a problem when different people choose -different formats for a single project, the developers set a standard coding format -for the said project. - -In the Rust world there is a tool, that helps developers to bring standard formatting -to their applications - [rustfmt](https://github.com/rust-lang/rustfmt). - -To install `rustfmt` use the following commands: - -```bash -rustup self update - -rustup component add rustfmt -``` - -### Clippy - Writing effective code - -At its core the process of programming consists of two parts: storing and managing -the resources of your computer. Rust provides a lot of means to accomplish these two -task. Unfortunately sometimes programmers do not use those means very effectively and -create programms that work correctly, but require a lot of resources like memory or time. - -To catch the most common ineffective usages of the Rust language, -a tool was created - [clippy](https://github.com/rust-lang/rust-clippy). - -To install `clippy` use the following commands: - -```bash -rustup self update - -rustup component add clippy -``` +[the official Rust website]: https://www.rust-lang.org/tools/install diff --git a/docs/RESOURCES.md b/docs/RESOURCES.md index cd050b3ec..6d8ee7ae7 100644 --- a/docs/RESOURCES.md +++ b/docs/RESOURCES.md @@ -6,4 +6,4 @@ * [Stack Overflow](http://stackoverflow.com/questions/tagged/rust) can be used to search for your problem and see if it has been answered already. You can also ask and answer questions. * The [Rust User Forum](http://users.rust-lang.org) is for general discussion about Rust. * [/r/rust](http://www.reddit.com/r/rust/) is the official Rust subreddit. -* [The Rust Programming Language](https://discord.gg/rust-lang) official server on [Discord](https://discordapp.com/) can be used for quick queries and discussions about the language. +* [The Rust Programming Language](https://discord.gg/rust-lang) official server on [Discord](https://discordapp.com/) can be used for quick queries and discussions about the language. diff --git a/docs/maintaining.md b/docs/maintaining.md deleted file mode 100644 index d794de3e9..000000000 --- a/docs/maintaining.md +++ /dev/null @@ -1,52 +0,0 @@ -# Maintaining Notes - -This document captures informal policies, tips, and topics useful to maintaining the Rust track. - -## Internal Tooling - -We have a number of scripts for CI tests. -They live in `bin/` and `_test/`. - -## Internal Tooling Style Guide - -This is non-exhaustive. - -- Adopt a Unix philosophy for tooling - - prefer using tools that do one thing well - - prefer using tools that are ubiquitous: `jq` or `sed` instead of `prettier` or `sd` - - write scripts to do one thing well - - prefer GNU versions of `sed` and other utilities -- Prefer Bash for scripting - - Strive for compatibility. macOS still distributes Bash v3.x by default, despite v5.x being current; this means that the scripts can't depend on certain features like map. -- Scripts should use `#!/usr/bin/env bash` as their shebang - - This increases portability on NixOS and macOS because contributors' preferred bash may not be installed in `/bin/bash`. -- Prefer snake case for script file names - - ```sh - hello_world.sh - ``` - - - This simplifies development when upgrading a script into a proper language. *Rusty tooling anyone?* -- Script file names should include the `.sh` extension -- Set the executable bit on scripts that should be called directly. -- Scripts should set the following options at the top - - ```bash - set -eo pipefail - ``` - -## Running CI Locally - -You can run CI tools locally. -Scripts expect GNU versions of tooling, so you may see unexpected results on macOS. -[Here](https://github.com/exercism/rust/issues/1138) is one example. -Windows users can also run tooling locally using [WSL](https://docs.microsoft.com/en-us/windows/wsl/). -We recommend WSL 2 with the distribution of your choice. - -## Maintainer Tips and Tricks - -Exercism tracks follow a specification that has evolved over time. -Maintainers often need to make ad-hoc migrations to files in this repository. -If you find yourself scripting such ad-hoc changes, include the source of your script using markdown codeblocks in a commit message. - -See [this commit](https://github.com/exercism/rust/commit/45eb8cc113a733636212394dee946ceff5949cc3) for an example. diff --git a/exercises/concept/magazine-cutout/.docs/instructions.md b/exercises/concept/magazine-cutout/.docs/instructions.md index 9d47432d9..8e367d5d0 100644 --- a/exercises/concept/magazine-cutout/.docs/instructions.md +++ b/exercises/concept/magazine-cutout/.docs/instructions.md @@ -27,7 +27,7 @@ assert!(!can_construct_note(&magazine, ¬e)); The function returns `false` since the magazine only contains one instance of `"two"` when the note requires two of them. -The following input will succeed: +The following input will succeed: ```rust let magazine = "Astronomer Amy Mainzer spent hours chatting with Leonardo DiCaprio for Netflix's 'Don't Look Up'".split_whitespace().collect::>(); diff --git a/exercises/concept/role-playing-game/.docs/introduction.md b/exercises/concept/role-playing-game/.docs/introduction.md index 3ab3b1cf4..53b3e9f6a 100644 --- a/exercises/concept/role-playing-game/.docs/introduction.md +++ b/exercises/concept/role-playing-game/.docs/introduction.md @@ -2,10 +2,10 @@ ## Null-References -If you have ever used another programming language (C/C++, Python, Java, Ruby, Lisp, etc.), it is likely that you have encountered `null` or `nil` before. -The use of `null` or `nil` is the way that these languages indicate that a particular variable has no value. -However, this makes accidentally using a variable that points to `null` an easy (and frequent) mistake to make. -As you might imagine, trying to call a function that isn't there, or access a value that doesn't exist can lead to all sorts of bugs and crashes. +If you have ever used another programming language (C/C++, Python, Java, Ruby, Lisp, etc.), it is likely that you have encountered `null` or `nil` before. +The use of `null` or `nil` is the way that these languages indicate that a particular variable has no value. +However, this makes accidentally using a variable that points to `null` an easy (and frequent) mistake to make. +As you might imagine, trying to call a function that isn't there, or access a value that doesn't exist can lead to all sorts of bugs and crashes. The creator of `null` went so far as to call it his ['billion-dollar mistake'.](https://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare/) ## The `Option` Type diff --git a/exercises/concept/short-fibonacci/.docs/instructions.md b/exercises/concept/short-fibonacci/.docs/instructions.md index 926d5e848..4b3d57af5 100644 --- a/exercises/concept/short-fibonacci/.docs/instructions.md +++ b/exercises/concept/short-fibonacci/.docs/instructions.md @@ -7,6 +7,7 @@ The Fibonacci sequence is a set of numbers where the next element is the sum of ## 1. Create a buffer of `count` zeroes. Create a function that creates a buffer of `count` zeroes. + ```rust let my_buffer = create_buffer(5); // [0, 0, 0, 0, 0] @@ -14,8 +15,9 @@ let my_buffer = create_buffer(5); ## 2. List the first five elements of the Fibonacci sequence -Create a function that returns the first five numbers of the Fibonacci sequence. +Create a function that returns the first five numbers of the Fibonacci sequence. Its first five elements are `1, 1, 2, 3, 5` + ```rust let first_five = fibonacci(); // [1, 1, 2, 3, 5] diff --git a/exercises/practice/acronym/.docs/instructions.md b/exercises/practice/acronym/.docs/instructions.md index e0515b4d1..c62fc3e85 100644 --- a/exercises/practice/acronym/.docs/instructions.md +++ b/exercises/practice/acronym/.docs/instructions.md @@ -4,5 +4,14 @@ Convert a phrase to its acronym. Techies love their TLA (Three Letter Acronyms)! -Help generate some jargon by writing a program that converts a long name -like Portable Network Graphics to its acronym (PNG). +Help generate some jargon by writing a program that converts a long name like Portable Network Graphics to its acronym (PNG). + +Punctuation is handled as follows: hyphens are word separators (like whitespace); all other punctuation can be removed from the input. + +For example: + +|Input|Output| +|-|-| +|As Soon As Possible|ASAP| +|Liquid-crystal display|LCD| +|Thank George It's Friday!|TGIF| diff --git a/exercises/practice/acronym/.meta/test_template.tera b/exercises/practice/acronym/.meta/test_template.tera new file mode 100644 index 000000000..c8de609e1 --- /dev/null +++ b/exercises/practice/acronym/.meta/test_template.tera @@ -0,0 +1,12 @@ +{% for test in cases %} +#[test] +{% if loop.index != 1 -%} +#[ignore] +{% endif -%} +fn {{ test.description | slugify | replace(from="-", to="_") }}() { + let input = {{ test.input.phrase | json_encode() }}; + let output = {{ crate_name }}::{{ fn_names[0] }}(input); + let expected = {{ test.expected | json_encode() }}; + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/acronym/.meta/tests.toml b/exercises/practice/acronym/.meta/tests.toml index 157cae14e..6e3277c68 100644 --- a/exercises/practice/acronym/.meta/tests.toml +++ b/exercises/practice/acronym/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [1e22cceb-c5e4-4562-9afe-aef07ad1eaf4] description = "basic" diff --git a/exercises/practice/acronym/tests/acronym.rs b/exercises/practice/acronym/tests/acronym.rs index decf634f9..e90d943a1 100644 --- a/exercises/practice/acronym/tests/acronym.rs +++ b/exercises/practice/acronym/tests/acronym.rs @@ -1,84 +1,79 @@ #[test] -fn empty() { - assert_eq!(acronym::abbreviate(""), ""); -} - -#[test] -#[ignore] fn basic() { - assert_eq!(acronym::abbreviate("Portable Network Graphics"), "PNG"); + let input = "Portable Network Graphics"; + let output = acronym::abbreviate(input); + let expected = "PNG"; + assert_eq!(output, expected); } #[test] #[ignore] fn lowercase_words() { - assert_eq!(acronym::abbreviate("Ruby on Rails"), "ROR"); -} - -#[test] -#[ignore] -fn camelcase() { - assert_eq!(acronym::abbreviate("HyperText Markup Language"), "HTML"); + let input = "Ruby on Rails"; + let output = acronym::abbreviate(input); + let expected = "ROR"; + assert_eq!(output, expected); } #[test] #[ignore] fn punctuation() { - assert_eq!(acronym::abbreviate("First In, First Out"), "FIFO"); + let input = "First In, First Out"; + let output = acronym::abbreviate(input); + let expected = "FIFO"; + assert_eq!(output, expected); } #[test] #[ignore] fn all_caps_word() { - assert_eq!( - acronym::abbreviate("GNU Image Manipulation Program"), - "GIMP" - ); -} - -#[test] -#[ignore] -fn all_caps_word_with_punctuation() { - assert_eq!(acronym::abbreviate("PHP: Hypertext Preprocessor"), "PHP"); + let input = "GNU Image Manipulation Program"; + let output = acronym::abbreviate(input); + let expected = "GIMP"; + assert_eq!(output, expected); } #[test] #[ignore] fn punctuation_without_whitespace() { - assert_eq!( - acronym::abbreviate("Complementary metal-oxide semiconductor"), - "CMOS" - ); + let input = "Complementary metal-oxide semiconductor"; + let output = acronym::abbreviate(input); + let expected = "CMOS"; + assert_eq!(output, expected); } #[test] #[ignore] fn very_long_abbreviation() { - assert_eq!( - acronym::abbreviate( - "Rolling On The Floor Laughing So Hard That My Dogs Came Over And Licked Me" - ), - "ROTFLSHTMDCOALM" - ); + let input = "Rolling On The Floor Laughing So Hard That My Dogs Came Over And Licked Me"; + let output = acronym::abbreviate(input); + let expected = "ROTFLSHTMDCOALM"; + assert_eq!(output, expected); } #[test] #[ignore] fn consecutive_delimiters() { - assert_eq!( - acronym::abbreviate("Something - I made up from thin air"), - "SIMUFTA" - ); + let input = "Something - I made up from thin air"; + let output = acronym::abbreviate(input); + let expected = "SIMUFTA"; + assert_eq!(output, expected); } #[test] #[ignore] fn apostrophes() { - assert_eq!(acronym::abbreviate("Halley's Comet"), "HC"); + let input = "Halley's Comet"; + let output = acronym::abbreviate(input); + let expected = "HC"; + assert_eq!(output, expected); } #[test] #[ignore] fn underscore_emphasis() { - assert_eq!(acronym::abbreviate("The Road _Not_ Taken"), "TRNT"); + let input = "The Road _Not_ Taken"; + let output = acronym::abbreviate(input); + let expected = "TRNT"; + assert_eq!(output, expected); } diff --git a/exercises/practice/allergies/.approaches/introduction.md b/exercises/practice/allergies/.approaches/introduction.md index fc60fb36a..80a1f732a 100644 --- a/exercises/practice/allergies/.approaches/introduction.md +++ b/exercises/practice/allergies/.approaches/introduction.md @@ -8,7 +8,6 @@ Another approach can be to store the `Allergen` values as a [`u32`][u32] and onl Something to keep in mind is to leverage [bitwise][bitwise] operations to implement the logic. - ## Approach: Create `Vec` on `new()` ```rust @@ -104,11 +103,11 @@ impl Allergies { pub fn new(n: u32) -> Allergies { Allergies { allergens: n } } - + pub fn is_allergic_to(&self, allergen: &Allergen) -> bool { self.allergens & *allergen as u32 != 0 } - + pub fn allergies(&self) -> Vec { ALLERGENS .iter() diff --git a/exercises/practice/allergies/.approaches/vec-when-requested/content.md b/exercises/practice/allergies/.approaches/vec-when-requested/content.md index 8ea98b250..c3bfce33c 100644 --- a/exercises/practice/allergies/.approaches/vec-when-requested/content.md +++ b/exercises/practice/allergies/.approaches/vec-when-requested/content.md @@ -32,11 +32,11 @@ impl Allergies { pub fn new(n: u32) -> Allergies { Allergies { allergens: n } } - + pub fn is_allergic_to(&self, allergen: &Allergen) -> bool { self.allergens & *allergen as u32 != 0 } - + pub fn allergies(&self) -> Vec { ALLERGENS .iter() @@ -69,7 +69,7 @@ The `new()` method sets its `allergens` field to the `u232` value passed in. The `is_allergic_to()` method uses the [bitwise AND operator][bitand] (`&`) to compare the `Allergen` passed in with the `allergens` `u32` field. The dereferenced `Allergen` passed in is [cast][cast] to a `u32` for the purpose of comparison with the `allergens` `u32` value. -The method returns if the comparison is not `0`. +The method returns if the comparison is not `0`. If the comparison is not `0`, then the `allergens` field contains the value of the `Allergen`, and `true` is returned. For example, if the `allergens` field is decimal `3`, it is binary `11`. diff --git a/exercises/practice/binary-search/.approaches/looping/content.md b/exercises/practice/binary-search/.approaches/looping/content.md index 2a31a7bf5..dc598f3b6 100644 --- a/exercises/practice/binary-search/.approaches/looping/content.md +++ b/exercises/practice/binary-search/.approaches/looping/content.md @@ -39,7 +39,7 @@ The `T` is constrained to be anything which implements the [`Ord`][ord] trait, w So, the `key` is of type `T` (orderable), and the `array` is of type `U` (a reference to an indexable container of orderable values of the same type as the `key`.) -Although `array` is defined as generic type `U`, which is constrained to be of type `AsRef`, +Although `array` is defined as generic type `U`, which is constrained to be of type `AsRef`, the [`as_ref()`][asref] method is used to get the reference to the actual type. Without it, the compiler would complain that "no method named `len` found for type parameter `U` in the current scope" and "cannot index into a value of type `U`". @@ -54,6 +54,7 @@ The [`cmp()`][cmp] method of the `Ord` trait is used to compare that element val Since the element is a reference, the `key` must also be referenced. The [`match`][match] arms each use a value from the `Ordering` enum. + - If the midpoint element value equals the `key`, then the midpoint is returned from the function wrapped in a [`Some`][some]. - If the midpoint element value is less than the `key`, then the `left` value is adjusted to be one to the right of the midpoint. - If the midpoint element value is greater than the `key`, then the `right` value is adjusted to be the midpoint. diff --git a/exercises/practice/binary-search/.approaches/recursion/content.md b/exercises/practice/binary-search/.approaches/recursion/content.md index f7a5facca..dcae57b84 100644 --- a/exercises/practice/binary-search/.approaches/recursion/content.md +++ b/exercises/practice/binary-search/.approaches/recursion/content.md @@ -39,7 +39,7 @@ of the same type as the `key`.) Since slices of the `array` will keep getting shorter with each recursive call to itself, `find_rec()` has an `offset` parameter to keep track of the actual midpoint as it relates to the original `array`. -Although `array` is defined as generic type `U`, which is constrained to be of type `AsRef`, +Although `array` is defined as generic type `U`, which is constrained to be of type `AsRef`, the [`as_ref()`][asref] method is used to get the reference to the actual type. Without it, the compiler would complain that "no method named `len` found for type parameter `U` in the current scope" and "cannot index into a value of type `U`". @@ -51,13 +51,14 @@ The [`cmp()`][cmp] method of the `Ord` trait is used to compare that element val Since the element is a reference, the `key` must also be referenced. The [`match`][match] arms each use a value from the `Ordering` enum. + - If the midpoint element value equals the `key`, then the midpoint plus the offset is returned from the function wrapped in a [`Some`][some]. - If the midpoint element value is less than the `key`, then `find_rec()` calls itself, -passing a slice of the `array` from the element to the right of the midpoint through the end of the `array`. -The offset is adjusted to be itself plus the midpoint plus `1`. + passing a slice of the `array` from the element to the right of the midpoint through the end of the `array`. + The offset is adjusted to be itself plus the midpoint plus `1`. - If the midpoint element value is greater than the `key`, then `find_rec()` calls itself, -passing a slice of the `array` from the beginning up to but not including the midpoint element. -The offset remains as is. + passing a slice of the `array` from the beginning up to but not including the midpoint element. + The offset remains as is. While the element value is not equal to the `key`, `find_rec()` keeps calling itself while halving the number of elements being searched, until either the `key` is found, or, if it is not in the `array`, the `array` is whittled down to empty. diff --git a/exercises/practice/collatz-conjecture/.docs/instructions.md b/exercises/practice/collatz-conjecture/.docs/instructions.md index 9a6de68c5..6eec8560e 100644 --- a/exercises/practice/collatz-conjecture/.docs/instructions.md +++ b/exercises/practice/collatz-conjecture/.docs/instructions.md @@ -7,8 +7,8 @@ odd, multiply n by 3 and add 1 to get 3n + 1. Repeat the process indefinitely. The conjecture states that no matter which number you start with, you will always reach 1 eventually. -But sometimes the number grow significantly before it reaches 1. -This can lead to an integer overflow and makes the algorithm unsolvable +But sometimes the number grow significantly before it reaches 1. +This can lead to an integer overflow and makes the algorithm unsolvable within the range of a number in u64. Given a number n, return the number of steps required to reach 1. diff --git a/exercises/practice/dot-dsl/.docs/instructions.append.md b/exercises/practice/dot-dsl/.docs/instructions.append.md index f2bb96c02..33fd9f817 100644 --- a/exercises/practice/dot-dsl/.docs/instructions.append.md +++ b/exercises/practice/dot-dsl/.docs/instructions.append.md @@ -1,7 +1,7 @@ # Builder pattern This exercise expects you to build several structs using `builder pattern`. -In short, this pattern allows you to split the construction function of your struct, that contains a lot of arguments, into +In short, this pattern allows you to split the construction function of your struct, that contains a lot of arguments, into several separate functions. This approach gives you the means to make compact but highly-flexible struct construction and configuration. You can read more about it on the [following page](https://doc.rust-lang.org/1.0.0/style/ownership/builders.html). diff --git a/exercises/practice/isogram/.approaches/filter-all/content.md b/exercises/practice/isogram/.approaches/filter-all/content.md index 5df0b0429..43e73fd5e 100644 --- a/exercises/practice/isogram/.approaches/filter-all/content.md +++ b/exercises/practice/isogram/.approaches/filter-all/content.md @@ -23,18 +23,19 @@ let mut hs = std::collections::HashSet::new(); ``` After the `HashSet` is instantiated, a series of functions are chained from the `candidate` `&str`. + - Since all of the characters are [ASCII][ascii], they can be iterated with the [`bytes`][bytes] method. -Each byte is iterated as a [`u8`][u8], which is an unsigned 8-bit integer. + Each byte is iterated as a [`u8`][u8], which is an unsigned 8-bit integer. - The [`filter`][filter] method [borrows][borrow] each byte as a [reference][reference] to a `u8` (`&u8`). -Inside of its [closure][closure] it tests each byte to see if it [`is_ascii_alphabetic`][is-ascii-alphabetic]. -Only bytes which are ASCII letters will survive the `filter` to be passed on to the [`map`][map] method. + Inside of its [closure][closure] it tests each byte to see if it [`is_ascii_alphabetic`][is-ascii-alphabetic]. + Only bytes which are ASCII letters will survive the `filter` to be passed on to the [`map`][map] method. - The `map` method calls [`to_ascii_lowercase`][to-ascii-lowercase] on each byte. - Each lowercased byte is then tested by the [`all`][all] method by using the [`insert`][insert] method of `HashSet`. -`all` will return `true` if every call to `insert` returns true. -If a call to `insert` returns `false` then `all` will "short-circuit" and immediately return `false`. -The `insert` method returns whether the value is _newly_ inserted. -So, for the word `"alpha"`, `insert` will return `true` when the first `a` is inserted, -but will return `false` when the second `a` is inserted. + `all` will return `true` if every call to `insert` returns true. + If a call to `insert` returns `false` then `all` will "short-circuit" and immediately return `false`. + The `insert` method returns whether the value is _newly_ inserted. + So, for the word `"alpha"`, `insert` will return `true` when the first `a` is inserted, + but will return `false` when the second `a` is inserted. ## Refactoring @@ -53,7 +54,7 @@ candidate However, changing the case of all characters in a `str` raised the average benchmark a few nanoseconds. It is a bit faster to `filter` out non-ASCII letters and to change the case of each surviving byte. -Since the performance is fairly close, either may be prefered. +Since the performance is fairly close, either may be prefered. ### using `filter_map` diff --git a/exercises/practice/ocr-numbers/.docs/instructions.md b/exercises/practice/ocr-numbers/.docs/instructions.md index 4086329bd..a246b898a 100644 --- a/exercises/practice/ocr-numbers/.docs/instructions.md +++ b/exercises/practice/ocr-numbers/.docs/instructions.md @@ -40,10 +40,10 @@ Update your program to recognize multi-character binary strings, replacing garbl Update your program to recognize all numbers 0 through 9, both individually and as part of a larger string. ```text - _ + _ _| -|_ - +|_ + ``` Is converted to "2" @@ -62,18 +62,18 @@ Is converted to "1234567890" Update your program to handle multiple numbers, one per line. When converting several lines, join the lines with commas. ```text - _ _ + _ _ | _| _| ||_ _| - - _ _ -|_||_ |_ + + _ _ +|_||_ |_ | _||_| - - _ _ _ + + _ _ _ ||_||_| ||_| _| - + ``` Is converted to "123,456,789" diff --git a/exercises/practice/rna-transcription/.docs/instructions.append.md b/exercises/practice/rna-transcription/.docs/instructions.append.md index ae0f7abed..25fc579e1 100644 --- a/exercises/practice/rna-transcription/.docs/instructions.append.md +++ b/exercises/practice/rna-transcription/.docs/instructions.append.md @@ -9,5 +9,5 @@ string has a valid RNA string, we don't need to return a `Result`/`Option` from This explains the type signatures you will see in the tests. The return types of both `DNA::new()` and `RNA::new()` are `Result`, -where the error type `usize` represents the index of the first invalid character +where the error type `usize` represents the index of the first invalid character (char index, not utf8). diff --git a/exercises/practice/secret-handshake/.approaches/introduction.md b/exercises/practice/secret-handshake/.approaches/introduction.md index 7c7d34ff8..ff9c1007e 100644 --- a/exercises/practice/secret-handshake/.approaches/introduction.md +++ b/exercises/practice/secret-handshake/.approaches/introduction.md @@ -20,7 +20,7 @@ pub fn actions(n: u8) -> Vec<&'static str> { _ => (3, -1, -1), }; let mut output: Vec<&'static str> = Vec::new(); - + loop { if action == end { break; diff --git a/exercises/practice/secret-handshake/.approaches/iterate-once/content.md b/exercises/practice/secret-handshake/.approaches/iterate-once/content.md index f6430f2ab..e4c686646 100644 --- a/exercises/practice/secret-handshake/.approaches/iterate-once/content.md +++ b/exercises/practice/secret-handshake/.approaches/iterate-once/content.md @@ -10,7 +10,7 @@ pub fn actions(n: u8) -> Vec<&'static str> { _ => (3, -1, -1), }; let mut output: Vec<&'static str> = Vec::new(); - + loop { if action == end { break; @@ -40,12 +40,14 @@ The [bitwise AND operator][bitand] is used to check if the input number contains For example, if the number passed in is `19`, which is `10011` in binary, then it is ANDed with `16`, which is `10000` in binary. The `1` in `10000` is also at the same position in `10011`, so the two values ANDed will not be `0`. + - `10011` AND - `10000` = - `10000` If the number passed in is `3`, which is `00011` in binary, then it is ANDed with `16`, which is `10000` in binary. The `1` in `10000` is not at the same position in `00011`, so the two values ANDed will be `0`. + - `00011` AND - `10000` = - `00000` diff --git a/exercises/practice/simple-linked-list/.approaches/do-not-keep-track-of-length/content.md b/exercises/practice/simple-linked-list/.approaches/do-not-keep-track-of-length/content.md index 24baa3232..55faa7807 100644 --- a/exercises/practice/simple-linked-list/.approaches/do-not-keep-track-of-length/content.md +++ b/exercises/practice/simple-linked-list/.approaches/do-not-keep-track-of-length/content.md @@ -23,11 +23,11 @@ impl SimpleLinkedList { pub fn new() -> Self { Self { head: None } } - + pub fn is_empty(&self) -> bool { self.head.is_none() } - + pub fn len(&self) -> usize { let mut current_node = &self.head; let mut size = 0; @@ -37,12 +37,12 @@ impl SimpleLinkedList { } size } - + pub fn push(&mut self, element: T) { let node = Box::new(Node::new(element, self.head.take())); self.head = Some(node); } - + pub fn pop(&mut self) -> Option { if self.head.is_some() { let head_node = self.head.take().unwrap(); @@ -52,11 +52,11 @@ impl SimpleLinkedList { None } } - + pub fn peek(&self) -> Option<&T> { self.head.as_ref().map(|head| &(head.data)) } - + pub fn rev(self) -> SimpleLinkedList { let mut list = SimpleLinkedList::new(); let mut cur_node = self.head; diff --git a/exercises/practice/simple-linked-list/.approaches/introduction.md b/exercises/practice/simple-linked-list/.approaches/introduction.md index 345176cbf..ea38f326f 100644 --- a/exercises/practice/simple-linked-list/.approaches/introduction.md +++ b/exercises/practice/simple-linked-list/.approaches/introduction.md @@ -7,7 +7,7 @@ Another approach is to calculate the length every time it is asked for. ## General guidance One thing to keep in mind is to not mutate the list when it is not necessary. -For instance, if you find yourself using `mut self` for `rev()` or `into()`, that is an indication that the list is being mutated when it is not necessary. +For instance, if you find yourself using `mut self` for `rev()` or `into()`, that is an indication that the list is being mutated when it is not necessary. A well-known treatment of writing linked lists in Rust is [`Learn Rust With Entirely Too Many Linked Lists`][too-many-lists]. @@ -132,11 +132,11 @@ impl SimpleLinkedList { pub fn new() -> Self { Self { head: None } } - + pub fn is_empty(&self) -> bool { self.head.is_none() } - + pub fn len(&self) -> usize { let mut current_node = &self.head; let mut size = 0; @@ -146,12 +146,12 @@ impl SimpleLinkedList { } size } - + pub fn push(&mut self, element: T) { let node = Box::new(Node::new(element, self.head.take())); self.head = Some(node); } - + pub fn pop(&mut self) -> Option { if self.head.is_some() { let head_node = self.head.take().unwrap(); @@ -161,11 +161,11 @@ impl SimpleLinkedList { None } } - + pub fn peek(&self) -> Option<&T> { self.head.as_ref().map(|head| &(head.data)) } - + pub fn rev(self) -> SimpleLinkedList { let mut list = SimpleLinkedList::new(); let mut cur_node = self.head; diff --git a/exercises/practice/simple-linked-list/.docs/instructions.append.md b/exercises/practice/simple-linked-list/.docs/instructions.append.md index eabb3266c..8803c5703 100644 --- a/exercises/practice/simple-linked-list/.docs/instructions.append.md +++ b/exercises/practice/simple-linked-list/.docs/instructions.append.md @@ -1,23 +1,30 @@ # Implementation Hints -Do not implement the struct `SimpleLinkedList` as a wrapper around a `Vec`. Instead, allocate nodes on the heap. +Do not implement the struct `SimpleLinkedList` as a wrapper around a `Vec`. Instead, allocate nodes on the heap. + This might be implemented as: + ``` pub struct SimpleLinkedList { head: Option>>, } ``` -The `head` field points to the first element (Node) of this linked list. + +The `head` field points to the first element (Node) of this linked list. + This implementation also requires a struct `Node` with the following fields: + ``` struct Node { data: T, next: Option>>, } ``` -`data` contains the stored data, and `next` points to the following node (if available) or None. + +`data` contains the stored data, and `next` points to the following node (if available) or None. ## Why `Option>>` and not just `Option>`? + Try it on your own. You will get the following error. ``` @@ -26,8 +33,8 @@ Try it on your own. You will get the following error. ... | next: Option>, | --------------------- recursive without indirection - ``` +``` - The problem is that at compile time the size of next must be known. - Since `next` is recursive ("a node has a node has a node..."), the compiler does not know how much memory is to be allocated. - In contrast, [Box](https://doc.rust-lang.org/std/boxed/) is a heap pointer with a defined size. +The problem is that at compile time the size of next must be known. +Since `next` is recursive ("a node has a node has a node..."), the compiler does not know how much memory is to be allocated. +In contrast, [Box](https://doc.rust-lang.org/std/boxed/) is a heap pointer with a defined size. diff --git a/exercises/practice/space-age/.docs/instructions.append.md b/exercises/practice/space-age/.docs/instructions.append.md index 977d1b6b4..cc76cdff4 100644 --- a/exercises/practice/space-age/.docs/instructions.append.md +++ b/exercises/practice/space-age/.docs/instructions.append.md @@ -4,14 +4,13 @@ Some Rust topics you may want to read about while solving this problem: - Traits, both the From trait and implementing your own traits - Default method implementations for traits -- Macros, the use of a macro could reduce boilerplate and increase readability - for this exercise. For instance, +- Macros, the use of a macro could reduce boilerplate and increase readability + for this exercise. For instance, [a macro can implement a trait for multiple types at once](https://stackoverflow.com/questions/39150216/implementing-a-trait-for-multiple-types-at-once), though it is fine to implement `years_during` in the Planet trait itself. A macro could - define both the structs and their implementations. Info to get started with macros can + define both the structs and their implementations. Info to get started with macros can be found at: - + - [The Macros chapter in The Rust Programming Language](https://doc.rust-lang.org/stable/book/ch19-06-macros.html) - [an older version of the Macros chapter with helpful detail](https://doc.rust-lang.org/1.30.0/book/first-edition/macros.html) - [Rust By Example](https://doc.rust-lang.org/stable/rust-by-example/macros.html) - diff --git a/justfile b/justfile new file mode 100644 index 000000000..eb2895877 --- /dev/null +++ b/justfile @@ -0,0 +1,28 @@ +_default: + just --list --unsorted + +# configlet wrapper, uses problem-specifications submodule +configlet *args="": + @[ -f bin/configlet ] || bin/fetch-configlet + ./bin/configlet {{ args }} + +# simulate CI locally (WIP) +test: + just configlet lint + ./bin/lint_markdown.sh + # TODO shellcheck + ./bin/check_exercises.sh + ./bin/ensure_stubs_compile.sh + cd rust-tooling && cargo test + # TODO format exercises + +add-practice-exercise: + cd rust-tooling && cargo run --quiet --bin generate_exercise + +update-practice-exercise: + cd rust-tooling && cargo run --quiet --bin generate_exercise update + +# TODO remove. resets result of add-practice-exercise. +clean: + git restore config.json exercises/practice + git clean -- exercises/practice diff --git a/problem-specifications b/problem-specifications new file mode 160000 index 000000000..d2229dedf --- /dev/null +++ b/problem-specifications @@ -0,0 +1 @@ +Subproject commit d2229dedfa6c6a6bb7d98dc49548d9ae06d0a848 diff --git a/rust-tooling/Cargo.lock b/rust-tooling/Cargo.lock new file mode 100644 index 000000000..ee7edff04 --- /dev/null +++ b/rust-tooling/Cargo.lock @@ -0,0 +1,1106 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" +dependencies = [ + "memchr", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bstr" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c2f7349907b712260e64b0afe2f84692af14a454be26187d9df565c7f69266a" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "bumpalo" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defd4e7873dbddba6c7c91e199c7fcb946abc4a6a4ac3195400bcfb01b5de877" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "windows-targets", +] + +[[package]] +name = "chrono-tz" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1369bc6b9e9a7dfdae2055f6ec151fe9c554a9d23d357c0237cee2e25eaabb7" +dependencies = [ + "chrono", + "chrono-tz-build", + "phf", +] + +[[package]] +name = "chrono-tz-build" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2f5ebdc942f57ed96d560a6d1a459bae5851102a25d5bf89dc04ae453e31ecf" +dependencies = [ + "parse-zoneinfo", + "phf", + "phf_codegen", +] + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "cpufeatures" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +dependencies = [ + "libc", +] + +[[package]] +name = "crossterm" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" +dependencies = [ + "bitflags", + "crossterm_winapi", + "libc", + "mio", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "deunicode" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d95203a6a50906215a502507c0f879a0ce7ff205a6111e2db2a5ef8e4bb92e43" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dyn-clone" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfc4744c1b8f2a09adc0e55242f60b1af195d88596bd8700be74418c056c555" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "exercism_tooling" +version = "0.1.0" +dependencies = [ + "convert_case", + "glob", + "ignore", + "inquire", + "once_cell", + "serde", + "serde_json", + "tera", + "uuid", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "globset" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759c97c1e17c55525b57192c06a267cda0ac5210b222d6b82189a2338fa1c13d" +dependencies = [ + "aho-corasick", + "bstr", + "fnv", + "log", + "regex", +] + +[[package]] +name = "globwalk" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" +dependencies = [ + "bitflags", + "ignore", + "walkdir", +] + +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" + +[[package]] +name = "humansize" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" +dependencies = [ + "libm", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ignore" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492" +dependencies = [ + "globset", + "lazy_static", + "log", + "memchr", + "regex", + "same-file", + "thread_local", + "walkdir", + "winapi-util", +] + +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "inquire" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33e7c1ddeb15c9abcbfef6029d8e29f69b52b6d6c891031b88ed91b5065803b" +dependencies = [ + "bitflags", + "crossterm", + "dyn-clone", + "lazy_static", + "newline-converter", + "thiserror", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "libm" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" + +[[package]] +name = "lock_api" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "memchr" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" + +[[package]] +name = "mio" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys", +] + +[[package]] +name = "newline-converter" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f71d09d5c87634207f894c6b31b6a2b2c64ea3bdcf71bd5599fdbbe1600c00f" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "num-traits" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "parse-zoneinfo" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41" +dependencies = [ + "regex", +] + +[[package]] +name = "percent-encoding" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" + +[[package]] +name = "pest" +version = "2.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a4d085fd991ac8d5b05a147b437791b4260b76326baf0fc60cf7c9c27ecd33" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bee7be22ce7918f641a33f08e3f43388c7656772244e2bbb2477f44cc9021a" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1511785c5e98d79a05e8a6bc34b4ac2168a0e3e92161862030ad84daa223141" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42f0394d3123e33353ca5e1e89092e533d2cc490389f2bd6131c43c634ebc5f" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "slug" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3bc762e6a4b6c6fcaade73e77f9ebc6991b676f88bb2358bddb56560f073373" +dependencies = [ + "deunicode", +] + +[[package]] +name = "smallvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" + +[[package]] +name = "syn" +version = "2.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "718fa2415bcb8d8bd775917a1bf12a7931b6dfa890753378538118181e0cb398" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tera" +version = "1.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "970dff17c11e884a4a09bc76e3a17ef71e01bb13447a11e85226e254fe6d10b8" +dependencies = [ + "chrono", + "chrono-tz", + "globwalk", + "humansize", + "lazy_static", + "percent-encoding", + "pest", + "pest_derive", + "rand", + "regex", + "serde", + "serde_json", + "slug", + "unic-segment", +] + +[[package]] +name = "thiserror" +version = "1.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-segment" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23" +dependencies = [ + "unic-ucd-segment", +] + +[[package]] +name = "unic-ucd-segment" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + +[[package]] +name = "unicode-ident" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "uuid" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" +dependencies = [ + "getrandom", +] + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "walkdir" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" diff --git a/rust-tooling/Cargo.toml b/rust-tooling/Cargo.toml new file mode 100644 index 000000000..a60bfb872 --- /dev/null +++ b/rust-tooling/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "exercism_tooling" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +convert_case = "0.6.0" +glob = "0.3.1" +ignore = "0.4.20" +inquire = "0.6.2" +once_cell = "1.18.0" +serde = { version = "1.0.188", features = ["derive"] } +serde_json = { version = "1.0.105", features = ["preserve_order"] } +tera = "1.19.1" +uuid = { version = "1.4.1", features = ["v4"] } diff --git a/rust-tooling/src/bin/generate_exercise.rs b/rust-tooling/src/bin/generate_exercise.rs new file mode 100644 index 000000000..aec5db1e1 --- /dev/null +++ b/rust-tooling/src/bin/generate_exercise.rs @@ -0,0 +1,219 @@ +use std::path::PathBuf; + +use convert_case::{Case, Casing}; +use exercism_tooling::{ + exercise_generation, fs_utils, + track_config::{self, TRACK_CONFIG}, +}; +use glob::glob; +use inquire::{validator::Validation, Select, Text}; + +enum Difficulty { + Easy, + Medium, + // I'm not sure why there are two medium difficulties + Medium2, + Hard, +} + +impl From for u8 { + fn from(difficulty: Difficulty) -> Self { + match difficulty { + Difficulty::Easy => 1, + Difficulty::Medium => 4, + Difficulty::Medium2 => 7, + Difficulty::Hard => 10, + } + } +} + +impl std::fmt::Display for Difficulty { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Difficulty::Easy => write!(f, "Easy (1)"), + Difficulty::Medium => write!(f, "Medium (4)"), + Difficulty::Medium2 => write!(f, "Medium (7)"), + Difficulty::Hard => write!(f, "Hard (10)"), + } + } +} + +fn main() { + fs_utils::cd_into_repo_root(); + + let is_update = std::env::args().any(|arg| arg == "update"); + + let slug = if is_update { + ask_for_exercise_to_update() + } else { + add_entry_to_track_config() + }; + + make_configlet_generate_what_it_can(&slug); + + generate_exercise_files(&slug, is_update); +} + +fn ask_for_exercise_to_update() -> String { + let implemented_exercises = glob("exercises/practice/*") + .unwrap() + .filter_map(Result::ok) + .map(|path| path.file_name().unwrap().to_str().unwrap().to_string()) + .collect::>(); + + Select::new( + "Which exercise would you like to update?", + implemented_exercises, + ) + .prompt() + .unwrap() +} + +/// Interactively prompts the user for required fields in the track config +/// and writes the answers to config.json. +/// Returns slug. +fn add_entry_to_track_config() -> String { + let implemented_exercises = glob("exercises/concept/*") + .unwrap() + .chain(glob("exercises/practice/*").unwrap()) + .filter_map(Result::ok) + .map(|path| path.file_name().unwrap().to_str().unwrap().to_string()) + .collect::>(); + + let unimplemented_with_spec = glob("problem-specifications/exercises/*") + .unwrap() + .filter_map(Result::ok) + .map(|path| path.file_name().unwrap().to_str().unwrap().to_string()) + .filter(|e| !implemented_exercises.contains(e)) + .collect::>(); + + println!("(suggestions are from problem-specifications)"); + let slug = Text::new("What's the slug of your exercise?") + .with_autocomplete(move |input: &_| { + let mut slugs = unimplemented_with_spec.clone(); + slugs.retain(|e| e.starts_with(input)); + Ok(slugs) + }) + .with_validator(|input: &str| { + if input.is_empty() { + Ok(Validation::Invalid("The slug must not be empty.".into())) + } else if !input.is_case(Case::Kebab) { + Ok(Validation::Invalid( + "The slug must be in kebab-case.".into(), + )) + } else { + Ok(Validation::Valid) + } + }) + .with_validator(move |input: &str| { + if !implemented_exercises.contains(&input.to_string()) { + Ok(Validation::Valid) + } else { + Ok(Validation::Invalid( + "An exercise with this slug already exists.".into(), + )) + } + }) + .prompt() + .unwrap(); + + let name = Text::new("What's the name of your exercise?") + .with_initial_value(&slug.to_case(Case::Title)) + .prompt() + .unwrap(); + + let difficulty = Select::::new( + "What's the difficulty of your exercise?", + vec![ + Difficulty::Easy, + Difficulty::Medium, + Difficulty::Medium2, + Difficulty::Hard, + ], + ) + .prompt() + .unwrap() + .into(); + + let config = track_config::PracticeExercise::new(slug.clone(), name, difficulty); + + let mut track_config = TRACK_CONFIG.clone(); + track_config.exercises.practice.push(config); + let mut new_config = serde_json::to_string_pretty(&track_config) + .unwrap() + .to_string(); + new_config += "\n"; + std::fs::write("config.json", new_config).unwrap(); + + println!( + "\ +Added your exercise to config.json. +You can add practices, prerequisites and topics if you like." + ); + + slug +} + +fn make_configlet_generate_what_it_can(slug: &str) { + let status = std::process::Command::new("just") + .args([ + "configlet", + "sync", + "--update", + "--yes", + "--docs", + "--metadata", + "--tests", + "include", + "--exercise", + slug, + ]) + .status() + .unwrap(); + if !status.success() { + panic!("configlet sync failed"); + } +} + +fn generate_exercise_files(slug: &str, is_update: bool) { + let fn_names = if is_update { + read_fn_names_from_lib_rs(slug) + } else { + vec!["TODO".to_string()] + }; + + let exercise = exercise_generation::new(slug, fn_names); + + let exercise_path = PathBuf::from("exercises/practice").join(slug); + + if !is_update { + std::fs::write(exercise_path.join(".gitignore"), exercise.gitignore).unwrap(); + std::fs::write(exercise_path.join("Cargo.toml"), exercise.manifest).unwrap(); + std::fs::create_dir(exercise_path.join("src")).ok(); + std::fs::write(exercise_path.join("src/lib.rs"), exercise.lib_rs).unwrap(); + std::fs::write(exercise_path.join(".meta/example.rs"), exercise.example).unwrap(); + } + + let template_path = exercise_path.join(".meta/test_template.tera"); + if std::fs::metadata(&template_path).is_err() { + std::fs::write(template_path, exercise.test_template).unwrap(); + } + + std::fs::create_dir(exercise_path.join("tests")).ok(); + std::fs::write( + exercise_path.join(format!("tests/{slug}.rs")), + exercise.tests, + ) + .unwrap(); +} + +fn read_fn_names_from_lib_rs(slug: &str) -> Vec { + let lib_rs = + std::fs::read_to_string(format!("exercises/practice/{}/src/lib.rs", slug)).unwrap(); + + lib_rs + .split("fn ") + .skip(1) + .map(|f| f.split_once('(').unwrap().0.to_string()) + .collect() +} diff --git a/rust-tooling/src/default_test_template.tera b/rust-tooling/src/default_test_template.tera new file mode 100644 index 000000000..39eb4727f --- /dev/null +++ b/rust-tooling/src/default_test_template.tera @@ -0,0 +1,12 @@ +{% for test in cases %} +#[test] +{% if loop.index != 1 -%} +#[ignore] +{% endif -%} +fn {{ test.description | slugify | replace(from="-", to="_") }}() { + let input = {{ test.input | json_encode() }}; + let output = {{ crate_name }}::{{ fn_names[0] }}(input); + let expected = {{ test.expected | json_encode() }}; + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/rust-tooling/src/exercise_config.rs b/rust-tooling/src/exercise_config.rs new file mode 100644 index 000000000..f8b170356 --- /dev/null +++ b/rust-tooling/src/exercise_config.rs @@ -0,0 +1,127 @@ +//! This module provides a data structure for exercise configuration stored in +//! `.meta/config`. It is capable of serializing and deserializing th +//! configuration, for example with `serde_json`. + +use serde::{Deserialize, Serialize}; +use tera::Tera; + +use crate::track_config::TRACK_CONFIG; + +#[derive(Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ConceptExercise { + pub authors: Vec, + pub contributors: Option>, + pub files: ConceptFiles, + pub icon: Option, + pub blurb: String, + pub source: Option, + pub source_url: Option, + pub test_runner: Option, +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ConceptFiles { + pub solution: Vec, + pub test: Vec, + pub exemplar: Vec, +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct PracticeExercise { + pub authors: Vec, + pub contributors: Option>, + pub files: PracticeFiles, + pub icon: Option, + pub blurb: String, + pub source: Option, + pub source_url: Option, + pub test_runner: Option, + pub custom: Option, +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct PracticeFiles { + pub solution: Vec, + pub test: Vec, + pub example: Vec, +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Custom { + #[serde(rename = "allowed-to-not-compile")] + pub allowed_to_not_compile: Option, + #[serde(rename = "test-in-release-mode")] + pub test_in_release_mode: Option, + #[serde(rename = "ignore-count-ignores")] + pub ignore_count_ignores: Option, +} + +pub fn get_all_concept_exercise_paths() -> impl Iterator { + let crate_dir = env!("CARGO_MANIFEST_DIR"); + + TRACK_CONFIG + .exercises + .concept + .iter() + .map(move |e| format!("{crate_dir}/../exercises/concept/{}", e.slug)) +} + +pub fn get_all_practice_exercise_paths() -> impl Iterator { + let crate_dir = env!("CARGO_MANIFEST_DIR"); + + TRACK_CONFIG + .exercises + .practice + .iter() + .map(move |e| format!("{crate_dir}/../exercises/practice/{}", e.slug)) +} + +pub fn get_all_exercise_paths() -> impl Iterator { + get_all_concept_exercise_paths().chain(get_all_practice_exercise_paths()) +} + +#[test] +fn test_deserialize_all() { + for path in get_all_concept_exercise_paths() { + let config_path = format!("{path}/.meta/config.json"); + let config_contents = std::fs::read_to_string(config_path).unwrap(); + let _: ConceptExercise = serde_json::from_str(config_contents.as_str()) + .expect("should deserialize concept exercise config"); + } + for path in get_all_practice_exercise_paths() { + let config_path = format!("{path}/.meta/config.json"); + let config_contents = std::fs::read_to_string(config_path).unwrap(); + let _: PracticeExercise = serde_json::from_str(config_contents.as_str()) + .expect("should deserialize practice exercise config"); + } +} + +/// Returns the uuids of the tests excluded in .meta/tests.toml +pub fn get_excluded_tests(slug: &str) -> Vec { + let path = std::path::PathBuf::from("exercises/practice") + .join(slug) + .join(".meta/tests.toml"); + let contents = std::fs::read_to_string(&path).unwrap(); + + let mut excluded_tests = Vec::new(); + + // shitty toml parser + for case in contents.split("\n[").skip(1) { + let (uuid, rest) = case.split_once(']').unwrap(); + if rest.contains("include = false") { + excluded_tests.push(uuid.to_string()); + } + } + + excluded_tests +} + +/// Returns the uuids of the tests excluded in .meta/tests.toml +pub fn get_test_emplate(slug: &str) -> Option { + Some(Tera::new(format!("exercises/practice/{slug}/.meta/*.tera").as_str()).unwrap()) +} diff --git a/rust-tooling/src/exercise_generation.rs b/rust-tooling/src/exercise_generation.rs new file mode 100644 index 000000000..028776a61 --- /dev/null +++ b/rust-tooling/src/exercise_generation.rs @@ -0,0 +1,104 @@ +use tera::Context; + +use crate::{ + exercise_config::{get_excluded_tests, get_test_emplate}, + problem_spec::{get_canonical_data, SingleTestCase, TestCase}, +}; + +pub struct GeneratedExercise { + pub gitignore: String, + pub manifest: String, + pub lib_rs: String, + pub example: String, + pub test_template: String, + pub tests: String, +} + +pub fn new(slug: &str, fn_names: Vec) -> GeneratedExercise { + let crate_name = slug.replace('-', "_"); + let first_fn_name = &fn_names[0]; + + GeneratedExercise { + gitignore: GITIGNORE.into(), + manifest: generate_manifest(&crate_name), + lib_rs: generate_lib_rs(&crate_name, first_fn_name), + example: generate_example_rs(first_fn_name), + test_template: TEST_TEMPLATE.into(), + tests: generate_tests(slug, fn_names), + } +} + +static GITIGNORE: &str = "\ +/target +/Cargo.lock +"; + +fn generate_manifest(crate_name: &str) -> String { + format!( + concat!( + "[package]\n", + "edition = \"2021\"\n", + "name = \"{crate_name}\"\n", + "version = \"1.0.0\"\n", + "\n", + "[dependencies]\n", + ), + crate_name = crate_name + ) +} + +fn generate_lib_rs(crate_name: &str, fn_name: &str) -> String { + format!( + concat!( + "pub fn {fn_name}(input: TODO) -> TODO {{\n", + " todo!(\"use {{input}} to implement {crate_name}\")\n", + "}}\n", + ), + fn_name = fn_name, + crate_name = crate_name, + ) +} + +fn generate_example_rs(fn_name: &str) -> String { + format!( + concat!( + "pub fn {fn_name}(input: TODO) -> TODO {{\n", + " TODO\n", + "}}\n", + ), + fn_name = fn_name + ) +} + +static TEST_TEMPLATE: &str = include_str!("default_test_template.tera"); + +fn extend_single_cases(single_cases: &mut Vec, cases: Vec) { + for case in cases { + match case { + TestCase::Single { case } => single_cases.push(case), + TestCase::Group { cases, .. } => extend_single_cases(single_cases, cases), + } + } +} + +fn generate_tests(slug: &str, fn_names: Vec) -> String { + let cases = get_canonical_data(slug).cases; + let excluded_tests = get_excluded_tests(slug); + let mut template = get_test_emplate(slug).unwrap(); + if template.get_template_names().next().is_none() { + template + .add_raw_template("test_template.tera", TEST_TEMPLATE) + .unwrap(); + } + + let mut single_cases = Vec::new(); + extend_single_cases(&mut single_cases, cases); + single_cases.retain(|case| !excluded_tests.contains(&case.uuid)); + + let mut context = Context::new(); + context.insert("crate_name", &slug.replace('-', "_")); + context.insert("fn_names", &fn_names); + context.insert("cases", &single_cases); + + template.render("test_template.tera", &context).unwrap().trim_start().into() +} diff --git a/rust-tooling/src/fs_utils.rs b/rust-tooling/src/fs_utils.rs new file mode 100644 index 000000000..6af30496a --- /dev/null +++ b/rust-tooling/src/fs_utils.rs @@ -0,0 +1,12 @@ +//! This module contains utilities for working with the files in this repo. + +/// Changes the current working directory to the root of the repository. +/// +/// This is intended to be used by executables which operate on files +/// of the repository, so they can use relative paths and still work +/// when called from anywhere within the repository. +pub fn cd_into_repo_root() { + static RUST_TOOLING_DIR: &str = env!("CARGO_MANIFEST_DIR"); + let repo_root_dir = std::path::PathBuf::from(RUST_TOOLING_DIR).join(".."); + std::env::set_current_dir(repo_root_dir).unwrap(); +} diff --git a/rust-tooling/src/lib.rs b/rust-tooling/src/lib.rs new file mode 100644 index 000000000..727eaca9a --- /dev/null +++ b/rust-tooling/src/lib.rs @@ -0,0 +1,5 @@ +pub mod problem_spec; +pub mod exercise_config; +pub mod track_config; +pub mod exercise_generation; +pub mod fs_utils; diff --git a/rust-tooling/src/problem_spec.rs b/rust-tooling/src/problem_spec.rs new file mode 100644 index 000000000..073b8ba97 --- /dev/null +++ b/rust-tooling/src/problem_spec.rs @@ -0,0 +1,69 @@ +use serde::{Deserialize, Serialize}; + +/// Remember that this is actually optional, not all exercises +/// must have a canonical data file in the problem-specifications repo. +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct CanonicalData { + pub exercise: String, + pub comments: Option>, + pub cases: Vec, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +#[serde(untagged)] +pub enum TestCase { + Single { + #[serde(flatten)] + case: SingleTestCase, + }, + Group { + description: String, + comments: Option>, + cases: Vec, + }, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct SingleTestCase { + pub uuid: String, + pub reimplements: Option, + pub description: String, + pub comments: Option>, + pub scenarios: Option>, + pub property: String, + pub input: serde_json::Value, + pub expected: serde_json::Value, +} + +pub fn get_canonical_data(slug: &str) -> CanonicalData { + let path = std::path::PathBuf::from("problem-specifications/exercises") + .join(slug) + .join("canonical-data.json"); + let contents = std::fs::read_to_string(&path).unwrap(); + serde_json::from_str(contents.as_str()).unwrap_or_else(|e| { + panic!( + "should deserialize canonical data for {}: {e}", + path.display() + ) + }) +} + +#[test] +fn test_deserialize_canonical_data() { + crate::fs_utils::cd_into_repo_root(); + for entry in ignore::Walk::new("problem-specifications/exercises") + .filter_map(|e| e.ok()) + .filter(|e| e.file_name().to_str().unwrap() == "canonical-data.json") + { + let contents = std::fs::read_to_string(entry.path()).unwrap(); + let _: CanonicalData = serde_json::from_str(contents.as_str()).unwrap_or_else(|e| { + panic!( + "should deserialize canonical data for {}: {e}", + entry.path().display() + ) + }); + } +} diff --git a/rust-tooling/src/track_config.rs b/rust-tooling/src/track_config.rs new file mode 100644 index 000000000..01c425982 --- /dev/null +++ b/rust-tooling/src/track_config.rs @@ -0,0 +1,145 @@ +//! This module provides a data structure for the track configuration. +//! It is capable of serializing and deserializing the configuration, +//! for example with `serde_json`. +//! +//! Some definitions may not be perfectly precise. +//! Feel free to improve this if need be. +//! (e.g. replace `String` with an enum of possible values) + +use std::collections::HashMap; + +use once_cell::sync::Lazy; +use serde::{Deserialize, Serialize}; + +pub static TRACK_CONFIG: Lazy = Lazy::new(|| { + let config = include_str!("../../config.json"); + serde_json::from_str(config).expect("should deserialize the track config") +}); + +#[derive(Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct TrackConfig { + pub language: String, + pub slug: String, + pub active: bool, + pub status: Status, + pub blurb: String, + pub version: u8, + pub online_editor: OnlineEditor, + pub test_runner: HashMap, + pub files: Files, + pub exercises: Exercises, + pub concepts: Vec, + pub key_features: Vec, + pub tags: Vec, +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Status { + pub concept_exercises: bool, + pub test_runner: bool, + pub representer: bool, + pub analyzer: bool, +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct OnlineEditor { + pub indent_style: String, + pub indent_size: u8, + pub highlightjs_language: String, +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Files { + pub solution: Vec, + pub test: Vec, + pub example: Vec, + pub exemplar: Vec, +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Exercises { + pub concept: Vec, + pub practice: Vec, + pub foregone: Vec, +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum ConceptExerciseStatus { + Active, + Wip, +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ConceptExercise { + pub slug: String, + pub uuid: String, + pub name: String, + pub difficulty: u8, + pub concepts: Vec, + pub prerequisites: Vec, + pub status: ConceptExerciseStatus, +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum PracticeExerciseStatus { + Deprecated, +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct PracticeExercise { + pub slug: String, + pub name: String, + pub uuid: String, + pub practices: Vec, + pub prerequisites: Vec, + pub difficulty: u8, + pub topics: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub status: Option, +} + +impl PracticeExercise { + pub fn new(slug: String, name: String, difficulty: u8) -> Self { + Self { + slug, + name, + uuid: uuid::Uuid::new_v4().to_string(), + practices: Vec::new(), + prerequisites: Vec::new(), + difficulty, + topics: Vec::new(), + status: None, + } + } +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct ConceptConfig { + pub uuid: String, + pub slug: String, + pub name: String, +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct KeyFeature { + pub icon: String, + pub title: String, + pub content: String, +} + +#[test] +fn test_deserialize() { + // force deserialization of lazy static + assert!(TRACK_CONFIG.active, "should deserialize track config"); +} diff --git a/rust-tooling/tests/bash_script_conventions.rs b/rust-tooling/tests/bash_script_conventions.rs new file mode 100644 index 000000000..ff95feb88 --- /dev/null +++ b/rust-tooling/tests/bash_script_conventions.rs @@ -0,0 +1,72 @@ +use std::path::PathBuf; + +use convert_case::{Case, Casing}; +use exercism_tooling::fs_utils; + +/// Runs a function for each bash script in the bin directory. +/// The function is passed the path of the script. +fn for_all_scripts(f: fn(PathBuf)) { + fs_utils::cd_into_repo_root(); + + for entry in std::fs::read_dir("bin").unwrap() { + f(entry.unwrap().path()) + } +} + +#[test] +fn test_file_extension() { + for_all_scripts(|path| { + let file_name = path.file_name().unwrap().to_str().unwrap(); + + // exceptions + if file_name == "fetch-configlet" || file_name == "configlet" { + return; + } + + assert!( + file_name.ends_with(".sh"), + "name of '{file_name}' should end with .sh" + ); + }) +} + +#[test] +fn test_snake_case_name() { + for_all_scripts(|path| { + let file_name = path + .file_name() + .unwrap() + .to_str() + .unwrap() + .trim_end_matches(".sh"); + + // fetch-configlet comes from upstream, we don't control its name + if file_name == "fetch-configlet" { + return; + } + + assert!( + file_name.is_case(Case::Snake), + "name of '{file_name}' should be snake_case" + ); + }) +} + +/// Notably on nixOS and macOS, bash is not installed in `/bin/bash`. +#[test] +fn test_portable_shebang() { + for_all_scripts(|path| { + let file_name = path.file_name().unwrap().to_str().unwrap(); + + // not a bash script, but it must be in `bin` + if file_name == "configlet" { + return; + } + + let contents = std::fs::read_to_string(&path).unwrap(); + assert!( + contents.starts_with("#!/usr/bin/env bash"), + "'{file_name}' should start with the shebang '#!/usr/bin/env bash'" + ); + }) +} diff --git a/rust-tooling/tests/count_ignores.rs b/rust-tooling/tests/count_ignores.rs new file mode 100644 index 000000000..4a78d04ca --- /dev/null +++ b/rust-tooling/tests/count_ignores.rs @@ -0,0 +1,34 @@ +use exercism_tooling::exercise_config::{ + get_all_concept_exercise_paths, get_all_practice_exercise_paths, PracticeExercise, +}; + +fn assert_one_less_ignore_than_tests(path: &str) { + let slug = path.split('/').last().unwrap(); + let test_path = format!("{path}/tests/{slug}.rs"); + let test_contents = std::fs::read_to_string(test_path).unwrap(); + let num_tests = test_contents.matches("#[test]").count(); + let num_ignores = test_contents.matches("#[ignore]").count(); + assert_eq!( + num_tests, + num_ignores + 1, + "should have one more test than ignore in {slug}" + ) +} + +#[test] +fn test_count_ignores() { + for path in get_all_concept_exercise_paths() { + assert_one_less_ignore_than_tests(&path); + } + for path in get_all_practice_exercise_paths() { + let config_path = format!("{path}/.meta/config.json"); + let config_contents = std::fs::read_to_string(config_path).unwrap(); + let config: PracticeExercise = serde_json::from_str(config_contents.as_str()).unwrap(); + if let Some(custom) = config.custom { + if custom.ignore_count_ignores.unwrap_or_default() { + continue; + } + } + assert_one_less_ignore_than_tests(&path); + } +} diff --git a/rust-tooling/tests/difficulties.rs b/rust-tooling/tests/difficulties.rs new file mode 100644 index 000000000..18c96bc03 --- /dev/null +++ b/rust-tooling/tests/difficulties.rs @@ -0,0 +1,18 @@ +//! Make sure exercise difficulties are set correctly + +use exercism_tooling::track_config::TRACK_CONFIG; + +#[test] +fn test_difficulties_are_valid() { + let mut difficulties = TRACK_CONFIG + .exercises + .concept + .iter() + .map(|e| e.difficulty) + .chain(TRACK_CONFIG.exercises.practice.iter().map(|e| e.difficulty)); + + assert!( + difficulties.all(|d| matches!(d, 1 | 4 | 7 | 10)), + "exercises must have a difficulty of 1, 4, 7, or 10" + ) +} diff --git a/rust-tooling/tests/no_authors_in_cargo_toml.rs b/rust-tooling/tests/no_authors_in_cargo_toml.rs new file mode 100644 index 000000000..c8428ce34 --- /dev/null +++ b/rust-tooling/tests/no_authors_in_cargo_toml.rs @@ -0,0 +1,16 @@ +use exercism_tooling::exercise_config::get_all_exercise_paths; + +/// The package manifest of each exercise should not contain an `authors` field. +/// The authors are already specified in the track configuration. +#[test] +fn test_no_authors_in_cargo_toml() { + let cargo_toml_paths = get_all_exercise_paths().map(|p| format!("{p}/Cargo.toml")); + + for path in cargo_toml_paths { + let cargo_toml = std::fs::read_to_string(path).unwrap(); + assert!( + !cargo_toml.contains("authors"), + "Cargo.toml should not contain an 'authors' field" + ); + } +} diff --git a/rust-tooling/tests/no_trailing_whitespace.rs b/rust-tooling/tests/no_trailing_whitespace.rs new file mode 100644 index 000000000..540c160b9 --- /dev/null +++ b/rust-tooling/tests/no_trailing_whitespace.rs @@ -0,0 +1,34 @@ +use std::path::Path; + +use exercism_tooling::fs_utils; + +fn contains_trailing_whitespace(p: &Path) -> bool { + let contents = std::fs::read_to_string(p).unwrap(); + for line in contents.lines() { + if line != line.trim_end() { + return true; + } + } + false +} + +#[test] +fn test_no_trailing_whitespace() { + fs_utils::cd_into_repo_root(); + + for entry in ignore::Walk::new("./") { + let entry = entry.unwrap(); + if !entry.file_type().is_some_and(|t| t.is_file()) { + continue; + } + let path = entry.path(); + let ext = path.extension().unwrap_or_default().to_str().unwrap(); + if matches!(ext, "rs" | "toml" | "md" | "sh") { + assert!( + !contains_trailing_whitespace(path), + "trailing whitespace in {}", + path.display() + ); + } + } +} diff --git a/util/escape_double_quotes/Cargo.lock b/util/escape_double_quotes/Cargo.lock deleted file mode 100644 index 54ec08296..000000000 --- a/util/escape_double_quotes/Cargo.lock +++ /dev/null @@ -1,7 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "escape_double_quotes" -version = "0.1.0" diff --git a/util/escape_double_quotes/Cargo.toml b/util/escape_double_quotes/Cargo.toml deleted file mode 100644 index c61c6ddbc..000000000 --- a/util/escape_double_quotes/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "escape_double_quotes" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] diff --git a/util/escape_double_quotes/build b/util/escape_double_quotes/build deleted file mode 100755 index b74a56633..000000000 --- a/util/escape_double_quotes/build +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env bash -cargo build --release --quiet && cp ./target/release/escape_double_quotes ../../bin/generator-utils && rm -rf ./target diff --git a/util/escape_double_quotes/src/lib.rs b/util/escape_double_quotes/src/lib.rs deleted file mode 100644 index e91f3a79b..000000000 --- a/util/escape_double_quotes/src/lib.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod utils; -pub use utils::escape_double_quotes::*; diff --git a/util/escape_double_quotes/src/main.rs b/util/escape_double_quotes/src/main.rs deleted file mode 100644 index 3890e92bf..000000000 --- a/util/escape_double_quotes/src/main.rs +++ /dev/null @@ -1,30 +0,0 @@ -use std::env; -use std::fs::File; -use std::io::{self, BufRead, BufReader, BufWriter, Write}; -mod utils; -use utils::escape_double_quotes::*; - - -fn main() -> io::Result<()> { - let args: Vec = env::args().collect(); - if args.len() != 2 { - eprintln!("Usage: {} ", args[0]); - std::process::exit(1); - } - - let file_path = &args[1]; - let file = File::open(file_path)?; - let reader = BufReader::new(file); - - let stdout = io::stdout(); - let mut writer = BufWriter::new(stdout.lock()); - - for line in reader.lines() { - let input = line?; - let output = escape_double_quotes(&input); - writeln!(writer, "{}", output)?; - } - - Ok(()) -} - diff --git a/util/escape_double_quotes/src/utils/escape_double_quotes.rs b/util/escape_double_quotes/src/utils/escape_double_quotes.rs deleted file mode 100644 index 83fb7b907..000000000 --- a/util/escape_double_quotes/src/utils/escape_double_quotes.rs +++ /dev/null @@ -1,22 +0,0 @@ -pub fn escape_double_quotes(input: &str) -> String { - let mut output = String::new(); - let mut escape = true; - - let mut chars = input.chars().peekable(); - while let Some(char) = chars.next() { - match char { - '$' if chars.peek() == Some(&'{') => { - escape = false; - output.push(char) - } - '}' if chars.peek() == Some(&'$') => { - escape = true; - output.push(char) - } - '"' if escape => output.push_str("\\\""), - _ => output.push(char), - } - } - - output -} diff --git a/util/escape_double_quotes/src/utils/mod.rs b/util/escape_double_quotes/src/utils/mod.rs deleted file mode 100644 index a352b9aaf..000000000 --- a/util/escape_double_quotes/src/utils/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod escape_double_quotes; diff --git a/util/escape_double_quotes/tests/test.rs b/util/escape_double_quotes/tests/test.rs deleted file mode 100644 index 99e82935e..000000000 --- a/util/escape_double_quotes/tests/test.rs +++ /dev/null @@ -1,36 +0,0 @@ -use escape_double_quotes::utils::escape_double_quotes::escape_double_quotes; - -#[test] -fn test_no_double_quotes() { - let input = "let x = 5;"; - let expected = "let x = 5;"; - assert_eq!(escape_double_quotes(input), expected); -} - -#[test] -fn test_simple_double_quotes() { - let input = "let something = \"string\";"; - let expected = "let something = \\\"string\\\";"; - assert_eq!(escape_double_quotes(input), expected); -} - -#[test] -fn test_braces_with_double_quotes() { - let input = "let expected = \"${expected | join(\\\"\\n\\\")}$\";"; - let expected = "let expected = \\\"${expected | join(\\\"\\n\\\")}$\\\";"; - assert_eq!(escape_double_quotes(input), expected); -} - -#[test] -fn test_mixed_double_quotes() { - let input = "let a = \"value\"; let b = \"${value | filter(\\\"text\\\")}$\";"; - let expected = "let a = \\\"value\\\"; let b = \\\"${value | filter(\\\"text\\\")}$\\\";"; - assert_eq!(escape_double_quotes(input), expected); -} - -#[test] -fn test_nested_braces() { - let input = "let nested = \"${outer {inner | escape(\\\"\\n\\\")}}$\";"; - let expected = "let nested = \\\"${outer {inner | escape(\\\"\\n\\\")}}$\\\";"; - assert_eq!(escape_double_quotes(input), expected); -} diff --git a/util/ngram/Cargo.lock b/util/ngram/Cargo.lock deleted file mode 100644 index b704c48b2..000000000 --- a/util/ngram/Cargo.lock +++ /dev/null @@ -1,16 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "ngram" -version = "0.1.0" -dependencies = [ - "ngrammatic", -] - -[[package]] -name = "ngrammatic" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c6f2f987e82da7fb8a290e959bba528638bc0e2629e38647591e845ecb5f6fe" diff --git a/util/ngram/Cargo.toml b/util/ngram/Cargo.toml deleted file mode 100644 index 5b48fcc38..000000000 --- a/util/ngram/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "ngram" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -ngrammatic = "0.4.0" diff --git a/util/ngram/build b/util/ngram/build deleted file mode 100755 index 8e428879f..000000000 --- a/util/ngram/build +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env bash -cargo build --release --quiet && cp ./target/release/ngram ../../bin/generator-utils && rm -rf ./target diff --git a/util/ngram/src/main.rs b/util/ngram/src/main.rs deleted file mode 100644 index 156b39cba..000000000 --- a/util/ngram/src/main.rs +++ /dev/null @@ -1,26 +0,0 @@ -use ngrammatic::{CorpusBuilder, Pad}; - -fn main() { - let mut args = std::env::args(); - let exercises = args.nth(1).expect("Missing exercises argument"); - let slug = args.nth(0).expect("Missing slug argument"); - let exercises: Vec<&str> = exercises - .split(|c: char| c.is_whitespace() || c == '\n') - .collect(); - let mut corpus = CorpusBuilder::new().arity(2).pad_full(Pad::Auto).finish(); - - for exercise in exercises.iter() { - corpus.add_text(exercise); - } - - if let Some(top_result) = corpus.search(&slug, 0.25).first() { - println!( - "{} - There is an exercise with a similar name: '{}' [{:.0}% match]", - slug, - top_result.text, - top_result.similarity * 100.0 - ); - } else { - println!("Couldn't find any exercise similar to this: {}", slug); - } -} From da5429a55c67bcd5b917760d58e1d9eb3708b1f4 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Thu, 14 Sep 2023 08:35:25 +0200 Subject: [PATCH 154/436] Use todo!() instead of unimplemented!() (#1723) The difference is one of intent, and the actual panic message. See the docs why todo is more appropriate: https://doc.rust-lang.org/stable/std/macro.todo.html [no important files changed] closes #1598 --- concepts/structs/about.md | 2 +- exercises/concept/assembly-line/src/lib.rs | 4 +-- exercises/concept/csv-builder/src/lib.rs | 6 ++--- .../health-statistics/.docs/introduction.md | 2 +- .../concept/health-statistics/src/lib.rs | 12 ++++----- .../low-power-embedded-game/.docs/hints.md | 2 +- .../low-power-embedded-game/src/lib.rs | 6 ++--- .../lucians-luscious-lasagna/src/lib.rs | 8 +++--- .../magazine-cutout/.docs/instructions.md | 2 +- exercises/concept/magazine-cutout/src/lib.rs | 2 +- exercises/concept/resistor-color/src/lib.rs | 6 ++--- .../concept/role-playing-game/src/lib.rs | 4 +-- .../rpn-calculator/.docs/instructions.md | 2 +- exercises/concept/rpn-calculator/src/lib.rs | 2 +- .../concept/semi-structured-logs/src/lib.rs | 8 +++--- exercises/concept/short-fibonacci/src/lib.rs | 6 ++--- exercises/practice/accumulate/src/lib.rs | 2 +- exercises/practice/acronym/src/lib.rs | 2 +- exercises/practice/affine-cipher/src/lib.rs | 4 +-- exercises/practice/all-your-base/src/lib.rs | 2 +- exercises/practice/allergies/src/lib.rs | 6 ++--- exercises/practice/alphametics/src/lib.rs | 2 +- exercises/practice/anagram/src/lib.rs | 4 +-- .../practice/armstrong-numbers/src/lib.rs | 2 +- exercises/practice/atbash-cipher/src/lib.rs | 4 +-- exercises/practice/beer-song/src/lib.rs | 4 +-- exercises/practice/binary-search/src/lib.rs | 2 +- exercises/practice/bob/src/lib.rs | 2 +- exercises/practice/book-store/src/lib.rs | 2 +- exercises/practice/bowling/src/lib.rs | 6 ++--- exercises/practice/circular-buffer/src/lib.rs | 10 +++---- exercises/practice/clock/src/lib.rs | 4 +-- .../practice/collatz-conjecture/src/lib.rs | 4 +-- exercises/practice/crypto-square/src/lib.rs | 2 +- exercises/practice/custom-set/src/lib.rs | 18 ++++++------- exercises/practice/decimal/src/lib.rs | 2 +- exercises/practice/diamond/src/lib.rs | 4 +-- .../practice/difference-of-squares/src/lib.rs | 6 ++--- exercises/practice/diffie-hellman/src/lib.rs | 8 +++--- exercises/practice/dominoes/src/lib.rs | 2 +- exercises/practice/dot-dsl/src/lib.rs | 2 +- .../practice/doubly-linked-list/src/lib.rs | 26 +++++++++---------- exercises/practice/etl/src/lib.rs | 2 +- exercises/practice/fizzy/src/lib.rs | 10 +++---- exercises/practice/forth/src/lib.rs | 6 ++--- exercises/practice/gigasecond/src/lib.rs | 2 +- exercises/practice/grade-school/src/lib.rs | 8 +++--- exercises/practice/grains/src/lib.rs | 4 +-- exercises/practice/grep/src/lib.rs | 4 +-- exercises/practice/hamming/src/lib.rs | 2 +- exercises/practice/hexadecimal/src/lib.rs | 2 +- exercises/practice/high-scores/src/lib.rs | 10 +++---- exercises/practice/isbn-verifier/src/lib.rs | 2 +- exercises/practice/isogram/src/lib.rs | 2 +- .../practice/kindergarten-garden/src/lib.rs | 2 +- exercises/practice/knapsack/src/lib.rs | 2 +- .../largest-series-product/src/lib.rs | 2 +- exercises/practice/leap/src/lib.rs | 2 +- exercises/practice/luhn-from/src/lib.rs | 4 +-- exercises/practice/luhn-trait/src/lib.rs | 2 +- exercises/practice/luhn/src/lib.rs | 2 +- exercises/practice/macros/src/lib.rs | 2 +- .../practice/matching-brackets/src/lib.rs | 2 +- exercises/practice/minesweeper/src/lib.rs | 2 +- exercises/practice/nth-prime/src/lib.rs | 2 +- .../practice/nucleotide-codons/src/lib.rs | 6 ++--- .../practice/nucleotide-count/src/lib.rs | 6 ++--- exercises/practice/ocr-numbers/src/lib.rs | 2 +- exercises/practice/paasio/src/lib.rs | 22 ++++++++-------- .../practice/palindrome-products/src/lib.rs | 6 ++--- exercises/practice/pangram/src/lib.rs | 2 +- .../parallel-letter-frequency/src/lib.rs | 2 +- .../practice/pascals-triangle/src/lib.rs | 4 +-- exercises/practice/perfect-numbers/src/lib.rs | 2 +- exercises/practice/phone-number/src/lib.rs | 2 +- exercises/practice/pig-latin/src/lib.rs | 4 +-- exercises/practice/poker/src/lib.rs | 2 +- exercises/practice/prime-factors/src/lib.rs | 2 +- .../practice/protein-translation/src/lib.rs | 8 +++--- exercises/practice/proverb/src/lib.rs | 2 +- .../practice/pythagorean-triplet/src/lib.rs | 2 +- exercises/practice/queen-attack/src/lib.rs | 6 ++--- .../practice/rail-fence-cipher/src/lib.rs | 6 ++--- exercises/practice/raindrops/src/lib.rs | 2 +- exercises/practice/react/src/lib.rs | 14 +++++----- exercises/practice/rectangles/src/lib.rs | 2 +- exercises/practice/reverse-string/src/lib.rs | 2 +- .../practice/rna-transcription/src/lib.rs | 6 ++--- exercises/practice/robot-name/src/lib.rs | 6 ++--- exercises/practice/robot-simulator/src/lib.rs | 14 +++++----- exercises/practice/roman-numerals/src/lib.rs | 4 +-- .../practice/rotational-cipher/src/lib.rs | 2 +- .../practice/run-length-encoding/src/lib.rs | 4 +-- exercises/practice/saddle-points/src/lib.rs | 2 +- exercises/practice/say/src/lib.rs | 2 +- exercises/practice/scale-generator/src/lib.rs | 6 ++--- exercises/practice/scrabble-score/src/lib.rs | 2 +- .../practice/secret-handshake/src/lib.rs | 2 +- exercises/practice/series/src/lib.rs | 2 +- exercises/practice/sieve/src/lib.rs | 2 +- exercises/practice/simple-cipher/src/lib.rs | 8 +++--- .../practice/simple-linked-list/src/lib.rs | 18 ++++++------- exercises/practice/space-age/src/lib.rs | 6 ++--- exercises/practice/spiral-matrix/src/lib.rs | 2 +- exercises/practice/sublist/src/lib.rs | 2 +- .../practice/sum-of-multiples/src/lib.rs | 2 +- exercises/practice/tournament/src/lib.rs | 2 +- exercises/practice/triangle/src/lib.rs | 8 +++--- exercises/practice/two-bucket/src/lib.rs | 2 +- exercises/practice/two-fer/src/lib.rs | 2 +- .../variable-length-quantity/src/lib.rs | 4 +-- exercises/practice/word-count/src/lib.rs | 2 +- exercises/practice/wordy/src/lib.rs | 4 +-- exercises/practice/xorcism/src/lib.rs | 6 ++--- exercises/practice/yacht/src/lib.rs | 2 +- rust-tooling/src/bin/generate_exercise.rs | 4 +-- 116 files changed, 249 insertions(+), 269 deletions(-) diff --git a/concepts/structs/about.md b/concepts/structs/about.md index 061ba5889..41f131212 100644 --- a/concepts/structs/about.md +++ b/concepts/structs/about.md @@ -32,7 +32,7 @@ Lastly, methods can be defined on structs inside of an `impl` block: impl Item { // initializes and returns a new instance of our Item struct fn new() -> Self { - unimplemented!() + todo!() } } ``` diff --git a/exercises/concept/assembly-line/src/lib.rs b/exercises/concept/assembly-line/src/lib.rs index 96e01eda4..3a2a928f6 100644 --- a/exercises/concept/assembly-line/src/lib.rs +++ b/exercises/concept/assembly-line/src/lib.rs @@ -1,7 +1,7 @@ pub fn production_rate_per_hour(speed: u8) -> f64 { - unimplemented!("calculate hourly production rate at speed: {speed}") + todo!("calculate hourly production rate at speed: {speed}") } pub fn working_items_per_minute(speed: u8) -> u32 { - unimplemented!("calculate the amount of working items at speed: {speed}") + todo!("calculate the amount of working items at speed: {speed}") } diff --git a/exercises/concept/csv-builder/src/lib.rs b/exercises/concept/csv-builder/src/lib.rs index ec5887180..4b1f94ff9 100644 --- a/exercises/concept/csv-builder/src/lib.rs +++ b/exercises/concept/csv-builder/src/lib.rs @@ -9,16 +9,16 @@ pub struct CsvRecordBuilder { impl CsvRecordBuilder { // Create a new builder pub fn new() -> Self { - unimplemented!("implement the `CsvRecordBuilder::new` method") + todo!("implement the `CsvRecordBuilder::new` method") } /// Adds an item to the list separated by a space and a comma. pub fn add(&mut self, val: &str) { - unimplemented!("implement the `CsvRecordBuilder::add` method, adding {val}") + todo!("implement the `CsvRecordBuilder::add` method, adding {val}") } /// Consumes the builder and returns the comma separated list pub fn build(self) -> String { - unimplemented!("implement the `CsvRecordBuilder::build` method") + todo!("implement the `CsvRecordBuilder::build` method") } } diff --git a/exercises/concept/health-statistics/.docs/introduction.md b/exercises/concept/health-statistics/.docs/introduction.md index 8894da4aa..4ab3579e9 100644 --- a/exercises/concept/health-statistics/.docs/introduction.md +++ b/exercises/concept/health-statistics/.docs/introduction.md @@ -32,7 +32,7 @@ Lastly, methods can be defined on structs inside of an `impl` block: impl Item { // initializes and returns a new instance of our Item struct fn new() -> Self { - unimplemented!() + todo!() } } ``` diff --git a/exercises/concept/health-statistics/src/lib.rs b/exercises/concept/health-statistics/src/lib.rs index f0e2fa0d7..c38c2b705 100644 --- a/exercises/concept/health-statistics/src/lib.rs +++ b/exercises/concept/health-statistics/src/lib.rs @@ -10,26 +10,26 @@ pub struct User { impl User { pub fn new(name: String, age: u32, weight: f32) -> Self { - unimplemented!() + todo!() } pub fn name(&self) -> &str { - unimplemented!() + todo!() } pub fn age(&self) -> u32 { - unimplemented!() + todo!() } pub fn weight(&self) -> f32 { - unimplemented!() + todo!() } pub fn set_age(&mut self, new_age: u32) { - unimplemented!() + todo!() } pub fn set_weight(&mut self, new_weight: f32) { - unimplemented!() + todo!() } } diff --git a/exercises/concept/low-power-embedded-game/.docs/hints.md b/exercises/concept/low-power-embedded-game/.docs/hints.md index e7317a614..dcf3408c2 100644 --- a/exercises/concept/low-power-embedded-game/.docs/hints.md +++ b/exercises/concept/low-power-embedded-game/.docs/hints.md @@ -18,6 +18,6 @@ ## 3. Implement a `manhattan` method on a `Position` tuple struct -- Don't worry about method syntax; just replacing the `unimplemented` portion within the `impl Position` block will do the right thing. +- Don't worry about method syntax; just replacing the `todo` portion within the `impl Position` block will do the right thing. - Consider that some values within a `Position` may be negative, but a distance is never negative. - Calculating the absolute value is [built-in](https://doc.rust-lang.org/std/primitive.i16.html#method.abs) diff --git a/exercises/concept/low-power-embedded-game/src/lib.rs b/exercises/concept/low-power-embedded-game/src/lib.rs index 48d259d14..917cf7ec8 100644 --- a/exercises/concept/low-power-embedded-game/src/lib.rs +++ b/exercises/concept/low-power-embedded-game/src/lib.rs @@ -3,11 +3,11 @@ #![allow(unused)] pub fn divmod(dividend: i16, divisor: i16) -> (i16, i16) { - unimplemented!("implement `fn divmod`"); + todo!("implement `fn divmod`"); } pub fn evens(iter: impl Iterator) -> impl Iterator { - unimplemented!("implement `fn evens`"); + todo!("implement `fn evens`"); // TODO: remove this; it's only necessary to allow this function to compile // before the student has done any work. std::iter::empty() @@ -16,6 +16,6 @@ pub fn evens(iter: impl Iterator) -> impl Iterator { pub struct Position(pub i16, pub i16); impl Position { pub fn manhattan(&self) -> i16 { - unimplemented!("implement `fn manhattan`") + todo!("implement `fn manhattan`") } } diff --git a/exercises/concept/lucians-luscious-lasagna/src/lib.rs b/exercises/concept/lucians-luscious-lasagna/src/lib.rs index c98729b81..8d2fd2d44 100644 --- a/exercises/concept/lucians-luscious-lasagna/src/lib.rs +++ b/exercises/concept/lucians-luscious-lasagna/src/lib.rs @@ -1,19 +1,19 @@ pub fn expected_minutes_in_oven() -> i32 { - unimplemented!("return expected minutes in the oven") + todo!("return expected minutes in the oven") } pub fn remaining_minutes_in_oven(actual_minutes_in_oven: i32) -> i32 { - unimplemented!( + todo!( "calculate remaining minutes in oven given actual minutes in oven: {actual_minutes_in_oven}" ) } pub fn preparation_time_in_minutes(number_of_layers: i32) -> i32 { - unimplemented!("calculate preparation time in minutes for number of layers: {number_of_layers}") + todo!("calculate preparation time in minutes for number of layers: {number_of_layers}") } pub fn elapsed_time_in_minutes(number_of_layers: i32, actual_minutes_in_oven: i32) -> i32 { - unimplemented!( + todo!( "calculate elapsed time in minutes for number of layers {number_of_layers} and actual minutes in oven {actual_minutes_in_oven}" ) } diff --git a/exercises/concept/magazine-cutout/.docs/instructions.md b/exercises/concept/magazine-cutout/.docs/instructions.md index 8e367d5d0..65433ec4a 100644 --- a/exercises/concept/magazine-cutout/.docs/instructions.md +++ b/exercises/concept/magazine-cutout/.docs/instructions.md @@ -13,7 +13,7 @@ You'll start with the following stubbed function signature: ```rust pub fn can_construct_note(magazine: &[&str], note: &[&str]) -> bool { - unimplemented!() + todo!() } ``` diff --git a/exercises/concept/magazine-cutout/src/lib.rs b/exercises/concept/magazine-cutout/src/lib.rs index eb1876e28..cbe23f634 100644 --- a/exercises/concept/magazine-cutout/src/lib.rs +++ b/exercises/concept/magazine-cutout/src/lib.rs @@ -5,5 +5,5 @@ use std::collections::HashMap; pub fn can_construct_note(magazine: &[&str], note: &[&str]) -> bool { - unimplemented!() + todo!() } diff --git a/exercises/concept/resistor-color/src/lib.rs b/exercises/concept/resistor-color/src/lib.rs index 96a5f1033..ff4c92267 100644 --- a/exercises/concept/resistor-color/src/lib.rs +++ b/exercises/concept/resistor-color/src/lib.rs @@ -13,13 +13,13 @@ pub enum ResistorColor { } pub fn color_to_value(color: ResistorColor) -> u32 { - unimplemented!("convert color {color:?} into a numerical representation") + todo!("convert color {color:?} into a numerical representation") } pub fn value_to_color_string(value: u32) -> String { - unimplemented!("convert the value {value} into a string representation of color") + todo!("convert the value {value} into a string representation of color") } pub fn colors() -> Vec { - unimplemented!("return a list of all the colors ordered by resistance") + todo!("return a list of all the colors ordered by resistance") } diff --git a/exercises/concept/role-playing-game/src/lib.rs b/exercises/concept/role-playing-game/src/lib.rs index effc3d614..1bdc203e1 100644 --- a/exercises/concept/role-playing-game/src/lib.rs +++ b/exercises/concept/role-playing-game/src/lib.rs @@ -6,10 +6,10 @@ pub struct Player { impl Player { pub fn revive(&self) -> Option { - unimplemented!("Revive this player") + todo!("Revive this player") } pub fn cast_spell(&mut self, mana_cost: u32) -> u32 { - unimplemented!("Cast a spell of cost {mana_cost}") + todo!("Cast a spell of cost {mana_cost}") } } diff --git a/exercises/concept/rpn-calculator/.docs/instructions.md b/exercises/concept/rpn-calculator/.docs/instructions.md index a749c9515..b6c58751d 100644 --- a/exercises/concept/rpn-calculator/.docs/instructions.md +++ b/exercises/concept/rpn-calculator/.docs/instructions.md @@ -105,7 +105,7 @@ pub enum CalculatorInput { } pub fn evaluate(inputs: &[CalculatorInput]) -> Option { - unimplemented!( + todo!( "Given the inputs: {inputs:?}, evaluate them as though they were a Reverse Polish notation expression" ); } diff --git a/exercises/concept/rpn-calculator/src/lib.rs b/exercises/concept/rpn-calculator/src/lib.rs index 0e0011f66..f740777eb 100644 --- a/exercises/concept/rpn-calculator/src/lib.rs +++ b/exercises/concept/rpn-calculator/src/lib.rs @@ -8,7 +8,7 @@ pub enum CalculatorInput { } pub fn evaluate(inputs: &[CalculatorInput]) -> Option { - unimplemented!( + todo!( "Given the inputs: {inputs:?}, evaluate them as though they were a Reverse Polish notation expression" ); } diff --git a/exercises/concept/semi-structured-logs/src/lib.rs b/exercises/concept/semi-structured-logs/src/lib.rs index 91b0560f1..07916f96e 100644 --- a/exercises/concept/semi-structured-logs/src/lib.rs +++ b/exercises/concept/semi-structured-logs/src/lib.rs @@ -11,14 +11,14 @@ pub enum LogLevel { } /// primary function for emitting logs pub fn log(level: LogLevel, message: &str) -> String { - unimplemented!("return a message for the given log level") + todo!("return a message for the given log level") } pub fn info(message: &str) -> String { - unimplemented!("return a message for info log level") + todo!("return a message for info log level") } pub fn warn(message: &str) -> String { - unimplemented!("return a message for warn log level") + todo!("return a message for warn log level") } pub fn error(message: &str) -> String { - unimplemented!("return a message for error log level") + todo!("return a message for error log level") } diff --git a/exercises/concept/short-fibonacci/src/lib.rs b/exercises/concept/short-fibonacci/src/lib.rs index 2d5dcbc5c..0db5a8feb 100644 --- a/exercises/concept/short-fibonacci/src/lib.rs +++ b/exercises/concept/short-fibonacci/src/lib.rs @@ -1,13 +1,13 @@ /// Create an empty vector pub fn create_empty() -> Vec { - unimplemented!() + todo!() } /// Create a buffer of `count` zeroes. /// /// Applications often use buffers when serializing data to send over the network. pub fn create_buffer(count: usize) -> Vec { - unimplemented!("create a zeroized buffer of {count} bytes") + todo!("create a zeroized buffer of {count} bytes") } /// Create a vector containing the first five elements of the Fibonacci sequence. @@ -15,5 +15,5 @@ pub fn create_buffer(count: usize) -> Vec { /// Fibonacci's sequence is the list of numbers where the next number is a sum of the previous two. /// Its first five elements are `1, 1, 2, 3, 5`. pub fn fibonacci() -> Vec { - unimplemented!() + todo!() } diff --git a/exercises/practice/accumulate/src/lib.rs b/exercises/practice/accumulate/src/lib.rs index bdacbd272..2ed0e9e22 100644 --- a/exercises/practice/accumulate/src/lib.rs +++ b/exercises/practice/accumulate/src/lib.rs @@ -1,4 +1,4 @@ /// What should the type of _function be? pub fn map(input: Vec, _function: ???) -> Vec { - unimplemented!("Transform input vector {input:?} using passed function"); + todo!("Transform input vector {input:?} using passed function"); } diff --git a/exercises/practice/acronym/src/lib.rs b/exercises/practice/acronym/src/lib.rs index fd1c026a3..d31dd96c2 100644 --- a/exercises/practice/acronym/src/lib.rs +++ b/exercises/practice/acronym/src/lib.rs @@ -1,3 +1,3 @@ pub fn abbreviate(phrase: &str) -> String { - unimplemented!("Given the phrase '{phrase}', return its acronym"); + todo!("Given the phrase '{phrase}', return its acronym"); } diff --git a/exercises/practice/affine-cipher/src/lib.rs b/exercises/practice/affine-cipher/src/lib.rs index 8e666711f..396aa99d0 100644 --- a/exercises/practice/affine-cipher/src/lib.rs +++ b/exercises/practice/affine-cipher/src/lib.rs @@ -8,11 +8,11 @@ pub enum AffineCipherError { /// Encodes the plaintext using the affine cipher with key (`a`, `b`). Note that, rather than /// returning a return code, the more common convention in Rust is to return a `Result`. pub fn encode(plaintext: &str, a: i32, b: i32) -> Result { - unimplemented!("Encode {plaintext} with the key ({a}, {b})"); + todo!("Encode {plaintext} with the key ({a}, {b})"); } /// Decodes the ciphertext using the affine cipher with key (`a`, `b`). Note that, rather than /// returning a return code, the more common convention in Rust is to return a `Result`. pub fn decode(ciphertext: &str, a: i32, b: i32) -> Result { - unimplemented!("Decode {ciphertext} with the key ({a}, {b})"); + todo!("Decode {ciphertext} with the key ({a}, {b})"); } diff --git a/exercises/practice/all-your-base/src/lib.rs b/exercises/practice/all-your-base/src/lib.rs index 0a1189323..2803bcaf9 100644 --- a/exercises/practice/all-your-base/src/lib.rs +++ b/exercises/practice/all-your-base/src/lib.rs @@ -36,5 +36,5 @@ pub enum Error { /// However, your function must be able to process input with leading 0 digits. /// pub fn convert(number: &[u32], from_base: u32, to_base: u32) -> Result, Error> { - unimplemented!("Convert {number:?} from base {from_base} to base {to_base}") + todo!("Convert {number:?} from base {from_base} to base {to_base}") } diff --git a/exercises/practice/allergies/src/lib.rs b/exercises/practice/allergies/src/lib.rs index a3715f409..a2fd3088f 100644 --- a/exercises/practice/allergies/src/lib.rs +++ b/exercises/practice/allergies/src/lib.rs @@ -14,14 +14,14 @@ pub enum Allergen { impl Allergies { pub fn new(score: u32) -> Self { - unimplemented!("Given the '{score}' score, construct a new Allergies struct."); + todo!("Given the '{score}' score, construct a new Allergies struct."); } pub fn is_allergic_to(&self, allergen: &Allergen) -> bool { - unimplemented!("Determine if the patient is allergic to the '{allergen:?}' allergen."); + todo!("Determine if the patient is allergic to the '{allergen:?}' allergen."); } pub fn allergies(&self) -> Vec { - unimplemented!("Return the list of allergens contained within the score with which the Allergies struct was made."); + todo!("Return the list of allergens contained within the score with which the Allergies struct was made."); } } diff --git a/exercises/practice/alphametics/src/lib.rs b/exercises/practice/alphametics/src/lib.rs index d5cb54146..a7eb2c2e2 100644 --- a/exercises/practice/alphametics/src/lib.rs +++ b/exercises/practice/alphametics/src/lib.rs @@ -1,5 +1,5 @@ use std::collections::HashMap; pub fn solve(input: &str) -> Option> { - unimplemented!("Solve the alphametic {input:?}") + todo!("Solve the alphametic {input:?}") } diff --git a/exercises/practice/anagram/src/lib.rs b/exercises/practice/anagram/src/lib.rs index 111521663..f029d0449 100644 --- a/exercises/practice/anagram/src/lib.rs +++ b/exercises/practice/anagram/src/lib.rs @@ -1,7 +1,5 @@ use std::collections::HashSet; pub fn anagrams_for<'a>(word: &str, possible_anagrams: &[&str]) -> HashSet<&'a str> { - unimplemented!( - "For the '{word}' word find anagrams among the following words: {possible_anagrams:?}" - ); + todo!("For the '{word}' word find anagrams among the following words: {possible_anagrams:?}"); } diff --git a/exercises/practice/armstrong-numbers/src/lib.rs b/exercises/practice/armstrong-numbers/src/lib.rs index 3830a8484..91a62532d 100644 --- a/exercises/practice/armstrong-numbers/src/lib.rs +++ b/exercises/practice/armstrong-numbers/src/lib.rs @@ -1,3 +1,3 @@ pub fn is_armstrong_number(num: u32) -> bool { - unimplemented!("true if {num} is an armstrong number") + todo!("true if {num} is an armstrong number") } diff --git a/exercises/practice/atbash-cipher/src/lib.rs b/exercises/practice/atbash-cipher/src/lib.rs index 17e480ae5..5f63c8b85 100644 --- a/exercises/practice/atbash-cipher/src/lib.rs +++ b/exercises/practice/atbash-cipher/src/lib.rs @@ -1,9 +1,9 @@ /// "Encipher" with the Atbash cipher. pub fn encode(plain: &str) -> String { - unimplemented!("Encoding of {plain:?} in Atbash cipher."); + todo!("Encoding of {plain:?} in Atbash cipher."); } /// "Decipher" with the Atbash cipher. pub fn decode(cipher: &str) -> String { - unimplemented!("Decoding of {cipher:?} in Atbash cipher."); + todo!("Decoding of {cipher:?} in Atbash cipher."); } diff --git a/exercises/practice/beer-song/src/lib.rs b/exercises/practice/beer-song/src/lib.rs index c35a613c2..fd6334416 100644 --- a/exercises/practice/beer-song/src/lib.rs +++ b/exercises/practice/beer-song/src/lib.rs @@ -1,7 +1,7 @@ pub fn verse(n: u32) -> String { - unimplemented!("emit verse {n}") + todo!("emit verse {n}") } pub fn sing(start: u32, end: u32) -> String { - unimplemented!("sing verses {start} to {end}, inclusive") + todo!("sing verses {start} to {end}, inclusive") } diff --git a/exercises/practice/binary-search/src/lib.rs b/exercises/practice/binary-search/src/lib.rs index 5b10dadac..fbdd6d6fd 100644 --- a/exercises/practice/binary-search/src/lib.rs +++ b/exercises/practice/binary-search/src/lib.rs @@ -1,5 +1,5 @@ pub fn find(array: &[i32], key: i32) -> Option { - unimplemented!( + todo!( "Using the binary search algorithm, find the element '{key}' in the array '{array:?}' and return its index." ); } diff --git a/exercises/practice/bob/src/lib.rs b/exercises/practice/bob/src/lib.rs index 30b51cbcf..833f2dfca 100644 --- a/exercises/practice/bob/src/lib.rs +++ b/exercises/practice/bob/src/lib.rs @@ -1,3 +1,3 @@ pub fn reply(message: &str) -> &str { - unimplemented!("have Bob reply to the incoming message: {message}") + todo!("have Bob reply to the incoming message: {message}") } diff --git a/exercises/practice/book-store/src/lib.rs b/exercises/practice/book-store/src/lib.rs index c8cf0232a..64ee8dd31 100644 --- a/exercises/practice/book-store/src/lib.rs +++ b/exercises/practice/book-store/src/lib.rs @@ -1,3 +1,3 @@ pub fn lowest_price(books: &[u32]) -> u32 { - unimplemented!("Find the lowest price of the bookbasket with books {books:?}") + todo!("Find the lowest price of the bookbasket with books {books:?}") } diff --git a/exercises/practice/bowling/src/lib.rs b/exercises/practice/bowling/src/lib.rs index 3b46f51b8..3163e6120 100644 --- a/exercises/practice/bowling/src/lib.rs +++ b/exercises/practice/bowling/src/lib.rs @@ -8,14 +8,14 @@ pub struct BowlingGame {} impl BowlingGame { pub fn new() -> Self { - unimplemented!(); + todo!(); } pub fn roll(&mut self, pins: u16) -> Result<(), Error> { - unimplemented!("Record that {pins} pins have been scored"); + todo!("Record that {pins} pins have been scored"); } pub fn score(&self) -> Option { - unimplemented!("Return the score if the game is complete, or None if not."); + todo!("Return the score if the game is complete, or None if not."); } } diff --git a/exercises/practice/circular-buffer/src/lib.rs b/exercises/practice/circular-buffer/src/lib.rs index 426fba8a3..63686c805 100644 --- a/exercises/practice/circular-buffer/src/lib.rs +++ b/exercises/practice/circular-buffer/src/lib.rs @@ -12,7 +12,7 @@ pub enum Error { impl CircularBuffer { pub fn new(capacity: usize) -> Self { - unimplemented!( + todo!( "Construct a new CircularBuffer with the capacity to hold {}.", match capacity { 1 => "1 element".to_string(), @@ -22,18 +22,18 @@ impl CircularBuffer { } pub fn write(&mut self, _element: T) -> Result<(), Error> { - unimplemented!("Write the passed element to the CircularBuffer or return FullBuffer error if CircularBuffer is full."); + todo!("Write the passed element to the CircularBuffer or return FullBuffer error if CircularBuffer is full."); } pub fn read(&mut self) -> Result { - unimplemented!("Read the oldest element from the CircularBuffer or return EmptyBuffer error if CircularBuffer is empty."); + todo!("Read the oldest element from the CircularBuffer or return EmptyBuffer error if CircularBuffer is empty."); } pub fn clear(&mut self) { - unimplemented!("Clear the CircularBuffer."); + todo!("Clear the CircularBuffer."); } pub fn overwrite(&mut self, _element: T) { - unimplemented!("Write the passed element to the CircularBuffer, overwriting the existing elements if CircularBuffer is full."); + todo!("Write the passed element to the CircularBuffer, overwriting the existing elements if CircularBuffer is full."); } } diff --git a/exercises/practice/clock/src/lib.rs b/exercises/practice/clock/src/lib.rs index 8627e2296..c625f73de 100644 --- a/exercises/practice/clock/src/lib.rs +++ b/exercises/practice/clock/src/lib.rs @@ -2,10 +2,10 @@ pub struct Clock; impl Clock { pub fn new(hours: i32, minutes: i32) -> Self { - unimplemented!("Construct a new Clock from {hours} hours and {minutes} minutes"); + todo!("Construct a new Clock from {hours} hours and {minutes} minutes"); } pub fn add_minutes(&self, minutes: i32) -> Self { - unimplemented!("Add {minutes} minutes to existing Clock time"); + todo!("Add {minutes} minutes to existing Clock time"); } } diff --git a/exercises/practice/collatz-conjecture/src/lib.rs b/exercises/practice/collatz-conjecture/src/lib.rs index 5bf41a567..84f178225 100644 --- a/exercises/practice/collatz-conjecture/src/lib.rs +++ b/exercises/practice/collatz-conjecture/src/lib.rs @@ -1,5 +1,3 @@ pub fn collatz(n: u64) -> Option { - unimplemented!( - "return Some(x) where x is the number of steps required to reach 1 starting with {n}" - ) + todo!("return Some(x) where x is the number of steps required to reach 1 starting with {n}") } diff --git a/exercises/practice/crypto-square/src/lib.rs b/exercises/practice/crypto-square/src/lib.rs index d9700db5c..c22623ef2 100644 --- a/exercises/practice/crypto-square/src/lib.rs +++ b/exercises/practice/crypto-square/src/lib.rs @@ -1,3 +1,3 @@ pub fn encrypt(input: &str) -> String { - unimplemented!("Encrypt {input:?} using a square code") + todo!("Encrypt {input:?} using a square code") } diff --git a/exercises/practice/custom-set/src/lib.rs b/exercises/practice/custom-set/src/lib.rs index f5ff8f44c..3cde4569c 100644 --- a/exercises/practice/custom-set/src/lib.rs +++ b/exercises/practice/custom-set/src/lib.rs @@ -7,41 +7,41 @@ pub struct CustomSet { impl CustomSet { pub fn new(_input: &[T]) -> Self { - unimplemented!(); + todo!(); } pub fn contains(&self, _element: &T) -> bool { - unimplemented!(); + todo!(); } pub fn add(&mut self, _element: T) { - unimplemented!(); + todo!(); } pub fn is_subset(&self, _other: &Self) -> bool { - unimplemented!(); + todo!(); } pub fn is_empty(&self) -> bool { - unimplemented!(); + todo!(); } pub fn is_disjoint(&self, _other: &Self) -> bool { - unimplemented!(); + todo!(); } #[must_use] pub fn intersection(&self, _other: &Self) -> Self { - unimplemented!(); + todo!(); } #[must_use] pub fn difference(&self, _other: &Self) -> Self { - unimplemented!(); + todo!(); } #[must_use] pub fn union(&self, _other: &Self) -> Self { - unimplemented!(); + todo!(); } } diff --git a/exercises/practice/decimal/src/lib.rs b/exercises/practice/decimal/src/lib.rs index e61d55bb5..c9ad7098f 100644 --- a/exercises/practice/decimal/src/lib.rs +++ b/exercises/practice/decimal/src/lib.rs @@ -5,6 +5,6 @@ pub struct Decimal { impl Decimal { pub fn try_from(input: &str) -> Option { - unimplemented!("Create a new decimal with a value of {input}") + todo!("Create a new decimal with a value of {input}") } } diff --git a/exercises/practice/diamond/src/lib.rs b/exercises/practice/diamond/src/lib.rs index 0bcc23a32..db78003f8 100644 --- a/exercises/practice/diamond/src/lib.rs +++ b/exercises/practice/diamond/src/lib.rs @@ -1,5 +1,3 @@ pub fn get_diamond(c: char) -> Vec { - unimplemented!( - "Return the vector of strings which represent the diamond with particular char {c}" - ); + todo!("Return the vector of strings which represent the diamond with particular char {c}"); } diff --git a/exercises/practice/difference-of-squares/src/lib.rs b/exercises/practice/difference-of-squares/src/lib.rs index 402286a6c..fdf2e93d8 100644 --- a/exercises/practice/difference-of-squares/src/lib.rs +++ b/exercises/practice/difference-of-squares/src/lib.rs @@ -1,11 +1,11 @@ pub fn square_of_sum(n: u32) -> u32 { - unimplemented!("square of sum of 1...{n}") + todo!("square of sum of 1...{n}") } pub fn sum_of_squares(n: u32) -> u32 { - unimplemented!("sum of squares of 1...{n}") + todo!("sum of squares of 1...{n}") } pub fn difference(n: u32) -> u32 { - unimplemented!("difference between square of sum of 1...{n} and sum of squares of 1...{n}") + todo!("difference between square of sum of 1...{n} and sum of squares of 1...{n}") } diff --git a/exercises/practice/diffie-hellman/src/lib.rs b/exercises/practice/diffie-hellman/src/lib.rs index a38954435..157e540d7 100644 --- a/exercises/practice/diffie-hellman/src/lib.rs +++ b/exercises/practice/diffie-hellman/src/lib.rs @@ -1,13 +1,11 @@ pub fn private_key(p: u64) -> u64 { - unimplemented!("Pick a private key greater than 1 and less than {p}") + todo!("Pick a private key greater than 1 and less than {p}") } pub fn public_key(p: u64, g: u64, a: u64) -> u64 { - unimplemented!("Calculate public key using prime numbers {p} and {g}, and private key {a}") + todo!("Calculate public key using prime numbers {p} and {g}, and private key {a}") } pub fn secret(p: u64, b_pub: u64, a: u64) -> u64 { - unimplemented!( - "Calculate secret key using prime number {p}, public key {b_pub}, and private key {a}" - ) + todo!("Calculate secret key using prime number {p}, public key {b_pub}, and private key {a}") } diff --git a/exercises/practice/dominoes/src/lib.rs b/exercises/practice/dominoes/src/lib.rs index 7591c7ab9..3137bddc7 100644 --- a/exercises/practice/dominoes/src/lib.rs +++ b/exercises/practice/dominoes/src/lib.rs @@ -1,3 +1,3 @@ pub fn chain(input: &[(u8, u8)]) -> Option> { - unimplemented!("From the given input '{input:?}' construct a proper dominoes chain or return None if it is not possible."); + todo!("From the given input '{input:?}' construct a proper dominoes chain or return None if it is not possible."); } diff --git a/exercises/practice/dot-dsl/src/lib.rs b/exercises/practice/dot-dsl/src/lib.rs index ace9a0e88..3f2514dc6 100644 --- a/exercises/practice/dot-dsl/src/lib.rs +++ b/exercises/practice/dot-dsl/src/lib.rs @@ -3,7 +3,7 @@ pub mod graph { impl Graph { pub fn new() -> Self { - unimplemented!("Construct a new Graph struct."); + todo!("Construct a new Graph struct."); } } } diff --git a/exercises/practice/doubly-linked-list/src/lib.rs b/exercises/practice/doubly-linked-list/src/lib.rs index d6391c6f3..a02fdb3f9 100644 --- a/exercises/practice/doubly-linked-list/src/lib.rs +++ b/exercises/practice/doubly-linked-list/src/lib.rs @@ -11,7 +11,7 @@ pub struct Iter<'a, T>(std::marker::PhantomData<&'a T>); impl LinkedList { pub fn new() -> Self { - unimplemented!() + todo!() } // You may be wondering why it's necessary to have is_empty() @@ -20,26 +20,26 @@ impl LinkedList { // whereas is_empty() is almost always cheap. // (Also ask yourself whether len() is expensive for LinkedList) pub fn is_empty(&self) -> bool { - unimplemented!() + todo!() } pub fn len(&self) -> usize { - unimplemented!() + todo!() } /// Return a cursor positioned on the front element pub fn cursor_front(&mut self) -> Cursor<'_, T> { - unimplemented!() + todo!() } /// Return a cursor positioned on the back element pub fn cursor_back(&mut self) -> Cursor<'_, T> { - unimplemented!() + todo!() } /// Return an iterator that moves from front to back pub fn iter(&self) -> Iter<'_, T> { - unimplemented!() + todo!() } } @@ -48,35 +48,35 @@ impl LinkedList { impl Cursor<'_, T> { /// Take a mutable reference to the current element pub fn peek_mut(&mut self) -> Option<&mut T> { - unimplemented!() + todo!() } /// Move one position forward (towards the back) and /// return a reference to the new position #[allow(clippy::should_implement_trait)] pub fn next(&mut self) -> Option<&mut T> { - unimplemented!() + todo!() } /// Move one position backward (towards the front) and /// return a reference to the new position pub fn prev(&mut self) -> Option<&mut T> { - unimplemented!() + todo!() } /// Remove and return the element at the current position and move the cursor /// to the neighboring element that's closest to the back. This can be /// either the next or previous position. pub fn take(&mut self) -> Option { - unimplemented!() + todo!() } pub fn insert_after(&mut self, _element: T) { - unimplemented!() + todo!() } pub fn insert_before(&mut self, _element: T) { - unimplemented!() + todo!() } } @@ -84,6 +84,6 @@ impl<'a, T> Iterator for Iter<'a, T> { type Item = &'a T; fn next(&mut self) -> Option<&'a T> { - unimplemented!() + todo!() } } diff --git a/exercises/practice/etl/src/lib.rs b/exercises/practice/etl/src/lib.rs index 3b451f1c1..25e941f27 100644 --- a/exercises/practice/etl/src/lib.rs +++ b/exercises/practice/etl/src/lib.rs @@ -1,5 +1,5 @@ use std::collections::BTreeMap; pub fn transform(h: &BTreeMap>) -> BTreeMap { - unimplemented!("How will you transform the tree {h:?}?") + todo!("How will you transform the tree {h:?}?") } diff --git a/exercises/practice/fizzy/src/lib.rs b/exercises/practice/fizzy/src/lib.rs index 89d0e802b..03b50ae73 100644 --- a/exercises/practice/fizzy/src/lib.rs +++ b/exercises/practice/fizzy/src/lib.rs @@ -7,7 +7,7 @@ pub struct Matcher(std::marker::PhantomData); impl Matcher { pub fn new(_matcher: F, _subs: S) -> Matcher { - unimplemented!() + todo!() } } @@ -24,18 +24,18 @@ pub struct Fizzy(std::marker::PhantomData); impl Fizzy { pub fn new() -> Self { - unimplemented!() + todo!() } // feel free to change the signature to `mut self` if you like #[must_use] pub fn add_matcher(self, _matcher: Matcher) -> Self { - unimplemented!() + todo!() } /// map this fizzy onto every element of an iterator, returning a new iterator pub fn apply(self, _iter: I) -> impl Iterator { - // unimplemented!() doesn't actually work, here; () is not an Iterator + // todo!() doesn't actually work, here; () is not an Iterator // that said, this is probably not the actual implementation you desire Vec::new().into_iter() } @@ -43,5 +43,5 @@ impl Fizzy { /// convenience function: return a Fizzy which applies the standard fizz-buzz rules pub fn fizz_buzz() -> Fizzy { - unimplemented!() + todo!() } diff --git a/exercises/practice/forth/src/lib.rs b/exercises/practice/forth/src/lib.rs index 16947dd9c..e0e06c73f 100644 --- a/exercises/practice/forth/src/lib.rs +++ b/exercises/practice/forth/src/lib.rs @@ -13,14 +13,14 @@ pub enum Error { impl Forth { pub fn new() -> Forth { - unimplemented!() + todo!() } pub fn stack(&self) -> &[Value] { - unimplemented!() + todo!() } pub fn eval(&mut self, input: &str) -> Result { - unimplemented!("result of evaluating '{input}'") + todo!("result of evaluating '{input}'") } } diff --git a/exercises/practice/gigasecond/src/lib.rs b/exercises/practice/gigasecond/src/lib.rs index 91daf88f5..2dbb9ab9a 100644 --- a/exercises/practice/gigasecond/src/lib.rs +++ b/exercises/practice/gigasecond/src/lib.rs @@ -2,5 +2,5 @@ use time::PrimitiveDateTime as DateTime; // Returns a DateTime one billion seconds after start. pub fn after(start: DateTime) -> DateTime { - unimplemented!("What time is a gigasecond later than {start}"); + todo!("What time is a gigasecond later than {start}"); } diff --git a/exercises/practice/grade-school/src/lib.rs b/exercises/practice/grade-school/src/lib.rs index b81e9a1ab..f90a271c1 100644 --- a/exercises/practice/grade-school/src/lib.rs +++ b/exercises/practice/grade-school/src/lib.rs @@ -9,15 +9,15 @@ pub struct School {} impl School { pub fn new() -> School { - unimplemented!() + todo!() } pub fn add(&mut self, grade: u32, student: &str) { - unimplemented!("Add {student} to the roster for {grade}") + todo!("Add {student} to the roster for {grade}") } pub fn grades(&self) -> Vec { - unimplemented!() + todo!() } // If `grade` returned a reference, `School` would be forced to keep a `Vec` @@ -25,6 +25,6 @@ impl School { // the internal structure can be completely arbitrary. The tradeoff is that some data // must be copied each time `grade` is called. pub fn grade(&self, grade: u32) -> Vec { - unimplemented!("Return the list of students in {grade}") + todo!("Return the list of students in {grade}") } } diff --git a/exercises/practice/grains/src/lib.rs b/exercises/practice/grains/src/lib.rs index a4e1da66d..8eeccb68b 100644 --- a/exercises/practice/grains/src/lib.rs +++ b/exercises/practice/grains/src/lib.rs @@ -1,7 +1,7 @@ pub fn square(s: u32) -> u64 { - unimplemented!("grains of rice on square {s}"); + todo!("grains of rice on square {s}"); } pub fn total() -> u64 { - unimplemented!(); + todo!(); } diff --git a/exercises/practice/grep/src/lib.rs b/exercises/practice/grep/src/lib.rs index 25b636a1e..9ef2c9d22 100644 --- a/exercises/practice/grep/src/lib.rs +++ b/exercises/practice/grep/src/lib.rs @@ -17,14 +17,14 @@ pub struct Flags; impl Flags { pub fn new(flags: &[&str]) -> Self { - unimplemented!( + todo!( "Given the flags {flags:?} implement your own 'Flags' struct to handle flags-related logic" ); } } pub fn grep(pattern: &str, flags: &Flags, files: &[&str]) -> Result, Error> { - unimplemented!( + todo!( "Search the files '{files:?}' for '{pattern}' pattern and save the matches in a vector. Your search logic should be aware of the given flags '{flags:?}'" ); } diff --git a/exercises/practice/hamming/src/lib.rs b/exercises/practice/hamming/src/lib.rs index 021efd9bc..391af3c52 100644 --- a/exercises/practice/hamming/src/lib.rs +++ b/exercises/practice/hamming/src/lib.rs @@ -1,5 +1,5 @@ /// Return the Hamming distance between the strings, /// or None if the lengths are mismatched. pub fn hamming_distance(s1: &str, s2: &str) -> Option { - unimplemented!("What is the Hamming Distance between {s1} and {s2}"); + todo!("What is the Hamming Distance between {s1} and {s2}"); } diff --git a/exercises/practice/hexadecimal/src/lib.rs b/exercises/practice/hexadecimal/src/lib.rs index 17fd514a1..870cc6e56 100644 --- a/exercises/practice/hexadecimal/src/lib.rs +++ b/exercises/practice/hexadecimal/src/lib.rs @@ -2,7 +2,7 @@ // Consider working on all-your-base instead. pub fn hex_to_int(string: &str) -> Option { - unimplemented!( + todo!( "what integer is represented by the base-16 digits {}?", string ); diff --git a/exercises/practice/high-scores/src/lib.rs b/exercises/practice/high-scores/src/lib.rs index ccb33d324..07f513ef6 100644 --- a/exercises/practice/high-scores/src/lib.rs +++ b/exercises/practice/high-scores/src/lib.rs @@ -3,22 +3,22 @@ pub struct HighScores; impl HighScores { pub fn new(scores: &[u32]) -> Self { - unimplemented!("Construct a HighScores struct, given the scores: {scores:?}") + todo!("Construct a HighScores struct, given the scores: {scores:?}") } pub fn scores(&self) -> &[u32] { - unimplemented!("Return all the scores as a slice") + todo!("Return all the scores as a slice") } pub fn latest(&self) -> Option { - unimplemented!("Return the latest (last) score") + todo!("Return the latest (last) score") } pub fn personal_best(&self) -> Option { - unimplemented!("Return the highest score") + todo!("Return the highest score") } pub fn personal_top_three(&self) -> Vec { - unimplemented!("Return 3 highest scores") + todo!("Return 3 highest scores") } } diff --git a/exercises/practice/isbn-verifier/src/lib.rs b/exercises/practice/isbn-verifier/src/lib.rs index e96ca32f4..c500b0bf5 100644 --- a/exercises/practice/isbn-verifier/src/lib.rs +++ b/exercises/practice/isbn-verifier/src/lib.rs @@ -1,4 +1,4 @@ /// Determines whether the supplied string is a valid ISBN number pub fn is_valid_isbn(isbn: &str) -> bool { - unimplemented!("Is {isbn:?} a valid ISBN number?"); + todo!("Is {isbn:?} a valid ISBN number?"); } diff --git a/exercises/practice/isogram/src/lib.rs b/exercises/practice/isogram/src/lib.rs index 7fd132af7..620281037 100644 --- a/exercises/practice/isogram/src/lib.rs +++ b/exercises/practice/isogram/src/lib.rs @@ -1,3 +1,3 @@ pub fn check(candidate: &str) -> bool { - unimplemented!("Is {candidate} an isogram?"); + todo!("Is {candidate} an isogram?"); } diff --git a/exercises/practice/kindergarten-garden/src/lib.rs b/exercises/practice/kindergarten-garden/src/lib.rs index 2c9fd777d..a8eae0480 100644 --- a/exercises/practice/kindergarten-garden/src/lib.rs +++ b/exercises/practice/kindergarten-garden/src/lib.rs @@ -1,3 +1,3 @@ pub fn plants(_diagram: &str, _student: &str) -> Vec<&'static str> { - unimplemented!("Solve kindergarten-garden exercise"); + todo!("Solve kindergarten-garden exercise"); } diff --git a/exercises/practice/knapsack/src/lib.rs b/exercises/practice/knapsack/src/lib.rs index 6f156ccbf..657f37c03 100644 --- a/exercises/practice/knapsack/src/lib.rs +++ b/exercises/practice/knapsack/src/lib.rs @@ -4,5 +4,5 @@ pub struct Item { } pub fn maximum_value(_max_weight: u32, _items: &[Item]) -> u32 { - unimplemented!("Solve the knapsack exercise"); + todo!("Solve the knapsack exercise"); } diff --git a/exercises/practice/largest-series-product/src/lib.rs b/exercises/practice/largest-series-product/src/lib.rs index 2741a299b..8b4ce6d73 100644 --- a/exercises/practice/largest-series-product/src/lib.rs +++ b/exercises/practice/largest-series-product/src/lib.rs @@ -5,5 +5,5 @@ pub enum Error { } pub fn lsp(string_digits: &str, span: usize) -> Result { - unimplemented!("largest series product of a span of {span} digits in {string_digits}"); + todo!("largest series product of a span of {span} digits in {string_digits}"); } diff --git a/exercises/practice/leap/src/lib.rs b/exercises/practice/leap/src/lib.rs index 78a67219a..edeefea4d 100644 --- a/exercises/practice/leap/src/lib.rs +++ b/exercises/practice/leap/src/lib.rs @@ -1,3 +1,3 @@ pub fn is_leap_year(year: u64) -> bool { - unimplemented!("true if {year} is a leap year") + todo!("true if {year} is a leap year") } diff --git a/exercises/practice/luhn-from/src/lib.rs b/exercises/practice/luhn-from/src/lib.rs index c0ca85945..f7c991220 100644 --- a/exercises/practice/luhn-from/src/lib.rs +++ b/exercises/practice/luhn-from/src/lib.rs @@ -2,7 +2,7 @@ pub struct Luhn; impl Luhn { pub fn is_valid(&self) -> bool { - unimplemented!("Determine if the current Luhn struct contains a valid credit card number."); + todo!("Determine if the current Luhn struct contains a valid credit card number."); } } @@ -13,6 +13,6 @@ impl Luhn { /// Perhaps there exists a better solution for this problem? impl<'a> From<&'a str> for Luhn { fn from(input: &'a str) -> Self { - unimplemented!("From the given input '{input}' create a new Luhn struct."); + todo!("From the given input '{input}' create a new Luhn struct."); } } diff --git a/exercises/practice/luhn-trait/src/lib.rs b/exercises/practice/luhn-trait/src/lib.rs index a7aa54218..6b958fecf 100644 --- a/exercises/practice/luhn-trait/src/lib.rs +++ b/exercises/practice/luhn-trait/src/lib.rs @@ -9,6 +9,6 @@ pub trait Luhn { /// Perhaps there exists a better solution for this problem? impl<'a> Luhn for &'a str { fn valid_luhn(&self) -> bool { - unimplemented!("Determine if '{self}' is a valid credit card number."); + todo!("Determine if '{self}' is a valid credit card number."); } } diff --git a/exercises/practice/luhn/src/lib.rs b/exercises/practice/luhn/src/lib.rs index 858a086e6..38e363f13 100644 --- a/exercises/practice/luhn/src/lib.rs +++ b/exercises/practice/luhn/src/lib.rs @@ -1,4 +1,4 @@ /// Check a Luhn checksum. pub fn is_valid(code: &str) -> bool { - unimplemented!("Is the Luhn checksum for {code} valid?"); + todo!("Is the Luhn checksum for {code} valid?"); } diff --git a/exercises/practice/macros/src/lib.rs b/exercises/practice/macros/src/lib.rs index bf7d88812..a4c8c16dc 100644 --- a/exercises/practice/macros/src/lib.rs +++ b/exercises/practice/macros/src/lib.rs @@ -1,6 +1,6 @@ #[macro_export] macro_rules! hashmap { () => { - unimplemented!() + todo!() }; } diff --git a/exercises/practice/matching-brackets/src/lib.rs b/exercises/practice/matching-brackets/src/lib.rs index a780fad56..2adc73149 100644 --- a/exercises/practice/matching-brackets/src/lib.rs +++ b/exercises/practice/matching-brackets/src/lib.rs @@ -1,3 +1,3 @@ pub fn brackets_are_balanced(string: &str) -> bool { - unimplemented!("Check if the string \"{string}\" contains balanced brackets"); + todo!("Check if the string \"{string}\" contains balanced brackets"); } diff --git a/exercises/practice/minesweeper/src/lib.rs b/exercises/practice/minesweeper/src/lib.rs index 3226570b0..55f516ca5 100644 --- a/exercises/practice/minesweeper/src/lib.rs +++ b/exercises/practice/minesweeper/src/lib.rs @@ -1,3 +1,3 @@ pub fn annotate(minefield: &[&str]) -> Vec { - unimplemented!("\nAnnotate each square of the given minefield with the number of mines that surround said square (blank if there are no surrounding mines):\n{minefield:#?}\n"); + todo!("\nAnnotate each square of the given minefield with the number of mines that surround said square (blank if there are no surrounding mines):\n{minefield:#?}\n"); } diff --git a/exercises/practice/nth-prime/src/lib.rs b/exercises/practice/nth-prime/src/lib.rs index f4726d180..563e947cf 100644 --- a/exercises/practice/nth-prime/src/lib.rs +++ b/exercises/practice/nth-prime/src/lib.rs @@ -1,3 +1,3 @@ pub fn nth(n: u32) -> u32 { - unimplemented!("What is the 0-indexed {n}th prime number?") + todo!("What is the 0-indexed {n}th prime number?") } diff --git a/exercises/practice/nucleotide-codons/src/lib.rs b/exercises/practice/nucleotide-codons/src/lib.rs index f76ce4a97..17a6829b3 100644 --- a/exercises/practice/nucleotide-codons/src/lib.rs +++ b/exercises/practice/nucleotide-codons/src/lib.rs @@ -16,19 +16,19 @@ pub struct Error; impl<'a> CodonsInfo<'a> { pub fn name_for(&self, codon: &str) -> Result<&'a str, Error> { - unimplemented!( + todo!( "Return the protein name for a '{}' codon or Err, if codon string is invalid", codon ); } pub fn of_rna(&self, rna: &str) -> Result, Error> { - unimplemented!("Return a list of protein names that correspond to the '{}' RNA string or Err if the RNA string is invalid", rna); + todo!("Return a list of protein names that correspond to the '{}' RNA string or Err if the RNA string is invalid", rna); } } pub fn parse<'a>(pairs: Vec<(&'a str, &'a str)>) -> CodonsInfo<'a> { - unimplemented!( + todo!( "Construct a new CodonsInfo struct from given pairs: {:?}", pairs ); diff --git a/exercises/practice/nucleotide-count/src/lib.rs b/exercises/practice/nucleotide-count/src/lib.rs index 40ac5abe3..c91615c21 100644 --- a/exercises/practice/nucleotide-count/src/lib.rs +++ b/exercises/practice/nucleotide-count/src/lib.rs @@ -1,11 +1,9 @@ use std::collections::HashMap; pub fn count(nucleotide: char, dna: &str) -> Result { - unimplemented!( - "How much of nucleotide type '{nucleotide}' is contained inside DNA string '{dna}'?" - ); + todo!("How much of nucleotide type '{nucleotide}' is contained inside DNA string '{dna}'?"); } pub fn nucleotide_counts(dna: &str) -> Result, char> { - unimplemented!("How much of every nucleotide type is contained inside DNA string '{dna}'?"); + todo!("How much of every nucleotide type is contained inside DNA string '{dna}'?"); } diff --git a/exercises/practice/ocr-numbers/src/lib.rs b/exercises/practice/ocr-numbers/src/lib.rs index baea39693..fd08adad5 100644 --- a/exercises/practice/ocr-numbers/src/lib.rs +++ b/exercises/practice/ocr-numbers/src/lib.rs @@ -8,5 +8,5 @@ pub enum Error { } pub fn convert(input: &str) -> Result { - unimplemented!("Convert the input '{input}' to a string"); + todo!("Convert the input '{input}' to a string"); } diff --git a/exercises/practice/paasio/src/lib.rs b/exercises/practice/paasio/src/lib.rs index f530460da..06666d172 100644 --- a/exercises/practice/paasio/src/lib.rs +++ b/exercises/practice/paasio/src/lib.rs @@ -10,25 +10,25 @@ impl ReadStats { // can't be passed through format!(). For actual implementation you will likely // wish to remove the leading underscore so the variable is not ignored. pub fn new(_wrapped: R) -> ReadStats { - unimplemented!() + todo!() } pub fn get_ref(&self) -> &R { - unimplemented!() + todo!() } pub fn bytes_through(&self) -> usize { - unimplemented!() + todo!() } pub fn reads(&self) -> usize { - unimplemented!() + todo!() } } impl Read for ReadStats { fn read(&mut self, buf: &mut [u8]) -> Result { - unimplemented!("Collect statistics about this call reading {buf:?}") + todo!("Collect statistics about this call reading {buf:?}") } } @@ -39,28 +39,28 @@ impl WriteStats { // can't be passed through format!(). For actual implementation you will likely // wish to remove the leading underscore so the variable is not ignored. pub fn new(_wrapped: W) -> WriteStats { - unimplemented!() + todo!() } pub fn get_ref(&self) -> &W { - unimplemented!() + todo!() } pub fn bytes_through(&self) -> usize { - unimplemented!() + todo!() } pub fn writes(&self) -> usize { - unimplemented!() + todo!() } } impl Write for WriteStats { fn write(&mut self, buf: &[u8]) -> Result { - unimplemented!("Collect statistics about this call writing {buf:?}") + todo!("Collect statistics about this call writing {buf:?}") } fn flush(&mut self) -> Result<()> { - unimplemented!() + todo!() } } diff --git a/exercises/practice/palindrome-products/src/lib.rs b/exercises/practice/palindrome-products/src/lib.rs index c90fffdf9..15d3fb609 100644 --- a/exercises/practice/palindrome-products/src/lib.rs +++ b/exercises/practice/palindrome-products/src/lib.rs @@ -8,17 +8,17 @@ pub struct Palindrome(u64); impl Palindrome { /// Create a `Palindrome` only if `value` is in fact a palindrome when represented in base ten. Otherwise, `None`. pub fn new(value: u64) -> Option { - unimplemented!("if the value {value} is a palindrome return Some, otherwise return None"); + todo!("if the value {value} is a palindrome return Some, otherwise return None"); } /// Get the value of this palindrome. pub fn into_inner(self) -> u64 { - unimplemented!("return inner value of a Palindrome"); + todo!("return inner value of a Palindrome"); } } pub fn palindrome_products(min: u64, max: u64) -> Option<(Palindrome, Palindrome)> { - unimplemented!( + todo!( "returns the minimum and maximum number of palindromes of the products of two factors in the range {min} to {max}" ); } diff --git a/exercises/practice/pangram/src/lib.rs b/exercises/practice/pangram/src/lib.rs index f9d959d85..685588e2f 100644 --- a/exercises/practice/pangram/src/lib.rs +++ b/exercises/practice/pangram/src/lib.rs @@ -1,4 +1,4 @@ /// Determine whether a sentence is a pangram. pub fn is_pangram(sentence: &str) -> bool { - unimplemented!("Is {sentence} a pangram?"); + todo!("Is {sentence} a pangram?"); } diff --git a/exercises/practice/parallel-letter-frequency/src/lib.rs b/exercises/practice/parallel-letter-frequency/src/lib.rs index 55933fa5a..43b349c9e 100644 --- a/exercises/practice/parallel-letter-frequency/src/lib.rs +++ b/exercises/practice/parallel-letter-frequency/src/lib.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; pub fn frequency(input: &[&str], worker_count: usize) -> HashMap { - unimplemented!( + todo!( "Count the frequency of letters in the given input '{input:?}'. Ensure that you are using {} to process the input.", match worker_count { 1 => "1 worker".to_string(), diff --git a/exercises/practice/pascals-triangle/src/lib.rs b/exercises/practice/pascals-triangle/src/lib.rs index d7b800e6c..7e7bf3bbf 100644 --- a/exercises/practice/pascals-triangle/src/lib.rs +++ b/exercises/practice/pascals-triangle/src/lib.rs @@ -2,10 +2,10 @@ pub struct PascalsTriangle; impl PascalsTriangle { pub fn new(row_count: u32) -> Self { - unimplemented!("create Pascal's triangle with {row_count} rows"); + todo!("create Pascal's triangle with {row_count} rows"); } pub fn rows(&self) -> Vec> { - unimplemented!(); + todo!(); } } diff --git a/exercises/practice/perfect-numbers/src/lib.rs b/exercises/practice/perfect-numbers/src/lib.rs index 3448be300..b2257e1b0 100644 --- a/exercises/practice/perfect-numbers/src/lib.rs +++ b/exercises/practice/perfect-numbers/src/lib.rs @@ -6,5 +6,5 @@ pub enum Classification { } pub fn classify(num: u64) -> Option { - unimplemented!("classify {num}"); + todo!("classify {num}"); } diff --git a/exercises/practice/phone-number/src/lib.rs b/exercises/practice/phone-number/src/lib.rs index 59a1747c1..af53e11c8 100644 --- a/exercises/practice/phone-number/src/lib.rs +++ b/exercises/practice/phone-number/src/lib.rs @@ -1,5 +1,5 @@ pub fn number(user_number: &str) -> Option { - unimplemented!( + todo!( "Given the number entered by user '{user_number}', convert it into SMS-friendly format. If the entered number is not a valid NANP number, return None." ); } diff --git a/exercises/practice/pig-latin/src/lib.rs b/exercises/practice/pig-latin/src/lib.rs index 928972389..f395af0b8 100644 --- a/exercises/practice/pig-latin/src/lib.rs +++ b/exercises/practice/pig-latin/src/lib.rs @@ -1,5 +1,3 @@ pub fn translate(input: &str) -> String { - unimplemented!( - "Using the Pig Latin text transformation rules, convert the given input '{input}'" - ); + todo!("Using the Pig Latin text transformation rules, convert the given input '{input}'"); } diff --git a/exercises/practice/poker/src/lib.rs b/exercises/practice/poker/src/lib.rs index 802f080ef..a6f117e30 100644 --- a/exercises/practice/poker/src/lib.rs +++ b/exercises/practice/poker/src/lib.rs @@ -3,5 +3,5 @@ /// Note the type signature: this function should return _the same_ reference to /// the winning hand(s) as were passed in, not reconstructed strings which happen to be equal. pub fn winning_hands<'a>(hands: &[&'a str]) -> Vec<&'a str> { - unimplemented!("Out of {hands:?}, which hand wins?") + todo!("Out of {hands:?}, which hand wins?") } diff --git a/exercises/practice/prime-factors/src/lib.rs b/exercises/practice/prime-factors/src/lib.rs index a2ce597a3..30d06a2fc 100644 --- a/exercises/practice/prime-factors/src/lib.rs +++ b/exercises/practice/prime-factors/src/lib.rs @@ -1,3 +1,3 @@ pub fn factors(n: u64) -> Vec { - unimplemented!("This should calculate the prime factors of {n}") + todo!("This should calculate the prime factors of {n}") } diff --git a/exercises/practice/protein-translation/src/lib.rs b/exercises/practice/protein-translation/src/lib.rs index 10fac37a7..af6df1b72 100644 --- a/exercises/practice/protein-translation/src/lib.rs +++ b/exercises/practice/protein-translation/src/lib.rs @@ -6,16 +6,14 @@ pub struct CodonsInfo<'a> { impl<'a> CodonsInfo<'a> { pub fn name_for(&self, codon: &str) -> Option<&'a str> { - unimplemented!( - "Return the protein name for a '{codon}' codon or None, if codon string is invalid" - ); + todo!("Return the protein name for a '{codon}' codon or None, if codon string is invalid"); } pub fn of_rna(&self, rna: &str) -> Option> { - unimplemented!("Return a list of protein names that correspond to the '{rna}' RNA string or None if the RNA string is invalid"); + todo!("Return a list of protein names that correspond to the '{rna}' RNA string or None if the RNA string is invalid"); } } pub fn parse<'a>(pairs: Vec<(&'a str, &'a str)>) -> CodonsInfo<'a> { - unimplemented!("Construct a new CodonsInfo struct from given pairs: {pairs:?}"); + todo!("Construct a new CodonsInfo struct from given pairs: {pairs:?}"); } diff --git a/exercises/practice/proverb/src/lib.rs b/exercises/practice/proverb/src/lib.rs index ec19e2686..56705813e 100644 --- a/exercises/practice/proverb/src/lib.rs +++ b/exercises/practice/proverb/src/lib.rs @@ -1,3 +1,3 @@ pub fn build_proverb(list: &[&str]) -> String { - unimplemented!("build a proverb from this list of items: {list:?}") + todo!("build a proverb from this list of items: {list:?}") } diff --git a/exercises/practice/pythagorean-triplet/src/lib.rs b/exercises/practice/pythagorean-triplet/src/lib.rs index 340d15571..45aa4a457 100644 --- a/exercises/practice/pythagorean-triplet/src/lib.rs +++ b/exercises/practice/pythagorean-triplet/src/lib.rs @@ -1,5 +1,5 @@ use std::collections::HashSet; pub fn find(sum: u32) -> HashSet<[u32; 3]> { - unimplemented!("Given the sum {sum}, return all possible Pythagorean triplets, which produce the said sum, or an empty HashSet if there are no such triplets. Note that you are expected to return triplets in [a, b, c] order, where a < b < c"); + todo!("Given the sum {sum}, return all possible Pythagorean triplets, which produce the said sum, or an empty HashSet if there are no such triplets. Note that you are expected to return triplets in [a, b, c] order, where a < b < c"); } diff --git a/exercises/practice/queen-attack/src/lib.rs b/exercises/practice/queen-attack/src/lib.rs index b2139bff0..2c6840034 100644 --- a/exercises/practice/queen-attack/src/lib.rs +++ b/exercises/practice/queen-attack/src/lib.rs @@ -6,7 +6,7 @@ pub struct Queen; impl ChessPosition { pub fn new(rank: i32, file: i32) -> Option { - unimplemented!( + todo!( "Construct a ChessPosition struct, given the following rank, file: ({rank}, {file}). If the position is invalid return None." ); } @@ -14,10 +14,10 @@ impl ChessPosition { impl Queen { pub fn new(position: ChessPosition) -> Self { - unimplemented!("Given the chess position {position:?}, construct a Queen struct."); + todo!("Given the chess position {position:?}, construct a Queen struct."); } pub fn can_attack(&self, other: &Queen) -> bool { - unimplemented!("Determine if this Queen can attack the other Queen {other:?}"); + todo!("Determine if this Queen can attack the other Queen {other:?}"); } } diff --git a/exercises/practice/rail-fence-cipher/src/lib.rs b/exercises/practice/rail-fence-cipher/src/lib.rs index 498d827bb..4b72261ce 100644 --- a/exercises/practice/rail-fence-cipher/src/lib.rs +++ b/exercises/practice/rail-fence-cipher/src/lib.rs @@ -2,14 +2,14 @@ pub struct RailFence; impl RailFence { pub fn new(rails: u32) -> RailFence { - unimplemented!("Construct a new fence with {rails} rails") + todo!("Construct a new fence with {rails} rails") } pub fn encode(&self, text: &str) -> String { - unimplemented!("Encode this text: {text}") + todo!("Encode this text: {text}") } pub fn decode(&self, cipher: &str) -> String { - unimplemented!("Decode this ciphertext: {cipher}") + todo!("Decode this ciphertext: {cipher}") } } diff --git a/exercises/practice/raindrops/src/lib.rs b/exercises/practice/raindrops/src/lib.rs index 6bcb31cdd..c16e39c18 100644 --- a/exercises/practice/raindrops/src/lib.rs +++ b/exercises/practice/raindrops/src/lib.rs @@ -1,3 +1,3 @@ pub fn raindrops(n: u32) -> String { - unimplemented!("what sound does Raindrop #{n} make?") + todo!("what sound does Raindrop #{n} make?") } diff --git a/exercises/practice/react/src/lib.rs b/exercises/practice/react/src/lib.rs index f87356c4d..0885ae2d3 100644 --- a/exercises/practice/react/src/lib.rs +++ b/exercises/practice/react/src/lib.rs @@ -41,12 +41,12 @@ pub struct Reactor { // You are guaranteed that Reactor will only be tested against types that are Copy + PartialEq. impl Reactor { pub fn new() -> Self { - unimplemented!() + todo!() } // Creates an input cell with the specified initial value, returning its ID. pub fn create_input(&mut self, _initial: T) -> InputCellId { - unimplemented!() + todo!() } // Creates a compute cell with the specified dependencies and compute function. @@ -67,7 +67,7 @@ impl Reactor { _dependencies: &[CellId], _compute_func: F, ) -> Result { - unimplemented!() + todo!() } // Retrieves the current value of the cell, or None if the cell does not exist. @@ -78,7 +78,7 @@ impl Reactor { // It turns out this introduces a significant amount of extra complexity to this exercise. // We chose not to cover this here, since this exercise is probably enough work as-is. pub fn value(&self, id: CellId) -> Option { - unimplemented!("Get the value of the cell whose id is {id:?}") + todo!("Get the value of the cell whose id is {id:?}") } // Sets the value of the specified input cell. @@ -90,7 +90,7 @@ impl Reactor { // // As before, that turned out to add too much extra complexity. pub fn set_value(&mut self, _id: InputCellId, _new_value: T) -> bool { - unimplemented!() + todo!() } // Adds a callback to the specified compute cell. @@ -110,7 +110,7 @@ impl Reactor { _id: ComputeCellId, _callback: F, ) -> Option { - unimplemented!() + todo!() } // Removes the specified callback, using an ID returned from add_callback. @@ -123,7 +123,7 @@ impl Reactor { cell: ComputeCellId, callback: CallbackId, ) -> Result<(), RemoveCallbackError> { - unimplemented!( + todo!( "Remove the callback identified by the CallbackId {callback:?} from the cell {cell:?}" ) } diff --git a/exercises/practice/rectangles/src/lib.rs b/exercises/practice/rectangles/src/lib.rs index 3644efa33..118113a8f 100644 --- a/exercises/practice/rectangles/src/lib.rs +++ b/exercises/practice/rectangles/src/lib.rs @@ -1,3 +1,3 @@ pub fn count(lines: &[&str]) -> u32 { - unimplemented!("\nDetermine the count of rectangles in the ASCII diagram represented by the following lines:\n{lines:#?}\n."); + todo!("\nDetermine the count of rectangles in the ASCII diagram represented by the following lines:\n{lines:#?}\n."); } diff --git a/exercises/practice/reverse-string/src/lib.rs b/exercises/practice/reverse-string/src/lib.rs index bdfce8c1f..01e0d4dc8 100644 --- a/exercises/practice/reverse-string/src/lib.rs +++ b/exercises/practice/reverse-string/src/lib.rs @@ -1,3 +1,3 @@ pub fn reverse(input: &str) -> String { - unimplemented!("Write a function to reverse {input}"); + todo!("Write a function to reverse {input}"); } diff --git a/exercises/practice/rna-transcription/src/lib.rs b/exercises/practice/rna-transcription/src/lib.rs index ec9c7bfc7..9a406c460 100644 --- a/exercises/practice/rna-transcription/src/lib.rs +++ b/exercises/practice/rna-transcription/src/lib.rs @@ -6,16 +6,16 @@ pub struct Rna; impl Dna { pub fn new(dna: &str) -> Result { - unimplemented!("Construct new Dna from '{dna}' string. If string contains invalid nucleotides return index of first invalid nucleotide"); + todo!("Construct new Dna from '{dna}' string. If string contains invalid nucleotides return index of first invalid nucleotide"); } pub fn into_rna(self) -> Rna { - unimplemented!("Transform Dna {self:?} into corresponding Rna"); + todo!("Transform Dna {self:?} into corresponding Rna"); } } impl Rna { pub fn new(rna: &str) -> Result { - unimplemented!("Construct new Rna from '{rna}' string. If string contains invalid nucleotides return index of first invalid nucleotide"); + todo!("Construct new Rna from '{rna}' string. If string contains invalid nucleotides return index of first invalid nucleotide"); } } diff --git a/exercises/practice/robot-name/src/lib.rs b/exercises/practice/robot-name/src/lib.rs index a2e05a17b..37bf158ed 100644 --- a/exercises/practice/robot-name/src/lib.rs +++ b/exercises/practice/robot-name/src/lib.rs @@ -2,14 +2,14 @@ pub struct Robot; impl Robot { pub fn new() -> Self { - unimplemented!("Construct a new Robot struct."); + todo!("Construct a new Robot struct."); } pub fn name(&self) -> &str { - unimplemented!("Return the reference to the robot's name."); + todo!("Return the reference to the robot's name."); } pub fn reset_name(&mut self) { - unimplemented!("Assign a new unique name to the robot."); + todo!("Assign a new unique name to the robot."); } } diff --git a/exercises/practice/robot-simulator/src/lib.rs b/exercises/practice/robot-simulator/src/lib.rs index f9f605fbf..ff3fab8ca 100644 --- a/exercises/practice/robot-simulator/src/lib.rs +++ b/exercises/practice/robot-simulator/src/lib.rs @@ -13,34 +13,34 @@ pub struct Robot; impl Robot { pub fn new(x: i32, y: i32, d: Direction) -> Self { - unimplemented!("Create a robot at (x, y) ({x}, {y}) facing {d:?}") + todo!("Create a robot at (x, y) ({x}, {y}) facing {d:?}") } #[must_use] pub fn turn_right(self) -> Self { - unimplemented!() + todo!() } #[must_use] pub fn turn_left(self) -> Self { - unimplemented!() + todo!() } #[must_use] pub fn advance(self) -> Self { - unimplemented!() + todo!() } #[must_use] pub fn instructions(self, instructions: &str) -> Self { - unimplemented!("Follow the given sequence of instructions: {instructions}") + todo!("Follow the given sequence of instructions: {instructions}") } pub fn position(&self) -> (i32, i32) { - unimplemented!() + todo!() } pub fn direction(&self) -> &Direction { - unimplemented!() + todo!() } } diff --git a/exercises/practice/roman-numerals/src/lib.rs b/exercises/practice/roman-numerals/src/lib.rs index 5c9d1b600..97ae5cf73 100644 --- a/exercises/practice/roman-numerals/src/lib.rs +++ b/exercises/practice/roman-numerals/src/lib.rs @@ -4,12 +4,12 @@ pub struct Roman; impl Display for Roman { fn fmt(&self, _f: &mut Formatter<'_>) -> Result { - unimplemented!("Return a roman-numeral string representation of the Roman object"); + todo!("Return a roman-numeral string representation of the Roman object"); } } impl From for Roman { fn from(num: u32) -> Self { - unimplemented!("Construct a Roman object from the '{num}' number"); + todo!("Construct a Roman object from the '{num}' number"); } } diff --git a/exercises/practice/rotational-cipher/src/lib.rs b/exercises/practice/rotational-cipher/src/lib.rs index 9f59d656e..9629515d0 100644 --- a/exercises/practice/rotational-cipher/src/lib.rs +++ b/exercises/practice/rotational-cipher/src/lib.rs @@ -1,5 +1,5 @@ pub fn rotate(input: &str, key: i8) -> String { - unimplemented!( + todo!( "How would input text '{input}' transform when every letter is shifted using key '{key}'?" ); } diff --git a/exercises/practice/run-length-encoding/src/lib.rs b/exercises/practice/run-length-encoding/src/lib.rs index 1860bed48..9b830aec9 100644 --- a/exercises/practice/run-length-encoding/src/lib.rs +++ b/exercises/practice/run-length-encoding/src/lib.rs @@ -1,7 +1,7 @@ pub fn encode(source: &str) -> String { - unimplemented!("Return the run-length encoding of {source}."); + todo!("Return the run-length encoding of {source}."); } pub fn decode(source: &str) -> String { - unimplemented!("Return the run-length decoding of {source}."); + todo!("Return the run-length decoding of {source}."); } diff --git a/exercises/practice/saddle-points/src/lib.rs b/exercises/practice/saddle-points/src/lib.rs index b9126326e..5e3a95bcd 100644 --- a/exercises/practice/saddle-points/src/lib.rs +++ b/exercises/practice/saddle-points/src/lib.rs @@ -1,3 +1,3 @@ pub fn find_saddle_points(input: &[Vec]) -> Vec<(usize, usize)> { - unimplemented!("find the saddle points of the following matrix: {input:?}") + todo!("find the saddle points of the following matrix: {input:?}") } diff --git a/exercises/practice/say/src/lib.rs b/exercises/practice/say/src/lib.rs index e10f3f3f1..38ddc45ed 100644 --- a/exercises/practice/say/src/lib.rs +++ b/exercises/practice/say/src/lib.rs @@ -1,3 +1,3 @@ pub fn encode(n: u64) -> String { - unimplemented!("Say {n} in English."); + todo!("Say {n} in English."); } diff --git a/exercises/practice/scale-generator/src/lib.rs b/exercises/practice/scale-generator/src/lib.rs index 74d8a0201..3cf6503c5 100644 --- a/exercises/practice/scale-generator/src/lib.rs +++ b/exercises/practice/scale-generator/src/lib.rs @@ -16,14 +16,14 @@ pub struct Scale; impl Scale { pub fn new(tonic: &str, intervals: &str) -> Result { - unimplemented!("Construct a new scale with tonic {tonic} and intervals {intervals}") + todo!("Construct a new scale with tonic {tonic} and intervals {intervals}") } pub fn chromatic(tonic: &str) -> Result { - unimplemented!("Construct a new chromatic scale with tonic {tonic}") + todo!("Construct a new chromatic scale with tonic {tonic}") } pub fn enumerate(&self) -> Vec { - unimplemented!() + todo!() } } diff --git a/exercises/practice/scrabble-score/src/lib.rs b/exercises/practice/scrabble-score/src/lib.rs index bd9a9d35d..d875e1977 100644 --- a/exercises/practice/scrabble-score/src/lib.rs +++ b/exercises/practice/scrabble-score/src/lib.rs @@ -1,4 +1,4 @@ /// Compute the Scrabble score for a word. pub fn score(word: &str) -> u64 { - unimplemented!("Score {word} in Scrabble."); + todo!("Score {word} in Scrabble."); } diff --git a/exercises/practice/secret-handshake/src/lib.rs b/exercises/practice/secret-handshake/src/lib.rs index 1666587dd..98f52b19e 100644 --- a/exercises/practice/secret-handshake/src/lib.rs +++ b/exercises/practice/secret-handshake/src/lib.rs @@ -1,3 +1,3 @@ pub fn actions(n: u8) -> Vec<&'static str> { - unimplemented!("What is the secret handshake for {n}?") + todo!("What is the secret handshake for {n}?") } diff --git a/exercises/practice/series/src/lib.rs b/exercises/practice/series/src/lib.rs index 9337bb7af..e73729f6c 100644 --- a/exercises/practice/series/src/lib.rs +++ b/exercises/practice/series/src/lib.rs @@ -1,3 +1,3 @@ pub fn series(digits: &str, len: usize) -> Vec { - unimplemented!("What are the series of length {len} in string {digits:?}") + todo!("What are the series of length {len} in string {digits:?}") } diff --git a/exercises/practice/sieve/src/lib.rs b/exercises/practice/sieve/src/lib.rs index 7f5d35f30..23a75707c 100644 --- a/exercises/practice/sieve/src/lib.rs +++ b/exercises/practice/sieve/src/lib.rs @@ -1,3 +1,3 @@ pub fn primes_up_to(upper_bound: u64) -> Vec { - unimplemented!("Construct a vector of all primes up to {upper_bound}"); + todo!("Construct a vector of all primes up to {upper_bound}"); } diff --git a/exercises/practice/simple-cipher/src/lib.rs b/exercises/practice/simple-cipher/src/lib.rs index 847d76533..b3ab8b141 100644 --- a/exercises/practice/simple-cipher/src/lib.rs +++ b/exercises/practice/simple-cipher/src/lib.rs @@ -1,13 +1,11 @@ pub fn encode(key: &str, s: &str) -> Option { - unimplemented!("Use {key} to encode {s} using shift cipher") + todo!("Use {key} to encode {s} using shift cipher") } pub fn decode(key: &str, s: &str) -> Option { - unimplemented!("Use {key} to decode {s} using shift cipher") + todo!("Use {key} to decode {s} using shift cipher") } pub fn encode_random(s: &str) -> (String, String) { - unimplemented!( - "Generate random key with only a-z chars and encode {s}. Return tuple (key, encoded s)" - ) + todo!("Generate random key with only a-z chars and encode {s}. Return tuple (key, encoded s)") } diff --git a/exercises/practice/simple-linked-list/src/lib.rs b/exercises/practice/simple-linked-list/src/lib.rs index 8d7d5cc44..f8597f8ef 100644 --- a/exercises/practice/simple-linked-list/src/lib.rs +++ b/exercises/practice/simple-linked-list/src/lib.rs @@ -8,7 +8,7 @@ pub struct SimpleLinkedList { impl SimpleLinkedList { pub fn new() -> Self { - unimplemented!() + todo!() } // You may be wondering why it's necessary to have is_empty() @@ -17,34 +17,34 @@ impl SimpleLinkedList { // whereas is_empty() is almost always cheap. // (Also ask yourself whether len() is expensive for SimpleLinkedList) pub fn is_empty(&self) -> bool { - unimplemented!() + todo!() } pub fn len(&self) -> usize { - unimplemented!() + todo!() } pub fn push(&mut self, _element: T) { - unimplemented!() + todo!() } pub fn pop(&mut self) -> Option { - unimplemented!() + todo!() } pub fn peek(&self) -> Option<&T> { - unimplemented!() + todo!() } #[must_use] pub fn rev(self) -> SimpleLinkedList { - unimplemented!() + todo!() } } impl FromIterator for SimpleLinkedList { fn from_iter>(_iter: I) -> Self { - unimplemented!() + todo!() } } @@ -61,6 +61,6 @@ impl FromIterator for SimpleLinkedList { impl From> for Vec { fn from(mut _linked_list: SimpleLinkedList) -> Vec { - unimplemented!() + todo!() } } diff --git a/exercises/practice/space-age/src/lib.rs b/exercises/practice/space-age/src/lib.rs index 59d904771..fd180d47c 100644 --- a/exercises/practice/space-age/src/lib.rs +++ b/exercises/practice/space-age/src/lib.rs @@ -6,15 +6,13 @@ pub struct Duration; impl From for Duration { fn from(s: u64) -> Self { - unimplemented!("s, measured in seconds: {s}") + todo!("s, measured in seconds: {s}") } } pub trait Planet { fn years_during(d: &Duration) -> f64 { - unimplemented!( - "convert a duration ({d:?}) to the number of years on this planet for that duration" - ); + todo!("convert a duration ({d:?}) to the number of years on this planet for that duration"); } } diff --git a/exercises/practice/spiral-matrix/src/lib.rs b/exercises/practice/spiral-matrix/src/lib.rs index 79037e487..16d2d2ccd 100644 --- a/exercises/practice/spiral-matrix/src/lib.rs +++ b/exercises/practice/spiral-matrix/src/lib.rs @@ -1,3 +1,3 @@ pub fn spiral_matrix(size: u32) -> Vec> { - unimplemented!("Function that returns the spiral matrix of square size {size}"); + todo!("Function that returns the spiral matrix of square size {size}"); } diff --git a/exercises/practice/sublist/src/lib.rs b/exercises/practice/sublist/src/lib.rs index 95205e5aa..96b2c1a09 100644 --- a/exercises/practice/sublist/src/lib.rs +++ b/exercises/practice/sublist/src/lib.rs @@ -7,5 +7,5 @@ pub enum Comparison { } pub fn sublist(_first_list: &[T], _second_list: &[T]) -> Comparison { - unimplemented!("Determine if the first list is equal to, sublist of, superlist of or unequal to the second list."); + todo!("Determine if the first list is equal to, sublist of, superlist of or unequal to the second list."); } diff --git a/exercises/practice/sum-of-multiples/src/lib.rs b/exercises/practice/sum-of-multiples/src/lib.rs index 09a5c5be5..c5e1645e6 100644 --- a/exercises/practice/sum-of-multiples/src/lib.rs +++ b/exercises/practice/sum-of-multiples/src/lib.rs @@ -1,3 +1,3 @@ pub fn sum_of_multiples(limit: u32, factors: &[u32]) -> u32 { - unimplemented!("Sum the multiples of all of {factors:?} which are less than {limit}") + todo!("Sum the multiples of all of {factors:?} which are less than {limit}") } diff --git a/exercises/practice/tournament/src/lib.rs b/exercises/practice/tournament/src/lib.rs index 47b3d36de..780d3acf8 100644 --- a/exercises/practice/tournament/src/lib.rs +++ b/exercises/practice/tournament/src/lib.rs @@ -1,5 +1,5 @@ pub fn tally(match_results: &str) -> String { - unimplemented!( + todo!( "Given the result of the played matches '{match_results}' return a properly formatted tally table string." ); } diff --git a/exercises/practice/triangle/src/lib.rs b/exercises/practice/triangle/src/lib.rs index f7f365798..be8ce7a76 100644 --- a/exercises/practice/triangle/src/lib.rs +++ b/exercises/practice/triangle/src/lib.rs @@ -2,18 +2,18 @@ pub struct Triangle; impl Triangle { pub fn build(sides: [u64; 3]) -> Option { - unimplemented!("Construct new Triangle from following sides: {sides:?}. Return None if the sides are invalid."); + todo!("Construct new Triangle from following sides: {sides:?}. Return None if the sides are invalid."); } pub fn is_equilateral(&self) -> bool { - unimplemented!("Determine if the Triangle is equilateral."); + todo!("Determine if the Triangle is equilateral."); } pub fn is_scalene(&self) -> bool { - unimplemented!("Determine if the Triangle is scalene."); + todo!("Determine if the Triangle is scalene."); } pub fn is_isosceles(&self) -> bool { - unimplemented!("Determine if the Triangle is isosceles."); + todo!("Determine if the Triangle is isosceles."); } } diff --git a/exercises/practice/two-bucket/src/lib.rs b/exercises/practice/two-bucket/src/lib.rs index 801d4c382..ae20764d7 100644 --- a/exercises/practice/two-bucket/src/lib.rs +++ b/exercises/practice/two-bucket/src/lib.rs @@ -23,7 +23,7 @@ pub fn solve( goal: u8, start_bucket: &Bucket, ) -> Option { - unimplemented!( + todo!( "Given one bucket of capacity {capacity_1}, another of capacity {capacity_2}, starting with {start_bucket:?}, find pours to reach {goal}, or None if impossible" ); } diff --git a/exercises/practice/two-fer/src/lib.rs b/exercises/practice/two-fer/src/lib.rs index 1c8494223..f7993705b 100644 --- a/exercises/practice/two-fer/src/lib.rs +++ b/exercises/practice/two-fer/src/lib.rs @@ -1,3 +1,3 @@ pub fn twofer(name: &str) -> String { - unimplemented!("how many for {name}") + todo!("how many for {name}") } diff --git a/exercises/practice/variable-length-quantity/src/lib.rs b/exercises/practice/variable-length-quantity/src/lib.rs index f60834668..76097b80d 100644 --- a/exercises/practice/variable-length-quantity/src/lib.rs +++ b/exercises/practice/variable-length-quantity/src/lib.rs @@ -6,10 +6,10 @@ pub enum Error { /// Convert a list of numbers to a stream of bytes encoded with variable length encoding. pub fn to_bytes(values: &[u32]) -> Vec { - unimplemented!("Convert the values {values:?} to a list of bytes") + todo!("Convert the values {values:?} to a list of bytes") } /// Given a stream of bytes, extract all numbers which are encoded in there. pub fn from_bytes(bytes: &[u8]) -> Result, Error> { - unimplemented!("Convert the list of bytes {bytes:?} to a list of numbers") + todo!("Convert the list of bytes {bytes:?} to a list of numbers") } diff --git a/exercises/practice/word-count/src/lib.rs b/exercises/practice/word-count/src/lib.rs index 62d0cad60..a0e9034ef 100644 --- a/exercises/practice/word-count/src/lib.rs +++ b/exercises/practice/word-count/src/lib.rs @@ -2,5 +2,5 @@ use std::collections::HashMap; /// Count occurrences of words. pub fn word_count(words: &str) -> HashMap { - unimplemented!("Count of occurrences of words in {words:?}"); + todo!("Count of occurrences of words in {words:?}"); } diff --git a/exercises/practice/wordy/src/lib.rs b/exercises/practice/wordy/src/lib.rs index 229739568..dff74fdac 100644 --- a/exercises/practice/wordy/src/lib.rs +++ b/exercises/practice/wordy/src/lib.rs @@ -1,5 +1,3 @@ pub fn answer(command: &str) -> Option { - unimplemented!( - "Return the result of the command '{command}' or None, if the command is invalid." - ); + todo!("Return the result of the command '{command}' or None, if the command is invalid."); } diff --git a/exercises/practice/xorcism/src/lib.rs b/exercises/practice/xorcism/src/lib.rs index 59adc045a..a471a03f1 100644 --- a/exercises/practice/xorcism/src/lib.rs +++ b/exercises/practice/xorcism/src/lib.rs @@ -11,7 +11,7 @@ impl<'a> Xorcism<'a> { /// /// Should accept anything which has a cheap conversion to a byte slice. pub fn new(key: &Key) -> Xorcism<'a> { - unimplemented!() + todo!() } /// XOR each byte of the input buffer with a byte from the key. @@ -19,7 +19,7 @@ impl<'a> Xorcism<'a> { /// Note that this is stateful: repeated calls are likely to produce different results, /// even with identical inputs. pub fn munge_in_place(&mut self, data: &mut [u8]) { - unimplemented!() + todo!() } /// XOR each byte of the data with a byte from the key. @@ -30,7 +30,7 @@ impl<'a> Xorcism<'a> { /// Should accept anything which has a cheap conversion to a byte iterator. /// Shouldn't matter whether the byte iterator's values are owned or borrowed. pub fn munge(&mut self, data: Data) -> impl Iterator { - unimplemented!(); + todo!(); // this empty iterator silences a compiler complaint that // () doesn't implement ExactSizeIterator std::iter::empty() diff --git a/exercises/practice/yacht/src/lib.rs b/exercises/practice/yacht/src/lib.rs index 58a29766d..1e92e6e84 100644 --- a/exercises/practice/yacht/src/lib.rs +++ b/exercises/practice/yacht/src/lib.rs @@ -15,5 +15,5 @@ pub enum Category { type Dice = [u8; 5]; pub fn score(_dice: Dice, _category: Category) -> u8 { - unimplemented!("Solve the Yacht exercise"); + todo!("Solve the Yacht exercise"); } diff --git a/rust-tooling/src/bin/generate_exercise.rs b/rust-tooling/src/bin/generate_exercise.rs index aec5db1e1..96f85ead4 100644 --- a/rust-tooling/src/bin/generate_exercise.rs +++ b/rust-tooling/src/bin/generate_exercise.rs @@ -80,7 +80,7 @@ fn add_entry_to_track_config() -> String { .map(|path| path.file_name().unwrap().to_str().unwrap().to_string()) .collect::>(); - let unimplemented_with_spec = glob("problem-specifications/exercises/*") + let todo_with_spec = glob("problem-specifications/exercises/*") .unwrap() .filter_map(Result::ok) .map(|path| path.file_name().unwrap().to_str().unwrap().to_string()) @@ -90,7 +90,7 @@ fn add_entry_to_track_config() -> String { println!("(suggestions are from problem-specifications)"); let slug = Text::new("What's the slug of your exercise?") .with_autocomplete(move |input: &_| { - let mut slugs = unimplemented_with_spec.clone(); + let mut slugs = todo_with_spec.clone(); slugs.retain(|e| e.starts_with(input)); Ok(slugs) }) From f88803c66afb4a16f950f20c741b02f80c2bade8 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Thu, 14 Sep 2023 08:54:17 +0200 Subject: [PATCH 155/436] Enforce idiomatic error handling in bash (#1736) * Enforce idiomatic error handling in bash Single commands that may error can stil be achieved with `|| true`. With this change, we'll only see a limited number of clippy warnings in CI. I think that's fine, the one fixing it can still remove these flags locally while working on the fix to see all warnings. * Fix CI and indent bash scripts with 4 spaces --- .github/workflows/tests.yml | 4 + bin/check_exercises.sh | 81 ++++++------- bin/ensure_stubs_compile.sh | 70 +++++------ bin/format_exercises.sh | 3 +- bin/test_exercise.sh | 114 +++++++++--------- rust-tooling/tests/bash_script_conventions.rs | 59 +++++---- 6 files changed, 165 insertions(+), 166 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7efc36b87..92b6e955d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -60,6 +60,9 @@ jobs: - name: Checkout code uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 + - name: Fetch origin/main + run: git fetch --depth=1 origin main + - name: Setup toolchain uses: dtolnay/rust-toolchain@0e66bd3e6b38ec0ad5312288c83e47c143e6b09e with: @@ -130,6 +133,7 @@ jobs: uses: dtolnay/rust-toolchain@0e66bd3e6b38ec0ad5312288c83e47c143e6b09e with: toolchain: ${{ matrix.rust }} + components: clippy - name: Clippy tests env: diff --git a/bin/check_exercises.sh b/bin/check_exercises.sh index 0b69cfea7..036188f07 100755 --- a/bin/check_exercises.sh +++ b/bin/check_exercises.sh @@ -1,11 +1,5 @@ #!/usr/bin/env bash - -# In DENYWARNINGS or CLIPPY mode, do not set -e so that we run all tests. -# This allows us to see all warnings. -# If we are in neither DENYWARNINGS nor CLIPPY mode, do set -e. -if [ -z "$DENYWARNINGS" ] && [ -z "$CLIPPY" ]; then - set -e -fi +set -eo pipefail # can't benchmark with a stable compiler; to bench, use # $ BENCHMARK=1 rustup run nightly bin/check_exercises.sh @@ -18,13 +12,14 @@ fi repo=$(git rev-parse --show-toplevel) if [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then - files="$( - git diff --diff-filter=d --name-only remotes/origin/main | - grep "exercises/" | - cut -d '/' -f -3 | - sort -u | - awk -v repo="$repo" '{print repo"/"$1}' - )" + git fetch --depth=1 origin main + files="$( + git diff --diff-filter=d --name-only remotes/origin/main | + grep "exercises/" || true | + cut -d '/' -f -3 | + sort -u | + awk -v repo="$repo" '{print repo"/"$1}' + )" else files="$repo/exercises/*/*" fi @@ -33,39 +28,39 @@ return_code=0 # An exercise worth testing is defined here as any top level directory with # a 'tests' directory and a .meta/config.json. for exercise in $files; do - exercise="$exercise/$target_dir" + exercise="$exercise/$target_dir" - # This assumes that exercises are only one directory deep - # and that the primary module is named the same as the directory - directory=$(dirname "${exercise}") + # This assumes that exercises are only one directory deep + # and that the primary module is named the same as the directory + directory=$(dirname "${exercise}") - # An exercise must have a .meta/config.json - metaconf="$directory/.meta/config.json" - if [ ! -f "$metaconf" ]; then - continue - fi + # An exercise must have a .meta/config.json + metaconf="$directory/.meta/config.json" + if [ ! -f "$metaconf" ]; then + continue + fi - release="" - if [ -z "$BENCHMARK" ] && jq --exit-status '.custom?."test-in-release-mode"?' "$metaconf"; then - # release mode is enabled if not benchmarking and the appropriate key is neither `false` nor `null`. - release="--release" - fi + release="" + if [ -z "$BENCHMARK" ] && jq --exit-status '.custom?."test-in-release-mode"?' "$metaconf"; then + # release mode is enabled if not benchmarking and the appropriate key is neither `false` nor `null`. + release="--release" + fi - if [ -n "$DENYWARNINGS" ]; then - # Output a progress dot, because we may not otherwise produce output in > 10 mins. - echo -n '.' - # No-run mode so we see no test output. - # Quiet mode so we see no compile output - # (such as "Compiling"/"Downloading"). - # Compiler errors will still be shown though. - # Both flags are necessary to keep things quiet. - ./bin/test_exercise.sh "$directory" --quiet --no-run - return_code=$((return_code | $?)) - else - # Run the test and get the status - ./bin/test_exercise.sh "$directory" $release - return_code=$((return_code | $?)) - fi + if [ -n "$DENYWARNINGS" ]; then + # Output a progress dot, because we may not otherwise produce output in > 10 mins. + echo -n '.' + # No-run mode so we see no test output. + # Quiet mode so we see no compile output + # (such as "Compiling"/"Downloading"). + # Compiler errors will still be shown though. + # Both flags are necessary to keep things quiet. + ./bin/test_exercise.sh "$directory" --quiet --no-run + return_code=$((return_code | $?)) + else + # Run the test and get the status + ./bin/test_exercise.sh "$directory" $release + return_code=$((return_code | $?)) + fi done exit $return_code diff --git a/bin/ensure_stubs_compile.sh b/bin/ensure_stubs_compile.sh index 623c2d1cd..fc7cb810b 100755 --- a/bin/ensure_stubs_compile.sh +++ b/bin/ensure_stubs_compile.sh @@ -1,15 +1,17 @@ #!/usr/bin/env bash +set -eo pipefail repo="$(git rev-parse --show-toplevel)" if [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then - changed_exercises="$( - git diff --diff-filter=d --name-only remotes/origin/main | - grep "exercises/" | - cut -d '/' -f -3 | - sort -u | - awk -v repo="$repo" '{print repo"/"$1}' - )" + git fetch --depth=1 origin main + changed_exercises="$( + git diff --diff-filter=d --name-only remotes/origin/main | + grep "exercises/" || true | + cut -d '/' -f -3 | + sort -u | + awk -v repo="$repo" '{print repo"/"$1}' + )" else # we want globbing and it does not actually assign locally # shellcheck disable=SC2125 @@ -31,47 +33,47 @@ for dir in $changed_exercises; do # An exercise must have a .meta/config.json if [ ! -f "$config_file" ]; then - continue + continue fi if jq --exit-status '.custom?."allowed-to-not-compile"?' "$config_file"; then - echo "$exercise's stub is allowed to not compile" - continue + echo "$exercise's stub is allowed to not compile" + continue fi # Backup tests and stub; this script will modify them. cp -r "$dir/tests" "$dir/tests.orig" cp "$dir/src/lib.rs" "$dir/lib.rs.orig" - # In CI, we may have already compiled using the example solution. - # So we want to touch the src/lib.rs in some way, - # so that we surely recompile using the stub. - # Since we also want to deny warnings, that will also serve as the touch. - sed -i -e '1i #![deny(warnings)]' "$dir/src/lib.rs" + # In CI, we may have already compiled using the example solution. + # So we want to touch the src/lib.rs in some way, + # so that we surely recompile using the stub. + # Since we also want to deny warnings, that will also serve as the touch. + sed -i -e '1i #![deny(warnings)]' "$dir/src/lib.rs" # Deny warnings in the tests that may result from compiling the stubs. # This helps avoid, for example, an overflowing literal warning # that could be caused by a stub with a type that is too small. sed -i -e '1i #![deny(warnings)]' "$dir"/tests/*.rs - if [ -n "$CLIPPY" ]; then - echo "clippy $exercise..." - # We don't find it useful in general to have students implement Default, - # since we're generally not going to test it. - if ! ( - cd "$dir" && - cargo clippy --lib --tests --color always -- --allow clippy::new_without_default 2>clippy.log - ); then - cat "$dir/clippy.log" - broken="$broken\n$exercise" - else - # Just to show progress - echo "... OK" + if [ -n "$CLIPPY" ]; then + echo "clippy $exercise..." + # We don't find it useful in general to have students implement Default, + # since we're generally not going to test it. + if ! ( + cd "$dir" && + cargo clippy --lib --tests --color always -- --allow clippy::new_without_default 2>clippy.log + ); then + cat "$dir/clippy.log" + broken="$broken\n$exercise" + else + # Just to show progress + echo "... OK" + fi + elif ! (cd "$dir" && cargo test --quiet --no-run); then + echo "$exercise's stub does not compile; please make it compile" + broken="$broken\n$exercise" fi - elif ! (cd "$dir" && cargo test --quiet --no-run); then - echo "$exercise's stub does not compile; please make it compile" - broken="$broken\n$exercise" - fi # Restore tests and stub. mv "$dir/lib.rs.orig" "$dir/src/lib.rs" @@ -80,6 +82,6 @@ for dir in $changed_exercises; do done if [ -n "$broken" ]; then - echo "Exercises that don't compile:$broken" - exit 1 + echo "Exercises that don't compile:$broken" + exit 1 fi diff --git a/bin/format_exercises.sh b/bin/format_exercises.sh index 8d8fa2d71..e463f09be 100755 --- a/bin/format_exercises.sh +++ b/bin/format_exercises.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash +set -eo pipefail + # Format existing exercises using rustfmt -set -e repo=$(git rev-parse --show-toplevel) diff --git a/bin/test_exercise.sh b/bin/test_exercise.sh index 2e1c04b26..1bff4d672 100755 --- a/bin/test_exercise.sh +++ b/bin/test_exercise.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash - set -eo pipefail + # Test an exercise # which exercise are we testing right now? @@ -8,24 +8,24 @@ set -eo pipefail # exercise directory. Otherwise, assume we're in # it currently. if [ $# -ge 1 ]; then - exercise=$1 - # if this script is called with arguments, it will pass through - # any beyond the first to cargo. Note that we can only get a - # free default argument if no arguments at all were passed, - # so if you are in the exercise directory and want to pass any - # arguments to cargo, you need to include the local path first. - # I.e. to test in release mode: - # $ test_exercise.sh . --release - shift 1 + exercise=$1 + # if this script is called with arguments, it will pass through + # any beyond the first to cargo. Note that we can only get a + # free default argument if no arguments at all were passed, + # so if you are in the exercise directory and want to pass any + # arguments to cargo, you need to include the local path first. + # I.e. to test in release mode: + # $ test_exercise.sh . --release + shift 1 else - exercise='.' + exercise='.' fi # what cargo command will we use? if [ -n "$BENCHMARK" ]; then - command="bench" + command="bench" else - command="test" + command="test" fi declare -a preserve_files=("src/lib.rs" "Cargo.toml" "Cargo.lock") @@ -33,17 +33,17 @@ declare -a preserve_dirs=("tests") # reset instructions reset () { - for file in "${preserve_files[@]}"; do - if [ -f "$exercise/$file.orig" ]; then - mv -f "$exercise/$file.orig" "$exercise/$file" - fi - done - for dir in "${preserve_dirs[@]}"; do - if [ -d "$exercise/$dir.orig" ]; then - rm -rf "${exercise:?}/$dir" - mv "$exercise/$dir.orig" "$exercise/$dir" - fi - done + for file in "${preserve_files[@]}"; do + if [ -f "$exercise/$file.orig" ]; then + mv -f "$exercise/$file.orig" "$exercise/$file" + fi + done + for dir in "${preserve_dirs[@]}"; do + if [ -d "$exercise/$dir.orig" ]; then + rm -rf "${exercise:?}/$dir" + mv "$exercise/$dir.orig" "$exercise/$dir" + fi + done } # cause the reset to execute when the script exits normally or is killed @@ -51,62 +51,62 @@ trap reset EXIT INT TERM # preserve the files and directories we care about for file in "${preserve_files[@]}"; do - if [ -f "$exercise/$file" ]; then - cp "$exercise/$file" "$exercise/$file.orig" - fi + if [ -f "$exercise/$file" ]; then + cp "$exercise/$file" "$exercise/$file.orig" + fi done for dir in "${preserve_dirs[@]}"; do - if [ -d "$exercise/$dir" ]; then - cp -r "$exercise/$dir" "$exercise/$dir.orig" - fi + if [ -d "$exercise/$dir" ]; then + cp -r "$exercise/$dir" "$exercise/$dir.orig" + fi done # Move example files to where Cargo expects them if [ -f "$exercise/.meta/example.rs" ]; then - example="$exercise/.meta/example.rs" + example="$exercise/.meta/example.rs" elif [ -f "$exercise/.meta/exemplar.rs" ]; then - example="$exercise/.meta/exemplar.rs" + example="$exercise/.meta/exemplar.rs" else - echo "Could not locate example implementation for $exercise" - exit 1 + echo "Could not locate example implementation for $exercise" + exit 1 fi cp -f "$example" "$exercise/src/lib.rs" if [ -f "$exercise/.meta/Cargo-example.toml" ]; then - cp -f "$exercise/.meta/Cargo-example.toml" "$exercise/Cargo.toml" + cp -f "$exercise/.meta/Cargo-example.toml" "$exercise/Cargo.toml" fi # If deny warnings, insert a deny warnings compiler directive in the header if [ -n "$DENYWARNINGS" ]; then - sed -i -e '1i #![deny(warnings)]' "$exercise/src/lib.rs" + sed -i -e '1i #![deny(warnings)]' "$exercise/src/lib.rs" fi # eliminate #[ignore] lines from tests for test in "$exercise/"tests/*.rs; do - sed -i -e '/#\[ignore\]/{ - s/#\[ignore\]\s*// - /^\s*$/d - }' "$test" + sed -i -e '/#\[ignore\]/{ + s/#\[ignore\]\s*// + /^\s*$/d + }' "$test" done # run tests from within exercise directory # (use subshell so we auto-reset to current pwd after) ( - cd "$exercise" - if [ -n "$CLIPPY" ]; then - # Consider any Clippy to be an error in tests only. - # For now, not the example solutions since they have many Clippy warnings, - # and are not shown to students anyway. - sed -i -e '1i #![deny(clippy::all)]' tests/*.rs - if ! cargo clippy --tests --color always "$@" 2>clippy.log; then - cat clippy.log - exit 1 - else - # Just to show progress - echo "clippy $exercise OK" - fi - else - # this is the last command; its exit code is what's passed on - time cargo $command "$@" - fi +cd "$exercise" + if [ -n "$CLIPPY" ]; then + # Consider any Clippy to be an error in tests only. + # For now, not the example solutions since they have many Clippy warnings, + # and are not shown to students anyway. + sed -i -e '1i #![deny(clippy::all)]' tests/*.rs + if ! cargo clippy --tests --color always "$@" 2>clippy.log; then + cat clippy.log + exit 1 + else + # Just to show progress + echo "clippy $exercise OK" + fi + else + # this is the last command; its exit code is what's passed on + time cargo $command "$@" + fi ) diff --git a/rust-tooling/tests/bash_script_conventions.rs b/rust-tooling/tests/bash_script_conventions.rs index ff95feb88..db34d150e 100644 --- a/rust-tooling/tests/bash_script_conventions.rs +++ b/rust-tooling/tests/bash_script_conventions.rs @@ -5,24 +5,27 @@ use exercism_tooling::fs_utils; /// Runs a function for each bash script in the bin directory. /// The function is passed the path of the script. -fn for_all_scripts(f: fn(PathBuf)) { +fn for_all_scripts(f: fn(&str)) { fs_utils::cd_into_repo_root(); for entry in std::fs::read_dir("bin").unwrap() { - f(entry.unwrap().path()) + let path = entry.unwrap().path(); + let file_name = path.file_name().unwrap().to_str().unwrap(); + + // exceptions: + // configlet is not a bash script, but it must be in `bin`. + // fetch-configlet comes from upstream, we don't control it. + if file_name == "configlet" || file_name == "fetch-configlet" { + continue; + } + + f(file_name) } } #[test] fn test_file_extension() { - for_all_scripts(|path| { - let file_name = path.file_name().unwrap().to_str().unwrap(); - - // exceptions - if file_name == "fetch-configlet" || file_name == "configlet" { - return; - } - + for_all_scripts(|file_name| { assert!( file_name.ends_with(".sh"), "name of '{file_name}' should end with .sh" @@ -32,18 +35,8 @@ fn test_file_extension() { #[test] fn test_snake_case_name() { - for_all_scripts(|path| { - let file_name = path - .file_name() - .unwrap() - .to_str() - .unwrap() - .trim_end_matches(".sh"); - - // fetch-configlet comes from upstream, we don't control its name - if file_name == "fetch-configlet" { - return; - } + for_all_scripts(|file_name| { + let file_name = file_name.trim_end_matches(".sh"); assert!( file_name.is_case(Case::Snake), @@ -55,18 +48,22 @@ fn test_snake_case_name() { /// Notably on nixOS and macOS, bash is not installed in `/bin/bash`. #[test] fn test_portable_shebang() { - for_all_scripts(|path| { - let file_name = path.file_name().unwrap().to_str().unwrap(); - - // not a bash script, but it must be in `bin` - if file_name == "configlet" { - return; - } - - let contents = std::fs::read_to_string(&path).unwrap(); + for_all_scripts(|file_name| { + let contents = std::fs::read_to_string(PathBuf::from("bin").join(file_name)).unwrap(); assert!( contents.starts_with("#!/usr/bin/env bash"), "'{file_name}' should start with the shebang '#!/usr/bin/env bash'" ); }) } + +#[test] +fn error_handling_flags() { + for_all_scripts(|file_name| { + let contents = std::fs::read_to_string(PathBuf::from("bin").join(file_name)).unwrap(); + assert!( + contents.contains("set -eo pipefail"), + "'{file_name}' should set error handling flags 'set -eo pipefail'" + ); + }) +} From 62d74800a5280237966f2f7bfaf495e4cf72ff45 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Thu, 14 Sep 2023 12:17:06 +0200 Subject: [PATCH 156/436] Remove test_ prefix from tests (#1727) closes #1720 --- .../concept/csv-builder/tests/csv-builder.rs | 10 +- .../tests/health-statistics.rs | 10 +- .../magazine-cutout/tests/magazine-cutout.rs | 12 +- .../resistor-color/tests/resistor-color.rs | 16 +-- .../tests/role-playing-game.rs | 16 +-- .../rpn-calculator/tests/rpn-calculator.rs | 22 ++-- .../short-fibonacci/tests/short-fibonacci.rs | 8 +- .../practice/alphametics/tests/alphametics.rs | 20 ++-- exercises/practice/anagram/tests/anagram.rs | 28 ++--- .../tests/armstrong-numbers.rs | 26 ++--- .../atbash-cipher/tests/atbash-cipher.rs | 28 ++--- .../practice/beer-song/tests/beer-song.rs | 12 +- .../bob/.articles/performance/code/main.rs | 6 +- .../bob/.articles/performance/content.md | 6 +- .../bob/.articles/performance/snippet.md | 6 +- exercises/practice/bob/tests/bob.rs | 50 ++++---- .../practice/book-store/tests/book-store.rs | 28 ++--- exercises/practice/clock/tests/clock.rs | 108 +++++++++--------- .../tests/collatz-conjecture.rs | 14 +-- .../crypto-square/tests/crypto-square.rs | 14 +-- exercises/practice/decimal/.meta/example.rs | 2 +- exercises/practice/decimal/tests/decimal.rs | 86 +++++++------- exercises/practice/diamond/tests/diamond.rs | 10 +- .../tests/difference-of-squares.rs | 18 +-- .../diffie-hellman/tests/diffie-hellman.rs | 18 +-- exercises/practice/dot-dsl/tests/dot-dsl.rs | 18 +-- exercises/practice/etl/tests/etl.rs | 8 +- exercises/practice/fizzy/.meta/example.rs | 14 +-- exercises/practice/fizzy/tests/fizzy.rs | 14 +-- .../practice/gigasecond/tests/gigasecond.rs | 10 +- .../grade-school/tests/grade-school.rs | 18 +-- .../grains/.articles/performance/code/main.rs | 12 +- .../grains/.articles/performance/content.md | 12 +- .../grains/.articles/performance/snippet.md | 12 +- exercises/practice/grains/tests/grains.rs | 28 ++--- exercises/practice/grep/tests/grep.rs | 64 +++++------ exercises/practice/hamming/tests/hamming.rs | 26 ++--- .../practice/hello-world/GETTING_STARTED.md | 16 +-- .../practice/hello-world/tests/hello-world.rs | 2 +- .../practice/hexadecimal/tests/hexadecimal.rs | 20 ++-- .../practice/high-scores/tests/high-scores.rs | 22 ++-- .../isbn-verifier/tests/isbn-verifier.rs | 28 ++--- .../.articles/performance/code/main.rs | 12 +- .../isogram/.articles/performance/content.md | 12 +- .../isogram/.articles/performance/snippet.md | 12 +- .../tests/kindergarten-garden.rs | 34 +++--- exercises/practice/knapsack/tests/knapsack.rs | 14 +-- .../leap/.articles/performance/code/main.rs | 12 +- .../leap/.articles/performance/content.md | 12 +- .../leap/.articles/performance/snippet.md | 10 +- exercises/practice/leap/tests/leap.rs | 28 ++--- .../practice/luhn-from/tests/luhn-from.rs | 2 +- .../practice/luhn-trait/tests/luhn-trait.rs | 2 +- exercises/practice/luhn/tests/luhn.rs | 38 +++--- exercises/practice/macros/tests/macros.rs | 34 +++--- .../practice/nth-prime/tests/nth-prime.rs | 8 +- .../tests/nucleotide-codons.rs | 14 +-- .../tests/nucleotide-count.rs | 16 +-- exercises/practice/paasio/tests/paasio.rs | 16 +-- .../tests/palindrome-products.rs | 28 ++--- .../.articles/performance/code/main.rs | 8 +- .../pangram/.articles/performance/content.md | 8 +- .../pangram/.articles/performance/snippet.md | 8 +- .../tests/parallel-letter-frequency.rs | 20 ++-- .../phone-number/tests/phone-number.rs | 36 +++--- .../practice/pig-latin/tests/pig-latin.rs | 42 +++---- exercises/practice/poker/tests/poker.rs | 70 ++++++------ .../prime-factors/tests/prime-factors.rs | 14 +-- .../tests/protein-translation.rs | 24 ++-- exercises/practice/proverb/tests/proverb.rs | 12 +- .../tests/pythagorean-triplet.rs | 14 +-- .../tests/rail-fence-cipher.rs | 14 +-- exercises/practice/react/tests/react.rs | 2 +- .../practice/rectangles/tests/rectangles.rs | 22 ++-- .../reverse-string/tests/reverse-string.rs | 16 +-- .../tests/rna-transcription.rs | 20 ++-- .../practice/robot-name/tests/robot-name.rs | 14 +-- .../roman-numerals/tests/roman-numerals.rs | 22 ++-- .../tests/run-length-encoding.rs | 26 ++--- exercises/practice/say/tests/say.rs | 36 +++--- .../practice/scale-generator/.meta/example.rs | 6 +- .../scale-generator/tests/scale-generator.rs | 34 +++--- exercises/practice/series/tests/series.rs | 10 +- .../tests/simple-linked-list.rs | 18 +-- exercises/practice/sublist/tests/sublist.rs | 10 +- .../practice/two-bucket/tests/two-bucket.rs | 8 +- .../practice/word-count/tests/word-count.rs | 12 +- exercises/practice/yacht/tests/yacht.rs | 58 +++++----- rust-tooling/src/exercise_config.rs | 2 +- rust-tooling/src/problem_spec.rs | 2 +- rust-tooling/tests/bash_script_conventions.rs | 6 +- rust-tooling/tests/count_ignores.rs | 2 +- rust-tooling/tests/difficulties.rs | 2 +- .../tests/no_authors_in_cargo_toml.rs | 2 +- rust-tooling/tests/no_trailing_whitespace.rs | 2 +- 95 files changed, 899 insertions(+), 905 deletions(-) diff --git a/exercises/concept/csv-builder/tests/csv-builder.rs b/exercises/concept/csv-builder/tests/csv-builder.rs index ad4b4b79d..69f550cf7 100644 --- a/exercises/concept/csv-builder/tests/csv-builder.rs +++ b/exercises/concept/csv-builder/tests/csv-builder.rs @@ -1,7 +1,7 @@ use csv_builder::*; #[test] -fn test_no_escaping() { +fn no_escaping() { let mut builder = CsvRecordBuilder::new(); builder.add("ant"); @@ -15,7 +15,7 @@ fn test_no_escaping() { #[test] #[ignore] -fn test_quote() { +fn quote() { let mut builder = CsvRecordBuilder::new(); builder.add("ant"); @@ -28,7 +28,7 @@ fn test_quote() { #[test] #[ignore] -fn test_new_line() { +fn new_line() { let mut builder = CsvRecordBuilder::new(); builder.add("ant"); @@ -39,7 +39,7 @@ fn test_new_line() { } #[test] #[ignore] -fn test_comma() { +fn comma() { let mut builder = CsvRecordBuilder::new(); builder.add("ant"); @@ -51,7 +51,7 @@ fn test_comma() { #[test] #[ignore] -fn test_empty() { +fn empty() { let builder = CsvRecordBuilder::new(); let list = builder.build(); assert!(list.is_empty()); diff --git a/exercises/concept/health-statistics/tests/health-statistics.rs b/exercises/concept/health-statistics/tests/health-statistics.rs index 26ff2a79b..e933f19a4 100644 --- a/exercises/concept/health-statistics/tests/health-statistics.rs +++ b/exercises/concept/health-statistics/tests/health-statistics.rs @@ -5,28 +5,28 @@ const AGE: u32 = 89; const WEIGHT: f32 = 131.6; #[test] -fn test_name() { +fn name() { let user = User::new(NAME.into(), AGE, WEIGHT); assert_eq!(user.name(), NAME); } #[test] #[ignore] -fn test_age() { +fn age() { let user = User::new(NAME.into(), AGE, WEIGHT); assert_eq!(user.age(), AGE); } #[test] #[ignore] -fn test_weight() { +fn weight() { let user = User::new(NAME.into(), AGE, WEIGHT); assert!((user.weight() - WEIGHT).abs() < f32::EPSILON); } #[test] #[ignore] -fn test_set_age() { +fn set_age() { let new_age: u32 = 90; let mut user = User::new(NAME.into(), AGE, WEIGHT); user.set_age(new_age); @@ -35,7 +35,7 @@ fn test_set_age() { #[test] #[ignore] -fn test_set_weight() { +fn set_weight() { let new_weight: f32 = 129.4; let mut user = User::new(NAME.into(), AGE, WEIGHT); user.set_weight(new_weight); diff --git a/exercises/concept/magazine-cutout/tests/magazine-cutout.rs b/exercises/concept/magazine-cutout/tests/magazine-cutout.rs index 2062d1d92..0b73c7a02 100644 --- a/exercises/concept/magazine-cutout/tests/magazine-cutout.rs +++ b/exercises/concept/magazine-cutout/tests/magazine-cutout.rs @@ -1,7 +1,7 @@ use magazine_cutout::*; #[test] -fn test_magazine_has_fewer_words_available_than_needed() { +fn magazine_has_fewer_words_available_than_needed() { let magazine = "two times three is not four" .split_whitespace() .collect::>(); @@ -13,7 +13,7 @@ fn test_magazine_has_fewer_words_available_than_needed() { #[test] #[ignore] -fn test_fn_returns_true_for_good_input() { +fn fn_returns_true_for_good_input() { let magazine = "The metro orchestra unveiled its new grand piano today. Its donor paraphrased Nathn Hale: \"I only regret that I have but one to give \"".split_whitespace().collect::>(); let note = "give one grand today." .split_whitespace() @@ -23,7 +23,7 @@ fn test_fn_returns_true_for_good_input() { #[test] #[ignore] -fn test_fn_returns_false_for_bad_input() { +fn fn_returns_false_for_bad_input() { let magazine = "I've got a lovely bunch of coconuts." .split_whitespace() .collect::>(); @@ -35,7 +35,7 @@ fn test_fn_returns_false_for_bad_input() { #[test] #[ignore] -fn test_case_sensitivity() { +fn case_sensitivity() { let magazine = "i've got some lovely coconuts" .split_whitespace() .collect::>(); @@ -55,7 +55,7 @@ fn test_case_sensitivity() { #[test] #[ignore] -fn test_magazine_has_more_words_available_than_needed() { +fn magazine_has_more_words_available_than_needed() { let magazine = "Enough is enough when enough is enough" .split_whitespace() .collect::>(); @@ -65,7 +65,7 @@ fn test_magazine_has_more_words_available_than_needed() { #[test] #[ignore] -fn test_magazine_has_one_good_word_many_times_but_still_cant_construct() { +fn magazine_has_one_good_word_many_times_but_still_cant_construct() { let magazine = "A A A".split_whitespace().collect::>(); let note = "A nice day".split_whitespace().collect::>(); assert!(!can_construct_note(&magazine, ¬e)); diff --git a/exercises/concept/resistor-color/tests/resistor-color.rs b/exercises/concept/resistor-color/tests/resistor-color.rs index 1e6523b12..b0806797b 100644 --- a/exercises/concept/resistor-color/tests/resistor-color.rs +++ b/exercises/concept/resistor-color/tests/resistor-color.rs @@ -1,43 +1,43 @@ use resistor_color::{color_to_value, colors, value_to_color_string, ResistorColor}; #[test] -fn test_black() { +fn black() { assert_eq!(color_to_value(ResistorColor::Black), 0); } #[test] #[ignore] -fn test_orange() { +fn orange() { assert_eq!(color_to_value(ResistorColor::Orange), 3); } #[test] #[ignore] -fn test_white() { +fn white() { assert_eq!(color_to_value(ResistorColor::White), 9); } #[test] #[ignore] -fn test_2() { +fn two() { assert_eq!(value_to_color_string(2), String::from("Red")); } #[test] #[ignore] -fn test_6() { +fn six() { assert_eq!(value_to_color_string(6), String::from("Blue")); } #[test] #[ignore] -fn test_8() { +fn eight() { assert_eq!(value_to_color_string(8), String::from("Grey")); } #[test] #[ignore] -fn test_11_out_of_range() { +fn eleven_out_of_range() { assert_eq!( value_to_color_string(11), String::from("value out of range") @@ -46,7 +46,7 @@ fn test_11_out_of_range() { #[test] #[ignore] -fn test_all_colors() { +fn all_colors() { use ResistorColor::*; assert_eq!( colors(), diff --git a/exercises/concept/role-playing-game/tests/role-playing-game.rs b/exercises/concept/role-playing-game/tests/role-playing-game.rs index 499320746..ac7ad71dc 100644 --- a/exercises/concept/role-playing-game/tests/role-playing-game.rs +++ b/exercises/concept/role-playing-game/tests/role-playing-game.rs @@ -1,7 +1,7 @@ use role_playing_game::*; #[test] -fn test_reviving_dead_player() { +fn reviving_dead_player() { let dead_player = Player { health: 0, mana: Some(0), @@ -17,7 +17,7 @@ fn test_reviving_dead_player() { #[test] #[ignore] -fn test_reviving_dead_level9_player() { +fn reviving_dead_level9_player() { let dead_player = Player { health: 0, mana: None, @@ -33,7 +33,7 @@ fn test_reviving_dead_level9_player() { #[test] #[ignore] -fn test_reviving_dead_level10_player() { +fn reviving_dead_level10_player() { let dead_player = Player { health: 0, mana: Some(0), @@ -49,7 +49,7 @@ fn test_reviving_dead_level10_player() { #[test] #[ignore] -fn test_reviving_alive_player() { +fn reviving_alive_player() { let alive_player = Player { health: 1, mana: None, @@ -60,7 +60,7 @@ fn test_reviving_alive_player() { #[test] #[ignore] -fn test_cast_spell_with_enough_mana() { +fn cast_spell_with_enough_mana() { const HEALTH: u32 = 99; const MANA: u32 = 100; const LEVEL: u32 = 100; @@ -80,7 +80,7 @@ fn test_cast_spell_with_enough_mana() { #[test] #[ignore] -fn test_cast_spell_with_insufficient_mana() { +fn cast_spell_with_insufficient_mana() { let mut no_mana_wizard = Player { health: 56, mana: Some(2), @@ -99,7 +99,7 @@ fn test_cast_spell_with_insufficient_mana() { #[test] #[ignore] -fn test_cast_spell_with_no_mana_pool() { +fn cast_spell_with_no_mana_pool() { const MANA_COST: u32 = 10; let mut underleveled_player = Player { @@ -120,7 +120,7 @@ fn test_cast_spell_with_no_mana_pool() { #[test] #[ignore] -fn test_cast_large_spell_with_no_mana_pool() { +fn cast_large_spell_with_no_mana_pool() { const MANA_COST: u32 = 30; let mut underleveled_player = Player { diff --git a/exercises/concept/rpn-calculator/tests/rpn-calculator.rs b/exercises/concept/rpn-calculator/tests/rpn-calculator.rs index eba9c5f96..e33d058be 100644 --- a/exercises/concept/rpn-calculator/tests/rpn-calculator.rs +++ b/exercises/concept/rpn-calculator/tests/rpn-calculator.rs @@ -13,77 +13,77 @@ fn calculator_input(s: &str) -> Vec { } #[test] -fn test_empty_input_returns_none() { +fn empty_input_returns_none() { let input = calculator_input(""); assert_eq!(evaluate(&input), None); } #[test] #[ignore] -fn test_simple_value() { +fn simple_value() { let input = calculator_input("10"); assert_eq!(evaluate(&input), Some(10)); } #[test] #[ignore] -fn test_simple_addition() { +fn simple_addition() { let input = calculator_input("2 2 +"); assert_eq!(evaluate(&input), Some(4)); } #[test] #[ignore] -fn test_simple_subtraction() { +fn simple_subtraction() { let input = calculator_input("7 11 -"); assert_eq!(evaluate(&input), Some(-4)); } #[test] #[ignore] -fn test_simple_multiplication() { +fn simple_multiplication() { let input = calculator_input("6 9 *"); assert_eq!(evaluate(&input), Some(54)); } #[test] #[ignore] -fn test_simple_division() { +fn simple_division() { let input = calculator_input("57 19 /"); assert_eq!(evaluate(&input), Some(3)); } #[test] #[ignore] -fn test_complex_operation() { +fn complex_operation() { let input = calculator_input("4 8 + 7 5 - /"); assert_eq!(evaluate(&input), Some(6)); } #[test] #[ignore] -fn test_too_few_operands_returns_none() { +fn too_few_operands_returns_none() { let input = calculator_input("2 +"); assert_eq!(evaluate(&input), None); } #[test] #[ignore] -fn test_too_many_operands_returns_none() { +fn too_many_operands_returns_none() { let input = calculator_input("2 2"); assert_eq!(evaluate(&input), None); } #[test] #[ignore] -fn test_zero_operands_returns_none() { +fn zero_operands_returns_none() { let input = calculator_input("+"); assert_eq!(evaluate(&input), None); } #[test] #[ignore] -fn test_intermediate_error_returns_none() { +fn intermediate_error_returns_none() { let input = calculator_input("+ 2 2 *"); assert_eq!(evaluate(&input), None); } diff --git a/exercises/concept/short-fibonacci/tests/short-fibonacci.rs b/exercises/concept/short-fibonacci/tests/short-fibonacci.rs index db6b2eb85..a0fb1a84d 100644 --- a/exercises/concept/short-fibonacci/tests/short-fibonacci.rs +++ b/exercises/concept/short-fibonacci/tests/short-fibonacci.rs @@ -1,21 +1,23 @@ use short_fibonacci::*; #[test] -fn test_empty() { +fn empty() { assert_eq!(create_empty(), Vec::new()); } + #[test] #[ignore] -fn test_buffer() { +fn buffer() { for n in 0..10 { let zeroized = create_buffer(n); assert_eq!(zeroized.len(), n); assert!(zeroized.iter().all(|&v| v == 0)); } } + #[test] #[ignore] -fn test_fibonacci() { +fn first_five_elements() { let fibb = fibonacci(); assert_eq!(fibb.len(), 5); assert_eq!(fibb[0], 1); diff --git a/exercises/practice/alphametics/tests/alphametics.rs b/exercises/practice/alphametics/tests/alphametics.rs index 8f9859185..2e07d87e3 100644 --- a/exercises/practice/alphametics/tests/alphametics.rs +++ b/exercises/practice/alphametics/tests/alphametics.rs @@ -7,27 +7,27 @@ fn assert_alphametic_solution_eq(puzzle: &str, solution: &[(char, u8)]) { } #[test] -fn test_with_three_letters() { +fn with_three_letters() { assert_alphametic_solution_eq("I + BB == ILL", &[('I', 1), ('B', 9), ('L', 0)]); } #[test] #[ignore] -fn test_must_have_unique_value_for_each_letter() { +fn must_have_unique_value_for_each_letter() { let answer = alphametics::solve("A == B"); assert_eq!(answer, None); } #[test] #[ignore] -fn test_leading_zero_solution_is_invalid() { +fn leading_zero_solution_is_invalid() { let answer = alphametics::solve("ACA + DD == BD"); assert_eq!(answer, None); } #[test] #[ignore] -fn test_sum_must_be_wide_enough() { +fn sum_must_be_wide_enough() { let answer = alphametics::solve("ABC + DEF == GH"); assert_eq!(answer, None); } @@ -43,13 +43,13 @@ fn puzzle_with_two_digits_final_carry() { #[test] #[ignore] -fn test_puzzle_with_four_letters() { +fn puzzle_with_four_letters() { assert_alphametic_solution_eq("AS + A == MOM", &[('A', 9), ('S', 2), ('M', 1), ('O', 0)]); } #[test] #[ignore] -fn test_puzzle_with_six_letters() { +fn puzzle_with_six_letters() { assert_alphametic_solution_eq( "NO + NO + TOO == LATE", &[('N', 7), ('O', 4), ('T', 9), ('L', 1), ('A', 0), ('E', 2)], @@ -58,7 +58,7 @@ fn test_puzzle_with_six_letters() { #[test] #[ignore] -fn test_puzzle_with_seven_letters() { +fn puzzle_with_seven_letters() { assert_alphametic_solution_eq( "HE + SEES + THE == LIGHT", &[ @@ -75,7 +75,7 @@ fn test_puzzle_with_seven_letters() { #[test] #[ignore] -fn test_puzzle_with_eight_letters() { +fn puzzle_with_eight_letters() { assert_alphametic_solution_eq( "SEND + MORE == MONEY", &[ @@ -93,7 +93,7 @@ fn test_puzzle_with_eight_letters() { #[test] #[ignore] -fn test_puzzle_with_ten_letters() { +fn puzzle_with_ten_letters() { assert_alphametic_solution_eq( "AND + A + STRONG + OFFENSE + AS + A + GOOD == DEFENSE", &[ @@ -113,7 +113,7 @@ fn test_puzzle_with_ten_letters() { #[test] #[ignore] -fn test_puzzle_with_ten_letters_and_199_addends() { +fn puzzle_with_ten_letters_and_199_addends() { assert_alphametic_solution_eq( "THIS + A + FIRE + THEREFORE + FOR + ALL + HISTORIES + I + TELL + A + TALE + THAT + FALSIFIES + ITS + TITLE + TIS + A + LIE + THE + TALE + OF + THE + LAST + FIRE + HORSES + LATE + AFTER + THE + FIRST + FATHERS + FORESEE + THE + HORRORS + THE + LAST + FREE + TROLL + TERRIFIES + THE + HORSES + OF + FIRE + THE + TROLL + RESTS + AT + THE + HOLE + OF + LOSSES + IT + IS + THERE + THAT + SHE + STORES + ROLES + OF + LEATHERS + AFTER + SHE + SATISFIES + HER + HATE + OFF + THOSE + FEARS + A + TASTE + RISES + AS + SHE + HEARS + THE + LEAST + FAR + HORSE + THOSE + FAST + HORSES + THAT + FIRST + HEAR + THE + TROLL + FLEE + OFF + TO + THE + FOREST + THE + HORSES + THAT + ALERTS + RAISE + THE + STARES + OF + THE + OTHERS + AS + THE + TROLL + ASSAILS + AT + THE + TOTAL + SHIFT + HER + TEETH + TEAR + HOOF + OFF + TORSO + AS + THE + LAST + HORSE + FORFEITS + ITS + LIFE + THE + FIRST + FATHERS + HEAR + OF + THE + HORRORS + THEIR + FEARS + THAT + THE + FIRES + FOR + THEIR + FEASTS + ARREST + AS + THE + FIRST + FATHERS + RESETTLE + THE + LAST + OF + THE + FIRE + HORSES + THE + LAST + TROLL + HARASSES + THE + FOREST + HEART + FREE + AT + LAST + OF + THE + LAST + TROLL + ALL + OFFER + THEIR + FIRE + HEAT + TO + THE + ASSISTERS + FAR + OFF + THE + TROLL + FASTS + ITS + LIFE + SHORTER + AS + STARS + RISE + THE + HORSES + REST + SAFE + AFTER + ALL + SHARE + HOT + FISH + AS + THEIR + AFFILIATES + TAILOR + A + ROOFS + FOR + THEIR + SAFE == FORTRESSES", &[ diff --git a/exercises/practice/anagram/tests/anagram.rs b/exercises/practice/anagram/tests/anagram.rs index 6fbb00933..4ac7a283c 100644 --- a/exercises/practice/anagram/tests/anagram.rs +++ b/exercises/practice/anagram/tests/anagram.rs @@ -9,7 +9,7 @@ fn process_anagram_case(word: &str, inputs: &[&str], expected: &[&str]) { } #[test] -fn test_no_matches() { +fn no_matches() { let word = "diaper"; let inputs = ["hello", "world", "zombies", "pants"]; @@ -21,7 +21,7 @@ fn test_no_matches() { #[test] #[ignore] -fn test_detect_simple_anagram() { +fn detect_simple_anagram() { let word = "ant"; let inputs = ["tan", "stand", "at"]; @@ -33,7 +33,7 @@ fn test_detect_simple_anagram() { #[test] #[ignore] -fn test_does_not_confuse_different_duplicates() { +fn does_not_confuse_different_duplicates() { let word = "galea"; let inputs = ["eagle"]; @@ -45,7 +45,7 @@ fn test_does_not_confuse_different_duplicates() { #[test] #[ignore] -fn test_eliminate_anagram_subsets() { +fn eliminate_anagram_subsets() { let word = "good"; let inputs = ["dog", "goody"]; @@ -57,7 +57,7 @@ fn test_eliminate_anagram_subsets() { #[test] #[ignore] -fn test_detect_anagram() { +fn detect_anagram() { let word = "listen"; let inputs = ["enlists", "google", "inlets", "banana"]; @@ -69,7 +69,7 @@ fn test_detect_anagram() { #[test] #[ignore] -fn test_multiple_anagrams() { +fn multiple_anagrams() { let word = "allergy"; let inputs = [ @@ -88,7 +88,7 @@ fn test_multiple_anagrams() { #[test] #[ignore] -fn test_case_insensitive_anagrams() { +fn case_insensitive_anagrams() { let word = "Orchestra"; let inputs = ["cashregister", "Carthorse", "radishes"]; @@ -100,7 +100,7 @@ fn test_case_insensitive_anagrams() { #[test] #[ignore] -fn test_unicode_anagrams() { +fn unicode_anagrams() { let word = "ΑΒΓ"; // These words don't make sense, they're just greek letters cobbled together. @@ -113,7 +113,7 @@ fn test_unicode_anagrams() { #[test] #[ignore] -fn test_misleading_unicode_anagrams() { +fn misleading_unicode_anagrams() { // Despite what a human might think these words contain different letters, the input uses Greek // A and B while the list of potential anagrams uses Latin A and B. let word = "ΑΒΓ"; @@ -127,7 +127,7 @@ fn test_misleading_unicode_anagrams() { #[test] #[ignore] -fn test_does_not_detect_a_word_as_its_own_anagram() { +fn does_not_detect_a_word_as_its_own_anagram() { let word = "banana"; let inputs = ["banana"]; @@ -139,7 +139,7 @@ fn test_does_not_detect_a_word_as_its_own_anagram() { #[test] #[ignore] -fn test_does_not_detect_a_differently_cased_word_as_its_own_anagram() { +fn does_not_detect_a_differently_cased_word_as_its_own_anagram() { let word = "banana"; let inputs = ["bAnana"]; @@ -151,7 +151,7 @@ fn test_does_not_detect_a_differently_cased_word_as_its_own_anagram() { #[test] #[ignore] -fn test_does_not_detect_a_differently_cased_unicode_word_as_its_own_anagram() { +fn does_not_detect_a_differently_cased_unicode_word_as_its_own_anagram() { let word = "ΑΒΓ"; let inputs = ["ΑΒγ"]; @@ -163,7 +163,7 @@ fn test_does_not_detect_a_differently_cased_unicode_word_as_its_own_anagram() { #[test] #[ignore] -fn test_same_bytes_different_chars() { +fn same_bytes_different_chars() { let word = "a⬂"; // 61 E2 AC 82 let inputs = ["€a"]; // E2 82 AC 61 @@ -175,7 +175,7 @@ fn test_same_bytes_different_chars() { #[test] #[ignore] -fn test_different_words_but_same_ascii_sum() { +fn different_words_but_same_ascii_sum() { let word = "bc"; let inputs = ["ad"]; diff --git a/exercises/practice/armstrong-numbers/tests/armstrong-numbers.rs b/exercises/practice/armstrong-numbers/tests/armstrong-numbers.rs index 8e5713b05..055ebe758 100644 --- a/exercises/practice/armstrong-numbers/tests/armstrong-numbers.rs +++ b/exercises/practice/armstrong-numbers/tests/armstrong-numbers.rs @@ -1,73 +1,73 @@ use armstrong_numbers::*; #[test] -fn test_zero_is_an_armstrong_number() { +fn zero_is_an_armstrong_number() { assert!(is_armstrong_number(0)) } #[test] #[ignore] -fn test_single_digit_numbers_are_armstrong_numbers() { +fn single_digit_numbers_are_armstrong_numbers() { assert!(is_armstrong_number(5)) } #[test] #[ignore] -fn test_there_are_no_2_digit_armstrong_numbers() { +fn there_are_no_2_digit_armstrong_numbers() { assert!(!is_armstrong_number(10)) } #[test] #[ignore] -fn test_three_digit_armstrong_number() { +fn three_digit_armstrong_number() { assert!(is_armstrong_number(153)) } #[test] #[ignore] -fn test_three_digit_non_armstrong_number() { +fn three_digit_non_armstrong_number() { assert!(!is_armstrong_number(100)) } #[test] #[ignore] -fn test_four_digit_armstrong_number() { +fn four_digit_armstrong_number() { assert!(is_armstrong_number(9474)) } #[test] #[ignore] -fn test_four_digit_non_armstrong_number() { +fn four_digit_non_armstrong_number() { assert!(!is_armstrong_number(9475)) } #[test] #[ignore] -fn test_seven_digit_armstrong_number() { +fn seven_digit_armstrong_number() { assert!(is_armstrong_number(9_926_315)) } #[test] #[ignore] -fn test_seven_digit_non_armstrong_number() { +fn seven_digit_non_armstrong_number() { assert!(!is_armstrong_number(9_926_316)) } #[test] #[ignore] -fn test_nine_digit_armstrong_number() { +fn nine_digit_armstrong_number() { assert!(is_armstrong_number(912_985_153)); } #[test] #[ignore] -fn test_nine_digit_non_armstrong_number() { +fn nine_digit_non_armstrong_number() { assert!(!is_armstrong_number(999_999_999)); } #[test] #[ignore] -fn test_ten_digit_non_armstrong_number() { +fn ten_digit_non_armstrong_number() { assert!(!is_armstrong_number(3_999_999_999)); } @@ -76,6 +76,6 @@ fn test_ten_digit_non_armstrong_number() { // incorrectly using wrapping arithmetic. #[test] #[ignore] -fn test_properly_handles_overflow() { +fn properly_handles_overflow() { assert!(!is_armstrong_number(4_106_098_957)); } diff --git a/exercises/practice/atbash-cipher/tests/atbash-cipher.rs b/exercises/practice/atbash-cipher/tests/atbash-cipher.rs index 689426f4f..214af9fe3 100644 --- a/exercises/practice/atbash-cipher/tests/atbash-cipher.rs +++ b/exercises/practice/atbash-cipher/tests/atbash-cipher.rs @@ -1,37 +1,37 @@ use atbash_cipher as cipher; #[test] -fn test_encode_yes() { +fn encode_yes() { assert_eq!(cipher::encode("yes"), "bvh"); } #[test] #[ignore] -fn test_encode_no() { +fn encode_no() { assert_eq!(cipher::encode("no"), "ml"); } #[test] #[ignore] -fn test_encode_omg() { +fn encode_omg() { assert_eq!(cipher::encode("OMG"), "lnt"); } #[test] #[ignore] -fn test_encode_spaces() { +fn encode_spaces() { assert_eq!(cipher::encode("O M G"), "lnt"); } #[test] #[ignore] -fn test_encode_mindblowingly() { +fn encode_mindblowingly() { assert_eq!(cipher::encode("mindblowingly"), "nrmwy oldrm tob"); } #[test] #[ignore] -fn test_encode_numbers() { +fn encode_numbers() { assert_eq!( cipher::encode("Testing,1 2 3, testing."), "gvhgr mt123 gvhgr mt" @@ -40,13 +40,13 @@ fn test_encode_numbers() { #[test] #[ignore] -fn test_encode_deep_thought() { +fn encode_deep_thought() { assert_eq!(cipher::encode("Truth is fiction."), "gifgs rhurx grlm"); } #[test] #[ignore] -fn test_encode_all_the_letters() { +fn encode_all_the_letters() { assert_eq!( cipher::encode("The quick brown fox jumps over the lazy dog."), "gsvjf rxpyi ldmul cqfnk hlevi gsvoz abwlt" @@ -55,13 +55,13 @@ fn test_encode_all_the_letters() { #[test] #[ignore] -fn test_decode_exercism() { +fn decode_exercism() { assert_eq!(cipher::decode("vcvix rhn"), "exercism"); } #[test] #[ignore] -fn test_decode_a_sentence() { +fn decode_a_sentence() { assert_eq!( cipher::decode("zmlyh gzxov rhlug vmzhg vkkrm thglm v"), "anobstacleisoftenasteppingstone" @@ -70,13 +70,13 @@ fn test_decode_a_sentence() { #[test] #[ignore] -fn test_decode_numbers() { +fn decode_numbers() { assert_eq!(cipher::decode("gvhgr mt123 gvhgr mt"), "testing123testing"); } #[test] #[ignore] -fn test_decode_all_the_letters() { +fn decode_all_the_letters() { assert_eq!( cipher::decode("gsvjf rxpyi ldmul cqfnk hlevi gsvoz abwlt"), "thequickbrownfoxjumpsoverthelazydog" @@ -85,13 +85,13 @@ fn test_decode_all_the_letters() { #[test] #[ignore] -fn test_decode_with_too_many_spaces() { +fn decode_with_too_many_spaces() { assert_eq!(cipher::decode("vc vix r hn"), "exercism"); } #[test] #[ignore] -fn test_decode_with_no_spaces() { +fn decode_with_no_spaces() { assert_eq!( cipher::decode("zmlyhgzxovrhlugvmzhgvkkrmthglmv"), "anobstacleisoftenasteppingstone", diff --git a/exercises/practice/beer-song/tests/beer-song.rs b/exercises/practice/beer-song/tests/beer-song.rs index d5daaf363..64fd99df7 100644 --- a/exercises/practice/beer-song/tests/beer-song.rs +++ b/exercises/practice/beer-song/tests/beer-song.rs @@ -1,36 +1,36 @@ use beer_song as beer; #[test] -fn test_verse_0() { +fn verse_0() { assert_eq!(beer::verse(0), "No more bottles of beer on the wall, no more bottles of beer.\nGo to the store and buy some more, 99 bottles of beer on the wall.\n"); } #[test] #[ignore] -fn test_verse_1() { +fn verse_1() { assert_eq!(beer::verse(1), "1 bottle of beer on the wall, 1 bottle of beer.\nTake it down and pass it around, no more bottles of beer on the wall.\n"); } #[test] #[ignore] -fn test_verse_2() { +fn verse_2() { assert_eq!(beer::verse(2), "2 bottles of beer on the wall, 2 bottles of beer.\nTake one down and pass it around, 1 bottle of beer on the wall.\n"); } #[test] #[ignore] -fn test_verse_8() { +fn verse_8() { assert_eq!(beer::verse(8), "8 bottles of beer on the wall, 8 bottles of beer.\nTake one down and pass it around, 7 bottles of beer on the wall.\n"); } #[test] #[ignore] -fn test_song_8_6() { +fn song_8_6() { assert_eq!(beer::sing(8, 6), "8 bottles of beer on the wall, 8 bottles of beer.\nTake one down and pass it around, 7 bottles of beer on the wall.\n\n7 bottles of beer on the wall, 7 bottles of beer.\nTake one down and pass it around, 6 bottles of beer on the wall.\n\n6 bottles of beer on the wall, 6 bottles of beer.\nTake one down and pass it around, 5 bottles of beer on the wall.\n"); } #[test] #[ignore] -fn test_song_3_0() { +fn song_3_0() { assert_eq!(beer::sing(3, 0), "3 bottles of beer on the wall, 3 bottles of beer.\nTake one down and pass it around, 2 bottles of beer on the wall.\n\n2 bottles of beer on the wall, 2 bottles of beer.\nTake one down and pass it around, 1 bottle of beer on the wall.\n\n1 bottle of beer on the wall, 1 bottle of beer.\nTake it down and pass it around, no more bottles of beer on the wall.\n\nNo more bottles of beer on the wall, no more bottles of beer.\nGo to the store and buy some more, 99 bottles of beer on the wall.\n"); } diff --git a/exercises/practice/bob/.articles/performance/code/main.rs b/exercises/practice/bob/.articles/performance/code/main.rs index 1eb8d37bd..ffff8656c 100644 --- a/exercises/practice/bob/.articles/performance/code/main.rs +++ b/exercises/practice/bob/.articles/performance/code/main.rs @@ -73,18 +73,18 @@ pub fn reply_array(msg: &str) -> &str { #[bench] /// multiple line question for match -fn test_multiple_line_question_match(b: &mut Bencher) { +fn multiple_line_question_match(b: &mut Bencher) { b.iter(|| reply_match("\rDoes this cryogenic chamber make me look fat?\rNo.")); } #[bench] /// multiple line question for if statements -fn test_multiple_line_question_if(b: &mut Bencher) { +fn multiple_line_question_if(b: &mut Bencher) { b.iter(|| reply_if_chain("\rDoes this cryogenic chamber make me look fat?\rNo.")); } #[bench] /// multiple line question for answer array -fn test_multiple_line_question_array(b: &mut Bencher) { +fn multiple_line_question_array(b: &mut Bencher) { b.iter(|| reply_array("\rDoes this cryogenic chamber make me look fat?\rNo.")); } diff --git a/exercises/practice/bob/.articles/performance/content.md b/exercises/practice/bob/.articles/performance/content.md index ec3bbc225..d24be3298 100644 --- a/exercises/practice/bob/.articles/performance/content.md +++ b/exercises/practice/bob/.articles/performance/content.md @@ -14,9 +14,9 @@ For our performance investigation, we'll also include a third approach that [use To benchmark the approaches, we wrote a [small benchmark application][benchmark-application]. ``` -test test_multiple_line_question_match ... bench: 96 ns/iter (+/- 17) -test test_multiple_line_question_if ... bench: 97 ns/iter (+/- 12) -test test_multiple_line_question_array ... bench: 100 ns/iter (+/- 2) +test multiple_line_question_match ... bench: 96 ns/iter (+/- 17) +test multiple_line_question_if ... bench: 97 ns/iter (+/- 12) +test multiple_line_question_array ... bench: 100 ns/iter (+/- 2) ``` All three approaches are close in performance, but the `if` statements and `match` approaches may be considered to be more idiomatic. diff --git a/exercises/practice/bob/.articles/performance/snippet.md b/exercises/practice/bob/.articles/performance/snippet.md index afaf94aa5..f6e718a27 100644 --- a/exercises/practice/bob/.articles/performance/snippet.md +++ b/exercises/practice/bob/.articles/performance/snippet.md @@ -1,5 +1,5 @@ ``` -test test_multiple_line_question_match ... bench: 96 ns/iter (+/- 17) -test test_multiple_line_question_if ... bench: 97 ns/iter (+/- 12) -test test_multiple_line_question_array ... bench: 100 ns/iter (+/- 2) +test multiple_line_question_match ... bench: 96 ns/iter (+/- 17) +test multiple_line_question_if ... bench: 97 ns/iter (+/- 12) +test multiple_line_question_array ... bench: 100 ns/iter (+/- 2) ``` diff --git a/exercises/practice/bob/tests/bob.rs b/exercises/practice/bob/tests/bob.rs index 330c7ff2b..fdee5588f 100644 --- a/exercises/practice/bob/tests/bob.rs +++ b/exercises/practice/bob/tests/bob.rs @@ -4,35 +4,35 @@ fn process_response_case(phrase: &str, expected_response: &str) { #[test] /// stating something -fn test_stating_something() { +fn stating_something() { process_response_case("Tom-ay-to, tom-aaaah-to.", "Whatever."); } #[test] #[ignore] /// ending with whitespace -fn test_ending_with_whitespace() { +fn ending_with_whitespace() { process_response_case("Okay if like my spacebar quite a bit? ", "Sure."); } #[test] #[ignore] /// shouting numbers -fn test_shouting_numbers() { +fn shouting_numbers() { process_response_case("1, 2, 3 GO!", "Whoa, chill out!"); } #[test] #[ignore] /// other whitespace -fn test_other_whitespace() { +fn other_whitespace() { process_response_case("\r\r ", "Fine. Be that way!"); } #[test] #[ignore] /// shouting with special characters -fn test_shouting_with_special_characters() { +fn shouting_with_special_characters() { process_response_case( "ZOMG THE %^*@#$(*^ ZOMBIES ARE COMING!!11!!1!", "Whoa, chill out!", @@ -42,56 +42,56 @@ fn test_shouting_with_special_characters() { #[test] #[ignore] /// talking forcefully -fn test_talking_forcefully() { +fn talking_forcefully() { process_response_case("Hi there!", "Whatever."); } #[test] #[ignore] /// prattling on -fn test_prattling_on() { +fn prattling_on() { process_response_case("Wait! Hang on. Are you going to be OK?", "Sure."); } #[test] #[ignore] /// forceful question -fn test_forceful_question() { +fn forceful_question() { process_response_case("WHAT'S GOING ON?", "Calm down, I know what I'm doing!"); } #[test] #[ignore] /// shouting with no exclamation mark -fn test_shouting_with_no_exclamation_mark() { +fn shouting_with_no_exclamation_mark() { process_response_case("I HATE THE DENTIST", "Whoa, chill out!"); } #[test] #[ignore] /// asking gibberish -fn test_asking_gibberish() { +fn asking_gibberish() { process_response_case("fffbbcbeab?", "Sure."); } #[test] #[ignore] /// question with no letters -fn test_question_with_no_letters() { +fn question_with_no_letters() { process_response_case("4?", "Sure."); } #[test] #[ignore] /// no letters -fn test_no_letters() { +fn no_letters() { process_response_case("1, 2, 3", "Whatever."); } #[test] #[ignore] /// statement containing question mark -fn test_statement_containing_question_mark() { +fn statement_containing_question_mark() { process_response_case("Ending with ? means a question.", "Whatever."); } @@ -99,7 +99,7 @@ fn test_statement_containing_question_mark() { #[test] #[ignore] /// multiple line question -fn test_multiple_line_question() { +fn multiple_line_question() { process_response_case( "\rDoes this cryogenic chamber make me look fat?\rNo.", "Whatever.", @@ -109,7 +109,7 @@ fn test_multiple_line_question() { #[test] #[ignore] /// non-question ending with whitespace -fn test_nonquestion_ending_with_whitespace() { +fn nonquestion_ending_with_whitespace() { process_response_case( "This is a statement ending with whitespace ", "Whatever.", @@ -119,56 +119,56 @@ fn test_nonquestion_ending_with_whitespace() { #[test] #[ignore] /// shouting -fn test_shouting() { +fn shouting() { process_response_case("WATCH OUT!", "Whoa, chill out!"); } #[test] #[ignore] /// non-letters with question -fn test_nonletters_with_question() { +fn nonletters_with_question() { process_response_case(":) ?", "Sure."); } #[test] #[ignore] /// shouting gibberish -fn test_shouting_gibberish() { +fn shouting_gibberish() { process_response_case("FCECDFCAAB", "Whoa, chill out!"); } #[test] #[ignore] /// asking a question -fn test_asking_a_question() { +fn asking_a_question() { process_response_case("Does this cryogenic chamber make me look fat?", "Sure."); } #[test] #[ignore] /// asking a numeric question -fn test_asking_a_numeric_question() { +fn asking_a_numeric_question() { process_response_case("You are, what, like 15?", "Sure."); } #[test] #[ignore] /// silence -fn test_silence() { +fn silence() { process_response_case("", "Fine. Be that way!"); } #[test] #[ignore] /// starting with whitespace -fn test_starting_with_whitespace() { +fn starting_with_whitespace() { process_response_case(" hmmmmmmm...", "Whatever."); } #[test] #[ignore] /// using acronyms in regular speech -fn test_using_acronyms_in_regular_speech() { +fn using_acronyms_in_regular_speech() { process_response_case( "It's OK if you don't want to go work for NASA.", "Whatever.", @@ -178,13 +178,13 @@ fn test_using_acronyms_in_regular_speech() { #[test] #[ignore] /// alternate silence -fn test_alternate_silence() { +fn alternate_silence() { process_response_case(" ", "Fine. Be that way!"); } #[test] #[ignore] /// prolonged silence -fn test_prolonged_silence() { +fn prolonged_silence() { process_response_case(" ", "Fine. Be that way!"); } diff --git a/exercises/practice/book-store/tests/book-store.rs b/exercises/practice/book-store/tests/book-store.rs index e959453b5..5dbee56a1 100644 --- a/exercises/practice/book-store/tests/book-store.rs +++ b/exercises/practice/book-store/tests/book-store.rs @@ -24,56 +24,56 @@ fn process_total_case(input: (Vec, Vec>), expected: u32) { #[test] /// Only a single book -fn test_only_a_single_book() { +fn only_a_single_book() { process_total_case((vec![1], vec![vec![1]]), 800); } #[test] #[ignore] /// Two of the same book -fn test_two_of_the_same_book() { +fn two_of_the_same_book() { process_total_case((vec![2, 2], vec![vec![2], vec![2]]), 1_600); } #[test] #[ignore] /// Empty basket -fn test_empty_basket() { +fn empty_basket() { process_total_case((vec![], vec![]), 0); } #[test] #[ignore] /// Two different books -fn test_two_different_books() { +fn two_different_books() { process_total_case((vec![1, 2], vec![vec![1, 2]]), 1_520); } #[test] #[ignore] /// Three different books -fn test_three_different_books() { +fn three_different_books() { process_total_case((vec![1, 2, 3], vec![vec![1, 2, 3]]), 2_160); } #[test] #[ignore] /// Four different books -fn test_four_different_books() { +fn four_different_books() { process_total_case((vec![1, 2, 3, 4], vec![vec![1, 2, 3, 4]]), 2_560); } #[test] #[ignore] /// Five different books -fn test_five_different_books() { +fn five_different_books() { process_total_case((vec![1, 2, 3, 4, 5], vec![vec![1, 2, 3, 4, 5]]), 3_000); } #[test] #[ignore] /// Two groups of four is cheaper than group of five plus group of three -fn test_two_groups_of_four_is_cheaper_than_group_of_five_plus_group_of_three() { +fn two_groups_of_four_is_cheaper_than_group_of_five_plus_group_of_three() { process_total_case( ( vec![1, 1, 2, 2, 3, 3, 4, 5], @@ -86,7 +86,7 @@ fn test_two_groups_of_four_is_cheaper_than_group_of_five_plus_group_of_three() { #[test] #[ignore] /// Group of four plus group of two is cheaper than two groups of three -fn test_group_of_four_plus_group_of_two_is_cheaper_than_two_groups_of_three() { +fn group_of_four_plus_group_of_two_is_cheaper_than_two_groups_of_three() { process_total_case( (vec![1, 1, 2, 2, 3, 4], vec![vec![1, 2, 3, 4], vec![1, 2]]), 4_080, @@ -96,7 +96,7 @@ fn test_group_of_four_plus_group_of_two_is_cheaper_than_two_groups_of_three() { #[test] #[ignore] /// Two each of first 4 books and 1 copy each of rest -fn test_two_each_of_first_4_books_and_1_copy_each_of_rest() { +fn two_each_of_first_4_books_and_1_copy_each_of_rest() { process_total_case( ( vec![1, 1, 2, 2, 3, 3, 4, 4, 5], @@ -109,7 +109,7 @@ fn test_two_each_of_first_4_books_and_1_copy_each_of_rest() { #[test] #[ignore] /// Two copies of each book -fn test_two_copies_of_each_book() { +fn two_copies_of_each_book() { process_total_case( ( vec![1, 1, 2, 2, 3, 3, 4, 4, 5, 5], @@ -122,7 +122,7 @@ fn test_two_copies_of_each_book() { #[test] #[ignore] /// Three copies of first book and 2 each of remaining -fn test_three_copies_of_first_book_and_2_each_of_remaining() { +fn three_copies_of_first_book_and_2_each_of_remaining() { process_total_case( ( vec![1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 1], @@ -135,7 +135,7 @@ fn test_three_copies_of_first_book_and_2_each_of_remaining() { #[test] #[ignore] /// Three each of first 2 books and 2 each of remaining books -fn test_three_each_of_first_2_books_and_2_each_of_remaining_books() { +fn three_each_of_first_2_books_and_2_each_of_remaining_books() { process_total_case( ( vec![1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 1, 2], @@ -148,7 +148,7 @@ fn test_three_each_of_first_2_books_and_2_each_of_remaining_books() { #[test] #[ignore] /// Four groups of four are cheaper than two groups each of five and three -fn test_four_groups_of_four_are_cheaper_than_two_groups_each_of_five_and_three() { +fn four_groups_of_four_are_cheaper_than_two_groups_each_of_five_and_three() { process_total_case( ( vec![1, 1, 2, 2, 3, 3, 4, 5, 1, 1, 2, 2, 3, 3, 4, 5], diff --git a/exercises/practice/clock/tests/clock.rs b/exercises/practice/clock/tests/clock.rs index 6d1ffdf54..416a6f985 100644 --- a/exercises/practice/clock/tests/clock.rs +++ b/exercises/practice/clock/tests/clock.rs @@ -5,133 +5,133 @@ use clock::Clock; // #[test] -fn test_on_the_hour() { +fn on_the_hour() { assert_eq!(Clock::new(8, 0).to_string(), "08:00"); } #[test] #[ignore] -fn test_past_the_hour() { +fn past_the_hour() { assert_eq!(Clock::new(11, 9).to_string(), "11:09"); } #[test] #[ignore] -fn test_midnight_is_zero_hours() { +fn midnight_is_zero_hours() { assert_eq!(Clock::new(24, 0).to_string(), "00:00"); } #[test] #[ignore] -fn test_hour_rolls_over() { +fn hour_rolls_over() { assert_eq!(Clock::new(25, 0).to_string(), "01:00"); } #[test] #[ignore] -fn test_hour_rolls_over_continuously() { +fn hour_rolls_over_continuously() { assert_eq!(Clock::new(100, 0).to_string(), "04:00"); } #[test] #[ignore] -fn test_sixty_minutes_is_next_hour() { +fn sixty_minutes_is_next_hour() { assert_eq!(Clock::new(1, 60).to_string(), "02:00"); } #[test] #[ignore] -fn test_minutes_roll_over() { +fn minutes_roll_over() { assert_eq!(Clock::new(0, 160).to_string(), "02:40"); } #[test] #[ignore] -fn test_minutes_roll_over_continuously() { +fn minutes_roll_over_continuously() { assert_eq!(Clock::new(0, 1723).to_string(), "04:43"); } #[test] #[ignore] -fn test_hours_and_minutes_roll_over() { +fn hours_and_minutes_roll_over() { assert_eq!(Clock::new(25, 160).to_string(), "03:40"); } #[test] #[ignore] -fn test_hours_and_minutes_roll_over_continuously() { +fn hours_and_minutes_roll_over_continuously() { assert_eq!(Clock::new(201, 3001).to_string(), "11:01"); } #[test] #[ignore] -fn test_hours_and_minutes_roll_over_to_exactly_midnight() { +fn hours_and_minutes_roll_over_to_exactly_midnight() { assert_eq!(Clock::new(72, 8640).to_string(), "00:00"); } #[test] #[ignore] -fn test_negative_hour() { +fn negative_hour() { assert_eq!(Clock::new(-1, 15).to_string(), "23:15"); } #[test] #[ignore] -fn test_negative_hour_roll_over() { +fn negative_hour_roll_over() { assert_eq!(Clock::new(-25, 00).to_string(), "23:00"); } #[test] #[ignore] -fn test_negative_hour_roll_over_continuously() { +fn negative_hour_roll_over_continuously() { assert_eq!(Clock::new(-91, 00).to_string(), "05:00"); } #[test] #[ignore] -fn test_negative_minutes() { +fn negative_minutes() { assert_eq!(Clock::new(1, -40).to_string(), "00:20"); } #[test] #[ignore] -fn test_negative_minutes_roll_over() { +fn negative_minutes_roll_over() { assert_eq!(Clock::new(1, -160).to_string(), "22:20"); } #[test] #[ignore] -fn test_negative_minutes_roll_over_continuously() { +fn negative_minutes_roll_over_continuously() { assert_eq!(Clock::new(1, -4820).to_string(), "16:40"); } #[test] #[ignore] -fn test_negative_sixty_minutes_is_prev_hour() { +fn negative_sixty_minutes_is_prev_hour() { assert_eq!(Clock::new(2, -60).to_string(), "01:00"); } #[test] #[ignore] -fn test_negative_one_twenty_minutes_is_two_prev_hours() { +fn negative_one_twenty_minutes_is_two_prev_hours() { assert_eq!(Clock::new(1, -120).to_string(), "23:00"); } #[test] #[ignore] -fn test_negative_hour_and_minutes_both_roll_over() { +fn negative_hour_and_minutes_both_roll_over() { assert_eq!(Clock::new(-25, -160).to_string(), "20:20"); } #[test] #[ignore] -fn test_negative_hour_and_minutes_both_roll_over_continuously() { +fn negative_hour_and_minutes_both_roll_over_continuously() { assert_eq!(Clock::new(-121, -5810).to_string(), "22:10"); } #[test] #[ignore] -fn test_zero_hour_and_negative_minutes() { +fn zero_hour_and_negative_minutes() { assert_eq!(Clock::new(0, -22).to_string(), "23:38"); } @@ -141,112 +141,112 @@ fn test_zero_hour_and_negative_minutes() { #[test] #[ignore] -fn test_add_minutes() { +fn add_minutes() { let clock = Clock::new(10, 0).add_minutes(3); assert_eq!(clock.to_string(), "10:03"); } #[test] #[ignore] -fn test_add_no_minutes() { +fn add_no_minutes() { let clock = Clock::new(6, 41).add_minutes(0); assert_eq!(clock.to_string(), "06:41"); } #[test] #[ignore] -fn test_add_to_next_hour() { +fn add_to_next_hour() { let clock = Clock::new(0, 45).add_minutes(40); assert_eq!(clock.to_string(), "01:25"); } #[test] #[ignore] -fn test_add_more_than_one_hour() { +fn add_more_than_one_hour() { let clock = Clock::new(10, 0).add_minutes(61); assert_eq!(clock.to_string(), "11:01"); } #[test] #[ignore] -fn test_add_more_than_two_hours_with_carry() { +fn add_more_than_two_hours_with_carry() { let clock = Clock::new(0, 45).add_minutes(160); assert_eq!(clock.to_string(), "03:25"); } #[test] #[ignore] -fn test_add_across_midnight() { +fn add_across_midnight() { let clock = Clock::new(23, 59).add_minutes(2); assert_eq!(clock.to_string(), "00:01"); } #[test] #[ignore] -fn test_add_more_than_one_day() { +fn add_more_than_one_day() { let clock = Clock::new(5, 32).add_minutes(1500); assert_eq!(clock.to_string(), "06:32"); } #[test] #[ignore] -fn test_add_more_than_two_days() { +fn add_more_than_two_days() { let clock = Clock::new(1, 1).add_minutes(3500); assert_eq!(clock.to_string(), "11:21"); } #[test] #[ignore] -fn test_subtract_minutes() { +fn subtract_minutes() { let clock = Clock::new(10, 3).add_minutes(-3); assert_eq!(clock.to_string(), "10:00"); } #[test] #[ignore] -fn test_subtract_to_previous_hour() { +fn subtract_to_previous_hour() { let clock = Clock::new(10, 3).add_minutes(-30); assert_eq!(clock.to_string(), "09:33"); } #[test] #[ignore] -fn test_subtract_more_than_an_hour() { +fn subtract_more_than_an_hour() { let clock = Clock::new(10, 3).add_minutes(-70); assert_eq!(clock.to_string(), "08:53"); } #[test] #[ignore] -fn test_subtract_across_midnight() { +fn subtract_across_midnight() { let clock = Clock::new(0, 3).add_minutes(-4); assert_eq!(clock.to_string(), "23:59"); } #[test] #[ignore] -fn test_subtract_more_than_two_hours() { +fn subtract_more_than_two_hours() { let clock = Clock::new(0, 0).add_minutes(-160); assert_eq!(clock.to_string(), "21:20"); } #[test] #[ignore] -fn test_subtract_more_than_two_hours_with_borrow() { +fn subtract_more_than_two_hours_with_borrow() { let clock = Clock::new(6, 15).add_minutes(-160); assert_eq!(clock.to_string(), "03:35"); } #[test] #[ignore] -fn test_subtract_more_than_one_day() { +fn subtract_more_than_one_day() { let clock = Clock::new(5, 32).add_minutes(-1500); assert_eq!(clock.to_string(), "04:32"); } #[test] #[ignore] -fn test_subtract_more_than_two_days() { +fn subtract_more_than_two_days() { let clock = Clock::new(2, 20).add_minutes(-3000); assert_eq!(clock.to_string(), "00:20"); } @@ -257,96 +257,96 @@ fn test_subtract_more_than_two_days() { #[test] #[ignore] -fn test_compare_clocks_for_equality() { +fn compare_clocks_for_equality() { assert_eq!(Clock::new(15, 37), Clock::new(15, 37)); } #[test] #[ignore] -fn test_compare_clocks_a_minute_apart() { +fn compare_clocks_a_minute_apart() { assert_ne!(Clock::new(15, 36), Clock::new(15, 37)); } #[test] #[ignore] -fn test_compare_clocks_an_hour_apart() { +fn compare_clocks_an_hour_apart() { assert_ne!(Clock::new(14, 37), Clock::new(15, 37)); } #[test] #[ignore] -fn test_compare_clocks_with_hour_overflow() { +fn compare_clocks_with_hour_overflow() { assert_eq!(Clock::new(10, 37), Clock::new(34, 37)); } #[test] #[ignore] -fn test_compare_clocks_with_hour_overflow_by_several_days() { +fn compare_clocks_with_hour_overflow_by_several_days() { assert_eq!(Clock::new(99, 11), Clock::new(3, 11)); } #[test] #[ignore] -fn test_compare_clocks_with_negative_hour() { +fn compare_clocks_with_negative_hour() { assert_eq!(Clock::new(-2, 40), Clock::new(22, 40)); } #[test] #[ignore] -fn test_compare_clocks_with_negative_hour_that_wraps() { +fn compare_clocks_with_negative_hour_that_wraps() { assert_eq!(Clock::new(-31, 3), Clock::new(17, 3)); } #[test] #[ignore] -fn test_compare_clocks_with_negative_hour_that_wraps_multiple_times() { +fn compare_clocks_with_negative_hour_that_wraps_multiple_times() { assert_eq!(Clock::new(-83, 49), Clock::new(13, 49)); } #[test] #[ignore] -fn test_compare_clocks_with_minutes_overflow() { +fn compare_clocks_with_minutes_overflow() { assert_eq!(Clock::new(0, 1441), Clock::new(0, 1)); } #[test] #[ignore] -fn test_compare_clocks_with_minutes_overflow_by_several_days() { +fn compare_clocks_with_minutes_overflow_by_several_days() { assert_eq!(Clock::new(2, 4322), Clock::new(2, 2)); } #[test] #[ignore] -fn test_compare_clocks_with_negative_minute() { +fn compare_clocks_with_negative_minute() { assert_eq!(Clock::new(3, -20), Clock::new(2, 40)); } #[test] #[ignore] -fn test_compare_clocks_with_negative_minute_that_wraps() { +fn compare_clocks_with_negative_minute_that_wraps() { assert_eq!(Clock::new(5, -1490), Clock::new(4, 10)); } #[test] #[ignore] -fn test_compare_clocks_with_negative_minute_that_wraps_multiple() { +fn compare_clocks_with_negative_minute_that_wraps_multiple() { assert_eq!(Clock::new(6, -4305), Clock::new(6, 15)); } #[test] #[ignore] -fn test_compare_clocks_with_negative_hours_and_minutes() { +fn compare_clocks_with_negative_hours_and_minutes() { assert_eq!(Clock::new(-12, -268), Clock::new(7, 32)); } #[test] #[ignore] -fn test_compare_clocks_with_negative_hours_and_minutes_that_wrap() { +fn compare_clocks_with_negative_hours_and_minutes_that_wrap() { assert_eq!(Clock::new(-54, -11_513), Clock::new(18, 7)); } #[test] #[ignore] -fn test_compare_full_clock_and_zeroed_clock() { +fn compare_full_clock_and_zeroed_clock() { assert_eq!(Clock::new(24, 0), Clock::new(0, 0)); } diff --git a/exercises/practice/collatz-conjecture/tests/collatz-conjecture.rs b/exercises/practice/collatz-conjecture/tests/collatz-conjecture.rs index b78442270..9c6690dc2 100644 --- a/exercises/practice/collatz-conjecture/tests/collatz-conjecture.rs +++ b/exercises/practice/collatz-conjecture/tests/collatz-conjecture.rs @@ -1,31 +1,31 @@ use collatz_conjecture::*; #[test] -fn test_1() { +fn one() { assert_eq!(Some(0), collatz(1)); } #[test] #[ignore] -fn test_16() { +fn sixteen() { assert_eq!(Some(4), collatz(16)); } #[test] #[ignore] -fn test_12() { +fn twelve() { assert_eq!(Some(9), collatz(12)); } #[test] #[ignore] -fn test_1000000() { +fn one_million() { assert_eq!(Some(152), collatz(1_000_000)); } #[test] #[ignore] -fn test_0() { +fn zero() { assert_eq!(None, collatz(0)); } @@ -38,14 +38,14 @@ fn test_110243094271() { #[test] #[ignore] -fn test_max_div_3() { +fn max_div_3() { let max = u64::MAX / 3; assert_eq!(None, collatz(max)); } #[test] #[ignore] -fn test_max_minus_1() { +fn max_minus_1() { let max = u64::MAX - 1; assert_eq!(None, collatz(max)); } diff --git a/exercises/practice/crypto-square/tests/crypto-square.rs b/exercises/practice/crypto-square/tests/crypto-square.rs index ba65c2e43..ebe67f3a9 100644 --- a/exercises/practice/crypto-square/tests/crypto-square.rs +++ b/exercises/practice/crypto-square/tests/crypto-square.rs @@ -5,13 +5,13 @@ fn test(input: &str, output: &str) { } #[test] -fn test_empty_input() { +fn empty_input() { test("", "") } #[test] #[ignore] -fn test_encrypt_also_decrypts_square() { +fn encrypt_also_decrypts_square() { // note that you only get the exact input back if: // 1. no punctuation // 2. even spacing @@ -23,7 +23,7 @@ fn test_encrypt_also_decrypts_square() { #[test] #[ignore] -fn test_example() { +fn example() { test( "If man was meant to stay on the ground, god would have given us roots.", "imtgdvs fearwer mayoogo anouuio ntnnlvt wttddes aohghn sseoau ", @@ -32,13 +32,13 @@ fn test_example() { #[test] #[ignore] -fn test_empty_last_line() { +fn empty_last_line() { test("congratulate", "crl oaa ntt gue") } #[test] #[ignore] -fn test_spaces_are_reorganized() { +fn spaces_are_reorganized() { test("abet", "ae bt"); test("a bet", "ae bt"); test(" a b e t ", "ae bt"); @@ -46,7 +46,7 @@ fn test_spaces_are_reorganized() { #[test] #[ignore] -fn test_everything_becomes_lowercase() { +fn everything_becomes_lowercase() { test("caSe", "cs ae"); test("cAsE", "cs ae"); test("CASE", "cs ae"); @@ -54,7 +54,7 @@ fn test_everything_becomes_lowercase() { #[test] #[ignore] -fn test_long() { +fn long() { test( r#" We choose to go to the moon. diff --git a/exercises/practice/decimal/.meta/example.rs b/exercises/practice/decimal/.meta/example.rs index 7f8c3faf7..e6c7d4c4e 100644 --- a/exercises/practice/decimal/.meta/example.rs +++ b/exercises/practice/decimal/.meta/example.rs @@ -171,7 +171,7 @@ mod tests { use super::*; #[test] - fn test_display_temp() { + fn display_temp() { for &test_str in &["0", "1", "20", "0.3", "0.04", "50.05", "66.0006", "0.007"] { println!( "Decimal representation of \"{}\": {}", diff --git a/exercises/practice/decimal/tests/decimal.rs b/exercises/practice/decimal/tests/decimal.rs index 02823fc3a..74a11e1c8 100644 --- a/exercises/practice/decimal/tests/decimal.rs +++ b/exercises/practice/decimal/tests/decimal.rs @@ -16,7 +16,7 @@ const BIGS: [&str; 3] = [ // test simple properties of required operations #[test] -fn test_eq() { +fn eq() { assert!(decimal("0.0") == decimal("0.0")); assert!(decimal("1.0") == decimal("1.0")); for big in BIGS.iter() { @@ -26,14 +26,14 @@ fn test_eq() { #[test] #[ignore] -fn test_ne() { +fn ne() { assert!(decimal("0.0") != decimal("1.0")); assert!(decimal(BIGS[0]) != decimal(BIGS[1])); } #[test] #[ignore] -fn test_gt() { +fn gt() { for slice_2 in BIGS.windows(2) { assert!(decimal(slice_2[1]) > decimal(slice_2[0])); } @@ -41,7 +41,7 @@ fn test_gt() { #[test] #[ignore] -fn test_lt() { +fn lt() { for slice_2 in BIGS.windows(2) { assert!(decimal(slice_2[0]) < decimal(slice_2[1])); } @@ -49,7 +49,7 @@ fn test_lt() { #[test] #[ignore] -fn test_add() { +fn add() { assert_eq!(decimal("0.1") + decimal("0.2"), decimal("0.3")); assert_eq!(decimal(BIGS[0]) + decimal(BIGS[1]), decimal(BIGS[2])); assert_eq!(decimal(BIGS[1]) + decimal(BIGS[0]), decimal(BIGS[2])); @@ -57,14 +57,14 @@ fn test_add() { #[test] #[ignore] -fn test_sub() { +fn sub() { assert_eq!(decimal(BIGS[2]) - decimal(BIGS[1]), decimal(BIGS[0])); assert_eq!(decimal(BIGS[2]) - decimal(BIGS[0]), decimal(BIGS[1])); } #[test] #[ignore] -fn test_mul() { +fn mul() { for big in BIGS.iter() { assert_eq!(decimal(big) * decimal("2"), decimal(big) + decimal(big)); } @@ -73,7 +73,7 @@ fn test_mul() { // test identities #[test] #[ignore] -fn test_add_id() { +fn add_id() { assert_eq!(decimal("1.0") + decimal("0.0"), decimal("1.0")); assert_eq!(decimal("0.1") + decimal("0.0"), decimal("0.1")); assert_eq!(decimal("0.0") + decimal("1.0"), decimal("1.0")); @@ -82,28 +82,28 @@ fn test_add_id() { #[test] #[ignore] -fn test_sub_id() { +fn sub_id() { assert_eq!(decimal("1.0") - decimal("0.0"), decimal("1.0")); assert_eq!(decimal("0.1") - decimal("0.0"), decimal("0.1")); } #[test] #[ignore] -fn test_mul_id() { +fn mul_id() { assert_eq!(decimal("2.1") * decimal("1.0"), decimal("2.1")); assert_eq!(decimal("1.0") * decimal("2.1"), decimal("2.1")); } #[test] #[ignore] -fn test_gt_positive_and_zero() { +fn gt_positive_and_zero() { assert!(decimal("1.0") > decimal("0.0")); assert!(decimal("0.1") > decimal("0.0")); } #[test] #[ignore] -fn test_gt_negative_and_zero() { +fn gt_negative_and_zero() { assert!(decimal("0.0") > decimal("-0.1")); assert!(decimal("0.0") > decimal("-1.0")); } @@ -111,20 +111,20 @@ fn test_gt_negative_and_zero() { // tests of arbitrary precision behavior #[test] #[ignore] -fn test_add_uneven_position() { +fn add_uneven_position() { assert_eq!(decimal("0.1") + decimal("0.02"), decimal("0.12")); } #[test] #[ignore] -fn test_eq_vary_sig_digits() { +fn eq_vary_sig_digits() { assert!(decimal("0") == decimal("0000000000000.0000000000000000000000")); assert!(decimal("1") == decimal("00000000000000001.000000000000000000")); } #[test] #[ignore] -fn test_add_vary_precision() { +fn add_vary_precision() { assert_eq!( decimal("100000000000000000000000000000000000000000000") + decimal("0.00000000000000000000000000000000000000001"), @@ -134,7 +134,7 @@ fn test_add_vary_precision() { #[test] #[ignore] -fn test_cleanup_precision() { +fn cleanup_precision() { assert_eq!( decimal("10000000000000000000000000000000000000000000000.999999999999999999999999998",) + decimal( @@ -146,7 +146,7 @@ fn test_cleanup_precision() { #[test] #[ignore] -fn test_gt_varying_positive_precisions() { +fn gt_varying_positive_precisions() { assert!(decimal("1.1") > decimal("1.01")); assert!(decimal("1.01") > decimal("1.0")); assert!(decimal("1.0") > decimal("0.1")); @@ -155,7 +155,7 @@ fn test_gt_varying_positive_precisions() { #[test] #[ignore] -fn test_gt_positive_and_negative() { +fn gt_positive_and_negative() { assert!(decimal("1.0") > decimal("-1.0")); assert!(decimal("1.1") > decimal("-1.1")); assert!(decimal("0.1") > decimal("-0.1")); @@ -163,7 +163,7 @@ fn test_gt_positive_and_negative() { #[test] #[ignore] -fn test_gt_varying_negative_precisions() { +fn gt_varying_negative_precisions() { assert!(decimal("-0.01") > decimal("-0.1")); assert!(decimal("-0.1") > decimal("-1.0")); assert!(decimal("-1.0") > decimal("-1.01")); @@ -173,7 +173,7 @@ fn test_gt_varying_negative_precisions() { // test signed properties #[test] #[ignore] -fn test_negatives() { +fn negatives() { assert!(Decimal::try_from("-1").is_some()); assert_eq!(decimal("0") - decimal("1"), decimal("-1")); assert_eq!(decimal("5.5") + decimal("-6.5"), decimal("-1")); @@ -181,21 +181,21 @@ fn test_negatives() { #[test] #[ignore] -fn test_explicit_positive() { +fn explicit_positive() { assert_eq!(decimal("+1"), decimal("1")); assert_eq!(decimal("+2.0") - decimal("-0002.0"), decimal("4")); } #[test] #[ignore] -fn test_multiply_by_negative() { +fn multiply_by_negative() { assert_eq!(decimal("5") * decimal("-0.2"), decimal("-1")); assert_eq!(decimal("-20") * decimal("-0.2"), decimal("4")); } #[test] #[ignore] -fn test_simple_partial_cmp() { +fn simple_partial_cmp() { assert!(decimal("1.0") < decimal("1.1")); assert!(decimal("0.00000000000000000000001") > decimal("-20000000000000000000000000000")); } @@ -205,122 +205,122 @@ fn test_simple_partial_cmp() { // integer and fractional parts of the number are stored separately #[test] #[ignore] -fn test_carry_into_integer() { +fn carry_into_integer() { assert_eq!(decimal("0.901") + decimal("0.1"), decimal("1.001")) } #[test] #[ignore] -fn test_carry_into_fractional_with_digits_to_right() { +fn carry_into_fractional_with_digits_to_right() { assert_eq!(decimal("0.0901") + decimal("0.01"), decimal("0.1001")) } #[test] #[ignore] -fn test_add_carry_over_negative() { +fn add_carry_over_negative() { assert_eq!(decimal("-1.99") + decimal("-0.01"), decimal("-2.0")) } #[test] #[ignore] -fn test_sub_carry_over_negative() { +fn sub_carry_over_negative() { assert_eq!(decimal("-1.99") - decimal("0.01"), decimal("-2.0")) } #[test] #[ignore] -fn test_add_carry_over_negative_with_fractional() { +fn add_carry_over_negative_with_fractional() { assert_eq!(decimal("-1.99") + decimal("-0.02"), decimal("-2.01")) } #[test] #[ignore] -fn test_sub_carry_over_negative_with_fractional() { +fn sub_carry_over_negative_with_fractional() { assert_eq!(decimal("-1.99") - decimal("0.02"), decimal("-2.01")) } #[test] #[ignore] -fn test_carry_from_rightmost_one() { +fn carry_from_rightmost_one() { assert_eq!(decimal("0.09") + decimal("0.01"), decimal("0.1")) } #[test] #[ignore] -fn test_carry_from_rightmost_more() { +fn carry_from_rightmost_more() { assert_eq!(decimal("0.099") + decimal("0.001"), decimal("0.1")) } #[test] #[ignore] -fn test_carry_from_rightmost_into_integer() { +fn carry_from_rightmost_into_integer() { assert_eq!(decimal("0.999") + decimal("0.001"), decimal("1.0")) } // test arithmetic borrow rules #[test] #[ignore] -fn test_add_borrow() { +fn add_borrow() { assert_eq!(decimal("0.01") + decimal("-0.0001"), decimal("0.0099")) } #[test] #[ignore] -fn test_sub_borrow() { +fn sub_borrow() { assert_eq!(decimal("0.01") - decimal("0.0001"), decimal("0.0099")) } #[test] #[ignore] -fn test_add_borrow_integral() { +fn add_borrow_integral() { assert_eq!(decimal("1.0") + decimal("-0.01"), decimal("0.99")) } #[test] #[ignore] -fn test_sub_borrow_integral() { +fn sub_borrow_integral() { assert_eq!(decimal("1.0") - decimal("0.01"), decimal("0.99")) } #[test] #[ignore] -fn test_add_borrow_integral_zeroes() { +fn add_borrow_integral_zeroes() { assert_eq!(decimal("1.0") + decimal("-0.99"), decimal("0.01")) } #[test] #[ignore] -fn test_sub_borrow_integral_zeroes() { +fn sub_borrow_integral_zeroes() { assert_eq!(decimal("1.0") - decimal("0.99"), decimal("0.01")) } #[test] #[ignore] -fn test_borrow_from_negative() { +fn borrow_from_negative() { assert_eq!(decimal("-1.0") + decimal("0.01"), decimal("-0.99")) } #[test] #[ignore] -fn test_add_into_fewer_digits() { +fn add_into_fewer_digits() { assert_eq!(decimal("0.011") + decimal("-0.001"), decimal("0.01")) } // misc tests of arithmetic properties #[test] #[ignore] -fn test_sub_into_fewer_digits() { +fn sub_into_fewer_digits() { assert_eq!(decimal("0.011") - decimal("0.001"), decimal("0.01")) } #[test] #[ignore] -fn test_add_away_decimal() { +fn add_away_decimal() { assert_eq!(decimal("1.1") + decimal("-0.1"), decimal("1.0")) } #[test] #[ignore] -fn test_sub_away_decimal() { +fn sub_away_decimal() { assert_eq!(decimal("1.1") - decimal("0.1"), decimal("1.0")) } diff --git a/exercises/practice/diamond/tests/diamond.rs b/exercises/practice/diamond/tests/diamond.rs index cb1c09005..2f18fc8a0 100644 --- a/exercises/practice/diamond/tests/diamond.rs +++ b/exercises/practice/diamond/tests/diamond.rs @@ -1,13 +1,13 @@ use diamond::*; #[test] -fn test_a() { +fn a() { assert_eq!(get_diamond('A'), vec!["A"]); } #[test] #[ignore] -fn test_b() { +fn b() { #[rustfmt::skip] assert_eq!( get_diamond('B'), @@ -21,7 +21,7 @@ fn test_b() { #[test] #[ignore] -fn test_c() { +fn c() { #[rustfmt::skip] assert_eq!( get_diamond('C'), @@ -37,7 +37,7 @@ fn test_c() { #[test] #[ignore] -fn test_d() { +fn d() { #[rustfmt::skip] assert_eq!( get_diamond('D'), @@ -55,7 +55,7 @@ fn test_d() { #[test] #[ignore] -fn test_e() { +fn e() { assert_eq!( get_diamond('Z'), vec![ diff --git a/exercises/practice/difference-of-squares/tests/difference-of-squares.rs b/exercises/practice/difference-of-squares/tests/difference-of-squares.rs index 552b9c4de..672faaee0 100644 --- a/exercises/practice/difference-of-squares/tests/difference-of-squares.rs +++ b/exercises/practice/difference-of-squares/tests/difference-of-squares.rs @@ -1,54 +1,54 @@ use difference_of_squares as squares; #[test] -fn test_square_of_sum_1() { +fn square_of_sum_1() { assert_eq!(1, squares::square_of_sum(1)); } #[test] #[ignore] -fn test_square_of_sum_5() { +fn square_of_sum_5() { assert_eq!(225, squares::square_of_sum(5)); } #[test] #[ignore] -fn test_square_of_sum_100() { +fn square_of_sum_100() { assert_eq!(25_502_500, squares::square_of_sum(100)); } #[test] #[ignore] -fn test_sum_of_squares_1() { +fn sum_of_squares_1() { assert_eq!(1, squares::sum_of_squares(1)); } #[test] #[ignore] -fn test_sum_of_squares_5() { +fn sum_of_squares_5() { assert_eq!(55, squares::sum_of_squares(5)); } #[test] #[ignore] -fn test_sum_of_squares_100() { +fn sum_of_squares_100() { assert_eq!(338_350, squares::sum_of_squares(100)); } #[test] #[ignore] -fn test_difference_1() { +fn difference_1() { assert_eq!(0, squares::difference(1)); } #[test] #[ignore] -fn test_difference_5() { +fn difference_5() { assert_eq!(170, squares::difference(5)); } #[test] #[ignore] -fn test_difference_100() { +fn difference_100() { assert_eq!(25_164_150, squares::difference(100)); } diff --git a/exercises/practice/diffie-hellman/tests/diffie-hellman.rs b/exercises/practice/diffie-hellman/tests/diffie-hellman.rs index 5c7504304..da338d9a0 100644 --- a/exercises/practice/diffie-hellman/tests/diffie-hellman.rs +++ b/exercises/practice/diffie-hellman/tests/diffie-hellman.rs @@ -1,7 +1,7 @@ use diffie_hellman::*; #[test] -fn test_private_key_in_range_key() { +fn private_key_in_range_key() { let primes: Vec = vec![ 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 773, 967, 3461, 6131, ]; @@ -14,7 +14,7 @@ fn test_private_key_in_range_key() { #[test] #[ignore] -fn test_public_key_correct() { +fn public_key_correct() { let p: u64 = 23; let g: u64 = 5; @@ -26,7 +26,7 @@ fn test_public_key_correct() { #[test] #[ignore] -fn test_secret_key_correct() { +fn secret_key_correct() { let p: u64 = 11; let private_key_a = 7; @@ -39,7 +39,7 @@ fn test_secret_key_correct() { #[test] #[ignore] -fn test_public_key_correct_big_numbers() { +fn public_key_correct_big_numbers() { let p: u64 = 4_294_967_299; let g: u64 = 8; @@ -53,7 +53,7 @@ fn test_public_key_correct_big_numbers() { #[test] #[ignore] -fn test_secret_key_correct_big_numbers() { +fn secret_key_correct_big_numbers() { let p: u64 = 4_294_967_927; let private_key_a = 4_294_967_300; @@ -80,7 +80,7 @@ const PUBLIC_KEY_64BIT: u64 = 0xB851_EB85_1EB8_51C1; #[test] #[ignore] #[cfg(feature = "big-primes")] -fn test_public_key_correct_biggest_numbers() { +fn public_key_correct_biggest_numbers() { assert_eq!( public_key(PRIME_64BIT_1, PRIME_64BIT_2, PRIVATE_KEY_64BIT), PUBLIC_KEY_64BIT @@ -90,7 +90,7 @@ fn test_public_key_correct_biggest_numbers() { #[test] #[ignore] #[cfg(feature = "big-primes")] -fn test_secret_key_correct_biggest_numbers() { +fn secret_key_correct_biggest_numbers() { let private_key_b = 0xEFFF_FFFF_FFFF_FFC0; let public_key_b = public_key(PRIME_64BIT_1, PRIME_64BIT_2, private_key_b); @@ -111,7 +111,7 @@ fn test_secret_key_correct_biggest_numbers() { #[test] #[ignore] #[cfg(feature = "big-primes")] -fn test_changed_secret_key_biggest_numbers() { +fn changed_secret_key_biggest_numbers() { let private_key_a = private_key(PRIME_64BIT_1); let public_key_a = public_key(PRIME_64BIT_1, PRIME_64BIT_2, private_key_a); @@ -126,7 +126,7 @@ fn test_changed_secret_key_biggest_numbers() { #[test] #[ignore] -fn test_changed_secret_key() { +fn changed_secret_key() { let p: u64 = 13; let g: u64 = 11; diff --git a/exercises/practice/dot-dsl/tests/dot-dsl.rs b/exercises/practice/dot-dsl/tests/dot-dsl.rs index cadd478e8..9bf673a87 100644 --- a/exercises/practice/dot-dsl/tests/dot-dsl.rs +++ b/exercises/practice/dot-dsl/tests/dot-dsl.rs @@ -4,7 +4,7 @@ use dot_dsl::graph::Graph; use maplit::hashmap; #[test] -fn test_empty_graph() { +fn empty_graph() { let graph = Graph::new(); assert!(graph.nodes.is_empty()); @@ -16,7 +16,7 @@ fn test_empty_graph() { #[test] #[ignore] -fn test_graph_with_one_node() { +fn graph_with_one_node() { let nodes = vec![Node::new("a")]; let graph = Graph::new().with_nodes(&nodes); @@ -30,7 +30,7 @@ fn test_graph_with_one_node() { #[test] #[ignore] -fn test_graph_with_one_node_with_keywords() { +fn graph_with_one_node_with_keywords() { let nodes = vec![Node::new("a").with_attrs(&[("color", "green")])]; let graph = Graph::new().with_nodes(&nodes); @@ -47,7 +47,7 @@ fn test_graph_with_one_node_with_keywords() { #[test] #[ignore] -fn test_graph_with_one_edge() { +fn graph_with_one_edge() { let edges = vec![Edge::new("a", "b")]; let graph = Graph::new().with_edges(&edges); @@ -61,7 +61,7 @@ fn test_graph_with_one_edge() { #[test] #[ignore] -fn test_graph_with_one_edge_with_keywords() { +fn graph_with_one_edge_with_keywords() { let edges = vec![Edge::new("a", "b").with_attrs(&[("color", "blue")])]; let graph = Graph::new().with_edges(&edges); @@ -78,7 +78,7 @@ fn test_graph_with_one_edge_with_keywords() { #[test] #[ignore] -fn test_graph_with_one_attribute() { +fn graph_with_one_attribute() { let graph = Graph::new().with_attrs(&[("foo", "1")]); let expected_attrs = hashmap! { @@ -94,7 +94,7 @@ fn test_graph_with_one_attribute() { #[test] #[ignore] -fn test_graph_with_attributes() { +fn graph_with_attributes() { let nodes = vec![ Node::new("a").with_attrs(&[("color", "green")]), Node::new("c"), @@ -141,7 +141,7 @@ fn test_graph_with_attributes() { #[test] #[ignore] -fn test_edges_store_attributes() { +fn edges_store_attributes() { let nodes = vec![ Node::new("a").with_attrs(&[("color", "green")]), Node::new("c"), @@ -178,7 +178,7 @@ fn test_edges_store_attributes() { #[test] #[ignore] -fn test_graph_nodes_store_attributes() { +fn graph_nodes_store_attributes() { let attributes = [("foo", "bar"), ("bat", "baz"), ("bim", "bef")]; let graph = Graph::new().with_nodes( &["a", "b", "c"] diff --git a/exercises/practice/etl/tests/etl.rs b/exercises/practice/etl/tests/etl.rs index 37241247e..e1324990d 100644 --- a/exercises/practice/etl/tests/etl.rs +++ b/exercises/practice/etl/tests/etl.rs @@ -1,7 +1,7 @@ use std::collections::BTreeMap; #[test] -fn test_transform_one_value() { +fn transform_one_value() { let input = input_from(&[(1, vec!['A'])]); let expected = expected_from(&[('a', 1)]); @@ -11,7 +11,7 @@ fn test_transform_one_value() { #[test] #[ignore] -fn test_transform_more_values() { +fn transform_more_values() { let input = input_from(&[(1, vec!['A', 'E', 'I', 'O', 'U'])]); let expected = expected_from(&[('a', 1), ('e', 1), ('i', 1), ('o', 1), ('u', 1)]); @@ -21,7 +21,7 @@ fn test_transform_more_values() { #[test] #[ignore] -fn test_more_keys() { +fn more_keys() { let input = input_from(&[(1, vec!['A', 'E']), (2, vec!['D', 'G'])]); let expected = expected_from(&[('a', 1), ('e', 1), ('d', 2), ('g', 2)]); @@ -31,7 +31,7 @@ fn test_more_keys() { #[test] #[ignore] -fn test_full_dataset() { +fn full_dataset() { let input = input_from(&[ (1, vec!['A', 'E', 'I', 'O', 'U', 'L', 'N', 'R', 'S', 'T']), (2, vec!['D', 'G']), diff --git a/exercises/practice/fizzy/.meta/example.rs b/exercises/practice/fizzy/.meta/example.rs index 0d11f9411..28e2dee98 100644 --- a/exercises/practice/fizzy/.meta/example.rs +++ b/exercises/practice/fizzy/.meta/example.rs @@ -85,7 +85,7 @@ mod test { use super::*; #[test] - fn test_fizz_buzz() { + fn fizz_buzz_i32() { let expect = vec![ "1", "2", "fizz", "4", "buzz", "fizz", "7", "8", "fizz", "buzz", "11", "fizz", "13", "14", "fizzbuzz", "16", @@ -95,7 +95,7 @@ mod test { } #[test] - fn test_fizz_buzz_u8() { + fn fizz_buzz_u8() { let expect = vec![ "1", "2", "fizz", "4", "buzz", "fizz", "7", "8", "fizz", "buzz", "11", "fizz", "13", "14", "fizzbuzz", "16", @@ -105,7 +105,7 @@ mod test { } #[test] - fn test_fizz_buzz_u64() { + fn fizz_buzz_u64() { let expect = vec![ "1", "2", "fizz", "4", "buzz", "fizz", "7", "8", "fizz", "buzz", "11", "fizz", "13", "14", "fizzbuzz", "16", @@ -115,7 +115,7 @@ mod test { } #[test] - fn test_fizz_buzz_nonsequential() { + fn fizz_buzz_nonsequential() { let collatz_12 = &[12, 6, 3, 10, 5, 16, 8, 4, 2, 1]; let expect = vec![ "fizz", "fizz", "fizz", "buzz", "buzz", "16", "8", "4", "2", "1", @@ -127,7 +127,7 @@ mod test { } #[test] - fn test_fizz_buzz_custom() { + fn fizz_buzz_custom() { let expect = vec![ "1", "2", "Fizz", "4", "Buzz", "Fizz", "Bam", "8", "Fizz", "Buzz", "11", "Fizz", "13", "Bam", "BuzzFizz", "16", @@ -142,7 +142,7 @@ mod test { } #[test] - fn test_map() { + fn map() { let expect = vec![ "1", "2", "fizz", "4", "buzz", "fizz", "7", "8", "fizz", "buzz", "11", "fizz", "13", "14", "fizzbuzz", "16", @@ -155,7 +155,7 @@ mod test { } #[test] - fn test_fizz_buzz_f64() { + fn fizz_buzz_f64() { let expect = vec![ "1", "2", "fizz", "4", "buzz", "fizz", "7", "8", "fizz", "buzz", "11", "fizz", "13", "14", "fizzbuzz", "16", diff --git a/exercises/practice/fizzy/tests/fizzy.rs b/exercises/practice/fizzy/tests/fizzy.rs index 85a7f8adb..f496ca45b 100644 --- a/exercises/practice/fizzy/tests/fizzy.rs +++ b/exercises/practice/fizzy/tests/fizzy.rs @@ -10,28 +10,28 @@ macro_rules! expect { } #[test] -fn test_simple() { +fn simple() { let got = fizz_buzz::().apply(1..=16).collect::>(); assert_eq!(expect!(), got); } #[test] #[ignore] -fn test_u8() { +fn u8() { let got = fizz_buzz::().apply(1_u8..=16).collect::>(); assert_eq!(expect!(), got); } #[test] #[ignore] -fn test_u64() { +fn u64() { let got = fizz_buzz::().apply(1_u64..=16).collect::>(); assert_eq!(expect!(), got); } #[test] #[ignore] -fn test_nonsequential() { +fn nonsequential() { let collatz_12 = &[12, 6, 3, 10, 5, 16, 8, 4, 2, 1]; let expect = vec![ "fizz", "fizz", "fizz", "buzz", "buzz", "16", "8", "4", "2", "1", @@ -44,7 +44,7 @@ fn test_nonsequential() { #[test] #[ignore] -fn test_custom() { +fn custom() { let expect = vec![ "1", "2", "Fizz", "4", "Buzz", "Fizz", "Bam", "8", "Fizz", "Buzz", "11", "Fizz", "13", "Bam", "BuzzFizz", "16", @@ -59,7 +59,7 @@ fn test_custom() { #[test] #[ignore] -fn test_f64() { +fn f64() { // a tiny bit more complicated becuase range isn't natively implemented on floats // NOTE: this test depends on a language feature introduced in Rust 1.34. If you // have an older compiler, upgrade. If you have an older compiler and cannot upgrade, @@ -73,7 +73,7 @@ fn test_f64() { #[test] #[ignore] -fn test_minimal_generic_bounds() { +fn minimal_generic_bounds() { // NOTE: this test depends on a language feature introduced in Rust 1.34. If you // have an older compiler, upgrade. If you have an older compiler and cannot upgrade, // feel free to ignore this test. diff --git a/exercises/practice/gigasecond/tests/gigasecond.rs b/exercises/practice/gigasecond/tests/gigasecond.rs index abd53a882..52237cf8b 100644 --- a/exercises/practice/gigasecond/tests/gigasecond.rs +++ b/exercises/practice/gigasecond/tests/gigasecond.rs @@ -13,7 +13,7 @@ fn dt(year: i32, month: u8, day: u8, hour: u8, minute: u8, second: u8) -> DateTi } #[test] -fn test_date() { +fn date() { let start_date = dt(2011, 4, 25, 0, 0, 0); assert_eq!(gigasecond::after(start_date), dt(2043, 1, 1, 1, 46, 40)); @@ -21,7 +21,7 @@ fn test_date() { #[test] #[ignore] -fn test_another_date() { +fn another_date() { let start_date = dt(1977, 6, 13, 0, 0, 0); assert_eq!(gigasecond::after(start_date), dt(2009, 2, 19, 1, 46, 40)); @@ -29,7 +29,7 @@ fn test_another_date() { #[test] #[ignore] -fn test_third_date() { +fn third_date() { let start_date = dt(1959, 7, 19, 0, 0, 0); assert_eq!(gigasecond::after(start_date), dt(1991, 3, 27, 1, 46, 40)); @@ -37,7 +37,7 @@ fn test_third_date() { #[test] #[ignore] -fn test_datetime() { +fn datetime() { let start_date = dt(2015, 1, 24, 22, 0, 0); assert_eq!(gigasecond::after(start_date), dt(2046, 10, 2, 23, 46, 40)); @@ -45,7 +45,7 @@ fn test_datetime() { #[test] #[ignore] -fn test_another_datetime() { +fn another_datetime() { let start_date = dt(2015, 1, 24, 23, 59, 59); assert_eq!(gigasecond::after(start_date), dt(2046, 10, 3, 1, 46, 39)); diff --git a/exercises/practice/grade-school/tests/grade-school.rs b/exercises/practice/grade-school/tests/grade-school.rs index eb41d0789..3d8a2d51b 100644 --- a/exercises/practice/grade-school/tests/grade-school.rs +++ b/exercises/practice/grade-school/tests/grade-school.rs @@ -5,14 +5,14 @@ fn to_owned(v: &[&str]) -> Vec { } #[test] -fn test_grades_for_empty_school() { +fn grades_for_empty_school() { let s = school::School::new(); assert_eq!(s.grades(), vec![]); } #[test] #[ignore] -fn test_grades_for_one_student() { +fn grades_for_one_student() { let mut s = school::School::new(); s.add(2, "Aimee"); assert_eq!(s.grades(), vec![2]); @@ -20,7 +20,7 @@ fn test_grades_for_one_student() { #[test] #[ignore] -fn test_grades_for_several_students_are_sorted() { +fn grades_for_several_students_are_sorted() { let mut s = school::School::new(); s.add(2, "Aimee"); s.add(7, "Logan"); @@ -30,7 +30,7 @@ fn test_grades_for_several_students_are_sorted() { #[test] #[ignore] -fn test_grades_when_several_students_have_the_same_grade() { +fn grades_when_several_students_have_the_same_grade() { let mut s = school::School::new(); s.add(2, "Aimee"); s.add(2, "Logan"); @@ -40,14 +40,14 @@ fn test_grades_when_several_students_have_the_same_grade() { #[test] #[ignore] -fn test_grade_for_empty_school() { +fn grade_for_empty_school() { let s = school::School::new(); assert_eq!(s.grade(1), Vec::::new()); } #[test] #[ignore] -fn test_grade_when_no_students_have_that_grade() { +fn grade_when_no_students_have_that_grade() { let mut s = school::School::new(); s.add(7, "Logan"); assert_eq!(s.grade(1), Vec::::new()); @@ -55,7 +55,7 @@ fn test_grade_when_no_students_have_that_grade() { #[test] #[ignore] -fn test_grade_for_one_student() { +fn grade_for_one_student() { let mut s = school::School::new(); s.add(2, "Aimee"); assert_eq!(s.grade(2), to_owned(&["Aimee"])); @@ -63,7 +63,7 @@ fn test_grade_for_one_student() { #[test] #[ignore] -fn test_grade_returns_students_sorted_by_name() { +fn grade_returns_students_sorted_by_name() { let mut s = school::School::new(); s.add(2, "James"); s.add(2, "Blair"); @@ -73,7 +73,7 @@ fn test_grade_returns_students_sorted_by_name() { #[test] #[ignore] -fn test_add_students_to_different_grades() { +fn add_students_to_different_grades() { let mut s = school::School::new(); s.add(3, "Chelsea"); s.add(7, "Logan"); diff --git a/exercises/practice/grains/.articles/performance/code/main.rs b/exercises/practice/grains/.articles/performance/code/main.rs index 3da5fcdcd..f62261bb0 100644 --- a/exercises/practice/grains/.articles/performance/code/main.rs +++ b/exercises/practice/grains/.articles/performance/code/main.rs @@ -41,31 +41,31 @@ pub fn total_pow_for() -> u64 { } #[bench] -fn test_square_bit(b: &mut Bencher) { +fn square_bit(b: &mut Bencher) { b.iter(|| square_bit(64)); } #[bench] -fn test_total_bit(b: &mut Bencher) { +fn total_bit(b: &mut Bencher) { b.iter(|| total_bit()); } #[bench] -fn test_square_pow(b: &mut Bencher) { +fn square_pow(b: &mut Bencher) { b.iter(|| square_pow(64)); } #[bench] -fn test_total_pow_u128(b: &mut Bencher) { +fn total_pow_u128(b: &mut Bencher) { b.iter(|| total_pow_u128()); } #[bench] -fn test_total_pow_fold(b: &mut Bencher) { +fn total_pow_fold(b: &mut Bencher) { b.iter(|| total_pow_fold()); } #[bench] -fn test_total_pow_for(b: &mut Bencher) { +fn total_pow_for(b: &mut Bencher) { b.iter(|| total_pow_for()); } diff --git a/exercises/practice/grains/.articles/performance/content.md b/exercises/practice/grains/.articles/performance/content.md index a337edcdd..a29997f72 100644 --- a/exercises/practice/grains/.articles/performance/content.md +++ b/exercises/practice/grains/.articles/performance/content.md @@ -14,12 +14,12 @@ Varieties of `pow` that iterate and call `square` were also benchmarked. To benchmark the approaches, we wrote a [small benchmark application][benchmark-application]. ``` -test test_square_bit ... bench: 0 ns/iter (+/- 0) -test test_total_bit ... bench: 0 ns/iter (+/- 0) -test test_square_pow ... bench: 0 ns/iter (+/- 0) -test test_total_pow_u128 ... bench: 0 ns/iter (+/- 0) -test test_total_pow_fold ... bench: 215 ns/iter (+/- 7) -test test_total_pow_for ... bench: 263 ns/iter (+/- 46) +test square_bit ... bench: 0 ns/iter (+/- 0) +test total_bit ... bench: 0 ns/iter (+/- 0) +test square_pow ... bench: 0 ns/iter (+/- 0) +test total_pow_u128 ... bench: 0 ns/iter (+/- 0) +test total_pow_fold ... bench: 215 ns/iter (+/- 7) +test total_pow_for ... bench: 263 ns/iter (+/- 46) ``` `pow` and bit shifting have the same measurable performance. diff --git a/exercises/practice/grains/.articles/performance/snippet.md b/exercises/practice/grains/.articles/performance/snippet.md index 5dac649d6..0c860e75d 100644 --- a/exercises/practice/grains/.articles/performance/snippet.md +++ b/exercises/practice/grains/.articles/performance/snippet.md @@ -1,8 +1,8 @@ ``` -test test_square_bit ... bench: 0 ns/iter (+/- 0) -test test_total_bit ... bench: 0 ns/iter (+/- 0) -test test_square_pow ... bench: 0 ns/iter (+/- 0) -test test_total_pow_u128 ... bench: 0 ns/iter (+/- 0) -test test_total_pow_fold ... bench: 215 ns/iter (+/- 7) -test test_total_pow_for ... bench: 263 ns/iter (+/- 46) +test square_bit ... bench: 0 ns/iter (+/- 0) +test total_bit ... bench: 0 ns/iter (+/- 0) +test square_pow ... bench: 0 ns/iter (+/- 0) +test total_pow_u128 ... bench: 0 ns/iter (+/- 0) +test total_pow_fold ... bench: 215 ns/iter (+/- 7) +test total_pow_for ... bench: 263 ns/iter (+/- 46) ``` diff --git a/exercises/practice/grains/tests/grains.rs b/exercises/practice/grains/tests/grains.rs index d7eb53a2b..51e08b643 100644 --- a/exercises/practice/grains/tests/grains.rs +++ b/exercises/practice/grains/tests/grains.rs @@ -3,70 +3,62 @@ fn process_square_case(input: u32, expected: u64) { } #[test] -/// 1 -fn test_1() { +fn one() { process_square_case(1, 1); } #[test] #[ignore] -/// 2 -fn test_2() { +fn two() { process_square_case(2, 2); } #[test] #[ignore] -/// 3 -fn test_3() { +fn three() { process_square_case(3, 4); } #[test] #[ignore] -/// 4 -fn test_4() { +fn four() { process_square_case(4, 8); } -//NEW #[test] #[ignore] -/// 16 -fn test_16() { +fn sixteen() { process_square_case(16, 32_768); } #[test] #[ignore] -/// 32 -fn test_32() { +fn thirty_two() { process_square_case(32, 2_147_483_648); } #[test] #[ignore] -/// 64 -fn test_64() { +fn sixty_four() { process_square_case(64, 9_223_372_036_854_775_808); } #[test] #[ignore] #[should_panic(expected = "Square must be between 1 and 64")] -fn test_square_0_raises_an_exception() { +fn square_0_raises_an_exception() { grains::square(0); } #[test] #[ignore] #[should_panic(expected = "Square must be between 1 and 64")] -fn test_square_greater_than_64_raises_an_exception() { +fn square_greater_than_64_raises_an_exception() { grains::square(65); } #[test] #[ignore] -fn test_returns_the_total_number_of_grains_on_the_board() { +fn returns_the_total_number_of_grains_on_the_board() { assert_eq!(grains::total(), 18_446_744_073_709_551_615); } diff --git a/exercises/practice/grep/tests/grep.rs b/exercises/practice/grep/tests/grep.rs index 398c2d8a5..49a6060b7 100644 --- a/exercises/practice/grep/tests/grep.rs +++ b/exercises/practice/grep/tests/grep.rs @@ -102,9 +102,9 @@ fn tear_down_files(files: &[&str]) { /// This macro is here so that every test case had its own set of files to be used in test. /// The approach is to create required files for every test case and to append test name to the -/// file names (so for test with a name 'test_one_file_one_match_no_flags' and a required file +/// file names (so for test with a name 'one_file_one_match_no_flags' and a required file /// 'iliad.txt' there would be created a file with a name -/// 'test_one_file_one_match_no_flags_iliad.txt'). +/// 'one_file_one_match_no_flags_iliad.txt'). /// This allows us to create files for every test case with no intersection between them. /// /// A better way would be to create required set of files at the start of tests run and to @@ -161,24 +161,24 @@ fn process_grep_case(pattern: &str, flags: &[&str], files: &[&str], expected: &[ // Test returning a Result #[test] -fn test_nonexistent_file_returns_error() { +fn nonexistent_file_returns_error() { let pattern = "Agamemnon"; let flags = Flags::new(&[]); - let files = vec!["test_nonexistent_file_returns_error_iliad.txt"]; + let files = vec!["nonexistent_file_returns_error_iliad.txt"]; assert!(grep(pattern, &flags, &files).is_err()); } #[test] #[ignore] -fn test_grep_returns_result() { +fn grep_returns_result() { let pattern = "Agamemnon"; let flags = Flags::new(&[]); - let files = vec!["test_grep_returns_result_iliad.txt"]; + let files = vec!["grep_returns_result_iliad.txt"]; let test_fixture = Fixture::new(&files); @@ -192,7 +192,7 @@ fn test_grep_returns_result() { set_up_test_case!( #[test] #[ignore] - test_one_file_one_match_no_flags( + one_file_one_match_no_flags( pattern = "Agamemnon", flags = [], files = ["iliad.txt"], @@ -203,7 +203,7 @@ set_up_test_case!( set_up_test_case!( #[test] #[ignore] - test_one_file_one_match_print_line_numbers_flag( + one_file_one_match_print_line_numbers_flag( pattern = "Forbidden", flags = ["-n"], files = ["paradise_lost.txt"], @@ -214,7 +214,7 @@ set_up_test_case!( set_up_test_case!( #[test] #[ignore] - test_one_file_one_match_caseinsensitive_flag( + one_file_one_match_caseinsensitive_flag( pattern = "FORBIDDEN", flags = ["-i"], files = ["paradise_lost.txt"], @@ -225,7 +225,7 @@ set_up_test_case!( set_up_test_case!( #[test] #[ignore] - test_one_file_one_match_print_file_names_flag( + one_file_one_match_print_file_names_flag( pattern = "Forbidden", flags = ["-l"], files = ["paradise_lost.txt"], @@ -236,7 +236,7 @@ set_up_test_case!( set_up_test_case!( #[test] #[ignore] - test_one_file_one_match_match_entire_lines_flag( + one_file_one_match_match_entire_lines_flag( pattern = "With loss of Eden, till one greater Man", flags = ["-x"], files = ["paradise_lost.txt"], @@ -247,7 +247,7 @@ set_up_test_case!( set_up_test_case!( #[test] #[ignore] - test_one_file_one_match_multiple_flags( + one_file_one_match_multiple_flags( pattern = "OF ATREUS, Agamemnon, KIng of MEN.", flags = ["-x", "-i", "-n"], files = ["iliad.txt"], @@ -258,7 +258,7 @@ set_up_test_case!( set_up_test_case!( #[test] #[ignore] - test_one_file_several_matches_no_flags( + one_file_several_matches_no_flags( pattern = "may", flags = [], files = ["midsummer_night.txt"], @@ -273,7 +273,7 @@ set_up_test_case!( set_up_test_case!( #[test] #[ignore] - test_one_file_several_matches_print_line_numbers_flag( + one_file_several_matches_print_line_numbers_flag( pattern = "may", flags = ["-n"], files = ["midsummer_night.txt"], @@ -288,7 +288,7 @@ set_up_test_case!( set_up_test_case!( #[test] #[ignore] - test_one_file_several_matches_match_entire_lines_flag( + one_file_several_matches_match_entire_lines_flag( pattern = "may", flags = ["-x"], files = ["midsummer_night.txt"], @@ -299,7 +299,7 @@ set_up_test_case!( set_up_test_case!( #[test] #[ignore] - test_one_file_several_matches_caseinsensitive_flag( + one_file_several_matches_caseinsensitive_flag( pattern = "ACHILLES", flags = ["-i"], files = ["iliad.txt"], @@ -313,7 +313,7 @@ set_up_test_case!( set_up_test_case!( #[test] #[ignore] - test_one_file_several_matches_inverted_flag( + one_file_several_matches_inverted_flag( pattern = "Of", flags = ["-v"], files = ["paradise_lost.txt"], @@ -330,7 +330,7 @@ set_up_test_case!( set_up_test_case!( #[test] #[ignore] - test_one_file_no_matches_various_flags( + one_file_no_matches_various_flags( pattern = "Gandalf", flags = ["-n", "-l", "-x", "-i"], files = ["iliad.txt"], @@ -341,7 +341,7 @@ set_up_test_case!( set_up_test_case!( #[test] #[ignore] - test_one_file_several_matches_inverted_and_match_entire_lines_flags( + one_file_several_matches_inverted_and_match_entire_lines_flags( pattern = "Illustrious into Ades premature,", flags = ["-x", "-v"], files = ["iliad.txt"], @@ -361,7 +361,7 @@ set_up_test_case!( set_up_test_case!( #[test] #[ignore] - test_one_file_one_match_file_flag_takes_precedence_over_line_flag( + one_file_one_match_file_flag_takes_precedence_over_line_flag( pattern = "ten", flags = ["-n", "-l"], files = ["iliad.txt"], @@ -374,7 +374,7 @@ set_up_test_case!( set_up_test_case!( #[test] #[ignore] - test_multiple_files_one_match_no_flags( + multiple_files_one_match_no_flags( pattern = "Agamemnon", flags = [], files = ["iliad.txt", "midsummer_night.txt", "paradise_lost.txt"], @@ -385,7 +385,7 @@ set_up_test_case!( set_up_test_case!( #[test] #[ignore] - test_multiple_files_several_matches_no_flags( + multiple_files_several_matches_no_flags( pattern = "may", flags = [], files = ["iliad.txt", "midsummer_night.txt", "paradise_lost.txt"], @@ -400,7 +400,7 @@ set_up_test_case!( set_up_test_case!( #[test] #[ignore] - test_multiple_files_several_matches_print_line_numbers_flag( + multiple_files_several_matches_print_line_numbers_flag( pattern = "that", flags = ["-n"], files = ["iliad.txt", "midsummer_night.txt", "paradise_lost.txt"], @@ -416,7 +416,7 @@ set_up_test_case!( set_up_test_case!( #[test] #[ignore] - test_multiple_files_one_match_print_file_names_flag( + multiple_files_one_match_print_file_names_flag( pattern = "who", flags = ["-l"], files = ["iliad.txt", "midsummer_night.txt", "paradise_lost.txt"], @@ -427,7 +427,7 @@ set_up_test_case!( set_up_test_case!( #[test] #[ignore] - test_multiple_files_several_matches_caseinsensitive_flag( + multiple_files_several_matches_caseinsensitive_flag( pattern = "TO", flags = ["-i"], files = ["iliad.txt", "midsummer_night.txt", "paradise_lost.txt"], @@ -449,7 +449,7 @@ set_up_test_case!( set_up_test_case!( #[test] #[ignore] - test_multiple_files_several_matches_caseinsensitive_flag_utf8( + multiple_files_several_matches_caseinsensitive_flag_utf8( pattern = "В", // This letter stands for cyrillic 'Ve' and not latin 'B'. Therefore there should be no matches from paradise_lost.txt flags = ["-i"], files = ["paradise_lost.txt", "in_the_white_night.txt"], @@ -465,7 +465,7 @@ set_up_test_case!( set_up_test_case!( #[test] #[ignore] - test_multiple_files_several_matches_inverted_flag( + multiple_files_several_matches_inverted_flag( pattern = "a", flags = ["-v"], files = ["iliad.txt", "midsummer_night.txt", "paradise_lost.txt"], @@ -480,7 +480,7 @@ set_up_test_case!( set_up_test_case!( #[test] #[ignore] - test_multiple_files_one_match_match_entire_lines_flag( + multiple_files_one_match_match_entire_lines_flag( pattern = "But I beseech your grace that I may know", flags = ["-x"], files = ["iliad.txt", "midsummer_night.txt", "paradise_lost.txt"], @@ -491,7 +491,7 @@ set_up_test_case!( set_up_test_case!( #[test] #[ignore] - test_multiple_files_one_match_multiple_flags( + multiple_files_one_match_multiple_flags( pattern = "WITH LOSS OF EDEN, TILL ONE GREATER MAN", flags = ["-n", "-i", "-x"], files = ["iliad.txt", "midsummer_night.txt", "paradise_lost.txt"], @@ -502,7 +502,7 @@ set_up_test_case!( set_up_test_case!( #[test] #[ignore] - test_multiple_files_no_matches_various_flags( + multiple_files_no_matches_various_flags( pattern = "Frodo", flags = ["-n", "-i", "-x", "-l"], files = ["iliad.txt", "midsummer_night.txt", "paradise_lost.txt"], @@ -513,7 +513,7 @@ set_up_test_case!( set_up_test_case!( #[test] #[ignore] - test_multiple_files_several_matches_file_flag_takes_precedence_over_line_number_flag( + multiple_files_several_matches_file_flag_takes_precedence_over_line_number_flag( pattern = "who", flags = ["-n", "-l"], files = ["iliad.txt", "midsummer_night.txt", "paradise_lost.txt"], @@ -524,7 +524,7 @@ set_up_test_case!( set_up_test_case!( #[test] #[ignore] - test_multiple_files_several_matches_inverted_and_match_entire_lines_flags( + multiple_files_several_matches_inverted_and_match_entire_lines_flags( pattern = "Illustrious into Ades premature,", flags = ["-x", "-v"], files = ["iliad.txt", "midsummer_night.txt", "paradise_lost.txt"], diff --git a/exercises/practice/hamming/tests/hamming.rs b/exercises/practice/hamming/tests/hamming.rs index 3e744ccd2..bcccb8311 100644 --- a/exercises/practice/hamming/tests/hamming.rs +++ b/exercises/practice/hamming/tests/hamming.rs @@ -6,84 +6,84 @@ fn process_distance_case(strand_pair: [&str; 2], expected_distance: Option bool { } #[bench] -fn test_check_hash(b: &mut Bencher) { +fn check_hash(b: &mut Bencher) { b.iter(|| check_hash("thumbscrew-japingly")); } #[bench] -fn test_check_bits(b: &mut Bencher) { +fn check_bits(b: &mut Bencher) { b.iter(|| check_bits("thumbscrew-japingly")); } #[bench] -fn test_check_bits_func(b: &mut Bencher) { +fn check_bits_func(b: &mut Bencher) { b.iter(|| check_bits_func("thumbscrew-japingly")); } #[bench] -fn test_check_hash_filtermap(b: &mut Bencher) { +fn check_hash_filtermap(b: &mut Bencher) { b.iter(|| check_hash_filtermap("thumbscrew-japingly")); } #[bench] -fn test_check_bits_func_filter_map(b: &mut Bencher) { +fn check_bits_func_filter_map(b: &mut Bencher) { b.iter(|| check_bits_func_filter_map("thumbscrew-japingly")); } #[bench] -fn test_check_hash_unicode(b: &mut Bencher) { +fn check_hash_unicode(b: &mut Bencher) { b.iter(|| check_hash_unicode("thumbscrew-japingly")); } diff --git a/exercises/practice/isogram/.articles/performance/content.md b/exercises/practice/isogram/.articles/performance/content.md index ed5f0c135..6a780051b 100644 --- a/exercises/practice/isogram/.articles/performance/content.md +++ b/exercises/practice/isogram/.articles/performance/content.md @@ -17,12 +17,12 @@ At the time of this writing, all tests use [ASCII][ascii] characters, so the let To benchmark the approaches, we wrote a [small benchmark application][benchmark-application]. ``` -test test_check_hash ... bench: 952 ns/iter (+/- 176) // using filter, then map -test test_check_bits ... bench: 17 ns/iter (+/- 3) -test test_check_bits_func ... bench: 22 ns/iter (+/- 4) // using filter_map -test test_check_hash_filtermap ... bench: 970 ns/iter (+/- 216) // using filter_map -test test_check_bits_func_filter_map ... bench: 37 ns/iter (+/- 7) // using filter, then map -test test_check_hash_unicode ... bench: 1,052 ns/iter (+/- 327) +test check_hash ... bench: 952 ns/iter (+/- 176) // using filter, then map +test check_bits ... bench: 17 ns/iter (+/- 3) +test check_bits_func ... bench: 22 ns/iter (+/- 4) // using filter_map +test check_hash_filtermap ... bench: 970 ns/iter (+/- 216) // using filter_map +test check_bits_func_filter_map ... bench: 37 ns/iter (+/- 7) // using filter, then map +test check_hash_unicode ... bench: 1,052 ns/iter (+/- 327) ``` The `HashSet` approach was also benchmarked using [`filter_map`][filter-map] instead of `filter` and `map`. diff --git a/exercises/practice/isogram/.articles/performance/snippet.md b/exercises/practice/isogram/.articles/performance/snippet.md index 818e88b2d..397764ef9 100644 --- a/exercises/practice/isogram/.articles/performance/snippet.md +++ b/exercises/practice/isogram/.articles/performance/snippet.md @@ -1,8 +1,8 @@ ``` -test test_check_hash ... bench: 952 ns/iter (+/- 176) -test test_check_bits ... bench: 17 ns/iter (+/- 3) -test test_check_bits_func ... bench: 22 ns/iter (+/- 4) -test test_check_hash_filtermap ... bench: 970 ns/iter (+/- 216) -test test_check_bits_func_filter_map ... bench: 37 ns/iter (+/- 7) -test test_check_hash_unicode ... bench: 1,052 ns/iter (+/- 327) +test check_hash ... bench: 952 ns/iter (+/- 176) +test check_bits ... bench: 17 ns/iter (+/- 3) +test check_bits_func ... bench: 22 ns/iter (+/- 4) +test check_hash_filtermap ... bench: 970 ns/iter (+/- 216) +test check_bits_func_filter_map ... bench: 37 ns/iter (+/- 7) +test check_hash_unicode ... bench: 1,052 ns/iter (+/- 327) ``` diff --git a/exercises/practice/kindergarten-garden/tests/kindergarten-garden.rs b/exercises/practice/kindergarten-garden/tests/kindergarten-garden.rs index 054d5c72e..644b29cd7 100644 --- a/exercises/practice/kindergarten-garden/tests/kindergarten-garden.rs +++ b/exercises/practice/kindergarten-garden/tests/kindergarten-garden.rs @@ -1,7 +1,7 @@ use kindergarten_garden::*; #[test] -fn test_garden_with_single_student() { +fn garden_with_single_student() { let diagram = "RC GG"; let student = "Alice"; @@ -11,7 +11,7 @@ GG"; #[test] #[ignore] -fn test_different_garden_with_single_student() { +fn different_garden_with_single_student() { let diagram = "VC RC"; let student = "Alice"; @@ -21,7 +21,7 @@ RC"; #[test] #[ignore] -fn test_garden_with_two_students() { +fn garden_with_two_students() { let diagram = "VVCG VVRC"; let student = "Bob"; @@ -31,7 +31,7 @@ VVRC"; #[test] #[ignore] -fn test_second_students_garden() { +fn second_students_garden() { let diagram = "VVCCGG VVCCGG"; let student = "Bob"; @@ -41,7 +41,7 @@ VVCCGG"; #[test] #[ignore] -fn test_third_students_garden() { +fn third_students_garden() { let diagram = "VVCCGG VVCCGG"; let student = "Charlie"; @@ -51,7 +51,7 @@ VVCCGG"; #[test] #[ignore] -fn test_for_alice_first_students_garden() { +fn for_alice_first_students_garden() { let diagram = "VRCGVVRVCGGCCGVRGCVCGCGV VRCCCGCRRGVCGCRVVCVGCGCV"; let student = "Alice"; @@ -61,7 +61,7 @@ VRCCCGCRRGVCGCRVVCVGCGCV"; #[test] #[ignore] -fn test_for_bob_second_students_garden() { +fn for_bob_second_students_garden() { let diagram = "VRCGVVRVCGGCCGVRGCVCGCGV VRCCCGCRRGVCGCRVVCVGCGCV"; let student = "Bob"; @@ -71,7 +71,7 @@ VRCCCGCRRGVCGCRVVCVGCGCV"; #[test] #[ignore] -fn test_for_charlie() { +fn for_charlie() { let diagram = "VRCGVVRVCGGCCGVRGCVCGCGV VRCCCGCRRGVCGCRVVCVGCGCV"; let student = "Charlie"; @@ -81,7 +81,7 @@ VRCCCGCRRGVCGCRVVCVGCGCV"; #[test] #[ignore] -fn test_for_david() { +fn for_david() { let diagram = "VRCGVVRVCGGCCGVRGCVCGCGV VRCCCGCRRGVCGCRVVCVGCGCV"; let student = "David"; @@ -91,7 +91,7 @@ VRCCCGCRRGVCGCRVVCVGCGCV"; #[test] #[ignore] -fn test_for_eve() { +fn for_eve() { let diagram = "VRCGVVRVCGGCCGVRGCVCGCGV VRCCCGCRRGVCGCRVVCVGCGCV"; let student = "Eve"; @@ -101,7 +101,7 @@ VRCCCGCRRGVCGCRVVCVGCGCV"; #[test] #[ignore] -fn test_for_fred() { +fn for_fred() { let diagram = "VRCGVVRVCGGCCGVRGCVCGCGV VRCCCGCRRGVCGCRVVCVGCGCV"; let student = "Fred"; @@ -111,7 +111,7 @@ VRCCCGCRRGVCGCRVVCVGCGCV"; #[test] #[ignore] -fn test_for_ginny() { +fn for_ginny() { let diagram = "VRCGVVRVCGGCCGVRGCVCGCGV VRCCCGCRRGVCGCRVVCVGCGCV"; let student = "Ginny"; @@ -121,7 +121,7 @@ VRCCCGCRRGVCGCRVVCVGCGCV"; #[test] #[ignore] -fn test_for_harriet() { +fn for_harriet() { let diagram = "VRCGVVRVCGGCCGVRGCVCGCGV VRCCCGCRRGVCGCRVVCVGCGCV"; let student = "Harriet"; @@ -131,7 +131,7 @@ VRCCCGCRRGVCGCRVVCVGCGCV"; #[test] #[ignore] -fn test_for_ileana() { +fn for_ileana() { let diagram = "VRCGVVRVCGGCCGVRGCVCGCGV VRCCCGCRRGVCGCRVVCVGCGCV"; let student = "Ileana"; @@ -141,7 +141,7 @@ VRCCCGCRRGVCGCRVVCVGCGCV"; #[test] #[ignore] -fn test_for_joseph() { +fn for_joseph() { let diagram = "VRCGVVRVCGGCCGVRGCVCGCGV VRCCCGCRRGVCGCRVVCVGCGCV"; let student = "Joseph"; @@ -151,7 +151,7 @@ VRCCCGCRRGVCGCRVVCVGCGCV"; #[test] #[ignore] -fn test_for_kincaid_second_to_last_students_garden() { +fn for_kincaid_second_to_last_students_garden() { let diagram = "VRCGVVRVCGGCCGVRGCVCGCGV VRCCCGCRRGVCGCRVVCVGCGCV"; let student = "Kincaid"; @@ -161,7 +161,7 @@ VRCCCGCRRGVCGCRVVCVGCGCV"; #[test] #[ignore] -fn test_for_larry_last_students_garden() { +fn for_larry_last_students_garden() { let diagram = "VRCGVVRVCGGCCGVRGCVCGCGV VRCCCGCRRGVCGCRVVCVGCGCV"; let student = "Larry"; diff --git a/exercises/practice/knapsack/tests/knapsack.rs b/exercises/practice/knapsack/tests/knapsack.rs index b351715bd..05b1b9b51 100644 --- a/exercises/practice/knapsack/tests/knapsack.rs +++ b/exercises/practice/knapsack/tests/knapsack.rs @@ -1,7 +1,7 @@ use knapsack::*; #[test] -fn test_example_knapsack() { +fn example_knapsack() { let max_weight = 10; let items = [ Item { @@ -27,7 +27,7 @@ fn test_example_knapsack() { #[test] #[ignore] -fn test_no_items() { +fn no_items() { let max_weight = 100; let items = []; @@ -36,7 +36,7 @@ fn test_no_items() { #[test] #[ignore] -fn test_one_item_too_heavy() { +fn one_item_too_heavy() { let max_weight = 10; let items = [Item { weight: 100, @@ -48,7 +48,7 @@ fn test_one_item_too_heavy() { #[test] #[ignore] -fn test_five_items_cannot_be_greedy_by_weight() { +fn five_items_cannot_be_greedy_by_weight() { let max_weight = 10; let items = [ Item { @@ -78,7 +78,7 @@ fn test_five_items_cannot_be_greedy_by_weight() { #[test] #[ignore] -fn test_five_items_cannot_be_greedy_by_value() { +fn five_items_cannot_be_greedy_by_value() { let max_weight = 10; let items = [ Item { @@ -108,7 +108,7 @@ fn test_five_items_cannot_be_greedy_by_value() { #[test] #[ignore] -fn test_8_items() { +fn eight_items() { let max_weight = 104; let items = [ Item { @@ -150,7 +150,7 @@ fn test_8_items() { #[test] #[ignore] -fn test_15_items() { +fn fifteen_items() { let max_weight = 750; let items = [ Item { diff --git a/exercises/practice/leap/.articles/performance/code/main.rs b/exercises/practice/leap/.articles/performance/code/main.rs index c1628cf4f..4ed266f5a 100644 --- a/exercises/practice/leap/.articles/performance/code/main.rs +++ b/exercises/practice/leap/.articles/performance/code/main.rs @@ -46,31 +46,31 @@ pub fn is_leap_year_naive(year: u64) -> bool { } #[bench] -fn test_ternary(b: &mut Bencher) { +fn ternary(b: &mut Bencher) { b.iter(|| is_leap_year(2000)); } #[bench] -fn test_one_line(b: &mut Bencher) { +fn one_line(b: &mut Bencher) { b.iter(|| is_leap_year_one_line(2000)); } #[bench] -fn test_match(b: &mut Bencher) { +fn match(b: &mut Bencher) { b.iter(|| is_leap_year_match(2000)); } #[bench] -fn test_time(b: &mut Bencher) { +fn time(b: &mut Bencher) { b.iter(|| is_leap_year_time(2000)); } #[bench] -fn test_chrono(b: &mut Bencher) { +fn chrono(b: &mut Bencher) { b.iter(|| is_leap_year_chrono(2000)); } #[bench] -fn test_naive(b: &mut Bencher) { +fn naive(b: &mut Bencher) { b.iter(|| is_leap_year_naive(2000)); } diff --git a/exercises/practice/leap/.articles/performance/content.md b/exercises/practice/leap/.articles/performance/content.md index 2edb23c34..53b79f4ff 100644 --- a/exercises/practice/leap/.articles/performance/content.md +++ b/exercises/practice/leap/.articles/performance/content.md @@ -18,12 +18,12 @@ For our performance investigation, we'll also include two other approaches: To benchmark the approaches, we wrote a [small benchmark application][benchmark-application]. ``` -test test_ternary ... bench: 0 ns/iter (+/- 0) -test test_one_line ... bench: 0 ns/iter (+/- 0) -test test_match ... bench: 0 ns/iter (+/- 0) -test test_time ... bench: 30 ns/iter (+/- 9) -test test_chrono ... bench: 18 ns/iter (+/- 3) -test test_naive ... bench: 18 ns/iter (+/- 1) +test ternary ... bench: 0 ns/iter (+/- 0) +test one_line ... bench: 0 ns/iter (+/- 0) +test match ... bench: 0 ns/iter (+/- 0) +test time ... bench: 30 ns/iter (+/- 9) +test chrono ... bench: 18 ns/iter (+/- 3) +test naive ... bench: 18 ns/iter (+/- 1) ``` The three main approaches were identical in measurable performance. diff --git a/exercises/practice/leap/.articles/performance/snippet.md b/exercises/practice/leap/.articles/performance/snippet.md index de3e4d3c8..ddb5f0085 100644 --- a/exercises/practice/leap/.articles/performance/snippet.md +++ b/exercises/practice/leap/.articles/performance/snippet.md @@ -1,7 +1,7 @@ ``` -test test_ternary ... bench: 0 ns/iter (+/- 0) -test test_one_line ... bench: 0 ns/iter (+/- 0) -test test_match ... bench: 0 ns/iter (+/- 0) -test test_time ... bench: 30 ns/iter (+/- 9) -test test_chrono ... bench: 19 ns/iter (+/- 1) +test ternary ... bench: 0 ns/iter (+/- 0) +test one_line ... bench: 0 ns/iter (+/- 0) +test match ... bench: 0 ns/iter (+/- 0) +test time ... bench: 30 ns/iter (+/- 9) +test chrono ... bench: 19 ns/iter (+/- 1) ``` diff --git a/exercises/practice/leap/tests/leap.rs b/exercises/practice/leap/tests/leap.rs index 4c7ee2ce6..88b5cae7f 100644 --- a/exercises/practice/leap/tests/leap.rs +++ b/exercises/practice/leap/tests/leap.rs @@ -3,67 +3,67 @@ fn process_leapyear_case(year: u64, expected: bool) { } #[test] -fn test_year_not_divisible_by_4_common_year() { +fn year_not_divisible_by_4_common_year() { process_leapyear_case(2015, false); } #[test] #[ignore] -fn test_year_divisible_by_2_not_divisible_by_4_in_common_year() { +fn year_divisible_by_2_not_divisible_by_4_in_common_year() { process_leapyear_case(1970, false); } #[test] #[ignore] -fn test_year_divisible_by_4_not_divisible_by_100_leap_year() { +fn year_divisible_by_4_not_divisible_by_100_leap_year() { process_leapyear_case(1996, true); } #[test] #[ignore] -fn test_year_divisible_by_4_and_5_is_still_a_leap_year() { +fn year_divisible_by_4_and_5_is_still_a_leap_year() { process_leapyear_case(1960, true); } #[test] #[ignore] -fn test_year_divisible_by_100_not_divisible_by_400_common_year() { +fn year_divisible_by_100_not_divisible_by_400_common_year() { process_leapyear_case(2100, false); } #[test] #[ignore] -fn test_year_divisible_by_100_but_not_by_3_is_still_not_a_leap_year() { +fn year_divisible_by_100_but_not_by_3_is_still_not_a_leap_year() { process_leapyear_case(1900, false); } #[test] #[ignore] -fn test_year_divisible_by_400_leap_year() { +fn year_divisible_by_400_leap_year() { process_leapyear_case(2000, true); } #[test] #[ignore] -fn test_year_divisible_by_400_but_not_by_125_is_still_a_leap_year() { +fn year_divisible_by_400_but_not_by_125_is_still_a_leap_year() { process_leapyear_case(2400, true); } #[test] #[ignore] -fn test_year_divisible_by_200_not_divisible_by_400_common_year() { +fn year_divisible_by_200_not_divisible_by_400_common_year() { process_leapyear_case(1800, false); } #[test] #[ignore] -fn test_any_old_year() { +fn any_old_year() { process_leapyear_case(1997, false); } #[test] #[ignore] -fn test_early_years() { +fn early_years() { process_leapyear_case(1, false); process_leapyear_case(4, true); process_leapyear_case(100, false); @@ -73,7 +73,7 @@ fn test_early_years() { #[test] #[ignore] -fn test_century() { +fn century() { process_leapyear_case(1700, false); process_leapyear_case(1800, false); process_leapyear_case(1900, false); @@ -81,7 +81,7 @@ fn test_century() { #[test] #[ignore] -fn test_exceptional_centuries() { +fn exceptional_centuries() { process_leapyear_case(1600, true); process_leapyear_case(2000, true); process_leapyear_case(2400, true); @@ -89,7 +89,7 @@ fn test_exceptional_centuries() { #[test] #[ignore] -fn test_years_1600_to_1699() { +fn years_1600_to_1699() { let incorrect_years = (1600..1700) .filter(|&year| leap::is_leap_year(year) != (year % 4 == 0)) .collect::>(); diff --git a/exercises/practice/luhn-from/tests/luhn-from.rs b/exercises/practice/luhn-from/tests/luhn-from.rs index a94e16da4..b5f42b3d9 100644 --- a/exercises/practice/luhn-from/tests/luhn-from.rs +++ b/exercises/practice/luhn-from/tests/luhn-from.rs @@ -100,6 +100,6 @@ fn strings_that_contain_non_digits_are_invalid() { #[test] #[ignore] -fn test_input_digit_9_is_still_correctly_converted_to_output_digit_9() { +fn input_digit_9_is_still_correctly_converted_to_output_digit_9() { assert!(Luhn::from("091").is_valid()); } diff --git a/exercises/practice/luhn-trait/tests/luhn-trait.rs b/exercises/practice/luhn-trait/tests/luhn-trait.rs index bc5bd9cc4..dd0454c17 100644 --- a/exercises/practice/luhn-trait/tests/luhn-trait.rs +++ b/exercises/practice/luhn-trait/tests/luhn-trait.rs @@ -58,6 +58,6 @@ fn you_can_validate_from_a_usize() { #[test] #[ignore] -fn test_input_digit_9_is_still_correctly_converted_to_output_digit_9() { +fn input_digit_9_is_still_correctly_converted_to_output_digit_9() { assert!("091".valid_luhn()); } diff --git a/exercises/practice/luhn/tests/luhn.rs b/exercises/practice/luhn/tests/luhn.rs index 1eab9c40f..c2ce6839b 100644 --- a/exercises/practice/luhn/tests/luhn.rs +++ b/exercises/practice/luhn/tests/luhn.rs @@ -5,49 +5,49 @@ fn process_valid_case(number: &str, is_luhn_expected: bool) { } #[test] -fn test_single_digit_strings_can_not_be_valid() { +fn single_digit_strings_can_not_be_valid() { process_valid_case("1", false); } #[test] #[ignore] -fn test_a_single_zero_is_invalid() { +fn a_single_zero_is_invalid() { process_valid_case("0", false); } #[test] #[ignore] -fn test_a_simple_valid_sin_that_remains_valid_if_reversed() { +fn a_simple_valid_sin_that_remains_valid_if_reversed() { process_valid_case("059", true); } #[test] #[ignore] -fn test_a_simple_valid_sin_that_becomes_invalid_if_reversed() { +fn a_simple_valid_sin_that_becomes_invalid_if_reversed() { process_valid_case("59", true); } #[test] #[ignore] -fn test_a_valid_canadian_sin() { +fn a_valid_canadian_sin() { process_valid_case("055 444 285", true); } #[test] #[ignore] -fn test_invalid_canadian_sin() { +fn invalid_canadian_sin() { process_valid_case("055 444 286", false); } #[test] #[ignore] -fn test_invalid_credit_card() { +fn invalid_credit_card() { process_valid_case("8273 1232 7352 0569", false); } #[test] #[ignore] -fn test_valid_number_with_an_even_number_of_digits() { +fn valid_number_with_an_even_number_of_digits() { process_valid_case("095 245 88", true); } @@ -59,7 +59,7 @@ fn strings_that_contain_non_digits_are_invalid() { #[test] #[ignore] -fn test_valid_strings_with_punctuation_included_become_invalid() { +fn valid_strings_with_punctuation_included_become_invalid() { process_valid_case("055-444-285", false); } @@ -71,19 +71,19 @@ fn symbols_are_invalid() { #[test] #[ignore] -fn test_single_zero_with_space_is_invalid() { +fn single_zero_with_space_is_invalid() { process_valid_case(" 0", false); } #[test] #[ignore] -fn test_more_than_a_single_zero_is_valid() { +fn more_than_a_single_zero_is_valid() { process_valid_case("0000 0", true); } #[test] #[ignore] -fn test_input_digit_9_is_correctly_converted_to_output_digit_9() { +fn input_digit_9_is_correctly_converted_to_output_digit_9() { process_valid_case("091", true); } @@ -92,21 +92,21 @@ fn test_input_digit_9_is_correctly_converted_to_output_digit_9() { /// using ASCII value for doubled non-digit isn't allowed /// Convert non-digits to their ASCII values and then offset them by 48 sometimes accidentally declare an invalid string to be valid. /// This test is designed to avoid that solution. -fn test_using_ascii_value_for_doubled_nondigit_isnt_allowed() { +fn using_ascii_value_for_doubled_nondigit_isnt_allowed() { process_valid_case(":9", false); } #[test] #[ignore] /// valid strings with a non-digit added at the end become invalid -fn test_valid_strings_with_a_nondigit_added_at_the_end_become_invalid() { +fn valid_strings_with_a_nondigit_added_at_the_end_become_invalid() { process_valid_case("059a", false); } #[test] #[ignore] /// valid strings with symbols included become invalid -fn test_valid_strings_with_symbols_included_become_invalid() { +fn valid_strings_with_symbols_included_become_invalid() { process_valid_case("055# 444$ 285", false); } @@ -115,27 +115,27 @@ fn test_valid_strings_with_symbols_included_become_invalid() { /// using ASCII value for non-doubled non-digit isn't allowed /// Convert non-digits to their ASCII values and then offset them by 48 sometimes accidentally declare an invalid string to be valid. /// This test is designed to avoid that solution. -fn test_using_ascii_value_for_nondoubled_nondigit_isnt_allowed() { +fn using_ascii_value_for_nondoubled_nondigit_isnt_allowed() { process_valid_case("055b 444 285", false); } #[test] #[ignore] /// valid number with an odd number of spaces -fn test_valid_number_with_an_odd_number_of_spaces() { +fn valid_number_with_an_odd_number_of_spaces() { process_valid_case("234 567 891 234", true); } #[test] #[ignore] /// non-numeric, non-space char in the middle with a sum that's divisible by 10 isn't allowed -fn test_invalid_char_in_middle_with_sum_divisible_by_10_isnt_allowed() { +fn invalid_char_in_middle_with_sum_divisible_by_10_isnt_allowed() { process_valid_case("59%59", false); } #[test] #[ignore] /// unicode numeric characters are not allowed in a otherwise valid number -fn test_valid_strings_with_numeric_unicode_characters_become_invalid() { +fn valid_strings_with_numeric_unicode_characters_become_invalid() { process_valid_case("1249①", false); } diff --git a/exercises/practice/macros/tests/macros.rs b/exercises/practice/macros/tests/macros.rs index b546174f2..8e212399a 100644 --- a/exercises/practice/macros/tests/macros.rs +++ b/exercises/practice/macros/tests/macros.rs @@ -2,7 +2,7 @@ use macros::hashmap; use std::collections::HashMap; #[test] -fn test_empty() { +fn empty() { let expected: HashMap = HashMap::new(); let computed: HashMap = hashmap!(); assert_eq!(computed, expected); @@ -10,7 +10,7 @@ fn test_empty() { #[test] #[ignore] -fn test_single() { +fn single() { let mut expected = HashMap::new(); expected.insert(1, "one"); assert_eq!(hashmap!(1 => "one"), expected); @@ -18,7 +18,7 @@ fn test_single() { #[test] #[ignore] -fn test_no_trailing_comma() { +fn no_trailing_comma() { let mut expected = HashMap::new(); expected.insert(1, "one"); expected.insert(2, "two"); @@ -27,7 +27,7 @@ fn test_no_trailing_comma() { #[test] #[ignore] -fn test_trailing_comma() { +fn trailing_comma() { let mut expected = HashMap::new(); expected.insert('h', 89); expected.insert('a', 1); @@ -46,7 +46,7 @@ fn test_trailing_comma() { #[test] #[ignore] -fn test_nested() { +fn nested() { let mut expected = HashMap::new(); expected.insert("non-empty", { let mut subhashmap = HashMap::new(); @@ -80,7 +80,7 @@ mod test { #[test] #[ignore] - fn test_macro_out_of_scope() { + fn macro_out_of_scope() { let _empty: ::std::collections::HashMap<(), ()> = macros::hashmap!(); let _without_comma = macros::hashmap!(23=> 623, 34 => 21); let _with_trailing = macros::hashmap!(23 => 623, 34 => 21,); @@ -89,7 +89,7 @@ mod test { #[test] #[ignore] -fn test_type_override() { +fn type_override() { // The macro should always use std::collections::HashMap and ignore crate::std::collections::HashMap mod std { pub mod collections { @@ -116,61 +116,61 @@ fn test_type_override() { #[test] #[ignore] -fn test_compile_fails_comma_sep() { +fn compile_fails_comma_sep() { simple_trybuild::compile_fail("comma-sep.rs"); } #[test] #[ignore] -fn test_compile_fails_double_commas() { +fn compile_fails_double_commas() { simple_trybuild::compile_fail("double-commas.rs"); } #[test] #[ignore] -fn test_compile_fails_only_comma() { +fn compile_fails_only_comma() { simple_trybuild::compile_fail("only-comma.rs"); } #[test] #[ignore] -fn test_compile_fails_single_argument() { +fn compile_fails_single_argument() { simple_trybuild::compile_fail("single-argument.rs"); } #[test] #[ignore] -fn test_compile_fails_triple_arguments() { +fn compile_fails_triple_arguments() { simple_trybuild::compile_fail("triple-arguments.rs"); } #[test] #[ignore] -fn test_compile_fails_only_arrow() { +fn compile_fails_only_arrow() { simple_trybuild::compile_fail("only-arrow.rs"); } #[test] #[ignore] -fn test_compile_fails_two_arrows() { +fn compile_fails_two_arrows() { simple_trybuild::compile_fail("two-arrows.rs"); } #[test] #[ignore] -fn test_compile_fails_leading_comma() { +fn compile_fails_leading_comma() { simple_trybuild::compile_fail("leading-comma.rs"); } #[test] #[ignore] -fn test_compile_fails_no_comma() { +fn compile_fails_no_comma() { simple_trybuild::compile_fail("no-comma.rs"); } #[test] #[ignore] -fn test_compile_fails_missing_argument() { +fn compile_fails_missing_argument() { simple_trybuild::compile_fail("missing-argument.rs"); } diff --git a/exercises/practice/nth-prime/tests/nth-prime.rs b/exercises/practice/nth-prime/tests/nth-prime.rs index 7b8c17e91..c9dfd371a 100644 --- a/exercises/practice/nth-prime/tests/nth-prime.rs +++ b/exercises/practice/nth-prime/tests/nth-prime.rs @@ -1,24 +1,24 @@ use nth_prime as np; #[test] -fn test_first_prime() { +fn first_prime() { assert_eq!(np::nth(0), 2); } #[test] #[ignore] -fn test_second_prime() { +fn second_prime() { assert_eq!(np::nth(1), 3); } #[test] #[ignore] -fn test_sixth_prime() { +fn sixth_prime() { assert_eq!(np::nth(5), 13); } #[test] #[ignore] -fn test_big_prime() { +fn big_prime() { assert_eq!(np::nth(10_000), 104_743); } diff --git a/exercises/practice/nucleotide-codons/tests/nucleotide-codons.rs b/exercises/practice/nucleotide-codons/tests/nucleotide-codons.rs index f585d57e2..bee25bb69 100644 --- a/exercises/practice/nucleotide-codons/tests/nucleotide-codons.rs +++ b/exercises/practice/nucleotide-codons/tests/nucleotide-codons.rs @@ -1,19 +1,19 @@ #[test] -fn test_methionine() { +fn methionine() { let info = nucleotide_codons::parse(make_pairs()); assert_eq!(info.name_for("ATG"), Ok("methionine")); } #[test] #[ignore] -fn test_cysteine_tgt() { +fn cysteine_tgt() { let info = nucleotide_codons::parse(make_pairs()); assert_eq!(info.name_for("TGT"), Ok("cysteine")); } #[test] #[ignore] -fn test_cysteine_tgy() { +fn cysteine_tgy() { // "compressed" name for TGT and TGC let info = nucleotide_codons::parse(make_pairs()); assert_eq!(info.name_for("TGT"), info.name_for("TGY")); @@ -22,28 +22,28 @@ fn test_cysteine_tgy() { #[test] #[ignore] -fn test_stop() { +fn stop() { let info = nucleotide_codons::parse(make_pairs()); assert_eq!(info.name_for("TAA"), Ok("stop codon")); } #[test] #[ignore] -fn test_valine() { +fn valine() { let info = nucleotide_codons::parse(make_pairs()); assert_eq!(info.name_for("GTN"), Ok("valine")); } #[test] #[ignore] -fn test_isoleucine() { +fn isoleucine() { let info = nucleotide_codons::parse(make_pairs()); assert_eq!(info.name_for("ATH"), Ok("isoleucine")); } #[test] #[ignore] -fn test_arginine_name() { +fn arginine_name() { // In arginine CGA can be "compressed" both as CGN and as MGR let info = nucleotide_codons::parse(make_pairs()); assert_eq!(info.name_for("CGA"), Ok("arginine")); diff --git a/exercises/practice/nucleotide-count/tests/nucleotide-count.rs b/exercises/practice/nucleotide-count/tests/nucleotide-count.rs index e910798ad..83b3bf5e2 100644 --- a/exercises/practice/nucleotide-count/tests/nucleotide-count.rs +++ b/exercises/practice/nucleotide-count/tests/nucleotide-count.rs @@ -23,7 +23,7 @@ fn count_returns_result() { #[test] #[ignore] -fn test_count_empty() { +fn count_empty() { assert_eq!(dna::count('A', ""), Ok(0)); } @@ -41,13 +41,13 @@ fn count_invalid_dna() { #[test] #[ignore] -fn test_count_repetitive_cytosine() { +fn count_repetitive_cytosine() { assert_eq!(dna::count('C', "CCCCC"), Ok(5)); } #[test] #[ignore] -fn test_count_only_thymine() { +fn count_only_thymine() { assert_eq!(dna::count('T', "GGGGGTAACCCGG"), Ok(1)); } @@ -59,27 +59,27 @@ fn counts_returns_result() { #[test] #[ignore] -fn test_empty_strand() { +fn empty_strand() { process_nucleotidecounts_case("", &[('A', 0), ('T', 0), ('C', 0), ('G', 0)]); } #[test] #[ignore] /// can count one nucleotide in single-character input -fn test_can_count_one_nucleotide_in_singlecharacter_input() { +fn can_count_one_nucleotide_in_singlecharacter_input() { process_nucleotidecounts_case("G", &[('A', 0), ('C', 0), ('G', 1), ('T', 0)]); } #[test] #[ignore] -fn test_strand_with_repeated_nucleotide() { +fn strand_with_repeated_nucleotide() { process_nucleotidecounts_case("GGGGGGG", &[('A', 0), ('T', 0), ('C', 0), ('G', 7)]); } #[test] #[ignore] /// strand with multiple nucleotides -fn test_strand_with_multiple_nucleotides() { +fn strand_with_multiple_nucleotides() { process_nucleotidecounts_case( "AGCTTTTCATTCTGACTGCAACGGGCAATATGTCTCTGTGTGGATTAAAAAAAGAGTGTCTGATAGCAGC", &[('A', 20), ('T', 21), ('C', 12), ('G', 17)], @@ -95,6 +95,6 @@ fn counts_invalid_nucleotide_results_in_err() { #[test] #[ignore] /// strand with invalid nucleotides -fn test_strand_with_invalid_nucleotides() { +fn strand_with_invalid_nucleotides() { assert_eq!(dna::nucleotide_counts("AGXXACT"), Err('X'),); } diff --git a/exercises/practice/paasio/tests/paasio.rs b/exercises/practice/paasio/tests/paasio.rs index 59fdb9999..9079691cb 100644 --- a/exercises/practice/paasio/tests/paasio.rs +++ b/exercises/practice/paasio/tests/paasio.rs @@ -9,7 +9,7 @@ macro_rules! test_read { $(#[$attr])* #[test] - fn test_read_passthrough() { + fn read_passthrough() { let data = $input; let len = $len; let size = len(&data); @@ -30,7 +30,7 @@ macro_rules! test_read { $(#[$attr])* #[test] - fn test_read_chunks() { + fn read_chunks() { let data = $input; let len = $len; let size = len(&data); @@ -51,7 +51,7 @@ macro_rules! test_read { $(#[$attr])* #[test] - fn test_read_buffered_chunks() { + fn read_buffered_chunks() { let data = $input; let len = $len; let size = len(&data); @@ -85,7 +85,7 @@ macro_rules! test_write { const CHUNK_SIZE: usize = 2; $(#[$attr])* #[test] - fn test_write_passthrough() { + fn write_passthrough() { let data = $input; let len = $len; let size = len(&data); @@ -100,7 +100,7 @@ macro_rules! test_write { $(#[$attr])* #[test] - fn test_sink_oneshot() { + fn sink_oneshot() { let data = $input; let len = $len; let size = len(&data); @@ -114,7 +114,7 @@ macro_rules! test_write { $(#[$attr])* #[test] - fn test_sink_windowed() { + fn sink_windowed() { let data = $input; let len = $len; let size = len(&data); @@ -133,7 +133,7 @@ macro_rules! test_write { $(#[$attr])* #[test] - fn test_sink_buffered_windowed() { + fn sink_buffered_windowed() { let data = $input; let len = $len; let size = len(&data); @@ -159,7 +159,7 @@ macro_rules! test_write { } #[test] -fn test_create_stats() { +fn create_stats() { let mut data: Vec = Vec::new(); let _ = paasio::ReadStats::new(data.as_slice()); let _ = paasio::WriteStats::new(data.as_mut_slice()); diff --git a/exercises/practice/palindrome-products/tests/palindrome-products.rs b/exercises/practice/palindrome-products/tests/palindrome-products.rs index b3a32f0ad..de3deb702 100644 --- a/exercises/practice/palindrome-products/tests/palindrome-products.rs +++ b/exercises/practice/palindrome-products/tests/palindrome-products.rs @@ -23,7 +23,7 @@ fn process_largest_case((from, to): (u64, u64), expected: Option) { #[test] /// test `Palindrome::new` with valid input -fn test_palindrome_new_return_some() { +fn palindrome_new_return_some() { for v in [1, 11, 121, 12321, 1234321, 123454321, 543212345] { assert_eq!(Palindrome::new(v).expect("is a palindrome").into_inner(), v); } @@ -32,7 +32,7 @@ fn test_palindrome_new_return_some() { #[test] #[ignore] /// test `Palindrome::new` with invalid input -fn test_palindrome_new_return_none() { +fn palindrome_new_return_none() { for v in [12, 2322, 23443, 1233211, 8932343] { assert_eq!(Palindrome::new(v), None); } @@ -41,83 +41,83 @@ fn test_palindrome_new_return_none() { #[test] #[ignore] /// finds the smallest palindrome from single digit factors -fn test_finds_the_smallest_palindrome_from_single_digit_factors() { +fn finds_the_smallest_palindrome_from_single_digit_factors() { process_smallest_case((1, 9), Some(1)); } #[test] #[ignore] /// finds the largest palindrome from single digit factors -fn test_finds_the_largest_palindrome_from_single_digit_factors() { +fn finds_the_largest_palindrome_from_single_digit_factors() { process_largest_case((1, 9), Some(9)); } #[test] #[ignore] /// find the smallest palindrome from double digit factors -fn test_find_the_smallest_palindrome_from_double_digit_factors() { +fn find_the_smallest_palindrome_from_double_digit_factors() { process_smallest_case((10, 99), Some(121)); } #[test] #[ignore] /// find the largest palindrome from double digit factors -fn test_find_the_largest_palindrome_from_double_digit_factors() { +fn find_the_largest_palindrome_from_double_digit_factors() { process_largest_case((10, 99), Some(9009)); } #[test] #[ignore] /// find smallest palindrome from triple digit factors -fn test_find_smallest_palindrome_from_triple_digit_factors() { +fn find_smallest_palindrome_from_triple_digit_factors() { process_smallest_case((100, 999), Some(10201)); } #[test] #[ignore] /// find the largest palindrome from triple digit factors -fn test_find_the_largest_palindrome_from_triple_digit_factors() { +fn find_the_largest_palindrome_from_triple_digit_factors() { process_largest_case((100, 999), Some(906609)); } #[test] #[ignore] /// find smallest palindrome from four digit factors -fn test_find_smallest_palindrome_from_four_digit_factors() { +fn find_smallest_palindrome_from_four_digit_factors() { process_smallest_case((1000, 9999), Some(1002001)); } #[test] #[ignore] /// find the largest palindrome from four digit factors -fn test_find_the_largest_palindrome_from_four_digit_factors() { +fn find_the_largest_palindrome_from_four_digit_factors() { process_largest_case((1000, 9999), Some(99000099)); } #[test] #[ignore] /// empty result for smallest if no palindrome in the range -fn test_empty_result_for_smallest_if_no_palindrome_in_the_range() { +fn empty_result_for_smallest_if_no_palindrome_in_the_range() { process_smallest_case((1002, 1003), None); } #[test] #[ignore] /// empty result for largest if no palindrome in the range -fn test_empty_result_for_largest_if_no_palindrome_in_the_range() { +fn empty_result_for_largest_if_no_palindrome_in_the_range() { process_largest_case((15, 15), None); } #[test] #[ignore] /// error result for smallest if min is more than max -fn test_error_result_for_smallest_if_min_is_more_than_max() { +fn error_result_for_smallest_if_min_is_more_than_max() { process_smallest_case((10000, 1), None); } #[test] #[ignore] /// error result for largest if min is more than max -fn test_error_result_for_largest_if_min_is_more_than_max() { +fn error_result_for_largest_if_min_is_more_than_max() { process_largest_case((2, 1), None); } diff --git a/exercises/practice/pangram/.articles/performance/code/main.rs b/exercises/practice/pangram/.articles/performance/code/main.rs index f56c3ce21..fadcc667d 100644 --- a/exercises/practice/pangram/.articles/performance/code/main.rs +++ b/exercises/practice/pangram/.articles/performance/code/main.rs @@ -47,7 +47,7 @@ pub fn is_pangram_bitfield(sentence: &str) -> bool { } #[bench] -fn test_is_pangram_all_contains(b: &mut Bencher) { +fn is_pangram_all_contains(b: &mut Bencher) { b.iter(|| { is_pangram_all_contains( "Victor jagt zwölf_(12) Boxkämpfer quer über den großen Sylter Deich.", @@ -56,7 +56,7 @@ fn test_is_pangram_all_contains(b: &mut Bencher) { } #[bench] -fn test_is_pangram_hash_is_subset(b: &mut Bencher) { +fn is_pangram_hash_is_subset(b: &mut Bencher) { b.iter(|| { is_pangram_hash_is_subset( "Victor jagt zwölf_(12) Boxkämpfer quer über den großen Sylter Deich.", @@ -65,7 +65,7 @@ fn test_is_pangram_hash_is_subset(b: &mut Bencher) { } #[bench] -fn test_is_pangram_hashset_len(b: &mut Bencher) { +fn is_pangram_hashset_len(b: &mut Bencher) { b.iter(|| { is_pangram_hashset_len( "Victor jagt zwölf_(12) Boxkämpfer quer über den großen Sylter Deich.", @@ -74,7 +74,7 @@ fn test_is_pangram_hashset_len(b: &mut Bencher) { } #[bench] -fn test_is_pangram_bitfield(b: &mut Bencher) { +fn is_pangram_bitfield(b: &mut Bencher) { b.iter(|| { is_pangram_bitfield("Victor jagt zwölf_(12) Boxkämpfer quer über den großen Sylter Deich.") }); diff --git a/exercises/practice/pangram/.articles/performance/content.md b/exercises/practice/pangram/.articles/performance/content.md index a13b56d55..e1ba60bbe 100644 --- a/exercises/practice/pangram/.articles/performance/content.md +++ b/exercises/practice/pangram/.articles/performance/content.md @@ -14,10 +14,10 @@ The [approaches page][approaches] lists four idiomatic approaches to this exerc To benchmark the approaches, we wrote a [small benchmark application][benchmark-application]. ``` -test test_is_pangram_all_contains ... bench: 850 ns/iter (+/- 525) -test test_is_pangram_hash_is_subset ... bench: 3,113 ns/iter (+/- 188) -test test_is_pangram_hashset_len ... bench: 1,737 ns/iter (+/- 331) -test test_is_pangram_bitfield ... bench: 76 ns/iter (+/- 12) +test is_pangram_all_contains ... bench: 850 ns/iter (+/- 525) +test is_pangram_hash_is_subset ... bench: 3,113 ns/iter (+/- 188) +test is_pangram_hashset_len ... bench: 1,737 ns/iter (+/- 331) +test is_pangram_bitfield ... bench: 76 ns/iter (+/- 12) ``` - The `HashSet` `len` approach is not quite twice as fast as the `HashSet` `is_subset` approach. diff --git a/exercises/practice/pangram/.articles/performance/snippet.md b/exercises/practice/pangram/.articles/performance/snippet.md index c160dc70b..dc80ecfd8 100644 --- a/exercises/practice/pangram/.articles/performance/snippet.md +++ b/exercises/practice/pangram/.articles/performance/snippet.md @@ -1,6 +1,6 @@ ``` -test test_is_pangram_all_contains ... bench: 850 ns/iter (+/- 525) -test test_is_pangram_hash_is_subset ... bench: 3,113 ns/iter (+/- 188) -test test_is_pangram_hashset_len ... bench: 1,737 ns/iter (+/- 331) -test test_is_pangram_bitfield ... bench: 76 ns/iter (+/- 12) +test is_pangram_all_contains ... bench: 850 ns/iter (+/- 525) +test is_pangram_hash_is_subset ... bench: 3,113 ns/iter (+/- 188) +test is_pangram_hashset_len ... bench: 1,737 ns/iter (+/- 331) +test is_pangram_bitfield ... bench: 76 ns/iter (+/- 12) ``` diff --git a/exercises/practice/parallel-letter-frequency/tests/parallel-letter-frequency.rs b/exercises/practice/parallel-letter-frequency/tests/parallel-letter-frequency.rs index 8e79feab6..128b6c495 100644 --- a/exercises/practice/parallel-letter-frequency/tests/parallel-letter-frequency.rs +++ b/exercises/practice/parallel-letter-frequency/tests/parallel-letter-frequency.rs @@ -39,13 +39,13 @@ const STAR_SPANGLED_BANNER: [&str; 8] = [ ]; #[test] -fn test_no_texts() { +fn no_texts() { assert_eq!(frequency::frequency(&[], 4), HashMap::new()); } #[test] #[ignore] -fn test_one_letter() { +fn one_letter() { let mut hm = HashMap::new(); hm.insert('a', 1); assert_eq!(frequency::frequency(&["a"], 4), hm); @@ -53,7 +53,7 @@ fn test_one_letter() { #[test] #[ignore] -fn test_case_insensitivity() { +fn case_insensitivity() { let mut hm = HashMap::new(); hm.insert('a', 2); assert_eq!(frequency::frequency(&["aA"], 4), hm); @@ -61,14 +61,14 @@ fn test_case_insensitivity() { #[test] #[ignore] -fn test_many_empty_lines() { +fn many_empty_lines() { let v = vec![""; 1000]; assert_eq!(frequency::frequency(&v[..], 4), HashMap::new()); } #[test] #[ignore] -fn test_many_times_same_text() { +fn many_times_same_text() { let v = vec!["abc"; 1000]; let mut hm = HashMap::new(); hm.insert('a', 1000); @@ -79,19 +79,19 @@ fn test_many_times_same_text() { #[test] #[ignore] -fn test_punctuation_doesnt_count() { +fn punctuation_doesnt_count() { assert!(!frequency::frequency(&WILHELMUS, 4).contains_key(&',')); } #[test] #[ignore] -fn test_numbers_dont_count() { +fn numbers_dont_count() { assert!(!frequency::frequency(&["Testing, 1, 2, 3"], 4).contains_key(&'1')); } #[test] #[ignore] -fn test_all_three_anthems_1_worker() { +fn all_three_anthems_1_worker() { let mut v = Vec::new(); for anthem in [ODE_AN_DIE_FREUDE, WILHELMUS, STAR_SPANGLED_BANNER].iter() { for line in anthem.iter() { @@ -106,7 +106,7 @@ fn test_all_three_anthems_1_worker() { #[test] #[ignore] -fn test_all_three_anthems_3_workers() { +fn all_three_anthems_3_workers() { let mut v = Vec::new(); for anthem in [ODE_AN_DIE_FREUDE, WILHELMUS, STAR_SPANGLED_BANNER].iter() { for line in anthem.iter() { @@ -121,7 +121,7 @@ fn test_all_three_anthems_3_workers() { #[test] #[ignore] -fn test_non_integer_multiple_of_threads() { +fn non_integer_multiple_of_threads() { let v = vec!["abc"; 999]; let mut hm = HashMap::new(); hm.insert('a', 999); diff --git a/exercises/practice/phone-number/tests/phone-number.rs b/exercises/practice/phone-number/tests/phone-number.rs index 2bde594e2..61751c014 100644 --- a/exercises/practice/phone-number/tests/phone-number.rs +++ b/exercises/practice/phone-number/tests/phone-number.rs @@ -5,108 +5,108 @@ fn process_clean_case(number: &str, expected: Option<&str>) { } #[test] -fn test_cleans_the_number() { +fn cleans_the_number() { process_clean_case("(223) 456-7890", Some("2234567890")); } #[test] #[ignore] -fn test_cleans_numbers_with_dots() { +fn cleans_numbers_with_dots() { process_clean_case("223.456.7890", Some("2234567890")); } #[test] #[ignore] -fn test_cleans_numbers_with_multiple_spaces() { +fn cleans_numbers_with_multiple_spaces() { process_clean_case("223 456 7890 ", Some("2234567890")); } #[test] #[ignore] -fn test_invalid_when_9_digits() { +fn invalid_when_9_digits() { process_clean_case("123456789", None); } #[test] #[ignore] -fn test_invalid_when_11_digits_does_not_start_with_a_1() { +fn invalid_when_11_digits_does_not_start_with_a_1() { process_clean_case("22234567890", None); } #[test] #[ignore] -fn test_valid_when_11_digits_and_starting_with_1() { +fn valid_when_11_digits_and_starting_with_1() { process_clean_case("12234567890", Some("2234567890")); } #[test] #[ignore] -fn test_valid_when_11_digits_and_starting_with_1_even_with_punctuation() { +fn valid_when_11_digits_and_starting_with_1_even_with_punctuation() { process_clean_case("+1 (223) 456-7890", Some("2234567890")); } #[test] #[ignore] -fn test_invalid_when_more_than_11_digits() { +fn invalid_when_more_than_11_digits() { process_clean_case("321234567890", None); } #[test] #[ignore] -fn test_invalid_with_letters() { +fn invalid_with_letters() { process_clean_case("123-abc-7890", None); } #[test] #[ignore] -fn test_invalid_with_punctuations() { +fn invalid_with_punctuations() { process_clean_case("123-@:!-7890", None); } #[test] #[ignore] -fn test_invalid_if_area_code_starts_with_1_on_valid_11digit_number() { +fn invalid_if_area_code_starts_with_1_on_valid_11digit_number() { process_clean_case("1 (123) 456-7890", None); } #[test] #[ignore] -fn test_invalid_if_area_code_starts_with_0_on_valid_11digit_number() { +fn invalid_if_area_code_starts_with_0_on_valid_11digit_number() { process_clean_case("1 (023) 456-7890", None); } #[test] #[ignore] -fn test_invalid_if_area_code_starts_with_1() { +fn invalid_if_area_code_starts_with_1() { process_clean_case("(123) 456-7890", None); } #[test] #[ignore] -fn test_invalid_if_exchange_code_starts_with_1() { +fn invalid_if_exchange_code_starts_with_1() { process_clean_case("(223) 156-7890", None); } #[test] #[ignore] -fn test_invalid_if_exchange_code_starts_with_0() { +fn invalid_if_exchange_code_starts_with_0() { process_clean_case("(223) 056-7890", None); } #[test] #[ignore] -fn test_invalid_if_exchange_code_starts_with_1_on_valid_11digit_number() { +fn invalid_if_exchange_code_starts_with_1_on_valid_11digit_number() { process_clean_case("1 (223) 156-7890", None); } #[test] #[ignore] -fn test_invalid_if_exchange_code_starts_with_0_on_valid_11digit_number() { +fn invalid_if_exchange_code_starts_with_0_on_valid_11digit_number() { process_clean_case("1 (223) 056-7890", None); } #[test] #[ignore] -fn test_invalid_if_area_code_starts_with_0() { +fn invalid_if_area_code_starts_with_0() { process_clean_case("(023) 456-7890", None); } diff --git a/exercises/practice/pig-latin/tests/pig-latin.rs b/exercises/practice/pig-latin/tests/pig-latin.rs index 6e3812744..5ebd5e85d 100644 --- a/exercises/practice/pig-latin/tests/pig-latin.rs +++ b/exercises/practice/pig-latin/tests/pig-latin.rs @@ -1,126 +1,126 @@ use pig_latin as pl; #[test] -fn test_word_beginning_with_a() { +fn word_beginning_with_a() { assert_eq!(pl::translate("apple"), "appleay"); } #[test] #[ignore] -fn test_word_beginning_with_e() { +fn word_beginning_with_e() { assert_eq!(pl::translate("ear"), "earay"); } #[test] #[ignore] -fn test_word_beginning_with_i() { +fn word_beginning_with_i() { assert_eq!(pl::translate("igloo"), "iglooay"); } #[test] #[ignore] -fn test_word_beginning_with_o() { +fn word_beginning_with_o() { assert_eq!(pl::translate("object"), "objectay"); } #[test] #[ignore] -fn test_word_beginning_with_u() { +fn word_beginning_with_u() { assert_eq!(pl::translate("under"), "underay"); } #[test] #[ignore] -fn test_word_beginning_with_a_vowel_and_followed_by_a_qu() { +fn word_beginning_with_a_vowel_and_followed_by_a_qu() { assert_eq!(pl::translate("equal"), "equalay"); } #[test] #[ignore] -fn test_word_beginning_with_p() { +fn word_beginning_with_p() { assert_eq!(pl::translate("pig"), "igpay"); } #[test] #[ignore] -fn test_word_beginning_with_k() { +fn word_beginning_with_k() { assert_eq!(pl::translate("koala"), "oalakay"); } #[test] #[ignore] -fn test_word_beginning_with_y() { +fn word_beginning_with_y() { assert_eq!(pl::translate("yellow"), "ellowyay"); } #[test] #[ignore] -fn test_word_beginning_with_x() { +fn word_beginning_with_x() { assert_eq!(pl::translate("xenon"), "enonxay"); } #[test] #[ignore] -fn test_word_beginning_with_q_without_a_following_u() { +fn word_beginning_with_q_without_a_following_u() { assert_eq!(pl::translate("qat"), "atqay"); } #[test] #[ignore] -fn test_word_beginning_with_ch() { +fn word_beginning_with_ch() { assert_eq!(pl::translate("chair"), "airchay"); } #[test] #[ignore] -fn test_word_beginning_with_qu() { +fn word_beginning_with_qu() { assert_eq!(pl::translate("queen"), "eenquay"); } #[test] #[ignore] -fn test_word_beginning_with_qu_and_a_preceding_consonant() { +fn word_beginning_with_qu_and_a_preceding_consonant() { assert_eq!(pl::translate("square"), "aresquay"); } #[test] #[ignore] -fn test_word_beginning_with_th() { +fn word_beginning_with_th() { assert_eq!(pl::translate("therapy"), "erapythay"); } #[test] #[ignore] -fn test_word_beginning_with_thr() { +fn word_beginning_with_thr() { assert_eq!(pl::translate("thrush"), "ushthray"); } #[test] #[ignore] -fn test_word_beginning_with_sch() { +fn word_beginning_with_sch() { assert_eq!(pl::translate("school"), "oolschay"); } #[test] #[ignore] -fn test_word_beginning_with_yt() { +fn word_beginning_with_yt() { assert_eq!(pl::translate("yttria"), "yttriaay"); } #[test] #[ignore] -fn test_word_beginning_with_xr() { +fn word_beginning_with_xr() { assert_eq!(pl::translate("xray"), "xrayay"); } #[test] #[ignore] -fn test_y_is_treated_like_a_vowel_at_the_end_of_a_consonant_cluster() { +fn y_is_treated_like_a_vowel_at_the_end_of_a_consonant_cluster() { assert_eq!(pl::translate("rhythm"), "ythmrhay"); } #[test] #[ignore] -fn test_a_whole_phrase() { +fn a_whole_phrase() { assert_eq!(pl::translate("quick fast run"), "ickquay astfay unray"); } diff --git a/exercises/practice/poker/tests/poker.rs b/exercises/practice/poker/tests/poker.rs index cd16325f3..44ed780e6 100644 --- a/exercises/practice/poker/tests/poker.rs +++ b/exercises/practice/poker/tests/poker.rs @@ -19,20 +19,20 @@ fn test(input: &[&str], expected: &[&str]) { } #[test] -fn test_single_hand_always_wins() { +fn single_hand_always_wins() { test(&["4S 5S 7H 8D JC"], &["4S 5S 7H 8D JC"]) } #[test] #[ignore] -fn test_duplicate_hands_always_tie() { +fn duplicate_hands_always_tie() { let input = &["3S 4S 5D 6H JH", "3S 4S 5D 6H JH", "3S 4S 5D 6H JH"]; assert_eq!(&winning_hands(input), input) } #[test] #[ignore] -fn test_highest_card_of_all_hands_wins() { +fn highest_card_of_all_hands_wins() { test( &["4D 5S 6S 8D 3C", "2S 4C 7S 9H 10H", "3S 4S 5D 6H JH"], &["3S 4S 5D 6H JH"], @@ -41,7 +41,7 @@ fn test_highest_card_of_all_hands_wins() { #[test] #[ignore] -fn test_a_tie_has_multiple_winners() { +fn a_tie_has_multiple_winners() { test( &[ "4D 5S 6S 8D 3C", @@ -55,7 +55,7 @@ fn test_a_tie_has_multiple_winners() { #[test] #[ignore] -fn test_high_card_can_be_low_card_in_an_otherwise_tie() { +fn high_card_can_be_low_card_in_an_otherwise_tie() { // multiple hands with the same high cards, tie compares next highest ranked, // down to last card test(&["3S 5H 6S 8D 7H", "2S 5D 6D 8C 7S"], &["3S 5H 6S 8D 7H"]) @@ -63,32 +63,32 @@ fn test_high_card_can_be_low_card_in_an_otherwise_tie() { #[test] #[ignore] -fn test_one_pair_beats_high_card() { +fn one_pair_beats_high_card() { test(&["4S 5H 6C 8D KH", "2S 4H 6S 4D JH"], &["2S 4H 6S 4D JH"]) } #[test] #[ignore] -fn test_highest_pair_wins() { +fn highest_pair_wins() { test(&["4S 2H 6S 2D JH", "2S 4H 6C 4D JD"], &["2S 4H 6C 4D JD"]) } #[test] #[ignore] -fn test_two_pairs_beats_one_pair() { +fn two_pairs_beats_one_pair() { test(&["2S 8H 6S 8D JH", "4S 5H 4C 8C 5C"], &["4S 5H 4C 8C 5C"]) } #[test] #[ignore] -fn test_two_pair_ranks() { +fn two_pair_ranks() { // both hands have two pairs, highest ranked pair wins test(&["2S 8H 2D 8D 3H", "4S 5H 4C 8S 5D"], &["2S 8H 2D 8D 3H"]) } #[test] #[ignore] -fn test_two_pairs_second_pair_cascade() { +fn two_pairs_second_pair_cascade() { // both hands have two pairs, with the same highest ranked pair, // tie goes to low pair test(&["2S QS 2C QD JH", "JD QH JS 8D QC"], &["JD QH JS 8D QC"]) @@ -96,7 +96,7 @@ fn test_two_pairs_second_pair_cascade() { #[test] #[ignore] -fn test_two_pairs_last_card_cascade() { +fn two_pairs_last_card_cascade() { // both hands have two identically ranked pairs, // tie goes to remaining card (kicker) test(&["JD QH JS 8D QC", "JS QS JC 2D QD"], &["JD QH JS 8D QC"]) @@ -104,26 +104,26 @@ fn test_two_pairs_last_card_cascade() { #[test] #[ignore] -fn test_three_of_a_kind_beats_two_pair() { +fn three_of_a_kind_beats_two_pair() { test(&["2S 8H 2H 8D JH", "4S 5H 4C 8S 4H"], &["4S 5H 4C 8S 4H"]) } #[test] #[ignore] -fn test_three_of_a_kind_ranks() { +fn three_of_a_kind_ranks() { //both hands have three of a kind, tie goes to highest ranked triplet test(&["2S 2H 2C 8D JH", "4S AH AS 8C AD"], &["4S AH AS 8C AD"]) } #[test] #[ignore] -fn test_low_three_of_a_kind_beats_high_two_pair() { +fn low_three_of_a_kind_beats_high_two_pair() { test(&["2H 2D 2C 8H 5H", "AS AC KS KC 6S"], &["2H 2D 2C 8H 5H"]) } #[test] #[ignore] -fn test_three_of_a_kind_cascade_ranks() { +fn three_of_a_kind_cascade_ranks() { // with multiple decks, two players can have same three of a kind, // ties go to highest remaining cards test(&["4S AH AS 7C AD", "4S AH AS 8C AD"], &["4S AH AS 8C AD"]) @@ -131,135 +131,135 @@ fn test_three_of_a_kind_cascade_ranks() { #[test] #[ignore] -fn test_straight_beats_three_of_a_kind() { +fn straight_beats_three_of_a_kind() { test(&["4S 5H 4C 8D 4H", "3S 4D 2S 6D 5C"], &["3S 4D 2S 6D 5C"]) } #[test] #[ignore] -fn test_aces_can_end_a_straight_high() { +fn aces_can_end_a_straight_high() { // aces can end a straight (10 J Q K A) test(&["4S 5H 4C 8D 4H", "10D JH QS KD AC"], &["10D JH QS KD AC"]) } #[test] #[ignore] -fn test_aces_can_start_a_straight_low() { +fn aces_can_start_a_straight_low() { // aces can start a straight (A 2 3 4 5) test(&["4S 5H 4C 8D 4H", "4D AH 3S 2D 5C"], &["4D AH 3S 2D 5C"]) } #[test] #[ignore] -fn test_no_ace_in_middle_of_straight() { +fn no_ace_in_middle_of_straight() { // aces cannot be in the middle of a straight (Q K A 2 3) test(&["2C 3D 7H 5H 2S", "QS KH AC 2D 3S"], &["2C 3D 7H 5H 2S"]) } #[test] #[ignore] -fn test_straight_ranks() { +fn straight_ranks() { // both hands with a straight, tie goes to highest ranked card test(&["4S 6C 7S 8D 5H", "5S 7H 8S 9D 6H"], &["5S 7H 8S 9D 6H"]) } #[test] #[ignore] -fn test_straight_scoring() { +fn straight_scoring() { // even though an ace is usually high, a 5-high straight is the lowest-scoring straight test(&["2H 3C 4D 5D 6H", "4S AH 3S 2D 5H"], &["2H 3C 4D 5D 6H"]) } #[test] #[ignore] -fn test_flush_beats_a_straight() { +fn flush_beats_a_straight() { test(&["4C 6H 7D 8D 5H", "2S 4S 5S 6S 7S"], &["2S 4S 5S 6S 7S"]) } #[test] #[ignore] -fn test_flush_cascade() { +fn flush_cascade() { // both hands have a flush, tie goes to high card, down to the last one if necessary test(&["4H 7H 8H 9H 6H", "2S 4S 5S 6S 7S"], &["4H 7H 8H 9H 6H"]) } #[test] #[ignore] -fn test_full_house_beats_a_flush() { +fn full_house_beats_a_flush() { test(&["3H 6H 7H 8H 5H", "4S 5C 4C 5D 4H"], &["4S 5C 4C 5D 4H"]) } #[test] #[ignore] -fn test_full_house_ranks() { +fn full_house_ranks() { // both hands have a full house, tie goes to highest-ranked triplet test(&["4H 4S 4D 9S 9D", "5H 5S 5D 8S 8D"], &["5H 5S 5D 8S 8D"]) } #[test] #[ignore] -fn test_full_house_cascade() { +fn full_house_cascade() { // with multiple decks, both hands have a full house with the same triplet, tie goes to the pair test(&["5H 5S 5D 9S 9D", "5H 5S 5D 8S 8D"], &["5H 5S 5D 9S 9D"]) } #[test] #[ignore] -fn test_four_of_a_kind_beats_full_house() { +fn four_of_a_kind_beats_full_house() { test(&["4S 5H 4D 5D 4H", "3S 3H 2S 3D 3C"], &["3S 3H 2S 3D 3C"]) } #[test] #[ignore] -fn test_four_of_a_kind_ranks() { +fn four_of_a_kind_ranks() { // both hands have four of a kind, tie goes to high quad test(&["2S 2H 2C 8D 2D", "4S 5H 5S 5D 5C"], &["4S 5H 5S 5D 5C"]) } #[test] #[ignore] -fn test_four_of_a_kind_cascade() { +fn four_of_a_kind_cascade() { // with multiple decks, both hands with identical four of a kind, tie determined by kicker test(&["3S 3H 2S 3D 3C", "3S 3H 4S 3D 3C"], &["3S 3H 4S 3D 3C"]) } #[test] #[ignore] -fn test_straight_flush_beats_four_of_a_kind() { +fn straight_flush_beats_four_of_a_kind() { test(&["4S 5H 5S 5D 5C", "7S 8S 9S 6S 10S"], &["7S 8S 9S 6S 10S"]) } #[test] #[ignore] -fn test_aces_can_end_a_straight_flush_high() { +fn aces_can_end_a_straight_flush_high() { // aces can end a straight flush (10 J Q K A) test(&["KC AH AS AD AC", "10C JC QC KC AC"], &["10C JC QC KC AC"]) } #[test] #[ignore] -fn test_aces_can_start_a_straight_flush_low() { +fn aces_can_start_a_straight_flush_low() { // aces can start a straight flush (A 2 3 4 5) test(&["KS AH AS AD AC", "4H AH 3H 2H 5H"], &["4H AH 3H 2H 5H"]) } #[test] #[ignore] -fn test_no_ace_in_middle_of_straight_flush() { +fn no_ace_in_middle_of_straight_flush() { // aces cannot be in the middle of a straight flush (Q K A 2 3) test(&["2C AC QC 10C KC", "QH KH AH 2H 3H"], &["2C AC QC 10C KC"]) } #[test] #[ignore] -fn test_straight_flush_ranks() { +fn straight_flush_ranks() { // both hands have a straight flush, tie goes to highest-ranked card test(&["4H 6H 7H 8H 5H", "5S 7S 8S 9S 6S"], &["5S 7S 8S 9S 6S"]) } #[test] #[ignore] -fn test_straight_flush_scoring() { +fn straight_flush_scoring() { // even though an ace is usually high, a 5-high straight flush is the lowest-scoring straight flush test(&["2H 3H 4H 5H 6H", "4D AD 3D 2D 5D"], &["2H 3H 4H 5H 6H"]) } diff --git a/exercises/practice/prime-factors/tests/prime-factors.rs b/exercises/practice/prime-factors/tests/prime-factors.rs index 3c17ec50a..2b66f2ac7 100644 --- a/exercises/practice/prime-factors/tests/prime-factors.rs +++ b/exercises/practice/prime-factors/tests/prime-factors.rs @@ -1,42 +1,42 @@ use prime_factors::factors; #[test] -fn test_no_factors() { +fn no_factors() { assert_eq!(factors(1), vec![]); } #[test] #[ignore] -fn test_prime_number() { +fn prime_number() { assert_eq!(factors(2), vec![2]); } #[test] #[ignore] -fn test_square_of_a_prime() { +fn square_of_a_prime() { assert_eq!(factors(9), vec![3, 3]); } #[test] #[ignore] -fn test_cube_of_a_prime() { +fn cube_of_a_prime() { assert_eq!(factors(8), vec![2, 2, 2]); } #[test] #[ignore] -fn test_product_of_primes_and_non_primes() { +fn product_of_primes_and_non_primes() { assert_eq!(factors(12), vec![2, 2, 3]); } #[test] #[ignore] -fn test_product_of_primes() { +fn product_of_primes() { assert_eq!(factors(901_255), vec![5, 17, 23, 461]); } #[test] #[ignore] -fn test_factors_include_large_prime() { +fn factors_include_large_prime() { assert_eq!(factors(93_819_012_551), vec![11, 9539, 894_119]); } diff --git a/exercises/practice/protein-translation/tests/protein-translation.rs b/exercises/practice/protein-translation/tests/protein-translation.rs index 828efab00..d7c0369d0 100644 --- a/exercises/practice/protein-translation/tests/protein-translation.rs +++ b/exercises/practice/protein-translation/tests/protein-translation.rs @@ -1,42 +1,42 @@ use protein_translation as proteins; #[test] -fn test_methionine() { +fn methionine() { let info = proteins::parse(make_pairs()); assert_eq!(info.name_for("AUG"), Some("methionine")); } #[test] #[ignore] -fn test_cysteine_tgt() { +fn cysteine_tgt() { let info = proteins::parse(make_pairs()); assert_eq!(info.name_for("UGU"), Some("cysteine")); } #[test] #[ignore] -fn test_stop() { +fn stop() { let info = proteins::parse(make_pairs()); assert_eq!(info.name_for("UAA"), Some("stop codon")); } #[test] #[ignore] -fn test_valine() { +fn valine() { let info = proteins::parse(make_pairs()); assert_eq!(info.name_for("GUU"), Some("valine")); } #[test] #[ignore] -fn test_isoleucine() { +fn isoleucine() { let info = proteins::parse(make_pairs()); assert_eq!(info.name_for("AUU"), Some("isoleucine")); } #[test] #[ignore] -fn test_arginine_name() { +fn arginine_name() { let info = proteins::parse(make_pairs()); assert_eq!(info.name_for("CGA"), Some("arginine")); assert_eq!(info.name_for("AGA"), Some("arginine")); @@ -73,7 +73,7 @@ fn too_long_is_invalid() { #[test] #[ignore] -fn test_translates_rna_strand_into_correct_protein() { +fn translates_rna_strand_into_correct_protein() { let info = proteins::parse(make_pairs()); assert_eq!( info.of_rna("AUGUUUUGG"), @@ -83,7 +83,7 @@ fn test_translates_rna_strand_into_correct_protein() { #[test] #[ignore] -fn test_stops_translation_if_stop_codon_present() { +fn stops_translation_if_stop_codon_present() { let info = proteins::parse(make_pairs()); assert_eq!( info.of_rna("AUGUUUUAA"), @@ -93,7 +93,7 @@ fn test_stops_translation_if_stop_codon_present() { #[test] #[ignore] -fn test_stops_translation_of_longer_strand() { +fn stops_translation_of_longer_strand() { let info = proteins::parse(make_pairs()); assert_eq!( info.of_rna("UGGUGUUAUUAAUGGUUU"), @@ -103,21 +103,21 @@ fn test_stops_translation_of_longer_strand() { #[test] #[ignore] -fn test_invalid_codons() { +fn invalid_codons() { let info = proteins::parse(make_pairs()); assert!(info.of_rna("CARROT").is_none()); } #[test] #[ignore] -fn test_invalid_length() { +fn invalid_length() { let info = proteins::parse(make_pairs()); assert!(info.of_rna("AUGUA").is_none()); } #[test] #[ignore] -fn test_valid_stopped_rna() { +fn valid_stopped_rna() { let info = proteins::parse(make_pairs()); assert_eq!(info.of_rna("AUGUAAASDF"), Some(vec!["methionine"])); } diff --git a/exercises/practice/proverb/tests/proverb.rs b/exercises/practice/proverb/tests/proverb.rs index a19c2cae5..5a921790e 100644 --- a/exercises/practice/proverb/tests/proverb.rs +++ b/exercises/practice/proverb/tests/proverb.rs @@ -1,7 +1,7 @@ use proverb::build_proverb; #[test] -fn test_two_pieces() { +fn two_pieces() { let input = vec!["nail", "shoe"]; let expected = [ "For want of a nail the shoe was lost.", @@ -14,7 +14,7 @@ fn test_two_pieces() { // Notice the change in the last line at three pieces. #[test] #[ignore] -fn test_three_pieces() { +fn three_pieces() { let input = vec!["nail", "shoe", "horse"]; let expected = [ "For want of a nail the shoe was lost.", @@ -27,7 +27,7 @@ fn test_three_pieces() { #[test] #[ignore] -fn test_one_piece() { +fn one_piece() { let input = vec!["nail"]; let expected = String::from("And all for the want of a nail."); assert_eq!(build_proverb(&input), expected); @@ -35,7 +35,7 @@ fn test_one_piece() { #[test] #[ignore] -fn test_zero_pieces() { +fn zero_pieces() { let input: Vec<&str> = vec![]; let expected = String::new(); assert_eq!(build_proverb(&input), expected); @@ -43,7 +43,7 @@ fn test_zero_pieces() { #[test] #[ignore] -fn test_full() { +fn full() { let input = vec![ "nail", "shoe", "horse", "rider", "message", "battle", "kingdom", ]; @@ -62,7 +62,7 @@ fn test_full() { #[test] #[ignore] -fn test_three_pieces_modernized() { +fn three_pieces_modernized() { let input = vec!["pin", "gun", "soldier", "battle"]; let expected = [ "For want of a pin the gun was lost.", diff --git a/exercises/practice/pythagorean-triplet/tests/pythagorean-triplet.rs b/exercises/practice/pythagorean-triplet/tests/pythagorean-triplet.rs index 3ca5c3728..45c255094 100644 --- a/exercises/practice/pythagorean-triplet/tests/pythagorean-triplet.rs +++ b/exercises/practice/pythagorean-triplet/tests/pythagorean-triplet.rs @@ -14,37 +14,37 @@ fn process_tripletswithsum_case(sum: u32, expected: &[[u32; 3]]) { } #[test] -fn test_triplets_whose_sum_is_12() { +fn triplets_whose_sum_is_12() { process_tripletswithsum_case(12, &[[3, 4, 5]]); } #[test] #[ignore] -fn test_triplets_whose_sum_is_108() { +fn triplets_whose_sum_is_108() { process_tripletswithsum_case(108, &[[27, 36, 45]]); } #[test] #[ignore] -fn test_triplets_whose_sum_is_1000() { +fn triplets_whose_sum_is_1000() { process_tripletswithsum_case(1000, &[[200, 375, 425]]); } #[test] #[ignore] -fn test_no_matching_triplets_for_1001() { +fn no_matching_triplets_for_1001() { process_tripletswithsum_case(1001, &[]); } #[test] #[ignore] -fn test_returns_all_matching_triplets() { +fn returns_all_matching_triplets() { process_tripletswithsum_case(90, &[[9, 40, 41], [15, 36, 39]]); } #[test] #[ignore] -fn test_several_matching_triplets() { +fn several_matching_triplets() { process_tripletswithsum_case( 840, &[ @@ -62,7 +62,7 @@ fn test_several_matching_triplets() { #[test] #[ignore] -fn test_triplets_for_large_number() { +fn triplets_for_large_number() { process_tripletswithsum_case( 30_000, &[ diff --git a/exercises/practice/rail-fence-cipher/tests/rail-fence-cipher.rs b/exercises/practice/rail-fence-cipher/tests/rail-fence-cipher.rs index 399f0c6da..66daa2137 100644 --- a/exercises/practice/rail-fence-cipher/tests/rail-fence-cipher.rs +++ b/exercises/practice/rail-fence-cipher/tests/rail-fence-cipher.rs @@ -32,21 +32,21 @@ fn process_decode_case(input: &str, rails: u32, expected: &str) { #[test] /// encode with two rails -fn test_encode_with_two_rails() { +fn encode_with_two_rails() { process_encode_case("XOXOXOXOXOXOXOXOXO", 2, "XXXXXXXXXOOOOOOOOO"); } #[test] #[ignore] /// encode with three rails -fn test_encode_with_three_rails() { +fn encode_with_three_rails() { process_encode_case("WEAREDISCOVEREDFLEEATONCE", 3, "WECRLTEERDSOEEFEAOCAIVDEN"); } #[test] #[ignore] /// encode with ending in the middle -fn test_encode_with_ending_in_the_middle() { +fn encode_with_ending_in_the_middle() { process_encode_case("EXERCISES", 4, "ESXIEECSR"); } @@ -55,21 +55,21 @@ fn test_encode_with_ending_in_the_middle() { #[test] #[ignore] /// decode with three rails -fn test_decode_with_three_rails() { +fn decode_with_three_rails() { process_decode_case("TEITELHDVLSNHDTISEIIEA", 3, "THEDEVILISINTHEDETAILS"); } #[test] #[ignore] /// decode with five rails -fn test_decode_with_five_rails() { +fn decode_with_five_rails() { process_decode_case("EIEXMSMESAORIWSCE", 5, "EXERCISMISAWESOME"); } #[test] #[ignore] /// decode with six rails -fn test_decode_with_six_rails() { +fn decode_with_six_rails() { process_decode_case( "133714114238148966225439541018335470986172518171757571896261", 6, @@ -87,6 +87,6 @@ fn test_decode_with_six_rails() { /// /// this text is possibly one of the most famous haiku of all time, by /// Matsuo Bashō (松尾芭蕉) -fn test_encode_wide_characters() { +fn encode_wide_characters() { process_encode_case("古池蛙飛び込む水の音", 3, "古びの池飛込水音蛙む"); } diff --git a/exercises/practice/react/tests/react.rs b/exercises/practice/react/tests/react.rs index af053b2d2..1735e6632 100644 --- a/exercises/practice/react/tests/react.rs +++ b/exercises/practice/react/tests/react.rs @@ -403,7 +403,7 @@ fn callbacks_should_not_be_called_if_dependencies_change_but_output_value_doesnt #[test] #[ignore] -fn test_adder_with_boolean_values() { +fn adder_with_boolean_values() { // This is a digital logic circuit called an adder: // https://en.wikipedia.org/wiki/Adder_(electronics) let mut reactor = Reactor::new(); diff --git a/exercises/practice/rectangles/tests/rectangles.rs b/exercises/practice/rectangles/tests/rectangles.rs index e8863eb8b..c3f72237c 100644 --- a/exercises/practice/rectangles/tests/rectangles.rs +++ b/exercises/practice/rectangles/tests/rectangles.rs @@ -1,28 +1,28 @@ use rectangles::count; #[test] -fn test_zero_area_1() { +fn zero_area_1() { let lines = &[]; assert_eq!(0, count(lines)) } #[test] #[ignore] -fn test_zero_area_2() { +fn zero_area_2() { let lines = &[""]; assert_eq!(0, count(lines)) } #[test] #[ignore] -fn test_empty_area() { +fn empty_area() { let lines = &[" "]; assert_eq!(0, count(lines)) } #[test] #[ignore] -fn test_one_rectangle() { +fn one_rectangle() { #[rustfmt::skip] let lines = &[ "+-+", @@ -34,7 +34,7 @@ fn test_one_rectangle() { #[test] #[ignore] -fn test_two_rectangles_no_shared_parts() { +fn two_rectangles_no_shared_parts() { #[rustfmt::skip] let lines = &[ " +-+", @@ -48,7 +48,7 @@ fn test_two_rectangles_no_shared_parts() { #[test] #[ignore] -fn test_five_rectangles_three_regions() { +fn five_rectangles_three_regions() { #[rustfmt::skip] let lines = &[ " +-+", @@ -96,7 +96,7 @@ fn unit_square() { #[test] #[ignore] -fn test_incomplete_rectangles() { +fn incomplete_rectangles() { #[rustfmt::skip] let lines = &[ " +-+", @@ -110,7 +110,7 @@ fn test_incomplete_rectangles() { #[test] #[ignore] -fn test_complicated() { +fn complicated() { let lines = &[ "+------+----+", "| | |", @@ -123,7 +123,7 @@ fn test_complicated() { #[test] #[ignore] -fn test_not_so_complicated() { +fn not_so_complicated() { let lines = &[ "+------+----+", "| | |", @@ -136,7 +136,7 @@ fn test_not_so_complicated() { #[test] #[ignore] -fn test_large_input_with_many_rectangles() { +fn large_input_with_many_rectangles() { let lines = &[ "+---+--+----+", "| +--+----+", @@ -152,7 +152,7 @@ fn test_large_input_with_many_rectangles() { #[test] #[ignore] -fn test_three_rectangles_no_shared_parts() { +fn three_rectangles_no_shared_parts() { #[rustfmt::skip] let lines = &[ " +-+ ", diff --git a/exercises/practice/reverse-string/tests/reverse-string.rs b/exercises/practice/reverse-string/tests/reverse-string.rs index b6f5b85e3..b80eef344 100644 --- a/exercises/practice/reverse-string/tests/reverse-string.rs +++ b/exercises/practice/reverse-string/tests/reverse-string.rs @@ -14,49 +14,49 @@ fn process_reverse_case(input: &str, expected: &str) { #[test] /// empty string -fn test_an_empty_string() { +fn an_empty_string() { process_reverse_case("", ""); } #[test] #[ignore] /// a word -fn test_a_word() { +fn a_word() { process_reverse_case("robot", "tobor"); } #[test] #[ignore] /// a capitalized word -fn test_a_capitalized_word() { +fn a_capitalized_word() { process_reverse_case("Ramen", "nemaR"); } #[test] #[ignore] /// a sentence with punctuation -fn test_a_sentence_with_punctuation() { +fn a_sentence_with_punctuation() { process_reverse_case("I'm hungry!", "!yrgnuh m'I"); } #[test] #[ignore] /// a palindrome -fn test_a_palindrome() { +fn a_palindrome() { process_reverse_case("racecar", "racecar"); } #[test] #[ignore] /// an even-sized word -fn test_an_even_sized_word() { +fn an_even_sized_word() { process_reverse_case("drawer", "reward"); } #[test] #[ignore] /// wide characters -fn test_wide_characters() { +fn wide_characters() { process_reverse_case("子猫", "猫子"); } @@ -64,6 +64,6 @@ fn test_wide_characters() { #[ignore] #[cfg(feature = "grapheme")] /// grapheme clusters -fn test_grapheme_clusters() { +fn grapheme_clusters() { process_reverse_case("uüu", "uüu"); } diff --git a/exercises/practice/rna-transcription/tests/rna-transcription.rs b/exercises/practice/rna-transcription/tests/rna-transcription.rs index 11c727f38..63e81c8e9 100644 --- a/exercises/practice/rna-transcription/tests/rna-transcription.rs +++ b/exercises/practice/rna-transcription/tests/rna-transcription.rs @@ -1,19 +1,19 @@ use rna_transcription as dna; #[test] -fn test_valid_dna_input() { +fn valid_dna_input() { assert!(dna::Dna::new("GCTA").is_ok()); } #[test] #[ignore] -fn test_valid_rna_input() { +fn valid_rna_input() { assert!(dna::Rna::new("CGAU").is_ok()); } #[test] #[ignore] -fn test_invalid_dna_input() { +fn invalid_dna_input() { // Invalid character assert_eq!(dna::Dna::new("X").err(), Some(0)); // Valid nucleotide, but invalid in context @@ -24,7 +24,7 @@ fn test_invalid_dna_input() { #[test] #[ignore] -fn test_invalid_rna_input() { +fn invalid_rna_input() { // Invalid character assert_eq!(dna::Rna::new("X").unwrap_err(), 0); // Valid nucleotide, but invalid in context @@ -35,7 +35,7 @@ fn test_invalid_rna_input() { #[test] #[ignore] -fn test_acid_equals_acid() { +fn acid_equals_acid() { assert_eq!(dna::Dna::new("CGA").unwrap(), dna::Dna::new("CGA").unwrap()); assert_ne!(dna::Dna::new("CGA").unwrap(), dna::Dna::new("AGC").unwrap()); assert_eq!(dna::Rna::new("CGA").unwrap(), dna::Rna::new("CGA").unwrap()); @@ -44,7 +44,7 @@ fn test_acid_equals_acid() { #[test] #[ignore] -fn test_transcribes_cytosine_guanine() { +fn transcribes_cytosine_guanine() { assert_eq!( dna::Rna::new("G").unwrap(), dna::Dna::new("C").unwrap().into_rna() @@ -53,7 +53,7 @@ fn test_transcribes_cytosine_guanine() { #[test] #[ignore] -fn test_transcribes_guanine_cytosine() { +fn transcribes_guanine_cytosine() { assert_eq!( dna::Rna::new("C").unwrap(), dna::Dna::new("G").unwrap().into_rna() @@ -62,7 +62,7 @@ fn test_transcribes_guanine_cytosine() { #[test] #[ignore] -fn test_transcribes_adenine_uracil() { +fn transcribes_adenine_uracil() { assert_eq!( dna::Rna::new("U").unwrap(), dna::Dna::new("A").unwrap().into_rna() @@ -71,7 +71,7 @@ fn test_transcribes_adenine_uracil() { #[test] #[ignore] -fn test_transcribes_thymine_to_adenine() { +fn transcribes_thymine_to_adenine() { assert_eq!( dna::Rna::new("A").unwrap(), dna::Dna::new("T").unwrap().into_rna() @@ -80,7 +80,7 @@ fn test_transcribes_thymine_to_adenine() { #[test] #[ignore] -fn test_transcribes_all_dna_to_rna() { +fn transcribes_all_dna_to_rna() { assert_eq!( dna::Rna::new("UGCACCAGAAUU").unwrap(), dna::Dna::new("ACGTGGTCTTAA").unwrap().into_rna() diff --git a/exercises/practice/robot-name/tests/robot-name.rs b/exercises/practice/robot-name/tests/robot-name.rs index ab848297d..011b3c0b2 100644 --- a/exercises/practice/robot-name/tests/robot-name.rs +++ b/exercises/practice/robot-name/tests/robot-name.rs @@ -22,20 +22,20 @@ fn assert_name_is_persistent(r: &robot::Robot) { } #[test] -fn test_name_should_match_expected_pattern() { +fn name_should_match_expected_pattern() { let r = robot::Robot::new(); assert_name_matches_pattern(r.name()); } #[test] #[ignore] -fn test_name_is_persistent() { +fn name_is_persistent() { assert_name_is_persistent(&robot::Robot::new()); } #[test] #[ignore] -fn test_different_robots_have_different_names() { +fn different_robots_have_different_names() { let r1 = robot::Robot::new(); let r2 = robot::Robot::new(); assert_ne!(r1.name(), r2.name(), "Robot names should be different"); @@ -43,7 +43,7 @@ fn test_different_robots_have_different_names() { #[test] #[ignore] -fn test_many_different_robots_have_different_names() { +fn many_different_robots_have_different_names() { use std::collections::HashSet; // In 3,529 random robot names, there is ~99.99% chance of a name collision @@ -56,7 +56,7 @@ fn test_many_different_robots_have_different_names() { #[test] #[ignore] -fn test_new_name_should_match_expected_pattern() { +fn new_name_should_match_expected_pattern() { let mut r = robot::Robot::new(); assert_name_matches_pattern(r.name()); r.reset_name(); @@ -65,7 +65,7 @@ fn test_new_name_should_match_expected_pattern() { #[test] #[ignore] -fn test_new_name_is_persistent() { +fn new_name_is_persistent() { let mut r = robot::Robot::new(); r.reset_name(); assert_name_is_persistent(&r); @@ -73,7 +73,7 @@ fn test_new_name_is_persistent() { #[test] #[ignore] -fn test_new_name_is_different_from_old_name() { +fn new_name_is_different_from_old_name() { let mut r = robot::Robot::new(); let n1 = r.name().to_string(); r.reset_name(); diff --git a/exercises/practice/roman-numerals/tests/roman-numerals.rs b/exercises/practice/roman-numerals/tests/roman-numerals.rs index cb175fec2..7a3337666 100644 --- a/exercises/practice/roman-numerals/tests/roman-numerals.rs +++ b/exercises/practice/roman-numerals/tests/roman-numerals.rs @@ -1,67 +1,67 @@ use roman_numerals::*; #[test] -fn test_one() { +fn one() { assert_eq!("I", Roman::from(1).to_string()); } #[test] #[ignore] -fn test_two() { +fn two() { assert_eq!("II", Roman::from(2).to_string()); } #[test] #[ignore] -fn test_three() { +fn three() { assert_eq!("III", Roman::from(3).to_string()); } #[test] #[ignore] -fn test_four() { +fn four() { assert_eq!("IV", Roman::from(4).to_string()); } #[test] #[ignore] -fn test_five() { +fn five() { assert_eq!("V", Roman::from(5).to_string()); } #[test] #[ignore] -fn test_six() { +fn six() { assert_eq!("VI", Roman::from(6).to_string()); } #[test] #[ignore] -fn test_nine() { +fn nine() { assert_eq!("IX", Roman::from(9).to_string()); } #[test] #[ignore] -fn test_twenty_seven() { +fn twenty_seven() { assert_eq!("XXVII", Roman::from(27).to_string()); } #[test] #[ignore] -fn test_forty_eight() { +fn forty_eight() { assert_eq!("XLVIII", Roman::from(48).to_string()); } #[test] #[ignore] -fn test_fifty_nine() { +fn fifty_nine() { assert_eq!("LIX", Roman::from(59).to_string()); } #[test] #[ignore] -fn test_ninety_three() { +fn ninety_three() { assert_eq!("XCIII", Roman::from(93).to_string()); } diff --git a/exercises/practice/run-length-encoding/tests/run-length-encoding.rs b/exercises/practice/run-length-encoding/tests/run-length-encoding.rs index bcb208e36..5f8354738 100644 --- a/exercises/practice/run-length-encoding/tests/run-length-encoding.rs +++ b/exercises/practice/run-length-encoding/tests/run-length-encoding.rs @@ -3,25 +3,25 @@ use run_length_encoding as rle; // encoding tests #[test] -fn test_encode_empty_string() { +fn encode_empty_string() { assert_eq!("", rle::encode("")); } #[test] #[ignore] -fn test_encode_single_characters() { +fn encode_single_characters() { assert_eq!("XYZ", rle::encode("XYZ")); } #[test] #[ignore] -fn test_encode_string_with_no_single_characters() { +fn encode_string_with_no_single_characters() { assert_eq!("2A3B4C", rle::encode("AABBBCCCC")); } #[test] #[ignore] -fn test_encode_single_characters_mixed_with_repeated_characters() { +fn encode_single_characters_mixed_with_repeated_characters() { assert_eq!( "12WB12W3B24WB", rle::encode("WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB") @@ -30,13 +30,13 @@ fn test_encode_single_characters_mixed_with_repeated_characters() { #[test] #[ignore] -fn test_encode_multiple_whitespace_mixed_in_string() { +fn encode_multiple_whitespace_mixed_in_string() { assert_eq!("2 hs2q q2w2 ", rle::encode(" hsqq qww ")); } #[test] #[ignore] -fn test_encode_lowercase_characters() { +fn encode_lowercase_characters() { assert_eq!("2a3b4c", rle::encode("aabbbcccc")); } @@ -44,25 +44,25 @@ fn test_encode_lowercase_characters() { #[test] #[ignore] -fn test_decode_empty_string() { +fn decode_empty_string() { assert_eq!("", rle::decode("")); } #[test] #[ignore] -fn test_decode_single_characters_only() { +fn decode_single_characters_only() { assert_eq!("XYZ", rle::decode("XYZ")); } #[test] #[ignore] -fn test_decode_string_with_no_single_characters() { +fn decode_string_with_no_single_characters() { assert_eq!("AABBBCCCC", rle::decode("2A3B4C")); } #[test] #[ignore] -fn test_decode_single_characters_with_repeated_characters() { +fn decode_single_characters_with_repeated_characters() { assert_eq!( "WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB", rle::decode("12WB12W3B24WB") @@ -71,13 +71,13 @@ fn test_decode_single_characters_with_repeated_characters() { #[test] #[ignore] -fn test_decode_multiple_whitespace_mixed_in_string() { +fn decode_multiple_whitespace_mixed_in_string() { assert_eq!(" hsqq qww ", rle::decode("2 hs2q q2w2 ")); } #[test] #[ignore] -fn test_decode_lower_case_string() { +fn decode_lower_case_string() { assert_eq!("aabbbcccc", rle::decode("2a3b4c")); } @@ -85,7 +85,7 @@ fn test_decode_lower_case_string() { #[test] #[ignore] -fn test_consistency() { +fn consistency() { assert_eq!( "zzz ZZ zZ", rle::decode(rle::encode("zzz ZZ zZ").as_str()) diff --git a/exercises/practice/say/tests/say.rs b/exercises/practice/say/tests/say.rs index dc320dd96..02a17fc10 100644 --- a/exercises/practice/say/tests/say.rs +++ b/exercises/practice/say/tests/say.rs @@ -3,7 +3,7 @@ // where it is common in British English to use the 'and'. #[test] -fn test_zero() { +fn zero() { assert_eq!(say::encode(0), String::from("zero")); } @@ -13,63 +13,63 @@ fn test_zero() { /* #[test] #[ignore] -fn test_negative() { +fn negative() { assert_eq!(say::encode(-1), String::from("won't compile")); } */ #[test] #[ignore] -fn test_one() { +fn one() { assert_eq!(say::encode(1), String::from("one")); } #[test] #[ignore] -fn test_fourteen() { +fn fourteen() { assert_eq!(say::encode(14), String::from("fourteen")); } #[test] #[ignore] -fn test_twenty() { +fn twenty() { assert_eq!(say::encode(20), String::from("twenty")); } #[test] #[ignore] -fn test_twenty_two() { +fn twenty_two() { assert_eq!(say::encode(22), String::from("twenty-two")); } #[test] #[ignore] -fn test_one_hundred() { +fn one_hundred() { assert_eq!(say::encode(100), String::from("one hundred")); } // note, using American style with no and #[test] #[ignore] -fn test_one_hundred_twenty() { +fn one_hundred_twenty() { assert_eq!(say::encode(120), String::from("one hundred twenty")); } #[test] #[ignore] -fn test_one_hundred_twenty_three() { +fn one_hundred_twenty_three() { assert_eq!(say::encode(123), String::from("one hundred twenty-three")); } #[test] #[ignore] -fn test_one_thousand() { +fn one_thousand() { assert_eq!(say::encode(1000), String::from("one thousand")); } #[test] #[ignore] -fn test_one_thousand_two_hundred_thirty_four() { +fn one_thousand_two_hundred_thirty_four() { assert_eq!( say::encode(1234), String::from("one thousand two hundred thirty-four") @@ -79,7 +79,7 @@ fn test_one_thousand_two_hundred_thirty_four() { // note, using American style with no and #[test] #[ignore] -fn test_eight_hundred_and_ten_thousand() { +fn eight_hundred_and_ten_thousand() { assert_eq!( say::encode(810_000), String::from("eight hundred ten thousand") @@ -88,20 +88,20 @@ fn test_eight_hundred_and_ten_thousand() { #[test] #[ignore] -fn test_one_million() { +fn one_million() { assert_eq!(say::encode(1_000_000), String::from("one million")); } // note, using American style with no and #[test] #[ignore] -fn test_one_million_two() { +fn one_million_two() { assert_eq!(say::encode(1_000_002), String::from("one million two")); } #[test] #[ignore] -fn test_1002345() { +fn one_million_two_thousand_three_hundred_forty_five() { assert_eq!( say::encode(1_002_345), String::from("one million two thousand three hundred forty-five") @@ -110,7 +110,7 @@ fn test_1002345() { #[test] #[ignore] -fn test_one_billion() { +fn one_billion() { assert_eq!(say::encode(1_000_000_000), String::from("one billion")); } @@ -133,7 +133,7 @@ fn test_987654321123() { */ #[test] #[ignore] -fn test_max_i64() { +fn max_i64() { assert_eq!( say::encode(9_223_372_036_854_775_807), String::from( @@ -147,7 +147,7 @@ fn test_max_i64() { #[test] #[ignore] -fn test_max_u64() { +fn max_u64() { assert_eq!( say::encode(18_446_744_073_709_551_615), String::from( diff --git a/exercises/practice/scale-generator/.meta/example.rs b/exercises/practice/scale-generator/.meta/example.rs index ceddf50f4..779218909 100644 --- a/exercises/practice/scale-generator/.meta/example.rs +++ b/exercises/practice/scale-generator/.meta/example.rs @@ -102,17 +102,17 @@ pub mod interval { use super::*; #[test] - fn test_parse_chromatic() { + fn parse_chromatic() { assert!("mmmmmmmmmmmm".parse::().is_ok()); } #[test] - fn test_parse_major() { + fn parse_major() { assert!("MMmMMMm".parse::().is_ok()); } #[test] - fn test_parse_minor() { + fn parse_minor() { assert!("MmMMmMM".parse::().is_ok()); } } diff --git a/exercises/practice/scale-generator/tests/scale-generator.rs b/exercises/practice/scale-generator/tests/scale-generator.rs index 8d2bcd92a..2119a91ea 100644 --- a/exercises/practice/scale-generator/tests/scale-generator.rs +++ b/exercises/practice/scale-generator/tests/scale-generator.rs @@ -31,7 +31,7 @@ fn process_interval_case(tonic: &str, intervals: &str, expected: &[&str]) { #[test] /// Chromatic scale with sharps -fn test_chromatic_scale_with_sharps() { +fn chromatic_scale_with_sharps() { process_chromatic_case( "C", &[ @@ -43,7 +43,7 @@ fn test_chromatic_scale_with_sharps() { #[test] #[ignore] /// Chromatic scale with flats -fn test_chromatic_scale_with_flats() { +fn chromatic_scale_with_flats() { process_chromatic_case( "F", &[ @@ -61,28 +61,28 @@ fn test_chromatic_scale_with_flats() { /// Simple major scale /// /// The simplest major scale, with no sharps or flats. -fn test_simple_major_scale() { +fn simple_major_scale() { process_interval_case("C", "MMmMMMm", &["C", "D", "E", "F", "G", "A", "B", "C"]); } #[test] #[ignore] /// Major scale with sharps -fn test_major_scale_with_sharps() { +fn major_scale_with_sharps() { process_interval_case("G", "MMmMMMm", &["G", "A", "B", "C", "D", "E", "F#", "G"]); } #[test] #[ignore] /// Major scale with flats -fn test_major_scale_with_flats() { +fn major_scale_with_flats() { process_interval_case("F", "MMmMMMm", &["F", "G", "A", "Bb", "C", "D", "E", "F"]); } #[test] #[ignore] /// Minor scale with sharps -fn test_minor_scale_with_sharps() { +fn minor_scale_with_sharps() { process_interval_case( "f#", "MmMMmMM", @@ -93,7 +93,7 @@ fn test_minor_scale_with_sharps() { #[test] #[ignore] /// Minor scale with flats -fn test_minor_scale_with_flats() { +fn minor_scale_with_flats() { process_interval_case( "bb", "MmMMmMM", @@ -104,14 +104,14 @@ fn test_minor_scale_with_flats() { #[test] #[ignore] /// Dorian mode -fn test_dorian_mode() { +fn dorian_mode() { process_interval_case("d", "MmMMMmM", &["D", "E", "F", "G", "A", "B", "C", "D"]); } #[test] #[ignore] /// Mixolydian mode -fn test_mixolydian_mode() { +fn mixolydian_mode() { process_interval_case( "Eb", "MMmMMmM", @@ -122,7 +122,7 @@ fn test_mixolydian_mode() { #[test] #[ignore] /// Lydian mode -fn test_lydian_mode() { +fn lydian_mode() { process_interval_case( "a", "MMMmMMm", @@ -133,14 +133,14 @@ fn test_lydian_mode() { #[test] #[ignore] /// Phrygian mode -fn test_phrygian_mode() { +fn phrygian_mode() { process_interval_case("e", "mMMMmMM", &["E", "F", "G", "A", "B", "C", "D", "E"]); } #[test] #[ignore] /// Locrian mode -fn test_locrian_mode() { +fn locrian_mode() { process_interval_case( "g", "mMMmMMM", @@ -153,14 +153,14 @@ fn test_locrian_mode() { /// Harmonic minor /// /// Note that this case introduces the augmented second interval (A) -fn test_harmonic_minor() { +fn harmonic_minor() { process_interval_case("d", "MmMMmAm", &["D", "E", "F", "G", "A", "Bb", "Db", "D"]); } #[test] #[ignore] /// Octatonic -fn test_octatonic() { +fn octatonic() { process_interval_case( "C", "MmMmMmMm", @@ -171,21 +171,21 @@ fn test_octatonic() { #[test] #[ignore] /// Hexatonic -fn test_hexatonic() { +fn hexatonic() { process_interval_case("Db", "MMMMMM", &["Db", "Eb", "F", "G", "A", "B", "Db"]); } #[test] #[ignore] /// Pentatonic -fn test_pentatonic() { +fn pentatonic() { process_interval_case("A", "MMAMA", &["A", "B", "C#", "E", "F#", "A"]); } #[test] #[ignore] /// Enigmatic -fn test_enigmatic() { +fn enigmatic() { process_interval_case( "G", "mAMMMmm", diff --git a/exercises/practice/series/tests/series.rs b/exercises/practice/series/tests/series.rs index ddfb6e2da..4757d8b55 100644 --- a/exercises/practice/series/tests/series.rs +++ b/exercises/practice/series/tests/series.rs @@ -1,14 +1,14 @@ use series::*; #[test] -fn test_with_zero_length() { +fn with_zero_length() { let expected = vec!["".to_string(); 6]; assert_eq!(series("92017", 0), expected); } #[test] #[ignore] -fn test_with_length_2() { +fn with_length_2() { let expected = vec![ "92".to_string(), "20".to_string(), @@ -20,21 +20,21 @@ fn test_with_length_2() { #[test] #[ignore] -fn test_with_numbers_length() { +fn with_numbers_length() { let expected = vec!["92017".to_string()]; assert_eq!(series("92017", 5), expected); } #[test] #[ignore] -fn test_too_long() { +fn too_long() { let expected: Vec = vec![]; assert_eq!(series("92017", 6), expected); } #[test] #[ignore] -fn test_way_too_long() { +fn way_too_long() { let expected: Vec = vec![]; assert_eq!(series("92017", 42), expected); } diff --git a/exercises/practice/simple-linked-list/tests/simple-linked-list.rs b/exercises/practice/simple-linked-list/tests/simple-linked-list.rs index a95a66665..896528666 100644 --- a/exercises/practice/simple-linked-list/tests/simple-linked-list.rs +++ b/exercises/practice/simple-linked-list/tests/simple-linked-list.rs @@ -1,14 +1,14 @@ use simple_linked_list::SimpleLinkedList; #[test] -fn test_new_list_is_empty() { +fn new_list_is_empty() { let list: SimpleLinkedList = SimpleLinkedList::new(); assert_eq!(list.len(), 0, "list's length must be 0"); } #[test] #[ignore] -fn test_push_increments_length() { +fn push_increments_length() { let mut list: SimpleLinkedList = SimpleLinkedList::new(); list.push(1); assert_eq!(list.len(), 1, "list's length must be 1"); @@ -18,7 +18,7 @@ fn test_push_increments_length() { #[test] #[ignore] -fn test_pop_decrements_length() { +fn pop_decrements_length() { let mut list: SimpleLinkedList = SimpleLinkedList::new(); list.push(1); list.push(2); @@ -30,7 +30,7 @@ fn test_pop_decrements_length() { #[test] #[ignore] -fn test_is_empty() { +fn is_empty() { let mut list: SimpleLinkedList = SimpleLinkedList::new(); assert!(list.is_empty(), "List wasn't empty on creation"); for inserts in 0..100 { @@ -57,7 +57,7 @@ fn test_is_empty() { #[test] #[ignore] -fn test_pop_returns_head_element_and_removes_it() { +fn pop_returns_head_element_and_removes_it() { let mut list: SimpleLinkedList = SimpleLinkedList::new(); list.push(1); list.push(2); @@ -68,7 +68,7 @@ fn test_pop_returns_head_element_and_removes_it() { #[test] #[ignore] -fn test_peek_returns_reference_to_head_element_but_does_not_remove_it() { +fn peek_returns_reference_to_head_element_but_does_not_remove_it() { let mut list: SimpleLinkedList = SimpleLinkedList::new(); assert_eq!(list.peek(), None, "No element should be contained in list"); list.push(2); @@ -84,7 +84,7 @@ fn test_peek_returns_reference_to_head_element_but_does_not_remove_it() { #[test] #[ignore] -fn test_from_slice() { +fn from_slice() { let mut array = vec!["1", "2", "3", "4"]; let mut list: SimpleLinkedList<_> = array.drain(..).collect(); assert_eq!(list.pop(), Some("4")); @@ -95,7 +95,7 @@ fn test_from_slice() { #[test] #[ignore] -fn test_reverse() { +fn reverse() { let mut list: SimpleLinkedList = SimpleLinkedList::new(); list.push(1); list.push(2); @@ -109,7 +109,7 @@ fn test_reverse() { #[test] #[ignore] -fn test_into_vector() { +fn into_vector() { let mut v = Vec::new(); let mut s = SimpleLinkedList::new(); for i in 1..4 { diff --git a/exercises/practice/sublist/tests/sublist.rs b/exercises/practice/sublist/tests/sublist.rs index 9653ab157..c9fb9a1b1 100644 --- a/exercises/practice/sublist/tests/sublist.rs +++ b/exercises/practice/sublist/tests/sublist.rs @@ -9,25 +9,25 @@ fn empty_equals_empty() { #[test] #[ignore] -fn test_empty_is_a_sublist_of_anything() { +fn empty_is_a_sublist_of_anything() { assert_eq!(Comparison::Sublist, sublist(&[], &['a', 's', 'd', 'f'])); } #[test] #[ignore] -fn test_anything_is_a_superlist_of_empty() { +fn anything_is_a_superlist_of_empty() { assert_eq!(Comparison::Superlist, sublist(&['a', 's', 'd', 'f'], &[])); } #[test] #[ignore] -fn test_1_is_not_2() { +fn one_is_not_two() { assert_eq!(Comparison::Unequal, sublist(&[1], &[2])); } #[test] #[ignore] -fn test_compare_larger_equal_lists() { +fn compare_larger_equal_lists() { use std::iter::repeat; let v: Vec = repeat('x').take(1000).collect(); @@ -37,7 +37,7 @@ fn test_compare_larger_equal_lists() { #[test] #[ignore] -fn test_sublist_at_start() { +fn sublist_at_start() { assert_eq!(Comparison::Sublist, sublist(&[1, 2, 3], &[1, 2, 3, 4, 5])); } diff --git a/exercises/practice/two-bucket/tests/two-bucket.rs b/exercises/practice/two-bucket/tests/two-bucket.rs index 397195aed..30346ec0d 100644 --- a/exercises/practice/two-bucket/tests/two-bucket.rs +++ b/exercises/practice/two-bucket/tests/two-bucket.rs @@ -1,7 +1,7 @@ use two_bucket::{solve, Bucket, BucketStats}; #[test] -fn test_case_1() { +fn case_1() { assert_eq!( solve(3, 5, 1, &Bucket::One), Some(BucketStats { @@ -14,7 +14,7 @@ fn test_case_1() { #[test] #[ignore] -fn test_case_2() { +fn case_2() { assert_eq!( solve(3, 5, 1, &Bucket::Two), Some(BucketStats { @@ -27,7 +27,7 @@ fn test_case_2() { #[test] #[ignore] -fn test_case_3() { +fn case_3() { assert_eq!( solve(7, 11, 2, &Bucket::One), Some(BucketStats { @@ -40,7 +40,7 @@ fn test_case_3() { #[test] #[ignore] -fn test_case_4() { +fn case_4() { assert_eq!( solve(7, 11, 2, &Bucket::Two), Some(BucketStats { diff --git a/exercises/practice/word-count/tests/word-count.rs b/exercises/practice/word-count/tests/word-count.rs index 58f1eb6ee..fdbd37f06 100644 --- a/exercises/practice/word-count/tests/word-count.rs +++ b/exercises/practice/word-count/tests/word-count.rs @@ -14,19 +14,19 @@ fn check_word_count(s: &str, pairs: &[(&str, u32)]) { } #[test] -fn test_count_one_word() { +fn count_one_word() { check_word_count("word", &[("word", 1)]); } #[test] #[ignore] -fn test_count_one_of_each() { +fn count_one_of_each() { check_word_count("one of each", &[("one", 1), ("of", 1), ("each", 1)]); } #[test] #[ignore] -fn test_count_multiple_occurrences() { +fn count_multiple_occurrences() { check_word_count( "one fish two fish red fish blue fish", &[("one", 1), ("fish", 4), ("two", 1), ("red", 1), ("blue", 1)], @@ -47,7 +47,7 @@ fn expanded_lists() { #[test] #[ignore] -fn test_ignore_punctuation() { +fn ignore_punctuation() { check_word_count( "car : carpet as java : javascript!!&@$%^&", &[ @@ -62,7 +62,7 @@ fn test_ignore_punctuation() { #[test] #[ignore] -fn test_include_numbers() { +fn include_numbers() { check_word_count( "testing, 1, 2 testing", &[("testing", 2), ("1", 1), ("2", 1)], @@ -71,7 +71,7 @@ fn test_include_numbers() { #[test] #[ignore] -fn test_normalize_case() { +fn normalize_case() { check_word_count("go Go GO Stop stop", &[("go", 3), ("stop", 2)]); } diff --git a/exercises/practice/yacht/tests/yacht.rs b/exercises/practice/yacht/tests/yacht.rs index 116b170f0..9d8c6cffd 100644 --- a/exercises/practice/yacht/tests/yacht.rs +++ b/exercises/practice/yacht/tests/yacht.rs @@ -1,203 +1,203 @@ use yacht::*; #[test] -fn test_yacht() { +fn yacht() { let expected = 50; assert_eq!(score([5, 5, 5, 5, 5], Category::Yacht), expected); } #[test] #[ignore] -fn test_not_yacht() { +fn not_yacht() { let expected = 0; assert_eq!(score([1, 3, 3, 2, 5], Category::Yacht), expected); } #[test] #[ignore] -fn test_ones() { +fn ones() { let expected = 3; assert_eq!(score([1, 1, 1, 3, 5], Category::Ones), expected); } #[test] #[ignore] -fn test_ones_out_of_order() { +fn ones_out_of_order() { let expected = 3; assert_eq!(score([3, 1, 1, 5, 1], Category::Ones), expected); } #[test] #[ignore] -fn test_no_ones() { +fn no_ones() { let expected = 0; assert_eq!(score([4, 3, 6, 5, 5], Category::Ones), expected); } #[test] #[ignore] -fn test_twos() { +fn twos() { let expected = 2; assert_eq!(score([2, 3, 4, 5, 6], Category::Twos), expected); } #[test] #[ignore] -fn test_fours() { +fn fours() { let expected = 8; assert_eq!(score([1, 4, 1, 4, 1], Category::Fours), expected); } #[test] #[ignore] -fn test_yacht_counted_as_threes() { +fn yacht_counted_as_threes() { let expected = 15; assert_eq!(score([3, 3, 3, 3, 3], Category::Threes), expected); } #[test] #[ignore] -fn test_yacht_of_3s_counted_as_fives() { +fn yacht_of_3s_counted_as_fives() { let expected = 0; assert_eq!(score([3, 3, 3, 3, 3], Category::Fives), expected); } #[test] #[ignore] -fn test_fives() { +fn fives() { let expected = 10; assert_eq!(score([1, 5, 3, 5, 3], Category::Fives), expected); } #[test] #[ignore] -fn test_sixes() { +fn sixes() { let expected = 6; assert_eq!(score([2, 3, 4, 5, 6], Category::Sixes), expected); } #[test] #[ignore] -fn test_full_house_two_small_three_big() { +fn full_house_two_small_three_big() { let expected = 16; assert_eq!(score([2, 2, 4, 4, 4], Category::FullHouse), expected); } #[test] #[ignore] -fn test_full_house_three_small_two_big() { +fn full_house_three_small_two_big() { let expected = 19; assert_eq!(score([5, 3, 3, 5, 3], Category::FullHouse), expected); } #[test] #[ignore] -fn test_two_pair_is_not_a_full_house() { +fn two_pair_is_not_a_full_house() { let expected = 0; assert_eq!(score([2, 2, 4, 4, 5], Category::FullHouse), expected); } #[test] #[ignore] -fn test_four_of_a_kind_is_not_a_full_house() { +fn four_of_a_kind_is_not_a_full_house() { let expected = 0; assert_eq!(score([1, 4, 4, 4, 4], Category::FullHouse), expected); } #[test] #[ignore] -fn test_yacht_is_not_a_full_house() { +fn yacht_is_not_a_full_house() { let expected = 0; assert_eq!(score([2, 2, 2, 2, 2], Category::FullHouse), expected); } #[test] #[ignore] -fn test_four_of_a_kind() { +fn four_of_a_kind() { let expected = 24; assert_eq!(score([6, 6, 4, 6, 6], Category::FourOfAKind), expected); } #[test] #[ignore] -fn test_yacht_can_be_scored_as_four_of_a_kind() { +fn yacht_can_be_scored_as_four_of_a_kind() { let expected = 12; assert_eq!(score([3, 3, 3, 3, 3], Category::FourOfAKind), expected); } #[test] #[ignore] -fn test_full_house_is_not_four_of_a_kind() { +fn full_house_is_not_four_of_a_kind() { let expected = 0; assert_eq!(score([3, 3, 3, 5, 5], Category::FourOfAKind), expected); } #[test] #[ignore] -fn test_little_straight() { +fn little_straight() { let expected = 30; assert_eq!(score([3, 5, 4, 1, 2], Category::LittleStraight), expected); } #[test] #[ignore] -fn test_little_straight_as_big_straight() { +fn little_straight_as_big_straight() { let expected = 0; assert_eq!(score([1, 2, 3, 4, 5], Category::BigStraight), expected); } #[test] #[ignore] -fn test_four_in_order_but_not_a_little_straight() { +fn four_in_order_but_not_a_little_straight() { let expected = 0; assert_eq!(score([1, 1, 2, 3, 4], Category::LittleStraight), expected); } #[test] #[ignore] -fn test_no_pairs_but_not_a_little_straight() { +fn no_pairs_but_not_a_little_straight() { let expected = 0; assert_eq!(score([1, 2, 3, 4, 6], Category::LittleStraight), expected); } #[test] #[ignore] -fn test_minimum_is_1_maximum_is_5_but_not_a_little_straight() { +fn minimum_is_1_maximum_is_5_but_not_a_little_straight() { let expected = 0; assert_eq!(score([1, 1, 3, 4, 5], Category::LittleStraight), expected); } #[test] #[ignore] -fn test_big_straight() { +fn big_straight() { let expected = 30; assert_eq!(score([4, 6, 2, 5, 3], Category::BigStraight), expected); } #[test] #[ignore] -fn test_big_straight_as_little_straight() { +fn big_straight_as_little_straight() { let expected = 0; assert_eq!(score([6, 5, 4, 3, 2], Category::LittleStraight), expected); } #[test] #[ignore] -fn test_no_pairs_but_not_a_big_straight() { +fn no_pairs_but_not_a_big_straight() { let expected = 0; assert_eq!(score([6, 5, 4, 3, 1], Category::BigStraight), expected); } #[test] #[ignore] -fn test_choice() { +fn choice() { let expected = 23; assert_eq!(score([3, 3, 5, 6, 6], Category::Choice), expected); } #[test] #[ignore] -fn test_yacht_as_choice() { +fn yacht_as_choice() { let expected = 10; assert_eq!(score([2, 2, 2, 2, 2], Category::Choice), expected); } diff --git a/rust-tooling/src/exercise_config.rs b/rust-tooling/src/exercise_config.rs index f8b170356..9ff9b6dae 100644 --- a/rust-tooling/src/exercise_config.rs +++ b/rust-tooling/src/exercise_config.rs @@ -86,7 +86,7 @@ pub fn get_all_exercise_paths() -> impl Iterator { } #[test] -fn test_deserialize_all() { +fn deserialize_all() { for path in get_all_concept_exercise_paths() { let config_path = format!("{path}/.meta/config.json"); let config_contents = std::fs::read_to_string(config_path).unwrap(); diff --git a/rust-tooling/src/problem_spec.rs b/rust-tooling/src/problem_spec.rs index 073b8ba97..b32bef808 100644 --- a/rust-tooling/src/problem_spec.rs +++ b/rust-tooling/src/problem_spec.rs @@ -52,7 +52,7 @@ pub fn get_canonical_data(slug: &str) -> CanonicalData { } #[test] -fn test_deserialize_canonical_data() { +fn deserialize_canonical_data() { crate::fs_utils::cd_into_repo_root(); for entry in ignore::Walk::new("problem-specifications/exercises") .filter_map(|e| e.ok()) diff --git a/rust-tooling/tests/bash_script_conventions.rs b/rust-tooling/tests/bash_script_conventions.rs index db34d150e..a6a9091ab 100644 --- a/rust-tooling/tests/bash_script_conventions.rs +++ b/rust-tooling/tests/bash_script_conventions.rs @@ -24,7 +24,7 @@ fn for_all_scripts(f: fn(&str)) { } #[test] -fn test_file_extension() { +fn file_extension() { for_all_scripts(|file_name| { assert!( file_name.ends_with(".sh"), @@ -34,7 +34,7 @@ fn test_file_extension() { } #[test] -fn test_snake_case_name() { +fn snake_case_name() { for_all_scripts(|file_name| { let file_name = file_name.trim_end_matches(".sh"); @@ -47,7 +47,7 @@ fn test_snake_case_name() { /// Notably on nixOS and macOS, bash is not installed in `/bin/bash`. #[test] -fn test_portable_shebang() { +fn portable_shebang() { for_all_scripts(|file_name| { let contents = std::fs::read_to_string(PathBuf::from("bin").join(file_name)).unwrap(); assert!( diff --git a/rust-tooling/tests/count_ignores.rs b/rust-tooling/tests/count_ignores.rs index 4a78d04ca..9b7349624 100644 --- a/rust-tooling/tests/count_ignores.rs +++ b/rust-tooling/tests/count_ignores.rs @@ -16,7 +16,7 @@ fn assert_one_less_ignore_than_tests(path: &str) { } #[test] -fn test_count_ignores() { +fn count_ignores() { for path in get_all_concept_exercise_paths() { assert_one_less_ignore_than_tests(&path); } diff --git a/rust-tooling/tests/difficulties.rs b/rust-tooling/tests/difficulties.rs index 18c96bc03..3808829ed 100644 --- a/rust-tooling/tests/difficulties.rs +++ b/rust-tooling/tests/difficulties.rs @@ -3,7 +3,7 @@ use exercism_tooling::track_config::TRACK_CONFIG; #[test] -fn test_difficulties_are_valid() { +fn difficulties_are_valid() { let mut difficulties = TRACK_CONFIG .exercises .concept diff --git a/rust-tooling/tests/no_authors_in_cargo_toml.rs b/rust-tooling/tests/no_authors_in_cargo_toml.rs index c8428ce34..2e312c671 100644 --- a/rust-tooling/tests/no_authors_in_cargo_toml.rs +++ b/rust-tooling/tests/no_authors_in_cargo_toml.rs @@ -3,7 +3,7 @@ use exercism_tooling::exercise_config::get_all_exercise_paths; /// The package manifest of each exercise should not contain an `authors` field. /// The authors are already specified in the track configuration. #[test] -fn test_no_authors_in_cargo_toml() { +fn no_authors_in_cargo_toml() { let cargo_toml_paths = get_all_exercise_paths().map(|p| format!("{p}/Cargo.toml")); for path in cargo_toml_paths { diff --git a/rust-tooling/tests/no_trailing_whitespace.rs b/rust-tooling/tests/no_trailing_whitespace.rs index 540c160b9..9fc3254c7 100644 --- a/rust-tooling/tests/no_trailing_whitespace.rs +++ b/rust-tooling/tests/no_trailing_whitespace.rs @@ -13,7 +13,7 @@ fn contains_trailing_whitespace(p: &Path) -> bool { } #[test] -fn test_no_trailing_whitespace() { +fn no_trailing_whitespace() { fs_utils::cd_into_repo_root(); for entry in ignore::Walk::new("./") { From 3b05a5f8440aa96471fb6161439d52367ceb393d Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Thu, 14 Sep 2023 20:29:38 +0200 Subject: [PATCH 157/436] Add tera filter to_hex for use in test templates (#1732) and improve documentation about creating test templates --- docs/CONTRIBUTING.md | 64 ++++++++++++++++++++++++- rust-tooling/src/exercise_generation.rs | 16 ++++++- 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index a0eb8e3a8..eef87865c 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -33,6 +33,7 @@ Run `just add-practice-exercise` and you'll be prompted for the minimal information required to generate the exercise stub for you. After that, jump in the generated exercise and fill in any todos you find. This includes most notably: + - adding an example solution in `.meta/example.rs` - Adjusting `.meta/test_template.tera` @@ -41,12 +42,14 @@ The input of the template is the canonical data from [`problem-specifications`]. if you want to exclude certain tests from being generated, you have to set `include = false` in `.meta/tests.toml`. +Find some tips about writing tera templates [here](#tera-templates). + [Tera]: https://keats.github.io/tera/docs/ [`problem-specifications`]: https://github.com/exercism/problem-specifications/ Many aspects of a correctly implemented exercises are checked in CI. I recommend that instead of spending lots of time studying and writing -documentation about the process, *just do it*. +documentation about the process, _just do it_. If something breaks, fix it and add a test / automation so it won't happen anymore. @@ -87,6 +90,65 @@ Run `just update-practice-exercise` to update an exercise. This outsources most work to `configlet sync --update` and runs the test generator again. +When updaing an exercise that doesn't have a tera template yet, +a new one will be generated for you. +You will likely have to adjust it to some extent. + +Find some tips about writing tera templates [in the next section](#tera-templates). + +## Tera templates + +The full documentation for tera templates is [here][tera-docs]. +Following are some approaches that have worked for our specific needs. + +The name of the input property is different for each exercise. +The default template will be something like this: + +```txt +let input = {{ test.input | json_encode() }}; +``` + +You will have to add the specific field of input for this exercise, e.g. + +```txt +let input = {{ test.input.integers | json_encode() }}; +``` + +Some exercises may have error return values. +You can use an if-else to render something different, +depending on the structure of the data: + +```txt +let expected = {% if test.expected is object -%} + None +{%- else -%} + Some({{ test.expected }}) +{%- endif %}; +``` + +If every test case needs to do some crunching of the inputs, +you can add utils functions at the top of the tera template. +See [`word-count`'s template][word-count-tmpl] for an example. + +Some exercises have multiple functions that need to be implemented +by the student and therefore tested. +The canonical data contains a field `property` in that case. +The template also has access to a value `fn_names`, +which is an array of functions found in `lib.rs`. +So, you can construct if-else-chains based on `test.property` +and render a different element of `fn_names` based on that. +See [`variable-length-quantity`'s template][var-len-q-tmpl] for an example. + +There is a custom tera fiter `to_hex`, which formats ints in hexadecimal. +Feel free to add your own in the crate `rust-tooling`. +Custom filters added there will be available to all templates. +How to create such custom filters is documented int he [tera docs][tera-docs-filters]. + +[tera-docs]: https://keats.github.io/tera/docs/#templates +[word-count-tmpl]: /exercises/practice/word-count/.meta/test_template.tera +[var-len-q-tmpl]: /exercises/practice/variable-length-quantity/.meta/test_template.tera +[tera-docs-filters]: https://keats.github.io/tera/docs/#filters + ## Syllabus The syllabus is currently deactivated due to low quality. diff --git a/rust-tooling/src/exercise_generation.rs b/rust-tooling/src/exercise_generation.rs index 028776a61..9e403d2e0 100644 --- a/rust-tooling/src/exercise_generation.rs +++ b/rust-tooling/src/exercise_generation.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use tera::Context; use crate::{ @@ -81,6 +83,13 @@ fn extend_single_cases(single_cases: &mut Vec, cases: Vec) -> tera::Result { + Ok(serde_json::Value::String(format!( + "{:x}", + value.as_u64().unwrap() + ))) +} + fn generate_tests(slug: &str, fn_names: Vec) -> String { let cases = get_canonical_data(slug).cases; let excluded_tests = get_excluded_tests(slug); @@ -90,6 +99,7 @@ fn generate_tests(slug: &str, fn_names: Vec) -> String { .add_raw_template("test_template.tera", TEST_TEMPLATE) .unwrap(); } + template.register_filter("to_hex", to_hex); let mut single_cases = Vec::new(); extend_single_cases(&mut single_cases, cases); @@ -100,5 +110,9 @@ fn generate_tests(slug: &str, fn_names: Vec) -> String { context.insert("fn_names", &fn_names); context.insert("cases", &single_cases); - template.render("test_template.tera", &context).unwrap().trim_start().into() + template + .render("test_template.tera", &context) + .unwrap() + .trim_start() + .into() } From 2a14ac5c2edbe9537351f61048dacc9f5b7a40f3 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Fri, 15 Sep 2023 11:12:42 +0200 Subject: [PATCH 158/436] Improve example solution of circular-buffer (#1724) The trait bounds `Default + Clone` are semantically incorrect. A proper circular buffer needs to handle all kinds of elements. The reason these bounds are here is because it allows to avoid `unsafe` while enjoying the most efficient memory layout. However, there is another performance downside: The implementations of `Default` and `Clone` may be expensive (e.g. cause heap allocations.) As came up in this discussion, there are other weird side effects, for example for `Rc` which has a non-standard implementation of `Clone`: https://github.com/exercism/rust/pull/1652 The approach I chose here get's rid of the trait bounds and wraps every element in an `Option`. In the worst case, this may lead to twice the memory consumption. (e.g. `Option` takes 16 bytes because of alignment) This approach is semantically correct, but not the most performant. The most performant solution would be to use `unsafe`. I'd like to avoid that in example solutions. --- .../practice/circular-buffer/.meta/example.rs | 50 +++++++++---------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/exercises/practice/circular-buffer/.meta/example.rs b/exercises/practice/circular-buffer/.meta/example.rs index 489891063..b85bcb558 100644 --- a/exercises/practice/circular-buffer/.meta/example.rs +++ b/exercises/practice/circular-buffer/.meta/example.rs @@ -4,20 +4,21 @@ pub enum Error { FullBuffer, } -pub struct CircularBuffer { - buffer: Vec, - size: usize, +pub struct CircularBuffer { + /// Using Option leads to less efficient memory layout, but + /// it allows us to avoid using `unsafe` to handle uninitialized + /// mempory ourselves. + data: Vec>, start: usize, end: usize, } -impl CircularBuffer { - // this circular buffer keeps an unallocated slot between the start and the end - // when the buffer is full. - pub fn new(size: usize) -> CircularBuffer { - CircularBuffer { - buffer: vec![T::default(); size + 1], - size: size + 1, +impl CircularBuffer { + pub fn new(capacity: usize) -> Self { + let mut data = Vec::with_capacity(capacity); + data.resize_with(capacity, || None); + Self { + data, start: 0, end: 0, } @@ -27,8 +28,9 @@ impl CircularBuffer { if self.is_empty() { return Err(Error::EmptyBuffer); } - - let v = self.buffer[self.start].clone(); + let v = self.data[self.start] + .take() + .expect("should not read 'uninitialized' memory"); self.advance_start(); Ok(v) } @@ -37,18 +39,16 @@ impl CircularBuffer { if self.is_full() { return Err(Error::FullBuffer); } - - self.buffer[self.end] = byte; + self.data[self.end] = Some(byte); self.advance_end(); Ok(()) } pub fn overwrite(&mut self, byte: T) { - if self.is_full() { + self.data[self.end] = Some(byte); + if self.start == self.end { self.advance_start(); } - - self.buffer[self.end] = byte; self.advance_end(); } @@ -57,24 +57,22 @@ impl CircularBuffer { self.end = 0; // Clear any values in the buffer - for element in self.buffer.iter_mut() { - std::mem::take(element); - } + self.data.fill_with(|| None); } - pub fn is_empty(&self) -> bool { - self.start == self.end + fn is_empty(&self) -> bool { + self.start == self.end && self.data[self.start].is_none() } - pub fn is_full(&self) -> bool { - (self.end + 1) % self.size == self.start + fn is_full(&self) -> bool { + self.start == self.end && self.data[self.start].is_some() } fn advance_start(&mut self) { - self.start = (self.start + 1) % self.size; + self.start = (self.start + 1) % self.data.len(); } fn advance_end(&mut self) { - self.end = (self.end + 1) % self.size; + self.end = (self.end + 1) % self.data.len(); } } From 212ee0ae997bc0f7f3742e70803e26c3a497f4d7 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Fri, 15 Sep 2023 11:15:27 +0200 Subject: [PATCH 159/436] Improve test order of low-power-embedded-game (#1725) Source: https://github.com/exercism/rust/pull/1583 Students may think they have to work with values instead of their position. This confusion can easily be avoided if the first test uses strings. [no important files changed] --- .../tests/low-power-embedded-game.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/exercises/concept/low-power-embedded-game/tests/low-power-embedded-game.rs b/exercises/concept/low-power-embedded-game/tests/low-power-embedded-game.rs index 10d965e45..580c1b285 100644 --- a/exercises/concept/low-power-embedded-game/tests/low-power-embedded-game.rs +++ b/exercises/concept/low-power-embedded-game/tests/low-power-embedded-game.rs @@ -38,6 +38,15 @@ mod divmod { mod evens { use low_power_embedded_game::evens; + #[test] + #[ignore] + fn strs() { + let input = "You really must never be above joking.".split_whitespace(); + let expected: Vec<_> = "You must be joking.".split_whitespace().collect(); + let out: Vec<_> = evens(input).collect(); + assert_eq!(out, expected); + } + #[test] #[ignore] fn simple_i32() { @@ -58,15 +67,6 @@ mod evens { let out: Vec = evens(1..).take(5).collect(); assert_eq!(out, &[1, 3, 5, 7, 9]); } - - #[test] - #[ignore] - fn strs() { - let input = "You really must never be above joking.".split_whitespace(); - let expected: Vec<_> = "You must be joking.".split_whitespace().collect(); - let out: Vec<_> = evens(input).collect(); - assert_eq!(out, expected); - } } mod manhattan { From 7328ece3e78c57e45e26bb975e03920a8eacc604 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Fri, 15 Sep 2023 11:16:46 +0200 Subject: [PATCH 160/436] Decimal: Compare different numbers of digits (#1726) source: https://github.com/exercism/rust/pull/1572 --- exercises/practice/decimal/tests/decimal.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/exercises/practice/decimal/tests/decimal.rs b/exercises/practice/decimal/tests/decimal.rs index 74a11e1c8..ebdf9d7d5 100644 --- a/exercises/practice/decimal/tests/decimal.rs +++ b/exercises/practice/decimal/tests/decimal.rs @@ -108,6 +108,17 @@ fn gt_negative_and_zero() { assert!(decimal("0.0") > decimal("-1.0")); } +#[test] +#[ignore] +fn unequal_number_of_decimal_places() { + assert!(decimal("3.14") > decimal("3.13")); + assert!(decimal("3.14") > decimal("3.131")); + assert!(decimal("3.14") > decimal("3.1")); + assert!(decimal("3.13") < decimal("3.14")); + assert!(decimal("3.131") < decimal("3.14")); + assert!(decimal("3.1") < decimal("3.14")); +} + // tests of arbitrary precision behavior #[test] #[ignore] From 7f2d256ed27ec4ed66abd69a639dd3d6901e0c5d Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Sat, 16 Sep 2023 22:31:27 +0200 Subject: [PATCH 161/436] Sync exercise wordy with problem spec (#1728) --- .../practice/wordy/.docs/instructions.md | 17 +- .../practice/wordy/.meta/test_template.tera | 16 ++ exercises/practice/wordy/.meta/tests.toml | 19 ++- exercises/practice/wordy/tests/wordy.rs | 156 ++++++++++++------ 4 files changed, 138 insertions(+), 70 deletions(-) create mode 100644 exercises/practice/wordy/.meta/test_template.tera diff --git a/exercises/practice/wordy/.docs/instructions.md b/exercises/practice/wordy/.docs/instructions.md index f65b05acf..0b9e67b6c 100644 --- a/exercises/practice/wordy/.docs/instructions.md +++ b/exercises/practice/wordy/.docs/instructions.md @@ -40,8 +40,7 @@ Now, perform the other three operations. Handle a set of operations, in sequence. -Since these are verbal word problems, evaluate the expression from -left-to-right, _ignoring the typical order of operations._ +Since these are verbal word problems, evaluate the expression from left-to-right, _ignoring the typical order of operations._ > What is 5 plus 13 plus 6? @@ -55,14 +54,6 @@ left-to-right, _ignoring the typical order of operations._ The parser should reject: -* Unsupported operations ("What is 52 cubed?") -* Non-math questions ("Who is the President of the United States") -* Word problems with invalid syntax ("What is 1 plus plus 2?") - -## Bonus — Exponentials - -If you'd like, handle exponentials. - -> What is 2 raised to the 5th power? - -32 +- Unsupported operations ("What is 52 cubed?") +- Non-math questions ("Who is the President of the United States") +- Word problems with invalid syntax ("What is 1 plus plus 2?") diff --git a/exercises/practice/wordy/.meta/test_template.tera b/exercises/practice/wordy/.meta/test_template.tera new file mode 100644 index 000000000..20f0191ba --- /dev/null +++ b/exercises/practice/wordy/.meta/test_template.tera @@ -0,0 +1,16 @@ +{% for test in cases %} +#[test] +{% if loop.index != 1 -%} +#[ignore] +{% endif -%} +fn {{ test.description | slugify | replace(from="-", to="_") }}() { + let input = {{ test.input | json_encode() }}; + let output = {{ crate_name }}::{{ fn_names[0] }}(input); + let expected = {% if test.expected is object -%} + None + {%- else -%} + Some({{ test.expected }}) + {%- endif %}; + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/wordy/.meta/tests.toml b/exercises/practice/wordy/.meta/tests.toml index b6a29d500..f812dfa98 100644 --- a/exercises/practice/wordy/.meta/tests.toml +++ b/exercises/practice/wordy/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [88bf4b28-0de3-4883-93c7-db1b14aa806e] description = "just a number" @@ -38,9 +45,15 @@ description = "multiple subtraction" [4f4a5749-ef0c-4f60-841f-abcfaf05d2ae] description = "subtraction then addition" +[312d908c-f68f-42c9-aa75-961623cc033f] +description = "multiple multiplication" + [38e33587-8940-4cc1-bc28-bfd7e3966276] description = "addition and multiplication" +[3c854f97-9311-46e8-b574-92b60d17d394] +description = "multiple division" + [3ad3e433-8af7-41ec-aa9b-97b42ab49357] description = "unknown operation" diff --git a/exercises/practice/wordy/tests/wordy.rs b/exercises/practice/wordy/tests/wordy.rs index 6550af76f..765011883 100644 --- a/exercises/practice/wordy/tests/wordy.rs +++ b/exercises/practice/wordy/tests/wordy.rs @@ -1,177 +1,225 @@ -use wordy::answer; - #[test] fn just_a_number() { - let command = "What is 5?"; - assert_eq!(Some(5), answer(command)); + let input = "What is 5?"; + let output = wordy::answer(input); + let expected = Some(5); + assert_eq!(output, expected); } #[test] #[ignore] fn addition() { - let command = "What is 1 plus 1?"; - assert_eq!(Some(2), answer(command)); + let input = "What is 1 plus 1?"; + let output = wordy::answer(input); + let expected = Some(2); + assert_eq!(output, expected); } #[test] #[ignore] fn more_addition() { - let command = "What is 53 plus 2?"; - assert_eq!(Some(55), answer(command)); + let input = "What is 53 plus 2?"; + let output = wordy::answer(input); + let expected = Some(55); + assert_eq!(output, expected); } #[test] #[ignore] fn addition_with_negative_numbers() { - let command = "What is -1 plus -10?"; - assert_eq!(Some(-11), answer(command)); + let input = "What is -1 plus -10?"; + let output = wordy::answer(input); + let expected = Some(-11); + assert_eq!(output, expected); } #[test] #[ignore] fn large_addition() { - let command = "What is 123 plus 45678?"; - assert_eq!(Some(45_801), answer(command)); + let input = "What is 123 plus 45678?"; + let output = wordy::answer(input); + let expected = Some(45801); + assert_eq!(output, expected); } #[test] #[ignore] fn subtraction() { - let command = "What is 4 minus -12?"; - assert_eq!(Some(16), answer(command)); + let input = "What is 4 minus -12?"; + let output = wordy::answer(input); + let expected = Some(16); + assert_eq!(output, expected); } #[test] #[ignore] fn multiplication() { - let command = "What is -3 multiplied by 25?"; - assert_eq!(Some(-75), answer(command)); + let input = "What is -3 multiplied by 25?"; + let output = wordy::answer(input); + let expected = Some(-75); + assert_eq!(output, expected); } #[test] #[ignore] fn division() { - let command = "What is 33 divided by -3?"; - assert_eq!(Some(-11), answer(command)); + let input = "What is 33 divided by -3?"; + let output = wordy::answer(input); + let expected = Some(-11); + assert_eq!(output, expected); } #[test] #[ignore] fn multiple_additions() { - let command = "What is 1 plus 1 plus 1?"; - assert_eq!(Some(3), answer(command)); + let input = "What is 1 plus 1 plus 1?"; + let output = wordy::answer(input); + let expected = Some(3); + assert_eq!(output, expected); } #[test] #[ignore] fn addition_and_subtraction() { - let command = "What is 1 plus 5 minus -2?"; - assert_eq!(Some(8), answer(command)); + let input = "What is 1 plus 5 minus -2?"; + let output = wordy::answer(input); + let expected = Some(8); + assert_eq!(output, expected); } #[test] #[ignore] fn multiple_subtraction() { - let command = "What is 20 minus 4 minus 13?"; - assert_eq!(Some(3), answer(command)); + let input = "What is 20 minus 4 minus 13?"; + let output = wordy::answer(input); + let expected = Some(3); + assert_eq!(output, expected); } #[test] #[ignore] fn subtraction_then_addition() { - let command = "What is 17 minus 6 plus 3?"; - assert_eq!(Some(14), answer(command)); + let input = "What is 17 minus 6 plus 3?"; + let output = wordy::answer(input); + let expected = Some(14); + assert_eq!(output, expected); } #[test] #[ignore] -fn multiple_multiplications() { - let command = "What is 2 multiplied by -2 multiplied by 3?"; - assert_eq!(Some(-12), answer(command)); +fn multiple_multiplication() { + let input = "What is 2 multiplied by -2 multiplied by 3?"; + let output = wordy::answer(input); + let expected = Some(-12); + assert_eq!(output, expected); } #[test] #[ignore] fn addition_and_multiplication() { - let command = "What is -3 plus 7 multiplied by -2?"; - assert_eq!(Some(-8), answer(command)); + let input = "What is -3 plus 7 multiplied by -2?"; + let output = wordy::answer(input); + let expected = Some(-8); + assert_eq!(output, expected); } #[test] #[ignore] -fn multiple_divisions() { - let command = "What is -12 divided by 2 divided by -3?"; - assert_eq!(Some(2), answer(command)); +fn multiple_division() { + let input = "What is -12 divided by 2 divided by -3?"; + let output = wordy::answer(input); + let expected = Some(2); + assert_eq!(output, expected); } #[test] #[ignore] fn unknown_operation() { - let command = "What is 52 cubed?"; - assert_eq!(None, answer(command)); + let input = "What is 52 cubed?"; + let output = wordy::answer(input); + let expected = None; + assert_eq!(output, expected); } #[test] #[ignore] fn non_math_question() { - let command = "Who is the President of the United States?"; - assert_eq!(None, answer(command)); + let input = "Who is the President of the United States?"; + let output = wordy::answer(input); + let expected = None; + assert_eq!(output, expected); } #[test] #[ignore] fn reject_problem_missing_an_operand() { - let command = "What is 1 plus?"; - assert_eq!(None, answer(command)); + let input = "What is 1 plus?"; + let output = wordy::answer(input); + let expected = None; + assert_eq!(output, expected); } #[test] #[ignore] fn reject_problem_with_no_operands_or_operators() { - let command = "What is?"; - assert_eq!(None, answer(command)); + let input = "What is?"; + let output = wordy::answer(input); + let expected = None; + assert_eq!(output, expected); } #[test] #[ignore] fn reject_two_operations_in_a_row() { - let command = "What is 1 plus plus 2?"; - assert_eq!(None, answer(command)); + let input = "What is 1 plus plus 2?"; + let output = wordy::answer(input); + let expected = None; + assert_eq!(output, expected); } #[test] #[ignore] fn reject_two_numbers_in_a_row() { - let command = "What is 1 plus 2 1?"; - assert_eq!(None, answer(command)); + let input = "What is 1 plus 2 1?"; + let output = wordy::answer(input); + let expected = None; + assert_eq!(output, expected); } #[test] #[ignore] fn reject_postfix_notation() { - let command = "What is 1 2 plus?"; - assert_eq!(None, answer(command)); + let input = "What is 1 2 plus?"; + let output = wordy::answer(input); + let expected = None; + assert_eq!(output, expected); } #[test] #[ignore] fn reject_prefix_notation() { - let command = "What is plus 1 2?"; - assert_eq!(None, answer(command)); + let input = "What is plus 1 2?"; + let output = wordy::answer(input); + let expected = None; + assert_eq!(output, expected); } #[test] #[ignore] #[cfg(feature = "exponentials")] fn exponential() { - let command = "What is 2 raised to the 5th power?"; - assert_eq!(Some(32), answer(command)); + let input = "What is 2 raised to the 5th power?"; + let output = wordy::answer(input); + let expected = Some(32); + assert_eq!(output, expected); } #[test] #[ignore] #[cfg(feature = "exponentials")] fn addition_and_exponential() { - let command = "What is 1 plus 2 raised to the 2nd power?"; - assert_eq!(Some(9), answer(command)); + let input = "What is 1 plus 2 raised to the 2nd power?"; + let output = wordy::answer(input); + let expected = Some(9); + assert_eq!(output, expected); } From d50471466997bc8bbfa3134987f09239b7f3fe30 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Mon, 18 Sep 2023 12:24:18 +0200 Subject: [PATCH 162/436] Symlink problem-specifications for easy reference (#1737) This is handy when referring to `canonical-data.json` of an exercise, while creating / updating the test template for it. It's all available in the same spot. --- .gitignore | 1 + bin/symlink_problem_specifications.sh | 12 ++++++++++++ docs/CONTRIBUTING.md | 5 +++++ 3 files changed, 18 insertions(+) create mode 100755 bin/symlink_problem_specifications.sh diff --git a/.gitignore b/.gitignore index edcc454ae..5af0f3619 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ tmp exercises/*/*/Cargo.lock exercises/*/*/clippy.log .vscode +.prob-spec diff --git a/bin/symlink_problem_specifications.sh b/bin/symlink_problem_specifications.sh new file mode 100755 index 000000000..bf86ebaa9 --- /dev/null +++ b/bin/symlink_problem_specifications.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -eo pipefail + +cd "$(git rev-parse --show-toplevel)" + +for exercise in exercises/practice/*; do + name="$(basename "$exercise")" + if [ -d "problem-specifications/exercises/$name" ]; then + [ -e "$exercise/.prob-spec" ] && rm "$exercise/.prob-spec" + ln -s "../../../problem-specifications/exercises/$name" "$exercise/.prob-spec" + fi +done diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index eef87865c..7d2e57e05 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -101,6 +101,11 @@ Find some tips about writing tera templates [in the next section](#tera-template The full documentation for tera templates is [here][tera-docs]. Following are some approaches that have worked for our specific needs. +You will likely want to look at the exercise's `canonical-data.json` +to see what structure your input data has. +You can use `bin/symlink_problem_specifications.sh` to have this data +symlinked into the actual exercise directory. Handy! + The name of the input property is different for each exercise. The default template will be something like this: From 3854da7d7408377f1413c55ef0e4aae73fd76598 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Mon, 18 Sep 2023 12:25:32 +0200 Subject: [PATCH 163/436] doubly-linked-list: Fix inaccurate instructions (#1740) closes #1376 --- exercises/practice/doubly-linked-list/.docs/instructions.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/exercises/practice/doubly-linked-list/.docs/instructions.md b/exercises/practice/doubly-linked-list/.docs/instructions.md index 4132785db..c8cf7898f 100644 --- a/exercises/practice/doubly-linked-list/.docs/instructions.md +++ b/exercises/practice/doubly-linked-list/.docs/instructions.md @@ -3,11 +3,7 @@ Write a doubly linked list using unsafe Rust, including an iterator over the list and a cursor for efficient mutation. -The doubly linked list is a fundamental data structure in computer science, -often used in the implementation of other data structures. They're -pervasive in functional programming languages, such as Clojure, Erlang, -or Haskell, but far less common in imperative languages such as Ruby or -Python. +The doubly linked list is a fundamental data structure in computer science. Each node in a doubly linked list contains data and pointers to the next and previous node, if they exist. From b164b6d73affdc4a0235dd21290e2533269e45d8 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Mon, 18 Sep 2023 12:26:31 +0200 Subject: [PATCH 164/436] Sync two-bucket with problem-specifications (#1739) --- .../practice/two-bucket/.docs/instructions.md | 42 ++++-- .../practice/two-bucket/.meta/config.json | 2 +- .../two-bucket/.meta/test_template.tera | 31 ++++ .../practice/two-bucket/.meta/tests.toml | 40 +++++- .../practice/two-bucket/tests/two-bucket.rs | 134 +++++++++--------- 5 files changed, 167 insertions(+), 82 deletions(-) create mode 100644 exercises/practice/two-bucket/.meta/test_template.tera diff --git a/exercises/practice/two-bucket/.docs/instructions.md b/exercises/practice/two-bucket/.docs/instructions.md index 73eb450ce..7249deb36 100644 --- a/exercises/practice/two-bucket/.docs/instructions.md +++ b/exercises/practice/two-bucket/.docs/instructions.md @@ -1,30 +1,46 @@ # Instructions -Given two buckets of different size, demonstrate how to measure an exact number of liters by strategically transferring liters of fluid between the buckets. +Given two buckets of different size and which bucket to fill first, determine how many actions are required to measure an exact number of liters by strategically transferring fluid between the buckets. -Since this mathematical problem is fairly subject to interpretation / individual approach, the tests have been written specifically to expect one overarching solution. +There are some rules that your solution must follow: -To help, the tests provide you with which bucket to fill first. That means, when starting with the larger bucket full, you are NOT allowed at any point to have the smaller bucket full and the larger bucket empty (aka, the opposite starting point); that would defeat the purpose of comparing both approaches! +- You can only do one action at a time. +- There are only 3 possible actions: + 1. Pouring one bucket into the other bucket until either: + a) the first bucket is empty + b) the second bucket is full + 2. Emptying a bucket and doing nothing to the other. + 3. Filling a bucket and doing nothing to the other. +- After an action, you may not arrive at a state where the starting bucket is empty and the other bucket is full. Your program will take as input: + - the size of bucket one - the size of bucket two - the desired number of liters to reach - which bucket to fill first, either bucket one or bucket two Your program should determine: -- the total number of "moves" it should take to reach the desired number of liters, including the first fill -- which bucket should end up with the desired number of liters (let's say this is bucket A) - either bucket one or bucket two -- how many liters are left in the other bucket (bucket B) -Note: any time a change is made to either or both buckets counts as one (1) move. +- the total number of actions it should take to reach the desired number of liters, including the first fill of the starting bucket +- which bucket should end up with the desired number of liters - either bucket one or bucket two +- how many liters are left in the other bucket + +Note: any time a change is made to either or both buckets counts as one (1) action. Example: -Bucket one can hold up to 7 liters, and bucket two can hold up to 11 liters. Let's say bucket one, at a given step, is holding 7 liters, and bucket two is holding 8 liters (7,8). If you empty bucket one and make no change to bucket two, leaving you with 0 liters and 8 liters respectively (0,8), that counts as one "move". Instead, if you had poured from bucket one into bucket two until bucket two was full, leaving you with 4 liters in bucket one and 11 liters in bucket two (4,11), that would count as only one "move" as well. +Bucket one can hold up to 7 liters, and bucket two can hold up to 11 liters. +Let's say at a given step, bucket one is holding 7 liters and bucket two is holding 8 liters (7,8). +If you empty bucket one and make no change to bucket two, leaving you with 0 liters and 8 liters respectively (0,8), that counts as one action. +Instead, if you had poured from bucket one into bucket two until bucket two was full, resulting in 4 liters in bucket one and 11 liters in bucket two (4,11), that would also only count as one action. + +Another Example: +Bucket one can hold 3 liters, and bucket two can hold up to 5 liters. +You are told you must start with bucket one. +So your first action is to fill bucket one. +You choose to empty bucket one for your second action. +For your third action, you may not fill bucket two, because this violates the third rule -- you may not end up in a state after any action where the starting bucket is empty and the other bucket is full. -To conclude, the only valid moves are: -- pouring from either bucket to another -- emptying either bucket and doing nothing to the other -- filling either bucket and doing nothing to the other +Written with <3 at [Fullstack Academy][fullstack] by Lindsay Levine. -Written with <3 at [Fullstack Academy](http://www.fullstackacademy.com/) by Lindsay Levine. +[fullstack]: https://www.fullstackacademy.com/ diff --git a/exercises/practice/two-bucket/.meta/config.json b/exercises/practice/two-bucket/.meta/config.json index aa046009b..bcc6a5e36 100644 --- a/exercises/practice/two-bucket/.meta/config.json +++ b/exercises/practice/two-bucket/.meta/config.json @@ -30,5 +30,5 @@ }, "blurb": "Given two buckets of different size, demonstrate how to measure an exact number of liters.", "source": "Water Pouring Problem", - "source_url": "http://demonstrations.wolfram.com/WaterPouringProblem/" + "source_url": "https://demonstrations.wolfram.com/WaterPouringProblem/" } diff --git a/exercises/practice/two-bucket/.meta/test_template.tera b/exercises/practice/two-bucket/.meta/test_template.tera new file mode 100644 index 000000000..b6de2697b --- /dev/null +++ b/exercises/practice/two-bucket/.meta/test_template.tera @@ -0,0 +1,31 @@ +{% macro bucket(label) -%} + {% if label == 'one' -%} + Bucket::One + {%- else -%} + Bucket::Two + {%- endif %} +{%- endmacro bucket -%} + +use two_bucket::{Bucket, BucketStats}; +{% for test in cases %} +#[test] +{% if loop.index != 1 -%} +#[ignore] +{% endif -%} +fn {{ test.description | slugify | replace(from="-", to="_") }}() { + let output = {{ crate_name }}::{{ fn_names[0] }}( + {{ test.input.bucketOne }}, {{ test.input.bucketTwo }}, {{ test.input.goal }}, + &{{ self::bucket(label=test.input.startBucket) }}, + ); + let expected = {% if 'error' in test.expected -%} + None + {%- else -%} + Some(BucketStats { + moves: {{ test.expected.moves }}, + goal_bucket: {{ self::bucket(label=test.expected.goalBucket) }}, + other_bucket: {{ test.expected.otherBucket }}, + }) + {%- endif %}; + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/two-bucket/.meta/tests.toml b/exercises/practice/two-bucket/.meta/tests.toml index be690e975..d6ff02f53 100644 --- a/exercises/practice/two-bucket/.meta/tests.toml +++ b/exercises/practice/two-bucket/.meta/tests.toml @@ -1,3 +1,37 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[a6f2b4ba-065f-4dca-b6f0-e3eee51cb661] +description = "Measure using bucket one of size 3 and bucket two of size 5 - start with bucket one" + +[6c4ea451-9678-4926-b9b3-68364e066d40] +description = "Measure using bucket one of size 3 and bucket two of size 5 - start with bucket two" + +[3389f45e-6a56-46d5-9607-75aa930502ff] +description = "Measure using bucket one of size 7 and bucket two of size 11 - start with bucket one" + +[fe0ff9a0-3ea5-4bf7-b17d-6d4243961aa1] +description = "Measure using bucket one of size 7 and bucket two of size 11 - start with bucket two" + +[0ee1f57e-da84-44f7-ac91-38b878691602] +description = "Measure one step using bucket one of size 1 and bucket two of size 3 - start with bucket two" + +[eb329c63-5540-4735-b30b-97f7f4df0f84] +description = "Measure using bucket one of size 2 and bucket two of size 3 - start with bucket one and end with bucket two" + +[449be72d-b10a-4f4b-a959-ca741e333b72] +description = "Not possible to reach the goal" + +[aac38b7a-77f4-4d62-9b91-8846d533b054] +description = "With the same buckets but a different goal, then it is possible" + +[74633132-0ccf-49de-8450-af4ab2e3b299] +description = "Goal larger than both buckets is impossible" diff --git a/exercises/practice/two-bucket/tests/two-bucket.rs b/exercises/practice/two-bucket/tests/two-bucket.rs index 30346ec0d..a21311a25 100644 --- a/exercises/practice/two-bucket/tests/two-bucket.rs +++ b/exercises/practice/two-bucket/tests/two-bucket.rs @@ -1,97 +1,101 @@ -use two_bucket::{solve, Bucket, BucketStats}; +use two_bucket::{Bucket, BucketStats}; #[test] -fn case_1() { - assert_eq!( - solve(3, 5, 1, &Bucket::One), - Some(BucketStats { - moves: 4, - goal_bucket: Bucket::One, - other_bucket: 5, - }) - ); +fn measure_using_bucket_one_of_size_3_and_bucket_two_of_size_5_start_with_bucket_one() { + let output = two_bucket::solve(3, 5, 1, &Bucket::One); + let expected = Some(BucketStats { + moves: 4, + goal_bucket: Bucket::One, + other_bucket: 5, + }); + assert_eq!(output, expected); } #[test] #[ignore] -fn case_2() { - assert_eq!( - solve(3, 5, 1, &Bucket::Two), - Some(BucketStats { - moves: 8, - goal_bucket: Bucket::Two, - other_bucket: 3, - }) - ); +fn measure_using_bucket_one_of_size_3_and_bucket_two_of_size_5_start_with_bucket_two() { + let output = two_bucket::solve(3, 5, 1, &Bucket::Two); + let expected = Some(BucketStats { + moves: 8, + goal_bucket: Bucket::Two, + other_bucket: 3, + }); + assert_eq!(output, expected); } #[test] #[ignore] -fn case_3() { - assert_eq!( - solve(7, 11, 2, &Bucket::One), - Some(BucketStats { - moves: 14, - goal_bucket: Bucket::One, - other_bucket: 11, - }) - ); +fn measure_using_bucket_one_of_size_7_and_bucket_two_of_size_11_start_with_bucket_one() { + let output = two_bucket::solve(7, 11, 2, &Bucket::One); + let expected = Some(BucketStats { + moves: 14, + goal_bucket: Bucket::One, + other_bucket: 11, + }); + assert_eq!(output, expected); } #[test] #[ignore] -fn case_4() { - assert_eq!( - solve(7, 11, 2, &Bucket::Two), - Some(BucketStats { - moves: 18, - goal_bucket: Bucket::Two, - other_bucket: 7, - }) - ); +fn measure_using_bucket_one_of_size_7_and_bucket_two_of_size_11_start_with_bucket_two() { + let output = two_bucket::solve(7, 11, 2, &Bucket::Two); + let expected = Some(BucketStats { + moves: 18, + goal_bucket: Bucket::Two, + other_bucket: 7, + }); + assert_eq!(output, expected); } #[test] #[ignore] -fn goal_equal_to_start_bucket() { - assert_eq!( - solve(1, 3, 3, &Bucket::Two), - Some(BucketStats { - moves: 1, - goal_bucket: Bucket::Two, - other_bucket: 0, - }) - ); +fn measure_one_step_using_bucket_one_of_size_1_and_bucket_two_of_size_3_start_with_bucket_two() { + let output = two_bucket::solve(1, 3, 3, &Bucket::Two); + let expected = Some(BucketStats { + moves: 1, + goal_bucket: Bucket::Two, + other_bucket: 0, + }); + assert_eq!(output, expected); } #[test] #[ignore] -fn goal_equal_to_other_bucket() { - assert_eq!( - solve(2, 3, 3, &Bucket::One), - Some(BucketStats { - moves: 2, - goal_bucket: Bucket::Two, - other_bucket: 2, - }) - ); +fn measure_using_bucket_one_of_size_2_and_bucket_two_of_size_3_start_with_bucket_one_and_end_with_bucket_two( +) { + let output = two_bucket::solve(2, 3, 3, &Bucket::One); + let expected = Some(BucketStats { + moves: 2, + goal_bucket: Bucket::Two, + other_bucket: 2, + }); + assert_eq!(output, expected); } #[test] #[ignore] fn not_possible_to_reach_the_goal() { - assert_eq!(solve(6, 15, 5, &Bucket::One), None); + let output = two_bucket::solve(6, 15, 5, &Bucket::One); + let expected = None; + assert_eq!(output, expected); } #[test] #[ignore] -fn with_same_buckets_but_different_goal_then_it_is_possible() { - assert_eq!( - solve(6, 15, 9, &Bucket::One), - Some(BucketStats { - moves: 10, - goal_bucket: Bucket::Two, - other_bucket: 0, - }) - ); +fn with_the_same_buckets_but_a_different_goal_then_it_is_possible() { + let output = two_bucket::solve(6, 15, 9, &Bucket::One); + let expected = Some(BucketStats { + moves: 10, + goal_bucket: Bucket::Two, + other_bucket: 0, + }); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn goal_larger_than_both_buckets_is_impossible() { + let output = two_bucket::solve(5, 7, 8, &Bucket::One); + let expected = None; + assert_eq!(output, expected); } From 4383ddda26719e53d168db07b2cd2d26f70ed8bc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Sep 2023 12:27:38 +0200 Subject: [PATCH 165/436] Bump dtolnay/rust-toolchain (#1744) Bumps [dtolnay/rust-toolchain](https://github.com/dtolnay/rust-toolchain) from 0e66bd3e6b38ec0ad5312288c83e47c143e6b09e to 1482605bfc5719782e1267fd0c0cc350fe7646b8. - [Release notes](https://github.com/dtolnay/rust-toolchain/releases) - [Commits](https://github.com/dtolnay/rust-toolchain/compare/0e66bd3e6b38ec0ad5312288c83e47c143e6b09e...1482605bfc5719782e1267fd0c0cc350fe7646b8) --- updated-dependencies: - dependency-name: dtolnay/rust-toolchain dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/tests.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 92b6e955d..143bd9455 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -64,7 +64,7 @@ jobs: run: git fetch --depth=1 origin main - name: Setup toolchain - uses: dtolnay/rust-toolchain@0e66bd3e6b38ec0ad5312288c83e47c143e6b09e + uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 with: toolchain: ${{ matrix.rust }} @@ -87,7 +87,7 @@ jobs: uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 - name: Setup toolchain - uses: dtolnay/rust-toolchain@0e66bd3e6b38ec0ad5312288c83e47c143e6b09e + uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 with: toolchain: stable @@ -103,7 +103,7 @@ jobs: uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 - name: Setup toolchain - uses: dtolnay/rust-toolchain@0e66bd3e6b38ec0ad5312288c83e47c143e6b09e + uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 with: toolchain: stable @@ -130,7 +130,7 @@ jobs: uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 - name: Setup toolchain - uses: dtolnay/rust-toolchain@0e66bd3e6b38ec0ad5312288c83e47c143e6b09e + uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 with: toolchain: ${{ matrix.rust }} components: clippy @@ -155,7 +155,7 @@ jobs: uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 - name: Setup nightly toolchain - uses: dtolnay/rust-toolchain@0e66bd3e6b38ec0ad5312288c83e47c143e6b09e + uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 with: toolchain: nightly From 47766ce7cf1ab2f2b68d27ed69e68f659015025f Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Mon, 18 Sep 2023 22:13:38 +0200 Subject: [PATCH 166/436] Sync book-store with problem-specifications (#1742) --- .../practice/book-store/.docs/instructions.md | 39 ++-- .../practice/book-store/.meta/config.json | 2 +- .../book-store/.meta/test_template.tera | 12 ++ .../practice/book-store/.meta/tests.toml | 67 +++++- .../practice/book-store/tests/book-store.rs | 197 +++++++++--------- 5 files changed, 191 insertions(+), 126 deletions(-) create mode 100644 exercises/practice/book-store/.meta/test_template.tera diff --git a/exercises/practice/book-store/.docs/instructions.md b/exercises/practice/book-store/.docs/instructions.md index 8ec0a7ba2..54403f17b 100644 --- a/exercises/practice/book-store/.docs/instructions.md +++ b/exercises/practice/book-store/.docs/instructions.md @@ -1,12 +1,10 @@ # Instructions -To try and encourage more sales of different books from a popular 5 book -series, a bookshop has decided to offer discounts on multiple book purchases. +To try and encourage more sales of different books from a popular 5 book series, a bookshop has decided to offer discounts on multiple book purchases. One copy of any of the five books costs $8. -If, however, you buy two different books, you get a 5% -discount on those two books. +If, however, you buy two different books, you get a 5% discount on those two books. If you buy 3 different books, you get a 10% discount. @@ -14,14 +12,9 @@ If you buy 4 different books, you get a 20% discount. If you buy all 5, you get a 25% discount. -Note: that if you buy four books, of which 3 are -different titles, you get a 10% discount on the 3 that -form part of a set, but the fourth book still costs $8. +Note that if you buy four books, of which 3 are different titles, you get a 10% discount on the 3 that form part of a set, but the fourth book still costs $8. -Your mission is to write a piece of code to calculate the -price of any conceivable shopping basket (containing only -books of the same series), giving as big a discount as -possible. +Your mission is to write code to calculate the price of any conceivable shopping basket (containing only books of the same series), giving as big a discount as possible. For example, how much does this basket of books cost? @@ -33,36 +26,36 @@ For example, how much does this basket of books cost? One way of grouping these 8 books is: -- 1 group of 5 --> 25% discount (1st,2nd,3rd,4th,5th) -- +1 group of 3 --> 10% discount (1st,2nd,3rd) +- 1 group of 5 (1st, 2nd,3rd, 4th, 5th) +- 1 group of 3 (1st, 2nd, 3rd) This would give a total of: - 5 books at a 25% discount -- +3 books at a 10% discount +- 3 books at a 10% discount Resulting in: -- 5 x (8 - 2.00) == 5 x 6.00 == $30.00 -- +3 x (8 - 0.80) == 3 x 7.20 == $21.60 +- 5 × (100% - 25%) × $8 = 5 × $6.00 = $30.00, plus +- 3 × (100% - 10%) × $8 = 3 × $7.20 = $21.60 -For a total of $51.60 +Which equals $51.60. However, a different way to group these 8 books is: -- 1 group of 4 books --> 20% discount (1st,2nd,3rd,4th) -- +1 group of 4 books --> 20% discount (1st,2nd,3rd,5th) +- 1 group of 4 books (1st, 2nd, 3rd, 4th) +- 1 group of 4 books (1st, 2nd, 3rd, 5th) This would give a total of: - 4 books at a 20% discount -- +4 books at a 20% discount +- 4 books at a 20% discount Resulting in: -- 4 x (8 - 1.60) == 4 x 6.40 == $25.60 -- +4 x (8 - 1.60) == 4 x 6.40 == $25.60 +- 4 × (100% - 20%) × $8 = 4 × $6.40 = $25.60, plus +- 4 × (100% - 20%) × $8 = 4 × $6.40 = $25.60 -For a total of $51.20 +Which equals $51.20. And $51.20 is the price with the biggest discount. diff --git a/exercises/practice/book-store/.meta/config.json b/exercises/practice/book-store/.meta/config.json index 5b7f784ce..2e37770ab 100644 --- a/exercises/practice/book-store/.meta/config.json +++ b/exercises/practice/book-store/.meta/config.json @@ -28,5 +28,5 @@ }, "blurb": "To try and encourage more sales of different books from a popular 5 book series, a bookshop has decided to offer discounts of multiple-book purchases.", "source": "Inspired by the harry potter kata from Cyber-Dojo.", - "source_url": "http://cyber-dojo.org/" + "source_url": "https://cyber-dojo.org/" } diff --git a/exercises/practice/book-store/.meta/test_template.tera b/exercises/practice/book-store/.meta/test_template.tera new file mode 100644 index 000000000..d06d0a57f --- /dev/null +++ b/exercises/practice/book-store/.meta/test_template.tera @@ -0,0 +1,12 @@ +{% for test in cases %} +#[test] +{% if loop.index != 1 -%} +#[ignore] +{% endif -%} +fn {{ test.description | slugify | replace(from="-", to="_") }}() { + let input = &{{ test.input.basket | json_encode() }}; + let output = {{ crate_name }}::{{ fn_names[0] }}(input); + let expected = {{ test.expected | json_encode() }}; + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/book-store/.meta/tests.toml b/exercises/practice/book-store/.meta/tests.toml index be690e975..4b7ce98be 100644 --- a/exercises/practice/book-store/.meta/tests.toml +++ b/exercises/practice/book-store/.meta/tests.toml @@ -1,3 +1,64 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[17146bd5-2e80-4557-ab4c-05632b6b0d01] +description = "Only a single book" + +[cc2de9ac-ff2a-4efd-b7c7-bfe0f43271ce] +description = "Two of the same book" + +[5a86eac0-45d2-46aa-bbf0-266b94393a1a] +description = "Empty basket" + +[158bd19a-3db4-4468-ae85-e0638a688990] +description = "Two different books" + +[f3833f6b-9332-4a1f-ad98-6c3f8e30e163] +description = "Three different books" + +[1951a1db-2fb6-4cd1-a69a-f691b6dd30a2] +description = "Four different books" + +[d70f6682-3019-4c3f-aede-83c6a8c647a3] +description = "Five different books" + +[78cacb57-911a-45f1-be52-2a5bd428c634] +description = "Two groups of four is cheaper than group of five plus group of three" + +[f808b5a4-e01f-4c0d-881f-f7b90d9739da] +description = "Two groups of four is cheaper than groups of five and three" + +[fe96401c-5268-4be2-9d9e-19b76478007c] +description = "Group of four plus group of two is cheaper than two groups of three" + +[68ea9b78-10ad-420e-a766-836a501d3633] +description = "Two each of first four books and one copy each of rest" + +[c0a779d5-a40c-47ae-9828-a340e936b866] +description = "Two copies of each book" + +[18fd86fe-08f1-4b68-969b-392b8af20513] +description = "Three copies of first book and two each of remaining" + +[0b19a24d-e4cf-4ec8-9db2-8899a41af0da] +description = "Three each of first two books and two each of remaining books" + +[bb376344-4fb2-49ab-ab85-e38d8354a58d] +description = "Four groups of four are cheaper than two groups each of five and three" + +[5260ddde-2703-4915-b45a-e54dbbac4303] +description = "Check that groups of four are created properly even when there are more groups of three than groups of five" + +[b0478278-c551-4747-b0fc-7e0be3158b1f] +description = "One group of one and four is cheaper than one group of two and three" + +[cf868453-6484-4ae1-9dfc-f8ee85bbde01] +description = "One group of one and two plus three groups of four is cheaper than one group of each size" diff --git a/exercises/practice/book-store/tests/book-store.rs b/exercises/practice/book-store/tests/book-store.rs index 5dbee56a1..10e71c8ca 100644 --- a/exercises/practice/book-store/tests/book-store.rs +++ b/exercises/practice/book-store/tests/book-store.rs @@ -1,164 +1,163 @@ -//! Tests for book-store -//! -//! Generated by [script][script] using [canonical data][canonical-data] -//! -//! [script]: https://github.com/exercism/rust/blob/main/bin/init_exercise.py -//! [canonical-data]: https://raw.githubusercontent.com/exercism/problem-specifications/main/exercises/book-store/canonical_data.json - -use book_store::*; - -/// Process a single test case for the property `total` -/// -/// All cases for the `total` property are implemented -/// in terms of this function. -/// -/// Expected input format: ('basket', 'targetgrouping') -fn process_total_case(input: (Vec, Vec>), expected: u32) { - assert_eq!(lowest_price(&input.0), expected) -} - -// Return the total basket price after applying the best discount. -// Calculate lowest price for a shopping basket containing books only from -// a single series. There is no discount advantage for having more than -// one copy of any single book in a grouping. - #[test] -/// Only a single book fn only_a_single_book() { - process_total_case((vec![1], vec![vec![1]]), 800); + let input = &[1]; + let output = book_store::lowest_price(input); + let expected = 800; + assert_eq!(output, expected); } #[test] #[ignore] -/// Two of the same book fn two_of_the_same_book() { - process_total_case((vec![2, 2], vec![vec![2], vec![2]]), 1_600); + let input = &[2, 2]; + let output = book_store::lowest_price(input); + let expected = 1600; + assert_eq!(output, expected); } #[test] #[ignore] -/// Empty basket fn empty_basket() { - process_total_case((vec![], vec![]), 0); + let input = &[]; + let output = book_store::lowest_price(input); + let expected = 0; + assert_eq!(output, expected); } #[test] #[ignore] -/// Two different books fn two_different_books() { - process_total_case((vec![1, 2], vec![vec![1, 2]]), 1_520); + let input = &[1, 2]; + let output = book_store::lowest_price(input); + let expected = 1520; + assert_eq!(output, expected); } #[test] #[ignore] -/// Three different books fn three_different_books() { - process_total_case((vec![1, 2, 3], vec![vec![1, 2, 3]]), 2_160); + let input = &[1, 2, 3]; + let output = book_store::lowest_price(input); + let expected = 2160; + assert_eq!(output, expected); } #[test] #[ignore] -/// Four different books fn four_different_books() { - process_total_case((vec![1, 2, 3, 4], vec![vec![1, 2, 3, 4]]), 2_560); + let input = &[1, 2, 3, 4]; + let output = book_store::lowest_price(input); + let expected = 2560; + assert_eq!(output, expected); } #[test] #[ignore] -/// Five different books fn five_different_books() { - process_total_case((vec![1, 2, 3, 4, 5], vec![vec![1, 2, 3, 4, 5]]), 3_000); + let input = &[1, 2, 3, 4, 5]; + let output = book_store::lowest_price(input); + let expected = 3000; + assert_eq!(output, expected); } #[test] #[ignore] -/// Two groups of four is cheaper than group of five plus group of three fn two_groups_of_four_is_cheaper_than_group_of_five_plus_group_of_three() { - process_total_case( - ( - vec![1, 1, 2, 2, 3, 3, 4, 5], - vec![vec![1, 2, 3, 4], vec![1, 2, 3, 5]], - ), - 5_120, - ); + let input = &[1, 1, 2, 2, 3, 3, 4, 5]; + let output = book_store::lowest_price(input); + let expected = 5120; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn two_groups_of_four_is_cheaper_than_groups_of_five_and_three() { + let input = &[1, 1, 2, 3, 4, 4, 5, 5]; + let output = book_store::lowest_price(input); + let expected = 5120; + assert_eq!(output, expected); } #[test] #[ignore] -/// Group of four plus group of two is cheaper than two groups of three fn group_of_four_plus_group_of_two_is_cheaper_than_two_groups_of_three() { - process_total_case( - (vec![1, 1, 2, 2, 3, 4], vec![vec![1, 2, 3, 4], vec![1, 2]]), - 4_080, - ); + let input = &[1, 1, 2, 2, 3, 4]; + let output = book_store::lowest_price(input); + let expected = 4080; + assert_eq!(output, expected); } #[test] #[ignore] -/// Two each of first 4 books and 1 copy each of rest -fn two_each_of_first_4_books_and_1_copy_each_of_rest() { - process_total_case( - ( - vec![1, 1, 2, 2, 3, 3, 4, 4, 5], - vec![vec![1, 2, 3, 4, 5], vec![1, 2, 3, 4]], - ), - 5_560, - ); +fn two_each_of_first_four_books_and_one_copy_each_of_rest() { + let input = &[1, 1, 2, 2, 3, 3, 4, 4, 5]; + let output = book_store::lowest_price(input); + let expected = 5560; + assert_eq!(output, expected); } #[test] #[ignore] -/// Two copies of each book fn two_copies_of_each_book() { - process_total_case( - ( - vec![1, 1, 2, 2, 3, 3, 4, 4, 5, 5], - vec![vec![1, 2, 3, 4, 5], vec![1, 2, 3, 4, 5]], - ), - 6_000, - ); + let input = &[1, 1, 2, 2, 3, 3, 4, 4, 5, 5]; + let output = book_store::lowest_price(input); + let expected = 6000; + assert_eq!(output, expected); } #[test] #[ignore] -/// Three copies of first book and 2 each of remaining -fn three_copies_of_first_book_and_2_each_of_remaining() { - process_total_case( - ( - vec![1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 1], - vec![vec![1, 2, 3, 4, 5], vec![1, 2, 3, 4, 5], vec![1]], - ), - 6_800, - ); +fn three_copies_of_first_book_and_two_each_of_remaining() { + let input = &[1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 1]; + let output = book_store::lowest_price(input); + let expected = 6800; + assert_eq!(output, expected); } #[test] #[ignore] -/// Three each of first 2 books and 2 each of remaining books -fn three_each_of_first_2_books_and_2_each_of_remaining_books() { - process_total_case( - ( - vec![1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 1, 2], - vec![vec![1, 2, 3, 4, 5], vec![1, 2, 3, 4, 5], vec![1, 2]], - ), - 7_520, - ); +fn three_each_of_first_two_books_and_two_each_of_remaining_books() { + let input = &[1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 1, 2]; + let output = book_store::lowest_price(input); + let expected = 7520; + assert_eq!(output, expected); } #[test] #[ignore] -/// Four groups of four are cheaper than two groups each of five and three fn four_groups_of_four_are_cheaper_than_two_groups_each_of_five_and_three() { - process_total_case( - ( - vec![1, 1, 2, 2, 3, 3, 4, 5, 1, 1, 2, 2, 3, 3, 4, 5], - vec![ - vec![1, 2, 3, 4], - vec![1, 2, 3, 5], - vec![1, 2, 3, 4], - vec![1, 2, 3, 5], - ], - ), - 10_240, - ); + let input = &[1, 1, 2, 2, 3, 3, 4, 5, 1, 1, 2, 2, 3, 3, 4, 5]; + let output = book_store::lowest_price(input); + let expected = 10240; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn check_that_groups_of_four_are_created_properly_even_when_there_are_more_groups_of_three_than_groups_of_five( +) { + let input = &[ + 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 4, 5, 5, + ]; + let output = book_store::lowest_price(input); + let expected = 14560; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn one_group_of_one_and_four_is_cheaper_than_one_group_of_two_and_three() { + let input = &[1, 1, 2, 3, 4]; + let output = book_store::lowest_price(input); + let expected = 3360; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn one_group_of_one_and_two_plus_three_groups_of_four_is_cheaper_than_one_group_of_each_size() { + let input = &[1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5]; + let output = book_store::lowest_price(input); + let expected = 10000; + assert_eq!(output, expected); } From d9406c4e456cfb1d2758f27fe4f01e94b0ecedb4 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Mon, 18 Sep 2023 22:14:51 +0200 Subject: [PATCH 167/436] Sync pascals-triangle with problem-specifications (#1743) --- .../pascals-triangle/.docs/instructions.md | 3 +- .../pascals-triangle/.meta/config.json | 2 +- .../pascals-triangle/.meta/test_template.tera | 14 ++++++ .../pascals-triangle/.meta/tests.toml | 22 ++++++++-- .../tests/pascals-triangle.rs | 44 ++++++------------- 5 files changed, 49 insertions(+), 36 deletions(-) create mode 100644 exercises/practice/pascals-triangle/.meta/test_template.tera diff --git a/exercises/practice/pascals-triangle/.docs/instructions.md b/exercises/practice/pascals-triangle/.docs/instructions.md index 7109334fb..f55678593 100644 --- a/exercises/practice/pascals-triangle/.docs/instructions.md +++ b/exercises/practice/pascals-triangle/.docs/instructions.md @@ -2,8 +2,7 @@ Compute Pascal's triangle up to a given number of rows. -In Pascal's Triangle each number is computed by adding the numbers to -the right and left of the current position in the previous row. +In Pascal's Triangle each number is computed by adding the numbers to the right and left of the current position in the previous row. ```text 1 diff --git a/exercises/practice/pascals-triangle/.meta/config.json b/exercises/practice/pascals-triangle/.meta/config.json index f8d75e1de..750d670aa 100644 --- a/exercises/practice/pascals-triangle/.meta/config.json +++ b/exercises/practice/pascals-triangle/.meta/config.json @@ -31,5 +31,5 @@ }, "blurb": "Compute Pascal's triangle up to a given number of rows.", "source": "Pascal's Triangle at Wolfram Math World", - "source_url": "http://mathworld.wolfram.com/PascalsTriangle.html" + "source_url": "https://www.wolframalpha.com/input/?i=Pascal%27s+triangle" } diff --git a/exercises/practice/pascals-triangle/.meta/test_template.tera b/exercises/practice/pascals-triangle/.meta/test_template.tera new file mode 100644 index 000000000..6ef434fa6 --- /dev/null +++ b/exercises/practice/pascals-triangle/.meta/test_template.tera @@ -0,0 +1,14 @@ +use pascals_triangle::PascalsTriangle; +{% for test in cases %} +#[test] +{% if loop.index != 1 -%} +#[ignore] +{% endif -%} +fn {{ test.description | slugify | replace(from="-", to="_") }}() { + let pt = PascalsTriangle::new({{ test.input.count }}); + let expected: Vec> = vec![{% for row in test.expected -%} + vec!{{ row | json_encode() }}, + {%- endfor %}]; + assert_eq!(pt.rows(), expected); +} +{% endfor -%} diff --git a/exercises/practice/pascals-triangle/.meta/tests.toml b/exercises/practice/pascals-triangle/.meta/tests.toml index b0c8be28c..2db0ee523 100644 --- a/exercises/practice/pascals-triangle/.meta/tests.toml +++ b/exercises/practice/pascals-triangle/.meta/tests.toml @@ -1,6 +1,19 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[9920ce55-9629-46d5-85d6-4201f4a4234d] +description = "zero rows" + +[70d643ce-a46d-4e93-af58-12d88dd01f21] +description = "single row" [a6e5a2a2-fc9a-4b47-9f4f-ed9ad9fbe4bd] description = "two rows" @@ -8,6 +21,9 @@ description = "two rows" [97206a99-79ba-4b04-b1c5-3c0fa1e16925] description = "three rows" +[565a0431-c797-417c-a2c8-2935e01ce306] +description = "four rows" + [06f9ea50-9f51-4eb2-b9a9-c00975686c27] description = "five rows" diff --git a/exercises/practice/pascals-triangle/tests/pascals-triangle.rs b/exercises/practice/pascals-triangle/tests/pascals-triangle.rs index fe2042bdc..24ba514fe 100644 --- a/exercises/practice/pascals-triangle/tests/pascals-triangle.rs +++ b/exercises/practice/pascals-triangle/tests/pascals-triangle.rs @@ -1,18 +1,18 @@ -use pascals_triangle::*; +use pascals_triangle::PascalsTriangle; #[test] -fn no_rows() { +fn zero_rows() { let pt = PascalsTriangle::new(0); - let expected: Vec> = Vec::new(); - assert_eq!(expected, pt.rows()); + let expected: Vec> = vec![]; + assert_eq!(pt.rows(), expected); } #[test] #[ignore] -fn one_row() { +fn single_row() { let pt = PascalsTriangle::new(1); let expected: Vec> = vec![vec![1]]; - assert_eq!(expected, pt.rows()); + assert_eq!(pt.rows(), expected); } #[test] @@ -20,7 +20,7 @@ fn one_row() { fn two_rows() { let pt = PascalsTriangle::new(2); let expected: Vec> = vec![vec![1], vec![1, 1]]; - assert_eq!(expected, pt.rows()); + assert_eq!(pt.rows(), expected); } #[test] @@ -28,15 +28,15 @@ fn two_rows() { fn three_rows() { let pt = PascalsTriangle::new(3); let expected: Vec> = vec![vec![1], vec![1, 1], vec![1, 2, 1]]; - assert_eq!(expected, pt.rows()); + assert_eq!(pt.rows(), expected); } #[test] #[ignore] -fn last_of_four_rows() { +fn four_rows() { let pt = PascalsTriangle::new(4); - let expected: Vec = vec![1, 3, 3, 1]; - assert_eq!(Some(expected), pt.rows().pop()); + let expected: Vec> = vec![vec![1], vec![1, 1], vec![1, 2, 1], vec![1, 3, 3, 1]]; + assert_eq!(pt.rows(), expected); } #[test] @@ -50,7 +50,7 @@ fn five_rows() { vec![1, 3, 3, 1], vec![1, 4, 6, 4, 1], ]; - assert_eq!(expected, pt.rows()); + assert_eq!(pt.rows(), expected); } #[test] @@ -65,23 +65,7 @@ fn six_rows() { vec![1, 4, 6, 4, 1], vec![1, 5, 10, 10, 5, 1], ]; - assert_eq!(expected, pt.rows()); -} - -#[test] -#[ignore] -fn seven_rows() { - let pt = PascalsTriangle::new(7); - let expected: Vec> = vec![ - vec![1], - vec![1, 1], - vec![1, 2, 1], - vec![1, 3, 3, 1], - vec![1, 4, 6, 4, 1], - vec![1, 5, 10, 10, 5, 1], - vec![1, 6, 15, 20, 15, 6, 1], - ]; - assert_eq!(expected, pt.rows()); + assert_eq!(pt.rows(), expected); } #[test] @@ -100,5 +84,5 @@ fn ten_rows() { vec![1, 8, 28, 56, 70, 56, 28, 8, 1], vec![1, 9, 36, 84, 126, 126, 84, 36, 9, 1], ]; - assert_eq!(expected, pt.rows()); + assert_eq!(pt.rows(), expected); } From 1030698083707e91d8cf86c71ec44a24f8704f30 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Mon, 18 Sep 2023 22:16:17 +0200 Subject: [PATCH 168/436] Sync variable-length-quantity with problem-specifications (#1731) Notably, this removes any tests about overflow as well as the overflow variant from the error enum in the stub. Overflow handling is a valuable thing to learn and practice, but there are other exercises which do that. So it seems unnecesary to expand the case only on the Rust track and maintain our own additional tests. This is a backwards compatible change, submissions which choose to handle overflow should still work fine. --- .../.docs/instructions.md | 6 +- .../.meta/test_template.tera | 33 +++ .../variable-length-quantity/.meta/tests.toml | 91 +++++- .../variable-length-quantity/src/lib.rs | 1 - .../tests/variable-length-quantity.rs | 276 ++++++++++++------ 5 files changed, 315 insertions(+), 92 deletions(-) create mode 100644 exercises/practice/variable-length-quantity/.meta/test_template.tera diff --git a/exercises/practice/variable-length-quantity/.docs/instructions.md b/exercises/practice/variable-length-quantity/.docs/instructions.md index eadce28d0..501254826 100644 --- a/exercises/practice/variable-length-quantity/.docs/instructions.md +++ b/exercises/practice/variable-length-quantity/.docs/instructions.md @@ -2,10 +2,10 @@ Implement variable length quantity encoding and decoding. -The goal of this exercise is to implement [VLQ](https://en.wikipedia.org/wiki/Variable-length_quantity) encoding/decoding. +The goal of this exercise is to implement [VLQ][vlq] encoding/decoding. In short, the goal of this encoding is to encode integer values in a way that would save bytes. -Only the first 7 bits of each byte is significant (right-justified; sort of like an ASCII byte). +Only the first 7 bits of each byte are significant (right-justified; sort of like an ASCII byte). So, if you have a 32-bit value, you have to unpack it into a series of 7-bit bytes. Of course, you will have a variable number of bytes depending upon your integer. To indicate which is the last byte of the series, you leave bit #7 clear. @@ -30,3 +30,5 @@ Here are examples of integers as 32-bit values, and the variable length quantiti 08000000 C0 80 80 00 0FFFFFFF FF FF FF 7F ``` + +[vlq]: https://en.wikipedia.org/wiki/Variable-length_quantity diff --git a/exercises/practice/variable-length-quantity/.meta/test_template.tera b/exercises/practice/variable-length-quantity/.meta/test_template.tera new file mode 100644 index 000000000..f36d9b504 --- /dev/null +++ b/exercises/practice/variable-length-quantity/.meta/test_template.tera @@ -0,0 +1,33 @@ +use variable_length_quantity as vlq; +{% for test in cases %} +#[test] +{% if loop.index != 1 -%} +#[ignore] +{% endif -%} +fn {{ test.description | slugify | replace(from="-", to="_") }}() { +{%- if test.property == "encode" %} + let input = &{{ test.input.integers | json_encode() }}; + let output = vlq::{{ fn_names[0] }}(input); + let expected = vec![ + {%- for byte in test.expected -%} + 0x{{ byte | to_hex }}, + {%- endfor -%} + ]; +{%- elif test.property == "decode" %} + let input = &[ + {%- for byte in test.input.integers -%} + 0x{{ byte | to_hex }}, + {%- endfor -%} + ]; + let output = vlq::{{ fn_names[1] }}(input); + let expected = {% if test.expected is object -%} + Err(vlq::Error::IncompleteNumber) + {%- else -%} + Ok(vec!{{ test.expected | json_encode() }}) + {%- endif %}; +{%- else %} + panic!("unknown property: {{ test.property }}"); +{%- endif %} + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/variable-length-quantity/.meta/tests.toml b/exercises/practice/variable-length-quantity/.meta/tests.toml index be690e975..c9af549fc 100644 --- a/exercises/practice/variable-length-quantity/.meta/tests.toml +++ b/exercises/practice/variable-length-quantity/.meta/tests.toml @@ -1,3 +1,88 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[35c9db2e-f781-4c52-b73b-8e76427defd0] +description = "Encode a series of integers, producing a series of bytes. -> zero" + +[be44d299-a151-4604-a10e-d4b867f41540] +description = "Encode a series of integers, producing a series of bytes. -> arbitrary single byte" + +[ea399615-d274-4af6-bbef-a1c23c9e1346] +description = "Encode a series of integers, producing a series of bytes. -> largest single byte" + +[77b07086-bd3f-4882-8476-8dcafee79b1c] +description = "Encode a series of integers, producing a series of bytes. -> smallest double byte" + +[63955a49-2690-4e22-a556-0040648d6b2d] +description = "Encode a series of integers, producing a series of bytes. -> arbitrary double byte" + +[29da7031-0067-43d3-83a7-4f14b29ed97a] +description = "Encode a series of integers, producing a series of bytes. -> largest double byte" + +[3345d2e3-79a9-4999-869e-d4856e3a8e01] +description = "Encode a series of integers, producing a series of bytes. -> smallest triple byte" + +[5df0bc2d-2a57-4300-a653-a75ee4bd0bee] +description = "Encode a series of integers, producing a series of bytes. -> arbitrary triple byte" + +[f51d8539-312d-4db1-945c-250222c6aa22] +description = "Encode a series of integers, producing a series of bytes. -> largest triple byte" + +[da78228b-544f-47b7-8bfe-d16b35bbe570] +description = "Encode a series of integers, producing a series of bytes. -> smallest quadruple byte" + +[11ed3469-a933-46f1-996f-2231e05d7bb6] +description = "Encode a series of integers, producing a series of bytes. -> arbitrary quadruple byte" + +[d5f3f3c3-e0f1-4e7f-aad0-18a44f223d1c] +description = "Encode a series of integers, producing a series of bytes. -> largest quadruple byte" + +[91a18b33-24e7-4bfb-bbca-eca78ff4fc47] +description = "Encode a series of integers, producing a series of bytes. -> smallest quintuple byte" + +[5f34ff12-2952-4669-95fe-2d11b693d331] +description = "Encode a series of integers, producing a series of bytes. -> arbitrary quintuple byte" + +[7489694b-88c3-4078-9864-6fe802411009] +description = "Encode a series of integers, producing a series of bytes. -> maximum 32-bit integer input" + +[f9b91821-cada-4a73-9421-3c81d6ff3661] +description = "Encode a series of integers, producing a series of bytes. -> two single-byte values" + +[68694449-25d2-4974-ba75-fa7bb36db212] +description = "Encode a series of integers, producing a series of bytes. -> two multi-byte values" + +[51a06b5c-de1b-4487-9a50-9db1b8930d85] +description = "Encode a series of integers, producing a series of bytes. -> many multi-byte values" + +[baa73993-4514-4915-bac0-f7f585e0e59a] +description = "Decode a series of bytes, producing a series of integers. -> one byte" + +[72e94369-29f9-46f2-8c95-6c5b7a595aee] +description = "Decode a series of bytes, producing a series of integers. -> two bytes" + +[df5a44c4-56f7-464e-a997-1db5f63ce691] +description = "Decode a series of bytes, producing a series of integers. -> three bytes" + +[1bb58684-f2dc-450a-8406-1f3452aa1947] +description = "Decode a series of bytes, producing a series of integers. -> four bytes" + +[cecd5233-49f1-4dd1-a41a-9840a40f09cd] +description = "Decode a series of bytes, producing a series of integers. -> maximum 32-bit integer" + +[e7d74ba3-8b8e-4bcb-858d-d08302e15695] +description = "Decode a series of bytes, producing a series of integers. -> incomplete sequence causes error" + +[aa378291-9043-4724-bc53-aca1b4a3fcb6] +description = "Decode a series of bytes, producing a series of integers. -> incomplete sequence causes error, even if value is zero" + +[a91e6f5a-c64a-48e3-8a75-ce1a81e0ebee] +description = "Decode a series of bytes, producing a series of integers. -> multiple values" diff --git a/exercises/practice/variable-length-quantity/src/lib.rs b/exercises/practice/variable-length-quantity/src/lib.rs index 76097b80d..15f55c68c 100644 --- a/exercises/practice/variable-length-quantity/src/lib.rs +++ b/exercises/practice/variable-length-quantity/src/lib.rs @@ -1,7 +1,6 @@ #[derive(Debug, PartialEq, Eq)] pub enum Error { IncompleteNumber, - Overflow, } /// Convert a list of numbers to a stream of bytes encoded with variable length encoding. diff --git a/exercises/practice/variable-length-quantity/tests/variable-length-quantity.rs b/exercises/practice/variable-length-quantity/tests/variable-length-quantity.rs index b2e3e7e29..68079b12c 100644 --- a/exercises/practice/variable-length-quantity/tests/variable-length-quantity.rs +++ b/exercises/practice/variable-length-quantity/tests/variable-length-quantity.rs @@ -1,134 +1,238 @@ use variable_length_quantity as vlq; #[test] -fn to_single_byte() { - assert_eq!(&[0x00], vlq::to_bytes(&[0x00]).as_slice()); - assert_eq!(&[0x40], vlq::to_bytes(&[0x40]).as_slice()); - assert_eq!(&[0x7f], vlq::to_bytes(&[0x7f]).as_slice()); +fn zero() { + let input = &[0]; + let output = vlq::to_bytes(input); + let expected = vec![0x0]; + assert_eq!(output, expected); } #[test] #[ignore] -fn to_double_byte() { - assert_eq!(&[0x81, 0x00], vlq::to_bytes(&[0x80]).as_slice()); - assert_eq!(&[0xc0, 0x00], vlq::to_bytes(&[0x2000]).as_slice()); - assert_eq!(&[0xff, 0x7f], vlq::to_bytes(&[0x3fff]).as_slice()); +fn arbitrary_single_byte() { + let input = &[64]; + let output = vlq::to_bytes(input); + let expected = vec![0x40]; + assert_eq!(output, expected); } #[test] #[ignore] -fn to_triple_byte() { - assert_eq!(&[0x81, 0x80, 0x00], vlq::to_bytes(&[0x4000]).as_slice()); - assert_eq!(&[0xc0, 0x80, 0x00], vlq::to_bytes(&[0x10_0000]).as_slice()); - assert_eq!(&[0xff, 0xff, 0x7f], vlq::to_bytes(&[0x1f_ffff]).as_slice()); +fn largest_single_byte() { + let input = &[127]; + let output = vlq::to_bytes(input); + let expected = vec![0x7f]; + assert_eq!(output, expected); } #[test] #[ignore] -fn to_quadruple_byte() { - assert_eq!( - &[0x81, 0x80, 0x80, 0x00], - vlq::to_bytes(&[0x20_0000]).as_slice() - ); - assert_eq!( - &[0xc0, 0x80, 0x80, 0x00], - vlq::to_bytes(&[0x0800_0000]).as_slice() - ); - assert_eq!( - &[0xff, 0xff, 0xff, 0x7f], - vlq::to_bytes(&[0x0fff_ffff]).as_slice() - ); +fn smallest_double_byte() { + let input = &[128]; + let output = vlq::to_bytes(input); + let expected = vec![0x81, 0x0]; + assert_eq!(output, expected); } #[test] #[ignore] -fn to_quintuple_byte() { - assert_eq!( - &[0x81, 0x80, 0x80, 0x80, 0x00], - vlq::to_bytes(&[0x1000_0000]).as_slice() - ); - assert_eq!( - &[0x8f, 0xf8, 0x80, 0x80, 0x00], - vlq::to_bytes(&[0xff00_0000]).as_slice() - ); - assert_eq!( - &[0x8f, 0xff, 0xff, 0xff, 0x7f], - vlq::to_bytes(&[0xffff_ffff]).as_slice() - ); +fn arbitrary_double_byte() { + let input = &[8192]; + let output = vlq::to_bytes(input); + let expected = vec![0xc0, 0x0]; + assert_eq!(output, expected); } #[test] #[ignore] -fn from_bytes() { - assert_eq!(Ok(vec![0x7f]), vlq::from_bytes(&[0x7f])); - assert_eq!(Ok(vec![0x2000]), vlq::from_bytes(&[0xc0, 0x00])); - assert_eq!(Ok(vec![0x1f_ffff]), vlq::from_bytes(&[0xff, 0xff, 0x7f])); - assert_eq!( - Ok(vec![0x20_0000]), - vlq::from_bytes(&[0x81, 0x80, 0x80, 0x00]) - ); - assert_eq!( - Ok(vec![0xffff_ffff]), - vlq::from_bytes(&[0x8f, 0xff, 0xff, 0xff, 0x7f]) - ); +fn largest_double_byte() { + let input = &[16383]; + let output = vlq::to_bytes(input); + let expected = vec![0xff, 0x7f]; + assert_eq!(output, expected); } #[test] #[ignore] -fn to_bytes_multiple_values() { - assert_eq!(&[0x40, 0x7f], vlq::to_bytes(&[0x40, 0x7f]).as_slice()); - assert_eq!( - &[0x81, 0x80, 0x00, 0xc8, 0xe8, 0x56], - vlq::to_bytes(&[0x4000, 0x12_3456]).as_slice() - ); - assert_eq!( - &[ - 0xc0, 0x00, 0xc8, 0xe8, 0x56, 0xff, 0xff, 0xff, 0x7f, 0x00, 0xff, 0x7f, 0x81, 0x80, - 0x00, - ], - vlq::to_bytes(&[0x2000, 0x12_3456, 0x0fff_ffff, 0x00, 0x3fff, 0x4000]).as_slice() - ); +fn smallest_triple_byte() { + let input = &[16384]; + let output = vlq::to_bytes(input); + let expected = vec![0x81, 0x80, 0x0]; + assert_eq!(output, expected); } #[test] #[ignore] -fn from_bytes_multiple_values() { - assert_eq!( - Ok(vec![0x2000, 0x12_3456, 0x0fff_ffff, 0x00, 0x3fff, 0x4000]), - vlq::from_bytes(&[ - 0xc0, 0x00, 0xc8, 0xe8, 0x56, 0xff, 0xff, 0xff, 0x7f, 0x00, 0xff, 0x7f, 0x81, 0x80, - 0x00, - ]) - ); +fn arbitrary_triple_byte() { + let input = &[1048576]; + let output = vlq::to_bytes(input); + let expected = vec![0xc0, 0x80, 0x0]; + assert_eq!(output, expected); } #[test] #[ignore] -fn incomplete_byte_sequence() { - assert_eq!(Err(vlq::Error::IncompleteNumber), vlq::from_bytes(&[0xff])); +fn largest_triple_byte() { + let input = &[2097151]; + let output = vlq::to_bytes(input); + let expected = vec![0xff, 0xff, 0x7f]; + assert_eq!(output, expected); } #[test] #[ignore] -fn zero_incomplete_byte_sequence() { - assert_eq!(Err(vlq::Error::IncompleteNumber), vlq::from_bytes(&[0x80])); +fn smallest_quadruple_byte() { + let input = &[2097152]; + let output = vlq::to_bytes(input); + let expected = vec![0x81, 0x80, 0x80, 0x0]; + assert_eq!(output, expected); } #[test] #[ignore] -fn overflow_u32() { - assert_eq!( - Err(vlq::Error::Overflow), - vlq::from_bytes(&[0xff, 0xff, 0xff, 0xff, 0x7f]) - ); +fn arbitrary_quadruple_byte() { + let input = &[134217728]; + let output = vlq::to_bytes(input); + let expected = vec![0xc0, 0x80, 0x80, 0x0]; + assert_eq!(output, expected); } #[test] #[ignore] -fn chained_execution_is_identity() { - let test = &[0xf2, 0xf6, 0x96, 0x9c, 0x3b, 0x39, 0x2e, 0x30, 0xb3, 0x24]; - assert_eq!( - Ok(Vec::from(&test[..])), - vlq::from_bytes(&vlq::to_bytes(test)) - ); +fn largest_quadruple_byte() { + let input = &[268435455]; + let output = vlq::to_bytes(input); + let expected = vec![0xff, 0xff, 0xff, 0x7f]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn smallest_quintuple_byte() { + let input = &[268435456]; + let output = vlq::to_bytes(input); + let expected = vec![0x81, 0x80, 0x80, 0x80, 0x0]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn arbitrary_quintuple_byte() { + let input = &[4278190080]; + let output = vlq::to_bytes(input); + let expected = vec![0x8f, 0xf8, 0x80, 0x80, 0x0]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn maximum_32_bit_integer_input() { + let input = &[4294967295]; + let output = vlq::to_bytes(input); + let expected = vec![0x8f, 0xff, 0xff, 0xff, 0x7f]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn two_single_byte_values() { + let input = &[64, 127]; + let output = vlq::to_bytes(input); + let expected = vec![0x40, 0x7f]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn two_multi_byte_values() { + let input = &[16384, 1193046]; + let output = vlq::to_bytes(input); + let expected = vec![0x81, 0x80, 0x0, 0xc8, 0xe8, 0x56]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn many_multi_byte_values() { + let input = &[8192, 1193046, 268435455, 0, 16383, 16384]; + let output = vlq::to_bytes(input); + let expected = vec![ + 0xc0, 0x0, 0xc8, 0xe8, 0x56, 0xff, 0xff, 0xff, 0x7f, 0x0, 0xff, 0x7f, 0x81, 0x80, 0x0, + ]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn one_byte() { + let input = &[0x7f]; + let output = vlq::from_bytes(input); + let expected = Ok(vec![127]); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn two_bytes() { + let input = &[0xc0, 0x0]; + let output = vlq::from_bytes(input); + let expected = Ok(vec![8192]); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn three_bytes() { + let input = &[0xff, 0xff, 0x7f]; + let output = vlq::from_bytes(input); + let expected = Ok(vec![2097151]); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn four_bytes() { + let input = &[0x81, 0x80, 0x80, 0x0]; + let output = vlq::from_bytes(input); + let expected = Ok(vec![2097152]); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn maximum_32_bit_integer() { + let input = &[0x8f, 0xff, 0xff, 0xff, 0x7f]; + let output = vlq::from_bytes(input); + let expected = Ok(vec![4294967295]); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn incomplete_sequence_causes_error() { + let input = &[0xff]; + let output = vlq::from_bytes(input); + let expected = Err(vlq::Error::IncompleteNumber); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn incomplete_sequence_causes_error_even_if_value_is_zero() { + let input = &[0x80]; + let output = vlq::from_bytes(input); + let expected = Err(vlq::Error::IncompleteNumber); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn multiple_values() { + let input = &[ + 0xc0, 0x0, 0xc8, 0xe8, 0x56, 0xff, 0xff, 0xff, 0x7f, 0x0, 0xff, 0x7f, 0x81, 0x80, 0x0, + ]; + let output = vlq::from_bytes(input); + let expected = Ok(vec![8192, 1193046, 268435455, 0, 16383, 16384]); + assert_eq!(output, expected); } From a666018acf3a431aa1c7dd0a188f893e02517c3f Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 19 Sep 2023 09:07:45 +0200 Subject: [PATCH 169/436] Sync word-count with problem-specifications (#1729) new tests found a bug in the example solution with apostrophe handling --- .../practice/word-count/.meta/example.rs | 2 +- .../word-count/.meta/test_template.tera | 27 +++ .../practice/word-count/.meta/tests.toml | 51 +++++- .../practice/word-count/tests/word-count.rs | 165 ++++++++++++------ 4 files changed, 185 insertions(+), 60 deletions(-) create mode 100644 exercises/practice/word-count/.meta/test_template.tera diff --git a/exercises/practice/word-count/.meta/example.rs b/exercises/practice/word-count/.meta/example.rs index 7e78fa1fd..f2074ec0c 100644 --- a/exercises/practice/word-count/.meta/example.rs +++ b/exercises/practice/word-count/.meta/example.rs @@ -6,8 +6,8 @@ pub fn word_count(input: &str) -> HashMap { let slice: &str = lower.as_ref(); for word in slice .split(|c: char| !c.is_alphanumeric() && c != '\'') - .filter(|s| !s.is_empty()) .map(|s| s.trim_matches('\'')) + .filter(|s| !s.is_empty()) { *map.entry(word.to_string()).or_insert(0) += 1; } diff --git a/exercises/practice/word-count/.meta/test_template.tera b/exercises/practice/word-count/.meta/test_template.tera new file mode 100644 index 000000000..20d887ede --- /dev/null +++ b/exercises/practice/word-count/.meta/test_template.tera @@ -0,0 +1,27 @@ +use std::collections::HashMap; + +fn check_word_count(mut output: HashMap, pairs: &[(&str, u32)]) { + // The reason for the awkward code in here is to ensure that the failure + // message for assert_eq! is as informative as possible. A simpler + // solution would simply check the length of the map, and then + // check for the presence and value of each key in the given pairs vector. + for &(k, v) in pairs.iter() { + assert_eq!((k, output.remove(&k.to_string()).unwrap_or(0)), (k, v)); + } + // may fail with a message that clearly shows all extra pairs in the map + assert_eq!(output.iter().collect::>(), vec![]); +} +{% for test in cases %} +#[test] +{% if loop.index != 1 -%} +#[ignore] +{% endif -%} +fn {{ test.description | slugify | replace(from="-", to="_") }}() { + let input = {{ test.input.sentence | json_encode() }}; + let output = {{ crate_name }}::{{ fn_names[0] }}(input); + let expected = &[{% for key, value in test.expected -%} + ({{ key | json_encode() }}, {{ value }}), + {%- endfor %}]; + check_word_count(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/word-count/.meta/tests.toml b/exercises/practice/word-count/.meta/tests.toml index 46d1b6fa5..1be425b33 100644 --- a/exercises/practice/word-count/.meta/tests.toml +++ b/exercises/practice/word-count/.meta/tests.toml @@ -1,12 +1,57 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[61559d5f-2cad-48fb-af53-d3973a9ee9ef] +description = "count one word" + +[5abd53a3-1aed-43a4-a15a-29f88c09cbbd] +description = "count one of each word" + +[2a3091e5-952e-4099-9fac-8f85d9655c0e] +description = "multiple occurrences of a word" + +[e81877ae-d4da-4af4-931c-d923cd621ca6] +description = "handles cramped lists" + +[7349f682-9707-47c0-a9af-be56e1e7ff30] +description = "handles expanded lists" + +[a514a0f2-8589-4279-8892-887f76a14c82] +description = "ignore punctuation" + +[d2e5cee6-d2ec-497b-bdc9-3ebe092ce55e] +description = "include numbers" + +[dac6bc6a-21ae-4954-945d-d7f716392dbf] +description = "normalize case" [4185a902-bdb0-4074-864c-f416e42a0f19] description = "with apostrophes" +include = false + +[4ff6c7d7-fcfc-43ef-b8e7-34ff1837a2d3] +description = "with apostrophes" +reimplements = "4185a902-bdb0-4074-864c-f416e42a0f19" [be72af2b-8afe-4337-b151-b297202e4a7b] description = "with quotations" +[8d6815fe-8a51-4a65-96f9-2fb3f6dc6ed6] +description = "substrings from the beginning" + [c5f4ef26-f3f7-4725-b314-855c04fb4c13] description = "multiple spaces not detected as a word" + +[50176e8a-fe8e-4f4c-b6b6-aa9cf8f20360] +description = "alternating word separators not detected as a word" + +[6d00f1db-901c-4bec-9829-d20eb3044557] +description = "quotation for word with apostrophe" diff --git a/exercises/practice/word-count/tests/word-count.rs b/exercises/practice/word-count/tests/word-count.rs index fdbd37f06..c37a3282f 100644 --- a/exercises/practice/word-count/tests/word-count.rs +++ b/exercises/practice/word-count/tests/word-count.rs @@ -1,116 +1,169 @@ use std::collections::HashMap; -fn check_word_count(s: &str, pairs: &[(&str, u32)]) { +fn check_word_count(mut output: HashMap, pairs: &[(&str, u32)]) { // The reason for the awkward code in here is to ensure that the failure // message for assert_eq! is as informative as possible. A simpler // solution would simply check the length of the map, and then // check for the presence and value of each key in the given pairs vector. - let mut m: HashMap = word_count::word_count(s); for &(k, v) in pairs.iter() { - assert_eq!((k, m.remove(&k.to_string()).unwrap_or(0)), (k, v)); + assert_eq!((k, output.remove(&k.to_string()).unwrap_or(0)), (k, v)); } // may fail with a message that clearly shows all extra pairs in the map - assert_eq!(m.iter().collect::>(), vec![]); + assert_eq!(output.iter().collect::>(), vec![]); } #[test] fn count_one_word() { - check_word_count("word", &[("word", 1)]); + let input = "word"; + let output = word_count::word_count(input); + let expected = &[("word", 1)]; + check_word_count(output, expected); } #[test] #[ignore] -fn count_one_of_each() { - check_word_count("one of each", &[("one", 1), ("of", 1), ("each", 1)]); +fn count_one_of_each_word() { + let input = "one of each"; + let output = word_count::word_count(input); + let expected = &[("one", 1), ("of", 1), ("each", 1)]; + check_word_count(output, expected); } #[test] #[ignore] -fn count_multiple_occurrences() { - check_word_count( - "one fish two fish red fish blue fish", - &[("one", 1), ("fish", 4), ("two", 1), ("red", 1), ("blue", 1)], - ); +fn multiple_occurrences_of_a_word() { + let input = "one fish two fish red fish blue fish"; + let output = word_count::word_count(input); + let expected = &[("one", 1), ("fish", 4), ("two", 1), ("red", 1), ("blue", 1)]; + check_word_count(output, expected); } #[test] #[ignore] -fn cramped_lists() { - check_word_count("one,two,three", &[("one", 1), ("two", 1), ("three", 1)]); +fn handles_cramped_lists() { + let input = "one,two,three"; + let output = word_count::word_count(input); + let expected = &[("one", 1), ("two", 1), ("three", 1)]; + check_word_count(output, expected); } #[test] #[ignore] -fn expanded_lists() { - check_word_count("one\ntwo\nthree", &[("one", 1), ("two", 1), ("three", 1)]); +fn handles_expanded_lists() { + let input = "one,\ntwo,\nthree"; + let output = word_count::word_count(input); + let expected = &[("one", 1), ("two", 1), ("three", 1)]; + check_word_count(output, expected); } #[test] #[ignore] fn ignore_punctuation() { - check_word_count( - "car : carpet as java : javascript!!&@$%^&", - &[ - ("car", 1), - ("carpet", 1), - ("as", 1), - ("java", 1), - ("javascript", 1), - ], - ); + let input = "car: carpet as java: javascript!!&@$%^&"; + let output = word_count::word_count(input); + let expected = &[ + ("car", 1), + ("carpet", 1), + ("as", 1), + ("java", 1), + ("javascript", 1), + ]; + check_word_count(output, expected); } #[test] #[ignore] fn include_numbers() { - check_word_count( - "testing, 1, 2 testing", - &[("testing", 2), ("1", 1), ("2", 1)], - ); + let input = "testing, 1, 2 testing"; + let output = word_count::word_count(input); + let expected = &[("testing", 2), ("1", 1), ("2", 1)]; + check_word_count(output, expected); } #[test] #[ignore] fn normalize_case() { - check_word_count("go Go GO Stop stop", &[("go", 3), ("stop", 2)]); + let input = "go Go GO Stop stop"; + let output = word_count::word_count(input); + let expected = &[("go", 3), ("stop", 2)]; + check_word_count(output, expected); } #[test] #[ignore] fn with_apostrophes() { - check_word_count( - "First: don't laugh. Then: don't cry.", - &[ - ("first", 1), - ("don't", 2), - ("laugh", 1), - ("then", 1), - ("cry", 1), - ], - ); + let input = "'First: don't laugh. Then: don't cry. You're getting it.'"; + let output = word_count::word_count(input); + let expected = &[ + ("first", 1), + ("don't", 2), + ("laugh", 1), + ("then", 1), + ("cry", 1), + ("you're", 1), + ("getting", 1), + ("it", 1), + ]; + check_word_count(output, expected); } #[test] #[ignore] fn with_quotations() { - check_word_count( - "Joe can't tell between 'large' and large.", - &[ - ("joe", 1), - ("can't", 1), - ("tell", 1), - ("between", 1), - ("large", 2), - ("and", 1), - ], - ); + let input = "Joe can't tell between 'large' and large."; + let output = word_count::word_count(input); + let expected = &[ + ("joe", 1), + ("can't", 1), + ("tell", 1), + ("between", 1), + ("large", 2), + ("and", 1), + ]; + check_word_count(output, expected); +} + +#[test] +#[ignore] +fn substrings_from_the_beginning() { + let input = "Joe can't tell between app, apple and a."; + let output = word_count::word_count(input); + let expected = &[ + ("joe", 1), + ("can't", 1), + ("tell", 1), + ("between", 1), + ("app", 1), + ("apple", 1), + ("and", 1), + ("a", 1), + ]; + check_word_count(output, expected); } #[test] #[ignore] fn multiple_spaces_not_detected_as_a_word() { - check_word_count( - " multiple whitespaces", - &[("multiple", 1), ("whitespaces", 1)], - ); + let input = " multiple whitespaces"; + let output = word_count::word_count(input); + let expected = &[("multiple", 1), ("whitespaces", 1)]; + check_word_count(output, expected); +} + +#[test] +#[ignore] +fn alternating_word_separators_not_detected_as_a_word() { + let input = ",\n,one,\n ,two \n 'three'"; + let output = word_count::word_count(input); + let expected = &[("one", 1), ("two", 1), ("three", 1)]; + check_word_count(output, expected); +} + +#[test] +#[ignore] +fn quotation_for_word_with_apostrophe() { + let input = "can, can't, 'can't'"; + let output = word_count::word_count(input); + let expected = &[("can", 1), ("can't", 2)]; + check_word_count(output, expected); } From d855eb6eeda6026ec19568cc91fe4c8b14717bd4 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 19 Sep 2023 14:46:46 +0200 Subject: [PATCH 170/436] Cleanup test scripts (#1746) Most importantly, move all required files for testing to a tmp dir. This means the actual files in the repository are not modified anymore. Running these scripts locally does not interrupt the git workflow now. The output is also cleaned up in various situations. closes #1738 --- bin/check_exercises.sh | 53 ++++--------- bin/ensure_stubs_compile.sh | 7 +- bin/test_exercise.sh | 143 ++++++++++++++---------------------- 3 files changed, 75 insertions(+), 128 deletions(-) diff --git a/bin/check_exercises.sh b/bin/check_exercises.sh index 036188f07..9f188f7f4 100755 --- a/bin/check_exercises.sh +++ b/bin/check_exercises.sh @@ -1,66 +1,45 @@ #!/usr/bin/env bash set -eo pipefail -# can't benchmark with a stable compiler; to bench, use -# $ BENCHMARK=1 rustup run nightly bin/check_exercises.sh -if [ -n "$BENCHMARK" ]; then - target_dir=benches -else - target_dir=tests -fi - repo=$(git rev-parse --show-toplevel) if [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then git fetch --depth=1 origin main files="$( git diff --diff-filter=d --name-only remotes/origin/main | - grep "exercises/" || true | - cut -d '/' -f -3 | - sort -u | - awk -v repo="$repo" '{print repo"/"$1}' + grep "exercises/" | + cut --delimiter '/' --fields -3 | + sort --unique || true )" else files="$repo/exercises/*/*" fi -return_code=0 # An exercise worth testing is defined here as any top level directory with # a 'tests' directory and a .meta/config.json. for exercise in $files; do - exercise="$exercise/$target_dir" - - # This assumes that exercises are only one directory deep - # and that the primary module is named the same as the directory - directory=$(dirname "${exercise}") + slug=$(basename "$exercise") # An exercise must have a .meta/config.json - metaconf="$directory/.meta/config.json" + metaconf="$exercise/.meta/config.json" if [ ! -f "$metaconf" ]; then continue fi - release="" - if [ -z "$BENCHMARK" ] && jq --exit-status '.custom?."test-in-release-mode"?' "$metaconf"; then + # suppress compilation output + cargo_args="--quiet" + + if [ -z "$BENCHMARK" ] && jq --exit-status '.custom?."test-in-release-mode"?' "$metaconf" > /dev/null; then # release mode is enabled if not benchmarking and the appropriate key is neither `false` nor `null`. - release="--release" + cargo_args="$cargo_args --release" fi - if [ -n "$DENYWARNINGS" ]; then - # Output a progress dot, because we may not otherwise produce output in > 10 mins. - echo -n '.' + if [ -n "$DENYWARNINGS" ] && [ -z "$CLIPPY" ]; then # No-run mode so we see no test output. - # Quiet mode so we see no compile output - # (such as "Compiling"/"Downloading"). - # Compiler errors will still be shown though. - # Both flags are necessary to keep things quiet. - ./bin/test_exercise.sh "$directory" --quiet --no-run - return_code=$((return_code | $?)) - else - # Run the test and get the status - ./bin/test_exercise.sh "$directory" $release - return_code=$((return_code | $?)) + # clippy does not understand this flag. + cargo_args="$cargo_args --no-run" fi -done -exit $return_code + echo "Checking $slug..." + ./bin/test_exercise.sh "$slug" "$cargo_args" +done diff --git a/bin/ensure_stubs_compile.sh b/bin/ensure_stubs_compile.sh index fc7cb810b..d0d743a81 100755 --- a/bin/ensure_stubs_compile.sh +++ b/bin/ensure_stubs_compile.sh @@ -7,10 +7,9 @@ if [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then git fetch --depth=1 origin main changed_exercises="$( git diff --diff-filter=d --name-only remotes/origin/main | - grep "exercises/" || true | - cut -d '/' -f -3 | - sort -u | - awk -v repo="$repo" '{print repo"/"$1}' + grep "exercises/" | + cut --delimiter '/' --fields -3 | + sort --unique || true )" else # we want globbing and it does not actually assign locally diff --git a/bin/test_exercise.sh b/bin/test_exercise.sh index 1bff4d672..616c03a8c 100755 --- a/bin/test_exercise.sh +++ b/bin/test_exercise.sh @@ -1,112 +1,81 @@ #!/usr/bin/env bash set -eo pipefail -# Test an exercise +cd "$(git rev-parse --show-toplevel)" -# which exercise are we testing right now? -# if we were passed an argument, that should be the -# exercise directory. Otherwise, assume we're in -# it currently. -if [ $# -ge 1 ]; then - exercise=$1 - # if this script is called with arguments, it will pass through - # any beyond the first to cargo. Note that we can only get a - # free default argument if no arguments at all were passed, - # so if you are in the exercise directory and want to pass any - # arguments to cargo, you need to include the local path first. - # I.e. to test in release mode: - # $ test_exercise.sh . --release - shift 1 -else - exercise='.' +if [ $# -eq 0 ]; then + echo "Usage: [cargo args]" + exit 1 +fi + +slug="$1" +cargo_args="$2" + +# determine the exercise path from the slug +for p in exercises/{practice,concept}/* ; do + if [[ "$p" =~ $slug ]]; then + exercise_path=$p + break + fi +done +if [ -z "$exercise_path" ]; then + echo "Could not find exercise path for $slug" + exit 1 fi # what cargo command will we use? if [ -n "$BENCHMARK" ]; then - command="bench" + command="+nightly bench" + if [ ! -e "$exercise_path/benches" ]; then + # no benches, nothing to do + exit + fi else command="test" fi -declare -a preserve_files=("src/lib.rs" "Cargo.toml" "Cargo.lock") -declare -a preserve_dirs=("tests") - -# reset instructions -reset () { - for file in "${preserve_files[@]}"; do - if [ -f "$exercise/$file.orig" ]; then - mv -f "$exercise/$file.orig" "$exercise/$file" - fi - done - for dir in "${preserve_dirs[@]}"; do - if [ -d "$exercise/$dir.orig" ]; then - rm -rf "${exercise:?}/$dir" - mv "$exercise/$dir.orig" "$exercise/$dir" - fi - done -} - -# cause the reset to execute when the script exits normally or is killed -trap reset EXIT INT TERM +# setup temporary directory for the exercise +tmp_dir=$(mktemp -d) +trap 'rm -rf $tmp_dir' EXIT INT TERM -# preserve the files and directories we care about -for file in "${preserve_files[@]}"; do - if [ -f "$exercise/$file" ]; then - cp "$exercise/$file" "$exercise/$file.orig" - fi -done -for dir in "${preserve_dirs[@]}"; do - if [ -d "$exercise/$dir" ]; then - cp -r "$exercise/$dir" "$exercise/$dir.orig" +# copy the exercise to the temporary directory +contents_to_copy=( + "src" + "tests" + "benches" + "Cargo.toml" +) +for c in "${contents_to_copy[@]}"; do + if [ ! -e "$exercise_path/$c" ]; then + continue fi + cp -r "$exercise_path/$c" "$tmp_dir" done # Move example files to where Cargo expects them -if [ -f "$exercise/.meta/example.rs" ]; then - example="$exercise/.meta/example.rs" -elif [ -f "$exercise/.meta/exemplar.rs" ]; then - example="$exercise/.meta/exemplar.rs" +if [ -f "$exercise_path/.meta/example.rs" ]; then + cp -f "$exercise_path/.meta/example.rs" "$tmp_dir/src/lib.rs" +elif [ -f "$exercise_path/.meta/exemplar.rs" ]; then + cp -f "$exercise_path/.meta/exemplar.rs" "$tmp_dir/src/lib.rs" else - echo "Could not locate example implementation for $exercise" + echo "Could not locate example implementation for $exercise_path" exit 1 fi - -cp -f "$example" "$exercise/src/lib.rs" -if [ -f "$exercise/.meta/Cargo-example.toml" ]; then - cp -f "$exercise/.meta/Cargo-example.toml" "$exercise/Cargo.toml" +if [ -f "$exercise_path/.meta/Cargo-example.toml" ]; then + cp -f "$exercise_path/.meta/Cargo-example.toml" "$tmp_dir/Cargo.toml" fi -# If deny warnings, insert a deny warnings compiler directive in the header if [ -n "$DENYWARNINGS" ]; then - sed -i -e '1i #![deny(warnings)]' "$exercise/src/lib.rs" + export RUSTFLAGS="$RUSTFLAGS -D warnings" fi -# eliminate #[ignore] lines from tests -for test in "$exercise/"tests/*.rs; do - sed -i -e '/#\[ignore\]/{ - s/#\[ignore\]\s*// - /^\s*$/d - }' "$test" -done - -# run tests from within exercise directory -# (use subshell so we auto-reset to current pwd after) -( -cd "$exercise" - if [ -n "$CLIPPY" ]; then - # Consider any Clippy to be an error in tests only. - # For now, not the example solutions since they have many Clippy warnings, - # and are not shown to students anyway. - sed -i -e '1i #![deny(clippy::all)]' tests/*.rs - if ! cargo clippy --tests --color always "$@" 2>clippy.log; then - cat clippy.log - exit 1 - else - # Just to show progress - echo "clippy $exercise OK" - fi - else - # this is the last command; its exit code is what's passed on - time cargo $command "$@" - fi -) +# run tests from within temporary directory +cd "$tmp_dir" +if [ -n "$CLIPPY" ]; then + export RUSTFLAGS="$RUSTFLAGS -D clippy::all" + # shellcheck disable=SC2086 + cargo clippy --tests $cargo_args +else + # shellcheck disable=SC2086 + cargo $command $cargo_args -- --include-ignored +fi From 09cb4c7887a0593e244c9db42582aa2bdf17fc54 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Wed, 20 Sep 2023 10:47:04 +0200 Subject: [PATCH 171/436] Fix clippy warnings in example solutions (#1747) --- docs/archived/exercise-concepts/luhn.md | 4 +- .../concept/csv-builder/.meta/exemplar.rs | 10 ++++- exercises/practice/acronym/.meta/example.rs | 2 +- .../practice/affine-cipher/.meta/example.rs | 6 +-- .../practice/atbash-cipher/.meta/example.rs | 2 +- .../practice/book-store/.meta/example.rs | 5 +-- exercises/practice/bowling/.meta/example.rs | 8 +++- .../practice/custom-set/.meta/example.rs | 4 +- .../practice/decimal/.meta/Cargo-example.toml | 4 +- exercises/practice/decimal/.meta/example.rs | 5 ++- exercises/practice/dot-dsl/.meta/example.rs | 6 +++ .../doubly-linked-list/.meta/example.rs | 8 +++- exercises/practice/forth/.meta/example.rs | 12 ++--- .../practice/grade-school/.meta/example.rs | 8 +++- .../practice/hexadecimal/.meta/example.rs | 4 +- .../practice/high-scores/.meta/example.rs | 2 +- .../largest-series-product/.meta/example.rs | 2 +- exercises/practice/luhn-from/.meta/example.rs | 2 +- exercises/practice/luhn/.meta/example.rs | 9 +++- .../matching-brackets/.meta/example.rs | 2 +- .../palindrome-products/.meta/example.rs | 2 +- .../practice/phone-number/.meta/example.rs | 5 ++- exercises/practice/pig-latin/.meta/example.rs | 2 +- .../practice/queen-attack/.meta/example.rs | 6 +-- exercises/practice/react/.meta/example.rs | 7 +++ .../practice/rectangles/.meta/example.rs | 2 + .../practice/robot-name/.meta/example.rs | 6 +++ .../practice/scale-generator/.meta/example.rs | 44 +++++++++---------- .../simple-linked-list/.meta/example.rs | 6 +++ .../practice/tournament/.meta/example.rs | 2 +- 30 files changed, 121 insertions(+), 66 deletions(-) diff --git a/docs/archived/exercise-concepts/luhn.md b/docs/archived/exercise-concepts/luhn.md index 0adece3d2..f8372c582 100644 --- a/docs/archived/exercise-concepts/luhn.md +++ b/docs/archived/exercise-concepts/luhn.md @@ -47,8 +47,8 @@ Canonical ```rust pub fn is_valid(candidate: &str) -> bool { - if candidate.chars().filter(|c| c.is_digit(10)).take(2).count() <= 1 - || candidate.chars().any(|c| !c.is_digit(10) && c != ' ') + if candidate.chars().filter(|c| c.is_ascii_digit()).take(2).count() <= 1 + || candidate.chars().any(|c| !c.is_ascii_digit() && c != ' ') { return false; } diff --git a/exercises/concept/csv-builder/.meta/exemplar.rs b/exercises/concept/csv-builder/.meta/exemplar.rs index c015adcbe..fa7bb77e0 100644 --- a/exercises/concept/csv-builder/.meta/exemplar.rs +++ b/exercises/concept/csv-builder/.meta/exemplar.rs @@ -16,9 +16,9 @@ impl CsvRecordBuilder { self.content.push(','); } - if val.contains(",") || val.contains(r#"""#) || val.contains("\n") { + if val.contains(',') || val.contains('"') || val.contains('\n') { self.content.push('"'); - self.content.push_str(&val.replace(r#"""#, r#""""#)); + self.content.push_str(&val.replace('"', r#""""#)); self.content.push('"'); } else { self.content.push_str(val); @@ -30,3 +30,9 @@ impl CsvRecordBuilder { self.content } } + +impl Default for CsvRecordBuilder { + fn default() -> Self { + Self::new() + } +} diff --git a/exercises/practice/acronym/.meta/example.rs b/exercises/practice/acronym/.meta/example.rs index 2bba845ef..80bd35821 100644 --- a/exercises/practice/acronym/.meta/example.rs +++ b/exercises/practice/acronym/.meta/example.rs @@ -1,7 +1,7 @@ pub fn abbreviate(phrase: &str) -> String { phrase .split(|c: char| c.is_whitespace() || (c != '\'' && !c.is_alphanumeric())) - .flat_map(|word| split_camel(word)) + .flat_map(split_camel) .filter_map(|word| word.chars().next()) .collect::() .to_uppercase() diff --git a/exercises/practice/affine-cipher/.meta/example.rs b/exercises/practice/affine-cipher/.meta/example.rs index 0be1aff26..d3c220daa 100644 --- a/exercises/practice/affine-cipher/.meta/example.rs +++ b/exercises/practice/affine-cipher/.meta/example.rs @@ -43,7 +43,7 @@ pub fn decode(ciphertext: &str, a: i32, b: i32) -> Result char { - if ch.is_digit(10) { + if ch.is_ascii_digit() { ch } else { let index = (ch as i32) - ('a' as i32); @@ -54,7 +54,7 @@ fn encode_char(ch: char, a: i32, b: i32) -> char { /// Decodes a single char using `inv` (the modular multiplicative inverse of `a`) and `b`. fn decode_char(ch: char, inv: i32, b: i32) -> char { - if ch.is_digit(10) { + if ch.is_ascii_digit() { ch } else { let index = (ch as i32) - ('a' as i32); @@ -82,7 +82,7 @@ fn modular_multiplicative_inverse(a: i32) -> Option { if rs.0 == 1 { // ts.0 gives a number such that (s * 26 + t * a) % 26 == 1. Since (s * 26) % 26 == 0, // we can further reduce this to (t * a) % 26 == 1. In other words, t is the MMI of a. - Some(ts.0 as i32) + Some(ts.0) } else { None } diff --git a/exercises/practice/atbash-cipher/.meta/example.rs b/exercises/practice/atbash-cipher/.meta/example.rs index 565659500..288ae96ac 100644 --- a/exercises/practice/atbash-cipher/.meta/example.rs +++ b/exercises/practice/atbash-cipher/.meta/example.rs @@ -3,7 +3,7 @@ fn ascii(ch: char) -> u8 { } fn get_transpose(ch: char) -> char { - if ch.is_digit(10) { + if ch.is_ascii_digit() { ch } else { (ascii('z') - ascii(ch) + ascii('a')) as char diff --git a/exercises/practice/book-store/.meta/example.rs b/exercises/practice/book-store/.meta/example.rs index dd348f96f..0293a5c89 100644 --- a/exercises/practice/book-store/.meta/example.rs +++ b/exercises/practice/book-store/.meta/example.rs @@ -3,7 +3,6 @@ use std::cmp::Ordering; use std::collections::hash_map::DefaultHasher; use std::collections::{BTreeSet, HashSet}; use std::hash::{Hash, Hasher}; -use std::mem; type Book = u32; type Price = u32; @@ -125,7 +124,7 @@ impl Iterator for DecomposeGroups { // then move the last item from the most populous group into a new group, alone, // and return let return_value = self.next.clone(); - if let Some(groups) = mem::replace(&mut self.next, None) { + if let Some(groups) = self.next.take() { if !(groups.is_empty() || groups.iter().all(|g| g.0.borrow().len() == 1)) { let mut hypothetical; for mpg_book in groups[0].0.borrow().iter() { @@ -169,7 +168,7 @@ impl DecomposeGroups { let mut book_groups = Vec::new(); 'nextbook: for book in books { for Group(book_group) in book_groups.iter() { - if !book_group.borrow().contains(&book) { + if !book_group.borrow().contains(book) { book_group.borrow_mut().insert(*book); continue 'nextbook; } diff --git a/exercises/practice/bowling/.meta/example.rs b/exercises/practice/bowling/.meta/example.rs index af7aa50fd..7a879e495 100644 --- a/exercises/practice/bowling/.meta/example.rs +++ b/exercises/practice/bowling/.meta/example.rs @@ -43,7 +43,7 @@ impl Frame { return self.bonus_score() <= 10; } - if let Some(first) = self.bonus.get(0) { + if let Some(first) = self.bonus.first() { if *first == 10 { self.bonus_score() <= 20 } else { @@ -139,3 +139,9 @@ impl BowlingGame { self.frames.len() == 10 && self.frames.iter().all(|f| f.is_complete()) } } + +impl Default for BowlingGame { + fn default() -> Self { + Self::new() + } +} diff --git a/exercises/practice/custom-set/.meta/example.rs b/exercises/practice/custom-set/.meta/example.rs index 8467e519f..ecb66442d 100644 --- a/exercises/practice/custom-set/.meta/example.rs +++ b/exercises/practice/custom-set/.meta/example.rs @@ -5,8 +5,8 @@ pub struct CustomSet { impl PartialEq for CustomSet { fn eq(&self, other: &Self) -> bool { - self.collection.iter().all(|x| other.contains(&x)) - && other.collection.iter().all(|x| self.contains(&x)) + self.collection.iter().all(|x| other.contains(x)) + && other.collection.iter().all(|x| self.contains(x)) } } diff --git a/exercises/practice/decimal/.meta/Cargo-example.toml b/exercises/practice/decimal/.meta/Cargo-example.toml index 5210e3217..96fbf9772 100644 --- a/exercises/practice/decimal/.meta/Cargo-example.toml +++ b/exercises/practice/decimal/.meta/Cargo-example.toml @@ -4,5 +4,5 @@ name = "decimal" version = "0.1.0" [dependencies] -num-bigint = "0.1.40" -num-traits = "0.1.40" +num-bigint = "0.4.4" +num-traits = "0.2.16" diff --git a/exercises/practice/decimal/.meta/example.rs b/exercises/practice/decimal/.meta/example.rs index e6c7d4c4e..8b44f6cee 100644 --- a/exercises/practice/decimal/.meta/example.rs +++ b/exercises/practice/decimal/.meta/example.rs @@ -63,7 +63,7 @@ impl Decimal { fn equalize_precision(one: &mut Decimal, two: &mut Decimal) { fn expand(lower_precision: &mut Decimal, higher_precision: &Decimal) { let precision_difference = - (higher_precision.decimal_index - lower_precision.decimal_index) as usize; + higher_precision.decimal_index - lower_precision.decimal_index; lower_precision.digits = &lower_precision.digits * pow(BigInt::from(10_usize), precision_difference); @@ -102,7 +102,9 @@ macro_rules! auto_impl_decimal_ops { fn $func_name(mut self, mut rhs: Self) -> Self { Decimal::equalize_precision(&mut self, &mut rhs); Decimal::new( + #[allow(clippy::redundant_closure_call)] $digits_operation(self.digits, rhs.digits), + #[allow(clippy::redundant_closure_call)] $index_operation(self.decimal_index, rhs.decimal_index), ) } @@ -125,6 +127,7 @@ macro_rules! auto_impl_decimal_cow { impl $trait for Decimal { fn $func_name(&self, other: &Self) -> $return_type { if self.decimal_index == other.decimal_index { + #[allow(clippy::redundant_closure_call)] $digits_operation(&self.digits, &other.digits) } else { // if we're here, the decimal indexes are unmatched. diff --git a/exercises/practice/dot-dsl/.meta/example.rs b/exercises/practice/dot-dsl/.meta/example.rs index 698088670..e957d61c2 100644 --- a/exercises/practice/dot-dsl/.meta/example.rs +++ b/exercises/practice/dot-dsl/.meta/example.rs @@ -44,6 +44,12 @@ pub mod graph { } } + impl Default for Graph { + fn default() -> Self { + Self::new() + } + } + pub mod graph_items { pub mod edge { use std::collections::HashMap; diff --git a/exercises/practice/doubly-linked-list/.meta/example.rs b/exercises/practice/doubly-linked-list/.meta/example.rs index 8d614af3a..d9b325ac3 100644 --- a/exercises/practice/doubly-linked-list/.meta/example.rs +++ b/exercises/practice/doubly-linked-list/.meta/example.rs @@ -67,6 +67,12 @@ impl Node { } } +impl Default for LinkedList { + fn default() -> Self { + Self::new() + } +} + impl LinkedList { pub fn new() -> Self { LinkedList { @@ -253,7 +259,7 @@ impl Cursor<'_, T> { } }; link_new_node(cursor_node, new_node); - let end_node = end_node(&mut self.list); + let end_node = end_node(self.list); if *end_node == Some(cursor_node) { *end_node = Some(new_node); } diff --git a/exercises/practice/forth/.meta/example.rs b/exercises/practice/forth/.meta/example.rs index 66ff0e69e..3eb990209 100644 --- a/exercises/practice/forth/.meta/example.rs +++ b/exercises/practice/forth/.meta/example.rs @@ -67,7 +67,6 @@ impl Forth { eval_op( parse_op(token, &self.words)?, &mut self.stack, - &self.words, &self.definitions, )?; } @@ -96,7 +95,7 @@ impl Forth { } (mode == Mode::Execution) - .then(|| ()) + .then_some(()) .ok_or(Error::InvalidWord) } } @@ -119,12 +118,7 @@ fn parse_op(token: &str, words: &HashMap) -> Result { }) } -fn eval_op( - op: Op, - stack: &mut Vec, - words: &HashMap, - definitions: &Vec>, -) -> ForthResult { +fn eval_op(op: Op, stack: &mut Vec, definitions: &Vec>) -> ForthResult { let mut pop = || stack.pop().ok_or(Error::StackUnderflow); match op { Op::Add => { @@ -165,7 +159,7 @@ fn eval_op( } Op::Call(fn_id) => { for op in &definitions[fn_id as usize] { - eval_op(*op, stack, words, definitions)?; + eval_op(*op, stack, definitions)?; } } } diff --git a/exercises/practice/grade-school/.meta/example.rs b/exercises/practice/grade-school/.meta/example.rs index 6e1e470f3..187535581 100644 --- a/exercises/practice/grade-school/.meta/example.rs +++ b/exercises/practice/grade-school/.meta/example.rs @@ -12,7 +12,7 @@ impl School { } pub fn add(&mut self, grade: u32, student: &str) { - let entry = self.grades.entry(grade).or_insert_with(Vec::new); + let entry = self.grades.entry(grade).or_default(); entry.push(student.to_string()); entry.sort_unstable(); } @@ -30,3 +30,9 @@ impl School { .unwrap_or_default() } } + +impl Default for School { + fn default() -> Self { + Self::new() + } +} diff --git a/exercises/practice/hexadecimal/.meta/example.rs b/exercises/practice/hexadecimal/.meta/example.rs index f539b3c9c..43fa3abab 100644 --- a/exercises/practice/hexadecimal/.meta/example.rs +++ b/exercises/practice/hexadecimal/.meta/example.rs @@ -27,7 +27,7 @@ pub fn hex_to_int(string: &str) -> Option { .chars() .rev() .enumerate() - .fold(Some(0), |acc, (pos, c)| { - parse_hex_digit(c).and_then(|n| acc.map(|acc| acc + n * base.pow(pos as u32))) + .try_fold(0, |acc, (pos, c)| { + parse_hex_digit(c).map(|n| acc + n * base.pow(pos as u32)) }) } diff --git a/exercises/practice/high-scores/.meta/example.rs b/exercises/practice/high-scores/.meta/example.rs index 5a7e74fd7..9e855ddb3 100644 --- a/exercises/practice/high-scores/.meta/example.rs +++ b/exercises/practice/high-scores/.meta/example.rs @@ -9,7 +9,7 @@ impl<'a> HighScores<'a> { } pub fn scores(&self) -> &'a [u32] { - &self.scores + self.scores } pub fn latest(&self) -> Option { diff --git a/exercises/practice/largest-series-product/.meta/example.rs b/exercises/practice/largest-series-product/.meta/example.rs index e89c42247..1f9bfbff6 100644 --- a/exercises/practice/largest-series-product/.meta/example.rs +++ b/exercises/practice/largest-series-product/.meta/example.rs @@ -9,7 +9,7 @@ pub fn lsp(string_digits: &str, span: usize) -> Result { return Ok(1); } - if let Some(invalid) = string_digits.chars().find(|c| !c.is_digit(10)) { + if let Some(invalid) = string_digits.chars().find(|c| !c.is_ascii_digit()) { return Err(Error::InvalidDigit(invalid)); } diff --git a/exercises/practice/luhn-from/.meta/example.rs b/exercises/practice/luhn-from/.meta/example.rs index 1cfd286a7..49f6436ef 100644 --- a/exercises/practice/luhn-from/.meta/example.rs +++ b/exercises/practice/luhn-from/.meta/example.rs @@ -4,7 +4,7 @@ pub struct Luhn { impl Luhn { pub fn is_valid(&self) -> bool { - if self.digits.iter().any(|c| c.is_alphabetic()) || self.digits.iter().count() == 1 { + if self.digits.iter().any(|c| c.is_alphabetic()) || self.digits.len() == 1 { return false; } diff --git a/exercises/practice/luhn/.meta/example.rs b/exercises/practice/luhn/.meta/example.rs index 4308887d6..d61897eff 100644 --- a/exercises/practice/luhn/.meta/example.rs +++ b/exercises/practice/luhn/.meta/example.rs @@ -1,6 +1,11 @@ pub fn is_valid(candidate: &str) -> bool { - if candidate.chars().filter(|c| c.is_digit(10)).take(2).count() <= 1 - || candidate.chars().any(|c| !c.is_digit(10) && c != ' ') + if candidate + .chars() + .filter(|c| c.is_ascii_digit()) + .take(2) + .count() + <= 1 + || candidate.chars().any(|c| !c.is_ascii_digit() && c != ' ') { return false; } diff --git a/exercises/practice/matching-brackets/.meta/example.rs b/exercises/practice/matching-brackets/.meta/example.rs index 8d3e46858..2da590030 100644 --- a/exercises/practice/matching-brackets/.meta/example.rs +++ b/exercises/practice/matching-brackets/.meta/example.rs @@ -23,7 +23,7 @@ impl Brackets { }; Brackets { - raw_brackets: s.chars().filter(|c| p.contains(&c)).collect::>(), + raw_brackets: s.chars().filter(|c| p.contains(c)).collect::>(), pairs: p, } } diff --git a/exercises/practice/palindrome-products/.meta/example.rs b/exercises/practice/palindrome-products/.meta/example.rs index a2e3ce7c9..a39c87fda 100644 --- a/exercises/practice/palindrome-products/.meta/example.rs +++ b/exercises/practice/palindrome-products/.meta/example.rs @@ -8,7 +8,7 @@ pub struct Palindrome(u64); impl Palindrome { /// Create a `Palindrome` only if `value` is in fact a palindrome when represented in base ten. Otherwise, `None`. pub fn new(value: u64) -> Option { - is_palindrome(value).then(move || Palindrome(value)) + is_palindrome(value).then_some(Palindrome(value)) } /// Get the value of this palindrome. diff --git a/exercises/practice/phone-number/.meta/example.rs b/exercises/practice/phone-number/.meta/example.rs index 88896c988..b8abacfb9 100644 --- a/exercises/practice/phone-number/.meta/example.rs +++ b/exercises/practice/phone-number/.meta/example.rs @@ -1,5 +1,8 @@ pub fn number(user_number: &str) -> Option { - let mut filtered_number: String = user_number.chars().filter(|ch| ch.is_digit(10)).collect(); + let mut filtered_number: String = user_number + .chars() + .filter(|ch| ch.is_ascii_digit()) + .collect(); let number_len = filtered_number.len(); diff --git a/exercises/practice/pig-latin/.meta/example.rs b/exercises/practice/pig-latin/.meta/example.rs index 6218f719f..d3a26d20b 100644 --- a/exercises/practice/pig-latin/.meta/example.rs +++ b/exercises/practice/pig-latin/.meta/example.rs @@ -26,7 +26,7 @@ pub fn translate_word(word: &str) -> String { pub fn translate(text: &str) -> String { text.split(' ') - .map(|w| translate_word(w)) + .map(translate_word) .collect::>() .join(" ") } diff --git a/exercises/practice/queen-attack/.meta/example.rs b/exercises/practice/queen-attack/.meta/example.rs index 76808ae09..e14c9bce4 100644 --- a/exercises/practice/queen-attack/.meta/example.rs +++ b/exercises/practice/queen-attack/.meta/example.rs @@ -14,9 +14,9 @@ impl ChessPiece for Queen { } fn can_attack(&self, piece: &T) -> bool { - self.position.horizontal_from(&piece.position()) - || self.position.vertical_from(&piece.position()) - || self.position.diagonal_from(&piece.position()) + self.position.horizontal_from(piece.position()) + || self.position.vertical_from(piece.position()) + || self.position.diagonal_from(piece.position()) } } diff --git a/exercises/practice/react/.meta/example.rs b/exercises/practice/react/.meta/example.rs index 86a25a405..199bb46d0 100644 --- a/exercises/practice/react/.meta/example.rs +++ b/exercises/practice/react/.meta/example.rs @@ -44,6 +44,7 @@ struct ComputeCell<'a, T: Copy> { cell: Cell, dependencies: Vec, + #[allow(clippy::type_complexity)] f: Box T>, callbacks_issued: usize, callbacks: HashMap>, @@ -247,3 +248,9 @@ impl<'a, T: Copy + PartialEq> Reactor<'a, T> { } } } + +impl<'a, T: Copy + PartialEq> Default for Reactor<'a, T> { + fn default() -> Self { + Self::new() + } +} diff --git a/exercises/practice/rectangles/.meta/example.rs b/exercises/practice/rectangles/.meta/example.rs index 001455c5c..ef18d07d7 100644 --- a/exercises/practice/rectangles/.meta/example.rs +++ b/exercises/practice/rectangles/.meta/example.rs @@ -71,9 +71,11 @@ struct TransposedArea<'a>(&'a RealArea); // For vertical scanning impl<'a> Area for TransposedArea<'a> { + #[allow(clippy::misnamed_getters)] fn width(&self) -> usize { self.0.height } + #[allow(clippy::misnamed_getters)] fn height(&self) -> usize { self.0.width } diff --git a/exercises/practice/robot-name/.meta/example.rs b/exercises/practice/robot-name/.meta/example.rs index efed25d51..07fbba8bf 100644 --- a/exercises/practice/robot-name/.meta/example.rs +++ b/exercises/practice/robot-name/.meta/example.rs @@ -44,3 +44,9 @@ impl Robot { self.name = generate_name(); } } + +impl Default for Robot { + fn default() -> Self { + Self::new() + } +} diff --git a/exercises/practice/scale-generator/.meta/example.rs b/exercises/practice/scale-generator/.meta/example.rs index 779218909..ace769a01 100644 --- a/exercises/practice/scale-generator/.meta/example.rs +++ b/exercises/practice/scale-generator/.meta/example.rs @@ -96,26 +96,6 @@ pub mod interval { &self.0 } } - - #[cfg(test)] - mod test { - use super::*; - - #[test] - fn parse_chromatic() { - assert!("mmmmmmmmmmmm".parse::().is_ok()); - } - - #[test] - fn parse_major() { - assert!("MMmMMMm".parse::().is_ok()); - } - - #[test] - fn parse_minor() { - assert!("MmMMmMM".parse::().is_ok()); - } - } } pub mod note { @@ -167,8 +147,8 @@ pub mod note { } impl Accidental { - fn to_i8(&self) -> i8 { - match *self { + fn to_i8(self) -> i8 { + match self { Accidental::Sharp => 1, Accidental::Flat => -1, } @@ -355,3 +335,23 @@ impl Scale { out } } + +#[cfg(test)] +mod test { + use super::interval::*; + + #[test] + fn parse_chromatic() { + assert!("mmmmmmmmmmmm".parse::().is_ok()); + } + + #[test] + fn parse_major() { + assert!("MMmMMMm".parse::().is_ok()); + } + + #[test] + fn parse_minor() { + assert!("MmMMmMM".parse::().is_ok()); + } +} diff --git a/exercises/practice/simple-linked-list/.meta/example.rs b/exercises/practice/simple-linked-list/.meta/example.rs index e64a55b01..9647fc5cb 100644 --- a/exercises/practice/simple-linked-list/.meta/example.rs +++ b/exercises/practice/simple-linked-list/.meta/example.rs @@ -10,6 +10,12 @@ struct Node { next: Option>>, } +impl Default for SimpleLinkedList { + fn default() -> Self { + Self::new() + } +} + impl SimpleLinkedList { pub fn new() -> Self { SimpleLinkedList { head: None, len: 0 } diff --git a/exercises/practice/tournament/.meta/example.rs b/exercises/practice/tournament/.meta/example.rs index e7767a057..9221684a9 100644 --- a/exercises/practice/tournament/.meta/example.rs +++ b/exercises/practice/tournament/.meta/example.rs @@ -70,7 +70,7 @@ fn write_tally(results: &HashMap) -> String { .collect(); // Sort by points descending, then name A-Z. v.sort_by(|a, b| match a.3.cmp(&(b.3)).reverse() { - Equal => a.0.cmp(&(b.0)), + Equal => a.0.cmp(b.0), other => other, }); let mut lines = vec![format!("{:30} | MP | W | D | L | P", "Team")]; From ec5f981aef864a405e3294077c30c37ff153f248 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Wed, 20 Sep 2023 13:57:05 +0200 Subject: [PATCH 172/436] Sync affine-cipher with problem-specifications (#1748) --- .../affine-cipher/.docs/instructions.md | 108 +++++++------- .../practice/affine-cipher/.meta/config.json | 2 +- .../affine-cipher/.meta/test_template.tera | 18 +++ .../affine-cipher/tests/affine-cipher.rs | 140 ++++++++++-------- problem-specifications | 2 +- 5 files changed, 157 insertions(+), 113 deletions(-) create mode 100644 exercises/practice/affine-cipher/.meta/test_template.tera diff --git a/exercises/practice/affine-cipher/.docs/instructions.md b/exercises/practice/affine-cipher/.docs/instructions.md index b3ebb9c76..26ce15342 100644 --- a/exercises/practice/affine-cipher/.docs/instructions.md +++ b/exercises/practice/affine-cipher/.docs/instructions.md @@ -1,70 +1,74 @@ # Instructions -Create an implementation of the affine cipher, -an ancient encryption system created in the Middle East. +Create an implementation of the affine cipher, an ancient encryption system created in the Middle East. The affine cipher is a type of monoalphabetic substitution cipher. -Each character is mapped to its numeric equivalent, encrypted with -a mathematical function and then converted to the letter relating to -its new numeric value. Although all monoalphabetic ciphers are weak, -the affine cypher is much stronger than the atbash cipher, -because it has many more keys. +Each character is mapped to its numeric equivalent, encrypted with a mathematical function and then converted to the letter relating to its new numeric value. +Although all monoalphabetic ciphers are weak, the affine cipher is much stronger than the atbash cipher, because it has many more keys. + +[//]: # " monoalphabetic as spelled by Merriam-Webster, compare to polyalphabetic " + +## Encryption The encryption function is: - `E(x) = (ax + b) mod m` - - where `x` is the letter's index from 0 - length of alphabet - 1 - - `m` is the length of the alphabet. For the roman alphabet `m == 26`. - - and `a` and `b` make the key +```text +E(x) = (ai + b) mod m +``` -The decryption function is: +Where: + +- `i` is the letter's index from `0` to the length of the alphabet - 1 +- `m` is the length of the alphabet. + For the Roman alphabet `m` is `26`. +- `a` and `b` are integers which make the encryption key + +Values `a` and `m` must be _coprime_ (or, _relatively prime_) for automatic decryption to succeed, i.e., they have number `1` as their only common factor (more information can be found in the [Wikipedia article about coprime integers][coprime-integers]). +In case `a` is not coprime to `m`, your program should indicate that this is an error. +Otherwise it should encrypt or decrypt with the provided key. + +For the purpose of this exercise, digits are valid input but they are not encrypted. +Spaces and punctuation characters are excluded. +Ciphertext is written out in groups of fixed length separated by space, the traditional group size being `5` letters. +This is to make it harder to guess encrypted text based on word boundaries. - `D(y) = a^-1(y - b) mod m` - - where `y` is the numeric value of an encrypted letter, ie. `y = E(x)` - - it is important to note that `a^-1` is the modular multiplicative inverse - of `a mod m` - - the modular multiplicative inverse of `a` only exists if `a` and `m` are - coprime. +## Decryption -To find the MMI of `a`: +The decryption function is: + +```text +D(y) = (a^-1)(y - b) mod m +``` - `an mod m = 1` - - where `n` is the modular multiplicative inverse of `a mod m` +Where: -More information regarding how to find a Modular Multiplicative Inverse -and what it means can be found [here.](https://en.wikipedia.org/wiki/Modular_multiplicative_inverse) +- `y` is the numeric value of an encrypted letter, i.e., `y = E(x)` +- it is important to note that `a^-1` is the modular multiplicative inverse (MMI) of `a mod m` +- the modular multiplicative inverse only exists if `a` and `m` are coprime. -Because automatic decryption fails if `a` is not coprime to `m` your -program should return status 1 and `"Error: a and m must be coprime."` -if they are not. Otherwise it should encode or decode with the -provided key. +The MMI of `a` is `x` such that the remainder after dividing `ax` by `m` is `1`: -The Caesar (shift) cipher is a simple affine cipher where `a` is 1 and -`b` as the magnitude results in a static displacement of the letters. -This is much less secure than a full implementation of the affine cipher. +```text +ax mod m = 1 +``` -Ciphertext is written out in groups of fixed length, the traditional group -size being 5 letters, and punctuation is excluded. This is to make it -harder to guess things based on word boundaries. +More information regarding how to find a Modular Multiplicative Inverse and what it means can be found in the [related Wikipedia article][mmi]. ## General Examples - - Encoding `test` gives `ybty` with the key a=5 b=7 - - Decoding `ybty` gives `test` with the key a=5 b=7 - - Decoding `ybty` gives `lqul` with the wrong key a=11 b=7 - - Decoding `kqlfd jzvgy tpaet icdhm rtwly kqlon ubstx` - - gives `thequickbrownfoxjumpsoverthelazydog` with the key a=19 b=13 - - Encoding `test` with the key a=18 b=13 - - gives `Error: a and m must be coprime.` - - because a and m are not relatively prime - -## Examples of finding a Modular Multiplicative Inverse (MMI) - - - simple example: - - `9 mod 26 = 9` - - `9 * 3 mod 26 = 27 mod 26 = 1` - - `3` is the MMI of `9 mod 26` - - a more complicated example: - - `15 mod 26 = 15` - - `15 * 7 mod 26 = 105 mod 26 = 1` - - `7` is the MMI of `15 mod 26` +- Encrypting `"test"` gives `"ybty"` with the key `a = 5`, `b = 7` +- Decrypting `"ybty"` gives `"test"` with the key `a = 5`, `b = 7` +- Decrypting `"ybty"` gives `"lqul"` with the wrong key `a = 11`, `b = 7` +- Decrypting `"kqlfd jzvgy tpaet icdhm rtwly kqlon ubstx"` gives `"thequickbrownfoxjumpsoverthelazydog"` with the key `a = 19`, `b = 13` +- Encrypting `"test"` with the key `a = 18`, `b = 13` is an error because `18` and `26` are not coprime + +## Example of finding a Modular Multiplicative Inverse (MMI) + +Finding MMI for `a = 15`: + +- `(15 * x) mod 26 = 1` +- `(15 * 7) mod 26 = 1`, ie. `105 mod 26 = 1` +- `7` is the MMI of `15 mod 26` + +[mmi]: https://en.wikipedia.org/wiki/Modular_multiplicative_inverse +[coprime-integers]: https://en.wikipedia.org/wiki/Coprime_integers diff --git a/exercises/practice/affine-cipher/.meta/config.json b/exercises/practice/affine-cipher/.meta/config.json index 10c809f0a..34bb0ac2b 100644 --- a/exercises/practice/affine-cipher/.meta/config.json +++ b/exercises/practice/affine-cipher/.meta/config.json @@ -24,5 +24,5 @@ }, "blurb": "Create an implementation of the Affine cipher, an ancient encryption algorithm from the Middle East.", "source": "Wikipedia", - "source_url": "http://en.wikipedia.org/wiki/Affine_cipher" + "source_url": "https://en.wikipedia.org/wiki/Affine_cipher" } diff --git a/exercises/practice/affine-cipher/.meta/test_template.tera b/exercises/practice/affine-cipher/.meta/test_template.tera new file mode 100644 index 000000000..0c15e457a --- /dev/null +++ b/exercises/practice/affine-cipher/.meta/test_template.tera @@ -0,0 +1,18 @@ +use affine_cipher::AffineCipherError::NotCoprime; +{% for test in cases %} +#[test] +{% if loop.index != 1 -%} +#[ignore] +{% endif -%} +fn {{ test.description | slugify | replace(from="-", to="_") }}() { + let phrase = {{ test.input.phrase | json_encode() }}; + let (a, b) = ({{ test.input.key.a }}, {{ test.input.key.b }}); + let output = {{ crate_name }}::{{ test.property }}(phrase, a, b); + let expected = {% if test.expected is object -%} + Err(NotCoprime({{ test.input.key.a }})) + {%- else -%} + Ok({{ test.expected | json_encode() }}.into()) + {%- endif %}; + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/affine-cipher/tests/affine-cipher.rs b/exercises/practice/affine-cipher/tests/affine-cipher.rs index 69dbf34ac..2476677a0 100644 --- a/exercises/practice/affine-cipher/tests/affine-cipher.rs +++ b/exercises/practice/affine-cipher/tests/affine-cipher.rs @@ -1,138 +1,160 @@ -use affine_cipher::*; +use affine_cipher::AffineCipherError::NotCoprime; #[test] fn encode_yes() { - assert_eq!(encode("yes", 5, 7).unwrap(), "xbt") + let phrase = "yes"; + let (a, b) = (5, 7); + let output = affine_cipher::encode(phrase, a, b); + let expected = Ok("xbt".into()); + assert_eq!(output, expected); } #[test] #[ignore] fn encode_no() { - assert_eq!(encode("no", 15, 18).unwrap(), "fu") + let phrase = "no"; + let (a, b) = (15, 18); + let output = affine_cipher::encode(phrase, a, b); + let expected = Ok("fu".into()); + assert_eq!(output, expected); } #[test] #[ignore] fn encode_omg() { - assert_eq!(encode("OMG", 21, 3).unwrap(), "lvz") + let phrase = "OMG"; + let (a, b) = (21, 3); + let output = affine_cipher::encode(phrase, a, b); + let expected = Ok("lvz".into()); + assert_eq!(output, expected); } #[test] #[ignore] fn encode_o_m_g() { - assert_eq!(encode("O M G", 25, 47).unwrap(), "hjp") + let phrase = "O M G"; + let (a, b) = (25, 47); + let output = affine_cipher::encode(phrase, a, b); + let expected = Ok("hjp".into()); + assert_eq!(output, expected); } #[test] #[ignore] fn encode_mindblowingly() { - assert_eq!(encode("mindblowingly", 11, 15).unwrap(), "rzcwa gnxzc dgt") + let phrase = "mindblowingly"; + let (a, b) = (11, 15); + let output = affine_cipher::encode(phrase, a, b); + let expected = Ok("rzcwa gnxzc dgt".into()); + assert_eq!(output, expected); } #[test] #[ignore] fn encode_numbers() { - assert_eq!( - encode("Testing,1 2 3, testing.", 3, 4).unwrap(), - "jqgjc rw123 jqgjc rw" - ) + let phrase = "Testing,1 2 3, testing."; + let (a, b) = (3, 4); + let output = affine_cipher::encode(phrase, a, b); + let expected = Ok("jqgjc rw123 jqgjc rw".into()); + assert_eq!(output, expected); } #[test] #[ignore] fn encode_deep_thought() { - assert_eq!( - encode("Truth is fiction", 5, 17).unwrap(), - "iynia fdqfb ifje" - ) + let phrase = "Truth is fiction."; + let (a, b) = (5, 17); + let output = affine_cipher::encode(phrase, a, b); + let expected = Ok("iynia fdqfb ifje".into()); + assert_eq!(output, expected); } #[test] #[ignore] fn encode_all_the_letters() { - assert_eq!( - encode("The quick brown fox jumps over the lazy dog.", 17, 33).unwrap(), - "swxtj npvyk lruol iejdc blaxk swxmh qzglf" - ) + let phrase = "The quick brown fox jumps over the lazy dog."; + let (a, b) = (17, 33); + let output = affine_cipher::encode(phrase, a, b); + let expected = Ok("swxtj npvyk lruol iejdc blaxk swxmh qzglf".into()); + assert_eq!(output, expected); } #[test] #[ignore] fn encode_with_a_not_coprime_to_m() { - const EXPECTED_ERROR: AffineCipherError = AffineCipherError::NotCoprime(6); - match encode("This is a test.", 6, 17) { - Err(EXPECTED_ERROR) => (), - Err(err) => panic!("Incorrect error: expected: {EXPECTED_ERROR:?}, actual: {err:?}."), - Ok(r) => panic!( - "Cannot encode/decode when a is coprime to m: expected: {EXPECTED_ERROR:?}, actual: {r:?}." - ), - } + let phrase = "This is a test."; + let (a, b) = (6, 17); + let output = affine_cipher::encode(phrase, a, b); + let expected = Err(NotCoprime(6)); + assert_eq!(output, expected); } #[test] #[ignore] fn decode_exercism() { - assert_eq!(decode("tytgn fjr", 3, 7).unwrap(), "exercism") + let phrase = "tytgn fjr"; + let (a, b) = (3, 7); + let output = affine_cipher::decode(phrase, a, b); + let expected = Ok("exercism".into()); + assert_eq!(output, expected); } #[test] #[ignore] fn decode_a_sentence() { - assert_eq!( - encode("anobstacleisoftenasteppingstone", 19, 16).unwrap(), - "qdwju nqcro muwhn odqun oppmd aunwd o" - ); - assert_eq!( - decode("qdwju nqcro muwhn odqun oppmd aunwd o", 19, 16).unwrap(), - "anobstacleisoftenasteppingstone" - ) + let phrase = "qdwju nqcro muwhn odqun oppmd aunwd o"; + let (a, b) = (19, 16); + let output = affine_cipher::decode(phrase, a, b); + let expected = Ok("anobstacleisoftenasteppingstone".into()); + assert_eq!(output, expected); } #[test] #[ignore] fn decode_numbers() { - assert_eq!( - decode("odpoz ub123 odpoz ub", 25, 7).unwrap(), - "testing123testing" - ) + let phrase = "odpoz ub123 odpoz ub"; + let (a, b) = (25, 7); + let output = affine_cipher::decode(phrase, a, b); + let expected = Ok("testing123testing".into()); + assert_eq!(output, expected); } #[test] #[ignore] fn decode_all_the_letters() { - assert_eq!( - decode("swxtj npvyk lruol iejdc blaxk swxmh qzglf", 17, 33).unwrap(), - "thequickbrownfoxjumpsoverthelazydog" - ) + let phrase = "swxtj npvyk lruol iejdc blaxk swxmh qzglf"; + let (a, b) = (17, 33); + let output = affine_cipher::decode(phrase, a, b); + let expected = Ok("thequickbrownfoxjumpsoverthelazydog".into()); + assert_eq!(output, expected); } #[test] #[ignore] fn decode_with_no_spaces_in_input() { - assert_eq!( - decode("swxtjnpvyklruoliejdcblaxkswxmhqzglf", 17, 33).unwrap(), - "thequickbrownfoxjumpsoverthelazydog" - ) + let phrase = "swxtjnpvyklruoliejdcblaxkswxmhqzglf"; + let (a, b) = (17, 33); + let output = affine_cipher::decode(phrase, a, b); + let expected = Ok("thequickbrownfoxjumpsoverthelazydog".into()); + assert_eq!(output, expected); } #[test] #[ignore] fn decode_with_too_many_spaces() { - assert_eq!( - decode("vszzm cly yd cg qdp", 15, 16).unwrap(), - "jollygreengiant" - ) + let phrase = "vszzm cly yd cg qdp"; + let (a, b) = (15, 16); + let output = affine_cipher::decode(phrase, a, b); + let expected = Ok("jollygreengiant".into()); + assert_eq!(output, expected); } #[test] #[ignore] fn decode_with_a_not_coprime_to_m() { - const EXPECTED_ERROR: AffineCipherError = AffineCipherError::NotCoprime(13); - match decode("Test", 13, 11) { - Err(EXPECTED_ERROR) => (), - Err(e) => panic!("Incorrect error: expected: {EXPECTED_ERROR:?}, actual: {e:?}."), - Ok(r) => panic!( - "Cannot encode/decode when a is coprime to m: expected: {EXPECTED_ERROR:?}, actual: {r:?}." - ), - } + let phrase = "Test"; + let (a, b) = (13, 5); + let output = affine_cipher::decode(phrase, a, b); + let expected = Err(NotCoprime(13)); + assert_eq!(output, expected); } diff --git a/problem-specifications b/problem-specifications index d2229dedf..03220a9ce 160000 --- a/problem-specifications +++ b/problem-specifications @@ -1 +1 @@ -Subproject commit d2229dedfa6c6a6bb7d98dc49548d9ae06d0a848 +Subproject commit 03220a9ceb507f46cb70e317aaf1620237435144 From 4a32737cb721f561df37e85aca933fc625818dd0 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Mon, 25 Sep 2023 14:05:20 +0200 Subject: [PATCH 173/436] series: Clarify edge case of zero-length substrings (#1751) --- exercises/practice/series/.docs/instructions.append.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 exercises/practice/series/.docs/instructions.append.md diff --git a/exercises/practice/series/.docs/instructions.append.md b/exercises/practice/series/.docs/instructions.append.md new file mode 100644 index 000000000..d55ab1e1b --- /dev/null +++ b/exercises/practice/series/.docs/instructions.append.md @@ -0,0 +1,4 @@ +# Instructions append + +Different languages on Exercism have different expectations about what the result should be if the length of the substrings is zero. +For Rust, we expect you to output a number of empty strings, which will be one greater than the length of the input string. From 795db9edcfc6650db6ff5890b1162eedcc1ddeff Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 26 Sep 2023 12:53:31 +0200 Subject: [PATCH 174/436] Sync custom-set with problem-specifications (#1741) --- .../practice/custom-set/.docs/instructions.md | 7 +- .../custom-set/.meta/test_template.tera | 60 ++++ .../practice/custom-set/.meta/tests.toml | 121 +++++++- .../practice/custom-set/tests/custom-set.rs | 289 ++++++++++-------- problem-specifications | 2 +- 5 files changed, 330 insertions(+), 149 deletions(-) create mode 100644 exercises/practice/custom-set/.meta/test_template.tera diff --git a/exercises/practice/custom-set/.docs/instructions.md b/exercises/practice/custom-set/.docs/instructions.md index e4931b058..33b90e28d 100644 --- a/exercises/practice/custom-set/.docs/instructions.md +++ b/exercises/practice/custom-set/.docs/instructions.md @@ -2,7 +2,6 @@ Create a custom set type. -Sometimes it is necessary to define a custom data structure of some -type, like a set. In this exercise you will define your own set. How it -works internally doesn't matter, as long as it behaves like a set of -unique elements. +Sometimes it is necessary to define a custom data structure of some type, like a set. +In this exercise you will define your own set. +How it works internally doesn't matter, as long as it behaves like a set of unique elements. diff --git a/exercises/practice/custom-set/.meta/test_template.tera b/exercises/practice/custom-set/.meta/test_template.tera new file mode 100644 index 000000000..c6fc07bb5 --- /dev/null +++ b/exercises/practice/custom-set/.meta/test_template.tera @@ -0,0 +1,60 @@ +use custom_set::CustomSet; +{% for test in cases %} +#[test] +{% if loop.index != 1 -%} +#[ignore] +{% endif -%} +fn {{ test.description | slugify | replace(from="-", to="_") }}() { +{%- if test.property == "empty" %} + let set = CustomSet::::new(&{{ test.input.set | json_encode() }}); + assert!( + {%- if not test.expected -%} ! {%- endif -%} + set.is_empty()); +{%- elif test.property == "contains" %} + let set = CustomSet::::new(&{{ test.input.set | json_encode() }}); + assert!( + {%- if not test.expected -%} ! {%- endif -%} + set.contains(&{{ test.input.element }})); +{%- elif test.property == "subset" %} + let set_1 = CustomSet::::new(&{{ test.input.set1 | json_encode() }}); + let set_2 = CustomSet::::new(&{{ test.input.set2 | json_encode() }}); + assert!( + {%- if not test.expected -%} ! {%- endif -%} + set_1.is_subset(&set_2)); +{%- elif test.property == "disjoint" %} + let set_1 = CustomSet::::new(&{{ test.input.set1 | json_encode() }}); + let set_2 = CustomSet::::new(&{{ test.input.set2 | json_encode() }}); + assert!( + {%- if not test.expected -%} ! {%- endif -%} + set_1.is_disjoint(&set_2)); +{%- elif test.property == "equal" %} + let set_1 = CustomSet::::new(&{{ test.input.set1 | json_encode() }}); + let set_2 = CustomSet::::new(&{{ test.input.set2 | json_encode() }}); + {% if test.expected -%} + assert_eq!(set_1, set_2); + {%- else -%} + assert_ne!(set_1, set_2); + {%- endif %} +{%- elif test.property == "add" %} + let mut set = CustomSet::::new(&{{ test.input.set | json_encode() }}); + set.add({{ test.input.element }}); + let expected = CustomSet::::new(&{{ test.expected | json_encode() }}); + assert_eq!(set, expected); +{%- elif test.property == "intersection" %} + let set_1 = CustomSet::::new(&{{ test.input.set1 | json_encode() }}); + let set_2 = CustomSet::::new(&{{ test.input.set2 | json_encode() }}); + let expected = CustomSet::::new(&{{ test.expected | json_encode() }}); + assert_eq!(set_1.intersection(&set_2), expected); +{%- elif test.property == "difference" %} + let set_1 = CustomSet::::new(&{{ test.input.set1 | json_encode() }}); + let set_2 = CustomSet::::new(&{{ test.input.set2 | json_encode() }}); + let expected = CustomSet::::new(&{{ test.expected | json_encode() }}); + assert_eq!(set_1.difference(&set_2), expected); +{%- elif test.property == "union" %} + let set_1 = CustomSet::::new(&{{ test.input.set1 | json_encode() }}); + let set_2 = CustomSet::::new(&{{ test.input.set2 | json_encode() }}); + let expected = CustomSet::::new(&{{ test.expected | json_encode() }}); + assert_eq!(set_1.union(&set_2), expected); +{%- endif %} +} +{% endfor -%} diff --git a/exercises/practice/custom-set/.meta/tests.toml b/exercises/practice/custom-set/.meta/tests.toml index 1f3929e49..8c450e0ba 100644 --- a/exercises/practice/custom-set/.meta/tests.toml +++ b/exercises/practice/custom-set/.meta/tests.toml @@ -1,30 +1,127 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [20c5f855-f83a-44a7-abdd-fe75c6cf022b] -description = "sets with no elements are empty" +description = "Returns true if the set contains no elements -> sets with no elements are empty" [d506485d-5706-40db-b7d8-5ceb5acf88d2] -description = "sets with elements are not empty" +description = "Returns true if the set contains no elements -> sets with elements are not empty" [759b9740-3417-44c3-8ca3-262b3c281043] -description = "nothing is contained in an empty set" +description = "Sets can report if they contain an element -> nothing is contained in an empty set" + +[f83cd2d1-2a85-41bc-b6be-80adbff4be49] +description = "Sets can report if they contain an element -> when the element is in the set" + +[93423fc0-44d0-4bc0-a2ac-376de8d7af34] +description = "Sets can report if they contain an element -> when the element is not in the set" + +[c392923a-637b-4495-b28e-34742cd6157a] +description = "A set is a subset if all of its elements are contained in the other set -> empty set is a subset of another empty set" + +[5635b113-be8c-4c6f-b9a9-23c485193917] +description = "A set is a subset if all of its elements are contained in the other set -> empty set is a subset of non-empty set" + +[832eda58-6d6e-44e2-92c2-be8cf0173cee] +description = "A set is a subset if all of its elements are contained in the other set -> non-empty set is not a subset of empty set" + +[c830c578-8f97-4036-b082-89feda876131] +description = "A set is a subset if all of its elements are contained in the other set -> set is a subset of set with exact same elements" + +[476a4a1c-0fd1-430f-aa65-5b70cbc810c5] +description = "A set is a subset if all of its elements are contained in the other set -> set is a subset of larger set with same elements" + +[d2498999-3e46-48e4-9660-1e20c3329d3d] +description = "A set is a subset if all of its elements are contained in the other set -> set is not a subset of set that does not contain its elements" + +[7d38155e-f472-4a7e-9ad8-5c1f8f95e4cc] +description = "Sets are disjoint if they share no elements -> the empty set is disjoint with itself" + +[7a2b3938-64b6-4b32-901a-fe16891998a6] +description = "Sets are disjoint if they share no elements -> empty set is disjoint with non-empty set" + +[589574a0-8b48-48ea-88b0-b652c5fe476f] +description = "Sets are disjoint if they share no elements -> non-empty set is disjoint with empty set" + +[febeaf4f-f180-4499-91fa-59165955a523] +description = "Sets are disjoint if they share no elements -> sets are not disjoint if they share an element" + +[0de20d2f-c952-468a-88c8-5e056740f020] +description = "Sets are disjoint if they share no elements -> sets are disjoint if they share no elements" [4bd24adb-45da-4320-9ff6-38c044e9dff8] -description = "empty sets are equal" +description = "Sets with the same elements are equal -> empty sets are equal" + +[f65c0a0e-6632-4b2d-b82c-b7c6da2ec224] +description = "Sets with the same elements are equal -> empty set is not equal to non-empty set" + +[81e53307-7683-4b1e-a30c-7e49155fe3ca] +description = "Sets with the same elements are equal -> non-empty set is not equal to empty set" [d57c5d7c-a7f3-48cc-a162-6b488c0fbbd0] -description = "sets with the same elements are equal" +description = "Sets with the same elements are equal -> sets with the same elements are equal" [dd61bafc-6653-42cc-961a-ab071ee0ee85] -description = "sets with different elements are not equal" +description = "Sets with the same elements are equal -> sets with different elements are not equal" + +[06059caf-9bf4-425e-aaff-88966cb3ea14] +description = "Sets with the same elements are equal -> set is not equal to larger set with same elements" + +[d4a1142f-09aa-4df9-8b83-4437dcf7ec24] +description = "Sets with the same elements are equal -> set is equal to a set constructed from an array with duplicates" [8a677c3c-a658-4d39-bb88-5b5b1a9659f4] -description = "add to empty set" +description = "Unique elements can be added to a set -> add to empty set" + +[0903dd45-904d-4cf2-bddd-0905e1a8d125] +description = "Unique elements can be added to a set -> add to non-empty set" + +[b0eb7bb7-5e5d-4733-b582-af771476cb99] +description = "Unique elements can be added to a set -> adding an existing element does not change the set" + +[893d5333-33b8-4151-a3d4-8f273358208a] +description = "Intersection returns a set of all shared elements -> intersection of two empty sets is an empty set" + +[d739940e-def2-41ab-a7bb-aaf60f7d782c] +description = "Intersection returns a set of all shared elements -> intersection of an empty set and non-empty set is an empty set" + +[3607d9d8-c895-4d6f-ac16-a14956e0a4b7] +description = "Intersection returns a set of all shared elements -> intersection of a non-empty set and an empty set is an empty set" [b5120abf-5b5e-41ab-aede-4de2ad85c34e] -description = "intersection of two sets with no shared elements is an empty set" +description = "Intersection returns a set of all shared elements -> intersection of two sets with no shared elements is an empty set" [af21ca1b-fac9-499c-81c0-92a591653d49] -description = "intersection of two sets with shared elements is a set of the shared elements" +description = "Intersection returns a set of all shared elements -> intersection of two sets with shared elements is a set of the shared elements" + +[c5e6e2e4-50e9-4bc2-b89f-c518f015b57e] +description = "Difference (or Complement) of a set is a set of all elements that are only in the first set -> difference of two empty sets is an empty set" + +[2024cc92-5c26-44ed-aafd-e6ca27d6fcd2] +description = "Difference (or Complement) of a set is a set of all elements that are only in the first set -> difference of empty set and non-empty set is an empty set" + +[e79edee7-08aa-4c19-9382-f6820974b43e] +description = "Difference (or Complement) of a set is a set of all elements that are only in the first set -> difference of a non-empty set and an empty set is the non-empty set" + +[c5ac673e-d707-4db5-8d69-7082c3a5437e] +description = "Difference (or Complement) of a set is a set of all elements that are only in the first set -> difference of two non-empty sets is a set of elements that are only in the first set" + +[c45aed16-5494-455a-9033-5d4c93589dc6] +description = "Union returns a set of all elements in either set -> union of empty sets is an empty set" + +[9d258545-33c2-4fcb-a340-9f8aa69e7a41] +description = "Union returns a set of all elements in either set -> union of an empty set and non-empty set is the non-empty set" + +[3aade50c-80c7-4db8-853d-75bac5818b83] +description = "Union returns a set of all elements in either set -> union of a non-empty set and empty set is the non-empty set" + +[a00bb91f-c4b4-4844-8f77-c73e2e9df77c] +description = "Union returns a set of all elements in either set -> union of non-empty sets contains all unique elements" diff --git a/exercises/practice/custom-set/tests/custom-set.rs b/exercises/practice/custom-set/tests/custom-set.rs index 10d384f22..6730dadb5 100644 --- a/exercises/practice/custom-set/tests/custom-set.rs +++ b/exercises/practice/custom-set/tests/custom-set.rs @@ -1,298 +1,323 @@ -use custom_set::*; +use custom_set::CustomSet; #[test] fn sets_with_no_elements_are_empty() { - let set: CustomSet<()> = CustomSet::new(&[]); + let set = CustomSet::::new(&[]); assert!(set.is_empty()); } #[test] #[ignore] fn sets_with_elements_are_not_empty() { - let set = CustomSet::new(&[1]); + let set = CustomSet::::new(&[1]); assert!(!set.is_empty()); } #[test] #[ignore] fn nothing_is_contained_in_an_empty_set() { - let set = CustomSet::new(&[]); + let set = CustomSet::::new(&[]); assert!(!set.contains(&1)); } #[test] #[ignore] -fn true_when_the_element_is_in_the_set() { - let set = CustomSet::new(&[1, 2, 3]); +fn when_the_element_is_in_the_set() { + let set = CustomSet::::new(&[1, 2, 3]); assert!(set.contains(&1)); } #[test] #[ignore] -fn false_when_the_element_is_not_in_the_set() { - let set = CustomSet::new(&[1, 2, 3]); +fn when_the_element_is_not_in_the_set() { + let set = CustomSet::::new(&[1, 2, 3]); assert!(!set.contains(&4)); } #[test] #[ignore] -fn empty_sets_are_subsets_of_each_other() { - let set1: CustomSet<()> = CustomSet::new(&[]); - let set2: CustomSet<()> = CustomSet::new(&[]); - assert!(set1.is_subset(&set2)); - assert!(set2.is_subset(&set1)); +fn empty_set_is_a_subset_of_another_empty_set() { + let set_1 = CustomSet::::new(&[]); + let set_2 = CustomSet::::new(&[]); + assert!(set_1.is_subset(&set_2)); } #[test] #[ignore] -fn empty_set_is_subset_of_non_empty_set() { - let set1 = CustomSet::new(&[]); - let set2 = CustomSet::new(&[1]); - assert!(set1.is_subset(&set2)); +fn empty_set_is_a_subset_of_non_empty_set() { + let set_1 = CustomSet::::new(&[]); + let set_2 = CustomSet::::new(&[1]); + assert!(set_1.is_subset(&set_2)); } #[test] #[ignore] -fn non_empty_set_is_not_subset_of_empty_set() { - let set1 = CustomSet::new(&[1]); - let set2 = CustomSet::new(&[]); - assert!(!set1.is_subset(&set2)); +fn non_empty_set_is_not_a_subset_of_empty_set() { + let set_1 = CustomSet::::new(&[1]); + let set_2 = CustomSet::::new(&[]); + assert!(!set_1.is_subset(&set_2)); } #[test] #[ignore] -fn sets_with_same_elements_are_subsets() { - let set1 = CustomSet::new(&[1, 2, 3]); - let set2 = CustomSet::new(&[1, 2, 3]); - assert!(set1.is_subset(&set2)); - assert!(set2.is_subset(&set1)); +fn set_is_a_subset_of_set_with_exact_same_elements() { + let set_1 = CustomSet::::new(&[1, 2, 3]); + let set_2 = CustomSet::::new(&[1, 2, 3]); + assert!(set_1.is_subset(&set_2)); } #[test] #[ignore] -fn set_contained_in_other_set_is_a_subset() { - let set1 = CustomSet::new(&[1, 2, 3]); - let set2 = CustomSet::new(&[4, 1, 2, 3]); - assert!(set1.is_subset(&set2)); +fn set_is_a_subset_of_larger_set_with_same_elements() { + let set_1 = CustomSet::::new(&[1, 2, 3]); + let set_2 = CustomSet::::new(&[4, 1, 2, 3]); + assert!(set_1.is_subset(&set_2)); } #[test] #[ignore] -fn set_not_contained_in_other_set_is_not_a_subset_one() { - let set1 = CustomSet::new(&[1, 2, 3]); - let set2 = CustomSet::new(&[4, 1, 3]); - assert!(!set1.is_subset(&set2)); +fn set_is_not_a_subset_of_set_that_does_not_contain_its_elements() { + let set_1 = CustomSet::::new(&[1, 2, 3]); + let set_2 = CustomSet::::new(&[4, 1, 3]); + assert!(!set_1.is_subset(&set_2)); } #[test] #[ignore] -fn empty_sets_are_disjoint_with_each_other() { - let set1: CustomSet<()> = CustomSet::new(&[]); - let set2: CustomSet<()> = CustomSet::new(&[]); - assert!(set1.is_disjoint(&set2)); - assert!(set2.is_disjoint(&set1)); +fn the_empty_set_is_disjoint_with_itself() { + let set_1 = CustomSet::::new(&[]); + let set_2 = CustomSet::::new(&[]); + assert!(set_1.is_disjoint(&set_2)); } #[test] #[ignore] -fn empty_set_disjoint_with_non_empty_set() { - let set1 = CustomSet::new(&[]); - let set2 = CustomSet::new(&[1]); - assert!(set1.is_disjoint(&set2)); +fn empty_set_is_disjoint_with_non_empty_set() { + let set_1 = CustomSet::::new(&[]); + let set_2 = CustomSet::::new(&[1]); + assert!(set_1.is_disjoint(&set_2)); } #[test] #[ignore] -fn non_empty_set_disjoint_with_empty_set() { - let set1 = CustomSet::new(&[1]); - let set2 = CustomSet::new(&[]); - assert!(set1.is_disjoint(&set2)); +fn non_empty_set_is_disjoint_with_empty_set() { + let set_1 = CustomSet::::new(&[1]); + let set_2 = CustomSet::::new(&[]); + assert!(set_1.is_disjoint(&set_2)); } #[test] #[ignore] -fn sets_with_one_element_in_common_are_not_disjoint() { - let set1 = CustomSet::new(&[1, 2]); - let set2 = CustomSet::new(&[2, 3]); - assert!(!set1.is_disjoint(&set2)); - assert!(!set2.is_disjoint(&set1)); +fn sets_are_not_disjoint_if_they_share_an_element() { + let set_1 = CustomSet::::new(&[1, 2]); + let set_2 = CustomSet::::new(&[2, 3]); + assert!(!set_1.is_disjoint(&set_2)); } #[test] #[ignore] -fn sets_with_no_elements_in_common_are_disjoint() { - let set1 = CustomSet::new(&[1, 2]); - let set2 = CustomSet::new(&[3, 4]); - assert!(set1.is_disjoint(&set2)); - assert!(set2.is_disjoint(&set1)); +fn sets_are_disjoint_if_they_share_no_elements() { + let set_1 = CustomSet::::new(&[1, 2]); + let set_2 = CustomSet::::new(&[3, 4]); + assert!(set_1.is_disjoint(&set_2)); } #[test] #[ignore] fn empty_sets_are_equal() { - let set1: CustomSet<()> = CustomSet::new(&[]); - let set2: CustomSet<()> = CustomSet::new(&[]); - assert_eq!(set1, set2); + let set_1 = CustomSet::::new(&[]); + let set_2 = CustomSet::::new(&[]); + assert_eq!(set_1, set_2); } #[test] #[ignore] -fn empty_set_is_not_equal_to_a_non_empty_set() { - let set1 = CustomSet::new(&[]); - let set2 = CustomSet::new(&[1, 2, 3]); - assert_ne!(set1, set2); +fn empty_set_is_not_equal_to_non_empty_set() { + let set_1 = CustomSet::::new(&[]); + let set_2 = CustomSet::::new(&[1, 2, 3]); + assert_ne!(set_1, set_2); } #[test] #[ignore] -fn non_empty_set_is_not_equal_to_an_empty_set() { - let set1 = CustomSet::new(&[1, 2, 3]); - let set2 = CustomSet::new(&[]); - assert_ne!(set1, set2); +fn non_empty_set_is_not_equal_to_empty_set() { + let set_1 = CustomSet::::new(&[1, 2, 3]); + let set_2 = CustomSet::::new(&[]); + assert_ne!(set_1, set_2); } #[test] #[ignore] fn sets_with_the_same_elements_are_equal() { - let set1 = CustomSet::new(&[1, 2]); - let set2 = CustomSet::new(&[2, 1]); - assert_eq!(set1, set2); + let set_1 = CustomSet::::new(&[1, 2]); + let set_2 = CustomSet::::new(&[2, 1]); + assert_eq!(set_1, set_2); } #[test] #[ignore] fn sets_with_different_elements_are_not_equal() { - let set1 = CustomSet::new(&[1, 2, 3]); - let set2 = CustomSet::new(&[2, 1, 4]); - assert_ne!(set1, set2); + let set_1 = CustomSet::::new(&[1, 2, 3]); + let set_2 = CustomSet::::new(&[1, 2, 4]); + assert_ne!(set_1, set_2); +} + +#[test] +#[ignore] +fn set_is_not_equal_to_larger_set_with_same_elements() { + let set_1 = CustomSet::::new(&[1, 2, 3]); + let set_2 = CustomSet::::new(&[1, 2, 3, 4]); + assert_ne!(set_1, set_2); +} + +#[test] +#[ignore] +fn set_is_equal_to_a_set_constructed_from_an_array_with_duplicates() { + let set_1 = CustomSet::::new(&[1]); + let set_2 = CustomSet::::new(&[1, 1]); + assert_eq!(set_1, set_2); } #[test] #[ignore] fn add_to_empty_set() { - let mut set = CustomSet::new(&[]); + let mut set = CustomSet::::new(&[]); set.add(3); - assert_eq!(set, CustomSet::new(&[3])); + let expected = CustomSet::::new(&[3]); + assert_eq!(set, expected); } #[test] #[ignore] fn add_to_non_empty_set() { - let mut set = CustomSet::new(&[1, 2, 4]); + let mut set = CustomSet::::new(&[1, 2, 4]); set.add(3); - assert_eq!(set, CustomSet::new(&[1, 2, 3, 4])); + let expected = CustomSet::::new(&[1, 2, 3, 4]); + assert_eq!(set, expected); } #[test] #[ignore] -fn add_existing_element() { - let mut set = CustomSet::new(&[1, 2, 3]); +fn adding_an_existing_element_does_not_change_the_set() { + let mut set = CustomSet::::new(&[1, 2, 3]); set.add(3); - assert_eq!(set, CustomSet::new(&[1, 2, 3])); + let expected = CustomSet::::new(&[1, 2, 3]); + assert_eq!(set, expected); } #[test] #[ignore] -fn intersecting_empty_sets_return_empty_set() { - let set1: CustomSet<()> = CustomSet::new(&[]); - let set2: CustomSet<()> = CustomSet::new(&[]); - assert_eq!(set1.intersection(&set2), CustomSet::new(&[])); +fn intersection_of_two_empty_sets_is_an_empty_set() { + let set_1 = CustomSet::::new(&[]); + let set_2 = CustomSet::::new(&[]); + let expected = CustomSet::::new(&[]); + assert_eq!(set_1.intersection(&set_2), expected); } #[test] #[ignore] -fn intersecting_empty_set_with_non_empty_returns_empty_set() { - let set1 = CustomSet::new(&[]); - let set2 = CustomSet::new(&[3, 2, 5]); - assert_eq!(set1.intersection(&set2), CustomSet::new(&[])); +fn intersection_of_an_empty_set_and_non_empty_set_is_an_empty_set() { + let set_1 = CustomSet::::new(&[]); + let set_2 = CustomSet::::new(&[3, 2, 5]); + let expected = CustomSet::::new(&[]); + assert_eq!(set_1.intersection(&set_2), expected); } #[test] #[ignore] -fn intersecting_non_empty_set_with_empty_returns_empty_set() { - let set1 = CustomSet::new(&[1, 2, 3, 4]); - let set2 = CustomSet::new(&[]); - assert_eq!(set1.intersection(&set2), CustomSet::new(&[])); +fn intersection_of_a_non_empty_set_and_an_empty_set_is_an_empty_set() { + let set_1 = CustomSet::::new(&[1, 2, 3, 4]); + let set_2 = CustomSet::::new(&[]); + let expected = CustomSet::::new(&[]); + assert_eq!(set_1.intersection(&set_2), expected); } #[test] #[ignore] fn intersection_of_two_sets_with_no_shared_elements_is_an_empty_set() { - let set1 = CustomSet::new(&[1, 2, 3]); - let set2 = CustomSet::new(&[4, 5, 6]); - assert_eq!(set1.intersection(&set2), CustomSet::new(&[])); - assert_eq!(set2.intersection(&set1), CustomSet::new(&[])); + let set_1 = CustomSet::::new(&[1, 2, 3]); + let set_2 = CustomSet::::new(&[4, 5, 6]); + let expected = CustomSet::::new(&[]); + assert_eq!(set_1.intersection(&set_2), expected); } #[test] #[ignore] fn intersection_of_two_sets_with_shared_elements_is_a_set_of_the_shared_elements() { - let set1 = CustomSet::new(&[1, 2, 3, 4]); - let set2 = CustomSet::new(&[3, 2, 5]); - assert_eq!(set1.intersection(&set2), CustomSet::new(&[2, 3])); - assert_eq!(set2.intersection(&set1), CustomSet::new(&[2, 3])); + let set_1 = CustomSet::::new(&[1, 2, 3, 4]); + let set_2 = CustomSet::::new(&[3, 2, 5]); + let expected = CustomSet::::new(&[2, 3]); + assert_eq!(set_1.intersection(&set_2), expected); } #[test] #[ignore] -fn difference_of_two_empty_sets_is_empty_set() { - let set1: CustomSet<()> = CustomSet::new(&[]); - let set2: CustomSet<()> = CustomSet::new(&[]); - assert_eq!(set1.difference(&set2), CustomSet::new(&[])); +fn difference_of_two_empty_sets_is_an_empty_set() { + let set_1 = CustomSet::::new(&[]); + let set_2 = CustomSet::::new(&[]); + let expected = CustomSet::::new(&[]); + assert_eq!(set_1.difference(&set_2), expected); } #[test] #[ignore] -fn difference_of_an_empty_and_non_empty_set_is_an_empty_set() { - let set1 = CustomSet::new(&[]); - let set2 = CustomSet::new(&[3, 2, 5]); - assert_eq!(set1.difference(&set2), CustomSet::new(&[])); +fn difference_of_empty_set_and_non_empty_set_is_an_empty_set() { + let set_1 = CustomSet::::new(&[]); + let set_2 = CustomSet::::new(&[3, 2, 5]); + let expected = CustomSet::::new(&[]); + assert_eq!(set_1.difference(&set_2), expected); } #[test] #[ignore] -fn difference_of_a_non_empty_set_and_empty_set_is_the_non_empty_set() { - let set1 = CustomSet::new(&[1, 2, 3, 4]); - let set2 = CustomSet::new(&[]); - assert_eq!(set1.difference(&set2), CustomSet::new(&[1, 2, 3, 4])); +fn difference_of_a_non_empty_set_and_an_empty_set_is_the_non_empty_set() { + let set_1 = CustomSet::::new(&[1, 2, 3, 4]); + let set_2 = CustomSet::::new(&[]); + let expected = CustomSet::::new(&[1, 2, 3, 4]); + assert_eq!(set_1.difference(&set_2), expected); } #[test] #[ignore] -fn difference_of_two_non_empty_sets_is_elements_only_in_first_set_one() { - let set1 = CustomSet::new(&[3, 2, 1]); - let set2 = CustomSet::new(&[2, 4]); - assert_eq!(set1.difference(&set2), CustomSet::new(&[1, 3])); +fn difference_of_two_non_empty_sets_is_a_set_of_elements_that_are_only_in_the_first_set() { + let set_1 = CustomSet::::new(&[3, 2, 1]); + let set_2 = CustomSet::::new(&[2, 4]); + let expected = CustomSet::::new(&[1, 3]); + assert_eq!(set_1.difference(&set_2), expected); } #[test] #[ignore] -fn union_of_two_empty_sets_is_empty_set() { - let set1: CustomSet<()> = CustomSet::new(&[]); - let set2: CustomSet<()> = CustomSet::new(&[]); - assert_eq!(set1.union(&set2), CustomSet::new(&[])); +fn union_of_empty_sets_is_an_empty_set() { + let set_1 = CustomSet::::new(&[]); + let set_2 = CustomSet::::new(&[]); + let expected = CustomSet::::new(&[]); + assert_eq!(set_1.union(&set_2), expected); } #[test] #[ignore] -fn union_of_empty_set_and_non_empty_set_is_all_elements() { - let set1 = CustomSet::new(&[]); - let set2 = CustomSet::new(&[2]); - assert_eq!(set1.union(&set2), CustomSet::new(&[2])); +fn union_of_an_empty_set_and_non_empty_set_is_the_non_empty_set() { + let set_1 = CustomSet::::new(&[]); + let set_2 = CustomSet::::new(&[2]); + let expected = CustomSet::::new(&[2]); + assert_eq!(set_1.union(&set_2), expected); } #[test] #[ignore] -fn union_of_non_empty_set_and_empty_set_is_the_non_empty_set() { - let set1 = CustomSet::new(&[1, 3]); - let set2 = CustomSet::new(&[]); - assert_eq!(set1.union(&set2), CustomSet::new(&[1, 3])); +fn union_of_a_non_empty_set_and_empty_set_is_the_non_empty_set() { + let set_1 = CustomSet::::new(&[1, 3]); + let set_2 = CustomSet::::new(&[]); + let expected = CustomSet::::new(&[1, 3]); + assert_eq!(set_1.union(&set_2), expected); } #[test] #[ignore] fn union_of_non_empty_sets_contains_all_unique_elements() { - let set1 = CustomSet::new(&[1, 3]); - let set2 = CustomSet::new(&[2, 3]); - assert_eq!(set1.union(&set2), CustomSet::new(&[3, 2, 1])); + let set_1 = CustomSet::::new(&[1, 3]); + let set_2 = CustomSet::::new(&[2, 3]); + let expected = CustomSet::::new(&[3, 2, 1]); + assert_eq!(set_1.union(&set_2), expected); } diff --git a/problem-specifications b/problem-specifications index 03220a9ce..988eafc34 160000 --- a/problem-specifications +++ b/problem-specifications @@ -1 +1 @@ -Subproject commit 03220a9ceb507f46cb70e317aaf1620237435144 +Subproject commit 988eafc342c42defd78d1fc87b80d8586bc01ac4 From a402d2dd98eb9658f3dfe05fa97c29aa763bc044 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Sep 2023 12:54:51 +0200 Subject: [PATCH 175/436] Bump actions/checkout from 4.0.0 to 4.1.0 (#1752) Bumps [actions/checkout](https://github.com/actions/checkout) from 4.0.0 to 4.1.0. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/3df4ab11eba7bda6032a0b82a6bb43b11571feac...8ade135a41bc03ea155e62e844d188df1ea18608) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/tests.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 143bd9455..9f6c705cb 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,7 +18,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - name: Fetch configlet run: ./bin/fetch-configlet @@ -32,7 +32,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - name: Run markdown lint run: ./bin/lint_markdown.sh @@ -43,7 +43,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - name: Run shellcheck uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # v2.0.0 @@ -58,7 +58,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - name: Fetch origin/main run: git fetch --depth=1 origin main @@ -84,7 +84,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - name: Setup toolchain uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 @@ -100,7 +100,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - name: Setup toolchain uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 @@ -127,7 +127,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - name: Setup toolchain uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 @@ -152,7 +152,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - name: Setup nightly toolchain uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 From ea9c04eee34e3b1727e85cdfe39fe2958aab9ca9 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Wed, 27 Sep 2023 09:56:17 +0200 Subject: [PATCH 176/436] Convert backtick (`) admonition fences to tildes (~). (#1753) --- .../practice/bob/.approaches/answer-array/content.md | 4 ++-- .../practice/bob/.approaches/if-statements/content.md | 4 ++-- .../practice/bob/.approaches/match-on-tuple/content.md | 8 ++++---- exercises/practice/gigasecond/.docs/introduction.md | 4 ++-- .../practice/grains/.approaches/bit-shifting/content.md | 4 ++-- exercises/practice/grains/.approaches/pow/content.md | 4 ++-- .../leap/.approaches/date-addition-chrono/content.md | 4 ++-- .../leap/.approaches/date-addition-time/content.md | 4 ++-- .../leap/.approaches/ternary-expression/content.md | 4 ++-- exercises/practice/pangram/.docs/introduction.md | 4 ++-- exercises/practice/sieve/.docs/instructions.md | 4 ++-- 11 files changed, 24 insertions(+), 24 deletions(-) diff --git a/exercises/practice/bob/.approaches/answer-array/content.md b/exercises/practice/bob/.approaches/answer-array/content.md index 20eb68c24..44b877c39 100644 --- a/exercises/practice/bob/.approaches/answer-array/content.md +++ b/exercises/practice/bob/.approaches/answer-array/content.md @@ -61,11 +61,11 @@ If the input is a yell, then `is_yelling` is given the value `2`, otherwise it i The final expression returns the value in the `ANSWERS` array at the index of the combined values for `is_questioning` and `is_yelling`. -```exercism/note +~~~~exercism/note Note that the final line is just `ANSWERS[is_questioning + is_yelling]` This is because the [last expression can be returned](https://doc.rust-lang.org/rust-by-example/fn.html) from a function without using `return` and a semicolon. -``` +~~~~ | is_yelling | is_questioning | Index | Answer | | ---------- | -------------- | --------- | ------------------------------------- | diff --git a/exercises/practice/bob/.approaches/if-statements/content.md b/exercises/practice/bob/.approaches/if-statements/content.md index 38b52a71b..ee5eff8d4 100644 --- a/exercises/practice/bob/.approaches/if-statements/content.md +++ b/exercises/practice/bob/.approaches/if-statements/content.md @@ -48,11 +48,11 @@ The uppercasing is done by using the `str` method [`to_uppercase`][to-uppercase] - If the input is a question, then the function returns the response for that. - Finally, if the function has not returned by the end, the response for neither a yell nor a question is returned. -```exercism/note +~~~~exercism/note Note that the final line is just `"Whatever."` This is because the [last expression can be returned](https://doc.rust-lang.org/rust-by-example/fn.html) from a function without using `return` and a semicolon. -``` +~~~~ [str]: https://doc.rust-lang.org/std/primitive.str.html [trim-end]: https://doc.rust-lang.org/std/primitive.str.html#method.trim_end diff --git a/exercises/practice/bob/.approaches/match-on-tuple/content.md b/exercises/practice/bob/.approaches/match-on-tuple/content.md index 6a719d21f..94d3fcca1 100644 --- a/exercises/practice/bob/.approaches/match-on-tuple/content.md +++ b/exercises/practice/bob/.approaches/match-on-tuple/content.md @@ -43,12 +43,12 @@ Since those values are booleans, each arm of the `match` tests a pattern of bool - If both the yell value in the tuple is `true` and the question part of the tuple is `true`, then the response is returned for a yelled question. -```exercism/note +~~~~exercism/note Note that each arm of the `match` is a single-line expression, so `return` is not needed in the responses returned by the `match` arms. And, since the `match` is the last expression in the function, `return` is not used before `match`. The [last expression can be returned](https://doc.rust-lang.org/rust-by-example/fn.html) from a function without using `return` and a semicolon. -``` +~~~~ - If the tuple is not `(true, true)`, then the next arm of the `match` tests if the yell value in the tuple is `true`. It uses the [wildcard pattern][wildcard] of `_` to disregard the value for the question part of the tuple. @@ -59,14 +59,14 @@ If the pattern matches, in other words, if the yell value in the tuple is `true` This is similar to `default` used in `switch` statements in other languages. It returns "Whatever." no matter what the values in the tuple are. -```exercism/note +~~~~exercism/note Note that a `match` in Rust must be exhaustive. This means it must match all conceivable patterns of the value being tested or the code will not compile. For a boolean value, you can have one arm to match `true` and another to match `false`, and the compiler will know that the `match` has checked all conceivable patterns for a boolean. For a value with many possible patterns, such as a `u32`, you can have each arm match whatever patterns you care about, such as `1` and `2`, and then have one final arm using the wildcard pattern to match on everything else. -``` +~~~~ [match]: https://doc.rust-lang.org/rust-by-example/flow_control/match.html [str]: https://doc.rust-lang.org/std/primitive.str.html diff --git a/exercises/practice/gigasecond/.docs/introduction.md b/exercises/practice/gigasecond/.docs/introduction.md index 74afaa994..18a3dc200 100644 --- a/exercises/practice/gigasecond/.docs/introduction.md +++ b/exercises/practice/gigasecond/.docs/introduction.md @@ -13,7 +13,7 @@ Then we can use metric system prefixes for writing large numbers of seconds in m - Perhaps you and your family would travel to somewhere exotic for two megaseconds (that's two million seconds). - And if you and your spouse were married for _a thousand million_ seconds, you would celebrate your one gigasecond anniversary. -```exercism/note +~~~~exercism/note If we ever colonize Mars or some other planet, measuring time is going to get even messier. If someone says "year" do they mean a year on Earth or a year on Mars? @@ -21,4 +21,4 @@ The idea for this exercise came from the science fiction novel ["A Deepness in t In it the author uses the metric system as the basis for time measurements. [vinge-novel]: https://www.tor.com/2017/08/03/science-fiction-with-something-for-everyone-a-deepness-in-the-sky-by-vernor-vinge/ -``` +~~~~ diff --git a/exercises/practice/grains/.approaches/bit-shifting/content.md b/exercises/practice/grains/.approaches/bit-shifting/content.md index a4d37f537..c0be0804c 100644 --- a/exercises/practice/grains/.approaches/bit-shifting/content.md +++ b/exercises/practice/grains/.approaches/bit-shifting/content.md @@ -38,12 +38,12 @@ However, we can't do this with a `u64` which has only `64` bits, so we need to u To go back to our two-square example, if we can grow to three squares, then we can shift `1_u128` two positions to the left for binary `100`, which is decimal `4`. -```exercism/note +~~~~exercism/note Note that the type of a binding can be defined by appending it to the binding name. It can optionally be appended using one or more `_` separators between the value and the type. More info can be found in the [Literals](https://doc.rust-lang.org/rust-by-example/types/literals.html) section of [Rust by Example](https://doc.rust-lang.org/rust-by-example/index.html) -``` +~~~~ By subtracting `1` we get `3`, which is the total amount of grains on the two squares. | Square | Binary Value | Decimal Value | diff --git a/exercises/practice/grains/.approaches/pow/content.md b/exercises/practice/grains/.approaches/pow/content.md index f3671fe31..14abea542 100644 --- a/exercises/practice/grains/.approaches/pow/content.md +++ b/exercises/practice/grains/.approaches/pow/content.md @@ -19,12 +19,12 @@ Rust does not have an exponential operator, but uses the [`pow`][pow-u64] method `pow` is nicely suited to the problem, since we start with one grain and keep doubling the number of grains on each successive square. `1` grain is `2u64.pow(0)`, `2` grains is `2u64.pow(1)`, `4` is `2u64.pow(2)`, and so on. -```exercism/note +~~~~exercism/note Note that the type of a binding can be defined by appending it to the binding name. It can optionally be appended using one or more `_` separators between the value and the type. More info can be found in the [Literals](https://doc.rust-lang.org/rust-by-example/types/literals.html) section of [Rust by Example](https://doc.rust-lang.org/rust-by-example/index.html) -``` +~~~~ So, to get the right exponent, we subtract `1` from the square number `s`. diff --git a/exercises/practice/leap/.approaches/date-addition-chrono/content.md b/exercises/practice/leap/.approaches/date-addition-chrono/content.md index 63081f66b..04a1be05d 100644 --- a/exercises/practice/leap/.approaches/date-addition-chrono/content.md +++ b/exercises/practice/leap/.approaches/date-addition-chrono/content.md @@ -8,9 +8,9 @@ pub fn is_leap_year(year: u64) -> bool { } ``` -```exercism/caution +~~~~exercism/caution This approach may be considered a "cheat" for this exercise. -``` +~~~~ By adding a day to February 28th for the year, you can see if the new day is the 29th or the 1st. If it is the 29th, then the year is a leap year. This is done by using the [`Duration::days(1)`][day-duration] method to add a day to a [`chrono::Date`][chrono-date] `struct` and comparing it to `29` with the [`day`][day-method] method. diff --git a/exercises/practice/leap/.approaches/date-addition-time/content.md b/exercises/practice/leap/.approaches/date-addition-time/content.md index 779a2931d..02fe8058f 100644 --- a/exercises/practice/leap/.approaches/date-addition-time/content.md +++ b/exercises/practice/leap/.approaches/date-addition-time/content.md @@ -9,9 +9,9 @@ pub fn is_leap_year(year: u64) -> bool { } ``` -```exercism/caution +~~~~exercism/caution This approach may be considered a "cheat" for this exercise. -``` +~~~~ By adding a day to February 28th for the year, you can see if the new day is the 29th or the 1st. If it is the 29th, then the year is a leap year. This is done by adding a [`Duration::DAY`][day-duration] to a [`time::Date`][time-date] `struct` and comparing it to `29` with the [`day`][day-method] method. diff --git a/exercises/practice/leap/.approaches/ternary-expression/content.md b/exercises/practice/leap/.approaches/ternary-expression/content.md index 5e4729f62..100d58fd0 100644 --- a/exercises/practice/leap/.approaches/ternary-expression/content.md +++ b/exercises/practice/leap/.approaches/ternary-expression/content.md @@ -33,11 +33,11 @@ if year % 100 == 0 { `year` is tested to be evenly divislbe by `100` by using the [remainder operator][remainder-operator]. If so, it returns if `year` is evenly divisible by `400`, as the last expression evaluated in the function. -```exercism/note +~~~~exercism/note Note that the line is just `year % 400 == 0`. This is because the [last expression can be returned](https://doc.rust-lang.org/rust-by-example/fn.html) from a function without using `return` and a semicolon. -``` +~~~~ If `year` is _not_ evenly divisible by `100`, then `else` is the last expression evaluated in the function diff --git a/exercises/practice/pangram/.docs/introduction.md b/exercises/practice/pangram/.docs/introduction.md index d38fa341d..32b6f1fc3 100644 --- a/exercises/practice/pangram/.docs/introduction.md +++ b/exercises/practice/pangram/.docs/introduction.md @@ -7,10 +7,10 @@ To give a comprehensive sense of the font, the random sentences should use **all They're running a competition to get suggestions for sentences that they can use. You're in charge of checking the submissions to see if they are valid. -```exercism/note +~~~~exercism/note Pangram comes from Greek, παν γράμμα, pan gramma, which means "every letter". The best known English pangram is: > The quick brown fox jumps over the lazy dog. -``` +~~~~ diff --git a/exercises/practice/sieve/.docs/instructions.md b/exercises/practice/sieve/.docs/instructions.md index ec14620ce..3adf1d551 100644 --- a/exercises/practice/sieve/.docs/instructions.md +++ b/exercises/practice/sieve/.docs/instructions.md @@ -18,11 +18,11 @@ Then you repeat the following steps: You keep repeating these steps until you've gone through every number in your list. At the end, all the unmarked numbers are prime. -```exercism/note +~~~~exercism/note [Wikipedia's Sieve of Eratosthenes article][eratosthenes] has a useful graphic that explains the algorithm. The tests don't check that you've implemented the algorithm, only that you've come up with the correct list of primes. A good first test is to check that you do not use division or remainder operations. [eratosthenes]: https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes -``` +~~~~ From 5ee312deca68291989b8ef0ee10ee9abc311e3ba Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Wed, 27 Sep 2023 10:05:46 +0200 Subject: [PATCH 177/436] acronym: Reintroduce camel case test (#1750) --- .../practice/acronym/.docs/instructions.md | 10 +++++----- .../practice/acronym/.meta/test_template.tera | 17 +++++++++++++++++ exercises/practice/acronym/tests/acronym.rs | 6 ++++++ 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/exercises/practice/acronym/.docs/instructions.md b/exercises/practice/acronym/.docs/instructions.md index c62fc3e85..133bd2cbb 100644 --- a/exercises/practice/acronym/.docs/instructions.md +++ b/exercises/practice/acronym/.docs/instructions.md @@ -10,8 +10,8 @@ Punctuation is handled as follows: hyphens are word separators (like whitespace) For example: -|Input|Output| -|-|-| -|As Soon As Possible|ASAP| -|Liquid-crystal display|LCD| -|Thank George It's Friday!|TGIF| +| Input | Output | +| ------------------------- | ------ | +| As Soon As Possible | ASAP | +| Liquid-crystal display | LCD | +| Thank George It's Friday! | TGIF | diff --git a/exercises/practice/acronym/.meta/test_template.tera b/exercises/practice/acronym/.meta/test_template.tera index c8de609e1..24212ac48 100644 --- a/exercises/practice/acronym/.meta/test_template.tera +++ b/exercises/practice/acronym/.meta/test_template.tera @@ -10,3 +10,20 @@ fn {{ test.description | slugify | replace(from="-", to="_") }}() { assert_eq!(output, expected); } {% endfor -%} + +{# + This test makes the exercise noticeably harder. + + Upstreaming this test is not a good option, + as most other languages would exclude it due to the added difficulty. + Removing the test from the Rust track is also not a good option, + because it creates confusion regarding existing community solutions. + + While deviations from problem-specifications should generally be avoided, + it seems like the best choice to stick with it in this case. +#} +#[test] +#[ignore] +fn camelcase() { + assert_eq!(acronym::abbreviate("HyperText Markup Language"), "HTML"); +} diff --git a/exercises/practice/acronym/tests/acronym.rs b/exercises/practice/acronym/tests/acronym.rs index e90d943a1..971b76887 100644 --- a/exercises/practice/acronym/tests/acronym.rs +++ b/exercises/practice/acronym/tests/acronym.rs @@ -77,3 +77,9 @@ fn underscore_emphasis() { let expected = "TRNT"; assert_eq!(output, expected); } + +#[test] +#[ignore] +fn camelcase() { + assert_eq!(acronym::abbreviate("HyperText Markup Language"), "HTML"); +} From 6e9e9cc57d8f861262ae0c848c89ffe916264aa3 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 17 Oct 2023 17:26:15 +0200 Subject: [PATCH 178/436] Fix lifetime annotation in anagram stub (#1755) [no important files changed] --- exercises/practice/anagram/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/anagram/src/lib.rs b/exercises/practice/anagram/src/lib.rs index f029d0449..5b021ac6d 100644 --- a/exercises/practice/anagram/src/lib.rs +++ b/exercises/practice/anagram/src/lib.rs @@ -1,5 +1,5 @@ use std::collections::HashSet; -pub fn anagrams_for<'a>(word: &str, possible_anagrams: &[&str]) -> HashSet<&'a str> { +pub fn anagrams_for<'a>(word: &str, possible_anagrams: &[&'a str]) -> HashSet<&'a str> { todo!("For the '{word}' word find anagrams among the following words: {possible_anagrams:?}"); } From 8a50d892c6db32b41e80a2615aaacd1bf6130f0b Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 17 Oct 2023 22:54:44 +0200 Subject: [PATCH 179/436] Don't run beta clippy in CI (#1757) --- .github/workflows/tests.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9f6c705cb..78fa18447 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -121,10 +121,6 @@ jobs: name: Clippy runs-on: ubuntu-latest - strategy: - matrix: - rust: ["stable", "beta"] - steps: - name: Checkout code uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 @@ -132,7 +128,7 @@ jobs: - name: Setup toolchain uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 with: - toolchain: ${{ matrix.rust }} + toolchain: stable components: clippy - name: Clippy tests From 025710e8ee0e1ed7b4d71bf8626a4e8af2992361 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Oct 2023 10:01:44 +0200 Subject: [PATCH 180/436] Bump actions/checkout from 4.1.0 to 4.1.1 (#1758) Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.0 to 4.1.1. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/8ade135a41bc03ea155e62e844d188df1ea18608...b4ffde65f46336ab88eb53be808477a3936bae11) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/tests.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 78fa18447..18004b75a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,7 +18,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Fetch configlet run: ./bin/fetch-configlet @@ -32,7 +32,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Run markdown lint run: ./bin/lint_markdown.sh @@ -43,7 +43,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Run shellcheck uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # v2.0.0 @@ -58,7 +58,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Fetch origin/main run: git fetch --depth=1 origin main @@ -84,7 +84,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Setup toolchain uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 @@ -100,7 +100,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Setup toolchain uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 @@ -123,7 +123,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Setup toolchain uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 @@ -148,7 +148,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Setup nightly toolchain uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 From 0f927c9ce212689422f22b0fc6e910c2b08b66ee Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Tue, 14 Nov 2023 12:26:22 +0100 Subject: [PATCH 181/436] Pin GitHub Actions runners to a specific version (#1763) [skip ci] --- .github/workflows/tests.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 18004b75a..cd9b346b0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,7 +14,7 @@ on: jobs: configlet: name: configlet lint - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout code @@ -28,7 +28,7 @@ jobs: markdownlint: name: markdown lint - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout code @@ -40,7 +40,7 @@ jobs: # stolen from https://raw.githubusercontent.com/exercism/github-actions/main/.github/workflows/shellcheck.yml shellcheck: name: Run shellcheck on scripts - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 @@ -50,7 +50,7 @@ jobs: compilation: name: Check compilation - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 strategy: matrix: @@ -80,7 +80,7 @@ jobs: tests: name: Run repository tests - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout code @@ -96,7 +96,7 @@ jobs: rustformat: name: Check Rust Formatting - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout code @@ -119,7 +119,7 @@ jobs: clippy: name: Clippy - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout code @@ -143,7 +143,7 @@ jobs: nightly-compilation: name: Check exercises on nightly (benchmark enabled) - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 continue-on-error: true # It's okay if the nightly job fails steps: From d1112137898045d156d8fd2e3b319284ffd7da3e Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 14 Nov 2023 12:40:36 +0100 Subject: [PATCH 182/436] Pipe generated tests through rustfmt (#1766) This improves the workflow of generating tests for exercises and synchronizing them with problem-specifications. After generating the tests, they would have to be formatted manually. This is now done automatically when generating the tests. --- rust-tooling/src/exercise_generation.rs | 33 +++++++++++++++++++++---- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/rust-tooling/src/exercise_generation.rs b/rust-tooling/src/exercise_generation.rs index 9e403d2e0..9c2e91a57 100644 --- a/rust-tooling/src/exercise_generation.rs +++ b/rust-tooling/src/exercise_generation.rs @@ -1,4 +1,8 @@ -use std::collections::HashMap; +use std::{ + collections::HashMap, + io::Write, + process::{Command, Stdio}, +}; use tera::Context; @@ -110,9 +114,28 @@ fn generate_tests(slug: &str, fn_names: Vec) -> String { context.insert("fn_names", &fn_names); context.insert("cases", &single_cases); - template - .render("test_template.tera", &context) + let rendered = template.render("test_template.tera", &context).unwrap(); + let rendered = rendered.trim_start(); + + let mut child = Command::new("rustfmt") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .expect("failed to spawn process"); + + child + .stdin + .as_mut() .unwrap() - .trim_start() - .into() + .write_all(rendered.as_bytes()) + .unwrap(); + let rustfmt_out = child.wait_with_output().unwrap(); + + if rustfmt_out.status.success() { + String::from_utf8(rustfmt_out.stdout).unwrap() + } else { + // if rustfmt fails, still return the unformatted + // content to be written to the file + rendered.into() + } } From 22c94f685add808cd29106800a2c166187baf004 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 14 Nov 2023 12:40:59 +0100 Subject: [PATCH 183/436] Test generator: strip generics from function names (#1768) --- rust-tooling/src/bin/generate_exercise.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/rust-tooling/src/bin/generate_exercise.rs b/rust-tooling/src/bin/generate_exercise.rs index 96f85ead4..b22fa1562 100644 --- a/rust-tooling/src/bin/generate_exercise.rs +++ b/rust-tooling/src/bin/generate_exercise.rs @@ -214,6 +214,14 @@ fn read_fn_names_from_lib_rs(slug: &str) -> Vec { lib_rs .split("fn ") .skip(1) - .map(|f| f.split_once('(').unwrap().0.to_string()) + .map(|f| { + let tmp = f.split_once('(').unwrap().0; + // strip generics + if let Some((res, _)) = tmp.split_once('<') { + res.to_string() + } else { + tmp.to_string() + } + }) .collect() } From cbd1748f4e364882e75bcde2f53352cc03f72591 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 14 Nov 2023 12:41:22 +0100 Subject: [PATCH 184/436] Enable generating track-specific test cases (#1774) --- docs/CONTRIBUTING.md | 3 +++ rust-tooling/src/exercise_generation.rs | 8 ++++++-- rust-tooling/src/problem_spec.rs | 19 +++++++++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 7d2e57e05..70b4fbb6d 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -41,6 +41,9 @@ The tests are generated using the template engine [Tera]. The input of the template is the canonical data from [`problem-specifications`]. if you want to exclude certain tests from being generated, you have to set `include = false` in `.meta/tests.toml`. +Please include a comment about the reason for excluding it. +If you want to add additional track-specific tests, you can do so in a file `.meta/additional-tests.json`. +Only do this with a good reason for not upstreaming these tests. Find some tips about writing tera templates [here](#tera-templates). diff --git a/rust-tooling/src/exercise_generation.rs b/rust-tooling/src/exercise_generation.rs index 9c2e91a57..3421abbf7 100644 --- a/rust-tooling/src/exercise_generation.rs +++ b/rust-tooling/src/exercise_generation.rs @@ -8,7 +8,7 @@ use tera::Context; use crate::{ exercise_config::{get_excluded_tests, get_test_emplate}, - problem_spec::{get_canonical_data, SingleTestCase, TestCase}, + problem_spec::{get_additional_test_cases, get_canonical_data, SingleTestCase, TestCase}, }; pub struct GeneratedExercise { @@ -95,7 +95,11 @@ fn to_hex(value: &tera::Value, _args: &HashMap) -> tera::Re } fn generate_tests(slug: &str, fn_names: Vec) -> String { - let cases = get_canonical_data(slug).cases; + let cases = { + let mut cases = get_canonical_data(slug).cases; + cases.extend_from_slice(&get_additional_test_cases(slug)); + cases + }; let excluded_tests = get_excluded_tests(slug); let mut template = get_test_emplate(slug).unwrap(); if template.get_template_names().next().is_none() { diff --git a/rust-tooling/src/problem_spec.rs b/rust-tooling/src/problem_spec.rs index b32bef808..56a5c4fe7 100644 --- a/rust-tooling/src/problem_spec.rs +++ b/rust-tooling/src/problem_spec.rs @@ -51,6 +51,25 @@ pub fn get_canonical_data(slug: &str) -> CanonicalData { }) } +/// The Rust track may define additional test cases in +/// `.meta/additional-tests.json`, which are not appropriate +/// to upstream for all other languages to implement. +pub fn get_additional_test_cases(slug: &str) -> Vec { + let path = std::path::PathBuf::from("exercises/practice") + .join(slug) + .join(".meta/additional-tests.json"); + if !path.exists() { + return Vec::new(); + } + let contents = std::fs::read_to_string(&path).unwrap(); + serde_json::from_str(contents.as_str()).unwrap_or_else(|e| { + panic!( + "should deserialize additional tests for {}: {e}", + path.display() + ) + }) +} + #[test] fn deserialize_canonical_data() { crate::fs_utils::cd_into_repo_root(); From ec0862b34b3fd187544439100688e56aa0f3d614 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 14 Nov 2023 15:01:51 +0100 Subject: [PATCH 185/436] Sync tournament with problem-specifications (#1764) --- .../practice/tournament/.docs/instructions.md | 13 +- .../tournament/.meta/test_template.tera | 13 + .../practice/tournament/.meta/tests.toml | 16 +- .../practice/tournament/tests/tournament.rs | 263 +++++++++++------- 4 files changed, 197 insertions(+), 108 deletions(-) create mode 100644 exercises/practice/tournament/.meta/test_template.tera diff --git a/exercises/practice/tournament/.docs/instructions.md b/exercises/practice/tournament/.docs/instructions.md index 8831dd195..e5ca23738 100644 --- a/exercises/practice/tournament/.docs/instructions.md +++ b/exercises/practice/tournament/.docs/instructions.md @@ -2,8 +2,7 @@ Tally the results of a small football competition. -Based on an input file containing which team played against which and what the -outcome was, create a file with a table like this: +Based on an input file containing which team played against which and what the outcome was, create a file with a table like this: ```text Team | MP | W | D | L | P @@ -21,9 +20,12 @@ What do those abbreviations mean? - L: Matches Lost - P: Points -A win earns a team 3 points. A draw earns 1. A loss earns 0. +A win earns a team 3 points. +A draw earns 1. +A loss earns 0. -The outcome should be ordered by points, descending. In case of a tie, teams are ordered alphabetically. +The outcome is ordered by points, descending. +In case of a tie, teams are ordered alphabetically. ## Input @@ -38,7 +40,8 @@ Blithering Badgers;Devastating Donkeys;loss Allegoric Alaskans;Courageous Californians;win ``` -The result of the match refers to the first team listed. So this line: +The result of the match refers to the first team listed. +So this line: ```text Allegoric Alaskans;Blithering Badgers;win diff --git a/exercises/practice/tournament/.meta/test_template.tera b/exercises/practice/tournament/.meta/test_template.tera new file mode 100644 index 000000000..e469e04d3 --- /dev/null +++ b/exercises/practice/tournament/.meta/test_template.tera @@ -0,0 +1,13 @@ +{% for test in cases %} +#[test] +{% if loop.index != 1 -%} +#[ignore] +{% endif -%} +fn {{ test.description | slugify | replace(from="-", to="_") }}() { + let input: &[&str] = &{{ test.input.rows | json_encode() }}; + let input = input.join("\n"); + let output = {{ crate_name }}::{{ fn_names[0] }}(&input); + let expected = {{ test.expected | json_encode() }}.join("\n"); + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/tournament/.meta/tests.toml b/exercises/practice/tournament/.meta/tests.toml index 598677086..0e33c87fc 100644 --- a/exercises/practice/tournament/.meta/tests.toml +++ b/exercises/practice/tournament/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [67e9fab1-07c1-49cf-9159-bc8671cc7c9c] description = "just the header if no input" @@ -34,3 +41,6 @@ description = "incomplete competition (not all pairs have played)" [3aa0386f-150b-4f99-90bb-5195e7b7d3b8] description = "ties broken alphabetically" + +[f9e20931-8a65-442a-81f6-503c0205b17a] +description = "ensure points sorted numerically" diff --git a/exercises/practice/tournament/tests/tournament.rs b/exercises/practice/tournament/tests/tournament.rs index 398e91807..300a5cf28 100644 --- a/exercises/practice/tournament/tests/tournament.rs +++ b/exercises/practice/tournament/tests/tournament.rs @@ -1,152 +1,215 @@ #[test] fn just_the_header_if_no_input() { - let input = ""; - let expected = "Team | MP | W | D | L | P"; - - assert_eq!(tournament::tally(input), expected); + let input: &[&str] = &[]; + let input = input.join("\n"); + let output = tournament::tally(&input); + let expected = ["Team | MP | W | D | L | P"].join("\n"); + assert_eq!(output, expected); } #[test] #[ignore] fn a_win_is_three_points_a_loss_is_zero_points() { - let input = "Allegoric Alaskans;Blithering Badgers;win"; - let expected = "".to_string() - + "Team | MP | W | D | L | P\n" - + "Allegoric Alaskans | 1 | 1 | 0 | 0 | 3\n" - + "Blithering Badgers | 1 | 0 | 0 | 1 | 0"; - - assert_eq!(tournament::tally(input), expected); + let input: &[&str] = &["Allegoric Alaskans;Blithering Badgers;win"]; + let input = input.join("\n"); + let output = tournament::tally(&input); + let expected = [ + "Team | MP | W | D | L | P", + "Allegoric Alaskans | 1 | 1 | 0 | 0 | 3", + "Blithering Badgers | 1 | 0 | 0 | 1 | 0", + ] + .join("\n"); + assert_eq!(output, expected); } #[test] #[ignore] fn a_win_can_also_be_expressed_as_a_loss() { - let input = "Blithering Badgers;Allegoric Alaskans;loss"; - let expected = "".to_string() - + "Team | MP | W | D | L | P\n" - + "Allegoric Alaskans | 1 | 1 | 0 | 0 | 3\n" - + "Blithering Badgers | 1 | 0 | 0 | 1 | 0"; - - assert_eq!(tournament::tally(input), expected); + let input: &[&str] = &["Blithering Badgers;Allegoric Alaskans;loss"]; + let input = input.join("\n"); + let output = tournament::tally(&input); + let expected = [ + "Team | MP | W | D | L | P", + "Allegoric Alaskans | 1 | 1 | 0 | 0 | 3", + "Blithering Badgers | 1 | 0 | 0 | 1 | 0", + ] + .join("\n"); + assert_eq!(output, expected); } #[test] #[ignore] fn a_different_team_can_win() { - let input = "Blithering Badgers;Allegoric Alaskans;win"; - let expected = "".to_string() - + "Team | MP | W | D | L | P\n" - + "Blithering Badgers | 1 | 1 | 0 | 0 | 3\n" - + "Allegoric Alaskans | 1 | 0 | 0 | 1 | 0"; - - assert_eq!(tournament::tally(input), expected); + let input: &[&str] = &["Blithering Badgers;Allegoric Alaskans;win"]; + let input = input.join("\n"); + let output = tournament::tally(&input); + let expected = [ + "Team | MP | W | D | L | P", + "Blithering Badgers | 1 | 1 | 0 | 0 | 3", + "Allegoric Alaskans | 1 | 0 | 0 | 1 | 0", + ] + .join("\n"); + assert_eq!(output, expected); } #[test] #[ignore] -fn there_can_be_more_than_one_match() { - let input = "Allegoric Alaskans;Blithering Badgers;win\n".to_string() - + "Allegoric Alaskans;Blithering Badgers;win"; - let expected = "".to_string() - + "Team | MP | W | D | L | P\n" - + "Allegoric Alaskans | 2 | 2 | 0 | 0 | 6\n" - + "Blithering Badgers | 2 | 0 | 0 | 2 | 0"; - - assert_eq!(tournament::tally(&input), expected); +fn a_draw_is_one_point_each() { + let input: &[&str] = &["Allegoric Alaskans;Blithering Badgers;draw"]; + let input = input.join("\n"); + let output = tournament::tally(&input); + let expected = [ + "Team | MP | W | D | L | P", + "Allegoric Alaskans | 1 | 0 | 1 | 0 | 1", + "Blithering Badgers | 1 | 0 | 1 | 0 | 1", + ] + .join("\n"); + assert_eq!(output, expected); } #[test] #[ignore] -fn a_draw_is_one_point_each() { - let input = "Allegoric Alaskans;Blithering Badgers;draw\n".to_string() - + "Allegoric Alaskans;Blithering Badgers;win"; - let expected = "".to_string() - + "Team | MP | W | D | L | P\n" - + "Allegoric Alaskans | 2 | 1 | 1 | 0 | 4\n" - + "Blithering Badgers | 2 | 0 | 1 | 1 | 1"; - - assert_eq!(tournament::tally(&input), expected); +fn there_can_be_more_than_one_match() { + let input: &[&str] = &[ + "Allegoric Alaskans;Blithering Badgers;win", + "Allegoric Alaskans;Blithering Badgers;win", + ]; + let input = input.join("\n"); + let output = tournament::tally(&input); + let expected = [ + "Team | MP | W | D | L | P", + "Allegoric Alaskans | 2 | 2 | 0 | 0 | 6", + "Blithering Badgers | 2 | 0 | 0 | 2 | 0", + ] + .join("\n"); + assert_eq!(output, expected); } #[test] #[ignore] fn there_can_be_more_than_one_winner() { - let input = "Allegoric Alaskans;Blithering Badgers;loss\n".to_string() - + "Allegoric Alaskans;Blithering Badgers;win"; - let expected = "".to_string() - + "Team | MP | W | D | L | P\n" - + "Allegoric Alaskans | 2 | 1 | 0 | 1 | 3\n" - + "Blithering Badgers | 2 | 1 | 0 | 1 | 3"; - - assert_eq!(tournament::tally(&input), expected); + let input: &[&str] = &[ + "Allegoric Alaskans;Blithering Badgers;loss", + "Allegoric Alaskans;Blithering Badgers;win", + ]; + let input = input.join("\n"); + let output = tournament::tally(&input); + let expected = [ + "Team | MP | W | D | L | P", + "Allegoric Alaskans | 2 | 1 | 0 | 1 | 3", + "Blithering Badgers | 2 | 1 | 0 | 1 | 3", + ] + .join("\n"); + assert_eq!(output, expected); } #[test] #[ignore] fn there_can_be_more_than_two_teams() { - let input = "Allegoric Alaskans;Blithering Badgers;win\n".to_string() - + "Blithering Badgers;Courageous Californians;win\n" - + "Courageous Californians;Allegoric Alaskans;loss"; - let expected = "".to_string() - + "Team | MP | W | D | L | P\n" - + "Allegoric Alaskans | 2 | 2 | 0 | 0 | 6\n" - + "Blithering Badgers | 2 | 1 | 0 | 1 | 3\n" - + "Courageous Californians | 2 | 0 | 0 | 2 | 0"; - - assert_eq!(tournament::tally(&input), expected); + let input: &[&str] = &[ + "Allegoric Alaskans;Blithering Badgers;win", + "Blithering Badgers;Courageous Californians;win", + "Courageous Californians;Allegoric Alaskans;loss", + ]; + let input = input.join("\n"); + let output = tournament::tally(&input); + let expected = [ + "Team | MP | W | D | L | P", + "Allegoric Alaskans | 2 | 2 | 0 | 0 | 6", + "Blithering Badgers | 2 | 1 | 0 | 1 | 3", + "Courageous Californians | 2 | 0 | 0 | 2 | 0", + ] + .join("\n"); + assert_eq!(output, expected); } #[test] #[ignore] fn typical_input() { - let input = "Allegoric Alaskans;Blithering Badgers;win\n".to_string() - + "Devastating Donkeys;Courageous Californians;draw\n" - + "Devastating Donkeys;Allegoric Alaskans;win\n" - + "Courageous Californians;Blithering Badgers;loss\n" - + "Blithering Badgers;Devastating Donkeys;loss\n" - + "Allegoric Alaskans;Courageous Californians;win"; - let expected = "".to_string() - + "Team | MP | W | D | L | P\n" - + "Devastating Donkeys | 3 | 2 | 1 | 0 | 7\n" - + "Allegoric Alaskans | 3 | 2 | 0 | 1 | 6\n" - + "Blithering Badgers | 3 | 1 | 0 | 2 | 3\n" - + "Courageous Californians | 3 | 0 | 1 | 2 | 1"; - - assert_eq!(tournament::tally(&input), expected); + let input: &[&str] = &[ + "Allegoric Alaskans;Blithering Badgers;win", + "Devastating Donkeys;Courageous Californians;draw", + "Devastating Donkeys;Allegoric Alaskans;win", + "Courageous Californians;Blithering Badgers;loss", + "Blithering Badgers;Devastating Donkeys;loss", + "Allegoric Alaskans;Courageous Californians;win", + ]; + let input = input.join("\n"); + let output = tournament::tally(&input); + let expected = [ + "Team | MP | W | D | L | P", + "Devastating Donkeys | 3 | 2 | 1 | 0 | 7", + "Allegoric Alaskans | 3 | 2 | 0 | 1 | 6", + "Blithering Badgers | 3 | 1 | 0 | 2 | 3", + "Courageous Californians | 3 | 0 | 1 | 2 | 1", + ] + .join("\n"); + assert_eq!(output, expected); } #[test] #[ignore] fn incomplete_competition_not_all_pairs_have_played() { - let input = "Allegoric Alaskans;Blithering Badgers;loss\n".to_string() - + "Devastating Donkeys;Allegoric Alaskans;loss\n" - + "Courageous Californians;Blithering Badgers;draw\n" - + "Allegoric Alaskans;Courageous Californians;win"; - let expected = "".to_string() - + "Team | MP | W | D | L | P\n" - + "Allegoric Alaskans | 3 | 2 | 0 | 1 | 6\n" - + "Blithering Badgers | 2 | 1 | 1 | 0 | 4\n" - + "Courageous Californians | 2 | 0 | 1 | 1 | 1\n" - + "Devastating Donkeys | 1 | 0 | 0 | 1 | 0"; - - assert_eq!(tournament::tally(&input), expected); + let input: &[&str] = &[ + "Allegoric Alaskans;Blithering Badgers;loss", + "Devastating Donkeys;Allegoric Alaskans;loss", + "Courageous Californians;Blithering Badgers;draw", + "Allegoric Alaskans;Courageous Californians;win", + ]; + let input = input.join("\n"); + let output = tournament::tally(&input); + let expected = [ + "Team | MP | W | D | L | P", + "Allegoric Alaskans | 3 | 2 | 0 | 1 | 6", + "Blithering Badgers | 2 | 1 | 1 | 0 | 4", + "Courageous Californians | 2 | 0 | 1 | 1 | 1", + "Devastating Donkeys | 1 | 0 | 0 | 1 | 0", + ] + .join("\n"); + assert_eq!(output, expected); } #[test] #[ignore] fn ties_broken_alphabetically() { - let input = "Courageous Californians;Devastating Donkeys;win\n".to_string() - + "Allegoric Alaskans;Blithering Badgers;win\n" - + "Devastating Donkeys;Allegoric Alaskans;loss\n" - + "Courageous Californians;Blithering Badgers;win\n" - + "Blithering Badgers;Devastating Donkeys;draw\n" - + "Allegoric Alaskans;Courageous Californians;draw"; - let expected = "".to_string() - + "Team | MP | W | D | L | P\n" - + "Allegoric Alaskans | 3 | 2 | 1 | 0 | 7\n" - + "Courageous Californians | 3 | 2 | 1 | 0 | 7\n" - + "Blithering Badgers | 3 | 0 | 1 | 2 | 1\n" - + "Devastating Donkeys | 3 | 0 | 1 | 2 | 1"; + let input: &[&str] = &[ + "Courageous Californians;Devastating Donkeys;win", + "Allegoric Alaskans;Blithering Badgers;win", + "Devastating Donkeys;Allegoric Alaskans;loss", + "Courageous Californians;Blithering Badgers;win", + "Blithering Badgers;Devastating Donkeys;draw", + "Allegoric Alaskans;Courageous Californians;draw", + ]; + let input = input.join("\n"); + let output = tournament::tally(&input); + let expected = [ + "Team | MP | W | D | L | P", + "Allegoric Alaskans | 3 | 2 | 1 | 0 | 7", + "Courageous Californians | 3 | 2 | 1 | 0 | 7", + "Blithering Badgers | 3 | 0 | 1 | 2 | 1", + "Devastating Donkeys | 3 | 0 | 1 | 2 | 1", + ] + .join("\n"); + assert_eq!(output, expected); +} - assert_eq!(tournament::tally(&input), expected); +#[test] +#[ignore] +fn ensure_points_sorted_numerically() { + let input: &[&str] = &[ + "Devastating Donkeys;Blithering Badgers;win", + "Devastating Donkeys;Blithering Badgers;win", + "Devastating Donkeys;Blithering Badgers;win", + "Devastating Donkeys;Blithering Badgers;win", + "Blithering Badgers;Devastating Donkeys;win", + ]; + let input = input.join("\n"); + let output = tournament::tally(&input); + let expected = [ + "Team | MP | W | D | L | P", + "Devastating Donkeys | 5 | 4 | 0 | 1 | 12", + "Blithering Badgers | 5 | 1 | 0 | 4 | 3", + ] + .join("\n"); + assert_eq!(output, expected); } From dbb64c9df6c3fded14a3ecad9add2a391d4d3ff1 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 14 Nov 2023 15:09:45 +0100 Subject: [PATCH 186/436] Sync sum-of-multiples with problem-specifications (#1765) [no important files changed] --- .../sum-of-multiples/.meta/config.json | 2 +- .../sum-of-multiples/.meta/test_template.tera | 13 +++ .../sum-of-multiples/.meta/tests.toml | 16 ++- .../tests/sum-of-multiples.rs | 98 +++++++++++++++---- 4 files changed, 107 insertions(+), 22 deletions(-) create mode 100644 exercises/practice/sum-of-multiples/.meta/test_template.tera diff --git a/exercises/practice/sum-of-multiples/.meta/config.json b/exercises/practice/sum-of-multiples/.meta/config.json index e020b0c58..dcd7e5dbd 100644 --- a/exercises/practice/sum-of-multiples/.meta/config.json +++ b/exercises/practice/sum-of-multiples/.meta/config.json @@ -35,5 +35,5 @@ }, "blurb": "Given a number, find the sum of all the multiples of particular numbers up to but not including that number.", "source": "A variation on Problem 1 at Project Euler", - "source_url": "http://projecteuler.net/problem=1" + "source_url": "https://projecteuler.net/problem=1" } diff --git a/exercises/practice/sum-of-multiples/.meta/test_template.tera b/exercises/practice/sum-of-multiples/.meta/test_template.tera new file mode 100644 index 000000000..5fd13a19d --- /dev/null +++ b/exercises/practice/sum-of-multiples/.meta/test_template.tera @@ -0,0 +1,13 @@ +{% for test in cases %} +#[test] +{% if loop.index != 1 -%} +#[ignore] +{% endif -%} +fn {{ test.description | slugify | replace(from="-", to="_") }}() { + let factors = &{{ test.input.factors | json_encode() }}; + let limit = {{ test.input.limit | json_encode() }}; + let output = {{ crate_name }}::{{ fn_names[0] }}(limit, factors); + let expected = {{ test.expected | json_encode() }}; + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/sum-of-multiples/.meta/tests.toml b/exercises/practice/sum-of-multiples/.meta/tests.toml index 59bd7ca12..1e9b1241d 100644 --- a/exercises/practice/sum-of-multiples/.meta/tests.toml +++ b/exercises/practice/sum-of-multiples/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [54aaab5a-ce86-4edc-8b40-d3ab2400a279] description = "no multiples within limit" @@ -46,3 +53,6 @@ description = "the only multiple of 0 is 0" [c423ae21-a0cb-4ec7-aeb1-32971af5b510] description = "the factor 0 does not affect the sum of multiples of other factors" + +[17053ba9-112f-4ac0-aadb-0519dd836342] +description = "solutions using include-exclude must extend to cardinality greater than 3" diff --git a/exercises/practice/sum-of-multiples/tests/sum-of-multiples.rs b/exercises/practice/sum-of-multiples/tests/sum-of-multiples.rs index bdcd30d77..488f06eb8 100644 --- a/exercises/practice/sum-of-multiples/tests/sum-of-multiples.rs +++ b/exercises/practice/sum-of-multiples/tests/sum-of-multiples.rs @@ -1,96 +1,158 @@ -use sum_of_multiples::*; - #[test] fn no_multiples_within_limit() { - assert_eq!(0, sum_of_multiples(1, &[3, 5])) + let factors = &[3, 5]; + let limit = 1; + let output = sum_of_multiples::sum_of_multiples(limit, factors); + let expected = 0; + assert_eq!(output, expected); } #[test] #[ignore] fn one_factor_has_multiples_within_limit() { - assert_eq!(3, sum_of_multiples(4, &[3, 5])) + let factors = &[3, 5]; + let limit = 4; + let output = sum_of_multiples::sum_of_multiples(limit, factors); + let expected = 3; + assert_eq!(output, expected); } #[test] #[ignore] fn more_than_one_multiple_within_limit() { - assert_eq!(9, sum_of_multiples(7, &[3])) + let factors = &[3]; + let limit = 7; + let output = sum_of_multiples::sum_of_multiples(limit, factors); + let expected = 9; + assert_eq!(output, expected); } #[test] #[ignore] fn more_than_one_factor_with_multiples_within_limit() { - assert_eq!(23, sum_of_multiples(10, &[3, 5])) + let factors = &[3, 5]; + let limit = 10; + let output = sum_of_multiples::sum_of_multiples(limit, factors); + let expected = 23; + assert_eq!(output, expected); } #[test] #[ignore] fn each_multiple_is_only_counted_once() { - assert_eq!(2318, sum_of_multiples(100, &[3, 5])) + let factors = &[3, 5]; + let limit = 100; + let output = sum_of_multiples::sum_of_multiples(limit, factors); + let expected = 2318; + assert_eq!(output, expected); } #[test] #[ignore] fn a_much_larger_limit() { - assert_eq!(233_168, sum_of_multiples(1000, &[3, 5])) + let factors = &[3, 5]; + let limit = 1000; + let output = sum_of_multiples::sum_of_multiples(limit, factors); + let expected = 233168; + assert_eq!(output, expected); } #[test] #[ignore] fn three_factors() { - assert_eq!(51, sum_of_multiples(20, &[7, 13, 17])) + let factors = &[7, 13, 17]; + let limit = 20; + let output = sum_of_multiples::sum_of_multiples(limit, factors); + let expected = 51; + assert_eq!(output, expected); } #[test] #[ignore] fn factors_not_relatively_prime() { - assert_eq!(30, sum_of_multiples(15, &[4, 6])) + let factors = &[4, 6]; + let limit = 15; + let output = sum_of_multiples::sum_of_multiples(limit, factors); + let expected = 30; + assert_eq!(output, expected); } #[test] #[ignore] fn some_pairs_of_factors_relatively_prime_and_some_not() { - assert_eq!(4419, sum_of_multiples(150, &[5, 6, 8])) + let factors = &[5, 6, 8]; + let limit = 150; + let output = sum_of_multiples::sum_of_multiples(limit, factors); + let expected = 4419; + assert_eq!(output, expected); } #[test] #[ignore] fn one_factor_is_a_multiple_of_another() { - assert_eq!(275, sum_of_multiples(51, &[5, 25])) + let factors = &[5, 25]; + let limit = 51; + let output = sum_of_multiples::sum_of_multiples(limit, factors); + let expected = 275; + assert_eq!(output, expected); } #[test] #[ignore] fn much_larger_factors() { - assert_eq!(2_203_160, sum_of_multiples(10_000, &[43, 47])) + let factors = &[43, 47]; + let limit = 10000; + let output = sum_of_multiples::sum_of_multiples(limit, factors); + let expected = 2203160; + assert_eq!(output, expected); } #[test] #[ignore] fn all_numbers_are_multiples_of_1() { - assert_eq!(4950, sum_of_multiples(100, &[1])) + let factors = &[1]; + let limit = 100; + let output = sum_of_multiples::sum_of_multiples(limit, factors); + let expected = 4950; + assert_eq!(output, expected); } #[test] #[ignore] fn no_factors_means_an_empty_sum() { - assert_eq!(0, sum_of_multiples(10_000, &[])) + let factors = &[]; + let limit = 10000; + let output = sum_of_multiples::sum_of_multiples(limit, factors); + let expected = 0; + assert_eq!(output, expected); } #[test] #[ignore] fn the_only_multiple_of_0_is_0() { - assert_eq!(0, sum_of_multiples(1, &[0])) + let factors = &[0]; + let limit = 1; + let output = sum_of_multiples::sum_of_multiples(limit, factors); + let expected = 0; + assert_eq!(output, expected); } #[test] #[ignore] fn the_factor_0_does_not_affect_the_sum_of_multiples_of_other_factors() { - assert_eq!(3, sum_of_multiples(4, &[3, 0])) + let factors = &[3, 0]; + let limit = 4; + let output = sum_of_multiples::sum_of_multiples(limit, factors); + let expected = 3; + assert_eq!(output, expected); } #[test] #[ignore] fn solutions_using_include_exclude_must_extend_to_cardinality_greater_than_3() { - assert_eq!(39_614_537, sum_of_multiples(10_000, &[2, 3, 5, 7, 11])) + let factors = &[2, 3, 5, 7, 11]; + let limit = 10000; + let output = sum_of_multiples::sum_of_multiples(limit, factors); + let expected = 39614537; + assert_eq!(output, expected); } From 5e05a27f4d7ecc2c2bff378c4c3ce647b520ac78 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 14 Nov 2023 15:20:16 +0100 Subject: [PATCH 187/436] Sync spiral-matrix with problem-specifications (#1769) [no important files changed] --- .../spiral-matrix/.docs/instructions.md | 5 +- .../practice/spiral-matrix/.meta/config.json | 2 +- .../spiral-matrix/.meta/test_template.tera | 16 ++++ .../practice/spiral-matrix/.meta/tests.toml | 28 ++++++- .../spiral-matrix/tests/spiral-matrix.rs | 74 ++++++++++--------- 5 files changed, 85 insertions(+), 40 deletions(-) create mode 100644 exercises/practice/spiral-matrix/.meta/test_template.tera diff --git a/exercises/practice/spiral-matrix/.docs/instructions.md b/exercises/practice/spiral-matrix/.docs/instructions.md index af412dd83..ba99e12c7 100644 --- a/exercises/practice/spiral-matrix/.docs/instructions.md +++ b/exercises/practice/spiral-matrix/.docs/instructions.md @@ -2,11 +2,10 @@ Given the size, return a square matrix of numbers in spiral order. -The matrix should be filled with natural numbers, starting from 1 -in the top-left corner, increasing in an inward, clockwise spiral order, -like these examples: +The matrix should be filled with natural numbers, starting from 1 in the top-left corner, increasing in an inward, clockwise spiral order, like these examples: ## Examples + ### Spiral matrix of size 3 ```text diff --git a/exercises/practice/spiral-matrix/.meta/config.json b/exercises/practice/spiral-matrix/.meta/config.json index 99b94dacf..0e2997e71 100644 --- a/exercises/practice/spiral-matrix/.meta/config.json +++ b/exercises/practice/spiral-matrix/.meta/config.json @@ -26,7 +26,7 @@ ".meta/example.rs" ] }, - "blurb": " Given the size, return a square matrix of numbers in spiral order.", + "blurb": "Given the size, return a square matrix of numbers in spiral order.", "source": "Reddit r/dailyprogrammer challenge #320 [Easy] Spiral Ascension.", "source_url": "https://www.reddit.com/r/dailyprogrammer/comments/6i60lr/20170619_challenge_320_easy_spiral_ascension/" } diff --git a/exercises/practice/spiral-matrix/.meta/test_template.tera b/exercises/practice/spiral-matrix/.meta/test_template.tera new file mode 100644 index 000000000..ab4e24399 --- /dev/null +++ b/exercises/practice/spiral-matrix/.meta/test_template.tera @@ -0,0 +1,16 @@ +{% for test in cases %} +#[test] +{% if loop.index != 1 -%} +#[ignore] +{% endif -%} +fn {{ test.description | slugify | replace(from="-", to="_") }}() { + let input = {{ test.input.size | json_encode() }}; + let output = {{ crate_name }}::{{ fn_names[0] }}(input); + let expected: [[u32; {{ test.input.size }}]; {{ test.input.size }}] = [ + {% for i in test.expected %} + {{ i | json_encode }}, + {% endfor %} + ]; + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/spiral-matrix/.meta/tests.toml b/exercises/practice/spiral-matrix/.meta/tests.toml index cb988949d..9ac5bacaa 100644 --- a/exercises/practice/spiral-matrix/.meta/tests.toml +++ b/exercises/practice/spiral-matrix/.meta/tests.toml @@ -1,6 +1,28 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [8f584201-b446-4bc9-b132-811c8edd9040] description = "empty spiral" + +[e40ae5f3-e2c9-4639-8116-8a119d632ab2] +description = "trivial spiral" + +[cf05e42d-eb78-4098-a36e-cdaf0991bc48] +description = "spiral of size 2" + +[1c475667-c896-4c23-82e2-e033929de939] +description = "spiral of size 3" + +[05ccbc48-d891-44f5-9137-f4ce462a759d] +description = "spiral of size 4" + +[f4d2165b-1738-4e0c-bed0-c459045ae50d] +description = "spiral of size 5" diff --git a/exercises/practice/spiral-matrix/tests/spiral-matrix.rs b/exercises/practice/spiral-matrix/tests/spiral-matrix.rs index 0c957b9b5..42c127edb 100644 --- a/exercises/practice/spiral-matrix/tests/spiral-matrix.rs +++ b/exercises/practice/spiral-matrix/tests/spiral-matrix.rs @@ -1,55 +1,63 @@ -use spiral_matrix::*; - #[test] fn empty_spiral() { - let expected: Vec> = Vec::new(); - assert_eq!(spiral_matrix(0), expected); + let input = 0; + let output = spiral_matrix::spiral_matrix(input); + let expected: [[u32; 0]; 0] = []; + assert_eq!(output, expected); } #[test] #[ignore] -fn size_one_spiral() { - let expected: Vec> = vec![vec![1]]; - assert_eq!(spiral_matrix(1), expected); +fn trivial_spiral() { + let input = 1; + let output = spiral_matrix::spiral_matrix(input); + let expected: [[u32; 1]; 1] = [[1]]; + assert_eq!(output, expected); } + #[test] #[ignore] -fn size_two_spiral() { - let expected: Vec> = vec![vec![1, 2], vec![4, 3]]; - assert_eq!(spiral_matrix(2), expected); +fn spiral_of_size_2() { + let input = 2; + let output = spiral_matrix::spiral_matrix(input); + let expected: [[u32; 2]; 2] = [[1, 2], [4, 3]]; + assert_eq!(output, expected); } #[test] #[ignore] -fn size_three_spiral() { - #[rustfmt::skip] - let expected: Vec> = vec![ - vec![1, 2, 3], - vec![8, 9, 4], - vec![7, 6, 5], - ]; - assert_eq!(spiral_matrix(3), expected); +fn spiral_of_size_3() { + let input = 3; + let output = spiral_matrix::spiral_matrix(input); + let expected: [[u32; 3]; 3] = [[1, 2, 3], [8, 9, 4], [7, 6, 5]]; + assert_eq!(output, expected); } + #[test] #[ignore] -fn size_four_spiral() { - let expected: Vec> = vec![ - vec![1, 2, 3, 4], - vec![12, 13, 14, 5], - vec![11, 16, 15, 6], - vec![10, 9, 8, 7], +fn spiral_of_size_4() { + let input = 4; + let output = spiral_matrix::spiral_matrix(input); + let expected: [[u32; 4]; 4] = [ + [1, 2, 3, 4], + [12, 13, 14, 5], + [11, 16, 15, 6], + [10, 9, 8, 7], ]; - assert_eq!(spiral_matrix(4), expected); + assert_eq!(output, expected); } + #[test] #[ignore] -fn size_five_spiral() { - let expected: Vec> = vec![ - vec![1, 2, 3, 4, 5], - vec![16, 17, 18, 19, 6], - vec![15, 24, 25, 20, 7], - vec![14, 23, 22, 21, 8], - vec![13, 12, 11, 10, 9], +fn spiral_of_size_5() { + let input = 5; + let output = spiral_matrix::spiral_matrix(input); + let expected: [[u32; 5]; 5] = [ + [1, 2, 3, 4, 5], + [16, 17, 18, 19, 6], + [15, 24, 25, 20, 7], + [14, 23, 22, 21, 8], + [13, 12, 11, 10, 9], ]; - assert_eq!(spiral_matrix(5), expected); + assert_eq!(output, expected); } From 0ed4c08443a94a5e44a84fad731f7153e80778cb Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 14 Nov 2023 15:26:13 +0100 Subject: [PATCH 188/436] Sync space-age with problem-specifications (#1770) [no important files changed] --- .../practice/space-age/.docs/instructions.md | 27 ++++--- .../practice/space-age/.meta/config.json | 2 +- .../space-age/.meta/test_template.tera | 22 ++++++ exercises/practice/space-age/.meta/tests.toml | 45 +++++++++++- .../practice/space-age/tests/space-age.rs | 72 ++++++++++++------- 5 files changed, 130 insertions(+), 38 deletions(-) create mode 100644 exercises/practice/space-age/.meta/test_template.tera diff --git a/exercises/practice/space-age/.docs/instructions.md b/exercises/practice/space-age/.docs/instructions.md index 19cca8bf9..fe938cc09 100644 --- a/exercises/practice/space-age/.docs/instructions.md +++ b/exercises/practice/space-age/.docs/instructions.md @@ -2,17 +2,24 @@ Given an age in seconds, calculate how old someone would be on: - - Mercury: orbital period 0.2408467 Earth years - - Venus: orbital period 0.61519726 Earth years - - Earth: orbital period 1.0 Earth years, 365.25 Earth days, or 31557600 seconds - - Mars: orbital period 1.8808158 Earth years - - Jupiter: orbital period 11.862615 Earth years - - Saturn: orbital period 29.447498 Earth years - - Uranus: orbital period 84.016846 Earth years - - Neptune: orbital period 164.79132 Earth years +- Mercury: orbital period 0.2408467 Earth years +- Venus: orbital period 0.61519726 Earth years +- Earth: orbital period 1.0 Earth years, 365.25 Earth days, or 31557600 seconds +- Mars: orbital period 1.8808158 Earth years +- Jupiter: orbital period 11.862615 Earth years +- Saturn: orbital period 29.447498 Earth years +- Uranus: orbital period 84.016846 Earth years +- Neptune: orbital period 164.79132 Earth years So if you were told someone were 1,000,000,000 seconds old, you should be able to say that they're 31.69 Earth-years old. -If you're wondering why Pluto didn't make the cut, go watch [this -youtube video](http://www.youtube.com/watch?v=Z_2gbGXzFbs). +If you're wondering why Pluto didn't make the cut, go watch [this YouTube video][pluto-video]. + +Note: The actual length of one complete orbit of the Earth around the sun is closer to 365.256 days (1 sidereal year). +The Gregorian calendar has, on average, 365.2425 days. +While not entirely accurate, 365.25 is the value used in this exercise. +See [Year on Wikipedia][year] for more ways to measure a year. + +[pluto-video]: https://www.youtube.com/watch?v=Z_2gbGXzFbs +[year]: https://en.wikipedia.org/wiki/Year#Summary diff --git a/exercises/practice/space-age/.meta/config.json b/exercises/practice/space-age/.meta/config.json index 93de819e2..6ec7a6be2 100644 --- a/exercises/practice/space-age/.meta/config.json +++ b/exercises/practice/space-age/.meta/config.json @@ -36,5 +36,5 @@ }, "blurb": "Given an age in seconds, calculate how old someone is in terms of a given planet's solar years.", "source": "Partially inspired by Chapter 1 in Chris Pine's online Learn to Program tutorial.", - "source_url": "http://pine.fm/LearnToProgram/?Chapter=01" + "source_url": "https://pine.fm/LearnToProgram/?Chapter=01" } diff --git a/exercises/practice/space-age/.meta/test_template.tera b/exercises/practice/space-age/.meta/test_template.tera new file mode 100644 index 000000000..cde9b9498 --- /dev/null +++ b/exercises/practice/space-age/.meta/test_template.tera @@ -0,0 +1,22 @@ +use space_age::*; + +fn assert_in_delta(expected: f64, actual: f64) { + let diff: f64 = (expected - actual).abs(); + let delta: f64 = 0.01; + if diff > delta { + panic!("Your result of {actual} should be within {delta} of the expected result {expected}") + } +} +{% for test in cases %} +#[test] +{% if loop.index != 1 -%} +#[ignore] +{% endif -%} +fn {{ test.description | slugify | replace(from="-", to="_") }}() { + let seconds = {{ test.input.seconds | json_encode() }}; + let duration = Duration::from(seconds); + let output = {{ test.input.planet }}::years_during(&duration); + let expected = {{ test.expected | json_encode() }}; + assert_in_delta(expected, output); +} +{% endfor -%} diff --git a/exercises/practice/space-age/.meta/tests.toml b/exercises/practice/space-age/.meta/tests.toml index be690e975..93f2ffb6f 100644 --- a/exercises/practice/space-age/.meta/tests.toml +++ b/exercises/practice/space-age/.meta/tests.toml @@ -1,3 +1,42 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[84f609af-5a91-4d68-90a3-9e32d8a5cd34] +description = "age on Earth" + +[ca20c4e9-6054-458c-9312-79679ffab40b] +description = "age on Mercury" + +[502c6529-fd1b-41d3-8fab-65e03082b024] +description = "age on Venus" + +[9ceadf5e-a0d5-4388-9d40-2c459227ceb8] +description = "age on Mars" + +[42927dc3-fe5e-4f76-a5b5-f737fc19bcde] +description = "age on Jupiter" + +[8469b332-7837-4ada-b27c-00ee043ebcad] +description = "age on Saturn" + +[999354c1-76f8-4bb5-a672-f317b6436743] +description = "age on Uranus" + +[80096d30-a0d4-4449-903e-a381178355d8] +description = "age on Neptune" + +[57b96e2a-1178-40b7-b34d-f3c9c34e4bf4] +description = "invalid planet causes error" +include = false +comment = """ + Our implementation of this exercise defines the Planets at compile time. + Therefore, there cannot be any runtime errors about invalid planets. +""" diff --git a/exercises/practice/space-age/tests/space-age.rs b/exercises/practice/space-age/tests/space-age.rs index a36c42bf7..9f379aff8 100644 --- a/exercises/practice/space-age/tests/space-age.rs +++ b/exercises/practice/space-age/tests/space-age.rs @@ -9,56 +9,80 @@ fn assert_in_delta(expected: f64, actual: f64) { } #[test] -fn earth_age() { - let duration = Duration::from(1_000_000_000); - assert_in_delta(31.69, Earth::years_during(&duration)); +fn age_on_earth() { + let seconds = 1000000000; + let duration = Duration::from(seconds); + let output = Earth::years_during(&duration); + let expected = 31.69; + assert_in_delta(expected, output); } #[test] #[ignore] -fn mercury_age() { - let duration = Duration::from(2_134_835_688); - assert_in_delta(280.88, Mercury::years_during(&duration)); +fn age_on_mercury() { + let seconds = 2134835688; + let duration = Duration::from(seconds); + let output = Mercury::years_during(&duration); + let expected = 280.88; + assert_in_delta(expected, output); } #[test] #[ignore] -fn venus_age() { - let duration = Duration::from(189_839_836); - assert_in_delta(9.78, Venus::years_during(&duration)); +fn age_on_venus() { + let seconds = 189839836; + let duration = Duration::from(seconds); + let output = Venus::years_during(&duration); + let expected = 9.78; + assert_in_delta(expected, output); } #[test] #[ignore] -fn mars_age() { - let duration = Duration::from(2_129_871_239); - assert_in_delta(35.88, Mars::years_during(&duration)); +fn age_on_mars() { + let seconds = 2129871239; + let duration = Duration::from(seconds); + let output = Mars::years_during(&duration); + let expected = 35.88; + assert_in_delta(expected, output); } #[test] #[ignore] -fn jupiter_age() { - let duration = Duration::from(901_876_382); - assert_in_delta(2.41, Jupiter::years_during(&duration)); +fn age_on_jupiter() { + let seconds = 901876382; + let duration = Duration::from(seconds); + let output = Jupiter::years_during(&duration); + let expected = 2.41; + assert_in_delta(expected, output); } #[test] #[ignore] -fn saturn_age() { - let duration = Duration::from(2_000_000_000); - assert_in_delta(2.15, Saturn::years_during(&duration)); +fn age_on_saturn() { + let seconds = 2000000000; + let duration = Duration::from(seconds); + let output = Saturn::years_during(&duration); + let expected = 2.15; + assert_in_delta(expected, output); } #[test] #[ignore] -fn uranus_age() { - let duration = Duration::from(1_210_123_456); - assert_in_delta(0.46, Uranus::years_during(&duration)); +fn age_on_uranus() { + let seconds = 1210123456; + let duration = Duration::from(seconds); + let output = Uranus::years_during(&duration); + let expected = 0.46; + assert_in_delta(expected, output); } #[test] #[ignore] -fn neptune_age() { - let duration = Duration::from(1_821_023_456); - assert_in_delta(0.35, Neptune::years_during(&duration)); +fn age_on_neptune() { + let seconds = 1821023456; + let duration = Duration::from(seconds); + let output = Neptune::years_during(&duration); + let expected = 0.35; + assert_in_delta(expected, output); } From 532fffe2458a8e739ea1a218821468355a37c220 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 14 Nov 2023 15:32:17 +0100 Subject: [PATCH 189/436] Sync sieve with problem-specifications (#1771) [no important files changed] --- exercises/practice/sieve/.meta/config.json | 2 +- .../practice/sieve/.meta/test_template.tera | 12 +++++++ exercises/practice/sieve/.meta/tests.toml | 25 ++++++++++++-- exercises/practice/sieve/tests/sieve.rs | 34 +++++++++++++------ 4 files changed, 59 insertions(+), 14 deletions(-) create mode 100644 exercises/practice/sieve/.meta/test_template.tera diff --git a/exercises/practice/sieve/.meta/config.json b/exercises/practice/sieve/.meta/config.json index 40fe741f2..caeb2186c 100644 --- a/exercises/practice/sieve/.meta/config.json +++ b/exercises/practice/sieve/.meta/config.json @@ -33,5 +33,5 @@ }, "blurb": "Use the Sieve of Eratosthenes to find all the primes from 2 up to a given number.", "source": "Sieve of Eratosthenes at Wikipedia", - "source_url": "http://en.wikipedia.org/wiki/Sieve_of_Eratosthenes" + "source_url": "https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes" } diff --git a/exercises/practice/sieve/.meta/test_template.tera b/exercises/practice/sieve/.meta/test_template.tera new file mode 100644 index 000000000..aab8df430 --- /dev/null +++ b/exercises/practice/sieve/.meta/test_template.tera @@ -0,0 +1,12 @@ +{% for test in cases %} +#[test] +{% if loop.index != 1 -%} +#[ignore] +{% endif -%} +fn {{ test.description | slugify | replace(from="-", to="_") }}() { + let input = {{ test.input.limit | json_encode() }}; + let output = {{ crate_name }}::{{ fn_names[0] }}(input); + let expected = {{ test.expected | json_encode() }}; + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/sieve/.meta/tests.toml b/exercises/practice/sieve/.meta/tests.toml index 69b57ccf2..fec5e1a1a 100644 --- a/exercises/practice/sieve/.meta/tests.toml +++ b/exercises/practice/sieve/.meta/tests.toml @@ -1,6 +1,25 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[88529125-c4ce-43cc-bb36-1eb4ddd7b44f] +description = "no primes under two" + +[4afe9474-c705-4477-9923-840e1024cc2b] +description = "find first prime" + +[974945d8-8cd9-4f00-9463-7d813c7f17b7] +description = "find primes up to 10" [2e2417b7-3f3a-452a-8594-b9af08af6d82] description = "limit is prime" + +[92102a05-4c7c-47de-9ed0-b7d5fcd00f21] +description = "find primes up to 1000" diff --git a/exercises/practice/sieve/tests/sieve.rs b/exercises/practice/sieve/tests/sieve.rs index 98e521e77..89e381d9b 100644 --- a/exercises/practice/sieve/tests/sieve.rs +++ b/exercises/practice/sieve/tests/sieve.rs @@ -1,30 +1,44 @@ #[test] -fn limit_lower_than_the_first_prime() { - assert_eq!(sieve::primes_up_to(1), []); +fn no_primes_under_two() { + let input = 1; + let output = sieve::primes_up_to(input); + let expected = []; + assert_eq!(output, expected); } #[test] #[ignore] -fn limit_is_the_first_prime() { - assert_eq!(sieve::primes_up_to(2), [2]); +fn find_first_prime() { + let input = 2; + let output = sieve::primes_up_to(input); + let expected = [2]; + assert_eq!(output, expected); } #[test] #[ignore] -fn primes_up_to_10() { - assert_eq!(sieve::primes_up_to(10), [2, 3, 5, 7]); +fn find_primes_up_to_10() { + let input = 10; + let output = sieve::primes_up_to(input); + let expected = [2, 3, 5, 7]; + assert_eq!(output, expected); } #[test] #[ignore] fn limit_is_prime() { - assert_eq!(sieve::primes_up_to(13), [2, 3, 5, 7, 11, 13]); + let input = 13; + let output = sieve::primes_up_to(input); + let expected = [2, 3, 5, 7, 11, 13]; + assert_eq!(output, expected); } #[test] #[ignore] -fn limit_of_1000() { - let expected = vec![ +fn find_primes_up_to_1000() { + let input = 1000; + let output = sieve::primes_up_to(input); + let expected = [ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, @@ -35,5 +49,5 @@ fn limit_of_1000() { 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997, ]; - assert_eq!(sieve::primes_up_to(1000), expected); + assert_eq!(output, expected); } From 253fcb608043e7a8aedd8b2bf22f9a75c0d4b6b2 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 14 Nov 2023 15:48:24 +0100 Subject: [PATCH 190/436] Sync sublist with problem-specifications (#1767) --- .../practice/sublist/.docs/instructions.md | 31 ++-- .../practice/sublist/.meta/test_template.tera | 22 +++ exercises/practice/sublist/.meta/tests.toml | 61 ++++++- exercises/practice/sublist/tests/sublist.rs | 163 ++++++++++++------ 4 files changed, 207 insertions(+), 70 deletions(-) create mode 100644 exercises/practice/sublist/.meta/test_template.tera diff --git a/exercises/practice/sublist/.docs/instructions.md b/exercises/practice/sublist/.docs/instructions.md index 45c3b9648..7535931af 100644 --- a/exercises/practice/sublist/.docs/instructions.md +++ b/exercises/practice/sublist/.docs/instructions.md @@ -1,18 +1,25 @@ # Instructions -Given two lists determine if the first list is contained within the second -list, if the second list is contained within the first list, if both lists are -contained within each other or if none of these are true. +Given any two lists `A` and `B`, determine if: -Specifically, a list A is a sublist of list B if by dropping 0 or more elements -from the front of B and 0 or more elements from the back of B you get a list -that's completely equal to A. +- List `A` is equal to list `B`; or +- List `A` contains list `B` (`A` is a superlist of `B`); or +- List `A` is contained by list `B` (`A` is a sublist of `B`); or +- None of the above is true, thus lists `A` and `B` are unequal + +Specifically, list `A` is equal to list `B` if both lists have the same values in the same order. +List `A` is a superlist of `B` if `A` contains a sub-sequence of values equal to `B`. +List `A` is a sublist of `B` if `B` contains a sub-sequence of values equal to `A`. Examples: - * A = [1, 2, 3], B = [1, 2, 3, 4, 5], A is a sublist of B - * A = [3, 4, 5], B = [1, 2, 3, 4, 5], A is a sublist of B - * A = [3, 4], B = [1, 2, 3, 4, 5], A is a sublist of B - * A = [1, 2, 3], B = [1, 2, 3], A is equal to B - * A = [1, 2, 3, 4, 5], B = [2, 3, 4], A is a superlist of B - * A = [1, 2, 4], B = [1, 2, 3, 4, 5], A is not a superlist of, sublist of or equal to B +- If `A = []` and `B = []` (both lists are empty), then `A` and `B` are equal +- If `A = [1, 2, 3]` and `B = []`, then `A` is a superlist of `B` +- If `A = []` and `B = [1, 2, 3]`, then `A` is a sublist of `B` +- If `A = [1, 2, 3]` and `B = [1, 2, 3, 4, 5]`, then `A` is a sublist of `B` +- If `A = [3, 4, 5]` and `B = [1, 2, 3, 4, 5]`, then `A` is a sublist of `B` +- If `A = [3, 4]` and `B = [1, 2, 3, 4, 5]`, then `A` is a sublist of `B` +- If `A = [1, 2, 3]` and `B = [1, 2, 3]`, then `A` and `B` are equal +- If `A = [1, 2, 3, 4, 5]` and `B = [2, 3, 4]`, then `A` is a superlist of `B` +- If `A = [1, 2, 4]` and `B = [1, 2, 3, 4, 5]`, then `A` and `B` are unequal +- If `A = [1, 2, 3]` and `B = [1, 3, 2]`, then `A` and `B` are unequal diff --git a/exercises/practice/sublist/.meta/test_template.tera b/exercises/practice/sublist/.meta/test_template.tera new file mode 100644 index 000000000..2b1f97d93 --- /dev/null +++ b/exercises/practice/sublist/.meta/test_template.tera @@ -0,0 +1,22 @@ +use sublist::Comparison; +{% for test in cases %} +#[test] +{% if loop.index != 1 -%} +#[ignore] +{% endif -%} +fn {{ test.description | slugify | replace(from="-", to="_") }}() { + let list_one: &[i32] = &{{ test.input.listOne | json_encode() }}; + let list_two: &[i32] = &{{ test.input.listTwo | json_encode() }}; + let output = {{ crate_name }}::{{ fn_names[0] }}(list_one, list_two); + let expected = {% if test.expected == "equal" -%} + Comparison::Equal + {%- elif test.expected == "sublist" -%} + Comparison::Sublist + {%- elif test.expected == "superlist" -%} + Comparison::Superlist + {%- else -%} + Comparison::Unequal + {%- endif %}; + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/sublist/.meta/tests.toml b/exercises/practice/sublist/.meta/tests.toml index de63ebcc8..de5020a9d 100644 --- a/exercises/practice/sublist/.meta/tests.toml +++ b/exercises/practice/sublist/.meta/tests.toml @@ -1,9 +1,64 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[97319c93-ebc5-47ab-a022-02a1980e1d29] +description = "empty lists" + +[de27dbd4-df52-46fe-a336-30be58457382] +description = "empty list within non empty list" + +[5487cfd1-bc7d-429f-ac6f-1177b857d4fb] +description = "non empty list contains empty list" + +[1f390b47-f6b2-4a93-bc23-858ba5dda9a6] +description = "list equals itself" + +[7ed2bfb2-922b-4363-ae75-f3a05e8274f5] +description = "different lists" + +[3b8a2568-6144-4f06-b0a1-9d266b365341] +description = "false start" + +[dc39ed58-6311-4814-be30-05a64bc8d9b1] +description = "consecutive" + +[d1270dab-a1ce-41aa-b29d-b3257241ac26] +description = "sublist at start" [81f3d3f7-4f25-4ada-bcdc-897c403de1b6] description = "sublist in middle" [43bcae1e-a9cf-470e-923e-0946e04d8fdd] description = "sublist at end" + +[76cf99ed-0ff0-4b00-94af-4dfb43fe5caa] +description = "at start of superlist" + +[b83989ec-8bdf-4655-95aa-9f38f3e357fd] +description = "in middle of superlist" + +[26f9f7c3-6cf6-4610-984a-662f71f8689b] +description = "at end of superlist" + +[0a6db763-3588-416a-8f47-76b1cedde31e] +description = "first list missing element from second list" + +[83ffe6d8-a445-4a3c-8795-1e51a95e65c3] +description = "second list missing element from first list" + +[7bc76cb8-5003-49ca-bc47-cdfbe6c2bb89] +description = "first list missing additional digits from second list" + +[0d7ee7c1-0347-45c8-9ef5-b88db152b30b] +description = "order matters to a list" + +[5f47ce86-944e-40f9-9f31-6368aad70aa6] +description = "same digits but different numbers" diff --git a/exercises/practice/sublist/tests/sublist.rs b/exercises/practice/sublist/tests/sublist.rs index c9fb9a1b1..d64df6953 100644 --- a/exercises/practice/sublist/tests/sublist.rs +++ b/exercises/practice/sublist/tests/sublist.rs @@ -1,127 +1,180 @@ -use sublist::{sublist, Comparison}; +use sublist::Comparison; #[test] -fn empty_equals_empty() { - let v: &[u32] = &[]; - - assert_eq!(Comparison::Equal, sublist(v, v)); +fn empty_lists() { + let list_one: &[i32] = &[]; + let list_two: &[i32] = &[]; + let output = sublist::sublist(list_one, list_two); + let expected = Comparison::Equal; + assert_eq!(output, expected); } #[test] #[ignore] -fn empty_is_a_sublist_of_anything() { - assert_eq!(Comparison::Sublist, sublist(&[], &['a', 's', 'd', 'f'])); +fn empty_list_within_non_empty_list() { + let list_one: &[i32] = &[]; + let list_two: &[i32] = &[1, 2, 3]; + let output = sublist::sublist(list_one, list_two); + let expected = Comparison::Sublist; + assert_eq!(output, expected); } #[test] #[ignore] -fn anything_is_a_superlist_of_empty() { - assert_eq!(Comparison::Superlist, sublist(&['a', 's', 'd', 'f'], &[])); +fn non_empty_list_contains_empty_list() { + let list_one: &[i32] = &[1, 2, 3]; + let list_two: &[i32] = &[]; + let output = sublist::sublist(list_one, list_two); + let expected = Comparison::Superlist; + assert_eq!(output, expected); } #[test] #[ignore] -fn one_is_not_two() { - assert_eq!(Comparison::Unequal, sublist(&[1], &[2])); +fn list_equals_itself() { + let list_one: &[i32] = &[1, 2, 3]; + let list_two: &[i32] = &[1, 2, 3]; + let output = sublist::sublist(list_one, list_two); + let expected = Comparison::Equal; + assert_eq!(output, expected); } #[test] #[ignore] -fn compare_larger_equal_lists() { - use std::iter::repeat; - - let v: Vec = repeat('x').take(1000).collect(); - - assert_eq!(Comparison::Equal, sublist(&v, &v)); +fn different_lists() { + let list_one: &[i32] = &[1, 2, 3]; + let list_two: &[i32] = &[2, 3, 4]; + let output = sublist::sublist(list_one, list_two); + let expected = Comparison::Unequal; + assert_eq!(output, expected); } #[test] #[ignore] -fn sublist_at_start() { - assert_eq!(Comparison::Sublist, sublist(&[1, 2, 3], &[1, 2, 3, 4, 5])); +fn false_start() { + let list_one: &[i32] = &[1, 2, 5]; + let list_two: &[i32] = &[0, 1, 2, 3, 1, 2, 5, 6]; + let output = sublist::sublist(list_one, list_two); + let expected = Comparison::Sublist; + assert_eq!(output, expected); } #[test] #[ignore] -fn sublist_in_middle() { - assert_eq!(Comparison::Sublist, sublist(&[4, 3, 2], &[5, 4, 3, 2, 1])); +fn consecutive() { + let list_one: &[i32] = &[1, 1, 2]; + let list_two: &[i32] = &[0, 1, 1, 1, 2, 1, 2]; + let output = sublist::sublist(list_one, list_two); + let expected = Comparison::Sublist; + assert_eq!(output, expected); } #[test] #[ignore] -fn sublist_at_end() { - assert_eq!(Comparison::Sublist, sublist(&[3, 4, 5], &[1, 2, 3, 4, 5])); +fn sublist_at_start() { + let list_one: &[i32] = &[0, 1, 2]; + let list_two: &[i32] = &[0, 1, 2, 3, 4, 5]; + let output = sublist::sublist(list_one, list_two); + let expected = Comparison::Sublist; + assert_eq!(output, expected); } #[test] #[ignore] -fn partially_matching_sublist_at_start() { - assert_eq!(Comparison::Sublist, sublist(&[1, 1, 2], &[1, 1, 1, 2])); +fn sublist_in_middle() { + let list_one: &[i32] = &[2, 3, 4]; + let list_two: &[i32] = &[0, 1, 2, 3, 4, 5]; + let output = sublist::sublist(list_one, list_two); + let expected = Comparison::Sublist; + assert_eq!(output, expected); } #[test] #[ignore] -fn sublist_early_in_huge_list() { - let huge: Vec = (1..1_000_000).collect(); - - assert_eq!(Comparison::Sublist, sublist(&[3, 4, 5], &huge)); +fn sublist_at_end() { + let list_one: &[i32] = &[3, 4, 5]; + let list_two: &[i32] = &[0, 1, 2, 3, 4, 5]; + let output = sublist::sublist(list_one, list_two); + let expected = Comparison::Sublist; + assert_eq!(output, expected); } #[test] #[ignore] -fn huge_sublist_not_in_huge_list() { - let v1: Vec = (10..1_000_001).collect(); - let v2: Vec = (1..1_000_000).collect(); - - assert_eq!(Comparison::Unequal, sublist(&v1, &v2)); +fn at_start_of_superlist() { + let list_one: &[i32] = &[0, 1, 2, 3, 4, 5]; + let list_two: &[i32] = &[0, 1, 2]; + let output = sublist::sublist(list_one, list_two); + let expected = Comparison::Superlist; + assert_eq!(output, expected); } #[test] #[ignore] -fn superlist_at_start() { - assert_eq!(Comparison::Superlist, sublist(&[1, 2, 3, 4, 5], &[1, 2, 3])); +fn in_middle_of_superlist() { + let list_one: &[i32] = &[0, 1, 2, 3, 4, 5]; + let list_two: &[i32] = &[2, 3]; + let output = sublist::sublist(list_one, list_two); + let expected = Comparison::Superlist; + assert_eq!(output, expected); } #[test] #[ignore] -fn superlist_in_middle() { - assert_eq!(Comparison::Superlist, sublist(&[5, 4, 3, 2, 1], &[4, 3, 2])); +fn at_end_of_superlist() { + let list_one: &[i32] = &[0, 1, 2, 3, 4, 5]; + let list_two: &[i32] = &[3, 4, 5]; + let output = sublist::sublist(list_one, list_two); + let expected = Comparison::Superlist; + assert_eq!(output, expected); } #[test] #[ignore] -fn superlist_at_end() { - assert_eq!(Comparison::Superlist, sublist(&[1, 2, 3, 4, 5], &[3, 4, 5])); +fn first_list_missing_element_from_second_list() { + let list_one: &[i32] = &[1, 3]; + let list_two: &[i32] = &[1, 2, 3]; + let output = sublist::sublist(list_one, list_two); + let expected = Comparison::Unequal; + assert_eq!(output, expected); } #[test] #[ignore] fn second_list_missing_element_from_first_list() { - assert_eq!(Comparison::Unequal, sublist(&[1, 2, 3], &[1, 3])); + let list_one: &[i32] = &[1, 2, 3]; + let list_two: &[i32] = &[1, 3]; + let output = sublist::sublist(list_one, list_two); + let expected = Comparison::Unequal; + assert_eq!(output, expected); } #[test] #[ignore] -fn superlist_early_in_huge_list() { - let huge: Vec = (1..1_000_000).collect(); - - assert_eq!(Comparison::Superlist, sublist(&huge, &[3, 4, 5])); +fn first_list_missing_additional_digits_from_second_list() { + let list_one: &[i32] = &[1, 2]; + let list_two: &[i32] = &[1, 22]; + let output = sublist::sublist(list_one, list_two); + let expected = Comparison::Unequal; + assert_eq!(output, expected); } #[test] #[ignore] -fn recurring_values_sublist() { - assert_eq!( - Comparison::Sublist, - sublist(&[1, 2, 1, 2, 3], &[1, 2, 3, 1, 2, 1, 2, 3, 2, 1]) - ); +fn order_matters_to_a_list() { + let list_one: &[i32] = &[1, 2, 3]; + let list_two: &[i32] = &[3, 2, 1]; + let output = sublist::sublist(list_one, list_two); + let expected = Comparison::Unequal; + assert_eq!(output, expected); } #[test] #[ignore] -fn recurring_values_unequal() { - assert_eq!( - Comparison::Unequal, - sublist(&[1, 2, 1, 2, 3], &[1, 2, 3, 1, 2, 3, 2, 3, 2, 1]) - ); +fn same_digits_but_different_numbers() { + let list_one: &[i32] = &[1, 0, 1]; + let list_two: &[i32] = &[10, 1]; + let output = sublist::sublist(list_one, list_two); + let expected = Comparison::Unequal; + assert_eq!(output, expected); } From f6a005b30e0d56e155d4526277e1076400ba8e56 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 14 Nov 2023 15:48:42 +0100 Subject: [PATCH 191/436] Sync saddle-point with problem-specifications (#1775) --- .../saddle-points/.docs/instructions.md | 6 +- .../saddle-points/.docs/introduction.md | 12 +- .../practice/saddle-points/.meta/config.json | 2 +- .../saddle-points/.meta/test_template.tera | 24 ++++ .../practice/saddle-points/.meta/tests.toml | 40 ++++++- .../saddle-points/tests/saddle-points.rs | 110 +++++++----------- 6 files changed, 117 insertions(+), 77 deletions(-) create mode 100644 exercises/practice/saddle-points/.meta/test_template.tera diff --git a/exercises/practice/saddle-points/.docs/instructions.md b/exercises/practice/saddle-points/.docs/instructions.md index e2d746764..c585568b4 100644 --- a/exercises/practice/saddle-points/.docs/instructions.md +++ b/exercises/practice/saddle-points/.docs/instructions.md @@ -5,14 +5,14 @@ Your task is to find the potential trees where you could build your tree house. The data company provides the data as grids that show the heights of the trees. The rows of the grid represent the east-west direction, and the columns represent the north-south direction. -An acceptable tree will be the the largest in its row, while being the smallest in its column. +An acceptable tree will be the largest in its row, while being the smallest in its column. A grid might not have any good trees at all. Or it might have one, or even several. Here is a grid that has exactly one candidate tree. -``` +```text 1 2 3 4 |----------- 1 | 9 8 7 8 @@ -20,7 +20,7 @@ Here is a grid that has exactly one candidate tree. 3 | 6 6 7 1 ``` -- Row 2 has values 5, 3, and 1. The largest value is 5. +- Row 2 has values 5, 3, 2, and 4. The largest value is 5. - Column 1 has values 9, 5, and 6. The smallest value is 5. So the point at `[2, 1]` (row: 2, column: 1) is a great spot for a tree house. diff --git a/exercises/practice/saddle-points/.docs/introduction.md b/exercises/practice/saddle-points/.docs/introduction.md index b582efbd2..34b2c77e0 100644 --- a/exercises/practice/saddle-points/.docs/introduction.md +++ b/exercises/practice/saddle-points/.docs/introduction.md @@ -1,9 +1,11 @@ # Introduction -You are planning on building a tree house in the woods near your house so that you can watch the sun rise and set. +You plan to build a tree house in the woods near your house so that you can watch the sun rise and set. -You've obtained data from a local survey company that shows the heights of all the trees in each rectangular section of the map. -You need to analyze each grid on the map to find the perfect tree for your tree house. +You've obtained data from a local survey company that show the height of every tree in each rectangular section of the map. +You need to analyze each grid on the map to find good trees for your tree house. -The best tree will be the tallest tree compared to all the other trees to the east and west, so that you have the best possible view of the sunrises and sunsets. -You don't like climbing too much, so the perfect tree will also be the shortest among all the trees to the north and to the south. +A good tree is both: + +- taller than every tree to the east and west, so that you have the best possible view of the sunrises and sunsets. +- shorter than every tree to the north and south, to minimize the amount of tree climbing. diff --git a/exercises/practice/saddle-points/.meta/config.json b/exercises/practice/saddle-points/.meta/config.json index 20499074a..f9a4c9573 100644 --- a/exercises/practice/saddle-points/.meta/config.json +++ b/exercises/practice/saddle-points/.meta/config.json @@ -36,5 +36,5 @@ }, "blurb": "Detect saddle points in a matrix.", "source": "J Dalbey's Programming Practice problems", - "source_url": "http://users.csc.calpoly.edu/~jdalbey/103/Projects/ProgrammingPractice.html" + "source_url": "https://users.csc.calpoly.edu/~jdalbey/103/Projects/ProgrammingPractice.html" } diff --git a/exercises/practice/saddle-points/.meta/test_template.tera b/exercises/practice/saddle-points/.meta/test_template.tera new file mode 100644 index 000000000..20dc39e80 --- /dev/null +++ b/exercises/practice/saddle-points/.meta/test_template.tera @@ -0,0 +1,24 @@ +// We don't care about order +fn find_sorted_saddle_points(input: &[Vec]) -> Vec<(usize, usize)> { + let mut result = saddle_points::find_saddle_points(input); + result.sort_unstable(); + result +} +{% for test in cases %} +#[test] +{% if loop.index != 1 -%} +#[ignore] +{% endif -%} +fn {{ test.description | slugify | replace(from="-", to="_") }}() { + let input = &[{% for row in test.input.matrix %} + vec!{{ row }}, + {% endfor %}]; + let output = find_sorted_saddle_points(input); + let expected = &[ + {% for p in test.expected | sort(attribute = "column") | sort(attribute = "row") %} + ({{ p.row - 1 }}, {{ p.column - 1 }}), + {% endfor %} + ]; + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/saddle-points/.meta/tests.toml b/exercises/practice/saddle-points/.meta/tests.toml index be690e975..ca0085202 100644 --- a/exercises/practice/saddle-points/.meta/tests.toml +++ b/exercises/practice/saddle-points/.meta/tests.toml @@ -1,3 +1,37 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[3e374e63-a2e0-4530-a39a-d53c560382bd] +description = "Can identify single saddle point" + +[6b501e2b-6c1f-491f-b1bb-7f278f760534] +description = "Can identify that empty matrix has no saddle points" + +[8c27cc64-e573-4fcb-a099-f0ae863fb02f] +description = "Can identify lack of saddle points when there are none" + +[6d1399bd-e105-40fd-a2c9-c6609507d7a3] +description = "Can identify multiple saddle points in a column" + +[3e81dce9-53b3-44e6-bf26-e328885fd5d1] +description = "Can identify multiple saddle points in a row" + +[88868621-b6f4-4837-bb8b-3fad8b25d46b] +description = "Can identify saddle point in bottom right corner" + +[5b9499ca-fcea-4195-830a-9c4584a0ee79] +description = "Can identify saddle points in a non square matrix" + +[ee99ccd2-a1f1-4283-ad39-f8c70f0cf594] +description = "Can identify that saddle points in a single column matrix are those with the minimum value" + +[63abf709-a84b-407f-a1b3-456638689713] +description = "Can identify that saddle points in a single row matrix are those with the maximum value" diff --git a/exercises/practice/saddle-points/tests/saddle-points.rs b/exercises/practice/saddle-points/tests/saddle-points.rs index 3bed9af77..730772d41 100644 --- a/exercises/practice/saddle-points/tests/saddle-points.rs +++ b/exercises/practice/saddle-points/tests/saddle-points.rs @@ -1,5 +1,3 @@ -use saddle_points::find_saddle_points; - // We don't care about order fn find_sorted_saddle_points(input: &[Vec]) -> Vec<(usize, usize)> { let mut result = saddle_points::find_saddle_points(input); @@ -8,99 +6,81 @@ fn find_sorted_saddle_points(input: &[Vec]) -> Vec<(usize, usize)> { } #[test] -fn identify_single_saddle_point() { - let input = vec![vec![9, 8, 7], vec![5, 3, 2], vec![6, 6, 7]]; - assert_eq!(vec![(1, 0)], find_saddle_points(&input)); -} - -#[test] -#[ignore] -fn identify_empty_matrix() { - let input = vec![vec![], vec![], vec![]]; - let expected: Vec<(usize, usize)> = Vec::new(); - assert_eq!(expected, find_saddle_points(&input)); -} - -#[test] -#[ignore] -fn identify_lack_of_saddle_point() { - let input = vec![vec![1, 2, 3], vec![3, 1, 2], vec![2, 3, 1]]; - let expected: Vec<(usize, usize)> = Vec::new(); - assert_eq!(expected, find_saddle_points(&input)); +fn can_identify_single_saddle_point() { + let input = &[vec![9, 8, 7], vec![5, 3, 2], vec![6, 6, 7]]; + let output = find_sorted_saddle_points(input); + let expected = &[(1, 0)]; + assert_eq!(output, expected); } #[test] #[ignore] -fn multiple_saddle_points_in_col() { - let input = vec![vec![4, 5, 4], vec![3, 5, 5], vec![1, 5, 4]]; - assert_eq!( - vec![(0, 1), (1, 1), (2, 1)], - find_sorted_saddle_points(&input) - ); +fn can_identify_that_empty_matrix_has_no_saddle_points() { + let input = &[vec![]]; + let output = find_sorted_saddle_points(input); + let expected = &[]; + assert_eq!(output, expected); } #[test] #[ignore] -fn multiple_saddle_points_in_row() { - let input = vec![vec![6, 7, 8], vec![5, 5, 5], vec![7, 5, 6]]; - assert_eq!( - vec![(1, 0), (1, 1), (1, 2)], - find_sorted_saddle_points(&input) - ); +fn can_identify_lack_of_saddle_points_when_there_are_none() { + let input = &[vec![1, 2, 3], vec![3, 1, 2], vec![2, 3, 1]]; + let output = find_sorted_saddle_points(input); + let expected = &[]; + assert_eq!(output, expected); } #[test] #[ignore] -fn identify_bottom_right_saddle_point() { - let input = vec![vec![8, 7, 9], vec![6, 7, 6], vec![3, 2, 5]]; - assert_eq!(vec![(2, 2)], find_saddle_points(&input)); +fn can_identify_multiple_saddle_points_in_a_column() { + let input = &[vec![4, 5, 4], vec![3, 5, 5], vec![1, 5, 4]]; + let output = find_sorted_saddle_points(input); + let expected = &[(0, 1), (1, 1), (2, 1)]; + assert_eq!(output, expected); } -// track specific as of v1.3 #[test] #[ignore] -fn non_square_matrix_high() { - let input = vec![vec![1, 5], vec![3, 6], vec![2, 7], vec![3, 8]]; - assert_eq!(vec![(0, 1)], find_saddle_points(&input)); +fn can_identify_multiple_saddle_points_in_a_row() { + let input = &[vec![6, 7, 8], vec![5, 5, 5], vec![7, 5, 6]]; + let output = find_sorted_saddle_points(input); + let expected = &[(1, 0), (1, 1), (1, 2)]; + assert_eq!(output, expected); } #[test] #[ignore] -fn non_square_matrix_wide() { - let input = vec![vec![3, 1, 3], vec![3, 2, 4]]; - assert_eq!(vec![(0, 0), (0, 2)], find_sorted_saddle_points(&input)); +fn can_identify_saddle_point_in_bottom_right_corner() { + let input = &[vec![8, 7, 9], vec![6, 7, 6], vec![3, 2, 5]]; + let output = find_sorted_saddle_points(input); + let expected = &[(2, 2)]; + assert_eq!(output, expected); } #[test] #[ignore] -fn single_column_matrix() { - let input = vec![vec![2], vec![1], vec![4], vec![1]]; - assert_eq!(vec![(1, 0), (3, 0)], find_sorted_saddle_points(&input)); +fn can_identify_saddle_points_in_a_non_square_matrix() { + let input = &[vec![3, 1, 3], vec![3, 2, 4]]; + let output = find_sorted_saddle_points(input); + let expected = &[(0, 0), (0, 2)]; + assert_eq!(output, expected); } #[test] #[ignore] -fn single_row_matrix() { - let input = vec![vec![2, 5, 3, 5]]; - assert_eq!(vec![(0, 1), (0, 3)], find_sorted_saddle_points(&input)); +fn can_identify_that_saddle_points_in_a_single_column_matrix_are_those_with_the_minimum_value() { + let input = &[vec![2], vec![1], vec![4], vec![1]]; + let output = find_sorted_saddle_points(input); + let expected = &[(1, 0), (3, 0)]; + assert_eq!(output, expected); } #[test] #[ignore] -fn identify_all_saddle_points() { - let input = vec![vec![5, 5, 5], vec![5, 5, 5], vec![5, 5, 5]]; - assert_eq!( - vec![ - (0, 0), - (0, 1), - (0, 2), - (1, 0), - (1, 1), - (1, 2), - (2, 0), - (2, 1), - (2, 2) - ], - find_sorted_saddle_points(&input) - ); +fn can_identify_that_saddle_points_in_a_single_row_matrix_are_those_with_the_maximum_value() { + let input = &[vec![2, 5, 3, 5]]; + let output = find_sorted_saddle_points(input); + let expected = &[(0, 1), (0, 3)]; + assert_eq!(output, expected); } From a8d71fe17dc91e362548a32ab14a12b3011173e5 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 14 Nov 2023 15:48:57 +0100 Subject: [PATCH 192/436] Sync say with problem-specifications (#1773) --- exercises/practice/say/.docs/instructions.md | 27 +-- .../practice/say/.meta/additional-tests.json | 20 +++ exercises/practice/say/.meta/config.json | 4 +- .../practice/say/.meta/test_template.tera | 12 ++ exercises/practice/say/.meta/tests.toml | 62 ++++++- exercises/practice/say/tests/say.rs | 169 +++++++++--------- 6 files changed, 189 insertions(+), 105 deletions(-) create mode 100644 exercises/practice/say/.meta/additional-tests.json create mode 100644 exercises/practice/say/.meta/test_template.tera diff --git a/exercises/practice/say/.docs/instructions.md b/exercises/practice/say/.docs/instructions.md index 727b0186d..fb4a6dfb9 100644 --- a/exercises/practice/say/.docs/instructions.md +++ b/exercises/practice/say/.docs/instructions.md @@ -6,11 +6,9 @@ Given a number from 0 to 999,999,999,999, spell out that number in English. Handle the basic case of 0 through 99. -If the input to the program is `22`, then the output should be -`'twenty-two'`. +If the input to the program is `22`, then the output should be `'twenty-two'`. -Your program should complain loudly if given a number outside the -blessed range. +Your program should complain loudly if given a number outside the blessed range. Some good test cases for this program are: @@ -23,15 +21,14 @@ Some good test cases for this program are: ### Extension -If you're on a Mac, shell out to Mac OS X's `say` program to talk out -loud. If you're on Linux or Windows, eSpeakNG may be available with the command `espeak`. +If you're on a Mac, shell out to Mac OS X's `say` program to talk out loud. +If you're on Linux or Windows, eSpeakNG may be available with the command `espeak`. ## Step 2 Implement breaking a number up into chunks of thousands. -So `1234567890` should yield a list like 1, 234, 567, and 890, while the -far simpler `1000` should yield just 1 and 0. +So `1234567890` should yield a list like 1, 234, 567, and 890, while the far simpler `1000` should yield just 1 and 0. The program must also report any values that are out of range. @@ -41,8 +38,8 @@ Now handle inserting the appropriate scale word between those chunks. So `1234567890` should yield `'1 billion 234 million 567 thousand 890'` -The program must also report any values that are out of range. It's -fine to stop at "trillion". +The program must also report any values that are out of range. +It's fine to stop at "trillion". ## Step 4 @@ -51,13 +48,3 @@ Put it all together to get nothing but plain English. `12345` should give `twelve thousand three hundred forty-five`. The program must also report any values that are out of range. - -### Extensions - -Use _and_ (correctly) when spelling out the number in English: - -- 14 becomes "fourteen". -- 100 becomes "one hundred". -- 120 becomes "one hundred and twenty". -- 1002 becomes "one thousand and two". -- 1323 becomes "one thousand three hundred and twenty-three". diff --git a/exercises/practice/say/.meta/additional-tests.json b/exercises/practice/say/.meta/additional-tests.json new file mode 100644 index 000000000..6df96e614 --- /dev/null +++ b/exercises/practice/say/.meta/additional-tests.json @@ -0,0 +1,20 @@ +[ + { + "uuid": "ecafab60-89f2-4680-9286-56b423e8a7b9", + "description": "max i64", + "property": "say", + "input": { + "number": 9223372036854775807 + }, + "expected": "nine quintillion two hundred twenty-three quadrillion three hundred seventy-two trillion thirty-six billion eight hundred fifty-four million seven hundred seventy-five thousand eight hundred seven" + }, + { + "uuid": "88808dac-dffb-46a6-95d4-68d5271a9c38", + "description": "max u64", + "property": "say", + "input": { + "number": 18446744073709551615 + }, + "expected": "eighteen quintillion four hundred forty-six quadrillion seven hundred forty-four trillion seventy-three billion seven hundred nine million five hundred fifty-one thousand six hundred fifteen" + } +] \ No newline at end of file diff --git a/exercises/practice/say/.meta/config.json b/exercises/practice/say/.meta/config.json index 42c063967..195df14da 100644 --- a/exercises/practice/say/.meta/config.json +++ b/exercises/practice/say/.meta/config.json @@ -33,6 +33,6 @@ ] }, "blurb": "Given a number from 0 to 999,999,999,999, spell out that number in English.", - "source": "A variation on JavaRanch CattleDrive, exercise 4a", - "source_url": "http://www.javaranch.com/say.jsp" + "source": "A variation on the JavaRanch CattleDrive, Assignment 4", + "source_url": "https://coderanch.com/wiki/718804" } diff --git a/exercises/practice/say/.meta/test_template.tera b/exercises/practice/say/.meta/test_template.tera new file mode 100644 index 000000000..51753e6f7 --- /dev/null +++ b/exercises/practice/say/.meta/test_template.tera @@ -0,0 +1,12 @@ +{% for test in cases %} +#[test] +{% if loop.index != 1 -%} +#[ignore] +{% endif -%} +fn {{ test.description | slugify | replace(from="-", to="_") }}() { + let input = {{ test.input.number | json_encode() }}; + let output = {{ crate_name }}::{{ fn_names[0] }}(input); + let expected = {{ test.expected | json_encode() }}; + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/say/.meta/tests.toml b/exercises/practice/say/.meta/tests.toml index 6ef482ff1..1d2c4d469 100644 --- a/exercises/practice/say/.meta/tests.toml +++ b/exercises/practice/say/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [5d22a120-ba0c-428c-bd25-8682235d83e8] description = "zero" @@ -13,3 +20,52 @@ description = "fourteen" [f541dd8e-f070-4329-92b4-b7ce2fcf06b4] description = "twenty" + +[d78601eb-4a84-4bfa-bf0e-665aeb8abe94] +description = "twenty-two" + +[f010d4ca-12c9-44e9-803a-27789841adb1] +description = "thirty" + +[738ce12d-ee5c-4dfb-ad26-534753a98327] +description = "ninety-nine" + +[e417d452-129e-4056-bd5b-6eb1df334dce] +description = "one hundred" + +[d6924f30-80ba-4597-acf6-ea3f16269da8] +description = "one hundred twenty-three" + +[2f061132-54bc-4fd4-b5df-0a3b778959b9] +description = "two hundred" + +[feed6627-5387-4d38-9692-87c0dbc55c33] +description = "nine hundred ninety-nine" + +[3d83da89-a372-46d3-b10d-de0c792432b3] +description = "one thousand" + +[865af898-1d5b-495f-8ff0-2f06d3c73709] +description = "one thousand two hundred thirty-four" + +[b6a3f442-266e-47a3-835d-7f8a35f6cf7f] +description = "one million" + +[2cea9303-e77e-4212-b8ff-c39f1978fc70] +description = "one million two thousand three hundred forty-five" + +[3e240eeb-f564-4b80-9421-db123f66a38f] +description = "one billion" + +[9a43fed1-c875-4710-8286-5065d73b8a9e] +description = "a big number" + +[49a6a17b-084e-423e-994d-a87c0ecc05ef] +description = "numbers below zero are out of range" +include = false +comment = "explanation in .docs/instructions.append.md" + +[4d6492eb-5853-4d16-9d34-b0f61b261fd9] +description = "numbers above 999,999,999,999 are out of range" +include = false +comment = "explanation in .docs/instructions.append.md" diff --git a/exercises/practice/say/tests/say.rs b/exercises/practice/say/tests/say.rs index 02a17fc10..e7b002ca1 100644 --- a/exercises/practice/say/tests/say.rs +++ b/exercises/practice/say/tests/say.rs @@ -1,160 +1,169 @@ -// Note: No tests created using 'and' with numbers. -// Apparently Most American English does not use the 'and' with numbers, -// where it is common in British English to use the 'and'. - #[test] fn zero() { - assert_eq!(say::encode(0), String::from("zero")); -} - -// -// If the below test is uncommented, it should not compile. -// -/* -#[test] -#[ignore] -fn negative() { - assert_eq!(say::encode(-1), String::from("won't compile")); + let input = 0; + let output = say::encode(input); + let expected = "zero"; + assert_eq!(output, expected); } -*/ #[test] #[ignore] fn one() { - assert_eq!(say::encode(1), String::from("one")); + let input = 1; + let output = say::encode(input); + let expected = "one"; + assert_eq!(output, expected); } #[test] #[ignore] fn fourteen() { - assert_eq!(say::encode(14), String::from("fourteen")); + let input = 14; + let output = say::encode(input); + let expected = "fourteen"; + assert_eq!(output, expected); } #[test] #[ignore] fn twenty() { - assert_eq!(say::encode(20), String::from("twenty")); + let input = 20; + let output = say::encode(input); + let expected = "twenty"; + assert_eq!(output, expected); } #[test] #[ignore] fn twenty_two() { - assert_eq!(say::encode(22), String::from("twenty-two")); + let input = 22; + let output = say::encode(input); + let expected = "twenty-two"; + assert_eq!(output, expected); } #[test] #[ignore] -fn one_hundred() { - assert_eq!(say::encode(100), String::from("one hundred")); +fn thirty() { + let input = 30; + let output = say::encode(input); + let expected = "thirty"; + assert_eq!(output, expected); } -// note, using American style with no and #[test] #[ignore] -fn one_hundred_twenty() { - assert_eq!(say::encode(120), String::from("one hundred twenty")); +fn ninety_nine() { + let input = 99; + let output = say::encode(input); + let expected = "ninety-nine"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn one_hundred() { + let input = 100; + let output = say::encode(input); + let expected = "one hundred"; + assert_eq!(output, expected); } #[test] #[ignore] fn one_hundred_twenty_three() { - assert_eq!(say::encode(123), String::from("one hundred twenty-three")); + let input = 123; + let output = say::encode(input); + let expected = "one hundred twenty-three"; + assert_eq!(output, expected); } #[test] #[ignore] -fn one_thousand() { - assert_eq!(say::encode(1000), String::from("one thousand")); +fn two_hundred() { + let input = 200; + let output = say::encode(input); + let expected = "two hundred"; + assert_eq!(output, expected); } #[test] #[ignore] -fn one_thousand_two_hundred_thirty_four() { - assert_eq!( - say::encode(1234), - String::from("one thousand two hundred thirty-four") - ); +fn nine_hundred_ninety_nine() { + let input = 999; + let output = say::encode(input); + let expected = "nine hundred ninety-nine"; + assert_eq!(output, expected); } -// note, using American style with no and #[test] #[ignore] -fn eight_hundred_and_ten_thousand() { - assert_eq!( - say::encode(810_000), - String::from("eight hundred ten thousand") - ); +fn one_thousand() { + let input = 1000; + let output = say::encode(input); + let expected = "one thousand"; + assert_eq!(output, expected); } #[test] #[ignore] -fn one_million() { - assert_eq!(say::encode(1_000_000), String::from("one million")); +fn one_thousand_two_hundred_thirty_four() { + let input = 1234; + let output = say::encode(input); + let expected = "one thousand two hundred thirty-four"; + assert_eq!(output, expected); } -// note, using American style with no and #[test] #[ignore] -fn one_million_two() { - assert_eq!(say::encode(1_000_002), String::from("one million two")); +fn one_million() { + let input = 1000000; + let output = say::encode(input); + let expected = "one million"; + assert_eq!(output, expected); } #[test] #[ignore] fn one_million_two_thousand_three_hundred_forty_five() { - assert_eq!( - say::encode(1_002_345), - String::from("one million two thousand three hundred forty-five") - ); + let input = 1002345; + let output = say::encode(input); + let expected = "one million two thousand three hundred forty-five"; + assert_eq!(output, expected); } #[test] #[ignore] fn one_billion() { - assert_eq!(say::encode(1_000_000_000), String::from("one billion")); + let input = 1000000000; + let output = say::encode(input); + let expected = "one billion"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_987654321123() { - assert_eq!( - say::encode(987_654_321_123), - String::from( - "nine hundred eighty-seven billion \ - six hundred fifty-four million \ - three hundred twenty-one thousand \ - one hundred twenty-three" - ) - ); +fn a_big_number() { + let input = 987654321123; + let output = say::encode(input); + let expected = "nine hundred eighty-seven billion six hundred fifty-four million three hundred twenty-one thousand one hundred twenty-three"; + assert_eq!(output, expected); } -/* - These tests are only if you implemented full parsing for u64 type. -*/ #[test] #[ignore] fn max_i64() { - assert_eq!( - say::encode(9_223_372_036_854_775_807), - String::from( - "nine quintillion two hundred twenty-three \ - quadrillion three hundred seventy-two trillion \ - thirty-six billion eight hundred fifty-four million \ - seven hundred seventy-five thousand eight hundred seven" - ) - ); + let input = 9223372036854775807; + let output = say::encode(input); + let expected = "nine quintillion two hundred twenty-three quadrillion three hundred seventy-two trillion thirty-six billion eight hundred fifty-four million seven hundred seventy-five thousand eight hundred seven"; + assert_eq!(output, expected); } #[test] #[ignore] fn max_u64() { - assert_eq!( - say::encode(18_446_744_073_709_551_615), - String::from( - "eighteen quintillion four hundred forty-six \ - quadrillion seven hundred forty-four trillion \ - seventy-three billion seven hundred nine million \ - five hundred fifty-one thousand six hundred fifteen" - ) - ); + let input = 18446744073709551615; + let output = say::encode(input); + let expected = "eighteen quintillion four hundred forty-six quadrillion seven hundred forty-four trillion seventy-three billion seven hundred nine million five hundred fifty-one thousand six hundred fifteen"; + assert_eq!(output, expected); } From ffa30af71b466bbf38b92a351048f111cdcf60de Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Fri, 17 Nov 2023 11:03:24 +0100 Subject: [PATCH 193/436] Sync scrabble-score with problem-specifications (#1772) The additional tests were not upstreamed, because they are unicode related. Handling unicode is tedious and out-of-scope for many languages. --- .../.meta/additional-tests.json | 20 ++++ .../scrabble-score/.meta/test_template.tera | 12 +++ .../practice/scrabble-score/.meta/tests.toml | 43 ++++++++- .../scrabble-score/tests/scrabble-score.rs | 91 ++++++++++++++----- 4 files changed, 138 insertions(+), 28 deletions(-) create mode 100644 exercises/practice/scrabble-score/.meta/additional-tests.json create mode 100644 exercises/practice/scrabble-score/.meta/test_template.tera diff --git a/exercises/practice/scrabble-score/.meta/additional-tests.json b/exercises/practice/scrabble-score/.meta/additional-tests.json new file mode 100644 index 000000000..6e5b33263 --- /dev/null +++ b/exercises/practice/scrabble-score/.meta/additional-tests.json @@ -0,0 +1,20 @@ +[ + { + "uuid": "9e0faee7-dc23-460b-afec-17c7145ae564", + "description": "non english scrabble letters do not score", + "property": "score", + "input": { + "word": "piñata" + }, + "expected": 7 + }, + { + "uuid": "3099e8fd-0077-4520-be33-54bb9a1c0dc7", + "description": "german letters do not score", + "property": "score", + "input": { + "word": "STRAßE" + }, + "expected": 5 + } +] diff --git a/exercises/practice/scrabble-score/.meta/test_template.tera b/exercises/practice/scrabble-score/.meta/test_template.tera new file mode 100644 index 000000000..093d6dc57 --- /dev/null +++ b/exercises/practice/scrabble-score/.meta/test_template.tera @@ -0,0 +1,12 @@ +{% for test in cases %} +#[test] +{% if loop.index != 1 -%} +#[ignore] +{% endif -%} +fn {{ test.description | slugify | replace(from="-", to="_") }}() { + let input = {{ test.input.word | json_encode() }}; + let output = {{ crate_name }}::{{ fn_names[0] }}(input); + let expected = {{ test.expected | json_encode() }}; + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/scrabble-score/.meta/tests.toml b/exercises/practice/scrabble-score/.meta/tests.toml index aa9692e2a..33a873c05 100644 --- a/exercises/practice/scrabble-score/.meta/tests.toml +++ b/exercises/practice/scrabble-score/.meta/tests.toml @@ -1,6 +1,43 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[f46cda29-1ca5-4ef2-bd45-388a767e3db2] +description = "lowercase letter" + +[f7794b49-f13e-45d1-a933-4e48459b2201] +description = "uppercase letter" + +[eaba9c76-f9fa-49c9-a1b0-d1ba3a5b31fa] +description = "valuable letter" + +[f3c8c94e-bb48-4da2-b09f-e832e103151e] +description = "short word" + +[71e3d8fa-900d-4548-930e-68e7067c4615] +description = "short, valuable word" [d3088ad9-570c-4b51-8764-c75d5a430e99] description = "medium word" + +[fa20c572-ad86-400a-8511-64512daac352] +description = "medium, valuable word" + +[9336f0ba-9c2b-4fa0-bd1c-2e2d328cf967] +description = "long, mixed-case word" + +[1e34e2c3-e444-4ea7-b598-3c2b46fd2c10] +description = "english-like word" + +[4efe3169-b3b6-4334-8bae-ff4ef24a7e4f] +description = "empty input" + +[3b305c1c-f260-4e15-a5b5-cb7d3ea7c3d7] +description = "entire alphabet available" diff --git a/exercises/practice/scrabble-score/tests/scrabble-score.rs b/exercises/practice/scrabble-score/tests/scrabble-score.rs index ab7439a92..b65f94e06 100644 --- a/exercises/practice/scrabble-score/tests/scrabble-score.rs +++ b/exercises/practice/scrabble-score/tests/scrabble-score.rs @@ -1,74 +1,115 @@ -use scrabble_score::*; - #[test] -fn a_is_worth_one_point() { - assert_eq!(score("a"), 1); +fn lowercase_letter() { + let input = "a"; + let output = scrabble_score::score(input); + let expected = 1; + assert_eq!(output, expected); } #[test] #[ignore] -fn scoring_is_case_insensitive() { - assert_eq!(score("A"), 1); +fn uppercase_letter() { + let input = "A"; + let output = scrabble_score::score(input); + let expected = 1; + assert_eq!(output, expected); } #[test] #[ignore] -fn f_is_worth_four() { - assert_eq!(score("f"), 4); +fn valuable_letter() { + let input = "f"; + let output = scrabble_score::score(input); + let expected = 4; + assert_eq!(output, expected); } #[test] #[ignore] -fn two_one_point_letters_make_a_two_point_word() { - assert_eq!(score("at"), 2); +fn short_word() { + let input = "at"; + let output = scrabble_score::score(input); + let expected = 2; + assert_eq!(output, expected); } #[test] #[ignore] -fn three_letter_word() { - assert_eq!(score("zoo"), 12); +fn short_valuable_word() { + let input = "zoo"; + let output = scrabble_score::score(input); + let expected = 12; + assert_eq!(output, expected); } #[test] #[ignore] fn medium_word() { - assert_eq!(score("street"), 6); + let input = "street"; + let output = scrabble_score::score(input); + let expected = 6; + assert_eq!(output, expected); } #[test] #[ignore] -fn longer_words_with_valuable_letters() { - assert_eq!(score("quirky"), 22); +fn medium_valuable_word() { + let input = "quirky"; + let output = scrabble_score::score(input); + let expected = 22; + assert_eq!(output, expected); } #[test] #[ignore] fn long_mixed_case_word() { - assert_eq!(score("OxyphenButazone"), 41); + let input = "OxyphenButazone"; + let output = scrabble_score::score(input); + let expected = 41; + assert_eq!(output, expected); } #[test] #[ignore] -fn non_english_scrabble_letters_do_not_score() { - assert_eq!(score("pinata"), 8, "'n' should score 1"); - assert_eq!(score("piñata"), 7, "'ñ' should score 0"); +fn english_like_word() { + let input = "pinata"; + let output = scrabble_score::score(input); + let expected = 8; + assert_eq!(output, expected); } #[test] #[ignore] -fn empty_words_are_worth_zero() { - assert_eq!(score(""), 0); +fn empty_input() { + let input = ""; + let output = scrabble_score::score(input); + let expected = 0; + assert_eq!(output, expected); } #[test] #[ignore] -fn all_letters_work() { - assert_eq!(score("abcdefghijklmnopqrstuvwxyz"), 87); +fn entire_alphabet_available() { + let input = "abcdefghijklmnopqrstuvwxyz"; + let output = scrabble_score::score(input); + let expected = 87; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn non_english_scrabble_letters_do_not_score() { + let input = "piñata"; + let output = scrabble_score::score(input); + let expected = 7; + assert_eq!(output, expected); } #[test] #[ignore] fn german_letters_do_not_score() { - assert_eq!(score("STRASSE"), 7, "\"SS\" should score 2"); - assert_eq!(score("STRAßE"), 5, "'ß' should score 0"); + let input = "STRAßE"; + let output = scrabble_score::score(input); + let expected = 5; + assert_eq!(output, expected); } From a0eebd014242ac0c90946a442df5842463941035 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Fri, 17 Nov 2023 14:19:42 +0100 Subject: [PATCH 194/436] Fix renamed clippy lint (#1777) --- exercises/practice/binary-search/tests/binary-search.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/binary-search/tests/binary-search.rs b/exercises/practice/binary-search/tests/binary-search.rs index 6795429e7..1473cf46e 100644 --- a/exercises/practice/binary-search/tests/binary-search.rs +++ b/exercises/practice/binary-search/tests/binary-search.rs @@ -3,7 +3,7 @@ // the borrows become needless. Since we want the tests to work // without clippy warnings for both people who take on the // additional challenge and people who don't, we disable this lint. -#![allow(clippy::needless_borrow)] +#![allow(clippy::needless_borrows_for_generic_args)] use binary_search::find; From debe15d6fd15830968dcf007e309a12944da4948 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Fri, 17 Nov 2023 15:13:04 +0100 Subject: [PATCH 195/436] Fix clippy warnings (#1778) --- exercises/practice/custom-set/.meta/example.rs | 6 +++--- justfile | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/exercises/practice/custom-set/.meta/example.rs b/exercises/practice/custom-set/.meta/example.rs index ecb66442d..9a2c918e5 100644 --- a/exercises/practice/custom-set/.meta/example.rs +++ b/exercises/practice/custom-set/.meta/example.rs @@ -49,8 +49,8 @@ impl CustomSet { &self .collection .iter() - .cloned() .filter(|c| other.contains(c)) + .cloned() .collect::>(), ) } @@ -61,8 +61,8 @@ impl CustomSet { &self .collection .iter() + .chain(other.collection.iter()) .cloned() - .chain(other.collection.iter().cloned()) .collect::>(), ) } @@ -73,8 +73,8 @@ impl CustomSet { &self .collection .iter() - .cloned() .filter(|c| !other.contains(c)) + .cloned() .collect::>(), ) } diff --git a/justfile b/justfile index eb2895877..61f517658 100644 --- a/justfile +++ b/justfile @@ -12,6 +12,7 @@ test: ./bin/lint_markdown.sh # TODO shellcheck ./bin/check_exercises.sh + CLIPPY=true ./bin/check_exercises.sh ./bin/ensure_stubs_compile.sh cd rust-tooling && cargo test # TODO format exercises From e3520b0bbe7c3190dd49665b5124c77503f2352a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20B=20Nagy?= <20251272+BNAndras@users.noreply.github.com> Date: Sat, 18 Nov 2023 02:08:59 -0800 Subject: [PATCH 196/436] Deprecate scale-generator (#1776) --- config.json | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/config.json b/config.json index 66e160a0e..be4a47491 100644 --- a/config.json +++ b/config.json @@ -1429,9 +1429,7 @@ "slug": "scale-generator", "name": "Scale Generator", "uuid": "b9436a89-90cb-4fb7-95b0-758f17c309ff", - "practices": [ - "enums" - ], + "practices": [], "prerequisites": [], "difficulty": 7, "topics": [ @@ -1439,7 +1437,8 @@ "music_theory", "to_primitive", "traits" - ] + ], + "status": "deprecated" }, { "slug": "dominoes", From 4cdb78a26b6a86039fa2676fc956e9b216a2ec1b Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Sat, 18 Nov 2023 12:00:49 +0100 Subject: [PATCH 197/436] Sync rotational-cipher with problem-specifications (#1780) The two tests with negative keys as input were removed. The exercise instructions specify a range of 0 to 26. The function signature of the skeleton was adjusted accordingly, using `u8` instead of `i8` to make negative numbers impossible. The removal of these tests was discussed on the forum: https://forum.exercism.org/t/rotational-cipher-negative-keys/8273 [no important files changed] --- .../rotational-cipher/.docs/instructions.md | 12 +-- .../rotational-cipher/.meta/example.rs | 4 +- .../.meta/test_template.tera | 14 +++ .../rotational-cipher/.meta/tests.toml | 34 ++++++- .../practice/rotational-cipher/src/lib.rs | 2 +- .../tests/rotational-cipher.rs | 95 +++++++++++-------- 6 files changed, 109 insertions(+), 52 deletions(-) create mode 100644 exercises/practice/rotational-cipher/.meta/test_template.tera diff --git a/exercises/practice/rotational-cipher/.docs/instructions.md b/exercises/practice/rotational-cipher/.docs/instructions.md index dbf6276f3..4bf64ca1d 100644 --- a/exercises/practice/rotational-cipher/.docs/instructions.md +++ b/exercises/practice/rotational-cipher/.docs/instructions.md @@ -2,11 +2,9 @@ Create an implementation of the rotational cipher, also sometimes called the Caesar cipher. -The Caesar cipher is a simple shift cipher that relies on -transposing all the letters in the alphabet using an integer key -between `0` and `26`. Using a key of `0` or `26` will always yield -the same output due to modular arithmetic. The letter is shifted -for as many values as the value of the key. +The Caesar cipher is a simple shift cipher that relies on transposing all the letters in the alphabet using an integer key between `0` and `26`. +Using a key of `0` or `26` will always yield the same output due to modular arithmetic. +The letter is shifted for as many values as the value of the key. The general notation for rotational ciphers is `ROT + `. The most commonly used rotational cipher is `ROT13`. @@ -24,8 +22,8 @@ Ciphertext is written out in the same formatting as the input including spaces a ## Examples -- ROT5 `omg` gives `trl` -- ROT0 `c` gives `c` +- ROT5 `omg` gives `trl` +- ROT0 `c` gives `c` - ROT26 `Cool` gives `Cool` - ROT13 `The quick brown fox jumps over the lazy dog.` gives `Gur dhvpx oebja sbk whzcf bire gur ynml qbt.` - ROT13 `Gur dhvpx oebja sbk whzcf bire gur ynml qbt.` gives `The quick brown fox jumps over the lazy dog.` diff --git a/exercises/practice/rotational-cipher/.meta/example.rs b/exercises/practice/rotational-cipher/.meta/example.rs index 44359ab95..c19c05688 100644 --- a/exercises/practice/rotational-cipher/.meta/example.rs +++ b/exercises/practice/rotational-cipher/.meta/example.rs @@ -1,6 +1,4 @@ -pub fn rotate(text: &str, key: i8) -> String { - let key = if key < 0 { (26 + key) as u8 } else { key as u8 }; - +pub fn rotate(text: &str, key: u8) -> String { text.chars() .map(|c| { if c.is_alphabetic() { diff --git a/exercises/practice/rotational-cipher/.meta/test_template.tera b/exercises/practice/rotational-cipher/.meta/test_template.tera new file mode 100644 index 000000000..561e69115 --- /dev/null +++ b/exercises/practice/rotational-cipher/.meta/test_template.tera @@ -0,0 +1,14 @@ +use rotational_cipher as cipher; +{% for test in cases %} +#[test] +{% if loop.index != 1 -%} +#[ignore] +{% endif -%} +fn {{ test.description | slugify | replace(from="-", to="_") }}() { + let text = {{ test.input.text | json_encode() }}; + let shift_key = {{ test.input.shiftKey | json_encode() }}; + let output = cipher::{{ fn_names[0] }}(text, shift_key); + let expected = {{ test.expected | json_encode() }}; + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/rotational-cipher/.meta/tests.toml b/exercises/practice/rotational-cipher/.meta/tests.toml index 12d669de4..53441ed22 100644 --- a/exercises/practice/rotational-cipher/.meta/tests.toml +++ b/exercises/practice/rotational-cipher/.meta/tests.toml @@ -1,6 +1,31 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[74e58a38-e484-43f1-9466-877a7515e10f] +description = "rotate a by 0, same output as input" + +[7ee352c6-e6b0-4930-b903-d09943ecb8f5] +description = "rotate a by 1" + +[edf0a733-4231-4594-a5ee-46a4009ad764] +description = "rotate a by 26, same output as input" + +[e3e82cb9-2a5b-403f-9931-e43213879300] +description = "rotate m by 13" + +[19f9eb78-e2ad-4da4-8fe3-9291d47c1709] +description = "rotate n by 13 with wrap around alphabet" + +[a116aef4-225b-4da9-884f-e8023ca6408a] +description = "rotate capital letters" [71b541bb-819c-4dc6-a9c3-132ef9bb737b] description = "rotate spaces" @@ -10,3 +35,6 @@ description = "rotate numbers" [32dd74f6-db2b-41a6-b02c-82eb4f93e549] description = "rotate punctuation" + +[9fb93fe6-42b0-46e6-9ec1-0bf0a062d8c9] +description = "rotate all letters" diff --git a/exercises/practice/rotational-cipher/src/lib.rs b/exercises/practice/rotational-cipher/src/lib.rs index 9629515d0..a1d0b53e5 100644 --- a/exercises/practice/rotational-cipher/src/lib.rs +++ b/exercises/practice/rotational-cipher/src/lib.rs @@ -1,4 +1,4 @@ -pub fn rotate(input: &str, key: i8) -> String { +pub fn rotate(input: &str, key: u8) -> String { todo!( "How would input text '{input}' transform when every letter is shifted using key '{key}'?" ); diff --git a/exercises/practice/rotational-cipher/tests/rotational-cipher.rs b/exercises/practice/rotational-cipher/tests/rotational-cipher.rs index f447495d0..d5bf7579d 100644 --- a/exercises/practice/rotational-cipher/tests/rotational-cipher.rs +++ b/exercises/practice/rotational-cipher/tests/rotational-cipher.rs @@ -1,81 +1,100 @@ use rotational_cipher as cipher; #[test] -fn rotate_a_1() { - assert_eq!("b", cipher::rotate("a", 1)); +fn rotate_a_by_0_same_output_as_input() { + let text = "a"; + let shift_key = 0; + let output = cipher::rotate(text, shift_key); + let expected = "a"; + assert_eq!(output, expected); } #[test] #[ignore] -fn rotate_a_26() { - assert_eq!("a", cipher::rotate("a", 26)); +fn rotate_a_by_1() { + let text = "a"; + let shift_key = 1; + let output = cipher::rotate(text, shift_key); + let expected = "b"; + assert_eq!(output, expected); } #[test] #[ignore] -fn rotate_a_0() { - assert_eq!("a", cipher::rotate("a", 0)); +fn rotate_a_by_26_same_output_as_input() { + let text = "a"; + let shift_key = 26; + let output = cipher::rotate(text, shift_key); + let expected = "a"; + assert_eq!(output, expected); } #[test] #[ignore] -fn rotate_m_13() { - assert_eq!("z", cipher::rotate("m", 13)); +fn rotate_m_by_13() { + let text = "m"; + let shift_key = 13; + let output = cipher::rotate(text, shift_key); + let expected = "z"; + assert_eq!(output, expected); } #[test] #[ignore] -fn rotate_n_13_with_wrap() { - assert_eq!("a", cipher::rotate("n", 13)); +fn rotate_n_by_13_with_wrap_around_alphabet() { + let text = "n"; + let shift_key = 13; + let output = cipher::rotate(text, shift_key); + let expected = "a"; + assert_eq!(output, expected); } #[test] #[ignore] -fn rotate_caps() { - assert_eq!("TRL", cipher::rotate("OMG", 5)); +fn rotate_capital_letters() { + let text = "OMG"; + let shift_key = 5; + let output = cipher::rotate(text, shift_key); + let expected = "TRL"; + assert_eq!(output, expected); } #[test] #[ignore] fn rotate_spaces() { - assert_eq!("T R L", cipher::rotate("O M G", 5)); + let text = "O M G"; + let shift_key = 5; + let output = cipher::rotate(text, shift_key); + let expected = "T R L"; + assert_eq!(output, expected); } #[test] #[ignore] fn rotate_numbers() { - assert_eq!( - "Xiwxmrk 1 2 3 xiwxmrk", - cipher::rotate("Testing 1 2 3 testing", 4) - ); + let text = "Testing 1 2 3 testing"; + let shift_key = 4; + let output = cipher::rotate(text, shift_key); + let expected = "Xiwxmrk 1 2 3 xiwxmrk"; + assert_eq!(output, expected); } #[test] #[ignore] fn rotate_punctuation() { - assert_eq!( - "Gzo\'n zvo, Bmviyhv!", - cipher::rotate("Let\'s eat, Grandma!", 21) - ); + let text = "Let's eat, Grandma!"; + let shift_key = 21; + let output = cipher::rotate(text, shift_key); + let expected = "Gzo'n zvo, Bmviyhv!"; + assert_eq!(output, expected); } #[test] #[ignore] -fn rotate_all_the_letters() { - assert_eq!( - "Gur dhvpx oebja sbk whzcf bire gur ynml qbt.", - cipher::rotate("The quick brown fox jumps over the lazy dog.", 13) - ); -} - -#[test] -#[ignore] -fn rotate_m_negative_1() { - assert_eq!("l", cipher::rotate("m", -1)); -} - -#[test] -#[ignore] -fn rotate_letters_negative_26() { - assert_eq!("omg", cipher::rotate("omg", -26)); +fn rotate_all_letters() { + let text = "The quick brown fox jumps over the lazy dog."; + let shift_key = 13; + let output = cipher::rotate(text, shift_key); + let expected = "Gur dhvpx oebja sbk whzcf bire gur ynml qbt."; + assert_eq!(output, expected); } From 70562b87af32d2b9e62964835c940c6a045767fe Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Mon, 20 Nov 2023 10:24:48 +0100 Subject: [PATCH 198/436] Sync run-length-encoding with problem-specifications (#1779) [no important files changed] --- .../run-length-encoding/.docs/instructions.md | 12 +-- .../.meta/test_template.tera | 18 ++++ .../run-length-encoding/.meta/tests.toml | 52 ++++++++++- .../tests/run-length-encoding.rs | 86 ++++++++++++------- rust-tooling/src/exercise_config.rs | 2 +- 5 files changed, 127 insertions(+), 43 deletions(-) create mode 100644 exercises/practice/run-length-encoding/.meta/test_template.tera diff --git a/exercises/practice/run-length-encoding/.docs/instructions.md b/exercises/practice/run-length-encoding/.docs/instructions.md index 95f7a9d69..fc8ce0569 100644 --- a/exercises/practice/run-length-encoding/.docs/instructions.md +++ b/exercises/practice/run-length-encoding/.docs/instructions.md @@ -2,8 +2,7 @@ Implement run-length encoding and decoding. -Run-length encoding (RLE) is a simple form of data compression, where runs -(consecutive data elements) are replaced by just one data value and count. +Run-length encoding (RLE) is a simple form of data compression, where runs (consecutive data elements) are replaced by just one data value and count. For example we can represent the original 53 characters with only 13. @@ -11,14 +10,11 @@ For example we can represent the original 53 characters with only 13. "WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB" -> "12WB12W3B24WB" ``` -RLE allows the original data to be perfectly reconstructed from -the compressed data, which makes it a lossless data compression. +RLE allows the original data to be perfectly reconstructed from the compressed data, which makes it a lossless data compression. ```text "AABCCCDEEEE" -> "2AB3CD4E" -> "AABCCCDEEEE" ``` -For simplicity, you can assume that the unencoded string will only contain -the letters A through Z (either lower or upper case) and whitespace. This way -data to be encoded will never contain any numbers and numbers inside data to -be decoded always represent the count for the following character. +For simplicity, you can assume that the unencoded string will only contain the letters A through Z (either lower or upper case) and whitespace. +This way data to be encoded will never contain any numbers and numbers inside data to be decoded always represent the count for the following character. diff --git a/exercises/practice/run-length-encoding/.meta/test_template.tera b/exercises/practice/run-length-encoding/.meta/test_template.tera new file mode 100644 index 000000000..15e8ffe88 --- /dev/null +++ b/exercises/practice/run-length-encoding/.meta/test_template.tera @@ -0,0 +1,18 @@ +use run_length_encoding as rle; + +{% for test in cases %} +#[test] +{% if loop.index != 1 -%} +#[ignore] +{% endif -%} +fn {{ test.property }}_{{ test.description | slugify | replace(from="-", to="_") }}() { + let input = {{ test.input.string | json_encode() }}; + {% if test.property == "consistency" -%} + let output = rle::decode(&rle::encode(input)); + {%- else -%} + let output = rle::{{ test.property }}(input); + {%- endif %} + let expected = {{ test.expected | json_encode() }}; + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/run-length-encoding/.meta/tests.toml b/exercises/practice/run-length-encoding/.meta/tests.toml index be690e975..7bdb80867 100644 --- a/exercises/practice/run-length-encoding/.meta/tests.toml +++ b/exercises/practice/run-length-encoding/.meta/tests.toml @@ -1,3 +1,49 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[ad53b61b-6ffc-422f-81a6-61f7df92a231] +description = "run-length encode a string -> empty string" + +[52012823-b7e6-4277-893c-5b96d42f82de] +description = "run-length encode a string -> single characters only are encoded without count" + +[b7868492-7e3a-415f-8da3-d88f51f80409] +description = "run-length encode a string -> string with no single characters" + +[859b822b-6e9f-44d6-9c46-6091ee6ae358] +description = "run-length encode a string -> single characters mixed with repeated characters" + +[1b34de62-e152-47be-bc88-469746df63b3] +description = "run-length encode a string -> multiple whitespace mixed in string" + +[abf176e2-3fbd-40ad-bb2f-2dd6d4df721a] +description = "run-length encode a string -> lowercase characters" + +[7ec5c390-f03c-4acf-ac29-5f65861cdeb5] +description = "run-length decode a string -> empty string" + +[ad23f455-1ac2-4b0e-87d0-b85b10696098] +description = "run-length decode a string -> single characters only" + +[21e37583-5a20-4a0e-826c-3dee2c375f54] +description = "run-length decode a string -> string with no single characters" + +[1389ad09-c3a8-4813-9324-99363fba429c] +description = "run-length decode a string -> single characters with repeated characters" + +[3f8e3c51-6aca-4670-b86c-a213bf4706b0] +description = "run-length decode a string -> multiple whitespace mixed in string" + +[29f721de-9aad-435f-ba37-7662df4fb551] +description = "run-length decode a string -> lowercase string" + +[2a762efd-8695-4e04-b0d6-9736899fbc16] +description = "encode and then decode -> encode followed by decode gives original string" diff --git a/exercises/practice/run-length-encoding/tests/run-length-encoding.rs b/exercises/practice/run-length-encoding/tests/run-length-encoding.rs index 5f8354738..4221b3a47 100644 --- a/exercises/practice/run-length-encoding/tests/run-length-encoding.rs +++ b/exercises/practice/run-length-encoding/tests/run-length-encoding.rs @@ -1,93 +1,117 @@ use run_length_encoding as rle; -// encoding tests - #[test] fn encode_empty_string() { - assert_eq!("", rle::encode("")); + let input = ""; + let output = rle::encode(input); + let expected = ""; + assert_eq!(output, expected); } #[test] #[ignore] -fn encode_single_characters() { - assert_eq!("XYZ", rle::encode("XYZ")); +fn encode_single_characters_only_are_encoded_without_count() { + let input = "XYZ"; + let output = rle::encode(input); + let expected = "XYZ"; + assert_eq!(output, expected); } #[test] #[ignore] fn encode_string_with_no_single_characters() { - assert_eq!("2A3B4C", rle::encode("AABBBCCCC")); + let input = "AABBBCCCC"; + let output = rle::encode(input); + let expected = "2A3B4C"; + assert_eq!(output, expected); } #[test] #[ignore] fn encode_single_characters_mixed_with_repeated_characters() { - assert_eq!( - "12WB12W3B24WB", - rle::encode("WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB") - ); + let input = "WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB"; + let output = rle::encode(input); + let expected = "12WB12W3B24WB"; + assert_eq!(output, expected); } #[test] #[ignore] fn encode_multiple_whitespace_mixed_in_string() { - assert_eq!("2 hs2q q2w2 ", rle::encode(" hsqq qww ")); + let input = " hsqq qww "; + let output = rle::encode(input); + let expected = "2 hs2q q2w2 "; + assert_eq!(output, expected); } #[test] #[ignore] fn encode_lowercase_characters() { - assert_eq!("2a3b4c", rle::encode("aabbbcccc")); + let input = "aabbbcccc"; + let output = rle::encode(input); + let expected = "2a3b4c"; + assert_eq!(output, expected); } -// decoding tests - #[test] #[ignore] fn decode_empty_string() { - assert_eq!("", rle::decode("")); + let input = ""; + let output = rle::decode(input); + let expected = ""; + assert_eq!(output, expected); } #[test] #[ignore] fn decode_single_characters_only() { - assert_eq!("XYZ", rle::decode("XYZ")); + let input = "XYZ"; + let output = rle::decode(input); + let expected = "XYZ"; + assert_eq!(output, expected); } #[test] #[ignore] fn decode_string_with_no_single_characters() { - assert_eq!("AABBBCCCC", rle::decode("2A3B4C")); + let input = "2A3B4C"; + let output = rle::decode(input); + let expected = "AABBBCCCC"; + assert_eq!(output, expected); } #[test] #[ignore] fn decode_single_characters_with_repeated_characters() { - assert_eq!( - "WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB", - rle::decode("12WB12W3B24WB") - ); + let input = "12WB12W3B24WB"; + let output = rle::decode(input); + let expected = "WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB"; + assert_eq!(output, expected); } #[test] #[ignore] fn decode_multiple_whitespace_mixed_in_string() { - assert_eq!(" hsqq qww ", rle::decode("2 hs2q q2w2 ")); + let input = "2 hs2q q2w2 "; + let output = rle::decode(input); + let expected = " hsqq qww "; + assert_eq!(output, expected); } #[test] #[ignore] -fn decode_lower_case_string() { - assert_eq!("aabbbcccc", rle::decode("2a3b4c")); +fn decode_lowercase_string() { + let input = "2a3b4c"; + let output = rle::decode(input); + let expected = "aabbbcccc"; + assert_eq!(output, expected); } -// consistency test - #[test] #[ignore] -fn consistency() { - assert_eq!( - "zzz ZZ zZ", - rle::decode(rle::encode("zzz ZZ zZ").as_str()) - ); +fn consistency_encode_followed_by_decode_gives_original_string() { + let input = "zzz ZZ zZ"; + let output = rle::decode(&rle::encode(input)); + let expected = "zzz ZZ zZ"; + assert_eq!(output, expected); } diff --git a/rust-tooling/src/exercise_config.rs b/rust-tooling/src/exercise_config.rs index 9ff9b6dae..c41d2807a 100644 --- a/rust-tooling/src/exercise_config.rs +++ b/rust-tooling/src/exercise_config.rs @@ -106,7 +106,7 @@ pub fn get_excluded_tests(slug: &str) -> Vec { let path = std::path::PathBuf::from("exercises/practice") .join(slug) .join(".meta/tests.toml"); - let contents = std::fs::read_to_string(&path).unwrap(); + let contents = std::fs::read_to_string(path).unwrap(); let mut excluded_tests = Vec::new(); From 03ad2d9969ead7ea9b212951d1bf69d89fdf17f6 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Mon, 20 Nov 2023 13:58:20 +0100 Subject: [PATCH 199/436] Sync rna-transcription with problem-specifications (#1782) There were some additional test which were kept. They are related to error handling, which is a big focus for Rust. So it seemed appropriate to keep these additional tests. [no important files changed] --- .../.meta/additional-tests.json | 38 +++++++ .../rna-transcription/.meta/config.json | 2 +- .../.meta/test_template.tera | 21 ++++ .../rna-transcription/.meta/tests.toml | 31 +++++- .../tests/rna-transcription.rs | 100 +++++++++--------- 5 files changed, 139 insertions(+), 53 deletions(-) create mode 100644 exercises/practice/rna-transcription/.meta/additional-tests.json create mode 100644 exercises/practice/rna-transcription/.meta/test_template.tera diff --git a/exercises/practice/rna-transcription/.meta/additional-tests.json b/exercises/practice/rna-transcription/.meta/additional-tests.json new file mode 100644 index 000000000..56e92a7d3 --- /dev/null +++ b/exercises/practice/rna-transcription/.meta/additional-tests.json @@ -0,0 +1,38 @@ +[ + { + "uuid": "6eedbb5c-12cb-4c8b-9f51-f8320b4dc2e7", + "description": "Invalid DNA input", + "property": "invalidDna", + "input": { + "dna": "U" + }, + "expected": 0 + }, + { + "uuid": "6eedbb5c-12cb-4c8b-9f51-f8320b4dc2e7", + "description": "Invalid DNA input at offset", + "property": "invalidDna", + "input": { + "dna": "ACGTUXXCTTAA" + }, + "expected": 4 + }, + { + "uuid": "6eedbb5c-12cb-4c8b-9f51-f8320b4dc2e7", + "description": "Invalid RNA input", + "property": "invalidRna", + "input": { + "dna": "T" + }, + "expected": 0 + }, + { + "uuid": "6eedbb5c-12cb-4c8b-9f51-f8320b4dc2e7", + "description": "Invalid RNA input at offset", + "property": "invalidRna", + "input": { + "dna": "ACGTUXXCTTAA" + }, + "expected": 3 + } +] diff --git a/exercises/practice/rna-transcription/.meta/config.json b/exercises/practice/rna-transcription/.meta/config.json index 4d3b4e329..e6d6f6967 100644 --- a/exercises/practice/rna-transcription/.meta/config.json +++ b/exercises/practice/rna-transcription/.meta/config.json @@ -43,5 +43,5 @@ }, "blurb": "Given a DNA strand, return its RNA Complement Transcription.", "source": "Hyperphysics", - "source_url": "http://hyperphysics.phy-astr.gsu.edu/hbase/Organic/transcription.html" + "source_url": "https://web.archive.org/web/20220408112140/http://hyperphysics.phy-astr.gsu.edu/hbase/Organic/transcription.html" } diff --git a/exercises/practice/rna-transcription/.meta/test_template.tera b/exercises/practice/rna-transcription/.meta/test_template.tera new file mode 100644 index 000000000..f85eac220 --- /dev/null +++ b/exercises/practice/rna-transcription/.meta/test_template.tera @@ -0,0 +1,21 @@ +use rna_transcription::{Dna, Rna}; +{% for test in cases %} +#[test] +{% if loop.index != 1 -%} +#[ignore] +{% endif -%} +fn {{ test.description | slugify | replace(from="-", to="_") }}() { + let input = {{ test.input.dna | json_encode() }}; +{% if test.property == "invalidDna" -%} + let output = Dna::new(input); + let expected = Err({{ test.expected }}); +{%- elif test.property == "invalidRna" -%} + let output = Rna::new(input); + let expected = Err({{ test.expected }}); +{%- else -%} + let output = Dna::new(input).unwrap().into_rna(); + let expected = Rna::new({{ test.expected | json_encode() }}).unwrap(); +{%- endif %} + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/rna-transcription/.meta/tests.toml b/exercises/practice/rna-transcription/.meta/tests.toml index be690e975..680051407 100644 --- a/exercises/practice/rna-transcription/.meta/tests.toml +++ b/exercises/practice/rna-transcription/.meta/tests.toml @@ -1,3 +1,28 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[b4631f82-c98c-4a2f-90b3-c5c2b6c6f661] +description = "Empty RNA sequence" + +[a9558a3c-318c-4240-9256-5d5ed47005a6] +description = "RNA complement of cytosine is guanine" + +[6eedbb5c-12cb-4c8b-9f51-f8320b4dc2e7] +description = "RNA complement of guanine is cytosine" + +[870bd3ec-8487-471d-8d9a-a25046488d3e] +description = "RNA complement of thymine is adenine" + +[aade8964-02e1-4073-872f-42d3ffd74c5f] +description = "RNA complement of adenine is uracil" + +[79ed2757-f018-4f47-a1d7-34a559392dbf] +description = "RNA complement" diff --git a/exercises/practice/rna-transcription/tests/rna-transcription.rs b/exercises/practice/rna-transcription/tests/rna-transcription.rs index 63e81c8e9..4d16cf279 100644 --- a/exercises/practice/rna-transcription/tests/rna-transcription.rs +++ b/exercises/practice/rna-transcription/tests/rna-transcription.rs @@ -1,88 +1,90 @@ -use rna_transcription as dna; +use rna_transcription::{Dna, Rna}; #[test] -fn valid_dna_input() { - assert!(dna::Dna::new("GCTA").is_ok()); +fn empty_rna_sequence() { + let input = ""; + let output = Dna::new(input).unwrap().into_rna(); + let expected = Rna::new("").unwrap(); + assert_eq!(output, expected); } #[test] #[ignore] -fn valid_rna_input() { - assert!(dna::Rna::new("CGAU").is_ok()); +fn rna_complement_of_cytosine_is_guanine() { + let input = "C"; + let output = Dna::new(input).unwrap().into_rna(); + let expected = Rna::new("G").unwrap(); + assert_eq!(output, expected); } #[test] #[ignore] -fn invalid_dna_input() { - // Invalid character - assert_eq!(dna::Dna::new("X").err(), Some(0)); - // Valid nucleotide, but invalid in context - assert_eq!(dna::Dna::new("U").err(), Some(0)); - // Longer string with contained errors - assert_eq!(dna::Dna::new("ACGTUXXCTTAA").err(), Some(4)); +fn rna_complement_of_guanine_is_cytosine() { + let input = "G"; + let output = Dna::new(input).unwrap().into_rna(); + let expected = Rna::new("C").unwrap(); + assert_eq!(output, expected); } #[test] #[ignore] -fn invalid_rna_input() { - // Invalid character - assert_eq!(dna::Rna::new("X").unwrap_err(), 0); - // Valid nucleotide, but invalid in context - assert_eq!(dna::Rna::new("T").unwrap_err(), 0); - // Longer string with contained errors - assert_eq!(dna::Rna::new("ACGUTTXCUUAA").unwrap_err(), 4); +fn rna_complement_of_thymine_is_adenine() { + let input = "T"; + let output = Dna::new(input).unwrap().into_rna(); + let expected = Rna::new("A").unwrap(); + assert_eq!(output, expected); } #[test] #[ignore] -fn acid_equals_acid() { - assert_eq!(dna::Dna::new("CGA").unwrap(), dna::Dna::new("CGA").unwrap()); - assert_ne!(dna::Dna::new("CGA").unwrap(), dna::Dna::new("AGC").unwrap()); - assert_eq!(dna::Rna::new("CGA").unwrap(), dna::Rna::new("CGA").unwrap()); - assert_ne!(dna::Rna::new("CGA").unwrap(), dna::Rna::new("AGC").unwrap()); +fn rna_complement_of_adenine_is_uracil() { + let input = "A"; + let output = Dna::new(input).unwrap().into_rna(); + let expected = Rna::new("U").unwrap(); + assert_eq!(output, expected); } #[test] #[ignore] -fn transcribes_cytosine_guanine() { - assert_eq!( - dna::Rna::new("G").unwrap(), - dna::Dna::new("C").unwrap().into_rna() - ); +fn rna_complement() { + let input = "ACGTGGTCTTAA"; + let output = Dna::new(input).unwrap().into_rna(); + let expected = Rna::new("UGCACCAGAAUU").unwrap(); + assert_eq!(output, expected); } #[test] #[ignore] -fn transcribes_guanine_cytosine() { - assert_eq!( - dna::Rna::new("C").unwrap(), - dna::Dna::new("G").unwrap().into_rna() - ); +fn invalid_dna_input() { + let input = "U"; + let output = Dna::new(input); + let expected = Err(0); + assert_eq!(output, expected); } #[test] #[ignore] -fn transcribes_adenine_uracil() { - assert_eq!( - dna::Rna::new("U").unwrap(), - dna::Dna::new("A").unwrap().into_rna() - ); +fn invalid_dna_input_at_offset() { + let input = "ACGTUXXCTTAA"; + let output = Dna::new(input); + let expected = Err(4); + assert_eq!(output, expected); } #[test] #[ignore] -fn transcribes_thymine_to_adenine() { - assert_eq!( - dna::Rna::new("A").unwrap(), - dna::Dna::new("T").unwrap().into_rna() - ); +fn invalid_rna_input() { + let input = "T"; + let output = Rna::new(input); + let expected = Err(0); + assert_eq!(output, expected); } #[test] #[ignore] -fn transcribes_all_dna_to_rna() { - assert_eq!( - dna::Rna::new("UGCACCAGAAUU").unwrap(), - dna::Dna::new("ACGTGGTCTTAA").unwrap().into_rna() - ) +fn invalid_rna_input_at_offset() { + let input = "ACGTUXXCTTAA"; + let output = Rna::new(input); + let expected = Err(3); + assert_eq!(output, expected); } From 098c092e456cc9537690345101689df2237b4e44 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Mon, 20 Nov 2023 14:12:36 +0100 Subject: [PATCH 200/436] Sync documentation and metadata with problem-specifications (#1783) Manual interventions: - The exercise clock had some Rust-specific stuff in `instructions.md`. This was moved to `instructions.append.md`. - Same with minesweeper. - The exercise queen-attack had additions to `instructions.md` which are not language specific. These were deleted. Upstreaming them will be discussed in a separate channel. If the additions are eventually upstreamed, they will find their way back into the Rust track. [no important files changed] --- .../practice/accumulate/.docs/instructions.md | 8 +- .../practice/accumulate/.meta/config.json | 2 +- .../all-your-base/.docs/instructions.md | 25 ++--- .../practice/allergies/.docs/instructions.md | 27 +++-- .../practice/allergies/.meta/config.json | 4 +- .../alphametics/.docs/instructions.md | 11 +- .../practice/anagram/.docs/instructions.md | 15 ++- .../armstrong-numbers/.docs/instructions.md | 8 +- .../atbash-cipher/.docs/instructions.md | 18 ++-- .../practice/atbash-cipher/.meta/config.json | 2 +- .../practice/beer-song/.docs/instructions.md | 14 --- .../practice/beer-song/.meta/config.json | 2 +- .../binary-search/.docs/instructions.md | 2 +- .../practice/binary-search/.meta/config.json | 2 +- exercises/practice/bob/.meta/config.json | 2 +- .../practice/bowling/.docs/instructions.md | 57 +++++----- exercises/practice/bowling/.meta/config.json | 2 +- .../circular-buffer/.docs/instructions.md | 101 ++++++++++-------- .../circular-buffer/.meta/config.json | 2 +- .../clock/.docs/instructions.append.md | 9 +- .../practice/clock/.docs/instructions.md | 2 - exercises/practice/clock/.meta/config.json | 1 - .../collatz-conjecture/.docs/instructions.md | 16 ++- .../crypto-square/.docs/instructions.md | 34 +++--- .../practice/crypto-square/.meta/config.json | 2 +- .../practice/diamond/.docs/instructions.md | 27 +++-- exercises/practice/diamond/.meta/config.json | 2 +- .../.docs/instructions.md | 9 +- .../difference-of-squares/.meta/config.json | 2 +- .../diffie-hellman/.docs/instructions.md | 25 +++-- .../practice/diffie-hellman/.meta/config.json | 2 +- .../practice/dominoes/.docs/instructions.md | 8 +- .../practice/dot-dsl/.docs/instructions.md | 29 +++-- exercises/practice/etl/.meta/config.json | 6 +- .../practice/forth/.docs/instructions.md | 21 ++-- .../practice/gigasecond/.meta/config.json | 2 +- .../grade-school/.docs/instructions.md | 33 ++---- .../practice/grains/.docs/instructions.md | 27 ++--- exercises/practice/grains/.meta/config.json | 4 +- exercises/practice/grep/.docs/instructions.md | 68 +++--------- exercises/practice/grep/.meta/config.json | 2 +- .../practice/hamming/.docs/instructions.md | 19 ++-- exercises/practice/hamming/.meta/config.json | 2 +- .../hello-world/.docs/instructions.md | 11 +- .../practice/hello-world/.meta/config.json | 4 +- .../high-scores/.docs/instructions.md | 3 +- .../isbn-verifier/.docs/instructions.md | 28 ++--- .../practice/isogram/.docs/instructions.md | 4 +- .../practice/knapsack/.docs/instructions.md | 5 +- .../largest-series-product/.meta/config.json | 2 +- exercises/practice/leap/.docs/instructions.md | 12 +-- exercises/practice/leap/.meta/config.json | 4 +- exercises/practice/luhn/.docs/instructions.md | 31 +++--- exercises/practice/luhn/.meta/config.json | 2 +- .../matching-brackets/.docs/instructions.md | 5 +- .../minesweeper/.docs/instructions.append.md | 10 ++ .../minesweeper/.docs/instructions.md | 30 ++---- .../practice/nth-prime/.docs/instructions.md | 6 +- .../practice/nth-prime/.meta/config.json | 2 +- .../nucleotide-count/.docs/instructions.md | 8 +- .../nucleotide-count/.meta/config.json | 2 +- .../ocr-numbers/.docs/instructions.md | 16 +-- .../practice/paasio/.docs/instructions.md | 9 +- .../palindrome-products/.docs/instructions.md | 21 ++-- .../palindrome-products/.meta/config.json | 2 +- .../practice/pangram/.docs/instructions.md | 2 +- .../.docs/instructions.md | 7 +- .../perfect-numbers/.docs/instructions.md | 13 ++- .../perfect-numbers/.meta/config.json | 2 +- .../phone-number/.docs/instructions.md | 13 ++- .../practice/phone-number/.meta/config.json | 4 +- .../practice/pig-latin/.docs/instructions.md | 20 ++-- .../practice/poker/.docs/instructions.md | 5 +- .../prime-factors/.docs/instructions.md | 26 +++-- .../practice/prime-factors/.meta/config.json | 2 +- .../protein-translation/.docs/instructions.md | 29 ++--- .../practice/proverb/.docs/instructions.md | 6 +- exercises/practice/proverb/.meta/config.json | 2 +- .../pythagorean-triplet/.docs/instructions.md | 7 +- .../pythagorean-triplet/.meta/config.json | 2 +- .../queen-attack/.docs/instructions.md | 65 +++-------- .../practice/queen-attack/.meta/config.json | 2 +- .../rail-fence-cipher/.docs/instructions.md | 10 +- .../practice/raindrops/.docs/instructions.md | 6 +- .../practice/react/.docs/instructions.md | 15 +-- .../practice/rectangles/.docs/instructions.md | 5 +- .../practice/robot-name/.docs/instructions.md | 12 +-- .../robot-simulator/.docs/instructions.md | 11 +- .../roman-numerals/.docs/instructions.md | 30 +++--- .../practice/roman-numerals/.meta/config.json | 2 +- .../secret-handshake/.docs/instructions.md | 1 + .../secret-handshake/.meta/config.json | 2 +- .../practice/series/.docs/instructions.md | 10 +- exercises/practice/series/.meta/config.json | 2 +- .../simple-cipher/.docs/instructions.md | 69 +++++------- .../practice/simple-cipher/.meta/config.json | 2 +- .../practice/triangle/.docs/instructions.md | 26 +++-- exercises/practice/triangle/.meta/config.json | 2 +- .../practice/wordy/.docs/instructions.md | 2 +- .../practice/yacht/.docs/instructions.md | 32 +++--- 100 files changed, 595 insertions(+), 724 deletions(-) create mode 100644 exercises/practice/minesweeper/.docs/instructions.append.md diff --git a/exercises/practice/accumulate/.docs/instructions.md b/exercises/practice/accumulate/.docs/instructions.md index 435e0b324..c25a03fab 100644 --- a/exercises/practice/accumulate/.docs/instructions.md +++ b/exercises/practice/accumulate/.docs/instructions.md @@ -1,9 +1,6 @@ # Instructions -Implement the `accumulate` operation, which, given a collection and an -operation to perform on each element of the collection, returns a new -collection containing the result of applying that operation to each element of -the input collection. +Implement the `accumulate` operation, which, given a collection and an operation to perform on each element of the collection, returns a new collection containing the result of applying that operation to each element of the input collection. Given the collection of numbers: @@ -21,6 +18,5 @@ Check out the test suite to see the expected function signature. ## Restrictions -Keep your hands off that collect/map/fmap/whatchamacallit functionality -provided by your standard library! +Keep your hands off that collect/map/fmap/whatchamacallit functionality provided by your standard library! Solve this one yourself using other basic tools instead. diff --git a/exercises/practice/accumulate/.meta/config.json b/exercises/practice/accumulate/.meta/config.json index 63054a41e..6f756d434 100644 --- a/exercises/practice/accumulate/.meta/config.json +++ b/exercises/practice/accumulate/.meta/config.json @@ -32,7 +32,7 @@ }, "blurb": "Implement the `accumulate` operation, which, given a collection and an operation to perform on each element of the collection, returns a new collection containing the result of applying that operation to each element of the input collection.", "source": "Conversation with James Edward Gray II", - "source_url": "https://twitter.com/jeg2", + "source_url": "http://graysoftinc.com/", "custom": { "allowed-to-not-compile": "Stub doesn't compile because there is no type specified for _function and _closure arguments. This exercise teaches students about the use of function pointers and closures, therefore specifying the type of arguments for them would reduce learning." } diff --git a/exercises/practice/all-your-base/.docs/instructions.md b/exercises/practice/all-your-base/.docs/instructions.md index c39686f28..4602b5cfa 100644 --- a/exercises/practice/all-your-base/.docs/instructions.md +++ b/exercises/practice/all-your-base/.docs/instructions.md @@ -2,31 +2,32 @@ Convert a number, represented as a sequence of digits in one base, to any other base. -Implement general base conversion. Given a number in base **a**, -represented as a sequence of digits, convert it to base **b**. +Implement general base conversion. +Given a number in base **a**, represented as a sequence of digits, convert it to base **b**. ## Note - Try to implement the conversion yourself. Do not use something else to perform the conversion for you. -## About [Positional Notation](https://en.wikipedia.org/wiki/Positional_notation) +## About [Positional Notation][positional-notation] -In positional notation, a number in base **b** can be understood as a linear -combination of powers of **b**. +In positional notation, a number in base **b** can be understood as a linear combination of powers of **b**. -The number 42, *in base 10*, means: +The number 42, _in base 10_, means: -(4 * 10^1) + (2 * 10^0) +`(4 * 10^1) + (2 * 10^0)` -The number 101010, *in base 2*, means: +The number 101010, _in base 2_, means: -(1 * 2^5) + (0 * 2^4) + (1 * 2^3) + (0 * 2^2) + (1 * 2^1) + (0 * 2^0) +`(1 * 2^5) + (0 * 2^4) + (1 * 2^3) + (0 * 2^2) + (1 * 2^1) + (0 * 2^0)` -The number 1120, *in base 3*, means: +The number 1120, _in base 3_, means: -(1 * 3^3) + (1 * 3^2) + (2 * 3^1) + (0 * 3^0) +`(1 * 3^3) + (1 * 3^2) + (2 * 3^1) + (0 * 3^0)` I think you got the idea! -*Yes. Those three numbers above are exactly the same. Congratulations!* +_Yes. Those three numbers above are exactly the same. Congratulations!_ + +[positional-notation]: https://en.wikipedia.org/wiki/Positional_notation diff --git a/exercises/practice/allergies/.docs/instructions.md b/exercises/practice/allergies/.docs/instructions.md index b8bbd5a3f..daf8cfde2 100644 --- a/exercises/practice/allergies/.docs/instructions.md +++ b/exercises/practice/allergies/.docs/instructions.md @@ -2,20 +2,18 @@ Given a person's allergy score, determine whether or not they're allergic to a given item, and their full list of allergies. -An allergy test produces a single numeric score which contains the -information about all the allergies the person has (that they were -tested for). +An allergy test produces a single numeric score which contains the information about all the allergies the person has (that they were tested for). The list of items (and their value) that were tested are: -* eggs (1) -* peanuts (2) -* shellfish (4) -* strawberries (8) -* tomatoes (16) -* chocolate (32) -* pollen (64) -* cats (128) +- eggs (1) +- peanuts (2) +- shellfish (4) +- strawberries (8) +- tomatoes (16) +- chocolate (32) +- pollen (64) +- cats (128) So if Tom is allergic to peanuts and chocolate, he gets a score of 34. @@ -24,7 +22,6 @@ Now, given just that score of 34, your program should be able to say: - Whether Tom is allergic to any one of those allergens listed above. - All the allergens Tom is allergic to. -Note: a given score may include allergens **not** listed above (i.e. -allergens that score 256, 512, 1024, etc.). Your program should -ignore those components of the score. For example, if the allergy -score is 257, your program should only report the eggs (1) allergy. +Note: a given score may include allergens **not** listed above (i.e. allergens that score 256, 512, 1024, etc.). +Your program should ignore those components of the score. +For example, if the allergy score is 257, your program should only report the eggs (1) allergy. diff --git a/exercises/practice/allergies/.meta/config.json b/exercises/practice/allergies/.meta/config.json index e09f00556..b39243c5e 100644 --- a/exercises/practice/allergies/.meta/config.json +++ b/exercises/practice/allergies/.meta/config.json @@ -35,6 +35,6 @@ ] }, "blurb": "Given a person's allergy score, determine whether or not they're allergic to a given item, and their full list of allergies.", - "source": "Jumpstart Lab Warm-up", - "source_url": "http://jumpstartlab.com/" + "source": "Exercise by the JumpstartLab team for students at The Turing School of Software and Design.", + "source_url": "https://turing.edu/" } diff --git a/exercises/practice/alphametics/.docs/instructions.md b/exercises/practice/alphametics/.docs/instructions.md index 6936c192d..649576ec7 100644 --- a/exercises/practice/alphametics/.docs/instructions.md +++ b/exercises/practice/alphametics/.docs/instructions.md @@ -2,8 +2,7 @@ Write a function to solve alphametics puzzles. -[Alphametics](https://en.wikipedia.org/wiki/Alphametics) is a puzzle where -letters in words are replaced with numbers. +[Alphametics][alphametics] is a puzzle where letters in words are replaced with numbers. For example `SEND + MORE = MONEY`: @@ -23,10 +22,10 @@ Replacing these with valid numbers gives: 1 0 6 5 2 ``` -This is correct because every letter is replaced by a different number and the -words, translated into numbers, then make a valid sum. +This is correct because every letter is replaced by a different number and the words, translated into numbers, then make a valid sum. -Each letter must represent a different digit, and the leading digit of -a multi-digit number must not be zero. +Each letter must represent a different digit, and the leading digit of a multi-digit number must not be zero. Write a function to solve alphametics puzzles. + +[alphametics]: https://en.wikipedia.org/wiki/Alphametics diff --git a/exercises/practice/anagram/.docs/instructions.md b/exercises/practice/anagram/.docs/instructions.md index 2675b5836..7d1c8283e 100644 --- a/exercises/practice/anagram/.docs/instructions.md +++ b/exercises/practice/anagram/.docs/instructions.md @@ -1,8 +1,13 @@ # Instructions -An anagram is a rearrangement of letters to form a new word. -Given a word and a list of candidates, select the sublist of anagrams of the given word. +An anagram is a rearrangement of letters to form a new word: for example `"owns"` is an anagram of `"snow"`. +A word is not its own anagram: for example, `"stop"` is not an anagram of `"stop"`. -Given `"listen"` and a list of candidates like `"enlists" "google" -"inlets" "banana"` the program should return a list containing -`"inlets"`. +Given a target word and a set of candidate words, this exercise requests the anagram set: the subset of the candidates that are anagrams of the target. + +The target and candidates are words of one or more ASCII alphabetic characters (`A`-`Z` and `a`-`z`). +Lowercase and uppercase characters are equivalent: for example, `"PoTS"` is an anagram of `"sTOp"`, but `StoP` is not an anagram of `sTOp`. +The anagram set is the subset of the candidate set that are anagrams of the target (in any order). +Words in the anagram set should have the same letter case as in the candidate set. + +Given the target `"stone"` and candidates `"stone"`, `"tones"`, `"banana"`, `"tons"`, `"notes"`, `"Seton"`, the anagram set is `"tones"`, `"notes"`, `"Seton"`. diff --git a/exercises/practice/armstrong-numbers/.docs/instructions.md b/exercises/practice/armstrong-numbers/.docs/instructions.md index 452a996fb..5e56bbe46 100644 --- a/exercises/practice/armstrong-numbers/.docs/instructions.md +++ b/exercises/practice/armstrong-numbers/.docs/instructions.md @@ -1,12 +1,14 @@ # Instructions -An [Armstrong number](https://en.wikipedia.org/wiki/Narcissistic_number) is a number that is the sum of its own digits each raised to the power of the number of digits. +An [Armstrong number][armstrong-number] is a number that is the sum of its own digits each raised to the power of the number of digits. For example: - 9 is an Armstrong number, because `9 = 9^1 = 9` -- 10 is *not* an Armstrong number, because `10 != 1^2 + 0^2 = 1` +- 10 is _not_ an Armstrong number, because `10 != 1^2 + 0^2 = 1` - 153 is an Armstrong number, because: `153 = 1^3 + 5^3 + 3^3 = 1 + 125 + 27 = 153` -- 154 is *not* an Armstrong number, because: `154 != 1^3 + 5^3 + 4^3 = 1 + 125 + 64 = 190` +- 154 is _not_ an Armstrong number, because: `154 != 1^3 + 5^3 + 4^3 = 1 + 125 + 64 = 190` Write some code to determine whether a number is an Armstrong number. + +[armstrong-number]: https://en.wikipedia.org/wiki/Narcissistic_number diff --git a/exercises/practice/atbash-cipher/.docs/instructions.md b/exercises/practice/atbash-cipher/.docs/instructions.md index 2f712b159..21ca2ce0a 100644 --- a/exercises/practice/atbash-cipher/.docs/instructions.md +++ b/exercises/practice/atbash-cipher/.docs/instructions.md @@ -2,10 +2,8 @@ Create an implementation of the atbash cipher, an ancient encryption system created in the Middle East. -The Atbash cipher is a simple substitution cipher that relies on -transposing all the letters in the alphabet such that the resulting -alphabet is backwards. The first letter is replaced with the last -letter, the second with the second-last, and so on. +The Atbash cipher is a simple substitution cipher that relies on transposing all the letters in the alphabet such that the resulting alphabet is backwards. +The first letter is replaced with the last letter, the second with the second-last, and so on. An Atbash cipher for the Latin alphabet would be as follows: @@ -14,16 +12,16 @@ Plain: abcdefghijklmnopqrstuvwxyz Cipher: zyxwvutsrqponmlkjihgfedcba ``` -It is a very weak cipher because it only has one possible key, and it is -a simple monoalphabetic substitution cipher. However, this may not have -been an issue in the cipher's time. +It is a very weak cipher because it only has one possible key, and it is a simple mono-alphabetic substitution cipher. +However, this may not have been an issue in the cipher's time. -Ciphertext is written out in groups of fixed length, the traditional group size -being 5 letters, and punctuation is excluded. This is to make it harder to guess -things based on word boundaries. +Ciphertext is written out in groups of fixed length, the traditional group size being 5 letters, leaving numbers unchanged, and punctuation is excluded. +This is to make it harder to guess things based on word boundaries. +All text will be encoded as lowercase letters. ## Examples - Encoding `test` gives `gvhg` +- Encoding `x123 yes` gives `c123b vh` - Decoding `gvhg` gives `test` - Decoding `gsvjf rxpyi ldmul cqfnk hlevi gsvoz abwlt` gives `thequickbrownfoxjumpsoverthelazydog` diff --git a/exercises/practice/atbash-cipher/.meta/config.json b/exercises/practice/atbash-cipher/.meta/config.json index 9343b43c5..82cfd2b8c 100644 --- a/exercises/practice/atbash-cipher/.meta/config.json +++ b/exercises/practice/atbash-cipher/.meta/config.json @@ -35,5 +35,5 @@ }, "blurb": "Create an implementation of the atbash cipher, an ancient encryption system created in the Middle East.", "source": "Wikipedia", - "source_url": "http://en.wikipedia.org/wiki/Atbash" + "source_url": "https://en.wikipedia.org/wiki/Atbash" } diff --git a/exercises/practice/beer-song/.docs/instructions.md b/exercises/practice/beer-song/.docs/instructions.md index 57429d8ab..e909cfe31 100644 --- a/exercises/practice/beer-song/.docs/instructions.md +++ b/exercises/practice/beer-song/.docs/instructions.md @@ -305,17 +305,3 @@ Take it down and pass it around, no more bottles of beer on the wall. No more bottles of beer on the wall, no more bottles of beer. Go to the store and buy some more, 99 bottles of beer on the wall. ``` - -## For bonus points - -Did you get the tests passing and the code clean? If you want to, these -are some additional things you could try: - -* Remove as much duplication as you possibly can. -* Optimize for readability, even if it means introducing duplication. -* If you've removed all the duplication, do you have a lot of - conditionals? Try replacing the conditionals with polymorphism, if it - applies in this language. How readable is it? - -Then please share your thoughts in a comment on the submission. Did this -experiment make the code better? Worse? Did you learn anything from it? diff --git a/exercises/practice/beer-song/.meta/config.json b/exercises/practice/beer-song/.meta/config.json index 3c0367363..dcd36935b 100644 --- a/exercises/practice/beer-song/.meta/config.json +++ b/exercises/practice/beer-song/.meta/config.json @@ -40,5 +40,5 @@ }, "blurb": "Produce the lyrics to that beloved classic, that field-trip favorite: 99 Bottles of Beer on the Wall.", "source": "Learn to Program by Chris Pine", - "source_url": "http://pine.fm/LearnToProgram/?Chapter=06" + "source_url": "https://pine.fm/LearnToProgram/?Chapter=06" } diff --git a/exercises/practice/binary-search/.docs/instructions.md b/exercises/practice/binary-search/.docs/instructions.md index aa1946cfb..12f4358eb 100644 --- a/exercises/practice/binary-search/.docs/instructions.md +++ b/exercises/practice/binary-search/.docs/instructions.md @@ -11,7 +11,7 @@ Binary search only works when a list has been sorted. The algorithm looks like this: -- Find the middle element of a *sorted* list and compare it with the item we're looking for. +- Find the middle element of a _sorted_ list and compare it with the item we're looking for. - If the middle element is our item, then we're done! - If the middle element is greater than our item, we can eliminate that element and all the elements **after** it. - If the middle element is less than our item, we can eliminate that element and all the elements **before** it. diff --git a/exercises/practice/binary-search/.meta/config.json b/exercises/practice/binary-search/.meta/config.json index 7937f5db7..b55dd3ed9 100644 --- a/exercises/practice/binary-search/.meta/config.json +++ b/exercises/practice/binary-search/.meta/config.json @@ -30,5 +30,5 @@ }, "blurb": "Implement a binary search algorithm.", "source": "Wikipedia", - "source_url": "http://en.wikipedia.org/wiki/Binary_search_algorithm" + "source_url": "https://en.wikipedia.org/wiki/Binary_search_algorithm" } diff --git a/exercises/practice/bob/.meta/config.json b/exercises/practice/bob/.meta/config.json index 98029fd20..9d8135c40 100644 --- a/exercises/practice/bob/.meta/config.json +++ b/exercises/practice/bob/.meta/config.json @@ -46,5 +46,5 @@ }, "blurb": "Bob is a lackadaisical teenager. In conversation, his responses are very limited.", "source": "Inspired by the 'Deaf Grandma' exercise in Chris Pine's Learn to Program tutorial.", - "source_url": "http://pine.fm/LearnToProgram/?Chapter=06" + "source_url": "https://pine.fm/LearnToProgram/?Chapter=06" } diff --git a/exercises/practice/bowling/.docs/instructions.md b/exercises/practice/bowling/.docs/instructions.md index be9b27faf..60ccad1b6 100644 --- a/exercises/practice/bowling/.docs/instructions.md +++ b/exercises/practice/bowling/.docs/instructions.md @@ -2,35 +2,30 @@ Score a bowling game. -Bowling is a game where players roll a heavy ball to knock down pins -arranged in a triangle. Write code to keep track of the score -of a game of bowling. +Bowling is a game where players roll a heavy ball to knock down pins arranged in a triangle. +Write code to keep track of the score of a game of bowling. ## Scoring Bowling -The game consists of 10 frames. A frame is composed of one or two ball -throws with 10 pins standing at frame initialization. There are three -cases for the tabulation of a frame. +The game consists of 10 frames. +A frame is composed of one or two ball throws with 10 pins standing at frame initialization. +There are three cases for the tabulation of a frame. -* An open frame is where a score of less than 10 is recorded for the - frame. In this case the score for the frame is the number of pins - knocked down. +- An open frame is where a score of less than 10 is recorded for the frame. + In this case the score for the frame is the number of pins knocked down. -* A spare is where all ten pins are knocked down by the second - throw. The total value of a spare is 10 plus the number of pins - knocked down in their next throw. +- A spare is where all ten pins are knocked down by the second throw. + The total value of a spare is 10 plus the number of pins knocked down in their next throw. -* A strike is where all ten pins are knocked down by the first - throw. The total value of a strike is 10 plus the number of pins - knocked down in the next two throws. If a strike is immediately - followed by a second strike, then the value of the first strike - cannot be determined until the ball is thrown one more time. +- A strike is where all ten pins are knocked down by the first throw. + The total value of a strike is 10 plus the number of pins knocked down in the next two throws. + If a strike is immediately followed by a second strike, then the value of the first strike cannot be determined until the ball is thrown one more time. Here is a three frame example: -| Frame 1 | Frame 2 | Frame 3 | -| :-------------: |:-------------:| :---------------------:| -| X (strike) | 5/ (spare) | 9 0 (open frame) | +| Frame 1 | Frame 2 | Frame 3 | +| :--------: | :--------: | :--------------: | +| X (strike) | 5/ (spare) | 9 0 (open frame) | Frame 1 is (10 + 5 + 5) = 20 @@ -40,11 +35,11 @@ Frame 3 is (9 + 0) = 9 This means the current running total is 48. -The tenth frame in the game is a special case. If someone throws a -strike or a spare then they get a fill ball. Fill balls exist to -calculate the total of the 10th frame. Scoring a strike or spare on -the fill ball does not give the player more fill balls. The total -value of the 10th frame is the total number of pins knocked down. +The tenth frame in the game is a special case. +If someone throws a spare or a strike then they get one or two fill balls respectively. +Fill balls exist to calculate the total of the 10th frame. +Scoring a strike or spare on the fill ball does not give the player more fill balls. +The total value of the 10th frame is the total number of pins knocked down. For a tenth frame of X1/ (strike and a spare), the total value is 20. @@ -52,10 +47,10 @@ For a tenth frame of XXX (three strikes), the total value is 30. ## Requirements -Write code to keep track of the score of a game of bowling. It should -support two operations: +Write code to keep track of the score of a game of bowling. +It should support two operations: -* `roll(pins : int)` is called each time the player rolls a ball. The - argument is the number of pins knocked down. -* `score() : int` is called only at the very end of the game. It - returns the total score for that game. +- `roll(pins : int)` is called each time the player rolls a ball. + The argument is the number of pins knocked down. +- `score() : int` is called only at the very end of the game. + It returns the total score for that game. diff --git a/exercises/practice/bowling/.meta/config.json b/exercises/practice/bowling/.meta/config.json index 478167521..b0787bf90 100644 --- a/exercises/practice/bowling/.meta/config.json +++ b/exercises/practice/bowling/.meta/config.json @@ -33,5 +33,5 @@ }, "blurb": "Score a bowling game.", "source": "The Bowling Game Kata from UncleBob", - "source_url": "http://butunclebob.com/ArticleS.UncleBob.TheBowlingGameKata" + "source_url": "https://web.archive.org/web/20221001111000/http://butunclebob.com/ArticleS.UncleBob.TheBowlingGameKata" } diff --git a/exercises/practice/circular-buffer/.docs/instructions.md b/exercises/practice/circular-buffer/.docs/instructions.md index e9b00b91d..2ba1fda2a 100644 --- a/exercises/practice/circular-buffer/.docs/instructions.md +++ b/exercises/practice/circular-buffer/.docs/instructions.md @@ -1,51 +1,58 @@ # Instructions -A circular buffer, cyclic buffer or ring buffer is a data structure that -uses a single, fixed-size buffer as if it were connected end-to-end. - -A circular buffer first starts empty and of some predefined length. For -example, this is a 7-element buffer: - - [ ][ ][ ][ ][ ][ ][ ] - -Assume that a 1 is written into the middle of the buffer (exact starting -location does not matter in a circular buffer): - - [ ][ ][ ][1][ ][ ][ ] - -Then assume that two more elements are added — 2 & 3 — which get -appended after the 1: - - [ ][ ][ ][1][2][3][ ] - -If two elements are then removed from the buffer, the oldest values -inside the buffer are removed. The two elements removed, in this case, -are 1 & 2, leaving the buffer with just a 3: - - [ ][ ][ ][ ][ ][3][ ] +A circular buffer, cyclic buffer or ring buffer is a data structure that uses a single, fixed-size buffer as if it were connected end-to-end. + +A circular buffer first starts empty and of some predefined length. +For example, this is a 7-element buffer: + +```text +[ ][ ][ ][ ][ ][ ][ ] +``` + +Assume that a 1 is written into the middle of the buffer (exact starting location does not matter in a circular buffer): + +```text +[ ][ ][ ][1][ ][ ][ ] +``` + +Then assume that two more elements are added — 2 & 3 — which get appended after the 1: + +```text +[ ][ ][ ][1][2][3][ ] +``` + +If two elements are then removed from the buffer, the oldest values inside the buffer are removed. +The two elements removed, in this case, are 1 & 2, leaving the buffer with just a 3: + +```text +[ ][ ][ ][ ][ ][3][ ] +``` If the buffer has 7 elements then it is completely full: - - [5][6][7][8][9][3][4] - -When the buffer is full an error will be raised, alerting the client -that further writes are blocked until a slot becomes free. - -When the buffer is full, the client can opt to overwrite the oldest -data with a forced write. In this case, two more elements — A & B — -are added and they overwrite the 3 & 4: - - [5][6][7][8][9][A][B] - -3 & 4 have been replaced by A & B making 5 now the oldest data in the -buffer. Finally, if two elements are removed then what would be -returned is 5 & 6 yielding the buffer: - - [ ][ ][7][8][9][A][B] - -Because there is space available, if the client again uses overwrite -to store C & D then the space where 5 & 6 were stored previously will -be used not the location of 7 & 8. 7 is still the oldest element and -the buffer is once again full. - - [C][D][7][8][9][A][B] + +```text +[5][6][7][8][9][3][4] +``` + +When the buffer is full an error will be raised, alerting the client that further writes are blocked until a slot becomes free. + +When the buffer is full, the client can opt to overwrite the oldest data with a forced write. +In this case, two more elements — A & B — are added and they overwrite the 3 & 4: + +```text +[5][6][7][8][9][A][B] +``` + +3 & 4 have been replaced by A & B making 5 now the oldest data in the buffer. +Finally, if two elements are removed then what would be returned is 5 & 6 yielding the buffer: + +```text +[ ][ ][7][8][9][A][B] +``` + +Because there is space available, if the client again uses overwrite to store C & D then the space where 5 & 6 were stored previously will be used not the location of 7 & 8. +7 is still the oldest element and the buffer is once again full. + +```text +[C][D][7][8][9][A][B] +``` diff --git a/exercises/practice/circular-buffer/.meta/config.json b/exercises/practice/circular-buffer/.meta/config.json index 37e850ca4..74871ba5c 100644 --- a/exercises/practice/circular-buffer/.meta/config.json +++ b/exercises/practice/circular-buffer/.meta/config.json @@ -37,5 +37,5 @@ }, "blurb": "A data structure that uses a single, fixed-size buffer as if it were connected end-to-end.", "source": "Wikipedia", - "source_url": "http://en.wikipedia.org/wiki/Circular_buffer" + "source_url": "https://en.wikipedia.org/wiki/Circular_buffer" } diff --git a/exercises/practice/clock/.docs/instructions.append.md b/exercises/practice/clock/.docs/instructions.append.md index 75c732f08..6ceff1140 100644 --- a/exercises/practice/clock/.docs/instructions.append.md +++ b/exercises/practice/clock/.docs/instructions.append.md @@ -1,9 +1,10 @@ # Rust Traits for `.to_string()` -Did you implement `.to_string()` for the `Clock` struct? +You will also need to implement `.to_string()` for the `Clock` struct. +We will be using this to display the Clock's state. +You can either do it via implementing it directly or using the [Display trait][display-trait]. -If so, try implementing the -[Display trait](https://doc.rust-lang.org/std/fmt/trait.Display.html) for `Clock` instead. +If so, try implementing the [Display trait][display-trait] for `Clock` instead. Traits allow for a common way to implement functionality for various types. @@ -11,3 +12,5 @@ For additional learning, consider how you might implement `String::from` for the You don't have to actually implement this—it's redundant with `Display`, which is generally the better choice when the destination type is `String`—but it's useful to have a few type-conversion traits in your toolkit. + +[display-trait]: https://doc.rust-lang.org/std/fmt/trait.Display.html diff --git a/exercises/practice/clock/.docs/instructions.md b/exercises/practice/clock/.docs/instructions.md index ebe272526..a1efc7890 100644 --- a/exercises/practice/clock/.docs/instructions.md +++ b/exercises/practice/clock/.docs/instructions.md @@ -5,5 +5,3 @@ Implement a clock that handles times without dates. You should be able to add and subtract minutes to it. Two clocks that represent the same time should be equal to each other. - -You will also need to implement `.to_string()` for the `Clock` struct. We will be using this to display the Clock's state. You can either do it via implementing it directly or using the [Display trait](https://doc.rust-lang.org/std/fmt/trait.Display.html). diff --git a/exercises/practice/clock/.meta/config.json b/exercises/practice/clock/.meta/config.json index 21c603c13..767284df7 100644 --- a/exercises/practice/clock/.meta/config.json +++ b/exercises/practice/clock/.meta/config.json @@ -36,7 +36,6 @@ }, "blurb": "Implement a clock that handles times without dates.", "source": "Pairing session with Erin Drummond", - "source_url": "https://twitter.com/ebdrummond", "custom": { "allowed-to-not-compile": "Stub doesn't compile because there is no to_string() implementation. This exercise is an introduction to derived and self-implemented traits, therefore adding template for a trait would reduce student learning." } diff --git a/exercises/practice/collatz-conjecture/.docs/instructions.md b/exercises/practice/collatz-conjecture/.docs/instructions.md index 6eec8560e..ba060483e 100644 --- a/exercises/practice/collatz-conjecture/.docs/instructions.md +++ b/exercises/practice/collatz-conjecture/.docs/instructions.md @@ -2,14 +2,11 @@ The Collatz Conjecture or 3x+1 problem can be summarized as follows: -Take any positive integer n. If n is even, divide n by 2 to get n / 2. If n is -odd, multiply n by 3 and add 1 to get 3n + 1. Repeat the process indefinitely. -The conjecture states that no matter which number you start with, you will -always reach 1 eventually. - -But sometimes the number grow significantly before it reaches 1. -This can lead to an integer overflow and makes the algorithm unsolvable -within the range of a number in u64. +Take any positive integer n. +If n is even, divide n by 2 to get n / 2. +If n is odd, multiply n by 3 and add 1 to get 3n + 1. +Repeat the process indefinitely. +The conjecture states that no matter which number you start with, you will always reach 1 eventually. Given a number n, return the number of steps required to reach 1. @@ -28,4 +25,5 @@ Starting with n = 12, the steps would be as follows: 8. 2 9. 1 -Resulting in 9 steps. So for input n = 12, the return value would be 9. +Resulting in 9 steps. +So for input n = 12, the return value would be 9. diff --git a/exercises/practice/crypto-square/.docs/instructions.md b/exercises/practice/crypto-square/.docs/instructions.md index 41615f819..6c3826ee5 100644 --- a/exercises/practice/crypto-square/.docs/instructions.md +++ b/exercises/practice/crypto-square/.docs/instructions.md @@ -4,11 +4,10 @@ Implement the classic method for composing secret messages called a square code. Given an English text, output the encoded version of that text. -First, the input is normalized: the spaces and punctuation are removed -from the English text and the message is downcased. +First, the input is normalized: the spaces and punctuation are removed from the English text and the message is down-cased. -Then, the normalized characters are broken into rows. These rows can be -regarded as forming a rectangle when printed with intervening newlines. +Then, the normalized characters are broken into rows. +These rows can be regarded as forming a rectangle when printed with intervening newlines. For example, the sentence @@ -22,13 +21,16 @@ is normalized to: "ifmanwasmeanttostayonthegroundgodwouldhavegivenusroots" ``` -The plaintext should be organized in to a rectangle. The size of the -rectangle (`r x c`) should be decided by the length of the message, -such that `c >= r` and `c - r <= 1`, where `c` is the number of columns -and `r` is the number of rows. +The plaintext should be organized into a rectangle as square as possible. +The size of the rectangle should be decided by the length of the message. -Our normalized text is 54 characters long, dictating a rectangle with -`c = 8` and `r = 7`: +If `c` is the number of columns and `r` is the number of rows, then for the rectangle `r` x `c` find the smallest possible integer `c` such that: + +- `r * c >= length of message`, +- and `c >= r`, +- and `c - r <= 1`. + +Our normalized text is 54 characters long, dictating a rectangle with `c = 8` and `r = 7`: ```text "ifmanwas" @@ -40,8 +42,7 @@ Our normalized text is 54 characters long, dictating a rectangle with "sroots " ``` -The coded message is obtained by reading down the columns going left to -right. +The coded message is obtained by reading down the columns going left to right. The message above is coded as: @@ -49,17 +50,14 @@ The message above is coded as: "imtgdvsfearwermayoogoanouuiontnnlvtwttddesaohghnsseoau" ``` -Output the encoded text in chunks that fill perfect rectangles `(r X c)`, -with `c` chunks of `r` length, separated by spaces. For phrases that are -`n` characters short of the perfect rectangle, pad each of the last `n` -chunks with a single trailing space. +Output the encoded text in chunks that fill perfect rectangles `(r X c)`, with `c` chunks of `r` length, separated by spaces. +For phrases that are `n` characters short of the perfect rectangle, pad each of the last `n` chunks with a single trailing space. ```text "imtgdvs fearwer mayoogo anouuio ntnnlvt wttddes aohghn sseoau " ``` -Notice that were we to stack these, we could visually decode the -ciphertext back in to the original message: +Notice that were we to stack these, we could visually decode the ciphertext back in to the original message: ```text "imtgdvs" diff --git a/exercises/practice/crypto-square/.meta/config.json b/exercises/practice/crypto-square/.meta/config.json index b8c261f2f..4cf36b421 100644 --- a/exercises/practice/crypto-square/.meta/config.json +++ b/exercises/practice/crypto-square/.meta/config.json @@ -32,5 +32,5 @@ }, "blurb": "Implement the classic method for composing secret messages called a square code.", "source": "J Dalbey's Programming Practice problems", - "source_url": "http://users.csc.calpoly.edu/~jdalbey/103/Projects/ProgrammingPractice.html" + "source_url": "https://users.csc.calpoly.edu/~jdalbey/103/Projects/ProgrammingPractice.html" } diff --git a/exercises/practice/diamond/.docs/instructions.md b/exercises/practice/diamond/.docs/instructions.md index 1de7016f0..3034802fe 100644 --- a/exercises/practice/diamond/.docs/instructions.md +++ b/exercises/practice/diamond/.docs/instructions.md @@ -1,22 +1,21 @@ # Instructions -The diamond kata takes as its input a letter, and outputs it in a diamond -shape. Given a letter, it prints a diamond starting with 'A', with the -supplied letter at the widest point. +The diamond kata takes as its input a letter, and outputs it in a diamond shape. +Given a letter, it prints a diamond starting with 'A', with the supplied letter at the widest point. ## Requirements -* The first row contains one 'A'. -* The last row contains one 'A'. -* All rows, except the first and last, have exactly two identical letters. -* All rows have as many trailing spaces as leading spaces. (This might be 0). -* The diamond is horizontally symmetric. -* The diamond is vertically symmetric. -* The diamond has a square shape (width equals height). -* The letters form a diamond shape. -* The top half has the letters in ascending order. -* The bottom half has the letters in descending order. -* The four corners (containing the spaces) are triangles. +- The first row contains one 'A'. +- The last row contains one 'A'. +- All rows, except the first and last, have exactly two identical letters. +- All rows have as many trailing spaces as leading spaces. (This might be 0). +- The diamond is horizontally symmetric. +- The diamond is vertically symmetric. +- The diamond has a square shape (width equals height). +- The letters form a diamond shape. +- The top half has the letters in ascending order. +- The bottom half has the letters in descending order. +- The four corners (containing the spaces) are triangles. ## Examples diff --git a/exercises/practice/diamond/.meta/config.json b/exercises/practice/diamond/.meta/config.json index 84e769b1b..b6ac35302 100644 --- a/exercises/practice/diamond/.meta/config.json +++ b/exercises/practice/diamond/.meta/config.json @@ -29,5 +29,5 @@ }, "blurb": "Given a letter, print a diamond starting with 'A' with the supplied letter at the widest point.", "source": "Seb Rose", - "source_url": "http://claysnow.co.uk/recycling-tests-in-tdd/" + "source_url": "https://web.archive.org/web/20220807163751/http://claysnow.co.uk/recycling-tests-in-tdd/" } diff --git a/exercises/practice/difference-of-squares/.docs/instructions.md b/exercises/practice/difference-of-squares/.docs/instructions.md index c3999e86a..39c38b509 100644 --- a/exercises/practice/difference-of-squares/.docs/instructions.md +++ b/exercises/practice/difference-of-squares/.docs/instructions.md @@ -8,10 +8,7 @@ The square of the sum of the first ten natural numbers is The sum of the squares of the first ten natural numbers is 1² + 2² + ... + 10² = 385. -Hence the difference between the square of the sum of the first -ten natural numbers and the sum of the squares of the first ten -natural numbers is 3025 - 385 = 2640. +Hence the difference between the square of the sum of the first ten natural numbers and the sum of the squares of the first ten natural numbers is 3025 - 385 = 2640. -You are not expected to discover an efficient solution to this yourself from -first principles; research is allowed, indeed, encouraged. Finding the best -algorithm for the problem is a key skill in software engineering. +You are not expected to discover an efficient solution to this yourself from first principles; research is allowed, indeed, encouraged. +Finding the best algorithm for the problem is a key skill in software engineering. diff --git a/exercises/practice/difference-of-squares/.meta/config.json b/exercises/practice/difference-of-squares/.meta/config.json index 25c70ef56..ac8053366 100644 --- a/exercises/practice/difference-of-squares/.meta/config.json +++ b/exercises/practice/difference-of-squares/.meta/config.json @@ -37,5 +37,5 @@ }, "blurb": "Find the difference between the square of the sum and the sum of the squares of the first N natural numbers.", "source": "Problem 6 at Project Euler", - "source_url": "http://projecteuler.net/problem=6" + "source_url": "https://projecteuler.net/problem=6" } diff --git a/exercises/practice/diffie-hellman/.docs/instructions.md b/exercises/practice/diffie-hellman/.docs/instructions.md index 589cbd19f..9f1c85e31 100644 --- a/exercises/practice/diffie-hellman/.docs/instructions.md +++ b/exercises/practice/diffie-hellman/.docs/instructions.md @@ -2,9 +2,8 @@ Diffie-Hellman key exchange. -Alice and Bob use Diffie-Hellman key exchange to share secrets. They -start with prime numbers, pick private keys, generate and share public -keys, and then generate a shared secret key. +Alice and Bob use Diffie-Hellman key exchange to share secrets. +They start with prime numbers, pick private keys, generate and share public keys, and then generate a shared secret key. ## Step 0 @@ -12,27 +11,27 @@ The test program supplies prime numbers p and g. ## Step 1 -Alice picks a private key, a, greater than 1 and less than p. Bob does -the same to pick a private key b. +Alice picks a private key, a, greater than 1 and less than p. +Bob does the same to pick a private key b. ## Step 2 Alice calculates a public key A. - A = g**a mod p + A = gᵃ mod p -Using the same p and g, Bob similarly calculates a public key B from his -private key b. +Using the same p and g, Bob similarly calculates a public key B from his private key b. ## Step 3 -Alice and Bob exchange public keys. Alice calculates secret key s. +Alice and Bob exchange public keys. +Alice calculates secret key s. - s = B**a mod p + s = Bᵃ mod p Bob calculates - s = A**b mod p + s = Aᵇ mod p -The calculations produce the same result! Alice and Bob now share -secret s. +The calculations produce the same result! +Alice and Bob now share secret s. diff --git a/exercises/practice/diffie-hellman/.meta/config.json b/exercises/practice/diffie-hellman/.meta/config.json index 94b35bae0..37fb2d378 100644 --- a/exercises/practice/diffie-hellman/.meta/config.json +++ b/exercises/practice/diffie-hellman/.meta/config.json @@ -31,5 +31,5 @@ }, "blurb": "Diffie-Hellman key exchange.", "source": "Wikipedia, 1024 bit key from www.cryptopp.com/wiki.", - "source_url": "http://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange" + "source_url": "https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange" } diff --git a/exercises/practice/dominoes/.docs/instructions.md b/exercises/practice/dominoes/.docs/instructions.md index 47f05a60d..1ced9f644 100644 --- a/exercises/practice/dominoes/.docs/instructions.md +++ b/exercises/practice/dominoes/.docs/instructions.md @@ -2,14 +2,12 @@ Make a chain of dominoes. -Compute a way to order a given set of dominoes in such a way that they form a -correct domino chain (the dots on one half of a stone match the dots on the -neighbouring half of an adjacent stone) and that dots on the halves of the -stones which don't have a neighbour (the first and last stone) match each other. +Compute a way to order a given set of dominoes in such a way that they form a correct domino chain (the dots on one half of a stone match the dots on the neighboring half of an adjacent stone) and that dots on the halves of the stones which don't have a neighbor (the first and last stone) match each other. For example given the stones `[2|1]`, `[2|3]` and `[1|3]` you should compute something like `[1|2] [2|3] [3|1]` or `[3|2] [2|1] [1|3]` or `[1|3] [3|2] [2|1]` etc, where the first and last numbers are the same. -For stones `[1|2]`, `[4|1]` and `[2|3]` the resulting chain is not valid: `[4|1] [1|2] [2|3]`'s first and last numbers are not the same. 4 != 3 +For stones `[1|2]`, `[4|1]` and `[2|3]` the resulting chain is not valid: `[4|1] [1|2] [2|3]`'s first and last numbers are not the same. +4 != 3 Some test cases may use duplicate stones in a chain solution, assume that multiple Domino sets are being used. diff --git a/exercises/practice/dot-dsl/.docs/instructions.md b/exercises/practice/dot-dsl/.docs/instructions.md index f61f3f0b4..b3a63996d 100644 --- a/exercises/practice/dot-dsl/.docs/instructions.md +++ b/exercises/practice/dot-dsl/.docs/instructions.md @@ -1,16 +1,12 @@ # Instructions -A [Domain Specific Language -(DSL)](https://en.wikipedia.org/wiki/Domain-specific_language) is a -small language optimized for a specific domain. Since a DSL is -targeted, it can greatly impact productivity/understanding by allowing the -writer to declare *what* they want rather than *how*. +A [Domain Specific Language (DSL)][dsl] is a small language optimized for a specific domain. +Since a DSL is targeted, it can greatly impact productivity/understanding by allowing the writer to declare _what_ they want rather than _how_. One problem area where they are applied are complex customizations/configurations. -For example the [DOT language](https://en.wikipedia.org/wiki/DOT_(graph_description_language)) allows -you to write a textual description of a graph which is then transformed into a picture by one of -the [Graphviz](http://graphviz.org/) tools (such as `dot`). A simple graph looks like this: +For example the [DOT language][dot-language] allows you to write a textual description of a graph which is then transformed into a picture by one of the [Graphviz][graphviz] tools (such as `dot`). +A simple graph looks like this: graph { graph [bgcolor="yellow"] @@ -19,15 +15,16 @@ the [Graphviz](http://graphviz.org/) tools (such as `dot`). A simple graph looks a -- b [color="green"] } -Putting this in a file `example.dot` and running `dot example.dot -T png --o example.png` creates an image `example.png` with red and blue circle -connected by a green line on a yellow background. +Putting this in a file `example.dot` and running `dot example.dot -T png -o example.png` creates an image `example.png` with red and blue circle connected by a green line on a yellow background. Write a Domain Specific Language similar to the Graphviz dot language. -Our DSL is similar to the Graphviz dot language in that our DSL will be used -to create graph data structures. However, unlike the DOT Language, our DSL will -be an internal DSL for use only in our language. +Our DSL is similar to the Graphviz dot language in that our DSL will be used to create graph data structures. +However, unlike the DOT Language, our DSL will be an internal DSL for use only in our language. -More information about the difference between internal and external DSLs can be -found [here](https://martinfowler.com/bliki/DomainSpecificLanguage.html). +More information about the difference between internal and external DSLs can be found [here][fowler-dsl]. + +[dsl]: https://en.wikipedia.org/wiki/Domain-specific_language +[dot-language]: https://en.wikipedia.org/wiki/DOT_(graph_description_language) +[graphviz]: https://graphviz.org/ +[fowler-dsl]: https://martinfowler.com/bliki/DomainSpecificLanguage.html diff --git a/exercises/practice/etl/.meta/config.json b/exercises/practice/etl/.meta/config.json index af4b8d315..c5cb84379 100644 --- a/exercises/practice/etl/.meta/config.json +++ b/exercises/practice/etl/.meta/config.json @@ -36,7 +36,7 @@ ".meta/example.rs" ] }, - "blurb": "We are going to do the `Transform` step of an Extract-Transform-Load.", - "source": "The Jumpstart Lab team", - "source_url": "http://jumpstartlab.com/" + "blurb": "Change the data format for scoring a game to more easily add other languages.", + "source": "Based on an exercise by the JumpstartLab team for students at The Turing School of Software and Design.", + "source_url": "https://turing.edu/" } diff --git a/exercises/practice/forth/.docs/instructions.md b/exercises/practice/forth/.docs/instructions.md index f481b725a..91ad26e6e 100644 --- a/exercises/practice/forth/.docs/instructions.md +++ b/exercises/practice/forth/.docs/instructions.md @@ -2,25 +2,22 @@ Implement an evaluator for a very simple subset of Forth. -[Forth](https://en.wikipedia.org/wiki/Forth_%28programming_language%29) -is a stack-based programming language. Implement a very basic evaluator -for a small subset of Forth. +[Forth][forth] +is a stack-based programming language. +Implement a very basic evaluator for a small subset of Forth. Your evaluator has to support the following words: - `+`, `-`, `*`, `/` (integer arithmetic) - `DUP`, `DROP`, `SWAP`, `OVER` (stack manipulation) -Your evaluator also has to support defining new words using the -customary syntax: `: word-name definition ;`. +Your evaluator also has to support defining new words using the customary syntax: `: word-name definition ;`. -To keep things simple the only data type you need to support is signed -integers of at least 16 bits size. +To keep things simple the only data type you need to support is signed integers of at least 16 bits size. -You should use the following rules for the syntax: a number is a -sequence of one or more (ASCII) digits, a word is a sequence of one or -more letters, digits, symbols or punctuation that is not a number. -(Forth probably uses slightly different rules, but this is close -enough.) +You should use the following rules for the syntax: a number is a sequence of one or more (ASCII) digits, a word is a sequence of one or more letters, digits, symbols or punctuation that is not a number. +(Forth probably uses slightly different rules, but this is close enough.) Words are case-insensitive. + +[forth]: https://en.wikipedia.org/wiki/Forth_%28programming_language%29 diff --git a/exercises/practice/gigasecond/.meta/config.json b/exercises/practice/gigasecond/.meta/config.json index 43bf4127e..d3e038dbb 100644 --- a/exercises/practice/gigasecond/.meta/config.json +++ b/exercises/practice/gigasecond/.meta/config.json @@ -41,5 +41,5 @@ }, "blurb": "Given a moment, determine the moment that would be after a gigasecond has passed.", "source": "Chapter 9 in Chris Pine's online Learn to Program tutorial.", - "source_url": "http://pine.fm/LearnToProgram/?Chapter=09" + "source_url": "https://pine.fm/LearnToProgram/?Chapter=09" } diff --git a/exercises/practice/grade-school/.docs/instructions.md b/exercises/practice/grade-school/.docs/instructions.md index 8bbbf6446..9a63e398d 100644 --- a/exercises/practice/grade-school/.docs/instructions.md +++ b/exercises/practice/grade-school/.docs/instructions.md @@ -1,7 +1,6 @@ # Instructions -Given students' names along with the grade that they are in, create a roster -for the school. +Given students' names along with the grade that they are in, create a roster for the school. In the end, you should be able to: @@ -11,28 +10,12 @@ In the end, you should be able to: - Get a list of all students enrolled in a grade - "Which students are in grade 2?" - "We've only got Jim just now." -- Get a sorted list of all students in all grades. Grades should sort - as 1, 2, 3, etc., and students within a grade should be sorted - alphabetically by name. +- Get a sorted list of all students in all grades. + Grades should sort as 1, 2, 3, etc., and students within a grade should be sorted alphabetically by name. - "Who all is enrolled in school right now?" - - "Let me think. We have - Anna, Barb, and Charlie in grade 1, - Alex, Peter, and Zoe in grade 2 - and Jim in grade 5. - So the answer is: Anna, Barb, Charlie, Alex, Peter, Zoe and Jim" + - "Let me think. + We have Anna, Barb, and Charlie in grade 1, Alex, Peter, and Zoe in grade 2 and Jim in grade 5. + So the answer is: Anna, Barb, Charlie, Alex, Peter, Zoe and Jim" -Note that all our students only have one name. (It's a small town, what -do you want?) - -## For bonus points - -Did you get the tests passing and the code clean? If you want to, these -are some additional things you could try: - -- If you're working in a language with mutable data structures and your - implementation allows outside code to mutate the school's internal DB - directly, see if you can prevent this. Feel free to introduce additional - tests. - -Then please share your thoughts in a comment on the submission. Did this -experiment make the code better? Worse? Did you learn anything from it? +Note that all our students only have one name (It's a small town, what do you want?) and each student cannot be added more than once to a grade or the roster. +In fact, when a test attempts to add the same student more than once, your implementation should indicate that this is incorrect. diff --git a/exercises/practice/grains/.docs/instructions.md b/exercises/practice/grains/.docs/instructions.md index 6f235bfc2..df479fc0a 100644 --- a/exercises/practice/grains/.docs/instructions.md +++ b/exercises/practice/grains/.docs/instructions.md @@ -1,28 +1,15 @@ # Instructions -Calculate the number of grains of wheat on a chessboard given that the number -on each square doubles. +Calculate the number of grains of wheat on a chessboard given that the number on each square doubles. -There once was a wise servant who saved the life of a prince. The king -promised to pay whatever the servant could dream up. Knowing that the -king loved chess, the servant told the king he would like to have grains -of wheat. One grain on the first square of a chess board, with the number -of grains doubling on each successive square. +There once was a wise servant who saved the life of a prince. +The king promised to pay whatever the servant could dream up. +Knowing that the king loved chess, the servant told the king he would like to have grains of wheat. +One grain on the first square of a chess board, with the number of grains doubling on each successive square. There are 64 squares on a chessboard (where square 1 has one grain, square 2 has two grains, and so on). Write code that shows: -- how many grains were on a given square, -- the total number of grains on the chessboard, and -- panics with a message of "Square must be between 1 and 64" if the value is not valid -## For bonus points - -Did you get the tests passing and the code clean? If you want to, these -are some additional things you could try: - -- Optimize for speed. -- Optimize for readability. - -Then please share your thoughts in a comment on the submission. Did this -experiment make the code better? Worse? Did you learn anything from it? +- how many grains were on a given square, and +- the total number of grains on the chessboard diff --git a/exercises/practice/grains/.meta/config.json b/exercises/practice/grains/.meta/config.json index ec7b1e311..21f042d6d 100644 --- a/exercises/practice/grains/.meta/config.json +++ b/exercises/practice/grains/.meta/config.json @@ -32,6 +32,6 @@ ] }, "blurb": "Calculate the number of grains of wheat on a chessboard given that the number on each square doubles.", - "source": "JavaRanch Cattle Drive, exercise 6", - "source_url": "http://www.javaranch.com/grains.jsp" + "source": "The CodeRanch Cattle Drive, Assignment 6", + "source_url": "https://coderanch.com/wiki/718824/Grains" } diff --git a/exercises/practice/grep/.docs/instructions.md b/exercises/practice/grep/.docs/instructions.md index 6c072e66b..004f28acd 100644 --- a/exercises/practice/grep/.docs/instructions.md +++ b/exercises/practice/grep/.docs/instructions.md @@ -1,65 +1,27 @@ # Instructions -Search a file for lines matching a regular expression pattern. Return the line -number and contents of each matching line. +Search files for lines matching a search string and return all matching lines. -The Unix [`grep`](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/grep.html) command can be used to search for lines in one or more files -that match a user-provided search query (known as the *pattern*). +The Unix [`grep`][grep] command searches files for lines that match a regular expression. +Your task is to implement a simplified `grep` command, which supports searching for fixed strings. The `grep` command takes three arguments: -1. The pattern used to match lines in a file. -2. Zero or more flags to customize the matching behavior. -3. One or more files in which to search for matching lines. +1. The string to search for. +2. Zero or more flags for customizing the command's behavior. +3. One or more files to search in. -Your task is to implement the `grep` function, which should read the contents -of the specified files, find the lines that match the specified pattern -and then output those lines as a single string. Note that the lines should -be output in the order in which they were found, with the first matching line -in the first file being output first. - -As an example, suppose there is a file named "input.txt" with the following contents: - -```text -hello -world -hello again -``` - -If we were to call `grep "hello" input.txt`, the returned string should be: - -```text -hello -hello again -``` +It then reads the contents of the specified files (in the order specified), finds the lines that contain the search string, and finally returns those lines in the order in which they were found. +When searching in multiple files, each matching line is prepended by the file name and a colon (':'). ## Flags -As said earlier, the `grep` command should also support the following flags: - -- `-n` Print the line numbers of each matching line. -- `-l` Print only the names of files that contain at least one matching line. -- `-i` Match line using a case-insensitive comparison. -- `-v` Invert the program -- collect all lines that fail to match the pattern. -- `-x` Only match entire lines, instead of lines that contain a match. - -If we run `grep -n "hello" input.txt`, the `-n` flag will require the matching -lines to be prefixed with its line number: - -```text -1:hello -3:hello again -``` - -And if we run `grep -i "HELLO" input.txt`, we'll do a case-insensitive match, -and the output will be: - -```text -hello -hello again -``` +The `grep` command supports the following flags: -The `grep` command should support multiple flags at once. +- `-n` Prepend the line number and a colon (':') to each line in the output, placing the number after the filename (if present). +- `-l` Output only the names of the files that contain at least one matching line. +- `-i` Match using a case-insensitive comparison. +- `-v` Invert the program -- collect all lines that fail to match. +- `-x` Search only for lines where the search string matches the entire line. -For example, running `grep -l -v "hello" file1.txt file2.txt` should -print the names of files that do not contain the string "hello". +[grep]: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/grep.html diff --git a/exercises/practice/grep/.meta/config.json b/exercises/practice/grep/.meta/config.json index ef0541fc6..76ccc4dfc 100644 --- a/exercises/practice/grep/.meta/config.json +++ b/exercises/practice/grep/.meta/config.json @@ -28,5 +28,5 @@ }, "blurb": "Search a file for lines matching a regular expression pattern. Return the line number and contents of each matching line.", "source": "Conversation with Nate Foster.", - "source_url": "http://www.cs.cornell.edu/Courses/cs3110/2014sp/hw/0/ps0.pdf" + "source_url": "https://www.cs.cornell.edu/Courses/cs3110/2014sp/hw/0/ps0.pdf" } diff --git a/exercises/practice/hamming/.docs/instructions.md b/exercises/practice/hamming/.docs/instructions.md index 56c5696de..020fdd02d 100644 --- a/exercises/practice/hamming/.docs/instructions.md +++ b/exercises/practice/hamming/.docs/instructions.md @@ -2,11 +2,17 @@ Calculate the Hamming Distance between two DNA strands. -Your body is made up of cells that contain DNA. Those cells regularly wear out and need replacing, which they achieve by dividing into daughter cells. In fact, the average human body experiences about 10 quadrillion cell divisions in a lifetime! +Your body is made up of cells that contain DNA. +Those cells regularly wear out and need replacing, which they achieve by dividing into daughter cells. +In fact, the average human body experiences about 10 quadrillion cell divisions in a lifetime! -When cells divide, their DNA replicates too. Sometimes during this process mistakes happen and single pieces of DNA get encoded with the incorrect information. If we compare two strands of DNA and count the differences between them we can see how many mistakes occurred. This is known as the "Hamming Distance". +When cells divide, their DNA replicates too. +Sometimes during this process mistakes happen and single pieces of DNA get encoded with the incorrect information. +If we compare two strands of DNA and count the differences between them we can see how many mistakes occurred. +This is known as the "Hamming Distance". -We read DNA using the letters C,A,G and T. Two strands might look like this: +We read DNA using the letters C,A,G and T. +Two strands might look like this: GAGCCTACTAACGGGAT CATCGTAATGACGGCCT @@ -16,9 +22,6 @@ They have 7 differences, and therefore the Hamming Distance is 7. The Hamming Distance is useful for lots of things in science, not just biology, so it's a nice phrase to be familiar with :) -# Implementation notes +## Implementation notes -The Hamming distance is only defined for sequences of equal length, so -an attempt to calculate it between sequences of different lengths should -not work. The general handling of this situation (e.g., raising an -exception vs returning a special value) may differ between languages. +The Hamming distance is only defined for sequences of equal length, so an attempt to calculate it between sequences of different lengths should not work. diff --git a/exercises/practice/hamming/.meta/config.json b/exercises/practice/hamming/.meta/config.json index 06811aa5e..4d90d1d72 100644 --- a/exercises/practice/hamming/.meta/config.json +++ b/exercises/practice/hamming/.meta/config.json @@ -37,5 +37,5 @@ }, "blurb": "Calculate the Hamming difference between two DNA strands.", "source": "The Calculating Point Mutations problem at Rosalind", - "source_url": "http://rosalind.info/problems/hamm/" + "source_url": "https://rosalind.info/problems/hamm/" } diff --git a/exercises/practice/hello-world/.docs/instructions.md b/exercises/practice/hello-world/.docs/instructions.md index 6e08ebba5..c9570e48a 100644 --- a/exercises/practice/hello-world/.docs/instructions.md +++ b/exercises/practice/hello-world/.docs/instructions.md @@ -1,15 +1,16 @@ # Instructions -The classical introductory exercise. Just say "Hello, World!". +The classical introductory exercise. +Just say "Hello, World!". -["Hello, World!"](http://en.wikipedia.org/wiki/%22Hello,_world!%22_program) is -the traditional first program for beginning programming in a new language -or environment. +["Hello, World!"][hello-world] is the traditional first program for beginning programming in a new language or environment. The objectives are simple: -- Write a function that returns the string "Hello, World!". +- Modify the provided code so that it produces the string "Hello, World!". - Run the test suite and make sure that it succeeds. - Submit your solution and check it at the website. If everything goes well, you will be ready to fetch your first real exercise. + +[hello-world]: https://en.wikipedia.org/wiki/%22Hello,_world!%22_program diff --git a/exercises/practice/hello-world/.meta/config.json b/exercises/practice/hello-world/.meta/config.json index 93473d3d8..a7701a5a2 100644 --- a/exercises/practice/hello-world/.meta/config.json +++ b/exercises/practice/hello-world/.meta/config.json @@ -36,7 +36,7 @@ ".meta/example.rs" ] }, - "blurb": "The classical introductory exercise. Just say \"Hello, World!\".", + "blurb": "Exercism's classic introductory exercise. Just say \"Hello, World!\".", "source": "This is an exercise to introduce users to using Exercism", - "source_url": "http://en.wikipedia.org/wiki/%22Hello,_world!%22_program" + "source_url": "https://en.wikipedia.org/wiki/%22Hello,_world!%22_program" } diff --git a/exercises/practice/high-scores/.docs/instructions.md b/exercises/practice/high-scores/.docs/instructions.md index 1f8154d5f..55802488c 100644 --- a/exercises/practice/high-scores/.docs/instructions.md +++ b/exercises/practice/high-scores/.docs/instructions.md @@ -2,4 +2,5 @@ Manage a game player's High Score list. -Your task is to build a high-score component of the classic Frogger game, one of the highest selling and addictive games of all time, and a classic of the arcade era. Your task is to write methods that return the highest score from the list, the last added score and the three highest scores. +Your task is to build a high-score component of the classic Frogger game, one of the highest selling and most addictive games of all time, and a classic of the arcade era. +Your task is to write methods that return the highest score from the list, the last added score and the three highest scores. diff --git a/exercises/practice/isbn-verifier/.docs/instructions.md b/exercises/practice/isbn-verifier/.docs/instructions.md index 7d6635edc..4a0244e55 100644 --- a/exercises/practice/isbn-verifier/.docs/instructions.md +++ b/exercises/practice/isbn-verifier/.docs/instructions.md @@ -1,22 +1,26 @@ # Instructions -The [ISBN-10 verification process](https://en.wikipedia.org/wiki/International_Standard_Book_Number) is used to validate book identification -numbers. These normally contain dashes and look like: `3-598-21508-8` +The [ISBN-10 verification process][isbn-verification] is used to validate book identification numbers. +These normally contain dashes and look like: `3-598-21508-8` ## ISBN -The ISBN-10 format is 9 digits (0 to 9) plus one check character (either a digit or an X only). In the case the check character is an X, this represents the value '10'. These may be communicated with or without hyphens, and can be checked for their validity by the following formula: +The ISBN-10 format is 9 digits (0 to 9) plus one check character (either a digit or an X only). +In the case the check character is an X, this represents the value '10'. +These may be communicated with or without hyphens, and can be checked for their validity by the following formula: -``` -(x1 * 10 + x2 * 9 + x3 * 8 + x4 * 7 + x5 * 6 + x6 * 5 + x7 * 4 + x8 * 3 + x9 * 2 + x10 * 1) mod 11 == 0 +```text +(d₁ * 10 + d₂ * 9 + d₃ * 8 + d₄ * 7 + d₅ * 6 + d₆ * 5 + d₇ * 4 + d₈ * 3 + d₉ * 2 + d₁₀ * 1) mod 11 == 0 ``` If the result is 0, then it is a valid ISBN-10, otherwise it is invalid. ## Example -Let's take the ISBN-10 `3-598-21508-8`. We plug it in to the formula, and get: -``` +Let's take the ISBN-10 `3-598-21508-8`. +We plug it in to the formula, and get: + +```text (3 * 10 + 5 * 9 + 9 * 8 + 8 * 7 + 2 * 6 + 1 * 5 + 5 * 4 + 0 * 3 + 8 * 2 + 8 * 1) mod 11 == 0 ``` @@ -29,14 +33,10 @@ Putting this into place requires some thinking about preprocessing/parsing of th The program should be able to verify ISBN-10 both with and without separating dashes. - ## Caveats Converting from strings to numbers can be tricky in certain languages. -Now, it's even trickier since the check digit of an ISBN-10 may be 'X' (representing '10'). For instance `3-598-21507-X` is a valid ISBN-10. - -## Bonus tasks - -* Generate a valid ISBN-13 from the input ISBN-10 (and maybe verify it again with a derived verifier). +Now, it's even trickier since the check digit of an ISBN-10 may be 'X' (representing '10'). +For instance `3-598-21507-X` is a valid ISBN-10. -* Generate valid ISBN, maybe even from a given starting ISBN. +[isbn-verification]: https://en.wikipedia.org/wiki/International_Standard_Book_Number diff --git a/exercises/practice/isogram/.docs/instructions.md b/exercises/practice/isogram/.docs/instructions.md index 9cc5350b6..2e8df851a 100644 --- a/exercises/practice/isogram/.docs/instructions.md +++ b/exercises/practice/isogram/.docs/instructions.md @@ -2,7 +2,7 @@ Determine if a word or phrase is an isogram. -An isogram (also known as a "nonpattern word") is a word or phrase without a repeating letter, however spaces and hyphens are allowed to appear multiple times. +An isogram (also known as a "non-pattern word") is a word or phrase without a repeating letter, however spaces and hyphens are allowed to appear multiple times. Examples of isograms: @@ -11,4 +11,4 @@ Examples of isograms: - downstream - six-year-old -The word *isograms*, however, is not an isogram, because the s repeats. +The word _isograms_, however, is not an isogram, because the s repeats. diff --git a/exercises/practice/knapsack/.docs/instructions.md b/exercises/practice/knapsack/.docs/instructions.md index 1dbbca91c..fadcee1b1 100644 --- a/exercises/practice/knapsack/.docs/instructions.md +++ b/exercises/practice/knapsack/.docs/instructions.md @@ -13,10 +13,12 @@ Given a knapsack with a specific carrying capacity (W), help Bob determine the m Note that Bob can take only one of each item. All values given will be strictly positive. -Items will be represented as a list of pairs, `wi` and `vi`, where the first element `wi` is the weight of the *i*th item and `vi` is the value for that item. +Items will be represented as a list of items. +Each item will have a weight and value. For example: +```none Items: [ { "weight": 5, "value": 10 }, { "weight": 4, "value": 40 }, @@ -25,6 +27,7 @@ Items: [ ] Knapsack Limit: 10 +``` For the above, the first item has weight 5 and value 10, the second item has weight 4 and value 40, and so on. diff --git a/exercises/practice/largest-series-product/.meta/config.json b/exercises/practice/largest-series-product/.meta/config.json index 6a457ceeb..9e4b495a8 100644 --- a/exercises/practice/largest-series-product/.meta/config.json +++ b/exercises/practice/largest-series-product/.meta/config.json @@ -31,5 +31,5 @@ }, "blurb": "Given a string of digits, calculate the largest product for a contiguous substring of digits of length n.", "source": "A variation on Problem 8 at Project Euler", - "source_url": "http://projecteuler.net/problem=8" + "source_url": "https://projecteuler.net/problem=8" } diff --git a/exercises/practice/leap/.docs/instructions.md b/exercises/practice/leap/.docs/instructions.md index dc7b4e816..a83826b2e 100644 --- a/exercises/practice/leap/.docs/instructions.md +++ b/exercises/practice/leap/.docs/instructions.md @@ -10,15 +10,13 @@ on every year that is evenly divisible by 4 unless the year is also evenly divisible by 400 ``` -For example, 1997 is not a leap year, but 1996 is. 1900 is not a leap -year, but 2000 is. +For example, 1997 is not a leap year, but 1996 is. +1900 is not a leap year, but 2000 is. ## Notes -Though our exercise adopts some very simple rules, there is more to -learn! +Though our exercise adopts some very simple rules, there is more to learn! -For a delightful, four minute explanation of the whole leap year -phenomenon, go watch [this youtube video][video]. +For a delightful, four minute explanation of the whole leap year phenomenon, go watch [this youtube video][video]. -[video]: http://www.youtube.com/watch?v=xX96xng7sAE +[video]: https://www.youtube.com/watch?v=xX96xng7sAE diff --git a/exercises/practice/leap/.meta/config.json b/exercises/practice/leap/.meta/config.json index 1f3b24293..38a2d0735 100644 --- a/exercises/practice/leap/.meta/config.json +++ b/exercises/practice/leap/.meta/config.json @@ -45,6 +45,6 @@ ] }, "blurb": "Given a year, report if it is a leap year.", - "source": "JavaRanch Cattle Drive, exercise 3", - "source_url": "http://www.javaranch.com/leap.jsp" + "source": "CodeRanch Cattle Drive, Assignment 3", + "source_url": "https://coderanch.com/t/718816/Leap" } diff --git a/exercises/practice/luhn/.docs/instructions.md b/exercises/practice/luhn/.docs/instructions.md index c7c7d3e0f..8cbe791fc 100644 --- a/exercises/practice/luhn/.docs/instructions.md +++ b/exercises/practice/luhn/.docs/instructions.md @@ -2,35 +2,31 @@ Given a number determine whether or not it is valid per the Luhn formula. -The [Luhn algorithm](https://en.wikipedia.org/wiki/Luhn_algorithm) is -a simple checksum formula used to validate a variety of identification -numbers, such as credit card numbers and Canadian Social Insurance -Numbers. +The [Luhn algorithm][luhn] is a simple checksum formula used to validate a variety of identification numbers, such as credit card numbers and Canadian Social Insurance Numbers. The task is to check if a given string is valid. -Validating a Number ------- +## Validating a Number -Strings of length 1 or less are not valid. Spaces are allowed in the input, -but they should be stripped before checking. All other non-digit characters -are disallowed. +Strings of length 1 or less are not valid. +Spaces are allowed in the input, but they should be stripped before checking. +All other non-digit characters are disallowed. -## Example 1: valid credit card number +### Example 1: valid credit card number ```text 4539 3195 0343 6467 ``` -The first step of the Luhn algorithm is to double every second digit, -starting from the right. We will be doubling +The first step of the Luhn algorithm is to double every second digit, starting from the right. +We will be doubling ```text 4_3_ 3_9_ 0_4_ 6_6_ ``` -If doubling the number results in a number greater than 9 then subtract 9 -from the product. The results of our doubling: +If doubling the number results in a number greater than 9 then subtract 9 from the product. +The results of our doubling: ```text 8569 6195 0383 3437 @@ -42,9 +38,10 @@ Then sum all of the digits: 8+5+6+9+6+1+9+5+0+3+8+3+3+4+3+7 = 80 ``` -If the sum is evenly divisible by 10, then the number is valid. This number is valid! +If the sum is evenly divisible by 10, then the number is valid. +This number is valid! -## Example 2: invalid credit card number +### Example 2: invalid credit card number ```text 8273 1232 7352 0569 @@ -63,3 +60,5 @@ Sum the digits ``` 57 is not evenly divisible by 10, so this number is not valid. + +[luhn]: https://en.wikipedia.org/wiki/Luhn_algorithm diff --git a/exercises/practice/luhn/.meta/config.json b/exercises/practice/luhn/.meta/config.json index 01a41e8e3..f8b02e36c 100644 --- a/exercises/practice/luhn/.meta/config.json +++ b/exercises/practice/luhn/.meta/config.json @@ -37,5 +37,5 @@ }, "blurb": "Given a number determine whether or not it is valid per the Luhn formula.", "source": "The Luhn Algorithm on Wikipedia", - "source_url": "http://en.wikipedia.org/wiki/Luhn_algorithm" + "source_url": "https://en.wikipedia.org/wiki/Luhn_algorithm" } diff --git a/exercises/practice/matching-brackets/.docs/instructions.md b/exercises/practice/matching-brackets/.docs/instructions.md index 364ecad21..544daa968 100644 --- a/exercises/practice/matching-brackets/.docs/instructions.md +++ b/exercises/practice/matching-brackets/.docs/instructions.md @@ -1,5 +1,4 @@ # Instructions -Given a string containing brackets `[]`, braces `{}`, parentheses `()`, -or any combination thereof, verify that any and all pairs are matched -and nested correctly. +Given a string containing brackets `[]`, braces `{}`, parentheses `()`, or any combination thereof, verify that any and all pairs are matched and nested correctly. +The string may also contain other characters, which for the purposes of this exercise should be ignored. diff --git a/exercises/practice/minesweeper/.docs/instructions.append.md b/exercises/practice/minesweeper/.docs/instructions.append.md new file mode 100644 index 000000000..51d0953a4 --- /dev/null +++ b/exercises/practice/minesweeper/.docs/instructions.append.md @@ -0,0 +1,10 @@ +# Instructions append + +## Performance Hint + +All the inputs and outputs are in ASCII. +Rust `String`s and `&str` are utf8, so while one might expect `"Hello".chars()` to be simple, it actually has to check each char to see if it's 1, 2, 3 or 4 `u8`s long. +If we know a `&str` is ASCII then we can call `.as_bytes()` and refer to the underlying data as a `&[u8]` (byte slice). +Iterating over a slice of ASCII bytes is much quicker as there are no codepoints involved - every ASCII byte is one `u8` long. + +Can you complete the challenge without cloning the input? diff --git a/exercises/practice/minesweeper/.docs/instructions.md b/exercises/practice/minesweeper/.docs/instructions.md index 72ffc65c5..f5f918bdf 100644 --- a/exercises/practice/minesweeper/.docs/instructions.md +++ b/exercises/practice/minesweeper/.docs/instructions.md @@ -2,25 +2,20 @@ Add the mine counts to a completed Minesweeper board. -Minesweeper is a popular game where the user has to find the mines using -numeric hints that indicate how many mines are directly adjacent -(horizontally, vertically, diagonally) to a square. +Minesweeper is a popular game where the user has to find the mines using numeric hints that indicate how many mines are directly adjacent (horizontally, vertically, diagonally) to a square. -In this exercise you have to create some code that counts the number of -mines adjacent to a given empty square and replaces that square with the -count. +In this exercise you have to create some code that counts the number of mines adjacent to a given empty square and replaces that square with the count. -The board is a rectangle composed of blank space (' ') characters. A mine -is represented by an asterisk ('\*') character. +The board is a rectangle composed of blank space (' ') characters. +A mine is represented by an asterisk (`*`) character. If a given space has no adjacent mines at all, leave that square blank. ## Examples -For example you may receive a 5 x 4 board like this (empty spaces are -represented here with the '·' character for display on screen): +For example you may receive a 5 x 4 board like this (empty spaces are represented here with the '·' character for display on screen): -``` +```text ·*·*· ··*·· ··*·· @@ -29,20 +24,9 @@ represented here with the '·' character for display on screen): And your code will transform it into this: -``` +```text 1*3*1 13*31 ·2*2· ·111· ``` - -## Performance Hint - -All the inputs and outputs are in ASCII. Rust `String`s and `&str` are utf8, -so while one might expect "Hello".chars() to be simple, it actually has to -check each char to see if it's 1, 2, 3 or 4 `u8`s long. If we know a `&str` -is ASCII then we can call `.as_bytes()` and refer to the underlying data via a `&[u8]` slice. -Iterating over a u8 slice of ASCII is much quicker as there are no codepoints -involved - every ASCII char is one u8 long. - -Can you complete the challenge without cloning the input? diff --git a/exercises/practice/nth-prime/.docs/instructions.md b/exercises/practice/nth-prime/.docs/instructions.md index 30a75216f..065e323ab 100644 --- a/exercises/practice/nth-prime/.docs/instructions.md +++ b/exercises/practice/nth-prime/.docs/instructions.md @@ -2,8 +2,6 @@ Given a number n, determine what the nth prime is. -By listing the first six prime numbers: 2, 3, 5, 7, 11, and 13, we can see that -the 6th prime is 13. +By listing the first six prime numbers: 2, 3, 5, 7, 11, and 13, we can see that the 6th prime is 13. -If your language provides methods in the standard library to deal with prime -numbers, pretend they don't exist and implement them yourself. +If your language provides methods in the standard library to deal with prime numbers, pretend they don't exist and implement them yourself. diff --git a/exercises/practice/nth-prime/.meta/config.json b/exercises/practice/nth-prime/.meta/config.json index 42f584310..05d34136d 100644 --- a/exercises/practice/nth-prime/.meta/config.json +++ b/exercises/practice/nth-prime/.meta/config.json @@ -33,5 +33,5 @@ }, "blurb": "Given a number n, determine what the nth prime is.", "source": "A variation on Problem 7 at Project Euler", - "source_url": "http://projecteuler.net/problem=7" + "source_url": "https://projecteuler.net/problem=7" } diff --git a/exercises/practice/nucleotide-count/.docs/instructions.md b/exercises/practice/nucleotide-count/.docs/instructions.md index cd0875894..548d9ba5a 100644 --- a/exercises/practice/nucleotide-count/.docs/instructions.md +++ b/exercises/practice/nucleotide-count/.docs/instructions.md @@ -1,10 +1,12 @@ # Instructions -Each of us inherits from our biological parents a set of chemical instructions known as DNA that influence how our bodies are constructed. All known life depends on DNA! +Each of us inherits from our biological parents a set of chemical instructions known as DNA that influence how our bodies are constructed. +All known life depends on DNA! > Note: You do not need to understand anything about nucleotides or DNA to complete this exercise. -DNA is a long chain of other chemicals and the most important are the four nucleotides, adenine, cytosine, guanine and thymine. A single DNA chain can contain billions of these four nucleotides and the order in which they occur is important! +DNA is a long chain of other chemicals and the most important are the four nucleotides, adenine, cytosine, guanine and thymine. +A single DNA chain can contain billions of these four nucleotides and the order in which they occur is important! We call the order of these nucleotides in a bit of DNA a "DNA sequence". We represent a DNA sequence as an ordered collection of these four nucleotides and a common way to do that is with a string of characters such as "ATTACG" for a DNA sequence of 6 nucleotides. @@ -15,7 +17,7 @@ If the string contains characters that aren't A, C, G, or T then it is invalid a For example: -``` +```text "GATTACA" -> 'A': 3, 'C': 1, 'G': 1, 'T': 2 "INVALID" -> error ``` diff --git a/exercises/practice/nucleotide-count/.meta/config.json b/exercises/practice/nucleotide-count/.meta/config.json index 6d0ff3e61..cf74f9523 100644 --- a/exercises/practice/nucleotide-count/.meta/config.json +++ b/exercises/practice/nucleotide-count/.meta/config.json @@ -38,5 +38,5 @@ }, "blurb": "Given a DNA string, compute how many times each nucleotide occurs in the string.", "source": "The Calculating DNA Nucleotides_problem at Rosalind", - "source_url": "http://rosalind.info/problems/dna/" + "source_url": "https://rosalind.info/problems/dna/" } diff --git a/exercises/practice/ocr-numbers/.docs/instructions.md b/exercises/practice/ocr-numbers/.docs/instructions.md index a246b898a..7beb25779 100644 --- a/exercises/practice/ocr-numbers/.docs/instructions.md +++ b/exercises/practice/ocr-numbers/.docs/instructions.md @@ -1,9 +1,8 @@ # Instructions -Given a 3 x 4 grid of pipes, underscores, and spaces, determine which number is -represented, or whether it is garbled. +Given a 3 x 4 grid of pipes, underscores, and spaces, determine which number is represented, or whether it is garbled. -# Step One +## Step One To begin with, convert a simple binary font to a string containing 0 or 1. @@ -31,11 +30,11 @@ If the input is the correct size, but not recognizable, your program should retu If the input is the incorrect size, your program should return an error. -# Step Two +## Step Two Update your program to recognize multi-character binary strings, replacing garbled numbers with ? -# Step Three +## Step Three Update your program to recognize all numbers 0 through 9, both individually and as part of a larger string. @@ -57,9 +56,10 @@ Is converted to "2" Is converted to "1234567890" -# Step Four +## Step Four -Update your program to handle multiple numbers, one per line. When converting several lines, join the lines with commas. +Update your program to handle multiple numbers, one per line. +When converting several lines, join the lines with commas. ```text _ _ @@ -76,4 +76,4 @@ Update your program to handle multiple numbers, one per line. When converting se ``` -Is converted to "123,456,789" +Is converted to "123,456,789". diff --git a/exercises/practice/paasio/.docs/instructions.md b/exercises/practice/paasio/.docs/instructions.md index 67aaefaaa..595644748 100644 --- a/exercises/practice/paasio/.docs/instructions.md +++ b/exercises/practice/paasio/.docs/instructions.md @@ -2,13 +2,12 @@ Report network IO statistics. -You are writing a [PaaS][], and you need a way to bill customers based -on network and filesystem usage. +You are writing a [PaaS][paas], and you need a way to bill customers based on network and filesystem usage. -Create a wrapper for network connections and files that can report IO -statistics. The wrapper must report: +Create a wrapper for network connections and files that can report IO statistics. +The wrapper must report: - The total number of bytes read/written. - The total number of read/write operations. -[PaaS]: http://en.wikipedia.org/wiki/Platform_as_a_service +[paas]: https://en.wikipedia.org/wiki/Platform_as_a_service diff --git a/exercises/practice/palindrome-products/.docs/instructions.md b/exercises/practice/palindrome-products/.docs/instructions.md index fd9a44124..aac66521c 100644 --- a/exercises/practice/palindrome-products/.docs/instructions.md +++ b/exercises/practice/palindrome-products/.docs/instructions.md @@ -2,15 +2,14 @@ Detect palindrome products in a given range. -A palindromic number is a number that remains the same when its digits are -reversed. For example, `121` is a palindromic number but `112` is not. +A palindromic number is a number that remains the same when its digits are reversed. +For example, `121` is a palindromic number but `112` is not. Given a range of numbers, find the largest and smallest palindromes which are products of two numbers within that range. -Your solution should return the largest and smallest palindromes, along with the -factors of each within the range. If the largest or smallest palindrome has more -than one pair of factors within the range, then return all the pairs. +Your solution should return the largest and smallest palindromes, along with the factors of each within the range. +If the largest or smallest palindrome has more than one pair of factors within the range, then return all the pairs. ## Example 1 @@ -22,12 +21,16 @@ And given the list of all possible products within this range: The palindrome products are all single digit numbers (in this case): `[1, 2, 3, 4, 5, 6, 7, 8, 9]` -The smallest palindrome product is `1`. Its factors are `(1, 1)`. -The largest palindrome product is `9`. Its factors are `(1, 9)` and `(3, 3)`. +The smallest palindrome product is `1`. +Its factors are `(1, 1)`. +The largest palindrome product is `9`. +Its factors are `(1, 9)` and `(3, 3)`. ## Example 2 Given the range `[10, 99]` (both inclusive)... -The smallest palindrome product is `121`. Its factors are `(11, 11)`. -The largest palindrome product is `9009`. Its factors are `(91, 99)`. +The smallest palindrome product is `121`. +Its factors are `(11, 11)`. +The largest palindrome product is `9009`. +Its factors are `(91, 99)`. diff --git a/exercises/practice/palindrome-products/.meta/config.json b/exercises/practice/palindrome-products/.meta/config.json index ffcc8fe67..a82707d20 100644 --- a/exercises/practice/palindrome-products/.meta/config.json +++ b/exercises/practice/palindrome-products/.meta/config.json @@ -30,7 +30,7 @@ }, "blurb": "Detect palindrome products in a given range.", "source": "Problem 4 at Project Euler", - "source_url": "http://projecteuler.net/problem=4", + "source_url": "https://projecteuler.net/problem=4", "custom": { "test-in-release-mode": true } diff --git a/exercises/practice/pangram/.docs/instructions.md b/exercises/practice/pangram/.docs/instructions.md index d5698bc2a..817c872d9 100644 --- a/exercises/practice/pangram/.docs/instructions.md +++ b/exercises/practice/pangram/.docs/instructions.md @@ -5,4 +5,4 @@ Your task is to figure out if a sentence is a pangram. A pangram is a sentence using every letter of the alphabet at least once. It is case insensitive, so it doesn't matter if a letter is lower-case (e.g. `k`) or upper-case (e.g. `K`). -For this exercise we only use the basic letters used in the English alphabet: `a` to `z`. +For this exercise, a sentence is a pangram if it contains each of the 26 letters in the English alphabet. diff --git a/exercises/practice/parallel-letter-frequency/.docs/instructions.md b/exercises/practice/parallel-letter-frequency/.docs/instructions.md index a5b936c5e..85abcf86a 100644 --- a/exercises/practice/parallel-letter-frequency/.docs/instructions.md +++ b/exercises/practice/parallel-letter-frequency/.docs/instructions.md @@ -2,7 +2,6 @@ Count the frequency of letters in texts using parallel computation. -Parallelism is about doing things in parallel that can also be done -sequentially. A common example is counting the frequency of letters. -Create a function that returns the total frequency of each letter in a -list of texts and that employs parallelism. +Parallelism is about doing things in parallel that can also be done sequentially. +A common example is counting the frequency of letters. +Create a function that returns the total frequency of each letter in a list of texts and that employs parallelism. diff --git a/exercises/practice/perfect-numbers/.docs/instructions.md b/exercises/practice/perfect-numbers/.docs/instructions.md index 144c9133e..689a73c00 100644 --- a/exercises/practice/perfect-numbers/.docs/instructions.md +++ b/exercises/practice/perfect-numbers/.docs/instructions.md @@ -1,9 +1,10 @@ # Instructions -Determine if a number is perfect, abundant, or deficient based on -Nicomachus' (60 - 120 CE) classification scheme for positive integers. +Determine if a number is perfect, abundant, or deficient based on Nicomachus' (60 - 120 CE) classification scheme for positive integers. -The Greek mathematician [Nicomachus](https://en.wikipedia.org/wiki/Nicomachus) devised a classification scheme for positive integers, identifying each as belonging uniquely to the categories of **perfect**, **abundant**, or **deficient** based on their [aliquot sum](https://en.wikipedia.org/wiki/Aliquot_sum). The aliquot sum is defined as the sum of the factors of a number not including the number itself. For example, the aliquot sum of 15 is (1 + 3 + 5) = 9 +The Greek mathematician [Nicomachus][nicomachus] devised a classification scheme for positive integers, identifying each as belonging uniquely to the categories of **perfect**, **abundant**, or **deficient** based on their [aliquot sum][aliquot-sum]. +The aliquot sum is defined as the sum of the factors of a number not including the number itself. +For example, the aliquot sum of `15` is `1 + 3 + 5 = 9`. - **Perfect**: aliquot sum = number - 6 is a perfect number because (1 + 2 + 3) = 6 @@ -15,4 +16,8 @@ The Greek mathematician [Nicomachus](https://en.wikipedia.org/wiki/Nicomachus) d - 8 is a deficient number because (1 + 2 + 4) = 7 - Prime numbers are deficient -Implement a way to determine whether a given number is **perfect**. Depending on your language track, you may also need to implement a way to determine whether a given number is **abundant** or **deficient**. +Implement a way to determine whether a given number is **perfect**. +Depending on your language track, you may also need to implement a way to determine whether a given number is **abundant** or **deficient**. + +[nicomachus]: https://en.wikipedia.org/wiki/Nicomachus +[aliquot-sum]: https://en.wikipedia.org/wiki/Aliquot_sum diff --git a/exercises/practice/perfect-numbers/.meta/config.json b/exercises/practice/perfect-numbers/.meta/config.json index 50d15d8d8..e2ba30d96 100644 --- a/exercises/practice/perfect-numbers/.meta/config.json +++ b/exercises/practice/perfect-numbers/.meta/config.json @@ -32,7 +32,7 @@ }, "blurb": "Determine if a number is perfect, abundant, or deficient based on Nicomachus' (60 - 120 CE) classification scheme for positive integers.", "source": "Taken from Chapter 2 of Functional Thinking by Neal Ford.", - "source_url": "http://shop.oreilly.com/product/0636920029687.do", + "source_url": "https://www.oreilly.com/library/view/functional-thinking/9781449365509/", "custom": { "ignore-count-ignores": true } diff --git a/exercises/practice/phone-number/.docs/instructions.md b/exercises/practice/phone-number/.docs/instructions.md index 6e36daefe..62ba48e96 100644 --- a/exercises/practice/phone-number/.docs/instructions.md +++ b/exercises/practice/phone-number/.docs/instructions.md @@ -2,21 +2,26 @@ Clean up user-entered phone numbers so that they can be sent SMS messages. -The **North American Numbering Plan (NANP)** is a telephone numbering system used by many countries in North America like the United States, Canada or Bermuda. All NANP-countries share the same international country code: `1`. +The **North American Numbering Plan (NANP)** is a telephone numbering system used by many countries in North America like the United States, Canada or Bermuda. +All NANP-countries share the same international country code: `1`. -NANP numbers are ten-digit numbers consisting of a three-digit Numbering Plan Area code, commonly known as *area code*, followed by a seven-digit local number. The first three digits of the local number represent the *exchange code*, followed by the unique four-digit number which is the *subscriber number*. +NANP numbers are ten-digit numbers consisting of a three-digit Numbering Plan Area code, commonly known as _area code_, followed by a seven-digit local number. +The first three digits of the local number represent the _exchange code_, followed by the unique four-digit number which is the _subscriber number_. The format is usually represented as ```text -(NXX)-NXX-XXXX +NXX NXX-XXXX ``` where `N` is any digit from 2 through 9 and `X` is any digit from 0 through 9. -Your task is to clean up differently formatted telephone numbers by removing punctuation and the country code (1) if present. +Sometimes they also have the country code (represented as `1` or `+1`) prefixed. + +Your task is to clean up differently formatted telephone numbers by removing punctuation and the country code if present. For example, the inputs + - `+1 (613)-995-0253` - `613-995-0253` - `1 613 995 0253` diff --git a/exercises/practice/phone-number/.meta/config.json b/exercises/practice/phone-number/.meta/config.json index e85aab65b..ce9e04ad6 100644 --- a/exercises/practice/phone-number/.meta/config.json +++ b/exercises/practice/phone-number/.meta/config.json @@ -40,6 +40,6 @@ ] }, "blurb": "Clean up user-entered phone numbers so that they can be sent SMS messages.", - "source": "Event Manager by JumpstartLab", - "source_url": "http://tutorials.jumpstartlab.com/projects/eventmanager.html" + "source": "Exercise by the JumpstartLab team for students at The Turing School of Software and Design.", + "source_url": "https://turing.edu/" } diff --git a/exercises/practice/pig-latin/.docs/instructions.md b/exercises/practice/pig-latin/.docs/instructions.md index bcb125117..032905aa9 100644 --- a/exercises/practice/pig-latin/.docs/instructions.md +++ b/exercises/practice/pig-latin/.docs/instructions.md @@ -2,17 +2,19 @@ Implement a program that translates from English to Pig Latin. -Pig Latin is a made-up children's language that's intended to be -confusing. It obeys a few simple rules (below), but when it's spoken -quickly it's really difficult for non-children (and non-native speakers) -to understand. +Pig Latin is a made-up children's language that's intended to be confusing. +It obeys a few simple rules (below), but when it's spoken quickly it's really difficult for non-children (and non-native speakers) to understand. -- **Rule 1**: If a word begins with a vowel sound, add an "ay" sound to the end of the word. Please note that "xr" and "yt" at the beginning of a word make vowel sounds (e.g. "xray" -> "xrayay", "yttria" -> "yttriaay"). -- **Rule 2**: If a word begins with a consonant sound, move it to the end of the word and then add an "ay" sound to the end of the word. Consonant sounds can be made up of multiple consonants, a.k.a. a consonant cluster (e.g. "chair" -> "airchay"). +- **Rule 1**: If a word begins with a vowel sound, add an "ay" sound to the end of the word. + Please note that "xr" and "yt" at the beginning of a word make vowel sounds (e.g. "xray" -> "xrayay", "yttria" -> "yttriaay"). +- **Rule 2**: If a word begins with a consonant sound, move it to the end of the word and then add an "ay" sound to the end of the word. + Consonant sounds can be made up of multiple consonants, such as the "ch" in "chair" or "st" in "stand" (e.g. "chair" -> "airchay"). - **Rule 3**: If a word starts with a consonant sound followed by "qu", move it to the end of the word, and then add an "ay" sound to the end of the word (e.g. "square" -> "aresquay"). - **Rule 4**: If a word contains a "y" after a consonant cluster or as the second letter in a two letter word it makes a vowel sound (e.g. "rhythm" -> "ythmrhay", "my" -> "ymay"). -There are a few more rules for edge cases, and there are regional -variants too. +There are a few more rules for edge cases, and there are regional variants too. +Check the tests for all the details. -See for more details. +Read more about [Pig Latin on Wikipedia][pig-latin]. + +[pig-latin]: https://en.wikipedia.org/wiki/Pig_latin diff --git a/exercises/practice/poker/.docs/instructions.md b/exercises/practice/poker/.docs/instructions.md index 6a38cf4bc..492fc4c9e 100644 --- a/exercises/practice/poker/.docs/instructions.md +++ b/exercises/practice/poker/.docs/instructions.md @@ -2,5 +2,6 @@ Pick the best hand(s) from a list of poker hands. -See [wikipedia](https://en.wikipedia.org/wiki/List_of_poker_hands) for an -overview of poker hands. +See [wikipedia][poker-hands] for an overview of poker hands. + +[poker-hands]: https://en.wikipedia.org/wiki/List_of_poker_hands diff --git a/exercises/practice/prime-factors/.docs/instructions.md b/exercises/practice/prime-factors/.docs/instructions.md index b5cb1657e..252cc8ee1 100644 --- a/exercises/practice/prime-factors/.docs/instructions.md +++ b/exercises/practice/prime-factors/.docs/instructions.md @@ -10,21 +10,27 @@ Note that 1 is not a prime number. What are the prime factors of 60? -- Our first divisor is 2. 2 goes into 60, leaving 30. +- Our first divisor is 2. + 2 goes into 60, leaving 30. - 2 goes into 30, leaving 15. - - 2 doesn't go cleanly into 15. So let's move on to our next divisor, 3. + - 2 doesn't go cleanly into 15. + So let's move on to our next divisor, 3. - 3 goes cleanly into 15, leaving 5. - - 3 does not go cleanly into 5. The next possible factor is 4. - - 4 does not go cleanly into 5. The next possible factor is 5. + - 3 does not go cleanly into 5. + The next possible factor is 4. + - 4 does not go cleanly into 5. + The next possible factor is 5. - 5 does go cleanly into 5. - We're left only with 1, so now, we're done. -Our successful divisors in that computation represent the list of prime -factors of 60: 2, 2, 3, and 5. +Our successful divisors in that computation represent the list of prime factors of 60: 2, 2, 3, and 5. You can check this yourself: -- 2 * 2 * 3 * 5 -- = 4 * 15 -- = 60 -- Success! +```text +2 * 2 * 3 * 5 += 4 * 15 += 60 +``` + +Success! diff --git a/exercises/practice/prime-factors/.meta/config.json b/exercises/practice/prime-factors/.meta/config.json index 79d265ace..1d2e5d6c6 100644 --- a/exercises/practice/prime-factors/.meta/config.json +++ b/exercises/practice/prime-factors/.meta/config.json @@ -32,5 +32,5 @@ }, "blurb": "Compute the prime factors of a given natural number.", "source": "The Prime Factors Kata by Uncle Bob", - "source_url": "http://butunclebob.com/ArticleS.UncleBob.ThePrimeFactorsKata" + "source_url": "https://web.archive.org/web/20221026171801/http://butunclebob.com/ArticleS.UncleBob.ThePrimeFactorsKata" } diff --git a/exercises/practice/protein-translation/.docs/instructions.md b/exercises/practice/protein-translation/.docs/instructions.md index c211345ed..7dc34d2ed 100644 --- a/exercises/practice/protein-translation/.docs/instructions.md +++ b/exercises/practice/protein-translation/.docs/instructions.md @@ -11,7 +11,8 @@ Codons: `"AUG", "UUU", "UCU"` Protein: `"Methionine", "Phenylalanine", "Serine"` -There are 64 codons which in turn correspond to 20 amino acids; however, all of the codon sequences and resulting amino acids are not important in this exercise. If it works for one codon, the program should work for all of them. +There are 64 codons which in turn correspond to 20 amino acids; however, all of the codon sequences and resulting amino acids are not important in this exercise. +If it works for one codon, the program should work for all of them. However, feel free to expand the list in the test suite to include them all. There are also three terminating codons (also known as 'STOP' codons); if any of these codons are encountered (by the ribosome), all translation ends and the protein is terminated. @@ -28,15 +29,17 @@ Note the stop codon `"UAA"` terminates the translation and the final methionine Below are the codons and resulting Amino Acids needed for the exercise. -Codon | Protein -:--- | :--- -AUG | Methionine -UUU, UUC | Phenylalanine -UUA, UUG | Leucine -UCU, UCC, UCA, UCG | Serine -UAU, UAC | Tyrosine -UGU, UGC | Cysteine -UGG | Tryptophan -UAA, UAG, UGA | STOP - -Learn more about [protein translation on Wikipedia](http://en.wikipedia.org/wiki/Translation_(biology)) +| Codon | Protein | +| :----------------- | :------------ | +| AUG | Methionine | +| UUU, UUC | Phenylalanine | +| UUA, UUG | Leucine | +| UCU, UCC, UCA, UCG | Serine | +| UAU, UAC | Tyrosine | +| UGU, UGC | Cysteine | +| UGG | Tryptophan | +| UAA, UAG, UGA | STOP | + +Learn more about [protein translation on Wikipedia][protein-translation]. + +[protein-translation]: https://en.wikipedia.org/wiki/Translation_(biology) diff --git a/exercises/practice/proverb/.docs/instructions.md b/exercises/practice/proverb/.docs/instructions.md index 0da9da2cb..f6fb85932 100644 --- a/exercises/practice/proverb/.docs/instructions.md +++ b/exercises/practice/proverb/.docs/instructions.md @@ -2,7 +2,8 @@ For want of a horseshoe nail, a kingdom was lost, or so the saying goes. -Given a list of inputs, generate the relevant proverb. For example, given the list `["nail", "shoe", "horse", "rider", "message", "battle", "kingdom"]`, you will output the full text of this proverbial rhyme: +Given a list of inputs, generate the relevant proverb. +For example, given the list `["nail", "shoe", "horse", "rider", "message", "battle", "kingdom"]`, you will output the full text of this proverbial rhyme: ```text For want of a nail the shoe was lost. @@ -14,4 +15,5 @@ For want of a battle the kingdom was lost. And all for the want of a nail. ``` -Note that the list of inputs may vary; your solution should be able to handle lists of arbitrary length (including zero elements, generating empty output) and content. No line of the output text should be a static, unchanging string; all should vary according to the input given. +Note that the list of inputs may vary; your solution should be able to handle lists of arbitrary length and content. +No line of the output text should be a static, unchanging string; all should vary according to the input given. diff --git a/exercises/practice/proverb/.meta/config.json b/exercises/practice/proverb/.meta/config.json index d3b955805..a2346dfa0 100644 --- a/exercises/practice/proverb/.meta/config.json +++ b/exercises/practice/proverb/.meta/config.json @@ -32,5 +32,5 @@ }, "blurb": "For want of a horseshoe nail, a kingdom was lost, or so the saying goes. Output the full text of this proverbial rhyme.", "source": "Wikipedia", - "source_url": "http://en.wikipedia.org/wiki/For_Want_of_a_Nail" + "source_url": "https://en.wikipedia.org/wiki/For_Want_of_a_Nail" } diff --git a/exercises/practice/pythagorean-triplet/.docs/instructions.md b/exercises/practice/pythagorean-triplet/.docs/instructions.md index 395ff6a55..1c1a8aea6 100644 --- a/exercises/practice/pythagorean-triplet/.docs/instructions.md +++ b/exercises/practice/pythagorean-triplet/.docs/instructions.md @@ -1,10 +1,9 @@ # Instructions -A Pythagorean triplet is a set of three natural numbers, {a, b, c}, for -which, +A Pythagorean triplet is a set of three natural numbers, {a, b, c}, for which, ```text -a**2 + b**2 = c**2 +a² + b² = c² ``` and such that, @@ -16,7 +15,7 @@ a < b < c For example, ```text -3**2 + 4**2 = 9 + 16 = 25 = 5**2. +3² + 4² = 5². ``` Given an input integer N, find all Pythagorean triplets for which `a + b + c = N`. diff --git a/exercises/practice/pythagorean-triplet/.meta/config.json b/exercises/practice/pythagorean-triplet/.meta/config.json index 6adbaf21a..8519cc3db 100644 --- a/exercises/practice/pythagorean-triplet/.meta/config.json +++ b/exercises/practice/pythagorean-triplet/.meta/config.json @@ -35,5 +35,5 @@ }, "blurb": "There exists exactly one Pythagorean triplet for which a + b + c = 1000. Find the product a * b * c.", "source": "Problem 9 at Project Euler", - "source_url": "http://projecteuler.net/problem=9" + "source_url": "https://projecteuler.net/problem=9" } diff --git a/exercises/practice/queen-attack/.docs/instructions.md b/exercises/practice/queen-attack/.docs/instructions.md index 42528d5fd..ad7ea9547 100644 --- a/exercises/practice/queen-attack/.docs/instructions.md +++ b/exercises/practice/queen-attack/.docs/instructions.md @@ -1,58 +1,25 @@ # Instructions -Given the position of two queens on a chess board, indicate whether or not they -are positioned so that they can attack each other. +Given the position of two queens on a chess board, indicate whether or not they are positioned so that they can attack each other. -In the game of chess, a queen can attack pieces which are on the same -row, column, or diagonal. +In the game of chess, a queen can attack pieces which are on the same row, column, or diagonal. -A chessboard can be represented by an 8 by 8 array. The rows of a chessboard are known as ranks and columns are known as files. +A chessboard can be represented by an 8 by 8 array. -So if you're told the white queen is at (2, 3) and the black queen at -(5, 6), then you'd know you've got a set-up like so: +So if you are told the white queen is at `c5` (zero-indexed at column 2, row 3) and the black queen at `f2` (zero-indexed at column 5, row 6), then you know that the set-up is like so: ```text -_ _ _ _ _ _ _ _ -_ _ _ _ _ _ _ _ -_ _ _ W _ _ _ _ -_ _ _ _ _ _ _ _ -_ _ _ _ _ _ _ _ -_ _ _ _ _ _ B _ -_ _ _ _ _ _ _ _ -_ _ _ _ _ _ _ _ + a b c d e f g h +8 _ _ _ _ _ _ _ _ 8 +7 _ _ _ _ _ _ _ _ 7 +6 _ _ _ _ _ _ _ _ 6 +5 _ _ W _ _ _ _ _ 5 +4 _ _ _ _ _ _ _ _ 4 +3 _ _ _ _ _ _ _ _ 3 +2 _ _ _ _ _ B _ _ 2 +1 _ _ _ _ _ _ _ _ 1 + a b c d e f g h ``` -You'd also be able to answer whether the queens can attack each other. -In this case, that answer would be yes, they can, because both pieces -share a diagonal. - -### Examples of queens attacking: - -```text -_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ -_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ W _ _ _ _ -_ _ _ W _ _ _ _ _ B _ _ _ W _ _ _ _ _ _ _ _ _ _ -_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ -_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ -_ _ _ _ _ _ B _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ -_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ B _ _ _ _ -_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ -``` - -### Examples of queens not interacting: - -```text - -_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ -_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ W _ _ _ _ -_ _ _ _ _ _ _ _ _ _ _ _ _ W _ _ _ _ _ _ _ _ _ _ -_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ -_ _ _ _ W _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ -_ _ _ _ _ _ B _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ -_ _ _ _ _ _ _ _ _ _ B _ _ _ _ _ _ _ _ _ _ _ B _ -_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ -``` - -B and W stand for **Black** and **White**, the two sides competing -against each other in a game of chess. For this exercise you do not need to know which side -the queens are on. +You are also able to answer whether the queens can attack each other. +In this case, that answer would be yes, they can, because both pieces share a diagonal. diff --git a/exercises/practice/queen-attack/.meta/config.json b/exercises/practice/queen-attack/.meta/config.json index 7359547ef..da456c469 100644 --- a/exercises/practice/queen-attack/.meta/config.json +++ b/exercises/practice/queen-attack/.meta/config.json @@ -33,5 +33,5 @@ }, "blurb": "Given the position of two queens on a chess board, indicate whether or not they are positioned so that they can attack each other.", "source": "J Dalbey's Programming Practice problems", - "source_url": "http://users.csc.calpoly.edu/~jdalbey/103/Projects/ProgrammingPractice.html" + "source_url": "https://users.csc.calpoly.edu/~jdalbey/103/Projects/ProgrammingPractice.html" } diff --git a/exercises/practice/rail-fence-cipher/.docs/instructions.md b/exercises/practice/rail-fence-cipher/.docs/instructions.md index 0e75a2bf7..e311de6cd 100644 --- a/exercises/practice/rail-fence-cipher/.docs/instructions.md +++ b/exercises/practice/rail-fence-cipher/.docs/instructions.md @@ -2,15 +2,13 @@ Implement encoding and decoding for the rail fence cipher. -The Rail Fence cipher is a form of transposition cipher that gets its name from -the way in which it's encoded. It was already used by the ancient Greeks. +The Rail Fence cipher is a form of transposition cipher that gets its name from the way in which it's encoded. +It was already used by the ancient Greeks. -In the Rail Fence cipher, the message is written downwards on successive "rails" -of an imaginary fence, then moving up when we get to the bottom (like a zig-zag). +In the Rail Fence cipher, the message is written downwards on successive "rails" of an imaginary fence, then moving up when we get to the bottom (like a zig-zag). Finally the message is then read off in rows. -For example, using three "rails" and the message "WE ARE DISCOVERED FLEE AT ONCE", -the cipherer writes out: +For example, using three "rails" and the message "WE ARE DISCOVERED FLEE AT ONCE", the cipherer writes out: ```text W . . . E . . . C . . . R . . . L . . . T . . . E diff --git a/exercises/practice/raindrops/.docs/instructions.md b/exercises/practice/raindrops/.docs/instructions.md index a78585df2..fc61d36e9 100644 --- a/exercises/practice/raindrops/.docs/instructions.md +++ b/exercises/practice/raindrops/.docs/instructions.md @@ -1,6 +1,8 @@ # Instructions -Your task is to convert a number into a string that contains raindrop sounds corresponding to certain potential factors. A factor is a number that evenly divides into another number, leaving no remainder. The simplest way to test if a one number is a factor of another is to use the [modulo operation](https://en.wikipedia.org/wiki/Modulo_operation). +Your task is to convert a number into a string that contains raindrop sounds corresponding to certain potential factors. +A factor is a number that evenly divides into another number, leaving no remainder. +The simplest way to test if one number is a factor of another is to use the [modulo operation][modulo]. The rules of `raindrops` are that if a given number: @@ -14,3 +16,5 @@ The rules of `raindrops` are that if a given number: - 28 has 7 as a factor, but not 3 or 5, so the result would be "Plong". - 30 has both 3 and 5 as factors, but not 7, so the result would be "PlingPlang". - 34 is not factored by 3, 5, or 7, so the result would be "34". + +[modulo]: https://en.wikipedia.org/wiki/Modulo_operation diff --git a/exercises/practice/react/.docs/instructions.md b/exercises/practice/react/.docs/instructions.md index 5cad8825f..1b9a175d0 100644 --- a/exercises/practice/react/.docs/instructions.md +++ b/exercises/practice/react/.docs/instructions.md @@ -2,15 +2,10 @@ Implement a basic reactive system. -Reactive programming is a programming paradigm that focuses on how values -are computed in terms of each other to allow a change to one value to -automatically propagate to other values, like in a spreadsheet. +Reactive programming is a programming paradigm that focuses on how values are computed in terms of each other to allow a change to one value to automatically propagate to other values, like in a spreadsheet. -Implement a basic reactive system with cells with settable values ("input" -cells) and cells with values computed in terms of other cells ("compute" -cells). Implement updates so that when an input value is changed, values -propagate to reach a new stable system state. +Implement a basic reactive system with cells with settable values ("input" cells) and cells with values computed in terms of other cells ("compute" cells). +Implement updates so that when an input value is changed, values propagate to reach a new stable system state. -In addition, compute cells should allow for registering change notification -callbacks. Call a cell’s callbacks when the cell’s value in a new stable -state has changed from the previous stable state. +In addition, compute cells should allow for registering change notification callbacks. +Call a cell’s callbacks when the cell’s value in a new stable state has changed from the previous stable state. diff --git a/exercises/practice/rectangles/.docs/instructions.md b/exercises/practice/rectangles/.docs/instructions.md index e1efd7473..8eb4ed470 100644 --- a/exercises/practice/rectangles/.docs/instructions.md +++ b/exercises/practice/rectangles/.docs/instructions.md @@ -10,7 +10,7 @@ Count the rectangles in an ASCII diagram like the one below. +--+--+ ``` -The above diagram contains 6 rectangles: +The above diagram contains these 6 rectangles: ```text @@ -60,5 +60,4 @@ The above diagram contains 6 rectangles: ``` -You may assume that the input is always a proper rectangle (i.e. the length of -every line equals the length of the first line). +You may assume that the input is always a proper rectangle (i.e. the length of every line equals the length of the first line). diff --git a/exercises/practice/robot-name/.docs/instructions.md b/exercises/practice/robot-name/.docs/instructions.md index a0079a341..fca3a41ae 100644 --- a/exercises/practice/robot-name/.docs/instructions.md +++ b/exercises/practice/robot-name/.docs/instructions.md @@ -4,13 +4,11 @@ Manage robot factory settings. When a robot comes off the factory floor, it has no name. -The first time you turn on a robot, a random name is generated in the format -of two uppercase letters followed by three digits, such as RX837 or BC811. +The first time you turn on a robot, a random name is generated in the format of two uppercase letters followed by three digits, such as RX837 or BC811. -Every once in a while we need to reset a robot to its factory settings, -which means that its name gets wiped. The next time you ask, that robot will -respond with a new random name. +Every once in a while we need to reset a robot to its factory settings, which means that its name gets wiped. +The next time you ask, that robot will respond with a new random name. The names must be random: they should not follow a predictable sequence. -Using random names means a risk of collisions. Your solution must ensure that -every existing robot has a unique name. +Using random names means a risk of collisions. +Your solution must ensure that every existing robot has a unique name. diff --git a/exercises/practice/robot-simulator/.docs/instructions.md b/exercises/practice/robot-simulator/.docs/instructions.md index 83be50ccc..0ac96ce0b 100644 --- a/exercises/practice/robot-simulator/.docs/instructions.md +++ b/exercises/practice/robot-simulator/.docs/instructions.md @@ -10,13 +10,10 @@ The robots have three possible movements: - turn left - advance -Robots are placed on a hypothetical infinite grid, facing a particular -direction (north, east, south, or west) at a set of {x,y} coordinates, +Robots are placed on a hypothetical infinite grid, facing a particular direction (north, east, south, or west) at a set of {x,y} coordinates, e.g., {3,8}, with coordinates increasing to the north and east. -The robot then receives a number of instructions, at which point the -testing facility verifies the robot's new position, and in which -direction it is pointing. +The robot then receives a number of instructions, at which point the testing facility verifies the robot's new position, and in which direction it is pointing. - The letter-string "RAALAL" means: - Turn right @@ -24,5 +21,5 @@ direction it is pointing. - Turn left - Advance once - Turn left yet again -- Say a robot starts at {7, 3} facing north. Then running this stream - of instructions should leave it at {9, 4} facing west. +- Say a robot starts at {7, 3} facing north. + Then running this stream of instructions should leave it at {9, 4} facing west. diff --git a/exercises/practice/roman-numerals/.docs/instructions.md b/exercises/practice/roman-numerals/.docs/instructions.md index ce25f205e..247ea0892 100644 --- a/exercises/practice/roman-numerals/.docs/instructions.md +++ b/exercises/practice/roman-numerals/.docs/instructions.md @@ -2,17 +2,15 @@ Write a function to convert from normal numbers to Roman Numerals. -The Romans were a clever bunch. They conquered most of Europe and ruled -it for hundreds of years. They invented concrete and straight roads and -even bikinis. One thing they never discovered though was the number -zero. This made writing and dating extensive histories of their exploits -slightly more challenging, but the system of numbers they came up with -is still in use today. For example the BBC uses Roman numerals to date -their programmes. - -The Romans wrote numbers using letters - I, V, X, L, C, D, M. (notice -these letters have lots of straight lines and are hence easy to hack -into stone tablets). +The Romans were a clever bunch. +They conquered most of Europe and ruled it for hundreds of years. +They invented concrete and straight roads and even bikinis. +One thing they never discovered though was the number zero. +This made writing and dating extensive histories of their exploits slightly more challenging, but the system of numbers they came up with is still in use today. +For example the BBC uses Roman numerals to date their programs. + +The Romans wrote numbers using letters - I, V, X, L, C, D, M. +(notice these letters have lots of straight lines and are hence easy to hack into stone tablets). ```text 1 => I @@ -20,12 +18,10 @@ into stone tablets). 7 => VII ``` -There is no need to be able to convert numbers larger than about 3000. +The maximum number supported by this notation is 3,999. (The Romans themselves didn't tend to go any higher) -Wikipedia says: Modern Roman numerals ... are written by expressing each -digit separately starting with the left most digit and skipping any -digit with a value of zero. +Wikipedia says: Modern Roman numerals ... are written by expressing each digit separately starting with the left most digit and skipping any digit with a value of zero. To see this in practice, consider the example of 1990. @@ -40,4 +36,6 @@ In Roman numerals 1990 is MCMXC: 2000=MM 8=VIII -See also: http://www.novaroma.org/via_romana/numbers.html +Learn more about [Roman numerals on Wikipedia][roman-numerals]. + +[roman-numerals]: https://wiki.imperivm-romanvm.com/wiki/Roman_Numerals diff --git a/exercises/practice/roman-numerals/.meta/config.json b/exercises/practice/roman-numerals/.meta/config.json index 1042d9e94..b8bde4679 100644 --- a/exercises/practice/roman-numerals/.meta/config.json +++ b/exercises/practice/roman-numerals/.meta/config.json @@ -34,5 +34,5 @@ }, "blurb": "Write a function to convert from normal numbers to Roman Numerals.", "source": "The Roman Numeral Kata", - "source_url": "http://codingdojo.org/cgi-bin/index.pl?KataRomanNumerals" + "source_url": "https://codingdojo.org/kata/RomanNumerals/" } diff --git a/exercises/practice/secret-handshake/.docs/instructions.md b/exercises/practice/secret-handshake/.docs/instructions.md index 77136cf0f..d2120b9bf 100644 --- a/exercises/practice/secret-handshake/.docs/instructions.md +++ b/exercises/practice/secret-handshake/.docs/instructions.md @@ -43,5 +43,6 @@ jump, double blink ~~~~exercism/note If you aren't sure what binary is or how it works, check out [this binary tutorial][intro-to-binary]. + [intro-to-binary]: https://medium.com/basecs/bits-bytes-building-with-binary-13cb4289aafa ~~~~ diff --git a/exercises/practice/secret-handshake/.meta/config.json b/exercises/practice/secret-handshake/.meta/config.json index 5507f49c0..3ff65e858 100644 --- a/exercises/practice/secret-handshake/.meta/config.json +++ b/exercises/practice/secret-handshake/.meta/config.json @@ -16,5 +16,5 @@ }, "blurb": "Given a decimal number, convert it to the appropriate sequence of events for a secret handshake.", "source": "Bert, in Mary Poppins", - "source_url": "https://www.imdb.com/title/tt0058331/quotes/qt0437047" + "source_url": "https://www.imdb.com/title/tt0058331/quotes/?item=qt0437047" } diff --git a/exercises/practice/series/.docs/instructions.md b/exercises/practice/series/.docs/instructions.md index 3f9d371fa..fd97a6706 100644 --- a/exercises/practice/series/.docs/instructions.md +++ b/exercises/practice/series/.docs/instructions.md @@ -1,7 +1,6 @@ # Instructions -Given a string of digits, output all the contiguous substrings of length `n` in -that string in the order that they appear. +Given a string of digits, output all the contiguous substrings of length `n` in that string in the order that they appear. For example, the string "49142" has the following 3-digit series: @@ -14,8 +13,7 @@ And the following 4-digit series: - "4914" - "9142" -And if you ask for a 6-digit series from a 5-digit string, you deserve -whatever you get. +And if you ask for a 6-digit series from a 5-digit string, you deserve whatever you get. -Note that these series are only required to occupy *adjacent positions* -in the input; the digits need not be *numerically consecutive*. +Note that these series are only required to occupy _adjacent positions_ in the input; +the digits need not be _numerically consecutive_. diff --git a/exercises/practice/series/.meta/config.json b/exercises/practice/series/.meta/config.json index 677992183..0960722a3 100644 --- a/exercises/practice/series/.meta/config.json +++ b/exercises/practice/series/.meta/config.json @@ -29,5 +29,5 @@ }, "blurb": "Given a string of digits, output all the contiguous substrings of length `n` in that string.", "source": "A subset of the Problem 8 at Project Euler", - "source_url": "http://projecteuler.net/problem=8" + "source_url": "https://projecteuler.net/problem=8" } diff --git a/exercises/practice/simple-cipher/.docs/instructions.md b/exercises/practice/simple-cipher/.docs/instructions.md index 22a7e4d4b..475af6182 100644 --- a/exercises/practice/simple-cipher/.docs/instructions.md +++ b/exercises/practice/simple-cipher/.docs/instructions.md @@ -4,42 +4,34 @@ Implement a simple shift cipher like Caesar and a more secure substitution ciphe ## Step 1 -"If he had anything confidential to say, he wrote it in cipher, that is, -by so changing the order of the letters of the alphabet, that not a word -could be made out. If anyone wishes to decipher these, and get at their -meaning, he must substitute the fourth letter of the alphabet, namely D, -for A, and so with the others." +"If he had anything confidential to say, he wrote it in cipher, that is, by so changing the order of the letters of the alphabet, that not a word could be made out. +If anyone wishes to decipher these, and get at their meaning, he must substitute the fourth letter of the alphabet, namely D, for A, and so with the others." —Suetonius, Life of Julius Caesar -Ciphers are very straight-forward algorithms that allow us to render -text less readable while still allowing easy deciphering. They are -vulnerable to many forms of cryptanalysis, but we are lucky that -generally our little sisters are not cryptanalysts. +Ciphers are very straight-forward algorithms that allow us to render text less readable while still allowing easy deciphering. +They are vulnerable to many forms of cryptanalysis, but Caesar was lucky that his enemies were not cryptanalysts. -The Caesar Cipher was used for some messages from Julius Caesar that -were sent afield. Now Caesar knew that the cipher wasn't very good, but -he had one ally in that respect: almost nobody could read well. So even -being a couple letters off was sufficient so that people couldn't -recognize the few words that they did know. +The Caesar Cipher was used for some messages from Julius Caesar that were sent afield. +Now Caesar knew that the cipher wasn't very good, but he had one ally in that respect: almost nobody could read well. +So even being a couple letters off was sufficient so that people couldn't recognize the few words that they did know. Your task is to create a simple shift cipher like the Caesar Cipher. This image is a great example of the Caesar Cipher: -![Caesar Cipher][1] +![Caesar Cipher][img-caesar-cipher] For example: -Giving "iamapandabear" as input to the encode function returns the cipher "ldpdsdqgdehdu". Obscure enough to keep our message secret in transit. +Giving "iamapandabear" as input to the encode function returns the cipher "ldpdsdqgdehdu". +Obscure enough to keep our message secret in transit. -When "ldpdsdqgdehdu" is put into the decode function it would return -the original "iamapandabear" letting your friend read your original -message. +When "ldpdsdqgdehdu" is put into the decode function it would return the original "iamapandabear" letting your friend read your original message. ## Step 2 -Shift ciphers are no fun though when your kid sister figures it out. Try -amending the code to allow us to specify a key and use that for the -shift distance. This is called a substitution cipher. +Shift ciphers quickly cease to be useful when the opposition commander figures them out. +So instead, let's try using a substitution cipher. +Try amending the code to allow us to specify a key and use that for the shift distance. Here's an example: @@ -49,31 +41,26 @@ would return the original "iamapandabear". Given the key "ddddddddddddddddd", encoding our string "iamapandabear" would return the obscured "ldpdsdqgdehdu" -In the example above, we've set a = 0 for the key value. So when the -plaintext is added to the key, we end up with the same message coming -out. So "aaaa" is not an ideal key. But if we set the key to "dddd", we -would get the same thing as the Caesar Cipher. +In the example above, we've set a = 0 for the key value. +So when the plaintext is added to the key, we end up with the same message coming out. +So "aaaa" is not an ideal key. +But if we set the key to "dddd", we would get the same thing as the Caesar Cipher. ## Step 3 -The weakest link in any cipher is the human being. Let's make your -substitution cipher a little more fault tolerant by providing a source -of randomness and ensuring that the key contains only lowercase letters. +The weakest link in any cipher is the human being. +Let's make your substitution cipher a little more fault tolerant by providing a source of randomness and ensuring that the key contains only lowercase letters. -If someone doesn't submit a key at all, generate a truly random key of -at least 100 lowercase characters in length. +If someone doesn't submit a key at all, generate a truly random key of at least 100 lowercase characters in length. ## Extensions -Shift ciphers work by making the text slightly odd, but are vulnerable -to frequency analysis. Substitution ciphers help that, but are still -very vulnerable when the key is short or if spaces are preserved. Later -on you'll see one solution to this problem in the exercise -"crypto-square". +Shift ciphers work by making the text slightly odd, but are vulnerable to frequency analysis. +Substitution ciphers help that, but are still very vulnerable when the key is short or if spaces are preserved. +Later on you'll see one solution to this problem in the exercise "crypto-square". -If you want to go farther in this field, the questions begin to be about -how we can exchange keys in a secure way. Take a look at [Diffie-Hellman -on Wikipedia][dh] for one of the first implementations of this scheme. +If you want to go farther in this field, the questions begin to be about how we can exchange keys in a secure way. +Take a look at [Diffie-Hellman on Wikipedia][dh] for one of the first implementations of this scheme. -[1]: https://upload.wikimedia.org/wikipedia/commons/thumb/4/4a/Caesar_cipher_left_shift_of_3.svg/320px-Caesar_cipher_left_shift_of_3.svg.png -[dh]: http://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange +[img-caesar-cipher]: https://upload.wikimedia.org/wikipedia/commons/thumb/4/4a/Caesar_cipher_left_shift_of_3.svg/320px-Caesar_cipher_left_shift_of_3.svg.png +[dh]: https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange diff --git a/exercises/practice/simple-cipher/.meta/config.json b/exercises/practice/simple-cipher/.meta/config.json index 34cd79628..2ae68a2d1 100644 --- a/exercises/practice/simple-cipher/.meta/config.json +++ b/exercises/practice/simple-cipher/.meta/config.json @@ -31,5 +31,5 @@ }, "blurb": "Implement a simple shift cipher like Caesar and a more secure substitution cipher.", "source": "Substitution Cipher at Wikipedia", - "source_url": "http://en.wikipedia.org/wiki/Substitution_cipher" + "source_url": "https://en.wikipedia.org/wiki/Substitution_cipher" } diff --git a/exercises/practice/triangle/.docs/instructions.md b/exercises/practice/triangle/.docs/instructions.md index 0a9c68e3b..ac3900872 100644 --- a/exercises/practice/triangle/.docs/instructions.md +++ b/exercises/practice/triangle/.docs/instructions.md @@ -4,20 +4,26 @@ Determine if a triangle is equilateral, isosceles, or scalene. An _equilateral_ triangle has all three sides the same length. -An _isosceles_ triangle has at least two sides the same length. (It is sometimes -specified as having exactly two sides the same length, but for the purposes of -this exercise we'll say at least two.) +An _isosceles_ triangle has at least two sides the same length. +(It is sometimes specified as having exactly two sides the same length, but for the purposes of this exercise we'll say at least two.) A _scalene_ triangle has all sides of different lengths. ## Note -For a shape to be a triangle at all, all sides have to be of length > 0, and -the sum of the lengths of any two sides must be greater than or equal to the -length of the third side. See [Triangle Inequality](https://en.wikipedia.org/wiki/Triangle_inequality). +For a shape to be a triangle at all, all sides have to be of length > 0, and the sum of the lengths of any two sides must be greater than or equal to the length of the third side. -## Dig Deeper +In equations: -The case where the sum of the lengths of two sides _equals_ that of the -third is known as a _degenerate_ triangle - it has zero area and looks like -a single line. Feel free to add your own code/tests to check for degenerate triangles. +Let `a`, `b`, and `c` be sides of the triangle. +Then all three of the following expressions must be true: + +```text +a + b ≥ c +b + c ≥ a +a + c ≥ b +``` + +See [Triangle Inequality][triangle-inequality] + +[triangle-inequality]: https://en.wikipedia.org/wiki/Triangle_inequality diff --git a/exercises/practice/triangle/.meta/config.json b/exercises/practice/triangle/.meta/config.json index 2994bb66a..0f26a1103 100644 --- a/exercises/practice/triangle/.meta/config.json +++ b/exercises/practice/triangle/.meta/config.json @@ -36,5 +36,5 @@ }, "blurb": "Determine if a triangle is equilateral, isosceles, or scalene.", "source": "The Ruby Koans triangle project, parts 1 & 2", - "source_url": "http://rubykoans.com/" + "source_url": "https://web.archive.org/web/20220831105330/http://rubykoans.com" } diff --git a/exercises/practice/wordy/.docs/instructions.md b/exercises/practice/wordy/.docs/instructions.md index 0b9e67b6c..aafb9ee54 100644 --- a/exercises/practice/wordy/.docs/instructions.md +++ b/exercises/practice/wordy/.docs/instructions.md @@ -48,7 +48,7 @@ Since these are verbal word problems, evaluate the expression from left-to-right > What is 3 plus 2 multiplied by 3? -15 (i.e. not 9) +15 (i.e. not 9) ## Iteration 4 — Errors diff --git a/exercises/practice/yacht/.docs/instructions.md b/exercises/practice/yacht/.docs/instructions.md index 163ba3792..54fdb452f 100644 --- a/exercises/practice/yacht/.docs/instructions.md +++ b/exercises/practice/yacht/.docs/instructions.md @@ -6,24 +6,24 @@ The score of a throw of the dice depends on category chosen. ## Scores in Yacht -| Category | Score | Description | Example | -| -------- | ----- | ----------- | ------- | -| Ones | 1 × number of ones | Any combination | 1 1 1 4 5 scores 3 | -| Twos | 2 × number of twos | Any combination | 2 2 3 4 5 scores 4 | -| Threes | 3 × number of threes | Any combination | 3 3 3 3 3 scores 15 | -| Fours | 4 × number of fours | Any combination | 1 2 3 3 5 scores 0 | -| Fives | 5 × number of fives| Any combination | 5 1 5 2 5 scores 15 | -| Sixes | 6 × number of sixes | Any combination | 2 3 4 5 6 scores 6 | -| Full House | Total of the dice | Three of one number and two of another | 3 3 3 5 5 scores 19 | -| Four of a Kind | Total of the four dice | At least four dice showing the same face | 4 4 4 4 6 scores 16 | -| Little Straight | 30 points | 1-2-3-4-5 | 1 2 3 4 5 scores 30 | -| Big Straight | 30 points | 2-3-4-5-6 | 2 3 4 5 6 scores 30 | -| Choice | Sum of the dice | Any combination | 2 3 3 4 6 scores 18 | -| Yacht | 50 points | All five dice showing the same face | 4 4 4 4 4 scores 50 | +| Category | Score | Description | Example | +| --------------- | ---------------------- | ---------------------------------------- | ------------------- | +| Ones | 1 × number of ones | Any combination | 1 1 1 4 5 scores 3 | +| Twos | 2 × number of twos | Any combination | 2 2 3 4 5 scores 4 | +| Threes | 3 × number of threes | Any combination | 3 3 3 3 3 scores 15 | +| Fours | 4 × number of fours | Any combination | 1 2 3 3 5 scores 0 | +| Fives | 5 × number of fives | Any combination | 5 1 5 2 5 scores 15 | +| Sixes | 6 × number of sixes | Any combination | 2 3 4 5 6 scores 6 | +| Full House | Total of the dice | Three of one number and two of another | 3 3 3 5 5 scores 19 | +| Four of a Kind | Total of the four dice | At least four dice showing the same face | 4 4 4 4 6 scores 16 | +| Little Straight | 30 points | 1-2-3-4-5 | 1 2 3 4 5 scores 30 | +| Big Straight | 30 points | 2-3-4-5-6 | 2 3 4 5 6 scores 30 | +| Choice | Sum of the dice | Any combination | 2 3 3 4 6 scores 18 | +| Yacht | 50 points | All five dice showing the same face | 4 4 4 4 4 scores 50 | If the dice do not satisfy the requirements of a category, the score is zero. -If, for example, *Four Of A Kind* is entered in the *Yacht* category, zero points are scored. -A *Yacht* scores zero if entered in the *Full House* category. +If, for example, _Four Of A Kind_ is entered in the _Yacht_ category, zero points are scored. +A _Yacht_ scores zero if entered in the _Full House_ category. ## Task From 96f03aa8a4af88300673c7491cb52bd2cf51f3db Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Mon, 20 Nov 2023 14:22:59 +0100 Subject: [PATCH 201/436] Sync reverse-string with problem-specifications (#1784) Addittional tests for unicode and grapheme clusters. [no important files changed] --- .../.meta/additional-tests.json | 20 ++++++ .../reverse-string/.meta/test_template.tera | 15 +++++ .../practice/reverse-string/.meta/tests.toml | 31 +++++++++- .../reverse-string/tests/reverse-string.rs | 62 ++++++++++--------- 4 files changed, 95 insertions(+), 33 deletions(-) create mode 100644 exercises/practice/reverse-string/.meta/additional-tests.json create mode 100644 exercises/practice/reverse-string/.meta/test_template.tera diff --git a/exercises/practice/reverse-string/.meta/additional-tests.json b/exercises/practice/reverse-string/.meta/additional-tests.json new file mode 100644 index 000000000..69279b378 --- /dev/null +++ b/exercises/practice/reverse-string/.meta/additional-tests.json @@ -0,0 +1,20 @@ +[ + { + "uuid": "01ebf55b-bebb-414e-9dec-06f7bb0bee3c", + "description": "wide characters", + "property": "reverse", + "input": { + "value": "子猫" + }, + "expected": "猫子" + }, + { + "uuid": "01ebf55b-bebb-414e-9dec-06f7bb0bee3c", + "description": "grapheme clusters", + "property": "grapheme", + "input": { + "value": "uüu" + }, + "expected": "uüu" + } +] diff --git a/exercises/practice/reverse-string/.meta/test_template.tera b/exercises/practice/reverse-string/.meta/test_template.tera new file mode 100644 index 000000000..4a14c3264 --- /dev/null +++ b/exercises/practice/reverse-string/.meta/test_template.tera @@ -0,0 +1,15 @@ +{% for test in cases %} +#[test] +{% if loop.index != 1 -%} +#[ignore] +{% endif -%} +{% if test.property == "grapheme" -%} +#[cfg(feature = "grapheme")] +{% endif -%} +fn {{ test.description | slugify | replace(from="-", to="_") }}() { + let input = {{ test.input.value | json_encode() }}; + let output = {{ crate_name }}::{{ fn_names[0] }}(input); + let expected = {{ test.expected | json_encode() }}; + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/reverse-string/.meta/tests.toml b/exercises/practice/reverse-string/.meta/tests.toml index be690e975..0b04c4cd7 100644 --- a/exercises/practice/reverse-string/.meta/tests.toml +++ b/exercises/practice/reverse-string/.meta/tests.toml @@ -1,3 +1,28 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[c3b7d806-dced-49ee-8543-933fd1719b1c] +description = "an empty string" + +[01ebf55b-bebb-414e-9dec-06f7bb0bee3c] +description = "a word" + +[0f7c07e4-efd1-4aaa-a07a-90b49ce0b746] +description = "a capitalized word" + +[71854b9c-f200-4469-9f5c-1e8e5eff5614] +description = "a sentence with punctuation" + +[1f8ed2f3-56f3-459b-8f3e-6d8d654a1f6c] +description = "a palindrome" + +[b9e7dec1-c6df-40bd-9fa3-cd7ded010c4c] +description = "an even-sized word" diff --git a/exercises/practice/reverse-string/tests/reverse-string.rs b/exercises/practice/reverse-string/tests/reverse-string.rs index b80eef344..fee55ee39 100644 --- a/exercises/practice/reverse-string/tests/reverse-string.rs +++ b/exercises/practice/reverse-string/tests/reverse-string.rs @@ -1,69 +1,71 @@ -//! Tests for reverse-string -//! -//! Generated by [script][script] using [canonical data][canonical-data] -//! -//! [script]: https://github.com/exercism/rust/blob/b829ce2/bin/init_exercise.py -//! [canonical-data]: https://raw.githubusercontent.com/exercism/problem-specifications/main/exercises/reverse-string/canonical_data.json - -use reverse_string::*; - -/// Process a single test case for the property `reverse` -fn process_reverse_case(input: &str, expected: &str) { - assert_eq!(&reverse(input), expected) -} - #[test] -/// empty string fn an_empty_string() { - process_reverse_case("", ""); + let input = ""; + let output = reverse_string::reverse(input); + let expected = ""; + assert_eq!(output, expected); } #[test] #[ignore] -/// a word fn a_word() { - process_reverse_case("robot", "tobor"); + let input = "robot"; + let output = reverse_string::reverse(input); + let expected = "tobor"; + assert_eq!(output, expected); } #[test] #[ignore] -/// a capitalized word fn a_capitalized_word() { - process_reverse_case("Ramen", "nemaR"); + let input = "Ramen"; + let output = reverse_string::reverse(input); + let expected = "nemaR"; + assert_eq!(output, expected); } #[test] #[ignore] -/// a sentence with punctuation fn a_sentence_with_punctuation() { - process_reverse_case("I'm hungry!", "!yrgnuh m'I"); + let input = "I'm hungry!"; + let output = reverse_string::reverse(input); + let expected = "!yrgnuh m'I"; + assert_eq!(output, expected); } #[test] #[ignore] -/// a palindrome fn a_palindrome() { - process_reverse_case("racecar", "racecar"); + let input = "racecar"; + let output = reverse_string::reverse(input); + let expected = "racecar"; + assert_eq!(output, expected); } #[test] #[ignore] -/// an even-sized word fn an_even_sized_word() { - process_reverse_case("drawer", "reward"); + let input = "drawer"; + let output = reverse_string::reverse(input); + let expected = "reward"; + assert_eq!(output, expected); } #[test] #[ignore] -/// wide characters fn wide_characters() { - process_reverse_case("子猫", "猫子"); + let input = "子猫"; + let output = reverse_string::reverse(input); + let expected = "猫子"; + assert_eq!(output, expected); } #[test] #[ignore] #[cfg(feature = "grapheme")] -/// grapheme clusters fn grapheme_clusters() { - process_reverse_case("uüu", "uüu"); + let input = "uüu"; + let output = reverse_string::reverse(input); + let expected = "uüu"; + assert_eq!(output, expected); } From ff08877736f16a837522c3f4d865d1a08d6cb605 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Mon, 20 Nov 2023 14:35:18 +0100 Subject: [PATCH 202/436] Sync raindrops with problem-specifications (#1786) [no important files changed] --- .../raindrops/.meta/test_template.tera | 12 ++ exercises/practice/raindrops/.meta/tests.toml | 67 ++++++++- .../practice/raindrops/tests/raindrops.rs | 132 ++++++++++++------ 3 files changed, 166 insertions(+), 45 deletions(-) create mode 100644 exercises/practice/raindrops/.meta/test_template.tera diff --git a/exercises/practice/raindrops/.meta/test_template.tera b/exercises/practice/raindrops/.meta/test_template.tera new file mode 100644 index 000000000..d7e68acbd --- /dev/null +++ b/exercises/practice/raindrops/.meta/test_template.tera @@ -0,0 +1,12 @@ +{% for test in cases %} +#[test] +{% if loop.index != 1 -%} +#[ignore] +{% endif -%} +fn test_{{ test.description | slugify | replace(from="-", to="_") }}() { + let input = {{ test.input.number | json_encode() }}; + let output = {{ crate_name }}::{{ fn_names[0] }}(input); + let expected = {{ test.expected | json_encode() }}; + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/raindrops/.meta/tests.toml b/exercises/practice/raindrops/.meta/tests.toml index be690e975..756d16ca1 100644 --- a/exercises/practice/raindrops/.meta/tests.toml +++ b/exercises/practice/raindrops/.meta/tests.toml @@ -1,3 +1,64 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[1575d549-e502-46d4-a8e1-6b7bec6123d8] +description = "the sound for 1 is 1" + +[1f51a9f9-4895-4539-b182-d7b0a5ab2913] +description = "the sound for 3 is Pling" + +[2d9bfae5-2b21-4bcd-9629-c8c0e388f3e0] +description = "the sound for 5 is Plang" + +[d7e60daa-32ef-4c23-b688-2abff46c4806] +description = "the sound for 7 is Plong" + +[6bb4947b-a724-430c-923f-f0dc3d62e56a] +description = "the sound for 6 is Pling as it has a factor 3" + +[ce51e0e8-d9d4-446d-9949-96eac4458c2d] +description = "2 to the power 3 does not make a raindrop sound as 3 is the exponent not the base" + +[0dd66175-e3e2-47fc-8750-d01739856671] +description = "the sound for 9 is Pling as it has a factor 3" + +[022c44d3-2182-4471-95d7-c575af225c96] +description = "the sound for 10 is Plang as it has a factor 5" + +[37ab74db-fed3-40ff-b7b9-04acdfea8edf] +description = "the sound for 14 is Plong as it has a factor of 7" + +[31f92999-6afb-40ee-9aa4-6d15e3334d0f] +description = "the sound for 15 is PlingPlang as it has factors 3 and 5" + +[ff9bb95d-6361-4602-be2c-653fe5239b54] +description = "the sound for 21 is PlingPlong as it has factors 3 and 7" + +[d2e75317-b72e-40ab-8a64-6734a21dece1] +description = "the sound for 25 is Plang as it has a factor 5" + +[a09c4c58-c662-4e32-97fe-f1501ef7125c] +description = "the sound for 27 is Pling as it has a factor 3" + +[bdf061de-8564-4899-a843-14b48b722789] +description = "the sound for 35 is PlangPlong as it has factors 5 and 7" + +[c4680bee-69ba-439d-99b5-70c5fd1a7a83] +description = "the sound for 49 is Plong as it has a factor 7" + +[17f2bc9a-b65a-4d23-8ccd-266e8c271444] +description = "the sound for 52 is 52" + +[e46677ed-ff1a-419f-a740-5c713d2830e4] +description = "the sound for 105 is PlingPlangPlong as it has factors 3, 5 and 7" + +[13c6837a-0fcd-4b86-a0eb-20572f7deb0b] +description = "the sound for 3125 is Plang as it has a factor 5" diff --git a/exercises/practice/raindrops/tests/raindrops.rs b/exercises/practice/raindrops/tests/raindrops.rs index c7fa1ecb8..a1d0b6939 100644 --- a/exercises/practice/raindrops/tests/raindrops.rs +++ b/exercises/practice/raindrops/tests/raindrops.rs @@ -1,112 +1,160 @@ #[test] -fn test_1() { - assert_eq!("1", raindrops::raindrops(1)); +fn test_the_sound_for_1_is_1() { + let input = 1; + let output = raindrops::raindrops(input); + let expected = "1"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_3() { - assert_eq!("Pling", raindrops::raindrops(3)); +fn test_the_sound_for_3_is_pling() { + let input = 3; + let output = raindrops::raindrops(input); + let expected = "Pling"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_5() { - assert_eq!("Plang", raindrops::raindrops(5)); +fn test_the_sound_for_5_is_plang() { + let input = 5; + let output = raindrops::raindrops(input); + let expected = "Plang"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_7() { - assert_eq!("Plong", raindrops::raindrops(7)); +fn test_the_sound_for_7_is_plong() { + let input = 7; + let output = raindrops::raindrops(input); + let expected = "Plong"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_6() { - assert_eq!("Pling", raindrops::raindrops(6)); +fn test_the_sound_for_6_is_pling_as_it_has_a_factor_3() { + let input = 6; + let output = raindrops::raindrops(input); + let expected = "Pling"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_8() { - assert_eq!("8", raindrops::raindrops(8)); +fn test_2_to_the_power_3_does_not_make_a_raindrop_sound_as_3_is_the_exponent_not_the_base() { + let input = 8; + let output = raindrops::raindrops(input); + let expected = "8"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_9() { - assert_eq!("Pling", raindrops::raindrops(9)); +fn test_the_sound_for_9_is_pling_as_it_has_a_factor_3() { + let input = 9; + let output = raindrops::raindrops(input); + let expected = "Pling"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_10() { - assert_eq!("Plang", raindrops::raindrops(10)); +fn test_the_sound_for_10_is_plang_as_it_has_a_factor_5() { + let input = 10; + let output = raindrops::raindrops(input); + let expected = "Plang"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_14() { - assert_eq!("Plong", raindrops::raindrops(14)); +fn test_the_sound_for_14_is_plong_as_it_has_a_factor_of_7() { + let input = 14; + let output = raindrops::raindrops(input); + let expected = "Plong"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_15() { - assert_eq!("PlingPlang", raindrops::raindrops(15)); +fn test_the_sound_for_15_is_plingplang_as_it_has_factors_3_and_5() { + let input = 15; + let output = raindrops::raindrops(input); + let expected = "PlingPlang"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_21() { - assert_eq!("PlingPlong", raindrops::raindrops(21)); +fn test_the_sound_for_21_is_plingplong_as_it_has_factors_3_and_7() { + let input = 21; + let output = raindrops::raindrops(input); + let expected = "PlingPlong"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_25() { - assert_eq!("Plang", raindrops::raindrops(25)); +fn test_the_sound_for_25_is_plang_as_it_has_a_factor_5() { + let input = 25; + let output = raindrops::raindrops(input); + let expected = "Plang"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_27() { - assert_eq!("Pling", raindrops::raindrops(27)); +fn test_the_sound_for_27_is_pling_as_it_has_a_factor_3() { + let input = 27; + let output = raindrops::raindrops(input); + let expected = "Pling"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_35() { - assert_eq!("PlangPlong", raindrops::raindrops(35)); +fn test_the_sound_for_35_is_plangplong_as_it_has_factors_5_and_7() { + let input = 35; + let output = raindrops::raindrops(input); + let expected = "PlangPlong"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_49() { - assert_eq!("Plong", raindrops::raindrops(49)); +fn test_the_sound_for_49_is_plong_as_it_has_a_factor_7() { + let input = 49; + let output = raindrops::raindrops(input); + let expected = "Plong"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_52() { - assert_eq!("52", raindrops::raindrops(52)); +fn test_the_sound_for_52_is_52() { + let input = 52; + let output = raindrops::raindrops(input); + let expected = "52"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_105() { - assert_eq!("PlingPlangPlong", raindrops::raindrops(105)); +fn test_the_sound_for_105_is_plingplangplong_as_it_has_factors_3_5_and_7() { + let input = 105; + let output = raindrops::raindrops(input); + let expected = "PlingPlangPlong"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_3125() { - assert_eq!("Plang", raindrops::raindrops(3125)); -} - -#[test] -#[ignore] -fn test_12121() { - assert_eq!("12121", raindrops::raindrops(12_121)); +fn test_the_sound_for_3125_is_plang_as_it_has_a_factor_5() { + let input = 3125; + let output = raindrops::raindrops(input); + let expected = "Plang"; + assert_eq!(output, expected); } From f9278f3fac06309b4be7d7697380064daf797601 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Mon, 20 Nov 2023 14:50:43 +0100 Subject: [PATCH 203/436] Sync rail-fence-cipher with problem-specifications (#1787) [no important files changed] --- .../.meta/additional-tests.json | 12 +++ .../.meta/test_template.tera | 19 ++++ .../rail-fence-cipher/.meta/tests.toml | 31 +++++- .../tests/rail-fence-cipher.rs | 101 ++++++++---------- 4 files changed, 102 insertions(+), 61 deletions(-) create mode 100644 exercises/practice/rail-fence-cipher/.meta/additional-tests.json create mode 100644 exercises/practice/rail-fence-cipher/.meta/test_template.tera diff --git a/exercises/practice/rail-fence-cipher/.meta/additional-tests.json b/exercises/practice/rail-fence-cipher/.meta/additional-tests.json new file mode 100644 index 000000000..78fa632e5 --- /dev/null +++ b/exercises/practice/rail-fence-cipher/.meta/additional-tests.json @@ -0,0 +1,12 @@ +[ + { + "uuid": "46dc5c50-5538-401d-93a5-41102680d068", + "description": "encode wide characters", + "property": "encode", + "input": { + "msg": "古池蛙飛び込む水の音", + "rails": 3 + }, + "expected": "古びの池飛込水音蛙む" + } +] diff --git a/exercises/practice/rail-fence-cipher/.meta/test_template.tera b/exercises/practice/rail-fence-cipher/.meta/test_template.tera new file mode 100644 index 000000000..ce5af15b9 --- /dev/null +++ b/exercises/practice/rail-fence-cipher/.meta/test_template.tera @@ -0,0 +1,19 @@ +use rail_fence_cipher::RailFence; +{% for test in cases %} +#[test] +{% if loop.index != 1 -%} +#[ignore] +{% endif -%} +fn {{ test.description | slugify | replace(from="-", to="_") }}() { + let input = {{ test.input.msg | json_encode() }}; + let rails = {{ test.input.rails | json_encode() }}; + let rail_fence = RailFence::new(rails); + {% if test.property == "encode" -%} + let output = rail_fence.encode(input); + {%- else -%} + let output = rail_fence.decode(input); + {%- endif %} + let expected = {{ test.expected | json_encode() }}; + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/rail-fence-cipher/.meta/tests.toml b/exercises/practice/rail-fence-cipher/.meta/tests.toml index be690e975..dfc5e16ba 100644 --- a/exercises/practice/rail-fence-cipher/.meta/tests.toml +++ b/exercises/practice/rail-fence-cipher/.meta/tests.toml @@ -1,3 +1,28 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[46dc5c50-5538-401d-93a5-41102680d068] +description = "encode -> encode with two rails" + +[25691697-fbd8-4278-8c38-b84068b7bc29] +description = "encode -> encode with three rails" + +[384f0fea-1442-4f1a-a7c4-5cbc2044002c] +description = "encode -> encode with ending in the middle" + +[cd525b17-ec34-45ef-8f0e-4f27c24a7127] +description = "decode -> decode with three rails" + +[dd7b4a98-1a52-4e5c-9499-cbb117833507] +description = "decode -> decode with five rails" + +[93e1ecf4-fac9-45d9-9cd2-591f47d3b8d3] +description = "decode -> decode with six rails" diff --git a/exercises/practice/rail-fence-cipher/tests/rail-fence-cipher.rs b/exercises/practice/rail-fence-cipher/tests/rail-fence-cipher.rs index 66daa2137..6fb28d611 100644 --- a/exercises/practice/rail-fence-cipher/tests/rail-fence-cipher.rs +++ b/exercises/practice/rail-fence-cipher/tests/rail-fence-cipher.rs @@ -1,92 +1,77 @@ -//! Tests for rail-fence-cipher -//! -//! Generated by [script][script] using [canonical data][canonical-data] -//! -//! [script]: https://github.com/exercism/rust/blob/main/bin/init_exercise.py -//! [canonical-data]: https://raw.githubusercontent.com/exercism/problem-specifications/main/exercises/rail-fence-cipher/canonical_data.json -//! -//! The tests do not expect any normalization or cleaning. -//! That trade is tested in enough other exercises. - -use rail_fence_cipher::*; - -/// Process a single test case for the property `encode` -/// -/// All cases for the `encode` property are implemented -/// in terms of this function. -fn process_encode_case(input: &str, rails: u32, expected: &str) { - let rail_fence = RailFence::new(rails); - assert_eq!(rail_fence.encode(input), expected); -} - -/// Process a single test case for the property `decode` -/// -/// All cases for the `decode` property are implemented -/// in terms of this function. -fn process_decode_case(input: &str, rails: u32, expected: &str) { - let rail_fence = RailFence::new(rails); - assert_eq!(rail_fence.decode(input), expected); -} - -// encode +use rail_fence_cipher::RailFence; #[test] -/// encode with two rails fn encode_with_two_rails() { - process_encode_case("XOXOXOXOXOXOXOXOXO", 2, "XXXXXXXXXOOOOOOOOO"); + let input = "XOXOXOXOXOXOXOXOXO"; + let rails = 2; + let rail_fence = RailFence::new(rails); + let output = rail_fence.encode(input); + let expected = "XXXXXXXXXOOOOOOOOO"; + assert_eq!(output, expected); } #[test] #[ignore] -/// encode with three rails fn encode_with_three_rails() { - process_encode_case("WEAREDISCOVEREDFLEEATONCE", 3, "WECRLTEERDSOEEFEAOCAIVDEN"); + let input = "WEAREDISCOVEREDFLEEATONCE"; + let rails = 3; + let rail_fence = RailFence::new(rails); + let output = rail_fence.encode(input); + let expected = "WECRLTEERDSOEEFEAOCAIVDEN"; + assert_eq!(output, expected); } #[test] #[ignore] -/// encode with ending in the middle fn encode_with_ending_in_the_middle() { - process_encode_case("EXERCISES", 4, "ESXIEECSR"); + let input = "EXERCISES"; + let rails = 4; + let rail_fence = RailFence::new(rails); + let output = rail_fence.encode(input); + let expected = "ESXIEECSR"; + assert_eq!(output, expected); } -// decode - #[test] #[ignore] -/// decode with three rails fn decode_with_three_rails() { - process_decode_case("TEITELHDVLSNHDTISEIIEA", 3, "THEDEVILISINTHEDETAILS"); + let input = "TEITELHDVLSNHDTISEIIEA"; + let rails = 3; + let rail_fence = RailFence::new(rails); + let output = rail_fence.decode(input); + let expected = "THEDEVILISINTHEDETAILS"; + assert_eq!(output, expected); } #[test] #[ignore] -/// decode with five rails fn decode_with_five_rails() { - process_decode_case("EIEXMSMESAORIWSCE", 5, "EXERCISMISAWESOME"); + let input = "EIEXMSMESAORIWSCE"; + let rails = 5; + let rail_fence = RailFence::new(rails); + let output = rail_fence.decode(input); + let expected = "EXERCISMISAWESOME"; + assert_eq!(output, expected); } #[test] #[ignore] -/// decode with six rails fn decode_with_six_rails() { - process_decode_case( - "133714114238148966225439541018335470986172518171757571896261", - 6, - "112358132134558914423337761098715972584418167651094617711286", - ); + let input = "133714114238148966225439541018335470986172518171757571896261"; + let rails = 6; + let rail_fence = RailFence::new(rails); + let output = rail_fence.decode(input); + let expected = "112358132134558914423337761098715972584418167651094617711286"; + assert_eq!(output, expected); } #[test] #[ignore] -/// encode wide characters -/// -/// normally unicode is not part of exercism exercises, but in an exercise -/// specifically oriented around shuffling characters, it seems worth ensuring -/// that wide characters are handled properly -/// -/// this text is possibly one of the most famous haiku of all time, by -/// Matsuo Bashō (松尾芭蕉) fn encode_wide_characters() { - process_encode_case("古池蛙飛び込む水の音", 3, "古びの池飛込水音蛙む"); + let input = "古池蛙飛び込む水の音"; + let rails = 3; + let rail_fence = RailFence::new(rails); + let output = rail_fence.encode(input); + let expected = "古びの池飛込水音蛙む"; + assert_eq!(output, expected); } From a6688bace4e4db1c1645ef2cebf291519df7c043 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Mon, 20 Nov 2023 15:09:23 +0100 Subject: [PATCH 204/436] Sync proverb with problem-specifications (#1789) [no important files changed] --- .../practice/proverb/.meta/test_template.tera | 16 +++++ exercises/practice/proverb/.meta/tests.toml | 31 ++++++++- exercises/practice/proverb/tests/proverb.rs | 67 ++++++++++--------- 3 files changed, 79 insertions(+), 35 deletions(-) create mode 100644 exercises/practice/proverb/.meta/test_template.tera diff --git a/exercises/practice/proverb/.meta/test_template.tera b/exercises/practice/proverb/.meta/test_template.tera new file mode 100644 index 000000000..0560a6f7d --- /dev/null +++ b/exercises/practice/proverb/.meta/test_template.tera @@ -0,0 +1,16 @@ +{% for test in cases %} +#[test] +{% if loop.index != 1 -%} +#[ignore] +{% endif -%} +fn {{ test.description | slugify | replace(from="-", to="_") }}() { + let input = &{{ test.input.strings | json_encode() }}; + let output = {{ crate_name }}::{{ fn_names[0] }}(input); + {% if test.expected | length == 0 -%} + let expected = String::new(); + {%- else -%} + let expected: String = {{ test.expected | json_encode() }}.join("\n"); + {%- endif %} + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/proverb/.meta/tests.toml b/exercises/practice/proverb/.meta/tests.toml index be690e975..dc92a0c96 100644 --- a/exercises/practice/proverb/.meta/tests.toml +++ b/exercises/practice/proverb/.meta/tests.toml @@ -1,3 +1,28 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[e974b73e-7851-484f-8d6d-92e07fe742fc] +description = "zero pieces" + +[2fcd5f5e-8b82-4e74-b51d-df28a5e0faa4] +description = "one piece" + +[d9d0a8a1-d933-46e2-aa94-eecf679f4b0e] +description = "two pieces" + +[c95ef757-5e94-4f0d-a6cb-d2083f5e5a83] +description = "three pieces" + +[433fb91c-35a2-4d41-aeab-4de1e82b2126] +description = "full proverb" + +[c1eefa5a-e8d9-41c7-91d4-99fab6d6b9f7] +description = "four pieces modernized" diff --git a/exercises/practice/proverb/tests/proverb.rs b/exercises/practice/proverb/tests/proverb.rs index 5a921790e..be930b16d 100644 --- a/exercises/practice/proverb/tests/proverb.rs +++ b/exercises/practice/proverb/tests/proverb.rs @@ -1,53 +1,55 @@ -use proverb::build_proverb; +#[test] +fn zero_pieces() { + let input = &[]; + let output = proverb::build_proverb(input); + let expected = String::new(); + assert_eq!(output, expected); +} #[test] +#[ignore] +fn one_piece() { + let input = &["nail"]; + let output = proverb::build_proverb(input); + let expected: String = ["And all for the want of a nail."].join("\n"); + assert_eq!(output, expected); +} + +#[test] +#[ignore] fn two_pieces() { - let input = vec!["nail", "shoe"]; - let expected = [ + let input = &["nail", "shoe"]; + let output = proverb::build_proverb(input); + let expected: String = [ "For want of a nail the shoe was lost.", "And all for the want of a nail.", ] .join("\n"); - assert_eq!(build_proverb(&input), expected); + assert_eq!(output, expected); } -// Notice the change in the last line at three pieces. #[test] #[ignore] fn three_pieces() { - let input = vec!["nail", "shoe", "horse"]; - let expected = [ + let input = &["nail", "shoe", "horse"]; + let output = proverb::build_proverb(input); + let expected: String = [ "For want of a nail the shoe was lost.", "For want of a shoe the horse was lost.", "And all for the want of a nail.", ] .join("\n"); - assert_eq!(build_proverb(&input), expected); -} - -#[test] -#[ignore] -fn one_piece() { - let input = vec!["nail"]; - let expected = String::from("And all for the want of a nail."); - assert_eq!(build_proverb(&input), expected); -} - -#[test] -#[ignore] -fn zero_pieces() { - let input: Vec<&str> = vec![]; - let expected = String::new(); - assert_eq!(build_proverb(&input), expected); + assert_eq!(output, expected); } #[test] #[ignore] -fn full() { - let input = vec![ +fn full_proverb() { + let input = &[ "nail", "shoe", "horse", "rider", "message", "battle", "kingdom", ]; - let expected = [ + let output = proverb::build_proverb(input); + let expected: String = [ "For want of a nail the shoe was lost.", "For want of a shoe the horse was lost.", "For want of a horse the rider was lost.", @@ -57,19 +59,20 @@ fn full() { "And all for the want of a nail.", ] .join("\n"); - assert_eq!(build_proverb(&input), expected); + assert_eq!(output, expected); } #[test] #[ignore] -fn three_pieces_modernized() { - let input = vec!["pin", "gun", "soldier", "battle"]; - let expected = [ +fn four_pieces_modernized() { + let input = &["pin", "gun", "soldier", "battle"]; + let output = proverb::build_proverb(input); + let expected: String = [ "For want of a pin the gun was lost.", "For want of a gun the soldier was lost.", "For want of a soldier the battle was lost.", "And all for the want of a pin.", ] .join("\n"); - assert_eq!(build_proverb(&input), expected); + assert_eq!(output, expected); } From 41fd16eb94c708afa03ec7a1fe130cfb00a3d88f Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Mon, 20 Nov 2023 15:18:51 +0100 Subject: [PATCH 205/436] Sync pythagorean-triplet with problem-specifications (#1788) [no important files changed] --- .../.meta/test_template.tera | 14 +++ .../pythagorean-triplet/.meta/tests.toml | 34 ++++++- .../tests/pythagorean-triplet.rs | 91 ++++++++++--------- 3 files changed, 95 insertions(+), 44 deletions(-) create mode 100644 exercises/practice/pythagorean-triplet/.meta/test_template.tera diff --git a/exercises/practice/pythagorean-triplet/.meta/test_template.tera b/exercises/practice/pythagorean-triplet/.meta/test_template.tera new file mode 100644 index 000000000..642b1a760 --- /dev/null +++ b/exercises/practice/pythagorean-triplet/.meta/test_template.tera @@ -0,0 +1,14 @@ +use std::collections::HashSet; +{% for test in cases %} +#[test] +{% if loop.index != 1 -%} +#[ignore] +{% endif -%} +fn {{ test.description | slugify | replace(from="-", to="_") }}() { + let input = {{ test.input.n | json_encode() }}; + let output = {{ crate_name }}::{{ fn_names[0] }}(input); + let expected = {{ test.expected | json_encode() }}; + let expected: HashSet<_> = expected.iter().cloned().collect(); + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/pythagorean-triplet/.meta/tests.toml b/exercises/practice/pythagorean-triplet/.meta/tests.toml index be690e975..719620a97 100644 --- a/exercises/practice/pythagorean-triplet/.meta/tests.toml +++ b/exercises/practice/pythagorean-triplet/.meta/tests.toml @@ -1,3 +1,31 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[a19de65d-35b8-4480-b1af-371d9541e706] +description = "triplets whose sum is 12" + +[48b21332-0a3d-43b2-9a52-90b2a6e5c9f5] +description = "triplets whose sum is 108" + +[dffc1266-418e-4daa-81af-54c3e95c3bb5] +description = "triplets whose sum is 1000" + +[5f86a2d4-6383-4cce-93a5-e4489e79b186] +description = "no matching triplets for 1001" + +[bf17ba80-1596-409a-bb13-343bdb3b2904] +description = "returns all matching triplets" + +[9d8fb5d5-6c6f-42df-9f95-d3165963ac57] +description = "several matching triplets" + +[f5be5734-8aa0-4bd1-99a2-02adcc4402b4] +description = "triplets for large number" diff --git a/exercises/practice/pythagorean-triplet/tests/pythagorean-triplet.rs b/exercises/practice/pythagorean-triplet/tests/pythagorean-triplet.rs index 45c255094..67722e3b9 100644 --- a/exercises/practice/pythagorean-triplet/tests/pythagorean-triplet.rs +++ b/exercises/practice/pythagorean-triplet/tests/pythagorean-triplet.rs @@ -1,76 +1,85 @@ -use pythagorean_triplet::find; use std::collections::HashSet; -fn process_tripletswithsum_case(sum: u32, expected: &[[u32; 3]]) { - let triplets = find(sum); - - if !expected.is_empty() { - let expected: HashSet<_> = expected.iter().cloned().collect(); - - assert_eq!(expected, triplets); - } else { - assert!(triplets.is_empty()); - } -} - #[test] fn triplets_whose_sum_is_12() { - process_tripletswithsum_case(12, &[[3, 4, 5]]); + let input = 12; + let output = pythagorean_triplet::find(input); + let expected = [[3, 4, 5]]; + let expected: HashSet<_> = expected.iter().cloned().collect(); + assert_eq!(output, expected); } #[test] #[ignore] fn triplets_whose_sum_is_108() { - process_tripletswithsum_case(108, &[[27, 36, 45]]); + let input = 108; + let output = pythagorean_triplet::find(input); + let expected = [[27, 36, 45]]; + let expected: HashSet<_> = expected.iter().cloned().collect(); + assert_eq!(output, expected); } #[test] #[ignore] fn triplets_whose_sum_is_1000() { - process_tripletswithsum_case(1000, &[[200, 375, 425]]); + let input = 1000; + let output = pythagorean_triplet::find(input); + let expected = [[200, 375, 425]]; + let expected: HashSet<_> = expected.iter().cloned().collect(); + assert_eq!(output, expected); } #[test] #[ignore] fn no_matching_triplets_for_1001() { - process_tripletswithsum_case(1001, &[]); + let input = 1001; + let output = pythagorean_triplet::find(input); + let expected = []; + let expected: HashSet<_> = expected.iter().cloned().collect(); + assert_eq!(output, expected); } #[test] #[ignore] fn returns_all_matching_triplets() { - process_tripletswithsum_case(90, &[[9, 40, 41], [15, 36, 39]]); + let input = 90; + let output = pythagorean_triplet::find(input); + let expected = [[9, 40, 41], [15, 36, 39]]; + let expected: HashSet<_> = expected.iter().cloned().collect(); + assert_eq!(output, expected); } #[test] #[ignore] fn several_matching_triplets() { - process_tripletswithsum_case( - 840, - &[ - [40, 399, 401], - [56, 390, 394], - [105, 360, 375], - [120, 350, 370], - [140, 336, 364], - [168, 315, 357], - [210, 280, 350], - [240, 252, 348], - ], - ); + let input = 840; + let output = pythagorean_triplet::find(input); + let expected = [ + [40, 399, 401], + [56, 390, 394], + [105, 360, 375], + [120, 350, 370], + [140, 336, 364], + [168, 315, 357], + [210, 280, 350], + [240, 252, 348], + ]; + let expected: HashSet<_> = expected.iter().cloned().collect(); + assert_eq!(output, expected); } #[test] #[ignore] fn triplets_for_large_number() { - process_tripletswithsum_case( - 30_000, - &[ - [1200, 14_375, 14_425], - [1875, 14_000, 14_125], - [5000, 12_000, 13_000], - [6000, 11_250, 12_750], - [7500, 10_000, 12_500], - ], - ); + let input = 30000; + let output = pythagorean_triplet::find(input); + let expected = [ + [1200, 14375, 14425], + [1875, 14000, 14125], + [5000, 12000, 13000], + [6000, 11250, 12750], + [7500, 10000, 12500], + ]; + let expected: HashSet<_> = expected.iter().cloned().collect(); + assert_eq!(output, expected); } From 133694fe5af3a55a80b0bce9be31ab2f1574ab61 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Mon, 20 Nov 2023 15:29:39 +0100 Subject: [PATCH 206/436] Sync roman-numerals with problem-specifications (#1781) --- .../roman-numerals/.meta/test_template.tera | 14 ++ .../practice/roman-numerals/.meta/tests.toml | 91 +++++++- .../roman-numerals/tests/roman-numerals.rs | 200 ++++++++++++++---- 3 files changed, 265 insertions(+), 40 deletions(-) create mode 100644 exercises/practice/roman-numerals/.meta/test_template.tera diff --git a/exercises/practice/roman-numerals/.meta/test_template.tera b/exercises/practice/roman-numerals/.meta/test_template.tera new file mode 100644 index 000000000..9b1114af4 --- /dev/null +++ b/exercises/practice/roman-numerals/.meta/test_template.tera @@ -0,0 +1,14 @@ +use roman_numerals::Roman; + +{% for test in cases %} +#[test] +{% if loop.index != 1 -%} +#[ignore] +{% endif -%} +fn number_{{ test.description | slugify | replace(from="-", to="_") }}() { + let input = {{ test.input.number | json_encode() }}; + let output = Roman::from(input).to_string(); + let expected = {{ test.expected | json_encode() }}; + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/roman-numerals/.meta/tests.toml b/exercises/practice/roman-numerals/.meta/tests.toml index be690e975..57c6c4be8 100644 --- a/exercises/practice/roman-numerals/.meta/tests.toml +++ b/exercises/practice/roman-numerals/.meta/tests.toml @@ -1,3 +1,88 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[19828a3a-fbf7-4661-8ddd-cbaeee0e2178] +description = "1 is I" + +[f088f064-2d35-4476-9a41-f576da3f7b03] +description = "2 is II" + +[b374a79c-3bea-43e6-8db8-1286f79c7106] +description = "3 is III" + +[05a0a1d4-a140-4db1-82e8-fcc21fdb49bb] +description = "4 is IV" + +[57c0f9ad-5024-46ab-975d-de18c430b290] +description = "5 is V" + +[20a2b47f-e57f-4797-a541-0b3825d7f249] +description = "6 is VI" + +[ff3fb08c-4917-4aab-9f4e-d663491d083d] +description = "9 is IX" + +[6d1d82d5-bf3e-48af-9139-87d7165ed509] +description = "16 is XVI" + +[2bda64ca-7d28-4c56-b08d-16ce65716cf6] +description = "27 is XXVII" + +[a1f812ef-84da-4e02-b4f0-89c907d0962c] +description = "48 is XLVIII" + +[607ead62-23d6-4c11-a396-ef821e2e5f75] +description = "49 is XLIX" + +[d5b283d4-455d-4e68-aacf-add6c4b51915] +description = "59 is LIX" + +[4465ffd5-34dc-44f3-ada5-56f5007b6dad] +description = "66 is LXVI" + +[46b46e5b-24da-4180-bfe2-2ef30b39d0d0] +description = "93 is XCIII" + +[30494be1-9afb-4f84-9d71-db9df18b55e3] +description = "141 is CXLI" + +[267f0207-3c55-459a-b81d-67cec7a46ed9] +description = "163 is CLXIII" + +[902ad132-0b4d-40e3-8597-ba5ed611dd8d] +description = "166 is CLXVI" + +[cdb06885-4485-4d71-8bfb-c9d0f496b404] +description = "402 is CDII" + +[6b71841d-13b2-46b4-ba97-dec28133ea80] +description = "575 is DLXXV" + +[dacb84b9-ea1c-4a61-acbb-ce6b36674906] +description = "666 is DCLXVI" + +[432de891-7fd6-4748-a7f6-156082eeca2f] +description = "911 is CMXI" + +[e6de6d24-f668-41c0-88d7-889c0254d173] +description = "1024 is MXXIV" + +[efbe1d6a-9f98-4eb5-82bc-72753e3ac328] +description = "1666 is MDCLXVI" + +[bb550038-d4eb-4be2-a9ce-f21961ac3bc6] +description = "3000 is MMM" + +[3bc4b41c-c2e6-49d9-9142-420691504336] +description = "3001 is MMMI" + +[4e18e96b-5fbb-43df-a91b-9cb511fe0856] +description = "3999 is MMMCMXCIX" diff --git a/exercises/practice/roman-numerals/tests/roman-numerals.rs b/exercises/practice/roman-numerals/tests/roman-numerals.rs index 7a3337666..c7d047e79 100644 --- a/exercises/practice/roman-numerals/tests/roman-numerals.rs +++ b/exercises/practice/roman-numerals/tests/roman-numerals.rs @@ -1,108 +1,234 @@ -use roman_numerals::*; +use roman_numerals::Roman; #[test] -fn one() { - assert_eq!("I", Roman::from(1).to_string()); +fn number_1_is_i() { + let input = 1; + let output = Roman::from(input).to_string(); + let expected = "I"; + assert_eq!(output, expected); } #[test] #[ignore] -fn two() { - assert_eq!("II", Roman::from(2).to_string()); +fn number_2_is_ii() { + let input = 2; + let output = Roman::from(input).to_string(); + let expected = "II"; + assert_eq!(output, expected); } #[test] #[ignore] -fn three() { - assert_eq!("III", Roman::from(3).to_string()); +fn number_3_is_iii() { + let input = 3; + let output = Roman::from(input).to_string(); + let expected = "III"; + assert_eq!(output, expected); } #[test] #[ignore] -fn four() { - assert_eq!("IV", Roman::from(4).to_string()); +fn number_4_is_iv() { + let input = 4; + let output = Roman::from(input).to_string(); + let expected = "IV"; + assert_eq!(output, expected); } #[test] #[ignore] -fn five() { - assert_eq!("V", Roman::from(5).to_string()); +fn number_5_is_v() { + let input = 5; + let output = Roman::from(input).to_string(); + let expected = "V"; + assert_eq!(output, expected); } #[test] #[ignore] -fn six() { - assert_eq!("VI", Roman::from(6).to_string()); +fn number_6_is_vi() { + let input = 6; + let output = Roman::from(input).to_string(); + let expected = "VI"; + assert_eq!(output, expected); } #[test] #[ignore] -fn nine() { - assert_eq!("IX", Roman::from(9).to_string()); +fn number_9_is_ix() { + let input = 9; + let output = Roman::from(input).to_string(); + let expected = "IX"; + assert_eq!(output, expected); } #[test] #[ignore] -fn twenty_seven() { - assert_eq!("XXVII", Roman::from(27).to_string()); +fn number_16_is_xvi() { + let input = 16; + let output = Roman::from(input).to_string(); + let expected = "XVI"; + assert_eq!(output, expected); } #[test] #[ignore] -fn forty_eight() { - assert_eq!("XLVIII", Roman::from(48).to_string()); +fn number_27_is_xxvii() { + let input = 27; + let output = Roman::from(input).to_string(); + let expected = "XXVII"; + assert_eq!(output, expected); } #[test] #[ignore] -fn fifty_nine() { - assert_eq!("LIX", Roman::from(59).to_string()); +fn number_48_is_xlviii() { + let input = 48; + let output = Roman::from(input).to_string(); + let expected = "XLVIII"; + assert_eq!(output, expected); } #[test] #[ignore] -fn ninety_three() { - assert_eq!("XCIII", Roman::from(93).to_string()); +fn number_49_is_xlix() { + let input = 49; + let output = Roman::from(input).to_string(); + let expected = "XLIX"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_141() { - assert_eq!("CXLI", Roman::from(141).to_string()); +fn number_59_is_lix() { + let input = 59; + let output = Roman::from(input).to_string(); + let expected = "LIX"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_163() { - assert_eq!("CLXIII", Roman::from(163).to_string()); +fn number_66_is_lxvi() { + let input = 66; + let output = Roman::from(input).to_string(); + let expected = "LXVI"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_402() { - assert_eq!("CDII", Roman::from(402).to_string()); +fn number_93_is_xciii() { + let input = 93; + let output = Roman::from(input).to_string(); + let expected = "XCIII"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_575() { - assert_eq!("DLXXV", Roman::from(575).to_string()); +fn number_141_is_cxli() { + let input = 141; + let output = Roman::from(input).to_string(); + let expected = "CXLI"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_911() { - assert_eq!("CMXI", Roman::from(911).to_string()); +fn number_163_is_clxiii() { + let input = 163; + let output = Roman::from(input).to_string(); + let expected = "CLXIII"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_1024() { - assert_eq!("MXXIV", Roman::from(1024).to_string()); +fn number_166_is_clxvi() { + let input = 166; + let output = Roman::from(input).to_string(); + let expected = "CLXVI"; + assert_eq!(output, expected); } #[test] #[ignore] -fn test_3000() { - assert_eq!("MMM", Roman::from(3000).to_string()); +fn number_402_is_cdii() { + let input = 402; + let output = Roman::from(input).to_string(); + let expected = "CDII"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn number_575_is_dlxxv() { + let input = 575; + let output = Roman::from(input).to_string(); + let expected = "DLXXV"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn number_666_is_dclxvi() { + let input = 666; + let output = Roman::from(input).to_string(); + let expected = "DCLXVI"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn number_911_is_cmxi() { + let input = 911; + let output = Roman::from(input).to_string(); + let expected = "CMXI"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn number_1024_is_mxxiv() { + let input = 1024; + let output = Roman::from(input).to_string(); + let expected = "MXXIV"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn number_1666_is_mdclxvi() { + let input = 1666; + let output = Roman::from(input).to_string(); + let expected = "MDCLXVI"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn number_3000_is_mmm() { + let input = 3000; + let output = Roman::from(input).to_string(); + let expected = "MMM"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn number_3001_is_mmmi() { + let input = 3001; + let output = Roman::from(input).to_string(); + let expected = "MMMI"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn number_3999_is_mmmcmxcix() { + let input = 3999; + let output = Roman::from(input).to_string(); + let expected = "MMMCMXCIX"; + assert_eq!(output, expected); } From 63814ba8ffe151e6a723111d6393a1fd97c3b4af Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Mon, 20 Nov 2023 15:29:49 +0100 Subject: [PATCH 207/436] Sync rectangles with problem-specifications (#1785) --- .../rectangles/.meta/test_template.tera | 19 +++ .../practice/rectangles/.meta/tests.toml | 55 +++++++- .../practice/rectangles/tests/rectangles.rs | 121 +++++++++++------- 3 files changed, 147 insertions(+), 48 deletions(-) create mode 100644 exercises/practice/rectangles/.meta/test_template.tera diff --git a/exercises/practice/rectangles/.meta/test_template.tera b/exercises/practice/rectangles/.meta/test_template.tera new file mode 100644 index 000000000..0813a456a --- /dev/null +++ b/exercises/practice/rectangles/.meta/test_template.tera @@ -0,0 +1,19 @@ +{% for test in cases %} +#[test] +{% if loop.index != 1 -%} +#[ignore] +{% endif -%} +fn test_{{ test.description | slugify | replace(from="-", to="_") }}() { + {% if test.input.strings | length > 1 -%} + #[rustfmt::skip] + {%- endif %} + let input = &[ + {%- for row in test.input.strings %} + {{ row | json_encode }}, + {%- endfor %} + ]; + let output = {{ crate_name }}::{{ fn_names[0] }}(input); + let expected = {{ test.expected | json_encode() }}; + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/rectangles/.meta/tests.toml b/exercises/practice/rectangles/.meta/tests.toml index be690e975..282015033 100644 --- a/exercises/practice/rectangles/.meta/tests.toml +++ b/exercises/practice/rectangles/.meta/tests.toml @@ -1,3 +1,52 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[485b7bab-4150-40aa-a8db-73013427d08c] +description = "no rows" + +[076929ed-27e8-45dc-b14b-08279944dc49] +description = "no columns" + +[0a8abbd1-a0a4-4180-aa4e-65c1b1a073fa] +description = "no rectangles" + +[a4ba42e9-4e7f-4973-b7c7-4ce0760ac6cd] +description = "one rectangle" + +[ced06550-83da-4d23-98b7-d24152e0db93] +description = "two rectangles without shared parts" + +[5942d69a-a07c-41c8-8b93-2d13877c706a] +description = "five rectangles with shared parts" + +[82d70be4-ab37-4bf2-a433-e33778d3bbf1] +description = "rectangle of height 1 is counted" + +[57f1bc0e-2782-401e-ab12-7c01d8bfc2e0] +description = "rectangle of width 1 is counted" + +[ef0bb65c-bd80-4561-9535-efc4067054f9] +description = "1x1 square is counted" + +[e1e1d444-e926-4d30-9bf3-7d8ec9a9e330] +description = "only complete rectangles are counted" + +[ca021a84-1281-4a56-9b9b-af14113933a4] +description = "rectangles can be of different sizes" + +[51f689a7-ef3f-41ae-aa2f-5ea09ad897ff] +description = "corner is required for a rectangle to be complete" + +[d78fe379-8c1b-4d3c-bdf7-29bfb6f6dc66] +description = "large input with many rectangles" + +[6ef24e0f-d191-46da-b929-4faca24b4cd2] +description = "rectangles must have four sides" diff --git a/exercises/practice/rectangles/tests/rectangles.rs b/exercises/practice/rectangles/tests/rectangles.rs index c3f72237c..6c8e467c9 100644 --- a/exercises/practice/rectangles/tests/rectangles.rs +++ b/exercises/practice/rectangles/tests/rectangles.rs @@ -1,143 +1,168 @@ -use rectangles::count; - #[test] -fn zero_area_1() { - let lines = &[]; - assert_eq!(0, count(lines)) +fn test_no_rows() { + let input = &[]; + let output = rectangles::count(input); + let expected = 0; + assert_eq!(output, expected); } #[test] #[ignore] -fn zero_area_2() { - let lines = &[""]; - assert_eq!(0, count(lines)) +fn test_no_columns() { + let input = &[""]; + let output = rectangles::count(input); + let expected = 0; + assert_eq!(output, expected); } #[test] #[ignore] -fn empty_area() { - let lines = &[" "]; - assert_eq!(0, count(lines)) +fn test_no_rectangles() { + let input = &[" "]; + let output = rectangles::count(input); + let expected = 0; + assert_eq!(output, expected); } #[test] #[ignore] -fn one_rectangle() { +fn test_one_rectangle() { #[rustfmt::skip] - let lines = &[ + let input = &[ "+-+", "| |", "+-+", ]; - assert_eq!(1, count(lines)) + let output = rectangles::count(input); + let expected = 1; + assert_eq!(output, expected); } #[test] #[ignore] -fn two_rectangles_no_shared_parts() { +fn test_two_rectangles_without_shared_parts() { #[rustfmt::skip] - let lines = &[ + let input = &[ " +-+", " | |", "+-+-+", "| | ", "+-+ ", ]; - assert_eq!(2, count(lines)) + let output = rectangles::count(input); + let expected = 2; + assert_eq!(output, expected); } #[test] #[ignore] -fn five_rectangles_three_regions() { +fn test_five_rectangles_with_shared_parts() { #[rustfmt::skip] - let lines = &[ + let input = &[ " +-+", " | |", "+-+-+", "| | |", "+-+-+", ]; - assert_eq!(5, count(lines)) + let output = rectangles::count(input); + let expected = 5; + assert_eq!(output, expected); } #[test] #[ignore] -fn rectangle_of_height_1() { +fn test_rectangle_of_height_1_is_counted() { #[rustfmt::skip] - let lines = &[ + let input = &[ "+--+", "+--+", ]; - assert_eq!(1, count(lines)) + let output = rectangles::count(input); + let expected = 1; + assert_eq!(output, expected); } #[test] #[ignore] -fn rectangle_of_width_1() { +fn test_rectangle_of_width_1_is_counted() { #[rustfmt::skip] - let lines = &[ + let input = &[ "++", "||", "++", ]; - assert_eq!(1, count(lines)) + let output = rectangles::count(input); + let expected = 1; + assert_eq!(output, expected); } #[test] #[ignore] -fn unit_square() { +fn test_1x1_square_is_counted() { #[rustfmt::skip] - let lines = &[ + let input = &[ "++", "++", ]; - assert_eq!(1, count(lines)) + let output = rectangles::count(input); + let expected = 1; + assert_eq!(output, expected); } #[test] #[ignore] -fn incomplete_rectangles() { +fn test_only_complete_rectangles_are_counted() { #[rustfmt::skip] - let lines = &[ + let input = &[ " +-+", " |", "+-+-+", "| | -", "+-+-+", ]; - assert_eq!(1, count(lines)) + let output = rectangles::count(input); + let expected = 1; + assert_eq!(output, expected); } #[test] #[ignore] -fn complicated() { - let lines = &[ +fn test_rectangles_can_be_of_different_sizes() { + #[rustfmt::skip] + let input = &[ "+------+----+", "| | |", "+---+--+ |", "| | |", "+---+-------+", ]; - assert_eq!(3, count(lines)) + let output = rectangles::count(input); + let expected = 3; + assert_eq!(output, expected); } #[test] #[ignore] -fn not_so_complicated() { - let lines = &[ +fn test_corner_is_required_for_a_rectangle_to_be_complete() { + #[rustfmt::skip] + let input = &[ "+------+----+", "| | |", "+------+ |", "| | |", "+---+-------+", ]; - assert_eq!(2, count(lines)) + let output = rectangles::count(input); + let expected = 2; + assert_eq!(output, expected); } #[test] #[ignore] -fn large_input_with_many_rectangles() { - let lines = &[ +fn test_large_input_with_many_rectangles() { + #[rustfmt::skip] + let input = &[ "+---+--+----+", "| +--+----+", "+---+--+ |", @@ -147,19 +172,25 @@ fn large_input_with_many_rectangles() { "+------+ | |", " +-+", ]; - assert_eq!(60, count(lines)) + let output = rectangles::count(input); + let expected = 60; + assert_eq!(output, expected); } #[test] #[ignore] -fn three_rectangles_no_shared_parts() { +fn test_rectangles_must_have_four_sides() { #[rustfmt::skip] - let lines = &[ - " +-+ ", + let input = &[ + "+-+ +-+", + "| | | |", + "+-+-+-+", " | | ", "+-+-+-+", "| | | |", "+-+ +-+", ]; - assert_eq!(3, count(lines)) + let output = rectangles::count(input); + let expected = 5; + assert_eq!(output, expected); } From b2f72efa6cbc0f2d37ab2439728f481a147750fd Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 21 Nov 2023 16:07:26 +0100 Subject: [PATCH 208/436] Split rust-tooling into separate crates (#1795) TL;DR: This makes CI faster. The main advantage of splitting the tooling into several crates is that the CI tests only need to compile the dependencies needed for them. Notably, that excludes Tera, which is used for exercise generation. This also paves the way for making the exercise generator more rich, using potentially compile-time heavy crates (e.g. `clap`). --- .github/workflows/tests.yml | 2 +- rust-tooling/Cargo.lock | 86 ++++++++++++------- rust-tooling/Cargo.toml | 20 +---- rust-tooling/ci-tests/Cargo.toml | 16 ++++ rust-tooling/ci-tests/src/lib.rs | 5 ++ .../tests/bash_script_conventions.rs | 3 +- .../{ => ci-tests}/tests/count_ignores.rs | 2 +- .../{ => ci-tests}/tests/difficulties.rs | 2 +- .../tests/no_authors_in_cargo_toml.rs | 2 +- .../tests/no_trailing_whitespace.rs | 4 +- rust-tooling/generator/Cargo.toml | 17 ++++ .../default_test_template.tera | 0 .../src/bin/generate_exercise.rs | 8 +- .../src/exercise_generation.rs | 14 +-- rust-tooling/generator/src/lib.rs | 1 + rust-tooling/models/Cargo.toml | 15 ++++ .../{ => models}/src/exercise_config.rs | 16 ++-- rust-tooling/{ => models}/src/lib.rs | 4 +- rust-tooling/{ => models}/src/problem_spec.rs | 2 +- rust-tooling/{ => models}/src/track_config.rs | 2 +- rust-tooling/utils/Cargo.toml | 8 ++ .../{src/fs_utils.rs => utils/src/fs.rs} | 12 ++- rust-tooling/utils/src/lib.rs | 1 + 23 files changed, 157 insertions(+), 85 deletions(-) create mode 100644 rust-tooling/ci-tests/Cargo.toml create mode 100644 rust-tooling/ci-tests/src/lib.rs rename rust-tooling/{ => ci-tests}/tests/bash_script_conventions.rs (96%) rename rust-tooling/{ => ci-tests}/tests/count_ignores.rs (96%) rename rust-tooling/{ => ci-tests}/tests/difficulties.rs (89%) rename rust-tooling/{ => ci-tests}/tests/no_authors_in_cargo_toml.rs (89%) rename rust-tooling/{ => ci-tests}/tests/no_trailing_whitespace.rs (92%) create mode 100644 rust-tooling/generator/Cargo.toml rename rust-tooling/{src => generator}/default_test_template.tera (100%) rename rust-tooling/{ => generator}/src/bin/generate_exercise.rs (97%) rename rust-tooling/{ => generator}/src/exercise_generation.rs (91%) create mode 100644 rust-tooling/generator/src/lib.rs create mode 100644 rust-tooling/models/Cargo.toml rename rust-tooling/{ => models}/src/exercise_config.rs (88%) rename rust-tooling/{ => models}/src/lib.rs (59%) rename rust-tooling/{ => models}/src/problem_spec.rs (98%) rename rust-tooling/{ => models}/src/track_config.rs (98%) create mode 100644 rust-tooling/utils/Cargo.toml rename rust-tooling/{src/fs_utils.rs => utils/src/fs.rs} (56%) create mode 100644 rust-tooling/utils/src/lib.rs diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index cd9b346b0..de62dbd1c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -92,7 +92,7 @@ jobs: toolchain: stable - name: Run tests - run: cd rust-tooling && cargo test + run: cd rust-tooling/ci-tests && cargo test rustformat: name: Check Rust Formatting diff --git a/rust-tooling/Cargo.lock b/rust-tooling/Cargo.lock index ee7edff04..9d878f58f 100644 --- a/rust-tooling/Cargo.lock +++ b/rust-tooling/Cargo.lock @@ -112,6 +112,17 @@ dependencies = [ "phf_codegen", ] +[[package]] +name = "ci-tests" +version = "0.1.0" +dependencies = [ + "convert_case", + "ignore", + "models", + "serde_json", + "utils", +] + [[package]] name = "convert_case" version = "0.6.0" @@ -189,9 +200,9 @@ dependencies = [ [[package]] name = "dyn-clone" -version = "1.0.13" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbfc4744c1b8f2a09adc0e55242f60b1af195d88596bd8700be74418c056c555" +checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" [[package]] name = "equivalent" @@ -200,26 +211,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] -name = "exercism_tooling" +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "generator" version = "0.1.0" dependencies = [ "convert_case", "glob", - "ignore", "inquire", - "once_cell", - "serde", + "models", "serde_json", "tera", + "utils", "uuid", ] -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - [[package]] name = "generic-array" version = "0.14.7" @@ -375,9 +385,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.147" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "libm" @@ -387,9 +397,9 @@ checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" [[package]] name = "lock_api" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" dependencies = [ "autocfg", "scopeguard", @@ -409,9 +419,9 @@ checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" [[package]] name = "mio" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" dependencies = [ "libc", "log", @@ -419,6 +429,18 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "models" +version = "0.1.0" +dependencies = [ + "ignore", + "once_cell", + "serde", + "serde_json", + "utils", + "uuid", +] + [[package]] name = "newline-converter" version = "0.2.2" @@ -455,9 +477,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", @@ -620,9 +642,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.3.5" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags", ] @@ -699,9 +721,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.105" +version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "indexmap", "itoa", @@ -767,9 +789,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.0" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" [[package]] name = "syn" @@ -910,15 +932,19 @@ checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" [[package]] name = "unicode-width" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + +[[package]] +name = "utils" +version = "0.1.0" [[package]] name = "uuid" -version = "1.4.1" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" +checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" dependencies = [ "getrandom", ] diff --git a/rust-tooling/Cargo.toml b/rust-tooling/Cargo.toml index a60bfb872..d636e848f 100644 --- a/rust-tooling/Cargo.toml +++ b/rust-tooling/Cargo.toml @@ -1,17 +1,3 @@ -[package] -name = "exercism_tooling" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -convert_case = "0.6.0" -glob = "0.3.1" -ignore = "0.4.20" -inquire = "0.6.2" -once_cell = "1.18.0" -serde = { version = "1.0.188", features = ["derive"] } -serde_json = { version = "1.0.105", features = ["preserve_order"] } -tera = "1.19.1" -uuid = { version = "1.4.1", features = ["v4"] } +[workspace] +members = ["generator", "ci-tests", "utils", "models"] +resolver = "2" diff --git a/rust-tooling/ci-tests/Cargo.toml b/rust-tooling/ci-tests/Cargo.toml new file mode 100644 index 000000000..ba6030c47 --- /dev/null +++ b/rust-tooling/ci-tests/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "ci-tests" +version = "0.1.0" +edition = "2021" +description = "Tests to be run in CI to make sure the repo is in good shape" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +# This crate is built in CI to run the tests. +# Be considerate when adding dependencies to keep compile times reasonable. +[dependencies] +convert_case = "0.6.0" +ignore = "0.4.20" +models = { version = "0.1.0", path = "../models" } +serde_json = "1.0.108" +utils = { version = "0.1.0", path = "../utils" } diff --git a/rust-tooling/ci-tests/src/lib.rs b/rust-tooling/ci-tests/src/lib.rs new file mode 100644 index 000000000..9255357f4 --- /dev/null +++ b/rust-tooling/ci-tests/src/lib.rs @@ -0,0 +1,5 @@ +//! dummy lib.rs +//! +//! This crate only exists for the tests. +//! The lib.rs may be used for shared code amoung the tests, +//! if that ever turns out to be useful. diff --git a/rust-tooling/tests/bash_script_conventions.rs b/rust-tooling/ci-tests/tests/bash_script_conventions.rs similarity index 96% rename from rust-tooling/tests/bash_script_conventions.rs rename to rust-tooling/ci-tests/tests/bash_script_conventions.rs index a6a9091ab..95f0792ce 100644 --- a/rust-tooling/tests/bash_script_conventions.rs +++ b/rust-tooling/ci-tests/tests/bash_script_conventions.rs @@ -1,12 +1,11 @@ use std::path::PathBuf; use convert_case::{Case, Casing}; -use exercism_tooling::fs_utils; /// Runs a function for each bash script in the bin directory. /// The function is passed the path of the script. fn for_all_scripts(f: fn(&str)) { - fs_utils::cd_into_repo_root(); + utils::fs::cd_into_repo_root(); for entry in std::fs::read_dir("bin").unwrap() { let path = entry.unwrap().path(); diff --git a/rust-tooling/tests/count_ignores.rs b/rust-tooling/ci-tests/tests/count_ignores.rs similarity index 96% rename from rust-tooling/tests/count_ignores.rs rename to rust-tooling/ci-tests/tests/count_ignores.rs index 9b7349624..e10919a64 100644 --- a/rust-tooling/tests/count_ignores.rs +++ b/rust-tooling/ci-tests/tests/count_ignores.rs @@ -1,4 +1,4 @@ -use exercism_tooling::exercise_config::{ +use models::exercise_config::{ get_all_concept_exercise_paths, get_all_practice_exercise_paths, PracticeExercise, }; diff --git a/rust-tooling/tests/difficulties.rs b/rust-tooling/ci-tests/tests/difficulties.rs similarity index 89% rename from rust-tooling/tests/difficulties.rs rename to rust-tooling/ci-tests/tests/difficulties.rs index 3808829ed..2fd3b2ea6 100644 --- a/rust-tooling/tests/difficulties.rs +++ b/rust-tooling/ci-tests/tests/difficulties.rs @@ -1,6 +1,6 @@ //! Make sure exercise difficulties are set correctly -use exercism_tooling::track_config::TRACK_CONFIG; +use models::track_config::TRACK_CONFIG; #[test] fn difficulties_are_valid() { diff --git a/rust-tooling/tests/no_authors_in_cargo_toml.rs b/rust-tooling/ci-tests/tests/no_authors_in_cargo_toml.rs similarity index 89% rename from rust-tooling/tests/no_authors_in_cargo_toml.rs rename to rust-tooling/ci-tests/tests/no_authors_in_cargo_toml.rs index 2e312c671..7cbece376 100644 --- a/rust-tooling/tests/no_authors_in_cargo_toml.rs +++ b/rust-tooling/ci-tests/tests/no_authors_in_cargo_toml.rs @@ -1,4 +1,4 @@ -use exercism_tooling::exercise_config::get_all_exercise_paths; +use models::exercise_config::get_all_exercise_paths; /// The package manifest of each exercise should not contain an `authors` field. /// The authors are already specified in the track configuration. diff --git a/rust-tooling/tests/no_trailing_whitespace.rs b/rust-tooling/ci-tests/tests/no_trailing_whitespace.rs similarity index 92% rename from rust-tooling/tests/no_trailing_whitespace.rs rename to rust-tooling/ci-tests/tests/no_trailing_whitespace.rs index 9fc3254c7..aa368e3ff 100644 --- a/rust-tooling/tests/no_trailing_whitespace.rs +++ b/rust-tooling/ci-tests/tests/no_trailing_whitespace.rs @@ -1,7 +1,5 @@ use std::path::Path; -use exercism_tooling::fs_utils; - fn contains_trailing_whitespace(p: &Path) -> bool { let contents = std::fs::read_to_string(p).unwrap(); for line in contents.lines() { @@ -14,7 +12,7 @@ fn contains_trailing_whitespace(p: &Path) -> bool { #[test] fn no_trailing_whitespace() { - fs_utils::cd_into_repo_root(); + utils::fs::cd_into_repo_root(); for entry in ignore::Walk::new("./") { let entry = entry.unwrap(); diff --git a/rust-tooling/generator/Cargo.toml b/rust-tooling/generator/Cargo.toml new file mode 100644 index 000000000..c92ed589b --- /dev/null +++ b/rust-tooling/generator/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "generator" +version = "0.1.0" +edition = "2021" +description = "Generates exercise boilerplate, especially test cases" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +convert_case = "0.6.0" +glob = "0.3.1" +inquire = "0.6.2" +models = { version = "0.1.0", path = "../models" } +serde_json = { version = "1.0.105", features = ["preserve_order"] } +tera = "1.19.1" +utils = { version = "0.1.0", path = "../utils" } +uuid = { version = "1.4.1", features = ["v4"] } diff --git a/rust-tooling/src/default_test_template.tera b/rust-tooling/generator/default_test_template.tera similarity index 100% rename from rust-tooling/src/default_test_template.tera rename to rust-tooling/generator/default_test_template.tera diff --git a/rust-tooling/src/bin/generate_exercise.rs b/rust-tooling/generator/src/bin/generate_exercise.rs similarity index 97% rename from rust-tooling/src/bin/generate_exercise.rs rename to rust-tooling/generator/src/bin/generate_exercise.rs index b22fa1562..0abc88db2 100644 --- a/rust-tooling/src/bin/generate_exercise.rs +++ b/rust-tooling/generator/src/bin/generate_exercise.rs @@ -1,12 +1,10 @@ use std::path::PathBuf; use convert_case::{Case, Casing}; -use exercism_tooling::{ - exercise_generation, fs_utils, - track_config::{self, TRACK_CONFIG}, -}; +use generator::exercise_generation; use glob::glob; use inquire::{validator::Validation, Select, Text}; +use models::track_config::{self, TRACK_CONFIG}; enum Difficulty { Easy, @@ -39,7 +37,7 @@ impl std::fmt::Display for Difficulty { } fn main() { - fs_utils::cd_into_repo_root(); + utils::fs::cd_into_repo_root(); let is_update = std::env::args().any(|arg| arg == "update"); diff --git a/rust-tooling/src/exercise_generation.rs b/rust-tooling/generator/src/exercise_generation.rs similarity index 91% rename from rust-tooling/src/exercise_generation.rs rename to rust-tooling/generator/src/exercise_generation.rs index 3421abbf7..f85687f2c 100644 --- a/rust-tooling/src/exercise_generation.rs +++ b/rust-tooling/generator/src/exercise_generation.rs @@ -4,10 +4,10 @@ use std::{ process::{Command, Stdio}, }; -use tera::Context; +use tera::{Context, Tera}; -use crate::{ - exercise_config::{get_excluded_tests, get_test_emplate}, +use models::{ + exercise_config::get_excluded_tests, problem_spec::{get_additional_test_cases, get_canonical_data, SingleTestCase, TestCase}, }; @@ -76,7 +76,7 @@ fn generate_example_rs(fn_name: &str) -> String { ) } -static TEST_TEMPLATE: &str = include_str!("default_test_template.tera"); +static TEST_TEMPLATE: &str = include_str!("../default_test_template.tera"); fn extend_single_cases(single_cases: &mut Vec, cases: Vec) { for case in cases { @@ -101,7 +101,7 @@ fn generate_tests(slug: &str, fn_names: Vec) -> String { cases }; let excluded_tests = get_excluded_tests(slug); - let mut template = get_test_emplate(slug).unwrap(); + let mut template = get_test_template(slug).unwrap(); if template.get_template_names().next().is_none() { template .add_raw_template("test_template.tera", TEST_TEMPLATE) @@ -143,3 +143,7 @@ fn generate_tests(slug: &str, fn_names: Vec) -> String { rendered.into() } } + +pub fn get_test_template(slug: &str) -> Option { + Some(Tera::new(format!("exercises/practice/{slug}/.meta/*.tera").as_str()).unwrap()) +} diff --git a/rust-tooling/generator/src/lib.rs b/rust-tooling/generator/src/lib.rs new file mode 100644 index 000000000..0657ebb64 --- /dev/null +++ b/rust-tooling/generator/src/lib.rs @@ -0,0 +1 @@ +pub mod exercise_generation; diff --git a/rust-tooling/models/Cargo.toml b/rust-tooling/models/Cargo.toml new file mode 100644 index 000000000..505f22dea --- /dev/null +++ b/rust-tooling/models/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "models" +version = "0.1.0" +edition = "2021" +description = "Data structures for exercism stuff" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +ignore = "0.4.20" +once_cell = "1.18.0" +serde = { version = "1.0.188", features = ["derive"] } +serde_json = { version = "1.0.105", features = ["preserve_order"] } +utils = { version = "0.1.0", path = "../utils" } +uuid = { version = "1.6.1", features = ["v4"] } diff --git a/rust-tooling/src/exercise_config.rs b/rust-tooling/models/src/exercise_config.rs similarity index 88% rename from rust-tooling/src/exercise_config.rs rename to rust-tooling/models/src/exercise_config.rs index c41d2807a..2ab4ff4b0 100644 --- a/rust-tooling/src/exercise_config.rs +++ b/rust-tooling/models/src/exercise_config.rs @@ -1,9 +1,8 @@ //! This module provides a data structure for exercise configuration stored in -//! `.meta/config`. It is capable of serializing and deserializing th +//! `.meta/config`. It is capable of serializing and deserializing the //! configuration, for example with `serde_json`. use serde::{Deserialize, Serialize}; -use tera::Tera; use crate::track_config::TRACK_CONFIG; @@ -62,23 +61,23 @@ pub struct Custom { } pub fn get_all_concept_exercise_paths() -> impl Iterator { - let crate_dir = env!("CARGO_MANIFEST_DIR"); + use utils::fs::REPO_ROOT_DIR; TRACK_CONFIG .exercises .concept .iter() - .map(move |e| format!("{crate_dir}/../exercises/concept/{}", e.slug)) + .map(move |e| format!("{REPO_ROOT_DIR}/exercises/concept/{}", e.slug)) } pub fn get_all_practice_exercise_paths() -> impl Iterator { - let crate_dir = env!("CARGO_MANIFEST_DIR"); + use utils::fs::REPO_ROOT_DIR; TRACK_CONFIG .exercises .practice .iter() - .map(move |e| format!("{crate_dir}/../exercises/practice/{}", e.slug)) + .map(move |e| format!("{REPO_ROOT_DIR}/exercises/practice/{}", e.slug)) } pub fn get_all_exercise_paths() -> impl Iterator { @@ -120,8 +119,3 @@ pub fn get_excluded_tests(slug: &str) -> Vec { excluded_tests } - -/// Returns the uuids of the tests excluded in .meta/tests.toml -pub fn get_test_emplate(slug: &str) -> Option { - Some(Tera::new(format!("exercises/practice/{slug}/.meta/*.tera").as_str()).unwrap()) -} diff --git a/rust-tooling/src/lib.rs b/rust-tooling/models/src/lib.rs similarity index 59% rename from rust-tooling/src/lib.rs rename to rust-tooling/models/src/lib.rs index 727eaca9a..6833d8f0e 100644 --- a/rust-tooling/src/lib.rs +++ b/rust-tooling/models/src/lib.rs @@ -1,5 +1,3 @@ -pub mod problem_spec; pub mod exercise_config; +pub mod problem_spec; pub mod track_config; -pub mod exercise_generation; -pub mod fs_utils; diff --git a/rust-tooling/src/problem_spec.rs b/rust-tooling/models/src/problem_spec.rs similarity index 98% rename from rust-tooling/src/problem_spec.rs rename to rust-tooling/models/src/problem_spec.rs index 56a5c4fe7..1c8c23161 100644 --- a/rust-tooling/src/problem_spec.rs +++ b/rust-tooling/models/src/problem_spec.rs @@ -72,7 +72,7 @@ pub fn get_additional_test_cases(slug: &str) -> Vec { #[test] fn deserialize_canonical_data() { - crate::fs_utils::cd_into_repo_root(); + utils::fs::cd_into_repo_root(); for entry in ignore::Walk::new("problem-specifications/exercises") .filter_map(|e| e.ok()) .filter(|e| e.file_name().to_str().unwrap() == "canonical-data.json") diff --git a/rust-tooling/src/track_config.rs b/rust-tooling/models/src/track_config.rs similarity index 98% rename from rust-tooling/src/track_config.rs rename to rust-tooling/models/src/track_config.rs index 01c425982..fc0bda868 100644 --- a/rust-tooling/src/track_config.rs +++ b/rust-tooling/models/src/track_config.rs @@ -12,7 +12,7 @@ use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; pub static TRACK_CONFIG: Lazy = Lazy::new(|| { - let config = include_str!("../../config.json"); + let config = include_str!("../../../config.json"); serde_json::from_str(config).expect("should deserialize the track config") }); diff --git a/rust-tooling/utils/Cargo.toml b/rust-tooling/utils/Cargo.toml new file mode 100644 index 000000000..a0cba9a27 --- /dev/null +++ b/rust-tooling/utils/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "utils" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/rust-tooling/src/fs_utils.rs b/rust-tooling/utils/src/fs.rs similarity index 56% rename from rust-tooling/src/fs_utils.rs rename to rust-tooling/utils/src/fs.rs index 6af30496a..6a86bca2b 100644 --- a/rust-tooling/src/fs_utils.rs +++ b/rust-tooling/utils/src/fs.rs @@ -1,12 +1,18 @@ //! This module contains utilities for working with the files in this repo. +pub static REPO_ROOT_DIR: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../.."); + +#[test] +fn repo_root_dir_is_correct() { + let git_database = std::path::PathBuf::from(REPO_ROOT_DIR).join(".git"); + assert!(git_database.is_dir()) +} + /// Changes the current working directory to the root of the repository. /// /// This is intended to be used by executables which operate on files /// of the repository, so they can use relative paths and still work /// when called from anywhere within the repository. pub fn cd_into_repo_root() { - static RUST_TOOLING_DIR: &str = env!("CARGO_MANIFEST_DIR"); - let repo_root_dir = std::path::PathBuf::from(RUST_TOOLING_DIR).join(".."); - std::env::set_current_dir(repo_root_dir).unwrap(); + std::env::set_current_dir(REPO_ROOT_DIR).unwrap(); } diff --git a/rust-tooling/utils/src/lib.rs b/rust-tooling/utils/src/lib.rs new file mode 100644 index 000000000..d521fbd77 --- /dev/null +++ b/rust-tooling/utils/src/lib.rs @@ -0,0 +1 @@ +pub mod fs; From ff92b1c3779ade2ffbbb6f239aef1952dd6605e5 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Thu, 23 Nov 2023 09:37:58 +0100 Subject: [PATCH 209/436] Improve exercise generator (#1797) - Add a clap-based CLI All user-input can now be entered upfront with cli flags or later by being prompted for it interactively. - Limit output of rustfmt passed onto user (There are usually many duplicates when generating test cases.) - Handle absence of canonical data (e.g. for custom exercises) not from problem-specifications) piggyback: - Make exercise difficulty in track config type-safe --- docs/CONTRIBUTING.md | 4 +- justfile | 13 +- rust-tooling/Cargo.lock | 127 +++++++++- rust-tooling/Cargo.toml | 2 +- rust-tooling/ci-tests/tests/difficulties.rs | 18 -- .../{generator => generate}/Cargo.toml | 3 +- rust-tooling/generate/src/cli.rs | 187 +++++++++++++++ .../src/exercise_generation.rs | 23 +- rust-tooling/generate/src/main.rs | 129 ++++++++++ .../templates}/default_test_template.tera | 0 .../generator/src/bin/generate_exercise.rs | 225 ------------------ rust-tooling/generator/src/lib.rs | 1 - rust-tooling/models/Cargo.toml | 1 + rust-tooling/models/src/problem_spec.rs | 4 +- rust-tooling/models/src/track_config.rs | 17 +- 15 files changed, 487 insertions(+), 267 deletions(-) delete mode 100644 rust-tooling/ci-tests/tests/difficulties.rs rename rust-tooling/{generator => generate}/Cargo.toml (88%) create mode 100644 rust-tooling/generate/src/cli.rs rename rust-tooling/{generator => generate}/src/exercise_generation.rs (82%) create mode 100644 rust-tooling/generate/src/main.rs rename rust-tooling/{generator => generate/templates}/default_test_template.tera (100%) delete mode 100644 rust-tooling/generator/src/bin/generate_exercise.rs delete mode 100644 rust-tooling/generator/src/lib.rs diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 70b4fbb6d..92f5d3df6 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -29,7 +29,7 @@ Please familiarize yourself with the [Exercism documentation about practice exer [Exercism documentation about practice exercises]: https://exercism.org/docs/building/tracks/practice-exercises -Run `just add-practice-exercise` and you'll be prompted for the minimal +Run `just add-exercise` and you'll be prompted for the minimal information required to generate the exercise stub for you. After that, jump in the generated exercise and fill in any todos you find. This includes most notably: @@ -89,7 +89,7 @@ This includes their test suite and user-facing documentation. Before proposing changes here, check if they should be made `problem-specifications` instead. -Run `just update-practice-exercise` to update an exercise. +Run `just update-exercise` to update an exercise. This outsources most work to `configlet sync --update` and runs the test generator again. diff --git a/justfile b/justfile index 61f517658..59bcadb55 100644 --- a/justfile +++ b/justfile @@ -17,13 +17,8 @@ test: cd rust-tooling && cargo test # TODO format exercises -add-practice-exercise: - cd rust-tooling && cargo run --quiet --bin generate_exercise +add-exercise *args="": + cd rust-tooling/generate; cargo run --quiet --release -- add {{ args }} -update-practice-exercise: - cd rust-tooling && cargo run --quiet --bin generate_exercise update - -# TODO remove. resets result of add-practice-exercise. -clean: - git restore config.json exercises/practice - git clean -- exercises/practice +update-exercise *args="": + cd rust-tooling/generate; cargo run --quiet --release -- update {{ args }} diff --git a/rust-tooling/Cargo.lock b/rust-tooling/Cargo.lock index 9d878f58f..747467f1f 100644 --- a/rust-tooling/Cargo.lock +++ b/rust-tooling/Cargo.lock @@ -26,6 +26,54 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "anstyle-parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +dependencies = [ + "anstyle", + "windows-sys", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -123,6 +171,52 @@ dependencies = [ "utils", ] +[[package]] +name = "clap" +version = "4.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "convert_case" version = "0.6.0" @@ -217,9 +311,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] -name = "generator" +name = "generate" version = "0.1.0" dependencies = [ + "clap", "convert_case", "glob", "inquire", @@ -287,6 +382,12 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "humansize" version = "2.1.3" @@ -437,6 +538,7 @@ dependencies = [ "once_cell", "serde", "serde_json", + "serde_repr", "utils", "uuid", ] @@ -731,6 +833,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "sha2" version = "0.10.7" @@ -793,6 +906,12 @@ version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "syn" version = "2.0.31" @@ -936,6 +1055,12 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "utils" version = "0.1.0" diff --git a/rust-tooling/Cargo.toml b/rust-tooling/Cargo.toml index d636e848f..47ea1e1ca 100644 --- a/rust-tooling/Cargo.toml +++ b/rust-tooling/Cargo.toml @@ -1,3 +1,3 @@ [workspace] -members = ["generator", "ci-tests", "utils", "models"] +members = ["generate", "ci-tests", "utils", "models"] resolver = "2" diff --git a/rust-tooling/ci-tests/tests/difficulties.rs b/rust-tooling/ci-tests/tests/difficulties.rs deleted file mode 100644 index 2fd3b2ea6..000000000 --- a/rust-tooling/ci-tests/tests/difficulties.rs +++ /dev/null @@ -1,18 +0,0 @@ -//! Make sure exercise difficulties are set correctly - -use models::track_config::TRACK_CONFIG; - -#[test] -fn difficulties_are_valid() { - let mut difficulties = TRACK_CONFIG - .exercises - .concept - .iter() - .map(|e| e.difficulty) - .chain(TRACK_CONFIG.exercises.practice.iter().map(|e| e.difficulty)); - - assert!( - difficulties.all(|d| matches!(d, 1 | 4 | 7 | 10)), - "exercises must have a difficulty of 1, 4, 7, or 10" - ) -} diff --git a/rust-tooling/generator/Cargo.toml b/rust-tooling/generate/Cargo.toml similarity index 88% rename from rust-tooling/generator/Cargo.toml rename to rust-tooling/generate/Cargo.toml index c92ed589b..05c31d74a 100644 --- a/rust-tooling/generator/Cargo.toml +++ b/rust-tooling/generate/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "generator" +name = "generate" version = "0.1.0" edition = "2021" description = "Generates exercise boilerplate, especially test cases" @@ -7,6 +7,7 @@ description = "Generates exercise boilerplate, especially test cases" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +clap = { version = "4.4.8", features = ["derive"] } convert_case = "0.6.0" glob = "0.3.1" inquire = "0.6.2" diff --git a/rust-tooling/generate/src/cli.rs b/rust-tooling/generate/src/cli.rs new file mode 100644 index 000000000..9c24bdff2 --- /dev/null +++ b/rust-tooling/generate/src/cli.rs @@ -0,0 +1,187 @@ +use clap::{Parser, Subcommand}; +use convert_case::{Case, Casing}; +use glob::glob; +use inquire::{validator::Validation, Select, Text}; +use models::track_config; + +#[derive(Parser)] +#[command(author, version, about, long_about = None)] +pub struct CliArgs { + #[command(subcommand)] + pub command: Command, +} + +#[derive(Subcommand)] +pub enum Command { + Add(AddArgs), + Update(UpdateArgs), +} + +impl std::fmt::Display for Command { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Command::Add(_) => write!(f, "Add"), + Command::Update(_) => write!(f, "Update"), + } + } +} + +#[derive(Parser)] +pub struct AddArgs { + #[arg(short, long)] + slug: Option, + + #[arg(short, long)] + name: Option, + + #[arg(short, long)] + difficulty: Option, +} + +pub struct FullAddArgs { + pub slug: String, + pub name: String, + pub difficulty: track_config::Difficulty, +} + +impl AddArgs { + pub fn unwrap_args_or_prompt(self) -> FullAddArgs { + let slug = self.slug.unwrap_or_else(prompt_for_add_slug); + let name = self.name.unwrap_or_else(|| prompt_for_exercise_name(&slug)); + let difficulty = self.difficulty.unwrap_or_else(prompt_for_difficulty).into(); + FullAddArgs { + slug, + name, + difficulty, + } + } +} + +#[derive(Parser)] +pub struct UpdateArgs { + /// slug of the exercise to update + #[arg(short, long)] + slug: Option, +} + +impl UpdateArgs { + pub fn unwrap_slug_or_prompt(self) -> String { + self.slug.unwrap_or_else(prompt_for_update_slug) + } +} + +pub fn prompt_for_update_slug() -> String { + let implemented_exercises = glob("exercises/practice/*") + .unwrap() + .filter_map(Result::ok) + .map(|path| path.file_name().unwrap().to_str().unwrap().to_string()) + .collect::>(); + + Select::new( + "Which exercise would you like to update?", + implemented_exercises, + ) + .prompt() + .unwrap() +} + +pub fn prompt_for_add_slug() -> String { + let implemented_exercises = glob("exercises/concept/*") + .unwrap() + .chain(glob("exercises/practice/*").unwrap()) + .filter_map(Result::ok) + .map(|path| path.file_name().unwrap().to_str().unwrap().to_string()) + .collect::>(); + + let todo_with_spec = glob("problem-specifications/exercises/*") + .unwrap() + .filter_map(Result::ok) + .map(|path| path.file_name().unwrap().to_str().unwrap().to_string()) + .filter(|e| !implemented_exercises.contains(e)) + .collect::>(); + + println!("(suggestions are from problem-specifications)"); + Text::new("What's the slug of your exercise?") + .with_autocomplete(move |input: &_| { + let mut slugs = todo_with_spec.clone(); + slugs.retain(|e| e.starts_with(input)); + Ok(slugs) + }) + .with_validator(|input: &str| { + if input.is_empty() { + Ok(Validation::Invalid("The slug must not be empty.".into())) + } else if !input.is_case(Case::Kebab) { + Ok(Validation::Invalid( + "The slug must be in kebab-case.".into(), + )) + } else { + Ok(Validation::Valid) + } + }) + .with_validator(move |input: &str| { + if !implemented_exercises.contains(&input.to_string()) { + Ok(Validation::Valid) + } else { + Ok(Validation::Invalid( + "An exercise with this slug already exists.".into(), + )) + } + }) + .prompt() + .unwrap() +} + +pub fn prompt_for_exercise_name(slug: &str) -> String { + Text::new("What's the name of your exercise?") + .with_initial_value(&slug.to_case(Case::Title)) + .prompt() + .unwrap() +} + +/// Mostly a clone of the `Difficulty` enum from `models::track_config`. +/// The purpose of this is that we can implement cli-specific traits in this crate. +#[derive(Debug, Clone, Copy, clap::ValueEnum)] +#[repr(u8)] +pub enum Difficulty { + Easy = 1, + Medium = 4, + // I'm not sure why there are two medium difficulties + Medium2 = 7, + Hard = 10, +} + +impl From for track_config::Difficulty { + fn from(value: Difficulty) -> Self { + match value { + Difficulty::Easy => track_config::Difficulty::Easy, + Difficulty::Medium => track_config::Difficulty::Medium, + Difficulty::Medium2 => track_config::Difficulty::Medium2, + Difficulty::Hard => track_config::Difficulty::Hard, + } + } +} + +impl std::fmt::Display for Difficulty { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Difficulty::Easy => write!(f, "Easy (1)"), + Difficulty::Medium => write!(f, "Medium (4)"), + Difficulty::Medium2 => write!(f, "Medium (7)"), + Difficulty::Hard => write!(f, "Hard (10)"), + } + } +} + +pub fn prompt_for_difficulty() -> Difficulty { + Select::new( + "What's the difficulty of your exercise?", + vec![ + Difficulty::Easy, + Difficulty::Medium, + Difficulty::Medium2, + Difficulty::Hard, + ], + ) + .prompt() + .unwrap() +} diff --git a/rust-tooling/generator/src/exercise_generation.rs b/rust-tooling/generate/src/exercise_generation.rs similarity index 82% rename from rust-tooling/generator/src/exercise_generation.rs rename to rust-tooling/generate/src/exercise_generation.rs index f85687f2c..4aa948949 100644 --- a/rust-tooling/generator/src/exercise_generation.rs +++ b/rust-tooling/generate/src/exercise_generation.rs @@ -76,7 +76,7 @@ fn generate_example_rs(fn_name: &str) -> String { ) } -static TEST_TEMPLATE: &str = include_str!("../default_test_template.tera"); +static TEST_TEMPLATE: &str = include_str!("../templates/default_test_template.tera"); fn extend_single_cases(single_cases: &mut Vec, cases: Vec) { for case in cases { @@ -96,7 +96,9 @@ fn to_hex(value: &tera::Value, _args: &HashMap) -> tera::Re fn generate_tests(slug: &str, fn_names: Vec) -> String { let cases = { - let mut cases = get_canonical_data(slug).cases; + let mut cases = get_canonical_data(slug) + .map(|data| data.cases) + .unwrap_or_default(); cases.extend_from_slice(&get_additional_test_cases(slug)); cases }; @@ -122,8 +124,10 @@ fn generate_tests(slug: &str, fn_names: Vec) -> String { let rendered = rendered.trim_start(); let mut child = Command::new("rustfmt") + .args(["--color=always"]) .stdin(Stdio::piped()) .stdout(Stdio::piped()) + .stderr(Stdio::piped()) .spawn() .expect("failed to spawn process"); @@ -138,8 +142,19 @@ fn generate_tests(slug: &str, fn_names: Vec) -> String { if rustfmt_out.status.success() { String::from_utf8(rustfmt_out.stdout).unwrap() } else { - // if rustfmt fails, still return the unformatted - // content to be written to the file + let rustfmt_error = String::from_utf8(rustfmt_out.stderr).unwrap(); + let mut last_16_error_lines = rustfmt_error.lines().rev().take(16).collect::>(); + last_16_error_lines.reverse(); + let last_16_error_lines = last_16_error_lines.join("\n"); + + println!( + "{last_16_error_lines}\ +^ last 16 lines of errors from rustfmt +Check the test template (.meta/test_template.tera) +It probably generates invalid Rust code." + ); + + // still return the unformatted content to be written to the file rendered.into() } } diff --git a/rust-tooling/generate/src/main.rs b/rust-tooling/generate/src/main.rs new file mode 100644 index 000000000..8eb2d624a --- /dev/null +++ b/rust-tooling/generate/src/main.rs @@ -0,0 +1,129 @@ +use std::path::PathBuf; + +use clap::Parser; +use cli::{AddArgs, FullAddArgs, UpdateArgs}; +use models::track_config::{self, TRACK_CONFIG}; + +mod cli; +mod exercise_generation; + +fn main() { + utils::fs::cd_into_repo_root(); + + let cli_args = cli::CliArgs::parse(); + + match cli_args.command { + cli::Command::Add(args) => add_exercise(args), + cli::Command::Update(args) => update_exercise(args), + } +} + +fn add_exercise(args: AddArgs) { + let FullAddArgs { + slug, + name, + difficulty, + } = args.unwrap_args_or_prompt(); + + let config = track_config::PracticeExercise::new(slug.clone(), name, difficulty); + + let mut track_config = TRACK_CONFIG.clone(); + track_config.exercises.practice.push(config); + let mut new_config = serde_json::to_string_pretty(&track_config) + .unwrap() + .to_string(); + new_config += "\n"; + std::fs::write("config.json", new_config).unwrap(); + + println!( + "\ +Added your exercise to config.json. +You can add practices, prerequisites and topics if you like." + ); + + make_configlet_generate_what_it_can(&slug); + + let is_update = false; + generate_exercise_files(&slug, is_update); +} + +fn update_exercise(args: UpdateArgs) { + let slug = args.unwrap_slug_or_prompt(); + + make_configlet_generate_what_it_can(&slug); + + let is_update = true; + generate_exercise_files(&slug, is_update); +} + +fn make_configlet_generate_what_it_can(slug: &str) { + let status = std::process::Command::new("just") + .args([ + "configlet", + "sync", + "--update", + "--yes", + "--docs", + "--metadata", + "--tests", + "include", + "--exercise", + slug, + ]) + .status() + .unwrap(); + if !status.success() { + panic!("configlet sync failed"); + } +} + +fn generate_exercise_files(slug: &str, is_update: bool) { + let fn_names = if is_update { + read_fn_names_from_lib_rs(slug) + } else { + vec!["TODO".to_string()] + }; + + let exercise = exercise_generation::new(slug, fn_names); + + let exercise_path = PathBuf::from("exercises/practice").join(slug); + + if !is_update { + std::fs::write(exercise_path.join(".gitignore"), exercise.gitignore).unwrap(); + std::fs::write(exercise_path.join("Cargo.toml"), exercise.manifest).unwrap(); + std::fs::create_dir(exercise_path.join("src")).ok(); + std::fs::write(exercise_path.join("src/lib.rs"), exercise.lib_rs).unwrap(); + std::fs::write(exercise_path.join(".meta/example.rs"), exercise.example).unwrap(); + } + + let template_path = exercise_path.join(".meta/test_template.tera"); + if std::fs::metadata(&template_path).is_err() { + std::fs::write(template_path, exercise.test_template).unwrap(); + } + + std::fs::create_dir(exercise_path.join("tests")).ok(); + std::fs::write( + exercise_path.join(format!("tests/{slug}.rs")), + exercise.tests, + ) + .unwrap(); +} + +fn read_fn_names_from_lib_rs(slug: &str) -> Vec { + let lib_rs = + std::fs::read_to_string(format!("exercises/practice/{}/src/lib.rs", slug)).unwrap(); + + lib_rs + .split("fn ") + .skip(1) + .map(|f| { + let tmp = f.split_once('(').unwrap().0; + // strip generics + if let Some((res, _)) = tmp.split_once('<') { + res.to_string() + } else { + tmp.to_string() + } + }) + .collect() +} diff --git a/rust-tooling/generator/default_test_template.tera b/rust-tooling/generate/templates/default_test_template.tera similarity index 100% rename from rust-tooling/generator/default_test_template.tera rename to rust-tooling/generate/templates/default_test_template.tera diff --git a/rust-tooling/generator/src/bin/generate_exercise.rs b/rust-tooling/generator/src/bin/generate_exercise.rs deleted file mode 100644 index 0abc88db2..000000000 --- a/rust-tooling/generator/src/bin/generate_exercise.rs +++ /dev/null @@ -1,225 +0,0 @@ -use std::path::PathBuf; - -use convert_case::{Case, Casing}; -use generator::exercise_generation; -use glob::glob; -use inquire::{validator::Validation, Select, Text}; -use models::track_config::{self, TRACK_CONFIG}; - -enum Difficulty { - Easy, - Medium, - // I'm not sure why there are two medium difficulties - Medium2, - Hard, -} - -impl From for u8 { - fn from(difficulty: Difficulty) -> Self { - match difficulty { - Difficulty::Easy => 1, - Difficulty::Medium => 4, - Difficulty::Medium2 => 7, - Difficulty::Hard => 10, - } - } -} - -impl std::fmt::Display for Difficulty { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Difficulty::Easy => write!(f, "Easy (1)"), - Difficulty::Medium => write!(f, "Medium (4)"), - Difficulty::Medium2 => write!(f, "Medium (7)"), - Difficulty::Hard => write!(f, "Hard (10)"), - } - } -} - -fn main() { - utils::fs::cd_into_repo_root(); - - let is_update = std::env::args().any(|arg| arg == "update"); - - let slug = if is_update { - ask_for_exercise_to_update() - } else { - add_entry_to_track_config() - }; - - make_configlet_generate_what_it_can(&slug); - - generate_exercise_files(&slug, is_update); -} - -fn ask_for_exercise_to_update() -> String { - let implemented_exercises = glob("exercises/practice/*") - .unwrap() - .filter_map(Result::ok) - .map(|path| path.file_name().unwrap().to_str().unwrap().to_string()) - .collect::>(); - - Select::new( - "Which exercise would you like to update?", - implemented_exercises, - ) - .prompt() - .unwrap() -} - -/// Interactively prompts the user for required fields in the track config -/// and writes the answers to config.json. -/// Returns slug. -fn add_entry_to_track_config() -> String { - let implemented_exercises = glob("exercises/concept/*") - .unwrap() - .chain(glob("exercises/practice/*").unwrap()) - .filter_map(Result::ok) - .map(|path| path.file_name().unwrap().to_str().unwrap().to_string()) - .collect::>(); - - let todo_with_spec = glob("problem-specifications/exercises/*") - .unwrap() - .filter_map(Result::ok) - .map(|path| path.file_name().unwrap().to_str().unwrap().to_string()) - .filter(|e| !implemented_exercises.contains(e)) - .collect::>(); - - println!("(suggestions are from problem-specifications)"); - let slug = Text::new("What's the slug of your exercise?") - .with_autocomplete(move |input: &_| { - let mut slugs = todo_with_spec.clone(); - slugs.retain(|e| e.starts_with(input)); - Ok(slugs) - }) - .with_validator(|input: &str| { - if input.is_empty() { - Ok(Validation::Invalid("The slug must not be empty.".into())) - } else if !input.is_case(Case::Kebab) { - Ok(Validation::Invalid( - "The slug must be in kebab-case.".into(), - )) - } else { - Ok(Validation::Valid) - } - }) - .with_validator(move |input: &str| { - if !implemented_exercises.contains(&input.to_string()) { - Ok(Validation::Valid) - } else { - Ok(Validation::Invalid( - "An exercise with this slug already exists.".into(), - )) - } - }) - .prompt() - .unwrap(); - - let name = Text::new("What's the name of your exercise?") - .with_initial_value(&slug.to_case(Case::Title)) - .prompt() - .unwrap(); - - let difficulty = Select::::new( - "What's the difficulty of your exercise?", - vec![ - Difficulty::Easy, - Difficulty::Medium, - Difficulty::Medium2, - Difficulty::Hard, - ], - ) - .prompt() - .unwrap() - .into(); - - let config = track_config::PracticeExercise::new(slug.clone(), name, difficulty); - - let mut track_config = TRACK_CONFIG.clone(); - track_config.exercises.practice.push(config); - let mut new_config = serde_json::to_string_pretty(&track_config) - .unwrap() - .to_string(); - new_config += "\n"; - std::fs::write("config.json", new_config).unwrap(); - - println!( - "\ -Added your exercise to config.json. -You can add practices, prerequisites and topics if you like." - ); - - slug -} - -fn make_configlet_generate_what_it_can(slug: &str) { - let status = std::process::Command::new("just") - .args([ - "configlet", - "sync", - "--update", - "--yes", - "--docs", - "--metadata", - "--tests", - "include", - "--exercise", - slug, - ]) - .status() - .unwrap(); - if !status.success() { - panic!("configlet sync failed"); - } -} - -fn generate_exercise_files(slug: &str, is_update: bool) { - let fn_names = if is_update { - read_fn_names_from_lib_rs(slug) - } else { - vec!["TODO".to_string()] - }; - - let exercise = exercise_generation::new(slug, fn_names); - - let exercise_path = PathBuf::from("exercises/practice").join(slug); - - if !is_update { - std::fs::write(exercise_path.join(".gitignore"), exercise.gitignore).unwrap(); - std::fs::write(exercise_path.join("Cargo.toml"), exercise.manifest).unwrap(); - std::fs::create_dir(exercise_path.join("src")).ok(); - std::fs::write(exercise_path.join("src/lib.rs"), exercise.lib_rs).unwrap(); - std::fs::write(exercise_path.join(".meta/example.rs"), exercise.example).unwrap(); - } - - let template_path = exercise_path.join(".meta/test_template.tera"); - if std::fs::metadata(&template_path).is_err() { - std::fs::write(template_path, exercise.test_template).unwrap(); - } - - std::fs::create_dir(exercise_path.join("tests")).ok(); - std::fs::write( - exercise_path.join(format!("tests/{slug}.rs")), - exercise.tests, - ) - .unwrap(); -} - -fn read_fn_names_from_lib_rs(slug: &str) -> Vec { - let lib_rs = - std::fs::read_to_string(format!("exercises/practice/{}/src/lib.rs", slug)).unwrap(); - - lib_rs - .split("fn ") - .skip(1) - .map(|f| { - let tmp = f.split_once('(').unwrap().0; - // strip generics - if let Some((res, _)) = tmp.split_once('<') { - res.to_string() - } else { - tmp.to_string() - } - }) - .collect() -} diff --git a/rust-tooling/generator/src/lib.rs b/rust-tooling/generator/src/lib.rs deleted file mode 100644 index 0657ebb64..000000000 --- a/rust-tooling/generator/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod exercise_generation; diff --git a/rust-tooling/models/Cargo.toml b/rust-tooling/models/Cargo.toml index 505f22dea..253be62b3 100644 --- a/rust-tooling/models/Cargo.toml +++ b/rust-tooling/models/Cargo.toml @@ -11,5 +11,6 @@ ignore = "0.4.20" once_cell = "1.18.0" serde = { version = "1.0.188", features = ["derive"] } serde_json = { version = "1.0.105", features = ["preserve_order"] } +serde_repr = "0.1.17" utils = { version = "0.1.0", path = "../utils" } uuid = { version = "1.6.1", features = ["v4"] } diff --git a/rust-tooling/models/src/problem_spec.rs b/rust-tooling/models/src/problem_spec.rs index 1c8c23161..2dfc39cc0 100644 --- a/rust-tooling/models/src/problem_spec.rs +++ b/rust-tooling/models/src/problem_spec.rs @@ -38,11 +38,11 @@ pub struct SingleTestCase { pub expected: serde_json::Value, } -pub fn get_canonical_data(slug: &str) -> CanonicalData { +pub fn get_canonical_data(slug: &str) -> Option { let path = std::path::PathBuf::from("problem-specifications/exercises") .join(slug) .join("canonical-data.json"); - let contents = std::fs::read_to_string(&path).unwrap(); + let contents = std::fs::read_to_string(&path).ok()?; serde_json::from_str(contents.as_str()).unwrap_or_else(|e| { panic!( "should deserialize canonical data for {}: {e}", diff --git a/rust-tooling/models/src/track_config.rs b/rust-tooling/models/src/track_config.rs index fc0bda868..0704b4564 100644 --- a/rust-tooling/models/src/track_config.rs +++ b/rust-tooling/models/src/track_config.rs @@ -10,6 +10,7 @@ use std::collections::HashMap; use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; +use serde_repr::{Deserialize_repr, Serialize_repr}; pub static TRACK_CONFIG: Lazy = Lazy::new(|| { let config = include_str!("../../../config.json"); @@ -75,13 +76,23 @@ pub enum ConceptExerciseStatus { Wip, } +#[derive(Debug, Clone, Copy, Serialize_repr, Deserialize_repr)] +#[repr(u8)] +pub enum Difficulty { + Easy = 1, + Medium = 4, + // I'm not sure why there are two medium difficulties + Medium2 = 7, + Hard = 10, +} + #[derive(Clone, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct ConceptExercise { pub slug: String, pub uuid: String, pub name: String, - pub difficulty: u8, + pub difficulty: Difficulty, pub concepts: Vec, pub prerequisites: Vec, pub status: ConceptExerciseStatus, @@ -101,14 +112,14 @@ pub struct PracticeExercise { pub uuid: String, pub practices: Vec, pub prerequisites: Vec, - pub difficulty: u8, + pub difficulty: Difficulty, pub topics: Vec, #[serde(skip_serializing_if = "Option::is_none")] pub status: Option, } impl PracticeExercise { - pub fn new(slug: String, name: String, difficulty: u8) -> Self { + pub fn new(slug: String, name: String, difficulty: Difficulty) -> Self { Self { slug, name, From 7772c68237137a837638ae015c2959be3090507a Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 28 Nov 2023 09:04:59 +0100 Subject: [PATCH 210/436] Fix test_exercise.sh script (#1815) The fuzzy matching of the path to the exercise slug would match "pascals-triangle" before "triangle". --- bin/test_exercise.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bin/test_exercise.sh b/bin/test_exercise.sh index 616c03a8c..cb4324cfc 100755 --- a/bin/test_exercise.sh +++ b/bin/test_exercise.sh @@ -13,8 +13,10 @@ cargo_args="$2" # determine the exercise path from the slug for p in exercises/{practice,concept}/* ; do - if [[ "$p" =~ $slug ]]; then - exercise_path=$p + current_slug="$(basename "$p")" + p="$(dirname "$p")" + if [[ "$current_slug" = "$slug" ]]; then + exercise_path="$p/$slug" break fi done From c8caf0b22c283e5bdbcc00b4cafbdaa47416da8d Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 28 Nov 2023 09:05:16 +0100 Subject: [PATCH 211/436] Enforce documentation of additional tests (#1817) --- .../.meta/additional-tests.json | 4 ++++ .../.meta/additional-tests.json | 8 +++++++ .../.meta/additional-tests.json | 16 ++++++++++++++ .../practice/say/.meta/additional-tests.json | 10 +++++++++ .../.meta/additional-tests.json | 8 +++++++ rust-tooling/Cargo.lock | 1 + rust-tooling/ci-tests/Cargo.toml | 1 + .../tests/additional_tests_are_documented.rs | 22 +++++++++++++++++++ 8 files changed, 70 insertions(+) create mode 100644 rust-tooling/ci-tests/tests/additional_tests_are_documented.rs diff --git a/exercises/practice/rail-fence-cipher/.meta/additional-tests.json b/exercises/practice/rail-fence-cipher/.meta/additional-tests.json index 78fa632e5..e25105de8 100644 --- a/exercises/practice/rail-fence-cipher/.meta/additional-tests.json +++ b/exercises/practice/rail-fence-cipher/.meta/additional-tests.json @@ -2,6 +2,10 @@ { "uuid": "46dc5c50-5538-401d-93a5-41102680d068", "description": "encode wide characters", + "comments": [ + "Unicode tests are not suitable to be upstreamed.", + "Handling unicode is tedious in many languages." + ], "property": "encode", "input": { "msg": "古池蛙飛び込む水の音", diff --git a/exercises/practice/reverse-string/.meta/additional-tests.json b/exercises/practice/reverse-string/.meta/additional-tests.json index 69279b378..c82e02b04 100644 --- a/exercises/practice/reverse-string/.meta/additional-tests.json +++ b/exercises/practice/reverse-string/.meta/additional-tests.json @@ -2,6 +2,10 @@ { "uuid": "01ebf55b-bebb-414e-9dec-06f7bb0bee3c", "description": "wide characters", + "comments": [ + "Unicode tests are not suitable to be upstreamed.", + "Handling unicode is tedious in many languages." + ], "property": "reverse", "input": { "value": "子猫" @@ -11,6 +15,10 @@ { "uuid": "01ebf55b-bebb-414e-9dec-06f7bb0bee3c", "description": "grapheme clusters", + "comments": [ + "Unicode tests are not suitable to be upstreamed.", + "Handling unicode is tedious in many languages." + ], "property": "grapheme", "input": { "value": "uüu" diff --git a/exercises/practice/rna-transcription/.meta/additional-tests.json b/exercises/practice/rna-transcription/.meta/additional-tests.json index 56e92a7d3..f1e449cb2 100644 --- a/exercises/practice/rna-transcription/.meta/additional-tests.json +++ b/exercises/practice/rna-transcription/.meta/additional-tests.json @@ -2,6 +2,10 @@ { "uuid": "6eedbb5c-12cb-4c8b-9f51-f8320b4dc2e7", "description": "Invalid DNA input", + "comments": [ + "Upstream does not focus on error handling,", + "but this is a big focus for Rust." + ], "property": "invalidDna", "input": { "dna": "U" @@ -11,6 +15,10 @@ { "uuid": "6eedbb5c-12cb-4c8b-9f51-f8320b4dc2e7", "description": "Invalid DNA input at offset", + "comments": [ + "Upstream does not focus on error handling,", + "but this is a big focus for Rust." + ], "property": "invalidDna", "input": { "dna": "ACGTUXXCTTAA" @@ -20,6 +28,10 @@ { "uuid": "6eedbb5c-12cb-4c8b-9f51-f8320b4dc2e7", "description": "Invalid RNA input", + "comments": [ + "Upstream does not focus on error handling,", + "but this is a big focus for Rust." + ], "property": "invalidRna", "input": { "dna": "T" @@ -29,6 +41,10 @@ { "uuid": "6eedbb5c-12cb-4c8b-9f51-f8320b4dc2e7", "description": "Invalid RNA input at offset", + "comments": [ + "Upstream does not focus on error handling,", + "but this is a big focus for Rust." + ], "property": "invalidRna", "input": { "dna": "ACGTUXXCTTAA" diff --git a/exercises/practice/say/.meta/additional-tests.json b/exercises/practice/say/.meta/additional-tests.json index 6df96e614..12d4a7906 100644 --- a/exercises/practice/say/.meta/additional-tests.json +++ b/exercises/practice/say/.meta/additional-tests.json @@ -2,6 +2,11 @@ { "uuid": "ecafab60-89f2-4680-9286-56b423e8a7b9", "description": "max i64", + "comments": [ + "Idiomatic Rust emphasizes the principle", + "'Make invalid states impossible to represent'.", + "So it is appropriate to test the entire range of possible inputs." + ], "property": "say", "input": { "number": 9223372036854775807 @@ -11,6 +16,11 @@ { "uuid": "88808dac-dffb-46a6-95d4-68d5271a9c38", "description": "max u64", + "comments": [ + "Idiomatic Rust emphasizes the principle", + "'Make invalid states impossible to represent'.", + "So it is appropriate to test the entire range of possible inputs." + ], "property": "say", "input": { "number": 18446744073709551615 diff --git a/exercises/practice/scrabble-score/.meta/additional-tests.json b/exercises/practice/scrabble-score/.meta/additional-tests.json index 6e5b33263..ad0cf25e6 100644 --- a/exercises/practice/scrabble-score/.meta/additional-tests.json +++ b/exercises/practice/scrabble-score/.meta/additional-tests.json @@ -2,6 +2,10 @@ { "uuid": "9e0faee7-dc23-460b-afec-17c7145ae564", "description": "non english scrabble letters do not score", + "comments": [ + "Unicode tests are not suitable to be upstreamed.", + "Handling unicode is tedious in many languages." + ], "property": "score", "input": { "word": "piñata" @@ -11,6 +15,10 @@ { "uuid": "3099e8fd-0077-4520-be33-54bb9a1c0dc7", "description": "german letters do not score", + "comments": [ + "Unicode tests are not suitable to be upstreamed.", + "Handling unicode is tedious in many languages." + ], "property": "score", "input": { "word": "STRAßE" diff --git a/rust-tooling/Cargo.lock b/rust-tooling/Cargo.lock index 747467f1f..b36160cf9 100644 --- a/rust-tooling/Cargo.lock +++ b/rust-tooling/Cargo.lock @@ -165,6 +165,7 @@ name = "ci-tests" version = "0.1.0" dependencies = [ "convert_case", + "glob", "ignore", "models", "serde_json", diff --git a/rust-tooling/ci-tests/Cargo.toml b/rust-tooling/ci-tests/Cargo.toml index ba6030c47..198ec2fe7 100644 --- a/rust-tooling/ci-tests/Cargo.toml +++ b/rust-tooling/ci-tests/Cargo.toml @@ -10,6 +10,7 @@ description = "Tests to be run in CI to make sure the repo is in good shape" # Be considerate when adding dependencies to keep compile times reasonable. [dependencies] convert_case = "0.6.0" +glob = "0.3.1" ignore = "0.4.20" models = { version = "0.1.0", path = "../models" } serde_json = "1.0.108" diff --git a/rust-tooling/ci-tests/tests/additional_tests_are_documented.rs b/rust-tooling/ci-tests/tests/additional_tests_are_documented.rs new file mode 100644 index 000000000..530817ef7 --- /dev/null +++ b/rust-tooling/ci-tests/tests/additional_tests_are_documented.rs @@ -0,0 +1,22 @@ +use glob::glob; +use models::problem_spec::SingleTestCase; +use utils::fs::cd_into_repo_root; + +#[test] +fn additional_tests_are_documented() { + cd_into_repo_root(); + for entry in glob("exercises/*/*/.meta/additional-tests.json").unwrap() { + let path = entry.unwrap(); + let f = std::fs::File::open(&path).unwrap(); + let reader = std::io::BufReader::new(f); + let test_cases: Vec = serde_json::from_reader(reader).unwrap(); + + for case in test_cases { + assert!( + !case.comments.unwrap_or_default().is_empty(), + "missing documentation for additional tests in {}", + path.display() + ); + } + } +} From c7351ddba275ade42f4a2333f6dddbffb3fe2063 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 12 Dec 2023 09:47:37 +0100 Subject: [PATCH 212/436] Use glob import in default test template (#1819) Glob imports are a better default for the test template. Some exercises may require the student to implement a method. If the test file doesn't have a glob import, the students will be forced to implement an inherent method on a struct. On the other hand, if a glob import is used, the students may choose to implement it as a method on a trait. Traits are a powerful feature of Rust, so we want to enable students to experiment with them whenever possible. --- rust-tooling/generate/templates/default_test_template.tera | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rust-tooling/generate/templates/default_test_template.tera b/rust-tooling/generate/templates/default_test_template.tera index 39eb4727f..94d4c88c8 100644 --- a/rust-tooling/generate/templates/default_test_template.tera +++ b/rust-tooling/generate/templates/default_test_template.tera @@ -1,3 +1,5 @@ +use {{ crate_name }}::*; + {% for test in cases %} #[test] {% if loop.index != 1 -%} @@ -5,7 +7,7 @@ {% endif -%} fn {{ test.description | slugify | replace(from="-", to="_") }}() { let input = {{ test.input | json_encode() }}; - let output = {{ crate_name }}::{{ fn_names[0] }}(input); + let output = {{ fn_names[0] }}(input); let expected = {{ test.expected | json_encode() }}; assert_eq!(output, expected); } From 350897a29e7a664e7fe37114c8ebf28d2d066c59 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 12 Dec 2023 09:49:19 +0100 Subject: [PATCH 213/436] Sync queen with problem-specifications (#1818) --- .../queen-attack/.meta/test_template.tera | 26 ++++++++ .../practice/queen-attack/.meta/tests.toml | 52 +++++++++++++++- .../queen-attack/tests/queen-attack.rs | 62 ++++++++++--------- 3 files changed, 108 insertions(+), 32 deletions(-) create mode 100644 exercises/practice/queen-attack/.meta/test_template.tera diff --git a/exercises/practice/queen-attack/.meta/test_template.tera b/exercises/practice/queen-attack/.meta/test_template.tera new file mode 100644 index 000000000..ff40eed91 --- /dev/null +++ b/exercises/practice/queen-attack/.meta/test_template.tera @@ -0,0 +1,26 @@ +use queen_attack::{ChessPiece, ChessPosition, Queen}; + +{% for test in cases %} +#[test] +{% if loop.index != 1 -%} +#[ignore] +{% endif -%} +fn {{ test.description | slugify | replace(from="-", to="_") }}() { +{% if test.property == "create" %} + let chess_position = ChessPosition::new({{ test.input.queen.position.row }}, {{ test.input.queen.position.column }}); +{%- if test.expected is object %} + assert!(chess_position.is_none()); +{% else %} + assert!(chess_position.is_some()); +{% endif -%} +{% else %} + let white_queen = Queen::new(ChessPosition::new({{ test.input.white_queen.position.row }}, {{ test.input.white_queen.position.column }}).unwrap()); + let black_queen = Queen::new(ChessPosition::new({{ test.input.black_queen.position.row }}, {{ test.input.black_queen.position.column }}).unwrap()); +{%- if test.expected %} + assert!(white_queen.can_attack(&black_queen)); +{% else %} + assert!(!white_queen.can_attack(&black_queen)); +{% endif -%} +{% endif %} +} +{% endfor -%} diff --git a/exercises/practice/queen-attack/.meta/tests.toml b/exercises/practice/queen-attack/.meta/tests.toml index be690e975..e0624123d 100644 --- a/exercises/practice/queen-attack/.meta/tests.toml +++ b/exercises/practice/queen-attack/.meta/tests.toml @@ -1,3 +1,49 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[3ac4f735-d36c-44c4-a3e2-316f79704203] +description = "Test creation of Queens with valid and invalid positions -> queen with a valid position" + +[4e812d5d-b974-4e38-9a6b-8e0492bfa7be] +description = "Test creation of Queens with valid and invalid positions -> queen must have positive row" + +[f07b7536-b66b-4f08-beb9-4d70d891d5c8] +description = "Test creation of Queens with valid and invalid positions -> queen must have row on board" + +[15a10794-36d9-4907-ae6b-e5a0d4c54ebe] +description = "Test creation of Queens with valid and invalid positions -> queen must have positive column" + +[6907762d-0e8a-4c38-87fb-12f2f65f0ce4] +description = "Test creation of Queens with valid and invalid positions -> queen must have column on board" + +[33ae4113-d237-42ee-bac1-e1e699c0c007] +description = "Test the ability of one queen to attack another -> cannot attack" + +[eaa65540-ea7c-4152-8c21-003c7a68c914] +description = "Test the ability of one queen to attack another -> can attack on same row" + +[bae6f609-2c0e-4154-af71-af82b7c31cea] +description = "Test the ability of one queen to attack another -> can attack on same column" + +[0e1b4139-b90d-4562-bd58-dfa04f1746c7] +description = "Test the ability of one queen to attack another -> can attack on first diagonal" + +[ff9b7ed4-e4b6-401b-8d16-bc894d6d3dcd] +description = "Test the ability of one queen to attack another -> can attack on second diagonal" + +[0a71e605-6e28-4cc2-aa47-d20a2e71037a] +description = "Test the ability of one queen to attack another -> can attack on third diagonal" + +[0790b588-ae73-4f1f-a968-dd0b34f45f86] +description = "Test the ability of one queen to attack another -> can attack on fourth diagonal" + +[543f8fd4-2597-4aad-8d77-cbdab63619f8] +description = "Test the ability of one queen to attack another -> cannot attack if falling diagonals are only the same when reflected across the longest falling diagonal" diff --git a/exercises/practice/queen-attack/tests/queen-attack.rs b/exercises/practice/queen-attack/tests/queen-attack.rs index b1f17d20c..b8e023c96 100644 --- a/exercises/practice/queen-attack/tests/queen-attack.rs +++ b/exercises/practice/queen-attack/tests/queen-attack.rs @@ -1,96 +1,100 @@ use queen_attack::*; #[test] -fn chess_position_on_the_board_is_some() { - assert!(ChessPosition::new(2, 4).is_some()); +fn queen_with_a_valid_position() { + let chess_position = ChessPosition::new(2, 2); + assert!(chess_position.is_some()); } #[test] #[ignore] -fn chess_position_off_the_board_is_none() { - assert!(ChessPosition::new(-1, 2).is_none()); - - assert!(ChessPosition::new(8, 2).is_none()); +fn queen_must_have_positive_row() { + let chess_position = ChessPosition::new(-2, 2); + assert!(chess_position.is_none()); +} - assert!(ChessPosition::new(5, -1).is_none()); +#[test] +#[ignore] +fn queen_must_have_row_on_board() { + let chess_position = ChessPosition::new(8, 4); + assert!(chess_position.is_none()); +} - assert!(ChessPosition::new(5, 8).is_none()); +#[test] +#[ignore] +fn queen_must_have_positive_column() { + let chess_position = ChessPosition::new(2, -2); + assert!(chess_position.is_none()); } #[test] #[ignore] -fn queen_is_created_with_a_valid_position() { - Queen::new(ChessPosition::new(2, 4).unwrap()); +fn queen_must_have_column_on_board() { + let chess_position = ChessPosition::new(4, 8); + assert!(chess_position.is_none()); } #[test] #[ignore] -fn queens_that_can_not_attack() { +fn cannot_attack() { let white_queen = Queen::new(ChessPosition::new(2, 4).unwrap()); let black_queen = Queen::new(ChessPosition::new(6, 6).unwrap()); - assert!(!white_queen.can_attack(&black_queen)); } #[test] #[ignore] -fn queens_on_the_same_rank_can_attack() { +fn can_attack_on_same_row() { let white_queen = Queen::new(ChessPosition::new(2, 4).unwrap()); let black_queen = Queen::new(ChessPosition::new(2, 6).unwrap()); - assert!(white_queen.can_attack(&black_queen)); } #[test] #[ignore] -fn queens_on_the_same_file_can_attack() { +fn can_attack_on_same_column() { let white_queen = Queen::new(ChessPosition::new(4, 5).unwrap()); - let black_queen = Queen::new(ChessPosition::new(3, 5).unwrap()); - + let black_queen = Queen::new(ChessPosition::new(2, 5).unwrap()); assert!(white_queen.can_attack(&black_queen)); } #[test] #[ignore] -fn queens_on_the_same_diagonal_can_attack_one() { +fn can_attack_on_first_diagonal() { let white_queen = Queen::new(ChessPosition::new(2, 2).unwrap()); let black_queen = Queen::new(ChessPosition::new(0, 4).unwrap()); - assert!(white_queen.can_attack(&black_queen)); } #[test] #[ignore] -fn queens_on_the_same_diagonal_can_attack_two() { +fn can_attack_on_second_diagonal() { let white_queen = Queen::new(ChessPosition::new(2, 2).unwrap()); let black_queen = Queen::new(ChessPosition::new(3, 1).unwrap()); - assert!(white_queen.can_attack(&black_queen)); } #[test] #[ignore] -fn queens_on_the_same_diagonal_can_attack_three() { +fn can_attack_on_third_diagonal() { let white_queen = Queen::new(ChessPosition::new(2, 2).unwrap()); let black_queen = Queen::new(ChessPosition::new(1, 1).unwrap()); - assert!(white_queen.can_attack(&black_queen)); } #[test] #[ignore] -fn queens_on_the_same_diagonal_can_attack_four() { - let white_queen = Queen::new(ChessPosition::new(2, 2).unwrap()); - let black_queen = Queen::new(ChessPosition::new(5, 5).unwrap()); - +fn can_attack_on_fourth_diagonal() { + let white_queen = Queen::new(ChessPosition::new(1, 7).unwrap()); + let black_queen = Queen::new(ChessPosition::new(0, 6).unwrap()); assert!(white_queen.can_attack(&black_queen)); } #[test] #[ignore] -fn queens_that_cannot_attack_with_equal_difference() { +fn cannot_attack_if_falling_diagonals_are_only_the_same_when_reflected_across_the_longest_falling_diagonal( +) { let white_queen = Queen::new(ChessPosition::new(4, 1).unwrap()); let black_queen = Queen::new(ChessPosition::new(2, 5).unwrap()); - assert!(!white_queen.can_attack(&black_queen)); } From 43074d5987ea1caaca130592f6df6dce5b8341ef Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 12 Dec 2023 09:49:46 +0100 Subject: [PATCH 214/436] Sync allergies with problem-specifications (#1820) --- .../allergies/.meta/test_template.tera | 43 ++ exercises/practice/allergies/.meta/tests.toml | 155 +++++++- .../practice/allergies/tests/allergies.rs | 371 ++++++++++++++++-- 3 files changed, 530 insertions(+), 39 deletions(-) create mode 100644 exercises/practice/allergies/.meta/test_template.tera diff --git a/exercises/practice/allergies/.meta/test_template.tera b/exercises/practice/allergies/.meta/test_template.tera new file mode 100644 index 000000000..986be5782 --- /dev/null +++ b/exercises/practice/allergies/.meta/test_template.tera @@ -0,0 +1,43 @@ +use {{ crate_name }}::*; + +fn compare_allergy_vectors(expected: &[Allergen], actual: &[Allergen]) { + for element in expected { + if !actual.contains(element) { + panic!("Allergen missing\n {element:?} should be in {actual:?}"); + } + } + + if actual.len() != expected.len() { + panic!( + "Allergy vectors are of different lengths\n expected {expected:?}\n got {actual:?}" + ); + } +} + +{% for test in cases %} +#[test] +{% if loop.index != 1 -%} +#[ignore] +{% endif -%} +{%- if test.property == "allergicTo" %} +{# canonical data contains multiple cases named "allergic to everything" for different items #} +fn {{ test.description | slugify | replace(from="-", to="_") }}_{{ test.input.item }}() { + let allergies = Allergies::new({{ test.input.score }}); +{%- if test.expected %} + assert!(allergies.is_allergic_to(&Allergen::{{ test.input.item | title }})) +{% else %} + assert!(!allergies.is_allergic_to(&Allergen::{{ test.input.item | title }})) +{% endif -%} +{% else %} +fn {{ test.description | slugify | replace(from="-", to="_") }}() { + let allergies = Allergies::new({{ test.input.score }}).allergies(); + let expected = &[ + {% for allergen in test.expected %} + Allergen::{{ allergen | title }}, + {% endfor %} + ]; + + compare_allergy_vectors(expected, &allergies); +{% endif -%} +} +{% endfor -%} diff --git a/exercises/practice/allergies/.meta/tests.toml b/exercises/practice/allergies/.meta/tests.toml index 63397b988..799ab8563 100644 --- a/exercises/practice/allergies/.meta/tests.toml +++ b/exercises/practice/allergies/.meta/tests.toml @@ -1,27 +1,160 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[17fc7296-2440-4ac4-ad7b-d07c321bc5a0] +description = "testing for eggs allergy -> not allergic to anything" + +[07ced27b-1da5-4c2e-8ae2-cb2791437546] +description = "testing for eggs allergy -> allergic only to eggs" + +[5035b954-b6fa-4b9b-a487-dae69d8c5f96] +description = "testing for eggs allergy -> allergic to eggs and something else" + +[64a6a83a-5723-4b5b-a896-663307403310] +description = "testing for eggs allergy -> allergic to something, but not eggs" [90c8f484-456b-41c4-82ba-2d08d93231c6] -description = "allergic to everything" +description = "testing for eggs allergy -> allergic to everything" + +[d266a59a-fccc-413b-ac53-d57cb1f0db9d] +description = "testing for peanuts allergy -> not allergic to anything" + +[ea210a98-860d-46b2-a5bf-50d8995b3f2a] +description = "testing for peanuts allergy -> allergic only to peanuts" + +[eac69ae9-8d14-4291-ac4b-7fd2c73d3a5b] +description = "testing for peanuts allergy -> allergic to peanuts and something else" + +[9152058c-ce39-4b16-9b1d-283ec6d25085] +description = "testing for peanuts allergy -> allergic to something, but not peanuts" [d2d71fd8-63d5-40f9-a627-fbdaf88caeab] -description = "allergic to everything" +description = "testing for peanuts allergy -> allergic to everything" + +[b948b0a1-cbf7-4b28-a244-73ff56687c80] +description = "testing for shellfish allergy -> not allergic to anything" + +[9ce9a6f3-53e9-4923-85e0-73019047c567] +description = "testing for shellfish allergy -> allergic only to shellfish" + +[b272fca5-57ba-4b00-bd0c-43a737ab2131] +description = "testing for shellfish allergy -> allergic to shellfish and something else" + +[21ef8e17-c227-494e-8e78-470a1c59c3d8] +description = "testing for shellfish allergy -> allergic to something, but not shellfish" [cc789c19-2b5e-4c67-b146-625dc8cfa34e] -description = "allergic to everything" +description = "testing for shellfish allergy -> allergic to everything" + +[651bde0a-2a74-46c4-ab55-02a0906ca2f5] +description = "testing for strawberries allergy -> not allergic to anything" + +[b649a750-9703-4f5f-b7f7-91da2c160ece] +description = "testing for strawberries allergy -> allergic only to strawberries" + +[50f5f8f3-3bac-47e6-8dba-2d94470a4bc6] +description = "testing for strawberries allergy -> allergic to strawberries and something else" + +[23dd6952-88c9-48d7-a7d5-5d0343deb18d] +description = "testing for strawberries allergy -> allergic to something, but not strawberries" [74afaae2-13b6-43a2-837a-286cd42e7d7e] -description = "allergic to everything" +description = "testing for strawberries allergy -> allergic to everything" + +[c49a91ef-6252-415e-907e-a9d26ef61723] +description = "testing for tomatoes allergy -> not allergic to anything" + +[b69c5131-b7d0-41ad-a32c-e1b2cc632df8] +description = "testing for tomatoes allergy -> allergic only to tomatoes" + +[1ca50eb1-f042-4ccf-9050-341521b929ec] +description = "testing for tomatoes allergy -> allergic to tomatoes and something else" + +[e9846baa-456b-4eff-8025-034b9f77bd8e] +description = "testing for tomatoes allergy -> allergic to something, but not tomatoes" [b2414f01-f3ad-4965-8391-e65f54dad35f] -description = "allergic to everything" +description = "testing for tomatoes allergy -> allergic to everything" + +[978467ab-bda4-49f7-b004-1d011ead947c] +description = "testing for chocolate allergy -> not allergic to anything" + +[59cf4e49-06ea-4139-a2c1-d7aad28f8cbc] +description = "testing for chocolate allergy -> allergic only to chocolate" + +[b0a7c07b-2db7-4f73-a180-565e07040ef1] +description = "testing for chocolate allergy -> allergic to chocolate and something else" + +[f5506893-f1ae-482a-b516-7532ba5ca9d2] +description = "testing for chocolate allergy -> allergic to something, but not chocolate" [02debb3d-d7e2-4376-a26b-3c974b6595c6] -description = "allergic to everything" +description = "testing for chocolate allergy -> allergic to everything" + +[17f4a42b-c91e-41b8-8a76-4797886c2d96] +description = "testing for pollen allergy -> not allergic to anything" + +[7696eba7-1837-4488-882a-14b7b4e3e399] +description = "testing for pollen allergy -> allergic only to pollen" + +[9a49aec5-fa1f-405d-889e-4dfc420db2b6] +description = "testing for pollen allergy -> allergic to pollen and something else" + +[3cb8e79f-d108-4712-b620-aa146b1954a9] +description = "testing for pollen allergy -> allergic to something, but not pollen" [1dc3fe57-7c68-4043-9d51-5457128744b2] -description = "allergic to everything" +description = "testing for pollen allergy -> allergic to everything" + +[d3f523d6-3d50-419b-a222-d4dfd62ce314] +description = "testing for cats allergy -> not allergic to anything" + +[eba541c3-c886-42d3-baef-c048cb7fcd8f] +description = "testing for cats allergy -> allergic only to cats" + +[ba718376-26e0-40b7-bbbe-060287637ea5] +description = "testing for cats allergy -> allergic to cats and something else" + +[3c6dbf4a-5277-436f-8b88-15a206f2d6c4] +description = "testing for cats allergy -> allergic to something, but not cats" [1faabb05-2b98-4995-9046-d83e4a48a7c1] -description = "allergic to everything" +description = "testing for cats allergy -> allergic to everything" + +[f9c1b8e7-7dc5-4887-aa93-cebdcc29dd8f] +description = "list when: -> no allergies" + +[9e1a4364-09a6-4d94-990f-541a94a4c1e8] +description = "list when: -> just eggs" + +[8851c973-805e-4283-9e01-d0c0da0e4695] +description = "list when: -> just peanuts" + +[2c8943cb-005e-435f-ae11-3e8fb558ea98] +description = "list when: -> just strawberries" + +[6fa95d26-044c-48a9-8a7b-9ee46ec32c5c] +description = "list when: -> eggs and peanuts" + +[19890e22-f63f-4c5c-a9fb-fb6eacddfe8e] +description = "list when: -> more than eggs but not peanuts" + +[4b68f470-067c-44e4-889f-c9fe28917d2f] +description = "list when: -> lots of stuff" + +[0881b7c5-9efa-4530-91bd-68370d054bc7] +description = "list when: -> everything" + +[12ce86de-b347-42a0-ab7c-2e0570f0c65b] +description = "list when: -> no allergen score parts" + +[93c2df3e-4f55-4fed-8116-7513092819cd] +description = "list when: -> no allergen score parts without highest valid score" diff --git a/exercises/practice/allergies/tests/allergies.rs b/exercises/practice/allergies/tests/allergies.rs index da57d7c60..ffcae1739 100644 --- a/exercises/practice/allergies/tests/allergies.rs +++ b/exercises/practice/allergies/tests/allergies.rs @@ -15,85 +15,389 @@ fn compare_allergy_vectors(expected: &[Allergen], actual: &[Allergen]) { } #[test] -fn is_not_allergic_to_anything() { + +fn not_allergic_to_anything_eggs() { let allergies = Allergies::new(0); - assert!(!allergies.is_allergic_to(&Allergen::Peanuts)); - assert!(!allergies.is_allergic_to(&Allergen::Cats)); - assert!(!allergies.is_allergic_to(&Allergen::Strawberries)); + assert!(!allergies.is_allergic_to(&Allergen::Eggs)) +} + +#[test] +#[ignore] + +fn allergic_only_to_eggs_eggs() { + let allergies = Allergies::new(1); + assert!(allergies.is_allergic_to(&Allergen::Eggs)) +} + +#[test] +#[ignore] + +fn allergic_to_eggs_and_something_else_eggs() { + let allergies = Allergies::new(3); + assert!(allergies.is_allergic_to(&Allergen::Eggs)) +} + +#[test] +#[ignore] + +fn allergic_to_something_but_not_eggs_eggs() { + let allergies = Allergies::new(2); + assert!(!allergies.is_allergic_to(&Allergen::Eggs)) +} + +#[test] +#[ignore] + +fn allergic_to_everything_eggs() { + let allergies = Allergies::new(255); + assert!(allergies.is_allergic_to(&Allergen::Eggs)) +} + +#[test] +#[ignore] + +fn not_allergic_to_anything_peanuts() { + let allergies = Allergies::new(0); + assert!(!allergies.is_allergic_to(&Allergen::Peanuts)) +} + +#[test] +#[ignore] + +fn allergic_only_to_peanuts_peanuts() { + let allergies = Allergies::new(2); + assert!(allergies.is_allergic_to(&Allergen::Peanuts)) } #[test] #[ignore] -fn is_allergic_to_eggs() { - assert!(Allergies::new(1).is_allergic_to(&Allergen::Eggs)); + +fn allergic_to_peanuts_and_something_else_peanuts() { + let allergies = Allergies::new(7); + assert!(allergies.is_allergic_to(&Allergen::Peanuts)) } #[test] #[ignore] -fn is_allergic_to_eggs_and_shellfish_but_not_strawberries() { + +fn allergic_to_something_but_not_peanuts_peanuts() { let allergies = Allergies::new(5); - assert!(allergies.is_allergic_to(&Allergen::Eggs)); - assert!(allergies.is_allergic_to(&Allergen::Shellfish)); - assert!(!allergies.is_allergic_to(&Allergen::Strawberries)); + assert!(!allergies.is_allergic_to(&Allergen::Peanuts)) } #[test] #[ignore] -fn no_allergies_at_all() { - let expected = &[]; + +fn allergic_to_everything_peanuts() { + let allergies = Allergies::new(255); + assert!(allergies.is_allergic_to(&Allergen::Peanuts)) +} + +#[test] +#[ignore] + +fn not_allergic_to_anything_shellfish() { + let allergies = Allergies::new(0); + assert!(!allergies.is_allergic_to(&Allergen::Shellfish)) +} + +#[test] +#[ignore] + +fn allergic_only_to_shellfish_shellfish() { + let allergies = Allergies::new(4); + assert!(allergies.is_allergic_to(&Allergen::Shellfish)) +} + +#[test] +#[ignore] + +fn allergic_to_shellfish_and_something_else_shellfish() { + let allergies = Allergies::new(14); + assert!(allergies.is_allergic_to(&Allergen::Shellfish)) +} + +#[test] +#[ignore] + +fn allergic_to_something_but_not_shellfish_shellfish() { + let allergies = Allergies::new(10); + assert!(!allergies.is_allergic_to(&Allergen::Shellfish)) +} + +#[test] +#[ignore] + +fn allergic_to_everything_shellfish() { + let allergies = Allergies::new(255); + assert!(allergies.is_allergic_to(&Allergen::Shellfish)) +} + +#[test] +#[ignore] + +fn not_allergic_to_anything_strawberries() { + let allergies = Allergies::new(0); + assert!(!allergies.is_allergic_to(&Allergen::Strawberries)) +} + +#[test] +#[ignore] + +fn allergic_only_to_strawberries_strawberries() { + let allergies = Allergies::new(8); + assert!(allergies.is_allergic_to(&Allergen::Strawberries)) +} + +#[test] +#[ignore] + +fn allergic_to_strawberries_and_something_else_strawberries() { + let allergies = Allergies::new(28); + assert!(allergies.is_allergic_to(&Allergen::Strawberries)) +} + +#[test] +#[ignore] + +fn allergic_to_something_but_not_strawberries_strawberries() { + let allergies = Allergies::new(20); + assert!(!allergies.is_allergic_to(&Allergen::Strawberries)) +} + +#[test] +#[ignore] + +fn allergic_to_everything_strawberries() { + let allergies = Allergies::new(255); + assert!(allergies.is_allergic_to(&Allergen::Strawberries)) +} + +#[test] +#[ignore] + +fn not_allergic_to_anything_tomatoes() { + let allergies = Allergies::new(0); + assert!(!allergies.is_allergic_to(&Allergen::Tomatoes)) +} + +#[test] +#[ignore] + +fn allergic_only_to_tomatoes_tomatoes() { + let allergies = Allergies::new(16); + assert!(allergies.is_allergic_to(&Allergen::Tomatoes)) +} + +#[test] +#[ignore] + +fn allergic_to_tomatoes_and_something_else_tomatoes() { + let allergies = Allergies::new(56); + assert!(allergies.is_allergic_to(&Allergen::Tomatoes)) +} + +#[test] +#[ignore] + +fn allergic_to_something_but_not_tomatoes_tomatoes() { + let allergies = Allergies::new(40); + assert!(!allergies.is_allergic_to(&Allergen::Tomatoes)) +} + +#[test] +#[ignore] + +fn allergic_to_everything_tomatoes() { + let allergies = Allergies::new(255); + assert!(allergies.is_allergic_to(&Allergen::Tomatoes)) +} + +#[test] +#[ignore] + +fn not_allergic_to_anything_chocolate() { + let allergies = Allergies::new(0); + assert!(!allergies.is_allergic_to(&Allergen::Chocolate)) +} + +#[test] +#[ignore] + +fn allergic_only_to_chocolate_chocolate() { + let allergies = Allergies::new(32); + assert!(allergies.is_allergic_to(&Allergen::Chocolate)) +} + +#[test] +#[ignore] + +fn allergic_to_chocolate_and_something_else_chocolate() { + let allergies = Allergies::new(112); + assert!(allergies.is_allergic_to(&Allergen::Chocolate)) +} + +#[test] +#[ignore] + +fn allergic_to_something_but_not_chocolate_chocolate() { + let allergies = Allergies::new(80); + assert!(!allergies.is_allergic_to(&Allergen::Chocolate)) +} + +#[test] +#[ignore] + +fn allergic_to_everything_chocolate() { + let allergies = Allergies::new(255); + assert!(allergies.is_allergic_to(&Allergen::Chocolate)) +} + +#[test] +#[ignore] + +fn not_allergic_to_anything_pollen() { + let allergies = Allergies::new(0); + assert!(!allergies.is_allergic_to(&Allergen::Pollen)) +} + +#[test] +#[ignore] + +fn allergic_only_to_pollen_pollen() { + let allergies = Allergies::new(64); + assert!(allergies.is_allergic_to(&Allergen::Pollen)) +} + +#[test] +#[ignore] + +fn allergic_to_pollen_and_something_else_pollen() { + let allergies = Allergies::new(224); + assert!(allergies.is_allergic_to(&Allergen::Pollen)) +} + +#[test] +#[ignore] + +fn allergic_to_something_but_not_pollen_pollen() { + let allergies = Allergies::new(160); + assert!(!allergies.is_allergic_to(&Allergen::Pollen)) +} + +#[test] +#[ignore] + +fn allergic_to_everything_pollen() { + let allergies = Allergies::new(255); + assert!(allergies.is_allergic_to(&Allergen::Pollen)) +} + +#[test] +#[ignore] + +fn not_allergic_to_anything_cats() { + let allergies = Allergies::new(0); + assert!(!allergies.is_allergic_to(&Allergen::Cats)) +} + +#[test] +#[ignore] + +fn allergic_only_to_cats_cats() { + let allergies = Allergies::new(128); + assert!(allergies.is_allergic_to(&Allergen::Cats)) +} + +#[test] +#[ignore] + +fn allergic_to_cats_and_something_else_cats() { + let allergies = Allergies::new(192); + assert!(allergies.is_allergic_to(&Allergen::Cats)) +} + +#[test] +#[ignore] + +fn allergic_to_something_but_not_cats_cats() { + let allergies = Allergies::new(64); + assert!(!allergies.is_allergic_to(&Allergen::Cats)) +} + +#[test] +#[ignore] + +fn allergic_to_everything_cats() { + let allergies = Allergies::new(255); + assert!(allergies.is_allergic_to(&Allergen::Cats)) +} + +#[test] +#[ignore] + +fn no_allergies() { let allergies = Allergies::new(0).allergies(); + let expected = &[]; compare_allergy_vectors(expected, &allergies); } #[test] #[ignore] -fn allergic_to_just_eggs() { - let expected = &[Allergen::Eggs]; + +fn just_eggs() { let allergies = Allergies::new(1).allergies(); + let expected = &[Allergen::Eggs]; compare_allergy_vectors(expected, &allergies); } #[test] #[ignore] -fn allergic_to_just_peanuts() { - let expected = &[Allergen::Peanuts]; + +fn just_peanuts() { let allergies = Allergies::new(2).allergies(); + let expected = &[Allergen::Peanuts]; compare_allergy_vectors(expected, &allergies); } #[test] #[ignore] -fn allergic_to_just_strawberries() { - let expected = &[Allergen::Strawberries]; + +fn just_strawberries() { let allergies = Allergies::new(8).allergies(); + let expected = &[Allergen::Strawberries]; compare_allergy_vectors(expected, &allergies); } #[test] #[ignore] -fn allergic_to_eggs_and_peanuts() { - let expected = &[Allergen::Eggs, Allergen::Peanuts]; + +fn eggs_and_peanuts() { let allergies = Allergies::new(3).allergies(); + let expected = &[Allergen::Eggs, Allergen::Peanuts]; compare_allergy_vectors(expected, &allergies); } #[test] #[ignore] -fn allergic_to_eggs_and_shellfish() { - let expected = &[Allergen::Eggs, Allergen::Shellfish]; + +fn more_than_eggs_but_not_peanuts() { let allergies = Allergies::new(5).allergies(); + let expected = &[Allergen::Eggs, Allergen::Shellfish]; compare_allergy_vectors(expected, &allergies); } #[test] #[ignore] -fn allergic_to_many_things() { + +fn lots_of_stuff() { + let allergies = Allergies::new(248).allergies(); let expected = &[ Allergen::Strawberries, Allergen::Tomatoes, @@ -101,14 +405,15 @@ fn allergic_to_many_things() { Allergen::Pollen, Allergen::Cats, ]; - let allergies = Allergies::new(248).allergies(); compare_allergy_vectors(expected, &allergies); } #[test] #[ignore] -fn allergic_to_everything() { + +fn everything() { + let allergies = Allergies::new(255).allergies(); let expected = &[ Allergen::Eggs, Allergen::Peanuts, @@ -119,14 +424,15 @@ fn allergic_to_everything() { Allergen::Pollen, Allergen::Cats, ]; - let allergies = Allergies::new(255).allergies(); compare_allergy_vectors(expected, &allergies); } #[test] #[ignore] -fn scores_over_255_do_not_trigger_false_positives() { + +fn no_allergen_score_parts() { + let allergies = Allergies::new(509).allergies(); let expected = &[ Allergen::Eggs, Allergen::Shellfish, @@ -136,7 +442,16 @@ fn scores_over_255_do_not_trigger_false_positives() { Allergen::Pollen, Allergen::Cats, ]; - let allergies = Allergies::new(509).allergies(); + + compare_allergy_vectors(expected, &allergies); +} + +#[test] +#[ignore] + +fn no_allergen_score_parts_without_highest_valid_score() { + let allergies = Allergies::new(257).allergies(); + let expected = &[Allergen::Eggs]; compare_allergy_vectors(expected, &allergies); } From 5916fdf6b9f51c05c8b17eec05e0657d87d43814 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 12 Dec 2023 19:07:13 +0100 Subject: [PATCH 215/436] Sync triangle with problem-specifications (#1816) There was one challenge implementing the test template. The problem specification of this exercise states that it is up to the individual language tracks how to handle invalid triangles. However, this property is not easy to detect from the specification of the test cases. The test cases with invalid triangles were excluded in tests.toml and reimplemented in additional-tests.json with a custom property of "invalid". It was also necessary to separate the different properties into individual modules. Some descriptions - and therefore the generated test case names - are duplicated across different properties, so they have to be in different namespaces. --- docs/CONTRIBUTING.md | 4 + .../triangle/.meta/additional-tests.json | 52 +++ exercises/practice/triangle/.meta/example.rs | 2 +- .../triangle/.meta/test_template.tera | 85 ++++ exercises/practice/triangle/.meta/tests.toml | 111 ++++- exercises/practice/triangle/tests/triangle.rs | 387 +++++++++--------- problem-specifications | 2 +- 7 files changed, 435 insertions(+), 208 deletions(-) create mode 100644 exercises/practice/triangle/.meta/additional-tests.json create mode 100644 exercises/practice/triangle/.meta/test_template.tera diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 92f5d3df6..484e28a04 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -152,6 +152,10 @@ Feel free to add your own in the crate `rust-tooling`. Custom filters added there will be available to all templates. How to create such custom filters is documented int he [tera docs][tera-docs-filters]. +For a rather complicated example, check out the test template of `triangle`. +It organizes the test cases in modules and dynamically detects which tests to put behind feature gates. +That exercise also reimplements some test cases from upstream in `additional-tests.json`, in order to add more information to them necessary for generating good tests. + [tera-docs]: https://keats.github.io/tera/docs/#templates [word-count-tmpl]: /exercises/practice/word-count/.meta/test_template.tera [var-len-q-tmpl]: /exercises/practice/variable-length-quantity/.meta/test_template.tera diff --git a/exercises/practice/triangle/.meta/additional-tests.json b/exercises/practice/triangle/.meta/additional-tests.json new file mode 100644 index 000000000..b797996a3 --- /dev/null +++ b/exercises/practice/triangle/.meta/additional-tests.json @@ -0,0 +1,52 @@ +[ + { + "uuid": "3ddc03ad-4066-41b0-8ba2-1b0ce0146064", + "description": "all zero sides is not a triangle", + "comments": ["reimplements 16e8ceb0-eadb-46d1-b892-c50327479251"], + "property": "invalid", + "input": { + "sides": [0, 0, 0] + }, + "expected": false + }, + { + "uuid": "3ddc03ad-4066-41b0-8ba2-1b0ce0146064", + "description": "first triangle inequality violation", + "comments": ["reimplements 2eba0cfb-6c65-4c40-8146-30b608905eae"], + "property": "invalid", + "input": { + "sides": [1, 1, 3] + }, + "expected": false + }, + { + "uuid": "3ddc03ad-4066-41b0-8ba2-1b0ce0146064", + "description": "second triangle inequality violation", + "comments": ["reimplements 278469cb-ac6b-41f0-81d4-66d9b828f8ac"], + "property": "invalid", + "input": { + "sides": [1, 3, 1] + }, + "expected": false + }, + { + "uuid": "3ddc03ad-4066-41b0-8ba2-1b0ce0146064", + "description": "third triangle inequality violation", + "comments": ["reimplements 90efb0c7-72bb-4514-b320-3a3892e278ff"], + "property": "invalid", + "input": { + "sides": [3, 1, 1] + }, + "expected": false + }, + { + "uuid": "3ddc03ad-4066-41b0-8ba2-1b0ce0146064", + "description": "may not violate triangle inequality", + "comments": ["reimplements 70ad5154-0033-48b7-af2c-b8d739cd9fdc"], + "property": "invalid", + "input": { + "sides": [7, 3, 2] + }, + "expected": false + } +] \ No newline at end of file diff --git a/exercises/practice/triangle/.meta/example.rs b/exercises/practice/triangle/.meta/example.rs index 213cfb788..39f85574a 100644 --- a/exercises/practice/triangle/.meta/example.rs +++ b/exercises/practice/triangle/.meta/example.rs @@ -37,7 +37,7 @@ where } pub fn is_isosceles(&self) -> bool { - self.count_distinct_pairs() == 2 + self.count_distinct_pairs() <= 2 } pub fn is_scalene(&self) -> bool { diff --git a/exercises/practice/triangle/.meta/test_template.tera b/exercises/practice/triangle/.meta/test_template.tera new file mode 100644 index 000000000..ed7c03a9f --- /dev/null +++ b/exercises/practice/triangle/.meta/test_template.tera @@ -0,0 +1,85 @@ +mod equilateral { +use triangle::Triangle; +{% for test in cases %} +{% if test.property != "equilateral" %}{% continue %}{% endif %} + +#[test] +{% if loop.index != 1 -%} +#[ignore] +{% endif -%} +{% if test.description is containing("float") %} +#[cfg(feature = "generic")] +{% endif -%} +fn {{ test.description | slugify | replace(from="-", to="_") }}() { + let input = {{ test.input.sides | json_encode() }}; + let output = Triangle::build(input).unwrap(); + {%- if test.expected %} + assert!(output.is_equilateral()); + {% else %} + assert!(!output.is_equilateral()); + {% endif -%} +} +{% endfor -%} +} + +mod isosceles { +use triangle::Triangle; +{% for test in cases %} +{% if test.property != "isosceles" %}{% continue %}{% endif %} + +#[test] +#[ignore] +{% if test.scenarios and test.scenarios is containing("floating-point") %} +#[cfg(feature = "generic")] +{% endif -%} +fn {{ test.description | slugify | replace(from="-", to="_") }}() { + let input = {{ test.input.sides | json_encode() }}; + let output = Triangle::build(input).unwrap(); + {%- if test.expected %} + assert!(output.is_isosceles()); + {% else %} + assert!(!output.is_isosceles()); + {% endif -%} +} +{% endfor -%} +} + +mod scalene { +use triangle::Triangle; +{% for test in cases %} +{% if test.property != "scalene" %}{% continue %}{% endif %} + +#[test] +#[ignore] +{% if test.scenarios and test.scenarios is containing("floating-point") %} +#[cfg(feature = "generic")] +{% endif -%} +fn {{ test.description | slugify | replace(from="-", to="_") }}() { + let input = {{ test.input.sides | json_encode() }}; + let output = Triangle::build(input).unwrap(); + {%- if test.expected %} + assert!(output.is_scalene()); + {% else %} + assert!(!output.is_scalene()); + {% endif -%} +} +{% endfor -%} +} + +mod invalid { +use triangle::Triangle; +{% for test in cases %} +{% if test.property != "invalid" %}{% continue %}{% endif %} + +#[test] +#[ignore] +{% if test.scenarios and test.scenarios is containing("floating-point") %} +#[cfg(feature = "generic")] +{% endif -%} +fn {{ test.description | slugify | replace(from="-", to="_") }}() { + let input = {{ test.input.sides | json_encode() }}; + let output = Triangle::build(input); + assert!(output.is_none()); +} +{% endfor -%} +} diff --git a/exercises/practice/triangle/.meta/tests.toml b/exercises/practice/triangle/.meta/tests.toml index be690e975..2590d6ff4 100644 --- a/exercises/practice/triangle/.meta/tests.toml +++ b/exercises/practice/triangle/.meta/tests.toml @@ -1,3 +1,108 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[8b2c43ac-7257-43f9-b552-7631a91988af] +description = "equilateral triangle -> all sides are equal" + +[33eb6f87-0498-4ccf-9573-7f8c3ce92b7b] +description = "equilateral triangle -> any side is unequal" + +[c6585b7d-a8c0-4ad8-8a34-e21d36f7ad87] +description = "equilateral triangle -> no sides are equal" + +[16e8ceb0-eadb-46d1-b892-c50327479251] +description = "equilateral triangle -> all zero sides is not a triangle" +include = false +comment = """ + This is testing an invalid triangle, which we chose to implement + using an option. The problem specifications make it hard to detect + invalid triangles. We therefore skip this test and reimplement it + in .meta/additional-tests.json with the custom property "invalid". +""" + +[3022f537-b8e5-4cc1-8f12-fd775827a00c] +description = "equilateral triangle -> sides may be floats" + +[cbc612dc-d75a-4c1c-87fc-e2d5edd70b71] +description = "isosceles triangle -> last two sides are equal" + +[e388ce93-f25e-4daf-b977-4b7ede992217] +description = "isosceles triangle -> first two sides are equal" + +[d2080b79-4523-4c3f-9d42-2da6e81ab30f] +description = "isosceles triangle -> first and last sides are equal" + +[8d71e185-2bd7-4841-b7e1-71689a5491d8] +description = "isosceles triangle -> equilateral triangles are also isosceles" + +[840ed5f8-366f-43c5-ac69-8f05e6f10bbb] +description = "isosceles triangle -> no sides are equal" + +[2eba0cfb-6c65-4c40-8146-30b608905eae] +description = "isosceles triangle -> first triangle inequality violation" +include = false +comment = """ + This is testing an invalid triangle, which we chose to implement + using an option. The problem specifications make it hard to detect + invalid triangles. We therefore skip this test and reimplement it + in .meta/additional-tests.json with the custom property "invalid". +""" + +[278469cb-ac6b-41f0-81d4-66d9b828f8ac] +description = "isosceles triangle -> second triangle inequality violation" +include = false +comment = """ + This is testing an invalid triangle, which we chose to implement + using an option. The problem specifications make it hard to detect + invalid triangles. We therefore skip this test and reimplement it + in .meta/additional-tests.json with the custom property "invalid". +""" + +[90efb0c7-72bb-4514-b320-3a3892e278ff] +description = "isosceles triangle -> third triangle inequality violation" +include = false +comment = """ + This is testing an invalid triangle, which we chose to implement + using an option. The problem specifications make it hard to detect + invalid triangles. We therefore skip this test and reimplement it + in .meta/additional-tests.json with the custom property "invalid". +""" + +[adb4ee20-532f-43dc-8d31-e9271b7ef2bc] +description = "isosceles triangle -> sides may be floats" + +[e8b5f09c-ec2e-47c1-abec-f35095733afb] +description = "scalene triangle -> no sides are equal" + +[2510001f-b44d-4d18-9872-2303e7977dc1] +description = "scalene triangle -> all sides are equal" + +[c6e15a92-90d9-4fb3-90a2-eef64f8d3e1e] +description = "scalene triangle -> first and second sides are equal" + +[3da23a91-a166-419a-9abf-baf4868fd985] +description = "scalene triangle -> first and third sides are equal" + +[b6a75d98-1fef-4c42-8e9a-9db854ba0a4d] +description = "scalene triangle -> second and third sides are equal" + +[70ad5154-0033-48b7-af2c-b8d739cd9fdc] +description = "scalene triangle -> may not violate triangle inequality" +include = false +comment = """ + This is testing an invalid triangle, which we chose to implement + using an option. The problem specifications make it hard to detect + invalid triangles. We therefore skip this test and reimplement it + in .meta/additional-tests.json with the custom property "invalid". +""" + +[26d9d59d-f8f1-40d3-ad58-ae4d54123d7d] +description = "scalene triangle -> sides may be floats" diff --git a/exercises/practice/triangle/tests/triangle.rs b/exercises/practice/triangle/tests/triangle.rs index c93221df4..4992a3df9 100644 --- a/exercises/practice/triangle/tests/triangle.rs +++ b/exercises/practice/triangle/tests/triangle.rs @@ -1,204 +1,185 @@ -use triangle::*; - -#[test] -fn positive_length_sides_are_ok() { - let sides = [2, 2, 2]; - let triangle = Triangle::build(sides); - assert!(triangle.is_some()); -} - -#[test] -#[ignore] -fn zero_length_sides_are_illegal() { - let sides = [0, 0, 0]; - let triangle = Triangle::build(sides); - assert!(triangle.is_none()); -} - -#[test] -#[ignore] -fn one_length_zero_side_first() { - let sides = [0, 2, 2]; - let triangle = Triangle::build(sides); - assert!(triangle.is_none()); -} - -#[test] -#[ignore] -fn one_length_zero_side_second() { - let sides = [2, 0, 2]; - let triangle = Triangle::build(sides); - assert!(triangle.is_none()); -} - -#[test] -#[ignore] -fn one_length_zero_side_third() { - let sides = [2, 2, 0]; - let triangle = Triangle::build(sides); - assert!(triangle.is_none()); -} - -#[test] -#[ignore] -fn equilateral_triangles_have_equal_sides() { - let sides = [2, 2, 2]; - let triangle = Triangle::build(sides).unwrap(); - assert!(triangle.is_equilateral()); - assert!(!triangle.is_scalene()); -} - -#[test] -#[ignore] -fn larger_equilateral_triangles_have_equal_sides() { - let sides = [10, 10, 10]; - let triangle = Triangle::build(sides).unwrap(); - assert!(triangle.is_equilateral()); - assert!(!triangle.is_scalene()); -} - -#[test] -#[ignore] -fn isosceles_triangles_have_two_equal_sides_one() { - let sides = [3, 4, 4]; - let triangle = Triangle::build(sides).unwrap(); - assert!(!triangle.is_equilateral()); - assert!(triangle.is_isosceles()); - assert!(!triangle.is_scalene()); -} - -#[test] -#[ignore] -fn isosceles_triangles_have_two_equal_sides_two() { - let sides = [4, 4, 3]; - let triangle = Triangle::build(sides).unwrap(); - assert!(!triangle.is_equilateral()); - assert!(triangle.is_isosceles()); - assert!(!triangle.is_scalene()); -} - -#[test] -#[ignore] -fn isosceles_triangles_have_two_equal_sides_three() { - let sides = [4, 3, 4]; - let triangle = Triangle::build(sides).unwrap(); - assert!(!triangle.is_equilateral()); - assert!(triangle.is_isosceles()); - assert!(!triangle.is_scalene()); -} - -#[test] -#[ignore] -fn isosceles_triangles_have_two_equal_sides_four() { - let sides = [4, 7, 4]; - let triangle = Triangle::build(sides).unwrap(); - assert!(!triangle.is_equilateral()); - assert!(triangle.is_isosceles()); - assert!(!triangle.is_scalene()); -} - -#[test] -#[ignore] -fn scalene_triangle_has_no_equal_sides_one() { - let sides = [3, 4, 5]; - let triangle = Triangle::build(sides).unwrap(); - assert!(!triangle.is_equilateral()); - assert!(!triangle.is_isosceles()); - assert!(triangle.is_scalene()); -} - -#[test] -#[ignore] -fn scalene_triangle_has_no_equal_sides_two() { - let sides = [5, 4, 6]; - let triangle = Triangle::build(sides).unwrap(); - assert!(!triangle.is_equilateral()); - assert!(!triangle.is_isosceles()); - assert!(triangle.is_scalene()); -} - -#[test] -#[ignore] -fn scalene_triangle_has_no_equal_sides_three() { - let sides = [10, 11, 12]; - let triangle = Triangle::build(sides).unwrap(); - assert!(!triangle.is_equilateral()); - assert!(!triangle.is_isosceles()); - assert!(triangle.is_scalene()); -} - -#[test] -#[ignore] -fn scalene_triangle_has_no_equal_sides_four() { - let sides = [5, 4, 2]; - let triangle = Triangle::build(sides).unwrap(); - assert!(!triangle.is_equilateral()); - assert!(!triangle.is_isosceles()); - assert!(triangle.is_scalene()); -} - -#[test] -#[ignore] -fn sum_of_two_sides_must_equal_or_exceed_the_remaining_side_one() { - let sides = [7, 3, 2]; - let triangle = Triangle::build(sides); - assert!(triangle.is_none()); -} - -#[test] -#[ignore] -fn sum_of_two_sides_must_equal_or_exceed_the_remaining_side_two() { - let sides = [1, 1, 3]; - let triangle = Triangle::build(sides); - assert!(triangle.is_none()); -} - -#[test] -#[ignore] -#[cfg(feature = "generic")] -fn scalene_triangle_with_floating_point_sides() { - let sides = [0.4, 0.6, 0.3]; - let triangle = Triangle::build(sides).unwrap(); - assert!(!triangle.is_equilateral()); - assert!(!triangle.is_isosceles()); - assert!(triangle.is_scalene()); -} - -#[test] -#[ignore] -#[cfg(feature = "generic")] -fn equilateral_triangles_with_floating_point_sides() { - let sides = [0.2, 0.2, 0.2]; - let triangle = Triangle::build(sides).unwrap(); - assert!(triangle.is_equilateral()); - assert!(!triangle.is_scalene()); -} - -#[test] -#[ignore] -#[cfg(feature = "generic")] -fn isosceles_triangle_with_floating_point_sides() { - let sides = [0.3, 0.4, 0.4]; - let triangle = Triangle::build(sides).unwrap(); - assert!(!triangle.is_equilateral()); - assert!(triangle.is_isosceles()); - assert!(!triangle.is_scalene()); -} - -#[test] -#[ignore] -#[cfg(feature = "generic")] -fn invalid_triangle_with_floating_point_sides_one() { - let sides = [0.0, 0.4, 0.3]; - let triangle = Triangle::build(sides); - assert!(triangle.is_none()); -} - -#[test] -#[ignore] -#[cfg(feature = "generic")] -fn invalid_triangle_with_floating_point_sides_two() { - let sides = [0.1, 0.3, 0.5]; - let triangle = Triangle::build(sides); - assert!(triangle.is_none()); +mod equilateral { + use triangle::Triangle; + + #[test] + fn all_sides_are_equal() { + let input = [2, 2, 2]; + let output = Triangle::build(input).unwrap(); + assert!(output.is_equilateral()); + } + + #[test] + #[ignore] + fn any_side_is_unequal() { + let input = [2, 3, 2]; + let output = Triangle::build(input).unwrap(); + assert!(!output.is_equilateral()); + } + + #[test] + #[ignore] + fn no_sides_are_equal() { + let input = [5, 4, 6]; + let output = Triangle::build(input).unwrap(); + assert!(!output.is_equilateral()); + } + + #[test] + #[ignore] + #[cfg(feature = "generic")] + fn sides_may_be_floats() { + let input = [0.5, 0.5, 0.5]; + let output = Triangle::build(input).unwrap(); + assert!(output.is_equilateral()); + } +} + +mod isosceles { + use triangle::Triangle; + + #[test] + #[ignore] + fn last_two_sides_are_equal() { + let input = [3, 4, 4]; + let output = Triangle::build(input).unwrap(); + assert!(output.is_isosceles()); + } + + #[test] + #[ignore] + fn first_two_sides_are_equal() { + let input = [4, 4, 3]; + let output = Triangle::build(input).unwrap(); + assert!(output.is_isosceles()); + } + + #[test] + #[ignore] + fn first_and_last_sides_are_equal() { + let input = [4, 3, 4]; + let output = Triangle::build(input).unwrap(); + assert!(output.is_isosceles()); + } + + #[test] + #[ignore] + fn equilateral_triangles_are_also_isosceles() { + let input = [4, 4, 4]; + let output = Triangle::build(input).unwrap(); + assert!(output.is_isosceles()); + } + + #[test] + #[ignore] + fn no_sides_are_equal() { + let input = [2, 3, 4]; + let output = Triangle::build(input).unwrap(); + assert!(!output.is_isosceles()); + } + + #[test] + #[ignore] + #[cfg(feature = "generic")] + fn sides_may_be_floats() { + let input = [0.5, 0.4, 0.5]; + let output = Triangle::build(input).unwrap(); + assert!(output.is_isosceles()); + } +} + +mod scalene { + use triangle::Triangle; + + #[test] + #[ignore] + fn no_sides_are_equal() { + let input = [5, 4, 6]; + let output = Triangle::build(input).unwrap(); + assert!(output.is_scalene()); + } + + #[test] + #[ignore] + fn all_sides_are_equal() { + let input = [4, 4, 4]; + let output = Triangle::build(input).unwrap(); + assert!(!output.is_scalene()); + } + + #[test] + #[ignore] + fn first_and_second_sides_are_equal() { + let input = [4, 4, 3]; + let output = Triangle::build(input).unwrap(); + assert!(!output.is_scalene()); + } + + #[test] + #[ignore] + fn first_and_third_sides_are_equal() { + let input = [3, 4, 3]; + let output = Triangle::build(input).unwrap(); + assert!(!output.is_scalene()); + } + + #[test] + #[ignore] + fn second_and_third_sides_are_equal() { + let input = [4, 3, 3]; + let output = Triangle::build(input).unwrap(); + assert!(!output.is_scalene()); + } + + #[test] + #[ignore] + #[cfg(feature = "generic")] + fn sides_may_be_floats() { + let input = [0.5, 0.4, 0.6]; + let output = Triangle::build(input).unwrap(); + assert!(output.is_scalene()); + } +} + +mod invalid { + use triangle::Triangle; + + #[test] + #[ignore] + fn all_zero_sides_is_not_a_triangle() { + let input = [0, 0, 0]; + let output = Triangle::build(input); + assert!(output.is_none()); + } + + #[test] + #[ignore] + fn first_triangle_inequality_violation() { + let input = [1, 1, 3]; + let output = Triangle::build(input); + assert!(output.is_none()); + } + + #[test] + #[ignore] + fn second_triangle_inequality_violation() { + let input = [1, 3, 1]; + let output = Triangle::build(input); + assert!(output.is_none()); + } + + #[test] + #[ignore] + fn third_triangle_inequality_violation() { + let input = [3, 1, 1]; + let output = Triangle::build(input); + assert!(output.is_none()); + } + + #[test] + #[ignore] + fn may_not_violate_triangle_inequality() { + let input = [7, 3, 2]; + let output = Triangle::build(input); + assert!(output.is_none()); + } } diff --git a/problem-specifications b/problem-specifications index 988eafc34..57f1387b3 160000 --- a/problem-specifications +++ b/problem-specifications @@ -1 +1 @@ -Subproject commit 988eafc342c42defd78d1fc87b80d8586bc01ac4 +Subproject commit 57f1387b313ae9c94ab4a43f9586e85af67aa870 From a7d3e8676e765a58a47995cff4e91213d1af546a Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Wed, 13 Dec 2023 10:40:30 +0100 Subject: [PATCH 216/436] Sync knapsack with problem-specifications (#1821) [no important files changed] --- .../knapsack/.meta/test_template.tera | 22 +++++ exercises/practice/knapsack/.meta/tests.toml | 5 + exercises/practice/knapsack/tests/knapsack.rs | 95 +++++++++---------- 3 files changed, 73 insertions(+), 49 deletions(-) create mode 100644 exercises/practice/knapsack/.meta/test_template.tera diff --git a/exercises/practice/knapsack/.meta/test_template.tera b/exercises/practice/knapsack/.meta/test_template.tera new file mode 100644 index 000000000..b7b4d4983 --- /dev/null +++ b/exercises/practice/knapsack/.meta/test_template.tera @@ -0,0 +1,22 @@ +use {{ crate_name }}::*; + +{% for test in cases %} +#[test] +{% if loop.index != 1 -%} +#[ignore] +{% endif -%} +fn test_{{ test.description | slugify | replace(from="-", to="_") }}() { + let max_weight = {{ test.input.maximumWeight }}; + let items = [ + {% for item in test.input.items -%} + Item { + weight: {{ item.weight }}, + value: {{ item.value }}, + }, + {% endfor -%} + ]; + let output = {{ fn_names[0] }}(max_weight, &items); + let expected = {{ test.expected | json_encode() }}; + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/knapsack/.meta/tests.toml b/exercises/practice/knapsack/.meta/tests.toml index febc7b26b..8e013ef19 100644 --- a/exercises/practice/knapsack/.meta/tests.toml +++ b/exercises/practice/knapsack/.meta/tests.toml @@ -11,6 +11,11 @@ [a4d7d2f0-ad8a-460c-86f3-88ba709d41a7] description = "no items" +include = false + +[3993a824-c20e-493d-b3c9-ee8a7753ee59] +description = "no items" +reimplements = "a4d7d2f0-ad8a-460c-86f3-88ba709d41a7" [1d39e98c-6249-4a8b-912f-87cb12e506b0] description = "one item, too heavy" diff --git a/exercises/practice/knapsack/tests/knapsack.rs b/exercises/practice/knapsack/tests/knapsack.rs index 05b1b9b51..165fe07dd 100644 --- a/exercises/practice/knapsack/tests/knapsack.rs +++ b/exercises/practice/knapsack/tests/knapsack.rs @@ -1,54 +1,20 @@ use knapsack::*; #[test] -fn example_knapsack() { - let max_weight = 10; - let items = [ - Item { - weight: 5, - value: 10, - }, - Item { - weight: 4, - value: 40, - }, - Item { - weight: 6, - value: 30, - }, - Item { - weight: 4, - value: 50, - }, - ]; - - assert_eq!(maximum_value(max_weight, &items), 90); -} - -#[test] -#[ignore] -fn no_items() { - let max_weight = 100; - let items = []; - - assert_eq!(maximum_value(max_weight, &items), 0); -} - -#[test] -#[ignore] -fn one_item_too_heavy() { +fn test_one_item_too_heavy() { let max_weight = 10; let items = [Item { weight: 100, value: 1, }]; - - assert_eq!(maximum_value(max_weight, &items), 0); + let output = maximum_value(max_weight, &items); + let expected = 0; + assert_eq!(output, expected); } #[test] #[ignore] -fn five_items_cannot_be_greedy_by_weight() { +fn test_five_items_cannot_be_greedy_by_weight() { let max_weight = 10; let items = [ Item { @@ -72,13 +38,14 @@ fn five_items_cannot_be_greedy_by_weight() { value: 21, }, ]; - - assert_eq!(maximum_value(max_weight, &items), 21); + let output = maximum_value(max_weight, &items); + let expected = 21; + assert_eq!(output, expected); } #[test] #[ignore] -fn five_items_cannot_be_greedy_by_value() { +fn test_five_items_cannot_be_greedy_by_value() { let max_weight = 10; let items = [ Item { @@ -102,13 +69,41 @@ fn five_items_cannot_be_greedy_by_value() { value: 50, }, ]; + let output = maximum_value(max_weight, &items); + let expected = 80; + assert_eq!(output, expected); +} - assert_eq!(maximum_value(max_weight, &items), 80); +#[test] +#[ignore] +fn test_example_knapsack() { + let max_weight = 10; + let items = [ + Item { + weight: 5, + value: 10, + }, + Item { + weight: 4, + value: 40, + }, + Item { + weight: 6, + value: 30, + }, + Item { + weight: 4, + value: 50, + }, + ]; + let output = maximum_value(max_weight, &items); + let expected = 90; + assert_eq!(output, expected); } #[test] #[ignore] -fn eight_items() { +fn test_8_items() { let max_weight = 104; let items = [ Item { @@ -144,13 +139,14 @@ fn eight_items() { value: 5, }, ]; - - assert_eq!(maximum_value(max_weight, &items), 900); + let output = maximum_value(max_weight, &items); + let expected = 900; + assert_eq!(output, expected); } #[test] #[ignore] -fn fifteen_items() { +fn test_15_items() { let max_weight = 750; let items = [ Item { @@ -214,6 +210,7 @@ fn fifteen_items() { value: 240, }, ]; - - assert_eq!(maximum_value(max_weight, &items), 1458); + let output = maximum_value(max_weight, &items); + let expected = 1458; + assert_eq!(output, expected); } From d8b29ebbf187ae9b7a3325c0d5b12cc601387d7f Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Thu, 14 Dec 2023 12:04:17 +0100 Subject: [PATCH 217/436] leap: fix invalid url in approaches introduction (#1823) --- exercises/practice/leap/.approaches/introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/leap/.approaches/introduction.md b/exercises/practice/leap/.approaches/introduction.md index 081254f59..da1fefae2 100644 --- a/exercises/practice/leap/.approaches/introduction.md +++ b/exercises/practice/leap/.approaches/introduction.md @@ -75,7 +75,7 @@ For more information, check the [Performance article][article-performance]. [remainder-operator]: https://doc.rust-lang.org/std/ops/trait.Rem.html [match]: https://doc.rust-lang.org/rust-by-example/flow_control/match.html [tuple]: https://doc.rust-lang.org/rust-by-example/primitives/tuples.html -[ternary-expression]: (https://doc.rust-lang.org/reference/expressions/if-expr.html) +[ternary-expression]: https://doc.rust-lang.org/reference/expressions/if-expr.html [approach-boolean-line]: https://exercism.org/tracks/rust/exercises/leap/approaches/boolean-one-line [approach-ternary-expression]: https://exercism.org/tracks/rust/exercises/leap/approaches/ternary-expression [approach-match-on-a-tuple]: https://exercism.org/tracks/rust/exercises/leap/approaches/match-on-a-tuple From f5a60c5d464e018cb3ef16ed272f96edc1f1b037 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Fri, 15 Dec 2023 20:19:37 +0100 Subject: [PATCH 218/436] Add documentation for debugging online (#1825) --- exercises/shared/.docs/debug.md | 60 +++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 exercises/shared/.docs/debug.md diff --git a/exercises/shared/.docs/debug.md b/exercises/shared/.docs/debug.md new file mode 100644 index 000000000..8f7f697b4 --- /dev/null +++ b/exercises/shared/.docs/debug.md @@ -0,0 +1,60 @@ +# Debug + +When a test fails, a message is displayed describing what went wrong and for which input. +You can also use the fact that anything printed to "standard out" will be shown too. +You can write to standard out using `println!`: + +```rust +println!("Debug message"); +``` + +Use braces `{}` to print the value of a variable or expressions: + +```rust +let secret_number = 4321; +println!("My banking password is {}, which is better than {}.", secret_number, 1200 + 34); +``` + +Note that not all types can be printed this way, only the ones that can be presented to users unambiguously. +For example, vectors (lists) cannot be printed like this, because it's not clear how a vector should be displayed to users! +For these other types, you can tell the `println!` macro to use _debug-formatting_ with: `{:?}`. +This representation is only intended to be seen by developers. +An example: + +```rust +let words = vec!["hello", "world"]; +println!("words that are often combined: {:?}", words); +``` + +There is even the handy `dbg!` macro, which can be used to debug large expressions inline, without having to pick it apart into separate variable declarations. + +Assume we have the following expression: + +```rust +"hello world".split(' ').collect::>().join("-") +``` + +Now we would like to see what the call to `collect` returns. +We could do the following: + +```rust +let my_temporary_debug_variable = "hello world".split(' ').collect::>(); +println!("{:?}", my_temporary_debug_variable); +my_temporary_debug_variable.join("-") +``` + +But that's tedious! +We don't wan't to rewrite our nice expressions just to be able to debug them. +Please welcome to the stage: `dbg!` + +```rust +dbg!( + "hello world".split(' ').collect::>() +).join("-") +``` + +The whitespace is only for clarity. +You can wrap any expression in `dbg!()`, which will print that value (with debug-formatting). +Afterwards you can keep using the value in a larger expression, as if the `dbg!` wasn't even there. + +Happy debugging! From bfbd00f1bcd3c9c52af9292918383be5e4b31de4 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Fri, 15 Dec 2023 20:20:59 +0100 Subject: [PATCH 219/436] Sync series with problem-specifications (#1822) --- .../practice/series/.meta/test_template.tera | 24 +++++ exercises/practice/series/.meta/tests.toml | 73 ++++++++++++++- exercises/practice/series/tests/series.rs | 90 +++++++++++++++---- 3 files changed, 165 insertions(+), 22 deletions(-) create mode 100644 exercises/practice/series/.meta/test_template.tera diff --git a/exercises/practice/series/.meta/test_template.tera b/exercises/practice/series/.meta/test_template.tera new file mode 100644 index 000000000..64a9fc5fc --- /dev/null +++ b/exercises/practice/series/.meta/test_template.tera @@ -0,0 +1,24 @@ +use {{ crate_name }}::*; + +{% for test in cases %} +#[test] +{% if loop.index != 1 -%} +#[ignore] +{% endif -%} +fn {{ test.description | slugify | replace(from="-", to="_") }}() { + let input = {{ test.input.series | json_encode() }}; + let length = {{ test.input.sliceLength | json_encode() }}; + let output = {{ fn_names[0] }}(input, length); +{%- if test.expected is object -%} + {# + The author of the exercise chose to define the semantics such that + "invalid" series return empty vectors. Adding error handling to the + exercise later would be a breaking change. + #} + let expected: &[&str] = &[]; +{% else %} + let expected = &{{ test.expected | json_encode() }}; +{% endif -%} + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/series/.meta/tests.toml b/exercises/practice/series/.meta/tests.toml index be690e975..778d6b794 100644 --- a/exercises/practice/series/.meta/tests.toml +++ b/exercises/practice/series/.meta/tests.toml @@ -1,3 +1,70 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[7ae7a46a-d992-4c2a-9c15-a112d125ebad] +description = "slices of one from one" + +[3143b71d-f6a5-4221-aeae-619f906244d2] +description = "slices of one from two" + +[dbb68ff5-76c5-4ccd-895a-93dbec6d5805] +description = "slices of two" + +[19bbea47-c987-4e11-a7d1-e103442adf86] +description = "slices of two overlap" + +[8e17148d-ba0a-4007-a07f-d7f87015d84c] +description = "slices can include duplicates" + +[bd5b085e-f612-4f81-97a8-6314258278b0] +description = "slices of a long series" + +[6d235d85-46cf-4fae-9955-14b6efef27cd] +description = "slice length is too large" + +[d7957455-346d-4e47-8e4b-87ed1564c6d7] +description = "slice length is way too large" + +[d34004ad-8765-4c09-8ba1-ada8ce776806] +description = "slice length cannot be zero" +include = false +comment = """ + This test case has caused a certain amount of confusion. + In the original implementation of the exercise, the test expected: + + series("12345", 0) -> ["", "", "", "", "", ""] + + Reasonable arguments have been made on both sides: + https://github.com/exercism/rust/pull/402 + https://github.com/exercism/rust/issues/671 + + The problem-specifications repository defines this case to return an error. + But the existing interface of the exercise doesn't allow error handling, + so changing it now would be a breaking change. + + It was decided to simply exclude this test case. + Handling this specific edge case in the very specific way it was designed + in the original implementation doesn't further the student's understanding + of Rust as a language. + + In order to avoid breaking existing solutions, there should not be any + tests added in the future where the length of the slice is exactly one more + than the length of the input string. If such tests are added in + problem-specifications, they should be excluded as well. +""" + +[10ab822d-8410-470a-a85d-23fbeb549e54] +description = "slice length cannot be negative" +include = false +comment = "the function signature already prevents this (usize)" + +[c7ed0812-0e4b-4bf3-99c4-28cbbfc246a2] +description = "empty series is invalid" diff --git a/exercises/practice/series/tests/series.rs b/exercises/practice/series/tests/series.rs index 4757d8b55..dc73172ee 100644 --- a/exercises/practice/series/tests/series.rs +++ b/exercises/practice/series/tests/series.rs @@ -1,40 +1,92 @@ use series::*; #[test] -fn with_zero_length() { - let expected = vec!["".to_string(); 6]; - assert_eq!(series("92017", 0), expected); +fn slices_of_one_from_one() { + let input = "1"; + let length = 1; + let output = series(input, length); + let expected = &["1"]; + assert_eq!(output, expected); } #[test] #[ignore] -fn with_length_2() { - let expected = vec![ - "92".to_string(), - "20".to_string(), - "01".to_string(), - "17".to_string(), +fn slices_of_one_from_two() { + let input = "12"; + let length = 1; + let output = series(input, length); + let expected = &["1", "2"]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn slices_of_two() { + let input = "35"; + let length = 2; + let output = series(input, length); + let expected = &["35"]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn slices_of_two_overlap() { + let input = "9142"; + let length = 2; + let output = series(input, length); + let expected = &["91", "14", "42"]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn slices_can_include_duplicates() { + let input = "777777"; + let length = 3; + let output = series(input, length); + let expected = &["777", "777", "777", "777"]; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn slices_of_a_long_series() { + let input = "918493904243"; + let length = 5; + let output = series(input, length); + let expected = &[ + "91849", "18493", "84939", "49390", "93904", "39042", "90424", "04243", ]; - assert_eq!(series("92017", 2), expected); + assert_eq!(output, expected); } #[test] #[ignore] -fn with_numbers_length() { - let expected = vec!["92017".to_string()]; - assert_eq!(series("92017", 5), expected); +fn slice_length_is_too_large() { + let input = "12345"; + let length = 6; + let output = series(input, length); + let expected: &[&str] = &[]; + assert_eq!(output, expected); } #[test] #[ignore] -fn too_long() { - let expected: Vec = vec![]; - assert_eq!(series("92017", 6), expected); +fn slice_length_is_way_too_large() { + let input = "12345"; + let length = 42; + let output = series(input, length); + let expected: &[&str] = &[]; + assert_eq!(output, expected); } #[test] #[ignore] -fn way_too_long() { - let expected: Vec = vec![]; - assert_eq!(series("92017", 42), expected); +fn empty_series_is_invalid() { + let input = ""; + let length = 1; + let output = series(input, length); + let expected: &[&str] = &[]; + assert_eq!(output, expected); } From 0a9469cc57f2815e0d61401dad97945c2ee97b45 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 19 Dec 2023 13:10:50 +0100 Subject: [PATCH 220/436] Document use of global mutability in robot-name (#1831) --- .../practice/robot-name/.docs/instructions.append.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 exercises/practice/robot-name/.docs/instructions.append.md diff --git a/exercises/practice/robot-name/.docs/instructions.append.md b/exercises/practice/robot-name/.docs/instructions.append.md new file mode 100644 index 000000000..48930b13d --- /dev/null +++ b/exercises/practice/robot-name/.docs/instructions.append.md @@ -0,0 +1,9 @@ +# Instructions append + +## Global Mutable State + +The way the tests are setup, you will be forced to use global mutable state. +This is generally frowned-upon and considered unidiomatic in Rust. +However, it's a delibarate choice for the purpose of learning here. + +Can you think of a better API design that doesn't use global mutable state? From a5c96c48cf2a0abcec14f8f87ad8e3b6e3f3eb78 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 19 Dec 2023 13:12:03 +0100 Subject: [PATCH 221/436] Sync prime-factors with problem-specifications (#1832) --- .../prime-factors/.meta/test_template.tera | 13 ++++ .../practice/prime-factors/.meta/tests.toml | 49 ++++++++++++- .../prime-factors/tests/prime-factors.rs | 72 ++++++++++++++++--- 3 files changed, 122 insertions(+), 12 deletions(-) create mode 100644 exercises/practice/prime-factors/.meta/test_template.tera diff --git a/exercises/practice/prime-factors/.meta/test_template.tera b/exercises/practice/prime-factors/.meta/test_template.tera new file mode 100644 index 000000000..8ca8d0eac --- /dev/null +++ b/exercises/practice/prime-factors/.meta/test_template.tera @@ -0,0 +1,13 @@ +use {{ crate_name }}::*; + +{% for test in cases %} +#[test] +{% if loop.index != 1 -%} +#[ignore] +{% endif -%} +fn {{ test.description | slugify | replace(from="-", to="_") }}() { + let factors = {{ fn_names[0] }}({{ test.input.value }}); + let expected = {{ test.expected | json_encode() }}; + assert_eq!(factors, expected); +} +{% endfor -%} diff --git a/exercises/practice/prime-factors/.meta/tests.toml b/exercises/practice/prime-factors/.meta/tests.toml index be690e975..6f9cc8ced 100644 --- a/exercises/practice/prime-factors/.meta/tests.toml +++ b/exercises/practice/prime-factors/.meta/tests.toml @@ -1,3 +1,46 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[924fc966-a8f5-4288-82f2-6b9224819ccd] +description = "no factors" + +[17e30670-b105-4305-af53-ddde182cb6ad] +description = "prime number" + +[238d57c8-4c12-42ef-af34-ae4929f94789] +description = "another prime number" + +[f59b8350-a180-495a-8fb1-1712fbee1158] +description = "square of a prime" + +[756949d3-3158-4e3d-91f2-c4f9f043ee70] +description = "product of first prime" + +[bc8c113f-9580-4516-8669-c5fc29512ceb] +description = "cube of a prime" + +[7d6a3300-a4cb-4065-bd33-0ced1de6cb44] +description = "product of second prime" + +[073ac0b2-c915-4362-929d-fc45f7b9a9e4] +description = "product of third prime" + +[6e0e4912-7fb6-47f3-a9ad-dbcd79340c75] +description = "product of first and second prime" + +[00485cd3-a3fe-4fbe-a64a-a4308fc1f870] +description = "product of primes and non-primes" + +[02251d54-3ca1-4a9b-85e1-b38f4b0ccb91] +description = "product of primes" + +[070cf8dc-e202-4285-aa37-8d775c9cd473] +description = "factors include a large prime" diff --git a/exercises/practice/prime-factors/tests/prime-factors.rs b/exercises/practice/prime-factors/tests/prime-factors.rs index 2b66f2ac7..c5b67581c 100644 --- a/exercises/practice/prime-factors/tests/prime-factors.rs +++ b/exercises/practice/prime-factors/tests/prime-factors.rs @@ -1,42 +1,96 @@ -use prime_factors::factors; +use prime_factors::*; #[test] fn no_factors() { - assert_eq!(factors(1), vec![]); + let factors = factors(1); + let expected = []; + assert_eq!(factors, expected); } #[test] #[ignore] fn prime_number() { - assert_eq!(factors(2), vec![2]); + let factors = factors(2); + let expected = [2]; + assert_eq!(factors, expected); +} + +#[test] +#[ignore] +fn another_prime_number() { + let factors = factors(3); + let expected = [3]; + assert_eq!(factors, expected); } #[test] #[ignore] fn square_of_a_prime() { - assert_eq!(factors(9), vec![3, 3]); + let factors = factors(9); + let expected = [3, 3]; + assert_eq!(factors, expected); +} + +#[test] +#[ignore] +fn product_of_first_prime() { + let factors = factors(4); + let expected = [2, 2]; + assert_eq!(factors, expected); } #[test] #[ignore] fn cube_of_a_prime() { - assert_eq!(factors(8), vec![2, 2, 2]); + let factors = factors(8); + let expected = [2, 2, 2]; + assert_eq!(factors, expected); +} + +#[test] +#[ignore] +fn product_of_second_prime() { + let factors = factors(27); + let expected = [3, 3, 3]; + assert_eq!(factors, expected); +} + +#[test] +#[ignore] +fn product_of_third_prime() { + let factors = factors(625); + let expected = [5, 5, 5, 5]; + assert_eq!(factors, expected); +} + +#[test] +#[ignore] +fn product_of_first_and_second_prime() { + let factors = factors(6); + let expected = [2, 3]; + assert_eq!(factors, expected); } #[test] #[ignore] fn product_of_primes_and_non_primes() { - assert_eq!(factors(12), vec![2, 2, 3]); + let factors = factors(12); + let expected = [2, 2, 3]; + assert_eq!(factors, expected); } #[test] #[ignore] fn product_of_primes() { - assert_eq!(factors(901_255), vec![5, 17, 23, 461]); + let factors = factors(901255); + let expected = [5, 17, 23, 461]; + assert_eq!(factors, expected); } #[test] #[ignore] -fn factors_include_large_prime() { - assert_eq!(factors(93_819_012_551), vec![11, 9539, 894_119]); +fn factors_include_a_large_prime() { + let factors = factors(93819012551); + let expected = [11, 9539, 894119]; + assert_eq!(factors, expected); } From d631291f5c7f87c28fdcecb14d28e30f3fd7729b Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 19 Dec 2023 13:13:33 +0100 Subject: [PATCH 222/436] Test parameter naming convention (#1837) --- .../practice/kindergarten-garden/src/lib.rs | 4 +- exercises/practice/knapsack/src/lib.rs | 5 +- exercises/practice/yacht/src/lib.rs | 6 +- .../tests/no_underscore_prefixed_idents.rs | 103 ++++++++++++++++++ 4 files changed, 112 insertions(+), 6 deletions(-) create mode 100644 rust-tooling/ci-tests/tests/no_underscore_prefixed_idents.rs diff --git a/exercises/practice/kindergarten-garden/src/lib.rs b/exercises/practice/kindergarten-garden/src/lib.rs index a8eae0480..45106de9b 100644 --- a/exercises/practice/kindergarten-garden/src/lib.rs +++ b/exercises/practice/kindergarten-garden/src/lib.rs @@ -1,3 +1,3 @@ -pub fn plants(_diagram: &str, _student: &str) -> Vec<&'static str> { - todo!("Solve kindergarten-garden exercise"); +pub fn plants(diagram: &str, student: &str) -> Vec<&'static str> { + todo!("based on the {diagram}, determine the plants the {student} is responsible for"); } diff --git a/exercises/practice/knapsack/src/lib.rs b/exercises/practice/knapsack/src/lib.rs index 657f37c03..9c505ce5c 100644 --- a/exercises/practice/knapsack/src/lib.rs +++ b/exercises/practice/knapsack/src/lib.rs @@ -1,8 +1,9 @@ +#[derive(Debug)] pub struct Item { pub weight: u32, pub value: u32, } -pub fn maximum_value(_max_weight: u32, _items: &[Item]) -> u32 { - todo!("Solve the knapsack exercise"); +pub fn maximum_value(max_weight: u32, items: &[Item]) -> u32 { + todo!("calculate the maximum value achievable with the given {items:?} and {max_weight}"); } diff --git a/exercises/practice/yacht/src/lib.rs b/exercises/practice/yacht/src/lib.rs index 1e92e6e84..32121a6f1 100644 --- a/exercises/practice/yacht/src/lib.rs +++ b/exercises/practice/yacht/src/lib.rs @@ -1,3 +1,4 @@ +#[derive(Debug)] pub enum Category { Ones, Twos, @@ -14,6 +15,7 @@ pub enum Category { } type Dice = [u8; 5]; -pub fn score(_dice: Dice, _category: Category) -> u8 { - todo!("Solve the Yacht exercise"); + +pub fn score(dice: Dice, category: Category) -> u8 { + todo!("determine the score for {dice:?} in the {category:?}"); } diff --git a/rust-tooling/ci-tests/tests/no_underscore_prefixed_idents.rs b/rust-tooling/ci-tests/tests/no_underscore_prefixed_idents.rs new file mode 100644 index 000000000..c86814be5 --- /dev/null +++ b/rust-tooling/ci-tests/tests/no_underscore_prefixed_idents.rs @@ -0,0 +1,103 @@ +//! A simple exercise stub might look like this: +//! +//! ```rust +//! pub fn fn_to_implement(parameter: i32) -> i32 { +//! todo!() +//! } +//! ``` +//! +//! The problem with this is that a warning will be generated because +//! `parameter` is unused. There are two obvious ways to fix this: +//! +//! 1. Prefix the parameter name with an underscore +//! +//! ```rust +//! pub fn fn_to_implement(_parameter: i32) -> i32 { +//! todo!() +//! } +//! ``` +//! +//! 2. Use the parameter in the `todo!()` macro +//! +//! ```rust +//! pub fn fn_to_implement(parameter: i32) -> i32 { +//! todo!("use {parameter} to solve the exercise") +//! } +//! ``` +//! +//! The second approach has the advantage that it disappears automatically. +//! Students will remove the macro in the process of solving the exercise. +//! The first approach requires a manual step to remove the underscore. +//! Some students don't do this (as was noticed during mentoring sessions). +//! This is ugly and even unidiomatic, because it means the compiler won't +//! warn about unused variables when that would actually be helpful. +//! +//! The second approach does have a disadvantage: it requires the parameter +//! to be `Debug`. This is usually not a problem, most of our test inputs are +//! indedd `Debug`, but it becomes a problem with generics. If a parameter is +//! generic, there must be a `Debug` bound on it. That would usually be +//! fulfilled, but it could be confusing to the students why the bound exists. +//! In that case, the first approach may be used as a fallback. +//! +//! This test makes sure the second approach is used consistently, with +//! explicitly listed exceptions. + +use glob::glob; +use utils::fs::cd_into_repo_root; + +static EXCEPTIONS: &[&str] = &[ + "accumulate", // has generics (not the stub, but the solution) + "circular-buffer", // has generics + "custom-set", // has generics + "doubly-linked-list", // has generics + "fizzy", // has generics + "paasio", // has generics + "react", // has generics + "roman-numerals", // std::fmt::Formatter is not Debug + "simple-linked-list", // has generics + "sublist", // has generics + "xorcism", // has PhantomData +]; + +fn line_is_not_a_comment(line: &&str) -> bool { + !line.trim().starts_with("//") +} + +#[test] +fn no_underscore_prefixed_idents() { + cd_into_repo_root(); + for lib_rs in glob("exercises/*/*/src/lib.rs") + .unwrap() + .map(Result::unwrap) + { + if EXCEPTIONS + .iter() + .any(|e| lib_rs.to_string_lossy().contains(e)) + { + continue; + } + + let exercise_stub = std::fs::read_to_string(&lib_rs).unwrap(); + + for identifier_like in exercise_stub + .lines() + .filter(line_is_not_a_comment) + // This is extremely crude parsing, but it can be improved if the need arises. + .flat_map(|line| line.split(|c: char| !(c.is_alphabetic() || c == '_'))) + { + if identifier_like.starts_with('_') && identifier_like != "_" { + panic!( + "Exercise stub in {} contains underscore-prefixed identifier {} + + ╔════════════════════════════════════════════════════════════════╗ + ║ The use of an underscore-prefixed identifier may be justified. ║ + ║ If you think it is, add it to the list of exceptions. ║ + ╚════════════════════════════════════════════════════════════════╝ +", + lib_rs.display(), + identifier_like + ); + } + } + } +} From 585c8b3fbb2b5389c07119ab5cd56606993ce356 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 19 Dec 2023 13:14:29 +0100 Subject: [PATCH 223/436] Fix instructions for submitting solutions (#1838) --- exercises/practice/hello-world/GETTING_STARTED.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/hello-world/GETTING_STARTED.md b/exercises/practice/hello-world/GETTING_STARTED.md index 767d42537..7ae584ca5 100644 --- a/exercises/practice/hello-world/GETTING_STARTED.md +++ b/exercises/practice/hello-world/GETTING_STARTED.md @@ -88,5 +88,5 @@ Once the test is passing, you can submit your code with the following command: ```sh -$ exercism submit src/lib.rs +$ exercism submit ``` From 12349526bb70a0eca1438a33bf91377945163a4c Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 19 Dec 2023 13:18:55 +0100 Subject: [PATCH 224/436] Test against clippy warnings in stubs (#1830) --- .gitignore | 2 +- exercises/concept/csv-builder/Cargo.toml | 3 + exercises/practice/bowling/Cargo.toml | 3 + exercises/practice/fizzy/Cargo.toml | 3 +- exercises/practice/forth/Cargo.toml | 3 + exercises/practice/grade-school/Cargo.toml | 3 + exercises/practice/react/Cargo.toml | 3 + exercises/practice/robot-name/Cargo.toml | 3 +- .../practice/simple-linked-list/Cargo.toml | 3 +- rust-tooling/Cargo.lock | 71 +++++++++++++++- rust-tooling/ci-tests/Cargo.toml | 1 + .../ci-tests/tests/stubs_are_warning_free.rs | 83 +++++++++++++++++++ 12 files changed, 173 insertions(+), 8 deletions(-) create mode 100644 rust-tooling/ci-tests/tests/stubs_are_warning_free.rs diff --git a/.gitignore b/.gitignore index 5af0f3619..7f0587770 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,6 @@ tmp /bin/configlet exercises/*/*/Cargo.lock -exercises/*/*/clippy.log +clippy.log .vscode .prob-spec diff --git a/exercises/concept/csv-builder/Cargo.toml b/exercises/concept/csv-builder/Cargo.toml index 4e0bfad88..29b5af9ee 100644 --- a/exercises/concept/csv-builder/Cargo.toml +++ b/exercises/concept/csv-builder/Cargo.toml @@ -2,3 +2,6 @@ name = "csv_builder" version = "0.1.0" edition = "2021" + +[lints.clippy] +new_without_default = "allow" diff --git a/exercises/practice/bowling/Cargo.toml b/exercises/practice/bowling/Cargo.toml index 705fc85f7..e8ab79369 100644 --- a/exercises/practice/bowling/Cargo.toml +++ b/exercises/practice/bowling/Cargo.toml @@ -2,3 +2,6 @@ edition = "2021" name = "bowling" version = "1.2.0" + +[lints.clippy] +new_without_default = "allow" diff --git a/exercises/practice/fizzy/Cargo.toml b/exercises/practice/fizzy/Cargo.toml index 005dcd594..3992643de 100644 --- a/exercises/practice/fizzy/Cargo.toml +++ b/exercises/practice/fizzy/Cargo.toml @@ -3,4 +3,5 @@ name = "fizzy" version = "0.0.0" edition = "2021" -[dependencies] +[lints.clippy] +new_without_default = "allow" diff --git a/exercises/practice/forth/Cargo.toml b/exercises/practice/forth/Cargo.toml index 9a51e6e8a..04e076d08 100644 --- a/exercises/practice/forth/Cargo.toml +++ b/exercises/practice/forth/Cargo.toml @@ -2,3 +2,6 @@ edition = "2021" name = "forth" version = "1.7.0" + +[lints.clippy] +new_without_default = "allow" diff --git a/exercises/practice/grade-school/Cargo.toml b/exercises/practice/grade-school/Cargo.toml index d8851568d..c8f979a76 100644 --- a/exercises/practice/grade-school/Cargo.toml +++ b/exercises/practice/grade-school/Cargo.toml @@ -2,3 +2,6 @@ edition = "2021" name = "grade-school" version = "0.0.0" + +[lints.clippy] +new_without_default = "allow" diff --git a/exercises/practice/react/Cargo.toml b/exercises/practice/react/Cargo.toml index 58e208248..3d2ebf0ad 100644 --- a/exercises/practice/react/Cargo.toml +++ b/exercises/practice/react/Cargo.toml @@ -2,3 +2,6 @@ edition = "2021" name = "react" version = "2.0.0" + +[lints.clippy] +new_without_default = "allow" diff --git a/exercises/practice/robot-name/Cargo.toml b/exercises/practice/robot-name/Cargo.toml index 97c58d658..1893bb13c 100644 --- a/exercises/practice/robot-name/Cargo.toml +++ b/exercises/practice/robot-name/Cargo.toml @@ -3,4 +3,5 @@ edition = "2021" name = "robot-name" version = "0.0.0" -[dependencies] +[lints.clippy] +new_without_default = "allow" diff --git a/exercises/practice/simple-linked-list/Cargo.toml b/exercises/practice/simple-linked-list/Cargo.toml index 81a5a00d3..211831446 100644 --- a/exercises/practice/simple-linked-list/Cargo.toml +++ b/exercises/practice/simple-linked-list/Cargo.toml @@ -3,4 +3,5 @@ edition = "2021" name = "simple_linked_list" version = "0.1.0" -[dependencies] +[lints.clippy] +new_without_default = "allow" diff --git a/rust-tooling/Cargo.lock b/rust-tooling/Cargo.lock index b36160cf9..dead1c520 100644 --- a/rust-tooling/Cargo.lock +++ b/rust-tooling/Cargo.lock @@ -169,6 +169,7 @@ dependencies = [ "ignore", "models", "serde_json", + "tempdir", "utils", ] @@ -311,6 +312,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + [[package]] name = "generate" version = "0.1.0" @@ -677,7 +684,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" dependencies = [ "phf_shared", - "rand", + "rand 0.8.5", ] [[package]] @@ -713,6 +720,19 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi", +] + [[package]] name = "rand" version = "0.8.5" @@ -721,7 +741,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -731,9 +751,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", ] +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + [[package]] name = "rand_core" version = "0.6.4" @@ -743,6 +778,15 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -781,6 +825,15 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + [[package]] name = "ryu" version = "1.0.15" @@ -924,6 +977,16 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempdir" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" +dependencies = [ + "rand 0.4.6", + "remove_dir_all", +] + [[package]] name = "tera" version = "1.19.1" @@ -938,7 +1001,7 @@ dependencies = [ "percent-encoding", "pest", "pest_derive", - "rand", + "rand 0.8.5", "regex", "serde", "serde_json", diff --git a/rust-tooling/ci-tests/Cargo.toml b/rust-tooling/ci-tests/Cargo.toml index 198ec2fe7..bc9621aa5 100644 --- a/rust-tooling/ci-tests/Cargo.toml +++ b/rust-tooling/ci-tests/Cargo.toml @@ -14,4 +14,5 @@ glob = "0.3.1" ignore = "0.4.20" models = { version = "0.1.0", path = "../models" } serde_json = "1.0.108" +tempdir = "0.3.7" utils = { version = "0.1.0", path = "../utils" } diff --git a/rust-tooling/ci-tests/tests/stubs_are_warning_free.rs b/rust-tooling/ci-tests/tests/stubs_are_warning_free.rs new file mode 100644 index 000000000..f13df7675 --- /dev/null +++ b/rust-tooling/ci-tests/tests/stubs_are_warning_free.rs @@ -0,0 +1,83 @@ +use std::process::{Command, Stdio}; + +use glob::glob; + +#[test] +fn stubs_are_warning_free() { + utils::fs::cd_into_repo_root(); + + let temp_dir = tempdir::TempDir::new("exercism-rust").unwrap(); + + let mut handles = vec![]; + + for manifest in glob("exercises/*/*/Cargo.toml") + .unwrap() + .map(Result::unwrap) + { + if std::fs::read_to_string(&manifest.parent().unwrap().join(".meta").join("config.json")) + .unwrap() + .contains("allowed-to-not-compile") + { + continue; + } + let slug = manifest + .parent() + .unwrap() + .file_name() + .unwrap() + .to_str() + .unwrap(); + let handle = Command::new("cargo") + .args([ + "clippy", + "--quiet", + "--manifest-path", + &manifest.display().to_string(), + "--target-dir", + &temp_dir.path().join(slug).display().to_string(), + "--", + // necessary for clippy to return a non-zero exit code + // if it finds warnings + "--deny", + "warnings", + ]) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + + handles.push((slug.to_string(), handle)); + } + + let mut log = String::new(); + + for (slug, handle) in handles { + let output = handle.wait_with_output().unwrap(); + + if output.status.success() { + continue; + } + let stderr = String::from_utf8(output.stderr).unwrap(); + log.push_str(&format!( + "\ +################################################################ +################ +################ {slug} + +{stderr} + +" + )); + } + + if !log.is_empty() { + std::fs::write("clippy.log", &log).expect("should write clippy.log"); + } + assert!( + log.is_empty(), + " + ╔═════════════════════════════════════════╗ + ║ clippy found warnings, check clippy.log ║ + ╚═════════════════════════════════════════╝ + " + ); +} From f3e544c6fd0a3d66d75456f4748d71ef54299560 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 19 Dec 2023 13:19:57 +0100 Subject: [PATCH 225/436] Sync pig-latin with problem-specifications (#1834) --- .../pig-latin/.meta/test_template.tera | 14 ++ exercises/practice/pig-latin/.meta/tests.toml | 79 ++++++++++- .../practice/pig-latin/tests/pig-latin.rs | 126 ++++++++++++++---- 3 files changed, 189 insertions(+), 30 deletions(-) create mode 100644 exercises/practice/pig-latin/.meta/test_template.tera diff --git a/exercises/practice/pig-latin/.meta/test_template.tera b/exercises/practice/pig-latin/.meta/test_template.tera new file mode 100644 index 000000000..f809b6247 --- /dev/null +++ b/exercises/practice/pig-latin/.meta/test_template.tera @@ -0,0 +1,14 @@ +use {{ crate_name }}::*; + +{% for test in cases %} +#[test] +{% if loop.index != 1 -%} +#[ignore] +{% endif -%} +fn {{ test.description | slugify | replace(from="-", to="_") }}() { + let input = {{ test.input.phrase | json_encode() }}; + let output = {{ fn_names[0] }}(input); + let expected = {{ test.expected | json_encode() }}; + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/pig-latin/.meta/tests.toml b/exercises/practice/pig-latin/.meta/tests.toml index be690e975..c29168c5e 100644 --- a/exercises/practice/pig-latin/.meta/tests.toml +++ b/exercises/practice/pig-latin/.meta/tests.toml @@ -1,3 +1,76 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[11567f84-e8c6-4918-aedb-435f0b73db57] +description = "ay is added to words that start with vowels -> word beginning with a" + +[f623f581-bc59-4f45-9032-90c3ca9d2d90] +description = "ay is added to words that start with vowels -> word beginning with e" + +[7dcb08b3-23a6-4e8a-b9aa-d4e859450d58] +description = "ay is added to words that start with vowels -> word beginning with i" + +[0e5c3bff-266d-41c8-909f-364e4d16e09c] +description = "ay is added to words that start with vowels -> word beginning with o" + +[614ba363-ca3c-4e96-ab09-c7320799723c] +description = "ay is added to words that start with vowels -> word beginning with u" + +[bf2538c6-69eb-4fa7-a494-5a3fec911326] +description = "ay is added to words that start with vowels -> word beginning with a vowel and followed by a qu" + +[e5be8a01-2d8a-45eb-abb4-3fcc9582a303] +description = "first letter and ay are moved to the end of words that start with consonants -> word beginning with p" + +[d36d1e13-a7ed-464d-a282-8820cb2261ce] +description = "first letter and ay are moved to the end of words that start with consonants -> word beginning with k" + +[d838b56f-0a89-4c90-b326-f16ff4e1dddc] +description = "first letter and ay are moved to the end of words that start with consonants -> word beginning with x" + +[bce94a7a-a94e-4e2b-80f4-b2bb02e40f71] +description = "first letter and ay are moved to the end of words that start with consonants -> word beginning with q without a following u" + +[c01e049a-e3e2-451c-bf8e-e2abb7e438b8] +description = "some letter clusters are treated like a single consonant -> word beginning with ch" + +[9ba1669e-c43f-4b93-837a-cfc731fd1425] +description = "some letter clusters are treated like a single consonant -> word beginning with qu" + +[92e82277-d5e4-43d7-8dd3-3a3b316c41f7] +description = "some letter clusters are treated like a single consonant -> word beginning with qu and a preceding consonant" + +[79ae4248-3499-4d5b-af46-5cb05fa073ac] +description = "some letter clusters are treated like a single consonant -> word beginning with th" + +[e0b3ae65-f508-4de3-8999-19c2f8e243e1] +description = "some letter clusters are treated like a single consonant -> word beginning with thr" + +[20bc19f9-5a35-4341-9d69-1627d6ee6b43] +description = "some letter clusters are treated like a single consonant -> word beginning with sch" + +[54b796cb-613d-4509-8c82-8fbf8fc0af9e] +description = "some letter clusters are treated like a single vowel -> word beginning with yt" + +[8c37c5e1-872e-4630-ba6e-d20a959b67f6] +description = "some letter clusters are treated like a single vowel -> word beginning with xr" + +[a4a36d33-96f3-422c-a233-d4021460ff00] +description = "position of y in a word determines if it is a consonant or a vowel -> y is treated like a consonant at the beginning of a word" + +[adc90017-1a12-4100-b595-e346105042c7] +description = "position of y in a word determines if it is a consonant or a vowel -> y is treated like a vowel at the end of a consonant cluster" + +[29b4ca3d-efe5-4a95-9a54-8467f2e5e59a] +description = "position of y in a word determines if it is a consonant or a vowel -> y as second letter in two letter word" + +[44616581-5ce3-4a81-82d0-40c7ab13d2cf] +description = "phrases are translated -> a whole phrase" diff --git a/exercises/practice/pig-latin/tests/pig-latin.rs b/exercises/practice/pig-latin/tests/pig-latin.rs index 5ebd5e85d..c1acf29e3 100644 --- a/exercises/practice/pig-latin/tests/pig-latin.rs +++ b/exercises/practice/pig-latin/tests/pig-latin.rs @@ -1,126 +1,198 @@ -use pig_latin as pl; +use pig_latin::*; #[test] fn word_beginning_with_a() { - assert_eq!(pl::translate("apple"), "appleay"); + let input = "apple"; + let output = translate(input); + let expected = "appleay"; + assert_eq!(output, expected); } #[test] #[ignore] fn word_beginning_with_e() { - assert_eq!(pl::translate("ear"), "earay"); + let input = "ear"; + let output = translate(input); + let expected = "earay"; + assert_eq!(output, expected); } #[test] #[ignore] fn word_beginning_with_i() { - assert_eq!(pl::translate("igloo"), "iglooay"); + let input = "igloo"; + let output = translate(input); + let expected = "iglooay"; + assert_eq!(output, expected); } #[test] #[ignore] fn word_beginning_with_o() { - assert_eq!(pl::translate("object"), "objectay"); + let input = "object"; + let output = translate(input); + let expected = "objectay"; + assert_eq!(output, expected); } #[test] #[ignore] fn word_beginning_with_u() { - assert_eq!(pl::translate("under"), "underay"); + let input = "under"; + let output = translate(input); + let expected = "underay"; + assert_eq!(output, expected); } #[test] #[ignore] fn word_beginning_with_a_vowel_and_followed_by_a_qu() { - assert_eq!(pl::translate("equal"), "equalay"); + let input = "equal"; + let output = translate(input); + let expected = "equalay"; + assert_eq!(output, expected); } #[test] #[ignore] fn word_beginning_with_p() { - assert_eq!(pl::translate("pig"), "igpay"); + let input = "pig"; + let output = translate(input); + let expected = "igpay"; + assert_eq!(output, expected); } #[test] #[ignore] fn word_beginning_with_k() { - assert_eq!(pl::translate("koala"), "oalakay"); -} - -#[test] -#[ignore] -fn word_beginning_with_y() { - assert_eq!(pl::translate("yellow"), "ellowyay"); + let input = "koala"; + let output = translate(input); + let expected = "oalakay"; + assert_eq!(output, expected); } #[test] #[ignore] fn word_beginning_with_x() { - assert_eq!(pl::translate("xenon"), "enonxay"); + let input = "xenon"; + let output = translate(input); + let expected = "enonxay"; + assert_eq!(output, expected); } #[test] #[ignore] fn word_beginning_with_q_without_a_following_u() { - assert_eq!(pl::translate("qat"), "atqay"); + let input = "qat"; + let output = translate(input); + let expected = "atqay"; + assert_eq!(output, expected); } #[test] #[ignore] fn word_beginning_with_ch() { - assert_eq!(pl::translate("chair"), "airchay"); + let input = "chair"; + let output = translate(input); + let expected = "airchay"; + assert_eq!(output, expected); } #[test] #[ignore] fn word_beginning_with_qu() { - assert_eq!(pl::translate("queen"), "eenquay"); + let input = "queen"; + let output = translate(input); + let expected = "eenquay"; + assert_eq!(output, expected); } #[test] #[ignore] fn word_beginning_with_qu_and_a_preceding_consonant() { - assert_eq!(pl::translate("square"), "aresquay"); + let input = "square"; + let output = translate(input); + let expected = "aresquay"; + assert_eq!(output, expected); } #[test] #[ignore] fn word_beginning_with_th() { - assert_eq!(pl::translate("therapy"), "erapythay"); + let input = "therapy"; + let output = translate(input); + let expected = "erapythay"; + assert_eq!(output, expected); } #[test] #[ignore] fn word_beginning_with_thr() { - assert_eq!(pl::translate("thrush"), "ushthray"); + let input = "thrush"; + let output = translate(input); + let expected = "ushthray"; + assert_eq!(output, expected); } #[test] #[ignore] fn word_beginning_with_sch() { - assert_eq!(pl::translate("school"), "oolschay"); + let input = "school"; + let output = translate(input); + let expected = "oolschay"; + assert_eq!(output, expected); } #[test] #[ignore] fn word_beginning_with_yt() { - assert_eq!(pl::translate("yttria"), "yttriaay"); + let input = "yttria"; + let output = translate(input); + let expected = "yttriaay"; + assert_eq!(output, expected); } #[test] #[ignore] fn word_beginning_with_xr() { - assert_eq!(pl::translate("xray"), "xrayay"); + let input = "xray"; + let output = translate(input); + let expected = "xrayay"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn y_is_treated_like_a_consonant_at_the_beginning_of_a_word() { + let input = "yellow"; + let output = translate(input); + let expected = "ellowyay"; + assert_eq!(output, expected); } #[test] #[ignore] fn y_is_treated_like_a_vowel_at_the_end_of_a_consonant_cluster() { - assert_eq!(pl::translate("rhythm"), "ythmrhay"); + let input = "rhythm"; + let output = translate(input); + let expected = "ythmrhay"; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn y_as_second_letter_in_two_letter_word() { + let input = "my"; + let output = translate(input); + let expected = "ymay"; + assert_eq!(output, expected); } #[test] #[ignore] fn a_whole_phrase() { - assert_eq!(pl::translate("quick fast run"), "ickquay astfay unray"); + let input = "quick fast run"; + let output = translate(input); + let expected = "ickquay astfay unray"; + assert_eq!(output, expected); } From f75c6f890d8ff9f44c73b32635efd5f2739a9b98 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 19 Dec 2023 13:21:31 +0100 Subject: [PATCH 226/436] Add documentation about IDE support (#1827) --- docs/INSTALLATION.md | 7 +++++++ exercises/shared/.docs/debug.md | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md index 8c86bce10..5625561c6 100644 --- a/docs/INSTALLATION.md +++ b/docs/INSTALLATION.md @@ -3,4 +3,11 @@ To get the recommended installation instructions for your platform, head over to [the official Rust website]. +Having the toolchain installed is not everything! +IDE support is a huge productivity boost that most developers have come to expect. +If you are using Visual Studio Code, check out its [documentation for Rust support][vscode]. +We also recommend you whole-heartedly to enable [linting with clippy][vscode-linting]! + [the official Rust website]: https://www.rust-lang.org/tools/install +[vscode]: https://code.visualstudio.com/docs/languages/rust +[vscode-linting]: https://code.visualstudio.com/docs/languages/rust#_linting diff --git a/exercises/shared/.docs/debug.md b/exercises/shared/.docs/debug.md index 8f7f697b4..cdd663089 100644 --- a/exercises/shared/.docs/debug.md +++ b/exercises/shared/.docs/debug.md @@ -8,7 +8,7 @@ You can write to standard out using `println!`: println!("Debug message"); ``` -Use braces `{}` to print the value of a variable or expressions: +Use braces `{}` to print the value of a variable or expression: ```rust let secret_number = 4321; From f730c67846b8dcf006246a95a8184c6cf4ab0e12 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 19 Dec 2023 13:22:26 +0100 Subject: [PATCH 227/436] Sync poker with problem-specifications (#1833) --- .../practice/poker/.meta/test_template.tera | 15 + exercises/practice/poker/.meta/tests.toml | 134 +++++++- exercises/practice/poker/tests/poker.rs | 314 +++++++++++------- 3 files changed, 342 insertions(+), 121 deletions(-) create mode 100644 exercises/practice/poker/.meta/test_template.tera diff --git a/exercises/practice/poker/.meta/test_template.tera b/exercises/practice/poker/.meta/test_template.tera new file mode 100644 index 000000000..91551496f --- /dev/null +++ b/exercises/practice/poker/.meta/test_template.tera @@ -0,0 +1,15 @@ +use {{ crate_name }}::*; +use std::collections::HashSet; + +{% for test in cases %} +#[test] +{% if loop.index != 1 -%} +#[ignore] +{% endif -%} +fn {{ test.description | slugify | replace(from="-", to="_") }}() { + let input = &{{ test.input.hands | json_encode() }}; + let output = {{ fn_names[0] }}(input).into_iter().collect::>(); + let expected = {{ test.expected | json_encode() }}.into_iter().collect::>(); + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/poker/.meta/tests.toml b/exercises/practice/poker/.meta/tests.toml index be690e975..2e654ef63 100644 --- a/exercises/practice/poker/.meta/tests.toml +++ b/exercises/practice/poker/.meta/tests.toml @@ -1,3 +1,131 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[161f485e-39c2-4012-84cf-bec0c755b66c] +description = "single hand always wins" + +[370ac23a-a00f-48a9-9965-6f3fb595cf45] +description = "highest card out of all hands wins" + +[d94ad5a7-17df-484b-9932-c64fc26cff52] +description = "a tie has multiple winners" + +[61ed83a9-cfaa-40a5-942a-51f52f0a8725] +description = "multiple hands with the same high cards, tie compares next highest ranked, down to last card" + +[da01becd-f5b0-4342-b7f3-1318191d0580] +description = "winning high card hand also has the lowest card" + +[f7175a89-34ff-44de-b3d7-f6fd97d1fca4] +description = "one pair beats high card" + +[e114fd41-a301-4111-a9e7-5a7f72a76561] +description = "highest pair wins" + +[b3acd3a7-f9fa-4647-85ab-e0a9e07d1365] +description = "both hands have the same pair, high card wins" + +[935bb4dc-a622-4400-97fa-86e7d06b1f76] +description = "two pairs beats one pair" + +[c8aeafe1-6e3d-4711-a6de-5161deca91fd] +description = "both hands have two pairs, highest ranked pair wins" + +[88abe1ba-7ad7-40f3-847e-0a26f8e46a60] +description = "both hands have two pairs, with the same highest ranked pair, tie goes to low pair" + +[15a7a315-0577-47a3-9981-d6cf8e6f387b] +description = "both hands have two identically ranked pairs, tie goes to remaining card (kicker)" + +[f761e21b-2560-4774-a02a-b3e9366a51ce] +description = "both hands have two pairs that add to the same value, win goes to highest pair" + +[fc6277ac-94ac-4078-8d39-9d441bc7a79e] +description = "two pairs first ranked by largest pair" + +[21e9f1e6-2d72-49a1-a930-228e5e0195dc] +description = "three of a kind beats two pair" + +[c2fffd1f-c287-480f-bf2d-9628e63bbcc3] +description = "both hands have three of a kind, tie goes to highest ranked triplet" + +[eb856cc2-481c-4b0d-9835-4d75d07a5d9d] +description = "with multiple decks, two players can have same three of a kind, ties go to highest remaining cards" +include = false + +[26a4a7d4-34a2-4f18-90b4-4a8dd35d2bb1] +description = "with multiple decks, two players can have same three of a kind, ties go to highest remaining cards" +reimplements = "eb856cc2-481c-4b0d-9835-4d75d07a5d9d" + +[a858c5d9-2f28-48e7-9980-b7fa04060a60] +description = "a straight beats three of a kind" + +[73c9c756-e63e-4b01-a88d-0d4491a7a0e3] +description = "aces can end a straight (10 J Q K A)" + +[76856b0d-35cd-49ce-a492-fe5db53abc02] +description = "aces can start a straight (A 2 3 4 5)" + +[e214b7df-dcba-45d3-a2e5-342d8c46c286] +description = "aces cannot be in the middle of a straight (Q K A 2 3)" + +[6980c612-bbff-4914-b17a-b044e4e69ea1] +description = "both hands with a straight, tie goes to highest ranked card" + +[5135675c-c2fc-4e21-9ba3-af77a32e9ba4] +description = "even though an ace is usually high, a 5-high straight is the lowest-scoring straight" + +[c601b5e6-e1df-4ade-b444-b60ce13b2571] +description = "flush beats a straight" + +[4d90261d-251c-49bd-a468-896bf10133de] +description = "both hands have a flush, tie goes to high card, down to the last one if necessary" +include = false + +[e04137c5-c19a-4dfc-97a1-9dfe9baaa2ff] +description = "both hands have a flush, tie goes to high card, down to the last one if necessary" +reimplements = "4d90261d-251c-49bd-a468-896bf10133de" + +[3a19361d-8974-455c-82e5-f7152f5dba7c] +description = "full house beats a flush" + +[eb73d0e6-b66c-4f0f-b8ba-bf96bc0a67f0] +description = "both hands have a full house, tie goes to highest-ranked triplet" + +[34b51168-1e43-4c0d-9b32-e356159b4d5d] +description = "with multiple decks, both hands have a full house with the same triplet, tie goes to the pair" + +[d61e9e99-883b-4f99-b021-18f0ae50c5f4] +description = "four of a kind beats a full house" + +[2e1c8c63-e0cb-4214-a01b-91954490d2fe] +description = "both hands have four of a kind, tie goes to high quad" + +[892ca75d-5474-495d-9f64-a6ce2dcdb7e1] +description = "with multiple decks, both hands with identical four of a kind, tie determined by kicker" + +[923bd910-dc7b-4f7d-a330-8b42ec10a3ac] +description = "straight flush beats four of a kind" + +[d9629e22-c943-460b-a951-2134d1b43346] +description = "aces can end a straight flush (10 J Q K A)" + +[05d5ede9-64a5-4678-b8ae-cf4c595dc824] +description = "aces can start a straight flush (A 2 3 4 5)" + +[ad655466-6d04-49e8-a50c-0043c3ac18ff] +description = "aces cannot be in the middle of a straight flush (Q K A 2 3)" + +[d0927f70-5aec-43db-aed8-1cbd1b6ee9ad] +description = "both hands have a straight flush, tie goes to highest-ranked card" + +[be620e09-0397-497b-ac37-d1d7a4464cfc] +description = "even though an ace is usually high, a 5-high straight flush is the lowest-scoring straight flush" diff --git a/exercises/practice/poker/tests/poker.rs b/exercises/practice/poker/tests/poker.rs index 44ed780e6..f15029ec7 100644 --- a/exercises/practice/poker/tests/poker.rs +++ b/exercises/practice/poker/tests/poker.rs @@ -1,265 +1,343 @@ -use poker::winning_hands; +use poker::*; use std::collections::HashSet; -fn hs_from<'a>(input: &[&'a str]) -> HashSet<&'a str> { - let mut hs = HashSet::new(); - for item in input.iter() { - hs.insert(*item); - } - hs -} - -/// Test that the expected output is produced from the given input -/// using the `winning_hands` function. -/// -/// Note that the output can be in any order. Here, we use a HashSet to -/// abstract away the order of outputs. -fn test(input: &[&str], expected: &[&str]) { - assert_eq!(hs_from(&winning_hands(input)), hs_from(expected)) -} - #[test] fn single_hand_always_wins() { - test(&["4S 5S 7H 8D JC"], &["4S 5S 7H 8D JC"]) + let input = &["4S 5S 7H 8D JC"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["4S 5S 7H 8D JC"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn duplicate_hands_always_tie() { - let input = &["3S 4S 5D 6H JH", "3S 4S 5D 6H JH", "3S 4S 5D 6H JH"]; - assert_eq!(&winning_hands(input), input) +fn highest_card_out_of_all_hands_wins() { + let input = &["4D 5S 6S 8D 3C", "2S 4C 7S 9H 10H", "3S 4S 5D 6H JH"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["3S 4S 5D 6H JH"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn highest_card_of_all_hands_wins() { - test( - &["4D 5S 6S 8D 3C", "2S 4C 7S 9H 10H", "3S 4S 5D 6H JH"], - &["3S 4S 5D 6H JH"], - ) +fn a_tie_has_multiple_winners() { + let input = &[ + "4D 5S 6S 8D 3C", + "2S 4C 7S 9H 10H", + "3S 4S 5D 6H JH", + "3H 4H 5C 6C JD", + ]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["3S 4S 5D 6H JH", "3H 4H 5C 6C JD"] + .into_iter() + .collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn a_tie_has_multiple_winners() { - test( - &[ - "4D 5S 6S 8D 3C", - "2S 4C 7S 9H 10H", - "3S 4S 5D 6H JH", - "3H 4H 5C 6C JD", - ], - &["3S 4S 5D 6H JH", "3H 4H 5C 6C JD"], - ) +fn multiple_hands_with_the_same_high_cards_tie_compares_next_highest_ranked_down_to_last_card() { + let input = &["3S 5H 6S 8D 7H", "2S 5D 6D 8C 7S"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["3S 5H 6S 8D 7H"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn high_card_can_be_low_card_in_an_otherwise_tie() { - // multiple hands with the same high cards, tie compares next highest ranked, - // down to last card - test(&["3S 5H 6S 8D 7H", "2S 5D 6D 8C 7S"], &["3S 5H 6S 8D 7H"]) +fn winning_high_card_hand_also_has_the_lowest_card() { + let input = &["2S 5H 6S 8D 7H", "3S 4D 6D 8C 7S"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["2S 5H 6S 8D 7H"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] fn one_pair_beats_high_card() { - test(&["4S 5H 6C 8D KH", "2S 4H 6S 4D JH"], &["2S 4H 6S 4D JH"]) + let input = &["4S 5H 6C 8D KH", "2S 4H 6S 4D JH"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["2S 4H 6S 4D JH"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] fn highest_pair_wins() { - test(&["4S 2H 6S 2D JH", "2S 4H 6C 4D JD"], &["2S 4H 6C 4D JD"]) + let input = &["4S 2H 6S 2D JH", "2S 4H 6C 4D JD"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["2S 4H 6C 4D JD"].into_iter().collect::>(); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn both_hands_have_the_same_pair_high_card_wins() { + let input = &["4H 4S AH JC 3D", "4C 4D AS 5D 6C"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["4H 4S AH JC 3D"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] fn two_pairs_beats_one_pair() { - test(&["2S 8H 6S 8D JH", "4S 5H 4C 8C 5C"], &["4S 5H 4C 8C 5C"]) + let input = &["2S 8H 6S 8D JH", "4S 5H 4C 8C 5C"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["4S 5H 4C 8C 5C"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn two_pair_ranks() { - // both hands have two pairs, highest ranked pair wins - test(&["2S 8H 2D 8D 3H", "4S 5H 4C 8S 5D"], &["2S 8H 2D 8D 3H"]) +fn both_hands_have_two_pairs_highest_ranked_pair_wins() { + let input = &["2S 8H 2D 8D 3H", "4S 5H 4C 8S 5D"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["2S 8H 2D 8D 3H"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn two_pairs_second_pair_cascade() { - // both hands have two pairs, with the same highest ranked pair, - // tie goes to low pair - test(&["2S QS 2C QD JH", "JD QH JS 8D QC"], &["JD QH JS 8D QC"]) +fn both_hands_have_two_pairs_with_the_same_highest_ranked_pair_tie_goes_to_low_pair() { + let input = &["2S QS 2C QD JH", "JD QH JS 8D QC"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["JD QH JS 8D QC"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn two_pairs_last_card_cascade() { - // both hands have two identically ranked pairs, - // tie goes to remaining card (kicker) - test(&["JD QH JS 8D QC", "JS QS JC 2D QD"], &["JD QH JS 8D QC"]) +fn both_hands_have_two_identically_ranked_pairs_tie_goes_to_remaining_card_kicker() { + let input = &["JD QH JS 8D QC", "JS QS JC 2D QD"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["JD QH JS 8D QC"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn three_of_a_kind_beats_two_pair() { - test(&["2S 8H 2H 8D JH", "4S 5H 4C 8S 4H"], &["4S 5H 4C 8S 4H"]) +fn both_hands_have_two_pairs_that_add_to_the_same_value_win_goes_to_highest_pair() { + let input = &["6S 6H 3S 3H AS", "7H 7S 2H 2S AC"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["7H 7S 2H 2S AC"].into_iter().collect::>(); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn two_pairs_first_ranked_by_largest_pair() { + let input = &["5C 2S 5S 4H 4C", "6S 2S 6H 7C 2C"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["6S 2S 6H 7C 2C"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn three_of_a_kind_ranks() { - //both hands have three of a kind, tie goes to highest ranked triplet - test(&["2S 2H 2C 8D JH", "4S AH AS 8C AD"], &["4S AH AS 8C AD"]) +fn three_of_a_kind_beats_two_pair() { + let input = &["2S 8H 2H 8D JH", "4S 5H 4C 8S 4H"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["4S 5H 4C 8S 4H"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn low_three_of_a_kind_beats_high_two_pair() { - test(&["2H 2D 2C 8H 5H", "AS AC KS KC 6S"], &["2H 2D 2C 8H 5H"]) +fn both_hands_have_three_of_a_kind_tie_goes_to_highest_ranked_triplet() { + let input = &["2S 2H 2C 8D JH", "4S AH AS 8C AD"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["4S AH AS 8C AD"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn three_of_a_kind_cascade_ranks() { - // with multiple decks, two players can have same three of a kind, - // ties go to highest remaining cards - test(&["4S AH AS 7C AD", "4S AH AS 8C AD"], &["4S AH AS 8C AD"]) +fn with_multiple_decks_two_players_can_have_same_three_of_a_kind_ties_go_to_highest_remaining_cards( +) { + let input = &["5S AH AS 7C AD", "4S AH AS 8C AD"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["4S AH AS 8C AD"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn straight_beats_three_of_a_kind() { - test(&["4S 5H 4C 8D 4H", "3S 4D 2S 6D 5C"], &["3S 4D 2S 6D 5C"]) +fn a_straight_beats_three_of_a_kind() { + let input = &["4S 5H 4C 8D 4H", "3S 4D 2S 6D 5C"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["3S 4D 2S 6D 5C"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn aces_can_end_a_straight_high() { - // aces can end a straight (10 J Q K A) - test(&["4S 5H 4C 8D 4H", "10D JH QS KD AC"], &["10D JH QS KD AC"]) +fn aces_can_end_a_straight_10_j_q_k_a() { + let input = &["4S 5H 4C 8D 4H", "10D JH QS KD AC"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["10D JH QS KD AC"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn aces_can_start_a_straight_low() { - // aces can start a straight (A 2 3 4 5) - test(&["4S 5H 4C 8D 4H", "4D AH 3S 2D 5C"], &["4D AH 3S 2D 5C"]) +fn aces_can_start_a_straight_a_2_3_4_5() { + let input = &["4S 5H 4C 8D 4H", "4D AH 3S 2D 5C"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["4D AH 3S 2D 5C"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn no_ace_in_middle_of_straight() { - // aces cannot be in the middle of a straight (Q K A 2 3) - test(&["2C 3D 7H 5H 2S", "QS KH AC 2D 3S"], &["2C 3D 7H 5H 2S"]) +fn aces_cannot_be_in_the_middle_of_a_straight_q_k_a_2_3() { + let input = &["2C 3D 7H 5H 2S", "QS KH AC 2D 3S"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["2C 3D 7H 5H 2S"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn straight_ranks() { - // both hands with a straight, tie goes to highest ranked card - test(&["4S 6C 7S 8D 5H", "5S 7H 8S 9D 6H"], &["5S 7H 8S 9D 6H"]) +fn both_hands_with_a_straight_tie_goes_to_highest_ranked_card() { + let input = &["4S 6C 7S 8D 5H", "5S 7H 8S 9D 6H"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["5S 7H 8S 9D 6H"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn straight_scoring() { - // even though an ace is usually high, a 5-high straight is the lowest-scoring straight - test(&["2H 3C 4D 5D 6H", "4S AH 3S 2D 5H"], &["2H 3C 4D 5D 6H"]) +fn even_though_an_ace_is_usually_high_a_5_high_straight_is_the_lowest_scoring_straight() { + let input = &["2H 3C 4D 5D 6H", "4S AH 3S 2D 5H"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["2H 3C 4D 5D 6H"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] fn flush_beats_a_straight() { - test(&["4C 6H 7D 8D 5H", "2S 4S 5S 6S 7S"], &["2S 4S 5S 6S 7S"]) + let input = &["4C 6H 7D 8D 5H", "2S 4S 5S 6S 7S"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["2S 4S 5S 6S 7S"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn flush_cascade() { - // both hands have a flush, tie goes to high card, down to the last one if necessary - test(&["4H 7H 8H 9H 6H", "2S 4S 5S 6S 7S"], &["4H 7H 8H 9H 6H"]) +fn both_hands_have_a_flush_tie_goes_to_high_card_down_to_the_last_one_if_necessary() { + let input = &["2H 7H 8H 9H 6H", "3S 5S 6S 7S 8S"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["2H 7H 8H 9H 6H"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] fn full_house_beats_a_flush() { - test(&["3H 6H 7H 8H 5H", "4S 5C 4C 5D 4H"], &["4S 5C 4C 5D 4H"]) + let input = &["3H 6H 7H 8H 5H", "4S 5H 4C 5D 4H"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["4S 5H 4C 5D 4H"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn full_house_ranks() { - // both hands have a full house, tie goes to highest-ranked triplet - test(&["4H 4S 4D 9S 9D", "5H 5S 5D 8S 8D"], &["5H 5S 5D 8S 8D"]) +fn both_hands_have_a_full_house_tie_goes_to_highest_ranked_triplet() { + let input = &["4H 4S 4D 9S 9D", "5H 5S 5D 8S 8D"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["5H 5S 5D 8S 8D"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn full_house_cascade() { - // with multiple decks, both hands have a full house with the same triplet, tie goes to the pair - test(&["5H 5S 5D 9S 9D", "5H 5S 5D 8S 8D"], &["5H 5S 5D 9S 9D"]) +fn with_multiple_decks_both_hands_have_a_full_house_with_the_same_triplet_tie_goes_to_the_pair() { + let input = &["5H 5S 5D 9S 9D", "5H 5S 5D 8S 8D"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["5H 5S 5D 9S 9D"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn four_of_a_kind_beats_full_house() { - test(&["4S 5H 4D 5D 4H", "3S 3H 2S 3D 3C"], &["3S 3H 2S 3D 3C"]) +fn four_of_a_kind_beats_a_full_house() { + let input = &["4S 5H 4D 5D 4H", "3S 3H 2S 3D 3C"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["3S 3H 2S 3D 3C"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn four_of_a_kind_ranks() { - // both hands have four of a kind, tie goes to high quad - test(&["2S 2H 2C 8D 2D", "4S 5H 5S 5D 5C"], &["4S 5H 5S 5D 5C"]) +fn both_hands_have_four_of_a_kind_tie_goes_to_high_quad() { + let input = &["2S 2H 2C 8D 2D", "4S 5H 5S 5D 5C"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["4S 5H 5S 5D 5C"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn four_of_a_kind_cascade() { - // with multiple decks, both hands with identical four of a kind, tie determined by kicker - test(&["3S 3H 2S 3D 3C", "3S 3H 4S 3D 3C"], &["3S 3H 4S 3D 3C"]) +fn with_multiple_decks_both_hands_with_identical_four_of_a_kind_tie_determined_by_kicker() { + let input = &["3S 3H 2S 3D 3C", "3S 3H 4S 3D 3C"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["3S 3H 4S 3D 3C"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] fn straight_flush_beats_four_of_a_kind() { - test(&["4S 5H 5S 5D 5C", "7S 8S 9S 6S 10S"], &["7S 8S 9S 6S 10S"]) + let input = &["4S 5H 5S 5D 5C", "7S 8S 9S 6S 10S"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["7S 8S 9S 6S 10S"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn aces_can_end_a_straight_flush_high() { - // aces can end a straight flush (10 J Q K A) - test(&["KC AH AS AD AC", "10C JC QC KC AC"], &["10C JC QC KC AC"]) +fn aces_can_end_a_straight_flush_10_j_q_k_a() { + let input = &["KC AH AS AD AC", "10C JC QC KC AC"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["10C JC QC KC AC"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn aces_can_start_a_straight_flush_low() { - // aces can start a straight flush (A 2 3 4 5) - test(&["KS AH AS AD AC", "4H AH 3H 2H 5H"], &["4H AH 3H 2H 5H"]) +fn aces_can_start_a_straight_flush_a_2_3_4_5() { + let input = &["KS AH AS AD AC", "4H AH 3H 2H 5H"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["4H AH 3H 2H 5H"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn no_ace_in_middle_of_straight_flush() { - // aces cannot be in the middle of a straight flush (Q K A 2 3) - test(&["2C AC QC 10C KC", "QH KH AH 2H 3H"], &["2C AC QC 10C KC"]) +fn aces_cannot_be_in_the_middle_of_a_straight_flush_q_k_a_2_3() { + let input = &["2C AC QC 10C KC", "QH KH AH 2H 3H"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["2C AC QC 10C KC"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn straight_flush_ranks() { - // both hands have a straight flush, tie goes to highest-ranked card - test(&["4H 6H 7H 8H 5H", "5S 7S 8S 9S 6S"], &["5S 7S 8S 9S 6S"]) +fn both_hands_have_a_straight_flush_tie_goes_to_highest_ranked_card() { + let input = &["4H 6H 7H 8H 5H", "5S 7S 8S 9S 6S"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["5S 7S 8S 9S 6S"].into_iter().collect::>(); + assert_eq!(output, expected); } #[test] #[ignore] -fn straight_flush_scoring() { - // even though an ace is usually high, a 5-high straight flush is the lowest-scoring straight flush - test(&["2H 3H 4H 5H 6H", "4D AD 3D 2D 5D"], &["2H 3H 4H 5H 6H"]) +fn even_though_an_ace_is_usually_high_a_5_high_straight_flush_is_the_lowest_scoring_straight_flush() +{ + let input = &["2H 3H 4H 5H 6H", "4D AD 3D 2D 5D"]; + let output = winning_hands(input).into_iter().collect::>(); + let expected = ["2H 3H 4H 5H 6H"].into_iter().collect::>(); + assert_eq!(output, expected); } From 0cece7a95131d2541dc59bfb14ec9fec423fdb7b Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 19 Dec 2023 13:43:41 +0100 Subject: [PATCH 228/436] Sync phone-number with problem-specifications (#1835) [no important files changed] --- .../phone-number/.meta/test_template.tera | 18 ++++ .../practice/phone-number/.meta/tests.toml | 87 ++++++++++++++++- .../phone-number/tests/phone-number.rs | 97 +++++++++++++------ 3 files changed, 169 insertions(+), 33 deletions(-) create mode 100644 exercises/practice/phone-number/.meta/test_template.tera diff --git a/exercises/practice/phone-number/.meta/test_template.tera b/exercises/practice/phone-number/.meta/test_template.tera new file mode 100644 index 000000000..98e6f74b1 --- /dev/null +++ b/exercises/practice/phone-number/.meta/test_template.tera @@ -0,0 +1,18 @@ +use {{ crate_name }}::*; + +{% for test in cases %} +#[test] +{% if loop.index != 1 -%} +#[ignore] +{% endif -%} +fn {{ test.description | slugify | replace(from="-", to="_") }}() { + let input = {{ test.input.phrase | json_encode() }}; + let output = {{ fn_names[0] }}(input); +{%- if test.expected is object %} + assert!(output.is_none()); +{% else %} + let expected = Some({{ test.expected | json_encode() }}.into()); + assert_eq!(output, expected); +{% endif -%} +} +{% endfor -%} diff --git a/exercises/practice/phone-number/.meta/tests.toml b/exercises/practice/phone-number/.meta/tests.toml index be690e975..24dbf07a7 100644 --- a/exercises/practice/phone-number/.meta/tests.toml +++ b/exercises/practice/phone-number/.meta/tests.toml @@ -1,3 +1,84 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[79666dce-e0f1-46de-95a1-563802913c35] +description = "cleans the number" + +[c360451f-549f-43e4-8aba-fdf6cb0bf83f] +description = "cleans numbers with dots" + +[08f94c34-9a37-46a2-a123-2a8e9727395d] +description = "cleans numbers with multiple spaces" + +[598d8432-0659-4019-a78b-1c6a73691d21] +description = "invalid when 9 digits" +include = false + +[2de74156-f646-42b5-8638-0ef1d8b58bc2] +description = "invalid when 9 digits" +reimplements = "598d8432-0659-4019-a78b-1c6a73691d21" + +[57061c72-07b5-431f-9766-d97da7c4399d] +description = "invalid when 11 digits does not start with a 1" + +[9962cbf3-97bb-4118-ba9b-38ff49c64430] +description = "valid when 11 digits and starting with 1" + +[fa724fbf-054c-4d91-95da-f65ab5b6dbca] +description = "valid when 11 digits and starting with 1 even with punctuation" + +[c6a5f007-895a-4fc5-90bc-a7e70f9b5cad] +description = "invalid when more than 11 digits" +include = false + +[4a1509b7-8953-4eec-981b-c483358ff531] +description = "invalid when more than 11 digits" +reimplements = "c6a5f007-895a-4fc5-90bc-a7e70f9b5cad" + +[63f38f37-53f6-4a5f-bd86-e9b404f10a60] +description = "invalid with letters" +include = false + +[eb8a1fc0-64e5-46d3-b0c6-33184208e28a] +description = "invalid with letters" +reimplements = "63f38f37-53f6-4a5f-bd86-e9b404f10a60" + +[4bd97d90-52fd-45d3-b0db-06ab95b1244e] +description = "invalid with punctuations" +include = false + +[065f6363-8394-4759-b080-e6c8c351dd1f] +description = "invalid with punctuations" +reimplements = "4bd97d90-52fd-45d3-b0db-06ab95b1244e" + +[d77d07f8-873c-4b17-8978-5f66139bf7d7] +description = "invalid if area code starts with 0" + +[c7485cfb-1e7b-4081-8e96-8cdb3b77f15e] +description = "invalid if area code starts with 1" + +[4d622293-6976-413d-b8bf-dd8a94d4e2ac] +description = "invalid if exchange code starts with 0" + +[4cef57b4-7d8e-43aa-8328-1e1b89001262] +description = "invalid if exchange code starts with 1" + +[9925b09c-1a0d-4960-a197-5d163cbe308c] +description = "invalid if area code starts with 0 on valid 11-digit number" + +[3f809d37-40f3-44b5-ad90-535838b1a816] +description = "invalid if area code starts with 1 on valid 11-digit number" + +[e08e5532-d621-40d4-b0cc-96c159276b65] +description = "invalid if exchange code starts with 0 on valid 11-digit number" + +[57b32f3d-696a-455c-8bf1-137b6d171cdf] +description = "invalid if exchange code starts with 1 on valid 11-digit number" diff --git a/exercises/practice/phone-number/tests/phone-number.rs b/exercises/practice/phone-number/tests/phone-number.rs index 61751c014..4ffc59931 100644 --- a/exercises/practice/phone-number/tests/phone-number.rs +++ b/exercises/practice/phone-number/tests/phone-number.rs @@ -1,112 +1,149 @@ -use phone_number as phone; - -fn process_clean_case(number: &str, expected: Option<&str>) { - assert_eq!(phone::number(number), expected.map(|x| x.to_string())); -} +use phone_number::*; #[test] fn cleans_the_number() { - process_clean_case("(223) 456-7890", Some("2234567890")); + let input = "(223) 456-7890"; + let output = number(input); + let expected = Some("2234567890".into()); + assert_eq!(output, expected); } #[test] #[ignore] fn cleans_numbers_with_dots() { - process_clean_case("223.456.7890", Some("2234567890")); + let input = "223.456.7890"; + let output = number(input); + let expected = Some("2234567890".into()); + assert_eq!(output, expected); } #[test] #[ignore] fn cleans_numbers_with_multiple_spaces() { - process_clean_case("223 456 7890 ", Some("2234567890")); + let input = "223 456 7890 "; + let output = number(input); + let expected = Some("2234567890".into()); + assert_eq!(output, expected); } #[test] #[ignore] fn invalid_when_9_digits() { - process_clean_case("123456789", None); + let input = "123456789"; + let output = number(input); + assert!(output.is_none()); } #[test] #[ignore] fn invalid_when_11_digits_does_not_start_with_a_1() { - process_clean_case("22234567890", None); + let input = "22234567890"; + let output = number(input); + assert!(output.is_none()); } #[test] #[ignore] fn valid_when_11_digits_and_starting_with_1() { - process_clean_case("12234567890", Some("2234567890")); + let input = "12234567890"; + let output = number(input); + let expected = Some("2234567890".into()); + assert_eq!(output, expected); } #[test] #[ignore] fn valid_when_11_digits_and_starting_with_1_even_with_punctuation() { - process_clean_case("+1 (223) 456-7890", Some("2234567890")); + let input = "+1 (223) 456-7890"; + let output = number(input); + let expected = Some("2234567890".into()); + assert_eq!(output, expected); } #[test] #[ignore] fn invalid_when_more_than_11_digits() { - process_clean_case("321234567890", None); + let input = "321234567890"; + let output = number(input); + assert!(output.is_none()); } #[test] #[ignore] fn invalid_with_letters() { - process_clean_case("123-abc-7890", None); + let input = "523-abc-7890"; + let output = number(input); + assert!(output.is_none()); } #[test] #[ignore] fn invalid_with_punctuations() { - process_clean_case("123-@:!-7890", None); + let input = "523-@:!-7890"; + let output = number(input); + assert!(output.is_none()); } #[test] #[ignore] -fn invalid_if_area_code_starts_with_1_on_valid_11digit_number() { - process_clean_case("1 (123) 456-7890", None); +fn invalid_if_area_code_starts_with_0() { + let input = "(023) 456-7890"; + let output = number(input); + assert!(output.is_none()); } #[test] #[ignore] -fn invalid_if_area_code_starts_with_0_on_valid_11digit_number() { - process_clean_case("1 (023) 456-7890", None); +fn invalid_if_area_code_starts_with_1() { + let input = "(123) 456-7890"; + let output = number(input); + assert!(output.is_none()); } #[test] #[ignore] -fn invalid_if_area_code_starts_with_1() { - process_clean_case("(123) 456-7890", None); +fn invalid_if_exchange_code_starts_with_0() { + let input = "(223) 056-7890"; + let output = number(input); + assert!(output.is_none()); } #[test] #[ignore] fn invalid_if_exchange_code_starts_with_1() { - process_clean_case("(223) 156-7890", None); + let input = "(223) 156-7890"; + let output = number(input); + assert!(output.is_none()); } #[test] #[ignore] -fn invalid_if_exchange_code_starts_with_0() { - process_clean_case("(223) 056-7890", None); +fn invalid_if_area_code_starts_with_0_on_valid_11_digit_number() { + let input = "1 (023) 456-7890"; + let output = number(input); + assert!(output.is_none()); } #[test] #[ignore] -fn invalid_if_exchange_code_starts_with_1_on_valid_11digit_number() { - process_clean_case("1 (223) 156-7890", None); +fn invalid_if_area_code_starts_with_1_on_valid_11_digit_number() { + let input = "1 (123) 456-7890"; + let output = number(input); + assert!(output.is_none()); } #[test] #[ignore] -fn invalid_if_exchange_code_starts_with_0_on_valid_11digit_number() { - process_clean_case("1 (223) 056-7890", None); +fn invalid_if_exchange_code_starts_with_0_on_valid_11_digit_number() { + let input = "1 (223) 056-7890"; + let output = number(input); + assert!(output.is_none()); } #[test] #[ignore] -fn invalid_if_area_code_starts_with_0() { - process_clean_case("(023) 456-7890", None); +fn invalid_if_exchange_code_starts_with_1_on_valid_11_digit_number() { + let input = "1 (223) 156-7890"; + let output = number(input); + assert!(output.is_none()); } From 87b3eda82492b076b78ff5e323489ad303ee85de Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 19 Dec 2023 13:43:56 +0100 Subject: [PATCH 229/436] Sync perfect-numbers with problem-specifications (#1836) --- .../perfect-numbers/.docs/instructions.md | 44 ++++-- .../perfect-numbers/.meta/test_template.tera | 18 +++ .../practice/perfect-numbers/.meta/tests.toml | 54 ++++++- .../perfect-numbers/tests/perfect-numbers.rs | 145 +++++++++++++----- 4 files changed, 205 insertions(+), 56 deletions(-) create mode 100644 exercises/practice/perfect-numbers/.meta/test_template.tera diff --git a/exercises/practice/perfect-numbers/.docs/instructions.md b/exercises/practice/perfect-numbers/.docs/instructions.md index 689a73c00..b2bc82ca3 100644 --- a/exercises/practice/perfect-numbers/.docs/instructions.md +++ b/exercises/practice/perfect-numbers/.docs/instructions.md @@ -2,22 +2,38 @@ Determine if a number is perfect, abundant, or deficient based on Nicomachus' (60 - 120 CE) classification scheme for positive integers. -The Greek mathematician [Nicomachus][nicomachus] devised a classification scheme for positive integers, identifying each as belonging uniquely to the categories of **perfect**, **abundant**, or **deficient** based on their [aliquot sum][aliquot-sum]. -The aliquot sum is defined as the sum of the factors of a number not including the number itself. +The Greek mathematician [Nicomachus][nicomachus] devised a classification scheme for positive integers, identifying each as belonging uniquely to the categories of [perfect](#perfect), [abundant](#abundant), or [deficient](#deficient) based on their [aliquot sum][aliquot-sum]. +The _aliquot sum_ is defined as the sum of the factors of a number not including the number itself. For example, the aliquot sum of `15` is `1 + 3 + 5 = 9`. -- **Perfect**: aliquot sum = number - - 6 is a perfect number because (1 + 2 + 3) = 6 - - 28 is a perfect number because (1 + 2 + 4 + 7 + 14) = 28 -- **Abundant**: aliquot sum > number - - 12 is an abundant number because (1 + 2 + 3 + 4 + 6) = 16 - - 24 is an abundant number because (1 + 2 + 3 + 4 + 6 + 8 + 12) = 36 -- **Deficient**: aliquot sum < number - - 8 is a deficient number because (1 + 2 + 4) = 7 - - Prime numbers are deficient - -Implement a way to determine whether a given number is **perfect**. -Depending on your language track, you may also need to implement a way to determine whether a given number is **abundant** or **deficient**. +## Perfect + +A number is perfect when it equals its aliquot sum. +For example: + +- `6` is a perfect number because `1 + 2 + 3 = 6` +- `28` is a perfect number because `1 + 2 + 4 + 7 + 14 = 28` + +## Abundant + +A number is abundant when it is less than its aliquot sum. +For example: + +- `12` is an abundant number because `1 + 2 + 3 + 4 + 6 = 16` +- `24` is an abundant number because `1 + 2 + 3 + 4 + 6 + 8 + 12 = 36` + +## Deficient + +A number is deficient when it is greater than its aliquot sum. +For example: + +- `8` is a deficient number because `1 + 2 + 4 = 7` +- Prime numbers are deficient + +## Task + +Implement a way to determine whether a given number is [perfect](#perfect). +Depending on your language track, you may also need to implement a way to determine whether a given number is [abundant](#abundant) or [deficient](#deficient). [nicomachus]: https://en.wikipedia.org/wiki/Nicomachus [aliquot-sum]: https://en.wikipedia.org/wiki/Aliquot_sum diff --git a/exercises/practice/perfect-numbers/.meta/test_template.tera b/exercises/practice/perfect-numbers/.meta/test_template.tera new file mode 100644 index 000000000..0f27541a4 --- /dev/null +++ b/exercises/practice/perfect-numbers/.meta/test_template.tera @@ -0,0 +1,18 @@ +use {{ crate_name }}::*; + +{% for test in cases %} +#[test] +{% if loop.index != 1 -%} +#[ignore] +{% endif -%} +fn {{ test.description | slugify | replace(from="-", to="_") }}() { + let input = {{ test.input.number | json_encode() }}; + let output = {{ fn_names[0] }}(input); +{%- if test.expected is object %} + assert!(output.is_none()); +{% else %} + let expected = Some(Classification::{{ test.expected | title }}); + assert_eq!(output, expected); +{% endif -%} +} +{% endfor -%} diff --git a/exercises/practice/perfect-numbers/.meta/tests.toml b/exercises/practice/perfect-numbers/.meta/tests.toml index be690e975..90976e329 100644 --- a/exercises/practice/perfect-numbers/.meta/tests.toml +++ b/exercises/practice/perfect-numbers/.meta/tests.toml @@ -1,3 +1,51 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[163e8e86-7bfd-4ee2-bd68-d083dc3381a3] +description = "Perfect numbers -> Smallest perfect number is classified correctly" + +[169a7854-0431-4ae0-9815-c3b6d967436d] +description = "Perfect numbers -> Medium perfect number is classified correctly" + +[ee3627c4-7b36-4245-ba7c-8727d585f402] +description = "Perfect numbers -> Large perfect number is classified correctly" + +[80ef7cf8-9ea8-49b9-8b2d-d9cb3db3ed7e] +description = "Abundant numbers -> Smallest abundant number is classified correctly" + +[3e300e0d-1a12-4f11-8c48-d1027165ab60] +description = "Abundant numbers -> Medium abundant number is classified correctly" + +[ec7792e6-8786-449c-b005-ce6dd89a772b] +description = "Abundant numbers -> Large abundant number is classified correctly" + +[e610fdc7-2b6e-43c3-a51c-b70fb37413ba] +description = "Deficient numbers -> Smallest prime deficient number is classified correctly" + +[0beb7f66-753a-443f-8075-ad7fbd9018f3] +description = "Deficient numbers -> Smallest non-prime deficient number is classified correctly" + +[1c802e45-b4c6-4962-93d7-1cad245821ef] +description = "Deficient numbers -> Medium deficient number is classified correctly" + +[47dd569f-9e5a-4a11-9a47-a4e91c8c28aa] +description = "Deficient numbers -> Large deficient number is classified correctly" + +[a696dec8-6147-4d68-afad-d38de5476a56] +description = "Deficient numbers -> Edge case (no factors other than itself) is classified correctly" + +[72445cee-660c-4d75-8506-6c40089dc302] +description = "Invalid inputs -> Zero is rejected (as it is not a positive integer)" + +[2d72ce2c-6802-49ac-8ece-c790ba3dae13] +description = "Invalid inputs -> Negative integer is rejected (as it is not a positive integer)" +include = false +comment = "the input type is u64, which prevents negative numbers at compile time" diff --git a/exercises/practice/perfect-numbers/tests/perfect-numbers.rs b/exercises/practice/perfect-numbers/tests/perfect-numbers.rs index 3c6590a08..fd29de0d9 100644 --- a/exercises/practice/perfect-numbers/tests/perfect-numbers.rs +++ b/exercises/practice/perfect-numbers/tests/perfect-numbers.rs @@ -1,40 +1,107 @@ -use perfect_numbers::{classify, Classification}; - -macro_rules! tests { - ($property_test_func:ident { - $( $(#[$attr:meta])* $test_name:ident( $( $param:expr ),* ); )+ - }) => { - $( - $(#[$attr])* - #[test] - fn $test_name() { - $property_test_func($( $param ),* ) - } - )+ - } -} - -fn test_classification(num: u64, result: Classification) { - assert_eq!(classify(num), Some(result)); -} - -#[test] -fn basic() { - assert_eq!(classify(0), None); -} - -tests! { - test_classification { - #[ignore] test_1(1, Classification::Deficient); - #[ignore] test_2(2, Classification::Deficient); - #[ignore] test_4(4, Classification::Deficient); - #[ignore] test_6(6, Classification::Perfect); - #[ignore] test_12(12, Classification::Abundant); - #[ignore] test_28(28, Classification::Perfect); - #[ignore] test_30(30, Classification::Abundant); - #[ignore] test_32(32, Classification::Deficient); - #[ignore] test_33550335(33_550_335, Classification::Abundant); - #[ignore] test_33550336(33_550_336, Classification::Perfect); - #[ignore] test_33550337(33_550_337, Classification::Deficient); - } +use perfect_numbers::*; + +#[test] +fn smallest_perfect_number_is_classified_correctly() { + let input = 6; + let output = classify(input); + let expected = Some(Classification::Perfect); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn medium_perfect_number_is_classified_correctly() { + let input = 28; + let output = classify(input); + let expected = Some(Classification::Perfect); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn large_perfect_number_is_classified_correctly() { + let input = 33550336; + let output = classify(input); + let expected = Some(Classification::Perfect); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn smallest_abundant_number_is_classified_correctly() { + let input = 12; + let output = classify(input); + let expected = Some(Classification::Abundant); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn medium_abundant_number_is_classified_correctly() { + let input = 30; + let output = classify(input); + let expected = Some(Classification::Abundant); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn large_abundant_number_is_classified_correctly() { + let input = 33550335; + let output = classify(input); + let expected = Some(Classification::Abundant); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn smallest_prime_deficient_number_is_classified_correctly() { + let input = 2; + let output = classify(input); + let expected = Some(Classification::Deficient); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn smallest_non_prime_deficient_number_is_classified_correctly() { + let input = 4; + let output = classify(input); + let expected = Some(Classification::Deficient); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn medium_deficient_number_is_classified_correctly() { + let input = 32; + let output = classify(input); + let expected = Some(Classification::Deficient); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn large_deficient_number_is_classified_correctly() { + let input = 33550337; + let output = classify(input); + let expected = Some(Classification::Deficient); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn edge_case_no_factors_other_than_itself_is_classified_correctly() { + let input = 1; + let output = classify(input); + let expected = Some(Classification::Deficient); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn zero_is_rejected_as_it_is_not_a_positive_integer() { + let input = 0; + let output = classify(input); + assert!(output.is_none()); } From 7afe0aee92cf9fe7fb5a93abce0073a8b0e83843 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 19 Dec 2023 14:40:33 +0100 Subject: [PATCH 230/436] Sync robot-simulator with problem-specifications (#1829) --- .../robot-simulator/.meta/test_template.tera | 30 ++++ .../practice/robot-simulator/.meta/tests.toml | 67 +++++++- .../robot-simulator/tests/robot-simulator.rs | 157 ++++++++++-------- 3 files changed, 180 insertions(+), 74 deletions(-) create mode 100644 exercises/practice/robot-simulator/.meta/test_template.tera diff --git a/exercises/practice/robot-simulator/.meta/test_template.tera b/exercises/practice/robot-simulator/.meta/test_template.tera new file mode 100644 index 000000000..fb0647357 --- /dev/null +++ b/exercises/practice/robot-simulator/.meta/test_template.tera @@ -0,0 +1,30 @@ +use {{ crate_name }}::*; + +{% for test in cases %} +#[test] +{% if loop.index != 1 -%} +#[ignore] +{% endif -%} +fn {{ test.description | slugify | replace(from="-", to="_") }}() { +{%- if test.property == "create" %} + let robot = Robot::new({{ test.input.position.x }}, {{ test.input.position.y }}, Direction::{{ test.input.direction | title }}); + assert_eq!(robot.position(), ({{ test.expected.position.x }}, {{ test.expected.position.y }})); + assert_eq!(robot.direction(), &Direction::{{ test.expected.direction | title }}); +} + {% continue %} +{% endif -%} + + let robot_start = Robot::new({{ test.input.position.x }}, {{ test.input.position.y }}, Direction::{{ test.input.direction | title }}); +{%- if test.input.instructions == "R" %} + let robot_end = robot_start.turn_right(); +{%- elif test.input.instructions == "L" %} + let robot_end = robot_start.turn_left(); +{%- elif test.input.instructions == "A" %} + let robot_end = robot_start.advance(); +{%- else %} + let robot_end = robot_start.instructions({{ test.input.instructions | json_encode() }}); +{% endif -%} + assert_eq!(robot_end.position(), ({{ test.expected.position.x }}, {{ test.expected.position.y }})); + assert_eq!(robot_end.direction(), &Direction::{{ test.expected.direction | title }}); +} +{% endfor -%} diff --git a/exercises/practice/robot-simulator/.meta/tests.toml b/exercises/practice/robot-simulator/.meta/tests.toml index be690e975..16da03d4b 100644 --- a/exercises/practice/robot-simulator/.meta/tests.toml +++ b/exercises/practice/robot-simulator/.meta/tests.toml @@ -1,3 +1,64 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[c557c16d-26c1-4e06-827c-f6602cd0785c] +description = "Create robot -> at origin facing north" + +[bf0dffce-f11c-4cdb-8a5e-2c89d8a5a67d] +description = "Create robot -> at negative position facing south" + +[8cbd0086-6392-4680-b9b9-73cf491e67e5] +description = "Rotating clockwise -> changes north to east" + +[8abc87fc-eab2-4276-93b7-9c009e866ba1] +description = "Rotating clockwise -> changes east to south" + +[3cfe1b85-bbf2-4bae-b54d-d73e7e93617a] +description = "Rotating clockwise -> changes south to west" + +[5ea9fb99-3f2c-47bd-86f7-46b7d8c3c716] +description = "Rotating clockwise -> changes west to north" + +[fa0c40f5-6ba3-443d-a4b3-58cbd6cb8d63] +description = "Rotating counter-clockwise -> changes north to west" + +[da33d734-831f-445c-9907-d66d7d2a92e2] +description = "Rotating counter-clockwise -> changes west to south" + +[bd1ca4b9-4548-45f4-b32e-900fc7c19389] +description = "Rotating counter-clockwise -> changes south to east" + +[2de27b67-a25c-4b59-9883-bc03b1b55bba] +description = "Rotating counter-clockwise -> changes east to north" + +[f0dc2388-cddc-4f83-9bed-bcf46b8fc7b8] +description = "Moving forward one -> facing north increments Y" + +[2786cf80-5bbf-44b0-9503-a89a9c5789da] +description = "Moving forward one -> facing south decrements Y" + +[84bf3c8c-241f-434d-883d-69817dbd6a48] +description = "Moving forward one -> facing east increments X" + +[bb69c4a7-3bbf-4f64-b415-666fa72d7b04] +description = "Moving forward one -> facing west decrements X" + +[e34ac672-4ed4-4be3-a0b8-d9af259cbaa1] +description = "Follow series of instructions -> moving east and north from README" + +[f30e4955-4b47-4aa3-8b39-ae98cfbd515b] +description = "Follow series of instructions -> moving west and north" + +[3e466bf6-20ab-4d79-8b51-264165182fca] +description = "Follow series of instructions -> moving west and south" + +[41f0bb96-c617-4e6b-acff-a4b279d44514] +description = "Follow series of instructions -> moving east and north" diff --git a/exercises/practice/robot-simulator/tests/robot-simulator.rs b/exercises/practice/robot-simulator/tests/robot-simulator.rs index 1c33c2f18..45ff1160a 100644 --- a/exercises/practice/robot-simulator/tests/robot-simulator.rs +++ b/exercises/practice/robot-simulator/tests/robot-simulator.rs @@ -1,145 +1,160 @@ use robot_simulator::*; #[test] -fn robots_are_created_with_position_and_direction() { +fn at_origin_facing_north() { let robot = Robot::new(0, 0, Direction::North); - assert_eq!((0, 0), robot.position()); - assert_eq!(&Direction::North, robot.direction()); + assert_eq!(robot.position(), (0, 0)); + assert_eq!(robot.direction(), &Direction::North); } #[test] #[ignore] -fn positions_can_be_negative() { +fn at_negative_position_facing_south() { let robot = Robot::new(-1, -1, Direction::South); - assert_eq!((-1, -1), robot.position()); - assert_eq!(&Direction::South, robot.direction()); + assert_eq!(robot.position(), (-1, -1)); + assert_eq!(robot.direction(), &Direction::South); } #[test] #[ignore] -fn turning_right_does_not_change_position() { - let robot = Robot::new(0, 0, Direction::North).turn_right(); - assert_eq!((0, 0), robot.position()); +fn changes_north_to_east() { + let robot_start = Robot::new(0, 0, Direction::North); + let robot_end = robot_start.turn_right(); + assert_eq!(robot_end.position(), (0, 0)); + assert_eq!(robot_end.direction(), &Direction::East); } #[test] #[ignore] -fn turning_right_from_north_points_the_robot_east() { - let robot = Robot::new(0, 0, Direction::North).turn_right(); - assert_eq!(&Direction::East, robot.direction()); +fn changes_east_to_south() { + let robot_start = Robot::new(0, 0, Direction::East); + let robot_end = robot_start.turn_right(); + assert_eq!(robot_end.position(), (0, 0)); + assert_eq!(robot_end.direction(), &Direction::South); } #[test] #[ignore] -fn turning_right_from_east_points_the_robot_south() { - let robot = Robot::new(0, 0, Direction::East).turn_right(); - assert_eq!(&Direction::South, robot.direction()); +fn changes_south_to_west() { + let robot_start = Robot::new(0, 0, Direction::South); + let robot_end = robot_start.turn_right(); + assert_eq!(robot_end.position(), (0, 0)); + assert_eq!(robot_end.direction(), &Direction::West); } #[test] #[ignore] -fn turning_right_from_south_points_the_robot_west() { - let robot = Robot::new(0, 0, Direction::South).turn_right(); - assert_eq!(&Direction::West, robot.direction()); +fn changes_west_to_north() { + let robot_start = Robot::new(0, 0, Direction::West); + let robot_end = robot_start.turn_right(); + assert_eq!(robot_end.position(), (0, 0)); + assert_eq!(robot_end.direction(), &Direction::North); } #[test] #[ignore] -fn turning_right_from_west_points_the_robot_north() { - let robot = Robot::new(0, 0, Direction::West).turn_right(); - assert_eq!(&Direction::North, robot.direction()); +fn changes_north_to_west() { + let robot_start = Robot::new(0, 0, Direction::North); + let robot_end = robot_start.turn_left(); + assert_eq!(robot_end.position(), (0, 0)); + assert_eq!(robot_end.direction(), &Direction::West); } #[test] #[ignore] -fn turning_left_does_not_change_position() { - let robot = Robot::new(0, 0, Direction::North).turn_left(); - assert_eq!((0, 0), robot.position()); +fn changes_west_to_south() { + let robot_start = Robot::new(0, 0, Direction::West); + let robot_end = robot_start.turn_left(); + assert_eq!(robot_end.position(), (0, 0)); + assert_eq!(robot_end.direction(), &Direction::South); } #[test] #[ignore] -fn turning_left_from_north_points_the_robot_west() { - let robot = Robot::new(0, 0, Direction::North).turn_left(); - assert_eq!(&Direction::West, robot.direction()); +fn changes_south_to_east() { + let robot_start = Robot::new(0, 0, Direction::South); + let robot_end = robot_start.turn_left(); + assert_eq!(robot_end.position(), (0, 0)); + assert_eq!(robot_end.direction(), &Direction::East); } #[test] #[ignore] -fn turning_left_from_west_points_the_robot_south() { - let robot = Robot::new(0, 0, Direction::West).turn_left(); - assert_eq!(&Direction::South, robot.direction()); +fn changes_east_to_north() { + let robot_start = Robot::new(0, 0, Direction::East); + let robot_end = robot_start.turn_left(); + assert_eq!(robot_end.position(), (0, 0)); + assert_eq!(robot_end.direction(), &Direction::North); } #[test] #[ignore] -fn turning_left_from_south_points_the_robot_east() { - let robot = Robot::new(0, 0, Direction::South).turn_left(); - assert_eq!(&Direction::East, robot.direction()); +fn facing_north_increments_y() { + let robot_start = Robot::new(0, 0, Direction::North); + let robot_end = robot_start.advance(); + assert_eq!(robot_end.position(), (0, 1)); + assert_eq!(robot_end.direction(), &Direction::North); } #[test] #[ignore] -fn turning_left_from_east_points_the_robot_north() { - let robot = Robot::new(0, 0, Direction::East).turn_left(); - assert_eq!(&Direction::North, robot.direction()); +fn facing_south_decrements_y() { + let robot_start = Robot::new(0, 0, Direction::South); + let robot_end = robot_start.advance(); + assert_eq!(robot_end.position(), (0, -1)); + assert_eq!(robot_end.direction(), &Direction::South); } #[test] #[ignore] -fn advance_does_not_change_the_direction() { - let robot = Robot::new(0, 0, Direction::North).advance(); - assert_eq!(&Direction::North, robot.direction()); +fn facing_east_increments_x() { + let robot_start = Robot::new(0, 0, Direction::East); + let robot_end = robot_start.advance(); + assert_eq!(robot_end.position(), (1, 0)); + assert_eq!(robot_end.direction(), &Direction::East); } #[test] #[ignore] -fn advance_increases_the_y_coordinate_by_one_when_facing_north() { - let robot = Robot::new(0, 0, Direction::North).advance(); - assert_eq!((0, 1), robot.position()); +fn facing_west_decrements_x() { + let robot_start = Robot::new(0, 0, Direction::West); + let robot_end = robot_start.advance(); + assert_eq!(robot_end.position(), (-1, 0)); + assert_eq!(robot_end.direction(), &Direction::West); } #[test] #[ignore] -fn advance_decreases_the_y_coordinate_by_one_when_facing_south() { - let robot = Robot::new(0, 0, Direction::South).advance(); - assert_eq!((0, -1), robot.position()); +fn moving_east_and_north_from_readme() { + let robot_start = Robot::new(7, 3, Direction::North); + let robot_end = robot_start.instructions("RAALAL"); + assert_eq!(robot_end.position(), (9, 4)); + assert_eq!(robot_end.direction(), &Direction::West); } #[test] #[ignore] -fn advance_increases_the_x_coordinate_by_one_when_facing_east() { - let robot = Robot::new(0, 0, Direction::East).advance(); - assert_eq!((1, 0), robot.position()); +fn moving_west_and_north() { + let robot_start = Robot::new(0, 0, Direction::North); + let robot_end = robot_start.instructions("LAAARALA"); + assert_eq!(robot_end.position(), (-4, 1)); + assert_eq!(robot_end.direction(), &Direction::West); } #[test] #[ignore] -fn advance_decreases_the_x_coordinate_by_one_when_facing_west() { - let robot = Robot::new(0, 0, Direction::West).advance(); - assert_eq!((-1, 0), robot.position()); +fn moving_west_and_south() { + let robot_start = Robot::new(2, -7, Direction::East); + let robot_end = robot_start.instructions("RRAAAAALA"); + assert_eq!(robot_end.position(), (-3, -8)); + assert_eq!(robot_end.direction(), &Direction::South); } #[test] #[ignore] -fn follow_instructions_to_move_west_and_north() { - let robot = Robot::new(0, 0, Direction::North).instructions("LAAARALA"); - assert_eq!((-4, 1), robot.position()); - assert_eq!(&Direction::West, robot.direction()); -} - -#[test] -#[ignore] -fn follow_instructions_to_move_west_and_south() { - let robot = Robot::new(2, -7, Direction::East).instructions("RRAAAAALA"); - assert_eq!((-3, -8), robot.position()); - assert_eq!(&Direction::South, robot.direction()); -} - -#[test] -#[ignore] -fn follow_instructions_to_move_east_and_north() { - let robot = Robot::new(8, 4, Direction::South).instructions("LAAARRRALLLL"); - assert_eq!((11, 5), robot.position()); - assert_eq!(&Direction::North, robot.direction()); +fn moving_east_and_north() { + let robot_start = Robot::new(8, 4, Direction::South); + let robot_end = robot_start.instructions("LAAARRRALLLL"); + assert_eq!(robot_end.position(), (11, 5)); + assert_eq!(robot_end.direction(), &Direction::North); } From 39163d0bd780782abdcd5e882cca1dcf5d2eee6f Mon Sep 17 00:00:00 2001 From: Lars <37488165+LarsKue@users.noreply.github.com> Date: Sun, 24 Dec 2023 00:43:22 +0100 Subject: [PATCH 231/436] circular-buffer: Test that elements are dropped when the buffer is dropped (#1842) A common solution to this exercise is to use `MaybeUninit` in the buffer. However, `MaybeUninit` explicitly never calls `T`s drop code. A sound solution should drop elements in the buffer when it is dropped. This test checks for that. --- .../circular-buffer/tests/circular-buffer.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/exercises/practice/circular-buffer/tests/circular-buffer.rs b/exercises/practice/circular-buffer/tests/circular-buffer.rs index 36f1b2b16..716f2e4f2 100644 --- a/exercises/practice/circular-buffer/tests/circular-buffer.rs +++ b/exercises/practice/circular-buffer/tests/circular-buffer.rs @@ -141,6 +141,18 @@ fn overwrite_replaces_the_oldest_item_remaining_in_buffer_following_a_read() { assert_eq!(Ok('5'), buffer.read()); } +#[test] +#[ignore] +fn dropping_the_buffer_drops_its_elements() { + let element = Rc::new(()); + { + let mut buffer = CircularBuffer::new(1); + assert!(buffer.write(Rc::clone(&element)).is_ok()); + assert_eq!(Rc::strong_count(&element), 2); + } + assert_eq!(Rc::strong_count(&element), 1); +} + #[test] #[ignore] fn integer_buffer() { From b2f3924239000b1deb3683284164d3ea97868517 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Fri, 5 Jan 2024 15:55:15 +0100 Subject: [PATCH 232/436] Add links to space-age documentation (#1843) --- exercises/practice/space-age/.docs/instructions.append.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/practice/space-age/.docs/instructions.append.md b/exercises/practice/space-age/.docs/instructions.append.md index cc76cdff4..3702e9904 100644 --- a/exercises/practice/space-age/.docs/instructions.append.md +++ b/exercises/practice/space-age/.docs/instructions.append.md @@ -2,8 +2,8 @@ Some Rust topics you may want to read about while solving this problem: -- Traits, both the From trait and implementing your own traits -- Default method implementations for traits +- Traits, both the From trait and [implementing your own traits](https://doc.rust-lang.org/book/ch10-02-traits.html) +- [Default method implementations](https://doc.rust-lang.org/book/ch10-02-traits.html#default-implementations) for traits - Macros, the use of a macro could reduce boilerplate and increase readability for this exercise. For instance, [a macro can implement a trait for multiple types at once](https://stackoverflow.com/questions/39150216/implementing-a-trait-for-multiple-types-at-once), From f03997cd1fe10ebce11f4dc16504f65628fb30fd Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Fri, 5 Jan 2024 15:55:43 +0100 Subject: [PATCH 233/436] Sync anagram with problem-specifications (#1844) --- .../practice/anagram/.meta/test_template.tera | 16 ++ exercises/practice/anagram/.meta/tests.toml | 83 ++++++- exercises/practice/anagram/tests/anagram.rs | 218 ++++++++---------- 3 files changed, 196 insertions(+), 121 deletions(-) create mode 100644 exercises/practice/anagram/.meta/test_template.tera diff --git a/exercises/practice/anagram/.meta/test_template.tera b/exercises/practice/anagram/.meta/test_template.tera new file mode 100644 index 000000000..c6f1533f7 --- /dev/null +++ b/exercises/practice/anagram/.meta/test_template.tera @@ -0,0 +1,16 @@ +use {{ crate_name }}::*; +use std::collections::HashSet; + +{% for test in cases %} +#[test] +{% if loop.index != 1 -%} +#[ignore] +{% endif -%} +fn {{ test.description | slugify | replace(from="-", to="_") }}() { + let word = {{ test.input.subject | json_encode() }}; + let inputs = &{{ test.input.candidates | json_encode() }}; + let output = {{ fn_names[0] }}(word, inputs); + let expected = HashSet::from_iter({{ test.expected | json_encode() }}); + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/anagram/.meta/tests.toml b/exercises/practice/anagram/.meta/tests.toml index be690e975..4f43811d2 100644 --- a/exercises/practice/anagram/.meta/tests.toml +++ b/exercises/practice/anagram/.meta/tests.toml @@ -1,3 +1,80 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[dd40c4d2-3c8b-44e5-992a-f42b393ec373] +description = "no matches" + +[b3cca662-f50a-489e-ae10-ab8290a09bdc] +description = "detects two anagrams" +include = false + +[03eb9bbe-8906-4ea0-84fa-ffe711b52c8b] +description = "detects two anagrams" +reimplements = "b3cca662-f50a-489e-ae10-ab8290a09bdc" + +[a27558ee-9ba0-4552-96b1-ecf665b06556] +description = "does not detect anagram subsets" + +[64cd4584-fc15-4781-b633-3d814c4941a4] +description = "detects anagram" + +[99c91beb-838f-4ccd-b123-935139917283] +description = "detects three anagrams" + +[78487770-e258-4e1f-a646-8ece10950d90] +description = "detects multiple anagrams with different case" + +[1d0ab8aa-362f-49b7-9902-3d0c668d557b] +description = "does not detect non-anagrams with identical checksum" + +[9e632c0b-c0b1-4804-8cc1-e295dea6d8a8] +description = "detects anagrams case-insensitively" + +[b248e49f-0905-48d2-9c8d-bd02d8c3e392] +description = "detects anagrams using case-insensitive subject" + +[f367325c-78ec-411c-be76-e79047f4bd54] +description = "detects anagrams using case-insensitive possible matches" + +[7cc195ad-e3c7-44ee-9fd2-d3c344806a2c] +description = "does not detect an anagram if the original word is repeated" +include = false + +[630abb71-a94e-4715-8395-179ec1df9f91] +description = "does not detect an anagram if the original word is repeated" +reimplements = "7cc195ad-e3c7-44ee-9fd2-d3c344806a2c" + +[9878a1c9-d6ea-4235-ae51-3ea2befd6842] +description = "anagrams must use all letters exactly once" + +[85757361-4535-45fd-ac0e-3810d40debc1] +description = "words are not anagrams of themselves (case-insensitive)" +include = false + +[68934ed0-010b-4ef9-857a-20c9012d1ebf] +description = "words are not anagrams of themselves" +reimplements = "85757361-4535-45fd-ac0e-3810d40debc1" + +[589384f3-4c8a-4e7d-9edc-51c3e5f0c90e] +description = "words are not anagrams of themselves even if letter case is partially different" +reimplements = "85757361-4535-45fd-ac0e-3810d40debc1" + +[ba53e423-7e02-41ee-9ae2-71f91e6d18e6] +description = "words are not anagrams of themselves even if letter case is completely different" +reimplements = "85757361-4535-45fd-ac0e-3810d40debc1" + +[a0705568-628c-4b55-9798-82e4acde51ca] +description = "words other than themselves can be anagrams" +include = false + +[33d3f67e-fbb9-49d3-a90e-0beb00861da7] +description = "words other than themselves can be anagrams" +reimplements = "a0705568-628c-4b55-9798-82e4acde51ca" diff --git a/exercises/practice/anagram/tests/anagram.rs b/exercises/practice/anagram/tests/anagram.rs index 4ac7a283c..44cab71a0 100644 --- a/exercises/practice/anagram/tests/anagram.rs +++ b/exercises/practice/anagram/tests/anagram.rs @@ -1,78 +1,50 @@ +use anagram::*; use std::collections::HashSet; -fn process_anagram_case(word: &str, inputs: &[&str], expected: &[&str]) { - let result = anagram::anagrams_for(word, inputs); - - let expected: HashSet<&str> = expected.iter().cloned().collect(); - - assert_eq!(result, expected); -} - #[test] fn no_matches() { let word = "diaper"; - - let inputs = ["hello", "world", "zombies", "pants"]; - - let outputs = vec![]; - - process_anagram_case(word, &inputs, &outputs); + let inputs = &["hello", "world", "zombies", "pants"]; + let output = anagrams_for(word, inputs); + let expected = HashSet::from_iter([]); + assert_eq!(output, expected); } #[test] #[ignore] -fn detect_simple_anagram() { - let word = "ant"; - - let inputs = ["tan", "stand", "at"]; - - let outputs = vec!["tan"]; - - process_anagram_case(word, &inputs, &outputs); +fn detects_two_anagrams() { + let word = "solemn"; + let inputs = &["lemons", "cherry", "melons"]; + let output = anagrams_for(word, inputs); + let expected = HashSet::from_iter(["lemons", "melons"]); + assert_eq!(output, expected); } #[test] #[ignore] -fn does_not_confuse_different_duplicates() { - let word = "galea"; - - let inputs = ["eagle"]; - - let outputs = vec![]; - - process_anagram_case(word, &inputs, &outputs); -} - -#[test] -#[ignore] -fn eliminate_anagram_subsets() { +fn does_not_detect_anagram_subsets() { let word = "good"; - - let inputs = ["dog", "goody"]; - - let outputs = vec![]; - - process_anagram_case(word, &inputs, &outputs); + let inputs = &["dog", "goody"]; + let output = anagrams_for(word, inputs); + let expected = HashSet::from_iter([]); + assert_eq!(output, expected); } #[test] #[ignore] -fn detect_anagram() { +fn detects_anagram() { let word = "listen"; - - let inputs = ["enlists", "google", "inlets", "banana"]; - - let outputs = vec!["inlets"]; - - process_anagram_case(word, &inputs, &outputs); + let inputs = &["enlists", "google", "inlets", "banana"]; + let output = anagrams_for(word, inputs); + let expected = HashSet::from_iter(["inlets"]); + assert_eq!(output, expected); } #[test] #[ignore] -fn multiple_anagrams() { +fn detects_three_anagrams() { let word = "allergy"; - - let inputs = [ + let inputs = &[ "gallery", "ballerina", "regally", @@ -80,107 +52,117 @@ fn multiple_anagrams() { "largely", "leading", ]; + let output = anagrams_for(word, inputs); + let expected = HashSet::from_iter(["gallery", "regally", "largely"]); + assert_eq!(output, expected); +} - let outputs = vec!["gallery", "regally", "largely"]; +#[test] +#[ignore] +fn detects_multiple_anagrams_with_different_case() { + let word = "nose"; + let inputs = &["Eons", "ONES"]; + let output = anagrams_for(word, inputs); + let expected = HashSet::from_iter(["Eons", "ONES"]); + assert_eq!(output, expected); +} - process_anagram_case(word, &inputs, &outputs); +#[test] +#[ignore] +fn does_not_detect_non_anagrams_with_identical_checksum() { + let word = "mass"; + let inputs = &["last"]; + let output = anagrams_for(word, inputs); + let expected = HashSet::from_iter([]); + assert_eq!(output, expected); } #[test] #[ignore] -fn case_insensitive_anagrams() { +fn detects_anagrams_case_insensitively() { let word = "Orchestra"; - - let inputs = ["cashregister", "Carthorse", "radishes"]; - - let outputs = vec!["Carthorse"]; - - process_anagram_case(word, &inputs, &outputs); + let inputs = &["cashregister", "Carthorse", "radishes"]; + let output = anagrams_for(word, inputs); + let expected = HashSet::from_iter(["Carthorse"]); + assert_eq!(output, expected); } #[test] #[ignore] -fn unicode_anagrams() { - let word = "ΑΒΓ"; - - // These words don't make sense, they're just greek letters cobbled together. - let inputs = ["ΒΓΑ", "ΒΓΔ", "γβα"]; - - let outputs = vec!["ΒΓΑ", "γβα"]; - - process_anagram_case(word, &inputs, &outputs); +fn detects_anagrams_using_case_insensitive_subject() { + let word = "Orchestra"; + let inputs = &["cashregister", "carthorse", "radishes"]; + let output = anagrams_for(word, inputs); + let expected = HashSet::from_iter(["carthorse"]); + assert_eq!(output, expected); } #[test] #[ignore] -fn misleading_unicode_anagrams() { - // Despite what a human might think these words contain different letters, the input uses Greek - // A and B while the list of potential anagrams uses Latin A and B. - let word = "ΑΒΓ"; - - let inputs = ["ABΓ"]; - - let outputs = vec![]; - - process_anagram_case(word, &inputs, &outputs); +fn detects_anagrams_using_case_insensitive_possible_matches() { + let word = "orchestra"; + let inputs = &["cashregister", "Carthorse", "radishes"]; + let output = anagrams_for(word, inputs); + let expected = HashSet::from_iter(["Carthorse"]); + assert_eq!(output, expected); } #[test] #[ignore] -fn does_not_detect_a_word_as_its_own_anagram() { - let word = "banana"; - - let inputs = ["banana"]; - - let outputs = vec![]; - - process_anagram_case(word, &inputs, &outputs); +fn does_not_detect_an_anagram_if_the_original_word_is_repeated() { + let word = "go"; + let inputs = &["goGoGO"]; + let output = anagrams_for(word, inputs); + let expected = HashSet::from_iter([]); + assert_eq!(output, expected); } #[test] #[ignore] -fn does_not_detect_a_differently_cased_word_as_its_own_anagram() { - let word = "banana"; - - let inputs = ["bAnana"]; - - let outputs = vec![]; - - process_anagram_case(word, &inputs, &outputs); +fn anagrams_must_use_all_letters_exactly_once() { + let word = "tapper"; + let inputs = &["patter"]; + let output = anagrams_for(word, inputs); + let expected = HashSet::from_iter([]); + assert_eq!(output, expected); } #[test] #[ignore] -fn does_not_detect_a_differently_cased_unicode_word_as_its_own_anagram() { - let word = "ΑΒΓ"; - - let inputs = ["ΑΒγ"]; - - let outputs = vec![]; - - process_anagram_case(word, &inputs, &outputs); +fn words_are_not_anagrams_of_themselves() { + let word = "BANANA"; + let inputs = &["BANANA"]; + let output = anagrams_for(word, inputs); + let expected = HashSet::from_iter([]); + assert_eq!(output, expected); } #[test] #[ignore] -fn same_bytes_different_chars() { - let word = "a⬂"; // 61 E2 AC 82 - - let inputs = ["€a"]; // E2 82 AC 61 - - let outputs = vec![]; - - process_anagram_case(word, &inputs, &outputs); +fn words_are_not_anagrams_of_themselves_even_if_letter_case_is_partially_different() { + let word = "BANANA"; + let inputs = &["Banana"]; + let output = anagrams_for(word, inputs); + let expected = HashSet::from_iter([]); + assert_eq!(output, expected); } #[test] #[ignore] -fn different_words_but_same_ascii_sum() { - let word = "bc"; - - let inputs = ["ad"]; - - let outputs = vec![]; +fn words_are_not_anagrams_of_themselves_even_if_letter_case_is_completely_different() { + let word = "BANANA"; + let inputs = &["banana"]; + let output = anagrams_for(word, inputs); + let expected = HashSet::from_iter([]); + assert_eq!(output, expected); +} - process_anagram_case(word, &inputs, &outputs); +#[test] +#[ignore] +fn words_other_than_themselves_can_be_anagrams() { + let word = "LISTEN"; + let inputs = &["LISTEN", "Silent"]; + let output = anagrams_for(word, inputs); + let expected = HashSet::from_iter(["Silent"]); + assert_eq!(output, expected); } From 3c51a2942f9eefae4bf0f2a40c3e7aee259819a6 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Fri, 5 Jan 2024 15:56:10 +0100 Subject: [PATCH 234/436] Fix already-solved lifetime challenge in anagram (#1847) This is basically a revert of #1755. The original "fix" was a reaction to this forum post: https://forum.exercism.org/t/the-function-signature-for-anagrams-in-rust-is-missing-a-lifetime/7677 It seems the exercise instructions, which explicitly mention that this missing lifetime annotation is intentional, went unnoticed. --- exercises/practice/anagram/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/anagram/src/lib.rs b/exercises/practice/anagram/src/lib.rs index 5b021ac6d..f029d0449 100644 --- a/exercises/practice/anagram/src/lib.rs +++ b/exercises/practice/anagram/src/lib.rs @@ -1,5 +1,5 @@ use std::collections::HashSet; -pub fn anagrams_for<'a>(word: &str, possible_anagrams: &[&'a str]) -> HashSet<&'a str> { +pub fn anagrams_for<'a>(word: &str, possible_anagrams: &[&str]) -> HashSet<&'a str> { todo!("For the '{word}' word find anagrams among the following words: {possible_anagrams:?}"); } From f3eb863034cb7ee0738042c275fa3b74dbbccd97 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 9 Jan 2024 20:14:06 +0100 Subject: [PATCH 235/436] Fix clippy warning in example.rs of wordy (#1849) --- exercises/practice/wordy/.meta/example.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/wordy/.meta/example.rs b/exercises/practice/wordy/.meta/example.rs index 6ceceeed4..81e201cec 100644 --- a/exercises/practice/wordy/.meta/example.rs +++ b/exercises/practice/wordy/.meta/example.rs @@ -23,7 +23,7 @@ fn apply_op<'a, 'b>(num1: i32, words: &'a [Token<'b>]) -> Option<(i32, &'a [Toke [Token::NonNumber("multiplied"), Token::NonNumber("by")] => Some(num1 * num2), [Token::NonNumber("divided"), Token::NonNumber("by")] => Some(num1 / num2), [Token::NonNumber("raised"), Token::NonNumber("to"), Token::NonNumber("the")] => { - if Some(&Token::NonNumber("power")) == remainder.get(0) { + if Some(&Token::NonNumber("power")) == remainder.first() { remainder = remainder.get(1..)?; Some(num1.pow(num2 as u32)) } else { From c5fc8e17f0487cc850d2f9046b93034cdf03e2a4 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Wed, 10 Jan 2024 09:42:19 +0100 Subject: [PATCH 236/436] Update instructions append for anagram (#1848) - Remove some performance considerations. These are basically giving away the implementation. Also, not every student might want to focus on performance. Such considerations may be better suited for approaches articles. - Remove non-Rust-related hints. These are pretty much covered by the instructions. I guess these were lacking at some point, but updated in the meantime. - Move the most important hint about lifetimes to the top. --- exercises/practice/anagram/.docs/hints.md | 4 ++++ .../practice/anagram/.docs/instructions.append.md | 13 +++---------- 2 files changed, 7 insertions(+), 10 deletions(-) create mode 100644 exercises/practice/anagram/.docs/hints.md diff --git a/exercises/practice/anagram/.docs/hints.md b/exercises/practice/anagram/.docs/hints.md new file mode 100644 index 000000000..4713528cb --- /dev/null +++ b/exercises/practice/anagram/.docs/hints.md @@ -0,0 +1,4 @@ +## General + +The solution is case insensitive, which means `"WOrd"` is the same as `"word"` or `"woRd"`. +It may help to take a peek at the [std library](https://doc.rust-lang.org/std/primitive.char.html) for functions that can convert between them. diff --git a/exercises/practice/anagram/.docs/instructions.append.md b/exercises/practice/anagram/.docs/instructions.append.md index a2142d202..739b6f2f2 100644 --- a/exercises/practice/anagram/.docs/instructions.append.md +++ b/exercises/practice/anagram/.docs/instructions.append.md @@ -1,11 +1,4 @@ -# Hints +# Instructions append -The solution is case insensitive, which means `"WOrd"` is the same as `"word"` or `"woRd"`. It may help to take a peek at the [std library](https://doc.rust-lang.org/std/primitive.char.html) for functions that can convert between them. - -The solution cannot contain the input word. A word is always an anagram of itself, which means it is not an interesting result. Given `"hello"` and the list `["hello", "olleh"]` the answer is `["olleh"]`. - -You are going to have to adjust the function signature provided in the stub in order for the lifetimes to work out properly. This is intentional: what's there demonstrates the basics of lifetime syntax, and what's missing teaches how to interpret lifetime-related compiler errors. - -Try to limit case changes. Case changes are expensive in terms of time, so it's faster to minimize them. - -If sorting, consider [sort_unstable](https://doc.rust-lang.org/std/primitive.slice.html#method.sort_unstable) which is typically faster than stable sorting. When applicable, unstable sorting is preferred because it is generally faster than stable sorting and it doesn't allocate auxiliary memory. \ No newline at end of file +You are going to have to adjust the function signature provided in the stub in order for the lifetimes to work out properly. +This is intentional: what's there demonstrates the basics of lifetime syntax, and what's missing teaches how to interpret lifetime-related compiler errors. From 1448584343701209c2f7cb276e1bc95356f76c7d Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Wed, 10 Jan 2024 09:42:58 +0100 Subject: [PATCH 237/436] Update dependencies (#1850) Replace tempdir with tempfile, which is much better supported. (last update 6 years vs 13 days ago) Although it doesn't affect us, the following security advisory triggered me to update. https://github.com/advisories/GHSA-mc8h-8q98-g5hr --- rust-tooling/Cargo.lock | 483 ++++++++++-------- rust-tooling/ci-tests/Cargo.toml | 2 +- .../ci-tests/tests/stubs_are_warning_free.rs | 2 +- 3 files changed, 277 insertions(+), 210 deletions(-) diff --git a/rust-tooling/Cargo.lock b/rust-tooling/Cargo.lock index dead1c520..319ae51b0 100644 --- a/rust-tooling/Cargo.lock +++ b/rust-tooling/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "aho-corasick" -version = "1.0.5" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] @@ -28,9 +28,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6" dependencies = [ "anstyle", "anstyle-parse", @@ -48,30 +48,30 @@ checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" [[package]] name = "anstyle-parse" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" dependencies = [ - "windows-sys", + "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.1" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" dependencies = [ "anstyle", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -86,6 +86,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + [[package]] name = "block-buffer" version = "0.10.4" @@ -97,9 +103,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.6.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c2f7349907b712260e64b0afe2f84692af14a454be26187d9df565c7f69266a" +checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc" dependencies = [ "memchr", "serde", @@ -107,9 +113,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.13.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "cc" @@ -128,21 +134,21 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.30" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defd4e7873dbddba6c7c91e199c7fcb946abc4a6a4ac3195400bcfb01b5de877" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", - "windows-targets", + "windows-targets 0.48.5", ] [[package]] name = "chrono-tz" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1369bc6b9e9a7dfdae2055f6ec151fe9c554a9d23d357c0237cee2e25eaabb7" +checksum = "91d7b79e99bfaa0d47da0687c43aa3b7381938a62ad3a6498599039321f660b7" dependencies = [ "chrono", "chrono-tz-build", @@ -151,9 +157,9 @@ dependencies = [ [[package]] name = "chrono-tz-build" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2f5ebdc942f57ed96d560a6d1a459bae5851102a25d5bf89dc04ae453e31ecf" +checksum = "433e39f13c9a060046954e0592a8d0a4bcb1040125cbf91cb8ee58964cfb350f" dependencies = [ "parse-zoneinfo", "phf", @@ -169,15 +175,15 @@ dependencies = [ "ignore", "models", "serde_json", - "tempdir", + "tempfile", "utils", ] [[package]] name = "clap" -version = "4.4.8" +version = "4.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64" +checksum = "33e92c5c1a78c62968ec57dbc2440366a2d6e5a23faf829970ff1585dc6b18e2" dependencies = [ "clap_builder", "clap_derive", @@ -185,9 +191,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.8" +version = "4.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc" +checksum = "f4323769dc8a61e2c39ad7dc26f6f2800524691a44d74fe3d1071a5c24db6370" dependencies = [ "anstream", "anstyle", @@ -230,26 +236,51 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "cpufeatures" -version = "0.2.9" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + [[package]] name = "crossterm" version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" dependencies = [ - "bitflags", + "bitflags 1.3.2", "crossterm_winapi", "libc", "mio", @@ -280,9 +311,9 @@ dependencies = [ [[package]] name = "deunicode" -version = "0.4.4" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95203a6a50906215a502507c0f879a0ce7ff205a6111e2db2a5ef8e4bb92e43" +checksum = "3ae2a35373c5c74340b79ae6780b498b2b183915ec5dacf263aac5a099bf485a" [[package]] name = "digest" @@ -307,16 +338,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] -name = "fnv" -version = "1.0.7" +name = "errno" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] [[package]] -name = "fuchsia-cprng" -version = "0.1.1" +name = "fastrand" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "generate" @@ -345,9 +380,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ "cfg-if", "libc", @@ -362,15 +397,15 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "globset" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759c97c1e17c55525b57192c06a267cda0ac5210b222d6b82189a2338fa1c13d" +checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" dependencies = [ "aho-corasick", "bstr", - "fnv", "log", - "regex", + "regex-automata", + "regex-syntax", ] [[package]] @@ -379,16 +414,16 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" dependencies = [ - "bitflags", + "bitflags 1.3.2", "ignore", "walkdir", ] [[package]] name = "hashbrown" -version = "0.14.0" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" [[package]] name = "heck" @@ -407,16 +442,16 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.57" +version = "0.1.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows", + "windows-core", ] [[package]] @@ -430,26 +465,25 @@ dependencies = [ [[package]] name = "ignore" -version = "0.4.20" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492" +checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1" dependencies = [ + "crossbeam-deque", "globset", - "lazy_static", "log", "memchr", - "regex", + "regex-automata", "same-file", - "thread_local", "walkdir", "winapi-util", ] [[package]] name = "indexmap" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", "hashbrown", @@ -461,7 +495,7 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c33e7c1ddeb15c9abcbfef6029d8e29f69b52b6d6c891031b88ed91b5065803b" dependencies = [ - "bitflags", + "bitflags 1.3.2", "crossterm", "dyn-clone", "lazy_static", @@ -473,15 +507,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "js-sys" -version = "0.3.64" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" dependencies = [ "wasm-bindgen", ] @@ -494,15 +528,21 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.150" +version = "0.2.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" [[package]] name = "libm" -version = "0.2.7" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "linux-raw-sys" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "lock_api" @@ -522,20 +562,20 @@ checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memchr" -version = "2.6.3" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "mio" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", "log", "wasi", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -562,18 +602,18 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "parking_lot" @@ -595,7 +635,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -609,15 +649,15 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.3" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a4d085fd991ac8d5b05a147b437791b4260b76326baf0fc60cf7c9c27ecd33" +checksum = "1f200d8d83c44a45b21764d1916299752ca035d15ecd46faca3e9a2a2bf6ad06" dependencies = [ "memchr", "thiserror", @@ -626,9 +666,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.3" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bee7be22ce7918f641a33f08e3f43388c7656772244e2bbb2477f44cc9021a" +checksum = "bcd6ab1236bbdb3a49027e920e693192ebfe8913f6d60e294de57463a493cfde" dependencies = [ "pest", "pest_generator", @@ -636,9 +676,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.3" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1511785c5e98d79a05e8a6bc34b4ac2168a0e3e92161862030ad84daa223141" +checksum = "2a31940305ffc96863a735bef7c7994a00b325a7138fdbc5bda0f1a0476d3275" dependencies = [ "pest", "pest_meta", @@ -649,9 +689,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.7.3" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42f0394d3123e33353ca5e1e89092e533d2cc490389f2bd6131c43c634ebc5f" +checksum = "a7ff62f5259e53b78d1af898941cdcdccfae7385cf7d793a6e55de5d05bb4b7d" dependencies = [ "once_cell", "pest", @@ -684,7 +724,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" dependencies = [ "phf_shared", - "rand 0.8.5", + "rand", ] [[package]] @@ -704,35 +744,22 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] -[[package]] -name = "rand" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" -dependencies = [ - "fuchsia-cprng", - "libc", - "rand_core 0.3.1", - "rdrand", - "winapi", -] - [[package]] name = "rand" version = "0.8.5" @@ -741,7 +768,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", - "rand_core 0.6.4", + "rand_core", ] [[package]] @@ -751,24 +778,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -dependencies = [ - "rand_core 0.4.2", + "rand_core", ] -[[package]] -name = "rand_core" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" - [[package]] name = "rand_core" version = "0.6.4" @@ -778,29 +790,20 @@ dependencies = [ "getrandom", ] -[[package]] -name = "rdrand" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" -dependencies = [ - "rand_core 0.3.1", -] - [[package]] name = "redox_syscall" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] name = "regex" -version = "1.9.5" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ "aho-corasick", "memchr", @@ -810,9 +813,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.8" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" dependencies = [ "aho-corasick", "memchr", @@ -821,24 +824,28 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.5" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] -name = "remove_dir_all" -version = "0.5.3" +name = "rustix" +version = "0.38.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" dependencies = [ - "winapi", + "bitflags 2.4.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", ] [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" [[package]] name = "same-file" @@ -857,18 +864,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.188" +version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.188" +version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" dependencies = [ "proc-macro2", "quote", @@ -877,9 +884,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.108" +version = "1.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" dependencies = [ "indexmap", "itoa", @@ -889,9 +896,9 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145" +checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb" dependencies = [ "proc-macro2", "quote", @@ -900,9 +907,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.7" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", @@ -947,11 +954,12 @@ checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" [[package]] name = "slug" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3bc762e6a4b6c6fcaade73e77f9ebc6991b676f88bb2358bddb56560f073373" +checksum = "3bd94acec9c8da640005f8e135a39fc0372e74535e6b368b7a04b875f784c8c4" dependencies = [ "deunicode", + "wasm-bindgen", ] [[package]] @@ -968,9 +976,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "2.0.31" +version = "2.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "718fa2415bcb8d8bd775917a1bf12a7931b6dfa890753378538118181e0cb398" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" dependencies = [ "proc-macro2", "quote", @@ -978,13 +986,16 @@ dependencies = [ ] [[package]] -name = "tempdir" -version = "0.3.7" +name = "tempfile" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" +checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" dependencies = [ - "rand 0.4.6", - "remove_dir_all", + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys 0.52.0", ] [[package]] @@ -1001,7 +1012,7 @@ dependencies = [ "percent-encoding", "pest", "pest_derive", - "rand 0.8.5", + "rand", "regex", "serde", "serde_json", @@ -1011,39 +1022,29 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.48" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.48" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" dependencies = [ "proc-macro2", "quote", "syn", ] -[[package]] -name = "thread_local" -version = "1.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" -dependencies = [ - "cfg-if", - "once_cell", -] - [[package]] name = "typenum" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "ucd-trie" @@ -1103,9 +1104,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-segmentation" @@ -1162,9 +1163,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1172,9 +1173,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" dependencies = [ "bumpalo", "log", @@ -1187,9 +1188,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1197,9 +1198,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", @@ -1210,9 +1211,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.87" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" [[package]] name = "winapi" @@ -1232,9 +1233,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi", ] @@ -1246,12 +1247,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows" -version = "0.48.0" +name = "windows-core" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets", + "windows-targets 0.52.0", ] [[package]] @@ -1260,7 +1261,16 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", ] [[package]] @@ -1269,13 +1279,28 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", ] [[package]] @@ -1284,38 +1309,80 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" diff --git a/rust-tooling/ci-tests/Cargo.toml b/rust-tooling/ci-tests/Cargo.toml index bc9621aa5..ee58ce99a 100644 --- a/rust-tooling/ci-tests/Cargo.toml +++ b/rust-tooling/ci-tests/Cargo.toml @@ -14,5 +14,5 @@ glob = "0.3.1" ignore = "0.4.20" models = { version = "0.1.0", path = "../models" } serde_json = "1.0.108" -tempdir = "0.3.7" +tempfile = "3.9.0" utils = { version = "0.1.0", path = "../utils" } diff --git a/rust-tooling/ci-tests/tests/stubs_are_warning_free.rs b/rust-tooling/ci-tests/tests/stubs_are_warning_free.rs index f13df7675..1796a1a64 100644 --- a/rust-tooling/ci-tests/tests/stubs_are_warning_free.rs +++ b/rust-tooling/ci-tests/tests/stubs_are_warning_free.rs @@ -6,7 +6,7 @@ use glob::glob; fn stubs_are_warning_free() { utils::fs::cd_into_repo_root(); - let temp_dir = tempdir::TempDir::new("exercism-rust").unwrap(); + let temp_dir = tempfile::tempdir().unwrap(); let mut handles = vec![]; From 9f9a97f40735738fcdaf74f4cd35f92401b543cf Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Wed, 10 Jan 2024 15:43:05 +0100 Subject: [PATCH 238/436] Make instructions append and hints consistent (#1851) - Move some content from instructions.append.md which contain hints to hints.md. - Make sure all instructions.append.md files start with the line `# Instructions append`. https://exercism.org/docs/building/tracks/practice-exercises#h-file-docs-introduction-append-md - Make sure all hints.md files start with the line `## General`. https://exercism.org/docs/building/tracks/practice-exercises#h-file-docs-hints-md - Add tests for the correct headings. --- .../{instructions.append.md => hints.md} | 2 +- .../practice/binary-search/.docs/hints.md | 16 ++++++++++ .../.docs/instructions.append.md | 17 ---------- .../clock/.docs/instructions.append.md | 4 ++- exercises/practice/decimal/.docs/hints.md | 5 +++ .../practice/decimal/.docs/instructions.md | 6 ---- .../dot-dsl/.docs/instructions.append.md | 4 ++- .../{instructions.append.md => hints.md} | 2 +- .../forth/.docs/instructions.append.md | 2 ++ .../hello-world/.docs/instructions.append.md | 2 ++ .../{instructions.append.md => hints.md} | 2 +- .../{instructions.append.md => hints.md} | 2 +- .../macros/.docs/instructions.append.md | 3 -- .../paasio/.docs/instructions.append.md | 4 ++- .../.docs/instructions.append.md | 4 ++- .../{instructions.append.md => hints.md} | 2 +- .../.docs/instructions.append.md | 5 ++- .../.docs/instructions.append.md | 4 ++- .../practice/say/.docs/instructions.append.md | 3 +- .../{instructions.append.md => hints.md} | 2 +- .../space-age/.docs/instructions.append.md | 4 ++- .../triangle/.docs/instructions.append.md | 2 +- .../xorcism/.docs/instructions.append.md | 4 ++- rust-tooling/ci-tests/tests/docs_are_valid.rs | 32 +++++++++++++++++++ 24 files changed, 91 insertions(+), 42 deletions(-) rename exercises/practice/accumulate/.docs/{instructions.append.md => hints.md} (98%) create mode 100644 exercises/practice/binary-search/.docs/hints.md create mode 100644 exercises/practice/decimal/.docs/hints.md rename exercises/practice/doubly-linked-list/.docs/{instructions.append.md => hints.md} (97%) rename exercises/practice/high-scores/.docs/{instructions.append.md => hints.md} (92%) rename exercises/practice/largest-series-product/.docs/{instructions.append.md => hints.md} (87%) delete mode 100644 exercises/practice/macros/.docs/instructions.append.md rename exercises/practice/poker/.docs/{instructions.append.md => hints.md} (98%) rename exercises/practice/simple-linked-list/.docs/{instructions.append.md => hints.md} (97%) create mode 100644 rust-tooling/ci-tests/tests/docs_are_valid.rs diff --git a/exercises/practice/accumulate/.docs/instructions.append.md b/exercises/practice/accumulate/.docs/hints.md similarity index 98% rename from exercises/practice/accumulate/.docs/instructions.append.md rename to exercises/practice/accumulate/.docs/hints.md index 3d79940ff..082f60c16 100644 --- a/exercises/practice/accumulate/.docs/instructions.append.md +++ b/exercises/practice/accumulate/.docs/hints.md @@ -1,4 +1,4 @@ -# Hints +## General It may help to look at the Fn\* traits: [Fn](https://doc.rust-lang.org/std/ops/trait.Fn.html), diff --git a/exercises/practice/binary-search/.docs/hints.md b/exercises/practice/binary-search/.docs/hints.md new file mode 100644 index 000000000..ed2f8ee3c --- /dev/null +++ b/exercises/practice/binary-search/.docs/hints.md @@ -0,0 +1,16 @@ +## General + +[Slices](https://doc.rust-lang.org/book/2018-edition/ch04-03-slices.html) have additionally to +the normal element access via indexing (slice[index]) many useful functions like +[split_at](https://doc.rust-lang.org/std/primitive.slice.html#method.split_at) or [getting +subslices](https://doc.rust-lang.org/std/primitive.slice.html#method.get) (slice[start..end]). + +You can solve this exercise by just using boring old element access via indexing, but maybe the +other provided functions can make your code cleaner and safer. + +## For Bonus Points + +- To get your function working with all kind of elements which can be ordered, + have a look at the [Ord Trait](https://doc.rust-lang.org/std/cmp/trait.Ord.html). +- To get your function working directly on Vec and Array, you can use the + [AsRef Trait](https://doc.rust-lang.org/std/convert/trait.AsRef.html) diff --git a/exercises/practice/binary-search/.docs/instructions.append.md b/exercises/practice/binary-search/.docs/instructions.append.md index f958a0db5..f2afbf7cb 100644 --- a/exercises/practice/binary-search/.docs/instructions.append.md +++ b/exercises/practice/binary-search/.docs/instructions.append.md @@ -6,16 +6,6 @@ Rust provides in its standard library already a [binary search function](https://doc.rust-lang.org/std/primitive.slice.html#method.binary_search). For this exercise you should not use this function but just other basic tools instead. -## Hints - -[Slices](https://doc.rust-lang.org/book/2018-edition/ch04-03-slices.html) have additionally to -the normal element access via indexing (slice[index]) many useful functions like -[split_at](https://doc.rust-lang.org/std/primitive.slice.html#method.split_at) or [getting -subslices](https://doc.rust-lang.org/std/primitive.slice.html#method.get) (slice[start..end]). - -You can solve this exercise by just using boring old element access via indexing, but maybe the -other provided functions can make your code cleaner and safer. - ## For bonus points Did you get the tests passing and the code clean? If you want to, there @@ -36,10 +26,3 @@ $ cargo test --features generic Then please share your thoughts in a comment on the submission. Did this experiment make the code better? Worse? Did you learn anything from it? - -### Hints for Bonus Points - -- To get your function working with all kind of elements which can be ordered, - have a look at the [Ord Trait](https://doc.rust-lang.org/std/cmp/trait.Ord.html). -- To get your function working directly on Vec and Array, you can use the - [AsRef Trait](https://doc.rust-lang.org/std/convert/trait.AsRef.html) diff --git a/exercises/practice/clock/.docs/instructions.append.md b/exercises/practice/clock/.docs/instructions.append.md index 6ceff1140..1e3a35101 100644 --- a/exercises/practice/clock/.docs/instructions.append.md +++ b/exercises/practice/clock/.docs/instructions.append.md @@ -1,4 +1,6 @@ -# Rust Traits for `.to_string()` +# Instructions append + +## Rust Traits for `.to_string()` You will also need to implement `.to_string()` for the `Clock` struct. We will be using this to display the Clock's state. diff --git a/exercises/practice/decimal/.docs/hints.md b/exercises/practice/decimal/.docs/hints.md new file mode 100644 index 000000000..40831648e --- /dev/null +++ b/exercises/practice/decimal/.docs/hints.md @@ -0,0 +1,5 @@ +## General + +- Instead of implementing arbitrary-precision arithmetic from scratch, consider building your type on top of the [num_bigint](https://crates.io/crates/num-bigint) crate. +- You might be able to [derive](https://doc.rust-lang.org/book/2018-edition/appendix-03-derivable-traits.html) some of the required traits. +- `Decimal` is assumed to be a signed type. You do not have to create a separate unsigned type, though you may do so as an implementation detail if you so choose. diff --git a/exercises/practice/decimal/.docs/instructions.md b/exercises/practice/decimal/.docs/instructions.md index 2ec586381..d954595df 100644 --- a/exercises/practice/decimal/.docs/instructions.md +++ b/exercises/practice/decimal/.docs/instructions.md @@ -13,9 +13,3 @@ In Rust, the way to get these operations on custom types is to implement the rel # Note It would be very easy to implement this exercise by using the [bigdecimal](https://crates.io/crates/bigdecimal) crate. Don't do that; implement this yourself. - -# Hints - -- Instead of implementing arbitrary-precision arithmetic from scratch, consider building your type on top of the [num_bigint](https://crates.io/crates/num-bigint) crate. -- You might be able to [derive](https://doc.rust-lang.org/book/2018-edition/appendix-03-derivable-traits.html) some of the required traits. -- `Decimal` is assumed to be a signed type. You do not have to create a separate unsigned type, though you may do so as an implementation detail if you so choose. diff --git a/exercises/practice/dot-dsl/.docs/instructions.append.md b/exercises/practice/dot-dsl/.docs/instructions.append.md index 33fd9f817..c9aabe67c 100644 --- a/exercises/practice/dot-dsl/.docs/instructions.append.md +++ b/exercises/practice/dot-dsl/.docs/instructions.append.md @@ -1,4 +1,6 @@ -# Builder pattern +# Instructions append + +## Builder pattern This exercise expects you to build several structs using `builder pattern`. In short, this pattern allows you to split the construction function of your struct, that contains a lot of arguments, into diff --git a/exercises/practice/doubly-linked-list/.docs/instructions.append.md b/exercises/practice/doubly-linked-list/.docs/hints.md similarity index 97% rename from exercises/practice/doubly-linked-list/.docs/instructions.append.md rename to exercises/practice/doubly-linked-list/.docs/hints.md index d2a6ef54e..afe765081 100644 --- a/exercises/practice/doubly-linked-list/.docs/instructions.append.md +++ b/exercises/practice/doubly-linked-list/.docs/hints.md @@ -1,4 +1,4 @@ -# Hints +## General * A doubly linked does not have a clear ownership hierarchy, which is why it requires either the use of unsafe or abstractions for shared ownership like `Rc`. The latter has some overhead that is unnecessary diff --git a/exercises/practice/forth/.docs/instructions.append.md b/exercises/practice/forth/.docs/instructions.append.md index 42b8ccdf0..947eed8e1 100644 --- a/exercises/practice/forth/.docs/instructions.append.md +++ b/exercises/practice/forth/.docs/instructions.append.md @@ -1,3 +1,5 @@ +# Instructions append + Note the additional test case in `tests/alloc-attack.rs`. It tests against algorithmically inefficient implementations. Because of that, it usually times out online instead of outright failing, leading to a less helpful error message. diff --git a/exercises/practice/hello-world/.docs/instructions.append.md b/exercises/practice/hello-world/.docs/instructions.append.md index 9c894cfaa..d14290126 100644 --- a/exercises/practice/hello-world/.docs/instructions.append.md +++ b/exercises/practice/hello-world/.docs/instructions.append.md @@ -1 +1,3 @@ +# Instructions append + In the Rust track, tests are run using the command `cargo test`. diff --git a/exercises/practice/high-scores/.docs/instructions.append.md b/exercises/practice/high-scores/.docs/hints.md similarity index 92% rename from exercises/practice/high-scores/.docs/instructions.append.md rename to exercises/practice/high-scores/.docs/hints.md index 20863a371..228e4850d 100644 --- a/exercises/practice/high-scores/.docs/instructions.append.md +++ b/exercises/practice/high-scores/.docs/hints.md @@ -1,4 +1,4 @@ -# Hints +## General Consider retaining a reference to `scores` in the struct - copying is not necessary. You will require some lifetime annotations, though. diff --git a/exercises/practice/largest-series-product/.docs/instructions.append.md b/exercises/practice/largest-series-product/.docs/hints.md similarity index 87% rename from exercises/practice/largest-series-product/.docs/instructions.append.md rename to exercises/practice/largest-series-product/.docs/hints.md index f91157776..7344c2e28 100644 --- a/exercises/practice/largest-series-product/.docs/instructions.append.md +++ b/exercises/practice/largest-series-product/.docs/hints.md @@ -1,4 +1,4 @@ -# Largest Series Product in Rust +## General These iterators may be useful, depending on your approach diff --git a/exercises/practice/macros/.docs/instructions.append.md b/exercises/practice/macros/.docs/instructions.append.md deleted file mode 100644 index 4c02cfac6..000000000 --- a/exercises/practice/macros/.docs/instructions.append.md +++ /dev/null @@ -1,3 +0,0 @@ -# Compatibility - -Note that this exercise requires Rust 1.36 or later. diff --git a/exercises/practice/paasio/.docs/instructions.append.md b/exercises/practice/paasio/.docs/instructions.append.md index 8fc197892..10428370d 100644 --- a/exercises/practice/paasio/.docs/instructions.append.md +++ b/exercises/practice/paasio/.docs/instructions.append.md @@ -1,4 +1,6 @@ -# Abstraction over Networks and Files +# Instructions append + +## Abstraction over Networks and Files Network and file operations are implemented in terms of the [`io::Read`][read] and [`io::Write`][write] traits. It will therefore be necessary to implement those traits for your types. diff --git a/exercises/practice/parallel-letter-frequency/.docs/instructions.append.md b/exercises/practice/parallel-letter-frequency/.docs/instructions.append.md index b858dd6c3..309eaba05 100644 --- a/exercises/practice/parallel-letter-frequency/.docs/instructions.append.md +++ b/exercises/practice/parallel-letter-frequency/.docs/instructions.append.md @@ -1,4 +1,6 @@ -# Parallel Letter Frequency in Rust +# Instructions append + +## Parallel Letter Frequency in Rust Learn more about concurrency in Rust here: diff --git a/exercises/practice/poker/.docs/instructions.append.md b/exercises/practice/poker/.docs/hints.md similarity index 98% rename from exercises/practice/poker/.docs/instructions.append.md rename to exercises/practice/poker/.docs/hints.md index fb531d554..0312531cc 100644 --- a/exercises/practice/poker/.docs/instructions.append.md +++ b/exercises/practice/poker/.docs/hints.md @@ -1,4 +1,4 @@ -# Hints +## General - Ranking a list of poker hands can be considered a sorting problem. - Rust provides the [sort](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.sort) method for `Vec where T: Ord`. diff --git a/exercises/practice/reverse-string/.docs/instructions.append.md b/exercises/practice/reverse-string/.docs/instructions.append.md index e13803cd4..f28f72c0a 100644 --- a/exercises/practice/reverse-string/.docs/instructions.append.md +++ b/exercises/practice/reverse-string/.docs/instructions.append.md @@ -1,4 +1,7 @@ -# Bonus +# Instructions append + +## Bonus + Test your function on this string: `uüu` and see what happens. Try to write a function that properly reverses this string. Hint: grapheme clusters diff --git a/exercises/practice/rna-transcription/.docs/instructions.append.md b/exercises/practice/rna-transcription/.docs/instructions.append.md index 25fc579e1..1273336ec 100644 --- a/exercises/practice/rna-transcription/.docs/instructions.append.md +++ b/exercises/practice/rna-transcription/.docs/instructions.append.md @@ -1,4 +1,6 @@ -# Notes on Rust implementation +# Instructions append + +## Notes on Rust implementation By using private fields in structs with public `new` functions returning `Option` or `Result` (as here with `DNA::new` & `RNA::new`), we can guarantee diff --git a/exercises/practice/say/.docs/instructions.append.md b/exercises/practice/say/.docs/instructions.append.md index 37260b9bf..41400ee83 100644 --- a/exercises/practice/say/.docs/instructions.append.md +++ b/exercises/practice/say/.docs/instructions.append.md @@ -1,3 +1,4 @@ +# Instructions append ## Rust Specific Exercise Notes @@ -14,6 +15,6 @@ Adding 'and' into number text has not been implemented in test cases. ### Extension -Add capability of converting up to the max value for u64: 18,446,744,073,709,551,615. +Add capability of converting up to the max value for u64: `18_446_744_073_709_551_615`. For hints at the output this should have, look at the last test case. diff --git a/exercises/practice/simple-linked-list/.docs/instructions.append.md b/exercises/practice/simple-linked-list/.docs/hints.md similarity index 97% rename from exercises/practice/simple-linked-list/.docs/instructions.append.md rename to exercises/practice/simple-linked-list/.docs/hints.md index 8803c5703..b9520d6d2 100644 --- a/exercises/practice/simple-linked-list/.docs/instructions.append.md +++ b/exercises/practice/simple-linked-list/.docs/hints.md @@ -1,4 +1,4 @@ -# Implementation Hints +## General Do not implement the struct `SimpleLinkedList` as a wrapper around a `Vec`. Instead, allocate nodes on the heap. diff --git a/exercises/practice/space-age/.docs/instructions.append.md b/exercises/practice/space-age/.docs/instructions.append.md index 3702e9904..1443ddea2 100644 --- a/exercises/practice/space-age/.docs/instructions.append.md +++ b/exercises/practice/space-age/.docs/instructions.append.md @@ -1,4 +1,6 @@ -# Topics +# Instructions append + +## Topics Some Rust topics you may want to read about while solving this problem: diff --git a/exercises/practice/triangle/.docs/instructions.append.md b/exercises/practice/triangle/.docs/instructions.append.md index deb679100..7ae558d39 100644 --- a/exercises/practice/triangle/.docs/instructions.append.md +++ b/exercises/practice/triangle/.docs/instructions.append.md @@ -1,4 +1,4 @@ -# Triangle in Rust +# Instructions append - [Result](https://doc.rust-lang.org/std/result/index.html) diff --git a/exercises/practice/xorcism/.docs/instructions.append.md b/exercises/practice/xorcism/.docs/instructions.append.md index c7bd1f3bf..7b90b0f4e 100644 --- a/exercises/practice/xorcism/.docs/instructions.append.md +++ b/exercises/practice/xorcism/.docs/instructions.append.md @@ -1,4 +1,6 @@ -# Lifetime of `munge` return value +# Instructions append + +## Lifetime of `munge` return value Due to the usage of the `impl Trait` feature, lifetime management may be a bit tricky when implementing the `munge` method. You may find it easier to write diff --git a/rust-tooling/ci-tests/tests/docs_are_valid.rs b/rust-tooling/ci-tests/tests/docs_are_valid.rs new file mode 100644 index 000000000..37d814d8a --- /dev/null +++ b/rust-tooling/ci-tests/tests/docs_are_valid.rs @@ -0,0 +1,32 @@ +use glob::glob; +use utils::fs::cd_into_repo_root; + +// https://exercism.org/docs/building/tracks/practice-exercises#h-file-docs-hints-md +#[test] +fn hints_have_correct_heading() { + cd_into_repo_root(); + for entry in glob("exercises/practice/*/.docs/hints.md").unwrap() { + let path = entry.unwrap(); + let content = std::fs::read_to_string(&path).unwrap(); + assert!( + content.starts_with("## General"), + "incorrect heading in {}", + path.display() + ) + } +} + +// https://exercism.org/docs/building/tracks/practice-exercises#h-file-docs-introduction-append-md +#[test] +fn instructions_append_have_correct_heading() { + cd_into_repo_root(); + for entry in glob("exercises/*/*/.docs/instructions.append.md").unwrap() { + let path = entry.unwrap(); + let content = std::fs::read_to_string(&path).unwrap(); + assert!( + content.starts_with("# Instructions append"), + "incorrect heading in {}", + path.display() + ) + } +} From 80cfa6139b1d37b11f490ba416473b7ff4d3ead9 Mon Sep 17 00:00:00 2001 From: Herobs Date: Sun, 14 Jan 2024 00:04:34 +0800 Subject: [PATCH 239/436] binary-search: Simplify recursion approach (#1852) --- .../binary-search/.approaches/introduction.md | 12 ++---- .../.approaches/recursion/content.md | 42 +++++++------------ .../.approaches/recursion/snippet.txt | 6 +-- 3 files changed, 21 insertions(+), 39 deletions(-) diff --git a/exercises/practice/binary-search/.approaches/introduction.md b/exercises/practice/binary-search/.approaches/introduction.md index 85aa49ae9..212f6c84b 100644 --- a/exercises/practice/binary-search/.approaches/introduction.md +++ b/exercises/practice/binary-search/.approaches/introduction.md @@ -42,23 +42,19 @@ For more information, check the [Looping approach][approach-looping]. ```rust use std::cmp::Ordering; -fn find_rec, T: Ord>(array: U, key: T, offset: usize) -> Option { +fn find, T: Ord>(array: U, key: T) -> Option { let array = array.as_ref(); - if array.len() == 0 { + if array.is_empty() { return None; } let mid = array.len() / 2; match array[mid].cmp(&key) { Ordering::Equal => Some(offset + mid), - Ordering::Less => find_rec(&array[mid + 1..], key, offset + mid + 1), - Ordering::Greater => find_rec(&array[..mid], key, offset), + Ordering::Greater => find(&array[..mid], key), + Ordering::Less => find(&array[mid + 1..], key).map(|p| p + mid + 1), } } - -pub fn find, T: Ord>(array: U, key: T) -> Option { - find_rec(array, key, 0) -} ``` For more information, check the [Recursion approach][approach-recursion]. diff --git a/exercises/practice/binary-search/.approaches/recursion/content.md b/exercises/practice/binary-search/.approaches/recursion/content.md index dcae57b84..44f0ad692 100644 --- a/exercises/practice/binary-search/.approaches/recursion/content.md +++ b/exercises/practice/binary-search/.approaches/recursion/content.md @@ -3,28 +3,24 @@ ```rust use std::cmp::Ordering; -fn find_rec, T: Ord>(array: U, key: T, offset: usize) -> Option { +fn find, T: Ord>(array: U, key: T) -> Option { let array = array.as_ref(); - if array.len() == 0 { + if array.is_empty() { return None; } let mid = array.len() / 2; match array[mid].cmp(&key) { - Ordering::Equal => Some(offset + mid), - Ordering::Less => find_rec(&array[mid + 1..], key, offset + mid + 1), - Ordering::Greater => find_rec(&array[..mid], key, offset), + Ordering::Equal => Some(mid), + Ordering::Greater => find(&array[..mid], key), + Ordering::Less => find(&array[mid + 1..], key).map(|p| p + mid + 1), } } - -pub fn find, T: Ord>(array: U, key: T) -> Option { - find_rec(array, key, 0) -} ``` This approach starts by using the [`Ordering`][ordering-enum] enum. -The `find_rec()` function has a signature to support the optional generic tests. +The `find()` function has a signature to support the optional generic tests. To support slices, arrays and Vecs, which can be of varying lengths and sizes at runtime, the compiler needs to be given informaton it can know at compile time. A reference to any of those containers will always be of the same size (essentially the size of a pointer), @@ -33,16 +29,11 @@ so [`AsRef`][asref] is used to constrain the generic type to be anything that is The `<[T]>` is used to constrain the reference type to an indexable type `T`. The `T` is constrained to be anything which implements the [`Ord`][ord] trait, which essentially means the values must be able to be ordered. -So, the `key` is of type `T` (orderable), and the `array` is of type `U` (a reference to an indexable container of orderable values -of the same type as the `key`.) - -Since slices of the `array` will keep getting shorter with each recursive call to itself, `find_rec()` has an `offset` parameter -to keep track of the actual midpoint as it relates to the original `array`. +So, the `key` is of type `T` (orderable), and the `array` is of type `U` (a reference to an indexable container of orderable values of the same type as the `key`.) Although `array` is defined as generic type `U`, which is constrained to be of type `AsRef`, the [`as_ref()`][asref] method is used to get the reference to the actual type. -Without it, the compiler would complain that "no method named `len` found for type parameter `U` in the current scope" and -"cannot index into a value of type `U`". +Without it, the compiler would complain that "no method named `len` found for type parameter `U` in the current scope" and "cannot index into a value of type `U`". If the `array` is empty, then [`None`][none] is returned. @@ -52,19 +43,14 @@ Since the element is a reference, the `key` must also be referenced. The [`match`][match] arms each use a value from the `Ordering` enum. -- If the midpoint element value equals the `key`, then the midpoint plus the offset is returned from the function wrapped in a [`Some`][some]. -- If the midpoint element value is less than the `key`, then `find_rec()` calls itself, - passing a slice of the `array` from the element to the right of the midpoint through the end of the `array`. - The offset is adjusted to be itself plus the midpoint plus `1`. -- If the midpoint element value is greater than the `key`, then `find_rec()` calls itself, +- If the midpoint element value equals the `key`, then the midpoint is returned from the function wrapped in a [`Some`][some]. +- If the midpoint element value is greater than the `key`, then `find()` calls itself, passing a slice of the `array` from the beginning up to but not including the midpoint element. - The offset remains as is. - -While the element value is not equal to the `key`, `find_rec()` keeps calling itself while halving the number of elements being searched, -until either the `key` is found, or, if it is not in the `array`, the `array` is whittled down to empty. +- If the midpoint element value is less than the `key`, then `find()` calls itself, + passing a slice of the `array` from the element to the right of the midpoint through the end of the `array`. + The return postion from the recursive call is the midpoint start from the new left(`mid + 1`), so we add `mid + 1` to the return postion. -The `find()` method returns the final result from calling the `find_rec()` method, passing in the `array`, `key`, and `0` for the initial -offset value. +While the element value is not equal to the `key`, `find()` keeps calling itself while halving the number of elements being searched, until either the `key` is found, or, if it is not in the `array`, the `array` is whittled down to empty. [ordering-enum]: https://doc.rust-lang.org/std/cmp/enum.Ordering.html [asref]: https://doc.rust-lang.org/std/convert/trait.AsRef.html diff --git a/exercises/practice/binary-search/.approaches/recursion/snippet.txt b/exercises/practice/binary-search/.approaches/recursion/snippet.txt index 0c8dd0da7..9d8ef3bd5 100644 --- a/exercises/practice/binary-search/.approaches/recursion/snippet.txt +++ b/exercises/practice/binary-search/.approaches/recursion/snippet.txt @@ -1,7 +1,7 @@ let mid = array.len() / 2; match array[mid].cmp(&key) { - Ordering::Equal => Some(offset + mid), - Ordering::Less => find_rec(&array[mid + 1..], key, offset + mid + 1), - Ordering::Greater => find_rec(&array[..mid], key, offset), + Ordering::Equal => Some(mid), + Ordering::Greater => find(&array[..mid], key), + Ordering::Less => find(&array[mid + 1..], key).map(|p| p + mid + 1), } From 636c2e739685ab9f7a518bbf49ddebd54d348bdc Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Mon, 15 Jan 2024 09:39:26 +0100 Subject: [PATCH 240/436] Sync leap with problem-specifications (#1853) A bunch of test cases not part of problem-specifications were deleted. Looking through the git history, it seems the manual cases were added first and the ones from problem-specfications later. It doesn't seem like there are any important edge cases not covered already. --- exercises/practice/leap/.docs/instructions.md | 21 +---- exercises/practice/leap/.docs/introduction.md | 16 ++++ exercises/practice/leap/.meta/config.json | 2 +- .../practice/leap/.meta/test_template.tera | 15 ++++ exercises/practice/leap/.meta/tests.toml | 40 +++++++++- exercises/practice/leap/tests/leap.rs | 76 ++++--------------- problem-specifications | 2 +- 7 files changed, 86 insertions(+), 86 deletions(-) create mode 100644 exercises/practice/leap/.docs/introduction.md create mode 100644 exercises/practice/leap/.meta/test_template.tera diff --git a/exercises/practice/leap/.docs/instructions.md b/exercises/practice/leap/.docs/instructions.md index a83826b2e..b14f8565d 100644 --- a/exercises/practice/leap/.docs/instructions.md +++ b/exercises/practice/leap/.docs/instructions.md @@ -1,22 +1,3 @@ # Instructions -Given a year, report if it is a leap year. - -The tricky thing here is that a leap year in the Gregorian calendar occurs: - -```text -on every year that is evenly divisible by 4 - except every year that is evenly divisible by 100 - unless the year is also evenly divisible by 400 -``` - -For example, 1997 is not a leap year, but 1996 is. -1900 is not a leap year, but 2000 is. - -## Notes - -Though our exercise adopts some very simple rules, there is more to learn! - -For a delightful, four minute explanation of the whole leap year phenomenon, go watch [this youtube video][video]. - -[video]: https://www.youtube.com/watch?v=xX96xng7sAE +Your task is to determine whether a given year is a leap year. diff --git a/exercises/practice/leap/.docs/introduction.md b/exercises/practice/leap/.docs/introduction.md new file mode 100644 index 000000000..1cb8b14cc --- /dev/null +++ b/exercises/practice/leap/.docs/introduction.md @@ -0,0 +1,16 @@ +# Introduction + +A leap year (in the Gregorian calendar) occurs: + +- In every year that is evenly divisible by 4 +- Unless the year is evenly divisible by 100, in which case it's only a leap year if the year is also evenly divisible by 400. + +Some examples: + +- 1997 was not a leap year as it's not divisible by 4. +- 1900 was not a leap year as it's not divisible by 400 +- 2000 was a leap year! + +~~~~exercism/note +For a delightful, four minute explanation of the whole phenomenon of leap years, check out [this youtube video](https://www.youtube.com/watch?v=xX96xng7sAE). +~~~~ diff --git a/exercises/practice/leap/.meta/config.json b/exercises/practice/leap/.meta/config.json index 38a2d0735..d02f64ba1 100644 --- a/exercises/practice/leap/.meta/config.json +++ b/exercises/practice/leap/.meta/config.json @@ -44,7 +44,7 @@ ".meta/example.rs" ] }, - "blurb": "Given a year, report if it is a leap year.", + "blurb": "Determine whether a given year is a leap year.", "source": "CodeRanch Cattle Drive, Assignment 3", "source_url": "https://coderanch.com/t/718816/Leap" } diff --git a/exercises/practice/leap/.meta/test_template.tera b/exercises/practice/leap/.meta/test_template.tera new file mode 100644 index 000000000..8cf7191cd --- /dev/null +++ b/exercises/practice/leap/.meta/test_template.tera @@ -0,0 +1,15 @@ +use {{ crate_name }}::*; + +{% for test in cases %} +#[test] +{% if loop.index != 1 -%} +#[ignore] +{% endif -%} +fn {{ test.description | slugify | replace(from="-", to="_") }}() { +{%- if test.expected %} + assert!(is_leap_year({{ test.input.year }})); +{% else %} + assert!(!is_leap_year({{ test.input.year }})); +{% endif -%} +} +{% endfor -%} diff --git a/exercises/practice/leap/.meta/tests.toml b/exercises/practice/leap/.meta/tests.toml index be690e975..ce6ba325e 100644 --- a/exercises/practice/leap/.meta/tests.toml +++ b/exercises/practice/leap/.meta/tests.toml @@ -1,3 +1,37 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[6466b30d-519c-438e-935d-388224ab5223] +description = "year not divisible by 4 in common year" + +[ac227e82-ee82-4a09-9eb6-4f84331ffdb0] +description = "year divisible by 2, not divisible by 4 in common year" + +[4fe9b84c-8e65-489e-970b-856d60b8b78e] +description = "year divisible by 4, not divisible by 100 in leap year" + +[7fc6aed7-e63c-48f5-ae05-5fe182f60a5d] +description = "year divisible by 4 and 5 is still a leap year" + +[78a7848f-9667-4192-ae53-87b30c9a02dd] +description = "year divisible by 100, not divisible by 400 in common year" + +[9d70f938-537c-40a6-ba19-f50739ce8bac] +description = "year divisible by 100 but not by 3 is still not a leap year" + +[42ee56ad-d3e6-48f1-8e3f-c84078d916fc] +description = "year divisible by 400 is leap year" + +[57902c77-6fe9-40de-8302-587b5c27121e] +description = "year divisible by 400 but not by 125 is still a leap year" + +[c30331f6-f9f6-4881-ad38-8ca8c12520c1] +description = "year divisible by 200, not divisible by 400 in common year" diff --git a/exercises/practice/leap/tests/leap.rs b/exercises/practice/leap/tests/leap.rs index 88b5cae7f..371f27e32 100644 --- a/exercises/practice/leap/tests/leap.rs +++ b/exercises/practice/leap/tests/leap.rs @@ -1,100 +1,54 @@ -fn process_leapyear_case(year: u64, expected: bool) { - assert_eq!(leap::is_leap_year(year), expected); -} +use leap::*; #[test] -fn year_not_divisible_by_4_common_year() { - process_leapyear_case(2015, false); +fn year_not_divisible_by_4_in_common_year() { + assert!(!is_leap_year(2015)); } #[test] #[ignore] fn year_divisible_by_2_not_divisible_by_4_in_common_year() { - process_leapyear_case(1970, false); + assert!(!is_leap_year(1970)); } #[test] #[ignore] -fn year_divisible_by_4_not_divisible_by_100_leap_year() { - process_leapyear_case(1996, true); +fn year_divisible_by_4_not_divisible_by_100_in_leap_year() { + assert!(is_leap_year(1996)); } #[test] #[ignore] fn year_divisible_by_4_and_5_is_still_a_leap_year() { - process_leapyear_case(1960, true); + assert!(is_leap_year(1960)); } #[test] #[ignore] -fn year_divisible_by_100_not_divisible_by_400_common_year() { - process_leapyear_case(2100, false); +fn year_divisible_by_100_not_divisible_by_400_in_common_year() { + assert!(!is_leap_year(2100)); } #[test] #[ignore] fn year_divisible_by_100_but_not_by_3_is_still_not_a_leap_year() { - process_leapyear_case(1900, false); + assert!(!is_leap_year(1900)); } #[test] #[ignore] -fn year_divisible_by_400_leap_year() { - process_leapyear_case(2000, true); +fn year_divisible_by_400_is_leap_year() { + assert!(is_leap_year(2000)); } #[test] #[ignore] fn year_divisible_by_400_but_not_by_125_is_still_a_leap_year() { - process_leapyear_case(2400, true); -} - -#[test] -#[ignore] -fn year_divisible_by_200_not_divisible_by_400_common_year() { - process_leapyear_case(1800, false); -} - -#[test] -#[ignore] -fn any_old_year() { - process_leapyear_case(1997, false); -} - -#[test] -#[ignore] -fn early_years() { - process_leapyear_case(1, false); - process_leapyear_case(4, true); - process_leapyear_case(100, false); - process_leapyear_case(400, true); - process_leapyear_case(900, false); -} - -#[test] -#[ignore] -fn century() { - process_leapyear_case(1700, false); - process_leapyear_case(1800, false); - process_leapyear_case(1900, false); + assert!(is_leap_year(2400)); } #[test] #[ignore] -fn exceptional_centuries() { - process_leapyear_case(1600, true); - process_leapyear_case(2000, true); - process_leapyear_case(2400, true); -} - -#[test] -#[ignore] -fn years_1600_to_1699() { - let incorrect_years = (1600..1700) - .filter(|&year| leap::is_leap_year(year) != (year % 4 == 0)) - .collect::>(); - - if !incorrect_years.is_empty() { - panic!("incorrect result for years: {incorrect_years:?}"); - } +fn year_divisible_by_200_not_divisible_by_400_in_common_year() { + assert!(!is_leap_year(1800)); } diff --git a/problem-specifications b/problem-specifications index 57f1387b3..1904c9bb0 160000 --- a/problem-specifications +++ b/problem-specifications @@ -1 +1 @@ -Subproject commit 57f1387b313ae9c94ab4a43f9586e85af67aa870 +Subproject commit 1904c9bb0ff2465d269744601d8ab84cc941c767 From e8dbccb5ef4b1f806e0f3221cc559b46ebe6b811 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Tue, 16 Jan 2024 20:31:36 +0100 Subject: [PATCH 241/436] leap: sync (#1856) --- exercises/practice/leap/.docs/introduction.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/exercises/practice/leap/.docs/introduction.md b/exercises/practice/leap/.docs/introduction.md index 1cb8b14cc..4ffd2da59 100644 --- a/exercises/practice/leap/.docs/introduction.md +++ b/exercises/practice/leap/.docs/introduction.md @@ -2,15 +2,15 @@ A leap year (in the Gregorian calendar) occurs: -- In every year that is evenly divisible by 4 +- In every year that is evenly divisible by 4. - Unless the year is evenly divisible by 100, in which case it's only a leap year if the year is also evenly divisible by 400. Some examples: - 1997 was not a leap year as it's not divisible by 4. -- 1900 was not a leap year as it's not divisible by 400 +- 1900 was not a leap year as it's not divisible by 400. - 2000 was a leap year! ~~~~exercism/note -For a delightful, four minute explanation of the whole phenomenon of leap years, check out [this youtube video](https://www.youtube.com/watch?v=xX96xng7sAE). +For a delightful, four-minute explanation of the whole phenomenon of leap years, check out [this YouTube video](https://www.youtube.com/watch?v=xX96xng7sAE). ~~~~ From e56c90e6cf31f058a7570092763d7a414a7aeeff Mon Sep 17 00:00:00 2001 From: Breno Ramalho Lemes Date: Tue, 16 Jan 2024 15:55:53 -0400 Subject: [PATCH 242/436] lasagna: Fix typo (#1857) --- exercises/concept/lucians-luscious-lasagna/.docs/hints.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/lucians-luscious-lasagna/.docs/hints.md b/exercises/concept/lucians-luscious-lasagna/.docs/hints.md index 36a9c10d5..28ee97c87 100644 --- a/exercises/concept/lucians-luscious-lasagna/.docs/hints.md +++ b/exercises/concept/lucians-luscious-lasagna/.docs/hints.md @@ -18,7 +18,7 @@ ## 3. Calculate the preparation time in minutes - You need to define a [function][functions] with a single parameter. -- You can use the [mathematical operator for multiplicaton][operators] to multiply values. +- You can use the [mathematical operator for multiplication][operators] to multiply values. ## 4. Calculate the elapsed time in minutes From 43226e1b23d73f6e4a7cc691eefc54589bd96873 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Fri, 19 Jan 2024 06:31:16 +0100 Subject: [PATCH 243/436] reverse-string: sync (#1859) --- .../practice/reverse-string/.docs/instructions.md | 10 ++++++---- .../practice/reverse-string/.docs/introduction.md | 5 +++++ exercises/practice/reverse-string/.meta/config.json | 2 +- 3 files changed, 12 insertions(+), 5 deletions(-) create mode 100644 exercises/practice/reverse-string/.docs/introduction.md diff --git a/exercises/practice/reverse-string/.docs/instructions.md b/exercises/practice/reverse-string/.docs/instructions.md index 039ee33ae..0ff4198e4 100644 --- a/exercises/practice/reverse-string/.docs/instructions.md +++ b/exercises/practice/reverse-string/.docs/instructions.md @@ -1,7 +1,9 @@ # Instructions -Reverse a string +Your task is to reverse a given string. -For example: -input: "cool" -output: "looc" +Some examples: + +- Turn `"stressed"` into `"desserts"`. +- Turn `"strops"` into `"sports"`. +- Turn `"racecar"` into `"racecar"`. diff --git a/exercises/practice/reverse-string/.docs/introduction.md b/exercises/practice/reverse-string/.docs/introduction.md new file mode 100644 index 000000000..02233e075 --- /dev/null +++ b/exercises/practice/reverse-string/.docs/introduction.md @@ -0,0 +1,5 @@ +# Introduction + +Reversing strings (reading them from right to left, rather than from left to right) is a surprisingly common task in programming. + +For example, in bioinformatics, reversing the sequence of DNA or RNA strings is often important for various analyses, such as finding complementary strands or identifying palindromic sequences that have biological significance. diff --git a/exercises/practice/reverse-string/.meta/config.json b/exercises/practice/reverse-string/.meta/config.json index 054b915c3..6fa8c43f1 100644 --- a/exercises/practice/reverse-string/.meta/config.json +++ b/exercises/practice/reverse-string/.meta/config.json @@ -32,7 +32,7 @@ ".meta/example.rs" ] }, - "blurb": "Reverse a string.", + "blurb": "Reverse a given string.", "source": "Introductory challenge to reverse an input string", "source_url": "https://medium.freecodecamp.org/how-to-reverse-a-string-in-javascript-in-3-different-ways-75e4763c68cb" } From 5d4a40e6da88e3495f389d256cdbd3a849f46955 Mon Sep 17 00:00:00 2001 From: Charles Lechasseur Date: Sun, 21 Jan 2024 05:06:50 -0500 Subject: [PATCH 244/436] sum-of-multiples: Add approaches (#1858) --- .../sum-of-multiples/.approaches/config.json | 27 +++++++++ .../.approaches/from-factors/content.md | 54 ++++++++++++++++++ .../.approaches/from-factors/snippet.txt | 8 +++ .../.approaches/from-range/content.md | 25 +++++++++ .../.approaches/from-range/snippet.txt | 5 ++ .../.approaches/introduction.md | 55 +++++++++++++++++++ 6 files changed, 174 insertions(+) create mode 100644 exercises/practice/sum-of-multiples/.approaches/config.json create mode 100644 exercises/practice/sum-of-multiples/.approaches/from-factors/content.md create mode 100644 exercises/practice/sum-of-multiples/.approaches/from-factors/snippet.txt create mode 100644 exercises/practice/sum-of-multiples/.approaches/from-range/content.md create mode 100644 exercises/practice/sum-of-multiples/.approaches/from-range/snippet.txt create mode 100644 exercises/practice/sum-of-multiples/.approaches/introduction.md diff --git a/exercises/practice/sum-of-multiples/.approaches/config.json b/exercises/practice/sum-of-multiples/.approaches/config.json new file mode 100644 index 000000000..4812f03b7 --- /dev/null +++ b/exercises/practice/sum-of-multiples/.approaches/config.json @@ -0,0 +1,27 @@ +{ + "introduction": { + "authors": [ + "clechasseur" + ] + }, + "approaches": [ + { + "uuid": "c0e599bf-a0b4-4eb3-af73-ab6c5b04dec8", + "slug": "from-factors", + "title": "Calculating sum from factors", + "blurb": "Calculate the sum by scanning the factors and computing their multiples.", + "authors": [ + "clechasseur" + ] + }, + { + "uuid": "305246ad-c36a-48ae-8047-cd00a4e7a3e4", + "slug": "from-range", + "title": "Calculating sum by iterating the whole range", + "blurb": "Calculate the sum by scanning the whole range and identifying any multiple via factors.", + "authors": [ + "clechasseur" + ] + } + ] + } diff --git a/exercises/practice/sum-of-multiples/.approaches/from-factors/content.md b/exercises/practice/sum-of-multiples/.approaches/from-factors/content.md new file mode 100644 index 000000000..af71f4433 --- /dev/null +++ b/exercises/practice/sum-of-multiples/.approaches/from-factors/content.md @@ -0,0 +1,54 @@ +# Calculating sum from factors + +```rust +pub fn sum_of_multiples_from_factors(limit: u32, factors: &[u32]) -> u32 { + let mut multiples: Vec<_> = factors + .iter() + .filter(|&&factor| factor != 0) + .flat_map(|&factor| (factor..limit).step_by(factor as usize)) + .collect(); + multiples.sort(); + multiples.dedup(); + multiples.iter().sum() +} +``` + +This approach implements the exact steps outlined in the exercise description: + +1. For each non-zero factor, find all multiples of that factor that are less than the `limit` +2. Collect all multiples in a [`Vec`][vec] +3. Remove duplicate multiples +3. Calculate the sum of all unique multiples + +In order to compute the list of multiples for a factor, we create a [`Range`][range] from the factor (inclusive) to the `limit` (exclusive), then use [`step_by`][iterator-step_by] with the same factor. + +To combine the multiples of all factors, we iterate the list of factors and use [`flat_map`][iterator-flat_map] on each factor's multiples. +[`flat_map`][iterator-flat_map] is a combination of [`map`][iterator-map] and [`flatten`][iterator-flatten]; it maps each factor into its multiples, then flattens them all in a single sequence. + +Since we need to have unique multiples to compute the proper sum, we [`collect`][iterator-collect] the multiples into a [`Vec`][vec], which allows us to then [`sort`][slice-sort][^1] them and use [`dedup`][vec-dedup] to remove the duplicates. +[`collect`][iterator-collect] is a powerful function that can collect the data in a sequence and store it in any kind of collection - however, because of this, the compiler is not able to infer the type of collection you want as the output. +To solve this problem, we type the `multiples` variable explicitly. + +Finally, calculating the sum of the remaining unique multiples in the set is easy: we can simply call [`sum`][iterator-sum]. + +[^1]: There is another method available to sort a slice: [`sort_unstable`][slice-sort_unstable]. Usually, using [`sort_unstable`][slice-sort_unstable] is recommended if we do not need to keep the ordering of duplicate elements (which is our case). However, [`sort`][slice-sort] has the advantage because of its implementation. From the documentation: + + > Current implementation + > + > The current algorithm is an adaptive, iterative merge sort inspired by timsort. It is designed to be very fast in cases where the slice is nearly sorted, or consists of two or more sorted sequences concatenated one after another. + + The last part is key, because this is exactly our use case: we concatenate sequences of _sorted_ multiples. + + Running a benchmark using the two methods shows that in our scenario, [`sort`][slice-sort] is about twice as fast as [`sort_unstable`][slice-sort_unstable]. + +[vec]: https://doc.rust-lang.org/std/vec/struct.Vec.html +[range]: https://doc.rust-lang.org/std/ops/struct.Range.html +[iterator-step_by]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.step_by +[iterator-flat_map]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.flat_map +[iterator-map]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.map +[iterator-flatten]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.flatten +[iterator-collect]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.collect +[slice-sort]: https://doc.rust-lang.org/std/primitive.slice.html#method.sort +[vec-dedup]: https://doc.rust-lang.org/std/vec/struct.Vec.html#method.dedup +[iterator-sum]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.sum +[slice-sort_unstable]: https://doc.rust-lang.org/std/primitive.slice.html#method.sort_unstable diff --git a/exercises/practice/sum-of-multiples/.approaches/from-factors/snippet.txt b/exercises/practice/sum-of-multiples/.approaches/from-factors/snippet.txt new file mode 100644 index 000000000..511d22574 --- /dev/null +++ b/exercises/practice/sum-of-multiples/.approaches/from-factors/snippet.txt @@ -0,0 +1,8 @@ +pub fn sum_of_multiples_from_factors(limit: u32, factors: &[u32]) -> u32 { + let mut multiples: Vec<_> = factors.iter() + .filter(|&&factor| factor != 0) + .flat_map(|&factor| (factor..limit).step_by(factor as usize)) + .collect(); + multiples.sort(); + multiples.dedup(); + multiples.iter().sum() diff --git a/exercises/practice/sum-of-multiples/.approaches/from-range/content.md b/exercises/practice/sum-of-multiples/.approaches/from-range/content.md new file mode 100644 index 000000000..24afd0a40 --- /dev/null +++ b/exercises/practice/sum-of-multiples/.approaches/from-range/content.md @@ -0,0 +1,25 @@ +# Calculating sum by iterating the whole range + +```rust +pub fn sum_of_multiples(limit: u32, factors: &[u32]) -> u32 { + (1..limit) + .filter(|&n| factors.iter().any(|&factor| factor != 0 && n % factor == 0)) + .sum() +} +``` + +Instead of implementing the steps in the exercise description, this approach uses another angle: + +1. Iterate all numbers between 1 (inclusive) and `limit` (exclusive) +2. Keep only numbers which have at least one factor in `factors` (automatically avoiding any duplicates) +3. Calculate the sum of all numbers kept + +After creating our range, we use [`filter`][iterator-filter] to keep only matching multiples. +To detect the multiples, we scan the `factors` and use [`any`][iterator-any] to check if at least one is a factor of the number we're checking. +([`any`][iterator-any] is short-circuiting: it stops as soon as it finds one compatible factor.) + +Finally, once we have the multiples, we can compute the sum easily using [`sum`][iterator-sum]. + +[iterator-filter]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.filter +[iterator-any]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.any +[iterator-sum]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.sum diff --git a/exercises/practice/sum-of-multiples/.approaches/from-range/snippet.txt b/exercises/practice/sum-of-multiples/.approaches/from-range/snippet.txt new file mode 100644 index 000000000..239db6b23 --- /dev/null +++ b/exercises/practice/sum-of-multiples/.approaches/from-range/snippet.txt @@ -0,0 +1,5 @@ +pub fn sum_of_multiples(limit: u32, factors: &[u32]) -> u32 { + (1..limit) + .filter(|&n| factors.iter().any(|&factor| factor != 0 && n % factor == 0)) + .sum() +} \ No newline at end of file diff --git a/exercises/practice/sum-of-multiples/.approaches/introduction.md b/exercises/practice/sum-of-multiples/.approaches/introduction.md new file mode 100644 index 000000000..a942e2dd4 --- /dev/null +++ b/exercises/practice/sum-of-multiples/.approaches/introduction.md @@ -0,0 +1,55 @@ +# Introduction + +There are a couple of different approaches available to solve Sum of Multiples. +One is to follow the algorithm [outlined in the exercise description][approach-from-factors], +but there are other ways, including [scanning the entire range][approach-from-range]. + +## General guidance + +The key to solving Sum of Multiples is to find the unique multiples of all provided factors. +To determine if `f` is a factor of a given number `n`, we can use the [remainder operator][rem]. +It is also possible to find the multiples by simple addition, starting from the factor. + +## Approach: Calculating sum from factors + +```rust +pub fn sum_of_multiples_from_factors(limit: u32, factors: &[u32]) -> u32 { + let mut multiples: Vec<_> = factors + .iter() + .filter(|&&factor| factor != 0) + .flat_map(|&factor| (factor..limit).step_by(factor as usize)) + .collect(); + multiples.sort(); + multiples.dedup(); + multiples.iter().sum() +} +``` + +For more information, check the [Sum from factors approach][approach-from-factors]. + +## Approach: Calculating sum by iterating the whole range + +```rust +pub fn sum_of_multiples(limit: u32, factors: &[u32]) -> u32 { + (1..limit) + .filter(|&n| factors.iter().any(|&factor| factor != 0 && n % factor == 0)) + .sum() +} +``` + +For more information, check the [Sum by iterating the whole range approach][approach-from-range]. + +## Which approach to use? + +- Computing the sum from factors can be efficient if we have a small number of factors and/or if they are large compared to the limit, because this will result in a small number of multiples to deduplicate. + However, as the number of multiples grows, this approach can result in a lot of work to deduplicate them. +- Computing the sum by iterating the whole range is less efficient for large ranges when the number of factors is small and/or when they are large. + However, this approach has the advantage of having stable complexity that is only dependent on the limit and the number of factors, since there is no deduplication involved. + It also avoids any additional memory allocation. + +Without proper benchmarks, the second approach may be preferred since it offers a more stable level of complexity (e.g. its performances varies less when the size of the input changes). +However, if you have some knowledge of the size and shape of the input, then benchmarking might reveal that one approach is better than the other for your specific use case. + +[approach-from-factors]: https://exercism.org/tracks/rust/exercises/sum-of-multiples/approaches/from-factors +[approach-from-range]: https://exercism.org/tracks/rust/exercises/sum-of-multiples/approaches/from-range +[rem]: https://doc.rust-lang.org/core/ops/trait.Rem.html From 4a9b30d6ad2969ba62a619485e4288979a5b7043 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Wed, 24 Jan 2024 14:26:27 +0100 Subject: [PATCH 245/436] anagram: Bring back unicode tests (#1860) I accidentally removed some unicode related tests in PR #1844. These are now upstreamed to problem-specifications. --- exercises/practice/anagram/.meta/tests.toml | 6 ++++++ exercises/practice/anagram/tests/anagram.rs | 20 ++++++++++++++++++++ problem-specifications | 2 +- 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/exercises/practice/anagram/.meta/tests.toml b/exercises/practice/anagram/.meta/tests.toml index 4f43811d2..4d9056270 100644 --- a/exercises/practice/anagram/.meta/tests.toml +++ b/exercises/practice/anagram/.meta/tests.toml @@ -78,3 +78,9 @@ include = false [33d3f67e-fbb9-49d3-a90e-0beb00861da7] description = "words other than themselves can be anagrams" reimplements = "a0705568-628c-4b55-9798-82e4acde51ca" + +[a6854f66-eec1-4afd-a137-62ef2870c051] +description = "handles case of greek letters" + +[fd3509e5-e3ba-409d-ac3d-a9ac84d13296] +description = "different characters may have the same bytes" diff --git a/exercises/practice/anagram/tests/anagram.rs b/exercises/practice/anagram/tests/anagram.rs index 44cab71a0..5814f693e 100644 --- a/exercises/practice/anagram/tests/anagram.rs +++ b/exercises/practice/anagram/tests/anagram.rs @@ -166,3 +166,23 @@ fn words_other_than_themselves_can_be_anagrams() { let expected = HashSet::from_iter(["Silent"]); assert_eq!(output, expected); } + +#[test] +#[ignore] +fn handles_case_of_greek_letters() { + let word = "ΑΒΓ"; + let inputs = &["ΒΓΑ", "ΒΓΔ", "γβα", "αβγ"]; + let output = anagrams_for(word, inputs); + let expected = HashSet::from_iter(["ΒΓΑ", "γβα"]); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn different_characters_may_have_the_same_bytes() { + let word = "a⬂"; + let inputs = &["€a"]; + let output = anagrams_for(word, inputs); + let expected = HashSet::from_iter([]); + assert_eq!(output, expected); +} diff --git a/problem-specifications b/problem-specifications index 1904c9bb0..524e2b3e1 160000 --- a/problem-specifications +++ b/problem-specifications @@ -1 +1 @@ -Subproject commit 1904c9bb0ff2465d269744601d8ab84cc941c767 +Subproject commit 524e2b3e186a5a307cd55d974e3c71796a947ebd From 4cebdf9763a188a49d4dfc93288423e1f23dd51b Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Wed, 24 Jan 2024 14:27:27 +0100 Subject: [PATCH 246/436] Apply additional tests consistently (#1861) The mechanism of the `additional-tests.json` file did not exist at the time I wrote this test template. This makes everything consistent. [no important files changed] --- .../acronym/.meta/additional-tests.json | 22 +++++++++++++++++++ .../practice/acronym/.meta/test_template.tera | 17 -------------- exercises/practice/acronym/tests/acronym.rs | 5 ++++- justfile | 4 ++++ 4 files changed, 30 insertions(+), 18 deletions(-) create mode 100644 exercises/practice/acronym/.meta/additional-tests.json diff --git a/exercises/practice/acronym/.meta/additional-tests.json b/exercises/practice/acronym/.meta/additional-tests.json new file mode 100644 index 000000000..18985ddab --- /dev/null +++ b/exercises/practice/acronym/.meta/additional-tests.json @@ -0,0 +1,22 @@ +[ + { + "uuid": "d9f6e6d2-79c3-4e53-a22d-66325e8614c6", + "description": "camelcase", + "comments": [ + "This test makes the exercise noticeably harder.", + "", + "Upstreaming this test is not a good option,", + "as most other languages would exclude it due to the added difficulty.", + "Removing the test from the Rust track is also not a good option,", + "because it creates confusion regarding existing community solutions.", + "", + "While deviations from problem-specifications should generally be avoided,", + "it seems like the best choice to stick with it in this case." + ], + "property": "abbreviate", + "input": { + "phrase": "HyperText Markup Language" + }, + "expected": "HTML" + } +] diff --git a/exercises/practice/acronym/.meta/test_template.tera b/exercises/practice/acronym/.meta/test_template.tera index 24212ac48..c8de609e1 100644 --- a/exercises/practice/acronym/.meta/test_template.tera +++ b/exercises/practice/acronym/.meta/test_template.tera @@ -10,20 +10,3 @@ fn {{ test.description | slugify | replace(from="-", to="_") }}() { assert_eq!(output, expected); } {% endfor -%} - -{# - This test makes the exercise noticeably harder. - - Upstreaming this test is not a good option, - as most other languages would exclude it due to the added difficulty. - Removing the test from the Rust track is also not a good option, - because it creates confusion regarding existing community solutions. - - While deviations from problem-specifications should generally be avoided, - it seems like the best choice to stick with it in this case. -#} -#[test] -#[ignore] -fn camelcase() { - assert_eq!(acronym::abbreviate("HyperText Markup Language"), "HTML"); -} diff --git a/exercises/practice/acronym/tests/acronym.rs b/exercises/practice/acronym/tests/acronym.rs index 971b76887..7dae77143 100644 --- a/exercises/practice/acronym/tests/acronym.rs +++ b/exercises/practice/acronym/tests/acronym.rs @@ -81,5 +81,8 @@ fn underscore_emphasis() { #[test] #[ignore] fn camelcase() { - assert_eq!(acronym::abbreviate("HyperText Markup Language"), "HTML"); + let input = "HyperText Markup Language"; + let output = acronym::abbreviate(input); + let expected = "HTML"; + assert_eq!(output, expected); } diff --git a/justfile b/justfile index 59bcadb55..ee2936d19 100644 --- a/justfile +++ b/justfile @@ -6,6 +6,10 @@ configlet *args="": @[ -f bin/configlet ] || bin/fetch-configlet ./bin/configlet {{ args }} +# generate a new uuid straight to your clipboard +uuid: + ./bin/configlet uuid | tr -d '[:space:]' | wl-copy + # simulate CI locally (WIP) test: just configlet lint From c655460cd9051475557c299e9571cfac0be3e269 Mon Sep 17 00:00:00 2001 From: Exercism Bot Date: Thu, 25 Jan 2024 15:16:44 +0000 Subject: [PATCH 247/436] =?UTF-8?q?=F0=9F=A4=96=20Sync=20org-wide=20files?= =?UTF-8?q?=20to=20upstream=20repo=20(#1862)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ℹ More info: https://github.com/exercism/org-wide-files/commit/42a096591ac4a748b6c4daf402d46c7eed640a1a 👁 Tracking issue: https://github.com/exercism/org-wide-files/issues/323 --- .../workflows/no-important-files-changed.yml | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 .github/workflows/no-important-files-changed.yml diff --git a/.github/workflows/no-important-files-changed.yml b/.github/workflows/no-important-files-changed.yml new file mode 100644 index 000000000..39a655174 --- /dev/null +++ b/.github/workflows/no-important-files-changed.yml @@ -0,0 +1,67 @@ +name: No important files changed + +on: + pull_request: + types: [opened] + branches: [main] + +permissions: + pull-requests: write + +jobs: + no_important_files_changed: + name: No important files changed + runs-on: ubuntu-22.04 + steps: + - name: Checkout code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Check if important files changed + id: check + run: | + set -exo pipefail + + # fetch a ref to the main branch so we can diff against it + git remote set-branches origin main + git fetch --depth 1 origin main + + for changed_file in $(git diff --diff-filter=M --name-only origin/main); do + if ! echo "$changed_file" | grep --quiet --extended-regexp 'exercises/(practice|concept)' ; then + continue + fi + slug="$(echo "$changed_file" | sed --regexp-extended 's#exercises/[^/]+/([^/]+)/.*#\1#' )" + path_before_slug="$(echo "$changed_file" | sed --regexp-extended "s#(.*)/$slug/.*#\\1#" )" + path_after_slug="$( echo "$changed_file" | sed --regexp-extended "s#.*/$slug/(.*)#\\1#" )" + + if ! [ -f "$path_before_slug/$slug/.meta/config.json" ]; then + # cannot determine if important files changed without .meta/config.json + continue + fi + + # returns 0 if the filter matches, 1 otherwise + # | contains($path_after_slug) + if jq --exit-status \ + --arg path_after_slug "$path_after_slug" \ + '[.files.test, .files.invalidator, .files.editor] | flatten | index($path_after_slug)' \ + "$path_before_slug/$slug/.meta/config.json" \ + > /dev/null; + then + echo "important_files_changed=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + done + + echo "important_files_changed=false" >> "$GITHUB_OUTPUT" + + - name: Suggest to add [no important files changed] + if: steps.check.outputs.important_files_changed == 'true' + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea + with: + script: | + const body = "This PR touches files which potentially affect the outcome of the tests of an exercise. This will cause all students' solutions to affected exercises to be re-tested.\n\nIf this PR does **not** affect the result of the test (or, for example, adds an edge case that is not worth rerunning all tests for), **please add the following to the merge-commit message** which will stops student's tests from re-running. Please copy-paste to avoid typos.\n```\n[no important files changed]\n```\n\n For more information, refer to the [documentation](https://exercism.org/docs/building/tracks#h-avoiding-triggering-unnecessary-test-runs). If you are unsure whether to add the message or not, please ping `@exercism/maintainers-admin` in a comment. Thank you!" + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: body + }) From ece7b5acb78c7869d6c47263c4365aeb2db33121 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Thu, 25 Jan 2024 22:35:01 +0100 Subject: [PATCH 248/436] anagram: Add note about unicode extension (#1863) --- exercises/practice/anagram/.docs/instructions.append.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/exercises/practice/anagram/.docs/instructions.append.md b/exercises/practice/anagram/.docs/instructions.append.md index 739b6f2f2..85e50cb5f 100644 --- a/exercises/practice/anagram/.docs/instructions.append.md +++ b/exercises/practice/anagram/.docs/instructions.append.md @@ -1,4 +1,6 @@ # Instructions append +The Rust track extends the possible letters to be any unicode character, not just ASCII alphabetic ones. + You are going to have to adjust the function signature provided in the stub in order for the lifetimes to work out properly. This is intentional: what's there demonstrates the basics of lifetime syntax, and what's missing teaches how to interpret lifetime-related compiler errors. From 3fa571432a2d3ba95621509bf1a083e3111c561b Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Mon, 29 Jan 2024 20:48:15 +0100 Subject: [PATCH 249/436] raindrops: sync (#1864) --- .../practice/raindrops/.docs/instructions.md | 26 +++++++++++-------- .../practice/raindrops/.docs/introduction.md | 3 +++ 2 files changed, 18 insertions(+), 11 deletions(-) create mode 100644 exercises/practice/raindrops/.docs/introduction.md diff --git a/exercises/practice/raindrops/.docs/instructions.md b/exercises/practice/raindrops/.docs/instructions.md index fc61d36e9..df6441075 100644 --- a/exercises/practice/raindrops/.docs/instructions.md +++ b/exercises/practice/raindrops/.docs/instructions.md @@ -1,20 +1,24 @@ # Instructions -Your task is to convert a number into a string that contains raindrop sounds corresponding to certain potential factors. -A factor is a number that evenly divides into another number, leaving no remainder. -The simplest way to test if one number is a factor of another is to use the [modulo operation][modulo]. +Your task is to convert a number into its corresponding raindrop sounds. -The rules of `raindrops` are that if a given number: +If a given number: -- has 3 as a factor, add 'Pling' to the result. -- has 5 as a factor, add 'Plang' to the result. -- has 7 as a factor, add 'Plong' to the result. -- _does not_ have any of 3, 5, or 7 as a factor, the result should be the digits of the number. +- is divisible by 3, add "Pling" to the result. +- is divisible by 5, add "Plang" to the result. +- is divisible by 7, add "Plong" to the result. +- **is not** divisible by 3, 5, or 7, the result should be the number as a string. ## Examples -- 28 has 7 as a factor, but not 3 or 5, so the result would be "Plong". -- 30 has both 3 and 5 as factors, but not 7, so the result would be "PlingPlang". -- 34 is not factored by 3, 5, or 7, so the result would be "34". +- 28 is divisible by 7, but not 3 or 5, so the result would be `"Plong"`. +- 30 is divisible by 3 and 5, but not 7, so the result would be `"PlingPlang"`. +- 34 is not divisible by 3, 5, or 7, so the result would be `"34"`. +~~~~exercism/note +A common way to test if one number is evenly divisible by another is to compare the [remainder][remainder] or [modulus][modulo] to zero. +Most languages provide operators or functions for one (or both) of these. + +[remainder]: https://exercism.org/docs/programming/operators/remainder [modulo]: https://en.wikipedia.org/wiki/Modulo_operation +~~~~ diff --git a/exercises/practice/raindrops/.docs/introduction.md b/exercises/practice/raindrops/.docs/introduction.md new file mode 100644 index 000000000..ba12100f3 --- /dev/null +++ b/exercises/practice/raindrops/.docs/introduction.md @@ -0,0 +1,3 @@ +# Introduction + +Raindrops is a slightly more complex version of the FizzBuzz challenge, a classic interview question. From 4cdc5c565ed9591d26e497fb028faf641e49f49b Mon Sep 17 00:00:00 2001 From: Exercism Bot Date: Thu, 1 Feb 2024 18:02:31 +0000 Subject: [PATCH 250/436] =?UTF-8?q?=F0=9F=A4=96=20Sync=20org-wide=20files?= =?UTF-8?q?=20to=20upstream=20repo=20(#1865)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/no-important-files-changed.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/no-important-files-changed.yml b/.github/workflows/no-important-files-changed.yml index 39a655174..26b068bc4 100644 --- a/.github/workflows/no-important-files-changed.yml +++ b/.github/workflows/no-important-files-changed.yml @@ -57,6 +57,7 @@ jobs: if: steps.check.outputs.important_files_changed == 'true' uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea with: + github-token: ${{ github.token }} script: | const body = "This PR touches files which potentially affect the outcome of the tests of an exercise. This will cause all students' solutions to affected exercises to be re-tested.\n\nIf this PR does **not** affect the result of the test (or, for example, adds an edge case that is not worth rerunning all tests for), **please add the following to the merge-commit message** which will stops student's tests from re-running. Please copy-paste to avoid typos.\n```\n[no important files changed]\n```\n\n For more information, refer to the [documentation](https://exercism.org/docs/building/tracks#h-avoiding-triggering-unnecessary-test-runs). If you are unsure whether to add the message or not, please ping `@exercism/maintainers-admin` in a comment. Thank you!" github.rest.issues.createComment({ From d8292da7c0dc5c8f411540c5172d66cb14b1c7c8 Mon Sep 17 00:00:00 2001 From: Exercism Bot Date: Fri, 2 Feb 2024 07:55:15 +0000 Subject: [PATCH 251/436] =?UTF-8?q?=F0=9F=A4=96=20Sync=20org-wide=20files?= =?UTF-8?q?=20to=20upstream=20repo=20(#1866)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CODE_OF_CONDUCT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index df8e36761..3f7813de1 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -90,4 +90,4 @@ This policy was initially adopted from the Front-end London Slack community and A version history can be seen on [GitHub](https://github.com/exercism/website-copy/edit/main/pages/code_of_conduct.md). _This policy is a "living" document, and subject to refinement and expansion in the future. -This policy applies to the Exercism website, the Exercism GitHub organization, any other Exercism-related communication channels (e.g. Slack, Twitter, email) and any other Exercism entity or event._ +This policy applies to the Exercism website, the Exercism GitHub organization, any other Exercism-related communication channels (e.g. Discord, Forum, Twitter, email) and any other Exercism entity or event._ From 39350f8f6edbeb245ad20e94bb6ef077cef68827 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Tue, 6 Feb 2024 13:03:04 +0100 Subject: [PATCH 252/436] roman-numerals: sync (#1867) --- .../roman-numerals/.docs/instructions.md | 45 +++----------- .../roman-numerals/.docs/introduction.md | 59 +++++++++++++++++++ .../practice/roman-numerals/.meta/config.json | 2 +- 3 files changed, 68 insertions(+), 38 deletions(-) create mode 100644 exercises/practice/roman-numerals/.docs/introduction.md diff --git a/exercises/practice/roman-numerals/.docs/instructions.md b/exercises/practice/roman-numerals/.docs/instructions.md index 247ea0892..50e2f5bf1 100644 --- a/exercises/practice/roman-numerals/.docs/instructions.md +++ b/exercises/practice/roman-numerals/.docs/instructions.md @@ -1,41 +1,12 @@ -# Instructions +# Introduction -Write a function to convert from normal numbers to Roman Numerals. +Your task is to convert a number from Arabic numerals to Roman numerals. -The Romans were a clever bunch. -They conquered most of Europe and ruled it for hundreds of years. -They invented concrete and straight roads and even bikinis. -One thing they never discovered though was the number zero. -This made writing and dating extensive histories of their exploits slightly more challenging, but the system of numbers they came up with is still in use today. -For example the BBC uses Roman numerals to date their programs. +For this exercise, we are only concerned about traditional Roman numerals, in which the largest number is MMMCMXCIX (or 3,999). -The Romans wrote numbers using letters - I, V, X, L, C, D, M. -(notice these letters have lots of straight lines and are hence easy to hack into stone tablets). +~~~~exercism/note +There are lots of different ways to convert between Arabic and Roman numerals. +We recommend taking a naive approach first to familiarise yourself with the concept of Roman numerals and then search for more efficient methods. -```text - 1 => I -10 => X - 7 => VII -``` - -The maximum number supported by this notation is 3,999. -(The Romans themselves didn't tend to go any higher) - -Wikipedia says: Modern Roman numerals ... are written by expressing each digit separately starting with the left most digit and skipping any digit with a value of zero. - -To see this in practice, consider the example of 1990. - -In Roman numerals 1990 is MCMXC: - -1000=M -900=CM -90=XC - -2008 is written as MMVIII: - -2000=MM -8=VIII - -Learn more about [Roman numerals on Wikipedia][roman-numerals]. - -[roman-numerals]: https://wiki.imperivm-romanvm.com/wiki/Roman_Numerals +Make sure to check out our Deep Dive video at the end to explore the different approaches you can take! +~~~~ diff --git a/exercises/practice/roman-numerals/.docs/introduction.md b/exercises/practice/roman-numerals/.docs/introduction.md new file mode 100644 index 000000000..6fd942fef --- /dev/null +++ b/exercises/practice/roman-numerals/.docs/introduction.md @@ -0,0 +1,59 @@ +# Description + +Today, most people in the world use Arabic numerals (0–9). +But if you travelled back two thousand years, you'd find that most Europeans were using Roman numerals instead. + +To write a Roman numeral we use the following Latin letters, each of which has a value: + +| M | D | C | L | X | V | I | +| ---- | --- | --- | --- | --- | --- | --- | +| 1000 | 500 | 100 | 50 | 10 | 5 | 1 | + +A Roman numeral is a sequence of these letters, and its value is the sum of the letters' values. +For example, `XVIII` has the value 18 (`10 + 5 + 1 + 1 + 1 = 18`). + +There's one rule that makes things trickier though, and that's that **the same letter cannot be used more than three times in succession**. +That means that we can't express numbers such as 4 with the seemingly natural `IIII`. +Instead, for those numbers, we use a subtraction method between two letters. +So we think of `4` not as `1 + 1 + 1 + 1` but instead as `5 - 1`. +And slightly confusingly to our modern thinking, we write the smaller number first. +This applies only in the following cases: 4 (`IV`), 9 (`IX`), 40 (`XL`), 90 (`XC`), 400 (`CD`) and 900 (`CM`). + +Order matters in Roman numerals! +Letters (and the special compounds above) must be ordered by decreasing value from left to right. + +Here are some examples: + +```text + 105 => CV +---- => -- + 100 => C ++ 5 => V +``` + +```text + 106 => CVI +---- => -- + 100 => C ++ 5 => V ++ 1 => I +``` + +```text + 104 => CIV +---- => --- + 100 => C ++ 4 => IV +``` + +And a final more complex example: + +```text + 1996 => MCMXCVI +----- => ------- + 1000 => M ++ 900 => CM ++ 90 => XC ++ 5 => V ++ 1 => I +``` diff --git a/exercises/practice/roman-numerals/.meta/config.json b/exercises/practice/roman-numerals/.meta/config.json index b8bde4679..b60c299ec 100644 --- a/exercises/practice/roman-numerals/.meta/config.json +++ b/exercises/practice/roman-numerals/.meta/config.json @@ -32,7 +32,7 @@ ".meta/example.rs" ] }, - "blurb": "Write a function to convert from normal numbers to Roman Numerals.", + "blurb": "Convert modern Arabic numbers into Roman numerals.", "source": "The Roman Numeral Kata", "source_url": "https://codingdojo.org/kata/RomanNumerals/" } From 2973309dfe99b6e7550ba37d459595910fe24dc7 Mon Sep 17 00:00:00 2001 From: Exercism Bot Date: Fri, 1 Mar 2024 06:28:32 +0000 Subject: [PATCH 253/436] =?UTF-8?q?=F0=9F=A4=96=20Sync=20org-wide=20files?= =?UTF-8?q?=20to=20upstream=20repo=20(#1869)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ℹ More info: https://github.com/exercism/org-wide-files/commit/0c0972d1df4cd18d98c7df316348315b06ef49b4 👁 Tracking issue: https://github.com/exercism/org-wide-files/issues/347 --- .../workflows/no-important-files-changed.yml | 71 ++++--------------- 1 file changed, 13 insertions(+), 58 deletions(-) diff --git a/.github/workflows/no-important-files-changed.yml b/.github/workflows/no-important-files-changed.yml index 26b068bc4..b940c5991 100644 --- a/.github/workflows/no-important-files-changed.yml +++ b/.github/workflows/no-important-files-changed.yml @@ -1,68 +1,23 @@ name: No important files changed on: - pull_request: + pull_request_target: types: [opened] branches: [main] + paths: + - "exercises/concept/**" + - "exercises/practice/**" + - "!exercises/*/*/.approaches/**" + - "!exercises/*/*/.articles/**" + - "!exercises/*/*/.docs/**" + - "!exercises/*/*/.meta/**" permissions: pull-requests: write jobs: - no_important_files_changed: - name: No important files changed - runs-on: ubuntu-22.04 - steps: - - name: Checkout code - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - - name: Check if important files changed - id: check - run: | - set -exo pipefail - - # fetch a ref to the main branch so we can diff against it - git remote set-branches origin main - git fetch --depth 1 origin main - - for changed_file in $(git diff --diff-filter=M --name-only origin/main); do - if ! echo "$changed_file" | grep --quiet --extended-regexp 'exercises/(practice|concept)' ; then - continue - fi - slug="$(echo "$changed_file" | sed --regexp-extended 's#exercises/[^/]+/([^/]+)/.*#\1#' )" - path_before_slug="$(echo "$changed_file" | sed --regexp-extended "s#(.*)/$slug/.*#\\1#" )" - path_after_slug="$( echo "$changed_file" | sed --regexp-extended "s#.*/$slug/(.*)#\\1#" )" - - if ! [ -f "$path_before_slug/$slug/.meta/config.json" ]; then - # cannot determine if important files changed without .meta/config.json - continue - fi - - # returns 0 if the filter matches, 1 otherwise - # | contains($path_after_slug) - if jq --exit-status \ - --arg path_after_slug "$path_after_slug" \ - '[.files.test, .files.invalidator, .files.editor] | flatten | index($path_after_slug)' \ - "$path_before_slug/$slug/.meta/config.json" \ - > /dev/null; - then - echo "important_files_changed=true" >> "$GITHUB_OUTPUT" - exit 0 - fi - done - - echo "important_files_changed=false" >> "$GITHUB_OUTPUT" - - - name: Suggest to add [no important files changed] - if: steps.check.outputs.important_files_changed == 'true' - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea - with: - github-token: ${{ github.token }} - script: | - const body = "This PR touches files which potentially affect the outcome of the tests of an exercise. This will cause all students' solutions to affected exercises to be re-tested.\n\nIf this PR does **not** affect the result of the test (or, for example, adds an edge case that is not worth rerunning all tests for), **please add the following to the merge-commit message** which will stops student's tests from re-running. Please copy-paste to avoid typos.\n```\n[no important files changed]\n```\n\n For more information, refer to the [documentation](https://exercism.org/docs/building/tracks#h-avoiding-triggering-unnecessary-test-runs). If you are unsure whether to add the message or not, please ping `@exercism/maintainers-admin` in a comment. Thank you!" - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: body - }) + pause: + uses: exercism/github-actions/.github/workflows/check-no-important-files-changed.yml@main + with: + repository: ${{ github.event.pull_request.head.repo.owner.login }}/${{ github.event.pull_request.head.repo.name }} + ref: ${{ github.head_ref }} From e7d58d093576d9d0082b9ccdd5116eeb2258f165 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Mar 2024 22:14:28 +0100 Subject: [PATCH 254/436] Bump mio from 0.8.10 to 0.8.11 in /rust-tooling (#1870) Bumps [mio](https://github.com/tokio-rs/mio) from 0.8.10 to 0.8.11.
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=mio&package-manager=cargo&previous-version=0.8.10&new-version=0.8.11)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/exercism/rust/network/alerts).
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- rust-tooling/Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rust-tooling/Cargo.lock b/rust-tooling/Cargo.lock index 319ae51b0..105856035 100644 --- a/rust-tooling/Cargo.lock +++ b/rust-tooling/Cargo.lock @@ -568,9 +568,9 @@ checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "mio" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "log", From b57a883070c57184211abd0409fdbf803f7f5d1c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Mar 2024 22:14:54 +0100 Subject: [PATCH 255/436] Bump actions/checkout from 4.1.1 to 4.1.2 (#1871) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.1 to 4.1.2.
Release notes

Sourced from actions/checkout's releases.

v4.1.2

We are investigating the following issue with this release and have rolled-back the v4 tag to point to v4.1.1

What's Changed

New Contributors

Full Changelog: https://github.com/actions/checkout/compare/v4.1.1...v4.1.2

Changelog

Sourced from actions/checkout's changelog.

Changelog

v4.1.2

v4.1.1

v4.1.0

v4.0.0

v3.6.0

v3.5.3

v3.5.2

v3.5.1

v3.5.0

v3.4.0

v3.3.0

v3.2.0

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=4.1.1&new-version=4.1.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/tests.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index de62dbd1c..4bafcdfc9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,7 +18,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - name: Fetch configlet run: ./bin/fetch-configlet @@ -32,7 +32,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - name: Run markdown lint run: ./bin/lint_markdown.sh @@ -43,7 +43,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - name: Run shellcheck uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # v2.0.0 @@ -58,7 +58,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - name: Fetch origin/main run: git fetch --depth=1 origin main @@ -84,7 +84,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - name: Setup toolchain uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 @@ -100,7 +100,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - name: Setup toolchain uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 @@ -123,7 +123,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - name: Setup toolchain uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 @@ -148,7 +148,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - name: Setup nightly toolchain uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 From 80d9a184ebe79f79ab3be3e8bfadd9f635dda688 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Wed, 27 Mar 2024 09:23:03 +0100 Subject: [PATCH 256/436] Use usize for counts in word-count (#1874) Due to type inference, this is not a breaking change. closes #1845 [no important files changed] --- .../practice/word-count/.meta/example.rs | 4 +- .../word-count/.meta/test_template.tera | 29 ++-- .../practice/word-count/tests/word-count.rs | 140 +++++++++++------- 3 files changed, 101 insertions(+), 72 deletions(-) diff --git a/exercises/practice/word-count/.meta/example.rs b/exercises/practice/word-count/.meta/example.rs index f2074ec0c..eb1d80942 100644 --- a/exercises/practice/word-count/.meta/example.rs +++ b/exercises/practice/word-count/.meta/example.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; -pub fn word_count(input: &str) -> HashMap { - let mut map: HashMap = HashMap::new(); +pub fn word_count(input: &str) -> HashMap { + let mut map = HashMap::new(); let lower = input.to_lowercase(); let slice: &str = lower.as_ref(); for word in slice diff --git a/exercises/practice/word-count/.meta/test_template.tera b/exercises/practice/word-count/.meta/test_template.tera index 20d887ede..faec5732d 100644 --- a/exercises/practice/word-count/.meta/test_template.tera +++ b/exercises/practice/word-count/.meta/test_template.tera @@ -1,16 +1,5 @@ -use std::collections::HashMap; +use word_count::*; -fn check_word_count(mut output: HashMap, pairs: &[(&str, u32)]) { - // The reason for the awkward code in here is to ensure that the failure - // message for assert_eq! is as informative as possible. A simpler - // solution would simply check the length of the map, and then - // check for the presence and value of each key in the given pairs vector. - for &(k, v) in pairs.iter() { - assert_eq!((k, output.remove(&k.to_string()).unwrap_or(0)), (k, v)); - } - // may fail with a message that clearly shows all extra pairs in the map - assert_eq!(output.iter().collect::>(), vec![]); -} {% for test in cases %} #[test] {% if loop.index != 1 -%} @@ -18,10 +7,20 @@ fn check_word_count(mut output: HashMap, pairs: &[(&str, u32)]) { {% endif -%} fn {{ test.description | slugify | replace(from="-", to="_") }}() { let input = {{ test.input.sentence | json_encode() }}; - let output = {{ crate_name }}::{{ fn_names[0] }}(input); - let expected = &[{% for key, value in test.expected -%} + let mut output = word_count(input); + let expected = [{% for key, value in test.expected -%} ({{ key | json_encode() }}, {{ value }}), {%- endfor %}]; - check_word_count(output, expected); + {#- + The reason for the awkward code in here is to ensure that the failure + message for assert_eq! is as informative as possible. A simpler + solution would simply check the length of the map, and then + check for the presence and value of each key in the given pairs vector. + #} + for (word, count) in expected { + assert_eq!((word, output.remove(word).unwrap_or(0)), (word, count)); + } + {#- may fail with a message that clearly shows all extra pairs in the map #} + assert_eq!(output.into_iter().collect::>(), vec![]); } {% endfor -%} diff --git a/exercises/practice/word-count/tests/word-count.rs b/exercises/practice/word-count/tests/word-count.rs index c37a3282f..40c92bd2c 100644 --- a/exercises/practice/word-count/tests/word-count.rs +++ b/exercises/practice/word-count/tests/word-count.rs @@ -1,100 +1,112 @@ -use std::collections::HashMap; - -fn check_word_count(mut output: HashMap, pairs: &[(&str, u32)]) { - // The reason for the awkward code in here is to ensure that the failure - // message for assert_eq! is as informative as possible. A simpler - // solution would simply check the length of the map, and then - // check for the presence and value of each key in the given pairs vector. - for &(k, v) in pairs.iter() { - assert_eq!((k, output.remove(&k.to_string()).unwrap_or(0)), (k, v)); - } - // may fail with a message that clearly shows all extra pairs in the map - assert_eq!(output.iter().collect::>(), vec![]); -} +use word_count::*; #[test] fn count_one_word() { let input = "word"; - let output = word_count::word_count(input); - let expected = &[("word", 1)]; - check_word_count(output, expected); + let mut output = word_count(input); + let expected = [("word", 1)]; + for (word, count) in expected { + assert_eq!((word, output.remove(word).unwrap_or(0)), (word, count)); + } + assert_eq!(output.into_iter().collect::>(), vec![]); } #[test] #[ignore] fn count_one_of_each_word() { let input = "one of each"; - let output = word_count::word_count(input); - let expected = &[("one", 1), ("of", 1), ("each", 1)]; - check_word_count(output, expected); + let mut output = word_count(input); + let expected = [("one", 1), ("of", 1), ("each", 1)]; + for (word, count) in expected { + assert_eq!((word, output.remove(word).unwrap_or(0)), (word, count)); + } + assert_eq!(output.into_iter().collect::>(), vec![]); } #[test] #[ignore] fn multiple_occurrences_of_a_word() { let input = "one fish two fish red fish blue fish"; - let output = word_count::word_count(input); - let expected = &[("one", 1), ("fish", 4), ("two", 1), ("red", 1), ("blue", 1)]; - check_word_count(output, expected); + let mut output = word_count(input); + let expected = [("one", 1), ("fish", 4), ("two", 1), ("red", 1), ("blue", 1)]; + for (word, count) in expected { + assert_eq!((word, output.remove(word).unwrap_or(0)), (word, count)); + } + assert_eq!(output.into_iter().collect::>(), vec![]); } #[test] #[ignore] fn handles_cramped_lists() { let input = "one,two,three"; - let output = word_count::word_count(input); - let expected = &[("one", 1), ("two", 1), ("three", 1)]; - check_word_count(output, expected); + let mut output = word_count(input); + let expected = [("one", 1), ("two", 1), ("three", 1)]; + for (word, count) in expected { + assert_eq!((word, output.remove(word).unwrap_or(0)), (word, count)); + } + assert_eq!(output.into_iter().collect::>(), vec![]); } #[test] #[ignore] fn handles_expanded_lists() { let input = "one,\ntwo,\nthree"; - let output = word_count::word_count(input); - let expected = &[("one", 1), ("two", 1), ("three", 1)]; - check_word_count(output, expected); + let mut output = word_count(input); + let expected = [("one", 1), ("two", 1), ("three", 1)]; + for (word, count) in expected { + assert_eq!((word, output.remove(word).unwrap_or(0)), (word, count)); + } + assert_eq!(output.into_iter().collect::>(), vec![]); } #[test] #[ignore] fn ignore_punctuation() { let input = "car: carpet as java: javascript!!&@$%^&"; - let output = word_count::word_count(input); - let expected = &[ + let mut output = word_count(input); + let expected = [ ("car", 1), ("carpet", 1), ("as", 1), ("java", 1), ("javascript", 1), ]; - check_word_count(output, expected); + for (word, count) in expected { + assert_eq!((word, output.remove(word).unwrap_or(0)), (word, count)); + } + assert_eq!(output.into_iter().collect::>(), vec![]); } #[test] #[ignore] fn include_numbers() { let input = "testing, 1, 2 testing"; - let output = word_count::word_count(input); - let expected = &[("testing", 2), ("1", 1), ("2", 1)]; - check_word_count(output, expected); + let mut output = word_count(input); + let expected = [("testing", 2), ("1", 1), ("2", 1)]; + for (word, count) in expected { + assert_eq!((word, output.remove(word).unwrap_or(0)), (word, count)); + } + assert_eq!(output.into_iter().collect::>(), vec![]); } #[test] #[ignore] fn normalize_case() { let input = "go Go GO Stop stop"; - let output = word_count::word_count(input); - let expected = &[("go", 3), ("stop", 2)]; - check_word_count(output, expected); + let mut output = word_count(input); + let expected = [("go", 3), ("stop", 2)]; + for (word, count) in expected { + assert_eq!((word, output.remove(word).unwrap_or(0)), (word, count)); + } + assert_eq!(output.into_iter().collect::>(), vec![]); } #[test] #[ignore] fn with_apostrophes() { let input = "'First: don't laugh. Then: don't cry. You're getting it.'"; - let output = word_count::word_count(input); - let expected = &[ + let mut output = word_count(input); + let expected = [ ("first", 1), ("don't", 2), ("laugh", 1), @@ -104,15 +116,18 @@ fn with_apostrophes() { ("getting", 1), ("it", 1), ]; - check_word_count(output, expected); + for (word, count) in expected { + assert_eq!((word, output.remove(word).unwrap_or(0)), (word, count)); + } + assert_eq!(output.into_iter().collect::>(), vec![]); } #[test] #[ignore] fn with_quotations() { let input = "Joe can't tell between 'large' and large."; - let output = word_count::word_count(input); - let expected = &[ + let mut output = word_count(input); + let expected = [ ("joe", 1), ("can't", 1), ("tell", 1), @@ -120,15 +135,18 @@ fn with_quotations() { ("large", 2), ("and", 1), ]; - check_word_count(output, expected); + for (word, count) in expected { + assert_eq!((word, output.remove(word).unwrap_or(0)), (word, count)); + } + assert_eq!(output.into_iter().collect::>(), vec![]); } #[test] #[ignore] fn substrings_from_the_beginning() { let input = "Joe can't tell between app, apple and a."; - let output = word_count::word_count(input); - let expected = &[ + let mut output = word_count(input); + let expected = [ ("joe", 1), ("can't", 1), ("tell", 1), @@ -138,32 +156,44 @@ fn substrings_from_the_beginning() { ("and", 1), ("a", 1), ]; - check_word_count(output, expected); + for (word, count) in expected { + assert_eq!((word, output.remove(word).unwrap_or(0)), (word, count)); + } + assert_eq!(output.into_iter().collect::>(), vec![]); } #[test] #[ignore] fn multiple_spaces_not_detected_as_a_word() { let input = " multiple whitespaces"; - let output = word_count::word_count(input); - let expected = &[("multiple", 1), ("whitespaces", 1)]; - check_word_count(output, expected); + let mut output = word_count(input); + let expected = [("multiple", 1), ("whitespaces", 1)]; + for (word, count) in expected { + assert_eq!((word, output.remove(word).unwrap_or(0)), (word, count)); + } + assert_eq!(output.into_iter().collect::>(), vec![]); } #[test] #[ignore] fn alternating_word_separators_not_detected_as_a_word() { let input = ",\n,one,\n ,two \n 'three'"; - let output = word_count::word_count(input); - let expected = &[("one", 1), ("two", 1), ("three", 1)]; - check_word_count(output, expected); + let mut output = word_count(input); + let expected = [("one", 1), ("two", 1), ("three", 1)]; + for (word, count) in expected { + assert_eq!((word, output.remove(word).unwrap_or(0)), (word, count)); + } + assert_eq!(output.into_iter().collect::>(), vec![]); } #[test] #[ignore] fn quotation_for_word_with_apostrophe() { let input = "can, can't, 'can't'"; - let output = word_count::word_count(input); - let expected = &[("can", 1), ("can't", 2)]; - check_word_count(output, expected); + let mut output = word_count(input); + let expected = [("can", 1), ("can't", 2)]; + for (word, count) in expected { + assert_eq!((word, output.remove(word).unwrap_or(0)), (word, count)); + } + assert_eq!(output.into_iter().collect::>(), vec![]); } From 6ba09220e03b1878fb06dd8239b52c97fb23d1c3 Mon Sep 17 00:00:00 2001 From: Khoi Ngo Date: Wed, 27 Mar 2024 04:26:57 -0400 Subject: [PATCH 257/436] Use `as_bytes()` instead of `chars()` for ASCII strings (#1872) [no important files changed] --- exercises/practice/minesweeper/tests/minesweeper.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/exercises/practice/minesweeper/tests/minesweeper.rs b/exercises/practice/minesweeper/tests/minesweeper.rs index f05255e5c..807ede4ae 100644 --- a/exercises/practice/minesweeper/tests/minesweeper.rs +++ b/exercises/practice/minesweeper/tests/minesweeper.rs @@ -5,9 +5,10 @@ fn remove_annotations(board: &[&str]) -> Vec { } fn remove_annotations_in_row(row: &str) -> String { - row.chars() - .map(|ch| match ch { - '*' => '*', + row.as_bytes() + .iter() + .map(|&ch| match ch { + b'*' => '*', _ => ' ', }) .collect() From 88fce47b11c5cbe16c53c6b6d1bfbefe525a8881 Mon Sep 17 00:00:00 2001 From: Exercism Bot Date: Wed, 27 Mar 2024 08:28:04 +0000 Subject: [PATCH 258/436] =?UTF-8?q?=F0=9F=A4=96=20Sync=20org-wide=20files?= =?UTF-8?q?=20to=20upstream=20repo=20(#1875)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/no-important-files-changed.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/no-important-files-changed.yml b/.github/workflows/no-important-files-changed.yml index b940c5991..812e91296 100644 --- a/.github/workflows/no-important-files-changed.yml +++ b/.github/workflows/no-important-files-changed.yml @@ -16,7 +16,7 @@ permissions: pull-requests: write jobs: - pause: + check: uses: exercism/github-actions/.github/workflows/check-no-important-files-changed.yml@main with: repository: ${{ github.event.pull_request.head.repo.owner.login }}/${{ github.event.pull_request.head.repo.name }} From d8b7fb237d823b65618c3fafe78695a45f3b9eb6 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Wed, 27 Mar 2024 10:07:56 +0100 Subject: [PATCH 259/436] Remove redundant imports to fix CI (#1876) --- exercises/practice/poker/.meta/example.rs | 2 +- exercises/practice/simple-linked-list/.meta/example.rs | 2 -- exercises/practice/simple-linked-list/src/lib.rs | 2 -- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/exercises/practice/poker/.meta/example.rs b/exercises/practice/poker/.meta/example.rs index 740cb2489..02b5ed78d 100644 --- a/exercises/practice/poker/.meta/example.rs +++ b/exercises/practice/poker/.meta/example.rs @@ -1,5 +1,5 @@ +use std::cmp::Ordering; use std::fmt; -use std::{cmp::Ordering, convert::TryFrom}; use counter::Counter; diff --git a/exercises/practice/simple-linked-list/.meta/example.rs b/exercises/practice/simple-linked-list/.meta/example.rs index 9647fc5cb..7061edb76 100644 --- a/exercises/practice/simple-linked-list/.meta/example.rs +++ b/exercises/practice/simple-linked-list/.meta/example.rs @@ -1,5 +1,3 @@ -use std::iter::FromIterator; - pub struct SimpleLinkedList { head: Option>>, len: usize, diff --git a/exercises/practice/simple-linked-list/src/lib.rs b/exercises/practice/simple-linked-list/src/lib.rs index f8597f8ef..42121a0ca 100644 --- a/exercises/practice/simple-linked-list/src/lib.rs +++ b/exercises/practice/simple-linked-list/src/lib.rs @@ -1,5 +1,3 @@ -use std::iter::FromIterator; - pub struct SimpleLinkedList { // Delete this field // dummy is needed to avoid unused parameter error during compilation From 2647c061c3a781eb57bb0b448bbcb4c9b48014b9 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Thu, 28 Mar 2024 09:39:30 +0100 Subject: [PATCH 260/436] Add test to ensure tera templates are in sync (#1879) closes #1878 --- exercises/practice/knapsack/tests/knapsack.rs | 10 +++++++ .../queen-attack/.meta/test_template.tera | 2 +- .../.meta/test_template.tera | 4 +-- .../wordy/.meta/additional-tests.json | 28 +++++++++++++++++++ .../practice/wordy/.meta/test_template.tera | 5 +++- .../src/{exercise_generation.rs => lib.rs} | 19 +++++++++++++ rust-tooling/generate/src/main.rs | 24 ++-------------- .../tests/tera_templates_are_in_sync.rs | 23 +++++++++++++++ 8 files changed, 89 insertions(+), 26 deletions(-) create mode 100644 exercises/practice/wordy/.meta/additional-tests.json rename rust-tooling/generate/src/{exercise_generation.rs => lib.rs} (90%) create mode 100644 rust-tooling/generate/tests/tera_templates_are_in_sync.rs diff --git a/exercises/practice/knapsack/tests/knapsack.rs b/exercises/practice/knapsack/tests/knapsack.rs index 165fe07dd..0463142a9 100644 --- a/exercises/practice/knapsack/tests/knapsack.rs +++ b/exercises/practice/knapsack/tests/knapsack.rs @@ -1,6 +1,16 @@ use knapsack::*; #[test] +fn test_no_items() { + let max_weight = 100; + let items = []; + let output = maximum_value(max_weight, &items); + let expected = 0; + assert_eq!(output, expected); +} + +#[test] +#[ignore] fn test_one_item_too_heavy() { let max_weight = 10; let items = [Item { diff --git a/exercises/practice/queen-attack/.meta/test_template.tera b/exercises/practice/queen-attack/.meta/test_template.tera index ff40eed91..da0a35ab9 100644 --- a/exercises/practice/queen-attack/.meta/test_template.tera +++ b/exercises/practice/queen-attack/.meta/test_template.tera @@ -1,4 +1,4 @@ -use queen_attack::{ChessPiece, ChessPosition, Queen}; +use queen_attack::*; {% for test in cases %} #[test] diff --git a/exercises/practice/variable-length-quantity/.meta/test_template.tera b/exercises/practice/variable-length-quantity/.meta/test_template.tera index f36d9b504..27cd22a1c 100644 --- a/exercises/practice/variable-length-quantity/.meta/test_template.tera +++ b/exercises/practice/variable-length-quantity/.meta/test_template.tera @@ -7,7 +7,7 @@ use variable_length_quantity as vlq; fn {{ test.description | slugify | replace(from="-", to="_") }}() { {%- if test.property == "encode" %} let input = &{{ test.input.integers | json_encode() }}; - let output = vlq::{{ fn_names[0] }}(input); + let output = vlq::to_bytes(input); let expected = vec![ {%- for byte in test.expected -%} 0x{{ byte | to_hex }}, @@ -19,7 +19,7 @@ fn {{ test.description | slugify | replace(from="-", to="_") }}() { 0x{{ byte | to_hex }}, {%- endfor -%} ]; - let output = vlq::{{ fn_names[1] }}(input); + let output = vlq::from_bytes(input); let expected = {% if test.expected is object -%} Err(vlq::Error::IncompleteNumber) {%- else -%} diff --git a/exercises/practice/wordy/.meta/additional-tests.json b/exercises/practice/wordy/.meta/additional-tests.json new file mode 100644 index 000000000..592290e2a --- /dev/null +++ b/exercises/practice/wordy/.meta/additional-tests.json @@ -0,0 +1,28 @@ +[ + { + "uuid": "88bf4b28-0de3-4883-93c7-db1b14aa806e", + "description": "exponential", + "comments": [ + "This test case was added a long time ago.", + "Upstreaming it would make the exercise more difficult." + ], + "property": "exponentials", + "input": { + "question": "What is 2 raised to the 5th power?" + }, + "expected": 32 + }, + { + "uuid": "bb8c655c-cf42-4dfc-90e0-152fcfd8d4e0", + "description": "addition and exponential", + "comments": [ + "This test case was added a long time ago.", + "Upstreaming it would make the exercise more difficult." + ], + "property": "exponentials", + "input": { + "question": "What is 1 plus 2 raised to the 2nd power?" + }, + "expected": 9 + } +] diff --git a/exercises/practice/wordy/.meta/test_template.tera b/exercises/practice/wordy/.meta/test_template.tera index 20f0191ba..8a362161f 100644 --- a/exercises/practice/wordy/.meta/test_template.tera +++ b/exercises/practice/wordy/.meta/test_template.tera @@ -3,8 +3,11 @@ {% if loop.index != 1 -%} #[ignore] {% endif -%} +{% if test.property == "exponentials" -%} +#[cfg(feature = "exponentials")] +{% endif -%} fn {{ test.description | slugify | replace(from="-", to="_") }}() { - let input = {{ test.input | json_encode() }}; + let input = {{ test.input.question | json_encode() }}; let output = {{ crate_name }}::{{ fn_names[0] }}(input); let expected = {% if test.expected is object -%} None diff --git a/rust-tooling/generate/src/exercise_generation.rs b/rust-tooling/generate/src/lib.rs similarity index 90% rename from rust-tooling/generate/src/exercise_generation.rs rename to rust-tooling/generate/src/lib.rs index 4aa948949..0090dee66 100644 --- a/rust-tooling/generate/src/exercise_generation.rs +++ b/rust-tooling/generate/src/lib.rs @@ -162,3 +162,22 @@ It probably generates invalid Rust code." pub fn get_test_template(slug: &str) -> Option { Some(Tera::new(format!("exercises/practice/{slug}/.meta/*.tera").as_str()).unwrap()) } + +pub fn read_fn_names_from_lib_rs(slug: &str) -> Vec { + let lib_rs = + std::fs::read_to_string(format!("exercises/practice/{}/src/lib.rs", slug)).unwrap(); + + lib_rs + .split("fn ") + .skip(1) + .map(|f| { + let tmp = f.split_once('(').unwrap().0; + // strip generics + if let Some((res, _)) = tmp.split_once('<') { + res.to_string() + } else { + tmp.to_string() + } + }) + .collect() +} diff --git a/rust-tooling/generate/src/main.rs b/rust-tooling/generate/src/main.rs index 8eb2d624a..c653959ea 100644 --- a/rust-tooling/generate/src/main.rs +++ b/rust-tooling/generate/src/main.rs @@ -5,7 +5,6 @@ use cli::{AddArgs, FullAddArgs, UpdateArgs}; use models::track_config::{self, TRACK_CONFIG}; mod cli; -mod exercise_generation; fn main() { utils::fs::cd_into_repo_root(); @@ -79,12 +78,12 @@ fn make_configlet_generate_what_it_can(slug: &str) { fn generate_exercise_files(slug: &str, is_update: bool) { let fn_names = if is_update { - read_fn_names_from_lib_rs(slug) + generate::read_fn_names_from_lib_rs(slug) } else { vec!["TODO".to_string()] }; - let exercise = exercise_generation::new(slug, fn_names); + let exercise = generate::new(slug, fn_names); let exercise_path = PathBuf::from("exercises/practice").join(slug); @@ -108,22 +107,3 @@ fn generate_exercise_files(slug: &str, is_update: bool) { ) .unwrap(); } - -fn read_fn_names_from_lib_rs(slug: &str) -> Vec { - let lib_rs = - std::fs::read_to_string(format!("exercises/practice/{}/src/lib.rs", slug)).unwrap(); - - lib_rs - .split("fn ") - .skip(1) - .map(|f| { - let tmp = f.split_once('(').unwrap().0; - // strip generics - if let Some((res, _)) = tmp.split_once('<') { - res.to_string() - } else { - tmp.to_string() - } - }) - .collect() -} diff --git a/rust-tooling/generate/tests/tera_templates_are_in_sync.rs b/rust-tooling/generate/tests/tera_templates_are_in_sync.rs new file mode 100644 index 000000000..94ef3f953 --- /dev/null +++ b/rust-tooling/generate/tests/tera_templates_are_in_sync.rs @@ -0,0 +1,23 @@ +//! This test is not run in CI. Doing so would slow down CI noticeably, because +//! exercise generation depends on `tera`, which takes a while to compile. + +use glob::glob; +use utils::fs::cd_into_repo_root; + +#[test] +fn tera_templates_are_in_sync() { + cd_into_repo_root(); + for entry in glob("exercises/*/*/.meta/test_template.tera").unwrap() { + let path = entry.unwrap(); + let exercise_dir = path.parent().unwrap().parent().unwrap(); + let slug = exercise_dir.file_name().unwrap().to_string_lossy(); + + let fn_names = generate::read_fn_names_from_lib_rs(&slug); + let generated = generate::new(&slug, fn_names); + + let test_path = exercise_dir.join("tests").join(format!("{slug}.rs")); + let on_disk = std::fs::read_to_string(test_path).unwrap(); + + assert_eq!(generated.tests, on_disk); + } +} From 81775fda87d7260a9aac0bc442129da9c7a0f9b9 Mon Sep 17 00:00:00 2001 From: Justin Pallansch <98477057+jpal91@users.noreply.github.com> Date: Thu, 28 Mar 2024 16:11:04 -0400 Subject: [PATCH 261/436] Add Exercise Matrix (#1877) --- config.json | 9 +++ .../matrix/.docs/instructions.append.md | 20 ++++++ .../practice/matrix/.docs/instructions.md | 38 ++++++++++ exercises/practice/matrix/.gitignore | 2 + .../matrix/.meta/additional-tests.json | 34 +++++++++ exercises/practice/matrix/.meta/config.json | 18 +++++ exercises/practice/matrix/.meta/example.rs | 26 +++++++ .../practice/matrix/.meta/test_template.tera | 16 +++++ exercises/practice/matrix/.meta/tests.toml | 34 +++++++++ exercises/practice/matrix/Cargo.toml | 6 ++ exercises/practice/matrix/src/lib.rs | 17 +++++ exercises/practice/matrix/tests/matrix.rs | 70 +++++++++++++++++++ 12 files changed, 290 insertions(+) create mode 100644 exercises/practice/matrix/.docs/instructions.append.md create mode 100644 exercises/practice/matrix/.docs/instructions.md create mode 100644 exercises/practice/matrix/.gitignore create mode 100644 exercises/practice/matrix/.meta/additional-tests.json create mode 100644 exercises/practice/matrix/.meta/config.json create mode 100644 exercises/practice/matrix/.meta/example.rs create mode 100644 exercises/practice/matrix/.meta/test_template.tera create mode 100644 exercises/practice/matrix/.meta/tests.toml create mode 100644 exercises/practice/matrix/Cargo.toml create mode 100644 exercises/practice/matrix/src/lib.rs create mode 100644 exercises/practice/matrix/tests/matrix.rs diff --git a/config.json b/config.json index be4a47491..25c22e7f8 100644 --- a/config.json +++ b/config.json @@ -1533,6 +1533,15 @@ "prerequisites": [], "difficulty": 4, "topics": [] + }, + { + "slug": "matrix", + "name": "Matrix", + "uuid": "9fceeb8b-0154-45f0-93a5-7117d4d81449", + "practices": [], + "prerequisites": [], + "difficulty": 4, + "topics": [] } ], "foregone": [ diff --git a/exercises/practice/matrix/.docs/instructions.append.md b/exercises/practice/matrix/.docs/instructions.append.md new file mode 100644 index 000000000..e96864709 --- /dev/null +++ b/exercises/practice/matrix/.docs/instructions.append.md @@ -0,0 +1,20 @@ +# Instructions append + +## Challenges + +- Can you implement this solution without using any `for` loops? (i.e. using [iterators][iterators] instead) +- Although it would be relatively straight-forward to have two data structures in `Matrix` (one representing rows and another for columns), can you implement this solution without having to hold two separate copies of the input in your struct? + +## Helpful Methods +- [`map()`][map] +- [`filter()`][filter] +- [`lines()`][lines] +- [`split()`][split] +- [`parse()`][parse] + +[iterators]: https://doc.rust-lang.org/book/ch13-02-iterators.html +[map]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.map +[filter]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.filter +[lines]: https://doc.rust-lang.org/std/primitive.str.html#method.lines +[split]: https://doc.rust-lang.org/std/primitive.str.html#method.split +[parse]: https://doc.rust-lang.org/std/primitive.str.html#method.parse \ No newline at end of file diff --git a/exercises/practice/matrix/.docs/instructions.md b/exercises/practice/matrix/.docs/instructions.md new file mode 100644 index 000000000..dadea8acb --- /dev/null +++ b/exercises/practice/matrix/.docs/instructions.md @@ -0,0 +1,38 @@ +# Instructions + +Given a string representing a matrix of numbers, return the rows and columns of that matrix. + +So given a string with embedded newlines like: + +```text +9 8 7 +5 3 2 +6 6 7 +``` + +representing this matrix: + +```text + 1 2 3 + |--------- +1 | 9 8 7 +2 | 5 3 2 +3 | 6 6 7 +``` + +your code should be able to spit out: + +- A list of the rows, reading each row left-to-right while moving top-to-bottom across the rows, +- A list of the columns, reading each column top-to-bottom while moving from left-to-right. + +The rows for our example matrix: + +- 9, 8, 7 +- 5, 3, 2 +- 6, 6, 7 + +And its columns: + +- 9, 5, 6 +- 8, 3, 6 +- 7, 2, 7 diff --git a/exercises/practice/matrix/.gitignore b/exercises/practice/matrix/.gitignore new file mode 100644 index 000000000..4fffb2f89 --- /dev/null +++ b/exercises/practice/matrix/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/exercises/practice/matrix/.meta/additional-tests.json b/exercises/practice/matrix/.meta/additional-tests.json new file mode 100644 index 000000000..2bb9cd369 --- /dev/null +++ b/exercises/practice/matrix/.meta/additional-tests.json @@ -0,0 +1,34 @@ +[ + { + "uuid": "9ca9c869-7220-4783-93ba-3e4f96b81732", + "description": "cannot extract row with no corresponding row in matrix", + "property": "row", + "input": { + "string": "1 2 3\n4 5 6\n7 8 9", + "index": 4 + }, + "expected": null, + "comments": [ + "Additional test to ensure the method can handle invalid input", + "Upstream does not want to add this test to avoid every exercise being about input validation.", + "Rust puts a lot of emphasis on error handling, hence the idiomatic Option return type.", + "With Option in the API, it makes sense to test for None at least once." + ] + }, + { + "uuid": "304d71d4-cf26-41d4-9b74-cc04441fec8c", + "description": "cannot extract column with no corresponding column in matrix", + "property": "column", + "input": { + "string": "1 2 3\n4 5 6\n7 8 9", + "index": 4 + }, + "expected": null, + "comments": [ + "Additional test to ensure the method can handle invalid input", + "Upstream does not want to add this test to avoid every exercise being about input validation.", + "Rust puts a lot of emphasis on error handling, hence the idiomatic Option return type.", + "With Option in the API, it makes sense to test for None at least once." + ] + } +] diff --git a/exercises/practice/matrix/.meta/config.json b/exercises/practice/matrix/.meta/config.json new file mode 100644 index 000000000..cc6fedb59 --- /dev/null +++ b/exercises/practice/matrix/.meta/config.json @@ -0,0 +1,18 @@ +{ + "authors": ["jpal91"], + "files": { + "solution": [ + "src/lib.rs", + "Cargo.toml" + ], + "test": [ + "tests/matrix.rs" + ], + "example": [ + ".meta/example.rs" + ] + }, + "blurb": "Given a string representing a matrix of numbers, return the rows and columns of that matrix.", + "source": "Exercise by the JumpstartLab team for students at The Turing School of Software and Design.", + "source_url": "https://turing.edu/" +} diff --git a/exercises/practice/matrix/.meta/example.rs b/exercises/practice/matrix/.meta/example.rs new file mode 100644 index 000000000..024642824 --- /dev/null +++ b/exercises/practice/matrix/.meta/example.rs @@ -0,0 +1,26 @@ +pub struct Matrix { + grid: Vec>, +} + +impl Matrix { + pub fn new(input: &str) -> Self { + let grid = input + .lines() + .map(|l| l.split(' ').map(|n| n.parse::().unwrap()).collect()) + .collect(); + + Self { grid } + } + + pub fn row(&self, row_no: usize) -> Option> { + self.grid.get(row_no - 1).map(|row| row.to_owned()) + } + + pub fn column(&self, col_no: usize) -> Option> { + if col_no > self.grid[0].len() { + return None; + }; + + Some(self.grid.iter().map(|row| row[col_no - 1]).collect()) + } +} diff --git a/exercises/practice/matrix/.meta/test_template.tera b/exercises/practice/matrix/.meta/test_template.tera new file mode 100644 index 000000000..de548e86c --- /dev/null +++ b/exercises/practice/matrix/.meta/test_template.tera @@ -0,0 +1,16 @@ +use {{ crate_name }}::*; + +{% for test in cases %} +#[test] +{% if loop.index != 1 -%} +#[ignore] +{% endif -%} +fn {{ test.description | slugify | replace(from="-", to="_") }}() { + let matrix = Matrix::new({{ test.input.string | json_encode() }}); + {% if test.expected -%} + assert_eq!(matrix.{{ test.property }}({{ test.input.index }}), Some(vec!{{ test.expected | json_encode() }})); + {% else -%} + assert_eq!(matrix.{{ test.property }}({{ test.input.index }}), None); + {% endif -%} +} +{% endfor -%} diff --git a/exercises/practice/matrix/.meta/tests.toml b/exercises/practice/matrix/.meta/tests.toml new file mode 100644 index 000000000..90b509c44 --- /dev/null +++ b/exercises/practice/matrix/.meta/tests.toml @@ -0,0 +1,34 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[ca733dab-9d85-4065-9ef6-a880a951dafd] +description = "extract row from one number matrix" + +[5c93ec93-80e1-4268-9fc2-63bc7d23385c] +description = "can extract row" + +[2f1aad89-ad0f-4bd2-9919-99a8bff0305a] +description = "extract row where numbers have different widths" + +[68f7f6ba-57e2-4e87-82d0-ad09889b5204] +description = "can extract row from non-square matrix with no corresponding column" + +[e8c74391-c93b-4aed-8bfe-f3c9beb89ebb] +description = "extract column from one number matrix" + +[7136bdbd-b3dc-48c4-a10c-8230976d3727] +description = "can extract column" + +[ad64f8d7-bba6-4182-8adf-0c14de3d0eca] +description = "can extract column from non-square matrix with no corresponding row" + +[9eddfa5c-8474-440e-ae0a-f018c2a0dd89] +description = "extract column where numbers have different widths" diff --git a/exercises/practice/matrix/Cargo.toml b/exercises/practice/matrix/Cargo.toml new file mode 100644 index 000000000..6ecb93ac8 --- /dev/null +++ b/exercises/practice/matrix/Cargo.toml @@ -0,0 +1,6 @@ +[package] +edition = "2021" +name = "matrix" +version = "1.0.0" + +[dependencies] diff --git a/exercises/practice/matrix/src/lib.rs b/exercises/practice/matrix/src/lib.rs new file mode 100644 index 000000000..119c38f83 --- /dev/null +++ b/exercises/practice/matrix/src/lib.rs @@ -0,0 +1,17 @@ +pub struct Matrix { + // Implement your Matrix struct +} + +impl Matrix { + pub fn new(input: &str) -> Self { + todo!("Create new method to store the {input}") + } + + pub fn row(&self, row_no: usize) -> Option> { + todo!("Return the row at {row_no} (1-indexed) or None if the number is invalid") + } + + pub fn column(&self, col_no: usize) -> Option> { + todo!("Return the column at {col_no} (1-indexed) or None if the number is invalid") + } +} diff --git a/exercises/practice/matrix/tests/matrix.rs b/exercises/practice/matrix/tests/matrix.rs new file mode 100644 index 000000000..257a48272 --- /dev/null +++ b/exercises/practice/matrix/tests/matrix.rs @@ -0,0 +1,70 @@ +use matrix::*; + +#[test] +fn extract_row_from_one_number_matrix() { + let matrix = Matrix::new("1"); + assert_eq!(matrix.row(1), Some(vec![1])); +} + +#[test] +#[ignore] +fn can_extract_row() { + let matrix = Matrix::new("1 2\n3 4"); + assert_eq!(matrix.row(2), Some(vec![3, 4])); +} + +#[test] +#[ignore] +fn extract_row_where_numbers_have_different_widths() { + let matrix = Matrix::new("1 2\n10 20"); + assert_eq!(matrix.row(2), Some(vec![10, 20])); +} + +#[test] +#[ignore] +fn can_extract_row_from_non_square_matrix_with_no_corresponding_column() { + let matrix = Matrix::new("1 2 3\n4 5 6\n7 8 9\n8 7 6"); + assert_eq!(matrix.row(4), Some(vec![8, 7, 6])); +} + +#[test] +#[ignore] +fn extract_column_from_one_number_matrix() { + let matrix = Matrix::new("1"); + assert_eq!(matrix.column(1), Some(vec![1])); +} + +#[test] +#[ignore] +fn can_extract_column() { + let matrix = Matrix::new("1 2 3\n4 5 6\n7 8 9"); + assert_eq!(matrix.column(3), Some(vec![3, 6, 9])); +} + +#[test] +#[ignore] +fn can_extract_column_from_non_square_matrix_with_no_corresponding_row() { + let matrix = Matrix::new("1 2 3 4\n5 6 7 8\n9 8 7 6"); + assert_eq!(matrix.column(4), Some(vec![4, 8, 6])); +} + +#[test] +#[ignore] +fn extract_column_where_numbers_have_different_widths() { + let matrix = Matrix::new("89 1903 3\n18 3 1\n9 4 800"); + assert_eq!(matrix.column(2), Some(vec![1903, 3, 4])); +} + +#[test] +#[ignore] +fn cannot_extract_row_with_no_corresponding_row_in_matrix() { + let matrix = Matrix::new("1 2 3\n4 5 6\n7 8 9"); + assert_eq!(matrix.row(4), None); +} + +#[test] +#[ignore] +fn cannot_extract_column_with_no_corresponding_column_in_matrix() { + let matrix = Matrix::new("1 2 3\n4 5 6\n7 8 9"); + assert_eq!(matrix.column(4), None); +} From 78c84bf232309c05af8b14562c7240421722bfbc Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Thu, 28 Mar 2024 22:19:30 +0100 Subject: [PATCH 262/436] Add exercise eliuds-eggs (#1873) --- config.json | 9 ++++ .../eliuds-eggs/.docs/instructions.md | 8 ++++ .../eliuds-eggs/.docs/introduction.md | 47 +++++++++++++++++++ exercises/practice/eliuds-eggs/.gitignore | 2 + .../practice/eliuds-eggs/.meta/config.json | 18 +++++++ .../practice/eliuds-eggs/.meta/example.rs | 3 ++ .../eliuds-eggs/.meta/test_template.tera | 14 ++++++ .../practice/eliuds-eggs/.meta/tests.toml | 22 +++++++++ exercises/practice/eliuds-eggs/Cargo.toml | 6 +++ exercises/practice/eliuds-eggs/src/lib.rs | 3 ++ .../practice/eliuds-eggs/tests/eliuds-eggs.rs | 36 ++++++++++++++ problem-specifications | 2 +- 12 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 exercises/practice/eliuds-eggs/.docs/instructions.md create mode 100644 exercises/practice/eliuds-eggs/.docs/introduction.md create mode 100644 exercises/practice/eliuds-eggs/.gitignore create mode 100644 exercises/practice/eliuds-eggs/.meta/config.json create mode 100644 exercises/practice/eliuds-eggs/.meta/example.rs create mode 100644 exercises/practice/eliuds-eggs/.meta/test_template.tera create mode 100644 exercises/practice/eliuds-eggs/.meta/tests.toml create mode 100644 exercises/practice/eliuds-eggs/Cargo.toml create mode 100644 exercises/practice/eliuds-eggs/src/lib.rs create mode 100644 exercises/practice/eliuds-eggs/tests/eliuds-eggs.rs diff --git a/config.json b/config.json index 25c22e7f8..6ce317701 100644 --- a/config.json +++ b/config.json @@ -1542,6 +1542,15 @@ "prerequisites": [], "difficulty": 4, "topics": [] + }, + { + "slug": "eliuds-eggs", + "name": "Eliuds Eggs", + "uuid": "2d738c77-8dde-437a-bf42-ed5542a414d1", + "practices": [], + "prerequisites": [], + "difficulty": 1, + "topics": [] } ], "foregone": [ diff --git a/exercises/practice/eliuds-eggs/.docs/instructions.md b/exercises/practice/eliuds-eggs/.docs/instructions.md new file mode 100644 index 000000000..b0c2df593 --- /dev/null +++ b/exercises/practice/eliuds-eggs/.docs/instructions.md @@ -0,0 +1,8 @@ +# Instructions + +Your task is to count the number of 1 bits in the binary representation of a number. + +## Restrictions + +Keep your hands off that bit-count functionality provided by your standard library! +Solve this one yourself using other basic tools instead. diff --git a/exercises/practice/eliuds-eggs/.docs/introduction.md b/exercises/practice/eliuds-eggs/.docs/introduction.md new file mode 100644 index 000000000..49eaffd8b --- /dev/null +++ b/exercises/practice/eliuds-eggs/.docs/introduction.md @@ -0,0 +1,47 @@ +# Introduction + +Your friend Eliud inherited a farm from her grandma Tigist. +Her granny was an inventor and had a tendency to build things in an overly complicated manner. +The chicken coop has a digital display showing an encoded number representing the positions of all eggs that could be picked up. + +Eliud is asking you to write a program that shows the actual number of eggs in the coop. + +The position information encoding is calculated as follows: + +1. Scan the potential egg-laying spots and mark down a `1` for an existing egg or a `0` for an empty spot. +2. Convert the number from binary to decimal. +3. Show the result on the display. + +Example 1: + +```text +Chicken Coop: + _ _ _ _ _ _ _ +|E| |E|E| | |E| + +Resulting Binary: + 1 0 1 1 0 0 1 + +Decimal number on the display: +89 + +Actual eggs in the coop: +4 +``` + +Example 2: + +```text +Chicken Coop: + _ _ _ _ _ _ _ _ +| | | |E| | | | | + +Resulting Binary: + 0 0 0 1 0 0 0 0 + +Decimal number on the display: +16 + +Actual eggs in the coop: +1 +``` diff --git a/exercises/practice/eliuds-eggs/.gitignore b/exercises/practice/eliuds-eggs/.gitignore new file mode 100644 index 000000000..4fffb2f89 --- /dev/null +++ b/exercises/practice/eliuds-eggs/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/exercises/practice/eliuds-eggs/.meta/config.json b/exercises/practice/eliuds-eggs/.meta/config.json new file mode 100644 index 000000000..27c4e6f6f --- /dev/null +++ b/exercises/practice/eliuds-eggs/.meta/config.json @@ -0,0 +1,18 @@ +{ + "authors": ["senekor"], + "files": { + "solution": [ + "src/lib.rs", + "Cargo.toml" + ], + "test": [ + "tests/eliuds-eggs.rs" + ], + "example": [ + ".meta/example.rs" + ] + }, + "blurb": "Help Eliud count the number of eggs in her chicken coop by counting the number of 1 bits in a binary representation.", + "source": "Christian Willner, Eric Willigers", + "source_url": "https://forum.exercism.org/t/new-exercise-suggestion-pop-count/7632/5" +} diff --git a/exercises/practice/eliuds-eggs/.meta/example.rs b/exercises/practice/eliuds-eggs/.meta/example.rs new file mode 100644 index 000000000..c4ff172a9 --- /dev/null +++ b/exercises/practice/eliuds-eggs/.meta/example.rs @@ -0,0 +1,3 @@ +pub fn egg_count(display_value: u32) -> usize { + (0..32).filter(|i| display_value & (1 << i) != 0).count() +} diff --git a/exercises/practice/eliuds-eggs/.meta/test_template.tera b/exercises/practice/eliuds-eggs/.meta/test_template.tera new file mode 100644 index 000000000..b77b6c34d --- /dev/null +++ b/exercises/practice/eliuds-eggs/.meta/test_template.tera @@ -0,0 +1,14 @@ +use {{ crate_name }}::*; + +{% for test in cases %} +#[test] +{% if loop.index != 1 -%} +#[ignore] +{% endif -%} +fn test_{{ test.description | slugify | replace(from="-", to="_") }}() { + let input = {{ test.input.number }}; + let output = {{ fn_names[0] }}(input); + let expected = {{ test.expected | json_encode() }}; + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/eliuds-eggs/.meta/tests.toml b/exercises/practice/eliuds-eggs/.meta/tests.toml new file mode 100644 index 000000000..e11683c2e --- /dev/null +++ b/exercises/practice/eliuds-eggs/.meta/tests.toml @@ -0,0 +1,22 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[559e789d-07d1-4422-9004-3b699f83bca3] +description = "0 eggs" + +[97223282-f71e-490c-92f0-b3ec9e275aba] +description = "1 egg" + +[1f8fd18f-26e9-4144-9a0e-57cdfc4f4ff5] +description = "4 eggs" + +[0c18be92-a498-4ef2-bcbb-28ac4b06cb81] +description = "13 eggs" diff --git a/exercises/practice/eliuds-eggs/Cargo.toml b/exercises/practice/eliuds-eggs/Cargo.toml new file mode 100644 index 000000000..5f9d055dc --- /dev/null +++ b/exercises/practice/eliuds-eggs/Cargo.toml @@ -0,0 +1,6 @@ +[package] +edition = "2021" +name = "eliuds_eggs" +version = "1.0.0" + +[dependencies] diff --git a/exercises/practice/eliuds-eggs/src/lib.rs b/exercises/practice/eliuds-eggs/src/lib.rs new file mode 100644 index 000000000..0565f7348 --- /dev/null +++ b/exercises/practice/eliuds-eggs/src/lib.rs @@ -0,0 +1,3 @@ +pub fn egg_count(display_value: u32) -> usize { + todo!("count the eggs in {display_value}") +} diff --git a/exercises/practice/eliuds-eggs/tests/eliuds-eggs.rs b/exercises/practice/eliuds-eggs/tests/eliuds-eggs.rs new file mode 100644 index 000000000..9204d0e77 --- /dev/null +++ b/exercises/practice/eliuds-eggs/tests/eliuds-eggs.rs @@ -0,0 +1,36 @@ +use eliuds_eggs::*; + +#[test] +fn test_0_eggs() { + let input = 0; + let output = egg_count(input); + let expected = 0; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn test_1_egg() { + let input = 16; + let output = egg_count(input); + let expected = 1; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn test_4_eggs() { + let input = 89; + let output = egg_count(input); + let expected = 4; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn test_13_eggs() { + let input = 2000000000; + let output = egg_count(input); + let expected = 13; + assert_eq!(output, expected); +} diff --git a/problem-specifications b/problem-specifications index 524e2b3e1..993abcc4e 160000 --- a/problem-specifications +++ b/problem-specifications @@ -1 +1 @@ -Subproject commit 524e2b3e186a5a307cd55d974e3c71796a947ebd +Subproject commit 993abcc4e327743a17497dab04f4a03f4f50c2ba From 0e7467894eff783d9afe3292b89ed7c1596c67fc Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Sat, 30 Mar 2024 09:39:15 +0100 Subject: [PATCH 263/436] two-fer: sync (#1882) --- .../practice/two-fer/.docs/instructions.md | 22 +++++++++---------- .../practice/two-fer/.docs/introduction.md | 8 +++++++ 2 files changed, 18 insertions(+), 12 deletions(-) create mode 100644 exercises/practice/two-fer/.docs/introduction.md diff --git a/exercises/practice/two-fer/.docs/instructions.md b/exercises/practice/two-fer/.docs/instructions.md index f4853c54d..adc534879 100644 --- a/exercises/practice/two-fer/.docs/instructions.md +++ b/exercises/practice/two-fer/.docs/instructions.md @@ -1,16 +1,14 @@ # Instructions -`Two-fer` or `2-fer` is short for two for one. One for you and one for me. +Your task is to determine what you will say as you give away the extra cookie. -Given a name, return a string with the message: +If you know the person's name (e.g. if they're named Do-yun), then you will say: ```text -One for name, one for me. +One for Do-yun, one for me. ``` -Where "name" is the given name. - -However, if the name is missing, return the string: +If you don't know the person's name, you will say _you_ instead. ```text One for you, one for me. @@ -18,9 +16,9 @@ One for you, one for me. Here are some examples: -|Name |String to return -|:-------|:------------------ -|Alice |One for Alice, one for me. -|Bob |One for Bob, one for me. -| |One for you, one for me. -|Zaphod |One for Zaphod, one for me. +| Name | Dialogue | +| :----- | :-------------------------- | +| Alice | One for Alice, one for me. | +| Bohdan | One for Bohdan, one for me. | +| | One for you, one for me. | +| Zaphod | One for Zaphod, one for me. | diff --git a/exercises/practice/two-fer/.docs/introduction.md b/exercises/practice/two-fer/.docs/introduction.md new file mode 100644 index 000000000..5947a2230 --- /dev/null +++ b/exercises/practice/two-fer/.docs/introduction.md @@ -0,0 +1,8 @@ +# Introduction + +In some English accents, when you say "two for" quickly, it sounds like "two fer". +Two-for-one is a way of saying that if you buy one, you also get one for free. +So the phrase "two-fer" often implies a two-for-one offer. + +Imagine a bakery that has a holiday offer where you can buy two cookies for the price of one ("two-fer one!"). +You take the offer and (very generously) decide to give the extra cookie to someone else in the queue. From c3733151211380cdc92342013a5a19ce44151c42 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Sat, 30 Mar 2024 09:39:51 +0100 Subject: [PATCH 264/436] sieve: sync (#1881) --- .../practice/sieve/.docs/instructions.md | 40 +++++++++++++------ 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/exercises/practice/sieve/.docs/instructions.md b/exercises/practice/sieve/.docs/instructions.md index 3adf1d551..085c0a57d 100644 --- a/exercises/practice/sieve/.docs/instructions.md +++ b/exercises/practice/sieve/.docs/instructions.md @@ -1,28 +1,42 @@ # Instructions -Your task is to create a program that implements the Sieve of Eratosthenes algorithm to find prime numbers. +Your task is to create a program that implements the Sieve of Eratosthenes algorithm to find all prime numbers less than or equal to a given number. -A prime number is a number that is only divisible by 1 and itself. +A prime number is a number larger than 1 that is only divisible by 1 and itself. For example, 2, 3, 5, 7, 11, and 13 are prime numbers. - -The Sieve of Eratosthenes is an ancient algorithm that works by taking a list of numbers and crossing out all the numbers that aren't prime. - -A number that is **not** prime is called a "composite number". +By contrast, 6 is _not_ a prime number as it not only divisible by 1 and itself, but also by 2 and 3. To use the Sieve of Eratosthenes, you first create a list of all the numbers between 2 and your given number. Then you repeat the following steps: -1. Find the next unmarked number in your list. This is a prime number. -2. Mark all the multiples of that prime number as composite (not prime). +1. Find the next unmarked number in your list (skipping over marked numbers). + This is a prime number. +2. Mark all the multiples of that prime number as **not** prime. You keep repeating these steps until you've gone through every number in your list. At the end, all the unmarked numbers are prime. ~~~~exercism/note -[Wikipedia's Sieve of Eratosthenes article][eratosthenes] has a useful graphic that explains the algorithm. - The tests don't check that you've implemented the algorithm, only that you've come up with the correct list of primes. -A good first test is to check that you do not use division or remainder operations. - -[eratosthenes]: https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes +To check you are implementing the Sieve correctly, a good first test is to check that you do not use division or remainder operations. ~~~~ + +## Example + +Let's say you're finding the primes less than or equal to 10. + +- List out 2, 3, 4, 5, 6, 7, 8, 9, 10, leaving them all unmarked. +- 2 is unmarked and is therefore a prime. + Mark 4, 6, 8 and 10 as "not prime". +- 3 is unmarked and is therefore a prime. + Mark 6 and 9 as not prime _(marking 6 is optional - as it's already been marked)_. +- 4 is marked as "not prime", so we skip over it. +- 5 is unmarked and is therefore a prime. + Mark 10 as not prime _(optional - as it's already been marked)_. +- 6 is marked as "not prime", so we skip over it. +- 7 is unmarked and is therefore a prime. +- 8 is marked as "not prime", so we skip over it. +- 9 is marked as "not prime", so we skip over it. +- 10 is marked as "not prime", so we stop as there are no more numbers to check. + +You've examined all numbers and found 2, 3, 5, and 7 are still unmarked, which means they're the primes less than or equal to 10. From ee47012f9e5aa8e8f2a1d7978652d5990775566b Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 2 Apr 2024 09:28:20 +0200 Subject: [PATCH 265/436] reverse-string: sync (#1883) [no important files changed] --- .../.meta/additional-tests.json | 28 ------------------- .../reverse-string/.meta/test_template.tera | 2 +- .../practice/reverse-string/.meta/tests.toml | 9 ++++++ .../reverse-string/tests/reverse-string.rs | 14 ++++++++-- .../tests/tera_templates_are_in_sync.rs | 8 +++++- 5 files changed, 29 insertions(+), 32 deletions(-) delete mode 100644 exercises/practice/reverse-string/.meta/additional-tests.json diff --git a/exercises/practice/reverse-string/.meta/additional-tests.json b/exercises/practice/reverse-string/.meta/additional-tests.json deleted file mode 100644 index c82e02b04..000000000 --- a/exercises/practice/reverse-string/.meta/additional-tests.json +++ /dev/null @@ -1,28 +0,0 @@ -[ - { - "uuid": "01ebf55b-bebb-414e-9dec-06f7bb0bee3c", - "description": "wide characters", - "comments": [ - "Unicode tests are not suitable to be upstreamed.", - "Handling unicode is tedious in many languages." - ], - "property": "reverse", - "input": { - "value": "子猫" - }, - "expected": "猫子" - }, - { - "uuid": "01ebf55b-bebb-414e-9dec-06f7bb0bee3c", - "description": "grapheme clusters", - "comments": [ - "Unicode tests are not suitable to be upstreamed.", - "Handling unicode is tedious in many languages." - ], - "property": "grapheme", - "input": { - "value": "uüu" - }, - "expected": "uüu" - } -] diff --git a/exercises/practice/reverse-string/.meta/test_template.tera b/exercises/practice/reverse-string/.meta/test_template.tera index 4a14c3264..cf8485eb7 100644 --- a/exercises/practice/reverse-string/.meta/test_template.tera +++ b/exercises/practice/reverse-string/.meta/test_template.tera @@ -3,7 +3,7 @@ {% if loop.index != 1 -%} #[ignore] {% endif -%} -{% if test.property == "grapheme" -%} +{% if test.description is starting_with("grapheme cluster") -%} #[cfg(feature = "grapheme")] {% endif -%} fn {{ test.description | slugify | replace(from="-", to="_") }}() { diff --git a/exercises/practice/reverse-string/.meta/tests.toml b/exercises/practice/reverse-string/.meta/tests.toml index 0b04c4cd7..0c313cc53 100644 --- a/exercises/practice/reverse-string/.meta/tests.toml +++ b/exercises/practice/reverse-string/.meta/tests.toml @@ -26,3 +26,12 @@ description = "a palindrome" [b9e7dec1-c6df-40bd-9fa3-cd7ded010c4c] description = "an even-sized word" + +[1bed0f8a-13b0-4bd3-9d59-3d0593326fa2] +description = "wide characters" + +[93d7e1b8-f60f-4f3c-9559-4056e10d2ead] +description = "grapheme cluster with pre-combined form" + +[1028b2c1-6763-4459-8540-2da47ca512d9] +description = "grapheme clusters" diff --git a/exercises/practice/reverse-string/tests/reverse-string.rs b/exercises/practice/reverse-string/tests/reverse-string.rs index fee55ee39..f41af3a55 100644 --- a/exercises/practice/reverse-string/tests/reverse-string.rs +++ b/exercises/practice/reverse-string/tests/reverse-string.rs @@ -60,12 +60,22 @@ fn wide_characters() { assert_eq!(output, expected); } +#[test] +#[ignore] +#[cfg(feature = "grapheme")] +fn grapheme_cluster_with_pre_combined_form() { + let input = "Würstchenstand"; + let output = reverse_string::reverse(input); + let expected = "dnatsnehctsrüW"; + assert_eq!(output, expected); +} + #[test] #[ignore] #[cfg(feature = "grapheme")] fn grapheme_clusters() { - let input = "uüu"; + let input = "ผู้เขียนโปรแกรม"; let output = reverse_string::reverse(input); - let expected = "uüu"; + let expected = "มรกแรปโนยขีเผู้"; assert_eq!(output, expected); } diff --git a/rust-tooling/generate/tests/tera_templates_are_in_sync.rs b/rust-tooling/generate/tests/tera_templates_are_in_sync.rs index 94ef3f953..c1cae8fa5 100644 --- a/rust-tooling/generate/tests/tera_templates_are_in_sync.rs +++ b/rust-tooling/generate/tests/tera_templates_are_in_sync.rs @@ -18,6 +18,12 @@ fn tera_templates_are_in_sync() { let test_path = exercise_dir.join("tests").join(format!("{slug}.rs")); let on_disk = std::fs::read_to_string(test_path).unwrap(); - assert_eq!(generated.tests, on_disk); + if generated.tests != on_disk { + panic!( + " + The Tera template for exercise '{slug}' is not in sync. + Run 'just update-exercise --slug {slug}' to fix it.\n" + ) + } } } From 256a1a3d942a97463bbc84c1978729218e9e78a5 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 2 Apr 2024 09:36:33 +0200 Subject: [PATCH 266/436] generator: strip first ignore-annotation in Rust (#1884) [no important files changed] --- exercises/practice/acronym/.meta/test_template.tera | 2 -- .../practice/affine-cipher/.meta/test_template.tera | 2 -- exercises/practice/allergies/.meta/test_template.tera | 2 -- exercises/practice/allergies/tests/allergies.rs | 10 ---------- exercises/practice/anagram/.meta/test_template.tera | 2 -- exercises/practice/book-store/.meta/test_template.tera | 2 -- exercises/practice/custom-set/.meta/test_template.tera | 2 -- .../practice/eliuds-eggs/.meta/test_template.tera | 2 -- exercises/practice/knapsack/.meta/test_template.tera | 2 -- exercises/practice/leap/.meta/test_template.tera | 2 -- exercises/practice/matrix/.meta/test_template.tera | 2 -- .../practice/pascals-triangle/.meta/test_template.tera | 2 -- .../practice/perfect-numbers/.meta/test_template.tera | 2 -- .../practice/phone-number/.meta/test_template.tera | 2 -- exercises/practice/pig-latin/.meta/test_template.tera | 2 -- exercises/practice/poker/.meta/test_template.tera | 2 -- .../practice/prime-factors/.meta/test_template.tera | 2 -- exercises/practice/proverb/.meta/test_template.tera | 2 -- .../pythagorean-triplet/.meta/test_template.tera | 2 -- .../practice/queen-attack/.meta/test_template.tera | 2 -- .../rail-fence-cipher/.meta/test_template.tera | 2 -- exercises/practice/raindrops/.meta/test_template.tera | 2 -- exercises/practice/rectangles/.meta/test_template.tera | 2 -- .../practice/reverse-string/.meta/test_template.tera | 2 -- .../rna-transcription/.meta/test_template.tera | 2 -- .../practice/robot-simulator/.meta/test_template.tera | 2 -- .../practice/roman-numerals/.meta/test_template.tera | 2 -- .../rotational-cipher/.meta/test_template.tera | 2 -- .../run-length-encoding/.meta/test_template.tera | 2 -- .../practice/saddle-points/.meta/test_template.tera | 2 -- exercises/practice/say/.meta/test_template.tera | 2 -- .../practice/scrabble-score/.meta/test_template.tera | 2 -- exercises/practice/series/.meta/test_template.tera | 2 -- exercises/practice/sieve/.meta/test_template.tera | 2 -- exercises/practice/space-age/.meta/test_template.tera | 2 -- .../practice/spiral-matrix/.meta/test_template.tera | 2 -- exercises/practice/sublist/.meta/test_template.tera | 2 -- .../practice/sum-of-multiples/.meta/test_template.tera | 2 -- exercises/practice/tournament/.meta/test_template.tera | 2 -- exercises/practice/triangle/.meta/test_template.tera | 2 -- exercises/practice/two-bucket/.meta/test_template.tera | 2 -- .../variable-length-quantity/.meta/test_template.tera | 2 -- exercises/practice/word-count/.meta/test_template.tera | 2 -- exercises/practice/wordy/.meta/test_template.tera | 2 -- rust-tooling/generate/src/lib.rs | 10 ++++++++-- .../generate/templates/default_test_template.tera | 2 -- 46 files changed, 8 insertions(+), 100 deletions(-) diff --git a/exercises/practice/acronym/.meta/test_template.tera b/exercises/practice/acronym/.meta/test_template.tera index c8de609e1..2c1911922 100644 --- a/exercises/practice/acronym/.meta/test_template.tera +++ b/exercises/practice/acronym/.meta/test_template.tera @@ -1,8 +1,6 @@ {% for test in cases %} #[test] -{% if loop.index != 1 -%} #[ignore] -{% endif -%} fn {{ test.description | slugify | replace(from="-", to="_") }}() { let input = {{ test.input.phrase | json_encode() }}; let output = {{ crate_name }}::{{ fn_names[0] }}(input); diff --git a/exercises/practice/affine-cipher/.meta/test_template.tera b/exercises/practice/affine-cipher/.meta/test_template.tera index 0c15e457a..8f04e1f7a 100644 --- a/exercises/practice/affine-cipher/.meta/test_template.tera +++ b/exercises/practice/affine-cipher/.meta/test_template.tera @@ -1,9 +1,7 @@ use affine_cipher::AffineCipherError::NotCoprime; {% for test in cases %} #[test] -{% if loop.index != 1 -%} #[ignore] -{% endif -%} fn {{ test.description | slugify | replace(from="-", to="_") }}() { let phrase = {{ test.input.phrase | json_encode() }}; let (a, b) = ({{ test.input.key.a }}, {{ test.input.key.b }}); diff --git a/exercises/practice/allergies/.meta/test_template.tera b/exercises/practice/allergies/.meta/test_template.tera index 986be5782..ee99d5f2d 100644 --- a/exercises/practice/allergies/.meta/test_template.tera +++ b/exercises/practice/allergies/.meta/test_template.tera @@ -16,9 +16,7 @@ fn compare_allergy_vectors(expected: &[Allergen], actual: &[Allergen]) { {% for test in cases %} #[test] -{% if loop.index != 1 -%} #[ignore] -{% endif -%} {%- if test.property == "allergicTo" %} {# canonical data contains multiple cases named "allergic to everything" for different items #} fn {{ test.description | slugify | replace(from="-", to="_") }}_{{ test.input.item }}() { diff --git a/exercises/practice/allergies/tests/allergies.rs b/exercises/practice/allergies/tests/allergies.rs index ffcae1739..ab8185e7c 100644 --- a/exercises/practice/allergies/tests/allergies.rs +++ b/exercises/practice/allergies/tests/allergies.rs @@ -335,7 +335,6 @@ fn allergic_to_everything_cats() { #[test] #[ignore] - fn no_allergies() { let allergies = Allergies::new(0).allergies(); let expected = &[]; @@ -345,7 +344,6 @@ fn no_allergies() { #[test] #[ignore] - fn just_eggs() { let allergies = Allergies::new(1).allergies(); let expected = &[Allergen::Eggs]; @@ -355,7 +353,6 @@ fn just_eggs() { #[test] #[ignore] - fn just_peanuts() { let allergies = Allergies::new(2).allergies(); let expected = &[Allergen::Peanuts]; @@ -365,7 +362,6 @@ fn just_peanuts() { #[test] #[ignore] - fn just_strawberries() { let allergies = Allergies::new(8).allergies(); let expected = &[Allergen::Strawberries]; @@ -375,7 +371,6 @@ fn just_strawberries() { #[test] #[ignore] - fn eggs_and_peanuts() { let allergies = Allergies::new(3).allergies(); let expected = &[Allergen::Eggs, Allergen::Peanuts]; @@ -385,7 +380,6 @@ fn eggs_and_peanuts() { #[test] #[ignore] - fn more_than_eggs_but_not_peanuts() { let allergies = Allergies::new(5).allergies(); let expected = &[Allergen::Eggs, Allergen::Shellfish]; @@ -395,7 +389,6 @@ fn more_than_eggs_but_not_peanuts() { #[test] #[ignore] - fn lots_of_stuff() { let allergies = Allergies::new(248).allergies(); let expected = &[ @@ -411,7 +404,6 @@ fn lots_of_stuff() { #[test] #[ignore] - fn everything() { let allergies = Allergies::new(255).allergies(); let expected = &[ @@ -430,7 +422,6 @@ fn everything() { #[test] #[ignore] - fn no_allergen_score_parts() { let allergies = Allergies::new(509).allergies(); let expected = &[ @@ -448,7 +439,6 @@ fn no_allergen_score_parts() { #[test] #[ignore] - fn no_allergen_score_parts_without_highest_valid_score() { let allergies = Allergies::new(257).allergies(); let expected = &[Allergen::Eggs]; diff --git a/exercises/practice/anagram/.meta/test_template.tera b/exercises/practice/anagram/.meta/test_template.tera index c6f1533f7..cb6f7347a 100644 --- a/exercises/practice/anagram/.meta/test_template.tera +++ b/exercises/practice/anagram/.meta/test_template.tera @@ -3,9 +3,7 @@ use std::collections::HashSet; {% for test in cases %} #[test] -{% if loop.index != 1 -%} #[ignore] -{% endif -%} fn {{ test.description | slugify | replace(from="-", to="_") }}() { let word = {{ test.input.subject | json_encode() }}; let inputs = &{{ test.input.candidates | json_encode() }}; diff --git a/exercises/practice/book-store/.meta/test_template.tera b/exercises/practice/book-store/.meta/test_template.tera index d06d0a57f..4c4ae9e77 100644 --- a/exercises/practice/book-store/.meta/test_template.tera +++ b/exercises/practice/book-store/.meta/test_template.tera @@ -1,8 +1,6 @@ {% for test in cases %} #[test] -{% if loop.index != 1 -%} #[ignore] -{% endif -%} fn {{ test.description | slugify | replace(from="-", to="_") }}() { let input = &{{ test.input.basket | json_encode() }}; let output = {{ crate_name }}::{{ fn_names[0] }}(input); diff --git a/exercises/practice/custom-set/.meta/test_template.tera b/exercises/practice/custom-set/.meta/test_template.tera index c6fc07bb5..a9d59ae14 100644 --- a/exercises/practice/custom-set/.meta/test_template.tera +++ b/exercises/practice/custom-set/.meta/test_template.tera @@ -1,9 +1,7 @@ use custom_set::CustomSet; {% for test in cases %} #[test] -{% if loop.index != 1 -%} #[ignore] -{% endif -%} fn {{ test.description | slugify | replace(from="-", to="_") }}() { {%- if test.property == "empty" %} let set = CustomSet::::new(&{{ test.input.set | json_encode() }}); diff --git a/exercises/practice/eliuds-eggs/.meta/test_template.tera b/exercises/practice/eliuds-eggs/.meta/test_template.tera index b77b6c34d..58e5932ed 100644 --- a/exercises/practice/eliuds-eggs/.meta/test_template.tera +++ b/exercises/practice/eliuds-eggs/.meta/test_template.tera @@ -2,9 +2,7 @@ use {{ crate_name }}::*; {% for test in cases %} #[test] -{% if loop.index != 1 -%} #[ignore] -{% endif -%} fn test_{{ test.description | slugify | replace(from="-", to="_") }}() { let input = {{ test.input.number }}; let output = {{ fn_names[0] }}(input); diff --git a/exercises/practice/knapsack/.meta/test_template.tera b/exercises/practice/knapsack/.meta/test_template.tera index b7b4d4983..78b8597fd 100644 --- a/exercises/practice/knapsack/.meta/test_template.tera +++ b/exercises/practice/knapsack/.meta/test_template.tera @@ -2,9 +2,7 @@ use {{ crate_name }}::*; {% for test in cases %} #[test] -{% if loop.index != 1 -%} #[ignore] -{% endif -%} fn test_{{ test.description | slugify | replace(from="-", to="_") }}() { let max_weight = {{ test.input.maximumWeight }}; let items = [ diff --git a/exercises/practice/leap/.meta/test_template.tera b/exercises/practice/leap/.meta/test_template.tera index 8cf7191cd..e2a29c41c 100644 --- a/exercises/practice/leap/.meta/test_template.tera +++ b/exercises/practice/leap/.meta/test_template.tera @@ -2,9 +2,7 @@ use {{ crate_name }}::*; {% for test in cases %} #[test] -{% if loop.index != 1 -%} #[ignore] -{% endif -%} fn {{ test.description | slugify | replace(from="-", to="_") }}() { {%- if test.expected %} assert!(is_leap_year({{ test.input.year }})); diff --git a/exercises/practice/matrix/.meta/test_template.tera b/exercises/practice/matrix/.meta/test_template.tera index de548e86c..7e7d76dca 100644 --- a/exercises/practice/matrix/.meta/test_template.tera +++ b/exercises/practice/matrix/.meta/test_template.tera @@ -2,9 +2,7 @@ use {{ crate_name }}::*; {% for test in cases %} #[test] -{% if loop.index != 1 -%} #[ignore] -{% endif -%} fn {{ test.description | slugify | replace(from="-", to="_") }}() { let matrix = Matrix::new({{ test.input.string | json_encode() }}); {% if test.expected -%} diff --git a/exercises/practice/pascals-triangle/.meta/test_template.tera b/exercises/practice/pascals-triangle/.meta/test_template.tera index 6ef434fa6..139cb5c68 100644 --- a/exercises/practice/pascals-triangle/.meta/test_template.tera +++ b/exercises/practice/pascals-triangle/.meta/test_template.tera @@ -1,9 +1,7 @@ use pascals_triangle::PascalsTriangle; {% for test in cases %} #[test] -{% if loop.index != 1 -%} #[ignore] -{% endif -%} fn {{ test.description | slugify | replace(from="-", to="_") }}() { let pt = PascalsTriangle::new({{ test.input.count }}); let expected: Vec> = vec![{% for row in test.expected -%} diff --git a/exercises/practice/perfect-numbers/.meta/test_template.tera b/exercises/practice/perfect-numbers/.meta/test_template.tera index 0f27541a4..96a575724 100644 --- a/exercises/practice/perfect-numbers/.meta/test_template.tera +++ b/exercises/practice/perfect-numbers/.meta/test_template.tera @@ -2,9 +2,7 @@ use {{ crate_name }}::*; {% for test in cases %} #[test] -{% if loop.index != 1 -%} #[ignore] -{% endif -%} fn {{ test.description | slugify | replace(from="-", to="_") }}() { let input = {{ test.input.number | json_encode() }}; let output = {{ fn_names[0] }}(input); diff --git a/exercises/practice/phone-number/.meta/test_template.tera b/exercises/practice/phone-number/.meta/test_template.tera index 98e6f74b1..26a84b679 100644 --- a/exercises/practice/phone-number/.meta/test_template.tera +++ b/exercises/practice/phone-number/.meta/test_template.tera @@ -2,9 +2,7 @@ use {{ crate_name }}::*; {% for test in cases %} #[test] -{% if loop.index != 1 -%} #[ignore] -{% endif -%} fn {{ test.description | slugify | replace(from="-", to="_") }}() { let input = {{ test.input.phrase | json_encode() }}; let output = {{ fn_names[0] }}(input); diff --git a/exercises/practice/pig-latin/.meta/test_template.tera b/exercises/practice/pig-latin/.meta/test_template.tera index f809b6247..e0a1aba6d 100644 --- a/exercises/practice/pig-latin/.meta/test_template.tera +++ b/exercises/practice/pig-latin/.meta/test_template.tera @@ -2,9 +2,7 @@ use {{ crate_name }}::*; {% for test in cases %} #[test] -{% if loop.index != 1 -%} #[ignore] -{% endif -%} fn {{ test.description | slugify | replace(from="-", to="_") }}() { let input = {{ test.input.phrase | json_encode() }}; let output = {{ fn_names[0] }}(input); diff --git a/exercises/practice/poker/.meta/test_template.tera b/exercises/practice/poker/.meta/test_template.tera index 91551496f..74415c2ab 100644 --- a/exercises/practice/poker/.meta/test_template.tera +++ b/exercises/practice/poker/.meta/test_template.tera @@ -3,9 +3,7 @@ use std::collections::HashSet; {% for test in cases %} #[test] -{% if loop.index != 1 -%} #[ignore] -{% endif -%} fn {{ test.description | slugify | replace(from="-", to="_") }}() { let input = &{{ test.input.hands | json_encode() }}; let output = {{ fn_names[0] }}(input).into_iter().collect::>(); diff --git a/exercises/practice/prime-factors/.meta/test_template.tera b/exercises/practice/prime-factors/.meta/test_template.tera index 8ca8d0eac..2fdf30196 100644 --- a/exercises/practice/prime-factors/.meta/test_template.tera +++ b/exercises/practice/prime-factors/.meta/test_template.tera @@ -2,9 +2,7 @@ use {{ crate_name }}::*; {% for test in cases %} #[test] -{% if loop.index != 1 -%} #[ignore] -{% endif -%} fn {{ test.description | slugify | replace(from="-", to="_") }}() { let factors = {{ fn_names[0] }}({{ test.input.value }}); let expected = {{ test.expected | json_encode() }}; diff --git a/exercises/practice/proverb/.meta/test_template.tera b/exercises/practice/proverb/.meta/test_template.tera index 0560a6f7d..4212b3c70 100644 --- a/exercises/practice/proverb/.meta/test_template.tera +++ b/exercises/practice/proverb/.meta/test_template.tera @@ -1,8 +1,6 @@ {% for test in cases %} #[test] -{% if loop.index != 1 -%} #[ignore] -{% endif -%} fn {{ test.description | slugify | replace(from="-", to="_") }}() { let input = &{{ test.input.strings | json_encode() }}; let output = {{ crate_name }}::{{ fn_names[0] }}(input); diff --git a/exercises/practice/pythagorean-triplet/.meta/test_template.tera b/exercises/practice/pythagorean-triplet/.meta/test_template.tera index 642b1a760..aea88ab26 100644 --- a/exercises/practice/pythagorean-triplet/.meta/test_template.tera +++ b/exercises/practice/pythagorean-triplet/.meta/test_template.tera @@ -1,9 +1,7 @@ use std::collections::HashSet; {% for test in cases %} #[test] -{% if loop.index != 1 -%} #[ignore] -{% endif -%} fn {{ test.description | slugify | replace(from="-", to="_") }}() { let input = {{ test.input.n | json_encode() }}; let output = {{ crate_name }}::{{ fn_names[0] }}(input); diff --git a/exercises/practice/queen-attack/.meta/test_template.tera b/exercises/practice/queen-attack/.meta/test_template.tera index da0a35ab9..6886087b6 100644 --- a/exercises/practice/queen-attack/.meta/test_template.tera +++ b/exercises/practice/queen-attack/.meta/test_template.tera @@ -2,9 +2,7 @@ use queen_attack::*; {% for test in cases %} #[test] -{% if loop.index != 1 -%} #[ignore] -{% endif -%} fn {{ test.description | slugify | replace(from="-", to="_") }}() { {% if test.property == "create" %} let chess_position = ChessPosition::new({{ test.input.queen.position.row }}, {{ test.input.queen.position.column }}); diff --git a/exercises/practice/rail-fence-cipher/.meta/test_template.tera b/exercises/practice/rail-fence-cipher/.meta/test_template.tera index ce5af15b9..adbea054b 100644 --- a/exercises/practice/rail-fence-cipher/.meta/test_template.tera +++ b/exercises/practice/rail-fence-cipher/.meta/test_template.tera @@ -1,9 +1,7 @@ use rail_fence_cipher::RailFence; {% for test in cases %} #[test] -{% if loop.index != 1 -%} #[ignore] -{% endif -%} fn {{ test.description | slugify | replace(from="-", to="_") }}() { let input = {{ test.input.msg | json_encode() }}; let rails = {{ test.input.rails | json_encode() }}; diff --git a/exercises/practice/raindrops/.meta/test_template.tera b/exercises/practice/raindrops/.meta/test_template.tera index d7e68acbd..53aaae9b1 100644 --- a/exercises/practice/raindrops/.meta/test_template.tera +++ b/exercises/practice/raindrops/.meta/test_template.tera @@ -1,8 +1,6 @@ {% for test in cases %} #[test] -{% if loop.index != 1 -%} #[ignore] -{% endif -%} fn test_{{ test.description | slugify | replace(from="-", to="_") }}() { let input = {{ test.input.number | json_encode() }}; let output = {{ crate_name }}::{{ fn_names[0] }}(input); diff --git a/exercises/practice/rectangles/.meta/test_template.tera b/exercises/practice/rectangles/.meta/test_template.tera index 0813a456a..5c7c99c3b 100644 --- a/exercises/practice/rectangles/.meta/test_template.tera +++ b/exercises/practice/rectangles/.meta/test_template.tera @@ -1,8 +1,6 @@ {% for test in cases %} #[test] -{% if loop.index != 1 -%} #[ignore] -{% endif -%} fn test_{{ test.description | slugify | replace(from="-", to="_") }}() { {% if test.input.strings | length > 1 -%} #[rustfmt::skip] diff --git a/exercises/practice/reverse-string/.meta/test_template.tera b/exercises/practice/reverse-string/.meta/test_template.tera index cf8485eb7..3ad0cbafe 100644 --- a/exercises/practice/reverse-string/.meta/test_template.tera +++ b/exercises/practice/reverse-string/.meta/test_template.tera @@ -1,8 +1,6 @@ {% for test in cases %} #[test] -{% if loop.index != 1 -%} #[ignore] -{% endif -%} {% if test.description is starting_with("grapheme cluster") -%} #[cfg(feature = "grapheme")] {% endif -%} diff --git a/exercises/practice/rna-transcription/.meta/test_template.tera b/exercises/practice/rna-transcription/.meta/test_template.tera index f85eac220..30693ca00 100644 --- a/exercises/practice/rna-transcription/.meta/test_template.tera +++ b/exercises/practice/rna-transcription/.meta/test_template.tera @@ -1,9 +1,7 @@ use rna_transcription::{Dna, Rna}; {% for test in cases %} #[test] -{% if loop.index != 1 -%} #[ignore] -{% endif -%} fn {{ test.description | slugify | replace(from="-", to="_") }}() { let input = {{ test.input.dna | json_encode() }}; {% if test.property == "invalidDna" -%} diff --git a/exercises/practice/robot-simulator/.meta/test_template.tera b/exercises/practice/robot-simulator/.meta/test_template.tera index fb0647357..e441b3958 100644 --- a/exercises/practice/robot-simulator/.meta/test_template.tera +++ b/exercises/practice/robot-simulator/.meta/test_template.tera @@ -2,9 +2,7 @@ use {{ crate_name }}::*; {% for test in cases %} #[test] -{% if loop.index != 1 -%} #[ignore] -{% endif -%} fn {{ test.description | slugify | replace(from="-", to="_") }}() { {%- if test.property == "create" %} let robot = Robot::new({{ test.input.position.x }}, {{ test.input.position.y }}, Direction::{{ test.input.direction | title }}); diff --git a/exercises/practice/roman-numerals/.meta/test_template.tera b/exercises/practice/roman-numerals/.meta/test_template.tera index 9b1114af4..97e0c4d01 100644 --- a/exercises/practice/roman-numerals/.meta/test_template.tera +++ b/exercises/practice/roman-numerals/.meta/test_template.tera @@ -2,9 +2,7 @@ use roman_numerals::Roman; {% for test in cases %} #[test] -{% if loop.index != 1 -%} #[ignore] -{% endif -%} fn number_{{ test.description | slugify | replace(from="-", to="_") }}() { let input = {{ test.input.number | json_encode() }}; let output = Roman::from(input).to_string(); diff --git a/exercises/practice/rotational-cipher/.meta/test_template.tera b/exercises/practice/rotational-cipher/.meta/test_template.tera index 561e69115..1f10fb77f 100644 --- a/exercises/practice/rotational-cipher/.meta/test_template.tera +++ b/exercises/practice/rotational-cipher/.meta/test_template.tera @@ -1,9 +1,7 @@ use rotational_cipher as cipher; {% for test in cases %} #[test] -{% if loop.index != 1 -%} #[ignore] -{% endif -%} fn {{ test.description | slugify | replace(from="-", to="_") }}() { let text = {{ test.input.text | json_encode() }}; let shift_key = {{ test.input.shiftKey | json_encode() }}; diff --git a/exercises/practice/run-length-encoding/.meta/test_template.tera b/exercises/practice/run-length-encoding/.meta/test_template.tera index 15e8ffe88..d82ce7a24 100644 --- a/exercises/practice/run-length-encoding/.meta/test_template.tera +++ b/exercises/practice/run-length-encoding/.meta/test_template.tera @@ -2,9 +2,7 @@ use run_length_encoding as rle; {% for test in cases %} #[test] -{% if loop.index != 1 -%} #[ignore] -{% endif -%} fn {{ test.property }}_{{ test.description | slugify | replace(from="-", to="_") }}() { let input = {{ test.input.string | json_encode() }}; {% if test.property == "consistency" -%} diff --git a/exercises/practice/saddle-points/.meta/test_template.tera b/exercises/practice/saddle-points/.meta/test_template.tera index 20dc39e80..722a4d346 100644 --- a/exercises/practice/saddle-points/.meta/test_template.tera +++ b/exercises/practice/saddle-points/.meta/test_template.tera @@ -6,9 +6,7 @@ fn find_sorted_saddle_points(input: &[Vec]) -> Vec<(usize, usize)> { } {% for test in cases %} #[test] -{% if loop.index != 1 -%} #[ignore] -{% endif -%} fn {{ test.description | slugify | replace(from="-", to="_") }}() { let input = &[{% for row in test.input.matrix %} vec!{{ row }}, diff --git a/exercises/practice/say/.meta/test_template.tera b/exercises/practice/say/.meta/test_template.tera index 51753e6f7..6118ad8ad 100644 --- a/exercises/practice/say/.meta/test_template.tera +++ b/exercises/practice/say/.meta/test_template.tera @@ -1,8 +1,6 @@ {% for test in cases %} #[test] -{% if loop.index != 1 -%} #[ignore] -{% endif -%} fn {{ test.description | slugify | replace(from="-", to="_") }}() { let input = {{ test.input.number | json_encode() }}; let output = {{ crate_name }}::{{ fn_names[0] }}(input); diff --git a/exercises/practice/scrabble-score/.meta/test_template.tera b/exercises/practice/scrabble-score/.meta/test_template.tera index 093d6dc57..e79e7248e 100644 --- a/exercises/practice/scrabble-score/.meta/test_template.tera +++ b/exercises/practice/scrabble-score/.meta/test_template.tera @@ -1,8 +1,6 @@ {% for test in cases %} #[test] -{% if loop.index != 1 -%} #[ignore] -{% endif -%} fn {{ test.description | slugify | replace(from="-", to="_") }}() { let input = {{ test.input.word | json_encode() }}; let output = {{ crate_name }}::{{ fn_names[0] }}(input); diff --git a/exercises/practice/series/.meta/test_template.tera b/exercises/practice/series/.meta/test_template.tera index 64a9fc5fc..eb3d1a34b 100644 --- a/exercises/practice/series/.meta/test_template.tera +++ b/exercises/practice/series/.meta/test_template.tera @@ -2,9 +2,7 @@ use {{ crate_name }}::*; {% for test in cases %} #[test] -{% if loop.index != 1 -%} #[ignore] -{% endif -%} fn {{ test.description | slugify | replace(from="-", to="_") }}() { let input = {{ test.input.series | json_encode() }}; let length = {{ test.input.sliceLength | json_encode() }}; diff --git a/exercises/practice/sieve/.meta/test_template.tera b/exercises/practice/sieve/.meta/test_template.tera index aab8df430..ccee077df 100644 --- a/exercises/practice/sieve/.meta/test_template.tera +++ b/exercises/practice/sieve/.meta/test_template.tera @@ -1,8 +1,6 @@ {% for test in cases %} #[test] -{% if loop.index != 1 -%} #[ignore] -{% endif -%} fn {{ test.description | slugify | replace(from="-", to="_") }}() { let input = {{ test.input.limit | json_encode() }}; let output = {{ crate_name }}::{{ fn_names[0] }}(input); diff --git a/exercises/practice/space-age/.meta/test_template.tera b/exercises/practice/space-age/.meta/test_template.tera index cde9b9498..fe5b89626 100644 --- a/exercises/practice/space-age/.meta/test_template.tera +++ b/exercises/practice/space-age/.meta/test_template.tera @@ -9,9 +9,7 @@ fn assert_in_delta(expected: f64, actual: f64) { } {% for test in cases %} #[test] -{% if loop.index != 1 -%} #[ignore] -{% endif -%} fn {{ test.description | slugify | replace(from="-", to="_") }}() { let seconds = {{ test.input.seconds | json_encode() }}; let duration = Duration::from(seconds); diff --git a/exercises/practice/spiral-matrix/.meta/test_template.tera b/exercises/practice/spiral-matrix/.meta/test_template.tera index ab4e24399..c4aa606fe 100644 --- a/exercises/practice/spiral-matrix/.meta/test_template.tera +++ b/exercises/practice/spiral-matrix/.meta/test_template.tera @@ -1,8 +1,6 @@ {% for test in cases %} #[test] -{% if loop.index != 1 -%} #[ignore] -{% endif -%} fn {{ test.description | slugify | replace(from="-", to="_") }}() { let input = {{ test.input.size | json_encode() }}; let output = {{ crate_name }}::{{ fn_names[0] }}(input); diff --git a/exercises/practice/sublist/.meta/test_template.tera b/exercises/practice/sublist/.meta/test_template.tera index 2b1f97d93..f5897a584 100644 --- a/exercises/practice/sublist/.meta/test_template.tera +++ b/exercises/practice/sublist/.meta/test_template.tera @@ -1,9 +1,7 @@ use sublist::Comparison; {% for test in cases %} #[test] -{% if loop.index != 1 -%} #[ignore] -{% endif -%} fn {{ test.description | slugify | replace(from="-", to="_") }}() { let list_one: &[i32] = &{{ test.input.listOne | json_encode() }}; let list_two: &[i32] = &{{ test.input.listTwo | json_encode() }}; diff --git a/exercises/practice/sum-of-multiples/.meta/test_template.tera b/exercises/practice/sum-of-multiples/.meta/test_template.tera index 5fd13a19d..df26c4a93 100644 --- a/exercises/practice/sum-of-multiples/.meta/test_template.tera +++ b/exercises/practice/sum-of-multiples/.meta/test_template.tera @@ -1,8 +1,6 @@ {% for test in cases %} #[test] -{% if loop.index != 1 -%} #[ignore] -{% endif -%} fn {{ test.description | slugify | replace(from="-", to="_") }}() { let factors = &{{ test.input.factors | json_encode() }}; let limit = {{ test.input.limit | json_encode() }}; diff --git a/exercises/practice/tournament/.meta/test_template.tera b/exercises/practice/tournament/.meta/test_template.tera index e469e04d3..e27c5d2a8 100644 --- a/exercises/practice/tournament/.meta/test_template.tera +++ b/exercises/practice/tournament/.meta/test_template.tera @@ -1,8 +1,6 @@ {% for test in cases %} #[test] -{% if loop.index != 1 -%} #[ignore] -{% endif -%} fn {{ test.description | slugify | replace(from="-", to="_") }}() { let input: &[&str] = &{{ test.input.rows | json_encode() }}; let input = input.join("\n"); diff --git a/exercises/practice/triangle/.meta/test_template.tera b/exercises/practice/triangle/.meta/test_template.tera index ed7c03a9f..18263a183 100644 --- a/exercises/practice/triangle/.meta/test_template.tera +++ b/exercises/practice/triangle/.meta/test_template.tera @@ -4,9 +4,7 @@ use triangle::Triangle; {% if test.property != "equilateral" %}{% continue %}{% endif %} #[test] -{% if loop.index != 1 -%} #[ignore] -{% endif -%} {% if test.description is containing("float") %} #[cfg(feature = "generic")] {% endif -%} diff --git a/exercises/practice/two-bucket/.meta/test_template.tera b/exercises/practice/two-bucket/.meta/test_template.tera index b6de2697b..28ddaff03 100644 --- a/exercises/practice/two-bucket/.meta/test_template.tera +++ b/exercises/practice/two-bucket/.meta/test_template.tera @@ -9,9 +9,7 @@ use two_bucket::{Bucket, BucketStats}; {% for test in cases %} #[test] -{% if loop.index != 1 -%} #[ignore] -{% endif -%} fn {{ test.description | slugify | replace(from="-", to="_") }}() { let output = {{ crate_name }}::{{ fn_names[0] }}( {{ test.input.bucketOne }}, {{ test.input.bucketTwo }}, {{ test.input.goal }}, diff --git a/exercises/practice/variable-length-quantity/.meta/test_template.tera b/exercises/practice/variable-length-quantity/.meta/test_template.tera index 27cd22a1c..7e6441992 100644 --- a/exercises/practice/variable-length-quantity/.meta/test_template.tera +++ b/exercises/practice/variable-length-quantity/.meta/test_template.tera @@ -1,9 +1,7 @@ use variable_length_quantity as vlq; {% for test in cases %} #[test] -{% if loop.index != 1 -%} #[ignore] -{% endif -%} fn {{ test.description | slugify | replace(from="-", to="_") }}() { {%- if test.property == "encode" %} let input = &{{ test.input.integers | json_encode() }}; diff --git a/exercises/practice/word-count/.meta/test_template.tera b/exercises/practice/word-count/.meta/test_template.tera index faec5732d..f459c4d16 100644 --- a/exercises/practice/word-count/.meta/test_template.tera +++ b/exercises/practice/word-count/.meta/test_template.tera @@ -2,9 +2,7 @@ use word_count::*; {% for test in cases %} #[test] -{% if loop.index != 1 -%} #[ignore] -{% endif -%} fn {{ test.description | slugify | replace(from="-", to="_") }}() { let input = {{ test.input.sentence | json_encode() }}; let mut output = word_count(input); diff --git a/exercises/practice/wordy/.meta/test_template.tera b/exercises/practice/wordy/.meta/test_template.tera index 8a362161f..3706fecc0 100644 --- a/exercises/practice/wordy/.meta/test_template.tera +++ b/exercises/practice/wordy/.meta/test_template.tera @@ -1,8 +1,6 @@ {% for test in cases %} #[test] -{% if loop.index != 1 -%} #[ignore] -{% endif -%} {% if test.property == "exponentials" -%} #[cfg(feature = "exponentials")] {% endif -%} diff --git a/rust-tooling/generate/src/lib.rs b/rust-tooling/generate/src/lib.rs index 0090dee66..a726cd1a0 100644 --- a/rust-tooling/generate/src/lib.rs +++ b/rust-tooling/generate/src/lib.rs @@ -121,7 +121,13 @@ fn generate_tests(slug: &str, fn_names: Vec) -> String { context.insert("cases", &single_cases); let rendered = template.render("test_template.tera", &context).unwrap(); - let rendered = rendered.trim_start(); + + // Remove ignore-annotation on first test. + // This could be done in the template itself, + // but doing it here makes all templates more readable. + // Also, it is harder to do this in the template when the template + // generates test functions inside a macro for modules. + let rendered = rendered.replacen("#[ignore]\n", "", 1); let mut child = Command::new("rustfmt") .args(["--color=always"]) @@ -155,7 +161,7 @@ It probably generates invalid Rust code." ); // still return the unformatted content to be written to the file - rendered.into() + rendered } } diff --git a/rust-tooling/generate/templates/default_test_template.tera b/rust-tooling/generate/templates/default_test_template.tera index 94d4c88c8..9c7d6fe83 100644 --- a/rust-tooling/generate/templates/default_test_template.tera +++ b/rust-tooling/generate/templates/default_test_template.tera @@ -2,9 +2,7 @@ use {{ crate_name }}::*; {% for test in cases %} #[test] -{% if loop.index != 1 -%} #[ignore] -{% endif -%} fn {{ test.description | slugify | replace(from="-", to="_") }}() { let input = {{ test.input | json_encode() }}; let output = {{ fn_names[0] }}(input); From 9c9a5bdeb6632727e6195a49451191ebde744c1c Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 2 Apr 2024 09:37:20 +0200 Subject: [PATCH 267/436] sync documentation and metadata (#1885) --- .../pythagorean-triplet/.meta/config.json | 2 +- .../queen-attack/.docs/instructions.md | 20 ++++++++----------- .../practice/raindrops/.meta/config.json | 2 +- exercises/practice/say/.docs/instructions.md | 2 -- .../practice/spiral-matrix/.meta/config.json | 2 +- .../practice/two-bucket/.docs/instructions.md | 2 +- 6 files changed, 12 insertions(+), 18 deletions(-) diff --git a/exercises/practice/pythagorean-triplet/.meta/config.json b/exercises/practice/pythagorean-triplet/.meta/config.json index 8519cc3db..61415459f 100644 --- a/exercises/practice/pythagorean-triplet/.meta/config.json +++ b/exercises/practice/pythagorean-triplet/.meta/config.json @@ -33,7 +33,7 @@ ".meta/example.rs" ] }, - "blurb": "There exists exactly one Pythagorean triplet for which a + b + c = 1000. Find the product a * b * c.", + "blurb": "There exists exactly one Pythagorean triplet for which a + b + c = 1000. Find the triplet.", "source": "Problem 9 at Project Euler", "source_url": "https://projecteuler.net/problem=9" } diff --git a/exercises/practice/queen-attack/.docs/instructions.md b/exercises/practice/queen-attack/.docs/instructions.md index ad7ea9547..97f22a0ae 100644 --- a/exercises/practice/queen-attack/.docs/instructions.md +++ b/exercises/practice/queen-attack/.docs/instructions.md @@ -8,18 +8,14 @@ A chessboard can be represented by an 8 by 8 array. So if you are told the white queen is at `c5` (zero-indexed at column 2, row 3) and the black queen at `f2` (zero-indexed at column 5, row 6), then you know that the set-up is like so: -```text - a b c d e f g h -8 _ _ _ _ _ _ _ _ 8 -7 _ _ _ _ _ _ _ _ 7 -6 _ _ _ _ _ _ _ _ 6 -5 _ _ W _ _ _ _ _ 5 -4 _ _ _ _ _ _ _ _ 4 -3 _ _ _ _ _ _ _ _ 3 -2 _ _ _ _ _ B _ _ 2 -1 _ _ _ _ _ _ _ _ 1 - a b c d e f g h -``` +![A chess board with two queens. Arrows emanating from the queen at c5 indicate possible directions of capture along file, rank and diagonal.](https://assets.exercism.org/images/exercises/queen-attack/queen-capture.svg) You are also able to answer whether the queens can attack each other. In this case, that answer would be yes, they can, because both pieces share a diagonal. + +## Credit + +The chessboard image was made by [habere-et-dispertire][habere-et-dispertire] using LaTeX and the [chessboard package][chessboard-package] by Ulrike Fischer. + +[habere-et-dispertire]: https://exercism.org/profiles/habere-et-dispertire +[chessboard-package]: https://github.com/u-fischer/chessboard diff --git a/exercises/practice/raindrops/.meta/config.json b/exercises/practice/raindrops/.meta/config.json index 6c6974228..fee65d4bb 100644 --- a/exercises/practice/raindrops/.meta/config.json +++ b/exercises/practice/raindrops/.meta/config.json @@ -36,7 +36,7 @@ ".meta/example.rs" ] }, - "blurb": "Convert a number to a string, the content of which depends on the number's factors.", + "blurb": "Convert a number into its corresponding raindrop sounds - Pling, Plang and Plong.", "source": "A variation on FizzBuzz, a famous technical interview question that is intended to weed out potential candidates. That question is itself derived from Fizz Buzz, a popular children's game for teaching division.", "source_url": "https://en.wikipedia.org/wiki/Fizz_buzz" } diff --git a/exercises/practice/say/.docs/instructions.md b/exercises/practice/say/.docs/instructions.md index fb4a6dfb9..ad3d34778 100644 --- a/exercises/practice/say/.docs/instructions.md +++ b/exercises/practice/say/.docs/instructions.md @@ -30,8 +30,6 @@ Implement breaking a number up into chunks of thousands. So `1234567890` should yield a list like 1, 234, 567, and 890, while the far simpler `1000` should yield just 1 and 0. -The program must also report any values that are out of range. - ## Step 3 Now handle inserting the appropriate scale word between those chunks. diff --git a/exercises/practice/spiral-matrix/.meta/config.json b/exercises/practice/spiral-matrix/.meta/config.json index 0e2997e71..498e920ab 100644 --- a/exercises/practice/spiral-matrix/.meta/config.json +++ b/exercises/practice/spiral-matrix/.meta/config.json @@ -28,5 +28,5 @@ }, "blurb": "Given the size, return a square matrix of numbers in spiral order.", "source": "Reddit r/dailyprogrammer challenge #320 [Easy] Spiral Ascension.", - "source_url": "https://www.reddit.com/r/dailyprogrammer/comments/6i60lr/20170619_challenge_320_easy_spiral_ascension/" + "source_url": "https://web.archive.org/web/20230607064729/https://old.reddit.com/r/dailyprogrammer/comments/6i60lr/20170619_challenge_320_easy_spiral_ascension/" } diff --git a/exercises/practice/two-bucket/.docs/instructions.md b/exercises/practice/two-bucket/.docs/instructions.md index 7249deb36..30d779aa9 100644 --- a/exercises/practice/two-bucket/.docs/instructions.md +++ b/exercises/practice/two-bucket/.docs/instructions.md @@ -11,7 +11,7 @@ There are some rules that your solution must follow: b) the second bucket is full 2. Emptying a bucket and doing nothing to the other. 3. Filling a bucket and doing nothing to the other. -- After an action, you may not arrive at a state where the starting bucket is empty and the other bucket is full. +- After an action, you may not arrive at a state where the initial starting bucket is empty and the other bucket is full. Your program will take as input: From 37449b69c922d5b192fbcffd280ca70b5c4b8706 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 2 Apr 2024 09:54:38 +0200 Subject: [PATCH 268/436] generator: remove custom context (#1886) [no important files changed] --- docs/CONTRIBUTING.md | 12 +--- .../practice/acronym/.meta/test_template.tera | 4 +- exercises/practice/acronym/tests/acronym.rs | 22 ++++--- .../affine-cipher/.meta/test_template.tera | 4 +- .../affine-cipher/tests/affine-cipher.rs | 34 +++++----- .../allergies/.meta/test_template.tera | 2 +- .../practice/anagram/.meta/test_template.tera | 4 +- .../book-store/.meta/test_template.tera | 4 +- .../practice/book-store/tests/book-store.rs | 38 +++++------ .../eliuds-eggs/.meta/test_template.tera | 4 +- .../knapsack/.meta/test_template.tera | 4 +- .../practice/leap/.meta/test_template.tera | 2 +- .../practice/matrix/.meta/test_template.tera | 2 +- .../perfect-numbers/.meta/test_template.tera | 4 +- .../phone-number/.meta/test_template.tera | 4 +- .../pig-latin/.meta/test_template.tera | 4 +- .../practice/poker/.meta/test_template.tera | 4 +- .../prime-factors/.meta/test_template.tera | 4 +- .../practice/proverb/.meta/test_template.tera | 4 +- exercises/practice/proverb/tests/proverb.rs | 14 +++-- .../.meta/test_template.tera | 4 +- .../tests/pythagorean-triplet.rs | 16 ++--- .../raindrops/.meta/test_template.tera | 4 +- .../practice/raindrops/tests/raindrops.rs | 38 +++++------ .../rectangles/.meta/test_template.tera | 4 +- .../practice/rectangles/tests/rectangles.rs | 30 ++++----- .../reverse-string/.meta/test_template.tera | 4 +- .../reverse-string/tests/reverse-string.rs | 20 +++--- .../robot-simulator/.meta/test_template.tera | 2 +- .../.meta/test_template.tera | 2 +- .../practice/say/.meta/test_template.tera | 4 +- exercises/practice/say/tests/say.rs | 40 ++++++------ .../scrabble-score/.meta/test_template.tera | 4 +- .../scrabble-score/tests/scrabble-score.rs | 28 +++++---- .../practice/series/.meta/test_template.tera | 4 +- .../practice/sieve/.meta/test_template.tera | 4 +- exercises/practice/sieve/tests/sieve.rs | 12 ++-- .../spiral-matrix/.meta/test_template.tera | 4 +- .../spiral-matrix/tests/spiral-matrix.rs | 14 +++-- .../practice/sublist/.meta/test_template.tera | 5 +- exercises/practice/sublist/tests/sublist.rs | 38 +++++------ .../sum-of-multiples/.meta/test_template.tera | 4 +- .../tests/sum-of-multiples.rs | 34 +++++----- .../tournament/.meta/test_template.tera | 4 +- .../practice/tournament/tests/tournament.rs | 26 ++++---- .../two-bucket/.meta/test_template.tera | 5 +- .../practice/two-bucket/tests/two-bucket.rs | 20 +++--- .../practice/wordy/.meta/test_template.tera | 4 +- exercises/practice/wordy/tests/wordy.rs | 52 +++++++-------- rust-tooling/generate/src/lib.rs | 63 +++++-------------- rust-tooling/generate/src/main.rs | 8 +-- .../templates/default_test_template.tera | 4 +- .../tests/tera_templates_are_in_sync.rs | 3 +- 53 files changed, 348 insertions(+), 334 deletions(-) diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 484e28a04..0dcc8e575 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -134,18 +134,10 @@ let expected = {% if test.expected is object -%} {%- endif %}; ``` -If every test case needs to do some crunching of the inputs, -you can add utils functions at the top of the tera template. -See [`word-count`'s template][word-count-tmpl] for an example. - Some exercises have multiple functions that need to be implemented by the student and therefore tested. The canonical data contains a field `property` in that case. -The template also has access to a value `fn_names`, -which is an array of functions found in `lib.rs`. -So, you can construct if-else-chains based on `test.property` -and render a different element of `fn_names` based on that. -See [`variable-length-quantity`'s template][var-len-q-tmpl] for an example. +You can construct if-else-chains based on `test.property` and render a different function based on that. There is a custom tera fiter `to_hex`, which formats ints in hexadecimal. Feel free to add your own in the crate `rust-tooling`. @@ -157,8 +149,6 @@ It organizes the test cases in modules and dynamically detects which tests to pu That exercise also reimplements some test cases from upstream in `additional-tests.json`, in order to add more information to them necessary for generating good tests. [tera-docs]: https://keats.github.io/tera/docs/#templates -[word-count-tmpl]: /exercises/practice/word-count/.meta/test_template.tera -[var-len-q-tmpl]: /exercises/practice/variable-length-quantity/.meta/test_template.tera [tera-docs-filters]: https://keats.github.io/tera/docs/#filters ## Syllabus diff --git a/exercises/practice/acronym/.meta/test_template.tera b/exercises/practice/acronym/.meta/test_template.tera index 2c1911922..a0c475be5 100644 --- a/exercises/practice/acronym/.meta/test_template.tera +++ b/exercises/practice/acronym/.meta/test_template.tera @@ -1,9 +1,11 @@ +use acronym::*; + {% for test in cases %} #[test] #[ignore] fn {{ test.description | slugify | replace(from="-", to="_") }}() { let input = {{ test.input.phrase | json_encode() }}; - let output = {{ crate_name }}::{{ fn_names[0] }}(input); + let output = abbreviate(input); let expected = {{ test.expected | json_encode() }}; assert_eq!(output, expected); } diff --git a/exercises/practice/acronym/tests/acronym.rs b/exercises/practice/acronym/tests/acronym.rs index 7dae77143..29e54bd52 100644 --- a/exercises/practice/acronym/tests/acronym.rs +++ b/exercises/practice/acronym/tests/acronym.rs @@ -1,7 +1,9 @@ +use acronym::*; + #[test] fn basic() { let input = "Portable Network Graphics"; - let output = acronym::abbreviate(input); + let output = abbreviate(input); let expected = "PNG"; assert_eq!(output, expected); } @@ -10,7 +12,7 @@ fn basic() { #[ignore] fn lowercase_words() { let input = "Ruby on Rails"; - let output = acronym::abbreviate(input); + let output = abbreviate(input); let expected = "ROR"; assert_eq!(output, expected); } @@ -19,7 +21,7 @@ fn lowercase_words() { #[ignore] fn punctuation() { let input = "First In, First Out"; - let output = acronym::abbreviate(input); + let output = abbreviate(input); let expected = "FIFO"; assert_eq!(output, expected); } @@ -28,7 +30,7 @@ fn punctuation() { #[ignore] fn all_caps_word() { let input = "GNU Image Manipulation Program"; - let output = acronym::abbreviate(input); + let output = abbreviate(input); let expected = "GIMP"; assert_eq!(output, expected); } @@ -37,7 +39,7 @@ fn all_caps_word() { #[ignore] fn punctuation_without_whitespace() { let input = "Complementary metal-oxide semiconductor"; - let output = acronym::abbreviate(input); + let output = abbreviate(input); let expected = "CMOS"; assert_eq!(output, expected); } @@ -46,7 +48,7 @@ fn punctuation_without_whitespace() { #[ignore] fn very_long_abbreviation() { let input = "Rolling On The Floor Laughing So Hard That My Dogs Came Over And Licked Me"; - let output = acronym::abbreviate(input); + let output = abbreviate(input); let expected = "ROTFLSHTMDCOALM"; assert_eq!(output, expected); } @@ -55,7 +57,7 @@ fn very_long_abbreviation() { #[ignore] fn consecutive_delimiters() { let input = "Something - I made up from thin air"; - let output = acronym::abbreviate(input); + let output = abbreviate(input); let expected = "SIMUFTA"; assert_eq!(output, expected); } @@ -64,7 +66,7 @@ fn consecutive_delimiters() { #[ignore] fn apostrophes() { let input = "Halley's Comet"; - let output = acronym::abbreviate(input); + let output = abbreviate(input); let expected = "HC"; assert_eq!(output, expected); } @@ -73,7 +75,7 @@ fn apostrophes() { #[ignore] fn underscore_emphasis() { let input = "The Road _Not_ Taken"; - let output = acronym::abbreviate(input); + let output = abbreviate(input); let expected = "TRNT"; assert_eq!(output, expected); } @@ -82,7 +84,7 @@ fn underscore_emphasis() { #[ignore] fn camelcase() { let input = "HyperText Markup Language"; - let output = acronym::abbreviate(input); + let output = abbreviate(input); let expected = "HTML"; assert_eq!(output, expected); } diff --git a/exercises/practice/affine-cipher/.meta/test_template.tera b/exercises/practice/affine-cipher/.meta/test_template.tera index 8f04e1f7a..140447056 100644 --- a/exercises/practice/affine-cipher/.meta/test_template.tera +++ b/exercises/practice/affine-cipher/.meta/test_template.tera @@ -1,3 +1,5 @@ +use affine_cipher::*; + use affine_cipher::AffineCipherError::NotCoprime; {% for test in cases %} #[test] @@ -5,7 +7,7 @@ use affine_cipher::AffineCipherError::NotCoprime; fn {{ test.description | slugify | replace(from="-", to="_") }}() { let phrase = {{ test.input.phrase | json_encode() }}; let (a, b) = ({{ test.input.key.a }}, {{ test.input.key.b }}); - let output = {{ crate_name }}::{{ test.property }}(phrase, a, b); + let output = {{ test.property }}(phrase, a, b); let expected = {% if test.expected is object -%} Err(NotCoprime({{ test.input.key.a }})) {%- else -%} diff --git a/exercises/practice/affine-cipher/tests/affine-cipher.rs b/exercises/practice/affine-cipher/tests/affine-cipher.rs index 2476677a0..f11b8665b 100644 --- a/exercises/practice/affine-cipher/tests/affine-cipher.rs +++ b/exercises/practice/affine-cipher/tests/affine-cipher.rs @@ -1,10 +1,12 @@ +use affine_cipher::*; + use affine_cipher::AffineCipherError::NotCoprime; #[test] fn encode_yes() { let phrase = "yes"; let (a, b) = (5, 7); - let output = affine_cipher::encode(phrase, a, b); + let output = encode(phrase, a, b); let expected = Ok("xbt".into()); assert_eq!(output, expected); } @@ -14,7 +16,7 @@ fn encode_yes() { fn encode_no() { let phrase = "no"; let (a, b) = (15, 18); - let output = affine_cipher::encode(phrase, a, b); + let output = encode(phrase, a, b); let expected = Ok("fu".into()); assert_eq!(output, expected); } @@ -24,7 +26,7 @@ fn encode_no() { fn encode_omg() { let phrase = "OMG"; let (a, b) = (21, 3); - let output = affine_cipher::encode(phrase, a, b); + let output = encode(phrase, a, b); let expected = Ok("lvz".into()); assert_eq!(output, expected); } @@ -34,7 +36,7 @@ fn encode_omg() { fn encode_o_m_g() { let phrase = "O M G"; let (a, b) = (25, 47); - let output = affine_cipher::encode(phrase, a, b); + let output = encode(phrase, a, b); let expected = Ok("hjp".into()); assert_eq!(output, expected); } @@ -44,7 +46,7 @@ fn encode_o_m_g() { fn encode_mindblowingly() { let phrase = "mindblowingly"; let (a, b) = (11, 15); - let output = affine_cipher::encode(phrase, a, b); + let output = encode(phrase, a, b); let expected = Ok("rzcwa gnxzc dgt".into()); assert_eq!(output, expected); } @@ -54,7 +56,7 @@ fn encode_mindblowingly() { fn encode_numbers() { let phrase = "Testing,1 2 3, testing."; let (a, b) = (3, 4); - let output = affine_cipher::encode(phrase, a, b); + let output = encode(phrase, a, b); let expected = Ok("jqgjc rw123 jqgjc rw".into()); assert_eq!(output, expected); } @@ -64,7 +66,7 @@ fn encode_numbers() { fn encode_deep_thought() { let phrase = "Truth is fiction."; let (a, b) = (5, 17); - let output = affine_cipher::encode(phrase, a, b); + let output = encode(phrase, a, b); let expected = Ok("iynia fdqfb ifje".into()); assert_eq!(output, expected); } @@ -74,7 +76,7 @@ fn encode_deep_thought() { fn encode_all_the_letters() { let phrase = "The quick brown fox jumps over the lazy dog."; let (a, b) = (17, 33); - let output = affine_cipher::encode(phrase, a, b); + let output = encode(phrase, a, b); let expected = Ok("swxtj npvyk lruol iejdc blaxk swxmh qzglf".into()); assert_eq!(output, expected); } @@ -84,7 +86,7 @@ fn encode_all_the_letters() { fn encode_with_a_not_coprime_to_m() { let phrase = "This is a test."; let (a, b) = (6, 17); - let output = affine_cipher::encode(phrase, a, b); + let output = encode(phrase, a, b); let expected = Err(NotCoprime(6)); assert_eq!(output, expected); } @@ -94,7 +96,7 @@ fn encode_with_a_not_coprime_to_m() { fn decode_exercism() { let phrase = "tytgn fjr"; let (a, b) = (3, 7); - let output = affine_cipher::decode(phrase, a, b); + let output = decode(phrase, a, b); let expected = Ok("exercism".into()); assert_eq!(output, expected); } @@ -104,7 +106,7 @@ fn decode_exercism() { fn decode_a_sentence() { let phrase = "qdwju nqcro muwhn odqun oppmd aunwd o"; let (a, b) = (19, 16); - let output = affine_cipher::decode(phrase, a, b); + let output = decode(phrase, a, b); let expected = Ok("anobstacleisoftenasteppingstone".into()); assert_eq!(output, expected); } @@ -114,7 +116,7 @@ fn decode_a_sentence() { fn decode_numbers() { let phrase = "odpoz ub123 odpoz ub"; let (a, b) = (25, 7); - let output = affine_cipher::decode(phrase, a, b); + let output = decode(phrase, a, b); let expected = Ok("testing123testing".into()); assert_eq!(output, expected); } @@ -124,7 +126,7 @@ fn decode_numbers() { fn decode_all_the_letters() { let phrase = "swxtj npvyk lruol iejdc blaxk swxmh qzglf"; let (a, b) = (17, 33); - let output = affine_cipher::decode(phrase, a, b); + let output = decode(phrase, a, b); let expected = Ok("thequickbrownfoxjumpsoverthelazydog".into()); assert_eq!(output, expected); } @@ -134,7 +136,7 @@ fn decode_all_the_letters() { fn decode_with_no_spaces_in_input() { let phrase = "swxtjnpvyklruoliejdcblaxkswxmhqzglf"; let (a, b) = (17, 33); - let output = affine_cipher::decode(phrase, a, b); + let output = decode(phrase, a, b); let expected = Ok("thequickbrownfoxjumpsoverthelazydog".into()); assert_eq!(output, expected); } @@ -144,7 +146,7 @@ fn decode_with_no_spaces_in_input() { fn decode_with_too_many_spaces() { let phrase = "vszzm cly yd cg qdp"; let (a, b) = (15, 16); - let output = affine_cipher::decode(phrase, a, b); + let output = decode(phrase, a, b); let expected = Ok("jollygreengiant".into()); assert_eq!(output, expected); } @@ -154,7 +156,7 @@ fn decode_with_too_many_spaces() { fn decode_with_a_not_coprime_to_m() { let phrase = "Test"; let (a, b) = (13, 5); - let output = affine_cipher::decode(phrase, a, b); + let output = decode(phrase, a, b); let expected = Err(NotCoprime(13)); assert_eq!(output, expected); } diff --git a/exercises/practice/allergies/.meta/test_template.tera b/exercises/practice/allergies/.meta/test_template.tera index ee99d5f2d..d3b16500b 100644 --- a/exercises/practice/allergies/.meta/test_template.tera +++ b/exercises/practice/allergies/.meta/test_template.tera @@ -1,4 +1,4 @@ -use {{ crate_name }}::*; +use allergies::*; fn compare_allergy_vectors(expected: &[Allergen], actual: &[Allergen]) { for element in expected { diff --git a/exercises/practice/anagram/.meta/test_template.tera b/exercises/practice/anagram/.meta/test_template.tera index cb6f7347a..313959ea8 100644 --- a/exercises/practice/anagram/.meta/test_template.tera +++ b/exercises/practice/anagram/.meta/test_template.tera @@ -1,4 +1,4 @@ -use {{ crate_name }}::*; +use anagram::*; use std::collections::HashSet; {% for test in cases %} @@ -7,7 +7,7 @@ use std::collections::HashSet; fn {{ test.description | slugify | replace(from="-", to="_") }}() { let word = {{ test.input.subject | json_encode() }}; let inputs = &{{ test.input.candidates | json_encode() }}; - let output = {{ fn_names[0] }}(word, inputs); + let output = anagrams_for(word, inputs); let expected = HashSet::from_iter({{ test.expected | json_encode() }}); assert_eq!(output, expected); } diff --git a/exercises/practice/book-store/.meta/test_template.tera b/exercises/practice/book-store/.meta/test_template.tera index 4c4ae9e77..4bbd5e63f 100644 --- a/exercises/practice/book-store/.meta/test_template.tera +++ b/exercises/practice/book-store/.meta/test_template.tera @@ -1,9 +1,11 @@ +use book_store::*; + {% for test in cases %} #[test] #[ignore] fn {{ test.description | slugify | replace(from="-", to="_") }}() { let input = &{{ test.input.basket | json_encode() }}; - let output = {{ crate_name }}::{{ fn_names[0] }}(input); + let output = lowest_price(input); let expected = {{ test.expected | json_encode() }}; assert_eq!(output, expected); } diff --git a/exercises/practice/book-store/tests/book-store.rs b/exercises/practice/book-store/tests/book-store.rs index 10e71c8ca..944139343 100644 --- a/exercises/practice/book-store/tests/book-store.rs +++ b/exercises/practice/book-store/tests/book-store.rs @@ -1,7 +1,9 @@ +use book_store::*; + #[test] fn only_a_single_book() { let input = &[1]; - let output = book_store::lowest_price(input); + let output = lowest_price(input); let expected = 800; assert_eq!(output, expected); } @@ -10,7 +12,7 @@ fn only_a_single_book() { #[ignore] fn two_of_the_same_book() { let input = &[2, 2]; - let output = book_store::lowest_price(input); + let output = lowest_price(input); let expected = 1600; assert_eq!(output, expected); } @@ -19,7 +21,7 @@ fn two_of_the_same_book() { #[ignore] fn empty_basket() { let input = &[]; - let output = book_store::lowest_price(input); + let output = lowest_price(input); let expected = 0; assert_eq!(output, expected); } @@ -28,7 +30,7 @@ fn empty_basket() { #[ignore] fn two_different_books() { let input = &[1, 2]; - let output = book_store::lowest_price(input); + let output = lowest_price(input); let expected = 1520; assert_eq!(output, expected); } @@ -37,7 +39,7 @@ fn two_different_books() { #[ignore] fn three_different_books() { let input = &[1, 2, 3]; - let output = book_store::lowest_price(input); + let output = lowest_price(input); let expected = 2160; assert_eq!(output, expected); } @@ -46,7 +48,7 @@ fn three_different_books() { #[ignore] fn four_different_books() { let input = &[1, 2, 3, 4]; - let output = book_store::lowest_price(input); + let output = lowest_price(input); let expected = 2560; assert_eq!(output, expected); } @@ -55,7 +57,7 @@ fn four_different_books() { #[ignore] fn five_different_books() { let input = &[1, 2, 3, 4, 5]; - let output = book_store::lowest_price(input); + let output = lowest_price(input); let expected = 3000; assert_eq!(output, expected); } @@ -64,7 +66,7 @@ fn five_different_books() { #[ignore] fn two_groups_of_four_is_cheaper_than_group_of_five_plus_group_of_three() { let input = &[1, 1, 2, 2, 3, 3, 4, 5]; - let output = book_store::lowest_price(input); + let output = lowest_price(input); let expected = 5120; assert_eq!(output, expected); } @@ -73,7 +75,7 @@ fn two_groups_of_four_is_cheaper_than_group_of_five_plus_group_of_three() { #[ignore] fn two_groups_of_four_is_cheaper_than_groups_of_five_and_three() { let input = &[1, 1, 2, 3, 4, 4, 5, 5]; - let output = book_store::lowest_price(input); + let output = lowest_price(input); let expected = 5120; assert_eq!(output, expected); } @@ -82,7 +84,7 @@ fn two_groups_of_four_is_cheaper_than_groups_of_five_and_three() { #[ignore] fn group_of_four_plus_group_of_two_is_cheaper_than_two_groups_of_three() { let input = &[1, 1, 2, 2, 3, 4]; - let output = book_store::lowest_price(input); + let output = lowest_price(input); let expected = 4080; assert_eq!(output, expected); } @@ -91,7 +93,7 @@ fn group_of_four_plus_group_of_two_is_cheaper_than_two_groups_of_three() { #[ignore] fn two_each_of_first_four_books_and_one_copy_each_of_rest() { let input = &[1, 1, 2, 2, 3, 3, 4, 4, 5]; - let output = book_store::lowest_price(input); + let output = lowest_price(input); let expected = 5560; assert_eq!(output, expected); } @@ -100,7 +102,7 @@ fn two_each_of_first_four_books_and_one_copy_each_of_rest() { #[ignore] fn two_copies_of_each_book() { let input = &[1, 1, 2, 2, 3, 3, 4, 4, 5, 5]; - let output = book_store::lowest_price(input); + let output = lowest_price(input); let expected = 6000; assert_eq!(output, expected); } @@ -109,7 +111,7 @@ fn two_copies_of_each_book() { #[ignore] fn three_copies_of_first_book_and_two_each_of_remaining() { let input = &[1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 1]; - let output = book_store::lowest_price(input); + let output = lowest_price(input); let expected = 6800; assert_eq!(output, expected); } @@ -118,7 +120,7 @@ fn three_copies_of_first_book_and_two_each_of_remaining() { #[ignore] fn three_each_of_first_two_books_and_two_each_of_remaining_books() { let input = &[1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 1, 2]; - let output = book_store::lowest_price(input); + let output = lowest_price(input); let expected = 7520; assert_eq!(output, expected); } @@ -127,7 +129,7 @@ fn three_each_of_first_two_books_and_two_each_of_remaining_books() { #[ignore] fn four_groups_of_four_are_cheaper_than_two_groups_each_of_five_and_three() { let input = &[1, 1, 2, 2, 3, 3, 4, 5, 1, 1, 2, 2, 3, 3, 4, 5]; - let output = book_store::lowest_price(input); + let output = lowest_price(input); let expected = 10240; assert_eq!(output, expected); } @@ -139,7 +141,7 @@ fn check_that_groups_of_four_are_created_properly_even_when_there_are_more_group let input = &[ 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 4, 5, 5, ]; - let output = book_store::lowest_price(input); + let output = lowest_price(input); let expected = 14560; assert_eq!(output, expected); } @@ -148,7 +150,7 @@ fn check_that_groups_of_four_are_created_properly_even_when_there_are_more_group #[ignore] fn one_group_of_one_and_four_is_cheaper_than_one_group_of_two_and_three() { let input = &[1, 1, 2, 3, 4]; - let output = book_store::lowest_price(input); + let output = lowest_price(input); let expected = 3360; assert_eq!(output, expected); } @@ -157,7 +159,7 @@ fn one_group_of_one_and_four_is_cheaper_than_one_group_of_two_and_three() { #[ignore] fn one_group_of_one_and_two_plus_three_groups_of_four_is_cheaper_than_one_group_of_each_size() { let input = &[1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5]; - let output = book_store::lowest_price(input); + let output = lowest_price(input); let expected = 10000; assert_eq!(output, expected); } diff --git a/exercises/practice/eliuds-eggs/.meta/test_template.tera b/exercises/practice/eliuds-eggs/.meta/test_template.tera index 58e5932ed..d9a91f564 100644 --- a/exercises/practice/eliuds-eggs/.meta/test_template.tera +++ b/exercises/practice/eliuds-eggs/.meta/test_template.tera @@ -1,11 +1,11 @@ -use {{ crate_name }}::*; +use eliuds_eggs::*; {% for test in cases %} #[test] #[ignore] fn test_{{ test.description | slugify | replace(from="-", to="_") }}() { let input = {{ test.input.number }}; - let output = {{ fn_names[0] }}(input); + let output = egg_count(input); let expected = {{ test.expected | json_encode() }}; assert_eq!(output, expected); } diff --git a/exercises/practice/knapsack/.meta/test_template.tera b/exercises/practice/knapsack/.meta/test_template.tera index 78b8597fd..7b3f781fa 100644 --- a/exercises/practice/knapsack/.meta/test_template.tera +++ b/exercises/practice/knapsack/.meta/test_template.tera @@ -1,4 +1,4 @@ -use {{ crate_name }}::*; +use knapsack::*; {% for test in cases %} #[test] @@ -13,7 +13,7 @@ fn test_{{ test.description | slugify | replace(from="-", to="_") }}() { }, {% endfor -%} ]; - let output = {{ fn_names[0] }}(max_weight, &items); + let output = maximum_value(max_weight, &items); let expected = {{ test.expected | json_encode() }}; assert_eq!(output, expected); } diff --git a/exercises/practice/leap/.meta/test_template.tera b/exercises/practice/leap/.meta/test_template.tera index e2a29c41c..5d9e4cf4e 100644 --- a/exercises/practice/leap/.meta/test_template.tera +++ b/exercises/practice/leap/.meta/test_template.tera @@ -1,4 +1,4 @@ -use {{ crate_name }}::*; +use leap::*; {% for test in cases %} #[test] diff --git a/exercises/practice/matrix/.meta/test_template.tera b/exercises/practice/matrix/.meta/test_template.tera index 7e7d76dca..397167761 100644 --- a/exercises/practice/matrix/.meta/test_template.tera +++ b/exercises/practice/matrix/.meta/test_template.tera @@ -1,4 +1,4 @@ -use {{ crate_name }}::*; +use matrix::*; {% for test in cases %} #[test] diff --git a/exercises/practice/perfect-numbers/.meta/test_template.tera b/exercises/practice/perfect-numbers/.meta/test_template.tera index 96a575724..c56ad1b9b 100644 --- a/exercises/practice/perfect-numbers/.meta/test_template.tera +++ b/exercises/practice/perfect-numbers/.meta/test_template.tera @@ -1,11 +1,11 @@ -use {{ crate_name }}::*; +use perfect_numbers::*; {% for test in cases %} #[test] #[ignore] fn {{ test.description | slugify | replace(from="-", to="_") }}() { let input = {{ test.input.number | json_encode() }}; - let output = {{ fn_names[0] }}(input); + let output = classify(input); {%- if test.expected is object %} assert!(output.is_none()); {% else %} diff --git a/exercises/practice/phone-number/.meta/test_template.tera b/exercises/practice/phone-number/.meta/test_template.tera index 26a84b679..82eac95dc 100644 --- a/exercises/practice/phone-number/.meta/test_template.tera +++ b/exercises/practice/phone-number/.meta/test_template.tera @@ -1,11 +1,11 @@ -use {{ crate_name }}::*; +use phone_number::*; {% for test in cases %} #[test] #[ignore] fn {{ test.description | slugify | replace(from="-", to="_") }}() { let input = {{ test.input.phrase | json_encode() }}; - let output = {{ fn_names[0] }}(input); + let output = number(input); {%- if test.expected is object %} assert!(output.is_none()); {% else %} diff --git a/exercises/practice/pig-latin/.meta/test_template.tera b/exercises/practice/pig-latin/.meta/test_template.tera index e0a1aba6d..4fc5c17ca 100644 --- a/exercises/practice/pig-latin/.meta/test_template.tera +++ b/exercises/practice/pig-latin/.meta/test_template.tera @@ -1,11 +1,11 @@ -use {{ crate_name }}::*; +use pig_latin::*; {% for test in cases %} #[test] #[ignore] fn {{ test.description | slugify | replace(from="-", to="_") }}() { let input = {{ test.input.phrase | json_encode() }}; - let output = {{ fn_names[0] }}(input); + let output = translate(input); let expected = {{ test.expected | json_encode() }}; assert_eq!(output, expected); } diff --git a/exercises/practice/poker/.meta/test_template.tera b/exercises/practice/poker/.meta/test_template.tera index 74415c2ab..ff55e7ca5 100644 --- a/exercises/practice/poker/.meta/test_template.tera +++ b/exercises/practice/poker/.meta/test_template.tera @@ -1,4 +1,4 @@ -use {{ crate_name }}::*; +use poker::*; use std::collections::HashSet; {% for test in cases %} @@ -6,7 +6,7 @@ use std::collections::HashSet; #[ignore] fn {{ test.description | slugify | replace(from="-", to="_") }}() { let input = &{{ test.input.hands | json_encode() }}; - let output = {{ fn_names[0] }}(input).into_iter().collect::>(); + let output = winning_hands(input).into_iter().collect::>(); let expected = {{ test.expected | json_encode() }}.into_iter().collect::>(); assert_eq!(output, expected); } diff --git a/exercises/practice/prime-factors/.meta/test_template.tera b/exercises/practice/prime-factors/.meta/test_template.tera index 2fdf30196..42752aada 100644 --- a/exercises/practice/prime-factors/.meta/test_template.tera +++ b/exercises/practice/prime-factors/.meta/test_template.tera @@ -1,10 +1,10 @@ -use {{ crate_name }}::*; +use prime_factors::*; {% for test in cases %} #[test] #[ignore] fn {{ test.description | slugify | replace(from="-", to="_") }}() { - let factors = {{ fn_names[0] }}({{ test.input.value }}); + let factors = factors({{ test.input.value }}); let expected = {{ test.expected | json_encode() }}; assert_eq!(factors, expected); } diff --git a/exercises/practice/proverb/.meta/test_template.tera b/exercises/practice/proverb/.meta/test_template.tera index 4212b3c70..6b9ddcf1d 100644 --- a/exercises/practice/proverb/.meta/test_template.tera +++ b/exercises/practice/proverb/.meta/test_template.tera @@ -1,9 +1,11 @@ +use proverb::*; + {% for test in cases %} #[test] #[ignore] fn {{ test.description | slugify | replace(from="-", to="_") }}() { let input = &{{ test.input.strings | json_encode() }}; - let output = {{ crate_name }}::{{ fn_names[0] }}(input); + let output = build_proverb(input); {% if test.expected | length == 0 -%} let expected = String::new(); {%- else -%} diff --git a/exercises/practice/proverb/tests/proverb.rs b/exercises/practice/proverb/tests/proverb.rs index be930b16d..becf70be3 100644 --- a/exercises/practice/proverb/tests/proverb.rs +++ b/exercises/practice/proverb/tests/proverb.rs @@ -1,7 +1,9 @@ +use proverb::*; + #[test] fn zero_pieces() { let input = &[]; - let output = proverb::build_proverb(input); + let output = build_proverb(input); let expected = String::new(); assert_eq!(output, expected); } @@ -10,7 +12,7 @@ fn zero_pieces() { #[ignore] fn one_piece() { let input = &["nail"]; - let output = proverb::build_proverb(input); + let output = build_proverb(input); let expected: String = ["And all for the want of a nail."].join("\n"); assert_eq!(output, expected); } @@ -19,7 +21,7 @@ fn one_piece() { #[ignore] fn two_pieces() { let input = &["nail", "shoe"]; - let output = proverb::build_proverb(input); + let output = build_proverb(input); let expected: String = [ "For want of a nail the shoe was lost.", "And all for the want of a nail.", @@ -32,7 +34,7 @@ fn two_pieces() { #[ignore] fn three_pieces() { let input = &["nail", "shoe", "horse"]; - let output = proverb::build_proverb(input); + let output = build_proverb(input); let expected: String = [ "For want of a nail the shoe was lost.", "For want of a shoe the horse was lost.", @@ -48,7 +50,7 @@ fn full_proverb() { let input = &[ "nail", "shoe", "horse", "rider", "message", "battle", "kingdom", ]; - let output = proverb::build_proverb(input); + let output = build_proverb(input); let expected: String = [ "For want of a nail the shoe was lost.", "For want of a shoe the horse was lost.", @@ -66,7 +68,7 @@ fn full_proverb() { #[ignore] fn four_pieces_modernized() { let input = &["pin", "gun", "soldier", "battle"]; - let output = proverb::build_proverb(input); + let output = build_proverb(input); let expected: String = [ "For want of a pin the gun was lost.", "For want of a gun the soldier was lost.", diff --git a/exercises/practice/pythagorean-triplet/.meta/test_template.tera b/exercises/practice/pythagorean-triplet/.meta/test_template.tera index aea88ab26..ff74382c1 100644 --- a/exercises/practice/pythagorean-triplet/.meta/test_template.tera +++ b/exercises/practice/pythagorean-triplet/.meta/test_template.tera @@ -1,10 +1,12 @@ +use pythagorean_triplet::*; + use std::collections::HashSet; {% for test in cases %} #[test] #[ignore] fn {{ test.description | slugify | replace(from="-", to="_") }}() { let input = {{ test.input.n | json_encode() }}; - let output = {{ crate_name }}::{{ fn_names[0] }}(input); + let output = find(input); let expected = {{ test.expected | json_encode() }}; let expected: HashSet<_> = expected.iter().cloned().collect(); assert_eq!(output, expected); diff --git a/exercises/practice/pythagorean-triplet/tests/pythagorean-triplet.rs b/exercises/practice/pythagorean-triplet/tests/pythagorean-triplet.rs index 67722e3b9..a10322554 100644 --- a/exercises/practice/pythagorean-triplet/tests/pythagorean-triplet.rs +++ b/exercises/practice/pythagorean-triplet/tests/pythagorean-triplet.rs @@ -1,9 +1,11 @@ +use pythagorean_triplet::*; + use std::collections::HashSet; #[test] fn triplets_whose_sum_is_12() { let input = 12; - let output = pythagorean_triplet::find(input); + let output = find(input); let expected = [[3, 4, 5]]; let expected: HashSet<_> = expected.iter().cloned().collect(); assert_eq!(output, expected); @@ -13,7 +15,7 @@ fn triplets_whose_sum_is_12() { #[ignore] fn triplets_whose_sum_is_108() { let input = 108; - let output = pythagorean_triplet::find(input); + let output = find(input); let expected = [[27, 36, 45]]; let expected: HashSet<_> = expected.iter().cloned().collect(); assert_eq!(output, expected); @@ -23,7 +25,7 @@ fn triplets_whose_sum_is_108() { #[ignore] fn triplets_whose_sum_is_1000() { let input = 1000; - let output = pythagorean_triplet::find(input); + let output = find(input); let expected = [[200, 375, 425]]; let expected: HashSet<_> = expected.iter().cloned().collect(); assert_eq!(output, expected); @@ -33,7 +35,7 @@ fn triplets_whose_sum_is_1000() { #[ignore] fn no_matching_triplets_for_1001() { let input = 1001; - let output = pythagorean_triplet::find(input); + let output = find(input); let expected = []; let expected: HashSet<_> = expected.iter().cloned().collect(); assert_eq!(output, expected); @@ -43,7 +45,7 @@ fn no_matching_triplets_for_1001() { #[ignore] fn returns_all_matching_triplets() { let input = 90; - let output = pythagorean_triplet::find(input); + let output = find(input); let expected = [[9, 40, 41], [15, 36, 39]]; let expected: HashSet<_> = expected.iter().cloned().collect(); assert_eq!(output, expected); @@ -53,7 +55,7 @@ fn returns_all_matching_triplets() { #[ignore] fn several_matching_triplets() { let input = 840; - let output = pythagorean_triplet::find(input); + let output = find(input); let expected = [ [40, 399, 401], [56, 390, 394], @@ -72,7 +74,7 @@ fn several_matching_triplets() { #[ignore] fn triplets_for_large_number() { let input = 30000; - let output = pythagorean_triplet::find(input); + let output = find(input); let expected = [ [1200, 14375, 14425], [1875, 14000, 14125], diff --git a/exercises/practice/raindrops/.meta/test_template.tera b/exercises/practice/raindrops/.meta/test_template.tera index 53aaae9b1..986ff926a 100644 --- a/exercises/practice/raindrops/.meta/test_template.tera +++ b/exercises/practice/raindrops/.meta/test_template.tera @@ -1,9 +1,11 @@ +use raindrops::*; + {% for test in cases %} #[test] #[ignore] fn test_{{ test.description | slugify | replace(from="-", to="_") }}() { let input = {{ test.input.number | json_encode() }}; - let output = {{ crate_name }}::{{ fn_names[0] }}(input); + let output = raindrops(input); let expected = {{ test.expected | json_encode() }}; assert_eq!(output, expected); } diff --git a/exercises/practice/raindrops/tests/raindrops.rs b/exercises/practice/raindrops/tests/raindrops.rs index a1d0b6939..d6999c98d 100644 --- a/exercises/practice/raindrops/tests/raindrops.rs +++ b/exercises/practice/raindrops/tests/raindrops.rs @@ -1,7 +1,9 @@ +use raindrops::*; + #[test] fn test_the_sound_for_1_is_1() { let input = 1; - let output = raindrops::raindrops(input); + let output = raindrops(input); let expected = "1"; assert_eq!(output, expected); } @@ -10,7 +12,7 @@ fn test_the_sound_for_1_is_1() { #[ignore] fn test_the_sound_for_3_is_pling() { let input = 3; - let output = raindrops::raindrops(input); + let output = raindrops(input); let expected = "Pling"; assert_eq!(output, expected); } @@ -19,7 +21,7 @@ fn test_the_sound_for_3_is_pling() { #[ignore] fn test_the_sound_for_5_is_plang() { let input = 5; - let output = raindrops::raindrops(input); + let output = raindrops(input); let expected = "Plang"; assert_eq!(output, expected); } @@ -28,7 +30,7 @@ fn test_the_sound_for_5_is_plang() { #[ignore] fn test_the_sound_for_7_is_plong() { let input = 7; - let output = raindrops::raindrops(input); + let output = raindrops(input); let expected = "Plong"; assert_eq!(output, expected); } @@ -37,7 +39,7 @@ fn test_the_sound_for_7_is_plong() { #[ignore] fn test_the_sound_for_6_is_pling_as_it_has_a_factor_3() { let input = 6; - let output = raindrops::raindrops(input); + let output = raindrops(input); let expected = "Pling"; assert_eq!(output, expected); } @@ -46,7 +48,7 @@ fn test_the_sound_for_6_is_pling_as_it_has_a_factor_3() { #[ignore] fn test_2_to_the_power_3_does_not_make_a_raindrop_sound_as_3_is_the_exponent_not_the_base() { let input = 8; - let output = raindrops::raindrops(input); + let output = raindrops(input); let expected = "8"; assert_eq!(output, expected); } @@ -55,7 +57,7 @@ fn test_2_to_the_power_3_does_not_make_a_raindrop_sound_as_3_is_the_exponent_not #[ignore] fn test_the_sound_for_9_is_pling_as_it_has_a_factor_3() { let input = 9; - let output = raindrops::raindrops(input); + let output = raindrops(input); let expected = "Pling"; assert_eq!(output, expected); } @@ -64,7 +66,7 @@ fn test_the_sound_for_9_is_pling_as_it_has_a_factor_3() { #[ignore] fn test_the_sound_for_10_is_plang_as_it_has_a_factor_5() { let input = 10; - let output = raindrops::raindrops(input); + let output = raindrops(input); let expected = "Plang"; assert_eq!(output, expected); } @@ -73,7 +75,7 @@ fn test_the_sound_for_10_is_plang_as_it_has_a_factor_5() { #[ignore] fn test_the_sound_for_14_is_plong_as_it_has_a_factor_of_7() { let input = 14; - let output = raindrops::raindrops(input); + let output = raindrops(input); let expected = "Plong"; assert_eq!(output, expected); } @@ -82,7 +84,7 @@ fn test_the_sound_for_14_is_plong_as_it_has_a_factor_of_7() { #[ignore] fn test_the_sound_for_15_is_plingplang_as_it_has_factors_3_and_5() { let input = 15; - let output = raindrops::raindrops(input); + let output = raindrops(input); let expected = "PlingPlang"; assert_eq!(output, expected); } @@ -91,7 +93,7 @@ fn test_the_sound_for_15_is_plingplang_as_it_has_factors_3_and_5() { #[ignore] fn test_the_sound_for_21_is_plingplong_as_it_has_factors_3_and_7() { let input = 21; - let output = raindrops::raindrops(input); + let output = raindrops(input); let expected = "PlingPlong"; assert_eq!(output, expected); } @@ -100,7 +102,7 @@ fn test_the_sound_for_21_is_plingplong_as_it_has_factors_3_and_7() { #[ignore] fn test_the_sound_for_25_is_plang_as_it_has_a_factor_5() { let input = 25; - let output = raindrops::raindrops(input); + let output = raindrops(input); let expected = "Plang"; assert_eq!(output, expected); } @@ -109,7 +111,7 @@ fn test_the_sound_for_25_is_plang_as_it_has_a_factor_5() { #[ignore] fn test_the_sound_for_27_is_pling_as_it_has_a_factor_3() { let input = 27; - let output = raindrops::raindrops(input); + let output = raindrops(input); let expected = "Pling"; assert_eq!(output, expected); } @@ -118,7 +120,7 @@ fn test_the_sound_for_27_is_pling_as_it_has_a_factor_3() { #[ignore] fn test_the_sound_for_35_is_plangplong_as_it_has_factors_5_and_7() { let input = 35; - let output = raindrops::raindrops(input); + let output = raindrops(input); let expected = "PlangPlong"; assert_eq!(output, expected); } @@ -127,7 +129,7 @@ fn test_the_sound_for_35_is_plangplong_as_it_has_factors_5_and_7() { #[ignore] fn test_the_sound_for_49_is_plong_as_it_has_a_factor_7() { let input = 49; - let output = raindrops::raindrops(input); + let output = raindrops(input); let expected = "Plong"; assert_eq!(output, expected); } @@ -136,7 +138,7 @@ fn test_the_sound_for_49_is_plong_as_it_has_a_factor_7() { #[ignore] fn test_the_sound_for_52_is_52() { let input = 52; - let output = raindrops::raindrops(input); + let output = raindrops(input); let expected = "52"; assert_eq!(output, expected); } @@ -145,7 +147,7 @@ fn test_the_sound_for_52_is_52() { #[ignore] fn test_the_sound_for_105_is_plingplangplong_as_it_has_factors_3_5_and_7() { let input = 105; - let output = raindrops::raindrops(input); + let output = raindrops(input); let expected = "PlingPlangPlong"; assert_eq!(output, expected); } @@ -154,7 +156,7 @@ fn test_the_sound_for_105_is_plingplangplong_as_it_has_factors_3_5_and_7() { #[ignore] fn test_the_sound_for_3125_is_plang_as_it_has_a_factor_5() { let input = 3125; - let output = raindrops::raindrops(input); + let output = raindrops(input); let expected = "Plang"; assert_eq!(output, expected); } diff --git a/exercises/practice/rectangles/.meta/test_template.tera b/exercises/practice/rectangles/.meta/test_template.tera index 5c7c99c3b..ac83e0e8b 100644 --- a/exercises/practice/rectangles/.meta/test_template.tera +++ b/exercises/practice/rectangles/.meta/test_template.tera @@ -1,3 +1,5 @@ +use rectangles::*; + {% for test in cases %} #[test] #[ignore] @@ -10,7 +12,7 @@ fn test_{{ test.description | slugify | replace(from="-", to="_") }}() { {{ row | json_encode }}, {%- endfor %} ]; - let output = {{ crate_name }}::{{ fn_names[0] }}(input); + let output = count(input); let expected = {{ test.expected | json_encode() }}; assert_eq!(output, expected); } diff --git a/exercises/practice/rectangles/tests/rectangles.rs b/exercises/practice/rectangles/tests/rectangles.rs index 6c8e467c9..59950a834 100644 --- a/exercises/practice/rectangles/tests/rectangles.rs +++ b/exercises/practice/rectangles/tests/rectangles.rs @@ -1,7 +1,9 @@ +use rectangles::*; + #[test] fn test_no_rows() { let input = &[]; - let output = rectangles::count(input); + let output = count(input); let expected = 0; assert_eq!(output, expected); } @@ -10,7 +12,7 @@ fn test_no_rows() { #[ignore] fn test_no_columns() { let input = &[""]; - let output = rectangles::count(input); + let output = count(input); let expected = 0; assert_eq!(output, expected); } @@ -19,7 +21,7 @@ fn test_no_columns() { #[ignore] fn test_no_rectangles() { let input = &[" "]; - let output = rectangles::count(input); + let output = count(input); let expected = 0; assert_eq!(output, expected); } @@ -33,7 +35,7 @@ fn test_one_rectangle() { "| |", "+-+", ]; - let output = rectangles::count(input); + let output = count(input); let expected = 1; assert_eq!(output, expected); } @@ -49,7 +51,7 @@ fn test_two_rectangles_without_shared_parts() { "| | ", "+-+ ", ]; - let output = rectangles::count(input); + let output = count(input); let expected = 2; assert_eq!(output, expected); } @@ -65,7 +67,7 @@ fn test_five_rectangles_with_shared_parts() { "| | |", "+-+-+", ]; - let output = rectangles::count(input); + let output = count(input); let expected = 5; assert_eq!(output, expected); } @@ -78,7 +80,7 @@ fn test_rectangle_of_height_1_is_counted() { "+--+", "+--+", ]; - let output = rectangles::count(input); + let output = count(input); let expected = 1; assert_eq!(output, expected); } @@ -92,7 +94,7 @@ fn test_rectangle_of_width_1_is_counted() { "||", "++", ]; - let output = rectangles::count(input); + let output = count(input); let expected = 1; assert_eq!(output, expected); } @@ -105,7 +107,7 @@ fn test_1x1_square_is_counted() { "++", "++", ]; - let output = rectangles::count(input); + let output = count(input); let expected = 1; assert_eq!(output, expected); } @@ -121,7 +123,7 @@ fn test_only_complete_rectangles_are_counted() { "| | -", "+-+-+", ]; - let output = rectangles::count(input); + let output = count(input); let expected = 1; assert_eq!(output, expected); } @@ -137,7 +139,7 @@ fn test_rectangles_can_be_of_different_sizes() { "| | |", "+---+-------+", ]; - let output = rectangles::count(input); + let output = count(input); let expected = 3; assert_eq!(output, expected); } @@ -153,7 +155,7 @@ fn test_corner_is_required_for_a_rectangle_to_be_complete() { "| | |", "+---+-------+", ]; - let output = rectangles::count(input); + let output = count(input); let expected = 2; assert_eq!(output, expected); } @@ -172,7 +174,7 @@ fn test_large_input_with_many_rectangles() { "+------+ | |", " +-+", ]; - let output = rectangles::count(input); + let output = count(input); let expected = 60; assert_eq!(output, expected); } @@ -190,7 +192,7 @@ fn test_rectangles_must_have_four_sides() { "| | | |", "+-+ +-+", ]; - let output = rectangles::count(input); + let output = count(input); let expected = 5; assert_eq!(output, expected); } diff --git a/exercises/practice/reverse-string/.meta/test_template.tera b/exercises/practice/reverse-string/.meta/test_template.tera index 3ad0cbafe..ec22fbbbf 100644 --- a/exercises/practice/reverse-string/.meta/test_template.tera +++ b/exercises/practice/reverse-string/.meta/test_template.tera @@ -1,3 +1,5 @@ +use reverse_string::*; + {% for test in cases %} #[test] #[ignore] @@ -6,7 +8,7 @@ {% endif -%} fn {{ test.description | slugify | replace(from="-", to="_") }}() { let input = {{ test.input.value | json_encode() }}; - let output = {{ crate_name }}::{{ fn_names[0] }}(input); + let output = reverse(input); let expected = {{ test.expected | json_encode() }}; assert_eq!(output, expected); } diff --git a/exercises/practice/reverse-string/tests/reverse-string.rs b/exercises/practice/reverse-string/tests/reverse-string.rs index f41af3a55..975204573 100644 --- a/exercises/practice/reverse-string/tests/reverse-string.rs +++ b/exercises/practice/reverse-string/tests/reverse-string.rs @@ -1,7 +1,9 @@ +use reverse_string::*; + #[test] fn an_empty_string() { let input = ""; - let output = reverse_string::reverse(input); + let output = reverse(input); let expected = ""; assert_eq!(output, expected); } @@ -10,7 +12,7 @@ fn an_empty_string() { #[ignore] fn a_word() { let input = "robot"; - let output = reverse_string::reverse(input); + let output = reverse(input); let expected = "tobor"; assert_eq!(output, expected); } @@ -19,7 +21,7 @@ fn a_word() { #[ignore] fn a_capitalized_word() { let input = "Ramen"; - let output = reverse_string::reverse(input); + let output = reverse(input); let expected = "nemaR"; assert_eq!(output, expected); } @@ -28,7 +30,7 @@ fn a_capitalized_word() { #[ignore] fn a_sentence_with_punctuation() { let input = "I'm hungry!"; - let output = reverse_string::reverse(input); + let output = reverse(input); let expected = "!yrgnuh m'I"; assert_eq!(output, expected); } @@ -37,7 +39,7 @@ fn a_sentence_with_punctuation() { #[ignore] fn a_palindrome() { let input = "racecar"; - let output = reverse_string::reverse(input); + let output = reverse(input); let expected = "racecar"; assert_eq!(output, expected); } @@ -46,7 +48,7 @@ fn a_palindrome() { #[ignore] fn an_even_sized_word() { let input = "drawer"; - let output = reverse_string::reverse(input); + let output = reverse(input); let expected = "reward"; assert_eq!(output, expected); } @@ -55,7 +57,7 @@ fn an_even_sized_word() { #[ignore] fn wide_characters() { let input = "子猫"; - let output = reverse_string::reverse(input); + let output = reverse(input); let expected = "猫子"; assert_eq!(output, expected); } @@ -65,7 +67,7 @@ fn wide_characters() { #[cfg(feature = "grapheme")] fn grapheme_cluster_with_pre_combined_form() { let input = "Würstchenstand"; - let output = reverse_string::reverse(input); + let output = reverse(input); let expected = "dnatsnehctsrüW"; assert_eq!(output, expected); } @@ -75,7 +77,7 @@ fn grapheme_cluster_with_pre_combined_form() { #[cfg(feature = "grapheme")] fn grapheme_clusters() { let input = "ผู้เขียนโปรแกรม"; - let output = reverse_string::reverse(input); + let output = reverse(input); let expected = "มรกแรปโนยขีเผู้"; assert_eq!(output, expected); } diff --git a/exercises/practice/robot-simulator/.meta/test_template.tera b/exercises/practice/robot-simulator/.meta/test_template.tera index e441b3958..fa07b208d 100644 --- a/exercises/practice/robot-simulator/.meta/test_template.tera +++ b/exercises/practice/robot-simulator/.meta/test_template.tera @@ -1,4 +1,4 @@ -use {{ crate_name }}::*; +use robot_simulator::*; {% for test in cases %} #[test] diff --git a/exercises/practice/rotational-cipher/.meta/test_template.tera b/exercises/practice/rotational-cipher/.meta/test_template.tera index 1f10fb77f..eb5a34c3d 100644 --- a/exercises/practice/rotational-cipher/.meta/test_template.tera +++ b/exercises/practice/rotational-cipher/.meta/test_template.tera @@ -5,7 +5,7 @@ use rotational_cipher as cipher; fn {{ test.description | slugify | replace(from="-", to="_") }}() { let text = {{ test.input.text | json_encode() }}; let shift_key = {{ test.input.shiftKey | json_encode() }}; - let output = cipher::{{ fn_names[0] }}(text, shift_key); + let output = cipher::rotate(text, shift_key); let expected = {{ test.expected | json_encode() }}; assert_eq!(output, expected); } diff --git a/exercises/practice/say/.meta/test_template.tera b/exercises/practice/say/.meta/test_template.tera index 6118ad8ad..657876993 100644 --- a/exercises/practice/say/.meta/test_template.tera +++ b/exercises/practice/say/.meta/test_template.tera @@ -1,9 +1,11 @@ +use say::*; + {% for test in cases %} #[test] #[ignore] fn {{ test.description | slugify | replace(from="-", to="_") }}() { let input = {{ test.input.number | json_encode() }}; - let output = {{ crate_name }}::{{ fn_names[0] }}(input); + let output = encode(input); let expected = {{ test.expected | json_encode() }}; assert_eq!(output, expected); } diff --git a/exercises/practice/say/tests/say.rs b/exercises/practice/say/tests/say.rs index e7b002ca1..78cb7a35c 100644 --- a/exercises/practice/say/tests/say.rs +++ b/exercises/practice/say/tests/say.rs @@ -1,7 +1,9 @@ +use say::*; + #[test] fn zero() { let input = 0; - let output = say::encode(input); + let output = encode(input); let expected = "zero"; assert_eq!(output, expected); } @@ -10,7 +12,7 @@ fn zero() { #[ignore] fn one() { let input = 1; - let output = say::encode(input); + let output = encode(input); let expected = "one"; assert_eq!(output, expected); } @@ -19,7 +21,7 @@ fn one() { #[ignore] fn fourteen() { let input = 14; - let output = say::encode(input); + let output = encode(input); let expected = "fourteen"; assert_eq!(output, expected); } @@ -28,7 +30,7 @@ fn fourteen() { #[ignore] fn twenty() { let input = 20; - let output = say::encode(input); + let output = encode(input); let expected = "twenty"; assert_eq!(output, expected); } @@ -37,7 +39,7 @@ fn twenty() { #[ignore] fn twenty_two() { let input = 22; - let output = say::encode(input); + let output = encode(input); let expected = "twenty-two"; assert_eq!(output, expected); } @@ -46,7 +48,7 @@ fn twenty_two() { #[ignore] fn thirty() { let input = 30; - let output = say::encode(input); + let output = encode(input); let expected = "thirty"; assert_eq!(output, expected); } @@ -55,7 +57,7 @@ fn thirty() { #[ignore] fn ninety_nine() { let input = 99; - let output = say::encode(input); + let output = encode(input); let expected = "ninety-nine"; assert_eq!(output, expected); } @@ -64,7 +66,7 @@ fn ninety_nine() { #[ignore] fn one_hundred() { let input = 100; - let output = say::encode(input); + let output = encode(input); let expected = "one hundred"; assert_eq!(output, expected); } @@ -73,7 +75,7 @@ fn one_hundred() { #[ignore] fn one_hundred_twenty_three() { let input = 123; - let output = say::encode(input); + let output = encode(input); let expected = "one hundred twenty-three"; assert_eq!(output, expected); } @@ -82,7 +84,7 @@ fn one_hundred_twenty_three() { #[ignore] fn two_hundred() { let input = 200; - let output = say::encode(input); + let output = encode(input); let expected = "two hundred"; assert_eq!(output, expected); } @@ -91,7 +93,7 @@ fn two_hundred() { #[ignore] fn nine_hundred_ninety_nine() { let input = 999; - let output = say::encode(input); + let output = encode(input); let expected = "nine hundred ninety-nine"; assert_eq!(output, expected); } @@ -100,7 +102,7 @@ fn nine_hundred_ninety_nine() { #[ignore] fn one_thousand() { let input = 1000; - let output = say::encode(input); + let output = encode(input); let expected = "one thousand"; assert_eq!(output, expected); } @@ -109,7 +111,7 @@ fn one_thousand() { #[ignore] fn one_thousand_two_hundred_thirty_four() { let input = 1234; - let output = say::encode(input); + let output = encode(input); let expected = "one thousand two hundred thirty-four"; assert_eq!(output, expected); } @@ -118,7 +120,7 @@ fn one_thousand_two_hundred_thirty_four() { #[ignore] fn one_million() { let input = 1000000; - let output = say::encode(input); + let output = encode(input); let expected = "one million"; assert_eq!(output, expected); } @@ -127,7 +129,7 @@ fn one_million() { #[ignore] fn one_million_two_thousand_three_hundred_forty_five() { let input = 1002345; - let output = say::encode(input); + let output = encode(input); let expected = "one million two thousand three hundred forty-five"; assert_eq!(output, expected); } @@ -136,7 +138,7 @@ fn one_million_two_thousand_three_hundred_forty_five() { #[ignore] fn one_billion() { let input = 1000000000; - let output = say::encode(input); + let output = encode(input); let expected = "one billion"; assert_eq!(output, expected); } @@ -145,7 +147,7 @@ fn one_billion() { #[ignore] fn a_big_number() { let input = 987654321123; - let output = say::encode(input); + let output = encode(input); let expected = "nine hundred eighty-seven billion six hundred fifty-four million three hundred twenty-one thousand one hundred twenty-three"; assert_eq!(output, expected); } @@ -154,7 +156,7 @@ fn a_big_number() { #[ignore] fn max_i64() { let input = 9223372036854775807; - let output = say::encode(input); + let output = encode(input); let expected = "nine quintillion two hundred twenty-three quadrillion three hundred seventy-two trillion thirty-six billion eight hundred fifty-four million seven hundred seventy-five thousand eight hundred seven"; assert_eq!(output, expected); } @@ -163,7 +165,7 @@ fn max_i64() { #[ignore] fn max_u64() { let input = 18446744073709551615; - let output = say::encode(input); + let output = encode(input); let expected = "eighteen quintillion four hundred forty-six quadrillion seven hundred forty-four trillion seventy-three billion seven hundred nine million five hundred fifty-one thousand six hundred fifteen"; assert_eq!(output, expected); } diff --git a/exercises/practice/scrabble-score/.meta/test_template.tera b/exercises/practice/scrabble-score/.meta/test_template.tera index e79e7248e..3f62b1f1b 100644 --- a/exercises/practice/scrabble-score/.meta/test_template.tera +++ b/exercises/practice/scrabble-score/.meta/test_template.tera @@ -1,9 +1,11 @@ +use scrabble_score::*; + {% for test in cases %} #[test] #[ignore] fn {{ test.description | slugify | replace(from="-", to="_") }}() { let input = {{ test.input.word | json_encode() }}; - let output = {{ crate_name }}::{{ fn_names[0] }}(input); + let output = score(input); let expected = {{ test.expected | json_encode() }}; assert_eq!(output, expected); } diff --git a/exercises/practice/scrabble-score/tests/scrabble-score.rs b/exercises/practice/scrabble-score/tests/scrabble-score.rs index b65f94e06..a864b899a 100644 --- a/exercises/practice/scrabble-score/tests/scrabble-score.rs +++ b/exercises/practice/scrabble-score/tests/scrabble-score.rs @@ -1,7 +1,9 @@ +use scrabble_score::*; + #[test] fn lowercase_letter() { let input = "a"; - let output = scrabble_score::score(input); + let output = score(input); let expected = 1; assert_eq!(output, expected); } @@ -10,7 +12,7 @@ fn lowercase_letter() { #[ignore] fn uppercase_letter() { let input = "A"; - let output = scrabble_score::score(input); + let output = score(input); let expected = 1; assert_eq!(output, expected); } @@ -19,7 +21,7 @@ fn uppercase_letter() { #[ignore] fn valuable_letter() { let input = "f"; - let output = scrabble_score::score(input); + let output = score(input); let expected = 4; assert_eq!(output, expected); } @@ -28,7 +30,7 @@ fn valuable_letter() { #[ignore] fn short_word() { let input = "at"; - let output = scrabble_score::score(input); + let output = score(input); let expected = 2; assert_eq!(output, expected); } @@ -37,7 +39,7 @@ fn short_word() { #[ignore] fn short_valuable_word() { let input = "zoo"; - let output = scrabble_score::score(input); + let output = score(input); let expected = 12; assert_eq!(output, expected); } @@ -46,7 +48,7 @@ fn short_valuable_word() { #[ignore] fn medium_word() { let input = "street"; - let output = scrabble_score::score(input); + let output = score(input); let expected = 6; assert_eq!(output, expected); } @@ -55,7 +57,7 @@ fn medium_word() { #[ignore] fn medium_valuable_word() { let input = "quirky"; - let output = scrabble_score::score(input); + let output = score(input); let expected = 22; assert_eq!(output, expected); } @@ -64,7 +66,7 @@ fn medium_valuable_word() { #[ignore] fn long_mixed_case_word() { let input = "OxyphenButazone"; - let output = scrabble_score::score(input); + let output = score(input); let expected = 41; assert_eq!(output, expected); } @@ -73,7 +75,7 @@ fn long_mixed_case_word() { #[ignore] fn english_like_word() { let input = "pinata"; - let output = scrabble_score::score(input); + let output = score(input); let expected = 8; assert_eq!(output, expected); } @@ -82,7 +84,7 @@ fn english_like_word() { #[ignore] fn empty_input() { let input = ""; - let output = scrabble_score::score(input); + let output = score(input); let expected = 0; assert_eq!(output, expected); } @@ -91,7 +93,7 @@ fn empty_input() { #[ignore] fn entire_alphabet_available() { let input = "abcdefghijklmnopqrstuvwxyz"; - let output = scrabble_score::score(input); + let output = score(input); let expected = 87; assert_eq!(output, expected); } @@ -100,7 +102,7 @@ fn entire_alphabet_available() { #[ignore] fn non_english_scrabble_letters_do_not_score() { let input = "piñata"; - let output = scrabble_score::score(input); + let output = score(input); let expected = 7; assert_eq!(output, expected); } @@ -109,7 +111,7 @@ fn non_english_scrabble_letters_do_not_score() { #[ignore] fn german_letters_do_not_score() { let input = "STRAßE"; - let output = scrabble_score::score(input); + let output = score(input); let expected = 5; assert_eq!(output, expected); } diff --git a/exercises/practice/series/.meta/test_template.tera b/exercises/practice/series/.meta/test_template.tera index eb3d1a34b..986a101ac 100644 --- a/exercises/practice/series/.meta/test_template.tera +++ b/exercises/practice/series/.meta/test_template.tera @@ -1,4 +1,4 @@ -use {{ crate_name }}::*; +use series::*; {% for test in cases %} #[test] @@ -6,7 +6,7 @@ use {{ crate_name }}::*; fn {{ test.description | slugify | replace(from="-", to="_") }}() { let input = {{ test.input.series | json_encode() }}; let length = {{ test.input.sliceLength | json_encode() }}; - let output = {{ fn_names[0] }}(input, length); + let output = series(input, length); {%- if test.expected is object -%} {# The author of the exercise chose to define the semantics such that diff --git a/exercises/practice/sieve/.meta/test_template.tera b/exercises/practice/sieve/.meta/test_template.tera index ccee077df..8af3c0a95 100644 --- a/exercises/practice/sieve/.meta/test_template.tera +++ b/exercises/practice/sieve/.meta/test_template.tera @@ -1,9 +1,11 @@ +use sieve::*; + {% for test in cases %} #[test] #[ignore] fn {{ test.description | slugify | replace(from="-", to="_") }}() { let input = {{ test.input.limit | json_encode() }}; - let output = {{ crate_name }}::{{ fn_names[0] }}(input); + let output = primes_up_to(input); let expected = {{ test.expected | json_encode() }}; assert_eq!(output, expected); } diff --git a/exercises/practice/sieve/tests/sieve.rs b/exercises/practice/sieve/tests/sieve.rs index 89e381d9b..312458825 100644 --- a/exercises/practice/sieve/tests/sieve.rs +++ b/exercises/practice/sieve/tests/sieve.rs @@ -1,7 +1,9 @@ +use sieve::*; + #[test] fn no_primes_under_two() { let input = 1; - let output = sieve::primes_up_to(input); + let output = primes_up_to(input); let expected = []; assert_eq!(output, expected); } @@ -10,7 +12,7 @@ fn no_primes_under_two() { #[ignore] fn find_first_prime() { let input = 2; - let output = sieve::primes_up_to(input); + let output = primes_up_to(input); let expected = [2]; assert_eq!(output, expected); } @@ -19,7 +21,7 @@ fn find_first_prime() { #[ignore] fn find_primes_up_to_10() { let input = 10; - let output = sieve::primes_up_to(input); + let output = primes_up_to(input); let expected = [2, 3, 5, 7]; assert_eq!(output, expected); } @@ -28,7 +30,7 @@ fn find_primes_up_to_10() { #[ignore] fn limit_is_prime() { let input = 13; - let output = sieve::primes_up_to(input); + let output = primes_up_to(input); let expected = [2, 3, 5, 7, 11, 13]; assert_eq!(output, expected); } @@ -37,7 +39,7 @@ fn limit_is_prime() { #[ignore] fn find_primes_up_to_1000() { let input = 1000; - let output = sieve::primes_up_to(input); + let output = primes_up_to(input); let expected = [ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, diff --git a/exercises/practice/spiral-matrix/.meta/test_template.tera b/exercises/practice/spiral-matrix/.meta/test_template.tera index c4aa606fe..0530a5ff6 100644 --- a/exercises/practice/spiral-matrix/.meta/test_template.tera +++ b/exercises/practice/spiral-matrix/.meta/test_template.tera @@ -1,9 +1,11 @@ +use spiral_matrix::*; + {% for test in cases %} #[test] #[ignore] fn {{ test.description | slugify | replace(from="-", to="_") }}() { let input = {{ test.input.size | json_encode() }}; - let output = {{ crate_name }}::{{ fn_names[0] }}(input); + let output = spiral_matrix(input); let expected: [[u32; {{ test.input.size }}]; {{ test.input.size }}] = [ {% for i in test.expected %} {{ i | json_encode }}, diff --git a/exercises/practice/spiral-matrix/tests/spiral-matrix.rs b/exercises/practice/spiral-matrix/tests/spiral-matrix.rs index 42c127edb..ef3493958 100644 --- a/exercises/practice/spiral-matrix/tests/spiral-matrix.rs +++ b/exercises/practice/spiral-matrix/tests/spiral-matrix.rs @@ -1,7 +1,9 @@ +use spiral_matrix::*; + #[test] fn empty_spiral() { let input = 0; - let output = spiral_matrix::spiral_matrix(input); + let output = spiral_matrix(input); let expected: [[u32; 0]; 0] = []; assert_eq!(output, expected); } @@ -10,7 +12,7 @@ fn empty_spiral() { #[ignore] fn trivial_spiral() { let input = 1; - let output = spiral_matrix::spiral_matrix(input); + let output = spiral_matrix(input); let expected: [[u32; 1]; 1] = [[1]]; assert_eq!(output, expected); } @@ -19,7 +21,7 @@ fn trivial_spiral() { #[ignore] fn spiral_of_size_2() { let input = 2; - let output = spiral_matrix::spiral_matrix(input); + let output = spiral_matrix(input); let expected: [[u32; 2]; 2] = [[1, 2], [4, 3]]; assert_eq!(output, expected); } @@ -28,7 +30,7 @@ fn spiral_of_size_2() { #[ignore] fn spiral_of_size_3() { let input = 3; - let output = spiral_matrix::spiral_matrix(input); + let output = spiral_matrix(input); let expected: [[u32; 3]; 3] = [[1, 2, 3], [8, 9, 4], [7, 6, 5]]; assert_eq!(output, expected); } @@ -37,7 +39,7 @@ fn spiral_of_size_3() { #[ignore] fn spiral_of_size_4() { let input = 4; - let output = spiral_matrix::spiral_matrix(input); + let output = spiral_matrix(input); let expected: [[u32; 4]; 4] = [ [1, 2, 3, 4], [12, 13, 14, 5], @@ -51,7 +53,7 @@ fn spiral_of_size_4() { #[ignore] fn spiral_of_size_5() { let input = 5; - let output = spiral_matrix::spiral_matrix(input); + let output = spiral_matrix(input); let expected: [[u32; 5]; 5] = [ [1, 2, 3, 4, 5], [16, 17, 18, 19, 6], diff --git a/exercises/practice/sublist/.meta/test_template.tera b/exercises/practice/sublist/.meta/test_template.tera index f5897a584..1b37261bc 100644 --- a/exercises/practice/sublist/.meta/test_template.tera +++ b/exercises/practice/sublist/.meta/test_template.tera @@ -1,11 +1,12 @@ -use sublist::Comparison; +use sublist::*; + {% for test in cases %} #[test] #[ignore] fn {{ test.description | slugify | replace(from="-", to="_") }}() { let list_one: &[i32] = &{{ test.input.listOne | json_encode() }}; let list_two: &[i32] = &{{ test.input.listTwo | json_encode() }}; - let output = {{ crate_name }}::{{ fn_names[0] }}(list_one, list_two); + let output = sublist(list_one, list_two); let expected = {% if test.expected == "equal" -%} Comparison::Equal {%- elif test.expected == "sublist" -%} diff --git a/exercises/practice/sublist/tests/sublist.rs b/exercises/practice/sublist/tests/sublist.rs index d64df6953..7b9327f41 100644 --- a/exercises/practice/sublist/tests/sublist.rs +++ b/exercises/practice/sublist/tests/sublist.rs @@ -1,10 +1,10 @@ -use sublist::Comparison; +use sublist::*; #[test] fn empty_lists() { let list_one: &[i32] = &[]; let list_two: &[i32] = &[]; - let output = sublist::sublist(list_one, list_two); + let output = sublist(list_one, list_two); let expected = Comparison::Equal; assert_eq!(output, expected); } @@ -14,7 +14,7 @@ fn empty_lists() { fn empty_list_within_non_empty_list() { let list_one: &[i32] = &[]; let list_two: &[i32] = &[1, 2, 3]; - let output = sublist::sublist(list_one, list_two); + let output = sublist(list_one, list_two); let expected = Comparison::Sublist; assert_eq!(output, expected); } @@ -24,7 +24,7 @@ fn empty_list_within_non_empty_list() { fn non_empty_list_contains_empty_list() { let list_one: &[i32] = &[1, 2, 3]; let list_two: &[i32] = &[]; - let output = sublist::sublist(list_one, list_two); + let output = sublist(list_one, list_two); let expected = Comparison::Superlist; assert_eq!(output, expected); } @@ -34,7 +34,7 @@ fn non_empty_list_contains_empty_list() { fn list_equals_itself() { let list_one: &[i32] = &[1, 2, 3]; let list_two: &[i32] = &[1, 2, 3]; - let output = sublist::sublist(list_one, list_two); + let output = sublist(list_one, list_two); let expected = Comparison::Equal; assert_eq!(output, expected); } @@ -44,7 +44,7 @@ fn list_equals_itself() { fn different_lists() { let list_one: &[i32] = &[1, 2, 3]; let list_two: &[i32] = &[2, 3, 4]; - let output = sublist::sublist(list_one, list_two); + let output = sublist(list_one, list_two); let expected = Comparison::Unequal; assert_eq!(output, expected); } @@ -54,7 +54,7 @@ fn different_lists() { fn false_start() { let list_one: &[i32] = &[1, 2, 5]; let list_two: &[i32] = &[0, 1, 2, 3, 1, 2, 5, 6]; - let output = sublist::sublist(list_one, list_two); + let output = sublist(list_one, list_two); let expected = Comparison::Sublist; assert_eq!(output, expected); } @@ -64,7 +64,7 @@ fn false_start() { fn consecutive() { let list_one: &[i32] = &[1, 1, 2]; let list_two: &[i32] = &[0, 1, 1, 1, 2, 1, 2]; - let output = sublist::sublist(list_one, list_two); + let output = sublist(list_one, list_two); let expected = Comparison::Sublist; assert_eq!(output, expected); } @@ -74,7 +74,7 @@ fn consecutive() { fn sublist_at_start() { let list_one: &[i32] = &[0, 1, 2]; let list_two: &[i32] = &[0, 1, 2, 3, 4, 5]; - let output = sublist::sublist(list_one, list_two); + let output = sublist(list_one, list_two); let expected = Comparison::Sublist; assert_eq!(output, expected); } @@ -84,7 +84,7 @@ fn sublist_at_start() { fn sublist_in_middle() { let list_one: &[i32] = &[2, 3, 4]; let list_two: &[i32] = &[0, 1, 2, 3, 4, 5]; - let output = sublist::sublist(list_one, list_two); + let output = sublist(list_one, list_two); let expected = Comparison::Sublist; assert_eq!(output, expected); } @@ -94,7 +94,7 @@ fn sublist_in_middle() { fn sublist_at_end() { let list_one: &[i32] = &[3, 4, 5]; let list_two: &[i32] = &[0, 1, 2, 3, 4, 5]; - let output = sublist::sublist(list_one, list_two); + let output = sublist(list_one, list_two); let expected = Comparison::Sublist; assert_eq!(output, expected); } @@ -104,7 +104,7 @@ fn sublist_at_end() { fn at_start_of_superlist() { let list_one: &[i32] = &[0, 1, 2, 3, 4, 5]; let list_two: &[i32] = &[0, 1, 2]; - let output = sublist::sublist(list_one, list_two); + let output = sublist(list_one, list_two); let expected = Comparison::Superlist; assert_eq!(output, expected); } @@ -114,7 +114,7 @@ fn at_start_of_superlist() { fn in_middle_of_superlist() { let list_one: &[i32] = &[0, 1, 2, 3, 4, 5]; let list_two: &[i32] = &[2, 3]; - let output = sublist::sublist(list_one, list_two); + let output = sublist(list_one, list_two); let expected = Comparison::Superlist; assert_eq!(output, expected); } @@ -124,7 +124,7 @@ fn in_middle_of_superlist() { fn at_end_of_superlist() { let list_one: &[i32] = &[0, 1, 2, 3, 4, 5]; let list_two: &[i32] = &[3, 4, 5]; - let output = sublist::sublist(list_one, list_two); + let output = sublist(list_one, list_two); let expected = Comparison::Superlist; assert_eq!(output, expected); } @@ -134,7 +134,7 @@ fn at_end_of_superlist() { fn first_list_missing_element_from_second_list() { let list_one: &[i32] = &[1, 3]; let list_two: &[i32] = &[1, 2, 3]; - let output = sublist::sublist(list_one, list_two); + let output = sublist(list_one, list_two); let expected = Comparison::Unequal; assert_eq!(output, expected); } @@ -144,7 +144,7 @@ fn first_list_missing_element_from_second_list() { fn second_list_missing_element_from_first_list() { let list_one: &[i32] = &[1, 2, 3]; let list_two: &[i32] = &[1, 3]; - let output = sublist::sublist(list_one, list_two); + let output = sublist(list_one, list_two); let expected = Comparison::Unequal; assert_eq!(output, expected); } @@ -154,7 +154,7 @@ fn second_list_missing_element_from_first_list() { fn first_list_missing_additional_digits_from_second_list() { let list_one: &[i32] = &[1, 2]; let list_two: &[i32] = &[1, 22]; - let output = sublist::sublist(list_one, list_two); + let output = sublist(list_one, list_two); let expected = Comparison::Unequal; assert_eq!(output, expected); } @@ -164,7 +164,7 @@ fn first_list_missing_additional_digits_from_second_list() { fn order_matters_to_a_list() { let list_one: &[i32] = &[1, 2, 3]; let list_two: &[i32] = &[3, 2, 1]; - let output = sublist::sublist(list_one, list_two); + let output = sublist(list_one, list_two); let expected = Comparison::Unequal; assert_eq!(output, expected); } @@ -174,7 +174,7 @@ fn order_matters_to_a_list() { fn same_digits_but_different_numbers() { let list_one: &[i32] = &[1, 0, 1]; let list_two: &[i32] = &[10, 1]; - let output = sublist::sublist(list_one, list_two); + let output = sublist(list_one, list_two); let expected = Comparison::Unequal; assert_eq!(output, expected); } diff --git a/exercises/practice/sum-of-multiples/.meta/test_template.tera b/exercises/practice/sum-of-multiples/.meta/test_template.tera index df26c4a93..919dae094 100644 --- a/exercises/practice/sum-of-multiples/.meta/test_template.tera +++ b/exercises/practice/sum-of-multiples/.meta/test_template.tera @@ -1,10 +1,12 @@ +use sum_of_multiples::*; + {% for test in cases %} #[test] #[ignore] fn {{ test.description | slugify | replace(from="-", to="_") }}() { let factors = &{{ test.input.factors | json_encode() }}; let limit = {{ test.input.limit | json_encode() }}; - let output = {{ crate_name }}::{{ fn_names[0] }}(limit, factors); + let output = sum_of_multiples(limit, factors); let expected = {{ test.expected | json_encode() }}; assert_eq!(output, expected); } diff --git a/exercises/practice/sum-of-multiples/tests/sum-of-multiples.rs b/exercises/practice/sum-of-multiples/tests/sum-of-multiples.rs index 488f06eb8..40c0dbdbb 100644 --- a/exercises/practice/sum-of-multiples/tests/sum-of-multiples.rs +++ b/exercises/practice/sum-of-multiples/tests/sum-of-multiples.rs @@ -1,8 +1,10 @@ +use sum_of_multiples::*; + #[test] fn no_multiples_within_limit() { let factors = &[3, 5]; let limit = 1; - let output = sum_of_multiples::sum_of_multiples(limit, factors); + let output = sum_of_multiples(limit, factors); let expected = 0; assert_eq!(output, expected); } @@ -12,7 +14,7 @@ fn no_multiples_within_limit() { fn one_factor_has_multiples_within_limit() { let factors = &[3, 5]; let limit = 4; - let output = sum_of_multiples::sum_of_multiples(limit, factors); + let output = sum_of_multiples(limit, factors); let expected = 3; assert_eq!(output, expected); } @@ -22,7 +24,7 @@ fn one_factor_has_multiples_within_limit() { fn more_than_one_multiple_within_limit() { let factors = &[3]; let limit = 7; - let output = sum_of_multiples::sum_of_multiples(limit, factors); + let output = sum_of_multiples(limit, factors); let expected = 9; assert_eq!(output, expected); } @@ -32,7 +34,7 @@ fn more_than_one_multiple_within_limit() { fn more_than_one_factor_with_multiples_within_limit() { let factors = &[3, 5]; let limit = 10; - let output = sum_of_multiples::sum_of_multiples(limit, factors); + let output = sum_of_multiples(limit, factors); let expected = 23; assert_eq!(output, expected); } @@ -42,7 +44,7 @@ fn more_than_one_factor_with_multiples_within_limit() { fn each_multiple_is_only_counted_once() { let factors = &[3, 5]; let limit = 100; - let output = sum_of_multiples::sum_of_multiples(limit, factors); + let output = sum_of_multiples(limit, factors); let expected = 2318; assert_eq!(output, expected); } @@ -52,7 +54,7 @@ fn each_multiple_is_only_counted_once() { fn a_much_larger_limit() { let factors = &[3, 5]; let limit = 1000; - let output = sum_of_multiples::sum_of_multiples(limit, factors); + let output = sum_of_multiples(limit, factors); let expected = 233168; assert_eq!(output, expected); } @@ -62,7 +64,7 @@ fn a_much_larger_limit() { fn three_factors() { let factors = &[7, 13, 17]; let limit = 20; - let output = sum_of_multiples::sum_of_multiples(limit, factors); + let output = sum_of_multiples(limit, factors); let expected = 51; assert_eq!(output, expected); } @@ -72,7 +74,7 @@ fn three_factors() { fn factors_not_relatively_prime() { let factors = &[4, 6]; let limit = 15; - let output = sum_of_multiples::sum_of_multiples(limit, factors); + let output = sum_of_multiples(limit, factors); let expected = 30; assert_eq!(output, expected); } @@ -82,7 +84,7 @@ fn factors_not_relatively_prime() { fn some_pairs_of_factors_relatively_prime_and_some_not() { let factors = &[5, 6, 8]; let limit = 150; - let output = sum_of_multiples::sum_of_multiples(limit, factors); + let output = sum_of_multiples(limit, factors); let expected = 4419; assert_eq!(output, expected); } @@ -92,7 +94,7 @@ fn some_pairs_of_factors_relatively_prime_and_some_not() { fn one_factor_is_a_multiple_of_another() { let factors = &[5, 25]; let limit = 51; - let output = sum_of_multiples::sum_of_multiples(limit, factors); + let output = sum_of_multiples(limit, factors); let expected = 275; assert_eq!(output, expected); } @@ -102,7 +104,7 @@ fn one_factor_is_a_multiple_of_another() { fn much_larger_factors() { let factors = &[43, 47]; let limit = 10000; - let output = sum_of_multiples::sum_of_multiples(limit, factors); + let output = sum_of_multiples(limit, factors); let expected = 2203160; assert_eq!(output, expected); } @@ -112,7 +114,7 @@ fn much_larger_factors() { fn all_numbers_are_multiples_of_1() { let factors = &[1]; let limit = 100; - let output = sum_of_multiples::sum_of_multiples(limit, factors); + let output = sum_of_multiples(limit, factors); let expected = 4950; assert_eq!(output, expected); } @@ -122,7 +124,7 @@ fn all_numbers_are_multiples_of_1() { fn no_factors_means_an_empty_sum() { let factors = &[]; let limit = 10000; - let output = sum_of_multiples::sum_of_multiples(limit, factors); + let output = sum_of_multiples(limit, factors); let expected = 0; assert_eq!(output, expected); } @@ -132,7 +134,7 @@ fn no_factors_means_an_empty_sum() { fn the_only_multiple_of_0_is_0() { let factors = &[0]; let limit = 1; - let output = sum_of_multiples::sum_of_multiples(limit, factors); + let output = sum_of_multiples(limit, factors); let expected = 0; assert_eq!(output, expected); } @@ -142,7 +144,7 @@ fn the_only_multiple_of_0_is_0() { fn the_factor_0_does_not_affect_the_sum_of_multiples_of_other_factors() { let factors = &[3, 0]; let limit = 4; - let output = sum_of_multiples::sum_of_multiples(limit, factors); + let output = sum_of_multiples(limit, factors); let expected = 3; assert_eq!(output, expected); } @@ -152,7 +154,7 @@ fn the_factor_0_does_not_affect_the_sum_of_multiples_of_other_factors() { fn solutions_using_include_exclude_must_extend_to_cardinality_greater_than_3() { let factors = &[2, 3, 5, 7, 11]; let limit = 10000; - let output = sum_of_multiples::sum_of_multiples(limit, factors); + let output = sum_of_multiples(limit, factors); let expected = 39614537; assert_eq!(output, expected); } diff --git a/exercises/practice/tournament/.meta/test_template.tera b/exercises/practice/tournament/.meta/test_template.tera index e27c5d2a8..ef76c2531 100644 --- a/exercises/practice/tournament/.meta/test_template.tera +++ b/exercises/practice/tournament/.meta/test_template.tera @@ -1,10 +1,12 @@ +use tournament::*; + {% for test in cases %} #[test] #[ignore] fn {{ test.description | slugify | replace(from="-", to="_") }}() { let input: &[&str] = &{{ test.input.rows | json_encode() }}; let input = input.join("\n"); - let output = {{ crate_name }}::{{ fn_names[0] }}(&input); + let output = tally(&input); let expected = {{ test.expected | json_encode() }}.join("\n"); assert_eq!(output, expected); } diff --git a/exercises/practice/tournament/tests/tournament.rs b/exercises/practice/tournament/tests/tournament.rs index 300a5cf28..4d3572585 100644 --- a/exercises/practice/tournament/tests/tournament.rs +++ b/exercises/practice/tournament/tests/tournament.rs @@ -1,8 +1,10 @@ +use tournament::*; + #[test] fn just_the_header_if_no_input() { let input: &[&str] = &[]; let input = input.join("\n"); - let output = tournament::tally(&input); + let output = tally(&input); let expected = ["Team | MP | W | D | L | P"].join("\n"); assert_eq!(output, expected); } @@ -12,7 +14,7 @@ fn just_the_header_if_no_input() { fn a_win_is_three_points_a_loss_is_zero_points() { let input: &[&str] = &["Allegoric Alaskans;Blithering Badgers;win"]; let input = input.join("\n"); - let output = tournament::tally(&input); + let output = tally(&input); let expected = [ "Team | MP | W | D | L | P", "Allegoric Alaskans | 1 | 1 | 0 | 0 | 3", @@ -27,7 +29,7 @@ fn a_win_is_three_points_a_loss_is_zero_points() { fn a_win_can_also_be_expressed_as_a_loss() { let input: &[&str] = &["Blithering Badgers;Allegoric Alaskans;loss"]; let input = input.join("\n"); - let output = tournament::tally(&input); + let output = tally(&input); let expected = [ "Team | MP | W | D | L | P", "Allegoric Alaskans | 1 | 1 | 0 | 0 | 3", @@ -42,7 +44,7 @@ fn a_win_can_also_be_expressed_as_a_loss() { fn a_different_team_can_win() { let input: &[&str] = &["Blithering Badgers;Allegoric Alaskans;win"]; let input = input.join("\n"); - let output = tournament::tally(&input); + let output = tally(&input); let expected = [ "Team | MP | W | D | L | P", "Blithering Badgers | 1 | 1 | 0 | 0 | 3", @@ -57,7 +59,7 @@ fn a_different_team_can_win() { fn a_draw_is_one_point_each() { let input: &[&str] = &["Allegoric Alaskans;Blithering Badgers;draw"]; let input = input.join("\n"); - let output = tournament::tally(&input); + let output = tally(&input); let expected = [ "Team | MP | W | D | L | P", "Allegoric Alaskans | 1 | 0 | 1 | 0 | 1", @@ -75,7 +77,7 @@ fn there_can_be_more_than_one_match() { "Allegoric Alaskans;Blithering Badgers;win", ]; let input = input.join("\n"); - let output = tournament::tally(&input); + let output = tally(&input); let expected = [ "Team | MP | W | D | L | P", "Allegoric Alaskans | 2 | 2 | 0 | 0 | 6", @@ -93,7 +95,7 @@ fn there_can_be_more_than_one_winner() { "Allegoric Alaskans;Blithering Badgers;win", ]; let input = input.join("\n"); - let output = tournament::tally(&input); + let output = tally(&input); let expected = [ "Team | MP | W | D | L | P", "Allegoric Alaskans | 2 | 1 | 0 | 1 | 3", @@ -112,7 +114,7 @@ fn there_can_be_more_than_two_teams() { "Courageous Californians;Allegoric Alaskans;loss", ]; let input = input.join("\n"); - let output = tournament::tally(&input); + let output = tally(&input); let expected = [ "Team | MP | W | D | L | P", "Allegoric Alaskans | 2 | 2 | 0 | 0 | 6", @@ -135,7 +137,7 @@ fn typical_input() { "Allegoric Alaskans;Courageous Californians;win", ]; let input = input.join("\n"); - let output = tournament::tally(&input); + let output = tally(&input); let expected = [ "Team | MP | W | D | L | P", "Devastating Donkeys | 3 | 2 | 1 | 0 | 7", @@ -157,7 +159,7 @@ fn incomplete_competition_not_all_pairs_have_played() { "Allegoric Alaskans;Courageous Californians;win", ]; let input = input.join("\n"); - let output = tournament::tally(&input); + let output = tally(&input); let expected = [ "Team | MP | W | D | L | P", "Allegoric Alaskans | 3 | 2 | 0 | 1 | 6", @@ -181,7 +183,7 @@ fn ties_broken_alphabetically() { "Allegoric Alaskans;Courageous Californians;draw", ]; let input = input.join("\n"); - let output = tournament::tally(&input); + let output = tally(&input); let expected = [ "Team | MP | W | D | L | P", "Allegoric Alaskans | 3 | 2 | 1 | 0 | 7", @@ -204,7 +206,7 @@ fn ensure_points_sorted_numerically() { "Blithering Badgers;Devastating Donkeys;win", ]; let input = input.join("\n"); - let output = tournament::tally(&input); + let output = tally(&input); let expected = [ "Team | MP | W | D | L | P", "Devastating Donkeys | 5 | 4 | 0 | 1 | 12", diff --git a/exercises/practice/two-bucket/.meta/test_template.tera b/exercises/practice/two-bucket/.meta/test_template.tera index 28ddaff03..edb4e4cb6 100644 --- a/exercises/practice/two-bucket/.meta/test_template.tera +++ b/exercises/practice/two-bucket/.meta/test_template.tera @@ -1,3 +1,5 @@ +use two_bucket::*; + {% macro bucket(label) -%} {% if label == 'one' -%} Bucket::One @@ -6,12 +8,11 @@ {%- endif %} {%- endmacro bucket -%} -use two_bucket::{Bucket, BucketStats}; {% for test in cases %} #[test] #[ignore] fn {{ test.description | slugify | replace(from="-", to="_") }}() { - let output = {{ crate_name }}::{{ fn_names[0] }}( + let output = solve( {{ test.input.bucketOne }}, {{ test.input.bucketTwo }}, {{ test.input.goal }}, &{{ self::bucket(label=test.input.startBucket) }}, ); diff --git a/exercises/practice/two-bucket/tests/two-bucket.rs b/exercises/practice/two-bucket/tests/two-bucket.rs index a21311a25..bdf75162c 100644 --- a/exercises/practice/two-bucket/tests/two-bucket.rs +++ b/exercises/practice/two-bucket/tests/two-bucket.rs @@ -1,8 +1,8 @@ -use two_bucket::{Bucket, BucketStats}; +use two_bucket::*; #[test] fn measure_using_bucket_one_of_size_3_and_bucket_two_of_size_5_start_with_bucket_one() { - let output = two_bucket::solve(3, 5, 1, &Bucket::One); + let output = solve(3, 5, 1, &Bucket::One); let expected = Some(BucketStats { moves: 4, goal_bucket: Bucket::One, @@ -14,7 +14,7 @@ fn measure_using_bucket_one_of_size_3_and_bucket_two_of_size_5_start_with_bucket #[test] #[ignore] fn measure_using_bucket_one_of_size_3_and_bucket_two_of_size_5_start_with_bucket_two() { - let output = two_bucket::solve(3, 5, 1, &Bucket::Two); + let output = solve(3, 5, 1, &Bucket::Two); let expected = Some(BucketStats { moves: 8, goal_bucket: Bucket::Two, @@ -26,7 +26,7 @@ fn measure_using_bucket_one_of_size_3_and_bucket_two_of_size_5_start_with_bucket #[test] #[ignore] fn measure_using_bucket_one_of_size_7_and_bucket_two_of_size_11_start_with_bucket_one() { - let output = two_bucket::solve(7, 11, 2, &Bucket::One); + let output = solve(7, 11, 2, &Bucket::One); let expected = Some(BucketStats { moves: 14, goal_bucket: Bucket::One, @@ -38,7 +38,7 @@ fn measure_using_bucket_one_of_size_7_and_bucket_two_of_size_11_start_with_bucke #[test] #[ignore] fn measure_using_bucket_one_of_size_7_and_bucket_two_of_size_11_start_with_bucket_two() { - let output = two_bucket::solve(7, 11, 2, &Bucket::Two); + let output = solve(7, 11, 2, &Bucket::Two); let expected = Some(BucketStats { moves: 18, goal_bucket: Bucket::Two, @@ -50,7 +50,7 @@ fn measure_using_bucket_one_of_size_7_and_bucket_two_of_size_11_start_with_bucke #[test] #[ignore] fn measure_one_step_using_bucket_one_of_size_1_and_bucket_two_of_size_3_start_with_bucket_two() { - let output = two_bucket::solve(1, 3, 3, &Bucket::Two); + let output = solve(1, 3, 3, &Bucket::Two); let expected = Some(BucketStats { moves: 1, goal_bucket: Bucket::Two, @@ -63,7 +63,7 @@ fn measure_one_step_using_bucket_one_of_size_1_and_bucket_two_of_size_3_start_wi #[ignore] fn measure_using_bucket_one_of_size_2_and_bucket_two_of_size_3_start_with_bucket_one_and_end_with_bucket_two( ) { - let output = two_bucket::solve(2, 3, 3, &Bucket::One); + let output = solve(2, 3, 3, &Bucket::One); let expected = Some(BucketStats { moves: 2, goal_bucket: Bucket::Two, @@ -75,7 +75,7 @@ fn measure_using_bucket_one_of_size_2_and_bucket_two_of_size_3_start_with_bucket #[test] #[ignore] fn not_possible_to_reach_the_goal() { - let output = two_bucket::solve(6, 15, 5, &Bucket::One); + let output = solve(6, 15, 5, &Bucket::One); let expected = None; assert_eq!(output, expected); } @@ -83,7 +83,7 @@ fn not_possible_to_reach_the_goal() { #[test] #[ignore] fn with_the_same_buckets_but_a_different_goal_then_it_is_possible() { - let output = two_bucket::solve(6, 15, 9, &Bucket::One); + let output = solve(6, 15, 9, &Bucket::One); let expected = Some(BucketStats { moves: 10, goal_bucket: Bucket::Two, @@ -95,7 +95,7 @@ fn with_the_same_buckets_but_a_different_goal_then_it_is_possible() { #[test] #[ignore] fn goal_larger_than_both_buckets_is_impossible() { - let output = two_bucket::solve(5, 7, 8, &Bucket::One); + let output = solve(5, 7, 8, &Bucket::One); let expected = None; assert_eq!(output, expected); } diff --git a/exercises/practice/wordy/.meta/test_template.tera b/exercises/practice/wordy/.meta/test_template.tera index 3706fecc0..831752169 100644 --- a/exercises/practice/wordy/.meta/test_template.tera +++ b/exercises/practice/wordy/.meta/test_template.tera @@ -1,3 +1,5 @@ +use wordy::*; + {% for test in cases %} #[test] #[ignore] @@ -6,7 +8,7 @@ {% endif -%} fn {{ test.description | slugify | replace(from="-", to="_") }}() { let input = {{ test.input.question | json_encode() }}; - let output = {{ crate_name }}::{{ fn_names[0] }}(input); + let output = answer(input); let expected = {% if test.expected is object -%} None {%- else -%} diff --git a/exercises/practice/wordy/tests/wordy.rs b/exercises/practice/wordy/tests/wordy.rs index 765011883..6d5ded615 100644 --- a/exercises/practice/wordy/tests/wordy.rs +++ b/exercises/practice/wordy/tests/wordy.rs @@ -1,7 +1,9 @@ +use wordy::*; + #[test] fn just_a_number() { let input = "What is 5?"; - let output = wordy::answer(input); + let output = answer(input); let expected = Some(5); assert_eq!(output, expected); } @@ -10,7 +12,7 @@ fn just_a_number() { #[ignore] fn addition() { let input = "What is 1 plus 1?"; - let output = wordy::answer(input); + let output = answer(input); let expected = Some(2); assert_eq!(output, expected); } @@ -19,7 +21,7 @@ fn addition() { #[ignore] fn more_addition() { let input = "What is 53 plus 2?"; - let output = wordy::answer(input); + let output = answer(input); let expected = Some(55); assert_eq!(output, expected); } @@ -28,7 +30,7 @@ fn more_addition() { #[ignore] fn addition_with_negative_numbers() { let input = "What is -1 plus -10?"; - let output = wordy::answer(input); + let output = answer(input); let expected = Some(-11); assert_eq!(output, expected); } @@ -37,7 +39,7 @@ fn addition_with_negative_numbers() { #[ignore] fn large_addition() { let input = "What is 123 plus 45678?"; - let output = wordy::answer(input); + let output = answer(input); let expected = Some(45801); assert_eq!(output, expected); } @@ -46,7 +48,7 @@ fn large_addition() { #[ignore] fn subtraction() { let input = "What is 4 minus -12?"; - let output = wordy::answer(input); + let output = answer(input); let expected = Some(16); assert_eq!(output, expected); } @@ -55,7 +57,7 @@ fn subtraction() { #[ignore] fn multiplication() { let input = "What is -3 multiplied by 25?"; - let output = wordy::answer(input); + let output = answer(input); let expected = Some(-75); assert_eq!(output, expected); } @@ -64,7 +66,7 @@ fn multiplication() { #[ignore] fn division() { let input = "What is 33 divided by -3?"; - let output = wordy::answer(input); + let output = answer(input); let expected = Some(-11); assert_eq!(output, expected); } @@ -73,7 +75,7 @@ fn division() { #[ignore] fn multiple_additions() { let input = "What is 1 plus 1 plus 1?"; - let output = wordy::answer(input); + let output = answer(input); let expected = Some(3); assert_eq!(output, expected); } @@ -82,7 +84,7 @@ fn multiple_additions() { #[ignore] fn addition_and_subtraction() { let input = "What is 1 plus 5 minus -2?"; - let output = wordy::answer(input); + let output = answer(input); let expected = Some(8); assert_eq!(output, expected); } @@ -91,7 +93,7 @@ fn addition_and_subtraction() { #[ignore] fn multiple_subtraction() { let input = "What is 20 minus 4 minus 13?"; - let output = wordy::answer(input); + let output = answer(input); let expected = Some(3); assert_eq!(output, expected); } @@ -100,7 +102,7 @@ fn multiple_subtraction() { #[ignore] fn subtraction_then_addition() { let input = "What is 17 minus 6 plus 3?"; - let output = wordy::answer(input); + let output = answer(input); let expected = Some(14); assert_eq!(output, expected); } @@ -109,7 +111,7 @@ fn subtraction_then_addition() { #[ignore] fn multiple_multiplication() { let input = "What is 2 multiplied by -2 multiplied by 3?"; - let output = wordy::answer(input); + let output = answer(input); let expected = Some(-12); assert_eq!(output, expected); } @@ -118,7 +120,7 @@ fn multiple_multiplication() { #[ignore] fn addition_and_multiplication() { let input = "What is -3 plus 7 multiplied by -2?"; - let output = wordy::answer(input); + let output = answer(input); let expected = Some(-8); assert_eq!(output, expected); } @@ -127,7 +129,7 @@ fn addition_and_multiplication() { #[ignore] fn multiple_division() { let input = "What is -12 divided by 2 divided by -3?"; - let output = wordy::answer(input); + let output = answer(input); let expected = Some(2); assert_eq!(output, expected); } @@ -136,7 +138,7 @@ fn multiple_division() { #[ignore] fn unknown_operation() { let input = "What is 52 cubed?"; - let output = wordy::answer(input); + let output = answer(input); let expected = None; assert_eq!(output, expected); } @@ -145,7 +147,7 @@ fn unknown_operation() { #[ignore] fn non_math_question() { let input = "Who is the President of the United States?"; - let output = wordy::answer(input); + let output = answer(input); let expected = None; assert_eq!(output, expected); } @@ -154,7 +156,7 @@ fn non_math_question() { #[ignore] fn reject_problem_missing_an_operand() { let input = "What is 1 plus?"; - let output = wordy::answer(input); + let output = answer(input); let expected = None; assert_eq!(output, expected); } @@ -163,7 +165,7 @@ fn reject_problem_missing_an_operand() { #[ignore] fn reject_problem_with_no_operands_or_operators() { let input = "What is?"; - let output = wordy::answer(input); + let output = answer(input); let expected = None; assert_eq!(output, expected); } @@ -172,7 +174,7 @@ fn reject_problem_with_no_operands_or_operators() { #[ignore] fn reject_two_operations_in_a_row() { let input = "What is 1 plus plus 2?"; - let output = wordy::answer(input); + let output = answer(input); let expected = None; assert_eq!(output, expected); } @@ -181,7 +183,7 @@ fn reject_two_operations_in_a_row() { #[ignore] fn reject_two_numbers_in_a_row() { let input = "What is 1 plus 2 1?"; - let output = wordy::answer(input); + let output = answer(input); let expected = None; assert_eq!(output, expected); } @@ -190,7 +192,7 @@ fn reject_two_numbers_in_a_row() { #[ignore] fn reject_postfix_notation() { let input = "What is 1 2 plus?"; - let output = wordy::answer(input); + let output = answer(input); let expected = None; assert_eq!(output, expected); } @@ -199,7 +201,7 @@ fn reject_postfix_notation() { #[ignore] fn reject_prefix_notation() { let input = "What is plus 1 2?"; - let output = wordy::answer(input); + let output = answer(input); let expected = None; assert_eq!(output, expected); } @@ -209,7 +211,7 @@ fn reject_prefix_notation() { #[cfg(feature = "exponentials")] fn exponential() { let input = "What is 2 raised to the 5th power?"; - let output = wordy::answer(input); + let output = answer(input); let expected = Some(32); assert_eq!(output, expected); } @@ -219,7 +221,7 @@ fn exponential() { #[cfg(feature = "exponentials")] fn addition_and_exponential() { let input = "What is 1 plus 2 raised to the 2nd power?"; - let output = wordy::answer(input); + let output = answer(input); let expected = Some(9); assert_eq!(output, expected); } diff --git a/rust-tooling/generate/src/lib.rs b/rust-tooling/generate/src/lib.rs index a726cd1a0..ceaf4f2b3 100644 --- a/rust-tooling/generate/src/lib.rs +++ b/rust-tooling/generate/src/lib.rs @@ -20,17 +20,16 @@ pub struct GeneratedExercise { pub tests: String, } -pub fn new(slug: &str, fn_names: Vec) -> GeneratedExercise { +pub fn new(slug: &str) -> GeneratedExercise { let crate_name = slug.replace('-', "_"); - let first_fn_name = &fn_names[0]; GeneratedExercise { gitignore: GITIGNORE.into(), manifest: generate_manifest(&crate_name), - lib_rs: generate_lib_rs(&crate_name, first_fn_name), - example: generate_example_rs(first_fn_name), + lib_rs: LIB_RS.into(), + example: EXAMPLE_RS.into(), test_template: TEST_TEMPLATE.into(), - tests: generate_tests(slug, fn_names), + tests: generate_tests(slug), } } @@ -53,28 +52,17 @@ fn generate_manifest(crate_name: &str) -> String { ) } -fn generate_lib_rs(crate_name: &str, fn_name: &str) -> String { - format!( - concat!( - "pub fn {fn_name}(input: TODO) -> TODO {{\n", - " todo!(\"use {{input}} to implement {crate_name}\")\n", - "}}\n", - ), - fn_name = fn_name, - crate_name = crate_name, - ) +static LIB_RS: &str = "\ +pub fn TODO(input: TODO) -> TODO { + todo!(\"use {input} to solve the exercise\") } +"; -fn generate_example_rs(fn_name: &str) -> String { - format!( - concat!( - "pub fn {fn_name}(input: TODO) -> TODO {{\n", - " TODO\n", - "}}\n", - ), - fn_name = fn_name - ) +static EXAMPLE_RS: &str = "\ +pub fn TODO(input: TODO) -> TODO { + TODO } +"; static TEST_TEMPLATE: &str = include_str!("../templates/default_test_template.tera"); @@ -94,7 +82,7 @@ fn to_hex(value: &tera::Value, _args: &HashMap) -> tera::Re ))) } -fn generate_tests(slug: &str, fn_names: Vec) -> String { +fn generate_tests(slug: &str) -> String { let cases = { let mut cases = get_canonical_data(slug) .map(|data| data.cases) @@ -116,11 +104,11 @@ fn generate_tests(slug: &str, fn_names: Vec) -> String { single_cases.retain(|case| !excluded_tests.contains(&case.uuid)); let mut context = Context::new(); - context.insert("crate_name", &slug.replace('-', "_")); - context.insert("fn_names", &fn_names); context.insert("cases", &single_cases); - let rendered = template.render("test_template.tera", &context).unwrap(); + let rendered = template + .render("test_template.tera", &context) + .unwrap_or_else(|_| panic!("failed to render template of '{slug}'")); // Remove ignore-annotation on first test. // This could be done in the template itself, @@ -168,22 +156,3 @@ It probably generates invalid Rust code." pub fn get_test_template(slug: &str) -> Option { Some(Tera::new(format!("exercises/practice/{slug}/.meta/*.tera").as_str()).unwrap()) } - -pub fn read_fn_names_from_lib_rs(slug: &str) -> Vec { - let lib_rs = - std::fs::read_to_string(format!("exercises/practice/{}/src/lib.rs", slug)).unwrap(); - - lib_rs - .split("fn ") - .skip(1) - .map(|f| { - let tmp = f.split_once('(').unwrap().0; - // strip generics - if let Some((res, _)) = tmp.split_once('<') { - res.to_string() - } else { - tmp.to_string() - } - }) - .collect() -} diff --git a/rust-tooling/generate/src/main.rs b/rust-tooling/generate/src/main.rs index c653959ea..ea3a8905b 100644 --- a/rust-tooling/generate/src/main.rs +++ b/rust-tooling/generate/src/main.rs @@ -77,13 +77,7 @@ fn make_configlet_generate_what_it_can(slug: &str) { } fn generate_exercise_files(slug: &str, is_update: bool) { - let fn_names = if is_update { - generate::read_fn_names_from_lib_rs(slug) - } else { - vec!["TODO".to_string()] - }; - - let exercise = generate::new(slug, fn_names); + let exercise = generate::new(slug); let exercise_path = PathBuf::from("exercises/practice").join(slug); diff --git a/rust-tooling/generate/templates/default_test_template.tera b/rust-tooling/generate/templates/default_test_template.tera index 9c7d6fe83..fe5943e36 100644 --- a/rust-tooling/generate/templates/default_test_template.tera +++ b/rust-tooling/generate/templates/default_test_template.tera @@ -1,11 +1,11 @@ -use {{ crate_name }}::*; +use crate_name::*; {% for test in cases %} #[test] #[ignore] fn {{ test.description | slugify | replace(from="-", to="_") }}() { let input = {{ test.input | json_encode() }}; - let output = {{ fn_names[0] }}(input); + let output = function_name(input); let expected = {{ test.expected | json_encode() }}; assert_eq!(output, expected); } diff --git a/rust-tooling/generate/tests/tera_templates_are_in_sync.rs b/rust-tooling/generate/tests/tera_templates_are_in_sync.rs index c1cae8fa5..a8c81c14d 100644 --- a/rust-tooling/generate/tests/tera_templates_are_in_sync.rs +++ b/rust-tooling/generate/tests/tera_templates_are_in_sync.rs @@ -12,8 +12,7 @@ fn tera_templates_are_in_sync() { let exercise_dir = path.parent().unwrap().parent().unwrap(); let slug = exercise_dir.file_name().unwrap().to_string_lossy(); - let fn_names = generate::read_fn_names_from_lib_rs(&slug); - let generated = generate::new(&slug, fn_names); + let generated = generate::new(&slug); let test_path = exercise_dir.join("tests").join(format!("{slug}.rs")); let on_disk = std::fs::read_to_string(test_path).unwrap(); From 8d89b316a8fe45d77e6a8a5b6b04fc7a9b2fe47e Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 2 Apr 2024 10:05:20 +0200 Subject: [PATCH 269/436] generator: add filter for snake_case (#1887) --- .../practice/acronym/.meta/test_template.tera | 2 +- .../affine-cipher/.meta/test_template.tera | 2 +- .../allergies/.meta/test_template.tera | 4 ++-- .../practice/anagram/.meta/test_template.tera | 2 +- .../book-store/.meta/test_template.tera | 2 +- .../custom-set/.meta/test_template.tera | 2 +- .../eliuds-eggs/.meta/test_template.tera | 2 +- .../knapsack/.meta/test_template.tera | 2 +- .../practice/leap/.meta/test_template.tera | 2 +- .../practice/matrix/.meta/test_template.tera | 2 +- .../pascals-triangle/.meta/test_template.tera | 2 +- .../perfect-numbers/.meta/test_template.tera | 2 +- .../phone-number/.meta/test_template.tera | 2 +- .../pig-latin/.meta/test_template.tera | 2 +- .../practice/poker/.meta/test_template.tera | 2 +- .../prime-factors/.meta/test_template.tera | 2 +- .../practice/proverb/.meta/test_template.tera | 2 +- .../.meta/test_template.tera | 2 +- .../queen-attack/.meta/test_template.tera | 2 +- .../.meta/test_template.tera | 2 +- .../raindrops/.meta/test_template.tera | 2 +- .../rectangles/.meta/test_template.tera | 2 +- .../reverse-string/.meta/test_template.tera | 2 +- .../.meta/test_template.tera | 2 +- .../robot-simulator/.meta/test_template.tera | 2 +- .../roman-numerals/.meta/test_template.tera | 2 +- .../.meta/test_template.tera | 2 +- .../.meta/test_template.tera | 2 +- .../saddle-points/.meta/test_template.tera | 2 +- .../practice/say/.meta/test_template.tera | 2 +- .../scrabble-score/.meta/test_template.tera | 2 +- .../practice/series/.meta/test_template.tera | 2 +- .../practice/sieve/.meta/test_template.tera | 2 +- .../space-age/.meta/test_template.tera | 2 +- .../spiral-matrix/.meta/test_template.tera | 2 +- .../practice/sublist/.meta/test_template.tera | 2 +- .../sum-of-multiples/.meta/test_template.tera | 2 +- .../tournament/.meta/test_template.tera | 2 +- .../triangle/.meta/test_template.tera | 8 +++---- .../two-bucket/.meta/test_template.tera | 2 +- .../.meta/test_template.tera | 2 +- .../word-count/.meta/test_template.tera | 2 +- .../practice/wordy/.meta/test_template.tera | 2 +- rust-tooling/Cargo.lock | 1 + rust-tooling/generate/Cargo.toml | 1 + rust-tooling/generate/src/custom_filters.rs | 21 +++++++++++++++++++ rust-tooling/generate/src/lib.rs | 15 ++++++------- 47 files changed, 76 insertions(+), 56 deletions(-) create mode 100644 rust-tooling/generate/src/custom_filters.rs diff --git a/exercises/practice/acronym/.meta/test_template.tera b/exercises/practice/acronym/.meta/test_template.tera index a0c475be5..5f8bee34d 100644 --- a/exercises/practice/acronym/.meta/test_template.tera +++ b/exercises/practice/acronym/.meta/test_template.tera @@ -3,7 +3,7 @@ use acronym::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | slugify | replace(from="-", to="_") }}() { +fn {{ test.description | snake_case }}() { let input = {{ test.input.phrase | json_encode() }}; let output = abbreviate(input); let expected = {{ test.expected | json_encode() }}; diff --git a/exercises/practice/affine-cipher/.meta/test_template.tera b/exercises/practice/affine-cipher/.meta/test_template.tera index 140447056..4db5f54b9 100644 --- a/exercises/practice/affine-cipher/.meta/test_template.tera +++ b/exercises/practice/affine-cipher/.meta/test_template.tera @@ -4,7 +4,7 @@ use affine_cipher::AffineCipherError::NotCoprime; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | slugify | replace(from="-", to="_") }}() { +fn {{ test.description | snake_case }}() { let phrase = {{ test.input.phrase | json_encode() }}; let (a, b) = ({{ test.input.key.a }}, {{ test.input.key.b }}); let output = {{ test.property }}(phrase, a, b); diff --git a/exercises/practice/allergies/.meta/test_template.tera b/exercises/practice/allergies/.meta/test_template.tera index d3b16500b..d74ecc449 100644 --- a/exercises/practice/allergies/.meta/test_template.tera +++ b/exercises/practice/allergies/.meta/test_template.tera @@ -19,7 +19,7 @@ fn compare_allergy_vectors(expected: &[Allergen], actual: &[Allergen]) { #[ignore] {%- if test.property == "allergicTo" %} {# canonical data contains multiple cases named "allergic to everything" for different items #} -fn {{ test.description | slugify | replace(from="-", to="_") }}_{{ test.input.item }}() { +fn {{ test.description | snake_case }}_{{ test.input.item }}() { let allergies = Allergies::new({{ test.input.score }}); {%- if test.expected %} assert!(allergies.is_allergic_to(&Allergen::{{ test.input.item | title }})) @@ -27,7 +27,7 @@ fn {{ test.description | slugify | replace(from="-", to="_") }}_{{ test.input.it assert!(!allergies.is_allergic_to(&Allergen::{{ test.input.item | title }})) {% endif -%} {% else %} -fn {{ test.description | slugify | replace(from="-", to="_") }}() { +fn {{ test.description | snake_case }}() { let allergies = Allergies::new({{ test.input.score }}).allergies(); let expected = &[ {% for allergen in test.expected %} diff --git a/exercises/practice/anagram/.meta/test_template.tera b/exercises/practice/anagram/.meta/test_template.tera index 313959ea8..e856d8900 100644 --- a/exercises/practice/anagram/.meta/test_template.tera +++ b/exercises/practice/anagram/.meta/test_template.tera @@ -4,7 +4,7 @@ use std::collections::HashSet; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | slugify | replace(from="-", to="_") }}() { +fn {{ test.description | snake_case }}() { let word = {{ test.input.subject | json_encode() }}; let inputs = &{{ test.input.candidates | json_encode() }}; let output = anagrams_for(word, inputs); diff --git a/exercises/practice/book-store/.meta/test_template.tera b/exercises/practice/book-store/.meta/test_template.tera index 4bbd5e63f..81ffc2d1e 100644 --- a/exercises/practice/book-store/.meta/test_template.tera +++ b/exercises/practice/book-store/.meta/test_template.tera @@ -3,7 +3,7 @@ use book_store::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | slugify | replace(from="-", to="_") }}() { +fn {{ test.description | snake_case }}() { let input = &{{ test.input.basket | json_encode() }}; let output = lowest_price(input); let expected = {{ test.expected | json_encode() }}; diff --git a/exercises/practice/custom-set/.meta/test_template.tera b/exercises/practice/custom-set/.meta/test_template.tera index a9d59ae14..040bf84eb 100644 --- a/exercises/practice/custom-set/.meta/test_template.tera +++ b/exercises/practice/custom-set/.meta/test_template.tera @@ -2,7 +2,7 @@ use custom_set::CustomSet; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | slugify | replace(from="-", to="_") }}() { +fn {{ test.description | snake_case }}() { {%- if test.property == "empty" %} let set = CustomSet::::new(&{{ test.input.set | json_encode() }}); assert!( diff --git a/exercises/practice/eliuds-eggs/.meta/test_template.tera b/exercises/practice/eliuds-eggs/.meta/test_template.tera index d9a91f564..318242671 100644 --- a/exercises/practice/eliuds-eggs/.meta/test_template.tera +++ b/exercises/practice/eliuds-eggs/.meta/test_template.tera @@ -3,7 +3,7 @@ use eliuds_eggs::*; {% for test in cases %} #[test] #[ignore] -fn test_{{ test.description | slugify | replace(from="-", to="_") }}() { +fn test_{{ test.description | snake_case }}() { let input = {{ test.input.number }}; let output = egg_count(input); let expected = {{ test.expected | json_encode() }}; diff --git a/exercises/practice/knapsack/.meta/test_template.tera b/exercises/practice/knapsack/.meta/test_template.tera index 7b3f781fa..3cbe97694 100644 --- a/exercises/practice/knapsack/.meta/test_template.tera +++ b/exercises/practice/knapsack/.meta/test_template.tera @@ -3,7 +3,7 @@ use knapsack::*; {% for test in cases %} #[test] #[ignore] -fn test_{{ test.description | slugify | replace(from="-", to="_") }}() { +fn test_{{ test.description | snake_case }}() { let max_weight = {{ test.input.maximumWeight }}; let items = [ {% for item in test.input.items -%} diff --git a/exercises/practice/leap/.meta/test_template.tera b/exercises/practice/leap/.meta/test_template.tera index 5d9e4cf4e..c93330cbb 100644 --- a/exercises/practice/leap/.meta/test_template.tera +++ b/exercises/practice/leap/.meta/test_template.tera @@ -3,7 +3,7 @@ use leap::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | slugify | replace(from="-", to="_") }}() { +fn {{ test.description | snake_case }}() { {%- if test.expected %} assert!(is_leap_year({{ test.input.year }})); {% else %} diff --git a/exercises/practice/matrix/.meta/test_template.tera b/exercises/practice/matrix/.meta/test_template.tera index 397167761..eff2a7edb 100644 --- a/exercises/practice/matrix/.meta/test_template.tera +++ b/exercises/practice/matrix/.meta/test_template.tera @@ -3,7 +3,7 @@ use matrix::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | slugify | replace(from="-", to="_") }}() { +fn {{ test.description | snake_case }}() { let matrix = Matrix::new({{ test.input.string | json_encode() }}); {% if test.expected -%} assert_eq!(matrix.{{ test.property }}({{ test.input.index }}), Some(vec!{{ test.expected | json_encode() }})); diff --git a/exercises/practice/pascals-triangle/.meta/test_template.tera b/exercises/practice/pascals-triangle/.meta/test_template.tera index 139cb5c68..6acfd5305 100644 --- a/exercises/practice/pascals-triangle/.meta/test_template.tera +++ b/exercises/practice/pascals-triangle/.meta/test_template.tera @@ -2,7 +2,7 @@ use pascals_triangle::PascalsTriangle; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | slugify | replace(from="-", to="_") }}() { +fn {{ test.description | snake_case }}() { let pt = PascalsTriangle::new({{ test.input.count }}); let expected: Vec> = vec![{% for row in test.expected -%} vec!{{ row | json_encode() }}, diff --git a/exercises/practice/perfect-numbers/.meta/test_template.tera b/exercises/practice/perfect-numbers/.meta/test_template.tera index c56ad1b9b..e148b3392 100644 --- a/exercises/practice/perfect-numbers/.meta/test_template.tera +++ b/exercises/practice/perfect-numbers/.meta/test_template.tera @@ -3,7 +3,7 @@ use perfect_numbers::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | slugify | replace(from="-", to="_") }}() { +fn {{ test.description | snake_case }}() { let input = {{ test.input.number | json_encode() }}; let output = classify(input); {%- if test.expected is object %} diff --git a/exercises/practice/phone-number/.meta/test_template.tera b/exercises/practice/phone-number/.meta/test_template.tera index 82eac95dc..e9871c39f 100644 --- a/exercises/practice/phone-number/.meta/test_template.tera +++ b/exercises/practice/phone-number/.meta/test_template.tera @@ -3,7 +3,7 @@ use phone_number::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | slugify | replace(from="-", to="_") }}() { +fn {{ test.description | snake_case }}() { let input = {{ test.input.phrase | json_encode() }}; let output = number(input); {%- if test.expected is object %} diff --git a/exercises/practice/pig-latin/.meta/test_template.tera b/exercises/practice/pig-latin/.meta/test_template.tera index 4fc5c17ca..3892ecbb6 100644 --- a/exercises/practice/pig-latin/.meta/test_template.tera +++ b/exercises/practice/pig-latin/.meta/test_template.tera @@ -3,7 +3,7 @@ use pig_latin::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | slugify | replace(from="-", to="_") }}() { +fn {{ test.description | snake_case }}() { let input = {{ test.input.phrase | json_encode() }}; let output = translate(input); let expected = {{ test.expected | json_encode() }}; diff --git a/exercises/practice/poker/.meta/test_template.tera b/exercises/practice/poker/.meta/test_template.tera index ff55e7ca5..7962198ad 100644 --- a/exercises/practice/poker/.meta/test_template.tera +++ b/exercises/practice/poker/.meta/test_template.tera @@ -4,7 +4,7 @@ use std::collections::HashSet; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | slugify | replace(from="-", to="_") }}() { +fn {{ test.description | snake_case }}() { let input = &{{ test.input.hands | json_encode() }}; let output = winning_hands(input).into_iter().collect::>(); let expected = {{ test.expected | json_encode() }}.into_iter().collect::>(); diff --git a/exercises/practice/prime-factors/.meta/test_template.tera b/exercises/practice/prime-factors/.meta/test_template.tera index 42752aada..9c8604d1e 100644 --- a/exercises/practice/prime-factors/.meta/test_template.tera +++ b/exercises/practice/prime-factors/.meta/test_template.tera @@ -3,7 +3,7 @@ use prime_factors::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | slugify | replace(from="-", to="_") }}() { +fn {{ test.description | snake_case }}() { let factors = factors({{ test.input.value }}); let expected = {{ test.expected | json_encode() }}; assert_eq!(factors, expected); diff --git a/exercises/practice/proverb/.meta/test_template.tera b/exercises/practice/proverb/.meta/test_template.tera index 6b9ddcf1d..afa61b9c6 100644 --- a/exercises/practice/proverb/.meta/test_template.tera +++ b/exercises/practice/proverb/.meta/test_template.tera @@ -3,7 +3,7 @@ use proverb::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | slugify | replace(from="-", to="_") }}() { +fn {{ test.description | snake_case }}() { let input = &{{ test.input.strings | json_encode() }}; let output = build_proverb(input); {% if test.expected | length == 0 -%} diff --git a/exercises/practice/pythagorean-triplet/.meta/test_template.tera b/exercises/practice/pythagorean-triplet/.meta/test_template.tera index ff74382c1..19fa6ab94 100644 --- a/exercises/practice/pythagorean-triplet/.meta/test_template.tera +++ b/exercises/practice/pythagorean-triplet/.meta/test_template.tera @@ -4,7 +4,7 @@ use std::collections::HashSet; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | slugify | replace(from="-", to="_") }}() { +fn {{ test.description | snake_case }}() { let input = {{ test.input.n | json_encode() }}; let output = find(input); let expected = {{ test.expected | json_encode() }}; diff --git a/exercises/practice/queen-attack/.meta/test_template.tera b/exercises/practice/queen-attack/.meta/test_template.tera index 6886087b6..5221ee6f5 100644 --- a/exercises/practice/queen-attack/.meta/test_template.tera +++ b/exercises/practice/queen-attack/.meta/test_template.tera @@ -3,7 +3,7 @@ use queen_attack::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | slugify | replace(from="-", to="_") }}() { +fn {{ test.description | snake_case }}() { {% if test.property == "create" %} let chess_position = ChessPosition::new({{ test.input.queen.position.row }}, {{ test.input.queen.position.column }}); {%- if test.expected is object %} diff --git a/exercises/practice/rail-fence-cipher/.meta/test_template.tera b/exercises/practice/rail-fence-cipher/.meta/test_template.tera index adbea054b..5f6ad063e 100644 --- a/exercises/practice/rail-fence-cipher/.meta/test_template.tera +++ b/exercises/practice/rail-fence-cipher/.meta/test_template.tera @@ -2,7 +2,7 @@ use rail_fence_cipher::RailFence; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | slugify | replace(from="-", to="_") }}() { +fn {{ test.description | snake_case }}() { let input = {{ test.input.msg | json_encode() }}; let rails = {{ test.input.rails | json_encode() }}; let rail_fence = RailFence::new(rails); diff --git a/exercises/practice/raindrops/.meta/test_template.tera b/exercises/practice/raindrops/.meta/test_template.tera index 986ff926a..53b990bec 100644 --- a/exercises/practice/raindrops/.meta/test_template.tera +++ b/exercises/practice/raindrops/.meta/test_template.tera @@ -3,7 +3,7 @@ use raindrops::*; {% for test in cases %} #[test] #[ignore] -fn test_{{ test.description | slugify | replace(from="-", to="_") }}() { +fn test_{{ test.description | snake_case }}() { let input = {{ test.input.number | json_encode() }}; let output = raindrops(input); let expected = {{ test.expected | json_encode() }}; diff --git a/exercises/practice/rectangles/.meta/test_template.tera b/exercises/practice/rectangles/.meta/test_template.tera index ac83e0e8b..a75dcde9c 100644 --- a/exercises/practice/rectangles/.meta/test_template.tera +++ b/exercises/practice/rectangles/.meta/test_template.tera @@ -3,7 +3,7 @@ use rectangles::*; {% for test in cases %} #[test] #[ignore] -fn test_{{ test.description | slugify | replace(from="-", to="_") }}() { +fn test_{{ test.description | snake_case }}() { {% if test.input.strings | length > 1 -%} #[rustfmt::skip] {%- endif %} diff --git a/exercises/practice/reverse-string/.meta/test_template.tera b/exercises/practice/reverse-string/.meta/test_template.tera index ec22fbbbf..56b1c8e6b 100644 --- a/exercises/practice/reverse-string/.meta/test_template.tera +++ b/exercises/practice/reverse-string/.meta/test_template.tera @@ -6,7 +6,7 @@ use reverse_string::*; {% if test.description is starting_with("grapheme cluster") -%} #[cfg(feature = "grapheme")] {% endif -%} -fn {{ test.description | slugify | replace(from="-", to="_") }}() { +fn {{ test.description | snake_case }}() { let input = {{ test.input.value | json_encode() }}; let output = reverse(input); let expected = {{ test.expected | json_encode() }}; diff --git a/exercises/practice/rna-transcription/.meta/test_template.tera b/exercises/practice/rna-transcription/.meta/test_template.tera index 30693ca00..ab0ed6016 100644 --- a/exercises/practice/rna-transcription/.meta/test_template.tera +++ b/exercises/practice/rna-transcription/.meta/test_template.tera @@ -2,7 +2,7 @@ use rna_transcription::{Dna, Rna}; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | slugify | replace(from="-", to="_") }}() { +fn {{ test.description | snake_case }}() { let input = {{ test.input.dna | json_encode() }}; {% if test.property == "invalidDna" -%} let output = Dna::new(input); diff --git a/exercises/practice/robot-simulator/.meta/test_template.tera b/exercises/practice/robot-simulator/.meta/test_template.tera index fa07b208d..2edb15c45 100644 --- a/exercises/practice/robot-simulator/.meta/test_template.tera +++ b/exercises/practice/robot-simulator/.meta/test_template.tera @@ -3,7 +3,7 @@ use robot_simulator::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | slugify | replace(from="-", to="_") }}() { +fn {{ test.description | snake_case }}() { {%- if test.property == "create" %} let robot = Robot::new({{ test.input.position.x }}, {{ test.input.position.y }}, Direction::{{ test.input.direction | title }}); assert_eq!(robot.position(), ({{ test.expected.position.x }}, {{ test.expected.position.y }})); diff --git a/exercises/practice/roman-numerals/.meta/test_template.tera b/exercises/practice/roman-numerals/.meta/test_template.tera index 97e0c4d01..c82367cd9 100644 --- a/exercises/practice/roman-numerals/.meta/test_template.tera +++ b/exercises/practice/roman-numerals/.meta/test_template.tera @@ -3,7 +3,7 @@ use roman_numerals::Roman; {% for test in cases %} #[test] #[ignore] -fn number_{{ test.description | slugify | replace(from="-", to="_") }}() { +fn number_{{ test.description | snake_case }}() { let input = {{ test.input.number | json_encode() }}; let output = Roman::from(input).to_string(); let expected = {{ test.expected | json_encode() }}; diff --git a/exercises/practice/rotational-cipher/.meta/test_template.tera b/exercises/practice/rotational-cipher/.meta/test_template.tera index eb5a34c3d..58f50b754 100644 --- a/exercises/practice/rotational-cipher/.meta/test_template.tera +++ b/exercises/practice/rotational-cipher/.meta/test_template.tera @@ -2,7 +2,7 @@ use rotational_cipher as cipher; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | slugify | replace(from="-", to="_") }}() { +fn {{ test.description | snake_case }}() { let text = {{ test.input.text | json_encode() }}; let shift_key = {{ test.input.shiftKey | json_encode() }}; let output = cipher::rotate(text, shift_key); diff --git a/exercises/practice/run-length-encoding/.meta/test_template.tera b/exercises/practice/run-length-encoding/.meta/test_template.tera index d82ce7a24..c1776ddba 100644 --- a/exercises/practice/run-length-encoding/.meta/test_template.tera +++ b/exercises/practice/run-length-encoding/.meta/test_template.tera @@ -3,7 +3,7 @@ use run_length_encoding as rle; {% for test in cases %} #[test] #[ignore] -fn {{ test.property }}_{{ test.description | slugify | replace(from="-", to="_") }}() { +fn {{ test.property }}_{{ test.description | snake_case }}() { let input = {{ test.input.string | json_encode() }}; {% if test.property == "consistency" -%} let output = rle::decode(&rle::encode(input)); diff --git a/exercises/practice/saddle-points/.meta/test_template.tera b/exercises/practice/saddle-points/.meta/test_template.tera index 722a4d346..1920506ce 100644 --- a/exercises/practice/saddle-points/.meta/test_template.tera +++ b/exercises/practice/saddle-points/.meta/test_template.tera @@ -7,7 +7,7 @@ fn find_sorted_saddle_points(input: &[Vec]) -> Vec<(usize, usize)> { {% for test in cases %} #[test] #[ignore] -fn {{ test.description | slugify | replace(from="-", to="_") }}() { +fn {{ test.description | snake_case }}() { let input = &[{% for row in test.input.matrix %} vec!{{ row }}, {% endfor %}]; diff --git a/exercises/practice/say/.meta/test_template.tera b/exercises/practice/say/.meta/test_template.tera index 657876993..b67fcdf1d 100644 --- a/exercises/practice/say/.meta/test_template.tera +++ b/exercises/practice/say/.meta/test_template.tera @@ -3,7 +3,7 @@ use say::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | slugify | replace(from="-", to="_") }}() { +fn {{ test.description | snake_case }}() { let input = {{ test.input.number | json_encode() }}; let output = encode(input); let expected = {{ test.expected | json_encode() }}; diff --git a/exercises/practice/scrabble-score/.meta/test_template.tera b/exercises/practice/scrabble-score/.meta/test_template.tera index 3f62b1f1b..f09bc5067 100644 --- a/exercises/practice/scrabble-score/.meta/test_template.tera +++ b/exercises/practice/scrabble-score/.meta/test_template.tera @@ -3,7 +3,7 @@ use scrabble_score::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | slugify | replace(from="-", to="_") }}() { +fn {{ test.description | snake_case }}() { let input = {{ test.input.word | json_encode() }}; let output = score(input); let expected = {{ test.expected | json_encode() }}; diff --git a/exercises/practice/series/.meta/test_template.tera b/exercises/practice/series/.meta/test_template.tera index 986a101ac..25e725057 100644 --- a/exercises/practice/series/.meta/test_template.tera +++ b/exercises/practice/series/.meta/test_template.tera @@ -3,7 +3,7 @@ use series::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | slugify | replace(from="-", to="_") }}() { +fn {{ test.description | snake_case }}() { let input = {{ test.input.series | json_encode() }}; let length = {{ test.input.sliceLength | json_encode() }}; let output = series(input, length); diff --git a/exercises/practice/sieve/.meta/test_template.tera b/exercises/practice/sieve/.meta/test_template.tera index 8af3c0a95..cd33602d8 100644 --- a/exercises/practice/sieve/.meta/test_template.tera +++ b/exercises/practice/sieve/.meta/test_template.tera @@ -3,7 +3,7 @@ use sieve::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | slugify | replace(from="-", to="_") }}() { +fn {{ test.description | snake_case }}() { let input = {{ test.input.limit | json_encode() }}; let output = primes_up_to(input); let expected = {{ test.expected | json_encode() }}; diff --git a/exercises/practice/space-age/.meta/test_template.tera b/exercises/practice/space-age/.meta/test_template.tera index fe5b89626..01d6d1c69 100644 --- a/exercises/practice/space-age/.meta/test_template.tera +++ b/exercises/practice/space-age/.meta/test_template.tera @@ -10,7 +10,7 @@ fn assert_in_delta(expected: f64, actual: f64) { {% for test in cases %} #[test] #[ignore] -fn {{ test.description | slugify | replace(from="-", to="_") }}() { +fn {{ test.description | snake_case }}() { let seconds = {{ test.input.seconds | json_encode() }}; let duration = Duration::from(seconds); let output = {{ test.input.planet }}::years_during(&duration); diff --git a/exercises/practice/spiral-matrix/.meta/test_template.tera b/exercises/practice/spiral-matrix/.meta/test_template.tera index 0530a5ff6..65637ea9d 100644 --- a/exercises/practice/spiral-matrix/.meta/test_template.tera +++ b/exercises/practice/spiral-matrix/.meta/test_template.tera @@ -3,7 +3,7 @@ use spiral_matrix::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | slugify | replace(from="-", to="_") }}() { +fn {{ test.description | snake_case }}() { let input = {{ test.input.size | json_encode() }}; let output = spiral_matrix(input); let expected: [[u32; {{ test.input.size }}]; {{ test.input.size }}] = [ diff --git a/exercises/practice/sublist/.meta/test_template.tera b/exercises/practice/sublist/.meta/test_template.tera index 1b37261bc..ed6181ece 100644 --- a/exercises/practice/sublist/.meta/test_template.tera +++ b/exercises/practice/sublist/.meta/test_template.tera @@ -3,7 +3,7 @@ use sublist::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | slugify | replace(from="-", to="_") }}() { +fn {{ test.description | snake_case }}() { let list_one: &[i32] = &{{ test.input.listOne | json_encode() }}; let list_two: &[i32] = &{{ test.input.listTwo | json_encode() }}; let output = sublist(list_one, list_two); diff --git a/exercises/practice/sum-of-multiples/.meta/test_template.tera b/exercises/practice/sum-of-multiples/.meta/test_template.tera index 919dae094..bf40be46b 100644 --- a/exercises/practice/sum-of-multiples/.meta/test_template.tera +++ b/exercises/practice/sum-of-multiples/.meta/test_template.tera @@ -3,7 +3,7 @@ use sum_of_multiples::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | slugify | replace(from="-", to="_") }}() { +fn {{ test.description | snake_case }}() { let factors = &{{ test.input.factors | json_encode() }}; let limit = {{ test.input.limit | json_encode() }}; let output = sum_of_multiples(limit, factors); diff --git a/exercises/practice/tournament/.meta/test_template.tera b/exercises/practice/tournament/.meta/test_template.tera index ef76c2531..c6819eb5f 100644 --- a/exercises/practice/tournament/.meta/test_template.tera +++ b/exercises/practice/tournament/.meta/test_template.tera @@ -3,7 +3,7 @@ use tournament::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | slugify | replace(from="-", to="_") }}() { +fn {{ test.description | snake_case }}() { let input: &[&str] = &{{ test.input.rows | json_encode() }}; let input = input.join("\n"); let output = tally(&input); diff --git a/exercises/practice/triangle/.meta/test_template.tera b/exercises/practice/triangle/.meta/test_template.tera index 18263a183..53a32cece 100644 --- a/exercises/practice/triangle/.meta/test_template.tera +++ b/exercises/practice/triangle/.meta/test_template.tera @@ -8,7 +8,7 @@ use triangle::Triangle; {% if test.description is containing("float") %} #[cfg(feature = "generic")] {% endif -%} -fn {{ test.description | slugify | replace(from="-", to="_") }}() { +fn {{ test.description | snake_case }}() { let input = {{ test.input.sides | json_encode() }}; let output = Triangle::build(input).unwrap(); {%- if test.expected %} @@ -30,7 +30,7 @@ use triangle::Triangle; {% if test.scenarios and test.scenarios is containing("floating-point") %} #[cfg(feature = "generic")] {% endif -%} -fn {{ test.description | slugify | replace(from="-", to="_") }}() { +fn {{ test.description | snake_case }}() { let input = {{ test.input.sides | json_encode() }}; let output = Triangle::build(input).unwrap(); {%- if test.expected %} @@ -52,7 +52,7 @@ use triangle::Triangle; {% if test.scenarios and test.scenarios is containing("floating-point") %} #[cfg(feature = "generic")] {% endif -%} -fn {{ test.description | slugify | replace(from="-", to="_") }}() { +fn {{ test.description | snake_case }}() { let input = {{ test.input.sides | json_encode() }}; let output = Triangle::build(input).unwrap(); {%- if test.expected %} @@ -74,7 +74,7 @@ use triangle::Triangle; {% if test.scenarios and test.scenarios is containing("floating-point") %} #[cfg(feature = "generic")] {% endif -%} -fn {{ test.description | slugify | replace(from="-", to="_") }}() { +fn {{ test.description | snake_case }}() { let input = {{ test.input.sides | json_encode() }}; let output = Triangle::build(input); assert!(output.is_none()); diff --git a/exercises/practice/two-bucket/.meta/test_template.tera b/exercises/practice/two-bucket/.meta/test_template.tera index edb4e4cb6..ebf9bb81e 100644 --- a/exercises/practice/two-bucket/.meta/test_template.tera +++ b/exercises/practice/two-bucket/.meta/test_template.tera @@ -11,7 +11,7 @@ use two_bucket::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | slugify | replace(from="-", to="_") }}() { +fn {{ test.description | snake_case }}() { let output = solve( {{ test.input.bucketOne }}, {{ test.input.bucketTwo }}, {{ test.input.goal }}, &{{ self::bucket(label=test.input.startBucket) }}, diff --git a/exercises/practice/variable-length-quantity/.meta/test_template.tera b/exercises/practice/variable-length-quantity/.meta/test_template.tera index 7e6441992..7f2203e15 100644 --- a/exercises/practice/variable-length-quantity/.meta/test_template.tera +++ b/exercises/practice/variable-length-quantity/.meta/test_template.tera @@ -2,7 +2,7 @@ use variable_length_quantity as vlq; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | slugify | replace(from="-", to="_") }}() { +fn {{ test.description | snake_case }}() { {%- if test.property == "encode" %} let input = &{{ test.input.integers | json_encode() }}; let output = vlq::to_bytes(input); diff --git a/exercises/practice/word-count/.meta/test_template.tera b/exercises/practice/word-count/.meta/test_template.tera index f459c4d16..6e313e057 100644 --- a/exercises/practice/word-count/.meta/test_template.tera +++ b/exercises/practice/word-count/.meta/test_template.tera @@ -3,7 +3,7 @@ use word_count::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | slugify | replace(from="-", to="_") }}() { +fn {{ test.description | snake_case }}() { let input = {{ test.input.sentence | json_encode() }}; let mut output = word_count(input); let expected = [{% for key, value in test.expected -%} diff --git a/exercises/practice/wordy/.meta/test_template.tera b/exercises/practice/wordy/.meta/test_template.tera index 831752169..b6e729d01 100644 --- a/exercises/practice/wordy/.meta/test_template.tera +++ b/exercises/practice/wordy/.meta/test_template.tera @@ -6,7 +6,7 @@ use wordy::*; {% if test.property == "exponentials" -%} #[cfg(feature = "exponentials")] {% endif -%} -fn {{ test.description | slugify | replace(from="-", to="_") }}() { +fn {{ test.description | snake_case }}() { let input = {{ test.input.question | json_encode() }}; let output = answer(input); let expected = {% if test.expected is object -%} diff --git a/rust-tooling/Cargo.lock b/rust-tooling/Cargo.lock index 105856035..a96595857 100644 --- a/rust-tooling/Cargo.lock +++ b/rust-tooling/Cargo.lock @@ -363,6 +363,7 @@ dependencies = [ "inquire", "models", "serde_json", + "slug", "tera", "utils", "uuid", diff --git a/rust-tooling/generate/Cargo.toml b/rust-tooling/generate/Cargo.toml index 05c31d74a..2484aab54 100644 --- a/rust-tooling/generate/Cargo.toml +++ b/rust-tooling/generate/Cargo.toml @@ -13,6 +13,7 @@ glob = "0.3.1" inquire = "0.6.2" models = { version = "0.1.0", path = "../models" } serde_json = { version = "1.0.105", features = ["preserve_order"] } +slug = "0.1.5" tera = "1.19.1" utils = { version = "0.1.0", path = "../utils" } uuid = { version = "1.4.1", features = ["v4"] } diff --git a/rust-tooling/generate/src/custom_filters.rs b/rust-tooling/generate/src/custom_filters.rs new file mode 100644 index 000000000..1caffa2a6 --- /dev/null +++ b/rust-tooling/generate/src/custom_filters.rs @@ -0,0 +1,21 @@ +use std::collections::HashMap; + +use tera::{Result, Value}; + +type Filter = fn(&Value, &HashMap) -> Result; + +pub static CUSTOM_FILTERS: &[(&str, Filter)] = &[("to_hex", to_hex), ("snake_case", snake_case)]; + +pub fn to_hex(value: &Value, _args: &HashMap) -> tera::Result { + Ok(serde_json::Value::String(format!( + "{:x}", + value.as_u64().unwrap() + ))) +} + +pub fn snake_case(value: &Value, _args: &HashMap) -> Result { + Ok(serde_json::Value::String( + // slug is the same dependency tera uses for its builtin 'slugify' + slug::slugify(value.as_str().unwrap()).replace('-', "_"), + )) +} diff --git a/rust-tooling/generate/src/lib.rs b/rust-tooling/generate/src/lib.rs index ceaf4f2b3..64a27dc53 100644 --- a/rust-tooling/generate/src/lib.rs +++ b/rust-tooling/generate/src/lib.rs @@ -1,16 +1,18 @@ use std::{ - collections::HashMap, io::Write, process::{Command, Stdio}, }; use tera::{Context, Tera}; +use custom_filters::CUSTOM_FILTERS; use models::{ exercise_config::get_excluded_tests, problem_spec::{get_additional_test_cases, get_canonical_data, SingleTestCase, TestCase}, }; +mod custom_filters; + pub struct GeneratedExercise { pub gitignore: String, pub manifest: String, @@ -75,13 +77,6 @@ fn extend_single_cases(single_cases: &mut Vec, cases: Vec) -> tera::Result { - Ok(serde_json::Value::String(format!( - "{:x}", - value.as_u64().unwrap() - ))) -} - fn generate_tests(slug: &str) -> String { let cases = { let mut cases = get_canonical_data(slug) @@ -97,7 +92,9 @@ fn generate_tests(slug: &str) -> String { .add_raw_template("test_template.tera", TEST_TEMPLATE) .unwrap(); } - template.register_filter("to_hex", to_hex); + for (name, filter) in CUSTOM_FILTERS { + template.register_filter(name, filter); + } let mut single_cases = Vec::new(); extend_single_cases(&mut single_cases, cases); From c341cc1287206a6720c28bdef2103f44bb0b4e09 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 2 Apr 2024 10:07:26 +0200 Subject: [PATCH 270/436] generator: improve error handling (#1888) --- rust-tooling/Cargo.lock | 7 +++ rust-tooling/generate/Cargo.toml | 6 ++ rust-tooling/generate/src/cli.rs | 60 ++++++++++++------- rust-tooling/generate/src/custom_filters.rs | 21 +++++-- rust-tooling/generate/src/lib.rs | 50 ++++++++-------- rust-tooling/generate/src/main.rs | 46 +++++++------- .../tests/tera_templates_are_in_sync.rs | 4 +- 7 files changed, 117 insertions(+), 77 deletions(-) diff --git a/rust-tooling/Cargo.lock b/rust-tooling/Cargo.lock index a96595857..1d2c0cc53 100644 --- a/rust-tooling/Cargo.lock +++ b/rust-tooling/Cargo.lock @@ -74,6 +74,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "anyhow" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" + [[package]] name = "autocfg" version = "1.1.0" @@ -357,6 +363,7 @@ checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" name = "generate" version = "0.1.0" dependencies = [ + "anyhow", "clap", "convert_case", "glob", diff --git a/rust-tooling/generate/Cargo.toml b/rust-tooling/generate/Cargo.toml index 2484aab54..5e04e0bd1 100644 --- a/rust-tooling/generate/Cargo.toml +++ b/rust-tooling/generate/Cargo.toml @@ -6,7 +6,13 @@ description = "Generates exercise boilerplate, especially test cases" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lints.clippy] +unwrap_used = "warn" +expect_used = "warn" +panic = "warn" + [dependencies] +anyhow = "1.0.81" clap = { version = "4.4.8", features = ["derive"] } convert_case = "0.6.0" glob = "0.3.1" diff --git a/rust-tooling/generate/src/cli.rs b/rust-tooling/generate/src/cli.rs index 9c24bdff2..7d1c03202 100644 --- a/rust-tooling/generate/src/cli.rs +++ b/rust-tooling/generate/src/cli.rs @@ -1,3 +1,4 @@ +use anyhow::{Context, Result}; use clap::{Parser, Subcommand}; use convert_case::{Case, Casing}; use glob::glob; @@ -45,15 +46,25 @@ pub struct FullAddArgs { } impl AddArgs { - pub fn unwrap_args_or_prompt(self) -> FullAddArgs { - let slug = self.slug.unwrap_or_else(prompt_for_add_slug); - let name = self.name.unwrap_or_else(|| prompt_for_exercise_name(&slug)); - let difficulty = self.difficulty.unwrap_or_else(prompt_for_difficulty).into(); - FullAddArgs { + pub fn unwrap_args_or_prompt(self) -> Result { + let slug = match self.slug { + Some(slug) => slug, + _ => prompt_for_add_slug()?, + }; + let name = match self.name { + Some(name) => name, + None => prompt_for_exercise_name(&slug)?, + }; + let difficulty = match self.difficulty { + Some(diff) => diff, + None => prompt_for_difficulty()?, + } + .into(); + Ok(FullAddArgs { slug, name, difficulty, - } + }) } } @@ -65,16 +76,19 @@ pub struct UpdateArgs { } impl UpdateArgs { - pub fn unwrap_slug_or_prompt(self) -> String { - self.slug.unwrap_or_else(prompt_for_update_slug) + pub fn unwrap_slug_or_prompt(self) -> Result { + match self.slug { + Some(slug) => Ok(slug), + _ => prompt_for_update_slug(), + } } } -pub fn prompt_for_update_slug() -> String { +pub fn prompt_for_update_slug() -> Result { let implemented_exercises = glob("exercises/practice/*") - .unwrap() + .map_err(anyhow::Error::from)? .filter_map(Result::ok) - .map(|path| path.file_name().unwrap().to_str().unwrap().to_string()) + .flat_map(|path| path.file_name()?.to_str().map(|s| s.to_owned())) .collect::>(); Select::new( @@ -82,21 +96,21 @@ pub fn prompt_for_update_slug() -> String { implemented_exercises, ) .prompt() - .unwrap() + .context("failed to select slug") } -pub fn prompt_for_add_slug() -> String { +pub fn prompt_for_add_slug() -> Result { let implemented_exercises = glob("exercises/concept/*") - .unwrap() - .chain(glob("exercises/practice/*").unwrap()) + .map_err(anyhow::Error::from)? + .chain(glob("exercises/practice/*").map_err(anyhow::Error::from)?) .filter_map(Result::ok) - .map(|path| path.file_name().unwrap().to_str().unwrap().to_string()) + .flat_map(|path| path.file_name()?.to_str().map(|s| s.to_owned())) .collect::>(); let todo_with_spec = glob("problem-specifications/exercises/*") - .unwrap() + .map_err(anyhow::Error::from)? .filter_map(Result::ok) - .map(|path| path.file_name().unwrap().to_str().unwrap().to_string()) + .flat_map(|path| path.file_name()?.to_str().map(|s| s.to_owned())) .filter(|e| !implemented_exercises.contains(e)) .collect::>(); @@ -128,14 +142,14 @@ pub fn prompt_for_add_slug() -> String { } }) .prompt() - .unwrap() + .context("failed to prompt for slug") } -pub fn prompt_for_exercise_name(slug: &str) -> String { +pub fn prompt_for_exercise_name(slug: &str) -> Result { Text::new("What's the name of your exercise?") .with_initial_value(&slug.to_case(Case::Title)) .prompt() - .unwrap() + .context("failed to prompt for exercise name") } /// Mostly a clone of the `Difficulty` enum from `models::track_config`. @@ -172,7 +186,7 @@ impl std::fmt::Display for Difficulty { } } -pub fn prompt_for_difficulty() -> Difficulty { +pub fn prompt_for_difficulty() -> Result { Select::new( "What's the difficulty of your exercise?", vec![ @@ -183,5 +197,5 @@ pub fn prompt_for_difficulty() -> Difficulty { ], ) .prompt() - .unwrap() + .context("failed to select difficulty") } diff --git a/rust-tooling/generate/src/custom_filters.rs b/rust-tooling/generate/src/custom_filters.rs index 1caffa2a6..23e17154a 100644 --- a/rust-tooling/generate/src/custom_filters.rs +++ b/rust-tooling/generate/src/custom_filters.rs @@ -6,16 +6,25 @@ type Filter = fn(&Value, &HashMap) -> Result; pub static CUSTOM_FILTERS: &[(&str, Filter)] = &[("to_hex", to_hex), ("snake_case", snake_case)]; -pub fn to_hex(value: &Value, _args: &HashMap) -> tera::Result { - Ok(serde_json::Value::String(format!( - "{:x}", - value.as_u64().unwrap() - ))) +pub fn to_hex(value: &Value, _args: &HashMap) -> Result { + let Some(value) = value.as_u64() else { + return Err(tera::Error::call_filter( + "to_hex filter expects an unsigned integer", + "serde_json::value::Value::as_u64", + )); + }; + Ok(serde_json::Value::String(format!("{:x}", value))) } pub fn snake_case(value: &Value, _args: &HashMap) -> Result { + let Some(value) = value.as_str() else { + return Err(tera::Error::call_filter( + "snake_case filter expects a string", + "serde_json::value::Value::as_str", + )); + }; Ok(serde_json::Value::String( // slug is the same dependency tera uses for its builtin 'slugify' - slug::slugify(value.as_str().unwrap()).replace('-', "_"), + slug::slugify(value).replace('-', "_"), )) } diff --git a/rust-tooling/generate/src/lib.rs b/rust-tooling/generate/src/lib.rs index 64a27dc53..42588a0e0 100644 --- a/rust-tooling/generate/src/lib.rs +++ b/rust-tooling/generate/src/lib.rs @@ -3,7 +3,8 @@ use std::{ process::{Command, Stdio}, }; -use tera::{Context, Tera}; +use anyhow::{Context, Result}; +use tera::Tera; use custom_filters::CUSTOM_FILTERS; use models::{ @@ -22,17 +23,17 @@ pub struct GeneratedExercise { pub tests: String, } -pub fn new(slug: &str) -> GeneratedExercise { +pub fn new(slug: &str) -> Result { let crate_name = slug.replace('-', "_"); - GeneratedExercise { + Ok(GeneratedExercise { gitignore: GITIGNORE.into(), manifest: generate_manifest(&crate_name), lib_rs: LIB_RS.into(), example: EXAMPLE_RS.into(), test_template: TEST_TEMPLATE.into(), - tests: generate_tests(slug), - } + tests: generate_tests(slug)?, + }) } static GITIGNORE: &str = "\ @@ -77,7 +78,7 @@ fn extend_single_cases(single_cases: &mut Vec, cases: Vec String { +fn generate_tests(slug: &str) -> Result { let cases = { let mut cases = get_canonical_data(slug) .map(|data| data.cases) @@ -86,11 +87,9 @@ fn generate_tests(slug: &str) -> String { cases }; let excluded_tests = get_excluded_tests(slug); - let mut template = get_test_template(slug).unwrap(); - if template.get_template_names().next().is_none() { - template - .add_raw_template("test_template.tera", TEST_TEMPLATE) - .unwrap(); + let mut template = get_test_template(slug).context("failed to get test template")?; + if template.get_template_names().next() != Some("test_template.tera") { + anyhow::bail!("'test_template.tera' not found"); } for (name, filter) in CUSTOM_FILTERS { template.register_filter(name, filter); @@ -100,12 +99,12 @@ fn generate_tests(slug: &str) -> String { extend_single_cases(&mut single_cases, cases); single_cases.retain(|case| !excluded_tests.contains(&case.uuid)); - let mut context = Context::new(); + let mut context = tera::Context::new(); context.insert("cases", &single_cases); let rendered = template .render("test_template.tera", &context) - .unwrap_or_else(|_| panic!("failed to render template of '{slug}'")); + .with_context(|| format!("failed to render template of '{slug}'"))?; // Remove ignore-annotation on first test. // This could be done in the template itself, @@ -120,25 +119,25 @@ fn generate_tests(slug: &str) -> String { .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn() - .expect("failed to spawn process"); + .context("failed to spawn rustfmt process")?; child .stdin .as_mut() - .unwrap() + .context("failed to get rustfmt's stdin")? .write_all(rendered.as_bytes()) - .unwrap(); - let rustfmt_out = child.wait_with_output().unwrap(); + .context("failed to write to rustfmt's stdin")?; + let rustfmt_out = child + .wait_with_output() + .context("failed to get rustfmt's output")?; - if rustfmt_out.status.success() { - String::from_utf8(rustfmt_out.stdout).unwrap() - } else { - let rustfmt_error = String::from_utf8(rustfmt_out.stderr).unwrap(); + if !rustfmt_out.status.success() { + let rustfmt_error = String::from_utf8_lossy(&rustfmt_out.stderr); let mut last_16_error_lines = rustfmt_error.lines().rev().take(16).collect::>(); last_16_error_lines.reverse(); let last_16_error_lines = last_16_error_lines.join("\n"); - println!( + eprintln!( "{last_16_error_lines}\ ^ last 16 lines of errors from rustfmt Check the test template (.meta/test_template.tera) @@ -146,10 +145,11 @@ It probably generates invalid Rust code." ); // still return the unformatted content to be written to the file - rendered + return Ok(rendered); } + Ok(String::from_utf8_lossy(&rustfmt_out.stdout).into_owned()) } -pub fn get_test_template(slug: &str) -> Option { - Some(Tera::new(format!("exercises/practice/{slug}/.meta/*.tera").as_str()).unwrap()) +pub fn get_test_template(slug: &str) -> Result { + Tera::new(format!("exercises/practice/{slug}/.meta/*.tera").as_str()).map_err(Into::into) } diff --git a/rust-tooling/generate/src/main.rs b/rust-tooling/generate/src/main.rs index ea3a8905b..9d8514efd 100644 --- a/rust-tooling/generate/src/main.rs +++ b/rust-tooling/generate/src/main.rs @@ -1,12 +1,13 @@ use std::path::PathBuf; +use anyhow::{Context, Result}; use clap::Parser; use cli::{AddArgs, FullAddArgs, UpdateArgs}; use models::track_config::{self, TRACK_CONFIG}; mod cli; -fn main() { +fn main() -> Result<()> { utils::fs::cd_into_repo_root(); let cli_args = cli::CliArgs::parse(); @@ -17,22 +18,22 @@ fn main() { } } -fn add_exercise(args: AddArgs) { +fn add_exercise(args: AddArgs) -> Result<()> { let FullAddArgs { slug, name, difficulty, - } = args.unwrap_args_or_prompt(); + } = args.unwrap_args_or_prompt()?; let config = track_config::PracticeExercise::new(slug.clone(), name, difficulty); let mut track_config = TRACK_CONFIG.clone(); track_config.exercises.practice.push(config); let mut new_config = serde_json::to_string_pretty(&track_config) - .unwrap() + .context("failed to deserialize track config")? .to_string(); new_config += "\n"; - std::fs::write("config.json", new_config).unwrap(); + std::fs::write("config.json", new_config)?; println!( "\ @@ -40,22 +41,22 @@ Added your exercise to config.json. You can add practices, prerequisites and topics if you like." ); - make_configlet_generate_what_it_can(&slug); + make_configlet_generate_what_it_can(&slug)?; let is_update = false; - generate_exercise_files(&slug, is_update); + generate_exercise_files(&slug, is_update) } -fn update_exercise(args: UpdateArgs) { - let slug = args.unwrap_slug_or_prompt(); +fn update_exercise(args: UpdateArgs) -> Result<()> { + let slug = args.unwrap_slug_or_prompt()?; - make_configlet_generate_what_it_can(&slug); + make_configlet_generate_what_it_can(&slug)?; let is_update = true; - generate_exercise_files(&slug, is_update); + generate_exercise_files(&slug, is_update) } -fn make_configlet_generate_what_it_can(slug: &str) { +fn make_configlet_generate_what_it_can(slug: &str) -> Result<()> { let status = std::process::Command::new("just") .args([ "configlet", @@ -70,28 +71,29 @@ fn make_configlet_generate_what_it_can(slug: &str) { slug, ]) .status() - .unwrap(); + .context("failed to run configlet sync")?; if !status.success() { - panic!("configlet sync failed"); + anyhow::bail!("configlet sync failed"); } + Ok(()) } -fn generate_exercise_files(slug: &str, is_update: bool) { - let exercise = generate::new(slug); +fn generate_exercise_files(slug: &str, is_update: bool) -> Result<()> { + let exercise = generate::new(slug).context("failed to generate exercise")?; let exercise_path = PathBuf::from("exercises/practice").join(slug); if !is_update { - std::fs::write(exercise_path.join(".gitignore"), exercise.gitignore).unwrap(); - std::fs::write(exercise_path.join("Cargo.toml"), exercise.manifest).unwrap(); + std::fs::write(exercise_path.join(".gitignore"), exercise.gitignore)?; + std::fs::write(exercise_path.join("Cargo.toml"), exercise.manifest)?; std::fs::create_dir(exercise_path.join("src")).ok(); - std::fs::write(exercise_path.join("src/lib.rs"), exercise.lib_rs).unwrap(); - std::fs::write(exercise_path.join(".meta/example.rs"), exercise.example).unwrap(); + std::fs::write(exercise_path.join("src/lib.rs"), exercise.lib_rs)?; + std::fs::write(exercise_path.join(".meta/example.rs"), exercise.example)?; } let template_path = exercise_path.join(".meta/test_template.tera"); if std::fs::metadata(&template_path).is_err() { - std::fs::write(template_path, exercise.test_template).unwrap(); + std::fs::write(template_path, exercise.test_template)?; } std::fs::create_dir(exercise_path.join("tests")).ok(); @@ -99,5 +101,5 @@ fn generate_exercise_files(slug: &str, is_update: bool) { exercise_path.join(format!("tests/{slug}.rs")), exercise.tests, ) - .unwrap(); + .map_err(Into::into) } diff --git a/rust-tooling/generate/tests/tera_templates_are_in_sync.rs b/rust-tooling/generate/tests/tera_templates_are_in_sync.rs index a8c81c14d..62100c36f 100644 --- a/rust-tooling/generate/tests/tera_templates_are_in_sync.rs +++ b/rust-tooling/generate/tests/tera_templates_are_in_sync.rs @@ -1,6 +1,8 @@ //! This test is not run in CI. Doing so would slow down CI noticeably, because //! exercise generation depends on `tera`, which takes a while to compile. +#![allow(clippy::unwrap_used, clippy::panic)] + use glob::glob; use utils::fs::cd_into_repo_root; @@ -12,7 +14,7 @@ fn tera_templates_are_in_sync() { let exercise_dir = path.parent().unwrap().parent().unwrap(); let slug = exercise_dir.file_name().unwrap().to_string_lossy(); - let generated = generate::new(&slug); + let generated = generate::new(&slug).unwrap(); let test_path = exercise_dir.join("tests").join(format!("{slug}.rs")); let on_disk = std::fs::read_to_string(test_path).unwrap(); From e318d66527420b800591fefb9a0fb6aff7ab4f8c Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 2 Apr 2024 10:12:07 +0200 Subject: [PATCH 271/436] generator: retain nested test case structure (#1889) Previously, the generator flattened any nesting of test cases from problem-specifications. This worked alright for medium-complexity exercises. However, it makes writing a functional test template harder in the general case. An example is the exercise `forth`, which has test cases with _the same description_ across different test groups. Flattening the groups leads to tests with the same name being generated. Preserving the structure makes it possible to organize the tests into one module per test group, solving the naming conflicts. --- .../affine-cipher/.meta/test_template.tera | 5 +- .../allergies/.meta/test_template.tera | 4 +- .../custom-set/.meta/test_template.tera | 5 +- .../perfect-numbers/.meta/test_template.tera | 4 +- .../pig-latin/.meta/test_template.tera | 4 +- .../queen-attack/.meta/test_template.tera | 4 +- .../.meta/additional-tests.json | 29 +++-- .../.meta/test_template.tera | 5 +- .../robot-simulator/.meta/test_template.tera | 4 +- .../.meta/test_template.tera | 4 +- .../triangle/.meta/additional-tests.json | 103 +++++++++--------- .../triangle/.meta/test_template.tera | 39 +++---- .../.meta/test_template.tera | 5 +- .../tests/additional_tests_are_documented.rs | 22 ++-- rust-tooling/generate/src/lib.rs | 21 ++-- 15 files changed, 144 insertions(+), 114 deletions(-) diff --git a/exercises/practice/affine-cipher/.meta/test_template.tera b/exercises/practice/affine-cipher/.meta/test_template.tera index 4db5f54b9..8f26ae364 100644 --- a/exercises/practice/affine-cipher/.meta/test_template.tera +++ b/exercises/practice/affine-cipher/.meta/test_template.tera @@ -1,7 +1,9 @@ use affine_cipher::*; use affine_cipher::AffineCipherError::NotCoprime; -{% for test in cases %} + +{% for test_group in cases %} +{% for test in test_group.cases %} #[test] #[ignore] fn {{ test.description | snake_case }}() { @@ -16,3 +18,4 @@ fn {{ test.description | snake_case }}() { assert_eq!(output, expected); } {% endfor -%} +{% endfor -%} diff --git a/exercises/practice/allergies/.meta/test_template.tera b/exercises/practice/allergies/.meta/test_template.tera index d74ecc449..da8cced1e 100644 --- a/exercises/practice/allergies/.meta/test_template.tera +++ b/exercises/practice/allergies/.meta/test_template.tera @@ -14,7 +14,8 @@ fn compare_allergy_vectors(expected: &[Allergen], actual: &[Allergen]) { } } -{% for test in cases %} +{% for test_group in cases %} +{% for test in test_group.cases %} #[test] #[ignore] {%- if test.property == "allergicTo" %} @@ -39,3 +40,4 @@ fn {{ test.description | snake_case }}() { {% endif -%} } {% endfor -%} +{% endfor -%} diff --git a/exercises/practice/custom-set/.meta/test_template.tera b/exercises/practice/custom-set/.meta/test_template.tera index 040bf84eb..b97fda33c 100644 --- a/exercises/practice/custom-set/.meta/test_template.tera +++ b/exercises/practice/custom-set/.meta/test_template.tera @@ -1,5 +1,7 @@ use custom_set::CustomSet; -{% for test in cases %} + +{% for test_group in cases %} +{% for test in test_group.cases %} #[test] #[ignore] fn {{ test.description | snake_case }}() { @@ -56,3 +58,4 @@ fn {{ test.description | snake_case }}() { {%- endif %} } {% endfor -%} +{% endfor -%} diff --git a/exercises/practice/perfect-numbers/.meta/test_template.tera b/exercises/practice/perfect-numbers/.meta/test_template.tera index e148b3392..4f3e334b4 100644 --- a/exercises/practice/perfect-numbers/.meta/test_template.tera +++ b/exercises/practice/perfect-numbers/.meta/test_template.tera @@ -1,6 +1,7 @@ use perfect_numbers::*; -{% for test in cases %} +{% for test_group in cases %} +{% for test in test_group.cases %} #[test] #[ignore] fn {{ test.description | snake_case }}() { @@ -14,3 +15,4 @@ fn {{ test.description | snake_case }}() { {% endif -%} } {% endfor -%} +{% endfor -%} diff --git a/exercises/practice/pig-latin/.meta/test_template.tera b/exercises/practice/pig-latin/.meta/test_template.tera index 3892ecbb6..0316cfc77 100644 --- a/exercises/practice/pig-latin/.meta/test_template.tera +++ b/exercises/practice/pig-latin/.meta/test_template.tera @@ -1,6 +1,7 @@ use pig_latin::*; -{% for test in cases %} +{% for test_group in cases %} +{% for test in test_group.cases %} #[test] #[ignore] fn {{ test.description | snake_case }}() { @@ -10,3 +11,4 @@ fn {{ test.description | snake_case }}() { assert_eq!(output, expected); } {% endfor -%} +{% endfor -%} diff --git a/exercises/practice/queen-attack/.meta/test_template.tera b/exercises/practice/queen-attack/.meta/test_template.tera index 5221ee6f5..642f877f3 100644 --- a/exercises/practice/queen-attack/.meta/test_template.tera +++ b/exercises/practice/queen-attack/.meta/test_template.tera @@ -1,6 +1,7 @@ use queen_attack::*; -{% for test in cases %} +{% for test_group in cases %} +{% for test in test_group.cases %} #[test] #[ignore] fn {{ test.description | snake_case }}() { @@ -22,3 +23,4 @@ fn {{ test.description | snake_case }}() { {% endif %} } {% endfor -%} +{% endfor -%} diff --git a/exercises/practice/rail-fence-cipher/.meta/additional-tests.json b/exercises/practice/rail-fence-cipher/.meta/additional-tests.json index e25105de8..ef3a0f2fb 100644 --- a/exercises/practice/rail-fence-cipher/.meta/additional-tests.json +++ b/exercises/practice/rail-fence-cipher/.meta/additional-tests.json @@ -1,16 +1,21 @@ [ { - "uuid": "46dc5c50-5538-401d-93a5-41102680d068", - "description": "encode wide characters", - "comments": [ - "Unicode tests are not suitable to be upstreamed.", - "Handling unicode is tedious in many languages." - ], - "property": "encode", - "input": { - "msg": "古池蛙飛び込む水の音", - "rails": 3 - }, - "expected": "古びの池飛込水音蛙む" + "description": "unicode", + "cases": [ + { + "uuid": "46dc5c50-5538-401d-93a5-41102680d068", + "description": "encode wide characters", + "comments": [ + "Unicode tests are not suitable to be upstreamed.", + "Handling unicode is tedious in many languages." + ], + "property": "encode", + "input": { + "msg": "古池蛙飛び込む水の音", + "rails": 3 + }, + "expected": "古びの池飛込水音蛙む" + } + ] } ] diff --git a/exercises/practice/rail-fence-cipher/.meta/test_template.tera b/exercises/practice/rail-fence-cipher/.meta/test_template.tera index 5f6ad063e..e0c6de5fa 100644 --- a/exercises/practice/rail-fence-cipher/.meta/test_template.tera +++ b/exercises/practice/rail-fence-cipher/.meta/test_template.tera @@ -1,5 +1,7 @@ use rail_fence_cipher::RailFence; -{% for test in cases %} + +{% for test_group in cases %} +{% for test in test_group.cases %} #[test] #[ignore] fn {{ test.description | snake_case }}() { @@ -15,3 +17,4 @@ fn {{ test.description | snake_case }}() { assert_eq!(output, expected); } {% endfor -%} +{% endfor -%} diff --git a/exercises/practice/robot-simulator/.meta/test_template.tera b/exercises/practice/robot-simulator/.meta/test_template.tera index 2edb15c45..6581819e1 100644 --- a/exercises/practice/robot-simulator/.meta/test_template.tera +++ b/exercises/practice/robot-simulator/.meta/test_template.tera @@ -1,6 +1,7 @@ use robot_simulator::*; -{% for test in cases %} +{% for test_group in cases %} +{% for test in test_group.cases %} #[test] #[ignore] fn {{ test.description | snake_case }}() { @@ -26,3 +27,4 @@ fn {{ test.description | snake_case }}() { assert_eq!(robot_end.direction(), &Direction::{{ test.expected.direction | title }}); } {% endfor -%} +{% endfor -%} diff --git a/exercises/practice/run-length-encoding/.meta/test_template.tera b/exercises/practice/run-length-encoding/.meta/test_template.tera index c1776ddba..4616df818 100644 --- a/exercises/practice/run-length-encoding/.meta/test_template.tera +++ b/exercises/practice/run-length-encoding/.meta/test_template.tera @@ -1,6 +1,7 @@ use run_length_encoding as rle; -{% for test in cases %} +{% for test_group in cases %} +{% for test in test_group.cases %} #[test] #[ignore] fn {{ test.property }}_{{ test.description | snake_case }}() { @@ -14,3 +15,4 @@ fn {{ test.property }}_{{ test.description | snake_case }}() { assert_eq!(output, expected); } {% endfor -%} +{% endfor -%} diff --git a/exercises/practice/triangle/.meta/additional-tests.json b/exercises/practice/triangle/.meta/additional-tests.json index b797996a3..617a06a15 100644 --- a/exercises/practice/triangle/.meta/additional-tests.json +++ b/exercises/practice/triangle/.meta/additional-tests.json @@ -1,52 +1,57 @@ [ { - "uuid": "3ddc03ad-4066-41b0-8ba2-1b0ce0146064", - "description": "all zero sides is not a triangle", - "comments": ["reimplements 16e8ceb0-eadb-46d1-b892-c50327479251"], - "property": "invalid", - "input": { - "sides": [0, 0, 0] - }, - "expected": false - }, - { - "uuid": "3ddc03ad-4066-41b0-8ba2-1b0ce0146064", - "description": "first triangle inequality violation", - "comments": ["reimplements 2eba0cfb-6c65-4c40-8146-30b608905eae"], - "property": "invalid", - "input": { - "sides": [1, 1, 3] - }, - "expected": false - }, - { - "uuid": "3ddc03ad-4066-41b0-8ba2-1b0ce0146064", - "description": "second triangle inequality violation", - "comments": ["reimplements 278469cb-ac6b-41f0-81d4-66d9b828f8ac"], - "property": "invalid", - "input": { - "sides": [1, 3, 1] - }, - "expected": false - }, - { - "uuid": "3ddc03ad-4066-41b0-8ba2-1b0ce0146064", - "description": "third triangle inequality violation", - "comments": ["reimplements 90efb0c7-72bb-4514-b320-3a3892e278ff"], - "property": "invalid", - "input": { - "sides": [3, 1, 1] - }, - "expected": false - }, - { - "uuid": "3ddc03ad-4066-41b0-8ba2-1b0ce0146064", - "description": "may not violate triangle inequality", - "comments": ["reimplements 70ad5154-0033-48b7-af2c-b8d739cd9fdc"], - "property": "invalid", - "input": { - "sides": [7, 3, 2] - }, - "expected": false + "description": "invalid triangle", + "cases": [ + { + "uuid": "3ddc03ad-4066-41b0-8ba2-1b0ce0146064", + "description": "all zero sides is not a triangle", + "comments": ["reimplements 16e8ceb0-eadb-46d1-b892-c50327479251"], + "property": "invalid", + "input": { + "sides": [0, 0, 0] + }, + "expected": false + }, + { + "uuid": "3ddc03ad-4066-41b0-8ba2-1b0ce0146064", + "description": "first triangle inequality violation", + "comments": ["reimplements 2eba0cfb-6c65-4c40-8146-30b608905eae"], + "property": "invalid", + "input": { + "sides": [1, 1, 3] + }, + "expected": false + }, + { + "uuid": "3ddc03ad-4066-41b0-8ba2-1b0ce0146064", + "description": "second triangle inequality violation", + "comments": ["reimplements 278469cb-ac6b-41f0-81d4-66d9b828f8ac"], + "property": "invalid", + "input": { + "sides": [1, 3, 1] + }, + "expected": false + }, + { + "uuid": "3ddc03ad-4066-41b0-8ba2-1b0ce0146064", + "description": "third triangle inequality violation", + "comments": ["reimplements 90efb0c7-72bb-4514-b320-3a3892e278ff"], + "property": "invalid", + "input": { + "sides": [3, 1, 1] + }, + "expected": false + }, + { + "uuid": "3ddc03ad-4066-41b0-8ba2-1b0ce0146064", + "description": "may not violate triangle inequality", + "comments": ["reimplements 70ad5154-0033-48b7-af2c-b8d739cd9fdc"], + "property": "invalid", + "input": { + "sides": [7, 3, 2] + }, + "expected": false + } + ] } -] \ No newline at end of file +] diff --git a/exercises/practice/triangle/.meta/test_template.tera b/exercises/practice/triangle/.meta/test_template.tera index 53a32cece..329f8ff95 100644 --- a/exercises/practice/triangle/.meta/test_template.tera +++ b/exercises/practice/triangle/.meta/test_template.tera @@ -1,8 +1,10 @@ -mod equilateral { -use triangle::Triangle; -{% for test in cases %} -{% if test.property != "equilateral" %}{% continue %}{% endif %} +{% for test_group in cases %} +mod {{ test_group.description | split(pat=" ") | first }} { + use triangle::Triangle; +{% for test in test_group.cases %} + +{% if test.property == "equilateral" %} #[test] #[ignore] {% if test.description is containing("float") %} @@ -17,14 +19,8 @@ fn {{ test.description | snake_case }}() { assert!(!output.is_equilateral()); {% endif -%} } -{% endfor -%} -} - -mod isosceles { -use triangle::Triangle; -{% for test in cases %} -{% if test.property != "isosceles" %}{% continue %}{% endif %} +{% elif test.property == "isosceles" %} #[test] #[ignore] {% if test.scenarios and test.scenarios is containing("floating-point") %} @@ -39,14 +35,8 @@ fn {{ test.description | snake_case }}() { assert!(!output.is_isosceles()); {% endif -%} } -{% endfor -%} -} - -mod scalene { -use triangle::Triangle; -{% for test in cases %} -{% if test.property != "scalene" %}{% continue %}{% endif %} +{% elif test.property == "scalene" %} #[test] #[ignore] {% if test.scenarios and test.scenarios is containing("floating-point") %} @@ -61,14 +51,8 @@ fn {{ test.description | snake_case }}() { assert!(!output.is_scalene()); {% endif -%} } -{% endfor -%} -} - -mod invalid { -use triangle::Triangle; -{% for test in cases %} -{% if test.property != "invalid" %}{% continue %}{% endif %} +{% elif test.property == "invalid" %} #[test] #[ignore] {% if test.scenarios and test.scenarios is containing("floating-point") %} @@ -79,5 +63,8 @@ fn {{ test.description | snake_case }}() { let output = Triangle::build(input); assert!(output.is_none()); } -{% endfor -%} + +{% endif %} +{% endfor %} } +{% endfor %} diff --git a/exercises/practice/variable-length-quantity/.meta/test_template.tera b/exercises/practice/variable-length-quantity/.meta/test_template.tera index 7f2203e15..b8c8c450a 100644 --- a/exercises/practice/variable-length-quantity/.meta/test_template.tera +++ b/exercises/practice/variable-length-quantity/.meta/test_template.tera @@ -1,5 +1,7 @@ use variable_length_quantity as vlq; -{% for test in cases %} + +{% for test_group in cases %} +{% for test in test_group.cases %} #[test] #[ignore] fn {{ test.description | snake_case }}() { @@ -29,3 +31,4 @@ fn {{ test.description | snake_case }}() { assert_eq!(output, expected); } {% endfor -%} +{% endfor -%} diff --git a/rust-tooling/ci-tests/tests/additional_tests_are_documented.rs b/rust-tooling/ci-tests/tests/additional_tests_are_documented.rs index 530817ef7..685a87984 100644 --- a/rust-tooling/ci-tests/tests/additional_tests_are_documented.rs +++ b/rust-tooling/ci-tests/tests/additional_tests_are_documented.rs @@ -1,5 +1,7 @@ +use std::path::Path; + use glob::glob; -use models::problem_spec::SingleTestCase; +use models::problem_spec::TestCase; use utils::fs::cd_into_repo_root; #[test] @@ -9,14 +11,20 @@ fn additional_tests_are_documented() { let path = entry.unwrap(); let f = std::fs::File::open(&path).unwrap(); let reader = std::io::BufReader::new(f); - let test_cases: Vec = serde_json::from_reader(reader).unwrap(); + let test_cases: Vec = serde_json::from_reader(reader).unwrap(); + fn rec(case: TestCase, path: &Path) { + match case { + TestCase::Single { case } => assert!( + !case.comments.unwrap_or_default().is_empty(), + "missing documentation for additional tests in {}", + path.display() + ), + TestCase::Group { cases, .. } => cases.into_iter().for_each(|c| rec(c, path)), + } + } for case in test_cases { - assert!( - !case.comments.unwrap_or_default().is_empty(), - "missing documentation for additional tests in {}", - path.display() - ); + rec(case, &path); } } } diff --git a/rust-tooling/generate/src/lib.rs b/rust-tooling/generate/src/lib.rs index 42588a0e0..1f2dfa757 100644 --- a/rust-tooling/generate/src/lib.rs +++ b/rust-tooling/generate/src/lib.rs @@ -9,7 +9,7 @@ use tera::Tera; use custom_filters::CUSTOM_FILTERS; use models::{ exercise_config::get_excluded_tests, - problem_spec::{get_additional_test_cases, get_canonical_data, SingleTestCase, TestCase}, + problem_spec::{get_additional_test_cases, get_canonical_data, TestCase}, }; mod custom_filters; @@ -69,17 +69,20 @@ pub fn TODO(input: TODO) -> TODO { static TEST_TEMPLATE: &str = include_str!("../templates/default_test_template.tera"); -fn extend_single_cases(single_cases: &mut Vec, cases: Vec) { +fn remove_excluded_tests(cases: &mut Vec, excluded_tests: &[String]) { + cases.retain(|case| match case { + TestCase::Single { case } => !excluded_tests.contains(&case.uuid), + _ => true, + }); for case in cases { - match case { - TestCase::Single { case } => single_cases.push(case), - TestCase::Group { cases, .. } => extend_single_cases(single_cases, cases), + if let TestCase::Group { cases, .. } = case { + remove_excluded_tests(cases, excluded_tests) } } } fn generate_tests(slug: &str) -> Result { - let cases = { + let mut cases = { let mut cases = get_canonical_data(slug) .map(|data| data.cases) .unwrap_or_default(); @@ -95,12 +98,10 @@ fn generate_tests(slug: &str) -> Result { template.register_filter(name, filter); } - let mut single_cases = Vec::new(); - extend_single_cases(&mut single_cases, cases); - single_cases.retain(|case| !excluded_tests.contains(&case.uuid)); + remove_excluded_tests(&mut cases, &excluded_tests); let mut context = tera::Context::new(); - context.insert("cases", &single_cases); + context.insert("cases", &cases); let rendered = template .render("test_template.tera", &context) From 092d1ecefab6dc9c47e052501a4d1ccf6dae19e8 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Wed, 3 Apr 2024 07:40:21 +0200 Subject: [PATCH 272/436] forth: sync (#1891) [no important files changed] --- .../practice/forth/.meta/test_template.tera | 44 + exercises/practice/forth/.meta/tests.toml | 148 +++- exercises/practice/forth/tests/forth.rs | 808 ++++++++++-------- 3 files changed, 613 insertions(+), 387 deletions(-) create mode 100644 exercises/practice/forth/.meta/test_template.tera diff --git a/exercises/practice/forth/.meta/test_template.tera b/exercises/practice/forth/.meta/test_template.tera new file mode 100644 index 000000000..1cc59aef5 --- /dev/null +++ b/exercises/practice/forth/.meta/test_template.tera @@ -0,0 +1,44 @@ +{% for test_group in cases %} +mod {{ test_group.description | snake_case }} { + use forth::*; + +{% for test in test_group.cases %} +#[test] +#[ignore] +fn {{ test.description | snake_case }}() { + let mut f = Forth::new(); + {% if test.property == "evaluateBoth" -%} + {% for instr in test.input.instructionsFirst -%} + assert!(f.eval("{{ instr }}").is_ok()); + {% endfor -%} + assert_eq!(f.stack(), {{ test.expected[0] | json_encode() }}); + let mut f = Forth::new(); + {% for instr in test.input.instructionsSecond -%} + assert!(f.eval("{{ instr }}").is_ok()); + {% endfor -%} + assert_eq!(f.stack(), {{ test.expected[1] | json_encode() }}); + } + {% continue %} + {% endif -%} + + {% if test.expected is object -%} + {% if test.expected.error == "empty stack" or test.expected.error == "only one value on the stack" -%} + assert_eq!(f.eval("{{ test.input.instructions[0] }}"), Err(Error::StackUnderflow)); + {% elif test.expected.error == "divide by zero" -%} + assert_eq!(f.eval("{{ test.input.instructions[0] }}"), Err(Error::DivisionByZero)); + {% elif test.expected.error == "illegal operation" -%} + assert_eq!(f.eval("{{ test.input.instructions[0] }}"), Err(Error::InvalidWord)); + {% elif test.expected.error == "undefined operation" -%} + assert_eq!(f.eval("{{ test.input.instructions[0] }}"), Err(Error::UnknownWord)); + {% endif -%} + {% else -%} + {% for instr in test.input.instructions -%} + assert!(f.eval("{{ instr }}").is_ok()); + {% endfor -%} + assert_eq!(f.stack(), {{ test.expected | json_encode() }}); + {% endif -%} +} +{% endfor %} + +} +{% endfor %} diff --git a/exercises/practice/forth/.meta/tests.toml b/exercises/practice/forth/.meta/tests.toml index ed1f74b2c..c9c1d6377 100644 --- a/exercises/practice/forth/.meta/tests.toml +++ b/exercises/practice/forth/.meta/tests.toml @@ -1,39 +1,157 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [9962203f-f00a-4a85-b404-8a8ecbcec09d] -description = "numbers just get pushed onto the stack" +description = "parsing and numbers -> numbers just get pushed onto the stack" + +[fd7a8da2-6818-4203-a866-fed0714e7aa0] +description = "parsing and numbers -> pushes negative numbers onto the stack" [9e69588e-a3d8-41a3-a371-ea02206c1e6e] -description = "can add two numbers" +description = "addition -> can add two numbers" + +[52336dd3-30da-4e5c-8523-bdf9a3427657] +description = "addition -> errors if there is nothing on the stack" + +[06efb9a4-817a-435e-b509-06166993c1b8] +description = "addition -> errors if there is only one value on the stack" [09687c99-7bbc-44af-8526-e402f997ccbf] -description = "can subtract two numbers" +description = "subtraction -> can subtract two numbers" + +[5d63eee2-1f7d-4538-b475-e27682ab8032] +description = "subtraction -> errors if there is nothing on the stack" + +[b3cee1b2-9159-418a-b00d-a1bb3765c23b] +description = "subtraction -> errors if there is only one value on the stack" [5df0ceb5-922e-401f-974d-8287427dbf21] -description = "can multiply two numbers" +description = "multiplication -> can multiply two numbers" + +[9e004339-15ac-4063-8ec1-5720f4e75046] +description = "multiplication -> errors if there is nothing on the stack" + +[8ba4b432-9f94-41e0-8fae-3b3712bd51b3] +description = "multiplication -> errors if there is only one value on the stack" [e74c2204-b057-4cff-9aa9-31c7c97a93f5] -description = "can divide two numbers" +description = "division -> can divide two numbers" [54f6711c-4b14-4bb0-98ad-d974a22c4620] -description = "performs integer division" +description = "division -> performs integer division" [a5df3219-29b4-4d2f-b427-81f82f42a3f1] -description = "errors if dividing by zero" +description = "division -> errors if dividing by zero" + +[1d5bb6b3-6749-4e02-8a79-b5d4d334cb8a] +description = "division -> errors if there is nothing on the stack" + +[d5547f43-c2ff-4d5c-9cb0-2a4f6684c20d] +description = "division -> errors if there is only one value on the stack" [ee28d729-6692-4a30-b9be-0d830c52a68c] -description = "addition and subtraction" +description = "combined arithmetic -> addition and subtraction" [40b197da-fa4b-4aca-a50b-f000d19422c1] -description = "multiplication and division" +description = "combined arithmetic -> multiplication and division" + +[c5758235-6eef-4bf6-ab62-c878e50b9957] +description = "dup -> copies a value on the stack" + +[f6889006-5a40-41e7-beb3-43b09e5a22f4] +description = "dup -> copies the top value on the stack" + +[40b7569c-8401-4bd4-a30d-9adf70d11bc4] +description = "dup -> errors if there is nothing on the stack" + +[1971da68-1df2-4569-927a-72bf5bb7263c] +description = "drop -> removes the top value on the stack if it is the only one" + +[8929d9f2-4a78-4e0f-90ad-be1a0f313fd9] +description = "drop -> removes the top value on the stack if it is not the only one" + +[6dd31873-6dd7-4cb8-9e90-7daa33ba045c] +description = "drop -> errors if there is nothing on the stack" + +[3ee68e62-f98a-4cce-9e6c-8aae6c65a4e3] +description = "swap -> swaps the top two values on the stack if they are the only ones" + +[8ce869d5-a503-44e4-ab55-1da36816ff1c] +description = "swap -> swaps the top two values on the stack if they are not the only ones" + +[74ba5b2a-b028-4759-9176-c5c0e7b2b154] +description = "swap -> errors if there is nothing on the stack" + +[dd52e154-5d0d-4a5c-9e5d-73eb36052bc8] +description = "swap -> errors if there is only one value on the stack" + +[a2654074-ba68-4f93-b014-6b12693a8b50] +description = "over -> copies the second element if there are only two" + +[c5b51097-741a-4da7-8736-5c93fa856339] +description = "over -> copies the second element if there are more than two" + +[6e1703a6-5963-4a03-abba-02e77e3181fd] +description = "over -> errors if there is nothing on the stack" + +[ee574dc4-ef71-46f6-8c6a-b4af3a10c45f] +description = "over -> errors if there is only one value on the stack" + +[ed45cbbf-4dbf-4901-825b-54b20dbee53b] +description = "user-defined words -> can consist of built-in words" [2726ea44-73e4-436b-bc2b-5ff0c6aa014b] -description = "execute in the right order" +description = "user-defined words -> execute in the right order" + +[9e53c2d0-b8ef-4ad8-b2c9-a559b421eb33] +description = "user-defined words -> can override other user-defined words" + +[669db3f3-5bd6-4be0-83d1-618cd6e4984b] +description = "user-defined words -> can override built-in words" + +[588de2f0-c56e-4c68-be0b-0bb1e603c500] +description = "user-defined words -> can override built-in operators" [ac12aaaf-26c6-4a10-8b3c-1c958fa2914c] -description = "can use different words with the same name" +description = "user-defined words -> can use different words with the same name" [53f82ef0-2750-4ccb-ac04-5d8c1aefabb1] -description = "can define word that uses word with the same name" +description = "user-defined words -> can define word that uses word with the same name" + +[35958cee-a976-4a0f-9378-f678518fa322] +description = "user-defined words -> cannot redefine non-negative numbers" + +[df5b2815-3843-4f55-b16c-c3ed507292a7] +description = "user-defined words -> cannot redefine negative numbers" + +[5180f261-89dd-491e-b230-62737e09806f] +description = "user-defined words -> errors if executing a non-existent word" + +[3c8bfef3-edbb-49c1-9993-21d4030043cb] +description = "user-defined words -> only defines locally" + +[7b83bb2e-b0e8-461f-ad3b-96ee2e111ed6] +description = "case-insensitivity -> DUP is case-insensitive" + +[339ed30b-f5b4-47ff-ab1c-67591a9cd336] +description = "case-insensitivity -> DROP is case-insensitive" + +[ee1af31e-1355-4b1b-bb95-f9d0b2961b87] +description = "case-insensitivity -> SWAP is case-insensitive" + +[acdc3a49-14c8-4cc2-945d-11edee6408fa] +description = "case-insensitivity -> OVER is case-insensitive" + +[5934454f-a24f-4efc-9fdd-5794e5f0c23c] +description = "case-insensitivity -> user-defined words are case-insensitive" + +[037d4299-195f-4be7-a46d-f07ca6280a06] +description = "case-insensitivity -> definitions are case-insensitive" diff --git a/exercises/practice/forth/tests/forth.rs b/exercises/practice/forth/tests/forth.rs index a2526e98e..c6ed17ed5 100644 --- a/exercises/practice/forth/tests/forth.rs +++ b/exercises/practice/forth/tests/forth.rs @@ -1,373 +1,437 @@ -use forth::{Error, Forth, Value}; - -#[test] -fn no_input_no_stack() { - assert_eq!(Vec::::new(), Forth::new().stack()); -} - -#[test] -#[ignore] -fn numbers_just_get_pushed_onto_the_stack() { - let mut f = Forth::new(); - assert!(f.eval("1 2 3 4 5").is_ok()); - assert_eq!(vec![1, 2, 3, 4, 5], f.stack()); -} - -#[test] -#[ignore] -fn can_add_two_numbers() { - let mut f = Forth::new(); - assert!(f.eval("1 2 +").is_ok()); - assert_eq!(vec![3], f.stack()); -} - -#[test] -#[ignore] -fn addition_error() { - let mut f = Forth::new(); - assert_eq!(Err(Error::StackUnderflow), f.eval("1 +")); - assert_eq!(Err(Error::StackUnderflow), f.eval("+")); -} - -#[test] -#[ignore] -fn can_subtract_two_numbers() { - let mut f = Forth::new(); - assert!(f.eval("3 4 -").is_ok()); - assert_eq!(vec![-1], f.stack()); -} - -#[test] -#[ignore] -fn subtraction_error() { - let mut f = Forth::new(); - assert_eq!(Err(Error::StackUnderflow), f.eval("1 -")); - assert_eq!(Err(Error::StackUnderflow), f.eval("-")); -} - -#[test] -#[ignore] -fn can_multiply_two_numbers() { - let mut f = Forth::new(); - assert!(f.eval("2 4 *").is_ok()); - assert_eq!(vec![8], f.stack()); -} - -#[test] -#[ignore] -fn multiplication_error() { - let mut f = Forth::new(); - assert_eq!(Err(Error::StackUnderflow), f.eval("1 *")); - assert_eq!(Err(Error::StackUnderflow), f.eval("*")); -} - -#[test] -#[ignore] -fn can_divide_two_numbers() { - let mut f = Forth::new(); - assert!(f.eval("12 3 /").is_ok()); - assert_eq!(vec![4], f.stack()); -} - -#[test] -#[ignore] -fn performs_integer_division() { - let mut f = Forth::new(); - assert!(f.eval("8 3 /").is_ok()); - assert_eq!(vec![2], f.stack()); -} - -#[test] -#[ignore] -fn division_error() { - let mut f = Forth::new(); - assert_eq!(Err(Error::StackUnderflow), f.eval("1 /")); - assert_eq!(Err(Error::StackUnderflow), f.eval("/")); -} - -#[test] -#[ignore] -fn errors_if_dividing_by_zero() { - let mut f = Forth::new(); - assert_eq!(Err(Error::DivisionByZero), f.eval("4 0 /")); -} - -#[test] -#[ignore] -fn addition_and_subtraction() { - let mut f = Forth::new(); - assert!(f.eval("1 2 + 4 -").is_ok()); - assert_eq!(vec![-1], f.stack()); -} - -#[test] -#[ignore] -fn multiplication_and_division() { - let mut f = Forth::new(); - assert!(f.eval("2 4 * 3 /").is_ok()); - assert_eq!(vec![2], f.stack()); -} - -#[test] -#[ignore] -fn dup() { - let mut f = Forth::new(); - assert!(f.eval("1 dup").is_ok()); - assert_eq!(vec![1, 1], f.stack()); -} - -#[test] -#[ignore] -fn dup_top_value_only() { - let mut f = Forth::new(); - assert!(f.eval("1 2 dup").is_ok()); - assert_eq!(vec![1, 2, 2], f.stack()); -} - -#[test] -#[ignore] -fn dup_case_insensitive() { - let mut f = Forth::new(); - assert!(f.eval("1 DUP Dup dup").is_ok()); - assert_eq!(vec![1, 1, 1, 1], f.stack()); -} - -#[test] -#[ignore] -fn dup_error() { - let mut f = Forth::new(); - assert_eq!(Err(Error::StackUnderflow), f.eval("dup")); -} - -#[test] -#[ignore] -fn drop() { - let mut f = Forth::new(); - assert!(f.eval("1 drop").is_ok()); - assert_eq!(Vec::::new(), f.stack()); -} - -#[test] -#[ignore] -fn drop_with_two() { - let mut f = Forth::new(); - assert!(f.eval("1 2 drop").is_ok()); - assert_eq!(vec![1], f.stack()); -} - -#[test] -#[ignore] -fn drop_case_insensitive() { - let mut f = Forth::new(); - assert!(f.eval("1 2 3 4 DROP Drop drop").is_ok()); - assert_eq!(vec![1], f.stack()); -} - -#[test] -#[ignore] -fn drop_error() { - let mut f = Forth::new(); - assert_eq!(Err(Error::StackUnderflow), f.eval("drop")); -} - -#[test] -#[ignore] -fn swap() { - let mut f = Forth::new(); - assert!(f.eval("1 2 swap").is_ok()); - assert_eq!(vec![2, 1], f.stack()); -} - -#[test] -#[ignore] -fn swap_with_three() { - let mut f = Forth::new(); - assert!(f.eval("1 2 3 swap").is_ok()); - assert_eq!(vec![1, 3, 2], f.stack()); -} - -#[test] -#[ignore] -fn swap_case_insensitive() { - let mut f = Forth::new(); - assert!(f.eval("1 2 SWAP 3 Swap 4 swap").is_ok()); - assert_eq!(vec![2, 3, 4, 1], f.stack()); -} - -#[test] -#[ignore] -fn swap_error() { - let mut f = Forth::new(); - assert_eq!(Err(Error::StackUnderflow), f.eval("1 swap")); - assert_eq!(Err(Error::StackUnderflow), f.eval("swap")); -} - -#[test] -#[ignore] -fn over() { - let mut f = Forth::new(); - assert!(f.eval("1 2 over").is_ok()); - assert_eq!(vec![1, 2, 1], f.stack()); -} - -#[test] -#[ignore] -fn over_with_three() { - let mut f = Forth::new(); - assert!(f.eval("1 2 3 over").is_ok()); - assert_eq!(vec![1, 2, 3, 2], f.stack()); -} - -#[test] -#[ignore] -fn over_case_insensitive() { - let mut f = Forth::new(); - assert!(f.eval("1 2 OVER Over over").is_ok()); - assert_eq!(vec![1, 2, 1, 2, 1], f.stack()); -} - -#[test] -#[ignore] -fn over_error() { - let mut f = Forth::new(); - assert_eq!(Err(Error::StackUnderflow), f.eval("1 over")); - assert_eq!(Err(Error::StackUnderflow), f.eval("over")); -} - -// User-defined words - -#[test] -#[ignore] -fn can_consist_of_built_in_words() { - let mut f = Forth::new(); - assert!(f.eval(": dup-twice dup dup ;").is_ok()); - assert!(f.eval("1 dup-twice").is_ok()); - assert_eq!(vec![1, 1, 1], f.stack()); -} - -#[test] -#[ignore] -fn execute_in_the_right_order() { - let mut f = Forth::new(); - assert!(f.eval(": countup 1 2 3 ;").is_ok()); - assert!(f.eval("countup").is_ok()); - assert_eq!(vec![1, 2, 3], f.stack()); -} - -#[test] -#[ignore] -fn redefining_an_existing_word() { - let mut f = Forth::new(); - assert!(f.eval(": foo dup ;").is_ok()); - assert!(f.eval(": foo dup dup ;").is_ok()); - assert!(f.eval("1 foo").is_ok()); - assert_eq!(vec![1, 1, 1], f.stack()); -} - -#[test] -#[ignore] -fn redefining_an_existing_built_in_word() { - let mut f = Forth::new(); - assert!(f.eval(": swap dup ;").is_ok()); - assert!(f.eval("1 swap").is_ok()); - assert_eq!(vec![1, 1], f.stack()); -} - -#[test] -#[ignore] -fn user_defined_words_are_case_insensitive() { - let mut f = Forth::new(); - assert!(f.eval(": foo dup ;").is_ok()); - assert!(f.eval("1 FOO Foo foo").is_ok()); - assert_eq!(vec![1, 1, 1, 1], f.stack()); -} - -#[test] -#[ignore] -fn definitions_are_case_insensitive() { - let mut f = Forth::new(); - assert!(f.eval(": SWAP DUP Dup dup ;").is_ok()); - assert!(f.eval("1 swap").is_ok()); - assert_eq!(vec![1, 1, 1, 1], f.stack()); -} - -#[test] -#[ignore] -fn redefining_a_built_in_operator() { - let mut f = Forth::new(); - assert!(f.eval(": + * ;").is_ok()); - assert!(f.eval("3 4 +").is_ok()); - assert_eq!(vec![12], f.stack()); -} - -#[test] -#[ignore] -fn can_use_different_words_with_the_same_name() { - let mut f = Forth::new(); - assert!(f.eval(": foo 5 ;").is_ok()); - assert!(f.eval(": bar foo ;").is_ok()); - assert!(f.eval(": foo 6 ;").is_ok()); - assert!(f.eval("bar foo").is_ok()); - assert_eq!(vec![5, 6], f.stack()); -} - -#[test] -#[ignore] -fn can_define_word_that_uses_word_with_the_same_name() { - let mut f = Forth::new(); - assert!(f.eval(": foo 10 ;").is_ok()); - assert!(f.eval(": foo foo 1 + ;").is_ok()); - assert!(f.eval("foo").is_ok()); - assert_eq!(vec![11], f.stack()); -} - -#[test] -#[ignore] -fn defining_a_number() { - let mut f = Forth::new(); - assert_eq!(Err(Error::InvalidWord), f.eval(": 1 2 ;")); -} - -#[test] -#[ignore] -fn malformed_word_definition() { - let mut f = Forth::new(); - assert_eq!(Err(Error::InvalidWord), f.eval(":")); - assert_eq!(Err(Error::InvalidWord), f.eval(": foo")); - assert_eq!(Err(Error::InvalidWord), f.eval(": foo 1")); -} - -#[test] -#[ignore] -fn calling_non_existing_word() { - let mut f = Forth::new(); - assert_eq!(Err(Error::UnknownWord), f.eval("1 foo")); -} - -#[test] -#[ignore] -fn multiple_definitions() { - let mut f = Forth::new(); - assert!(f.eval(": one 1 ; : two 2 ; one two +").is_ok()); - assert_eq!(vec![3], f.stack()); -} - -#[test] -#[ignore] -fn definitions_after_ops() { - let mut f = Forth::new(); - assert!(f.eval("1 2 + : addone 1 + ; addone").is_ok()); - assert_eq!(vec![4], f.stack()); -} - -#[test] -#[ignore] -fn redefine_an_existing_word_with_another_existing_word() { - let mut f = Forth::new(); - assert!(f.eval(": foo 5 ;").is_ok()); - assert!(f.eval(": bar foo ;").is_ok()); - assert!(f.eval(": foo 6 ;").is_ok()); - assert!(f.eval(": bar foo ;").is_ok()); - assert!(f.eval("bar foo").is_ok()); - assert_eq!(vec![6, 6], f.stack()); +mod parsing_and_numbers { + use forth::*; + + #[test] + fn numbers_just_get_pushed_onto_the_stack() { + let mut f = Forth::new(); + assert!(f.eval("1 2 3 4 5").is_ok()); + assert_eq!(f.stack(), [1, 2, 3, 4, 5]); + } + + #[test] + #[ignore] + fn pushes_negative_numbers_onto_the_stack() { + let mut f = Forth::new(); + assert!(f.eval("-1 -2 -3 -4 -5").is_ok()); + assert_eq!(f.stack(), [-1, -2, -3, -4, -5]); + } +} + +mod addition { + use forth::*; + + #[test] + #[ignore] + fn can_add_two_numbers() { + let mut f = Forth::new(); + assert!(f.eval("1 2 +").is_ok()); + assert_eq!(f.stack(), [3]); + } + + #[test] + #[ignore] + fn errors_if_there_is_nothing_on_the_stack() { + let mut f = Forth::new(); + assert_eq!(f.eval("+"), Err(Error::StackUnderflow)); + } + + #[test] + #[ignore] + fn errors_if_there_is_only_one_value_on_the_stack() { + let mut f = Forth::new(); + assert_eq!(f.eval("1 +"), Err(Error::StackUnderflow)); + } +} + +mod subtraction { + use forth::*; + + #[test] + #[ignore] + fn can_subtract_two_numbers() { + let mut f = Forth::new(); + assert!(f.eval("3 4 -").is_ok()); + assert_eq!(f.stack(), [-1]); + } + + #[test] + #[ignore] + fn errors_if_there_is_nothing_on_the_stack() { + let mut f = Forth::new(); + assert_eq!(f.eval("-"), Err(Error::StackUnderflow)); + } + + #[test] + #[ignore] + fn errors_if_there_is_only_one_value_on_the_stack() { + let mut f = Forth::new(); + assert_eq!(f.eval("1 -"), Err(Error::StackUnderflow)); + } +} + +mod multiplication { + use forth::*; + + #[test] + #[ignore] + fn can_multiply_two_numbers() { + let mut f = Forth::new(); + assert!(f.eval("2 4 *").is_ok()); + assert_eq!(f.stack(), [8]); + } + + #[test] + #[ignore] + fn errors_if_there_is_nothing_on_the_stack() { + let mut f = Forth::new(); + assert_eq!(f.eval("*"), Err(Error::StackUnderflow)); + } + + #[test] + #[ignore] + fn errors_if_there_is_only_one_value_on_the_stack() { + let mut f = Forth::new(); + assert_eq!(f.eval("1 *"), Err(Error::StackUnderflow)); + } +} + +mod division { + use forth::*; + + #[test] + #[ignore] + fn can_divide_two_numbers() { + let mut f = Forth::new(); + assert!(f.eval("12 3 /").is_ok()); + assert_eq!(f.stack(), [4]); + } + + #[test] + #[ignore] + fn performs_integer_division() { + let mut f = Forth::new(); + assert!(f.eval("8 3 /").is_ok()); + assert_eq!(f.stack(), [2]); + } + + #[test] + #[ignore] + fn errors_if_dividing_by_zero() { + let mut f = Forth::new(); + assert_eq!(f.eval("4 0 /"), Err(Error::DivisionByZero)); + } + + #[test] + #[ignore] + fn errors_if_there_is_nothing_on_the_stack() { + let mut f = Forth::new(); + assert_eq!(f.eval("/"), Err(Error::StackUnderflow)); + } + + #[test] + #[ignore] + fn errors_if_there_is_only_one_value_on_the_stack() { + let mut f = Forth::new(); + assert_eq!(f.eval("1 /"), Err(Error::StackUnderflow)); + } +} + +mod combined_arithmetic { + use forth::*; + + #[test] + #[ignore] + fn addition_and_subtraction() { + let mut f = Forth::new(); + assert!(f.eval("1 2 + 4 -").is_ok()); + assert_eq!(f.stack(), [-1]); + } + + #[test] + #[ignore] + fn multiplication_and_division() { + let mut f = Forth::new(); + assert!(f.eval("2 4 * 3 /").is_ok()); + assert_eq!(f.stack(), [2]); + } +} + +mod dup { + use forth::*; + + #[test] + #[ignore] + fn copies_a_value_on_the_stack() { + let mut f = Forth::new(); + assert!(f.eval("1 dup").is_ok()); + assert_eq!(f.stack(), [1, 1]); + } + + #[test] + #[ignore] + fn copies_the_top_value_on_the_stack() { + let mut f = Forth::new(); + assert!(f.eval("1 2 dup").is_ok()); + assert_eq!(f.stack(), [1, 2, 2]); + } + + #[test] + #[ignore] + fn errors_if_there_is_nothing_on_the_stack() { + let mut f = Forth::new(); + assert_eq!(f.eval("dup"), Err(Error::StackUnderflow)); + } +} + +mod drop { + use forth::*; + + #[test] + #[ignore] + fn removes_the_top_value_on_the_stack_if_it_is_the_only_one() { + let mut f = Forth::new(); + assert!(f.eval("1 drop").is_ok()); + assert_eq!(f.stack(), []); + } + + #[test] + #[ignore] + fn removes_the_top_value_on_the_stack_if_it_is_not_the_only_one() { + let mut f = Forth::new(); + assert!(f.eval("1 2 drop").is_ok()); + assert_eq!(f.stack(), [1]); + } + + #[test] + #[ignore] + fn errors_if_there_is_nothing_on_the_stack() { + let mut f = Forth::new(); + assert_eq!(f.eval("drop"), Err(Error::StackUnderflow)); + } +} + +mod swap { + use forth::*; + + #[test] + #[ignore] + fn swaps_the_top_two_values_on_the_stack_if_they_are_the_only_ones() { + let mut f = Forth::new(); + assert!(f.eval("1 2 swap").is_ok()); + assert_eq!(f.stack(), [2, 1]); + } + + #[test] + #[ignore] + fn swaps_the_top_two_values_on_the_stack_if_they_are_not_the_only_ones() { + let mut f = Forth::new(); + assert!(f.eval("1 2 3 swap").is_ok()); + assert_eq!(f.stack(), [1, 3, 2]); + } + + #[test] + #[ignore] + fn errors_if_there_is_nothing_on_the_stack() { + let mut f = Forth::new(); + assert_eq!(f.eval("swap"), Err(Error::StackUnderflow)); + } + + #[test] + #[ignore] + fn errors_if_there_is_only_one_value_on_the_stack() { + let mut f = Forth::new(); + assert_eq!(f.eval("1 swap"), Err(Error::StackUnderflow)); + } +} + +mod over { + use forth::*; + + #[test] + #[ignore] + fn copies_the_second_element_if_there_are_only_two() { + let mut f = Forth::new(); + assert!(f.eval("1 2 over").is_ok()); + assert_eq!(f.stack(), [1, 2, 1]); + } + + #[test] + #[ignore] + fn copies_the_second_element_if_there_are_more_than_two() { + let mut f = Forth::new(); + assert!(f.eval("1 2 3 over").is_ok()); + assert_eq!(f.stack(), [1, 2, 3, 2]); + } + + #[test] + #[ignore] + fn errors_if_there_is_nothing_on_the_stack() { + let mut f = Forth::new(); + assert_eq!(f.eval("over"), Err(Error::StackUnderflow)); + } + + #[test] + #[ignore] + fn errors_if_there_is_only_one_value_on_the_stack() { + let mut f = Forth::new(); + assert_eq!(f.eval("1 over"), Err(Error::StackUnderflow)); + } +} + +mod user_defined_words { + use forth::*; + + #[test] + #[ignore] + fn can_consist_of_built_in_words() { + let mut f = Forth::new(); + assert!(f.eval(": dup-twice dup dup ;").is_ok()); + assert!(f.eval("1 dup-twice").is_ok()); + assert_eq!(f.stack(), [1, 1, 1]); + } + + #[test] + #[ignore] + fn execute_in_the_right_order() { + let mut f = Forth::new(); + assert!(f.eval(": countup 1 2 3 ;").is_ok()); + assert!(f.eval("countup").is_ok()); + assert_eq!(f.stack(), [1, 2, 3]); + } + + #[test] + #[ignore] + fn can_override_other_user_defined_words() { + let mut f = Forth::new(); + assert!(f.eval(": foo dup ;").is_ok()); + assert!(f.eval(": foo dup dup ;").is_ok()); + assert!(f.eval("1 foo").is_ok()); + assert_eq!(f.stack(), [1, 1, 1]); + } + + #[test] + #[ignore] + fn can_override_built_in_words() { + let mut f = Forth::new(); + assert!(f.eval(": swap dup ;").is_ok()); + assert!(f.eval("1 swap").is_ok()); + assert_eq!(f.stack(), [1, 1]); + } + + #[test] + #[ignore] + fn can_override_built_in_operators() { + let mut f = Forth::new(); + assert!(f.eval(": + * ;").is_ok()); + assert!(f.eval("3 4 +").is_ok()); + assert_eq!(f.stack(), [12]); + } + + #[test] + #[ignore] + fn can_use_different_words_with_the_same_name() { + let mut f = Forth::new(); + assert!(f.eval(": foo 5 ;").is_ok()); + assert!(f.eval(": bar foo ;").is_ok()); + assert!(f.eval(": foo 6 ;").is_ok()); + assert!(f.eval("bar foo").is_ok()); + assert_eq!(f.stack(), [5, 6]); + } + + #[test] + #[ignore] + fn can_define_word_that_uses_word_with_the_same_name() { + let mut f = Forth::new(); + assert!(f.eval(": foo 10 ;").is_ok()); + assert!(f.eval(": foo foo 1 + ;").is_ok()); + assert!(f.eval("foo").is_ok()); + assert_eq!(f.stack(), [11]); + } + + #[test] + #[ignore] + fn cannot_redefine_non_negative_numbers() { + let mut f = Forth::new(); + assert_eq!(f.eval(": 1 2 ;"), Err(Error::InvalidWord)); + } + + #[test] + #[ignore] + fn cannot_redefine_negative_numbers() { + let mut f = Forth::new(); + assert_eq!(f.eval(": -1 2 ;"), Err(Error::InvalidWord)); + } + + #[test] + #[ignore] + fn errors_if_executing_a_non_existent_word() { + let mut f = Forth::new(); + assert_eq!(f.eval("foo"), Err(Error::UnknownWord)); + } + + #[test] + #[ignore] + fn only_defines_locally() { + let mut f = Forth::new(); + assert!(f.eval(": + - ;").is_ok()); + assert!(f.eval("1 1 +").is_ok()); + assert_eq!(f.stack(), [0]); + let mut f = Forth::new(); + assert!(f.eval("1 1 +").is_ok()); + assert_eq!(f.stack(), [2]); + } +} + +mod case_insensitivity { + use forth::*; + + #[test] + #[ignore] + fn dup_is_case_insensitive() { + let mut f = Forth::new(); + assert!(f.eval("1 DUP Dup dup").is_ok()); + assert_eq!(f.stack(), [1, 1, 1, 1]); + } + + #[test] + #[ignore] + fn drop_is_case_insensitive() { + let mut f = Forth::new(); + assert!(f.eval("1 2 3 4 DROP Drop drop").is_ok()); + assert_eq!(f.stack(), [1]); + } + + #[test] + #[ignore] + fn swap_is_case_insensitive() { + let mut f = Forth::new(); + assert!(f.eval("1 2 SWAP 3 Swap 4 swap").is_ok()); + assert_eq!(f.stack(), [2, 3, 4, 1]); + } + + #[test] + #[ignore] + fn over_is_case_insensitive() { + let mut f = Forth::new(); + assert!(f.eval("1 2 OVER Over over").is_ok()); + assert_eq!(f.stack(), [1, 2, 1, 2, 1]); + } + + #[test] + #[ignore] + fn user_defined_words_are_case_insensitive() { + let mut f = Forth::new(); + assert!(f.eval(": foo dup ;").is_ok()); + assert!(f.eval("1 FOO Foo foo").is_ok()); + assert_eq!(f.stack(), [1, 1, 1, 1]); + } + + #[test] + #[ignore] + fn definitions_are_case_insensitive() { + let mut f = Forth::new(); + assert!(f.eval(": SWAP DUP Dup dup ;").is_ok()); + assert!(f.eval("1 swap").is_ok()); + assert_eq!(f.stack(), [1, 1, 1, 1]); + } } From d4a23c4b1a31e39ef19375eb275512ef2c73f77c Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Wed, 3 Apr 2024 07:41:05 +0200 Subject: [PATCH 273/436] docs: update contributing documentation (#1892) --- docs/CONTRIBUTING.md | 48 ++++++++++++------- .../perfect-numbers/.meta/config.json | 5 +- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 0dcc8e575..bde876fa0 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -23,6 +23,18 @@ Feel free to extend it. If you want to run CI tests locally, `just test` will get you quite far. +## Excluding tests and adding custom ones + +While creating or updating an exercise, you may decide to exclude certain tests from being generated. +You can do so by setting `include = false` in `.meta/tests.toml`. +Please include a comment about the reason for excluding it. + +If you want to add additional track-specific tests, you can do so in a file `.meta/additional-tests.json`. +Only do this with a good reason for not upstreaming these tests. +A comment in the additional test should answer these questions: +- Why is the test no upstreamed to `problem-specifications`, i.e. why is it not generally valuable to other languages? +- Why is the test valuable for the Rust track anyway? + ## Creating a new exercise Please familiarize yourself with the [Exercism documentation about practice exercises]. @@ -31,19 +43,15 @@ Please familiarize yourself with the [Exercism documentation about practice exer Run `just add-exercise` and you'll be prompted for the minimal information required to generate the exercise stub for you. -After that, jump in the generated exercise and fill in any todos you find. +After that, jump in the generated exercise and fill in any placeholders you find. This includes most notably: +- writing the exercise stub in `src/lib.rs` - adding an example solution in `.meta/example.rs` - Adjusting `.meta/test_template.tera` The tests are generated using the template engine [Tera]. The input of the template is the canonical data from [`problem-specifications`]. -if you want to exclude certain tests from being generated, -you have to set `include = false` in `.meta/tests.toml`. -Please include a comment about the reason for excluding it. -If you want to add additional track-specific tests, you can do so in a file `.meta/additional-tests.json`. -Only do this with a good reason for not upstreaming these tests. Find some tips about writing tera templates [here](#tera-templates). @@ -74,26 +82,27 @@ include a `.custom."allowed-to-not-compile"` key in the exercise's `.meta/config.json` containing the reason. If your exercise implements macro-based testing -(see [#392](https://github.com/exercism/rust/issues/392#issuecomment-343865993) -and [`perfect-numbers.rs`](https://github.com/exercism/rust/blob/main/exercises/practice/perfect-numbers/tests/perfect-numbers.rs)), +(e.g. [`xorcism`](/exercises/practice/xorcism/tests/xorcism.rs)), you will likely run afoul of a CI check which counts the `#[ignore]` lines and compares the result to the number of `#[test]` lines. To fix this, add a marker to the exercise's `.meta/config.json`: `.custom."ignore-count-ignores"` should be `true` to disable that check for your exercise. +However, tera templates should generally be preferred to generate many similar test cases. +See [issue #1824](https://github.com/exercism/rust/issues/1824) for the reasoning. ## Updating an exercise Many exercises are derived from [`problem-specifications`]. This includes their test suite and user-facing documentation. Before proposing changes here, -check if they should be made `problem-specifications` instead. +check if they should be made in `problem-specifications` instead. Run `just update-exercise` to update an exercise. This outsources most work to `configlet sync --update` and runs the test generator again. -When updaing an exercise that doesn't have a tera template yet, +When updating an exercise that doesn't have a tera template yet, a new one will be generated for you. You will likely have to adjust it to some extent. @@ -104,8 +113,7 @@ Find some tips about writing tera templates [in the next section](#tera-template The full documentation for tera templates is [here][tera-docs]. Following are some approaches that have worked for our specific needs. -You will likely want to look at the exercise's `canonical-data.json` -to see what structure your input data has. +You will likely want to look at the exercise's `canonical-data.json` to see what your input data looks like. You can use `bin/symlink_problem_specifications.sh` to have this data symlinked into the actual exercise directory. Handy! @@ -139,17 +147,25 @@ by the student and therefore tested. The canonical data contains a field `property` in that case. You can construct if-else-chains based on `test.property` and render a different function based on that. -There is a custom tera fiter `to_hex`, which formats ints in hexadecimal. +Some exercises have their test cases organized into _test groups_. +Your template will have to account for that. +In many cases, you can simply flatten the structure. (example: [`affine-cipher`](/exercises/practice/affine-cipher/.meta/test_template.tera)) +However, tests sometimes have the same description in different groups, which leads to naming conflicts if the structure is flattened. +In that case, you can use the group description to organize the tests in modules. (example: [`forth`](/exercises/practice/forth/.meta/test_template.tera)) + +There are some custom tera filters in [`rust-tooling`](/rust-tooling/generate/src/custom_filters.rs). +Here's the hopefully up-to-date list: +- `to_hex` formats ints in hexadecimal +- `snake_case` massages an arbitrary string into a decent Rust identifier + Feel free to add your own in the crate `rust-tooling`. -Custom filters added there will be available to all templates. -How to create such custom filters is documented int he [tera docs][tera-docs-filters]. +Hopefully you'll remember to update the list here as well. 🙂 For a rather complicated example, check out the test template of `triangle`. It organizes the test cases in modules and dynamically detects which tests to put behind feature gates. That exercise also reimplements some test cases from upstream in `additional-tests.json`, in order to add more information to them necessary for generating good tests. [tera-docs]: https://keats.github.io/tera/docs/#templates -[tera-docs-filters]: https://keats.github.io/tera/docs/#filters ## Syllabus diff --git a/exercises/practice/perfect-numbers/.meta/config.json b/exercises/practice/perfect-numbers/.meta/config.json index e2ba30d96..b080f1119 100644 --- a/exercises/practice/perfect-numbers/.meta/config.json +++ b/exercises/practice/perfect-numbers/.meta/config.json @@ -32,8 +32,5 @@ }, "blurb": "Determine if a number is perfect, abundant, or deficient based on Nicomachus' (60 - 120 CE) classification scheme for positive integers.", "source": "Taken from Chapter 2 of Functional Thinking by Neal Ford.", - "source_url": "https://www.oreilly.com/library/view/functional-thinking/9781449365509/", - "custom": { - "ignore-count-ignores": true - } + "source_url": "https://www.oreilly.com/library/view/functional-thinking/9781449365509/" } From 13fb84cbd46455302ec62d24942bc3e2b92e1853 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Wed, 3 Apr 2024 07:41:29 +0200 Subject: [PATCH 274/436] circular-buffer: sync (#1893) [no important files changed] --- .../.meta/additional-tests.json | 42 ++++ .../circular-buffer/.meta/test_template.tera | 61 ++++++ .../practice/circular-buffer/.meta/tests.toml | 28 ++- .../circular-buffer/tests/circular-buffer.rs | 181 +++++++++--------- 4 files changed, 220 insertions(+), 92 deletions(-) create mode 100644 exercises/practice/circular-buffer/.meta/additional-tests.json create mode 100644 exercises/practice/circular-buffer/.meta/test_template.tera diff --git a/exercises/practice/circular-buffer/.meta/additional-tests.json b/exercises/practice/circular-buffer/.meta/additional-tests.json new file mode 100644 index 000000000..769940038 --- /dev/null +++ b/exercises/practice/circular-buffer/.meta/additional-tests.json @@ -0,0 +1,42 @@ +[ + { + "uuid": "8a9c5921-a266-4c11-b392-3b2fbb53517c", + "description": "char buffer", + "comments": [ + "The upstream tests intentionally do not cover generics,", + "but recommends individual languages may do it." + ], + "property": "generic", + "input": { + "capacity": 1, + "operations": [ + { + "operation": "write", + "item": "'A'", + "should_succeed": true + } + ] + }, + "expected": {} + }, + { + "uuid": "8a9c5921-a266-4c11-b392-3b2fbb53517c", + "description": "string buffer", + "comments": [ + "The upstream tests intentionally do not cover generics,", + "but recommends individual languages may do it." + ], + "property": "generic", + "input": { + "capacity": 1, + "operations": [ + { + "operation": "write", + "item": "\"Testing\".to_string()", + "should_succeed": true + } + ] + }, + "expected": {} + } +] diff --git a/exercises/practice/circular-buffer/.meta/test_template.tera b/exercises/practice/circular-buffer/.meta/test_template.tera new file mode 100644 index 000000000..36a0193d8 --- /dev/null +++ b/exercises/practice/circular-buffer/.meta/test_template.tera @@ -0,0 +1,61 @@ +use circular_buffer::*; +use std::rc::Rc; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | snake_case }}() { + let mut buffer = CircularBuffer{% if loop.index == 1 %}::{% endif %}::new({{ test.input.capacity }}); +{%- for op in test.input.operations %} + {%- if op.operation == "read" %} + {%- if op.should_succeed %} + assert_eq!(buffer.read(), Ok({{ op.expected }})); + {%- else %} + assert_eq!(buffer.read(), Err(Error::EmptyBuffer)); + {% endif -%} + {%- elif op.operation == "write" %} + {%- if op.should_succeed %} + assert!(buffer.write({{ op.item }}).is_ok()); + {%- else %} + assert_eq!(buffer.write({{ op.item }}), Err(Error::FullBuffer)); + {% endif -%} + {%- elif op.operation == "overwrite" %} + buffer.overwrite({{ op.item }}); + {%- elif op.operation == "clear" %} + buffer.clear(); + {%- else %} + panic!("error in test template: unknown operation"); + {% endif -%} +{% endfor %} +} +{% endfor %} + +{#- + We usually do not add additional tests directly in the template. + In this case however, the structure of the tests is different from the + regular ones. Accommodating that in the template would be complicated + and unnecessary. +#} + +#[test] +#[ignore] +fn clear_actually_frees_up_its_elements() { + let mut buffer = CircularBuffer::new(1); + let element = Rc::new(()); + assert!(buffer.write(Rc::clone(&element)).is_ok()); + assert_eq!(Rc::strong_count(&element), 2); + buffer.clear(); + assert_eq!(Rc::strong_count(&element), 1); +} + +#[test] +#[ignore] +fn dropping_the_buffer_drops_its_elements() { + let element = Rc::new(()); + { + let mut buffer = CircularBuffer::new(1); + assert!(buffer.write(Rc::clone(&element)).is_ok()); + assert_eq!(Rc::strong_count(&element), 2); + } + assert_eq!(Rc::strong_count(&element), 1); +} diff --git a/exercises/practice/circular-buffer/.meta/tests.toml b/exercises/practice/circular-buffer/.meta/tests.toml index cfdc1544c..0fb3143dd 100644 --- a/exercises/practice/circular-buffer/.meta/tests.toml +++ b/exercises/practice/circular-buffer/.meta/tests.toml @@ -1,6 +1,19 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[28268ed4-4ff3-45f3-820e-895b44d53dfa] +description = "reading empty buffer should fail" + +[2e6db04a-58a1-425d-ade8-ac30b5f318f3] +description = "can read an item just written" [90741fe8-a448-45ce-be2b-de009a24c144] description = "each item may only be read once" @@ -11,6 +24,9 @@ description = "items are read in the order they are written" [2af22046-3e44-4235-bfe6-05ba60439d38] description = "full buffer can't be written to" +[547d192c-bbf0-4369-b8fa-fc37e71f2393] +description = "a read frees up capacity for another write" + [04a56659-3a81-4113-816b-6ecb659b4471] description = "read position is maintained even across multiple writes" @@ -23,8 +39,14 @@ description = "clear frees up capacity for another write" [e1ac5170-a026-4725-bfbe-0cf332eddecd] description = "clear does nothing on empty buffer" +[9c2d4f26-3ec7-453f-a895-7e7ff8ae7b5b] +description = "overwrite acts like write on non-full buffer" + [880f916b-5039-475c-bd5c-83463c36a147] description = "overwrite replaces the oldest item on full buffer" [bfecab5b-aca1-4fab-a2b0-cd4af2b053c3] description = "overwrite replaces the oldest item remaining in buffer following a read" + +[9cebe63a-c405-437b-8b62-e3fdc1ecec5a] +description = "initial clear does not affect wrapping around" diff --git a/exercises/practice/circular-buffer/tests/circular-buffer.rs b/exercises/practice/circular-buffer/tests/circular-buffer.rs index 716f2e4f2..2f4e0712f 100644 --- a/exercises/practice/circular-buffer/tests/circular-buffer.rs +++ b/exercises/practice/circular-buffer/tests/circular-buffer.rs @@ -1,87 +1,86 @@ -use circular_buffer::{CircularBuffer, Error}; +use circular_buffer::*; use std::rc::Rc; #[test] -fn error_on_read_empty_buffer() { - let mut buffer = CircularBuffer::::new(1); - assert_eq!(Err(Error::EmptyBuffer), buffer.read()); +fn reading_empty_buffer_should_fail() { + let mut buffer = CircularBuffer::::new(1); + assert_eq!(buffer.read(), Err(Error::EmptyBuffer)); } #[test] #[ignore] -fn can_read_item_just_written() { +fn can_read_an_item_just_written() { let mut buffer = CircularBuffer::new(1); - assert!(buffer.write('1').is_ok()); - assert_eq!(Ok('1'), buffer.read()); + assert!(buffer.write(1).is_ok()); + assert_eq!(buffer.read(), Ok(1)); } #[test] #[ignore] fn each_item_may_only_be_read_once() { let mut buffer = CircularBuffer::new(1); - assert!(buffer.write('1').is_ok()); - assert_eq!(Ok('1'), buffer.read()); - assert_eq!(Err(Error::EmptyBuffer), buffer.read()); + assert!(buffer.write(1).is_ok()); + assert_eq!(buffer.read(), Ok(1)); + assert_eq!(buffer.read(), Err(Error::EmptyBuffer)); } #[test] #[ignore] fn items_are_read_in_the_order_they_are_written() { let mut buffer = CircularBuffer::new(2); - assert!(buffer.write('1').is_ok()); - assert!(buffer.write('2').is_ok()); - assert_eq!(Ok('1'), buffer.read()); - assert_eq!(Ok('2'), buffer.read()); - assert_eq!(Err(Error::EmptyBuffer), buffer.read()); + assert!(buffer.write(1).is_ok()); + assert!(buffer.write(2).is_ok()); + assert_eq!(buffer.read(), Ok(1)); + assert_eq!(buffer.read(), Ok(2)); } #[test] #[ignore] -fn full_buffer_cant_be_written_to() { +fn full_buffer_can_t_be_written_to() { let mut buffer = CircularBuffer::new(1); - assert!(buffer.write('1').is_ok()); - assert_eq!(Err(Error::FullBuffer), buffer.write('2')); + assert!(buffer.write(1).is_ok()); + assert_eq!(buffer.write(2), Err(Error::FullBuffer)); } #[test] #[ignore] -fn read_frees_up_capacity_for_another_write() { +fn a_read_frees_up_capacity_for_another_write() { let mut buffer = CircularBuffer::new(1); - assert!(buffer.write('1').is_ok()); - assert_eq!(Ok('1'), buffer.read()); - assert!(buffer.write('2').is_ok()); - assert_eq!(Ok('2'), buffer.read()); + assert!(buffer.write(1).is_ok()); + assert_eq!(buffer.read(), Ok(1)); + assert!(buffer.write(2).is_ok()); + assert_eq!(buffer.read(), Ok(2)); } #[test] #[ignore] fn read_position_is_maintained_even_across_multiple_writes() { let mut buffer = CircularBuffer::new(3); - assert!(buffer.write('1').is_ok()); - assert!(buffer.write('2').is_ok()); - assert_eq!(Ok('1'), buffer.read()); - assert!(buffer.write('3').is_ok()); - assert_eq!(Ok('2'), buffer.read()); - assert_eq!(Ok('3'), buffer.read()); + assert!(buffer.write(1).is_ok()); + assert!(buffer.write(2).is_ok()); + assert_eq!(buffer.read(), Ok(1)); + assert!(buffer.write(3).is_ok()); + assert_eq!(buffer.read(), Ok(2)); + assert_eq!(buffer.read(), Ok(3)); } #[test] #[ignore] -fn items_cleared_out_of_buffer_cant_be_read() { +fn items_cleared_out_of_buffer_can_t_be_read() { let mut buffer = CircularBuffer::new(1); - assert!(buffer.write('1').is_ok()); + assert!(buffer.write(1).is_ok()); buffer.clear(); - assert_eq!(Err(Error::EmptyBuffer), buffer.read()); + assert_eq!(buffer.read(), Err(Error::EmptyBuffer)); } #[test] #[ignore] fn clear_frees_up_capacity_for_another_write() { let mut buffer = CircularBuffer::new(1); - assert!(buffer.write('1').is_ok()); + assert!(buffer.write(1).is_ok()); buffer.clear(); - assert!(buffer.write('2').is_ok()); - assert_eq!(Ok('2'), buffer.read()); + assert!(buffer.write(2).is_ok()); + assert_eq!(buffer.read(), Ok(2)); } #[test] @@ -89,89 +88,93 @@ fn clear_frees_up_capacity_for_another_write() { fn clear_does_nothing_on_empty_buffer() { let mut buffer = CircularBuffer::new(1); buffer.clear(); - assert!(buffer.write('1').is_ok()); - assert_eq!(Ok('1'), buffer.read()); -} - -#[test] -#[ignore] -fn clear_actually_frees_up_its_elements() { - let mut buffer = CircularBuffer::new(1); - let element = Rc::new(()); - assert!(buffer.write(Rc::clone(&element)).is_ok()); - assert_eq!(Rc::strong_count(&element), 2); - buffer.clear(); - assert_eq!(Rc::strong_count(&element), 1); + assert!(buffer.write(1).is_ok()); + assert_eq!(buffer.read(), Ok(1)); } #[test] #[ignore] fn overwrite_acts_like_write_on_non_full_buffer() { let mut buffer = CircularBuffer::new(2); - assert!(buffer.write('1').is_ok()); - buffer.overwrite('2'); - assert_eq!(Ok('1'), buffer.read()); - assert_eq!(Ok('2'), buffer.read()); - assert_eq!(Err(Error::EmptyBuffer), buffer.read()); + assert!(buffer.write(1).is_ok()); + buffer.overwrite(2); + assert_eq!(buffer.read(), Ok(1)); + assert_eq!(buffer.read(), Ok(2)); } #[test] #[ignore] fn overwrite_replaces_the_oldest_item_on_full_buffer() { let mut buffer = CircularBuffer::new(2); - assert!(buffer.write('1').is_ok()); - assert!(buffer.write('2').is_ok()); - buffer.overwrite('A'); - assert_eq!(Ok('2'), buffer.read()); - assert_eq!(Ok('A'), buffer.read()); + assert!(buffer.write(1).is_ok()); + assert!(buffer.write(2).is_ok()); + buffer.overwrite(3); + assert_eq!(buffer.read(), Ok(2)); + assert_eq!(buffer.read(), Ok(3)); } #[test] #[ignore] fn overwrite_replaces_the_oldest_item_remaining_in_buffer_following_a_read() { let mut buffer = CircularBuffer::new(3); - assert!(buffer.write('1').is_ok()); - assert!(buffer.write('2').is_ok()); - assert!(buffer.write('3').is_ok()); - assert_eq!(Ok('1'), buffer.read()); - assert!(buffer.write('4').is_ok()); - buffer.overwrite('5'); - assert_eq!(Ok('3'), buffer.read()); - assert_eq!(Ok('4'), buffer.read()); - assert_eq!(Ok('5'), buffer.read()); + assert!(buffer.write(1).is_ok()); + assert!(buffer.write(2).is_ok()); + assert!(buffer.write(3).is_ok()); + assert_eq!(buffer.read(), Ok(1)); + assert!(buffer.write(4).is_ok()); + buffer.overwrite(5); + assert_eq!(buffer.read(), Ok(3)); + assert_eq!(buffer.read(), Ok(4)); + assert_eq!(buffer.read(), Ok(5)); } #[test] #[ignore] -fn dropping_the_buffer_drops_its_elements() { - let element = Rc::new(()); - { - let mut buffer = CircularBuffer::new(1); - assert!(buffer.write(Rc::clone(&element)).is_ok()); - assert_eq!(Rc::strong_count(&element), 2); - } - assert_eq!(Rc::strong_count(&element), 1); +fn initial_clear_does_not_affect_wrapping_around() { + let mut buffer = CircularBuffer::new(2); + buffer.clear(); + assert!(buffer.write(1).is_ok()); + assert!(buffer.write(2).is_ok()); + buffer.overwrite(3); + buffer.overwrite(4); + assert_eq!(buffer.read(), Ok(3)); + assert_eq!(buffer.read(), Ok(4)); + assert_eq!(buffer.read(), Err(Error::EmptyBuffer)); } #[test] #[ignore] -fn integer_buffer() { - let mut buffer = CircularBuffer::new(2); - assert!(buffer.write(1).is_ok()); - assert!(buffer.write(2).is_ok()); - assert_eq!(Ok(1), buffer.read()); - assert!(buffer.write(-1).is_ok()); - assert_eq!(Ok(2), buffer.read()); - assert_eq!(Ok(-1), buffer.read()); - assert_eq!(Err(Error::EmptyBuffer), buffer.read()); +fn char_buffer() { + let mut buffer = CircularBuffer::new(1); + assert!(buffer.write('A').is_ok()); } #[test] #[ignore] fn string_buffer() { - let mut buffer = CircularBuffer::new(2); - buffer.write("".to_string()).unwrap(); - buffer.write("Testing".to_string()).unwrap(); - assert_eq!(0, buffer.read().unwrap().len()); - assert_eq!(Ok("Testing".to_string()), buffer.read()); + let mut buffer = CircularBuffer::new(1); + assert!(buffer.write("Testing".to_string()).is_ok()); +} + +#[test] +#[ignore] +fn clear_actually_frees_up_its_elements() { + let mut buffer = CircularBuffer::new(1); + let element = Rc::new(()); + assert!(buffer.write(Rc::clone(&element)).is_ok()); + assert_eq!(Rc::strong_count(&element), 2); + buffer.clear(); + assert_eq!(Rc::strong_count(&element), 1); +} + +#[test] +#[ignore] +fn dropping_the_buffer_drops_its_elements() { + let element = Rc::new(()); + { + let mut buffer = CircularBuffer::new(1); + assert!(buffer.write(Rc::clone(&element)).is_ok()); + assert_eq!(Rc::strong_count(&element), 2); + } + assert_eq!(Rc::strong_count(&element), 1); } From 2af9fe071eb28f39a841180b4019b1c03589298a Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Wed, 3 Apr 2024 07:42:01 +0200 Subject: [PATCH 275/436] roman-numerals: sync (#1894) [no important files changed] --- exercises/practice/roman-numerals/.meta/tests.toml | 3 +++ .../practice/roman-numerals/tests/roman-numerals.rs | 9 +++++++++ problem-specifications | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/exercises/practice/roman-numerals/.meta/tests.toml b/exercises/practice/roman-numerals/.meta/tests.toml index 57c6c4be8..709011b55 100644 --- a/exercises/practice/roman-numerals/.meta/tests.toml +++ b/exercises/practice/roman-numerals/.meta/tests.toml @@ -84,5 +84,8 @@ description = "3000 is MMM" [3bc4b41c-c2e6-49d9-9142-420691504336] description = "3001 is MMMI" +[2f89cad7-73f6-4d1b-857b-0ef531f68b7e] +description = "3888 is MMMDCCCLXXXVIII" + [4e18e96b-5fbb-43df-a91b-9cb511fe0856] description = "3999 is MMMCMXCIX" diff --git a/exercises/practice/roman-numerals/tests/roman-numerals.rs b/exercises/practice/roman-numerals/tests/roman-numerals.rs index c7d047e79..34df41ae1 100644 --- a/exercises/practice/roman-numerals/tests/roman-numerals.rs +++ b/exercises/practice/roman-numerals/tests/roman-numerals.rs @@ -224,6 +224,15 @@ fn number_3001_is_mmmi() { assert_eq!(output, expected); } +#[test] +#[ignore] +fn number_3888_is_mmmdccclxxxviii() { + let input = 3888; + let output = Roman::from(input).to_string(); + let expected = "MMMDCCCLXXXVIII"; + assert_eq!(output, expected); +} + #[test] #[ignore] fn number_3999_is_mmmcmxcix() { diff --git a/problem-specifications b/problem-specifications index 993abcc4e..8b6a412a9 160000 --- a/problem-specifications +++ b/problem-specifications @@ -1 +1 @@ -Subproject commit 993abcc4e327743a17497dab04f4a03f4f50c2ba +Subproject commit 8b6a412a949d9080b08869156067a16521c4d1ba From 8c249f3d8e95b94d0ebd5baf07a2a24fdf5fc023 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 9 Apr 2024 09:32:26 +0200 Subject: [PATCH 276/436] collatz-conjecture: sync (#1898) The additional test cases for overflow are incorrect even with u64 in the API. u128 can be used as needed to calculate the intermediate values without overflow. [no important files changed] --- .../.meta/test_template.tera | 16 ++++++ .../collatz-conjecture/.meta/tests.toml | 43 ++++++++++++++-- .../tests/collatz-conjecture.rs | 50 ++++++++----------- problem-specifications | 2 +- rust-tooling/generate/src/lib.rs | 6 ++- .../templates/default_test_template.tera | 2 +- 6 files changed, 84 insertions(+), 35 deletions(-) create mode 100644 exercises/practice/collatz-conjecture/.meta/test_template.tera diff --git a/exercises/practice/collatz-conjecture/.meta/test_template.tera b/exercises/practice/collatz-conjecture/.meta/test_template.tera new file mode 100644 index 000000000..eec8050c3 --- /dev/null +++ b/exercises/practice/collatz-conjecture/.meta/test_template.tera @@ -0,0 +1,16 @@ +use collatz_conjecture::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | snake_case }}() { + let output = collatz({{ test.input.number | json_encode() }}); + + let expected = {% if test.expected is object %} + None + {% else %} + Some({{ test.expected | json_encode() }}) + {% endif %}; + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/collatz-conjecture/.meta/tests.toml b/exercises/practice/collatz-conjecture/.meta/tests.toml index be690e975..0bd122074 100644 --- a/exercises/practice/collatz-conjecture/.meta/tests.toml +++ b/exercises/practice/collatz-conjecture/.meta/tests.toml @@ -1,3 +1,40 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[540a3d51-e7a6-47a5-92a3-4ad1838f0bfd] +description = "zero steps for one" + +[3d76a0a6-ea84-444a-821a-f7857c2c1859] +description = "divide if even" + +[754dea81-123c-429e-b8bc-db20b05a87b9] +description = "even and odd steps" + +[ecfd0210-6f85-44f6-8280-f65534892ff6] +description = "large number of even and odd steps" + +[7d4750e6-def9-4b86-aec7-9f7eb44f95a3] +description = "zero is an error" +include = false + +[2187673d-77d6-4543-975e-66df6c50e2da] +description = "zero is an error" +reimplements = "7d4750e6-def9-4b86-aec7-9f7eb44f95a3" + +[c6c795bf-a288-45e9-86a1-841359ad426d] +description = "negative value is an error" +include = false + +[ec11f479-56bc-47fd-a434-bcd7a31a7a2e] +description = "negative value is an error" +reimplements = "c6c795bf-a288-45e9-86a1-841359ad426d" +include = false +comment = "The exercise uses u64, which doesn't allow negative numbers." diff --git a/exercises/practice/collatz-conjecture/tests/collatz-conjecture.rs b/exercises/practice/collatz-conjecture/tests/collatz-conjecture.rs index 9c6690dc2..9bbced3ec 100644 --- a/exercises/practice/collatz-conjecture/tests/collatz-conjecture.rs +++ b/exercises/practice/collatz-conjecture/tests/collatz-conjecture.rs @@ -1,51 +1,45 @@ use collatz_conjecture::*; #[test] -fn one() { - assert_eq!(Some(0), collatz(1)); -} +fn zero_steps_for_one() { + let output = collatz(1); -#[test] -#[ignore] -fn sixteen() { - assert_eq!(Some(4), collatz(16)); + let expected = Some(0); + assert_eq!(output, expected); } #[test] #[ignore] -fn twelve() { - assert_eq!(Some(9), collatz(12)); -} +fn divide_if_even() { + let output = collatz(16); -#[test] -#[ignore] -fn one_million() { - assert_eq!(Some(152), collatz(1_000_000)); + let expected = Some(4); + assert_eq!(output, expected); } #[test] #[ignore] -fn zero() { - assert_eq!(None, collatz(0)); -} +fn even_and_odd_steps() { + let output = collatz(12); -#[test] -#[ignore] -fn test_110243094271() { - let val = 110243094271; - assert_eq!(None, collatz(val)); + let expected = Some(9); + assert_eq!(output, expected); } #[test] #[ignore] -fn max_div_3() { - let max = u64::MAX / 3; - assert_eq!(None, collatz(max)); +fn large_number_of_even_and_odd_steps() { + let output = collatz(1000000); + + let expected = Some(152); + assert_eq!(output, expected); } #[test] #[ignore] -fn max_minus_1() { - let max = u64::MAX - 1; - assert_eq!(None, collatz(max)); +fn zero_is_an_error() { + let output = collatz(0); + + let expected = None; + assert_eq!(output, expected); } diff --git a/problem-specifications b/problem-specifications index 8b6a412a9..381bb40d6 160000 --- a/problem-specifications +++ b/problem-specifications @@ -1 +1 @@ -Subproject commit 8b6a412a949d9080b08869156067a16521c4d1ba +Subproject commit 381bb40d6e01d175be183eece44e60897dc9b12d diff --git a/rust-tooling/generate/src/lib.rs b/rust-tooling/generate/src/lib.rs index 1f2dfa757..0c7d31aae 100644 --- a/rust-tooling/generate/src/lib.rs +++ b/rust-tooling/generate/src/lib.rs @@ -91,8 +91,10 @@ fn generate_tests(slug: &str) -> Result { }; let excluded_tests = get_excluded_tests(slug); let mut template = get_test_template(slug).context("failed to get test template")?; - if template.get_template_names().next() != Some("test_template.tera") { - anyhow::bail!("'test_template.tera' not found"); + if template.get_template_names().next().is_none() { + template + .add_raw_template("test_template.tera", TEST_TEMPLATE) + .context("failed to add default template")?; } for (name, filter) in CUSTOM_FILTERS { template.register_filter(name, filter); diff --git a/rust-tooling/generate/templates/default_test_template.tera b/rust-tooling/generate/templates/default_test_template.tera index fe5943e36..448333d3f 100644 --- a/rust-tooling/generate/templates/default_test_template.tera +++ b/rust-tooling/generate/templates/default_test_template.tera @@ -3,7 +3,7 @@ use crate_name::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | slugify | replace(from="-", to="_") }}() { +fn {{ test.description | snake_case }}() { let input = {{ test.input | json_encode() }}; let output = function_name(input); let expected = {{ test.expected | json_encode() }}; From 2c18582b0bc7d98ac12b66353b3cf5fa0cd92dea Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 9 Apr 2024 09:34:18 +0200 Subject: [PATCH 277/436] sieve: improve range start in approaches (#1897) --- exercises/practice/sieve/.approaches/introduction.md | 2 +- .../sieve/.approaches/ranges-and-filtermap/content.md | 4 ++-- .../sieve/.approaches/ranges-and-filtermap/snippet.txt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/exercises/practice/sieve/.approaches/introduction.md b/exercises/practice/sieve/.approaches/introduction.md index 7291c2883..a247dc0e8 100644 --- a/exercises/practice/sieve/.approaches/introduction.md +++ b/exercises/practice/sieve/.approaches/introduction.md @@ -20,7 +20,7 @@ pub fn primes_up_to(upper_bound: u64) -> Vec { (2..numbers.len()) .filter_map(|i| { let prime = numbers[i].take()? as usize; - (prime + prime..=upper_bound) + (prime * prime..=upper_bound) .step_by(prime) .for_each(|j| numbers[j] = None); Some(prime as u64) diff --git a/exercises/practice/sieve/.approaches/ranges-and-filtermap/content.md b/exercises/practice/sieve/.approaches/ranges-and-filtermap/content.md index cba7635de..d9fc134ad 100644 --- a/exercises/practice/sieve/.approaches/ranges-and-filtermap/content.md +++ b/exercises/practice/sieve/.approaches/ranges-and-filtermap/content.md @@ -7,7 +7,7 @@ pub fn primes_up_to(upper_bound: u64) -> Vec { (2..numbers.len()) .filter_map(|i| { let prime = numbers[i].take()? as usize; - (prime + prime..=upper_bound) + (prime * prime..=upper_bound) .step_by(prime) .for_each(|j| numbers[j] = None); Some(prime as u64) @@ -25,7 +25,7 @@ Each number from the range is passed to the [`filter_map()`][filtermap]. The [closure][closure] (also known as a lambda) in the body of the `filter_map()` uses the [`take()`][take] method, combined with the unwrap operator (`?`), to get the element value in the `Vec` at the index of the number passed in from the range. If the element value is `None`, then no further processing happens in the lambda for that iteration. -If the element value is `Some` number, then an inner range is defined, starting from the element value plus itself and going through the upper bound. +If the element value is `Some` number, then an inner range is defined, starting from the element value times itself and going through the upper bound. The [`step_by()`][stepby] method is used to traverse the range in steps the size of the element value. diff --git a/exercises/practice/sieve/.approaches/ranges-and-filtermap/snippet.txt b/exercises/practice/sieve/.approaches/ranges-and-filtermap/snippet.txt index f098c28c6..a331efc85 100644 --- a/exercises/practice/sieve/.approaches/ranges-and-filtermap/snippet.txt +++ b/exercises/practice/sieve/.approaches/ranges-and-filtermap/snippet.txt @@ -1,7 +1,7 @@ (2..numbers.len()) .filter_map(|i| { let prime = numbers[i].take()? as usize; - (prime + prime..=upper_bound) + (prime * prime..=upper_bound) .step_by(prime) .for_each(|j| numbers[j] = None); Some(prime as u64) From bbae1929663ac17b4b053a6da6a8831c703aa833 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 9 Apr 2024 12:40:47 +0200 Subject: [PATCH 278/436] protein-translation: sync (#1895) [no important files changed] --- .../.meta/additional-tests.json | 182 +++++++++++++ .../.meta/test_template.tera | 68 +++++ .../protein-translation/.meta/tests.toml | 105 +++++++- .../tests/protein-translation.rs | 250 +++++++++++++++--- problem-specifications | 2 +- 5 files changed, 568 insertions(+), 39 deletions(-) create mode 100644 exercises/practice/protein-translation/.meta/additional-tests.json create mode 100644 exercises/practice/protein-translation/.meta/test_template.tera diff --git a/exercises/practice/protein-translation/.meta/additional-tests.json b/exercises/practice/protein-translation/.meta/additional-tests.json new file mode 100644 index 000000000..530dc1483 --- /dev/null +++ b/exercises/practice/protein-translation/.meta/additional-tests.json @@ -0,0 +1,182 @@ +[ + { + "uuid": "ecafab60-89f2-4680-9286-56b423e8a7b9", + "description": "methionine", + "comments": [ + "The original design of the exercise deviates from problem-specifications", + "by having a name_for function for individual codons.", + "Upstreaming is impossible as it would change the design of the exercise.", + "Omitting them would leave the function untested, which would be confusing." + ], + "property": "name_for", + "input": { + "name": "AUG" + }, + "expected": "Some(\"methionine\")" + }, + { + "uuid": "ecafab60-89f2-4680-9286-56b423e8a7b9", + "description": "cysteine tgt", + "comments": [ + "The original design of the exercise deviates from problem-specifications", + "by having a name_for function for individual codons.", + "Upstreaming is impossible as it would change the design of the exercise.", + "Omitting them would leave the function untested, which would be confusing." + ], + "property": "name_for", + "input": { + "name": "UGU" + }, + "expected": "Some(\"cysteine\")" + }, + { + "uuid": "ecafab60-89f2-4680-9286-56b423e8a7b9", + "description": "stop", + "comments": [ + "The original design of the exercise deviates from problem-specifications", + "by having a name_for function for individual codons.", + "Upstreaming is impossible as it would change the design of the exercise.", + "Omitting them would leave the function untested, which would be confusing." + ], + "property": "name_for", + "input": { + "name": "UAA" + }, + "expected": "Some(\"stop codon\")" + }, + { + "uuid": "ecafab60-89f2-4680-9286-56b423e8a7b9", + "description": "valine", + "comments": [ + "The original design of the exercise deviates from problem-specifications", + "by having a name_for function for individual codons.", + "Upstreaming is impossible as it would change the design of the exercise.", + "Omitting them would leave the function untested, which would be confusing." + ], + "property": "name_for", + "input": { + "name": "GUU" + }, + "expected": "Some(\"valine\")" + }, + { + "uuid": "ecafab60-89f2-4680-9286-56b423e8a7b9", + "description": "isoleucine", + "comments": [ + "The original design of the exercise deviates from problem-specifications", + "by having a name_for function for individual codons.", + "Upstreaming is impossible as it would change the design of the exercise.", + "Omitting them would leave the function untested, which would be confusing." + ], + "property": "name_for", + "input": { + "name": "AUU" + }, + "expected": "Some(\"isoleucine\")" + }, + { + "uuid": "ecafab60-89f2-4680-9286-56b423e8a7b9", + "description": "arginine cga", + "comments": [ + "The original design of the exercise deviates from problem-specifications", + "by having a name_for function for individual codons.", + "Upstreaming is impossible as it would change the design of the exercise.", + "Omitting them would leave the function untested, which would be confusing." + ], + "property": "name_for", + "input": { + "name": "CGA" + }, + "expected": "Some(\"arginine\")" + }, + { + "uuid": "ecafab60-89f2-4680-9286-56b423e8a7b9", + "description": "arginine aga", + "comments": [ + "The original design of the exercise deviates from problem-specifications", + "by having a name_for function for individual codons.", + "Upstreaming is impossible as it would change the design of the exercise.", + "Omitting them would leave the function untested, which would be confusing." + ], + "property": "name_for", + "input": { + "name": "AGA" + }, + "expected": "Some(\"arginine\")" + }, + { + "uuid": "ecafab60-89f2-4680-9286-56b423e8a7b9", + "description": "arginine agg", + "comments": [ + "The original design of the exercise deviates from problem-specifications", + "by having a name_for function for individual codons.", + "Upstreaming is impossible as it would change the design of the exercise.", + "Omitting them would leave the function untested, which would be confusing." + ], + "property": "name_for", + "input": { + "name": "AGG" + }, + "expected": "Some(\"arginine\")" + }, + { + "uuid": "ecafab60-89f2-4680-9286-56b423e8a7b9", + "description": "empty is invalid", + "comments": [ + "The original design of the exercise deviates from problem-specifications", + "by having a name_for function for individual codons.", + "Upstreaming is impossible as it would change the design of the exercise.", + "Omitting them would leave the function untested, which would be confusing." + ], + "property": "name_for", + "input": { + "name": "" + }, + "expected": "None" + }, + { + "uuid": "ecafab60-89f2-4680-9286-56b423e8a7b9", + "description": "x is not shorthand so is invalid", + "comments": [ + "The original design of the exercise deviates from problem-specifications", + "by having a name_for function for individual codons.", + "Upstreaming is impossible as it would change the design of the exercise.", + "Omitting them would leave the function untested, which would be confusing." + ], + "property": "name_for", + "input": { + "name": "VWX" + }, + "expected": "None" + }, + { + "uuid": "ecafab60-89f2-4680-9286-56b423e8a7b9", + "description": "too short is invalid", + "comments": [ + "The original design of the exercise deviates from problem-specifications", + "by having a name_for function for individual codons.", + "Upstreaming is impossible as it would change the design of the exercise.", + "Omitting them would leave the function untested, which would be confusing." + ], + "property": "name_for", + "input": { + "name": "AU" + }, + "expected": "None" + }, + { + "uuid": "ecafab60-89f2-4680-9286-56b423e8a7b9", + "description": "too long is invalid", + "comments": [ + "The original design of the exercise deviates from problem-specifications", + "by having a name_for function for individual codons.", + "Upstreaming is impossible as it would change the design of the exercise.", + "Omitting them would leave the function untested, which would be confusing." + ], + "property": "name_for", + "input": { + "name": "ATTA" + }, + "expected": "None" + } +] diff --git a/exercises/practice/protein-translation/.meta/test_template.tera b/exercises/practice/protein-translation/.meta/test_template.tera new file mode 100644 index 000000000..e124dbac1 --- /dev/null +++ b/exercises/practice/protein-translation/.meta/test_template.tera @@ -0,0 +1,68 @@ +use protein_translation::*; + +{% for test in cases %} +{# custom name_for tests are first, of_rna is supposed to build on it #} +{% if test.property != "name_for" %}{% continue %}{% endif %} +#[test] +#[ignore] +fn {{ test.description | snake_case }}() { + let info = parse(make_pairs()); + assert_eq!(info.name_for({{ test.input.name | json_encode() }}), {{ test.expected }}); +} +{% endfor -%} + +{% for test in cases %} +{% if test.property != "proteins" %}{% continue %}{% endif %} +#[test] +#[ignore] +fn {{ test.description | snake_case }}() { + let info = parse(make_pairs()); + assert_eq!( + info.of_rna({{ test.input.strand | json_encode() }}), + {% if test.expected is object %} + None + {% else %} + Some(vec![ + {% for s in test.expected %} + "{{ s | lower }}" {% if not loop.last %} , {% endif %} + {% endfor %} + ]) + {% endif %}, + ); +} +{% endfor -%} + +// The input data constructor. Returns a list of codon, name pairs. +fn make_pairs() -> Vec<(&'static str, &'static str)> { + let grouped = vec![ + ("isoleucine", vec!["AUU", "AUC", "AUA"]), + ("valine", vec!["GUU", "GUC", "GUA", "GUG"]), + ("phenylalanine", vec!["UUU", "UUC"]), + ("methionine", vec!["AUG"]), + ("cysteine", vec!["UGU", "UGC"]), + ("alanine", vec!["GCU", "GCC", "GCA", "GCG"]), + ("glycine", vec!["GGU", "GGC", "GGA", "GGG"]), + ("proline", vec!["CCU", "CCC", "CCA", "CCG"]), + ("threonine", vec!["ACU", "ACC", "ACA", "ACG"]), + ("serine", vec!["UCU", "UCC", "UCA", "UCG"]), + ("tyrosine", vec!["UAU", "UAC"]), + ("tryptophan", vec!["UGG"]), + ("glutamine", vec!["CAA", "CAG"]), + ("asparagine", vec!["AAU", "AAC"]), + ("histidine", vec!["CAU", "CAC"]), + ("glutamic acid", vec!["GAA", "GAG"]), + ("aspartic acid", vec!["GAU", "GAC"]), + ("lysine", vec!["AAA", "AAG"]), + ("arginine", vec!["CGU", "CGC", "CGA", "CGG", "AGA", "AGG"]), + ("leucine", vec!["UUA", "UUG"]), + ("stop codon", vec!["UAA", "UAG", "UGA"]), + ]; + let mut pairs = Vec::<(&'static str, &'static str)>::new(); + for (name, codons) in grouped.into_iter() { + for codon in codons { + pairs.push((codon, name)); + } + } + pairs.sort_by(|&(_, a), &(_, b)| a.cmp(b)); + pairs +} diff --git a/exercises/practice/protein-translation/.meta/tests.toml b/exercises/practice/protein-translation/.meta/tests.toml index be690e975..804b446e6 100644 --- a/exercises/practice/protein-translation/.meta/tests.toml +++ b/exercises/practice/protein-translation/.meta/tests.toml @@ -1,3 +1,102 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[2c44f7bf-ba20-43f7-a3bf-f2219c0c3f98] +description = "Empty RNA sequence results in no proteins" + +[96d3d44f-34a2-4db4-84cd-fff523e069be] +description = "Methionine RNA sequence" + +[1b4c56d8-d69f-44eb-be0e-7b17546143d9] +description = "Phenylalanine RNA sequence 1" + +[81b53646-bd57-4732-b2cb-6b1880e36d11] +description = "Phenylalanine RNA sequence 2" + +[42f69d4f-19d2-4d2c-a8b0-f0ae9ee1b6b4] +description = "Leucine RNA sequence 1" + +[ac5edadd-08ed-40a3-b2b9-d82bb50424c4] +description = "Leucine RNA sequence 2" + +[8bc36e22-f984-44c3-9f6b-ee5d4e73f120] +description = "Serine RNA sequence 1" + +[5c3fa5da-4268-44e5-9f4b-f016ccf90131] +description = "Serine RNA sequence 2" + +[00579891-b594-42b4-96dc-7ff8bf519606] +description = "Serine RNA sequence 3" + +[08c61c3b-fa34-4950-8c4a-133945570ef6] +description = "Serine RNA sequence 4" + +[54e1e7d8-63c0-456d-91d2-062c72f8eef5] +description = "Tyrosine RNA sequence 1" + +[47bcfba2-9d72-46ad-bbce-22f7666b7eb1] +description = "Tyrosine RNA sequence 2" + +[3a691829-fe72-43a7-8c8e-1bd083163f72] +description = "Cysteine RNA sequence 1" + +[1b6f8a26-ca2f-43b8-8262-3ee446021767] +description = "Cysteine RNA sequence 2" + +[1e91c1eb-02c0-48a0-9e35-168ad0cb5f39] +description = "Tryptophan RNA sequence" + +[e547af0b-aeab-49c7-9f13-801773a73557] +description = "STOP codon RNA sequence 1" + +[67640947-ff02-4f23-a2ef-816f8a2ba72e] +description = "STOP codon RNA sequence 2" + +[9c2ad527-ebc9-4ace-808b-2b6447cb54cb] +description = "STOP codon RNA sequence 3" + +[f4d9d8ee-00a8-47bf-a1e3-1641d4428e54] +description = "Sequence of two protein codons translates into proteins" + +[dd22eef3-b4f1-4ad6-bb0b-27093c090a9d] +description = "Sequence of two different protein codons translates into proteins" + +[d0f295df-fb70-425c-946c-ec2ec185388e] +description = "Translate RNA strand into correct protein list" + +[e30e8505-97ec-4e5f-a73e-5726a1faa1f4] +description = "Translation stops if STOP codon at beginning of sequence" + +[5358a20b-6f4c-4893-bce4-f929001710f3] +description = "Translation stops if STOP codon at end of two-codon sequence" + +[ba16703a-1a55-482f-bb07-b21eef5093a3] +description = "Translation stops if STOP codon at end of three-codon sequence" + +[4089bb5a-d5b4-4e71-b79e-b8d1f14a2911] +description = "Translation stops if STOP codon in middle of three-codon sequence" + +[2c2a2a60-401f-4a80-b977-e0715b23b93d] +description = "Translation stops if STOP codon in middle of six-codon sequence" + +[1e75ea2a-f907-4994-ae5c-118632a1cb0f] +description = "Non-existing codon can't translate" +include = false + +[9eac93f3-627a-4c90-8653-6d0a0595bc6f] +description = "Unknown amino acids, not part of a codon, can't translate" +reimplements = "1e75ea2a-f907-4994-ae5c-118632a1cb0f" + +[9d73899f-e68e-4291-b1e2-7bf87c00f024] +description = "Incomplete RNA sequence can't translate" + +[43945cf7-9968-402d-ab9f-b8a28750b050] +description = "Incomplete RNA sequence can translate if valid until a STOP codon" diff --git a/exercises/practice/protein-translation/tests/protein-translation.rs b/exercises/practice/protein-translation/tests/protein-translation.rs index d7c0369d0..a8ece5736 100644 --- a/exercises/practice/protein-translation/tests/protein-translation.rs +++ b/exercises/practice/protein-translation/tests/protein-translation.rs @@ -1,125 +1,304 @@ -use protein_translation as proteins; +use protein_translation::*; #[test] fn methionine() { - let info = proteins::parse(make_pairs()); + let info = parse(make_pairs()); assert_eq!(info.name_for("AUG"), Some("methionine")); } #[test] #[ignore] fn cysteine_tgt() { - let info = proteins::parse(make_pairs()); + let info = parse(make_pairs()); assert_eq!(info.name_for("UGU"), Some("cysteine")); } #[test] #[ignore] fn stop() { - let info = proteins::parse(make_pairs()); + let info = parse(make_pairs()); assert_eq!(info.name_for("UAA"), Some("stop codon")); } #[test] #[ignore] fn valine() { - let info = proteins::parse(make_pairs()); + let info = parse(make_pairs()); assert_eq!(info.name_for("GUU"), Some("valine")); } #[test] #[ignore] fn isoleucine() { - let info = proteins::parse(make_pairs()); + let info = parse(make_pairs()); assert_eq!(info.name_for("AUU"), Some("isoleucine")); } #[test] #[ignore] -fn arginine_name() { - let info = proteins::parse(make_pairs()); +fn arginine_cga() { + let info = parse(make_pairs()); assert_eq!(info.name_for("CGA"), Some("arginine")); +} + +#[test] +#[ignore] +fn arginine_aga() { + let info = parse(make_pairs()); assert_eq!(info.name_for("AGA"), Some("arginine")); +} + +#[test] +#[ignore] +fn arginine_agg() { + let info = parse(make_pairs()); assert_eq!(info.name_for("AGG"), Some("arginine")); } #[test] #[ignore] fn empty_is_invalid() { - let info = proteins::parse(make_pairs()); - assert!(info.name_for("").is_none()); + let info = parse(make_pairs()); + assert_eq!(info.name_for(""), None); } #[test] #[ignore] fn x_is_not_shorthand_so_is_invalid() { - let info = proteins::parse(make_pairs()); - assert!(info.name_for("VWX").is_none()); + let info = parse(make_pairs()); + assert_eq!(info.name_for("VWX"), None); } #[test] #[ignore] fn too_short_is_invalid() { - let info = proteins::parse(make_pairs()); - assert!(info.name_for("AU").is_none()); + let info = parse(make_pairs()); + assert_eq!(info.name_for("AU"), None); } #[test] #[ignore] fn too_long_is_invalid() { - let info = proteins::parse(make_pairs()); - assert!(info.name_for("ATTA").is_none()); + let info = parse(make_pairs()); + assert_eq!(info.name_for("ATTA"), None); +} + +#[test] +#[ignore] +fn empty_rna_sequence_results_in_no_proteins() { + let info = parse(make_pairs()); + assert_eq!(info.of_rna(""), Some(vec![]),); +} + +#[test] +#[ignore] +fn methionine_rna_sequence() { + let info = parse(make_pairs()); + assert_eq!(info.of_rna("AUG"), Some(vec!["methionine"]),); +} + +#[test] +#[ignore] +fn phenylalanine_rna_sequence_1() { + let info = parse(make_pairs()); + assert_eq!(info.of_rna("UUU"), Some(vec!["phenylalanine"]),); +} + +#[test] +#[ignore] +fn phenylalanine_rna_sequence_2() { + let info = parse(make_pairs()); + assert_eq!(info.of_rna("UUC"), Some(vec!["phenylalanine"]),); +} + +#[test] +#[ignore] +fn leucine_rna_sequence_1() { + let info = parse(make_pairs()); + assert_eq!(info.of_rna("UUA"), Some(vec!["leucine"]),); +} + +#[test] +#[ignore] +fn leucine_rna_sequence_2() { + let info = parse(make_pairs()); + assert_eq!(info.of_rna("UUG"), Some(vec!["leucine"]),); +} + +#[test] +#[ignore] +fn serine_rna_sequence_1() { + let info = parse(make_pairs()); + assert_eq!(info.of_rna("UCU"), Some(vec!["serine"]),); +} + +#[test] +#[ignore] +fn serine_rna_sequence_2() { + let info = parse(make_pairs()); + assert_eq!(info.of_rna("UCC"), Some(vec!["serine"]),); +} + +#[test] +#[ignore] +fn serine_rna_sequence_3() { + let info = parse(make_pairs()); + assert_eq!(info.of_rna("UCA"), Some(vec!["serine"]),); +} + +#[test] +#[ignore] +fn serine_rna_sequence_4() { + let info = parse(make_pairs()); + assert_eq!(info.of_rna("UCG"), Some(vec!["serine"]),); +} + +#[test] +#[ignore] +fn tyrosine_rna_sequence_1() { + let info = parse(make_pairs()); + assert_eq!(info.of_rna("UAU"), Some(vec!["tyrosine"]),); } #[test] #[ignore] -fn translates_rna_strand_into_correct_protein() { - let info = proteins::parse(make_pairs()); +fn tyrosine_rna_sequence_2() { + let info = parse(make_pairs()); + assert_eq!(info.of_rna("UAC"), Some(vec!["tyrosine"]),); +} + +#[test] +#[ignore] +fn cysteine_rna_sequence_1() { + let info = parse(make_pairs()); + assert_eq!(info.of_rna("UGU"), Some(vec!["cysteine"]),); +} + +#[test] +#[ignore] +fn cysteine_rna_sequence_2() { + let info = parse(make_pairs()); + assert_eq!(info.of_rna("UGC"), Some(vec!["cysteine"]),); +} + +#[test] +#[ignore] +fn tryptophan_rna_sequence() { + let info = parse(make_pairs()); + assert_eq!(info.of_rna("UGG"), Some(vec!["tryptophan"]),); +} + +#[test] +#[ignore] +fn stop_codon_rna_sequence_1() { + let info = parse(make_pairs()); + assert_eq!(info.of_rna("UAA"), Some(vec![]),); +} + +#[test] +#[ignore] +fn stop_codon_rna_sequence_2() { + let info = parse(make_pairs()); + assert_eq!(info.of_rna("UAG"), Some(vec![]),); +} + +#[test] +#[ignore] +fn stop_codon_rna_sequence_3() { + let info = parse(make_pairs()); + assert_eq!(info.of_rna("UGA"), Some(vec![]),); +} + +#[test] +#[ignore] +fn sequence_of_two_protein_codons_translates_into_proteins() { + let info = parse(make_pairs()); + assert_eq!( + info.of_rna("UUUUUU"), + Some(vec!["phenylalanine", "phenylalanine"]), + ); +} + +#[test] +#[ignore] +fn sequence_of_two_different_protein_codons_translates_into_proteins() { + let info = parse(make_pairs()); + assert_eq!(info.of_rna("UUAUUG"), Some(vec!["leucine", "leucine"]),); +} + +#[test] +#[ignore] +fn translate_rna_strand_into_correct_protein_list() { + let info = parse(make_pairs()); assert_eq!( info.of_rna("AUGUUUUGG"), - Some(vec!["methionine", "phenylalanine", "tryptophan"]) + Some(vec!["methionine", "phenylalanine", "tryptophan"]), ); } #[test] #[ignore] -fn stops_translation_if_stop_codon_present() { - let info = proteins::parse(make_pairs()); +fn translation_stops_if_stop_codon_at_beginning_of_sequence() { + let info = parse(make_pairs()); + assert_eq!(info.of_rna("UAGUGG"), Some(vec![]),); +} + +#[test] +#[ignore] +fn translation_stops_if_stop_codon_at_end_of_two_codon_sequence() { + let info = parse(make_pairs()); + assert_eq!(info.of_rna("UGGUAG"), Some(vec!["tryptophan"]),); +} + +#[test] +#[ignore] +fn translation_stops_if_stop_codon_at_end_of_three_codon_sequence() { + let info = parse(make_pairs()); assert_eq!( info.of_rna("AUGUUUUAA"), - Some(vec!["methionine", "phenylalanine"]) + Some(vec!["methionine", "phenylalanine"]), ); } #[test] #[ignore] -fn stops_translation_of_longer_strand() { - let info = proteins::parse(make_pairs()); +fn translation_stops_if_stop_codon_in_middle_of_three_codon_sequence() { + let info = parse(make_pairs()); + assert_eq!(info.of_rna("UGGUAGUGG"), Some(vec!["tryptophan"]),); +} + +#[test] +#[ignore] +fn translation_stops_if_stop_codon_in_middle_of_six_codon_sequence() { + let info = parse(make_pairs()); assert_eq!( info.of_rna("UGGUGUUAUUAAUGGUUU"), - Some(vec!["tryptophan", "cysteine", "tyrosine"]) + Some(vec!["tryptophan", "cysteine", "tyrosine"]), ); } #[test] #[ignore] -fn invalid_codons() { - let info = proteins::parse(make_pairs()); - assert!(info.of_rna("CARROT").is_none()); +fn unknown_amino_acids_not_part_of_a_codon_can_t_translate() { + let info = parse(make_pairs()); + assert_eq!(info.of_rna("XYZ"), None,); } #[test] #[ignore] -fn invalid_length() { - let info = proteins::parse(make_pairs()); - assert!(info.of_rna("AUGUA").is_none()); +fn incomplete_rna_sequence_can_t_translate() { + let info = parse(make_pairs()); + assert_eq!(info.of_rna("AUGU"), None,); } #[test] #[ignore] -fn valid_stopped_rna() { - let info = proteins::parse(make_pairs()); - assert_eq!(info.of_rna("AUGUAAASDF"), Some(vec!["methionine"])); +fn incomplete_rna_sequence_can_translate_if_valid_until_a_stop_codon() { + let info = parse(make_pairs()); + assert_eq!( + info.of_rna("UUCUUCUAAUGGU"), + Some(vec!["phenylalanine", "phenylalanine"]), + ); } // The input data constructor. Returns a list of codon, name pairs. @@ -134,7 +313,7 @@ fn make_pairs() -> Vec<(&'static str, &'static str)> { ("glycine", vec!["GGU", "GGC", "GGA", "GGG"]), ("proline", vec!["CCU", "CCC", "CCA", "CCG"]), ("threonine", vec!["ACU", "ACC", "ACA", "ACG"]), - ("serine", vec!["AGU", "AGC"]), + ("serine", vec!["UCU", "UCC", "UCA", "UCG"]), ("tyrosine", vec!["UAU", "UAC"]), ("tryptophan", vec!["UGG"]), ("glutamine", vec!["CAA", "CAG"]), @@ -144,6 +323,7 @@ fn make_pairs() -> Vec<(&'static str, &'static str)> { ("aspartic acid", vec!["GAU", "GAC"]), ("lysine", vec!["AAA", "AAG"]), ("arginine", vec!["CGU", "CGC", "CGA", "CGG", "AGA", "AGG"]), + ("leucine", vec!["UUA", "UUG"]), ("stop codon", vec!["UAA", "UAG", "UGA"]), ]; let mut pairs = Vec::<(&'static str, &'static str)>::new(); diff --git a/problem-specifications b/problem-specifications index 381bb40d6..72d52dd52 160000 --- a/problem-specifications +++ b/problem-specifications @@ -1 +1 @@ -Subproject commit 381bb40d6e01d175be183eece44e60897dc9b12d +Subproject commit 72d52dd5287ef7ec8c2fbb56889c220476f8baf8 From efc750293a7c1118e852e8a0bc73f5d975b23075 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Wed, 10 Apr 2024 21:20:50 +0200 Subject: [PATCH 279/436] generator: expose configlet's offline flag (#1899) --- rust-tooling/generate/src/cli.rs | 10 +++++++++ rust-tooling/generate/src/main.rs | 36 ++++++++++++++++++------------- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/rust-tooling/generate/src/cli.rs b/rust-tooling/generate/src/cli.rs index 7d1c03202..37787dafe 100644 --- a/rust-tooling/generate/src/cli.rs +++ b/rust-tooling/generate/src/cli.rs @@ -37,12 +37,17 @@ pub struct AddArgs { #[arg(short, long)] difficulty: Option, + + /// do not update problem-specifications cache + #[arg(long)] + offline: bool, } pub struct FullAddArgs { pub slug: String, pub name: String, pub difficulty: track_config::Difficulty, + pub offline: bool, } impl AddArgs { @@ -64,6 +69,7 @@ impl AddArgs { slug, name, difficulty, + offline: self.offline, }) } } @@ -73,6 +79,10 @@ pub struct UpdateArgs { /// slug of the exercise to update #[arg(short, long)] slug: Option, + + /// do not update problem-specifications cache + #[arg(long)] + pub offline: bool, } impl UpdateArgs { diff --git a/rust-tooling/generate/src/main.rs b/rust-tooling/generate/src/main.rs index 9d8514efd..0299c5f51 100644 --- a/rust-tooling/generate/src/main.rs +++ b/rust-tooling/generate/src/main.rs @@ -23,6 +23,7 @@ fn add_exercise(args: AddArgs) -> Result<()> { slug, name, difficulty, + offline, } = args.unwrap_args_or_prompt()?; let config = track_config::PracticeExercise::new(slug.clone(), name, difficulty); @@ -41,35 +42,40 @@ Added your exercise to config.json. You can add practices, prerequisites and topics if you like." ); - make_configlet_generate_what_it_can(&slug)?; + make_configlet_generate_what_it_can(&slug, offline)?; let is_update = false; generate_exercise_files(&slug, is_update) } fn update_exercise(args: UpdateArgs) -> Result<()> { + let offline = args.offline; let slug = args.unwrap_slug_or_prompt()?; - make_configlet_generate_what_it_can(&slug)?; + make_configlet_generate_what_it_can(&slug, offline)?; let is_update = true; generate_exercise_files(&slug, is_update) } -fn make_configlet_generate_what_it_can(slug: &str) -> Result<()> { +fn make_configlet_generate_what_it_can(slug: &str, offline: bool) -> Result<()> { let status = std::process::Command::new("just") - .args([ - "configlet", - "sync", - "--update", - "--yes", - "--docs", - "--metadata", - "--tests", - "include", - "--exercise", - slug, - ]) + .args( + [ + "configlet", + "sync", + "--update", + "--yes", + "--docs", + "--metadata", + "--tests", + "include", + "--exercise", + slug, + ] + .into_iter() + .chain(offline.then_some("--offline")), + ) .status() .context("failed to run configlet sync")?; if !status.success() { From d389f51ac28be546b828acb1972ba36d1b4f8635 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Thu, 11 Apr 2024 18:25:12 +0200 Subject: [PATCH 280/436] pangram: sync (#1900) [no important files changed] --- .../practice/pangram/.meta/test_template.tera | 14 ++++++ exercises/practice/pangram/.meta/tests.toml | 48 +++++++++++++++++-- exercises/practice/pangram/tests/pangram.rs | 34 ++++++------- 3 files changed, 76 insertions(+), 20 deletions(-) create mode 100644 exercises/practice/pangram/.meta/test_template.tera diff --git a/exercises/practice/pangram/.meta/test_template.tera b/exercises/practice/pangram/.meta/test_template.tera new file mode 100644 index 000000000..49c14ea0b --- /dev/null +++ b/exercises/practice/pangram/.meta/test_template.tera @@ -0,0 +1,14 @@ +use pangram::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | snake_case }}() { + let sentence = {{ test.input.sentence | json_encode() }}; + {% if test.expected -%} + assert!(is_pangram(sentence)); + {% else -%} + assert!(!is_pangram(sentence)); + {% endif -%} +} +{% endfor -%} diff --git a/exercises/practice/pangram/.meta/tests.toml b/exercises/practice/pangram/.meta/tests.toml index be690e975..10b5a335a 100644 --- a/exercises/practice/pangram/.meta/tests.toml +++ b/exercises/practice/pangram/.meta/tests.toml @@ -1,3 +1,45 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[64f61791-508e-4f5c-83ab-05de042b0149] +description = "empty sentence" + +[74858f80-4a4d-478b-8a5e-c6477e4e4e84] +description = "perfect lower case" + +[61288860-35ca-4abe-ba08-f5df76ecbdcd] +description = "only lower case" + +[6564267d-8ac5-4d29-baf2-e7d2e304a743] +description = "missing the letter 'x'" + +[c79af1be-d715-4cdb-a5f2-b2fa3e7e0de0] +description = "missing the letter 'h'" + +[d835ec38-bc8f-48e4-9e36-eb232427b1df] +description = "with underscores" + +[8cc1e080-a178-4494-b4b3-06982c9be2a8] +description = "with numbers" + +[bed96b1c-ff95-45b8-9731-fdbdcb6ede9a] +description = "missing letters replaced by numbers" + +[938bd5d8-ade5-40e2-a2d9-55a338a01030] +description = "mixed case and punctuation" + +[2577bf54-83c8-402d-a64b-a2c0f7bb213a] +description = "case insensitive" +include = false + +[7138e389-83e4-4c6e-8413-1e40a0076951] +description = "a-m and A-M are 26 different characters but not a pangram" +reimplements = "2577bf54-83c8-402d-a64b-a2c0f7bb213a" diff --git a/exercises/practice/pangram/tests/pangram.rs b/exercises/practice/pangram/tests/pangram.rs index 27edf4cdf..2471315ef 100644 --- a/exercises/practice/pangram/tests/pangram.rs +++ b/exercises/practice/pangram/tests/pangram.rs @@ -1,70 +1,70 @@ use pangram::*; #[test] -fn empty_strings_are_not_pangrams() { +fn empty_sentence() { let sentence = ""; assert!(!is_pangram(sentence)); } #[test] #[ignore] -fn classic_pangram_is_a_pangram() { - let sentence = "the quick brown fox jumps over the lazy dog"; +fn perfect_lower_case() { + let sentence = "abcdefghijklmnopqrstuvwxyz"; assert!(is_pangram(sentence)); } #[test] #[ignore] -fn pangrams_must_have_all_letters() { - let sentence = "a quick movement of the enemy will jeopardize five gunboats"; - assert!(!is_pangram(sentence)); +fn only_lower_case() { + let sentence = "the quick brown fox jumps over the lazy dog"; + assert!(is_pangram(sentence)); } #[test] #[ignore] -fn pangrams_must_have_all_letters_two() { - let sentence = "the quick brown fish jumps over the lazy dog"; +fn missing_the_letter_x() { + let sentence = "a quick movement of the enemy will jeopardize five gunboats"; assert!(!is_pangram(sentence)); } #[test] #[ignore] -fn pangrams_must_include_z() { - let sentence = "the quick brown fox jumps over the lay dog"; +fn missing_the_letter_h() { + let sentence = "five boxing wizards jump quickly at it"; assert!(!is_pangram(sentence)); } #[test] #[ignore] -fn underscores_do_not_affect_pangrams() { +fn with_underscores() { let sentence = "the_quick_brown_fox_jumps_over_the_lazy_dog"; assert!(is_pangram(sentence)); } #[test] #[ignore] -fn numbers_do_not_affect_pangrams() { +fn with_numbers() { let sentence = "the 1 quick brown fox jumps over the 2 lazy dogs"; assert!(is_pangram(sentence)); } #[test] #[ignore] -fn numbers_can_not_replace_letters() { +fn missing_letters_replaced_by_numbers() { let sentence = "7h3 qu1ck brown fox jumps ov3r 7h3 lazy dog"; assert!(!is_pangram(sentence)); } #[test] #[ignore] -fn capitals_and_punctuation_can_be_in_pangrams() { +fn mixed_case_and_punctuation() { let sentence = "\"Five quacking Zephyrs jolt my wax bed.\""; assert!(is_pangram(sentence)); } #[test] #[ignore] -fn non_ascii_characters_can_be_in_pangrams() { - let sentence = "Victor jagt zwölf Boxkämpfer quer über den großen Sylter Deich."; - assert!(is_pangram(sentence)); +fn a_m_and_a_m_are_26_different_characters_but_not_a_pangram() { + let sentence = "abcdefghijklm ABCDEFGHIJKLM"; + assert!(!is_pangram(sentence)); } From 2d002434813f9e92e5e58cac681221885c30377a Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Wed, 17 Apr 2024 12:47:36 +0200 Subject: [PATCH 281/436] scrabble-score: sync (#1902) --- .../scrabble-score/.docs/instructions.md | 47 +++++++------------ .../scrabble-score/.docs/introduction.md | 7 +++ 2 files changed, 23 insertions(+), 31 deletions(-) create mode 100644 exercises/practice/scrabble-score/.docs/introduction.md diff --git a/exercises/practice/scrabble-score/.docs/instructions.md b/exercises/practice/scrabble-score/.docs/instructions.md index 3f986c947..738f928c5 100644 --- a/exercises/practice/scrabble-score/.docs/instructions.md +++ b/exercises/practice/scrabble-score/.docs/instructions.md @@ -1,40 +1,25 @@ # Instructions -Given a word, compute the Scrabble score for that word. +Your task is to compute a word's Scrabble score by summing the values of its letters. -## Letter Values +The letters are valued as follows: -You'll need these: +| Letter | Value | +| ---------------------------- | ----- | +| A, E, I, O, U, L, N, R, S, T | 1 | +| D, G | 2 | +| B, C, M, P | 3 | +| F, H, V, W, Y | 4 | +| K | 5 | +| J, X | 8 | +| Q, Z | 10 | -```text -Letter Value -A, E, I, O, U, L, N, R, S, T 1 -D, G 2 -B, C, M, P 3 -F, H, V, W, Y 4 -K 5 -J, X 8 -Q, Z 10 -``` - -## Examples - -"cabbage" should be scored as worth 14 points: +For example, the word "cabbage" is worth 14 points: - 3 points for C -- 1 point for A, twice -- 3 points for B, twice +- 1 point for A +- 3 points for B +- 3 points for B +- 1 point for A - 2 points for G - 1 point for E - -And to total: - -- `3 + 2*1 + 2*3 + 2 + 1` -- = `3 + 2 + 6 + 3` -- = `5 + 9` -- = 14 - -## Extensions - -- You can play a double or a triple letter. -- You can play a double or a triple word. diff --git a/exercises/practice/scrabble-score/.docs/introduction.md b/exercises/practice/scrabble-score/.docs/introduction.md new file mode 100644 index 000000000..8821f240b --- /dev/null +++ b/exercises/practice/scrabble-score/.docs/introduction.md @@ -0,0 +1,7 @@ +# Introduction + +[Scrabble][wikipedia] is a word game where players place letter tiles on a board to form words. +Each letter has a value. +A word's score is the sum of its letters' values. + +[wikipedia]: https://en.wikipedia.org/wiki/Scrabble From cb7663a1551752b3dd5dfa46bf1e17f5a0a20007 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Wed, 17 Apr 2024 12:48:26 +0200 Subject: [PATCH 282/436] minesweeper: sync (#1903) --- .../minesweeper/.docs/instructions.md | 20 +++++++------------ .../minesweeper/.docs/introduction.md | 5 +++++ 2 files changed, 12 insertions(+), 13 deletions(-) create mode 100644 exercises/practice/minesweeper/.docs/introduction.md diff --git a/exercises/practice/minesweeper/.docs/instructions.md b/exercises/practice/minesweeper/.docs/instructions.md index f5f918bdf..7c1df2e4b 100644 --- a/exercises/practice/minesweeper/.docs/instructions.md +++ b/exercises/practice/minesweeper/.docs/instructions.md @@ -1,19 +1,13 @@ # Instructions -Add the mine counts to a completed Minesweeper board. +Your task is to add the mine counts to empty squares in a completed Minesweeper board. +The board itself is a rectangle composed of squares that are either empty (`' '`) or a mine (`'*'`). -Minesweeper is a popular game where the user has to find the mines using numeric hints that indicate how many mines are directly adjacent (horizontally, vertically, diagonally) to a square. +For each empty square, count the number of mines adjacent to it (horizontally, vertically, diagonally). +If the empty square has no adjacent mines, leave it empty. +Otherwise replace it with the adjacent mines count. -In this exercise you have to create some code that counts the number of mines adjacent to a given empty square and replaces that square with the count. - -The board is a rectangle composed of blank space (' ') characters. -A mine is represented by an asterisk (`*`) character. - -If a given space has no adjacent mines at all, leave that square blank. - -## Examples - -For example you may receive a 5 x 4 board like this (empty spaces are represented here with the '·' character for display on screen): +For example, you may receive a 5 x 4 board like this (empty spaces are represented here with the '·' character for display on screen): ```text ·*·*· @@ -22,7 +16,7 @@ For example you may receive a 5 x 4 board like this (empty spaces are represente ····· ``` -And your code will transform it into this: +Which your code should transform into this: ```text 1*3*1 diff --git a/exercises/practice/minesweeper/.docs/introduction.md b/exercises/practice/minesweeper/.docs/introduction.md new file mode 100644 index 000000000..5f74a742b --- /dev/null +++ b/exercises/practice/minesweeper/.docs/introduction.md @@ -0,0 +1,5 @@ +# Introduction + +[Minesweeper][wikipedia] is a popular game where the user has to find the mines using numeric hints that indicate how many mines are directly adjacent (horizontally, vertically, diagonally) to a square. + +[wikipedia]: https://en.wikipedia.org/wiki/Minesweeper_(video_game) From e327f2c9aa87549cf845b83c3f72804ef5e385e6 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Wed, 17 Apr 2024 12:49:56 +0200 Subject: [PATCH 283/436] all-your-base: sync (#1904) --- .../all-your-base/.docs/instructions.md | 21 +++++++------------ .../all-your-base/.docs/introduction.md | 8 +++++++ 2 files changed, 16 insertions(+), 13 deletions(-) create mode 100644 exercises/practice/all-your-base/.docs/introduction.md diff --git a/exercises/practice/all-your-base/.docs/instructions.md b/exercises/practice/all-your-base/.docs/instructions.md index 4602b5cfa..1b688b691 100644 --- a/exercises/practice/all-your-base/.docs/instructions.md +++ b/exercises/practice/all-your-base/.docs/instructions.md @@ -1,14 +1,11 @@ # Instructions -Convert a number, represented as a sequence of digits in one base, to any other base. +Convert a sequence of digits in one base, representing a number, into a sequence of digits in another base, representing the same number. -Implement general base conversion. -Given a number in base **a**, represented as a sequence of digits, convert it to base **b**. - -## Note - -- Try to implement the conversion yourself. - Do not use something else to perform the conversion for you. +~~~~exercism/note +Try to implement the conversion yourself. +Do not use something else to perform the conversion for you. +~~~~ ## About [Positional Notation][positional-notation] @@ -16,17 +13,15 @@ In positional notation, a number in base **b** can be understood as a linear com The number 42, _in base 10_, means: -`(4 * 10^1) + (2 * 10^0)` +`(4 × 10¹) + (2 × 10⁰)` The number 101010, _in base 2_, means: -`(1 * 2^5) + (0 * 2^4) + (1 * 2^3) + (0 * 2^2) + (1 * 2^1) + (0 * 2^0)` +`(1 × 2⁵) + (0 × 2⁴) + (1 × 2³) + (0 × 2²) + (1 × 2¹) + (0 × 2⁰)` The number 1120, _in base 3_, means: -`(1 * 3^3) + (1 * 3^2) + (2 * 3^1) + (0 * 3^0)` - -I think you got the idea! +`(1 × 3³) + (1 × 3²) + (2 × 3¹) + (0 × 3⁰)` _Yes. Those three numbers above are exactly the same. Congratulations!_ diff --git a/exercises/practice/all-your-base/.docs/introduction.md b/exercises/practice/all-your-base/.docs/introduction.md new file mode 100644 index 000000000..68aaffbed --- /dev/null +++ b/exercises/practice/all-your-base/.docs/introduction.md @@ -0,0 +1,8 @@ +# Introduction + +You've just been hired as professor of mathematics. +Your first week went well, but something is off in your second week. +The problem is that every answer given by your students is wrong! +Luckily, your math skills have allowed you to identify the problem: the student answers _are_ correct, but they're all in base 2 (binary)! +Amazingly, it turns out that each week, the students use a different base. +To help you quickly verify the student answers, you'll be building a tool to translate between bases. From 9546f79cdbefe03e80b2640c6be24dbbca56d9f3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Apr 2024 09:23:59 +0200 Subject: [PATCH 284/436] build(deps): bump actions/checkout from 4.1.2 to 4.1.3 (#1906) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/tests.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4bafcdfc9..dc91b4b45 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,7 +18,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 - name: Fetch configlet run: ./bin/fetch-configlet @@ -32,7 +32,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 - name: Run markdown lint run: ./bin/lint_markdown.sh @@ -43,7 +43,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 - name: Run shellcheck uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # v2.0.0 @@ -58,7 +58,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 - name: Fetch origin/main run: git fetch --depth=1 origin main @@ -84,7 +84,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 - name: Setup toolchain uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 @@ -100,7 +100,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 - name: Setup toolchain uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 @@ -123,7 +123,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 - name: Setup toolchain uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 @@ -148,7 +148,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 + uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 - name: Setup nightly toolchain uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 From 3938f78bcbf8187b2712cdd0debe13f1c3813b6f Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Wed, 24 Apr 2024 13:03:46 +0200 Subject: [PATCH 285/436] sync documentation and metadata (#1907) --- .../alphametics/.docs/instructions.md | 4 +-- .../practice/alphametics/.meta/config.json | 2 +- .../matching-brackets/.docs/instructions.md | 3 +- .../matching-brackets/.docs/introduction.md | 8 +++++ .../.docs/instructions.md | 2 +- .../practice/pig-latin/.docs/instructions.md | 18 +++-------- .../practice/pig-latin/.docs/introduction.md | 8 +++++ .../practice/space-age/.docs/instructions.md | 31 ++++++++++--------- .../practice/space-age/.docs/introduction.md | 20 ++++++++++++ problem-specifications | 2 +- 10 files changed, 63 insertions(+), 35 deletions(-) create mode 100644 exercises/practice/matching-brackets/.docs/introduction.md create mode 100644 exercises/practice/pig-latin/.docs/introduction.md create mode 100644 exercises/practice/space-age/.docs/introduction.md diff --git a/exercises/practice/alphametics/.docs/instructions.md b/exercises/practice/alphametics/.docs/instructions.md index 649576ec7..ef2cbb4a7 100644 --- a/exercises/practice/alphametics/.docs/instructions.md +++ b/exercises/practice/alphametics/.docs/instructions.md @@ -1,6 +1,6 @@ # Instructions -Write a function to solve alphametics puzzles. +Given an alphametics puzzle, find the correct solution. [Alphametics][alphametics] is a puzzle where letters in words are replaced with numbers. @@ -26,6 +26,4 @@ This is correct because every letter is replaced by a different number and the w Each letter must represent a different digit, and the leading digit of a multi-digit number must not be zero. -Write a function to solve alphametics puzzles. - [alphametics]: https://en.wikipedia.org/wiki/Alphametics diff --git a/exercises/practice/alphametics/.meta/config.json b/exercises/practice/alphametics/.meta/config.json index 79eff93dc..eaa3d5531 100644 --- a/exercises/practice/alphametics/.meta/config.json +++ b/exercises/practice/alphametics/.meta/config.json @@ -35,7 +35,7 @@ ".meta/example.rs" ] }, - "blurb": "Write a function to solve alphametics puzzles.", + "blurb": "Given an alphametics puzzle, find the correct solution.", "custom": { "test-in-release-mode": true } diff --git a/exercises/practice/matching-brackets/.docs/instructions.md b/exercises/practice/matching-brackets/.docs/instructions.md index 544daa968..ea1708423 100644 --- a/exercises/practice/matching-brackets/.docs/instructions.md +++ b/exercises/practice/matching-brackets/.docs/instructions.md @@ -1,4 +1,5 @@ # Instructions Given a string containing brackets `[]`, braces `{}`, parentheses `()`, or any combination thereof, verify that any and all pairs are matched and nested correctly. -The string may also contain other characters, which for the purposes of this exercise should be ignored. +Any other characters should be ignored. +For example, `"{what is (42)}?"` is balanced and `"[text}"` is not. diff --git a/exercises/practice/matching-brackets/.docs/introduction.md b/exercises/practice/matching-brackets/.docs/introduction.md new file mode 100644 index 000000000..0618221b2 --- /dev/null +++ b/exercises/practice/matching-brackets/.docs/introduction.md @@ -0,0 +1,8 @@ +# Introduction + +You're given the opportunity to write software for the Bracketeer™, an ancient but powerful mainframe. +The software that runs on it is written in a proprietary language. +Much of its syntax is familiar, but you notice _lots_ of brackets, braces and parentheses. +Despite the Bracketeer™ being powerful, it lacks flexibility. +If the source code has any unbalanced brackets, braces or parentheses, the Bracketeer™ crashes and must be rebooted. +To avoid such a scenario, you start writing code that can verify that brackets, braces, and parentheses are balanced before attempting to run it on the Bracketeer™. diff --git a/exercises/practice/parallel-letter-frequency/.docs/instructions.md b/exercises/practice/parallel-letter-frequency/.docs/instructions.md index 85abcf86a..6147b90af 100644 --- a/exercises/practice/parallel-letter-frequency/.docs/instructions.md +++ b/exercises/practice/parallel-letter-frequency/.docs/instructions.md @@ -4,4 +4,4 @@ Count the frequency of letters in texts using parallel computation. Parallelism is about doing things in parallel that can also be done sequentially. A common example is counting the frequency of letters. -Create a function that returns the total frequency of each letter in a list of texts and that employs parallelism. +Employ parallelism to calculate the total frequency of each letter in a list of texts. diff --git a/exercises/practice/pig-latin/.docs/instructions.md b/exercises/practice/pig-latin/.docs/instructions.md index 032905aa9..571708814 100644 --- a/exercises/practice/pig-latin/.docs/instructions.md +++ b/exercises/practice/pig-latin/.docs/instructions.md @@ -1,20 +1,10 @@ # Instructions -Implement a program that translates from English to Pig Latin. +Your task is to translate text from English to Pig Latin using the following rules: -Pig Latin is a made-up children's language that's intended to be confusing. -It obeys a few simple rules (below), but when it's spoken quickly it's really difficult for non-children (and non-native speakers) to understand. - -- **Rule 1**: If a word begins with a vowel sound, add an "ay" sound to the end of the word. +- **Rule 1**: If a word begins with a vowel sound, add an "ay" sound to the end of the word (e.g. "apple" -> "appleay"). Please note that "xr" and "yt" at the beginning of a word make vowel sounds (e.g. "xray" -> "xrayay", "yttria" -> "yttriaay"). -- **Rule 2**: If a word begins with a consonant sound, move it to the end of the word and then add an "ay" sound to the end of the word. +- **Rule 2**: If a word begins with a consonant sound, move it to the end of the word and then add an "ay" sound to the end of the word (e.g. "pig" -> "igpay"). Consonant sounds can be made up of multiple consonants, such as the "ch" in "chair" or "st" in "stand" (e.g. "chair" -> "airchay"). -- **Rule 3**: If a word starts with a consonant sound followed by "qu", move it to the end of the word, and then add an "ay" sound to the end of the word (e.g. "square" -> "aresquay"). +- **Rule 3**: If a word starts with a consonant sound followed by "qu", move them to the end of the word, and then add an "ay" sound to the end of the word (e.g. "square" -> "aresquay"). - **Rule 4**: If a word contains a "y" after a consonant cluster or as the second letter in a two letter word it makes a vowel sound (e.g. "rhythm" -> "ythmrhay", "my" -> "ymay"). - -There are a few more rules for edge cases, and there are regional variants too. -Check the tests for all the details. - -Read more about [Pig Latin on Wikipedia][pig-latin]. - -[pig-latin]: https://en.wikipedia.org/wiki/Pig_latin diff --git a/exercises/practice/pig-latin/.docs/introduction.md b/exercises/practice/pig-latin/.docs/introduction.md new file mode 100644 index 000000000..04baa4758 --- /dev/null +++ b/exercises/practice/pig-latin/.docs/introduction.md @@ -0,0 +1,8 @@ +# Introduction + +Your parents have challenged you and your sibling to a game of two-on-two basketball. +Confident they'll win, they let you score the first couple of points, but then start taking over the game. +Needing a little boost, you start speaking in [Pig Latin][pig-latin], which is a made-up children's language that's difficult for non-children to understand. +This will give you the edge to prevail over your parents! + +[pig-latin]: https://en.wikipedia.org/wiki/Pig_latin diff --git a/exercises/practice/space-age/.docs/instructions.md b/exercises/practice/space-age/.docs/instructions.md index fe938cc09..f23b5e2c1 100644 --- a/exercises/practice/space-age/.docs/instructions.md +++ b/exercises/practice/space-age/.docs/instructions.md @@ -1,25 +1,28 @@ # Instructions -Given an age in seconds, calculate how old someone would be on: +Given an age in seconds, calculate how old someone would be on a planet in our Solar System. -- Mercury: orbital period 0.2408467 Earth years -- Venus: orbital period 0.61519726 Earth years -- Earth: orbital period 1.0 Earth years, 365.25 Earth days, or 31557600 seconds -- Mars: orbital period 1.8808158 Earth years -- Jupiter: orbital period 11.862615 Earth years -- Saturn: orbital period 29.447498 Earth years -- Uranus: orbital period 84.016846 Earth years -- Neptune: orbital period 164.79132 Earth years +One Earth year equals 365.25 Earth days, or 31,557,600 seconds. +If you were told someone was 1,000,000,000 seconds old, their age would be 31.69 Earth-years. -So if you were told someone were 1,000,000,000 seconds old, you should -be able to say that they're 31.69 Earth-years old. +For the other planets, you have to account for their orbital period in Earth Years: -If you're wondering why Pluto didn't make the cut, go watch [this YouTube video][pluto-video]. +| Planet | Orbital period in Earth Years | +| ------- | ----------------------------- | +| Mercury | 0.2408467 | +| Venus | 0.61519726 | +| Earth | 1.0 | +| Mars | 1.8808158 | +| Jupiter | 11.862615 | +| Saturn | 29.447498 | +| Uranus | 84.016846 | +| Neptune | 164.79132 | -Note: The actual length of one complete orbit of the Earth around the sun is closer to 365.256 days (1 sidereal year). +~~~~exercism/note +The actual length of one complete orbit of the Earth around the sun is closer to 365.256 days (1 sidereal year). The Gregorian calendar has, on average, 365.2425 days. While not entirely accurate, 365.25 is the value used in this exercise. See [Year on Wikipedia][year] for more ways to measure a year. -[pluto-video]: https://www.youtube.com/watch?v=Z_2gbGXzFbs [year]: https://en.wikipedia.org/wiki/Year#Summary +~~~~ diff --git a/exercises/practice/space-age/.docs/introduction.md b/exercises/practice/space-age/.docs/introduction.md new file mode 100644 index 000000000..014d78857 --- /dev/null +++ b/exercises/practice/space-age/.docs/introduction.md @@ -0,0 +1,20 @@ +# Introduction + +The year is 2525 and you've just embarked on a journey to visit all planets in the Solar System (Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus and Neptune). +The first stop is Mercury, where customs require you to fill out a form (bureaucracy is apparently _not_ Earth-specific). +As you hand over the form to the customs officer, they scrutinize it and frown. +"Do you _really_ expect me to believe you're just 50 years old? +You must be closer to 200 years old!" + +Amused, you wait for the customs officer to start laughing, but they appear to be dead serious. +You realize that you've entered your age in _Earth years_, but the officer expected it in _Mercury years_! +As Mercury's orbital period around the sun is significantly shorter than Earth, you're actually a lot older in Mercury years. +After some quick calculations, you're able to provide your age in Mercury Years. +The customs officer smiles, satisfied, and waves you through. +You make a mental note to pre-calculate your planet-specific age _before_ future customs checks, to avoid such mix-ups. + +~~~~exercism/note +If you're wondering why Pluto didn't make the cut, go watch [this YouTube video][pluto-video]. + +[pluto-video]: https://www.youtube.com/watch?v=Z_2gbGXzFbs +~~~~ diff --git a/problem-specifications b/problem-specifications index 72d52dd52..a7ea37faf 160000 --- a/problem-specifications +++ b/problem-specifications @@ -1 +1 @@ -Subproject commit 72d52dd5287ef7ec8c2fbb56889c220476f8baf8 +Subproject commit a7ea37faf511152a41c9ea47e2958772375ba6c4 From d5121fbfeab17bcec51c3c4c6f9431904b49ab15 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Wed, 24 Apr 2024 13:13:57 +0200 Subject: [PATCH 286/436] nth-prime: sync (#1908) [no important files changed] --- .../nth-prime/.meta/test_template.tera | 11 +++++++ exercises/practice/nth-prime/.meta/tests.toml | 33 +++++++++++++++++-- .../practice/nth-prime/tests/nth-prime.rs | 18 +++++++--- 3 files changed, 54 insertions(+), 8 deletions(-) create mode 100644 exercises/practice/nth-prime/.meta/test_template.tera diff --git a/exercises/practice/nth-prime/.meta/test_template.tera b/exercises/practice/nth-prime/.meta/test_template.tera new file mode 100644 index 000000000..a4c4b8d26 --- /dev/null +++ b/exercises/practice/nth-prime/.meta/test_template.tera @@ -0,0 +1,11 @@ +use nth_prime::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | snake_case }}() { + let output = nth({{ test.input.number - 1 }}); + let expected = {{ test.expected | json_encode() }}; + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/nth-prime/.meta/tests.toml b/exercises/practice/nth-prime/.meta/tests.toml index be690e975..8655ede29 100644 --- a/exercises/practice/nth-prime/.meta/tests.toml +++ b/exercises/practice/nth-prime/.meta/tests.toml @@ -1,3 +1,30 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[75c65189-8aef-471a-81de-0a90c728160c] +description = "first prime" + +[2c38804c-295f-4701-b728-56dea34fd1a0] +description = "second prime" + +[56692534-781e-4e8c-b1f9-3e82c1640259] +description = "sixth prime" + +[fce1e979-0edb-412d-93aa-2c744e8f50ff] +description = "big prime" + +[bd0a9eae-6df7-485b-a144-80e13c7d55b2] +description = "there is no zeroth prime" +include = false +comment = """ + The zeroth prime was defined to be 2 (zero-based indexing). + Changing it now would be an unnecessary breaking change. +""" diff --git a/exercises/practice/nth-prime/tests/nth-prime.rs b/exercises/practice/nth-prime/tests/nth-prime.rs index c9dfd371a..19e6433fc 100644 --- a/exercises/practice/nth-prime/tests/nth-prime.rs +++ b/exercises/practice/nth-prime/tests/nth-prime.rs @@ -1,24 +1,32 @@ -use nth_prime as np; +use nth_prime::*; #[test] fn first_prime() { - assert_eq!(np::nth(0), 2); + let output = nth(0); + let expected = 2; + assert_eq!(output, expected); } #[test] #[ignore] fn second_prime() { - assert_eq!(np::nth(1), 3); + let output = nth(1); + let expected = 3; + assert_eq!(output, expected); } #[test] #[ignore] fn sixth_prime() { - assert_eq!(np::nth(5), 13); + let output = nth(5); + let expected = 13; + assert_eq!(output, expected); } #[test] #[ignore] fn big_prime() { - assert_eq!(np::nth(10_000), 104_743); + let output = nth(10000); + let expected = 104743; + assert_eq!(output, expected); } From 73566aa12d096d79dff7d1da581daf65eafe5bcb Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Wed, 24 Apr 2024 15:23:47 +0200 Subject: [PATCH 287/436] palindrome-products: sync (#1910) --- .../.meta/test_template.tera | 42 ++++++++ .../palindrome-products/.meta/tests.toml | 52 +++++++++- .../tests/palindrome-products.rs | 99 +++++++++---------- 3 files changed, 140 insertions(+), 53 deletions(-) create mode 100644 exercises/practice/palindrome-products/.meta/test_template.tera diff --git a/exercises/practice/palindrome-products/.meta/test_template.tera b/exercises/practice/palindrome-products/.meta/test_template.tera new file mode 100644 index 000000000..53828a5c5 --- /dev/null +++ b/exercises/practice/palindrome-products/.meta/test_template.tera @@ -0,0 +1,42 @@ +use palindrome_products::*; + +{# + These first two custom test cases are for the object-oriented design of the exercise. + They don't fit the structure of the upstream tests, so they're implemented here. +#} + +#[test] +#[ignore] +/// test `Palindrome::new` with valid input +fn palindrome_new_return_some() { + for v in [1, 11, 121, 12321, 1234321, 123454321, 543212345] { + assert_eq!(Palindrome::new(v).expect("is a palindrome").into_inner(), v); + } +} + +#[test] +#[ignore] +/// test `Palindrome::new` with invalid input +fn palindrome_new_return_none() { + for v in [12, 2322, 23443, 1233211, 8932343] { + assert_eq!(Palindrome::new(v), None); + } +} + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | snake_case }}() { + {%- if test.property == "smallest" %} + let output = palindrome_products({{ test.input.min }}, {{ test.input.max }}).map(|(min, _)| min.into_inner()); + {%- else %} + let output = palindrome_products({{ test.input.min }}, {{ test.input.max }}).map(|(_, max)| max.into_inner()); + {%- endif%} + {%- if test.expected.error is defined or not test.expected.value %} + let expected = None; + {%- else %} + let expected = Some({{ test.expected.value }}); + {%- endif%} + assert_eq!(output, expected); +} +{% endfor -%} diff --git a/exercises/practice/palindrome-products/.meta/tests.toml b/exercises/practice/palindrome-products/.meta/tests.toml index be690e975..a3bc41750 100644 --- a/exercises/practice/palindrome-products/.meta/tests.toml +++ b/exercises/practice/palindrome-products/.meta/tests.toml @@ -1,3 +1,49 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[5cff78fe-cf02-459d-85c2-ce584679f887] +description = "find the smallest palindrome from single digit factors" + +[0853f82c-5fc4-44ae-be38-fadb2cced92d] +description = "find the largest palindrome from single digit factors" + +[66c3b496-bdec-4103-9129-3fcb5a9063e1] +description = "find the smallest palindrome from double digit factors" + +[a10682ae-530a-4e56-b89d-69664feafe53] +description = "find the largest palindrome from double digit factors" + +[cecb5a35-46d1-4666-9719-fa2c3af7499d] +description = "find the smallest palindrome from triple digit factors" + +[edab43e1-c35f-4ea3-8c55-2f31dddd92e5] +description = "find the largest palindrome from triple digit factors" + +[4f802b5a-9d74-4026-a70f-b53ff9234e4e] +description = "find the smallest palindrome from four digit factors" + +[787525e0-a5f9-40f3-8cb2-23b52cf5d0be] +description = "find the largest palindrome from four digit factors" + +[58fb1d63-fddb-4409-ab84-a7a8e58d9ea0] +description = "empty result for smallest if no palindrome in the range" + +[9de9e9da-f1d9-49a5-8bfc-3d322efbdd02] +description = "empty result for largest if no palindrome in the range" + +[12e73aac-d7ee-4877-b8aa-2aa3dcdb9f8a] +description = "error result for smallest if min is more than max" + +[eeeb5bff-3f47-4b1e-892f-05829277bd74] +description = "error result for largest if min is more than max" + +[16481711-26c4-42e0-9180-e2e4e8b29c23] +description = "smallest product does not use the smallest factor" diff --git a/exercises/practice/palindrome-products/tests/palindrome-products.rs b/exercises/practice/palindrome-products/tests/palindrome-products.rs index de3deb702..711fb9339 100644 --- a/exercises/practice/palindrome-products/tests/palindrome-products.rs +++ b/exercises/practice/palindrome-products/tests/palindrome-products.rs @@ -1,25 +1,4 @@ -//! This test suite was generated by the rust exercise tool, which can be found at -//! https://github.com/exercism/rust/tree/main/util/exercise - -use palindrome_products::{palindrome_products, Palindrome}; - -/// Process a single test case for the property `smallest` -/// -/// All cases for the `smallest` property are implemented -/// in terms of this function. -fn process_smallest_case((from, to): (u64, u64), expected: Option) { - let min = palindrome_products(from, to).map(|(min, _)| min); - assert_eq!(min.map(|newtype| newtype.into_inner()), expected); -} - -/// Process a single test case for the property `largest` -/// -/// All cases for the `largest` property are implemented -/// in terms of this function. -fn process_largest_case((from, to): (u64, u64), expected: Option) { - let max = palindrome_products(from, to).map(|(_, max)| max); - assert_eq!(max.map(|newtype| newtype.into_inner()), expected); -} +use palindrome_products::*; #[test] /// test `Palindrome::new` with valid input @@ -40,84 +19,104 @@ fn palindrome_new_return_none() { #[test] #[ignore] -/// finds the smallest palindrome from single digit factors -fn finds_the_smallest_palindrome_from_single_digit_factors() { - process_smallest_case((1, 9), Some(1)); +fn find_the_smallest_palindrome_from_single_digit_factors() { + let output = palindrome_products(1, 9).map(|(min, _)| min.into_inner()); + let expected = Some(1); + assert_eq!(output, expected); } #[test] #[ignore] -/// finds the largest palindrome from single digit factors -fn finds_the_largest_palindrome_from_single_digit_factors() { - process_largest_case((1, 9), Some(9)); +fn find_the_largest_palindrome_from_single_digit_factors() { + let output = palindrome_products(1, 9).map(|(_, max)| max.into_inner()); + let expected = Some(9); + assert_eq!(output, expected); } #[test] #[ignore] -/// find the smallest palindrome from double digit factors fn find_the_smallest_palindrome_from_double_digit_factors() { - process_smallest_case((10, 99), Some(121)); + let output = palindrome_products(10, 99).map(|(min, _)| min.into_inner()); + let expected = Some(121); + assert_eq!(output, expected); } #[test] #[ignore] -/// find the largest palindrome from double digit factors fn find_the_largest_palindrome_from_double_digit_factors() { - process_largest_case((10, 99), Some(9009)); + let output = palindrome_products(10, 99).map(|(_, max)| max.into_inner()); + let expected = Some(9009); + assert_eq!(output, expected); } #[test] #[ignore] -/// find smallest palindrome from triple digit factors -fn find_smallest_palindrome_from_triple_digit_factors() { - process_smallest_case((100, 999), Some(10201)); +fn find_the_smallest_palindrome_from_triple_digit_factors() { + let output = palindrome_products(100, 999).map(|(min, _)| min.into_inner()); + let expected = Some(10201); + assert_eq!(output, expected); } #[test] #[ignore] -/// find the largest palindrome from triple digit factors fn find_the_largest_palindrome_from_triple_digit_factors() { - process_largest_case((100, 999), Some(906609)); + let output = palindrome_products(100, 999).map(|(_, max)| max.into_inner()); + let expected = Some(906609); + assert_eq!(output, expected); } #[test] #[ignore] -/// find smallest palindrome from four digit factors -fn find_smallest_palindrome_from_four_digit_factors() { - process_smallest_case((1000, 9999), Some(1002001)); +fn find_the_smallest_palindrome_from_four_digit_factors() { + let output = palindrome_products(1000, 9999).map(|(min, _)| min.into_inner()); + let expected = Some(1002001); + assert_eq!(output, expected); } #[test] #[ignore] -/// find the largest palindrome from four digit factors fn find_the_largest_palindrome_from_four_digit_factors() { - process_largest_case((1000, 9999), Some(99000099)); + let output = palindrome_products(1000, 9999).map(|(_, max)| max.into_inner()); + let expected = Some(99000099); + assert_eq!(output, expected); } #[test] #[ignore] -/// empty result for smallest if no palindrome in the range fn empty_result_for_smallest_if_no_palindrome_in_the_range() { - process_smallest_case((1002, 1003), None); + let output = palindrome_products(1002, 1003).map(|(min, _)| min.into_inner()); + let expected = None; + assert_eq!(output, expected); } #[test] #[ignore] -/// empty result for largest if no palindrome in the range fn empty_result_for_largest_if_no_palindrome_in_the_range() { - process_largest_case((15, 15), None); + let output = palindrome_products(15, 15).map(|(_, max)| max.into_inner()); + let expected = None; + assert_eq!(output, expected); } #[test] #[ignore] -/// error result for smallest if min is more than max fn error_result_for_smallest_if_min_is_more_than_max() { - process_smallest_case((10000, 1), None); + let output = palindrome_products(10000, 1).map(|(min, _)| min.into_inner()); + let expected = None; + assert_eq!(output, expected); } #[test] #[ignore] -/// error result for largest if min is more than max fn error_result_for_largest_if_min_is_more_than_max() { - process_largest_case((2, 1), None); + let output = palindrome_products(2, 1).map(|(_, max)| max.into_inner()); + let expected = None; + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn smallest_product_does_not_use_the_smallest_factor() { + let output = palindrome_products(3215, 4000).map(|(min, _)| min.into_inner()); + let expected = Some(10988901); + assert_eq!(output, expected); } From a14652d984f1ee71c39e96abc85bec465249670b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 25 Apr 2024 20:38:11 +0200 Subject: [PATCH 288/436] build(deps): bump actions/checkout from 4.1.3 to 4.1.4 (#1911) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/tests.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index dc91b4b45..6f21b9da4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,7 +18,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: Fetch configlet run: ./bin/fetch-configlet @@ -32,7 +32,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: Run markdown lint run: ./bin/lint_markdown.sh @@ -43,7 +43,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: Run shellcheck uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # v2.0.0 @@ -58,7 +58,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: Fetch origin/main run: git fetch --depth=1 origin main @@ -84,7 +84,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: Setup toolchain uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 @@ -100,7 +100,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: Setup toolchain uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 @@ -123,7 +123,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: Setup toolchain uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 @@ -148,7 +148,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: Setup nightly toolchain uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 From 142289435848ddf9b21b78642bb3bbf2dabe802d Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 30 Apr 2024 11:34:11 +0200 Subject: [PATCH 289/436] improve exercise order (#1912) I believe some work went into the exercise order by the previous maintainers, so I didn't completely redo it. I only fixed the obviously bad aspects of the order: - difficulty 10 exercises being way too early in the track - new, easy exercises being at the end of the exercise list forum discussion: https://forum.exercism.org/t/rust-track-exercise-order/10855 --- config.json | 456 ++++++++++++++++++++++++++-------------------------- 1 file changed, 228 insertions(+), 228 deletions(-) diff --git a/config.json b/config.json index 6ce317701..376c21e92 100644 --- a/config.json +++ b/config.json @@ -309,59 +309,6 @@ "str_to_digits" ] }, - { - "slug": "parallel-letter-frequency", - "name": "Parallel Letter Frequency", - "uuid": "e114b19f-9a9a-402d-a5cb-1cad8de5088e", - "practices": [], - "prerequisites": [], - "difficulty": 10, - "topics": [ - "multi_threading" - ] - }, - { - "slug": "macros", - "name": "Macros", - "uuid": "29583cc6-d56d-4bee-847d-93d74e5a30e7", - "practices": [ - "hashmap" - ], - "prerequisites": [], - "difficulty": 10, - "topics": [ - "macros", - "macros_by_example" - ] - }, - { - "slug": "poker", - "name": "Poker", - "uuid": "0a33f3ac-cedd-4a40-a132-9d044b0e9977", - "practices": [ - "enums", - "strings", - "structs" - ], - "prerequisites": [], - "difficulty": 10, - "topics": [ - "lifetimes", - "parsing", - "traits" - ] - }, - { - "slug": "forth", - "name": "Forth", - "uuid": "55976c49-1be5-4170-8aa3-056c2223abbb", - "practices": [], - "prerequisites": [], - "difficulty": 10, - "topics": [ - "parsing" - ] - }, { "slug": "armstrong-numbers", "name": "Armstrong Numbers", @@ -527,6 +474,92 @@ "stack_or_recursion" ] }, + { + "slug": "collatz-conjecture", + "name": "Collatz Conjecture", + "uuid": "f9afd650-8103-4373-a284-fa4ecfee7207", + "practices": [], + "prerequisites": [], + "difficulty": 1, + "topics": [ + "math", + "option_type" + ] + }, + { + "slug": "diffie-hellman", + "name": "Diffie-Hellman", + "uuid": "ff9344b6-b185-4d53-bb03-f1d2bce8c959", + "practices": [], + "prerequisites": [], + "difficulty": 1, + "topics": [ + "math" + ] + }, + { + "slug": "series", + "name": "Series", + "uuid": "9de405e1-3a05-43cb-8eb3-00b81a2968e9", + "practices": [], + "prerequisites": [], + "difficulty": 1, + "topics": [ + "strings", + "vectors" + ] + }, + { + "slug": "hexadecimal", + "name": "Hexadecimal", + "uuid": "496fd79f-1678-4aa2-8110-c32c6aaf545e", + "practices": [], + "prerequisites": [], + "difficulty": 1, + "topics": [], + "status": "deprecated" + }, + { + "slug": "nucleotide-codons", + "name": "Nucleotide Codons", + "uuid": "8dae8f4d-368d-477d-907e-bf746921bfbf", + "practices": [], + "prerequisites": [], + "difficulty": 1, + "topics": [], + "status": "deprecated" + }, + { + "slug": "two-fer", + "name": "Two Fer", + "uuid": "585e963b-366c-48bc-b523-29b6be4175c8", + "practices": [], + "prerequisites": [], + "difficulty": 1, + "topics": [ + "match", + "strings" + ], + "status": "deprecated" + }, + { + "slug": "kindergarten-garden", + "name": "Kindergarten Garden", + "uuid": "c27e4878-28a4-4637-bde2-2af681a7ff0d", + "practices": [], + "prerequisites": [], + "difficulty": 1, + "topics": [] + }, + { + "slug": "eliuds-eggs", + "name": "Eliuds Eggs", + "uuid": "2d738c77-8dde-437a-bf42-ed5542a414d1", + "practices": [], + "prerequisites": [], + "difficulty": 1, + "topics": [] + }, { "slug": "acronym", "name": "Acronym", @@ -1007,32 +1040,6 @@ "structs" ] }, - { - "slug": "ocr-numbers", - "name": "OCR Numbers", - "uuid": "704aab91-b83a-4e64-8c21-fb0be5076289", - "practices": [], - "prerequisites": [], - "difficulty": 10, - "topics": [ - "chunks", - "lines", - "slices" - ] - }, - { - "slug": "react", - "name": "React", - "uuid": "8708ccc7-711a-4862-b5a4-ff59fde2241c", - "practices": [], - "prerequisites": [], - "difficulty": 10, - "topics": [ - "closures", - "generics", - "lifetimes" - ] - }, { "slug": "wordy", "name": "Wordy", @@ -1061,68 +1068,6 @@ "vectors" ] }, - { - "slug": "circular-buffer", - "name": "Circular Buffer", - "uuid": "6ff1a539-251b-49d4-81b5-a6b1e9ba66a4", - "practices": [], - "prerequisites": [], - "difficulty": 10, - "topics": [ - "buffers", - "generics" - ] - }, - { - "slug": "rectangles", - "name": "Rectangles", - "uuid": "cc4ccd99-1c97-4ee7-890c-d629b4e1e46d", - "practices": [ - "enums" - ], - "prerequisites": [], - "difficulty": 10, - "topics": [ - "algorithms", - "structs", - "traits" - ] - }, - { - "slug": "collatz-conjecture", - "name": "Collatz Conjecture", - "uuid": "f9afd650-8103-4373-a284-fa4ecfee7207", - "practices": [], - "prerequisites": [], - "difficulty": 1, - "topics": [ - "math", - "option_type" - ] - }, - { - "slug": "diffie-hellman", - "name": "Diffie-Hellman", - "uuid": "ff9344b6-b185-4d53-bb03-f1d2bce8c959", - "practices": [], - "prerequisites": [], - "difficulty": 1, - "topics": [ - "math" - ] - }, - { - "slug": "series", - "name": "Series", - "uuid": "9de405e1-3a05-43cb-8eb3-00b81a2968e9", - "practices": [], - "prerequisites": [], - "difficulty": 1, - "topics": [ - "strings", - "vectors" - ] - }, { "slug": "accumulate", "name": "Accumulate", @@ -1341,6 +1286,42 @@ "strings" ] }, + { + "slug": "secret-handshake", + "name": "Secret Handshake", + "uuid": "8c044530-9deb-4ff7-a638-98d6c05ebbb8", + "practices": [], + "prerequisites": [], + "difficulty": 4, + "topics": [] + }, + { + "slug": "knapsack", + "name": "Knapsack", + "uuid": "cbccd0c5-eb15-4705-9a4c-0209861f078c", + "practices": [], + "prerequisites": [], + "difficulty": 4, + "topics": [] + }, + { + "slug": "yacht", + "name": "Yacht", + "uuid": "1a0e8e34-f578-4a53-91b0-8a1260446553", + "practices": [], + "prerequisites": [], + "difficulty": 4, + "topics": [] + }, + { + "slug": "matrix", + "name": "Matrix", + "uuid": "9fceeb8b-0154-45f0-93a5-7117d4d81449", + "practices": [], + "prerequisites": [], + "difficulty": 4, + "topics": [] + }, { "slug": "fizzy", "name": "Fizzy", @@ -1365,6 +1346,112 @@ "math" ] }, + { + "slug": "parallel-letter-frequency", + "name": "Parallel Letter Frequency", + "uuid": "e114b19f-9a9a-402d-a5cb-1cad8de5088e", + "practices": [], + "prerequisites": [], + "difficulty": 10, + "topics": [ + "multi_threading" + ] + }, + { + "slug": "macros", + "name": "Macros", + "uuid": "29583cc6-d56d-4bee-847d-93d74e5a30e7", + "practices": [ + "hashmap" + ], + "prerequisites": [], + "difficulty": 10, + "topics": [ + "macros", + "macros_by_example" + ] + }, + { + "slug": "poker", + "name": "Poker", + "uuid": "0a33f3ac-cedd-4a40-a132-9d044b0e9977", + "practices": [ + "enums", + "strings", + "structs" + ], + "prerequisites": [], + "difficulty": 10, + "topics": [ + "lifetimes", + "parsing", + "traits" + ] + }, + { + "slug": "forth", + "name": "Forth", + "uuid": "55976c49-1be5-4170-8aa3-056c2223abbb", + "practices": [], + "prerequisites": [], + "difficulty": 10, + "topics": [ + "parsing" + ] + }, + { + "slug": "ocr-numbers", + "name": "OCR Numbers", + "uuid": "704aab91-b83a-4e64-8c21-fb0be5076289", + "practices": [], + "prerequisites": [], + "difficulty": 10, + "topics": [ + "chunks", + "lines", + "slices" + ] + }, + { + "slug": "react", + "name": "React", + "uuid": "8708ccc7-711a-4862-b5a4-ff59fde2241c", + "practices": [], + "prerequisites": [], + "difficulty": 10, + "topics": [ + "closures", + "generics", + "lifetimes" + ] + }, + { + "slug": "circular-buffer", + "name": "Circular Buffer", + "uuid": "6ff1a539-251b-49d4-81b5-a6b1e9ba66a4", + "practices": [], + "prerequisites": [], + "difficulty": 10, + "topics": [ + "buffers", + "generics" + ] + }, + { + "slug": "rectangles", + "name": "Rectangles", + "uuid": "cc4ccd99-1c97-4ee7-890c-d629b4e1e46d", + "practices": [ + "enums" + ], + "prerequisites": [], + "difficulty": 10, + "topics": [ + "algorithms", + "structs", + "traits" + ] + }, { "slug": "xorcism", "name": "Xorcism", @@ -1464,93 +1551,6 @@ "lists", "unsafe" ] - }, - { - "slug": "secret-handshake", - "name": "Secret Handshake", - "uuid": "8c044530-9deb-4ff7-a638-98d6c05ebbb8", - "practices": [], - "prerequisites": [], - "difficulty": 4, - "topics": [] - }, - { - "slug": "hexadecimal", - "name": "Hexadecimal", - "uuid": "496fd79f-1678-4aa2-8110-c32c6aaf545e", - "practices": [], - "prerequisites": [], - "difficulty": 1, - "topics": [], - "status": "deprecated" - }, - { - "slug": "nucleotide-codons", - "name": "Nucleotide Codons", - "uuid": "8dae8f4d-368d-477d-907e-bf746921bfbf", - "practices": [], - "prerequisites": [], - "difficulty": 1, - "topics": [], - "status": "deprecated" - }, - { - "slug": "two-fer", - "name": "Two Fer", - "uuid": "585e963b-366c-48bc-b523-29b6be4175c8", - "practices": [], - "prerequisites": [], - "difficulty": 1, - "topics": [ - "match", - "strings" - ], - "status": "deprecated" - }, - { - "slug": "knapsack", - "name": "Knapsack", - "uuid": "cbccd0c5-eb15-4705-9a4c-0209861f078c", - "practices": [], - "prerequisites": [], - "difficulty": 4, - "topics": [] - }, - { - "slug": "kindergarten-garden", - "name": "Kindergarten Garden", - "uuid": "c27e4878-28a4-4637-bde2-2af681a7ff0d", - "practices": [], - "prerequisites": [], - "difficulty": 1, - "topics": [] - }, - { - "slug": "yacht", - "name": "Yacht", - "uuid": "1a0e8e34-f578-4a53-91b0-8a1260446553", - "practices": [], - "prerequisites": [], - "difficulty": 4, - "topics": [] - }, - { - "slug": "matrix", - "name": "Matrix", - "uuid": "9fceeb8b-0154-45f0-93a5-7117d4d81449", - "practices": [], - "prerequisites": [], - "difficulty": 4, - "topics": [] - }, - { - "slug": "eliuds-eggs", - "name": "Eliuds Eggs", - "uuid": "2d738c77-8dde-437a-bf42-ed5542a414d1", - "practices": [], - "prerequisites": [], - "difficulty": 1, - "topics": [] } ], "foregone": [ From e828c2ff7b58b655581e1bc2d2ea2f9aa1e77ceb Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Tue, 30 Apr 2024 11:35:04 +0200 Subject: [PATCH 290/436] yacht: sync (#1913) --- .../practice/yacht/.docs/instructions.md | 21 +++++++------------ .../practice/yacht/.docs/introduction.md | 11 ++++++++++ 2 files changed, 19 insertions(+), 13 deletions(-) create mode 100644 exercises/practice/yacht/.docs/introduction.md diff --git a/exercises/practice/yacht/.docs/instructions.md b/exercises/practice/yacht/.docs/instructions.md index 54fdb452f..519b7a68b 100644 --- a/exercises/practice/yacht/.docs/instructions.md +++ b/exercises/practice/yacht/.docs/instructions.md @@ -1,8 +1,12 @@ # Instructions -The dice game [Yacht][yacht] is from the same family as Poker Dice, Generala and particularly Yahtzee, of which it is a precursor. -In the game, five dice are rolled and the result can be entered in any of twelve categories. -The score of a throw of the dice depends on category chosen. +Given five dice and a category, calculate the score of the dice for that category. + +~~~~exercism/note +You'll always be presented with five dice. +Each dice's value will be between one and six inclusively. +The dice may be unordered. +~~~~ ## Scores in Yacht @@ -21,15 +25,6 @@ The score of a throw of the dice depends on category chosen. | Choice | Sum of the dice | Any combination | 2 3 3 4 6 scores 18 | | Yacht | 50 points | All five dice showing the same face | 4 4 4 4 4 scores 50 | -If the dice do not satisfy the requirements of a category, the score is zero. +If the dice do **not** satisfy the requirements of a category, the score is zero. If, for example, _Four Of A Kind_ is entered in the _Yacht_ category, zero points are scored. A _Yacht_ scores zero if entered in the _Full House_ category. - -## Task - -Given a list of values for five dice and a category, your solution should return the score of the dice for that category. -If the dice do not satisfy the requirements of the category your solution should return 0. -You can assume that five values will always be presented, and the value of each will be between one and six inclusively. -You should not assume that the dice are ordered. - -[yacht]: https://en.wikipedia.org/wiki/Yacht_(dice_game) diff --git a/exercises/practice/yacht/.docs/introduction.md b/exercises/practice/yacht/.docs/introduction.md new file mode 100644 index 000000000..5b541f562 --- /dev/null +++ b/exercises/practice/yacht/.docs/introduction.md @@ -0,0 +1,11 @@ +# Introduction + +Each year, something new is "all the rage" in your high school. +This year it is a dice game: [Yacht][yacht]. + +The game of Yacht is from the same family as Poker Dice, Generala and particularly Yahtzee, of which it is a precursor. +The game consists of twelve rounds. +In each, five dice are rolled and the player chooses one of twelve categories. +The chosen category is then used to score the throw of the dice. + +[yacht]: https://en.wikipedia.org/wiki/Yacht_(dice_game) From 648df80a1598cffff649fe53330d8740ac0ff709 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 30 Apr 2024 13:50:02 +0200 Subject: [PATCH 291/436] scale-generator: fix outdated example (#1914) There was some obscure warning generated by the derive macro of the `failure` crate. The library itself is very outdated, people use `thiserror` these days. Since the exercise is deprecated anyway, I just replaced the example with my own dependency-free solution. --- .../scale-generator/.meta/Cargo-example.toml | 11 - .../practice/scale-generator/.meta/example.rs | 383 +++--------------- 2 files changed, 47 insertions(+), 347 deletions(-) delete mode 100644 exercises/practice/scale-generator/.meta/Cargo-example.toml diff --git a/exercises/practice/scale-generator/.meta/Cargo-example.toml b/exercises/practice/scale-generator/.meta/Cargo-example.toml deleted file mode 100644 index 1e740af67..000000000 --- a/exercises/practice/scale-generator/.meta/Cargo-example.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -edition = "2021" -name = "scale_generator" -version = "1.0.0" - -[dependencies] -enum-primitive-derive = "^0.1" -failure = "0.1" -failure_derive = "0.1" -itertools = "0.7" -num-traits = "^0.1" diff --git a/exercises/practice/scale-generator/.meta/example.rs b/exercises/practice/scale-generator/.meta/example.rs index ace769a01..afa7631d6 100644 --- a/exercises/practice/scale-generator/.meta/example.rs +++ b/exercises/practice/scale-generator/.meta/example.rs @@ -1,357 +1,68 @@ -#[macro_use] -extern crate enum_primitive_derive; -extern crate failure; -#[macro_use] -extern crate failure_derive; -extern crate itertools; -extern crate num_traits; - -pub use self::interval::{Interval, Intervals}; -use self::note::Accidental; -pub use self::note::Note; -use failure::Error; -use std::str::FromStr; - -pub mod interval { - use itertools::Itertools; - use std::fmt; - use std::ops::Deref; - use std::str::FromStr; - - #[derive(Debug, Clone, Copy, PartialEq, Eq, Fail)] - pub enum ParseErr { - #[fail(display = "invalid interval")] - InvalidInterval, - #[fail(display = "wrong number of semitones")] - WrongNumberOfSemitones, - } - - #[derive(Debug, Clone, Copy, PartialEq, Eq, Primitive)] - pub enum Interval { - HalfStep = 1, - WholeStep = 2, - AugmentedFirst = 3, - } - - impl fmt::Display for Interval { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use self::Interval::*; - write!( - f, - "{}", - match self { - HalfStep => "m", - WholeStep => "M", - AugmentedFirst => "A", - } - ) - } - } - - impl FromStr for Interval { - type Err = ParseErr; - - fn from_str(s: &str) -> Result { - use self::Interval::*; - match s { - "m" => Ok(HalfStep), - "M" => Ok(WholeStep), - "A" => Ok(AugmentedFirst), - _ => Err(ParseErr::InvalidInterval), - } - } - } +#[derive(Debug)] +pub struct Error; - #[derive(Debug, Clone, PartialEq, Eq)] - pub struct Intervals(Vec); +pub struct Scale(Vec); - impl fmt::Display for Intervals { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.0.iter().join("")) - } - } +const NUM_TONICS: usize = 12; - impl FromStr for Intervals { - type Err = ParseErr; +const SCALE_WITH_SHARPS: [&str; NUM_TONICS] = [ + "A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", +]; - fn from_str(s: &str) -> Result { - let mut semitones = Vec::with_capacity(s.len()); +const SCALE_WITH_FLATS: [&str; NUM_TONICS] = [ + "A", "Bb", "B", "C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", +]; - for (i, c) in s.char_indices() { - semitones.push(Interval::from_str(&s[i..i + c.len_utf8()])?); - } +const USING_FLATS: [&str; 12] = [ + "F", "Bb", "Eb", "Ab", "Db", "Gb", "d", "g", "c", "f", "bb", "eb", +]; - if semitones.iter().take(12).map(|&i| i as u8).sum::() == 12 { - Ok(Intervals(semitones)) - } else { - Err(ParseErr::WrongNumberOfSemitones) - } - } - } +fn get_uppercase_tonic(tonic: &str) -> String { + let mut iter = tonic.chars(); + let mut s = String::new(); - impl Deref for Intervals { - type Target = Vec; + let first = iter.next().unwrap().to_uppercase().next().unwrap(); - fn deref(&self) -> &Self::Target { - &self.0 - } - } + s.push(first); + s.extend(iter); + s } -pub mod note { - use crate::Interval; - use num_traits::{FromPrimitive, ToPrimitive}; - use std::fmt; - use std::ops::AddAssign; - use std::str::FromStr; - - pub const SEMITONES: i8 = 12; - - #[derive(Debug, Clone, Copy, PartialEq, Eq, Primitive)] - pub enum Semitone { - A = 0, - ASharp = 1, - B = 2, - C = 3, - CSharp = 4, - D = 5, - DSharp = 6, - E = 7, - F = 8, - FSharp = 9, - G = 10, - GSharp = 11, - } - - #[derive(Debug, Clone, Copy, PartialEq, Eq, Primitive)] - pub enum Root { - A = 0, - B = 2, - C = 3, - D = 5, - E = 7, - F = 8, - G = 10, - } - - impl fmt::Display for Root { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}", self) - } - } - - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - pub enum Accidental { - Sharp, - Flat, - } - - impl Accidental { - fn to_i8(self) -> i8 { - match self { - Accidental::Sharp => 1, - Accidental::Flat => -1, - } - } - - pub fn from_tonic(tonic: &str) -> Accidental { - match tonic { - "C" | "a" | "G" | "D" | "A" | "E" | "B" | "F#" | "e" | "b" | "f#" | "c#" | "g#" - | "d#" => Accidental::Sharp, - _ => Accidental::Flat, - } - } - } - - impl fmt::Display for Accidental { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "{}", - match &self { - Accidental::Sharp => '#', - Accidental::Flat => 'b', - } - ) - } - } - - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - pub struct Note { - tonic: Root, - accidental: Option, - } - - impl Note { - pub fn canonicalize(&self, lean: Accidental) -> Note { - let mut n: Note = Semitone::from(*self).into(); - if let Some(accidental) = n.accidental { - if accidental != lean && lean == Accidental::Flat { - n += Interval::HalfStep; - n.accidental = Some(Accidental::Flat); - } - } - n - } - } - - impl fmt::Display for Note { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "{}{}", - self.tonic, - self.accidental.map_or(String::new(), |a| a.to_string()), - ) - } - } - - #[derive(Debug, Clone, Copy, PartialEq, Eq, Fail)] - pub enum ParseErr { - #[fail(display = "invalid length")] - InvalidLength, - #[fail(display = "invalid tonic")] - InvalidTonic, - #[fail(display = "invalid accidental")] - InvalidAccidental, - } - - impl FromStr for Note { - type Err = ParseErr; - - fn from_str(s: &str) -> Result { - let lc = s.to_lowercase(); - let mut iter = lc.chars(); - - let mut note = match iter.next() { - Some(c) if ('a'..='g').contains(&c) => Note { - tonic: match c { - 'a' => Root::A, - 'b' => Root::B, - 'c' => Root::C, - 'd' => Root::D, - 'e' => Root::E, - 'f' => Root::F, - 'g' => Root::G, - _ => return Err(ParseErr::InvalidTonic), - }, - accidental: None, - }, - Some(_) => return Err(ParseErr::InvalidTonic), - None => return Err(ParseErr::InvalidLength), - }; - - match iter.next() { - Some('b') => note.accidental = Some(Accidental::Flat), - Some('#') => note.accidental = Some(Accidental::Sharp), - Some(_) => return Err(ParseErr::InvalidAccidental), - None => {} - } - - if iter.next().is_some() { - return Err(ParseErr::InvalidLength); - } - - Ok(note) - } - } - - impl From for Note { - fn from(s: Semitone) -> Self { - Note { - tonic: match s { - Semitone::A | Semitone::ASharp => Root::A, - Semitone::B => Root::B, - Semitone::C | Semitone::CSharp => Root::C, - Semitone::D | Semitone::DSharp => Root::D, - Semitone::E => Root::E, - Semitone::F | Semitone::FSharp => Root::F, - Semitone::G | Semitone::GSharp => Root::G, - }, - accidental: match s { - Semitone::ASharp - | Semitone::CSharp - | Semitone::DSharp - | Semitone::FSharp - | Semitone::GSharp => Some(Accidental::Sharp), - _ => None, - }, +impl Scale { + pub fn new(tonic: &str, intervals: &str) -> Result { + let scale = if USING_FLATS.contains(&tonic) { + &SCALE_WITH_FLATS + } else { + &SCALE_WITH_SHARPS + }; + + let tonic = get_uppercase_tonic(tonic); + + let mut tonics_iter = scale + .iter() + .cycle() + .skip_while(|&&t| t != tonic) + .map(|&t| t.to_owned()); + + let mut v = vec![tonics_iter.next().unwrap()]; + + for interv in intervals.bytes() { + match interv { + b'm' => v.push(tonics_iter.next().unwrap()), + b'M' => v.push(tonics_iter.nth(1).unwrap()), + b'A' => v.push(tonics_iter.nth(2).unwrap()), + _ => panic!("unknown interval"), } } - } - impl From for Semitone { - fn from(n: Note) -> Self { - Semitone::from_i8( - (SEMITONES + n.tonic.to_i8().unwrap() + n.accidental.map_or(0, |a| a.to_i8())) - % SEMITONES, - ) - .expect("must have valid semitone") - } - } - - impl AddAssign for Note { - fn add_assign(&mut self, rhs: Interval) { - *self = Semitone::from_i8( - (SEMITONES + Semitone::from(*self).to_i8().unwrap() + rhs.to_i8().unwrap()) - % SEMITONES, - ) - .unwrap() - .into(); - } - } -} - -#[derive(Debug)] -pub struct Scale { - tonic: Note, - lean: Accidental, - intervals: Intervals, -} - -impl Scale { - pub fn new(tonic: &str, intervals: &str) -> Result { - Ok(Scale { - tonic: Note::from_str(tonic)?, - lean: Accidental::from_tonic(tonic), - intervals: Intervals::from_str(intervals)?, - }) + Ok(Scale(v)) } pub fn chromatic(tonic: &str) -> Result { - Scale::new(tonic, "mmmmmmmmmmmm") + Self::new(tonic, "mmmmmmmmmmmm") } pub fn enumerate(&self) -> Vec { - let mut out = Vec::with_capacity(self.intervals.len()); - - let mut note = self.tonic; - out.push(note.canonicalize(self.lean).to_string()); - for &interval in self.intervals.iter() { - note += interval; - out.push(note.canonicalize(self.lean).to_string()); - } - - out - } -} - -#[cfg(test)] -mod test { - use super::interval::*; - - #[test] - fn parse_chromatic() { - assert!("mmmmmmmmmmmm".parse::().is_ok()); - } - - #[test] - fn parse_major() { - assert!("MMmMMMm".parse::().is_ok()); - } - - #[test] - fn parse_minor() { - assert!("MmMMmMM".parse::().is_ok()); + self.0.clone() } } From 7946b9bea75fc59d47a5ac8baa8cc261b15376e8 Mon Sep 17 00:00:00 2001 From: Chris Zwicker Date: Tue, 30 Apr 2024 15:05:25 +0200 Subject: [PATCH 292/436] sum-of-multiples: update "deep dive" (#1915) --- .../.approaches/from-factors/content.md | 32 +++++++------------ .../.approaches/from-factors/snippet.txt | 10 +++--- .../.approaches/introduction.md | 17 +++++----- 3 files changed, 24 insertions(+), 35 deletions(-) diff --git a/exercises/practice/sum-of-multiples/.approaches/from-factors/content.md b/exercises/practice/sum-of-multiples/.approaches/from-factors/content.md index af71f4433..0300b5fa0 100644 --- a/exercises/practice/sum-of-multiples/.approaches/from-factors/content.md +++ b/exercises/practice/sum-of-multiples/.approaches/from-factors/content.md @@ -2,22 +2,20 @@ ```rust pub fn sum_of_multiples_from_factors(limit: u32, factors: &[u32]) -> u32 { - let mut multiples: Vec<_> = factors + factors .iter() .filter(|&&factor| factor != 0) .flat_map(|&factor| (factor..limit).step_by(factor as usize)) - .collect(); - multiples.sort(); - multiples.dedup(); - multiples.iter().sum() + .collect::>() + .iter() + .sum() } ``` This approach implements the exact steps outlined in the exercise description: 1. For each non-zero factor, find all multiples of that factor that are less than the `limit` -2. Collect all multiples in a [`Vec`][vec] -3. Remove duplicate multiples +2. Collect all multiples in a [`HashSet`][hash_set] 3. Calculate the sum of all unique multiples In order to compute the list of multiples for a factor, we create a [`Range`][range] from the factor (inclusive) to the `limit` (exclusive), then use [`step_by`][iterator-step_by] with the same factor. @@ -25,24 +23,16 @@ In order to compute the list of multiples for a factor, we create a [`Range`][ra To combine the multiples of all factors, we iterate the list of factors and use [`flat_map`][iterator-flat_map] on each factor's multiples. [`flat_map`][iterator-flat_map] is a combination of [`map`][iterator-map] and [`flatten`][iterator-flatten]; it maps each factor into its multiples, then flattens them all in a single sequence. -Since we need to have unique multiples to compute the proper sum, we [`collect`][iterator-collect] the multiples into a [`Vec`][vec], which allows us to then [`sort`][slice-sort][^1] them and use [`dedup`][vec-dedup] to remove the duplicates. -[`collect`][iterator-collect] is a powerful function that can collect the data in a sequence and store it in any kind of collection - however, because of this, the compiler is not able to infer the type of collection you want as the output. -To solve this problem, we type the `multiples` variable explicitly. - -Finally, calculating the sum of the remaining unique multiples in the set is easy: we can simply call [`sum`][iterator-sum]. - -[^1]: There is another method available to sort a slice: [`sort_unstable`][slice-sort_unstable]. Usually, using [`sort_unstable`][slice-sort_unstable] is recommended if we do not need to keep the ordering of duplicate elements (which is our case). However, [`sort`][slice-sort] has the advantage because of its implementation. From the documentation: - - > Current implementation - > - > The current algorithm is an adaptive, iterative merge sort inspired by timsort. It is designed to be very fast in cases where the slice is nearly sorted, or consists of two or more sorted sequences concatenated one after another. - - The last part is key, because this is exactly our use case: we concatenate sequences of _sorted_ multiples. +Since we need to have unique multiples to compute the proper sum, we [`collect`][iterator-collect] the multiples into a [`HashSet`][hash_set], which only keeps one of each of its entries, thus removing duplicates. +[`collect`][iterator-collect] is a powerful function that can collect the data in a sequence and store it in any kind of collection - however, because of this, the compiler in this case is not able to infer the type of collection you want as the output. +To solve this problem, we specify the type `HashSet<_>` explicitly. - Running a benchmark using the two methods shows that in our scenario, [`sort`][slice-sort] is about twice as fast as [`sort_unstable`][slice-sort_unstable]. +Finally, calculating the sum of the remaining unique multiples in the set is easy: we can simply get an [Iterator][iterator] and call [`sum`][iterator-sum]. [vec]: https://doc.rust-lang.org/std/vec/struct.Vec.html +[hash_set]: https://doc.rust-lang.org/std/collections/struct.HashSet.html [range]: https://doc.rust-lang.org/std/ops/struct.Range.html +[iterator]: https://doc.rust-lang.org/std/iter/trait.Iterator.html [iterator-step_by]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.step_by [iterator-flat_map]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.flat_map [iterator-map]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.map diff --git a/exercises/practice/sum-of-multiples/.approaches/from-factors/snippet.txt b/exercises/practice/sum-of-multiples/.approaches/from-factors/snippet.txt index 511d22574..105d1bd45 100644 --- a/exercises/practice/sum-of-multiples/.approaches/from-factors/snippet.txt +++ b/exercises/practice/sum-of-multiples/.approaches/from-factors/snippet.txt @@ -1,8 +1,8 @@ pub fn sum_of_multiples_from_factors(limit: u32, factors: &[u32]) -> u32 { - let mut multiples: Vec<_> = factors.iter() + factors + .iter() .filter(|&&factor| factor != 0) .flat_map(|&factor| (factor..limit).step_by(factor as usize)) - .collect(); - multiples.sort(); - multiples.dedup(); - multiples.iter().sum() + .collect::>() + .iter() + .sum() \ No newline at end of file diff --git a/exercises/practice/sum-of-multiples/.approaches/introduction.md b/exercises/practice/sum-of-multiples/.approaches/introduction.md index a942e2dd4..dd5bf42d8 100644 --- a/exercises/practice/sum-of-multiples/.approaches/introduction.md +++ b/exercises/practice/sum-of-multiples/.approaches/introduction.md @@ -14,14 +14,13 @@ It is also possible to find the multiples by simple addition, starting from the ```rust pub fn sum_of_multiples_from_factors(limit: u32, factors: &[u32]) -> u32 { - let mut multiples: Vec<_> = factors + factors .iter() .filter(|&&factor| factor != 0) .flat_map(|&factor| (factor..limit).step_by(factor as usize)) - .collect(); - multiples.sort(); - multiples.dedup(); - multiples.iter().sum() + .collect::>() + .iter() + .sum() } ``` @@ -41,10 +40,10 @@ For more information, check the [Sum by iterating the whole range approach][appr ## Which approach to use? -- Computing the sum from factors can be efficient if we have a small number of factors and/or if they are large compared to the limit, because this will result in a small number of multiples to deduplicate. - However, as the number of multiples grows, this approach can result in a lot of work to deduplicate them. -- Computing the sum by iterating the whole range is less efficient for large ranges when the number of factors is small and/or when they are large. - However, this approach has the advantage of having stable complexity that is only dependent on the limit and the number of factors, since there is no deduplication involved. +- Computing the sum from factors can be efficient if we have a small number of `factors` and/or if they are large compared to the `limit`, because this will result in a small number of hashes to compute "in vain". + However, as the number of multiples grows, this approach can result in a lot of effort updating the `HashMap` to eliminate duplicates. +- Computing the sum by iterating the whole range can be efficient if we have a small range (low `limit`) and a comparatively large amount of `factors`. + Additionally, this approach has the advantage of having stable complexity that is only dependent on the limit and the number of factors, since there is no deduplication involved. It also avoids any additional memory allocation. Without proper benchmarks, the second approach may be preferred since it offers a more stable level of complexity (e.g. its performances varies less when the size of the input changes). From 1a5352d08b62ce8e93faaccc40c9e8da1fc1c729 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 7 May 2024 09:06:48 +0200 Subject: [PATCH 293/436] series: fix outdated instructions (#1919) The controversial test case was previously removed without updating the exercise instructions. This change brings the instructions in line with the tests. - [PR removing the test][1] - [forum discussion about removing the test][2] - [report of outdated documentation][3] [1]: https://github.com/exercism/rust/pull/1822 [2]: https://forum.exercism.org/t/feedback-wanted-removing-controversial-test-from-series-exercise/8659 [3]: https://forum.exercism.org/t/add-test-for-zero-length-case-on-exercise-series/11070 --- exercises/practice/series/.docs/instructions.append.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/exercises/practice/series/.docs/instructions.append.md b/exercises/practice/series/.docs/instructions.append.md index d55ab1e1b..536a61a3d 100644 --- a/exercises/practice/series/.docs/instructions.append.md +++ b/exercises/practice/series/.docs/instructions.append.md @@ -1,4 +1,10 @@ # Instructions append Different languages on Exercism have different expectations about what the result should be if the length of the substrings is zero. -For Rust, we expect you to output a number of empty strings, which will be one greater than the length of the input string. +On the Rust track, we don't have a test for that case, so you are free to do what you feel is most appropriate. + +Consider the advantages and disadvantages of the following possibilities: +- Crash the program with `panic!`. +- Return a `Result::Err`. (not possible here, because the function signature is given) +- Return an empty vector. +- Return a vector containing as many empty strings as the lenght of the string "digits" **plus one**. (this has some nice mathematical properties!) From bef73aca74959d65dcdc50d044744571a91ba4bf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 May 2024 10:34:49 +0200 Subject: [PATCH 294/436] build(deps): bump actions/checkout from 4.1.4 to 4.1.5 (#1920) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.4 to 4.1.5.
Release notes

Sourced from actions/checkout's releases.

v4.1.5

What's Changed

Full Changelog: https://github.com/actions/checkout/compare/v4.1.4...v4.1.5

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/checkout&package-manager=github_actions&previous-version=4.1.4&new-version=4.1.5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/tests.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6f21b9da4..2d0746bd5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,7 +18,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: Fetch configlet run: ./bin/fetch-configlet @@ -32,7 +32,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: Run markdown lint run: ./bin/lint_markdown.sh @@ -43,7 +43,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: Run shellcheck uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # v2.0.0 @@ -58,7 +58,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: Fetch origin/main run: git fetch --depth=1 origin main @@ -84,7 +84,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: Setup toolchain uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 @@ -100,7 +100,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: Setup toolchain uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 @@ -123,7 +123,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: Setup toolchain uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 @@ -148,7 +148,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: Setup nightly toolchain uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 From f95081fcbfffc1acf8751ce5ad80aae89cdd2284 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Wed, 8 May 2024 09:34:11 +0200 Subject: [PATCH 295/436] grep: fix clippy warning (#1921) --- exercises/practice/grep/.meta/example.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/grep/.meta/example.rs b/exercises/practice/grep/.meta/example.rs index 063d77318..357162b0c 100644 --- a/exercises/practice/grep/.meta/example.rs +++ b/exercises/practice/grep/.meta/example.rs @@ -92,7 +92,7 @@ pub fn grep(pattern: &str, flags: &Flags, files: &[&str]) -> Result, } if flags.print_file_name { - result = file_name.to_owned().to_owned(); + file_name.to_owned().clone_into(&mut result); } result From ccf72a3a63549dc4855e972b21e90d9fd4eabf7d Mon Sep 17 00:00:00 2001 From: Haokun Tang <60290183+TomCN0803@users.noreply.github.com> Date: Thu, 9 May 2024 05:34:06 +0800 Subject: [PATCH 296/436] series: fix typo in instructions (#1922) --- exercises/practice/series/.docs/instructions.append.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/series/.docs/instructions.append.md b/exercises/practice/series/.docs/instructions.append.md index 536a61a3d..a45087895 100644 --- a/exercises/practice/series/.docs/instructions.append.md +++ b/exercises/practice/series/.docs/instructions.append.md @@ -7,4 +7,4 @@ Consider the advantages and disadvantages of the following possibilities: - Crash the program with `panic!`. - Return a `Result::Err`. (not possible here, because the function signature is given) - Return an empty vector. -- Return a vector containing as many empty strings as the lenght of the string "digits" **plus one**. (this has some nice mathematical properties!) +- Return a vector containing as many empty strings as the length of the string "digits" **plus one**. (this has some nice mathematical properties!) From 3cefffd1503742e16ff14cb6751cf2457254c045 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Sun, 12 May 2024 06:49:17 +0200 Subject: [PATCH 297/436] custom-set: sync (#1923) --- exercises/practice/custom-set/.meta/tests.toml | 3 +++ exercises/practice/custom-set/tests/custom-set.rs | 9 +++++++++ problem-specifications | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/exercises/practice/custom-set/.meta/tests.toml b/exercises/practice/custom-set/.meta/tests.toml index 8c450e0ba..430c139e6 100644 --- a/exercises/practice/custom-set/.meta/tests.toml +++ b/exercises/practice/custom-set/.meta/tests.toml @@ -114,6 +114,9 @@ description = "Difference (or Complement) of a set is a set of all elements that [c5ac673e-d707-4db5-8d69-7082c3a5437e] description = "Difference (or Complement) of a set is a set of all elements that are only in the first set -> difference of two non-empty sets is a set of elements that are only in the first set" +[20d0a38f-7bb7-4c4a-ac15-90c7392ecf2b] +description = "Difference (or Complement) of a set is a set of all elements that are only in the first set -> difference removes all duplicates in the first set" + [c45aed16-5494-455a-9033-5d4c93589dc6] description = "Union returns a set of all elements in either set -> union of empty sets is an empty set" diff --git a/exercises/practice/custom-set/tests/custom-set.rs b/exercises/practice/custom-set/tests/custom-set.rs index 6730dadb5..2d47870dd 100644 --- a/exercises/practice/custom-set/tests/custom-set.rs +++ b/exercises/practice/custom-set/tests/custom-set.rs @@ -286,6 +286,15 @@ fn difference_of_two_non_empty_sets_is_a_set_of_elements_that_are_only_in_the_fi assert_eq!(set_1.difference(&set_2), expected); } +#[test] +#[ignore] +fn difference_removes_all_duplicates_in_the_first_set() { + let set_1 = CustomSet::::new(&[1, 1]); + let set_2 = CustomSet::::new(&[1]); + let expected = CustomSet::::new(&[]); + assert_eq!(set_1.difference(&set_2), expected); +} + #[test] #[ignore] fn union_of_empty_sets_is_an_empty_set() { diff --git a/problem-specifications b/problem-specifications index a7ea37faf..3d9837ec2 160000 --- a/problem-specifications +++ b/problem-specifications @@ -1 +1 @@ -Subproject commit a7ea37faf511152a41c9ea47e2958772375ba6c4 +Subproject commit 3d9837ec2953f7e678b63da6af82eb6db5035dd0 From 85d8fca5dd512477a4cea3cc3b33a48161061c54 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Thu, 16 May 2024 14:26:58 +0200 Subject: [PATCH 298/436] spiral-matrix: sync (#1924) --- .../practice/spiral-matrix/.docs/instructions.md | 2 +- .../practice/spiral-matrix/.docs/introduction.md | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 exercises/practice/spiral-matrix/.docs/introduction.md diff --git a/exercises/practice/spiral-matrix/.docs/instructions.md b/exercises/practice/spiral-matrix/.docs/instructions.md index ba99e12c7..01e8a77f8 100644 --- a/exercises/practice/spiral-matrix/.docs/instructions.md +++ b/exercises/practice/spiral-matrix/.docs/instructions.md @@ -1,6 +1,6 @@ # Instructions -Given the size, return a square matrix of numbers in spiral order. +Your task is to return a square matrix of a given size. The matrix should be filled with natural numbers, starting from 1 in the top-left corner, increasing in an inward, clockwise spiral order, like these examples: diff --git a/exercises/practice/spiral-matrix/.docs/introduction.md b/exercises/practice/spiral-matrix/.docs/introduction.md new file mode 100644 index 000000000..25c7eb595 --- /dev/null +++ b/exercises/practice/spiral-matrix/.docs/introduction.md @@ -0,0 +1,11 @@ +# Introduction + +In a small village near an ancient forest, there was a legend of a hidden treasure buried deep within the woods. +Despite numerous attempts, no one had ever succeeded in finding it. +This was about to change, however, thanks to a young explorer named Elara. +She had discovered an old document containing instructions on how to locate the treasure. +Using these instructions, Elara was able to draw a map that revealed the path to the treasure. + +To her surprise, the path followed a peculiar clockwise spiral. +It was no wonder no one had been able to find the treasure before! +With the map in hand, Elara embarks on her journey to uncover the hidden treasure. From 1cd79b49bdbdf81ce8c582d272a5fd956c426d36 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Thu, 16 May 2024 14:27:25 +0200 Subject: [PATCH 299/436] anagram: sync (#1925) --- exercises/practice/anagram/.docs/instructions.md | 6 +++--- exercises/practice/anagram/.docs/introduction.md | 12 ++++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 exercises/practice/anagram/.docs/introduction.md diff --git a/exercises/practice/anagram/.docs/instructions.md b/exercises/practice/anagram/.docs/instructions.md index 7d1c8283e..a7298485b 100644 --- a/exercises/practice/anagram/.docs/instructions.md +++ b/exercises/practice/anagram/.docs/instructions.md @@ -1,9 +1,9 @@ # Instructions -An anagram is a rearrangement of letters to form a new word: for example `"owns"` is an anagram of `"snow"`. -A word is not its own anagram: for example, `"stop"` is not an anagram of `"stop"`. +Your task is to, given a target word and a set of candidate words, to find the subset of the candidates that are anagrams of the target. -Given a target word and a set of candidate words, this exercise requests the anagram set: the subset of the candidates that are anagrams of the target. +An anagram is a rearrangement of letters to form a new word: for example `"owns"` is an anagram of `"snow"`. +A word is _not_ its own anagram: for example, `"stop"` is not an anagram of `"stop"`. The target and candidates are words of one or more ASCII alphabetic characters (`A`-`Z` and `a`-`z`). Lowercase and uppercase characters are equivalent: for example, `"PoTS"` is an anagram of `"sTOp"`, but `StoP` is not an anagram of `sTOp`. diff --git a/exercises/practice/anagram/.docs/introduction.md b/exercises/practice/anagram/.docs/introduction.md new file mode 100644 index 000000000..1acbdf00b --- /dev/null +++ b/exercises/practice/anagram/.docs/introduction.md @@ -0,0 +1,12 @@ +# Introduction + +At a garage sale, you find a lovely vintage typewriter at a bargain price! +Excitedly, you rush home, insert a sheet of paper, and start typing away. +However, your excitement wanes when you examine the output: all words are garbled! +For example, it prints "stop" instead of "post" and "least" instead of "stale." +Carefully, you try again, but now it prints "spot" and "slate." +After some experimentation, you find there is a random delay before each letter is printed, which messes up the order. +You now understand why they sold it for so little money! + +You realize this quirk allows you to generate anagrams, which are words formed by rearranging the letters of another word. +Pleased with your finding, you spend the rest of the day generating hundreds of anagrams. From 1e09413ce54251169595e714dac9de9c4bf8da0a Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Thu, 16 May 2024 14:28:20 +0200 Subject: [PATCH 300/436] knapsack: sync (#1926) --- .../practice/knapsack/.docs/instructions.md | 20 +++++-------------- .../practice/knapsack/.docs/introduction.md | 8 ++++++++ 2 files changed, 13 insertions(+), 15 deletions(-) create mode 100644 exercises/practice/knapsack/.docs/introduction.md diff --git a/exercises/practice/knapsack/.docs/instructions.md b/exercises/practice/knapsack/.docs/instructions.md index fadcee1b1..3411db988 100644 --- a/exercises/practice/knapsack/.docs/instructions.md +++ b/exercises/practice/knapsack/.docs/instructions.md @@ -1,24 +1,15 @@ # Instructions -In this exercise, let's try to solve a classic problem. +Your task is to determine which items to take so that the total value of his selection is maximized, taking into account the knapsack's carrying capacity. -Bob is a thief. -After months of careful planning, he finally manages to crack the security systems of a high-class apartment. - -In front of him are many items, each with a value (v) and weight (w). -Bob, of course, wants to maximize the total value he can get; he would gladly take all of the items if he could. -However, to his horror, he realizes that the knapsack he carries with him can only hold so much weight (W). - -Given a knapsack with a specific carrying capacity (W), help Bob determine the maximum value he can get from the items in the house. -Note that Bob can take only one of each item. - -All values given will be strictly positive. Items will be represented as a list of items. Each item will have a weight and value. +All values given will be strictly positive. +Bob can take only one of each item. For example: -```none +```text Items: [ { "weight": 5, "value": 10 }, { "weight": 4, "value": 40 }, @@ -26,10 +17,9 @@ Items: [ { "weight": 4, "value": 50 } ] -Knapsack Limit: 10 +Knapsack Maximum Weight: 10 ``` For the above, the first item has weight 5 and value 10, the second item has weight 4 and value 40, and so on. - In this example, Bob should take the second and fourth item to maximize his value, which, in this case, is 90. He cannot get more than 90 as his knapsack has a weight limit of 10. diff --git a/exercises/practice/knapsack/.docs/introduction.md b/exercises/practice/knapsack/.docs/introduction.md new file mode 100644 index 000000000..9b2bed8b4 --- /dev/null +++ b/exercises/practice/knapsack/.docs/introduction.md @@ -0,0 +1,8 @@ +# Introduction + +Bob is a thief. +After months of careful planning, he finally manages to crack the security systems of a fancy store. + +In front of him are many items, each with a value and weight. +Bob would gladly take all of the items, but his knapsack can only hold so much weight. +Bob has to carefully consider which items to take so that the total value of his selection is maximized. From 382c943ae52a8a496f5a0bd909c6021df3232614 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Thu, 16 May 2024 15:47:18 +0200 Subject: [PATCH 301/436] kindergarten-garden: sync (#1927) --- .../kindergarten-garden/.docs/instructions.md | 32 +++++++++---------- .../kindergarten-garden/.docs/introduction.md | 6 ++++ 2 files changed, 21 insertions(+), 17 deletions(-) create mode 100644 exercises/practice/kindergarten-garden/.docs/introduction.md diff --git a/exercises/practice/kindergarten-garden/.docs/instructions.md b/exercises/practice/kindergarten-garden/.docs/instructions.md index 472ee26f6..6fe11a58c 100644 --- a/exercises/practice/kindergarten-garden/.docs/instructions.md +++ b/exercises/practice/kindergarten-garden/.docs/instructions.md @@ -1,16 +1,21 @@ # Instructions -Given a diagram, determine which plants each child in the kindergarten class is -responsible for. +Your task is to, given a diagram, determine which plants each child in the kindergarten class is responsible for. -The kindergarten class is learning about growing plants. -The teacher thought it would be a good idea to give them actual seeds, plant them in actual dirt, and grow actual plants. +There are 12 children in the class: + +- Alice, Bob, Charlie, David, Eve, Fred, Ginny, Harriet, Ileana, Joseph, Kincaid, and Larry. + +Four different types of seeds are planted: -They've chosen to grow grass, clover, radishes, and violets. +| Plant | Diagram encoding | +| ------ | ---------------- | +| Grass | G | +| Clover | C | +| Radish | R | +| Violet | V | -To this end, the children have put little cups along the window sills, and -planted one type of plant in each cup, choosing randomly from the available -types of seeds. +Each child gets four cups, two on each row: ```text [window][window][window] @@ -18,16 +23,9 @@ types of seeds. ........................ ``` -There are 12 children in the class: - -- Alice, Bob, Charlie, David, -- Eve, Fred, Ginny, Harriet, -- Ileana, Joseph, Kincaid, and Larry. - -Each child gets 4 cups, two on each row. -Their teacher assigns cups to the children alphabetically by their names. +Their teacher assigns cups to the children alphabetically by their names, which means that Alice comes first and Larry comes last. -The following diagram represents Alice's plants: +Here is an example diagram representing Alice's plants: ```text [window][window][window] diff --git a/exercises/practice/kindergarten-garden/.docs/introduction.md b/exercises/practice/kindergarten-garden/.docs/introduction.md new file mode 100644 index 000000000..5ad97d23e --- /dev/null +++ b/exercises/practice/kindergarten-garden/.docs/introduction.md @@ -0,0 +1,6 @@ +# Introduction + +The kindergarten class is learning about growing plants. +The teacher thought it would be a good idea to give the class seeds to plant and grow in the dirt. +To this end, the children have put little cups along the window sills and planted one type of plant in each cup. +The children got to pick their favorites from four available types of seeds: grass, clover, radishes, and violets. From e97614fcccf58126997bf6dfd60bd0523a410306 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 18 May 2024 20:35:51 +0200 Subject: [PATCH 302/436] build(deps): bump actions/checkout from 4.1.5 to 4.1.6 (#1928) --- .github/workflows/tests.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2d0746bd5..efbb9a528 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,7 +18,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Fetch configlet run: ./bin/fetch-configlet @@ -32,7 +32,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Run markdown lint run: ./bin/lint_markdown.sh @@ -43,7 +43,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Run shellcheck uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # v2.0.0 @@ -58,7 +58,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Fetch origin/main run: git fetch --depth=1 origin main @@ -84,7 +84,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Setup toolchain uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 @@ -100,7 +100,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Setup toolchain uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 @@ -123,7 +123,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Setup toolchain uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 @@ -148,7 +148,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Setup nightly toolchain uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 From cccd86c63164c2cfafafba4c4940fb77a235de63 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Thu, 30 May 2024 11:13:51 +0200 Subject: [PATCH 303/436] pig-latin: sync (#1929) Sync the `pig-latin` exercise with the latest data, as defined in https://github.com/exercism/problem-specifications/tree/main/exercises/pig-latin. - Feel free to close this PR if there is another PR that also syncs this exercise. - When approved, feel free to merge this PR yourselves (you don't have to wait for me). As this PR only updates docs and/or metadata, it won't trigger a re-run of existing solutions (see [the docs](https://exercism.org/docs/building/tracks)). --- .../practice/pig-latin/.docs/instructions.md | 52 ++++++++++++++++--- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/exercises/practice/pig-latin/.docs/instructions.md b/exercises/practice/pig-latin/.docs/instructions.md index 571708814..6c843080d 100644 --- a/exercises/practice/pig-latin/.docs/instructions.md +++ b/exercises/practice/pig-latin/.docs/instructions.md @@ -1,10 +1,46 @@ # Instructions -Your task is to translate text from English to Pig Latin using the following rules: - -- **Rule 1**: If a word begins with a vowel sound, add an "ay" sound to the end of the word (e.g. "apple" -> "appleay"). - Please note that "xr" and "yt" at the beginning of a word make vowel sounds (e.g. "xray" -> "xrayay", "yttria" -> "yttriaay"). -- **Rule 2**: If a word begins with a consonant sound, move it to the end of the word and then add an "ay" sound to the end of the word (e.g. "pig" -> "igpay"). - Consonant sounds can be made up of multiple consonants, such as the "ch" in "chair" or "st" in "stand" (e.g. "chair" -> "airchay"). -- **Rule 3**: If a word starts with a consonant sound followed by "qu", move them to the end of the word, and then add an "ay" sound to the end of the word (e.g. "square" -> "aresquay"). -- **Rule 4**: If a word contains a "y" after a consonant cluster or as the second letter in a two letter word it makes a vowel sound (e.g. "rhythm" -> "ythmrhay", "my" -> "ymay"). +Your task is to translate text from English to Pig Latin. +The translation is defined using four rules, which look at the pattern of vowels and consonants at the beginning of a word. +These rules look at each word's use of vowels and consonants: + +- vowels: the letters `a`, `e`, `i`, `o`, and `u` +- consonants: the other 21 letters of the English alphabet + +## Rule 1 + +If a word begins with a vowel, or starts with `"xr"` or `"yt"`, add an `"ay"` sound to the end of the word. + +For example: + +- `"apple"` -> `"appleay"` (starts with vowel) +- `"xray"` -> `"xrayay"` (starts with `"xr"`) +- `"yttria"` -> `"yttriaay"` (starts with `"yt"`) + +## Rule 2 + +If a word begins with a one or more consonants, first move those consonants to the end of the word and then add an `"ay"` sound to the end of the word. + +For example: + +- `"pig"` -> `"igp"` -> `"igpay"` (starts with single consonant) +- `"chair"` -> `"airch"` -> `"airchay"` (starts with multiple consonants) +- `"thrush"` -> `"ushthr"` -> `"ushthray"` (starts with multiple consonants) + +## Rule 3 + +If a word starts with zero or more consonants followed by `"qu"`, first move those consonants (if any) and the `"qu"` part to the end of the word, and then add an `"ay"` sound to the end of the word. + +For example: + +- `"quick"` -> `"ickqu"` -> `"ay"` (starts with `"qu"`, no preceding consonants) +- `"square"` -> `"aresqu"` -> `"aresquay"` (starts with one consonant followed by `"qu`") + +## Rule 4 + +If a word starts with one or more consonants followed by `"y"`, first move the consonants preceding the `"y"`to the end of the word, and then add an `"ay"` sound to the end of the word. + +Some examples: + +- `"my"` -> `"ym"` -> `"ymay"` (starts with single consonant followed by `"y"`) +- `"rhythm"` -> `"ythmrh"` -> `"ythmrhay"` (starts with multiple consonants followed by `"y"`) From 0b8e1942df6d34fe9a2673754c561544549eb32e Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Mon, 10 Jun 2024 09:57:22 +0200 Subject: [PATCH 304/436] isbn-verifier: sync (#1930) --- .../isbn-verifier/.meta/test_template.tera | 13 ++++ .../practice/isbn-verifier/.meta/tests.toml | 60 +++++++++++++++++-- .../isbn-verifier/tests/isbn-verifier.rs | 57 +++++++++--------- problem-specifications | 2 +- 4 files changed, 97 insertions(+), 35 deletions(-) create mode 100644 exercises/practice/isbn-verifier/.meta/test_template.tera diff --git a/exercises/practice/isbn-verifier/.meta/test_template.tera b/exercises/practice/isbn-verifier/.meta/test_template.tera new file mode 100644 index 000000000..c51d0e82c --- /dev/null +++ b/exercises/practice/isbn-verifier/.meta/test_template.tera @@ -0,0 +1,13 @@ +use isbn_verifier::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | snake_case }}() { + {% if test.expected %} + assert!(is_valid_isbn("{{ test.input.isbn }}")); + {% else %} + assert!(!is_valid_isbn("{{ test.input.isbn }}")); + {% endif %} +} +{% endfor -%} diff --git a/exercises/practice/isbn-verifier/.meta/tests.toml b/exercises/practice/isbn-verifier/.meta/tests.toml index 1519d218d..6d5a84599 100644 --- a/exercises/practice/isbn-verifier/.meta/tests.toml +++ b/exercises/practice/isbn-verifier/.meta/tests.toml @@ -1,10 +1,56 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[0caa3eac-d2e3-4c29-8df8-b188bc8c9292] +description = "valid isbn" + +[19f76b53-7c24-45f8-87b8-4604d0ccd248] +description = "invalid isbn check digit" + +[4164bfee-fb0a-4a1c-9f70-64c6a1903dcd] +description = "valid isbn with a check digit of 10" + +[3ed50db1-8982-4423-a993-93174a20825c] +description = "check digit is a character other than X" + +[9416f4a5-fe01-4b61-a07b-eb75892ef562] +description = "invalid check digit in isbn is not treated as zero" + +[c19ba0c4-014f-4dc3-a63f-ff9aefc9b5ec] +description = "invalid character in isbn is not treated as zero" + +[28025280-2c39-4092-9719-f3234b89c627] +description = "X is only valid as a check digit" + +[f6294e61-7e79-46b3-977b-f48789a4945b] +description = "valid isbn without separating dashes" + +[185ab99b-3a1b-45f3-aeec-b80d80b07f0b] +description = "isbn without separating dashes and X as check digit" + +[7725a837-ec8e-4528-a92a-d981dd8cf3e2] +description = "isbn without check digit and dashes" + +[47e4dfba-9c20-46ed-9958-4d3190630bdf] +description = "too long isbn and no dashes" [737f4e91-cbba-4175-95bf-ae630b41fb60] description = "too short isbn" +[5458a128-a9b6-4ff8-8afb-674e74567cef] +description = "isbn without check digit" + +[70b6ad83-d0a2-4ca7-a4d5-a9ab731800f7] +description = "check digit of X should not be used for 0" + [94610459-55ab-4c35-9b93-ff6ea1a8e562] description = "empty isbn" @@ -12,4 +58,10 @@ description = "empty isbn" description = "input is 9 characters" [ed6e8d1b-382c-4081-8326-8b772c581fec] -description = "invalid characters are not ignored" +description = "invalid characters are not ignored after checking length" + +[daad3e58-ce00-4395-8a8e-e3eded1cdc86] +description = "invalid characters are not ignored before checking length" + +[fb5e48d8-7c03-4bfb-a088-b101df16fdc3] +description = "input is too long but contains a valid isbn" diff --git a/exercises/practice/isbn-verifier/tests/isbn-verifier.rs b/exercises/practice/isbn-verifier/tests/isbn-verifier.rs index 6a831d51c..73f27f406 100644 --- a/exercises/practice/isbn-verifier/tests/isbn-verifier.rs +++ b/exercises/practice/isbn-verifier/tests/isbn-verifier.rs @@ -1,63 +1,67 @@ -use isbn_verifier::is_valid_isbn; +use isbn_verifier::*; #[test] -fn valid() { +fn valid_isbn() { assert!(is_valid_isbn("3-598-21508-8")); } #[test] #[ignore] -fn invalid_check_digit() { +fn invalid_isbn_check_digit() { assert!(!is_valid_isbn("3-598-21508-9")); } #[test] #[ignore] -fn valid_check_digit_of_10() { +fn valid_isbn_with_a_check_digit_of_10() { assert!(is_valid_isbn("3-598-21507-X")); } #[test] #[ignore] -fn invalid_character_as_check_digit() { +fn check_digit_is_a_character_other_than_x() { assert!(!is_valid_isbn("3-598-21507-A")); } #[test] #[ignore] -fn invalid_character_in_isbn() { +fn invalid_check_digit_in_isbn_is_not_treated_as_zero() { + assert!(!is_valid_isbn("4-598-21507-B")); +} + +#[test] +#[ignore] +fn invalid_character_in_isbn_is_not_treated_as_zero() { assert!(!is_valid_isbn("3-598-P1581-X")); } #[test] #[ignore] -#[allow(non_snake_case)] -fn invalid_isbn_with_invalid_X() { +fn x_is_only_valid_as_a_check_digit() { assert!(!is_valid_isbn("3-598-2X507-9")); } #[test] #[ignore] -fn valid_isbn_without_dashes() { +fn valid_isbn_without_separating_dashes() { assert!(is_valid_isbn("3598215088")); } #[test] #[ignore] -#[allow(non_snake_case)] -fn valid_isbn_without_dashes_and_X_as_check() { +fn isbn_without_separating_dashes_and_x_as_check_digit() { assert!(is_valid_isbn("359821507X")); } #[test] #[ignore] -fn invalid_isbn_without_dashes_and_no_check_digit() { +fn isbn_without_check_digit_and_dashes() { assert!(!is_valid_isbn("359821507")); } #[test] #[ignore] -fn invalid_isbn_without_dashes_and_too_long() { +fn too_long_isbn_and_no_dashes() { assert!(!is_valid_isbn("3598215078X")); } @@ -69,26 +73,13 @@ fn too_short_isbn() { #[test] #[ignore] -fn invalid_isbn_without_check_digit() { +fn isbn_without_check_digit() { assert!(!is_valid_isbn("3-598-21507")); } #[test] #[ignore] -fn valid_digits_invalid_length() { - assert!(!is_valid_isbn("35982150881")); -} - -#[test] -#[ignore] -fn special_characters() { - assert!(!is_valid_isbn("!@#%!@")); -} - -#[test] -#[ignore] -#[allow(non_snake_case)] -fn invalid_isbn_with_check_digit_X_instead_of_0() { +fn check_digit_of_x_should_not_be_used_for_0() { assert!(!is_valid_isbn("3-598-21515-X")); } @@ -106,12 +97,18 @@ fn input_is_9_characters() { #[test] #[ignore] -fn invalid_characters_are_not_ignored() { +fn invalid_characters_are_not_ignored_after_checking_length() { assert!(!is_valid_isbn("3132P34035")); } #[test] #[ignore] -fn too_long_but_contains_a_valid_isbn() { +fn invalid_characters_are_not_ignored_before_checking_length() { + assert!(!is_valid_isbn("3598P215088")); +} + +#[test] +#[ignore] +fn input_is_too_long_but_contains_a_valid_isbn() { assert!(!is_valid_isbn("98245726788")); } diff --git a/problem-specifications b/problem-specifications index 3d9837ec2..238b8b090 160000 --- a/problem-specifications +++ b/problem-specifications @@ -1 +1 @@ -Subproject commit 3d9837ec2953f7e678b63da6af82eb6db5035dd0 +Subproject commit 238b8b090c331273eaccd5daba06df6f62863342 From 02bb602b02c066ce96f8590fb02ae65612282211 Mon Sep 17 00:00:00 2001 From: mckzm <134839822+mckzm@users.noreply.github.com> Date: Wed, 19 Jun 2024 14:52:15 +0900 Subject: [PATCH 305/436] Update instructions.append.md for Triangle (#1933) --- exercises/practice/triangle/.docs/instructions.append.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/triangle/.docs/instructions.append.md b/exercises/practice/triangle/.docs/instructions.append.md index 7ae558d39..cca76d677 100644 --- a/exercises/practice/triangle/.docs/instructions.append.md +++ b/exercises/practice/triangle/.docs/instructions.append.md @@ -13,7 +13,7 @@ Or maybe you will come up with an approach that uses none of those! ## Non-integer lengths The base exercise tests identification of triangles whose sides are all -integers. However, some triangles cannot be represented by pure integers. A simple example is a right triangle (an isosceles triangle whose equal sides are separated by 90 degrees) whose equal sides both have length of 1. Its hypotenuse is the square root of 2, which is an irrational number: no simple multiplication can represent this number as an integer. +integers. However, some triangles cannot be represented by pure integers. A simple example is a triangle with a 90 degree angle between two equal sides of length 1. Its third side has the length square root of 2, which is an irrational number. No integer can represent it. It would be tedious to rewrite the analysis functions to handle both integer and floating-point cases, and particularly tedious to do so for all potential integer and floating point types: given signed and unsigned variants of bitwidths 8, 16, 32, 64, and 128, that would be 10 reimplementations of fundamentally the same code even before considering floats! From a7e7b35f997d3632fd2e823764f3d6c06edb822d Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Sat, 22 Jun 2024 09:11:44 +0200 Subject: [PATCH 306/436] triangle: update instructions append (#1934) [forum discussion](http://forum.exercism.org/t/pr-to-update-instructions-append-md-for-triangle/11752/6) --- exercises/practice/triangle/.docs/instructions.append.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/triangle/.docs/instructions.append.md b/exercises/practice/triangle/.docs/instructions.append.md index cca76d677..d38f8268d 100644 --- a/exercises/practice/triangle/.docs/instructions.append.md +++ b/exercises/practice/triangle/.docs/instructions.append.md @@ -13,7 +13,7 @@ Or maybe you will come up with an approach that uses none of those! ## Non-integer lengths The base exercise tests identification of triangles whose sides are all -integers. However, some triangles cannot be represented by pure integers. A simple example is a triangle with a 90 degree angle between two equal sides of length 1. Its third side has the length square root of 2, which is an irrational number. No integer can represent it. +integers. However, some triangles cannot be represented by pure integers. A simple example is a triangle with a 90 degree angle between two equal sides of length 1. Its third side has the length square root of 2, which is an irrational number (meaning it cannot be written as an integer or a fraction). It would be tedious to rewrite the analysis functions to handle both integer and floating-point cases, and particularly tedious to do so for all potential integer and floating point types: given signed and unsigned variants of bitwidths 8, 16, 32, 64, and 128, that would be 10 reimplementations of fundamentally the same code even before considering floats! From d31a56e78a4d44a99fde7e125180a7807006b955 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Jul 2024 09:19:23 +0200 Subject: [PATCH 307/436] build(deps): bump actions/checkout from 4.1.6 to 4.1.7 (#1932) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/tests.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index efbb9a528..f76540358 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,7 +18,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Fetch configlet run: ./bin/fetch-configlet @@ -32,7 +32,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Run markdown lint run: ./bin/lint_markdown.sh @@ -43,7 +43,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Run shellcheck uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # v2.0.0 @@ -58,7 +58,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Fetch origin/main run: git fetch --depth=1 origin main @@ -84,7 +84,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Setup toolchain uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 @@ -100,7 +100,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Setup toolchain uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 @@ -123,7 +123,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Setup toolchain uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 @@ -148,7 +148,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Setup nightly toolchain uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 From 81eb13c5120f5d64df72e893c5f4940939d51b81 Mon Sep 17 00:00:00 2001 From: Exercism Bot Date: Wed, 10 Jul 2024 14:15:30 +0100 Subject: [PATCH 308/436] =?UTF-8?q?=F0=9F=A4=96=20Sync=20org-wide=20files?= =?UTF-8?q?=20to=20upstream=20repo=20(#1939)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bin/fetch-configlet | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/bin/fetch-configlet b/bin/fetch-configlet index 4800e1508..6bef43ab7 100755 --- a/bin/fetch-configlet +++ b/bin/fetch-configlet @@ -24,10 +24,11 @@ get_download_url() { local latest='https://api.github.com/repos/exercism/configlet/releases/latest' local arch case "$(uname -m)" in - x86_64) arch='x86-64' ;; - *686*) arch='i386' ;; - *386*) arch='i386' ;; - *) arch='x86-64' ;; + aarch64|arm64) arch='arm64' ;; + x86_64) arch='x86-64' ;; + *686*) arch='i386' ;; + *386*) arch='i386' ;; + *) arch='x86-64' ;; esac local suffix="${os}_${arch}.${ext}" curl "${curlopts[@]}" --header 'Accept: application/vnd.github.v3+json' "${latest}" | @@ -47,7 +48,7 @@ main() { fi local os - case "$(uname)" in + case "$(uname -s)" in Darwin*) os='macos' ;; Linux*) os='linux' ;; Windows*) os='windows' ;; @@ -58,8 +59,8 @@ main() { local ext case "${os}" in - windows*) ext='zip' ;; - *) ext='tar.gz' ;; + windows) ext='zip' ;; + *) ext='tar.gz' ;; esac echo "Fetching configlet..." >&2 @@ -69,16 +70,16 @@ main() { curl "${curlopts[@]}" --output "${output_path}" "${download_url}" case "${ext}" in - *zip) unzip "${output_path}" -d "${output_dir}" ;; - *) tar xzf "${output_path}" -C "${output_dir}" ;; + zip) unzip "${output_path}" -d "${output_dir}" ;; + *) tar xzf "${output_path}" -C "${output_dir}" ;; esac rm -f "${output_path}" local executable_ext case "${os}" in - windows*) executable_ext='.exe' ;; - *) executable_ext='' ;; + windows) executable_ext='.exe' ;; + *) executable_ext='' ;; esac local configlet_path="${output_dir}/configlet${executable_ext}" From 739b2a961436e03e7c92a83b14dcdfa73dedbfb1 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Wed, 10 Jul 2024 15:16:05 +0200 Subject: [PATCH 309/436] Simplify protein-translation (#1940) closes #1937 --- .../.meta/additional-tests.json | 182 --------------- .../protein-translation/.meta/example.rs | 42 ++-- .../.meta/test_template.tera | 50 +---- .../protein-translation/.meta/tests.toml | 3 + .../practice/protein-translation/src/lib.rs | 20 +- .../tests/protein-translation.rs | 212 +++--------------- problem-specifications | 2 +- 7 files changed, 58 insertions(+), 453 deletions(-) delete mode 100644 exercises/practice/protein-translation/.meta/additional-tests.json diff --git a/exercises/practice/protein-translation/.meta/additional-tests.json b/exercises/practice/protein-translation/.meta/additional-tests.json deleted file mode 100644 index 530dc1483..000000000 --- a/exercises/practice/protein-translation/.meta/additional-tests.json +++ /dev/null @@ -1,182 +0,0 @@ -[ - { - "uuid": "ecafab60-89f2-4680-9286-56b423e8a7b9", - "description": "methionine", - "comments": [ - "The original design of the exercise deviates from problem-specifications", - "by having a name_for function for individual codons.", - "Upstreaming is impossible as it would change the design of the exercise.", - "Omitting them would leave the function untested, which would be confusing." - ], - "property": "name_for", - "input": { - "name": "AUG" - }, - "expected": "Some(\"methionine\")" - }, - { - "uuid": "ecafab60-89f2-4680-9286-56b423e8a7b9", - "description": "cysteine tgt", - "comments": [ - "The original design of the exercise deviates from problem-specifications", - "by having a name_for function for individual codons.", - "Upstreaming is impossible as it would change the design of the exercise.", - "Omitting them would leave the function untested, which would be confusing." - ], - "property": "name_for", - "input": { - "name": "UGU" - }, - "expected": "Some(\"cysteine\")" - }, - { - "uuid": "ecafab60-89f2-4680-9286-56b423e8a7b9", - "description": "stop", - "comments": [ - "The original design of the exercise deviates from problem-specifications", - "by having a name_for function for individual codons.", - "Upstreaming is impossible as it would change the design of the exercise.", - "Omitting them would leave the function untested, which would be confusing." - ], - "property": "name_for", - "input": { - "name": "UAA" - }, - "expected": "Some(\"stop codon\")" - }, - { - "uuid": "ecafab60-89f2-4680-9286-56b423e8a7b9", - "description": "valine", - "comments": [ - "The original design of the exercise deviates from problem-specifications", - "by having a name_for function for individual codons.", - "Upstreaming is impossible as it would change the design of the exercise.", - "Omitting them would leave the function untested, which would be confusing." - ], - "property": "name_for", - "input": { - "name": "GUU" - }, - "expected": "Some(\"valine\")" - }, - { - "uuid": "ecafab60-89f2-4680-9286-56b423e8a7b9", - "description": "isoleucine", - "comments": [ - "The original design of the exercise deviates from problem-specifications", - "by having a name_for function for individual codons.", - "Upstreaming is impossible as it would change the design of the exercise.", - "Omitting them would leave the function untested, which would be confusing." - ], - "property": "name_for", - "input": { - "name": "AUU" - }, - "expected": "Some(\"isoleucine\")" - }, - { - "uuid": "ecafab60-89f2-4680-9286-56b423e8a7b9", - "description": "arginine cga", - "comments": [ - "The original design of the exercise deviates from problem-specifications", - "by having a name_for function for individual codons.", - "Upstreaming is impossible as it would change the design of the exercise.", - "Omitting them would leave the function untested, which would be confusing." - ], - "property": "name_for", - "input": { - "name": "CGA" - }, - "expected": "Some(\"arginine\")" - }, - { - "uuid": "ecafab60-89f2-4680-9286-56b423e8a7b9", - "description": "arginine aga", - "comments": [ - "The original design of the exercise deviates from problem-specifications", - "by having a name_for function for individual codons.", - "Upstreaming is impossible as it would change the design of the exercise.", - "Omitting them would leave the function untested, which would be confusing." - ], - "property": "name_for", - "input": { - "name": "AGA" - }, - "expected": "Some(\"arginine\")" - }, - { - "uuid": "ecafab60-89f2-4680-9286-56b423e8a7b9", - "description": "arginine agg", - "comments": [ - "The original design of the exercise deviates from problem-specifications", - "by having a name_for function for individual codons.", - "Upstreaming is impossible as it would change the design of the exercise.", - "Omitting them would leave the function untested, which would be confusing." - ], - "property": "name_for", - "input": { - "name": "AGG" - }, - "expected": "Some(\"arginine\")" - }, - { - "uuid": "ecafab60-89f2-4680-9286-56b423e8a7b9", - "description": "empty is invalid", - "comments": [ - "The original design of the exercise deviates from problem-specifications", - "by having a name_for function for individual codons.", - "Upstreaming is impossible as it would change the design of the exercise.", - "Omitting them would leave the function untested, which would be confusing." - ], - "property": "name_for", - "input": { - "name": "" - }, - "expected": "None" - }, - { - "uuid": "ecafab60-89f2-4680-9286-56b423e8a7b9", - "description": "x is not shorthand so is invalid", - "comments": [ - "The original design of the exercise deviates from problem-specifications", - "by having a name_for function for individual codons.", - "Upstreaming is impossible as it would change the design of the exercise.", - "Omitting them would leave the function untested, which would be confusing." - ], - "property": "name_for", - "input": { - "name": "VWX" - }, - "expected": "None" - }, - { - "uuid": "ecafab60-89f2-4680-9286-56b423e8a7b9", - "description": "too short is invalid", - "comments": [ - "The original design of the exercise deviates from problem-specifications", - "by having a name_for function for individual codons.", - "Upstreaming is impossible as it would change the design of the exercise.", - "Omitting them would leave the function untested, which would be confusing." - ], - "property": "name_for", - "input": { - "name": "AU" - }, - "expected": "None" - }, - { - "uuid": "ecafab60-89f2-4680-9286-56b423e8a7b9", - "description": "too long is invalid", - "comments": [ - "The original design of the exercise deviates from problem-specifications", - "by having a name_for function for individual codons.", - "Upstreaming is impossible as it would change the design of the exercise.", - "Omitting them would leave the function untested, which would be confusing." - ], - "property": "name_for", - "input": { - "name": "ATTA" - }, - "expected": "None" - } -] diff --git a/exercises/practice/protein-translation/.meta/example.rs b/exercises/practice/protein-translation/.meta/example.rs index 38d7da580..49d857b84 100644 --- a/exercises/practice/protein-translation/.meta/example.rs +++ b/exercises/practice/protein-translation/.meta/example.rs @@ -1,27 +1,17 @@ -use std::collections::HashMap; - -pub struct CodonsInfo<'a> { - actual_codons: HashMap<&'a str, &'a str>, -} - -pub fn parse<'a>(pairs: Vec<(&'a str, &'a str)>) -> CodonsInfo<'a> { - CodonsInfo { - actual_codons: pairs.into_iter().collect(), - } -} - -impl<'a> CodonsInfo<'a> { - pub fn name_for(&self, codon: &str) -> Option<&'a str> { - self.actual_codons.get(&codon).cloned() - } - - pub fn of_rna(&self, strand: &str) -> Option> { - strand - .chars() - .collect::>() - .chunks(3) - .map(|chars| self.name_for(&chars.iter().collect::())) - .take_while(|result| result.is_none() || result.unwrap() != "stop codon") - .collect() - } +pub fn translate(rna: &str) -> Option> { + rna.as_bytes() + .chunks(3) + .map(|codon| match codon { + b"AUG" => Some("methionine"), + b"UUU" | b"UUC" => Some("phenylalanine"), + b"UUA" | b"UUG" => Some("leucine"), + b"UCU" | b"UCC" | b"UCA" | b"UCG" => Some("serine"), + b"UAU" | b"UAC" => Some("tyrosine"), + b"UGU" | b"UGC" => Some("cysteine"), + b"UGG" => Some("tryptophan"), + b"UAA" | b"UAG" | b"UGA" => Some("STOP"), + _ => None, + }) + .take_while(|result| result.is_none() || result.unwrap() != "STOP") + .collect() } diff --git a/exercises/practice/protein-translation/.meta/test_template.tera b/exercises/practice/protein-translation/.meta/test_template.tera index e124dbac1..6bcff9bb9 100644 --- a/exercises/practice/protein-translation/.meta/test_template.tera +++ b/exercises/practice/protein-translation/.meta/test_template.tera @@ -1,24 +1,11 @@ use protein_translation::*; {% for test in cases %} -{# custom name_for tests are first, of_rna is supposed to build on it #} -{% if test.property != "name_for" %}{% continue %}{% endif %} #[test] #[ignore] fn {{ test.description | snake_case }}() { - let info = parse(make_pairs()); - assert_eq!(info.name_for({{ test.input.name | json_encode() }}), {{ test.expected }}); -} -{% endfor -%} - -{% for test in cases %} -{% if test.property != "proteins" %}{% continue %}{% endif %} -#[test] -#[ignore] -fn {{ test.description | snake_case }}() { - let info = parse(make_pairs()); assert_eq!( - info.of_rna({{ test.input.strand | json_encode() }}), + translate({{ test.input.strand | json_encode() }}), {% if test.expected is object %} None {% else %} @@ -31,38 +18,3 @@ fn {{ test.description | snake_case }}() { ); } {% endfor -%} - -// The input data constructor. Returns a list of codon, name pairs. -fn make_pairs() -> Vec<(&'static str, &'static str)> { - let grouped = vec![ - ("isoleucine", vec!["AUU", "AUC", "AUA"]), - ("valine", vec!["GUU", "GUC", "GUA", "GUG"]), - ("phenylalanine", vec!["UUU", "UUC"]), - ("methionine", vec!["AUG"]), - ("cysteine", vec!["UGU", "UGC"]), - ("alanine", vec!["GCU", "GCC", "GCA", "GCG"]), - ("glycine", vec!["GGU", "GGC", "GGA", "GGG"]), - ("proline", vec!["CCU", "CCC", "CCA", "CCG"]), - ("threonine", vec!["ACU", "ACC", "ACA", "ACG"]), - ("serine", vec!["UCU", "UCC", "UCA", "UCG"]), - ("tyrosine", vec!["UAU", "UAC"]), - ("tryptophan", vec!["UGG"]), - ("glutamine", vec!["CAA", "CAG"]), - ("asparagine", vec!["AAU", "AAC"]), - ("histidine", vec!["CAU", "CAC"]), - ("glutamic acid", vec!["GAA", "GAG"]), - ("aspartic acid", vec!["GAU", "GAC"]), - ("lysine", vec!["AAA", "AAG"]), - ("arginine", vec!["CGU", "CGC", "CGA", "CGG", "AGA", "AGG"]), - ("leucine", vec!["UUA", "UUG"]), - ("stop codon", vec!["UAA", "UAG", "UGA"]), - ]; - let mut pairs = Vec::<(&'static str, &'static str)>::new(); - for (name, codons) in grouped.into_iter() { - for codon in codons { - pairs.push((codon, name)); - } - } - pairs.sort_by(|&(_, a), &(_, b)| a.cmp(b)); - pairs -} diff --git a/exercises/practice/protein-translation/.meta/tests.toml b/exercises/practice/protein-translation/.meta/tests.toml index 804b446e6..de680e39e 100644 --- a/exercises/practice/protein-translation/.meta/tests.toml +++ b/exercises/practice/protein-translation/.meta/tests.toml @@ -87,6 +87,9 @@ description = "Translation stops if STOP codon in middle of three-codon sequence [2c2a2a60-401f-4a80-b977-e0715b23b93d] description = "Translation stops if STOP codon in middle of six-codon sequence" +[f6f92714-769f-4187-9524-e353e8a41a80] +description = "Sequence of two non-STOP codons does not translate to a STOP codon" + [1e75ea2a-f907-4994-ae5c-118632a1cb0f] description = "Non-existing codon can't translate" include = false diff --git a/exercises/practice/protein-translation/src/lib.rs b/exercises/practice/protein-translation/src/lib.rs index af6df1b72..ddbfc80dc 100644 --- a/exercises/practice/protein-translation/src/lib.rs +++ b/exercises/practice/protein-translation/src/lib.rs @@ -1,19 +1,3 @@ -pub struct CodonsInfo<'a> { - // We fake using 'a here, so the compiler does not complain that - // "parameter `'a` is never used". Delete when no longer needed. - phantom: std::marker::PhantomData<&'a ()>, -} - -impl<'a> CodonsInfo<'a> { - pub fn name_for(&self, codon: &str) -> Option<&'a str> { - todo!("Return the protein name for a '{codon}' codon or None, if codon string is invalid"); - } - - pub fn of_rna(&self, rna: &str) -> Option> { - todo!("Return a list of protein names that correspond to the '{rna}' RNA string or None if the RNA string is invalid"); - } -} - -pub fn parse<'a>(pairs: Vec<(&'a str, &'a str)>) -> CodonsInfo<'a> { - todo!("Construct a new CodonsInfo struct from given pairs: {pairs:?}"); +pub fn translate(rna: &str) -> Option> { + todo!("Return a list of protein names that correspond to the '{rna}' RNA string or None if the RNA string is invalid"); } diff --git a/exercises/practice/protein-translation/tests/protein-translation.rs b/exercises/practice/protein-translation/tests/protein-translation.rs index a8ece5736..e73c1f9d0 100644 --- a/exercises/practice/protein-translation/tests/protein-translation.rs +++ b/exercises/practice/protein-translation/tests/protein-translation.rs @@ -1,220 +1,117 @@ use protein_translation::*; #[test] -fn methionine() { - let info = parse(make_pairs()); - assert_eq!(info.name_for("AUG"), Some("methionine")); -} - -#[test] -#[ignore] -fn cysteine_tgt() { - let info = parse(make_pairs()); - assert_eq!(info.name_for("UGU"), Some("cysteine")); -} - -#[test] -#[ignore] -fn stop() { - let info = parse(make_pairs()); - assert_eq!(info.name_for("UAA"), Some("stop codon")); -} - -#[test] -#[ignore] -fn valine() { - let info = parse(make_pairs()); - assert_eq!(info.name_for("GUU"), Some("valine")); -} - -#[test] -#[ignore] -fn isoleucine() { - let info = parse(make_pairs()); - assert_eq!(info.name_for("AUU"), Some("isoleucine")); -} - -#[test] -#[ignore] -fn arginine_cga() { - let info = parse(make_pairs()); - assert_eq!(info.name_for("CGA"), Some("arginine")); -} - -#[test] -#[ignore] -fn arginine_aga() { - let info = parse(make_pairs()); - assert_eq!(info.name_for("AGA"), Some("arginine")); -} - -#[test] -#[ignore] -fn arginine_agg() { - let info = parse(make_pairs()); - assert_eq!(info.name_for("AGG"), Some("arginine")); -} - -#[test] -#[ignore] -fn empty_is_invalid() { - let info = parse(make_pairs()); - assert_eq!(info.name_for(""), None); -} - -#[test] -#[ignore] -fn x_is_not_shorthand_so_is_invalid() { - let info = parse(make_pairs()); - assert_eq!(info.name_for("VWX"), None); -} - -#[test] -#[ignore] -fn too_short_is_invalid() { - let info = parse(make_pairs()); - assert_eq!(info.name_for("AU"), None); -} - -#[test] -#[ignore] -fn too_long_is_invalid() { - let info = parse(make_pairs()); - assert_eq!(info.name_for("ATTA"), None); -} - -#[test] -#[ignore] fn empty_rna_sequence_results_in_no_proteins() { - let info = parse(make_pairs()); - assert_eq!(info.of_rna(""), Some(vec![]),); + assert_eq!(translate(""), Some(vec![]),); } #[test] #[ignore] fn methionine_rna_sequence() { - let info = parse(make_pairs()); - assert_eq!(info.of_rna("AUG"), Some(vec!["methionine"]),); + assert_eq!(translate("AUG"), Some(vec!["methionine"]),); } #[test] #[ignore] fn phenylalanine_rna_sequence_1() { - let info = parse(make_pairs()); - assert_eq!(info.of_rna("UUU"), Some(vec!["phenylalanine"]),); + assert_eq!(translate("UUU"), Some(vec!["phenylalanine"]),); } #[test] #[ignore] fn phenylalanine_rna_sequence_2() { - let info = parse(make_pairs()); - assert_eq!(info.of_rna("UUC"), Some(vec!["phenylalanine"]),); + assert_eq!(translate("UUC"), Some(vec!["phenylalanine"]),); } #[test] #[ignore] fn leucine_rna_sequence_1() { - let info = parse(make_pairs()); - assert_eq!(info.of_rna("UUA"), Some(vec!["leucine"]),); + assert_eq!(translate("UUA"), Some(vec!["leucine"]),); } #[test] #[ignore] fn leucine_rna_sequence_2() { - let info = parse(make_pairs()); - assert_eq!(info.of_rna("UUG"), Some(vec!["leucine"]),); + assert_eq!(translate("UUG"), Some(vec!["leucine"]),); } #[test] #[ignore] fn serine_rna_sequence_1() { - let info = parse(make_pairs()); - assert_eq!(info.of_rna("UCU"), Some(vec!["serine"]),); + assert_eq!(translate("UCU"), Some(vec!["serine"]),); } #[test] #[ignore] fn serine_rna_sequence_2() { - let info = parse(make_pairs()); - assert_eq!(info.of_rna("UCC"), Some(vec!["serine"]),); + assert_eq!(translate("UCC"), Some(vec!["serine"]),); } #[test] #[ignore] fn serine_rna_sequence_3() { - let info = parse(make_pairs()); - assert_eq!(info.of_rna("UCA"), Some(vec!["serine"]),); + assert_eq!(translate("UCA"), Some(vec!["serine"]),); } #[test] #[ignore] fn serine_rna_sequence_4() { - let info = parse(make_pairs()); - assert_eq!(info.of_rna("UCG"), Some(vec!["serine"]),); + assert_eq!(translate("UCG"), Some(vec!["serine"]),); } #[test] #[ignore] fn tyrosine_rna_sequence_1() { - let info = parse(make_pairs()); - assert_eq!(info.of_rna("UAU"), Some(vec!["tyrosine"]),); + assert_eq!(translate("UAU"), Some(vec!["tyrosine"]),); } #[test] #[ignore] fn tyrosine_rna_sequence_2() { - let info = parse(make_pairs()); - assert_eq!(info.of_rna("UAC"), Some(vec!["tyrosine"]),); + assert_eq!(translate("UAC"), Some(vec!["tyrosine"]),); } #[test] #[ignore] fn cysteine_rna_sequence_1() { - let info = parse(make_pairs()); - assert_eq!(info.of_rna("UGU"), Some(vec!["cysteine"]),); + assert_eq!(translate("UGU"), Some(vec!["cysteine"]),); } #[test] #[ignore] fn cysteine_rna_sequence_2() { - let info = parse(make_pairs()); - assert_eq!(info.of_rna("UGC"), Some(vec!["cysteine"]),); + assert_eq!(translate("UGC"), Some(vec!["cysteine"]),); } #[test] #[ignore] fn tryptophan_rna_sequence() { - let info = parse(make_pairs()); - assert_eq!(info.of_rna("UGG"), Some(vec!["tryptophan"]),); + assert_eq!(translate("UGG"), Some(vec!["tryptophan"]),); } #[test] #[ignore] fn stop_codon_rna_sequence_1() { - let info = parse(make_pairs()); - assert_eq!(info.of_rna("UAA"), Some(vec![]),); + assert_eq!(translate("UAA"), Some(vec![]),); } #[test] #[ignore] fn stop_codon_rna_sequence_2() { - let info = parse(make_pairs()); - assert_eq!(info.of_rna("UAG"), Some(vec![]),); + assert_eq!(translate("UAG"), Some(vec![]),); } #[test] #[ignore] fn stop_codon_rna_sequence_3() { - let info = parse(make_pairs()); - assert_eq!(info.of_rna("UGA"), Some(vec![]),); + assert_eq!(translate("UGA"), Some(vec![]),); } #[test] #[ignore] fn sequence_of_two_protein_codons_translates_into_proteins() { - let info = parse(make_pairs()); assert_eq!( - info.of_rna("UUUUUU"), + translate("UUUUUU"), Some(vec!["phenylalanine", "phenylalanine"]), ); } @@ -222,16 +119,14 @@ fn sequence_of_two_protein_codons_translates_into_proteins() { #[test] #[ignore] fn sequence_of_two_different_protein_codons_translates_into_proteins() { - let info = parse(make_pairs()); - assert_eq!(info.of_rna("UUAUUG"), Some(vec!["leucine", "leucine"]),); + assert_eq!(translate("UUAUUG"), Some(vec!["leucine", "leucine"]),); } #[test] #[ignore] fn translate_rna_strand_into_correct_protein_list() { - let info = parse(make_pairs()); assert_eq!( - info.of_rna("AUGUUUUGG"), + translate("AUGUUUUGG"), Some(vec!["methionine", "phenylalanine", "tryptophan"]), ); } @@ -239,23 +134,20 @@ fn translate_rna_strand_into_correct_protein_list() { #[test] #[ignore] fn translation_stops_if_stop_codon_at_beginning_of_sequence() { - let info = parse(make_pairs()); - assert_eq!(info.of_rna("UAGUGG"), Some(vec![]),); + assert_eq!(translate("UAGUGG"), Some(vec![]),); } #[test] #[ignore] fn translation_stops_if_stop_codon_at_end_of_two_codon_sequence() { - let info = parse(make_pairs()); - assert_eq!(info.of_rna("UGGUAG"), Some(vec!["tryptophan"]),); + assert_eq!(translate("UGGUAG"), Some(vec!["tryptophan"]),); } #[test] #[ignore] fn translation_stops_if_stop_codon_at_end_of_three_codon_sequence() { - let info = parse(make_pairs()); assert_eq!( - info.of_rna("AUGUUUUAA"), + translate("AUGUUUUAA"), Some(vec!["methionine", "phenylalanine"]), ); } @@ -263,75 +155,41 @@ fn translation_stops_if_stop_codon_at_end_of_three_codon_sequence() { #[test] #[ignore] fn translation_stops_if_stop_codon_in_middle_of_three_codon_sequence() { - let info = parse(make_pairs()); - assert_eq!(info.of_rna("UGGUAGUGG"), Some(vec!["tryptophan"]),); + assert_eq!(translate("UGGUAGUGG"), Some(vec!["tryptophan"]),); } #[test] #[ignore] fn translation_stops_if_stop_codon_in_middle_of_six_codon_sequence() { - let info = parse(make_pairs()); assert_eq!( - info.of_rna("UGGUGUUAUUAAUGGUUU"), + translate("UGGUGUUAUUAAUGGUUU"), Some(vec!["tryptophan", "cysteine", "tyrosine"]), ); } +#[test] +#[ignore] +fn sequence_of_two_non_stop_codons_does_not_translate_to_a_stop_codon() { + assert_eq!(translate("AUGAUG"), Some(vec!["methionine", "methionine"]),); +} + #[test] #[ignore] fn unknown_amino_acids_not_part_of_a_codon_can_t_translate() { - let info = parse(make_pairs()); - assert_eq!(info.of_rna("XYZ"), None,); + assert_eq!(translate("XYZ"), None,); } #[test] #[ignore] fn incomplete_rna_sequence_can_t_translate() { - let info = parse(make_pairs()); - assert_eq!(info.of_rna("AUGU"), None,); + assert_eq!(translate("AUGU"), None,); } #[test] #[ignore] fn incomplete_rna_sequence_can_translate_if_valid_until_a_stop_codon() { - let info = parse(make_pairs()); assert_eq!( - info.of_rna("UUCUUCUAAUGGU"), + translate("UUCUUCUAAUGGU"), Some(vec!["phenylalanine", "phenylalanine"]), ); } - -// The input data constructor. Returns a list of codon, name pairs. -fn make_pairs() -> Vec<(&'static str, &'static str)> { - let grouped = vec![ - ("isoleucine", vec!["AUU", "AUC", "AUA"]), - ("valine", vec!["GUU", "GUC", "GUA", "GUG"]), - ("phenylalanine", vec!["UUU", "UUC"]), - ("methionine", vec!["AUG"]), - ("cysteine", vec!["UGU", "UGC"]), - ("alanine", vec!["GCU", "GCC", "GCA", "GCG"]), - ("glycine", vec!["GGU", "GGC", "GGA", "GGG"]), - ("proline", vec!["CCU", "CCC", "CCA", "CCG"]), - ("threonine", vec!["ACU", "ACC", "ACA", "ACG"]), - ("serine", vec!["UCU", "UCC", "UCA", "UCG"]), - ("tyrosine", vec!["UAU", "UAC"]), - ("tryptophan", vec!["UGG"]), - ("glutamine", vec!["CAA", "CAG"]), - ("asparagine", vec!["AAU", "AAC"]), - ("histidine", vec!["CAU", "CAC"]), - ("glutamic acid", vec!["GAA", "GAG"]), - ("aspartic acid", vec!["GAU", "GAC"]), - ("lysine", vec!["AAA", "AAG"]), - ("arginine", vec!["CGU", "CGC", "CGA", "CGG", "AGA", "AGG"]), - ("leucine", vec!["UUA", "UUG"]), - ("stop codon", vec!["UAA", "UAG", "UGA"]), - ]; - let mut pairs = Vec::<(&'static str, &'static str)>::new(); - for (name, codons) in grouped.into_iter() { - for codon in codons { - pairs.push((codon, name)); - } - } - pairs.sort_by(|&(_, a), &(_, b)| a.cmp(b)); - pairs -} diff --git a/problem-specifications b/problem-specifications index 238b8b090..6c44c8323 160000 --- a/problem-specifications +++ b/problem-specifications @@ -1 +1 @@ -Subproject commit 238b8b090c331273eaccd5daba06df6f62863342 +Subproject commit 6c44c83239e5d8dc6a7d36e800ece2548d106a65 From 1861d736c5896151e859872dd381edd677589b61 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Wed, 10 Jul 2024 15:16:24 +0200 Subject: [PATCH 310/436] isogram: sync (#1941) --- .../practice/isogram/.meta/test_template.tera | 13 +++ exercises/practice/isogram/.meta/tests.toml | 43 +++++++++- exercises/practice/isogram/tests/isogram.rs | 79 +++++++++++-------- 3 files changed, 97 insertions(+), 38 deletions(-) create mode 100644 exercises/practice/isogram/.meta/test_template.tera diff --git a/exercises/practice/isogram/.meta/test_template.tera b/exercises/practice/isogram/.meta/test_template.tera new file mode 100644 index 000000000..51bd452ba --- /dev/null +++ b/exercises/practice/isogram/.meta/test_template.tera @@ -0,0 +1,13 @@ +use isogram::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | snake_case }}() { + {% if test.expected %} + assert!(check({{ test.input.phrase | json_encode() }})); + {% else %} + assert!(!check({{ test.input.phrase | json_encode() }})); + {% endif %} +} +{% endfor -%} diff --git a/exercises/practice/isogram/.meta/tests.toml b/exercises/practice/isogram/.meta/tests.toml index cf1f7ea7d..ba04c6645 100644 --- a/exercises/practice/isogram/.meta/tests.toml +++ b/exercises/practice/isogram/.meta/tests.toml @@ -1,15 +1,52 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [a0e97d2d-669e-47c7-8134-518a1e2c4555] description = "empty string" +[9a001b50-f194-4143-bc29-2af5ec1ef652] +description = "isogram with only lower case characters" + +[8ddb0ca3-276e-4f8b-89da-d95d5bae78a4] +description = "word with one duplicated character" + +[6450b333-cbc2-4b24-a723-0b459b34fe18] +description = "word with one duplicated character from the end of the alphabet" + [a15ff557-dd04-4764-99e7-02cc1a385863] description = "longest reported english isogram" +[f1a7f6c7-a42f-4915-91d7-35b2ea11c92e] +description = "word with duplicated character in mixed case" + +[14a4f3c1-3b47-4695-b645-53d328298942] +description = "word with duplicated character in mixed case, lowercase first" + +[423b850c-7090-4a8a-b057-97f1cadd7c42] +description = "hypothetical isogrammic word with hyphen" + +[93dbeaa0-3c5a-45c2-8b25-428b8eacd4f2] +description = "hypothetical word with duplicated character following hyphen" + [36b30e5c-173f-49c6-a515-93a3e825553f] description = "isogram with duplicated hyphen" +[cdabafa0-c9f4-4c1f-b142-689c6ee17d93] +description = "made-up name that is an isogram" + [5fc61048-d74e-48fd-bc34-abfc21552d4d] description = "duplicated character in the middle" + +[310ac53d-8932-47bc-bbb4-b2b94f25a83e] +description = "same first and last characters" + +[0d0b8644-0a1e-4a31-a432-2b3ee270d847] +description = "word with duplicated character and with two hyphens" diff --git a/exercises/practice/isogram/tests/isogram.rs b/exercises/practice/isogram/tests/isogram.rs index b0c6a2d21..29ee0e0b3 100644 --- a/exercises/practice/isogram/tests/isogram.rs +++ b/exercises/practice/isogram/tests/isogram.rs @@ -1,75 +1,84 @@ -use isogram::check; +use isogram::*; #[test] fn empty_string() { - assert!(check(""), "An empty string should be an isogram.") + assert!(check("")); } #[test] #[ignore] -fn only_lower_case_characters() { - assert!(check("isogram"), "\"isogram\" should be an isogram.") +fn isogram_with_only_lower_case_characters() { + assert!(check("isogram")); } #[test] #[ignore] -fn one_duplicated_character() { - assert!( - !check("eleven"), - "\"eleven\" has more than one \'e\', therefore it is no isogram." - ) +fn word_with_one_duplicated_character() { + assert!(!check("eleven")); +} + +#[test] +#[ignore] +fn word_with_one_duplicated_character_from_the_end_of_the_alphabet() { + assert!(!check("zzyzx")); } #[test] #[ignore] fn longest_reported_english_isogram() { - assert!( - check("subdermatoglyphic"), - "\"subdermatoglyphic\" should be an isogram." - ) + assert!(check("subdermatoglyphic")); +} + +#[test] +#[ignore] +fn word_with_duplicated_character_in_mixed_case() { + assert!(!check("Alphabet")); +} + +#[test] +#[ignore] +fn word_with_duplicated_character_in_mixed_case_lowercase_first() { + assert!(!check("alphAbet")); } #[test] #[ignore] -fn one_duplicated_character_mixed_case() { - assert!( - !check("Alphabet"), - "\"Alphabet\" has more than one \'a\', therefore it is no isogram." - ) +fn hypothetical_isogrammic_word_with_hyphen() { + assert!(check("thumbscrew-japingly")); } #[test] #[ignore] -fn hypothetical_isogramic_word_with_hyphen() { - assert!( - check("thumbscrew-japingly"), - "\"thumbscrew-japingly\" should be an isogram." - ) +fn hypothetical_word_with_duplicated_character_following_hyphen() { + assert!(!check("thumbscrew-jappingly")); } #[test] #[ignore] fn isogram_with_duplicated_hyphen() { - assert!( - check("six-year-old"), - "\"six-year-old\" should be an isogram." - ) + assert!(check("six-year-old")); } #[test] #[ignore] fn made_up_name_that_is_an_isogram() { - assert!( - check("Emily Jung Schwartzkopf"), - "\"Emily Jung Schwartzkopf\" should be an isogram." - ) + assert!(check("Emily Jung Schwartzkopf")); } #[test] #[ignore] fn duplicated_character_in_the_middle() { - assert!( - !check("accentor"), - "\"accentor\" has more than one \'c\', therefore it is no isogram." - ) + assert!(!check("accentor")); +} + +#[test] +#[ignore] +fn same_first_and_last_characters() { + assert!(!check("angola")); +} + +#[test] +#[ignore] +fn word_with_duplicated_character_and_with_two_hyphens() { + assert!(!check("up-to-date")); } From a4c1bb026711eabd35e4244c739410827ac54f9e Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Wed, 10 Jul 2024 20:33:26 +0200 Subject: [PATCH 311/436] grep: remove suggestion of unavailable crates (#1942) clap and structopt are not available in the test runner. This is because their derive-API (which is the most popular) takes too long to compile. --- exercises/practice/grep/src/lib.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/exercises/practice/grep/src/lib.rs b/exercises/practice/grep/src/lib.rs index 9ef2c9d22..f64031266 100644 --- a/exercises/practice/grep/src/lib.rs +++ b/exercises/practice/grep/src/lib.rs @@ -5,13 +5,7 @@ use anyhow::Error; /// both more convenient and more idiomatic to contain runtime configuration in /// a dedicated struct. Therefore, we suggest that you do so in this exercise. /// -/// In the real world, it's common to use crates such as [`clap`] or -/// [`structopt`] to handle argument parsing, and of course doing so is -/// permitted in this exercise as well, though it may be somewhat overkill. -/// -/// [`clap`]: https://crates.io/crates/clap /// [`std::env::args`]: https://doc.rust-lang.org/std/env/fn.args.html -/// [`structopt`]: https://crates.io/crates/structopt #[derive(Debug)] pub struct Flags; From 8df80016ba30829c3e25c0bd89993eae984e86ae Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 23 Jul 2024 08:23:19 +0200 Subject: [PATCH 312/436] protein-translation: keep names uppercase (#1946) This is to stay consistent with canonical-data and the instructions. Users can more easily copy-paste the names from the instructions. --- .../protein-translation/.meta/example.rs | 14 +++--- .../.meta/test_template.tera | 2 +- .../tests/protein-translation.rs | 46 +++++++++---------- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/exercises/practice/protein-translation/.meta/example.rs b/exercises/practice/protein-translation/.meta/example.rs index 49d857b84..c64ef5ff4 100644 --- a/exercises/practice/protein-translation/.meta/example.rs +++ b/exercises/practice/protein-translation/.meta/example.rs @@ -2,13 +2,13 @@ pub fn translate(rna: &str) -> Option> { rna.as_bytes() .chunks(3) .map(|codon| match codon { - b"AUG" => Some("methionine"), - b"UUU" | b"UUC" => Some("phenylalanine"), - b"UUA" | b"UUG" => Some("leucine"), - b"UCU" | b"UCC" | b"UCA" | b"UCG" => Some("serine"), - b"UAU" | b"UAC" => Some("tyrosine"), - b"UGU" | b"UGC" => Some("cysteine"), - b"UGG" => Some("tryptophan"), + b"AUG" => Some("Methionine"), + b"UUU" | b"UUC" => Some("Phenylalanine"), + b"UUA" | b"UUG" => Some("Leucine"), + b"UCU" | b"UCC" | b"UCA" | b"UCG" => Some("Serine"), + b"UAU" | b"UAC" => Some("Tyrosine"), + b"UGU" | b"UGC" => Some("Cysteine"), + b"UGG" => Some("Tryptophan"), b"UAA" | b"UAG" | b"UGA" => Some("STOP"), _ => None, }) diff --git a/exercises/practice/protein-translation/.meta/test_template.tera b/exercises/practice/protein-translation/.meta/test_template.tera index 6bcff9bb9..27d1b694d 100644 --- a/exercises/practice/protein-translation/.meta/test_template.tera +++ b/exercises/practice/protein-translation/.meta/test_template.tera @@ -11,7 +11,7 @@ fn {{ test.description | snake_case }}() { {% else %} Some(vec![ {% for s in test.expected %} - "{{ s | lower }}" {% if not loop.last %} , {% endif %} + "{{ s }}" {% if not loop.last %} , {% endif %} {% endfor %} ]) {% endif %}, diff --git a/exercises/practice/protein-translation/tests/protein-translation.rs b/exercises/practice/protein-translation/tests/protein-translation.rs index e73c1f9d0..7ac612ba1 100644 --- a/exercises/practice/protein-translation/tests/protein-translation.rs +++ b/exercises/practice/protein-translation/tests/protein-translation.rs @@ -8,85 +8,85 @@ fn empty_rna_sequence_results_in_no_proteins() { #[test] #[ignore] fn methionine_rna_sequence() { - assert_eq!(translate("AUG"), Some(vec!["methionine"]),); + assert_eq!(translate("AUG"), Some(vec!["Methionine"]),); } #[test] #[ignore] fn phenylalanine_rna_sequence_1() { - assert_eq!(translate("UUU"), Some(vec!["phenylalanine"]),); + assert_eq!(translate("UUU"), Some(vec!["Phenylalanine"]),); } #[test] #[ignore] fn phenylalanine_rna_sequence_2() { - assert_eq!(translate("UUC"), Some(vec!["phenylalanine"]),); + assert_eq!(translate("UUC"), Some(vec!["Phenylalanine"]),); } #[test] #[ignore] fn leucine_rna_sequence_1() { - assert_eq!(translate("UUA"), Some(vec!["leucine"]),); + assert_eq!(translate("UUA"), Some(vec!["Leucine"]),); } #[test] #[ignore] fn leucine_rna_sequence_2() { - assert_eq!(translate("UUG"), Some(vec!["leucine"]),); + assert_eq!(translate("UUG"), Some(vec!["Leucine"]),); } #[test] #[ignore] fn serine_rna_sequence_1() { - assert_eq!(translate("UCU"), Some(vec!["serine"]),); + assert_eq!(translate("UCU"), Some(vec!["Serine"]),); } #[test] #[ignore] fn serine_rna_sequence_2() { - assert_eq!(translate("UCC"), Some(vec!["serine"]),); + assert_eq!(translate("UCC"), Some(vec!["Serine"]),); } #[test] #[ignore] fn serine_rna_sequence_3() { - assert_eq!(translate("UCA"), Some(vec!["serine"]),); + assert_eq!(translate("UCA"), Some(vec!["Serine"]),); } #[test] #[ignore] fn serine_rna_sequence_4() { - assert_eq!(translate("UCG"), Some(vec!["serine"]),); + assert_eq!(translate("UCG"), Some(vec!["Serine"]),); } #[test] #[ignore] fn tyrosine_rna_sequence_1() { - assert_eq!(translate("UAU"), Some(vec!["tyrosine"]),); + assert_eq!(translate("UAU"), Some(vec!["Tyrosine"]),); } #[test] #[ignore] fn tyrosine_rna_sequence_2() { - assert_eq!(translate("UAC"), Some(vec!["tyrosine"]),); + assert_eq!(translate("UAC"), Some(vec!["Tyrosine"]),); } #[test] #[ignore] fn cysteine_rna_sequence_1() { - assert_eq!(translate("UGU"), Some(vec!["cysteine"]),); + assert_eq!(translate("UGU"), Some(vec!["Cysteine"]),); } #[test] #[ignore] fn cysteine_rna_sequence_2() { - assert_eq!(translate("UGC"), Some(vec!["cysteine"]),); + assert_eq!(translate("UGC"), Some(vec!["Cysteine"]),); } #[test] #[ignore] fn tryptophan_rna_sequence() { - assert_eq!(translate("UGG"), Some(vec!["tryptophan"]),); + assert_eq!(translate("UGG"), Some(vec!["Tryptophan"]),); } #[test] @@ -112,14 +112,14 @@ fn stop_codon_rna_sequence_3() { fn sequence_of_two_protein_codons_translates_into_proteins() { assert_eq!( translate("UUUUUU"), - Some(vec!["phenylalanine", "phenylalanine"]), + Some(vec!["Phenylalanine", "Phenylalanine"]), ); } #[test] #[ignore] fn sequence_of_two_different_protein_codons_translates_into_proteins() { - assert_eq!(translate("UUAUUG"), Some(vec!["leucine", "leucine"]),); + assert_eq!(translate("UUAUUG"), Some(vec!["Leucine", "Leucine"]),); } #[test] @@ -127,7 +127,7 @@ fn sequence_of_two_different_protein_codons_translates_into_proteins() { fn translate_rna_strand_into_correct_protein_list() { assert_eq!( translate("AUGUUUUGG"), - Some(vec!["methionine", "phenylalanine", "tryptophan"]), + Some(vec!["Methionine", "Phenylalanine", "Tryptophan"]), ); } @@ -140,7 +140,7 @@ fn translation_stops_if_stop_codon_at_beginning_of_sequence() { #[test] #[ignore] fn translation_stops_if_stop_codon_at_end_of_two_codon_sequence() { - assert_eq!(translate("UGGUAG"), Some(vec!["tryptophan"]),); + assert_eq!(translate("UGGUAG"), Some(vec!["Tryptophan"]),); } #[test] @@ -148,14 +148,14 @@ fn translation_stops_if_stop_codon_at_end_of_two_codon_sequence() { fn translation_stops_if_stop_codon_at_end_of_three_codon_sequence() { assert_eq!( translate("AUGUUUUAA"), - Some(vec!["methionine", "phenylalanine"]), + Some(vec!["Methionine", "Phenylalanine"]), ); } #[test] #[ignore] fn translation_stops_if_stop_codon_in_middle_of_three_codon_sequence() { - assert_eq!(translate("UGGUAGUGG"), Some(vec!["tryptophan"]),); + assert_eq!(translate("UGGUAGUGG"), Some(vec!["Tryptophan"]),); } #[test] @@ -163,14 +163,14 @@ fn translation_stops_if_stop_codon_in_middle_of_three_codon_sequence() { fn translation_stops_if_stop_codon_in_middle_of_six_codon_sequence() { assert_eq!( translate("UGGUGUUAUUAAUGGUUU"), - Some(vec!["tryptophan", "cysteine", "tyrosine"]), + Some(vec!["Tryptophan", "Cysteine", "Tyrosine"]), ); } #[test] #[ignore] fn sequence_of_two_non_stop_codons_does_not_translate_to_a_stop_codon() { - assert_eq!(translate("AUGAUG"), Some(vec!["methionine", "methionine"]),); + assert_eq!(translate("AUGAUG"), Some(vec!["Methionine", "Methionine"]),); } #[test] @@ -190,6 +190,6 @@ fn incomplete_rna_sequence_can_t_translate() { fn incomplete_rna_sequence_can_translate_if_valid_until_a_stop_codon() { assert_eq!( translate("UUCUUCUAAUGGU"), - Some(vec!["phenylalanine", "phenylalanine"]), + Some(vec!["Phenylalanine", "Phenylalanine"]), ); } From 289164d1880be83a413ec8344e8d3b6296e1bd92 Mon Sep 17 00:00:00 2001 From: Ethan Bradley Date: Fri, 26 Jul 2024 02:29:34 -0400 Subject: [PATCH 313/436] Add error handling tests to PaaS I/O (#1947) Adds two tests to the PaaS I/O exercise to check that I/O errors are propagated rather than unwrapped/expected. [Fixes #992] --- exercises/practice/paasio/tests/paasio.rs | 66 +++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/exercises/practice/paasio/tests/paasio.rs b/exercises/practice/paasio/tests/paasio.rs index 9079691cb..e5c6f5eac 100644 --- a/exercises/practice/paasio/tests/paasio.rs +++ b/exercises/practice/paasio/tests/paasio.rs @@ -1,3 +1,5 @@ +use std::io::{Error, ErrorKind, Read, Result, Write}; + /// test a few read scenarios macro_rules! test_read { ($(#[$attr:meta])* $modname:ident ($input:expr, $len:expr)) => { @@ -207,3 +209,67 @@ fn read_stats_by_ref_returns_wrapped_reader() { let reader = ReadStats::new(input); assert_eq!(reader.get_ref(), &input); } + +/// a Read type that always errors +struct ReadFails; + +impl ReadFails { + const MESSAGE: &'static str = "this reader always fails"; +} + +impl Read for ReadFails { + fn read(&mut self, _buf: &mut [u8]) -> Result { + Err(Error::other(Self::MESSAGE)) + } +} + +/// a Write type that always errors +struct WriteFails; + +impl WriteFails { + const MESSAGE: &'static str = "this writer always fails"; +} + +impl Write for WriteFails { + fn write(&mut self, _buf: &[u8]) -> Result { + Err(Error::other(Self::MESSAGE)) + } + + fn flush(&mut self) -> Result<()> { + Err(Error::other(Self::MESSAGE)) + } +} + +#[test] +#[ignore] +fn read_propagates_errors() { + use paasio::ReadStats; + + let mut reader = ReadStats::new(ReadFails); + let mut buffer = Vec::new(); + + let Err(e) = reader.read(&mut buffer) else { + panic!("read error not propagated") + }; + + // check that the correct error was returned + assert_eq!(e.kind(), ErrorKind::Other); + assert_eq!(e.get_ref().unwrap().to_string(), ReadFails::MESSAGE); +} + +#[test] +#[ignore] +fn write_propagates_errors() { + use paasio::WriteStats; + + let mut writer = WriteStats::new(WriteFails); + let buffer = "This text won't be written"; + + let Err(e) = writer.write(buffer.as_bytes()) else { + panic!("write error not propagated") + }; + + // check that the correct error is returned + assert_eq!(e.kind(), ErrorKind::Other); + assert_eq!(e.get_ref().unwrap().to_string(), WriteFails::MESSAGE); +} From 178476c520d88932f10ad08fe256b61309989c28 Mon Sep 17 00:00:00 2001 From: Florent Linguenheld Date: Thu, 8 Aug 2024 17:41:11 +0200 Subject: [PATCH 314/436] Update book links (#1948) --- exercises/practice/triangle/.docs/instructions.append.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/exercises/practice/triangle/.docs/instructions.append.md b/exercises/practice/triangle/.docs/instructions.append.md index d38f8268d..a5eb5f15e 100644 --- a/exercises/practice/triangle/.docs/instructions.append.md +++ b/exercises/practice/triangle/.docs/instructions.append.md @@ -4,8 +4,8 @@ Implementation of this can take many forms. Here are some topics that may help you, depending on the approach you take. -- [Enums](https://doc.rust-lang.org/book/2018-edition/ch06-00-enums.html) -- [Traits](https://doc.rust-lang.org/book/2018-edition/ch10-02-traits.html) +- [Enums](https://doc.rust-lang.org/book/ch06-00-enums.html) +- [Traits](https://doc.rust-lang.org/book/ch10-02-traits.html) - [BTreeSet](https://doc.rust-lang.org/std/collections/btree_set/struct.BTreeSet.html) Or maybe you will come up with an approach that uses none of those! @@ -17,7 +17,7 @@ integers. However, some triangles cannot be represented by pure integers. A simp It would be tedious to rewrite the analysis functions to handle both integer and floating-point cases, and particularly tedious to do so for all potential integer and floating point types: given signed and unsigned variants of bitwidths 8, 16, 32, 64, and 128, that would be 10 reimplementations of fundamentally the same code even before considering floats! -There's a better way: [generics](https://doc.rust-lang.org/stable/book/2018-edition/ch10-00-generics.html). By rewriting your Triangle as a `Triangle`, you can write your code once, and hand off the work of generating all those specializations to the compiler. Note that in order to use mathematical operations, you'll need to constrain your generic type to types which support those operations using traits. +There's a better way: [generics](https://doc.rust-lang.org/stable/book/ch10-00-generics.html). By rewriting your Triangle as a `Triangle`, you can write your code once, and hand off the work of generating all those specializations to the compiler. Note that in order to use mathematical operations, you'll need to constrain your generic type to types which support those operations using traits. There are some bonus tests you can run which test your implementation on floating-point numbers. To enable them, run your tests with the `generic` feature flag, like this: From 8c408f05bba738e45bc432470eb0b1352eb21d69 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Fri, 9 Aug 2024 20:38:50 +0200 Subject: [PATCH 315/436] Cleanup new_without_default workarounds (#1949) --- bin/ensure_stubs_compile.sh | 2 +- exercises/practice/grade-school/src/lib.rs | 7 ------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/bin/ensure_stubs_compile.sh b/bin/ensure_stubs_compile.sh index d0d743a81..08c700265 100755 --- a/bin/ensure_stubs_compile.sh +++ b/bin/ensure_stubs_compile.sh @@ -61,7 +61,7 @@ for dir in $changed_exercises; do # since we're generally not going to test it. if ! ( cd "$dir" && - cargo clippy --lib --tests --color always -- --allow clippy::new_without_default 2>clippy.log + cargo clippy --lib --tests --color always -- 2>clippy.log ); then cat "$dir/clippy.log" broken="$broken\n$exercise" diff --git a/exercises/practice/grade-school/src/lib.rs b/exercises/practice/grade-school/src/lib.rs index f90a271c1..da3f53603 100644 --- a/exercises/practice/grade-school/src/lib.rs +++ b/exercises/practice/grade-school/src/lib.rs @@ -1,10 +1,3 @@ -// This annotation prevents Clippy from warning us that `School` has a -// `fn new()` with no arguments, but doesn't implement the `Default` trait. -// -// Normally, it's good practice to just do what Clippy tells you, but in this -// case, we want to keep things relatively simple. The `Default` trait is not the point -// of this exercise. -#[allow(clippy::new_without_default)] pub struct School {} impl School { From 2a66d7d4d7b1b8fc1e4b255357175a37af85780e Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Fri, 9 Aug 2024 20:39:15 +0200 Subject: [PATCH 316/436] Pin problem-specifications to a specific commit (#1950) The previous approach used a submodule to keep things somewhat stable. That worked well for the rust-tooling (exercise generator) which made use of the submodule. However, configlet continued to use its own cache. (see https://github.com/exercism/configlet/issues/816) This could lead to problems where the configlet cache and the submodule are out of sync and don't agree. The new approach ditches the submodule and makes everything use the configlet cache. Some helper scripts are responsible to make sure the cache is checked out at the pinned commit and a configlet wrapper sets the `--offile` flag to prevent configlet from updating the cache. --- .gitignore | 1 + .gitmodules | 3 -- ...ut_pinned_problem_specifications_commit.sh | 17 +++++++++++ bin/configlet_wrapper.sh | 29 +++++++++++++++++++ bin/get_problem_specifications_dir.sh | 13 +++++++++ bin/symlink_problem_specifications.sh | 4 +++ bin/update_problem_specifications.sh | 15 ++++++++++ justfile | 13 +++++---- problem-specifications | 1 - .../ci-tests/tests/bash_script_conventions.rs | 2 +- 10 files changed, 88 insertions(+), 10 deletions(-) create mode 100755 bin/checkout_pinned_problem_specifications_commit.sh create mode 100755 bin/configlet_wrapper.sh create mode 100755 bin/get_problem_specifications_dir.sh create mode 100755 bin/update_problem_specifications.sh delete mode 160000 problem-specifications diff --git a/.gitignore b/.gitignore index 7f0587770..3eea4c1c0 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ exercises/*/*/Cargo.lock clippy.log .vscode .prob-spec +problem-specifications diff --git a/.gitmodules b/.gitmodules index bf863ee5b..e69de29bb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "problem-specifications"] - path = problem-specifications - url = git@github.com:exercism/problem-specifications diff --git a/bin/checkout_pinned_problem_specifications_commit.sh b/bin/checkout_pinned_problem_specifications_commit.sh new file mode 100755 index 000000000..7f29be359 --- /dev/null +++ b/bin/checkout_pinned_problem_specifications_commit.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(git rev-parse --show-toplevel)" + +PINNED_COMMIT_HASH="685ec55d9388937bbb3cc836b52b3ce27f208f37" + +dir="$(./bin/get_problem_specifications_dir.sh)" + +[ -d "$dir" ] || ./bin/configlet info &> /dev/null # initial population of cache + +if ! git -C "$dir" checkout --quiet --detach "$PINNED_COMMIT_HASH" &> /dev/null +then + # maybe the pinned commit hash was updated and the cache has to be refreshed + ./bin/configlet info &> /dev/null + git -C "$dir" checkout --quiet --detach "$PINNED_COMMIT_HASH" +fi diff --git a/bin/configlet_wrapper.sh b/bin/configlet_wrapper.sh new file mode 100755 index 000000000..ef58d28b9 --- /dev/null +++ b/bin/configlet_wrapper.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +set -eo pipefail + +# This wrapper makes sure the problem-specifications repository is checked out +# at the pinned commit and the --offline flag is set for the relevant commands. + +cd "$(git rev-parse --show-toplevel)" + +[ -f ./bin/configlet ] || ./bin/fetch-configlet + +if [ "$#" == 0 ] +then + ./bin/configlet + exit +fi + +cmd="$1" ; shift + +if ! [ "$cmd" == "create" ] && ! [ "$cmd" == "sync" ] && ! [ "$cmd" == "info" ] +then + # problem-specifications independent commands + ./bin/configlet "$cmd" "$@" + exit +fi + +./bin/checkout_pinned_problem_specifications_commit.sh + +set -x # show the added --offile flag +./bin/configlet "$cmd" --offline "$@" diff --git a/bin/get_problem_specifications_dir.sh b/bin/get_problem_specifications_dir.sh new file mode 100755 index 000000000..2f0ea1aea --- /dev/null +++ b/bin/get_problem_specifications_dir.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ "$OSTYPE" == "linux-gnu"* ]]; then + prefix="${XDG_CACHE_HOME:-$HOME/.cache}" +elif [[ "$OSTYPE" == "darwin"* ]]; then + prefix="${XDG_CACHE_HOME:-$HOME/Library/Caches}" +else + echo "Unsupported OS: $OSTYPE" >&2 + exit 1 +fi + +echo -n "$prefix/exercism/configlet/problem-specifications" diff --git a/bin/symlink_problem_specifications.sh b/bin/symlink_problem_specifications.sh index bf86ebaa9..119579e03 100755 --- a/bin/symlink_problem_specifications.sh +++ b/bin/symlink_problem_specifications.sh @@ -3,6 +3,10 @@ set -eo pipefail cd "$(git rev-parse --show-toplevel)" +[ -e "problem-specifications" ] || ln -s "$(./bin/get_problem_specifications_dir.sh)" "problem-specifications" + +./bin/checkout_pinned_problem_specifications_commit.sh + for exercise in exercises/practice/*; do name="$(basename "$exercise")" if [ -d "problem-specifications/exercises/$name" ]; then diff --git a/bin/update_problem_specifications.sh b/bin/update_problem_specifications.sh new file mode 100755 index 000000000..bb32bd47f --- /dev/null +++ b/bin/update_problem_specifications.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(git rev-parse --show-toplevel)" + +./bin/checkout_pinned_problem_specifications_commit.sh + +dir="$(./bin/get_problem_specifications_dir.sh)" + +git -C "$dir" checkout --quiet main +git -C "$dir" pull --quiet + +new_commit_hash="$(git -C "$dir" rev-parse main)" + +sed -i "s/^PINNED_COMMIT_HASH=.*$/PINNED_COMMIT_HASH=\"$new_commit_hash\"/g" ./bin/checkout_pinned_problem_specifications_commit.sh diff --git a/justfile b/justfile index ee2936d19..8151c2420 100644 --- a/justfile +++ b/justfile @@ -1,14 +1,17 @@ _default: just --list --unsorted -# configlet wrapper, uses problem-specifications submodule -configlet *args="": - @[ -f bin/configlet ] || bin/fetch-configlet - ./bin/configlet {{ args }} +# configlet wrapper, uses pinned problem-specifications commit +@configlet *args="": + ./bin/configlet_wrapper.sh {{ args }} + +# update the pinned commit hash +update-problem-specs: + ./bin/update_problem_specifications.sh # generate a new uuid straight to your clipboard uuid: - ./bin/configlet uuid | tr -d '[:space:]' | wl-copy + just configlet uuid | tr -d '[:space:]' | wl-copy # simulate CI locally (WIP) test: diff --git a/problem-specifications b/problem-specifications deleted file mode 160000 index 6c44c8323..000000000 --- a/problem-specifications +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6c44c83239e5d8dc6a7d36e800ece2548d106a65 diff --git a/rust-tooling/ci-tests/tests/bash_script_conventions.rs b/rust-tooling/ci-tests/tests/bash_script_conventions.rs index 95f0792ce..907c2077e 100644 --- a/rust-tooling/ci-tests/tests/bash_script_conventions.rs +++ b/rust-tooling/ci-tests/tests/bash_script_conventions.rs @@ -61,7 +61,7 @@ fn error_handling_flags() { for_all_scripts(|file_name| { let contents = std::fs::read_to_string(PathBuf::from("bin").join(file_name)).unwrap(); assert!( - contents.contains("set -eo pipefail"), + contents.contains("set -euo pipefail") || contents.contains("set -eo pipefail"), "'{file_name}' should set error handling flags 'set -eo pipefail'" ); }) From 806b9c157e0f0d6b2eac937b09fd3714d2a79b6a Mon Sep 17 00:00:00 2001 From: keiravillekode Date: Mon, 12 Aug 2024 15:37:29 +1000 Subject: [PATCH 317/436] Eliuds Eggs => Eliud's Eggs (#1957) --- config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.json b/config.json index 376c21e92..b2b8a98f2 100644 --- a/config.json +++ b/config.json @@ -553,7 +553,7 @@ }, { "slug": "eliuds-eggs", - "name": "Eliuds Eggs", + "name": "Eliud's Eggs", "uuid": "2d738c77-8dde-437a-bf42-ed5542a414d1", "practices": [], "prerequisites": [], From a37101a44e8b5cbdbf8d593a55e10d978ec34ab7 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Mon, 12 Aug 2024 17:40:41 +0200 Subject: [PATCH 318/436] reverse-string: point out limited crates (#1958) --- .../reverse-string/.docs/instructions.append.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/exercises/practice/reverse-string/.docs/instructions.append.md b/exercises/practice/reverse-string/.docs/instructions.append.md index f28f72c0a..1c877a155 100644 --- a/exercises/practice/reverse-string/.docs/instructions.append.md +++ b/exercises/practice/reverse-string/.docs/instructions.append.md @@ -2,16 +2,19 @@ ## Bonus -Test your function on this string: `uüu` and see what happens. Try to write a function that properly -reverses this string. Hint: grapheme clusters +Test your function on this string: `uüu` and see what happens. +Try to write a function that properly reverses this string. +Hint: grapheme clusters -To get the bonus test to run, remove the ignore flag (`#[ignore]`) from the -last test, and execute the tests with: +To get the bonus test to run, remove the ignore flag (`#[ignore]`) from the last test, and execute the tests with: ```bash -$ cargo test --features grapheme +cargo test --features grapheme ``` -You will need to use external libraries (a `crate` in rust lingo) for the bonus task. A good place to look for those is [crates.io](https://crates.io/), the official repository of crates. +You will need to use external libraries (a `crate` in rust lingo) for the bonus task. +A good place to look for those is [crates.io](https://crates.io/), the official repository of crates. +Please remember that only a limited set of crates is supported by our test runner. +The full list is in [this file](https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml) under the section `[dependencies]`. [Check the documentation](https://doc.rust-lang.org/cargo/guide/dependencies.html) for instructions on how to use external crates in your projects. From 9534dc53a0ec3a2066b30599fc2f465994fa9f1e Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Mon, 12 Aug 2024 17:43:26 +0200 Subject: [PATCH 319/436] armstrong-numbers: sync (#1959) This also adds a new custom tera filter to format number literals in a more human-readable way (by inserting `_` every third digit). Most of the additional tests don't seem to add any particular value except for the last one, which is [under consideration for upstreaming](https://forum.exercism.org/t/armstrong-numbers-test-case-against-overflow/12457). [no important files changed] --- docs/CONTRIBUTING.md | 1 + .../.meta/test_template.tera | 9 +++ .../armstrong-numbers/.meta/tests.toml | 56 ++++++++++++++++++- .../tests/armstrong-numbers.rs | 47 ++++------------ rust-tooling/generate/Cargo.toml | 2 +- rust-tooling/generate/src/custom_filters.rs | 28 +++++++++- 6 files changed, 101 insertions(+), 42 deletions(-) create mode 100644 exercises/practice/armstrong-numbers/.meta/test_template.tera diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index bde876fa0..31d44bbb1 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -157,6 +157,7 @@ There are some custom tera filters in [`rust-tooling`](/rust-tooling/generate/sr Here's the hopefully up-to-date list: - `to_hex` formats ints in hexadecimal - `snake_case` massages an arbitrary string into a decent Rust identifier +- `fmt_num` format number literals (insert `_` every third digit) Feel free to add your own in the crate `rust-tooling`. Hopefully you'll remember to update the list here as well. 🙂 diff --git a/exercises/practice/armstrong-numbers/.meta/test_template.tera b/exercises/practice/armstrong-numbers/.meta/test_template.tera new file mode 100644 index 000000000..d5c167b8d --- /dev/null +++ b/exercises/practice/armstrong-numbers/.meta/test_template.tera @@ -0,0 +1,9 @@ +use armstrong_numbers::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | snake_case }}() { + assert!({% if not test.expected %} ! {% endif %}is_armstrong_number({{ test.input.number | fmt_num }})) +} +{% endfor -%} diff --git a/exercises/practice/armstrong-numbers/.meta/tests.toml b/exercises/practice/armstrong-numbers/.meta/tests.toml index be690e975..15a217c87 100644 --- a/exercises/practice/armstrong-numbers/.meta/tests.toml +++ b/exercises/practice/armstrong-numbers/.meta/tests.toml @@ -1,3 +1,53 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[c1ed103c-258d-45b2-be73-d8c6d9580c7b] +description = "Zero is an Armstrong number" + +[579e8f03-9659-4b85-a1a2-d64350f6b17a] +description = "Single-digit numbers are Armstrong numbers" + +[2d6db9dc-5bf8-4976-a90b-b2c2b9feba60] +description = "There are no two-digit Armstrong numbers" + +[509c087f-e327-4113-a7d2-26a4e9d18283] +description = "Three-digit number that is an Armstrong number" + +[7154547d-c2ce-468d-b214-4cb953b870cf] +description = "Three-digit number that is not an Armstrong number" + +[6bac5b7b-42e9-4ecb-a8b0-4832229aa103] +description = "Four-digit number that is an Armstrong number" + +[eed4b331-af80-45b5-a80b-19c9ea444b2e] +description = "Four-digit number that is not an Armstrong number" + +[f971ced7-8d68-4758-aea1-d4194900b864] +description = "Seven-digit number that is an Armstrong number" + +[7ee45d52-5d35-4fbd-b6f1-5c8cd8a67f18] +description = "Seven-digit number that is not an Armstrong number" + +[5ee2fdf8-334e-4a46-bb8d-e5c19c02c148] +description = "Armstrong number containing seven zeroes" +include = false +comment = """ + The exercise was designed with u32 in the interface, + which does not support numbers that big. +""" + +[12ffbf10-307a-434e-b4ad-c925680e1dd4] +description = "The largest and last Armstrong number" +include = false +comment = """ + The exercise was designed with u32 in the interface, + which does not support numbers that big. +""" diff --git a/exercises/practice/armstrong-numbers/tests/armstrong-numbers.rs b/exercises/practice/armstrong-numbers/tests/armstrong-numbers.rs index 055ebe758..6e15144cd 100644 --- a/exercises/practice/armstrong-numbers/tests/armstrong-numbers.rs +++ b/exercises/practice/armstrong-numbers/tests/armstrong-numbers.rs @@ -13,69 +13,42 @@ fn single_digit_numbers_are_armstrong_numbers() { #[test] #[ignore] -fn there_are_no_2_digit_armstrong_numbers() { +fn there_are_no_two_digit_armstrong_numbers() { assert!(!is_armstrong_number(10)) } #[test] #[ignore] -fn three_digit_armstrong_number() { +fn three_digit_number_that_is_an_armstrong_number() { assert!(is_armstrong_number(153)) } #[test] #[ignore] -fn three_digit_non_armstrong_number() { +fn three_digit_number_that_is_not_an_armstrong_number() { assert!(!is_armstrong_number(100)) } #[test] #[ignore] -fn four_digit_armstrong_number() { - assert!(is_armstrong_number(9474)) +fn four_digit_number_that_is_an_armstrong_number() { + assert!(is_armstrong_number(9_474)) } #[test] #[ignore] -fn four_digit_non_armstrong_number() { - assert!(!is_armstrong_number(9475)) +fn four_digit_number_that_is_not_an_armstrong_number() { + assert!(!is_armstrong_number(9_475)) } #[test] #[ignore] -fn seven_digit_armstrong_number() { +fn seven_digit_number_that_is_an_armstrong_number() { assert!(is_armstrong_number(9_926_315)) } #[test] #[ignore] -fn seven_digit_non_armstrong_number() { - assert!(!is_armstrong_number(9_926_316)) -} - -#[test] -#[ignore] -fn nine_digit_armstrong_number() { - assert!(is_armstrong_number(912_985_153)); -} - -#[test] -#[ignore] -fn nine_digit_non_armstrong_number() { - assert!(!is_armstrong_number(999_999_999)); -} - -#[test] -#[ignore] -fn ten_digit_non_armstrong_number() { - assert!(!is_armstrong_number(3_999_999_999)); -} - -// The following number has an Armstrong sum equal to 2^32 plus itself, -// and therefore will be detected as an Armstrong number if you are -// incorrectly using wrapping arithmetic. -#[test] -#[ignore] -fn properly_handles_overflow() { - assert!(!is_armstrong_number(4_106_098_957)); +fn seven_digit_number_that_is_not_an_armstrong_number() { + assert!(!is_armstrong_number(9_926_314)) } diff --git a/rust-tooling/generate/Cargo.toml b/rust-tooling/generate/Cargo.toml index 5e04e0bd1..d84109f47 100644 --- a/rust-tooling/generate/Cargo.toml +++ b/rust-tooling/generate/Cargo.toml @@ -18,7 +18,7 @@ convert_case = "0.6.0" glob = "0.3.1" inquire = "0.6.2" models = { version = "0.1.0", path = "../models" } -serde_json = { version = "1.0.105", features = ["preserve_order"] } +serde_json = { version = "1.0.105", features = ["arbitrary_precision", "preserve_order"] } slug = "0.1.5" tera = "1.19.1" utils = { version = "0.1.0", path = "../utils" } diff --git a/rust-tooling/generate/src/custom_filters.rs b/rust-tooling/generate/src/custom_filters.rs index 23e17154a..8dee6896f 100644 --- a/rust-tooling/generate/src/custom_filters.rs +++ b/rust-tooling/generate/src/custom_filters.rs @@ -4,7 +4,11 @@ use tera::{Result, Value}; type Filter = fn(&Value, &HashMap) -> Result; -pub static CUSTOM_FILTERS: &[(&str, Filter)] = &[("to_hex", to_hex), ("snake_case", snake_case)]; +pub static CUSTOM_FILTERS: &[(&str, Filter)] = &[ + ("to_hex", to_hex), + ("snake_case", snake_case), + ("fmt_num", fmt_num), +]; pub fn to_hex(value: &Value, _args: &HashMap) -> Result { let Some(value) = value.as_u64() else { @@ -28,3 +32,25 @@ pub fn snake_case(value: &Value, _args: &HashMap) -> Result) -> Result { + let Some(value) = value.as_number() else { + return Err(tera::Error::call_filter( + "fmt_num filter expects a number", + "serde_json::value::Value::as_number", + )); + }; + let mut num: Vec<_> = value.to_string().into(); + num.reverse(); + + let mut pretty_digits = num + .chunks(3) + .flat_map(|digits| digits.iter().copied().chain([b'_'])) + .collect::>(); + if pretty_digits.last() == Some(&b'_') { + pretty_digits.pop(); + } + pretty_digits.reverse(); + let pretty_num = String::from_utf8(pretty_digits).unwrap_or_default(); + Ok(Value::String(pretty_num)) +} From e6c6861517a2979a6211f4e935439f4de6c9a159 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 13 Aug 2024 07:52:28 +0200 Subject: [PATCH 320/436] hello-world: sync (#1951) --- exercises/practice/hello-world/.meta/tests.toml | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/exercises/practice/hello-world/.meta/tests.toml b/exercises/practice/hello-world/.meta/tests.toml index be690e975..73466d677 100644 --- a/exercises/practice/hello-world/.meta/tests.toml +++ b/exercises/practice/hello-world/.meta/tests.toml @@ -1,3 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[af9ffe10-dc13-42d8-a742-e7bdafac449d] +description = "Say Hi!" From d7b5cd70bb125faa1d4d8a97a176c5a5831b9ad6 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 13 Aug 2024 07:52:46 +0200 Subject: [PATCH 321/436] generator: remove redundant flag (#1952) With the configlet wrapper added in https://github.com/exercism/rust/pull/1950, the `--offline` flag is now always set. --- .../ci-tests/tests/stubs_are_warning_free.rs | 2 +- rust-tooling/generate/src/cli.rs | 10 ------ rust-tooling/generate/src/main.rs | 36 ++++++++----------- 3 files changed, 16 insertions(+), 32 deletions(-) diff --git a/rust-tooling/ci-tests/tests/stubs_are_warning_free.rs b/rust-tooling/ci-tests/tests/stubs_are_warning_free.rs index 1796a1a64..94bef91c4 100644 --- a/rust-tooling/ci-tests/tests/stubs_are_warning_free.rs +++ b/rust-tooling/ci-tests/tests/stubs_are_warning_free.rs @@ -14,7 +14,7 @@ fn stubs_are_warning_free() { .unwrap() .map(Result::unwrap) { - if std::fs::read_to_string(&manifest.parent().unwrap().join(".meta").join("config.json")) + if std::fs::read_to_string(manifest.parent().unwrap().join(".meta").join("config.json")) .unwrap() .contains("allowed-to-not-compile") { diff --git a/rust-tooling/generate/src/cli.rs b/rust-tooling/generate/src/cli.rs index 37787dafe..7d1c03202 100644 --- a/rust-tooling/generate/src/cli.rs +++ b/rust-tooling/generate/src/cli.rs @@ -37,17 +37,12 @@ pub struct AddArgs { #[arg(short, long)] difficulty: Option, - - /// do not update problem-specifications cache - #[arg(long)] - offline: bool, } pub struct FullAddArgs { pub slug: String, pub name: String, pub difficulty: track_config::Difficulty, - pub offline: bool, } impl AddArgs { @@ -69,7 +64,6 @@ impl AddArgs { slug, name, difficulty, - offline: self.offline, }) } } @@ -79,10 +73,6 @@ pub struct UpdateArgs { /// slug of the exercise to update #[arg(short, long)] slug: Option, - - /// do not update problem-specifications cache - #[arg(long)] - pub offline: bool, } impl UpdateArgs { diff --git a/rust-tooling/generate/src/main.rs b/rust-tooling/generate/src/main.rs index 0299c5f51..9d8514efd 100644 --- a/rust-tooling/generate/src/main.rs +++ b/rust-tooling/generate/src/main.rs @@ -23,7 +23,6 @@ fn add_exercise(args: AddArgs) -> Result<()> { slug, name, difficulty, - offline, } = args.unwrap_args_or_prompt()?; let config = track_config::PracticeExercise::new(slug.clone(), name, difficulty); @@ -42,40 +41,35 @@ Added your exercise to config.json. You can add practices, prerequisites and topics if you like." ); - make_configlet_generate_what_it_can(&slug, offline)?; + make_configlet_generate_what_it_can(&slug)?; let is_update = false; generate_exercise_files(&slug, is_update) } fn update_exercise(args: UpdateArgs) -> Result<()> { - let offline = args.offline; let slug = args.unwrap_slug_or_prompt()?; - make_configlet_generate_what_it_can(&slug, offline)?; + make_configlet_generate_what_it_can(&slug)?; let is_update = true; generate_exercise_files(&slug, is_update) } -fn make_configlet_generate_what_it_can(slug: &str, offline: bool) -> Result<()> { +fn make_configlet_generate_what_it_can(slug: &str) -> Result<()> { let status = std::process::Command::new("just") - .args( - [ - "configlet", - "sync", - "--update", - "--yes", - "--docs", - "--metadata", - "--tests", - "include", - "--exercise", - slug, - ] - .into_iter() - .chain(offline.then_some("--offline")), - ) + .args([ + "configlet", + "sync", + "--update", + "--yes", + "--docs", + "--metadata", + "--tests", + "include", + "--exercise", + slug, + ]) .status() .context("failed to run configlet sync")?; if !status.success() { From d39bcae01c2cd895bac6b7869cdd2e5aff3a6c37 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 13 Aug 2024 07:53:03 +0200 Subject: [PATCH 322/436] generator: only add template upon request (#1953) --- rust-tooling/generate/src/cli.rs | 7 +++++++ rust-tooling/generate/src/main.rs | 21 +++++++++++++-------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/rust-tooling/generate/src/cli.rs b/rust-tooling/generate/src/cli.rs index 7d1c03202..0ded09527 100644 --- a/rust-tooling/generate/src/cli.rs +++ b/rust-tooling/generate/src/cli.rs @@ -199,3 +199,10 @@ pub fn prompt_for_difficulty() -> Result { .prompt() .context("failed to select difficulty") } + +pub fn prompt_for_template_generation() -> bool { + inquire::Confirm::new("Would you like to add a template for test generation?") + .with_default(false) + .prompt() + .unwrap_or_default() +} diff --git a/rust-tooling/generate/src/main.rs b/rust-tooling/generate/src/main.rs index 9d8514efd..aa0b0fb65 100644 --- a/rust-tooling/generate/src/main.rs +++ b/rust-tooling/generate/src/main.rs @@ -2,7 +2,7 @@ use std::path::PathBuf; use anyhow::{Context, Result}; use clap::Parser; -use cli::{AddArgs, FullAddArgs, UpdateArgs}; +use cli::{prompt_for_template_generation, AddArgs, FullAddArgs, UpdateArgs}; use models::track_config::{self, TRACK_CONFIG}; mod cli; @@ -92,14 +92,19 @@ fn generate_exercise_files(slug: &str, is_update: bool) -> Result<()> { } let template_path = exercise_path.join(".meta/test_template.tera"); - if std::fs::metadata(&template_path).is_err() { - std::fs::write(template_path, exercise.test_template)?; + if !template_path.exists() && (!is_update || prompt_for_template_generation()) { + // Some exercises have existing test cases that are difficult to migrate + // to the test generator. That's why we only generate a template for + // existing exercises if the users requests it. + std::fs::write(&template_path, exercise.test_template)?; } std::fs::create_dir(exercise_path.join("tests")).ok(); - std::fs::write( - exercise_path.join(format!("tests/{slug}.rs")), - exercise.tests, - ) - .map_err(Into::into) + if template_path.exists() { + std::fs::write( + exercise_path.join(format!("tests/{slug}.rs")), + exercise.tests, + )?; + } + Ok(()) } From e6a3c8ea96d412ae8bf61b1c8663a83019373aa9 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 13 Aug 2024 07:53:58 +0200 Subject: [PATCH 323/436] accumulate: sync (#1954) --- .../practice/accumulate/.meta/tests.toml | 33 +++++++++++-- .../practice/accumulate/tests/accumulate.rs | 46 +++++++++++++++++-- 2 files changed, 73 insertions(+), 6 deletions(-) diff --git a/exercises/practice/accumulate/.meta/tests.toml b/exercises/practice/accumulate/.meta/tests.toml index be690e975..d7858e07b 100644 --- a/exercises/practice/accumulate/.meta/tests.toml +++ b/exercises/practice/accumulate/.meta/tests.toml @@ -1,3 +1,30 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[64d97c14-36dd-44a8-9621-2cecebd6ed23] +description = "accumulate empty" + +[00008ed2-4651-4929-8c08-8b4dbd70872e] +description = "accumulate squares" + +[551016da-4396-4cae-b0ec-4c3a1a264125] +description = "accumulate upcases" + +[cdf95597-b6ec-4eac-a838-3480d13d0d05] +description = "accumulate reversed strings" + +[bee8e9b6-b16f-4cd2-be3b-ccf7457e50bb] +description = "accumulate recursively" +include = false + +[0b357334-4cad-49e1-a741-425202edfc7c] +description = "accumulate recursively" +reimplements = "bee8e9b6-b16f-4cd2-be3b-ccf7457e50bb" diff --git a/exercises/practice/accumulate/tests/accumulate.rs b/exercises/practice/accumulate/tests/accumulate.rs index 0cfb6a184..501e6bace 100644 --- a/exercises/practice/accumulate/tests/accumulate.rs +++ b/exercises/practice/accumulate/tests/accumulate.rs @@ -5,6 +5,14 @@ fn square(x: i32) -> i32 { } #[test] +fn accumulate_empty() { + let input = vec![]; + let expected = vec![]; + assert_eq!(map(input, square), expected); +} + +#[test] +#[ignore] fn func_single() { let input = vec![2]; let expected = vec![4]; @@ -13,9 +21,9 @@ fn func_single() { #[test] #[ignore] -fn func_multi() { - let input = vec![2, 3, 4, 5]; - let expected = vec![4, 9, 16, 25]; +fn accumulate_squares() { + let input = vec![1, 2, 3]; + let expected = vec![1, 4, 9]; assert_eq!(map(input, square), expected); } @@ -43,6 +51,38 @@ fn strings() { assert_eq!(map(input, |s| s.repeat(2)), expected); } +#[test] +#[ignore] +fn accumulate_upcases() { + let input = vec!["Hello", "world"]; + let expected = vec!["HELLO", "WORLD"]; + assert_eq!(map(input, str::to_uppercase), expected); +} + +#[test] +#[ignore] +fn accumulate_reversed_strings() { + let input = vec!["the", "quick", "brown", "fox", "etc"]; + let expected = vec!["eht", "kciuq", "nworb", "xof", "cte"]; + let reverse = |s: &str| s.chars().rev().collect::(); + assert_eq!(map(input, reverse), expected); +} + +#[test] +#[ignore] +fn accumulate_recursively() { + let input = vec!["a", "b", "c"]; + let expected = vec![ + vec!["a1", "a2", "a3"], + vec!["b1", "b2", "b3"], + vec!["c1", "c2", "c3"], + ]; + assert_eq!( + map(input, |x| map(vec!["1", "2", "3"], |y| [x, y].join(""))), + expected + ); +} + #[test] #[ignore] fn change_in_type() { From 104b472a8eb0f97ca3a3899a2d4a4182f3db8467 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 13 Aug 2024 07:54:12 +0200 Subject: [PATCH 324/436] all-your-base: sync (#1955) [no important files changed] --- docs/CONTRIBUTING.md | 1 + .../all-your-base/.meta/test_template.tera | 26 ++++++++++++++ .../practice/all-your-base/.meta/tests.toml | 36 +++++++++++++++++-- .../all-your-base/tests/all-your-base.rs | 32 ++++++++--------- rust-tooling/generate/src/custom_filters.rs | 16 +++++++++ 5 files changed, 92 insertions(+), 19 deletions(-) create mode 100644 exercises/practice/all-your-base/.meta/test_template.tera diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 31d44bbb1..fc59b7183 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -157,6 +157,7 @@ There are some custom tera filters in [`rust-tooling`](/rust-tooling/generate/sr Here's the hopefully up-to-date list: - `to_hex` formats ints in hexadecimal - `snake_case` massages an arbitrary string into a decent Rust identifier +- `make_test_ident` is like snake case, but prepends `test_` if the string starts with a digit - `fmt_num` format number literals (insert `_` every third digit) Feel free to add your own in the crate `rust-tooling`. diff --git a/exercises/practice/all-your-base/.meta/test_template.tera b/exercises/practice/all-your-base/.meta/test_template.tera new file mode 100644 index 000000000..829e823c7 --- /dev/null +++ b/exercises/practice/all-your-base/.meta/test_template.tera @@ -0,0 +1,26 @@ +use allyourbase as ayb; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_test_ident }}() { + let input_base = {{ test.input.inputBase }}; + let input_digits = &{{ test.input.digits | json_encode() }}; + let output_base = {{ test.input.outputBase }}; + {%- if not test.expected is object %} + let output_digits = vec!{{ test.expected | json_encode() }}; + {%- endif %} + assert_eq!( + ayb::convert(input_digits, input_base, output_base), + {%- if not test.expected is object %} + Ok(output_digits) + {%- elif test.expected.error == "input base must be >= 2" %} + Err(ayb::Error::InvalidInputBase) + {%- elif test.expected.error == "all digits must satisfy 0 <= d < input base" %} + Err(ayb::Error::InvalidDigit(2)) + {%- elif test.expected.error == "output base must be >= 2" %} + Err(ayb::Error::InvalidOutputBase) + {%- endif %} + ); +} +{% endfor -%} diff --git a/exercises/practice/all-your-base/.meta/tests.toml b/exercises/practice/all-your-base/.meta/tests.toml index c954370df..628e7d15b 100644 --- a/exercises/practice/all-your-base/.meta/tests.toml +++ b/exercises/practice/all-your-base/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [5ce422f9-7a4b-4f44-ad29-49c67cb32d2c] description = "single bit one to decimal" @@ -23,6 +30,9 @@ description = "trinary to hexadecimal" [d3901c80-8190-41b9-bd86-38d988efa956] description = "hexadecimal to trinary" +[5d42f85e-21ad-41bd-b9be-a3e8e4258bbf] +description = "15-bit integer" + [d68788f7-66dd-43f8-a543-f15b6d233f83] description = "empty list" @@ -41,6 +51,16 @@ description = "input base is one" [e21a693a-7a69-450b-b393-27415c26a016] description = "input base is zero" +[54a23be5-d99e-41cc-88e0-a650ffe5fcc2] +description = "input base is negative" +include = false +comment = "we use unsigned integers" + +[9eccf60c-dcc9-407b-95d8-c37b8be56bb6] +description = "negative digit" +include = false +comment = "we use unsigned integers" + [232fa4a5-e761-4939-ba0c-ed046cd0676a] description = "invalid positive digit" @@ -49,3 +69,13 @@ description = "output base is one" [73dac367-da5c-4a37-95fe-c87fad0a4047] description = "output base is zero" + +[13f81f42-ff53-4e24-89d9-37603a48ebd9] +description = "output base is negative" +include = false +comment = "we use unsigned integers" + +[0e6c895d-8a5d-4868-a345-309d094cfe8d] +description = "both bases are negative" +include = false +comment = "we use unsigned integers" diff --git a/exercises/practice/all-your-base/tests/all-your-base.rs b/exercises/practice/all-your-base/tests/all-your-base.rs index 9012a40ae..75cd0d043 100644 --- a/exercises/practice/all-your-base/tests/all-your-base.rs +++ b/exercises/practice/all-your-base/tests/all-your-base.rs @@ -92,7 +92,7 @@ fn hexadecimal_to_trinary() { #[test] #[ignore] -fn fifteen_bit_integer() { +fn test_15_bit_integer() { let input_base = 97; let input_digits = &[3, 46, 60]; let output_base = 73; @@ -157,20 +157,20 @@ fn leading_zeros() { #[test] #[ignore] -fn invalid_positive_digit() { - let input_base = 2; - let input_digits = &[1, 2, 1, 0, 1, 0]; +fn input_base_is_one() { + let input_base = 1; + let input_digits = &[0]; let output_base = 10; assert_eq!( ayb::convert(input_digits, input_base, output_base), - Err(ayb::Error::InvalidDigit(2)) + Err(ayb::Error::InvalidInputBase) ); } #[test] #[ignore] -fn input_base_is_one() { - let input_base = 1; +fn input_base_is_zero() { + let input_base = 0; let input_digits = &[]; let output_base = 10; assert_eq!( @@ -181,25 +181,25 @@ fn input_base_is_one() { #[test] #[ignore] -fn output_base_is_one() { +fn invalid_positive_digit() { let input_base = 2; - let input_digits = &[1, 0, 1, 0, 1, 0]; - let output_base = 1; + let input_digits = &[1, 2, 1, 0, 1, 0]; + let output_base = 10; assert_eq!( ayb::convert(input_digits, input_base, output_base), - Err(ayb::Error::InvalidOutputBase) + Err(ayb::Error::InvalidDigit(2)) ); } #[test] #[ignore] -fn input_base_is_zero() { - let input_base = 0; - let input_digits = &[]; - let output_base = 10; +fn output_base_is_one() { + let input_base = 2; + let input_digits = &[1, 0, 1, 0, 1, 0]; + let output_base = 1; assert_eq!( ayb::convert(input_digits, input_base, output_base), - Err(ayb::Error::InvalidInputBase) + Err(ayb::Error::InvalidOutputBase) ); } diff --git a/rust-tooling/generate/src/custom_filters.rs b/rust-tooling/generate/src/custom_filters.rs index 8dee6896f..302cc8fa9 100644 --- a/rust-tooling/generate/src/custom_filters.rs +++ b/rust-tooling/generate/src/custom_filters.rs @@ -7,6 +7,7 @@ type Filter = fn(&Value, &HashMap) -> Result; pub static CUSTOM_FILTERS: &[(&str, Filter)] = &[ ("to_hex", to_hex), ("snake_case", snake_case), + ("make_test_ident", make_test_ident), ("fmt_num", fmt_num), ]; @@ -33,6 +34,21 @@ pub fn snake_case(value: &Value, _args: &HashMap) -> Result) -> Result { + let value = snake_case(value, _args)?; + let Some(value) = value.as_str() else { + return Err(tera::Error::call_filter( + "make_test_ident filter expects a string", + "serde_json::value::Value::as_str", + )); + }; + if !value.chars().next().unwrap_or_default().is_alphabetic() { + // identifiers cannot start with digits etc. + return Ok(Value::String(format!("test_{value}"))); + } + Ok(Value::String(value.into())) +} + pub fn fmt_num(value: &Value, _args: &HashMap) -> Result { let Some(value) = value.as_number() else { return Err(tera::Error::call_filter( From eafd87399f15136ef27bb5e44fe56aa3e9b94afc Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 13 Aug 2024 07:54:31 +0200 Subject: [PATCH 325/436] alphametics: sync (#1956) [no important files changed] --- .../alphametics/.meta/test_template.tera | 19 ++ .../practice/alphametics/.meta/tests.toml | 40 ++++- .../practice/alphametics/tests/alphametics.rs | 163 +++++++++--------- 3 files changed, 136 insertions(+), 86 deletions(-) create mode 100644 exercises/practice/alphametics/.meta/test_template.tera diff --git a/exercises/practice/alphametics/.meta/test_template.tera b/exercises/practice/alphametics/.meta/test_template.tera new file mode 100644 index 000000000..a67a99381 --- /dev/null +++ b/exercises/practice/alphametics/.meta/test_template.tera @@ -0,0 +1,19 @@ +use alphametics::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | snake_case }}() { + let answer = solve({{ test.input.puzzle | json_encode() }}); + {%- if test.expected is object %} + let expected = [ + {%- for key, val in test.expected %} + ('{{ key }}', {{val}}), + {%- endfor %} + ].into_iter().collect(); + assert_eq!(answer, Some(expected)); + {%- else %} + assert_eq!(answer, None); + {%- endif %} +} +{% endfor -%} diff --git a/exercises/practice/alphametics/.meta/tests.toml b/exercises/practice/alphametics/.meta/tests.toml index 8697da6fe..f599b3da6 100644 --- a/exercises/practice/alphametics/.meta/tests.toml +++ b/exercises/practice/alphametics/.meta/tests.toml @@ -1,6 +1,40 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[e0c08b07-9028-4d5f-91e1-d178fead8e1a] +description = "puzzle with three letters" + +[a504ee41-cb92-4ec2-9f11-c37e95ab3f25] +description = "solution must have unique value for each letter" + +[4e3b81d2-be7b-4c5c-9a80-cd72bc6d465a] +description = "leading zero solution is invalid" [8a3e3168-d1ee-4df7-94c7-b9c54845ac3a] description = "puzzle with two digits final carry" + +[a9630645-15bd-48b6-a61e-d85c4021cc09] +description = "puzzle with four letters" + +[3d905a86-5a52-4e4e-bf80-8951535791bd] +description = "puzzle with six letters" + +[4febca56-e7b7-4789-97b9-530d09ba95f0] +description = "puzzle with seven letters" + +[12125a75-7284-4f9a-a5fa-191471e0d44f] +description = "puzzle with eight letters" + +[fb05955f-38dc-477a-a0b6-5ef78969fffa] +description = "puzzle with ten letters" + +[9a101e81-9216-472b-b458-b513a7adacf7] +description = "puzzle with ten letters and 199 addends" diff --git a/exercises/practice/alphametics/tests/alphametics.rs b/exercises/practice/alphametics/tests/alphametics.rs index 2e07d87e3..06f2920bc 100644 --- a/exercises/practice/alphametics/tests/alphametics.rs +++ b/exercises/practice/alphametics/tests/alphametics.rs @@ -1,132 +1,129 @@ -use std::collections::HashMap; - -fn assert_alphametic_solution_eq(puzzle: &str, solution: &[(char, u8)]) { - let answer = alphametics::solve(puzzle); - let solution: HashMap = solution.iter().cloned().collect(); - assert_eq!(answer, Some(solution)); -} +use alphametics::*; #[test] -fn with_three_letters() { - assert_alphametic_solution_eq("I + BB == ILL", &[('I', 1), ('B', 9), ('L', 0)]); +fn puzzle_with_three_letters() { + let answer = solve("I + BB == ILL"); + let expected = [('I', 1), ('B', 9), ('L', 0)].into_iter().collect(); + assert_eq!(answer, Some(expected)); } #[test] #[ignore] -fn must_have_unique_value_for_each_letter() { - let answer = alphametics::solve("A == B"); +fn solution_must_have_unique_value_for_each_letter() { + let answer = solve("A == B"); assert_eq!(answer, None); } #[test] #[ignore] fn leading_zero_solution_is_invalid() { - let answer = alphametics::solve("ACA + DD == BD"); - assert_eq!(answer, None); -} - -#[test] -#[ignore] -fn sum_must_be_wide_enough() { - let answer = alphametics::solve("ABC + DEF == GH"); + let answer = solve("ACA + DD == BD"); assert_eq!(answer, None); } #[test] #[ignore] fn puzzle_with_two_digits_final_carry() { - assert_alphametic_solution_eq( - "A + A + A + A + A + A + A + A + A + A + A + B == BCC", - &[('A', 9), ('B', 1), ('C', 0)], - ); + let answer = solve("A + A + A + A + A + A + A + A + A + A + A + B == BCC"); + let expected = [('A', 9), ('B', 1), ('C', 0)].into_iter().collect(); + assert_eq!(answer, Some(expected)); } #[test] #[ignore] fn puzzle_with_four_letters() { - assert_alphametic_solution_eq("AS + A == MOM", &[('A', 9), ('S', 2), ('M', 1), ('O', 0)]); + let answer = solve("AS + A == MOM"); + let expected = [('A', 9), ('S', 2), ('M', 1), ('O', 0)] + .into_iter() + .collect(); + assert_eq!(answer, Some(expected)); } #[test] #[ignore] fn puzzle_with_six_letters() { - assert_alphametic_solution_eq( - "NO + NO + TOO == LATE", - &[('N', 7), ('O', 4), ('T', 9), ('L', 1), ('A', 0), ('E', 2)], - ); + let answer = solve("NO + NO + TOO == LATE"); + let expected = [('N', 7), ('O', 4), ('T', 9), ('L', 1), ('A', 0), ('E', 2)] + .into_iter() + .collect(); + assert_eq!(answer, Some(expected)); } #[test] #[ignore] fn puzzle_with_seven_letters() { - assert_alphametic_solution_eq( - "HE + SEES + THE == LIGHT", - &[ - ('E', 4), - ('G', 2), - ('H', 5), - ('I', 0), - ('L', 1), - ('S', 9), - ('T', 7), - ], - ); + let answer = solve("HE + SEES + THE == LIGHT"); + let expected = [ + ('E', 4), + ('G', 2), + ('H', 5), + ('I', 0), + ('L', 1), + ('S', 9), + ('T', 7), + ] + .into_iter() + .collect(); + assert_eq!(answer, Some(expected)); } #[test] #[ignore] fn puzzle_with_eight_letters() { - assert_alphametic_solution_eq( - "SEND + MORE == MONEY", - &[ - ('S', 9), - ('E', 5), - ('N', 6), - ('D', 7), - ('M', 1), - ('O', 0), - ('R', 8), - ('Y', 2), - ], - ); + let answer = solve("SEND + MORE == MONEY"); + let expected = [ + ('S', 9), + ('E', 5), + ('N', 6), + ('D', 7), + ('M', 1), + ('O', 0), + ('R', 8), + ('Y', 2), + ] + .into_iter() + .collect(); + assert_eq!(answer, Some(expected)); } #[test] #[ignore] fn puzzle_with_ten_letters() { - assert_alphametic_solution_eq( - "AND + A + STRONG + OFFENSE + AS + A + GOOD == DEFENSE", - &[ - ('A', 5), - ('D', 3), - ('E', 4), - ('F', 7), - ('G', 8), - ('N', 0), - ('O', 2), - ('R', 1), - ('S', 6), - ('T', 9), - ], - ); + let answer = solve("AND + A + STRONG + OFFENSE + AS + A + GOOD == DEFENSE"); + let expected = [ + ('A', 5), + ('D', 3), + ('E', 4), + ('F', 7), + ('G', 8), + ('N', 0), + ('O', 2), + ('R', 1), + ('S', 6), + ('T', 9), + ] + .into_iter() + .collect(); + assert_eq!(answer, Some(expected)); } #[test] #[ignore] fn puzzle_with_ten_letters_and_199_addends() { - assert_alphametic_solution_eq( - "THIS + A + FIRE + THEREFORE + FOR + ALL + HISTORIES + I + TELL + A + TALE + THAT + FALSIFIES + ITS + TITLE + TIS + A + LIE + THE + TALE + OF + THE + LAST + FIRE + HORSES + LATE + AFTER + THE + FIRST + FATHERS + FORESEE + THE + HORRORS + THE + LAST + FREE + TROLL + TERRIFIES + THE + HORSES + OF + FIRE + THE + TROLL + RESTS + AT + THE + HOLE + OF + LOSSES + IT + IS + THERE + THAT + SHE + STORES + ROLES + OF + LEATHERS + AFTER + SHE + SATISFIES + HER + HATE + OFF + THOSE + FEARS + A + TASTE + RISES + AS + SHE + HEARS + THE + LEAST + FAR + HORSE + THOSE + FAST + HORSES + THAT + FIRST + HEAR + THE + TROLL + FLEE + OFF + TO + THE + FOREST + THE + HORSES + THAT + ALERTS + RAISE + THE + STARES + OF + THE + OTHERS + AS + THE + TROLL + ASSAILS + AT + THE + TOTAL + SHIFT + HER + TEETH + TEAR + HOOF + OFF + TORSO + AS + THE + LAST + HORSE + FORFEITS + ITS + LIFE + THE + FIRST + FATHERS + HEAR + OF + THE + HORRORS + THEIR + FEARS + THAT + THE + FIRES + FOR + THEIR + FEASTS + ARREST + AS + THE + FIRST + FATHERS + RESETTLE + THE + LAST + OF + THE + FIRE + HORSES + THE + LAST + TROLL + HARASSES + THE + FOREST + HEART + FREE + AT + LAST + OF + THE + LAST + TROLL + ALL + OFFER + THEIR + FIRE + HEAT + TO + THE + ASSISTERS + FAR + OFF + THE + TROLL + FASTS + ITS + LIFE + SHORTER + AS + STARS + RISE + THE + HORSES + REST + SAFE + AFTER + ALL + SHARE + HOT + FISH + AS + THEIR + AFFILIATES + TAILOR + A + ROOFS + FOR + THEIR + SAFE == FORTRESSES", - &[ - ('A', 1), - ('E', 0), - ('F', 5), - ('H', 8), - ('I', 7), - ('L', 2), - ('O', 6), - ('R', 3), - ('S', 4), - ('T', 9), - ], - ); + let answer = solve("THIS + A + FIRE + THEREFORE + FOR + ALL + HISTORIES + I + TELL + A + TALE + THAT + FALSIFIES + ITS + TITLE + TIS + A + LIE + THE + TALE + OF + THE + LAST + FIRE + HORSES + LATE + AFTER + THE + FIRST + FATHERS + FORESEE + THE + HORRORS + THE + LAST + FREE + TROLL + TERRIFIES + THE + HORSES + OF + FIRE + THE + TROLL + RESTS + AT + THE + HOLE + OF + LOSSES + IT + IS + THERE + THAT + SHE + STORES + ROLES + OF + LEATHERS + AFTER + SHE + SATISFIES + HER + HATE + OFF + THOSE + FEARS + A + TASTE + RISES + AS + SHE + HEARS + THE + LEAST + FAR + HORSE + THOSE + FAST + HORSES + THAT + FIRST + HEAR + THE + TROLL + FLEE + OFF + TO + THE + FOREST + THE + HORSES + THAT + ALERTS + RAISE + THE + STARES + OF + THE + OTHERS + AS + THE + TROLL + ASSAILS + AT + THE + TOTAL + SHIFT + HER + TEETH + TEAR + HOOF + OFF + TORSO + AS + THE + LAST + HORSE + FORFEITS + ITS + LIFE + THE + FIRST + FATHERS + HEAR + OF + THE + HORRORS + THEIR + FEARS + THAT + THE + FIRES + FOR + THEIR + FEASTS + ARREST + AS + THE + FIRST + FATHERS + RESETTLE + THE + LAST + OF + THE + FIRE + HORSES + THE + LAST + TROLL + HARASSES + THE + FOREST + HEART + FREE + AT + LAST + OF + THE + LAST + TROLL + ALL + OFFER + THEIR + FIRE + HEAT + TO + THE + ASSISTERS + FAR + OFF + THE + TROLL + FASTS + ITS + LIFE + SHORTER + AS + STARS + RISE + THE + HORSES + REST + SAFE + AFTER + ALL + SHARE + HOT + FISH + AS + THEIR + AFFILIATES + TAILOR + A + ROOFS + FOR + THEIR + SAFE == FORTRESSES"); + let expected = [ + ('A', 1), + ('E', 0), + ('F', 5), + ('H', 8), + ('I', 7), + ('L', 2), + ('O', 6), + ('R', 3), + ('S', 4), + ('T', 9), + ] + .into_iter() + .collect(); + assert_eq!(answer, Some(expected)); } From 2ad527731bbfdb1764232c863a026716baf9ad31 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 13 Aug 2024 14:41:45 +0200 Subject: [PATCH 326/436] binary-search: sync (#1960) [no important files changed] --- bin/test_exercise.sh | 2 +- .../binary-search/.meta/test_template.tera | 37 +++++++++++++++++++ .../practice/binary-search/.meta/tests.toml | 22 +++++++++-- exercises/practice/binary-search/Cargo.toml | 3 ++ .../binary-search/tests/binary-search.rs | 27 ++------------ 5 files changed, 64 insertions(+), 27 deletions(-) create mode 100644 exercises/practice/binary-search/.meta/test_template.tera diff --git a/bin/test_exercise.sh b/bin/test_exercise.sh index cb4324cfc..5d4ea9492 100755 --- a/bin/test_exercise.sh +++ b/bin/test_exercise.sh @@ -74,7 +74,7 @@ fi # run tests from within temporary directory cd "$tmp_dir" if [ -n "$CLIPPY" ]; then - export RUSTFLAGS="$RUSTFLAGS -D clippy::all" + export RUSTFLAGS="$RUSTFLAGS -D warnings" # shellcheck disable=SC2086 cargo clippy --tests $cargo_args else diff --git a/exercises/practice/binary-search/.meta/test_template.tera b/exercises/practice/binary-search/.meta/test_template.tera new file mode 100644 index 000000000..59cf54ae0 --- /dev/null +++ b/exercises/practice/binary-search/.meta/test_template.tera @@ -0,0 +1,37 @@ +use binary_search::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | snake_case }}() { + assert_eq!(find(&{{ test.input.array | json_encode() }}, {{ test.input.value }}), {% if test.expected is object -%} + None + {%- else -%} + Some({{ test.expected }}) + {%- endif -%}); +} +{% endfor %} + +#[test] +#[ignore] +#[cfg(feature = "generic")] +fn works_for_arrays() { + assert_eq!(find([6], 6), Some(0)); +} + +#[test] +#[ignore] +#[cfg(feature = "generic")] +fn works_for_vec() { + let vector = vec![6]; + assert_eq!(find(&vector, 6), Some(0)); + assert_eq!(find(vector, 6), Some(0)); +} + +#[test] +#[ignore] +#[cfg(feature = "generic")] +fn works_for_str_elements() { + assert_eq!(find(["a"], "a"), Some(0)); + assert_eq!(find(["a", "b"], "b"), Some(1)); +} diff --git a/exercises/practice/binary-search/.meta/tests.toml b/exercises/practice/binary-search/.meta/tests.toml index 4bd95d1c4..61e2b0682 100644 --- a/exercises/practice/binary-search/.meta/tests.toml +++ b/exercises/practice/binary-search/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [b55c24a9-a98d-4379-a08c-2adcf8ebeee8] description = "finds a value in an array with one element" @@ -23,5 +30,14 @@ description = "finds a value in an array of even length" [da7db20a-354f-49f7-a6a1-650a54998aa6] description = "identifies that a value is not included in the array" +[95d869ff-3daf-4c79-b622-6e805c675f97] +description = "a value smaller than the array's smallest value is not found" + +[8b24ef45-6e51-4a94-9eac-c2bf38fdb0ba] +description = "a value larger than the array's largest value is not found" + +[f439a0fa-cf42-4262-8ad1-64bf41ce566a] +description = "nothing is found in an empty array" + [2c353967-b56d-40b8-acff-ce43115eed64] description = "nothing is found when the left and right bounds cross" diff --git a/exercises/practice/binary-search/Cargo.toml b/exercises/practice/binary-search/Cargo.toml index 6cfecd68d..ec75216c6 100644 --- a/exercises/practice/binary-search/Cargo.toml +++ b/exercises/practice/binary-search/Cargo.toml @@ -7,3 +7,6 @@ version = "1.3.0" [features] generic = [] + +[lints.clippy] +needless_borrows_for_generic_args = "allow" diff --git a/exercises/practice/binary-search/tests/binary-search.rs b/exercises/practice/binary-search/tests/binary-search.rs index 1473cf46e..1f9ae3bb3 100644 --- a/exercises/practice/binary-search/tests/binary-search.rs +++ b/exercises/practice/binary-search/tests/binary-search.rs @@ -1,29 +1,10 @@ -// The &[] borrows are required for the base exercise, -// where `find` is not generic. Once `find` is made generic, -// the borrows become needless. Since we want the tests to work -// without clippy warnings for both people who take on the -// additional challenge and people who don't, we disable this lint. -#![allow(clippy::needless_borrows_for_generic_args)] - -use binary_search::find; +use binary_search::*; #[test] fn finds_a_value_in_an_array_with_one_element() { assert_eq!(find(&[6], 6), Some(0)); } -#[test] -#[ignore] -fn finds_first_value_in_an_array_with_two_element() { - assert_eq!(find(&[1, 2], 1), Some(0)); -} - -#[test] -#[ignore] -fn finds_second_value_in_an_array_with_two_element() { - assert_eq!(find(&[1, 2], 2), Some(1)); -} - #[test] #[ignore] fn finds_a_value_in_the_middle_of_an_array() { @@ -68,19 +49,19 @@ fn identifies_that_a_value_is_not_included_in_the_array() { #[test] #[ignore] -fn a_value_smaller_than_the_arrays_smallest_value_is_not_included() { +fn a_value_smaller_than_the_array_s_smallest_value_is_not_found() { assert_eq!(find(&[1, 3, 4, 6, 8, 9, 11], 0), None); } #[test] #[ignore] -fn a_value_larger_than_the_arrays_largest_value_is_not_included() { +fn a_value_larger_than_the_array_s_largest_value_is_not_found() { assert_eq!(find(&[1, 3, 4, 6, 8, 9, 11], 13), None); } #[test] #[ignore] -fn nothing_is_included_in_an_empty_array() { +fn nothing_is_found_in_an_empty_array() { assert_eq!(find(&[], 1), None); } From 81f3c40104b5189f4ffb4a85d1c7b379cb009f72 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 13 Aug 2024 14:42:23 +0200 Subject: [PATCH 327/436] beer-song: sync (#1961) --- .../beer-song/.meta/test_template.tera | 24 +++++ exercises/practice/beer-song/.meta/tests.toml | 37 ++++++- .../practice/beer-song/tests/beer-song.rs | 98 +++++++++++++------ 3 files changed, 128 insertions(+), 31 deletions(-) create mode 100644 exercises/practice/beer-song/.meta/test_template.tera diff --git a/exercises/practice/beer-song/.meta/test_template.tera b/exercises/practice/beer-song/.meta/test_template.tera new file mode 100644 index 000000000..0f32c94e4 --- /dev/null +++ b/exercises/practice/beer-song/.meta/test_template.tera @@ -0,0 +1,24 @@ +{% for stupid_uselessly_nested_test_group in cases -%} +{% for test_group in stupid_uselessly_nested_test_group.cases -%} +mod {{ test_group.description | snake_case }} { + use beer_song::*; + +{% for test in test_group.cases %} + #[test] + #[ignore] + fn {{ test.description | snake_case }}() { + assert_eq!( + {% if stupid_uselessly_nested_test_group.description == "verse" -%} + verse({{ test.input.startBottles }}).trim(), + {% else -%} + sing({{ test.input.startBottles }}, {{ test.input.startBottles - test.input.takeDown + 1 }}).trim(), + {% endif -%} + {{ test.expected | join(sep=" +") | json_encode() }}, + ); + } +{% endfor -%} +} + +{% endfor -%} +{% endfor -%} diff --git a/exercises/practice/beer-song/.meta/tests.toml b/exercises/practice/beer-song/.meta/tests.toml index be690e975..306cf88f0 100644 --- a/exercises/practice/beer-song/.meta/tests.toml +++ b/exercises/practice/beer-song/.meta/tests.toml @@ -1,3 +1,34 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[5a02fd08-d336-4607-8006-246fe6fa9fb0] +description = "verse -> single verse -> first generic verse" + +[77299ca6-545e-4217-a9cc-606b342e0187] +description = "verse -> single verse -> last generic verse" + +[102cbca0-b197-40fd-b548-e99609b06428] +description = "verse -> single verse -> verse with 2 bottles" + +[b8ef9fce-960e-4d85-a0c9-980a04ec1972] +description = "verse -> single verse -> verse with 1 bottle" + +[c59d4076-f671-4ee3-baaa-d4966801f90d] +description = "verse -> single verse -> verse with 0 bottles" + +[7e17c794-402d-4ca6-8f96-4d8f6ee1ec7e] +description = "lyrics -> multiple verses -> first two verses" + +[949868e7-67e8-43d3-9bb4-69277fe020fb] +description = "lyrics -> multiple verses -> last three verses" + +[bc220626-126c-4e72-8df4-fddfc0c3e458] +description = "lyrics -> multiple verses -> all verses" diff --git a/exercises/practice/beer-song/tests/beer-song.rs b/exercises/practice/beer-song/tests/beer-song.rs index 64fd99df7..41f54fec2 100644 --- a/exercises/practice/beer-song/tests/beer-song.rs +++ b/exercises/practice/beer-song/tests/beer-song.rs @@ -1,36 +1,78 @@ -use beer_song as beer; +mod single_verse { + use beer_song::*; -#[test] -fn verse_0() { - assert_eq!(beer::verse(0), "No more bottles of beer on the wall, no more bottles of beer.\nGo to the store and buy some more, 99 bottles of beer on the wall.\n"); -} + #[test] + fn first_generic_verse() { + assert_eq!( + verse(99).trim(), + "99 bottles of beer on the wall, 99 bottles of beer.\nTake one down and pass it around, 98 bottles of beer on the wall.", + ); + } -#[test] -#[ignore] -fn verse_1() { - assert_eq!(beer::verse(1), "1 bottle of beer on the wall, 1 bottle of beer.\nTake it down and pass it around, no more bottles of beer on the wall.\n"); -} + #[test] + #[ignore] + fn last_generic_verse() { + assert_eq!( + verse(3).trim(), + "3 bottles of beer on the wall, 3 bottles of beer.\nTake one down and pass it around, 2 bottles of beer on the wall.", + ); + } -#[test] -#[ignore] -fn verse_2() { - assert_eq!(beer::verse(2), "2 bottles of beer on the wall, 2 bottles of beer.\nTake one down and pass it around, 1 bottle of beer on the wall.\n"); -} + #[test] + #[ignore] + fn verse_with_2_bottles() { + assert_eq!( + verse(2).trim(), + "2 bottles of beer on the wall, 2 bottles of beer.\nTake one down and pass it around, 1 bottle of beer on the wall.", + ); + } -#[test] -#[ignore] -fn verse_8() { - assert_eq!(beer::verse(8), "8 bottles of beer on the wall, 8 bottles of beer.\nTake one down and pass it around, 7 bottles of beer on the wall.\n"); -} + #[test] + #[ignore] + fn verse_with_1_bottle() { + assert_eq!( + verse(1).trim(), + "1 bottle of beer on the wall, 1 bottle of beer.\nTake it down and pass it around, no more bottles of beer on the wall.", + ); + } -#[test] -#[ignore] -fn song_8_6() { - assert_eq!(beer::sing(8, 6), "8 bottles of beer on the wall, 8 bottles of beer.\nTake one down and pass it around, 7 bottles of beer on the wall.\n\n7 bottles of beer on the wall, 7 bottles of beer.\nTake one down and pass it around, 6 bottles of beer on the wall.\n\n6 bottles of beer on the wall, 6 bottles of beer.\nTake one down and pass it around, 5 bottles of beer on the wall.\n"); + #[test] + #[ignore] + fn verse_with_0_bottles() { + assert_eq!( + verse(0).trim(), + "No more bottles of beer on the wall, no more bottles of beer.\nGo to the store and buy some more, 99 bottles of beer on the wall.", + ); + } } -#[test] -#[ignore] -fn song_3_0() { - assert_eq!(beer::sing(3, 0), "3 bottles of beer on the wall, 3 bottles of beer.\nTake one down and pass it around, 2 bottles of beer on the wall.\n\n2 bottles of beer on the wall, 2 bottles of beer.\nTake one down and pass it around, 1 bottle of beer on the wall.\n\n1 bottle of beer on the wall, 1 bottle of beer.\nTake it down and pass it around, no more bottles of beer on the wall.\n\nNo more bottles of beer on the wall, no more bottles of beer.\nGo to the store and buy some more, 99 bottles of beer on the wall.\n"); +mod multiple_verses { + use beer_song::*; + + #[test] + #[ignore] + fn first_two_verses() { + assert_eq!( + sing(99, 98).trim(), + "99 bottles of beer on the wall, 99 bottles of beer.\nTake one down and pass it around, 98 bottles of beer on the wall.\n\n98 bottles of beer on the wall, 98 bottles of beer.\nTake one down and pass it around, 97 bottles of beer on the wall.", + ); + } + + #[test] + #[ignore] + fn last_three_verses() { + assert_eq!( + sing(2, 0).trim(), + "2 bottles of beer on the wall, 2 bottles of beer.\nTake one down and pass it around, 1 bottle of beer on the wall.\n\n1 bottle of beer on the wall, 1 bottle of beer.\nTake it down and pass it around, no more bottles of beer on the wall.\n\nNo more bottles of beer on the wall, no more bottles of beer.\nGo to the store and buy some more, 99 bottles of beer on the wall.", + ); + } + + #[test] + #[ignore] + fn all_verses() { + assert_eq!( + sing(99, 0).trim(), + "99 bottles of beer on the wall, 99 bottles of beer.\nTake one down and pass it around, 98 bottles of beer on the wall.\n\n98 bottles of beer on the wall, 98 bottles of beer.\nTake one down and pass it around, 97 bottles of beer on the wall.\n\n97 bottles of beer on the wall, 97 bottles of beer.\nTake one down and pass it around, 96 bottles of beer on the wall.\n\n96 bottles of beer on the wall, 96 bottles of beer.\nTake one down and pass it around, 95 bottles of beer on the wall.\n\n95 bottles of beer on the wall, 95 bottles of beer.\nTake one down and pass it around, 94 bottles of beer on the wall.\n\n94 bottles of beer on the wall, 94 bottles of beer.\nTake one down and pass it around, 93 bottles of beer on the wall.\n\n93 bottles of beer on the wall, 93 bottles of beer.\nTake one down and pass it around, 92 bottles of beer on the wall.\n\n92 bottles of beer on the wall, 92 bottles of beer.\nTake one down and pass it around, 91 bottles of beer on the wall.\n\n91 bottles of beer on the wall, 91 bottles of beer.\nTake one down and pass it around, 90 bottles of beer on the wall.\n\n90 bottles of beer on the wall, 90 bottles of beer.\nTake one down and pass it around, 89 bottles of beer on the wall.\n\n89 bottles of beer on the wall, 89 bottles of beer.\nTake one down and pass it around, 88 bottles of beer on the wall.\n\n88 bottles of beer on the wall, 88 bottles of beer.\nTake one down and pass it around, 87 bottles of beer on the wall.\n\n87 bottles of beer on the wall, 87 bottles of beer.\nTake one down and pass it around, 86 bottles of beer on the wall.\n\n86 bottles of beer on the wall, 86 bottles of beer.\nTake one down and pass it around, 85 bottles of beer on the wall.\n\n85 bottles of beer on the wall, 85 bottles of beer.\nTake one down and pass it around, 84 bottles of beer on the wall.\n\n84 bottles of beer on the wall, 84 bottles of beer.\nTake one down and pass it around, 83 bottles of beer on the wall.\n\n83 bottles of beer on the wall, 83 bottles of beer.\nTake one down and pass it around, 82 bottles of beer on the wall.\n\n82 bottles of beer on the wall, 82 bottles of beer.\nTake one down and pass it around, 81 bottles of beer on the wall.\n\n81 bottles of beer on the wall, 81 bottles of beer.\nTake one down and pass it around, 80 bottles of beer on the wall.\n\n80 bottles of beer on the wall, 80 bottles of beer.\nTake one down and pass it around, 79 bottles of beer on the wall.\n\n79 bottles of beer on the wall, 79 bottles of beer.\nTake one down and pass it around, 78 bottles of beer on the wall.\n\n78 bottles of beer on the wall, 78 bottles of beer.\nTake one down and pass it around, 77 bottles of beer on the wall.\n\n77 bottles of beer on the wall, 77 bottles of beer.\nTake one down and pass it around, 76 bottles of beer on the wall.\n\n76 bottles of beer on the wall, 76 bottles of beer.\nTake one down and pass it around, 75 bottles of beer on the wall.\n\n75 bottles of beer on the wall, 75 bottles of beer.\nTake one down and pass it around, 74 bottles of beer on the wall.\n\n74 bottles of beer on the wall, 74 bottles of beer.\nTake one down and pass it around, 73 bottles of beer on the wall.\n\n73 bottles of beer on the wall, 73 bottles of beer.\nTake one down and pass it around, 72 bottles of beer on the wall.\n\n72 bottles of beer on the wall, 72 bottles of beer.\nTake one down and pass it around, 71 bottles of beer on the wall.\n\n71 bottles of beer on the wall, 71 bottles of beer.\nTake one down and pass it around, 70 bottles of beer on the wall.\n\n70 bottles of beer on the wall, 70 bottles of beer.\nTake one down and pass it around, 69 bottles of beer on the wall.\n\n69 bottles of beer on the wall, 69 bottles of beer.\nTake one down and pass it around, 68 bottles of beer on the wall.\n\n68 bottles of beer on the wall, 68 bottles of beer.\nTake one down and pass it around, 67 bottles of beer on the wall.\n\n67 bottles of beer on the wall, 67 bottles of beer.\nTake one down and pass it around, 66 bottles of beer on the wall.\n\n66 bottles of beer on the wall, 66 bottles of beer.\nTake one down and pass it around, 65 bottles of beer on the wall.\n\n65 bottles of beer on the wall, 65 bottles of beer.\nTake one down and pass it around, 64 bottles of beer on the wall.\n\n64 bottles of beer on the wall, 64 bottles of beer.\nTake one down and pass it around, 63 bottles of beer on the wall.\n\n63 bottles of beer on the wall, 63 bottles of beer.\nTake one down and pass it around, 62 bottles of beer on the wall.\n\n62 bottles of beer on the wall, 62 bottles of beer.\nTake one down and pass it around, 61 bottles of beer on the wall.\n\n61 bottles of beer on the wall, 61 bottles of beer.\nTake one down and pass it around, 60 bottles of beer on the wall.\n\n60 bottles of beer on the wall, 60 bottles of beer.\nTake one down and pass it around, 59 bottles of beer on the wall.\n\n59 bottles of beer on the wall, 59 bottles of beer.\nTake one down and pass it around, 58 bottles of beer on the wall.\n\n58 bottles of beer on the wall, 58 bottles of beer.\nTake one down and pass it around, 57 bottles of beer on the wall.\n\n57 bottles of beer on the wall, 57 bottles of beer.\nTake one down and pass it around, 56 bottles of beer on the wall.\n\n56 bottles of beer on the wall, 56 bottles of beer.\nTake one down and pass it around, 55 bottles of beer on the wall.\n\n55 bottles of beer on the wall, 55 bottles of beer.\nTake one down and pass it around, 54 bottles of beer on the wall.\n\n54 bottles of beer on the wall, 54 bottles of beer.\nTake one down and pass it around, 53 bottles of beer on the wall.\n\n53 bottles of beer on the wall, 53 bottles of beer.\nTake one down and pass it around, 52 bottles of beer on the wall.\n\n52 bottles of beer on the wall, 52 bottles of beer.\nTake one down and pass it around, 51 bottles of beer on the wall.\n\n51 bottles of beer on the wall, 51 bottles of beer.\nTake one down and pass it around, 50 bottles of beer on the wall.\n\n50 bottles of beer on the wall, 50 bottles of beer.\nTake one down and pass it around, 49 bottles of beer on the wall.\n\n49 bottles of beer on the wall, 49 bottles of beer.\nTake one down and pass it around, 48 bottles of beer on the wall.\n\n48 bottles of beer on the wall, 48 bottles of beer.\nTake one down and pass it around, 47 bottles of beer on the wall.\n\n47 bottles of beer on the wall, 47 bottles of beer.\nTake one down and pass it around, 46 bottles of beer on the wall.\n\n46 bottles of beer on the wall, 46 bottles of beer.\nTake one down and pass it around, 45 bottles of beer on the wall.\n\n45 bottles of beer on the wall, 45 bottles of beer.\nTake one down and pass it around, 44 bottles of beer on the wall.\n\n44 bottles of beer on the wall, 44 bottles of beer.\nTake one down and pass it around, 43 bottles of beer on the wall.\n\n43 bottles of beer on the wall, 43 bottles of beer.\nTake one down and pass it around, 42 bottles of beer on the wall.\n\n42 bottles of beer on the wall, 42 bottles of beer.\nTake one down and pass it around, 41 bottles of beer on the wall.\n\n41 bottles of beer on the wall, 41 bottles of beer.\nTake one down and pass it around, 40 bottles of beer on the wall.\n\n40 bottles of beer on the wall, 40 bottles of beer.\nTake one down and pass it around, 39 bottles of beer on the wall.\n\n39 bottles of beer on the wall, 39 bottles of beer.\nTake one down and pass it around, 38 bottles of beer on the wall.\n\n38 bottles of beer on the wall, 38 bottles of beer.\nTake one down and pass it around, 37 bottles of beer on the wall.\n\n37 bottles of beer on the wall, 37 bottles of beer.\nTake one down and pass it around, 36 bottles of beer on the wall.\n\n36 bottles of beer on the wall, 36 bottles of beer.\nTake one down and pass it around, 35 bottles of beer on the wall.\n\n35 bottles of beer on the wall, 35 bottles of beer.\nTake one down and pass it around, 34 bottles of beer on the wall.\n\n34 bottles of beer on the wall, 34 bottles of beer.\nTake one down and pass it around, 33 bottles of beer on the wall.\n\n33 bottles of beer on the wall, 33 bottles of beer.\nTake one down and pass it around, 32 bottles of beer on the wall.\n\n32 bottles of beer on the wall, 32 bottles of beer.\nTake one down and pass it around, 31 bottles of beer on the wall.\n\n31 bottles of beer on the wall, 31 bottles of beer.\nTake one down and pass it around, 30 bottles of beer on the wall.\n\n30 bottles of beer on the wall, 30 bottles of beer.\nTake one down and pass it around, 29 bottles of beer on the wall.\n\n29 bottles of beer on the wall, 29 bottles of beer.\nTake one down and pass it around, 28 bottles of beer on the wall.\n\n28 bottles of beer on the wall, 28 bottles of beer.\nTake one down and pass it around, 27 bottles of beer on the wall.\n\n27 bottles of beer on the wall, 27 bottles of beer.\nTake one down and pass it around, 26 bottles of beer on the wall.\n\n26 bottles of beer on the wall, 26 bottles of beer.\nTake one down and pass it around, 25 bottles of beer on the wall.\n\n25 bottles of beer on the wall, 25 bottles of beer.\nTake one down and pass it around, 24 bottles of beer on the wall.\n\n24 bottles of beer on the wall, 24 bottles of beer.\nTake one down and pass it around, 23 bottles of beer on the wall.\n\n23 bottles of beer on the wall, 23 bottles of beer.\nTake one down and pass it around, 22 bottles of beer on the wall.\n\n22 bottles of beer on the wall, 22 bottles of beer.\nTake one down and pass it around, 21 bottles of beer on the wall.\n\n21 bottles of beer on the wall, 21 bottles of beer.\nTake one down and pass it around, 20 bottles of beer on the wall.\n\n20 bottles of beer on the wall, 20 bottles of beer.\nTake one down and pass it around, 19 bottles of beer on the wall.\n\n19 bottles of beer on the wall, 19 bottles of beer.\nTake one down and pass it around, 18 bottles of beer on the wall.\n\n18 bottles of beer on the wall, 18 bottles of beer.\nTake one down and pass it around, 17 bottles of beer on the wall.\n\n17 bottles of beer on the wall, 17 bottles of beer.\nTake one down and pass it around, 16 bottles of beer on the wall.\n\n16 bottles of beer on the wall, 16 bottles of beer.\nTake one down and pass it around, 15 bottles of beer on the wall.\n\n15 bottles of beer on the wall, 15 bottles of beer.\nTake one down and pass it around, 14 bottles of beer on the wall.\n\n14 bottles of beer on the wall, 14 bottles of beer.\nTake one down and pass it around, 13 bottles of beer on the wall.\n\n13 bottles of beer on the wall, 13 bottles of beer.\nTake one down and pass it around, 12 bottles of beer on the wall.\n\n12 bottles of beer on the wall, 12 bottles of beer.\nTake one down and pass it around, 11 bottles of beer on the wall.\n\n11 bottles of beer on the wall, 11 bottles of beer.\nTake one down and pass it around, 10 bottles of beer on the wall.\n\n10 bottles of beer on the wall, 10 bottles of beer.\nTake one down and pass it around, 9 bottles of beer on the wall.\n\n9 bottles of beer on the wall, 9 bottles of beer.\nTake one down and pass it around, 8 bottles of beer on the wall.\n\n8 bottles of beer on the wall, 8 bottles of beer.\nTake one down and pass it around, 7 bottles of beer on the wall.\n\n7 bottles of beer on the wall, 7 bottles of beer.\nTake one down and pass it around, 6 bottles of beer on the wall.\n\n6 bottles of beer on the wall, 6 bottles of beer.\nTake one down and pass it around, 5 bottles of beer on the wall.\n\n5 bottles of beer on the wall, 5 bottles of beer.\nTake one down and pass it around, 4 bottles of beer on the wall.\n\n4 bottles of beer on the wall, 4 bottles of beer.\nTake one down and pass it around, 3 bottles of beer on the wall.\n\n3 bottles of beer on the wall, 3 bottles of beer.\nTake one down and pass it around, 2 bottles of beer on the wall.\n\n2 bottles of beer on the wall, 2 bottles of beer.\nTake one down and pass it around, 1 bottle of beer on the wall.\n\n1 bottle of beer on the wall, 1 bottle of beer.\nTake it down and pass it around, no more bottles of beer on the wall.\n\nNo more bottles of beer on the wall, no more bottles of beer.\nGo to the store and buy some more, 99 bottles of beer on the wall.", + ); + } } From 938cac5330e8938ab1f4109f28450a9b1abc2456 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 13 Aug 2024 14:43:15 +0200 Subject: [PATCH 328/436] bob: sync (#1962) [no important files changed] --- .../practice/bob/.meta/test_template.tera | 9 + exercises/practice/bob/.meta/tests.toml | 82 ++++++++- exercises/practice/bob/tests/bob.rs | 156 ++++++++---------- 3 files changed, 155 insertions(+), 92 deletions(-) create mode 100644 exercises/practice/bob/.meta/test_template.tera diff --git a/exercises/practice/bob/.meta/test_template.tera b/exercises/practice/bob/.meta/test_template.tera new file mode 100644 index 000000000..dd7557012 --- /dev/null +++ b/exercises/practice/bob/.meta/test_template.tera @@ -0,0 +1,9 @@ +use bob::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | snake_case }}() { + assert_eq!(reply({{ test.input.heyBob | json_encode() }}), "{{ test.expected }}"); +} +{% endfor -%} diff --git a/exercises/practice/bob/.meta/tests.toml b/exercises/practice/bob/.meta/tests.toml index f357dccdb..ea47d6bb0 100644 --- a/exercises/practice/bob/.meta/tests.toml +++ b/exercises/practice/bob/.meta/tests.toml @@ -1,9 +1,85 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[e162fead-606f-437a-a166-d051915cea8e] +description = "stating something" [73a966dc-8017-47d6-bb32-cf07d1a5fcd9] description = "shouting" +[d6c98afd-df35-4806-b55e-2c457c3ab748] +description = "shouting gibberish" + +[8a2e771d-d6f1-4e3f-b6c6-b41495556e37] +description = "asking a question" + +[81080c62-4e4d-4066-b30a-48d8d76920d9] +description = "asking a numeric question" + +[2a02716d-685b-4e2e-a804-2adaf281c01e] +description = "asking gibberish" + +[c02f9179-ab16-4aa7-a8dc-940145c385f7] +description = "talking forcefully" + +[153c0e25-9bb5-4ec5-966e-598463658bcd] +description = "using acronyms in regular speech" + +[a5193c61-4a92-4f68-93e2-f554eb385ec6] +description = "forceful question" + +[a20e0c54-2224-4dde-8b10-bd2cdd4f61bc] +description = "shouting numbers" + +[f7bc4b92-bdff-421e-a238-ae97f230ccac] +description = "no letters" + +[bb0011c5-cd52-4a5b-8bfb-a87b6283b0e2] +description = "question with no letters" + +[496143c8-1c31-4c01-8a08-88427af85c66] +description = "shouting with special characters" + +[e6793c1c-43bd-4b8d-bc11-499aea73925f] +description = "shouting with no exclamation mark" + +[aa8097cc-c548-4951-8856-14a404dd236a] +description = "statement containing question mark" + +[9bfc677d-ea3a-45f2-be44-35bc8fa3753e] +description = "non-letters with question" + +[8608c508-f7de-4b17-985b-811878b3cf45] +description = "prattling on" + [bc39f7c6-f543-41be-9a43-fd1c2f753fc0] description = "silence" + +[d6c47565-372b-4b09-b1dd-c40552b8378b] +description = "prolonged silence" + +[4428f28d-4100-4d85-a902-e5a78cb0ecd3] +description = "alternate silence" + +[66953780-165b-4e7e-8ce3-4bcb80b6385a] +description = "multiple line question" + +[5371ef75-d9ea-4103-bcfa-2da973ddec1b] +description = "starting with whitespace" + +[05b304d6-f83b-46e7-81e0-4cd3ca647900] +description = "ending with whitespace" + +[72bd5ad3-9b2f-4931-a988-dce1f5771de2] +description = "other whitespace" + +[12983553-8601-46a8-92fa-fcaa3bc4a2a0] +description = "non-question ending with whitespace" diff --git a/exercises/practice/bob/tests/bob.rs b/exercises/practice/bob/tests/bob.rs index fdee5588f..4fa71681c 100644 --- a/exercises/practice/bob/tests/bob.rs +++ b/exercises/practice/bob/tests/bob.rs @@ -1,190 +1,168 @@ -fn process_response_case(phrase: &str, expected_response: &str) { - assert_eq!(bob::reply(phrase), expected_response); -} +use bob::*; #[test] -/// stating something fn stating_something() { - process_response_case("Tom-ay-to, tom-aaaah-to.", "Whatever."); + assert_eq!(reply("Tom-ay-to, tom-aaaah-to."), "Whatever."); } #[test] #[ignore] -/// ending with whitespace -fn ending_with_whitespace() { - process_response_case("Okay if like my spacebar quite a bit? ", "Sure."); +fn shouting() { + assert_eq!(reply("WATCH OUT!"), "Whoa, chill out!"); } #[test] #[ignore] -/// shouting numbers -fn shouting_numbers() { - process_response_case("1, 2, 3 GO!", "Whoa, chill out!"); +fn shouting_gibberish() { + assert_eq!(reply("FCECDFCAAB"), "Whoa, chill out!"); } #[test] #[ignore] -/// other whitespace -fn other_whitespace() { - process_response_case("\r\r ", "Fine. Be that way!"); +fn asking_a_question() { + assert_eq!( + reply("Does this cryogenic chamber make me look fat?"), + "Sure." + ); } #[test] #[ignore] -/// shouting with special characters -fn shouting_with_special_characters() { - process_response_case( - "ZOMG THE %^*@#$(*^ ZOMBIES ARE COMING!!11!!1!", - "Whoa, chill out!", - ); +fn asking_a_numeric_question() { + assert_eq!(reply("You are, what, like 15?"), "Sure."); } #[test] #[ignore] -/// talking forcefully -fn talking_forcefully() { - process_response_case("Hi there!", "Whatever."); +fn asking_gibberish() { + assert_eq!(reply("fffbbcbeab?"), "Sure."); } #[test] #[ignore] -/// prattling on -fn prattling_on() { - process_response_case("Wait! Hang on. Are you going to be OK?", "Sure."); +fn talking_forcefully() { + assert_eq!(reply("Hi there!"), "Whatever."); } #[test] #[ignore] -/// forceful question -fn forceful_question() { - process_response_case("WHAT'S GOING ON?", "Calm down, I know what I'm doing!"); +fn using_acronyms_in_regular_speech() { + assert_eq!( + reply("It's OK if you don't want to go work for NASA."), + "Whatever." + ); } #[test] #[ignore] -/// shouting with no exclamation mark -fn shouting_with_no_exclamation_mark() { - process_response_case("I HATE THE DENTIST", "Whoa, chill out!"); +fn forceful_question() { + assert_eq!( + reply("WHAT'S GOING ON?"), + "Calm down, I know what I'm doing!" + ); } #[test] #[ignore] -/// asking gibberish -fn asking_gibberish() { - process_response_case("fffbbcbeab?", "Sure."); +fn shouting_numbers() { + assert_eq!(reply("1, 2, 3 GO!"), "Whoa, chill out!"); } #[test] #[ignore] -/// question with no letters -fn question_with_no_letters() { - process_response_case("4?", "Sure."); +fn no_letters() { + assert_eq!(reply("1, 2, 3"), "Whatever."); } #[test] #[ignore] -/// no letters -fn no_letters() { - process_response_case("1, 2, 3", "Whatever."); +fn question_with_no_letters() { + assert_eq!(reply("4?"), "Sure."); } #[test] #[ignore] -/// statement containing question mark -fn statement_containing_question_mark() { - process_response_case("Ending with ? means a question.", "Whatever."); +fn shouting_with_special_characters() { + assert_eq!( + reply("ZOMG THE %^*@#$(*^ ZOMBIES ARE COMING!!11!!1!"), + "Whoa, chill out!" + ); } -//NEW #[test] #[ignore] -/// multiple line question -fn multiple_line_question() { - process_response_case( - "\rDoes this cryogenic chamber make me look fat?\rNo.", - "Whatever.", - ); +fn shouting_with_no_exclamation_mark() { + assert_eq!(reply("I HATE THE DENTIST"), "Whoa, chill out!"); } #[test] #[ignore] -/// non-question ending with whitespace -fn nonquestion_ending_with_whitespace() { - process_response_case( - "This is a statement ending with whitespace ", - "Whatever.", - ); +fn statement_containing_question_mark() { + assert_eq!(reply("Ending with ? means a question."), "Whatever."); } #[test] #[ignore] -/// shouting -fn shouting() { - process_response_case("WATCH OUT!", "Whoa, chill out!"); +fn non_letters_with_question() { + assert_eq!(reply(":) ?"), "Sure."); } #[test] #[ignore] -/// non-letters with question -fn nonletters_with_question() { - process_response_case(":) ?", "Sure."); +fn prattling_on() { + assert_eq!(reply("Wait! Hang on. Are you going to be OK?"), "Sure."); } #[test] #[ignore] -/// shouting gibberish -fn shouting_gibberish() { - process_response_case("FCECDFCAAB", "Whoa, chill out!"); +fn silence() { + assert_eq!(reply(""), "Fine. Be that way!"); } #[test] #[ignore] -/// asking a question -fn asking_a_question() { - process_response_case("Does this cryogenic chamber make me look fat?", "Sure."); +fn prolonged_silence() { + assert_eq!(reply(" "), "Fine. Be that way!"); } #[test] #[ignore] -/// asking a numeric question -fn asking_a_numeric_question() { - process_response_case("You are, what, like 15?", "Sure."); +fn alternate_silence() { + assert_eq!(reply("\t\t\t\t\t\t\t\t\t\t"), "Fine. Be that way!"); } #[test] #[ignore] -/// silence -fn silence() { - process_response_case("", "Fine. Be that way!"); +fn multiple_line_question() { + assert_eq!( + reply("\nDoes this cryogenic chamber make me look fat?\nNo."), + "Whatever." + ); } #[test] #[ignore] -/// starting with whitespace fn starting_with_whitespace() { - process_response_case(" hmmmmmmm...", "Whatever."); + assert_eq!(reply(" hmmmmmmm..."), "Whatever."); } #[test] #[ignore] -/// using acronyms in regular speech -fn using_acronyms_in_regular_speech() { - process_response_case( - "It's OK if you don't want to go work for NASA.", - "Whatever.", - ); +fn ending_with_whitespace() { + assert_eq!(reply("Okay if like my spacebar quite a bit? "), "Sure."); } #[test] #[ignore] -/// alternate silence -fn alternate_silence() { - process_response_case(" ", "Fine. Be that way!"); +fn other_whitespace() { + assert_eq!(reply("\n\r \t"), "Fine. Be that way!"); } #[test] #[ignore] -/// prolonged silence -fn prolonged_silence() { - process_response_case(" ", "Fine. Be that way!"); +fn non_question_ending_with_whitespace() { + assert_eq!( + reply("This is a statement ending with whitespace "), + "Whatever." + ); } From 08386e5269f1e744974261479b212f46cd9fd474 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Wed, 14 Aug 2024 10:00:14 +0200 Subject: [PATCH 329/436] fizzy: remove macro from tests (#1964) [no important files changed] part of https://github.com/exercism/rust/issues/1824 --- exercises/practice/fizzy/tests/fizzy.rs | 71 +++++++++++++------------ 1 file changed, 38 insertions(+), 33 deletions(-) diff --git a/exercises/practice/fizzy/tests/fizzy.rs b/exercises/practice/fizzy/tests/fizzy.rs index f496ca45b..517fcc194 100644 --- a/exercises/practice/fizzy/tests/fizzy.rs +++ b/exercises/practice/fizzy/tests/fizzy.rs @@ -1,51 +1,54 @@ use fizzy::*; -macro_rules! expect { - () => { - vec![ - "1", "2", "fizz", "4", "buzz", "fizz", "7", "8", "fizz", "buzz", "11", "fizz", "13", - "14", "fizzbuzz", "16", - ] - }; -} - #[test] fn simple() { - let got = fizz_buzz::().apply(1..=16).collect::>(); - assert_eq!(expect!(), got); + let actual = fizz_buzz::().apply(1..=16).collect::>(); + let expected = [ + "1", "2", "fizz", "4", "buzz", "fizz", "7", "8", "fizz", "buzz", "11", "fizz", "13", "14", + "fizzbuzz", "16", + ]; + assert_eq!(actual, expected); } #[test] #[ignore] fn u8() { - let got = fizz_buzz::().apply(1_u8..=16).collect::>(); - assert_eq!(expect!(), got); + let actual = fizz_buzz::().apply(1_u8..=16).collect::>(); + let expected = [ + "1", "2", "fizz", "4", "buzz", "fizz", "7", "8", "fizz", "buzz", "11", "fizz", "13", "14", + "fizzbuzz", "16", + ]; + assert_eq!(actual, expected); } #[test] #[ignore] fn u64() { - let got = fizz_buzz::().apply(1_u64..=16).collect::>(); - assert_eq!(expect!(), got); + let actual = fizz_buzz::().apply(1_u64..=16).collect::>(); + let expected = [ + "1", "2", "fizz", "4", "buzz", "fizz", "7", "8", "fizz", "buzz", "11", "fizz", "13", "14", + "fizzbuzz", "16", + ]; + assert_eq!(actual, expected); } #[test] #[ignore] fn nonsequential() { let collatz_12 = &[12, 6, 3, 10, 5, 16, 8, 4, 2, 1]; - let expect = vec![ - "fizz", "fizz", "fizz", "buzz", "buzz", "16", "8", "4", "2", "1", - ]; - let got = fizz_buzz::() + let actual = fizz_buzz::() .apply(collatz_12.iter().cloned()) .collect::>(); - assert_eq!(expect, got); + let expected = vec![ + "fizz", "fizz", "fizz", "buzz", "buzz", "16", "8", "4", "2", "1", + ]; + assert_eq!(actual, expected); } #[test] #[ignore] fn custom() { - let expect = vec![ + let expected = vec![ "1", "2", "Fizz", "4", "Buzz", "Fizz", "Bam", "8", "Fizz", "Buzz", "11", "Fizz", "13", "Bam", "BuzzFizz", "16", ]; @@ -53,30 +56,28 @@ fn custom() { .add_matcher(Matcher::new(|n: i32| n % 5 == 0, "Buzz")) .add_matcher(Matcher::new(|n: i32| n % 3 == 0, "Fizz")) .add_matcher(Matcher::new(|n: i32| n % 7 == 0, "Bam")); - let got = fizzer.apply(1..=16).collect::>(); - assert_eq!(expect, got); + let actual = fizzer.apply(1..=16).collect::>(); + assert_eq!(actual, expected); } #[test] #[ignore] fn f64() { // a tiny bit more complicated becuase range isn't natively implemented on floats - // NOTE: this test depends on a language feature introduced in Rust 1.34. If you - // have an older compiler, upgrade. If you have an older compiler and cannot upgrade, - // feel free to ignore this test. - let got = fizz_buzz::() + let actual = fizz_buzz::() .apply(std::iter::successors(Some(1.0), |prev| Some(prev + 1.0))) .take(16) .collect::>(); - assert_eq!(expect!(), got); + let expected = [ + "1", "2", "fizz", "4", "buzz", "fizz", "7", "8", "fizz", "buzz", "11", "fizz", "13", "14", + "fizzbuzz", "16", + ]; + assert_eq!(actual, expected); } #[test] #[ignore] fn minimal_generic_bounds() { - // NOTE: this test depends on a language feature introduced in Rust 1.34. If you - // have an older compiler, upgrade. If you have an older compiler and cannot upgrade, - // feel free to ignore this test. use std::fmt; use std::ops::{Add, Rem}; @@ -114,11 +115,15 @@ fn minimal_generic_bounds() { } } - let got = fizz_buzz::() + let actual = fizz_buzz::() .apply(std::iter::successors(Some(Fizzable(1)), |prev| { Some(*prev + 1.into()) })) .take(16) .collect::>(); - assert_eq!(expect!(), got); + let expected = [ + "1", "2", "fizz", "4", "buzz", "fizz", "7", "8", "fizz", "buzz", "11", "fizz", "13", "14", + "fizzbuzz", "16", + ]; + assert_eq!(actual, expected); } From f29b58c498cb2901f76dd783a5fd9e6cbbf95b81 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Wed, 14 Aug 2024 10:01:29 +0200 Subject: [PATCH 330/436] Make number literals more human readable (#1963) [no important files changed] --- .ignore | 1 + .../.meta/test_template.tera | 3 +- .../tests/collatz-conjecture.rs | 7 +--- .../eliuds-eggs/.meta/test_template.tera | 2 +- .../practice/eliuds-eggs/tests/eliuds-eggs.rs | 2 +- .../nth-prime/.meta/test_template.tera | 4 +-- .../practice/nth-prime/tests/nth-prime.rs | 4 +-- .../perfect-numbers/.meta/test_template.tera | 2 +- .../perfect-numbers/tests/perfect-numbers.rs | 6 ++-- .../prime-factors/.meta/test_template.tera | 6 ++-- .../prime-factors/tests/prime-factors.rs | 6 ++-- .../.meta/test_template.tera | 8 +++-- .../tests/pythagorean-triplet.rs | 16 ++++----- .../practice/say/.meta/test_template.tera | 2 +- exercises/practice/say/tests/say.rs | 16 ++++----- .../space-age/.meta/test_template.tera | 2 +- .../practice/space-age/tests/space-age.rs | 16 ++++----- .../sum-of-multiples/.meta/test_template.tera | 4 +-- .../tests/sum-of-multiples.rs | 20 +++++------ .../.meta/test_template.tera | 8 +++-- .../tests/variable-length-quantity.rs | 36 +++++++++---------- 21 files changed, 88 insertions(+), 83 deletions(-) create mode 100644 .ignore diff --git a/.ignore b/.ignore new file mode 100644 index 000000000..6a275d68e --- /dev/null +++ b/.ignore @@ -0,0 +1 @@ +!problem-specifications diff --git a/exercises/practice/collatz-conjecture/.meta/test_template.tera b/exercises/practice/collatz-conjecture/.meta/test_template.tera index eec8050c3..8a4d405ce 100644 --- a/exercises/practice/collatz-conjecture/.meta/test_template.tera +++ b/exercises/practice/collatz-conjecture/.meta/test_template.tera @@ -4,8 +4,7 @@ use collatz_conjecture::*; #[test] #[ignore] fn {{ test.description | snake_case }}() { - let output = collatz({{ test.input.number | json_encode() }}); - + let output = collatz({{ test.input.number | fmt_num }}); let expected = {% if test.expected is object %} None {% else %} diff --git a/exercises/practice/collatz-conjecture/tests/collatz-conjecture.rs b/exercises/practice/collatz-conjecture/tests/collatz-conjecture.rs index 9bbced3ec..8ccceefc2 100644 --- a/exercises/practice/collatz-conjecture/tests/collatz-conjecture.rs +++ b/exercises/practice/collatz-conjecture/tests/collatz-conjecture.rs @@ -3,7 +3,6 @@ use collatz_conjecture::*; #[test] fn zero_steps_for_one() { let output = collatz(1); - let expected = Some(0); assert_eq!(output, expected); } @@ -12,7 +11,6 @@ fn zero_steps_for_one() { #[ignore] fn divide_if_even() { let output = collatz(16); - let expected = Some(4); assert_eq!(output, expected); } @@ -21,7 +19,6 @@ fn divide_if_even() { #[ignore] fn even_and_odd_steps() { let output = collatz(12); - let expected = Some(9); assert_eq!(output, expected); } @@ -29,8 +26,7 @@ fn even_and_odd_steps() { #[test] #[ignore] fn large_number_of_even_and_odd_steps() { - let output = collatz(1000000); - + let output = collatz(1_000_000); let expected = Some(152); assert_eq!(output, expected); } @@ -39,7 +35,6 @@ fn large_number_of_even_and_odd_steps() { #[ignore] fn zero_is_an_error() { let output = collatz(0); - let expected = None; assert_eq!(output, expected); } diff --git a/exercises/practice/eliuds-eggs/.meta/test_template.tera b/exercises/practice/eliuds-eggs/.meta/test_template.tera index 318242671..9a2306d5b 100644 --- a/exercises/practice/eliuds-eggs/.meta/test_template.tera +++ b/exercises/practice/eliuds-eggs/.meta/test_template.tera @@ -4,7 +4,7 @@ use eliuds_eggs::*; #[test] #[ignore] fn test_{{ test.description | snake_case }}() { - let input = {{ test.input.number }}; + let input = {{ test.input.number | fmt_num }}; let output = egg_count(input); let expected = {{ test.expected | json_encode() }}; assert_eq!(output, expected); diff --git a/exercises/practice/eliuds-eggs/tests/eliuds-eggs.rs b/exercises/practice/eliuds-eggs/tests/eliuds-eggs.rs index 9204d0e77..d34e79d1f 100644 --- a/exercises/practice/eliuds-eggs/tests/eliuds-eggs.rs +++ b/exercises/practice/eliuds-eggs/tests/eliuds-eggs.rs @@ -29,7 +29,7 @@ fn test_4_eggs() { #[test] #[ignore] fn test_13_eggs() { - let input = 2000000000; + let input = 2_000_000_000; let output = egg_count(input); let expected = 13; assert_eq!(output, expected); diff --git a/exercises/practice/nth-prime/.meta/test_template.tera b/exercises/practice/nth-prime/.meta/test_template.tera index a4c4b8d26..4f3e44aa2 100644 --- a/exercises/practice/nth-prime/.meta/test_template.tera +++ b/exercises/practice/nth-prime/.meta/test_template.tera @@ -4,8 +4,8 @@ use nth_prime::*; #[test] #[ignore] fn {{ test.description | snake_case }}() { - let output = nth({{ test.input.number - 1 }}); - let expected = {{ test.expected | json_encode() }}; + let output = nth({{ test.input.number - 1 | fmt_num }}); + let expected = {{ test.expected | fmt_num }}; assert_eq!(output, expected); } {% endfor -%} diff --git a/exercises/practice/nth-prime/tests/nth-prime.rs b/exercises/practice/nth-prime/tests/nth-prime.rs index 19e6433fc..3a496849a 100644 --- a/exercises/practice/nth-prime/tests/nth-prime.rs +++ b/exercises/practice/nth-prime/tests/nth-prime.rs @@ -26,7 +26,7 @@ fn sixth_prime() { #[test] #[ignore] fn big_prime() { - let output = nth(10000); - let expected = 104743; + let output = nth(10_000); + let expected = 104_743; assert_eq!(output, expected); } diff --git a/exercises/practice/perfect-numbers/.meta/test_template.tera b/exercises/practice/perfect-numbers/.meta/test_template.tera index 4f3e334b4..8d90392dc 100644 --- a/exercises/practice/perfect-numbers/.meta/test_template.tera +++ b/exercises/practice/perfect-numbers/.meta/test_template.tera @@ -5,7 +5,7 @@ use perfect_numbers::*; #[test] #[ignore] fn {{ test.description | snake_case }}() { - let input = {{ test.input.number | json_encode() }}; + let input = {{ test.input.number | fmt_num }}; let output = classify(input); {%- if test.expected is object %} assert!(output.is_none()); diff --git a/exercises/practice/perfect-numbers/tests/perfect-numbers.rs b/exercises/practice/perfect-numbers/tests/perfect-numbers.rs index fd29de0d9..582862278 100644 --- a/exercises/practice/perfect-numbers/tests/perfect-numbers.rs +++ b/exercises/practice/perfect-numbers/tests/perfect-numbers.rs @@ -20,7 +20,7 @@ fn medium_perfect_number_is_classified_correctly() { #[test] #[ignore] fn large_perfect_number_is_classified_correctly() { - let input = 33550336; + let input = 33_550_336; let output = classify(input); let expected = Some(Classification::Perfect); assert_eq!(output, expected); @@ -47,7 +47,7 @@ fn medium_abundant_number_is_classified_correctly() { #[test] #[ignore] fn large_abundant_number_is_classified_correctly() { - let input = 33550335; + let input = 33_550_335; let output = classify(input); let expected = Some(Classification::Abundant); assert_eq!(output, expected); @@ -83,7 +83,7 @@ fn medium_deficient_number_is_classified_correctly() { #[test] #[ignore] fn large_deficient_number_is_classified_correctly() { - let input = 33550337; + let input = 33_550_337; let output = classify(input); let expected = Some(Classification::Deficient); assert_eq!(output, expected); diff --git a/exercises/practice/prime-factors/.meta/test_template.tera b/exercises/practice/prime-factors/.meta/test_template.tera index 9c8604d1e..2a0bbb62b 100644 --- a/exercises/practice/prime-factors/.meta/test_template.tera +++ b/exercises/practice/prime-factors/.meta/test_template.tera @@ -4,8 +4,10 @@ use prime_factors::*; #[test] #[ignore] fn {{ test.description | snake_case }}() { - let factors = factors({{ test.input.value }}); - let expected = {{ test.expected | json_encode() }}; + let factors = factors({{ test.input.value | fmt_num }}); + let expected = [{% for factor in test.expected -%} + {{ factor | fmt_num }}, + {%- endfor %}]; assert_eq!(factors, expected); } {% endfor -%} diff --git a/exercises/practice/prime-factors/tests/prime-factors.rs b/exercises/practice/prime-factors/tests/prime-factors.rs index c5b67581c..119c9c8a9 100644 --- a/exercises/practice/prime-factors/tests/prime-factors.rs +++ b/exercises/practice/prime-factors/tests/prime-factors.rs @@ -82,7 +82,7 @@ fn product_of_primes_and_non_primes() { #[test] #[ignore] fn product_of_primes() { - let factors = factors(901255); + let factors = factors(901_255); let expected = [5, 17, 23, 461]; assert_eq!(factors, expected); } @@ -90,7 +90,7 @@ fn product_of_primes() { #[test] #[ignore] fn factors_include_a_large_prime() { - let factors = factors(93819012551); - let expected = [11, 9539, 894119]; + let factors = factors(93_819_012_551); + let expected = [11, 9_539, 894_119]; assert_eq!(factors, expected); } diff --git a/exercises/practice/pythagorean-triplet/.meta/test_template.tera b/exercises/practice/pythagorean-triplet/.meta/test_template.tera index 19fa6ab94..6801d172b 100644 --- a/exercises/practice/pythagorean-triplet/.meta/test_template.tera +++ b/exercises/practice/pythagorean-triplet/.meta/test_template.tera @@ -5,9 +5,13 @@ use std::collections::HashSet; #[test] #[ignore] fn {{ test.description | snake_case }}() { - let input = {{ test.input.n | json_encode() }}; + let input = {{ test.input.n | fmt_num }}; let output = find(input); - let expected = {{ test.expected | json_encode() }}; + let expected = [{% for triple in test.expected -%} + [{% for side in triple -%} + {{ side | fmt_num }}, + {%- endfor %}], + {%- endfor %}]; let expected: HashSet<_> = expected.iter().cloned().collect(); assert_eq!(output, expected); } diff --git a/exercises/practice/pythagorean-triplet/tests/pythagorean-triplet.rs b/exercises/practice/pythagorean-triplet/tests/pythagorean-triplet.rs index a10322554..9568f9095 100644 --- a/exercises/practice/pythagorean-triplet/tests/pythagorean-triplet.rs +++ b/exercises/practice/pythagorean-triplet/tests/pythagorean-triplet.rs @@ -24,7 +24,7 @@ fn triplets_whose_sum_is_108() { #[test] #[ignore] fn triplets_whose_sum_is_1000() { - let input = 1000; + let input = 1_000; let output = find(input); let expected = [[200, 375, 425]]; let expected: HashSet<_> = expected.iter().cloned().collect(); @@ -34,7 +34,7 @@ fn triplets_whose_sum_is_1000() { #[test] #[ignore] fn no_matching_triplets_for_1001() { - let input = 1001; + let input = 1_001; let output = find(input); let expected = []; let expected: HashSet<_> = expected.iter().cloned().collect(); @@ -73,14 +73,14 @@ fn several_matching_triplets() { #[test] #[ignore] fn triplets_for_large_number() { - let input = 30000; + let input = 30_000; let output = find(input); let expected = [ - [1200, 14375, 14425], - [1875, 14000, 14125], - [5000, 12000, 13000], - [6000, 11250, 12750], - [7500, 10000, 12500], + [1_200, 14_375, 14_425], + [1_875, 14_000, 14_125], + [5_000, 12_000, 13_000], + [6_000, 11_250, 12_750], + [7_500, 10_000, 12_500], ]; let expected: HashSet<_> = expected.iter().cloned().collect(); assert_eq!(output, expected); diff --git a/exercises/practice/say/.meta/test_template.tera b/exercises/practice/say/.meta/test_template.tera index b67fcdf1d..ab79d73cd 100644 --- a/exercises/practice/say/.meta/test_template.tera +++ b/exercises/practice/say/.meta/test_template.tera @@ -4,7 +4,7 @@ use say::*; #[test] #[ignore] fn {{ test.description | snake_case }}() { - let input = {{ test.input.number | json_encode() }}; + let input = {{ test.input.number | fmt_num }}; let output = encode(input); let expected = {{ test.expected | json_encode() }}; assert_eq!(output, expected); diff --git a/exercises/practice/say/tests/say.rs b/exercises/practice/say/tests/say.rs index 78cb7a35c..6d902f4fb 100644 --- a/exercises/practice/say/tests/say.rs +++ b/exercises/practice/say/tests/say.rs @@ -101,7 +101,7 @@ fn nine_hundred_ninety_nine() { #[test] #[ignore] fn one_thousand() { - let input = 1000; + let input = 1_000; let output = encode(input); let expected = "one thousand"; assert_eq!(output, expected); @@ -110,7 +110,7 @@ fn one_thousand() { #[test] #[ignore] fn one_thousand_two_hundred_thirty_four() { - let input = 1234; + let input = 1_234; let output = encode(input); let expected = "one thousand two hundred thirty-four"; assert_eq!(output, expected); @@ -119,7 +119,7 @@ fn one_thousand_two_hundred_thirty_four() { #[test] #[ignore] fn one_million() { - let input = 1000000; + let input = 1_000_000; let output = encode(input); let expected = "one million"; assert_eq!(output, expected); @@ -128,7 +128,7 @@ fn one_million() { #[test] #[ignore] fn one_million_two_thousand_three_hundred_forty_five() { - let input = 1002345; + let input = 1_002_345; let output = encode(input); let expected = "one million two thousand three hundred forty-five"; assert_eq!(output, expected); @@ -137,7 +137,7 @@ fn one_million_two_thousand_three_hundred_forty_five() { #[test] #[ignore] fn one_billion() { - let input = 1000000000; + let input = 1_000_000_000; let output = encode(input); let expected = "one billion"; assert_eq!(output, expected); @@ -146,7 +146,7 @@ fn one_billion() { #[test] #[ignore] fn a_big_number() { - let input = 987654321123; + let input = 987_654_321_123; let output = encode(input); let expected = "nine hundred eighty-seven billion six hundred fifty-four million three hundred twenty-one thousand one hundred twenty-three"; assert_eq!(output, expected); @@ -155,7 +155,7 @@ fn a_big_number() { #[test] #[ignore] fn max_i64() { - let input = 9223372036854775807; + let input = 9_223_372_036_854_775_807; let output = encode(input); let expected = "nine quintillion two hundred twenty-three quadrillion three hundred seventy-two trillion thirty-six billion eight hundred fifty-four million seven hundred seventy-five thousand eight hundred seven"; assert_eq!(output, expected); @@ -164,7 +164,7 @@ fn max_i64() { #[test] #[ignore] fn max_u64() { - let input = 18446744073709551615; + let input = 18_446_744_073_709_551_615; let output = encode(input); let expected = "eighteen quintillion four hundred forty-six quadrillion seven hundred forty-four trillion seventy-three billion seven hundred nine million five hundred fifty-one thousand six hundred fifteen"; assert_eq!(output, expected); diff --git a/exercises/practice/space-age/.meta/test_template.tera b/exercises/practice/space-age/.meta/test_template.tera index 01d6d1c69..a69f248ef 100644 --- a/exercises/practice/space-age/.meta/test_template.tera +++ b/exercises/practice/space-age/.meta/test_template.tera @@ -11,7 +11,7 @@ fn assert_in_delta(expected: f64, actual: f64) { #[test] #[ignore] fn {{ test.description | snake_case }}() { - let seconds = {{ test.input.seconds | json_encode() }}; + let seconds = {{ test.input.seconds | fmt_num }}; let duration = Duration::from(seconds); let output = {{ test.input.planet }}::years_during(&duration); let expected = {{ test.expected | json_encode() }}; diff --git a/exercises/practice/space-age/tests/space-age.rs b/exercises/practice/space-age/tests/space-age.rs index 9f379aff8..995feedb1 100644 --- a/exercises/practice/space-age/tests/space-age.rs +++ b/exercises/practice/space-age/tests/space-age.rs @@ -10,7 +10,7 @@ fn assert_in_delta(expected: f64, actual: f64) { #[test] fn age_on_earth() { - let seconds = 1000000000; + let seconds = 1_000_000_000; let duration = Duration::from(seconds); let output = Earth::years_during(&duration); let expected = 31.69; @@ -20,7 +20,7 @@ fn age_on_earth() { #[test] #[ignore] fn age_on_mercury() { - let seconds = 2134835688; + let seconds = 2_134_835_688; let duration = Duration::from(seconds); let output = Mercury::years_during(&duration); let expected = 280.88; @@ -30,7 +30,7 @@ fn age_on_mercury() { #[test] #[ignore] fn age_on_venus() { - let seconds = 189839836; + let seconds = 189_839_836; let duration = Duration::from(seconds); let output = Venus::years_during(&duration); let expected = 9.78; @@ -40,7 +40,7 @@ fn age_on_venus() { #[test] #[ignore] fn age_on_mars() { - let seconds = 2129871239; + let seconds = 2_129_871_239; let duration = Duration::from(seconds); let output = Mars::years_during(&duration); let expected = 35.88; @@ -50,7 +50,7 @@ fn age_on_mars() { #[test] #[ignore] fn age_on_jupiter() { - let seconds = 901876382; + let seconds = 901_876_382; let duration = Duration::from(seconds); let output = Jupiter::years_during(&duration); let expected = 2.41; @@ -60,7 +60,7 @@ fn age_on_jupiter() { #[test] #[ignore] fn age_on_saturn() { - let seconds = 2000000000; + let seconds = 2_000_000_000; let duration = Duration::from(seconds); let output = Saturn::years_during(&duration); let expected = 2.15; @@ -70,7 +70,7 @@ fn age_on_saturn() { #[test] #[ignore] fn age_on_uranus() { - let seconds = 1210123456; + let seconds = 1_210_123_456; let duration = Duration::from(seconds); let output = Uranus::years_during(&duration); let expected = 0.46; @@ -80,7 +80,7 @@ fn age_on_uranus() { #[test] #[ignore] fn age_on_neptune() { - let seconds = 1821023456; + let seconds = 1_821_023_456; let duration = Duration::from(seconds); let output = Neptune::years_during(&duration); let expected = 0.35; diff --git a/exercises/practice/sum-of-multiples/.meta/test_template.tera b/exercises/practice/sum-of-multiples/.meta/test_template.tera index bf40be46b..a4f690cbd 100644 --- a/exercises/practice/sum-of-multiples/.meta/test_template.tera +++ b/exercises/practice/sum-of-multiples/.meta/test_template.tera @@ -5,9 +5,9 @@ use sum_of_multiples::*; #[ignore] fn {{ test.description | snake_case }}() { let factors = &{{ test.input.factors | json_encode() }}; - let limit = {{ test.input.limit | json_encode() }}; + let limit = {{ test.input.limit | fmt_num }}; let output = sum_of_multiples(limit, factors); - let expected = {{ test.expected | json_encode() }}; + let expected = {{ test.expected | fmt_num }}; assert_eq!(output, expected); } {% endfor -%} diff --git a/exercises/practice/sum-of-multiples/tests/sum-of-multiples.rs b/exercises/practice/sum-of-multiples/tests/sum-of-multiples.rs index 40c0dbdbb..9864598ce 100644 --- a/exercises/practice/sum-of-multiples/tests/sum-of-multiples.rs +++ b/exercises/practice/sum-of-multiples/tests/sum-of-multiples.rs @@ -45,7 +45,7 @@ fn each_multiple_is_only_counted_once() { let factors = &[3, 5]; let limit = 100; let output = sum_of_multiples(limit, factors); - let expected = 2318; + let expected = 2_318; assert_eq!(output, expected); } @@ -53,9 +53,9 @@ fn each_multiple_is_only_counted_once() { #[ignore] fn a_much_larger_limit() { let factors = &[3, 5]; - let limit = 1000; + let limit = 1_000; let output = sum_of_multiples(limit, factors); - let expected = 233168; + let expected = 233_168; assert_eq!(output, expected); } @@ -85,7 +85,7 @@ fn some_pairs_of_factors_relatively_prime_and_some_not() { let factors = &[5, 6, 8]; let limit = 150; let output = sum_of_multiples(limit, factors); - let expected = 4419; + let expected = 4_419; assert_eq!(output, expected); } @@ -103,9 +103,9 @@ fn one_factor_is_a_multiple_of_another() { #[ignore] fn much_larger_factors() { let factors = &[43, 47]; - let limit = 10000; + let limit = 10_000; let output = sum_of_multiples(limit, factors); - let expected = 2203160; + let expected = 2_203_160; assert_eq!(output, expected); } @@ -115,7 +115,7 @@ fn all_numbers_are_multiples_of_1() { let factors = &[1]; let limit = 100; let output = sum_of_multiples(limit, factors); - let expected = 4950; + let expected = 4_950; assert_eq!(output, expected); } @@ -123,7 +123,7 @@ fn all_numbers_are_multiples_of_1() { #[ignore] fn no_factors_means_an_empty_sum() { let factors = &[]; - let limit = 10000; + let limit = 10_000; let output = sum_of_multiples(limit, factors); let expected = 0; assert_eq!(output, expected); @@ -153,8 +153,8 @@ fn the_factor_0_does_not_affect_the_sum_of_multiples_of_other_factors() { #[ignore] fn solutions_using_include_exclude_must_extend_to_cardinality_greater_than_3() { let factors = &[2, 3, 5, 7, 11]; - let limit = 10000; + let limit = 10_000; let output = sum_of_multiples(limit, factors); - let expected = 39614537; + let expected = 39_614_537; assert_eq!(output, expected); } diff --git a/exercises/practice/variable-length-quantity/.meta/test_template.tera b/exercises/practice/variable-length-quantity/.meta/test_template.tera index b8c8c450a..f09cf6504 100644 --- a/exercises/practice/variable-length-quantity/.meta/test_template.tera +++ b/exercises/practice/variable-length-quantity/.meta/test_template.tera @@ -6,7 +6,9 @@ use variable_length_quantity as vlq; #[ignore] fn {{ test.description | snake_case }}() { {%- if test.property == "encode" %} - let input = &{{ test.input.integers | json_encode() }}; + let input = &[{% for integer in test.input.integers -%} + {{ integer | fmt_num }}, + {%- endfor %}]; let output = vlq::to_bytes(input); let expected = vec![ {%- for byte in test.expected -%} @@ -23,7 +25,9 @@ fn {{ test.description | snake_case }}() { let expected = {% if test.expected is object -%} Err(vlq::Error::IncompleteNumber) {%- else -%} - Ok(vec!{{ test.expected | json_encode() }}) + Ok(vec![{% for integer in test.expected -%} + {{ integer | fmt_num }}, + {%- endfor %}]) {%- endif %}; {%- else %} panic!("unknown property: {{ test.property }}"); diff --git a/exercises/practice/variable-length-quantity/tests/variable-length-quantity.rs b/exercises/practice/variable-length-quantity/tests/variable-length-quantity.rs index 68079b12c..e3f3dfe5f 100644 --- a/exercises/practice/variable-length-quantity/tests/variable-length-quantity.rs +++ b/exercises/practice/variable-length-quantity/tests/variable-length-quantity.rs @@ -38,7 +38,7 @@ fn smallest_double_byte() { #[test] #[ignore] fn arbitrary_double_byte() { - let input = &[8192]; + let input = &[8_192]; let output = vlq::to_bytes(input); let expected = vec![0xc0, 0x0]; assert_eq!(output, expected); @@ -47,7 +47,7 @@ fn arbitrary_double_byte() { #[test] #[ignore] fn largest_double_byte() { - let input = &[16383]; + let input = &[16_383]; let output = vlq::to_bytes(input); let expected = vec![0xff, 0x7f]; assert_eq!(output, expected); @@ -56,7 +56,7 @@ fn largest_double_byte() { #[test] #[ignore] fn smallest_triple_byte() { - let input = &[16384]; + let input = &[16_384]; let output = vlq::to_bytes(input); let expected = vec![0x81, 0x80, 0x0]; assert_eq!(output, expected); @@ -65,7 +65,7 @@ fn smallest_triple_byte() { #[test] #[ignore] fn arbitrary_triple_byte() { - let input = &[1048576]; + let input = &[1_048_576]; let output = vlq::to_bytes(input); let expected = vec![0xc0, 0x80, 0x0]; assert_eq!(output, expected); @@ -74,7 +74,7 @@ fn arbitrary_triple_byte() { #[test] #[ignore] fn largest_triple_byte() { - let input = &[2097151]; + let input = &[2_097_151]; let output = vlq::to_bytes(input); let expected = vec![0xff, 0xff, 0x7f]; assert_eq!(output, expected); @@ -83,7 +83,7 @@ fn largest_triple_byte() { #[test] #[ignore] fn smallest_quadruple_byte() { - let input = &[2097152]; + let input = &[2_097_152]; let output = vlq::to_bytes(input); let expected = vec![0x81, 0x80, 0x80, 0x0]; assert_eq!(output, expected); @@ -92,7 +92,7 @@ fn smallest_quadruple_byte() { #[test] #[ignore] fn arbitrary_quadruple_byte() { - let input = &[134217728]; + let input = &[134_217_728]; let output = vlq::to_bytes(input); let expected = vec![0xc0, 0x80, 0x80, 0x0]; assert_eq!(output, expected); @@ -101,7 +101,7 @@ fn arbitrary_quadruple_byte() { #[test] #[ignore] fn largest_quadruple_byte() { - let input = &[268435455]; + let input = &[268_435_455]; let output = vlq::to_bytes(input); let expected = vec![0xff, 0xff, 0xff, 0x7f]; assert_eq!(output, expected); @@ -110,7 +110,7 @@ fn largest_quadruple_byte() { #[test] #[ignore] fn smallest_quintuple_byte() { - let input = &[268435456]; + let input = &[268_435_456]; let output = vlq::to_bytes(input); let expected = vec![0x81, 0x80, 0x80, 0x80, 0x0]; assert_eq!(output, expected); @@ -119,7 +119,7 @@ fn smallest_quintuple_byte() { #[test] #[ignore] fn arbitrary_quintuple_byte() { - let input = &[4278190080]; + let input = &[4_278_190_080]; let output = vlq::to_bytes(input); let expected = vec![0x8f, 0xf8, 0x80, 0x80, 0x0]; assert_eq!(output, expected); @@ -128,7 +128,7 @@ fn arbitrary_quintuple_byte() { #[test] #[ignore] fn maximum_32_bit_integer_input() { - let input = &[4294967295]; + let input = &[4_294_967_295]; let output = vlq::to_bytes(input); let expected = vec![0x8f, 0xff, 0xff, 0xff, 0x7f]; assert_eq!(output, expected); @@ -146,7 +146,7 @@ fn two_single_byte_values() { #[test] #[ignore] fn two_multi_byte_values() { - let input = &[16384, 1193046]; + let input = &[16_384, 1_193_046]; let output = vlq::to_bytes(input); let expected = vec![0x81, 0x80, 0x0, 0xc8, 0xe8, 0x56]; assert_eq!(output, expected); @@ -155,7 +155,7 @@ fn two_multi_byte_values() { #[test] #[ignore] fn many_multi_byte_values() { - let input = &[8192, 1193046, 268435455, 0, 16383, 16384]; + let input = &[8_192, 1_193_046, 268_435_455, 0, 16_383, 16_384]; let output = vlq::to_bytes(input); let expected = vec![ 0xc0, 0x0, 0xc8, 0xe8, 0x56, 0xff, 0xff, 0xff, 0x7f, 0x0, 0xff, 0x7f, 0x81, 0x80, 0x0, @@ -177,7 +177,7 @@ fn one_byte() { fn two_bytes() { let input = &[0xc0, 0x0]; let output = vlq::from_bytes(input); - let expected = Ok(vec![8192]); + let expected = Ok(vec![8_192]); assert_eq!(output, expected); } @@ -186,7 +186,7 @@ fn two_bytes() { fn three_bytes() { let input = &[0xff, 0xff, 0x7f]; let output = vlq::from_bytes(input); - let expected = Ok(vec![2097151]); + let expected = Ok(vec![2_097_151]); assert_eq!(output, expected); } @@ -195,7 +195,7 @@ fn three_bytes() { fn four_bytes() { let input = &[0x81, 0x80, 0x80, 0x0]; let output = vlq::from_bytes(input); - let expected = Ok(vec![2097152]); + let expected = Ok(vec![2_097_152]); assert_eq!(output, expected); } @@ -204,7 +204,7 @@ fn four_bytes() { fn maximum_32_bit_integer() { let input = &[0x8f, 0xff, 0xff, 0xff, 0x7f]; let output = vlq::from_bytes(input); - let expected = Ok(vec![4294967295]); + let expected = Ok(vec![4_294_967_295]); assert_eq!(output, expected); } @@ -233,6 +233,6 @@ fn multiple_values() { 0xc0, 0x0, 0xc8, 0xe8, 0x56, 0xff, 0xff, 0xff, 0x7f, 0x0, 0xff, 0x7f, 0x81, 0x80, 0x0, ]; let output = vlq::from_bytes(input); - let expected = Ok(vec![8192, 1193046, 268435455, 0, 16383, 16384]); + let expected = Ok(vec![8_192, 1_193_046, 268_435_455, 0, 16_383, 16_384]); assert_eq!(output, expected); } From c5a341a5749cab8bed833ba18f818651fcf4afb7 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Wed, 14 Aug 2024 12:54:18 +0200 Subject: [PATCH 331/436] grep: sync (#1965) [no important files changed] part of https://github.com/exercism/rust/issues/1824 --- .../practice/grep/.meta/test_template.tera | 148 +++ exercises/practice/grep/.meta/tests.toml | 88 +- exercises/practice/grep/tests/grep.rs | 944 ++++++++---------- 3 files changed, 676 insertions(+), 504 deletions(-) create mode 100644 exercises/practice/grep/.meta/test_template.tera diff --git a/exercises/practice/grep/.meta/test_template.tera b/exercises/practice/grep/.meta/test_template.tera new file mode 100644 index 000000000..de27f4dc3 --- /dev/null +++ b/exercises/practice/grep/.meta/test_template.tera @@ -0,0 +1,148 @@ +use grep::*; + +#[test] +#[ignore] +fn nonexistent_file_returns_error() { + let pattern = "Agamemnon"; + let flags = Flags::new(&[]); + let files = ["0-1-nonexistent-file-returns-error-iliad.txt"]; + assert!(grep(pattern, &flags, &files).is_err()); +} + +#[test] +#[ignore] +fn grep_returns_result() { + let pattern = "Agamemnon"; + let flags = Flags::new(&[]); + let files = Files::new(&["0-2-grep-returns-result-iliad.txt"]); + assert!(grep(pattern, &flags, files.as_ref()).is_ok()); +} + +{% for test_group in cases %} +{% set group_idx = loop.index %} +{% for test in test_group.cases %} +{% set test_idx = loop.index %} + +{# + This prefix is added to the file names to ensure each test case has its own + set of files. This is necessary because every test case creates and deletes + its own files, so the names must be unique to prevent conflicts. +#} +{% set prefix = group_idx ~ "-" ~ test_idx %} + +#[test] +#[ignore] +fn {{ test.description | snake_case }}() { + let pattern = {{ test.input.pattern | json_encode() }}; + let flags = Flags::new(&{{ test.input.flags | json_encode() }}); + let files = Files::new(&[ + {% for file in test.input.files -%} + "{{ prefix }}-{{ file }}", + {%- endfor %} + ]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &[ + {% if test.input.files | length > 1 or test.input.flags is containing("-l") %} + {% for line in test.expected %} + "{{ prefix }}-{{ line }}", + {% endfor %} + {% else %} + {% for line in test.expected %} + "{{ line }}", + {% endfor %} + {% endif %} + ]; + assert_eq!(actual, expected); +} +{% endfor %} +{% endfor %} + +static ILIAD_CONTENT: &str = "\ +Achilles sing, O Goddess! Peleus' son; +His wrath pernicious, who ten thousand woes +Caused to Achaia's host, sent many a soul +Illustrious into Ades premature, +And Heroes gave (so stood the will of Jove) +To dogs and to all ravening fowls a prey, +When fierce dispute had separated once +The noble Chief Achilles from the son +Of Atreus, Agamemnon, King of men. +"; + +static MIDSUMMER_NIGHT_CONTENT: &str = "\ +I do entreat your grace to pardon me. +I know not by what power I am made bold, +Nor how it may concern my modesty, +In such a presence here to plead my thoughts; +But I beseech your grace that I may know +The worst that may befall me in this case, +If I refuse to wed Demetrius. +"; + +static PARADISE_LOST_CONTENT: &str = "\ +Of Mans First Disobedience, and the Fruit +Of that Forbidden Tree, whose mortal tast +Brought Death into the World, and all our woe, +With loss of Eden, till one greater Man +Restore us, and regain the blissful Seat, +Sing Heav'nly Muse, that on the secret top +Of Oreb, or of Sinai, didst inspire +That Shepherd, who first taught the chosen Seed +"; + +/// In The White Night +/// A poem by Alexander Blok(https://en.wikipedia.org/wiki/Alexander_Blok) +/// a Russian poet who is regarded as one of the most important figures of the Silver Age of Russian Poetry +/// You can read the translation here: https://lyricstranslate.com/ru/белой-ночью-месяц-красный-white-night-crimson-crescent.html +static IN_THE_WHITE_NIGHT_CONTENT: &str = " +Белой ночью месяц красный +Выплывает в синеве. +Бродит призрачно-прекрасный, +Отражается в Неве. +Мне провидится и снится +Исполненье тайных дум. +В вас ли доброе таится, +Красный месяц, тихий шум?.. +"; + +struct Files<'a> { + file_names: &'a [&'a str], +} + +impl<'a> Files<'a> { + fn new(file_names: &'a [&'a str]) -> Self { + for file_name in file_names { + let content = if file_name.ends_with("iliad.txt") { + ILIAD_CONTENT + } else if file_name.ends_with("midsummer-night.txt") { + MIDSUMMER_NIGHT_CONTENT + } else if file_name.ends_with("paradise-lost.txt") { + PARADISE_LOST_CONTENT + } else { + IN_THE_WHITE_NIGHT_CONTENT + }; + std::fs::write(file_name, content).unwrap_or_else(|_| { + panic!( + "Error setting up file '{file_name}' with the following content:\n{content}" + ) + }); + } + + Self { file_names } + } +} + +impl<'a> Drop for Files<'a> { + fn drop(&mut self) { + for file_name in self.file_names { + std::fs::remove_file(file_name) + .unwrap_or_else(|e| panic!("Could not delete file '{file_name}': {e}")); + } + } +} + +impl<'a> AsRef<[&'a str]> for Files<'a> { + fn as_ref(&self) -> &[&'a str] { + self.file_names + } +} diff --git a/exercises/practice/grep/.meta/tests.toml b/exercises/practice/grep/.meta/tests.toml index be690e975..04c51e71b 100644 --- a/exercises/practice/grep/.meta/tests.toml +++ b/exercises/practice/grep/.meta/tests.toml @@ -1,3 +1,85 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[9049fdfd-53a7-4480-a390-375203837d09] +description = "Test grepping a single file -> One file, one match, no flags" + +[76519cce-98e3-46cd-b287-aac31b1d77d6] +description = "Test grepping a single file -> One file, one match, print line numbers flag" + +[af0b6d3c-e0e8-475e-a112-c0fc10a1eb30] +description = "Test grepping a single file -> One file, one match, case-insensitive flag" + +[ff7af839-d1b8-4856-a53e-99283579b672] +description = "Test grepping a single file -> One file, one match, print file names flag" + +[8625238a-720c-4a16-81f2-924ec8e222cb] +description = "Test grepping a single file -> One file, one match, match entire lines flag" + +[2a6266b3-a60f-475c-a5f5-f5008a717d3e] +description = "Test grepping a single file -> One file, one match, multiple flags" + +[842222da-32e8-4646-89df-0d38220f77a1] +description = "Test grepping a single file -> One file, several matches, no flags" + +[4d84f45f-a1d8-4c2e-a00e-0b292233828c] +description = "Test grepping a single file -> One file, several matches, print line numbers flag" + +[0a483b66-315b-45f5-bc85-3ce353a22539] +description = "Test grepping a single file -> One file, several matches, match entire lines flag" + +[3d2ca86a-edd7-494c-8938-8eeed1c61cfa] +description = "Test grepping a single file -> One file, several matches, case-insensitive flag" + +[1f52001f-f224-4521-9456-11120cad4432] +description = "Test grepping a single file -> One file, several matches, inverted flag" + +[7a6ede7f-7dd5-4364-8bf8-0697c53a09fe] +description = "Test grepping a single file -> One file, no matches, various flags" + +[3d3dfc23-8f2a-4e34-abd6-7b7d140291dc] +description = "Test grepping a single file -> One file, one match, file flag takes precedence over line flag" + +[87b21b24-b788-4d6e-a68b-7afe9ca141fe] +description = "Test grepping a single file -> One file, several matches, inverted and match entire lines flags" + +[ba496a23-6149-41c6-a027-28064ed533e5] +description = "Test grepping multiples files at once -> Multiple files, one match, no flags" + +[4539bd36-6daa-4bc3-8e45-051f69f5aa95] +description = "Test grepping multiples files at once -> Multiple files, several matches, no flags" + +[9fb4cc67-78e2-4761-8e6b-a4b57aba1938] +description = "Test grepping multiples files at once -> Multiple files, several matches, print line numbers flag" + +[aeee1ef3-93c7-4cd5-af10-876f8c9ccc73] +description = "Test grepping multiples files at once -> Multiple files, one match, print file names flag" + +[d69f3606-7d15-4ddf-89ae-01df198e6b6c] +description = "Test grepping multiples files at once -> Multiple files, several matches, case-insensitive flag" + +[82ef739d-6701-4086-b911-007d1a3deb21] +description = "Test grepping multiples files at once -> Multiple files, several matches, inverted flag" + +[77b2eb07-2921-4ea0-8971-7636b44f5d29] +description = "Test grepping multiples files at once -> Multiple files, one match, match entire lines flag" + +[e53a2842-55bb-4078-9bb5-04ac38929989] +description = "Test grepping multiples files at once -> Multiple files, one match, multiple flags" + +[9c4f7f9a-a555-4e32-bb06-4b8f8869b2cb] +description = "Test grepping multiples files at once -> Multiple files, no matches, various flags" + +[ba5a540d-bffd-481b-bd0c-d9a30f225e01] +description = "Test grepping multiples files at once -> Multiple files, several matches, file flag takes precedence over line number flag" + +[ff406330-2f0b-4b17-9ee4-4b71c31dd6d2] +description = "Test grepping multiples files at once -> Multiple files, several matches, inverted and match entire lines flags" diff --git a/exercises/practice/grep/tests/grep.rs b/exercises/practice/grep/tests/grep.rs index 49a6060b7..e6358f42c 100644 --- a/exercises/practice/grep/tests/grep.rs +++ b/exercises/practice/grep/tests/grep.rs @@ -1,8 +1,417 @@ -use grep::{grep, Flags}; +use grep::*; -use std::fs; +#[test] +fn nonexistent_file_returns_error() { + let pattern = "Agamemnon"; + let flags = Flags::new(&[]); + let files = ["0-1-nonexistent-file-returns-error-iliad.txt"]; + assert!(grep(pattern, &flags, &files).is_err()); +} + +#[test] +#[ignore] +fn grep_returns_result() { + let pattern = "Agamemnon"; + let flags = Flags::new(&[]); + let files = Files::new(&["0-2-grep-returns-result-iliad.txt"]); + assert!(grep(pattern, &flags, files.as_ref()).is_ok()); +} + +#[test] +#[ignore] +fn one_file_one_match_no_flags() { + let pattern = "Agamemnon"; + let flags = Flags::new(&[]); + let files = Files::new(&["1-1-iliad.txt"]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &["Of Atreus, Agamemnon, King of men."]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn one_file_one_match_print_line_numbers_flag() { + let pattern = "Forbidden"; + let flags = Flags::new(&["-n"]); + let files = Files::new(&["1-2-paradise-lost.txt"]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &["2:Of that Forbidden Tree, whose mortal tast"]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn one_file_one_match_case_insensitive_flag() { + let pattern = "FORBIDDEN"; + let flags = Flags::new(&["-i"]); + let files = Files::new(&["1-3-paradise-lost.txt"]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &["Of that Forbidden Tree, whose mortal tast"]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn one_file_one_match_print_file_names_flag() { + let pattern = "Forbidden"; + let flags = Flags::new(&["-l"]); + let files = Files::new(&["1-4-paradise-lost.txt"]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &["1-4-paradise-lost.txt"]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn one_file_one_match_match_entire_lines_flag() { + let pattern = "With loss of Eden, till one greater Man"; + let flags = Flags::new(&["-x"]); + let files = Files::new(&["1-5-paradise-lost.txt"]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &["With loss of Eden, till one greater Man"]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn one_file_one_match_multiple_flags() { + let pattern = "OF ATREUS, Agamemnon, KIng of MEN."; + let flags = Flags::new(&["-n", "-i", "-x"]); + let files = Files::new(&["1-6-iliad.txt"]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &["9:Of Atreus, Agamemnon, King of men."]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn one_file_several_matches_no_flags() { + let pattern = "may"; + let flags = Flags::new(&[]); + let files = Files::new(&["1-7-midsummer-night.txt"]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &[ + "Nor how it may concern my modesty,", + "But I beseech your grace that I may know", + "The worst that may befall me in this case,", + ]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn one_file_several_matches_print_line_numbers_flag() { + let pattern = "may"; + let flags = Flags::new(&["-n"]); + let files = Files::new(&["1-8-midsummer-night.txt"]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &[ + "3:Nor how it may concern my modesty,", + "5:But I beseech your grace that I may know", + "6:The worst that may befall me in this case,", + ]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn one_file_several_matches_match_entire_lines_flag() { + let pattern = "may"; + let flags = Flags::new(&["-x"]); + let files = Files::new(&["1-9-midsummer-night.txt"]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &[]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn one_file_several_matches_case_insensitive_flag() { + let pattern = "ACHILLES"; + let flags = Flags::new(&["-i"]); + let files = Files::new(&["1-10-iliad.txt"]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &[ + "Achilles sing, O Goddess! Peleus' son;", + "The noble Chief Achilles from the son", + ]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn one_file_several_matches_inverted_flag() { + let pattern = "Of"; + let flags = Flags::new(&["-v"]); + let files = Files::new(&["1-11-paradise-lost.txt"]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &[ + "Brought Death into the World, and all our woe,", + "With loss of Eden, till one greater Man", + "Restore us, and regain the blissful Seat,", + "Sing Heav'nly Muse, that on the secret top", + "That Shepherd, who first taught the chosen Seed", + ]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn one_file_no_matches_various_flags() { + let pattern = "Gandalf"; + let flags = Flags::new(&["-n", "-l", "-x", "-i"]); + let files = Files::new(&["1-12-iliad.txt"]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &[]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn one_file_one_match_file_flag_takes_precedence_over_line_flag() { + let pattern = "ten"; + let flags = Flags::new(&["-n", "-l"]); + let files = Files::new(&["1-13-iliad.txt"]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &["1-13-iliad.txt"]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn one_file_several_matches_inverted_and_match_entire_lines_flags() { + let pattern = "Illustrious into Ades premature,"; + let flags = Flags::new(&["-x", "-v"]); + let files = Files::new(&["1-14-iliad.txt"]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &[ + "Achilles sing, O Goddess! Peleus' son;", + "His wrath pernicious, who ten thousand woes", + "Caused to Achaia's host, sent many a soul", + "And Heroes gave (so stood the will of Jove)", + "To dogs and to all ravening fowls a prey,", + "When fierce dispute had separated once", + "The noble Chief Achilles from the son", + "Of Atreus, Agamemnon, King of men.", + ]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn multiple_files_one_match_no_flags() { + let pattern = "Agamemnon"; + let flags = Flags::new(&[]); + let files = Files::new(&[ + "2-1-iliad.txt", + "2-1-midsummer-night.txt", + "2-1-paradise-lost.txt", + ]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &["2-1-iliad.txt:Of Atreus, Agamemnon, King of men."]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn multiple_files_several_matches_no_flags() { + let pattern = "may"; + let flags = Flags::new(&[]); + let files = Files::new(&[ + "2-2-iliad.txt", + "2-2-midsummer-night.txt", + "2-2-paradise-lost.txt", + ]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &[ + "2-2-midsummer-night.txt:Nor how it may concern my modesty,", + "2-2-midsummer-night.txt:But I beseech your grace that I may know", + "2-2-midsummer-night.txt:The worst that may befall me in this case,", + ]; + assert_eq!(actual, expected); +} -static ILIAD_CONTENT: &str = "Achilles sing, O Goddess! Peleus' son; +#[test] +#[ignore] +fn multiple_files_several_matches_print_line_numbers_flag() { + let pattern = "that"; + let flags = Flags::new(&["-n"]); + let files = Files::new(&[ + "2-3-iliad.txt", + "2-3-midsummer-night.txt", + "2-3-paradise-lost.txt", + ]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &[ + "2-3-midsummer-night.txt:5:But I beseech your grace that I may know", + "2-3-midsummer-night.txt:6:The worst that may befall me in this case,", + "2-3-paradise-lost.txt:2:Of that Forbidden Tree, whose mortal tast", + "2-3-paradise-lost.txt:6:Sing Heav'nly Muse, that on the secret top", + ]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn multiple_files_one_match_print_file_names_flag() { + let pattern = "who"; + let flags = Flags::new(&["-l"]); + let files = Files::new(&[ + "2-4-iliad.txt", + "2-4-midsummer-night.txt", + "2-4-paradise-lost.txt", + ]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &["2-4-iliad.txt", "2-4-paradise-lost.txt"]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn multiple_files_several_matches_case_insensitive_flag() { + let pattern = "TO"; + let flags = Flags::new(&["-i"]); + let files = Files::new(&[ + "2-5-iliad.txt", + "2-5-midsummer-night.txt", + "2-5-paradise-lost.txt", + ]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &[ + "2-5-iliad.txt:Caused to Achaia's host, sent many a soul", + "2-5-iliad.txt:Illustrious into Ades premature,", + "2-5-iliad.txt:And Heroes gave (so stood the will of Jove)", + "2-5-iliad.txt:To dogs and to all ravening fowls a prey,", + "2-5-midsummer-night.txt:I do entreat your grace to pardon me.", + "2-5-midsummer-night.txt:In such a presence here to plead my thoughts;", + "2-5-midsummer-night.txt:If I refuse to wed Demetrius.", + "2-5-paradise-lost.txt:Brought Death into the World, and all our woe,", + "2-5-paradise-lost.txt:Restore us, and regain the blissful Seat,", + "2-5-paradise-lost.txt:Sing Heav'nly Muse, that on the secret top", + ]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn multiple_files_several_matches_inverted_flag() { + let pattern = "a"; + let flags = Flags::new(&["-v"]); + let files = Files::new(&[ + "2-6-iliad.txt", + "2-6-midsummer-night.txt", + "2-6-paradise-lost.txt", + ]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &[ + "2-6-iliad.txt:Achilles sing, O Goddess! Peleus' son;", + "2-6-iliad.txt:The noble Chief Achilles from the son", + "2-6-midsummer-night.txt:If I refuse to wed Demetrius.", + ]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn multiple_files_one_match_match_entire_lines_flag() { + let pattern = "But I beseech your grace that I may know"; + let flags = Flags::new(&["-x"]); + let files = Files::new(&[ + "2-7-iliad.txt", + "2-7-midsummer-night.txt", + "2-7-paradise-lost.txt", + ]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &["2-7-midsummer-night.txt:But I beseech your grace that I may know"]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn multiple_files_one_match_multiple_flags() { + let pattern = "WITH LOSS OF EDEN, TILL ONE GREATER MAN"; + let flags = Flags::new(&["-n", "-i", "-x"]); + let files = Files::new(&[ + "2-8-iliad.txt", + "2-8-midsummer-night.txt", + "2-8-paradise-lost.txt", + ]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &["2-8-paradise-lost.txt:4:With loss of Eden, till one greater Man"]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn multiple_files_no_matches_various_flags() { + let pattern = "Frodo"; + let flags = Flags::new(&["-n", "-l", "-x", "-i"]); + let files = Files::new(&[ + "2-9-iliad.txt", + "2-9-midsummer-night.txt", + "2-9-paradise-lost.txt", + ]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &[]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn multiple_files_several_matches_file_flag_takes_precedence_over_line_number_flag() { + let pattern = "who"; + let flags = Flags::new(&["-n", "-l"]); + let files = Files::new(&[ + "2-10-iliad.txt", + "2-10-midsummer-night.txt", + "2-10-paradise-lost.txt", + ]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &["2-10-iliad.txt", "2-10-paradise-lost.txt"]; + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn multiple_files_several_matches_inverted_and_match_entire_lines_flags() { + let pattern = "Illustrious into Ades premature,"; + let flags = Flags::new(&["-x", "-v"]); + let files = Files::new(&[ + "2-11-iliad.txt", + "2-11-midsummer-night.txt", + "2-11-paradise-lost.txt", + ]); + let actual = grep(pattern, &flags, files.as_ref()).unwrap(); + let expected: &[&str] = &[ + "2-11-iliad.txt:Achilles sing, O Goddess! Peleus' son;", + "2-11-iliad.txt:His wrath pernicious, who ten thousand woes", + "2-11-iliad.txt:Caused to Achaia's host, sent many a soul", + "2-11-iliad.txt:And Heroes gave (so stood the will of Jove)", + "2-11-iliad.txt:To dogs and to all ravening fowls a prey,", + "2-11-iliad.txt:When fierce dispute had separated once", + "2-11-iliad.txt:The noble Chief Achilles from the son", + "2-11-iliad.txt:Of Atreus, Agamemnon, King of men.", + "2-11-midsummer-night.txt:I do entreat your grace to pardon me.", + "2-11-midsummer-night.txt:I know not by what power I am made bold,", + "2-11-midsummer-night.txt:Nor how it may concern my modesty,", + "2-11-midsummer-night.txt:In such a presence here to plead my thoughts;", + "2-11-midsummer-night.txt:But I beseech your grace that I may know", + "2-11-midsummer-night.txt:The worst that may befall me in this case,", + "2-11-midsummer-night.txt:If I refuse to wed Demetrius.", + "2-11-paradise-lost.txt:Of Mans First Disobedience, and the Fruit", + "2-11-paradise-lost.txt:Of that Forbidden Tree, whose mortal tast", + "2-11-paradise-lost.txt:Brought Death into the World, and all our woe,", + "2-11-paradise-lost.txt:With loss of Eden, till one greater Man", + "2-11-paradise-lost.txt:Restore us, and regain the blissful Seat,", + "2-11-paradise-lost.txt:Sing Heav'nly Muse, that on the secret top", + "2-11-paradise-lost.txt:Of Oreb, or of Sinai, didst inspire", + "2-11-paradise-lost.txt:That Shepherd, who first taught the chosen Seed", + ]; + assert_eq!(actual, expected); +} + +static ILIAD_CONTENT: &str = "\ +Achilles sing, O Goddess! Peleus' son; His wrath pernicious, who ten thousand woes Caused to Achaia's host, sent many a soul Illustrious into Ades premature, @@ -13,7 +422,8 @@ The noble Chief Achilles from the son Of Atreus, Agamemnon, King of men. "; -static MIDSUMMER_NIGHT_CONTENT: &str = "I do entreat your grace to pardon me. +static MIDSUMMER_NIGHT_CONTENT: &str = "\ +I do entreat your grace to pardon me. I know not by what power I am made bold, Nor how it may concern my modesty, In such a presence here to plead my thoughts; @@ -22,7 +432,8 @@ The worst that may befall me in this case, If I refuse to wed Demetrius. "; -static PARADISE_LOST_CONTENT: &str = "Of Mans First Disobedience, and the Fruit +static PARADISE_LOST_CONTENT: &str = "\ +Of Mans First Disobedience, and the Fruit Of that Forbidden Tree, whose mortal tast Brought Death into the World, and all our woe, With loss of Eden, till one greater Man @@ -36,7 +447,8 @@ That Shepherd, who first taught the chosen Seed /// A poem by Alexander Blok(https://en.wikipedia.org/wiki/Alexander_Blok) /// a Russian poet who is regarded as one of the most important figures of the Silver Age of Russian Poetry /// You can read the translation here: https://lyricstranslate.com/ru/белой-ночью-месяц-красный-white-night-crimson-crescent.html -static IN_THE_WHITE_NIGHT_CONTENT: &str = "Белой ночью месяц красный +static IN_THE_WHITE_NIGHT_CONTENT: &str = " +Белой ночью месяц красный Выплывает в синеве. Бродит призрачно-прекрасный, Отражается в Неве. @@ -46,512 +458,42 @@ static IN_THE_WHITE_NIGHT_CONTENT: &str = "Белой ночью месяц кр Красный месяц, тихий шум?.. "; -struct Fixture<'a> { +struct Files<'a> { file_names: &'a [&'a str], } -impl<'a> Fixture<'a> { +impl<'a> Files<'a> { fn new(file_names: &'a [&'a str]) -> Self { - Fixture { file_names } - } + for file_name in file_names { + let content = if file_name.ends_with("iliad.txt") { + ILIAD_CONTENT + } else if file_name.ends_with("midsummer-night.txt") { + MIDSUMMER_NIGHT_CONTENT + } else if file_name.ends_with("paradise-lost.txt") { + PARADISE_LOST_CONTENT + } else { + IN_THE_WHITE_NIGHT_CONTENT + }; + std::fs::write(file_name, content).unwrap_or_else(|_| { + panic!("Error setting up file '{file_name}' with the following content:\n{content}") + }); + } - fn set_up(&self) { - let file_name_content_pairs = self - .file_names - .iter() - .cloned() - .map(|file_name| { - if file_name.ends_with("iliad.txt") { - (file_name, ILIAD_CONTENT) - } else if file_name.ends_with("midsummer_night.txt") { - (file_name, MIDSUMMER_NIGHT_CONTENT) - } else if file_name.ends_with("paradise_lost.txt") { - (file_name, PARADISE_LOST_CONTENT) - } else { - (file_name, IN_THE_WHITE_NIGHT_CONTENT) - } - }) - .collect::>(); - - set_up_files(&file_name_content_pairs); + Self { file_names } } } -impl<'a> Drop for Fixture<'a> { +impl<'a> Drop for Files<'a> { fn drop(&mut self) { - tear_down_files(self.file_names); - } -} - -fn set_up_files(files: &[(&str, &str)]) { - for (file_name, file_content) in files { - fs::write(file_name, file_content).unwrap_or_else(|_| { - panic!( - "Error setting up file '{file_name}' with the following content:\n{file_content}" - ) - }); - } -} - -fn tear_down_files(files: &[&str]) { - for file_name in files { - fs::remove_file(file_name) - .unwrap_or_else(|_| panic!("Could not delete file '{file_name}'")); - } -} - -/// This macro is here so that every test case had its own set of files to be used in test. -/// The approach is to create required files for every test case and to append test name to the -/// file names (so for test with a name 'one_file_one_match_no_flags' and a required file -/// 'iliad.txt' there would be created a file with a name -/// 'one_file_one_match_no_flags_iliad.txt'). -/// This allows us to create files for every test case with no intersection between them. -/// -/// A better way would be to create required set of files at the start of tests run and to -/// delete them after every test is finished, but there is no trivial way to create such -/// a test fixture in standard Rust, and Exercism restricts the usage of external dependencies -/// in test files. Therefore the above approach is chosen. -/// -/// If you have an idea about a better way to implement test fixture for this exercise, -/// please submit PR to the Rust Exercism track: https://github.com/exercism/rust -macro_rules! set_up_test_case { - ($(#[$flag:meta])+ $test_case_name:ident(pattern=$pattern:expr, flags=[$($grep_flag:expr),*], files=[$($file:expr),+], expected=[$($expected:expr),*])) => { - $(#[$flag])+ - fn $test_case_name() { - let pattern = $pattern; - - let flags = vec![$($grep_flag),*]; - - let files = vec![$(concat!(stringify!($test_case_name), "_" , $file)),+]; - - let expected = vec![$($expected),*]; - - process_grep_case(pattern, &flags, &files, &expected); - } - }; - ($(#[$flag:meta])+ $test_case_name:ident(pattern=$pattern:expr, flags=[$($grep_flag:expr),*], files=[$($file:expr),+], prefix_expected=[$($expected:expr),*])) => { - $(#[$flag])+ - fn $test_case_name() { - let pattern = $pattern; - - let flags = vec![$($grep_flag),*]; - - let files = vec![$(concat!(stringify!($test_case_name), "_" , $file)),+]; - - let expected = vec![$(concat!(stringify!($test_case_name), "_", $expected)),*]; - - process_grep_case(pattern, &flags, &files, &expected); + for file_name in self.file_names { + std::fs::remove_file(file_name) + .unwrap_or_else(|e| panic!("Could not delete file '{file_name}': {e}")); } - } } -fn process_grep_case(pattern: &str, flags: &[&str], files: &[&str], expected: &[&str]) { - let test_fixture = Fixture::new(files); - - test_fixture.set_up(); - - let flags = Flags::new(flags); - - let grep_result = grep(pattern, &flags, files).unwrap(); - - assert_eq!(grep_result, expected); -} - -// Test returning a Result - -#[test] -fn nonexistent_file_returns_error() { - let pattern = "Agamemnon"; - - let flags = Flags::new(&[]); - - let files = vec!["nonexistent_file_returns_error_iliad.txt"]; - - assert!(grep(pattern, &flags, &files).is_err()); -} - -#[test] -#[ignore] -fn grep_returns_result() { - let pattern = "Agamemnon"; - - let flags = Flags::new(&[]); - - let files = vec!["grep_returns_result_iliad.txt"]; - - let test_fixture = Fixture::new(&files); - - test_fixture.set_up(); - - assert!(grep(pattern, &flags, &files).is_ok()); +impl<'a> AsRef<[&'a str]> for Files<'a> { + fn as_ref(&self) -> &[&'a str] { + self.file_names + } } - -// Test grepping a single file - -set_up_test_case!( - #[test] - #[ignore] - one_file_one_match_no_flags( - pattern = "Agamemnon", - flags = [], - files = ["iliad.txt"], - expected = ["Of Atreus, Agamemnon, King of men."] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - one_file_one_match_print_line_numbers_flag( - pattern = "Forbidden", - flags = ["-n"], - files = ["paradise_lost.txt"], - expected = ["2:Of that Forbidden Tree, whose mortal tast"] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - one_file_one_match_caseinsensitive_flag( - pattern = "FORBIDDEN", - flags = ["-i"], - files = ["paradise_lost.txt"], - expected = ["Of that Forbidden Tree, whose mortal tast"] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - one_file_one_match_print_file_names_flag( - pattern = "Forbidden", - flags = ["-l"], - files = ["paradise_lost.txt"], - prefix_expected = ["paradise_lost.txt"] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - one_file_one_match_match_entire_lines_flag( - pattern = "With loss of Eden, till one greater Man", - flags = ["-x"], - files = ["paradise_lost.txt"], - expected = ["With loss of Eden, till one greater Man"] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - one_file_one_match_multiple_flags( - pattern = "OF ATREUS, Agamemnon, KIng of MEN.", - flags = ["-x", "-i", "-n"], - files = ["iliad.txt"], - expected = ["9:Of Atreus, Agamemnon, King of men."] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - one_file_several_matches_no_flags( - pattern = "may", - flags = [], - files = ["midsummer_night.txt"], - expected = [ - "Nor how it may concern my modesty,", - "But I beseech your grace that I may know", - "The worst that may befall me in this case," - ] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - one_file_several_matches_print_line_numbers_flag( - pattern = "may", - flags = ["-n"], - files = ["midsummer_night.txt"], - expected = [ - "3:Nor how it may concern my modesty,", - "5:But I beseech your grace that I may know", - "6:The worst that may befall me in this case," - ] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - one_file_several_matches_match_entire_lines_flag( - pattern = "may", - flags = ["-x"], - files = ["midsummer_night.txt"], - expected = [] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - one_file_several_matches_caseinsensitive_flag( - pattern = "ACHILLES", - flags = ["-i"], - files = ["iliad.txt"], - expected = [ - "Achilles sing, O Goddess! Peleus' son;", - "The noble Chief Achilles from the son" - ] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - one_file_several_matches_inverted_flag( - pattern = "Of", - flags = ["-v"], - files = ["paradise_lost.txt"], - expected = [ - "Brought Death into the World, and all our woe,", - "With loss of Eden, till one greater Man", - "Restore us, and regain the blissful Seat,", - "Sing Heav'nly Muse, that on the secret top", - "That Shepherd, who first taught the chosen Seed" - ] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - one_file_no_matches_various_flags( - pattern = "Gandalf", - flags = ["-n", "-l", "-x", "-i"], - files = ["iliad.txt"], - expected = [] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - one_file_several_matches_inverted_and_match_entire_lines_flags( - pattern = "Illustrious into Ades premature,", - flags = ["-x", "-v"], - files = ["iliad.txt"], - expected = [ - "Achilles sing, O Goddess! Peleus' son;", - "His wrath pernicious, who ten thousand woes", - "Caused to Achaia's host, sent many a soul", - "And Heroes gave (so stood the will of Jove)", - "To dogs and to all ravening fowls a prey,", - "When fierce dispute had separated once", - "The noble Chief Achilles from the son", - "Of Atreus, Agamemnon, King of men." - ] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - one_file_one_match_file_flag_takes_precedence_over_line_flag( - pattern = "ten", - flags = ["-n", "-l"], - files = ["iliad.txt"], - prefix_expected = ["iliad.txt"] - ) -); - -// Test grepping multiples files at once - -set_up_test_case!( - #[test] - #[ignore] - multiple_files_one_match_no_flags( - pattern = "Agamemnon", - flags = [], - files = ["iliad.txt", "midsummer_night.txt", "paradise_lost.txt"], - prefix_expected = ["iliad.txt:Of Atreus, Agamemnon, King of men."] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - multiple_files_several_matches_no_flags( - pattern = "may", - flags = [], - files = ["iliad.txt", "midsummer_night.txt", "paradise_lost.txt"], - prefix_expected = [ - "midsummer_night.txt:Nor how it may concern my modesty,", - "midsummer_night.txt:But I beseech your grace that I may know", - "midsummer_night.txt:The worst that may befall me in this case," - ] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - multiple_files_several_matches_print_line_numbers_flag( - pattern = "that", - flags = ["-n"], - files = ["iliad.txt", "midsummer_night.txt", "paradise_lost.txt"], - prefix_expected = [ - "midsummer_night.txt:5:But I beseech your grace that I may know", - "midsummer_night.txt:6:The worst that may befall me in this case,", - "paradise_lost.txt:2:Of that Forbidden Tree, whose mortal tast", - "paradise_lost.txt:6:Sing Heav'nly Muse, that on the secret top" - ] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - multiple_files_one_match_print_file_names_flag( - pattern = "who", - flags = ["-l"], - files = ["iliad.txt", "midsummer_night.txt", "paradise_lost.txt"], - prefix_expected = ["iliad.txt", "paradise_lost.txt"] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - multiple_files_several_matches_caseinsensitive_flag( - pattern = "TO", - flags = ["-i"], - files = ["iliad.txt", "midsummer_night.txt", "paradise_lost.txt"], - prefix_expected = [ - "iliad.txt:Caused to Achaia's host, sent many a soul", - "iliad.txt:Illustrious into Ades premature,", - "iliad.txt:And Heroes gave (so stood the will of Jove)", - "iliad.txt:To dogs and to all ravening fowls a prey,", - "midsummer_night.txt:I do entreat your grace to pardon me.", - "midsummer_night.txt:In such a presence here to plead my thoughts;", - "midsummer_night.txt:If I refuse to wed Demetrius.", - "paradise_lost.txt:Brought Death into the World, and all our woe,", - "paradise_lost.txt:Restore us, and regain the blissful Seat,", - "paradise_lost.txt:Sing Heav'nly Muse, that on the secret top" - ] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - multiple_files_several_matches_caseinsensitive_flag_utf8( - pattern = "В", // This letter stands for cyrillic 'Ve' and not latin 'B'. Therefore there should be no matches from paradise_lost.txt - flags = ["-i"], - files = ["paradise_lost.txt", "in_the_white_night.txt"], - prefix_expected = [ - "in_the_white_night.txt:Выплывает в синеве.", - "in_the_white_night.txt:Отражается в Неве.", - "in_the_white_night.txt:Мне провидится и снится", - "in_the_white_night.txt:В вас ли доброе таится," - ] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - multiple_files_several_matches_inverted_flag( - pattern = "a", - flags = ["-v"], - files = ["iliad.txt", "midsummer_night.txt", "paradise_lost.txt"], - prefix_expected = [ - "iliad.txt:Achilles sing, O Goddess! Peleus' son;", - "iliad.txt:The noble Chief Achilles from the son", - "midsummer_night.txt:If I refuse to wed Demetrius." - ] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - multiple_files_one_match_match_entire_lines_flag( - pattern = "But I beseech your grace that I may know", - flags = ["-x"], - files = ["iliad.txt", "midsummer_night.txt", "paradise_lost.txt"], - prefix_expected = ["midsummer_night.txt:But I beseech your grace that I may know"] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - multiple_files_one_match_multiple_flags( - pattern = "WITH LOSS OF EDEN, TILL ONE GREATER MAN", - flags = ["-n", "-i", "-x"], - files = ["iliad.txt", "midsummer_night.txt", "paradise_lost.txt"], - prefix_expected = ["paradise_lost.txt:4:With loss of Eden, till one greater Man"] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - multiple_files_no_matches_various_flags( - pattern = "Frodo", - flags = ["-n", "-i", "-x", "-l"], - files = ["iliad.txt", "midsummer_night.txt", "paradise_lost.txt"], - expected = [] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - multiple_files_several_matches_file_flag_takes_precedence_over_line_number_flag( - pattern = "who", - flags = ["-n", "-l"], - files = ["iliad.txt", "midsummer_night.txt", "paradise_lost.txt"], - prefix_expected = ["iliad.txt", "paradise_lost.txt"] - ) -); - -set_up_test_case!( - #[test] - #[ignore] - multiple_files_several_matches_inverted_and_match_entire_lines_flags( - pattern = "Illustrious into Ades premature,", - flags = ["-x", "-v"], - files = ["iliad.txt", "midsummer_night.txt", "paradise_lost.txt"], - prefix_expected = [ - "iliad.txt:Achilles sing, O Goddess! Peleus' son;", - "iliad.txt:His wrath pernicious, who ten thousand woes", - "iliad.txt:Caused to Achaia's host, sent many a soul", - "iliad.txt:And Heroes gave (so stood the will of Jove)", - "iliad.txt:To dogs and to all ravening fowls a prey,", - "iliad.txt:When fierce dispute had separated once", - "iliad.txt:The noble Chief Achilles from the son", - "iliad.txt:Of Atreus, Agamemnon, King of men.", - "midsummer_night.txt:I do entreat your grace to pardon me.", - "midsummer_night.txt:I know not by what power I am made bold,", - "midsummer_night.txt:Nor how it may concern my modesty,", - "midsummer_night.txt:In such a presence here to plead my thoughts;", - "midsummer_night.txt:But I beseech your grace that I may know", - "midsummer_night.txt:The worst that may befall me in this case,", - "midsummer_night.txt:If I refuse to wed Demetrius.", - "paradise_lost.txt:Of Mans First Disobedience, and the Fruit", - "paradise_lost.txt:Of that Forbidden Tree, whose mortal tast", - "paradise_lost.txt:Brought Death into the World, and all our woe,", - "paradise_lost.txt:With loss of Eden, till one greater Man", - "paradise_lost.txt:Restore us, and regain the blissful Seat,", - "paradise_lost.txt:Sing Heav'nly Muse, that on the secret top", - "paradise_lost.txt:Of Oreb, or of Sinai, didst inspire", - "paradise_lost.txt:That Shepherd, who first taught the chosen Seed" - ] - ) -); From 9b0682907b4a0e28179791051d63ecb925954e0b Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Wed, 14 Aug 2024 13:30:35 +0200 Subject: [PATCH 332/436] paasio: remove macros from tests (#1966) [no important files changed] part of https://github.com/exercism/rust/issues/1824 This is simply a manual expansion of the macros. There is no canonical data from problem specifications, so the tests won't change often and the maintenance burden of the duplication should be acceptable. --- exercises/practice/paasio/tests/paasio.rs | 607 +++++++++++++++------- 1 file changed, 422 insertions(+), 185 deletions(-) diff --git a/exercises/practice/paasio/tests/paasio.rs b/exercises/practice/paasio/tests/paasio.rs index e5c6f5eac..da984be88 100644 --- a/exercises/practice/paasio/tests/paasio.rs +++ b/exercises/practice/paasio/tests/paasio.rs @@ -1,203 +1,440 @@ use std::io::{Error, ErrorKind, Read, Result, Write}; -/// test a few read scenarios -macro_rules! test_read { - ($(#[$attr:meta])* $modname:ident ($input:expr, $len:expr)) => { - mod $modname { - use std::io::{Read, BufReader}; - use paasio::*; - - const CHUNK_SIZE: usize = 2; - - $(#[$attr])* - #[test] - fn read_passthrough() { - let data = $input; - let len = $len; - let size = len(&data); - let mut reader = ReadStats::new(data); - - let mut buffer = Vec::with_capacity(size); - let qty_read = reader.read_to_end(&mut buffer); - - assert!(qty_read.is_ok()); - assert_eq!(size, qty_read.unwrap()); - assert_eq!(size, buffer.len()); - // 2: first to read all the data, second to check that - // there wasn't any more pending data which simply didn't - // fit into the existing buffer - assert_eq!(2, reader.reads()); - assert_eq!(size, reader.bytes_through()); - } - - $(#[$attr])* - #[test] - fn read_chunks() { - let data = $input; - let len = $len; - let size = len(&data); - let mut reader = ReadStats::new(data); - - let mut buffer = [0_u8; CHUNK_SIZE]; - let mut chunks_read = 0; - while reader.read(&mut buffer[..]).unwrap_or_else(|_| panic!("read failed at chunk {}", chunks_read+1)) > 0 { - chunks_read += 1; - } - - assert_eq!(size / CHUNK_SIZE + std::cmp::min(1, size % CHUNK_SIZE), chunks_read); - // we read once more than the number of chunks, because the final - // read returns 0 new bytes - assert_eq!(1+chunks_read, reader.reads()); - assert_eq!(size, reader.bytes_through()); - } - - $(#[$attr])* - #[test] - fn read_buffered_chunks() { - let data = $input; - let len = $len; - let size = len(&data); - let mut reader = BufReader::new(ReadStats::new(data)); - - let mut buffer = [0_u8; CHUNK_SIZE]; - let mut chunks_read = 0; - while reader.read(&mut buffer[..]).unwrap_or_else(|_| panic!("read failed at chunk {}", chunks_read+1)) > 0 { - chunks_read += 1; - } - - assert_eq!(size / CHUNK_SIZE + std::cmp::min(1, size % CHUNK_SIZE), chunks_read); - // the BufReader should smooth out the reads, collecting into - // a buffer and performing only two read operations: - // the first collects everything into the buffer, - // and the second ensures that no data remains - assert_eq!(2, reader.get_ref().reads()); - assert_eq!(size, reader.get_ref().bytes_through()); - } +#[test] +fn create_stats() { + let mut data: Vec = Vec::new(); + let _ = paasio::ReadStats::new(data.as_slice()); + let _ = paasio::WriteStats::new(data.as_mut_slice()); +} + +mod read_string { + use paasio::*; + use std::io::{BufReader, Read}; + + const CHUNK_SIZE: usize = 2; + + static INPUT: &[u8] = b"Twas brillig, and the slithy toves/Did gyre and gimble in the wabe:/All mimsy were the borogoves,/And the mome raths outgrabe."; + + #[test] + #[ignore] + fn read_passthrough() { + let data = INPUT; + let len = |d: &[u8]| d.len(); + let size = len(data); + let mut reader = ReadStats::new(data); + + let mut buffer = Vec::with_capacity(size); + let qty_read = reader.read_to_end(&mut buffer); + + assert!(qty_read.is_ok()); + assert_eq!(size, qty_read.unwrap()); + assert_eq!(size, buffer.len()); + // 2: first to read all the data, second to check that + // there wasn't any more pending data which simply didn't + // fit into the existing buffer + assert_eq!(2, reader.reads()); + assert_eq!(size, reader.bytes_through()); + } + + #[test] + #[ignore] + fn read_chunks() { + let data = INPUT; + let len = |d: &[u8]| d.len(); + let size = len(data); + let mut reader = ReadStats::new(data); + + let mut buffer = [0_u8; CHUNK_SIZE]; + let mut chunks_read = 0; + while reader + .read(&mut buffer[..]) + .unwrap_or_else(|_| panic!("read failed at chunk {}", chunks_read + 1)) + > 0 + { + chunks_read += 1; } - }; + + assert_eq!( + size / CHUNK_SIZE + std::cmp::min(1, size % CHUNK_SIZE), + chunks_read + ); + // we read once more than the number of chunks, because the final + // read returns 0 new bytes + assert_eq!(1 + chunks_read, reader.reads()); + assert_eq!(size, reader.bytes_through()); + } + + #[test] + #[ignore] + fn read_buffered_chunks() { + let data = INPUT; + let len = |d: &[u8]| d.len(); + let size = len(data); + let mut reader = BufReader::new(ReadStats::new(data)); + + let mut buffer = [0_u8; CHUNK_SIZE]; + let mut chunks_read = 0; + while reader + .read(&mut buffer[..]) + .unwrap_or_else(|_| panic!("read failed at chunk {}", chunks_read + 1)) + > 0 + { + chunks_read += 1; + } + + assert_eq!( + size / CHUNK_SIZE + std::cmp::min(1, size % CHUNK_SIZE), + chunks_read + ); + // the BufReader should smooth out the reads, collecting into + // a buffer and performing only two read operations: + // the first collects everything into the buffer, + // and the second ensures that no data remains + assert_eq!(2, reader.get_ref().reads()); + assert_eq!(size, reader.get_ref().bytes_through()); + } } -/// test a few write scenarios -macro_rules! test_write { - ($(#[$attr:meta])* $modname:ident ($input:expr, $len:expr)) => { - mod $modname { - use std::io::{self, Write, BufWriter}; - use paasio::*; - - const CHUNK_SIZE: usize = 2; - $(#[$attr])* - #[test] - fn write_passthrough() { - let data = $input; - let len = $len; - let size = len(&data); - let mut writer = WriteStats::new(Vec::with_capacity(size)); - let written = writer.write(data); - assert!(written.is_ok()); - assert_eq!(size, written.unwrap()); - assert_eq!(size, writer.bytes_through()); - assert_eq!(1, writer.writes()); - assert_eq!(data, writer.get_ref().as_slice()); - } - - $(#[$attr])* - #[test] - fn sink_oneshot() { - let data = $input; - let len = $len; - let size = len(&data); - let mut writer = WriteStats::new(io::sink()); - let written = writer.write(data); - assert!(written.is_ok()); - assert_eq!(size, written.unwrap()); - assert_eq!(size, writer.bytes_through()); - assert_eq!(1, writer.writes()); - } - - $(#[$attr])* - #[test] - fn sink_windowed() { - let data = $input; - let len = $len; - let size = len(&data); - let mut writer = WriteStats::new(io::sink()); - - let mut chunk_count = 0; - for chunk in data.chunks(CHUNK_SIZE) { - chunk_count += 1; - let written = writer.write(chunk); - assert!(written.is_ok()); - assert_eq!(CHUNK_SIZE, written.unwrap()); - } - assert_eq!(size, writer.bytes_through()); - assert_eq!(chunk_count, writer.writes()); - } - - $(#[$attr])* - #[test] - fn sink_buffered_windowed() { - let data = $input; - let len = $len; - let size = len(&data); - let mut writer = BufWriter::new(WriteStats::new(io::sink())); - - for chunk in data.chunks(CHUNK_SIZE) { - let written = writer.write(chunk); - assert!(written.is_ok()); - assert_eq!(CHUNK_SIZE, written.unwrap()); - } - // at this point, nothing should have yet been passed through to - // our writer - assert_eq!(0, writer.get_ref().bytes_through()); - assert_eq!(0, writer.get_ref().writes()); - - // after flushing, everything should pass through in one go - assert!(writer.flush().is_ok()); - assert_eq!(size, writer.get_ref().bytes_through()); - assert_eq!(1, writer.get_ref().writes()); - } +mod write_string { + use paasio::*; + use std::io::{self, BufWriter, Write}; + + const CHUNK_SIZE: usize = 2; + + static INPUT: &[u8] = b"Beware the Jabberwock, my son!/The jaws that bite, the claws that catch!/Beware the Jubjub bird, and shun/The frumious Bandersnatch!"; + + #[test] + #[ignore] + fn write_passthrough() { + let data = INPUT; + let len = |d: &[u8]| d.len(); + let size = len(data); + let mut writer = WriteStats::new(Vec::with_capacity(size)); + let written = writer.write(data); + assert!(written.is_ok()); + assert_eq!(size, written.unwrap()); + assert_eq!(size, writer.bytes_through()); + assert_eq!(1, writer.writes()); + assert_eq!(data, writer.get_ref().as_slice()); + } + + #[test] + #[ignore] + fn sink_oneshot() { + let data = INPUT; + let len = |d: &[u8]| d.len(); + let size = len(data); + let mut writer = WriteStats::new(io::sink()); + let written = writer.write(data); + assert!(written.is_ok()); + assert_eq!(size, written.unwrap()); + assert_eq!(size, writer.bytes_through()); + assert_eq!(1, writer.writes()); + } + + #[test] + #[ignore] + fn sink_windowed() { + let data = INPUT; + let len = |d: &[u8]| d.len(); + let size = len(data); + let mut writer = WriteStats::new(io::sink()); + + let mut chunk_count = 0; + for chunk in data.chunks(CHUNK_SIZE) { + chunk_count += 1; + let written = writer.write(chunk); + assert!(written.is_ok()); + assert_eq!(CHUNK_SIZE, written.unwrap()); } - }; + assert_eq!(size, writer.bytes_through()); + assert_eq!(chunk_count, writer.writes()); + } + + #[test] + #[ignore] + fn sink_buffered_windowed() { + let data = INPUT; + let len = |d: &[u8]| d.len(); + let size = len(data); + let mut writer = BufWriter::new(WriteStats::new(io::sink())); + + for chunk in data.chunks(CHUNK_SIZE) { + let written = writer.write(chunk); + assert!(written.is_ok()); + assert_eq!(CHUNK_SIZE, written.unwrap()); + } + // at this point, nothing should have yet been passed through to + // our writer + assert_eq!(0, writer.get_ref().bytes_through()); + assert_eq!(0, writer.get_ref().writes()); + + // after flushing, everything should pass through in one go + assert!(writer.flush().is_ok()); + assert_eq!(size, writer.get_ref().bytes_through()); + assert_eq!(1, writer.get_ref().writes()); + } } -#[test] -fn create_stats() { - let mut data: Vec = Vec::new(); - let _ = paasio::ReadStats::new(data.as_slice()); - let _ = paasio::WriteStats::new(data.as_mut_slice()); +mod read_byte_literal { + use paasio::*; + use std::io::{BufReader, Read}; + + const CHUNK_SIZE: usize = 2; + + static INPUT: &[u8] = &[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144]; + + #[test] + #[ignore] + fn read_passthrough() { + let data = INPUT; + let len = |d: &[u8]| d.len(); + let size = len(data); + let mut reader = ReadStats::new(data); + + let mut buffer = Vec::with_capacity(size); + let qty_read = reader.read_to_end(&mut buffer); + + assert!(qty_read.is_ok()); + assert_eq!(size, qty_read.unwrap()); + assert_eq!(size, buffer.len()); + // 2: first to read all the data, second to check that + // there wasn't any more pending data which simply didn't + // fit into the existing buffer + assert_eq!(2, reader.reads()); + assert_eq!(size, reader.bytes_through()); + } + + #[test] + #[ignore] + fn read_chunks() { + let data = INPUT; + let len = |d: &[u8]| d.len(); + let size = len(data); + let mut reader = ReadStats::new(data); + + let mut buffer = [0_u8; CHUNK_SIZE]; + let mut chunks_read = 0; + while reader + .read(&mut buffer[..]) + .unwrap_or_else(|_| panic!("read failed at chunk {}", chunks_read + 1)) + > 0 + { + chunks_read += 1; + } + + assert_eq!( + size / CHUNK_SIZE + std::cmp::min(1, size % CHUNK_SIZE), + chunks_read + ); + // we read once more than the number of chunks, because the final + // read returns 0 new bytes + assert_eq!(1 + chunks_read, reader.reads()); + assert_eq!(size, reader.bytes_through()); + } + + #[test] + #[ignore] + fn read_buffered_chunks() { + let data = INPUT; + let len = |d: &[u8]| d.len(); + let size = len(data); + let mut reader = BufReader::new(ReadStats::new(data)); + + let mut buffer = [0_u8; CHUNK_SIZE]; + let mut chunks_read = 0; + while reader + .read(&mut buffer[..]) + .unwrap_or_else(|_| panic!("read failed at chunk {}", chunks_read + 1)) + > 0 + { + chunks_read += 1; + } + + assert_eq!( + size / CHUNK_SIZE + std::cmp::min(1, size % CHUNK_SIZE), + chunks_read + ); + // the BufReader should smooth out the reads, collecting into + // a buffer and performing only two read operations: + // the first collects everything into the buffer, + // and the second ensures that no data remains + assert_eq!(2, reader.get_ref().reads()); + assert_eq!(size, reader.get_ref().bytes_through()); + } } -test_read!(#[ignore] read_string ( - "Twas brillig, and the slithy toves/Did gyre and gimble in the wabe:/All mimsy were the borogoves,/And the mome raths outgrabe.".as_bytes(), - |d: &[u8]| d.len() -)); -test_write!(#[ignore] write_string ( - "Beware the Jabberwock, my son!/The jaws that bite, the claws that catch!/Beware the Jubjub bird, and shun/The frumious Bandersnatch!".as_bytes(), - |d: &[u8]| d.len() -)); +mod write_byte_literal { + use paasio::*; + use std::io::{self, BufWriter, Write}; + + const CHUNK_SIZE: usize = 2; + + static INPUT: &[u8] = &[ + 2_u8, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, + ]; -test_read!( + #[test] #[ignore] - read_byte_literal( - &[1_u8, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144][..], - |d: &[u8]| d.len() - ) -); -test_write!( + fn write_passthrough() { + let data = INPUT; + let len = |d: &[u8]| d.len(); + let size = len(data); + let mut writer = WriteStats::new(Vec::with_capacity(size)); + let written = writer.write(data); + assert!(written.is_ok()); + assert_eq!(size, written.unwrap()); + assert_eq!(size, writer.bytes_through()); + assert_eq!(1, writer.writes()); + assert_eq!(data, writer.get_ref().as_slice()); + } + + #[test] #[ignore] - write_byte_literal( - &[2_u8, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61,][..], - |d: &[u8]| d.len() - ) -); + fn sink_oneshot() { + let data = INPUT; + let len = |d: &[u8]| d.len(); + let size = len(data); + let mut writer = WriteStats::new(io::sink()); + let written = writer.write(data); + assert!(written.is_ok()); + assert_eq!(size, written.unwrap()); + assert_eq!(size, writer.bytes_through()); + assert_eq!(1, writer.writes()); + } -test_read!( + #[test] #[ignore] - read_file( - ::std::fs::File::open("Cargo.toml").expect("Cargo.toml must be present"), - |f: &::std::fs::File| f.metadata().expect("metadata must be present").len() as usize - ) -); + fn sink_windowed() { + let data = INPUT; + let len = |d: &[u8]| d.len(); + let size = len(data); + let mut writer = WriteStats::new(io::sink()); + + let mut chunk_count = 0; + for chunk in data.chunks(CHUNK_SIZE) { + chunk_count += 1; + let written = writer.write(chunk); + assert!(written.is_ok()); + assert_eq!(CHUNK_SIZE, written.unwrap()); + } + assert_eq!(size, writer.bytes_through()); + assert_eq!(chunk_count, writer.writes()); + } + + #[test] + #[ignore] + fn sink_buffered_windowed() { + let data = INPUT; + let len = |d: &[u8]| d.len(); + let size = len(data); + let mut writer = BufWriter::new(WriteStats::new(io::sink())); + + for chunk in data.chunks(CHUNK_SIZE) { + let written = writer.write(chunk); + assert!(written.is_ok()); + assert_eq!(CHUNK_SIZE, written.unwrap()); + } + // at this point, nothing should have yet been passed through to + // our writer + assert_eq!(0, writer.get_ref().bytes_through()); + assert_eq!(0, writer.get_ref().writes()); + + // after flushing, everything should pass through in one go + assert!(writer.flush().is_ok()); + assert_eq!(size, writer.get_ref().bytes_through()); + assert_eq!(1, writer.get_ref().writes()); + } +} + +mod read_file { + use paasio::*; + use std::io::{BufReader, Read}; + + const CHUNK_SIZE: usize = 2; + + #[test] + #[ignore] + fn read_passthrough() { + let data = std::fs::File::open("Cargo.toml").expect("Cargo.toml must be present"); + let len = + |f: &::std::fs::File| f.metadata().expect("metadata must be present").len() as usize; + let size = len(&data); + let mut reader = ReadStats::new(data); + + let mut buffer = Vec::with_capacity(size); + let qty_read = reader.read_to_end(&mut buffer); + + assert!(qty_read.is_ok()); + assert_eq!(size, qty_read.unwrap()); + assert_eq!(size, buffer.len()); + // 2: first to read all the data, second to check that + // there wasn't any more pending data which simply didn't + // fit into the existing buffer + assert_eq!(2, reader.reads()); + assert_eq!(size, reader.bytes_through()); + } + + #[test] + #[ignore] + fn read_chunks() { + let data = std::fs::File::open("Cargo.toml").expect("Cargo.toml must be present"); + let len = + |f: &::std::fs::File| f.metadata().expect("metadata must be present").len() as usize; + let size = len(&data); + let mut reader = ReadStats::new(data); + + let mut buffer = [0_u8; CHUNK_SIZE]; + let mut chunks_read = 0; + while reader + .read(&mut buffer[..]) + .unwrap_or_else(|_| panic!("read failed at chunk {}", chunks_read + 1)) + > 0 + { + chunks_read += 1; + } + + assert_eq!( + size / CHUNK_SIZE + std::cmp::min(1, size % CHUNK_SIZE), + chunks_read + ); + // we read once more than the number of chunks, because the final + // read returns 0 new bytes + assert_eq!(1 + chunks_read, reader.reads()); + assert_eq!(size, reader.bytes_through()); + } + + #[test] + #[ignore] + fn read_buffered_chunks() { + let data = std::fs::File::open("Cargo.toml").expect("Cargo.toml must be present"); + let len = + |f: &::std::fs::File| f.metadata().expect("metadata must be present").len() as usize; + let size = len(&data); + let mut reader = BufReader::new(ReadStats::new(data)); + + let mut buffer = [0_u8; CHUNK_SIZE]; + let mut chunks_read = 0; + while reader + .read(&mut buffer[..]) + .unwrap_or_else(|_| panic!("read failed at chunk {}", chunks_read + 1)) + > 0 + { + chunks_read += 1; + } + + assert_eq!( + size / CHUNK_SIZE + std::cmp::min(1, size % CHUNK_SIZE), + chunks_read + ); + // the BufReader should smooth out the reads, collecting into + // a buffer and performing only two read operations: + // the first collects everything into the buffer, + // and the second ensures that no data remains + assert_eq!(2, reader.get_ref().reads()); + assert_eq!(size, reader.get_ref().bytes_through()); + } +} #[test] #[ignore] From bbd6958ef92f2c1fa476731582586cfe251c5770 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Thu, 15 Aug 2024 08:11:49 +0200 Subject: [PATCH 333/436] xorcism: remove macro from tests (#1967) [no important files changed] part of https://github.com/exercism/rust/issues/1824 --- .../xorcism/.meta/additional-tests.json | 90 + .../practice/xorcism/.meta/test_template.tera | 290 +++ exercises/practice/xorcism/tests/xorcism.rs | 1925 ++++++++++++++--- rust-tooling/models/src/exercise_config.rs | 4 +- 4 files changed, 2065 insertions(+), 244 deletions(-) create mode 100644 exercises/practice/xorcism/.meta/additional-tests.json create mode 100644 exercises/practice/xorcism/.meta/test_template.tera diff --git a/exercises/practice/xorcism/.meta/additional-tests.json b/exercises/practice/xorcism/.meta/additional-tests.json new file mode 100644 index 000000000..2799c2bb8 --- /dev/null +++ b/exercises/practice/xorcism/.meta/additional-tests.json @@ -0,0 +1,90 @@ +[ + { + "uuid": "93f3415a-28f4-40e9-9df6-9d61bef26108", + "description": "key_shorter_than_data", + "comments": ["xorcism doesn't have canonical data, but we want to use the templating system"], + "property": "xorcism", + "input": { + "key": "abcde", + "input": "123455" + }, + "expected": [80,80,80,80,80,84] + }, + { + "uuid": "93f3415a-28f4-40e9-9df6-9d61bef26108", + "description": "key_len_equal_to_data", + "comments": ["xorcism doesn't have canonical data, but we want to use the templating system"], + "property": "xorcism", + "input": { + "key": "The quick brown fox jumped over the lazy dog.", + "input": "Wait, oops, this is not the pangram exercise!" + }, + "expected": [3,9,12,84,93,85,6,12,27,83,78,82,27,31,7,83,70,6,11,0,4,26,25,80,17,12,69,79,6,4,28,71,6,9,8,0,9,25,31,11,67,13,28,2,15] + }, + { + "uuid": "93f3415a-28f4-40e9-9df6-9d61bef26108", + "description": "key_longer_than_data", + "comments": ["xorcism doesn't have canonical data, but we want to use the templating system"], + "property": "xorcism", + "input": { + "key": "A properly cryptographically random key longer than the data can actually be fairly secure.", + "input": "Text is not cryptographically random." + }, + "expected": [21,69,8,6,79,25,22,82,2,22,84,67,17,11,9,4,27,8,21,19,17,24,1,10,2,13,0,21,89,82,19,15,10,11,2,77,69] + }, + { + "uuid": "93f3415a-28f4-40e9-9df6-9d61bef26108", + "description": "shakespearean", + "comments": ["xorcism doesn't have canonical data, but we want to use the templating system"], + "property": "xorcism", + "input": { + "key": "Forsooth, let us never break our trust!", + "input": "The sacred brothership in which we share shall never from our hearts be lost." + }, + "expected": [18,7,23,83,28,14,23,26,73,68,76,7,6,79,1,27,69,28,22,30,12,2,0,11,28,69,22,3,73,12,29,82,87,17,82,6,27,21,83,35,79,1,27,14,3,24,72,66,69,26,0,6,0,19,1,79,3,69,25,16,0,0,10,23,4,19,31,83,79,23,23,0,24,29,6,7,90] + }, + { + "uuid": "93f3415a-28f4-40e9-9df6-9d61bef26108", + "description": "comics", + "comments": ["xorcism doesn't have canonical data, but we want to use the templating system"], + "property": "xorcism", + "input": { + "key": "Who knows what evil lurks in the hearts of men?", + "input": "Spiderman! It's spiderman! Not a bird, or a plane, or a fireman! Just spiderman!" + }, + "expected": [4,24,6,68,14,28,2,22,29,1,87,33,21,83,83,69,5,25,5,68,9,7,31,10,29,1,73,32,79,0,72,4,0,10,12,19,22,88,83,79,29,70,65,77,21,2,94,57,13,67,0,4,28,79,22,83,70,30,26,4,25,65,11,87,73,38,85,31,1,82,24,3,73,13,11,82,25,9,11,1] + }, + { + "uuid": "93f3415a-28f4-40e9-9df6-9d61bef26108", + "description": "mad_science", + "comments": ["xorcism doesn't have canonical data, but we want to use the templating system"], + "property": "xorcism", + "input": { + "key": "TRANSMUTATION_NOTES_1", + "input": "If wishes were horses, beggars would ride." + }, + "expected": [29,52,97,57,58,62,61,49,50,116,62,42,60,58,110,39,59,55,32,58,66,120,114,35,43,52,42,52,38,50,116,62,32,59,51,42,111,38,44,55,58,31] + }, + { + "uuid": "93f3415a-28f4-40e9-9df6-9d61bef26108", + "description": "metaphor", + "comments": ["xorcism doesn't have canonical data, but we want to use the templating system"], + "property": "xorcism", + "input": { + "key": "Contextualism", + "input": "The globe is text, its people prose; all the world's a page." + }, + "expected": [23,7,11,84,2,20,27,23,4,76,0,0,77,55,10,22,0,73,88,29,1,18,76,25,22,2,51,3,11,84,21,10,27,6,4,87,73,18,1,47,79,26,28,0,88,3,26,19,0,13,84,30,99,14,78,4,4,31,17,91] + }, + { + "uuid": "93f3415a-28f4-40e9-9df6-9d61bef26108", + "description": "emoji", + "comments": ["xorcism doesn't have canonical data, but we want to use the templating system"], + "property": "xorcism", + "input": { + "key": "🔑🗝️… 🎹?", + "input": "⌨️! 🔒+💻+🧠=🔓" + }, + "expected": [18,19,60,126,72,16,182,189,31,39,27,112,171,86,191,98,36,165,73,160,87,63,169,97,111,11,4] + } +] diff --git a/exercises/practice/xorcism/.meta/test_template.tera b/exercises/practice/xorcism/.meta/test_template.tera new file mode 100644 index 000000000..58fdea30c --- /dev/null +++ b/exercises/practice/xorcism/.meta/test_template.tera @@ -0,0 +1,290 @@ +#[cfg(feature = "io")] +use std::io::{Read, Write}; +use xorcism::Xorcism; + +#[test] +#[ignore] +fn munge_in_place_identity() { + let mut xs = Xorcism::new(&[0]); + let input = "This is super-secret, cutting edge encryption, folks.".as_bytes(); + let mut output = input.to_owned(); + xs.munge_in_place(&mut output); + + assert_eq!(&input, &output); +} + +#[test] +#[ignore] +fn munge_in_place_roundtrip() { + let mut xs1 = Xorcism::new(&[1, 2, 3, 4, 5]); + let mut xs2 = Xorcism::new(&[1, 2, 3, 4, 5]); + let input = "This is super-secret, cutting edge encryption, folks.".as_bytes(); + let mut cipher = input.to_owned(); + xs1.munge_in_place(&mut cipher); + assert_ne!(&input, &cipher); + let mut output = cipher; + xs2.munge_in_place(&mut output); + assert_eq!(&input, &output); +} + +#[test] +#[ignore] +fn munge_in_place_stateful() { + let mut xs = Xorcism::new(&[1, 2, 3, 4, 5]); + let input = "This is super-secret, cutting edge encryption, folks.".as_bytes(); + + let mut cipher1 = input.to_owned(); + let mut cipher2 = input.to_owned(); + xs.munge_in_place(&mut cipher1); + xs.munge_in_place(&mut cipher2); + + assert_ne!(&input, &cipher1); + assert_ne!(&input, &cipher2); + assert_ne!(&cipher1, &cipher2); +} + +#[test] +#[ignore] +fn munge_identity() { + let mut xs = Xorcism::new(&[0]); + let data = "This is super-secret, cutting edge encryption, folks."; + + assert_eq!( + xs.munge(data.as_bytes()).collect::>(), + data.as_bytes() + ); +} + +#[test] +#[ignore] +fn statefulness() { + // we expect Xorcism to be stateful: at the end of a munging run, the key has rotated. + // this means that until the key has completely rotated around, equal inputs will produce + // unequal outputs. + let key = &[0, 1, 2, 3, 4, 5, 6, 7]; + let input = &[0b1010_1010, 0b0101_0101]; + + let mut xs = Xorcism::new(&key); + let out1: Vec<_> = xs.munge(input).collect(); + let out2: Vec<_> = xs.munge(input).collect(); + let out3: Vec<_> = xs.munge(input).collect(); + let out4: Vec<_> = xs.munge(input).collect(); + let out5: Vec<_> = xs.munge(input).collect(); + + assert_ne!(out1, out2); + assert_ne!(out2, out3); + assert_ne!(out3, out4); + assert_ne!(out4, out5); + assert_eq!(out1, out5); +} + +{% for test in cases %} + +mod {{ test.description | snake_case }} { + + use super::*; + + const KEY: &str = {{ test.input.key | json_encode() }}; + const INPUT: &str = {{ test.input.input | json_encode() }}; + const EXPECT: &[u8] = &{{ test.expected | json_encode() }}; + + /// tests where the key is expressed as `&str`, and input is expressed as `&[u8]` + mod str_slice { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + // we transform the input into a `Vec` despite its presence in this + // module because of the more restricted syntax that this function accepts + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let mut xorcism = Xorcism::new(KEY); + let result: Vec = xorcism.munge(INPUT.as_bytes()).collect(); + assert_eq!(INPUT.len(), result.len()); + assert_ne!(INPUT.as_bytes(), result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(INPUT.as_bytes()); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(INPUT.as_bytes(), result); + } + } + + /// tests where the key and input are both expressed as `&[u8]` + mod slice_slice { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + let key = KEY.as_bytes(); + + // we transform the input into a `Vec` despite its presence in this + // module because of the more restricted syntax that this function accepts + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(key); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let key = KEY.as_bytes(); + let input = INPUT.as_bytes(); + + let mut xorcism = Xorcism::new(key); + let result: Vec = xorcism.munge(input).collect(); + assert_eq!(input.len(), result.len()); + assert_ne!(input, result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let key = KEY.as_bytes(); + let input = INPUT.as_bytes(); + + let mut xorcism1 = Xorcism::new(key); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(input); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(input, result); + } + } + + /// tests where the key is expressed as `&str` and input is expressed as `Vec` + mod vec_vec { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let owned_input = INPUT.as_bytes().to_vec(); + + let mut xorcism = Xorcism::new(KEY); + let result: Vec = xorcism.munge(owned_input).collect(); + assert_eq!(INPUT.len(), result.len()); + assert_ne!(INPUT.as_bytes(), result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let owned_input = INPUT.as_bytes().to_vec(); + + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(owned_input); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(INPUT.as_bytes(), result); + } + } + + #[cfg(feature = "io")] + mod io { + use super::*; + + #[test] + #[ignore] + fn reader_munges() { + let mut reader = Xorcism::new(KEY).reader(INPUT.as_bytes()); + let mut buf = Vec::with_capacity(INPUT.len()); + let bytes_read = reader.read_to_end(&mut buf).unwrap(); + assert_eq!(bytes_read, INPUT.len()); + assert_eq!(buf, EXPECT); + } + + #[test] + #[ignore] + fn reader_roundtrip() { + let xs = Xorcism::new(KEY); + let reader1 = xs.clone().reader(INPUT.as_bytes()); + let mut reader2 = xs.clone().reader(reader1); + let mut buf = Vec::with_capacity(INPUT.len()); + let bytes_read = reader2.read_to_end(&mut buf).unwrap(); + assert_eq!(bytes_read, INPUT.len()); + assert_eq!(buf, INPUT.as_bytes()); + } + + #[test] + #[ignore] + fn writer_munges() { + let mut writer_dest = Vec::new(); + { + let mut writer = Xorcism::new(KEY).writer(&mut writer_dest); + assert!(writer.write_all(INPUT.as_bytes()).is_ok()); + } + assert_eq!(writer_dest, EXPECT); + } + + #[test] + #[ignore] + fn writer_roundtrip() { + let mut writer_dest = Vec::new(); + let xs = Xorcism::new(KEY); + { + let writer1 = xs.clone().writer(&mut writer_dest); + let mut writer2 = xs.writer(writer1); + assert!(writer2.write_all(INPUT.as_bytes()).is_ok()); + } + assert_eq!(writer_dest, INPUT.as_bytes()); + } + } +} +{% endfor -%} diff --git a/exercises/practice/xorcism/tests/xorcism.rs b/exercises/practice/xorcism/tests/xorcism.rs index ccd1337eb..fe237c38c 100644 --- a/exercises/practice/xorcism/tests/xorcism.rs +++ b/exercises/practice/xorcism/tests/xorcism.rs @@ -77,255 +77,1694 @@ fn statefulness() { assert_eq!(out1, out5); } -macro_rules! test_cases { - ($($name:ident, $key:literal, $input:literal, $expect:expr);+) => { - $(mod $name { - use super::*; - - const KEY: &str = $key; - const INPUT: &str = $input; - const EXPECT: &[u8] = $expect; - - /// tests where the key is expressed as `&str`, and input is expressed as `&[u8]` - mod str_slice { - use super::*; - - #[test] - #[ignore] - fn munge_in_place() { - // we transform the input into a `Vec` despite its presence in this - // module because of the more restricted syntax that this function accepts - let mut input = INPUT.as_bytes().to_vec(); - let original = input.clone(); - - // in-place munging is stateful on Xorcism, so clone it - // to ensure the keys positions stay synchronized - let mut xorcism1 = Xorcism::new(KEY); - let mut xorcism2 = xorcism1.clone(); - - xorcism1.munge_in_place(&mut input); - assert_eq!(input.len(), original.len()); - assert_ne!(input, original); - assert_eq!(input, EXPECT); - xorcism2.munge_in_place(&mut input); - assert_eq!(input, original); - } - - #[test] - #[ignore] - fn munges() { - let mut xorcism = Xorcism::new(KEY); - let result: Vec = xorcism.munge(INPUT.as_bytes()).collect(); - assert_eq!(INPUT.len(), result.len()); - assert_ne!(INPUT.as_bytes(), result); - assert_eq!(result, EXPECT); - } - - #[test] - #[ignore] - fn round_trip() { - let mut xorcism1 = Xorcism::new(KEY); - let mut xorcism2 = xorcism1.clone(); - let munge_iter = xorcism1.munge(INPUT.as_bytes()); - let result: Vec = xorcism2.munge(munge_iter).collect(); - assert_eq!(INPUT.as_bytes(), result); - } +mod key_shorter_than_data { + + use super::*; + + const KEY: &str = "abcde"; + const INPUT: &str = "123455"; + const EXPECT: &[u8] = &[80, 80, 80, 80, 80, 84]; + + /// tests where the key is expressed as `&str`, and input is expressed as `&[u8]` + mod str_slice { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + // we transform the input into a `Vec` despite its presence in this + // module because of the more restricted syntax that this function accepts + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let mut xorcism = Xorcism::new(KEY); + let result: Vec = xorcism.munge(INPUT.as_bytes()).collect(); + assert_eq!(INPUT.len(), result.len()); + assert_ne!(INPUT.as_bytes(), result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(INPUT.as_bytes()); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(INPUT.as_bytes(), result); + } + } + + /// tests where the key and input are both expressed as `&[u8]` + mod slice_slice { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + let key = KEY.as_bytes(); + + // we transform the input into a `Vec` despite its presence in this + // module because of the more restricted syntax that this function accepts + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(key); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let key = KEY.as_bytes(); + let input = INPUT.as_bytes(); + + let mut xorcism = Xorcism::new(key); + let result: Vec = xorcism.munge(input).collect(); + assert_eq!(input.len(), result.len()); + assert_ne!(input, result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let key = KEY.as_bytes(); + let input = INPUT.as_bytes(); + + let mut xorcism1 = Xorcism::new(key); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(input); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(input, result); + } + } + + /// tests where the key is expressed as `&str` and input is expressed as `Vec` + mod vec_vec { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let owned_input = INPUT.as_bytes().to_vec(); + + let mut xorcism = Xorcism::new(KEY); + let result: Vec = xorcism.munge(owned_input).collect(); + assert_eq!(INPUT.len(), result.len()); + assert_ne!(INPUT.as_bytes(), result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let owned_input = INPUT.as_bytes().to_vec(); + + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(owned_input); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(INPUT.as_bytes(), result); + } + } + + #[cfg(feature = "io")] + mod io { + use super::*; + + #[test] + #[ignore] + fn reader_munges() { + let mut reader = Xorcism::new(KEY).reader(INPUT.as_bytes()); + let mut buf = Vec::with_capacity(INPUT.len()); + let bytes_read = reader.read_to_end(&mut buf).unwrap(); + assert_eq!(bytes_read, INPUT.len()); + assert_eq!(buf, EXPECT); + } + + #[test] + #[ignore] + fn reader_roundtrip() { + let xs = Xorcism::new(KEY); + let reader1 = xs.clone().reader(INPUT.as_bytes()); + let mut reader2 = xs.clone().reader(reader1); + let mut buf = Vec::with_capacity(INPUT.len()); + let bytes_read = reader2.read_to_end(&mut buf).unwrap(); + assert_eq!(bytes_read, INPUT.len()); + assert_eq!(buf, INPUT.as_bytes()); + } + + #[test] + #[ignore] + fn writer_munges() { + let mut writer_dest = Vec::new(); + { + let mut writer = Xorcism::new(KEY).writer(&mut writer_dest); + assert!(writer.write_all(INPUT.as_bytes()).is_ok()); + } + assert_eq!(writer_dest, EXPECT); + } + + #[test] + #[ignore] + fn writer_roundtrip() { + let mut writer_dest = Vec::new(); + let xs = Xorcism::new(KEY); + { + let writer1 = xs.clone().writer(&mut writer_dest); + let mut writer2 = xs.writer(writer1); + assert!(writer2.write_all(INPUT.as_bytes()).is_ok()); + } + assert_eq!(writer_dest, INPUT.as_bytes()); + } + } +} + +mod key_len_equal_to_data { + + use super::*; + + const KEY: &str = "The quick brown fox jumped over the lazy dog."; + const INPUT: &str = "Wait, oops, this is not the pangram exercise!"; + const EXPECT: &[u8] = &[ + 3, 9, 12, 84, 93, 85, 6, 12, 27, 83, 78, 82, 27, 31, 7, 83, 70, 6, 11, 0, 4, 26, 25, 80, + 17, 12, 69, 79, 6, 4, 28, 71, 6, 9, 8, 0, 9, 25, 31, 11, 67, 13, 28, 2, 15, + ]; + + /// tests where the key is expressed as `&str`, and input is expressed as `&[u8]` + mod str_slice { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + // we transform the input into a `Vec` despite its presence in this + // module because of the more restricted syntax that this function accepts + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let mut xorcism = Xorcism::new(KEY); + let result: Vec = xorcism.munge(INPUT.as_bytes()).collect(); + assert_eq!(INPUT.len(), result.len()); + assert_ne!(INPUT.as_bytes(), result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(INPUT.as_bytes()); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(INPUT.as_bytes(), result); + } + } + + /// tests where the key and input are both expressed as `&[u8]` + mod slice_slice { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + let key = KEY.as_bytes(); + + // we transform the input into a `Vec` despite its presence in this + // module because of the more restricted syntax that this function accepts + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(key); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let key = KEY.as_bytes(); + let input = INPUT.as_bytes(); + + let mut xorcism = Xorcism::new(key); + let result: Vec = xorcism.munge(input).collect(); + assert_eq!(input.len(), result.len()); + assert_ne!(input, result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let key = KEY.as_bytes(); + let input = INPUT.as_bytes(); + + let mut xorcism1 = Xorcism::new(key); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(input); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(input, result); + } + } + + /// tests where the key is expressed as `&str` and input is expressed as `Vec` + mod vec_vec { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let owned_input = INPUT.as_bytes().to_vec(); + + let mut xorcism = Xorcism::new(KEY); + let result: Vec = xorcism.munge(owned_input).collect(); + assert_eq!(INPUT.len(), result.len()); + assert_ne!(INPUT.as_bytes(), result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let owned_input = INPUT.as_bytes().to_vec(); + + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(owned_input); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(INPUT.as_bytes(), result); + } + } + + #[cfg(feature = "io")] + mod io { + use super::*; + + #[test] + #[ignore] + fn reader_munges() { + let mut reader = Xorcism::new(KEY).reader(INPUT.as_bytes()); + let mut buf = Vec::with_capacity(INPUT.len()); + let bytes_read = reader.read_to_end(&mut buf).unwrap(); + assert_eq!(bytes_read, INPUT.len()); + assert_eq!(buf, EXPECT); + } + + #[test] + #[ignore] + fn reader_roundtrip() { + let xs = Xorcism::new(KEY); + let reader1 = xs.clone().reader(INPUT.as_bytes()); + let mut reader2 = xs.clone().reader(reader1); + let mut buf = Vec::with_capacity(INPUT.len()); + let bytes_read = reader2.read_to_end(&mut buf).unwrap(); + assert_eq!(bytes_read, INPUT.len()); + assert_eq!(buf, INPUT.as_bytes()); + } + + #[test] + #[ignore] + fn writer_munges() { + let mut writer_dest = Vec::new(); + { + let mut writer = Xorcism::new(KEY).writer(&mut writer_dest); + assert!(writer.write_all(INPUT.as_bytes()).is_ok()); + } + assert_eq!(writer_dest, EXPECT); + } + + #[test] + #[ignore] + fn writer_roundtrip() { + let mut writer_dest = Vec::new(); + let xs = Xorcism::new(KEY); + { + let writer1 = xs.clone().writer(&mut writer_dest); + let mut writer2 = xs.writer(writer1); + assert!(writer2.write_all(INPUT.as_bytes()).is_ok()); + } + assert_eq!(writer_dest, INPUT.as_bytes()); + } + } +} + +mod key_longer_than_data { + + use super::*; + + const KEY: &str = "A properly cryptographically random key longer than the data can actually be fairly secure."; + const INPUT: &str = "Text is not cryptographically random."; + const EXPECT: &[u8] = &[ + 21, 69, 8, 6, 79, 25, 22, 82, 2, 22, 84, 67, 17, 11, 9, 4, 27, 8, 21, 19, 17, 24, 1, 10, 2, + 13, 0, 21, 89, 82, 19, 15, 10, 11, 2, 77, 69, + ]; + + /// tests where the key is expressed as `&str`, and input is expressed as `&[u8]` + mod str_slice { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + // we transform the input into a `Vec` despite its presence in this + // module because of the more restricted syntax that this function accepts + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let mut xorcism = Xorcism::new(KEY); + let result: Vec = xorcism.munge(INPUT.as_bytes()).collect(); + assert_eq!(INPUT.len(), result.len()); + assert_ne!(INPUT.as_bytes(), result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(INPUT.as_bytes()); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(INPUT.as_bytes(), result); + } + } + + /// tests where the key and input are both expressed as `&[u8]` + mod slice_slice { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + let key = KEY.as_bytes(); + + // we transform the input into a `Vec` despite its presence in this + // module because of the more restricted syntax that this function accepts + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(key); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let key = KEY.as_bytes(); + let input = INPUT.as_bytes(); + + let mut xorcism = Xorcism::new(key); + let result: Vec = xorcism.munge(input).collect(); + assert_eq!(input.len(), result.len()); + assert_ne!(input, result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let key = KEY.as_bytes(); + let input = INPUT.as_bytes(); + + let mut xorcism1 = Xorcism::new(key); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(input); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(input, result); + } + } + + /// tests where the key is expressed as `&str` and input is expressed as `Vec` + mod vec_vec { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let owned_input = INPUT.as_bytes().to_vec(); + + let mut xorcism = Xorcism::new(KEY); + let result: Vec = xorcism.munge(owned_input).collect(); + assert_eq!(INPUT.len(), result.len()); + assert_ne!(INPUT.as_bytes(), result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let owned_input = INPUT.as_bytes().to_vec(); + + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(owned_input); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(INPUT.as_bytes(), result); + } + } + + #[cfg(feature = "io")] + mod io { + use super::*; + + #[test] + #[ignore] + fn reader_munges() { + let mut reader = Xorcism::new(KEY).reader(INPUT.as_bytes()); + let mut buf = Vec::with_capacity(INPUT.len()); + let bytes_read = reader.read_to_end(&mut buf).unwrap(); + assert_eq!(bytes_read, INPUT.len()); + assert_eq!(buf, EXPECT); + } + + #[test] + #[ignore] + fn reader_roundtrip() { + let xs = Xorcism::new(KEY); + let reader1 = xs.clone().reader(INPUT.as_bytes()); + let mut reader2 = xs.clone().reader(reader1); + let mut buf = Vec::with_capacity(INPUT.len()); + let bytes_read = reader2.read_to_end(&mut buf).unwrap(); + assert_eq!(bytes_read, INPUT.len()); + assert_eq!(buf, INPUT.as_bytes()); + } + + #[test] + #[ignore] + fn writer_munges() { + let mut writer_dest = Vec::new(); + { + let mut writer = Xorcism::new(KEY).writer(&mut writer_dest); + assert!(writer.write_all(INPUT.as_bytes()).is_ok()); + } + assert_eq!(writer_dest, EXPECT); + } + + #[test] + #[ignore] + fn writer_roundtrip() { + let mut writer_dest = Vec::new(); + let xs = Xorcism::new(KEY); + { + let writer1 = xs.clone().writer(&mut writer_dest); + let mut writer2 = xs.writer(writer1); + assert!(writer2.write_all(INPUT.as_bytes()).is_ok()); + } + assert_eq!(writer_dest, INPUT.as_bytes()); + } + } +} + +mod shakespearean { + + use super::*; + + const KEY: &str = "Forsooth, let us never break our trust!"; + const INPUT: &str = + "The sacred brothership in which we share shall never from our hearts be lost."; + const EXPECT: &[u8] = &[ + 18, 7, 23, 83, 28, 14, 23, 26, 73, 68, 76, 7, 6, 79, 1, 27, 69, 28, 22, 30, 12, 2, 0, 11, + 28, 69, 22, 3, 73, 12, 29, 82, 87, 17, 82, 6, 27, 21, 83, 35, 79, 1, 27, 14, 3, 24, 72, 66, + 69, 26, 0, 6, 0, 19, 1, 79, 3, 69, 25, 16, 0, 0, 10, 23, 4, 19, 31, 83, 79, 23, 23, 0, 24, + 29, 6, 7, 90, + ]; + + /// tests where the key is expressed as `&str`, and input is expressed as `&[u8]` + mod str_slice { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + // we transform the input into a `Vec` despite its presence in this + // module because of the more restricted syntax that this function accepts + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let mut xorcism = Xorcism::new(KEY); + let result: Vec = xorcism.munge(INPUT.as_bytes()).collect(); + assert_eq!(INPUT.len(), result.len()); + assert_ne!(INPUT.as_bytes(), result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(INPUT.as_bytes()); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(INPUT.as_bytes(), result); + } + } + + /// tests where the key and input are both expressed as `&[u8]` + mod slice_slice { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + let key = KEY.as_bytes(); + + // we transform the input into a `Vec` despite its presence in this + // module because of the more restricted syntax that this function accepts + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(key); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let key = KEY.as_bytes(); + let input = INPUT.as_bytes(); + + let mut xorcism = Xorcism::new(key); + let result: Vec = xorcism.munge(input).collect(); + assert_eq!(input.len(), result.len()); + assert_ne!(input, result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let key = KEY.as_bytes(); + let input = INPUT.as_bytes(); + + let mut xorcism1 = Xorcism::new(key); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(input); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(input, result); + } + } + + /// tests where the key is expressed as `&str` and input is expressed as `Vec` + mod vec_vec { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let owned_input = INPUT.as_bytes().to_vec(); + + let mut xorcism = Xorcism::new(KEY); + let result: Vec = xorcism.munge(owned_input).collect(); + assert_eq!(INPUT.len(), result.len()); + assert_ne!(INPUT.as_bytes(), result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let owned_input = INPUT.as_bytes().to_vec(); + + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(owned_input); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(INPUT.as_bytes(), result); + } + } + + #[cfg(feature = "io")] + mod io { + use super::*; + + #[test] + #[ignore] + fn reader_munges() { + let mut reader = Xorcism::new(KEY).reader(INPUT.as_bytes()); + let mut buf = Vec::with_capacity(INPUT.len()); + let bytes_read = reader.read_to_end(&mut buf).unwrap(); + assert_eq!(bytes_read, INPUT.len()); + assert_eq!(buf, EXPECT); + } + + #[test] + #[ignore] + fn reader_roundtrip() { + let xs = Xorcism::new(KEY); + let reader1 = xs.clone().reader(INPUT.as_bytes()); + let mut reader2 = xs.clone().reader(reader1); + let mut buf = Vec::with_capacity(INPUT.len()); + let bytes_read = reader2.read_to_end(&mut buf).unwrap(); + assert_eq!(bytes_read, INPUT.len()); + assert_eq!(buf, INPUT.as_bytes()); + } + + #[test] + #[ignore] + fn writer_munges() { + let mut writer_dest = Vec::new(); + { + let mut writer = Xorcism::new(KEY).writer(&mut writer_dest); + assert!(writer.write_all(INPUT.as_bytes()).is_ok()); } + assert_eq!(writer_dest, EXPECT); + } - /// tests where the key and input are both expressed as `&[u8]` - mod slice_slice { - use super::*; - - #[test] - #[ignore] - fn munge_in_place() { - let key = KEY.as_bytes(); - - // we transform the input into a `Vec` despite its presence in this - // module because of the more restricted syntax that this function accepts - let mut input = INPUT.as_bytes().to_vec(); - let original = input.clone(); - - // in-place munging is stateful on Xorcism, so clone it - // to ensure the keys positions stay synchronized - let mut xorcism1 = Xorcism::new(key); - let mut xorcism2 = xorcism1.clone(); - - xorcism1.munge_in_place(&mut input); - assert_eq!(input.len(), original.len()); - assert_ne!(input, original); - assert_eq!(input, EXPECT); - xorcism2.munge_in_place(&mut input); - assert_eq!(input, original); - } - - #[test] - #[ignore] - fn munges() { - let key = KEY.as_bytes(); - let input = INPUT.as_bytes(); - - let mut xorcism = Xorcism::new(key); - let result: Vec = xorcism.munge(input).collect(); - assert_eq!(input.len(), result.len()); - assert_ne!(input, result); - assert_eq!(result, EXPECT); - } - - #[test] - #[ignore] - fn round_trip() { - let key = KEY.as_bytes(); - let input = INPUT.as_bytes(); - - let mut xorcism1 = Xorcism::new(key); - let mut xorcism2 = xorcism1.clone(); - let munge_iter = xorcism1.munge(input); - let result: Vec = xorcism2.munge(munge_iter).collect(); - assert_eq!(input, result); - } + #[test] + #[ignore] + fn writer_roundtrip() { + let mut writer_dest = Vec::new(); + let xs = Xorcism::new(KEY); + { + let writer1 = xs.clone().writer(&mut writer_dest); + let mut writer2 = xs.writer(writer1); + assert!(writer2.write_all(INPUT.as_bytes()).is_ok()); } + assert_eq!(writer_dest, INPUT.as_bytes()); + } + } +} + +mod comics { + + use super::*; + + const KEY: &str = "Who knows what evil lurks in the hearts of men?"; + const INPUT: &str = + "Spiderman! It's spiderman! Not a bird, or a plane, or a fireman! Just spiderman!"; + const EXPECT: &[u8] = &[ + 4, 24, 6, 68, 14, 28, 2, 22, 29, 1, 87, 33, 21, 83, 83, 69, 5, 25, 5, 68, 9, 7, 31, 10, 29, + 1, 73, 32, 79, 0, 72, 4, 0, 10, 12, 19, 22, 88, 83, 79, 29, 70, 65, 77, 21, 2, 94, 57, 13, + 67, 0, 4, 28, 79, 22, 83, 70, 30, 26, 4, 25, 65, 11, 87, 73, 38, 85, 31, 1, 82, 24, 3, 73, + 13, 11, 82, 25, 9, 11, 1, + ]; + + /// tests where the key is expressed as `&str`, and input is expressed as `&[u8]` + mod str_slice { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + // we transform the input into a `Vec` despite its presence in this + // module because of the more restricted syntax that this function accepts + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let mut xorcism = Xorcism::new(KEY); + let result: Vec = xorcism.munge(INPUT.as_bytes()).collect(); + assert_eq!(INPUT.len(), result.len()); + assert_ne!(INPUT.as_bytes(), result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(INPUT.as_bytes()); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(INPUT.as_bytes(), result); + } + } + + /// tests where the key and input are both expressed as `&[u8]` + mod slice_slice { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + let key = KEY.as_bytes(); + + // we transform the input into a `Vec` despite its presence in this + // module because of the more restricted syntax that this function accepts + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(key); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let key = KEY.as_bytes(); + let input = INPUT.as_bytes(); + + let mut xorcism = Xorcism::new(key); + let result: Vec = xorcism.munge(input).collect(); + assert_eq!(input.len(), result.len()); + assert_ne!(input, result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let key = KEY.as_bytes(); + let input = INPUT.as_bytes(); + + let mut xorcism1 = Xorcism::new(key); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(input); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(input, result); + } + } + + /// tests where the key is expressed as `&str` and input is expressed as `Vec` + mod vec_vec { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let owned_input = INPUT.as_bytes().to_vec(); + + let mut xorcism = Xorcism::new(KEY); + let result: Vec = xorcism.munge(owned_input).collect(); + assert_eq!(INPUT.len(), result.len()); + assert_ne!(INPUT.as_bytes(), result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let owned_input = INPUT.as_bytes().to_vec(); + + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(owned_input); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(INPUT.as_bytes(), result); + } + } + + #[cfg(feature = "io")] + mod io { + use super::*; + + #[test] + #[ignore] + fn reader_munges() { + let mut reader = Xorcism::new(KEY).reader(INPUT.as_bytes()); + let mut buf = Vec::with_capacity(INPUT.len()); + let bytes_read = reader.read_to_end(&mut buf).unwrap(); + assert_eq!(bytes_read, INPUT.len()); + assert_eq!(buf, EXPECT); + } + + #[test] + #[ignore] + fn reader_roundtrip() { + let xs = Xorcism::new(KEY); + let reader1 = xs.clone().reader(INPUT.as_bytes()); + let mut reader2 = xs.clone().reader(reader1); + let mut buf = Vec::with_capacity(INPUT.len()); + let bytes_read = reader2.read_to_end(&mut buf).unwrap(); + assert_eq!(bytes_read, INPUT.len()); + assert_eq!(buf, INPUT.as_bytes()); + } + + #[test] + #[ignore] + fn writer_munges() { + let mut writer_dest = Vec::new(); + { + let mut writer = Xorcism::new(KEY).writer(&mut writer_dest); + assert!(writer.write_all(INPUT.as_bytes()).is_ok()); + } + assert_eq!(writer_dest, EXPECT); + } + + #[test] + #[ignore] + fn writer_roundtrip() { + let mut writer_dest = Vec::new(); + let xs = Xorcism::new(KEY); + { + let writer1 = xs.clone().writer(&mut writer_dest); + let mut writer2 = xs.writer(writer1); + assert!(writer2.write_all(INPUT.as_bytes()).is_ok()); + } + assert_eq!(writer_dest, INPUT.as_bytes()); + } + } +} + +mod mad_science { + + use super::*; + + const KEY: &str = "TRANSMUTATION_NOTES_1"; + const INPUT: &str = "If wishes were horses, beggars would ride."; + const EXPECT: &[u8] = &[ + 29, 52, 97, 57, 58, 62, 61, 49, 50, 116, 62, 42, 60, 58, 110, 39, 59, 55, 32, 58, 66, 120, + 114, 35, 43, 52, 42, 52, 38, 50, 116, 62, 32, 59, 51, 42, 111, 38, 44, 55, 58, 31, + ]; + + /// tests where the key is expressed as `&str`, and input is expressed as `&[u8]` + mod str_slice { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + // we transform the input into a `Vec` despite its presence in this + // module because of the more restricted syntax that this function accepts + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let mut xorcism = Xorcism::new(KEY); + let result: Vec = xorcism.munge(INPUT.as_bytes()).collect(); + assert_eq!(INPUT.len(), result.len()); + assert_ne!(INPUT.as_bytes(), result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(INPUT.as_bytes()); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(INPUT.as_bytes(), result); + } + } + + /// tests where the key and input are both expressed as `&[u8]` + mod slice_slice { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + let key = KEY.as_bytes(); + + // we transform the input into a `Vec` despite its presence in this + // module because of the more restricted syntax that this function accepts + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(key); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let key = KEY.as_bytes(); + let input = INPUT.as_bytes(); - /// tests where the key is expressed as `&str` and input is expressed as `Vec` - mod vec_vec { - use super::*; - - #[test] - #[ignore] - fn munge_in_place() { - let mut input = INPUT.as_bytes().to_vec(); - let original = input.clone(); - - // in-place munging is stateful on Xorcism, so clone it - // to ensure the keys positions stay synchronized - let mut xorcism1 = Xorcism::new(KEY); - let mut xorcism2 = xorcism1.clone(); - - xorcism1.munge_in_place(&mut input); - assert_eq!(input.len(), original.len()); - assert_ne!(input, original); - assert_eq!(input, EXPECT); - xorcism2.munge_in_place(&mut input); - assert_eq!(input, original); - } - - #[test] - #[ignore] - fn munges() { - let owned_input = INPUT.as_bytes().to_vec(); - - let mut xorcism = Xorcism::new(KEY); - let result: Vec = xorcism.munge(owned_input).collect(); - assert_eq!(INPUT.len(), result.len()); - assert_ne!(INPUT.as_bytes(), result); - assert_eq!(result, EXPECT); - } - - #[test] - #[ignore] - fn round_trip() { - let owned_input = INPUT.as_bytes().to_vec(); - - let mut xorcism1 = Xorcism::new(KEY); - let mut xorcism2 = xorcism1.clone(); - let munge_iter = xorcism1.munge(owned_input); - let result: Vec = xorcism2.munge(munge_iter).collect(); - assert_eq!(INPUT.as_bytes(), result); - } + let mut xorcism = Xorcism::new(key); + let result: Vec = xorcism.munge(input).collect(); + assert_eq!(input.len(), result.len()); + assert_ne!(input, result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let key = KEY.as_bytes(); + let input = INPUT.as_bytes(); + + let mut xorcism1 = Xorcism::new(key); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(input); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(input, result); + } + } + + /// tests where the key is expressed as `&str` and input is expressed as `Vec` + mod vec_vec { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let owned_input = INPUT.as_bytes().to_vec(); + + let mut xorcism = Xorcism::new(KEY); + let result: Vec = xorcism.munge(owned_input).collect(); + assert_eq!(INPUT.len(), result.len()); + assert_ne!(INPUT.as_bytes(), result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let owned_input = INPUT.as_bytes().to_vec(); + + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(owned_input); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(INPUT.as_bytes(), result); + } + } + + #[cfg(feature = "io")] + mod io { + use super::*; + + #[test] + #[ignore] + fn reader_munges() { + let mut reader = Xorcism::new(KEY).reader(INPUT.as_bytes()); + let mut buf = Vec::with_capacity(INPUT.len()); + let bytes_read = reader.read_to_end(&mut buf).unwrap(); + assert_eq!(bytes_read, INPUT.len()); + assert_eq!(buf, EXPECT); + } + + #[test] + #[ignore] + fn reader_roundtrip() { + let xs = Xorcism::new(KEY); + let reader1 = xs.clone().reader(INPUT.as_bytes()); + let mut reader2 = xs.clone().reader(reader1); + let mut buf = Vec::with_capacity(INPUT.len()); + let bytes_read = reader2.read_to_end(&mut buf).unwrap(); + assert_eq!(bytes_read, INPUT.len()); + assert_eq!(buf, INPUT.as_bytes()); + } + + #[test] + #[ignore] + fn writer_munges() { + let mut writer_dest = Vec::new(); + { + let mut writer = Xorcism::new(KEY).writer(&mut writer_dest); + assert!(writer.write_all(INPUT.as_bytes()).is_ok()); } + assert_eq!(writer_dest, EXPECT); + } - #[cfg(feature = "io")] - mod io { - use super::*; - - #[test] - #[ignore] - fn reader_munges() { - let mut reader = Xorcism::new(KEY).reader(INPUT.as_bytes()); - let mut buf = Vec::with_capacity(INPUT.len()); - let bytes_read = reader.read_to_end(&mut buf).unwrap(); - assert_eq!(bytes_read, INPUT.len()); - assert_eq!(buf, EXPECT); - } - - #[test] - #[ignore] - fn reader_roundtrip() { - let xs = Xorcism::new(KEY); - let reader1 = xs.clone().reader(INPUT.as_bytes()); - let mut reader2 = xs.clone().reader(reader1); - let mut buf = Vec::with_capacity(INPUT.len()); - let bytes_read = reader2.read_to_end(&mut buf).unwrap(); - assert_eq!(bytes_read, INPUT.len()); - assert_eq!(buf, INPUT.as_bytes()); - } - - #[test] - #[ignore] - fn writer_munges() { - let mut writer_dest = Vec::new(); - { - let mut writer = Xorcism::new(KEY).writer(&mut writer_dest); - assert!(writer.write_all(INPUT.as_bytes()).is_ok()); - } - assert_eq!(writer_dest, EXPECT); - } - - #[test] - #[ignore] - fn writer_roundtrip() { - let mut writer_dest = Vec::new(); - let xs = Xorcism::new(KEY); - { - let writer1 = xs.clone().writer(&mut writer_dest); - let mut writer2 = xs.writer(writer1); - assert!(writer2.write_all(INPUT.as_bytes()).is_ok()); - } - assert_eq!(writer_dest, INPUT.as_bytes()); - } + #[test] + #[ignore] + fn writer_roundtrip() { + let mut writer_dest = Vec::new(); + let xs = Xorcism::new(KEY); + { + let writer1 = xs.clone().writer(&mut writer_dest); + let mut writer2 = xs.writer(writer1); + assert!(writer2.write_all(INPUT.as_bytes()).is_ok()); } - })+ - }; + assert_eq!(writer_dest, INPUT.as_bytes()); + } + } } -test_cases!( - key_shorter_than_data, "abcde", "123455", &[80,80,80,80,80,84] - ; - key_len_equal_to_data, - "The quick brown fox jumped over the lazy dog.", - "Wait, oops, this is not the pangram exercise!", - &[3,9,12,84,93,85,6,12,27,83,78,82,27,31,7,83,70,6,11,0,4,26,25,80,17,12,69,79,6,4,28,71,6,9,8,0,9,25,31,11,67,13,28,2,15] - ; - key_longer_than_data, - "A properly cryptographically random key longer than the data can actually be fairly secure.", - "Text is not cryptographically random.", - &[21,69,8,6,79,25,22,82,2,22,84,67,17,11,9,4,27,8,21,19,17,24,1,10,2,13,0,21,89,82,19,15,10,11,2,77,69] - ; - shakespearean, - "Forsooth, let us never break our trust!", - "The sacred brothership in which we share shall never from our hearts be lost.", - &[18,7,23,83,28,14,23,26,73,68,76,7,6,79,1,27,69,28,22,30,12,2,0,11,28,69,22,3,73,12,29,82,87,17,82,6,27,21,83,35,79,1,27,14,3,24,72,66,69,26,0,6,0,19,1,79,3,69,25,16,0,0,10,23,4,19,31,83,79,23,23,0,24,29,6,7,90] - ; - comics, - "Who knows what evil lurks in the hearts of men?", - "Spiderman! It's spiderman! Not a bird, or a plane, or a fireman! Just spiderman!", - &[4,24,6,68,14,28,2,22,29,1,87,33,21,83,83,69,5,25,5,68,9,7,31,10,29,1,73,32,79,0,72,4,0,10,12,19,22,88,83,79,29,70,65,77,21,2,94,57,13,67,0,4,28,79,22,83,70,30,26,4,25,65,11,87,73,38,85,31,1,82,24,3,73,13,11,82,25,9,11,1] - ; - mad_science, - "TRANSMUTATION_NOTES_1", - "If wishes were horses, beggars would ride.", - &[29,52,97,57,58,62,61,49,50,116,62,42,60,58,110,39,59,55,32,58,66,120,114,35,43,52,42,52,38,50,116,62,32,59,51,42,111,38,44,55,58,31] - ; - metaphor, - "Contextualism", - "The globe is text, its people prose; all the world's a page.", - &[23,7,11,84,2,20,27,23,4,76,0,0,77,55,10,22,0,73,88,29,1,18,76,25,22,2,51,3,11,84,21,10,27,6,4,87,73,18,1,47,79,26,28,0,88,3,26,19,0,13,84,30,99,14,78,4,4,31,17,91] - ; - emoji, - "🔑🗝️… 🎹?", - "⌨️! 🔒+💻+🧠=🔓", - &[18,19,60,126,72,16,182,189,31,39,27,112,171,86,191,98,36,165,73,160,87,63,169,97,111,11,4] -); -// note the emoji case above. Most exercism tests don't test emoji, because we like to use strings -// as input, but in this case, they're fine: they're arbitrary binary data that _just happen_ to -// represent emoji when construed as a string, in this case. +mod metaphor { + + use super::*; + + const KEY: &str = "Contextualism"; + const INPUT: &str = "The globe is text, its people prose; all the world's a page."; + const EXPECT: &[u8] = &[ + 23, 7, 11, 84, 2, 20, 27, 23, 4, 76, 0, 0, 77, 55, 10, 22, 0, 73, 88, 29, 1, 18, 76, 25, + 22, 2, 51, 3, 11, 84, 21, 10, 27, 6, 4, 87, 73, 18, 1, 47, 79, 26, 28, 0, 88, 3, 26, 19, 0, + 13, 84, 30, 99, 14, 78, 4, 4, 31, 17, 91, + ]; + + /// tests where the key is expressed as `&str`, and input is expressed as `&[u8]` + mod str_slice { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + // we transform the input into a `Vec` despite its presence in this + // module because of the more restricted syntax that this function accepts + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let mut xorcism = Xorcism::new(KEY); + let result: Vec = xorcism.munge(INPUT.as_bytes()).collect(); + assert_eq!(INPUT.len(), result.len()); + assert_ne!(INPUT.as_bytes(), result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(INPUT.as_bytes()); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(INPUT.as_bytes(), result); + } + } + + /// tests where the key and input are both expressed as `&[u8]` + mod slice_slice { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + let key = KEY.as_bytes(); + + // we transform the input into a `Vec` despite its presence in this + // module because of the more restricted syntax that this function accepts + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(key); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let key = KEY.as_bytes(); + let input = INPUT.as_bytes(); + + let mut xorcism = Xorcism::new(key); + let result: Vec = xorcism.munge(input).collect(); + assert_eq!(input.len(), result.len()); + assert_ne!(input, result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let key = KEY.as_bytes(); + let input = INPUT.as_bytes(); + + let mut xorcism1 = Xorcism::new(key); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(input); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(input, result); + } + } + + /// tests where the key is expressed as `&str` and input is expressed as `Vec` + mod vec_vec { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let owned_input = INPUT.as_bytes().to_vec(); + + let mut xorcism = Xorcism::new(KEY); + let result: Vec = xorcism.munge(owned_input).collect(); + assert_eq!(INPUT.len(), result.len()); + assert_ne!(INPUT.as_bytes(), result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let owned_input = INPUT.as_bytes().to_vec(); + + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(owned_input); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(INPUT.as_bytes(), result); + } + } + + #[cfg(feature = "io")] + mod io { + use super::*; + + #[test] + #[ignore] + fn reader_munges() { + let mut reader = Xorcism::new(KEY).reader(INPUT.as_bytes()); + let mut buf = Vec::with_capacity(INPUT.len()); + let bytes_read = reader.read_to_end(&mut buf).unwrap(); + assert_eq!(bytes_read, INPUT.len()); + assert_eq!(buf, EXPECT); + } + + #[test] + #[ignore] + fn reader_roundtrip() { + let xs = Xorcism::new(KEY); + let reader1 = xs.clone().reader(INPUT.as_bytes()); + let mut reader2 = xs.clone().reader(reader1); + let mut buf = Vec::with_capacity(INPUT.len()); + let bytes_read = reader2.read_to_end(&mut buf).unwrap(); + assert_eq!(bytes_read, INPUT.len()); + assert_eq!(buf, INPUT.as_bytes()); + } + + #[test] + #[ignore] + fn writer_munges() { + let mut writer_dest = Vec::new(); + { + let mut writer = Xorcism::new(KEY).writer(&mut writer_dest); + assert!(writer.write_all(INPUT.as_bytes()).is_ok()); + } + assert_eq!(writer_dest, EXPECT); + } + + #[test] + #[ignore] + fn writer_roundtrip() { + let mut writer_dest = Vec::new(); + let xs = Xorcism::new(KEY); + { + let writer1 = xs.clone().writer(&mut writer_dest); + let mut writer2 = xs.writer(writer1); + assert!(writer2.write_all(INPUT.as_bytes()).is_ok()); + } + assert_eq!(writer_dest, INPUT.as_bytes()); + } + } +} + +mod emoji { + + use super::*; + + const KEY: &str = "🔑🗝️… 🎹?"; + const INPUT: &str = "⌨️! 🔒+💻+🧠=🔓"; + const EXPECT: &[u8] = &[ + 18, 19, 60, 126, 72, 16, 182, 189, 31, 39, 27, 112, 171, 86, 191, 98, 36, 165, 73, 160, 87, + 63, 169, 97, 111, 11, 4, + ]; + + /// tests where the key is expressed as `&str`, and input is expressed as `&[u8]` + mod str_slice { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + // we transform the input into a `Vec` despite its presence in this + // module because of the more restricted syntax that this function accepts + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let mut xorcism = Xorcism::new(KEY); + let result: Vec = xorcism.munge(INPUT.as_bytes()).collect(); + assert_eq!(INPUT.len(), result.len()); + assert_ne!(INPUT.as_bytes(), result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(INPUT.as_bytes()); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(INPUT.as_bytes(), result); + } + } + + /// tests where the key and input are both expressed as `&[u8]` + mod slice_slice { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + let key = KEY.as_bytes(); + + // we transform the input into a `Vec` despite its presence in this + // module because of the more restricted syntax that this function accepts + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(key); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let key = KEY.as_bytes(); + let input = INPUT.as_bytes(); + + let mut xorcism = Xorcism::new(key); + let result: Vec = xorcism.munge(input).collect(); + assert_eq!(input.len(), result.len()); + assert_ne!(input, result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let key = KEY.as_bytes(); + let input = INPUT.as_bytes(); + + let mut xorcism1 = Xorcism::new(key); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(input); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(input, result); + } + } + + /// tests where the key is expressed as `&str` and input is expressed as `Vec` + mod vec_vec { + use super::*; + + #[test] + #[ignore] + fn munge_in_place() { + let mut input = INPUT.as_bytes().to_vec(); + let original = input.clone(); + + // in-place munging is stateful on Xorcism, so clone it + // to ensure the keys positions stay synchronized + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + + xorcism1.munge_in_place(&mut input); + assert_eq!(input.len(), original.len()); + assert_ne!(input, original); + assert_eq!(input, EXPECT); + xorcism2.munge_in_place(&mut input); + assert_eq!(input, original); + } + + #[test] + #[ignore] + fn munges() { + let owned_input = INPUT.as_bytes().to_vec(); + + let mut xorcism = Xorcism::new(KEY); + let result: Vec = xorcism.munge(owned_input).collect(); + assert_eq!(INPUT.len(), result.len()); + assert_ne!(INPUT.as_bytes(), result); + assert_eq!(result, EXPECT); + } + + #[test] + #[ignore] + fn round_trip() { + let owned_input = INPUT.as_bytes().to_vec(); + + let mut xorcism1 = Xorcism::new(KEY); + let mut xorcism2 = xorcism1.clone(); + let munge_iter = xorcism1.munge(owned_input); + let result: Vec = xorcism2.munge(munge_iter).collect(); + assert_eq!(INPUT.as_bytes(), result); + } + } + + #[cfg(feature = "io")] + mod io { + use super::*; + + #[test] + #[ignore] + fn reader_munges() { + let mut reader = Xorcism::new(KEY).reader(INPUT.as_bytes()); + let mut buf = Vec::with_capacity(INPUT.len()); + let bytes_read = reader.read_to_end(&mut buf).unwrap(); + assert_eq!(bytes_read, INPUT.len()); + assert_eq!(buf, EXPECT); + } + + #[test] + #[ignore] + fn reader_roundtrip() { + let xs = Xorcism::new(KEY); + let reader1 = xs.clone().reader(INPUT.as_bytes()); + let mut reader2 = xs.clone().reader(reader1); + let mut buf = Vec::with_capacity(INPUT.len()); + let bytes_read = reader2.read_to_end(&mut buf).unwrap(); + assert_eq!(bytes_read, INPUT.len()); + assert_eq!(buf, INPUT.as_bytes()); + } + + #[test] + #[ignore] + fn writer_munges() { + let mut writer_dest = Vec::new(); + { + let mut writer = Xorcism::new(KEY).writer(&mut writer_dest); + assert!(writer.write_all(INPUT.as_bytes()).is_ok()); + } + assert_eq!(writer_dest, EXPECT); + } + + #[test] + #[ignore] + fn writer_roundtrip() { + let mut writer_dest = Vec::new(); + let xs = Xorcism::new(KEY); + { + let writer1 = xs.clone().writer(&mut writer_dest); + let mut writer2 = xs.writer(writer1); + assert!(writer2.write_all(INPUT.as_bytes()).is_ok()); + } + assert_eq!(writer_dest, INPUT.as_bytes()); + } + } +} diff --git a/rust-tooling/models/src/exercise_config.rs b/rust-tooling/models/src/exercise_config.rs index 2ab4ff4b0..7ccc4c08b 100644 --- a/rust-tooling/models/src/exercise_config.rs +++ b/rust-tooling/models/src/exercise_config.rs @@ -105,7 +105,9 @@ pub fn get_excluded_tests(slug: &str) -> Vec { let path = std::path::PathBuf::from("exercises/practice") .join(slug) .join(".meta/tests.toml"); - let contents = std::fs::read_to_string(path).unwrap(); + let Ok(contents) = std::fs::read_to_string(path) else { + return vec![]; + }; let mut excluded_tests = Vec::new(); From e8e0aab3fbc75ea82d8fc36e7cdfa19c89e7aa06 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Thu, 15 Aug 2024 12:37:32 +0200 Subject: [PATCH 334/436] Remove unused ignore-count-ignores (#1968) After having removed all macro-based testing, this is no longer needed. --- docs/CONTRIBUTING.md | 10 ----- exercises/practice/paasio/.meta/config.json | 5 +-- exercises/practice/xorcism/.meta/config.json | 3 +- rust-tooling/ci-tests/tests/count_ignores.rs | 41 ++++++-------------- rust-tooling/models/src/exercise_config.rs | 2 - 5 files changed, 14 insertions(+), 47 deletions(-) diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index fc59b7183..fd5c9e3a6 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -81,16 +81,6 @@ If there is a justified reason why this is not possible, include a `.custom."allowed-to-not-compile"` key in the exercise's `.meta/config.json` containing the reason. -If your exercise implements macro-based testing -(e.g. [`xorcism`](/exercises/practice/xorcism/tests/xorcism.rs)), -you will likely run afoul of a CI check which counts the `#[ignore]` lines -and compares the result to the number of `#[test]` lines. -To fix this, add a marker to the exercise's `.meta/config.json`: -`.custom."ignore-count-ignores"` should be `true` -to disable that check for your exercise. -However, tera templates should generally be preferred to generate many similar test cases. -See [issue #1824](https://github.com/exercism/rust/issues/1824) for the reasoning. - ## Updating an exercise Many exercises are derived from [`problem-specifications`]. diff --git a/exercises/practice/paasio/.meta/config.json b/exercises/practice/paasio/.meta/config.json index 7feeed06a..04506cf73 100644 --- a/exercises/practice/paasio/.meta/config.json +++ b/exercises/practice/paasio/.meta/config.json @@ -29,8 +29,5 @@ }, "blurb": "Report network IO statistics.", "source": "Brian Matsuo", - "source_url": "https://github.com/bmatsuo", - "custom": { - "ignore-count-ignores": true - } + "source_url": "https://github.com/bmatsuo" } diff --git a/exercises/practice/xorcism/.meta/config.json b/exercises/practice/xorcism/.meta/config.json index d32911474..1735a26e9 100644 --- a/exercises/practice/xorcism/.meta/config.json +++ b/exercises/practice/xorcism/.meta/config.json @@ -24,7 +24,6 @@ "blurb": "Implement zero-copy streaming adaptors", "source": "Peter Goodspeed-Niklaus", "custom": { - "allowed-to-not-compile": "The point of this exercise is for students to figure out the appropriate function signatures and generic bounds for their implementations, so we cannot provide those.", - "ignore-count-ignores": true + "allowed-to-not-compile": "The point of this exercise is for students to figure out the appropriate function signatures and generic bounds for their implementations, so we cannot provide those." } } diff --git a/rust-tooling/ci-tests/tests/count_ignores.rs b/rust-tooling/ci-tests/tests/count_ignores.rs index e10919a64..4804effea 100644 --- a/rust-tooling/ci-tests/tests/count_ignores.rs +++ b/rust-tooling/ci-tests/tests/count_ignores.rs @@ -1,34 +1,17 @@ -use models::exercise_config::{ - get_all_concept_exercise_paths, get_all_practice_exercise_paths, PracticeExercise, -}; - -fn assert_one_less_ignore_than_tests(path: &str) { - let slug = path.split('/').last().unwrap(); - let test_path = format!("{path}/tests/{slug}.rs"); - let test_contents = std::fs::read_to_string(test_path).unwrap(); - let num_tests = test_contents.matches("#[test]").count(); - let num_ignores = test_contents.matches("#[ignore]").count(); - assert_eq!( - num_tests, - num_ignores + 1, - "should have one more test than ignore in {slug}" - ) -} +use models::exercise_config::get_all_exercise_paths; #[test] fn count_ignores() { - for path in get_all_concept_exercise_paths() { - assert_one_less_ignore_than_tests(&path); - } - for path in get_all_practice_exercise_paths() { - let config_path = format!("{path}/.meta/config.json"); - let config_contents = std::fs::read_to_string(config_path).unwrap(); - let config: PracticeExercise = serde_json::from_str(config_contents.as_str()).unwrap(); - if let Some(custom) = config.custom { - if custom.ignore_count_ignores.unwrap_or_default() { - continue; - } - } - assert_one_less_ignore_than_tests(&path); + for path in get_all_exercise_paths() { + let slug = path.split('/').last().unwrap(); + let test_path = format!("{path}/tests/{slug}.rs"); + let test_contents = std::fs::read_to_string(test_path).unwrap(); + let num_tests = test_contents.matches("#[test]").count(); + let num_ignores = test_contents.matches("#[ignore]").count(); + assert_eq!( + num_tests, + num_ignores + 1, + "should have one more test than ignore in {slug}" + ) } } diff --git a/rust-tooling/models/src/exercise_config.rs b/rust-tooling/models/src/exercise_config.rs index 7ccc4c08b..d4d44e140 100644 --- a/rust-tooling/models/src/exercise_config.rs +++ b/rust-tooling/models/src/exercise_config.rs @@ -56,8 +56,6 @@ pub struct Custom { pub allowed_to_not_compile: Option, #[serde(rename = "test-in-release-mode")] pub test_in_release_mode: Option, - #[serde(rename = "ignore-count-ignores")] - pub ignore_count_ignores: Option, } pub fn get_all_concept_exercise_paths() -> impl Iterator { From f0cfd47c3d2b1ef7751b16f8e6382bc5ca49ee65 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Thu, 15 Aug 2024 12:37:59 +0200 Subject: [PATCH 335/436] generator: replace snake_case with make_ident (#1969) [no important files changed] --- docs/CONTRIBUTING.md | 4 +- .../practice/acronym/.meta/test_template.tera | 2 +- .../affine-cipher/.meta/test_template.tera | 2 +- .../all-your-base/.meta/test_template.tera | 2 +- .../allergies/.meta/test_template.tera | 4 +- .../alphametics/.meta/test_template.tera | 2 +- .../practice/anagram/.meta/test_template.tera | 2 +- .../.meta/test_template.tera | 2 +- .../beer-song/.meta/test_template.tera | 4 +- .../binary-search/.meta/test_template.tera | 2 +- .../practice/bob/.meta/test_template.tera | 2 +- .../book-store/.meta/test_template.tera | 2 +- .../circular-buffer/.meta/test_template.tera | 2 +- .../.meta/test_template.tera | 2 +- .../custom-set/.meta/test_template.tera | 2 +- .../eliuds-eggs/.meta/test_template.tera | 2 +- .../practice/forth/.meta/test_template.tera | 4 +- .../practice/grep/.meta/test_template.tera | 2 +- .../isbn-verifier/.meta/test_template.tera | 2 +- .../practice/isogram/.meta/test_template.tera | 2 +- .../knapsack/.meta/test_template.tera | 2 +- exercises/practice/knapsack/tests/knapsack.rs | 10 ++-- .../practice/leap/.meta/test_template.tera | 2 +- .../practice/matrix/.meta/test_template.tera | 2 +- .../nth-prime/.meta/test_template.tera | 2 +- .../.meta/test_template.tera | 2 +- .../practice/pangram/.meta/test_template.tera | 2 +- .../pascals-triangle/.meta/test_template.tera | 2 +- .../perfect-numbers/.meta/test_template.tera | 2 +- .../phone-number/.meta/test_template.tera | 2 +- .../pig-latin/.meta/test_template.tera | 2 +- .../practice/poker/.meta/test_template.tera | 2 +- .../prime-factors/.meta/test_template.tera | 2 +- .../.meta/test_template.tera | 2 +- .../practice/proverb/.meta/test_template.tera | 2 +- .../.meta/test_template.tera | 2 +- .../queen-attack/.meta/test_template.tera | 2 +- .../.meta/test_template.tera | 2 +- .../raindrops/.meta/test_template.tera | 2 +- .../practice/raindrops/tests/raindrops.rs | 34 ++++++------ .../rectangles/.meta/test_template.tera | 2 +- .../practice/rectangles/tests/rectangles.rs | 26 ++++----- .../reverse-string/.meta/test_template.tera | 2 +- .../.meta/test_template.tera | 2 +- .../robot-simulator/.meta/test_template.tera | 2 +- .../roman-numerals/.meta/test_template.tera | 2 +- .../roman-numerals/tests/roman-numerals.rs | 54 +++++++++---------- .../.meta/test_template.tera | 2 +- .../.meta/test_template.tera | 2 +- .../saddle-points/.meta/test_template.tera | 2 +- .../practice/say/.meta/test_template.tera | 2 +- .../scrabble-score/.meta/test_template.tera | 2 +- .../practice/series/.meta/test_template.tera | 2 +- .../practice/sieve/.meta/test_template.tera | 2 +- .../space-age/.meta/test_template.tera | 2 +- .../spiral-matrix/.meta/test_template.tera | 2 +- .../practice/sublist/.meta/test_template.tera | 2 +- .../sum-of-multiples/.meta/test_template.tera | 2 +- .../tournament/.meta/test_template.tera | 2 +- .../triangle/.meta/test_template.tera | 8 +-- .../two-bucket/.meta/test_template.tera | 2 +- .../.meta/test_template.tera | 2 +- .../word-count/.meta/test_template.tera | 2 +- .../practice/wordy/.meta/test_template.tera | 2 +- .../practice/xorcism/.meta/test_template.tera | 2 +- rust-tooling/generate/src/custom_filters.rs | 24 ++------- .../templates/default_test_template.tera | 2 +- 67 files changed, 136 insertions(+), 150 deletions(-) diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index fd5c9e3a6..51ca7dded 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -146,8 +146,8 @@ In that case, you can use the group description to organize the tests in modules There are some custom tera filters in [`rust-tooling`](/rust-tooling/generate/src/custom_filters.rs). Here's the hopefully up-to-date list: - `to_hex` formats ints in hexadecimal -- `snake_case` massages an arbitrary string into a decent Rust identifier -- `make_test_ident` is like snake case, but prepends `test_` if the string starts with a digit +- `make_ident` turns an arbitrary string into a decent Rust identifier. + Most useful for generating function names from test descriptions. - `fmt_num` format number literals (insert `_` every third digit) Feel free to add your own in the crate `rust-tooling`. diff --git a/exercises/practice/acronym/.meta/test_template.tera b/exercises/practice/acronym/.meta/test_template.tera index 5f8bee34d..e20a88d95 100644 --- a/exercises/practice/acronym/.meta/test_template.tera +++ b/exercises/practice/acronym/.meta/test_template.tera @@ -3,7 +3,7 @@ use acronym::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let input = {{ test.input.phrase | json_encode() }}; let output = abbreviate(input); let expected = {{ test.expected | json_encode() }}; diff --git a/exercises/practice/affine-cipher/.meta/test_template.tera b/exercises/practice/affine-cipher/.meta/test_template.tera index 8f26ae364..bcb4aa736 100644 --- a/exercises/practice/affine-cipher/.meta/test_template.tera +++ b/exercises/practice/affine-cipher/.meta/test_template.tera @@ -6,7 +6,7 @@ use affine_cipher::AffineCipherError::NotCoprime; {% for test in test_group.cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let phrase = {{ test.input.phrase | json_encode() }}; let (a, b) = ({{ test.input.key.a }}, {{ test.input.key.b }}); let output = {{ test.property }}(phrase, a, b); diff --git a/exercises/practice/all-your-base/.meta/test_template.tera b/exercises/practice/all-your-base/.meta/test_template.tera index 829e823c7..1e8fe4c10 100644 --- a/exercises/practice/all-your-base/.meta/test_template.tera +++ b/exercises/practice/all-your-base/.meta/test_template.tera @@ -3,7 +3,7 @@ use allyourbase as ayb; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | make_test_ident }}() { +fn {{ test.description | make_ident }}() { let input_base = {{ test.input.inputBase }}; let input_digits = &{{ test.input.digits | json_encode() }}; let output_base = {{ test.input.outputBase }}; diff --git a/exercises/practice/allergies/.meta/test_template.tera b/exercises/practice/allergies/.meta/test_template.tera index da8cced1e..9b050e434 100644 --- a/exercises/practice/allergies/.meta/test_template.tera +++ b/exercises/practice/allergies/.meta/test_template.tera @@ -20,7 +20,7 @@ fn compare_allergy_vectors(expected: &[Allergen], actual: &[Allergen]) { #[ignore] {%- if test.property == "allergicTo" %} {# canonical data contains multiple cases named "allergic to everything" for different items #} -fn {{ test.description | snake_case }}_{{ test.input.item }}() { +fn {{ test.description | make_ident }}_{{ test.input.item }}() { let allergies = Allergies::new({{ test.input.score }}); {%- if test.expected %} assert!(allergies.is_allergic_to(&Allergen::{{ test.input.item | title }})) @@ -28,7 +28,7 @@ fn {{ test.description | snake_case }}_{{ test.input.item }}() { assert!(!allergies.is_allergic_to(&Allergen::{{ test.input.item | title }})) {% endif -%} {% else %} -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let allergies = Allergies::new({{ test.input.score }}).allergies(); let expected = &[ {% for allergen in test.expected %} diff --git a/exercises/practice/alphametics/.meta/test_template.tera b/exercises/practice/alphametics/.meta/test_template.tera index a67a99381..c71e14f23 100644 --- a/exercises/practice/alphametics/.meta/test_template.tera +++ b/exercises/practice/alphametics/.meta/test_template.tera @@ -3,7 +3,7 @@ use alphametics::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let answer = solve({{ test.input.puzzle | json_encode() }}); {%- if test.expected is object %} let expected = [ diff --git a/exercises/practice/anagram/.meta/test_template.tera b/exercises/practice/anagram/.meta/test_template.tera index e856d8900..2fb8bc521 100644 --- a/exercises/practice/anagram/.meta/test_template.tera +++ b/exercises/practice/anagram/.meta/test_template.tera @@ -4,7 +4,7 @@ use std::collections::HashSet; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let word = {{ test.input.subject | json_encode() }}; let inputs = &{{ test.input.candidates | json_encode() }}; let output = anagrams_for(word, inputs); diff --git a/exercises/practice/armstrong-numbers/.meta/test_template.tera b/exercises/practice/armstrong-numbers/.meta/test_template.tera index d5c167b8d..f60a5d881 100644 --- a/exercises/practice/armstrong-numbers/.meta/test_template.tera +++ b/exercises/practice/armstrong-numbers/.meta/test_template.tera @@ -3,7 +3,7 @@ use armstrong_numbers::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { assert!({% if not test.expected %} ! {% endif %}is_armstrong_number({{ test.input.number | fmt_num }})) } {% endfor -%} diff --git a/exercises/practice/beer-song/.meta/test_template.tera b/exercises/practice/beer-song/.meta/test_template.tera index 0f32c94e4..842f5af64 100644 --- a/exercises/practice/beer-song/.meta/test_template.tera +++ b/exercises/practice/beer-song/.meta/test_template.tera @@ -1,12 +1,12 @@ {% for stupid_uselessly_nested_test_group in cases -%} {% for test_group in stupid_uselessly_nested_test_group.cases -%} -mod {{ test_group.description | snake_case }} { +mod {{ test_group.description | make_ident }} { use beer_song::*; {% for test in test_group.cases %} #[test] #[ignore] - fn {{ test.description | snake_case }}() { + fn {{ test.description | make_ident }}() { assert_eq!( {% if stupid_uselessly_nested_test_group.description == "verse" -%} verse({{ test.input.startBottles }}).trim(), diff --git a/exercises/practice/binary-search/.meta/test_template.tera b/exercises/practice/binary-search/.meta/test_template.tera index 59cf54ae0..34fa5f46e 100644 --- a/exercises/practice/binary-search/.meta/test_template.tera +++ b/exercises/practice/binary-search/.meta/test_template.tera @@ -3,7 +3,7 @@ use binary_search::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { assert_eq!(find(&{{ test.input.array | json_encode() }}, {{ test.input.value }}), {% if test.expected is object -%} None {%- else -%} diff --git a/exercises/practice/bob/.meta/test_template.tera b/exercises/practice/bob/.meta/test_template.tera index dd7557012..20e41953f 100644 --- a/exercises/practice/bob/.meta/test_template.tera +++ b/exercises/practice/bob/.meta/test_template.tera @@ -3,7 +3,7 @@ use bob::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { assert_eq!(reply({{ test.input.heyBob | json_encode() }}), "{{ test.expected }}"); } {% endfor -%} diff --git a/exercises/practice/book-store/.meta/test_template.tera b/exercises/practice/book-store/.meta/test_template.tera index 81ffc2d1e..702c05b73 100644 --- a/exercises/practice/book-store/.meta/test_template.tera +++ b/exercises/practice/book-store/.meta/test_template.tera @@ -3,7 +3,7 @@ use book_store::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let input = &{{ test.input.basket | json_encode() }}; let output = lowest_price(input); let expected = {{ test.expected | json_encode() }}; diff --git a/exercises/practice/circular-buffer/.meta/test_template.tera b/exercises/practice/circular-buffer/.meta/test_template.tera index 36a0193d8..84f5d6ea3 100644 --- a/exercises/practice/circular-buffer/.meta/test_template.tera +++ b/exercises/practice/circular-buffer/.meta/test_template.tera @@ -4,7 +4,7 @@ use std::rc::Rc; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let mut buffer = CircularBuffer{% if loop.index == 1 %}::{% endif %}::new({{ test.input.capacity }}); {%- for op in test.input.operations %} {%- if op.operation == "read" %} diff --git a/exercises/practice/collatz-conjecture/.meta/test_template.tera b/exercises/practice/collatz-conjecture/.meta/test_template.tera index 8a4d405ce..1f2720a26 100644 --- a/exercises/practice/collatz-conjecture/.meta/test_template.tera +++ b/exercises/practice/collatz-conjecture/.meta/test_template.tera @@ -3,7 +3,7 @@ use collatz_conjecture::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let output = collatz({{ test.input.number | fmt_num }}); let expected = {% if test.expected is object %} None diff --git a/exercises/practice/custom-set/.meta/test_template.tera b/exercises/practice/custom-set/.meta/test_template.tera index b97fda33c..e2aac4f24 100644 --- a/exercises/practice/custom-set/.meta/test_template.tera +++ b/exercises/practice/custom-set/.meta/test_template.tera @@ -4,7 +4,7 @@ use custom_set::CustomSet; {% for test in test_group.cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { {%- if test.property == "empty" %} let set = CustomSet::::new(&{{ test.input.set | json_encode() }}); assert!( diff --git a/exercises/practice/eliuds-eggs/.meta/test_template.tera b/exercises/practice/eliuds-eggs/.meta/test_template.tera index 9a2306d5b..1687e0108 100644 --- a/exercises/practice/eliuds-eggs/.meta/test_template.tera +++ b/exercises/practice/eliuds-eggs/.meta/test_template.tera @@ -3,7 +3,7 @@ use eliuds_eggs::*; {% for test in cases %} #[test] #[ignore] -fn test_{{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let input = {{ test.input.number | fmt_num }}; let output = egg_count(input); let expected = {{ test.expected | json_encode() }}; diff --git a/exercises/practice/forth/.meta/test_template.tera b/exercises/practice/forth/.meta/test_template.tera index 1cc59aef5..dc7152268 100644 --- a/exercises/practice/forth/.meta/test_template.tera +++ b/exercises/practice/forth/.meta/test_template.tera @@ -1,11 +1,11 @@ {% for test_group in cases %} -mod {{ test_group.description | snake_case }} { +mod {{ test_group.description | make_ident }} { use forth::*; {% for test in test_group.cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let mut f = Forth::new(); {% if test.property == "evaluateBoth" -%} {% for instr in test.input.instructionsFirst -%} diff --git a/exercises/practice/grep/.meta/test_template.tera b/exercises/practice/grep/.meta/test_template.tera index de27f4dc3..e585a4ebd 100644 --- a/exercises/practice/grep/.meta/test_template.tera +++ b/exercises/practice/grep/.meta/test_template.tera @@ -32,7 +32,7 @@ fn grep_returns_result() { #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let pattern = {{ test.input.pattern | json_encode() }}; let flags = Flags::new(&{{ test.input.flags | json_encode() }}); let files = Files::new(&[ diff --git a/exercises/practice/isbn-verifier/.meta/test_template.tera b/exercises/practice/isbn-verifier/.meta/test_template.tera index c51d0e82c..3a3dc7233 100644 --- a/exercises/practice/isbn-verifier/.meta/test_template.tera +++ b/exercises/practice/isbn-verifier/.meta/test_template.tera @@ -3,7 +3,7 @@ use isbn_verifier::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { {% if test.expected %} assert!(is_valid_isbn("{{ test.input.isbn }}")); {% else %} diff --git a/exercises/practice/isogram/.meta/test_template.tera b/exercises/practice/isogram/.meta/test_template.tera index 51bd452ba..70e9382ae 100644 --- a/exercises/practice/isogram/.meta/test_template.tera +++ b/exercises/practice/isogram/.meta/test_template.tera @@ -3,7 +3,7 @@ use isogram::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { {% if test.expected %} assert!(check({{ test.input.phrase | json_encode() }})); {% else %} diff --git a/exercises/practice/knapsack/.meta/test_template.tera b/exercises/practice/knapsack/.meta/test_template.tera index 3cbe97694..1a6a7ad53 100644 --- a/exercises/practice/knapsack/.meta/test_template.tera +++ b/exercises/practice/knapsack/.meta/test_template.tera @@ -3,7 +3,7 @@ use knapsack::*; {% for test in cases %} #[test] #[ignore] -fn test_{{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let max_weight = {{ test.input.maximumWeight }}; let items = [ {% for item in test.input.items -%} diff --git a/exercises/practice/knapsack/tests/knapsack.rs b/exercises/practice/knapsack/tests/knapsack.rs index 0463142a9..f601dac8c 100644 --- a/exercises/practice/knapsack/tests/knapsack.rs +++ b/exercises/practice/knapsack/tests/knapsack.rs @@ -1,7 +1,7 @@ use knapsack::*; #[test] -fn test_no_items() { +fn no_items() { let max_weight = 100; let items = []; let output = maximum_value(max_weight, &items); @@ -11,7 +11,7 @@ fn test_no_items() { #[test] #[ignore] -fn test_one_item_too_heavy() { +fn one_item_too_heavy() { let max_weight = 10; let items = [Item { weight: 100, @@ -24,7 +24,7 @@ fn test_one_item_too_heavy() { #[test] #[ignore] -fn test_five_items_cannot_be_greedy_by_weight() { +fn five_items_cannot_be_greedy_by_weight() { let max_weight = 10; let items = [ Item { @@ -55,7 +55,7 @@ fn test_five_items_cannot_be_greedy_by_weight() { #[test] #[ignore] -fn test_five_items_cannot_be_greedy_by_value() { +fn five_items_cannot_be_greedy_by_value() { let max_weight = 10; let items = [ Item { @@ -86,7 +86,7 @@ fn test_five_items_cannot_be_greedy_by_value() { #[test] #[ignore] -fn test_example_knapsack() { +fn example_knapsack() { let max_weight = 10; let items = [ Item { diff --git a/exercises/practice/leap/.meta/test_template.tera b/exercises/practice/leap/.meta/test_template.tera index c93330cbb..bed0ba0a6 100644 --- a/exercises/practice/leap/.meta/test_template.tera +++ b/exercises/practice/leap/.meta/test_template.tera @@ -3,7 +3,7 @@ use leap::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { {%- if test.expected %} assert!(is_leap_year({{ test.input.year }})); {% else %} diff --git a/exercises/practice/matrix/.meta/test_template.tera b/exercises/practice/matrix/.meta/test_template.tera index eff2a7edb..c857c4ca4 100644 --- a/exercises/practice/matrix/.meta/test_template.tera +++ b/exercises/practice/matrix/.meta/test_template.tera @@ -3,7 +3,7 @@ use matrix::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let matrix = Matrix::new({{ test.input.string | json_encode() }}); {% if test.expected -%} assert_eq!(matrix.{{ test.property }}({{ test.input.index }}), Some(vec!{{ test.expected | json_encode() }})); diff --git a/exercises/practice/nth-prime/.meta/test_template.tera b/exercises/practice/nth-prime/.meta/test_template.tera index 4f3e44aa2..a69cc5c50 100644 --- a/exercises/practice/nth-prime/.meta/test_template.tera +++ b/exercises/practice/nth-prime/.meta/test_template.tera @@ -3,7 +3,7 @@ use nth_prime::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let output = nth({{ test.input.number - 1 | fmt_num }}); let expected = {{ test.expected | fmt_num }}; assert_eq!(output, expected); diff --git a/exercises/practice/palindrome-products/.meta/test_template.tera b/exercises/practice/palindrome-products/.meta/test_template.tera index 53828a5c5..e184921f5 100644 --- a/exercises/practice/palindrome-products/.meta/test_template.tera +++ b/exercises/practice/palindrome-products/.meta/test_template.tera @@ -26,7 +26,7 @@ fn palindrome_new_return_none() { {% for test in cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { {%- if test.property == "smallest" %} let output = palindrome_products({{ test.input.min }}, {{ test.input.max }}).map(|(min, _)| min.into_inner()); {%- else %} diff --git a/exercises/practice/pangram/.meta/test_template.tera b/exercises/practice/pangram/.meta/test_template.tera index 49c14ea0b..8d9eb5cd4 100644 --- a/exercises/practice/pangram/.meta/test_template.tera +++ b/exercises/practice/pangram/.meta/test_template.tera @@ -3,7 +3,7 @@ use pangram::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let sentence = {{ test.input.sentence | json_encode() }}; {% if test.expected -%} assert!(is_pangram(sentence)); diff --git a/exercises/practice/pascals-triangle/.meta/test_template.tera b/exercises/practice/pascals-triangle/.meta/test_template.tera index 6acfd5305..60e67468c 100644 --- a/exercises/practice/pascals-triangle/.meta/test_template.tera +++ b/exercises/practice/pascals-triangle/.meta/test_template.tera @@ -2,7 +2,7 @@ use pascals_triangle::PascalsTriangle; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let pt = PascalsTriangle::new({{ test.input.count }}); let expected: Vec> = vec![{% for row in test.expected -%} vec!{{ row | json_encode() }}, diff --git a/exercises/practice/perfect-numbers/.meta/test_template.tera b/exercises/practice/perfect-numbers/.meta/test_template.tera index 8d90392dc..82be1fa7b 100644 --- a/exercises/practice/perfect-numbers/.meta/test_template.tera +++ b/exercises/practice/perfect-numbers/.meta/test_template.tera @@ -4,7 +4,7 @@ use perfect_numbers::*; {% for test in test_group.cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let input = {{ test.input.number | fmt_num }}; let output = classify(input); {%- if test.expected is object %} diff --git a/exercises/practice/phone-number/.meta/test_template.tera b/exercises/practice/phone-number/.meta/test_template.tera index e9871c39f..3f50cc8f6 100644 --- a/exercises/practice/phone-number/.meta/test_template.tera +++ b/exercises/practice/phone-number/.meta/test_template.tera @@ -3,7 +3,7 @@ use phone_number::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let input = {{ test.input.phrase | json_encode() }}; let output = number(input); {%- if test.expected is object %} diff --git a/exercises/practice/pig-latin/.meta/test_template.tera b/exercises/practice/pig-latin/.meta/test_template.tera index 0316cfc77..a6e1b9dff 100644 --- a/exercises/practice/pig-latin/.meta/test_template.tera +++ b/exercises/practice/pig-latin/.meta/test_template.tera @@ -4,7 +4,7 @@ use pig_latin::*; {% for test in test_group.cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let input = {{ test.input.phrase | json_encode() }}; let output = translate(input); let expected = {{ test.expected | json_encode() }}; diff --git a/exercises/practice/poker/.meta/test_template.tera b/exercises/practice/poker/.meta/test_template.tera index 7962198ad..546fe4850 100644 --- a/exercises/practice/poker/.meta/test_template.tera +++ b/exercises/practice/poker/.meta/test_template.tera @@ -4,7 +4,7 @@ use std::collections::HashSet; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let input = &{{ test.input.hands | json_encode() }}; let output = winning_hands(input).into_iter().collect::>(); let expected = {{ test.expected | json_encode() }}.into_iter().collect::>(); diff --git a/exercises/practice/prime-factors/.meta/test_template.tera b/exercises/practice/prime-factors/.meta/test_template.tera index 2a0bbb62b..0c80353ba 100644 --- a/exercises/practice/prime-factors/.meta/test_template.tera +++ b/exercises/practice/prime-factors/.meta/test_template.tera @@ -3,7 +3,7 @@ use prime_factors::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let factors = factors({{ test.input.value | fmt_num }}); let expected = [{% for factor in test.expected -%} {{ factor | fmt_num }}, diff --git a/exercises/practice/protein-translation/.meta/test_template.tera b/exercises/practice/protein-translation/.meta/test_template.tera index 27d1b694d..5b10a3706 100644 --- a/exercises/practice/protein-translation/.meta/test_template.tera +++ b/exercises/practice/protein-translation/.meta/test_template.tera @@ -3,7 +3,7 @@ use protein_translation::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { assert_eq!( translate({{ test.input.strand | json_encode() }}), {% if test.expected is object %} diff --git a/exercises/practice/proverb/.meta/test_template.tera b/exercises/practice/proverb/.meta/test_template.tera index afa61b9c6..3dbfb203d 100644 --- a/exercises/practice/proverb/.meta/test_template.tera +++ b/exercises/practice/proverb/.meta/test_template.tera @@ -3,7 +3,7 @@ use proverb::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let input = &{{ test.input.strings | json_encode() }}; let output = build_proverb(input); {% if test.expected | length == 0 -%} diff --git a/exercises/practice/pythagorean-triplet/.meta/test_template.tera b/exercises/practice/pythagorean-triplet/.meta/test_template.tera index 6801d172b..904903c2c 100644 --- a/exercises/practice/pythagorean-triplet/.meta/test_template.tera +++ b/exercises/practice/pythagorean-triplet/.meta/test_template.tera @@ -4,7 +4,7 @@ use std::collections::HashSet; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let input = {{ test.input.n | fmt_num }}; let output = find(input); let expected = [{% for triple in test.expected -%} diff --git a/exercises/practice/queen-attack/.meta/test_template.tera b/exercises/practice/queen-attack/.meta/test_template.tera index 642f877f3..941f42d33 100644 --- a/exercises/practice/queen-attack/.meta/test_template.tera +++ b/exercises/practice/queen-attack/.meta/test_template.tera @@ -4,7 +4,7 @@ use queen_attack::*; {% for test in test_group.cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { {% if test.property == "create" %} let chess_position = ChessPosition::new({{ test.input.queen.position.row }}, {{ test.input.queen.position.column }}); {%- if test.expected is object %} diff --git a/exercises/practice/rail-fence-cipher/.meta/test_template.tera b/exercises/practice/rail-fence-cipher/.meta/test_template.tera index e0c6de5fa..c9ada5303 100644 --- a/exercises/practice/rail-fence-cipher/.meta/test_template.tera +++ b/exercises/practice/rail-fence-cipher/.meta/test_template.tera @@ -4,7 +4,7 @@ use rail_fence_cipher::RailFence; {% for test in test_group.cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let input = {{ test.input.msg | json_encode() }}; let rails = {{ test.input.rails | json_encode() }}; let rail_fence = RailFence::new(rails); diff --git a/exercises/practice/raindrops/.meta/test_template.tera b/exercises/practice/raindrops/.meta/test_template.tera index 53b990bec..8e97f5142 100644 --- a/exercises/practice/raindrops/.meta/test_template.tera +++ b/exercises/practice/raindrops/.meta/test_template.tera @@ -3,7 +3,7 @@ use raindrops::*; {% for test in cases %} #[test] #[ignore] -fn test_{{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let input = {{ test.input.number | json_encode() }}; let output = raindrops(input); let expected = {{ test.expected | json_encode() }}; diff --git a/exercises/practice/raindrops/tests/raindrops.rs b/exercises/practice/raindrops/tests/raindrops.rs index d6999c98d..ea34ac57e 100644 --- a/exercises/practice/raindrops/tests/raindrops.rs +++ b/exercises/practice/raindrops/tests/raindrops.rs @@ -1,7 +1,7 @@ use raindrops::*; #[test] -fn test_the_sound_for_1_is_1() { +fn the_sound_for_1_is_1() { let input = 1; let output = raindrops(input); let expected = "1"; @@ -10,7 +10,7 @@ fn test_the_sound_for_1_is_1() { #[test] #[ignore] -fn test_the_sound_for_3_is_pling() { +fn the_sound_for_3_is_pling() { let input = 3; let output = raindrops(input); let expected = "Pling"; @@ -19,7 +19,7 @@ fn test_the_sound_for_3_is_pling() { #[test] #[ignore] -fn test_the_sound_for_5_is_plang() { +fn the_sound_for_5_is_plang() { let input = 5; let output = raindrops(input); let expected = "Plang"; @@ -28,7 +28,7 @@ fn test_the_sound_for_5_is_plang() { #[test] #[ignore] -fn test_the_sound_for_7_is_plong() { +fn the_sound_for_7_is_plong() { let input = 7; let output = raindrops(input); let expected = "Plong"; @@ -37,7 +37,7 @@ fn test_the_sound_for_7_is_plong() { #[test] #[ignore] -fn test_the_sound_for_6_is_pling_as_it_has_a_factor_3() { +fn the_sound_for_6_is_pling_as_it_has_a_factor_3() { let input = 6; let output = raindrops(input); let expected = "Pling"; @@ -55,7 +55,7 @@ fn test_2_to_the_power_3_does_not_make_a_raindrop_sound_as_3_is_the_exponent_not #[test] #[ignore] -fn test_the_sound_for_9_is_pling_as_it_has_a_factor_3() { +fn the_sound_for_9_is_pling_as_it_has_a_factor_3() { let input = 9; let output = raindrops(input); let expected = "Pling"; @@ -64,7 +64,7 @@ fn test_the_sound_for_9_is_pling_as_it_has_a_factor_3() { #[test] #[ignore] -fn test_the_sound_for_10_is_plang_as_it_has_a_factor_5() { +fn the_sound_for_10_is_plang_as_it_has_a_factor_5() { let input = 10; let output = raindrops(input); let expected = "Plang"; @@ -73,7 +73,7 @@ fn test_the_sound_for_10_is_plang_as_it_has_a_factor_5() { #[test] #[ignore] -fn test_the_sound_for_14_is_plong_as_it_has_a_factor_of_7() { +fn the_sound_for_14_is_plong_as_it_has_a_factor_of_7() { let input = 14; let output = raindrops(input); let expected = "Plong"; @@ -82,7 +82,7 @@ fn test_the_sound_for_14_is_plong_as_it_has_a_factor_of_7() { #[test] #[ignore] -fn test_the_sound_for_15_is_plingplang_as_it_has_factors_3_and_5() { +fn the_sound_for_15_is_plingplang_as_it_has_factors_3_and_5() { let input = 15; let output = raindrops(input); let expected = "PlingPlang"; @@ -91,7 +91,7 @@ fn test_the_sound_for_15_is_plingplang_as_it_has_factors_3_and_5() { #[test] #[ignore] -fn test_the_sound_for_21_is_plingplong_as_it_has_factors_3_and_7() { +fn the_sound_for_21_is_plingplong_as_it_has_factors_3_and_7() { let input = 21; let output = raindrops(input); let expected = "PlingPlong"; @@ -100,7 +100,7 @@ fn test_the_sound_for_21_is_plingplong_as_it_has_factors_3_and_7() { #[test] #[ignore] -fn test_the_sound_for_25_is_plang_as_it_has_a_factor_5() { +fn the_sound_for_25_is_plang_as_it_has_a_factor_5() { let input = 25; let output = raindrops(input); let expected = "Plang"; @@ -109,7 +109,7 @@ fn test_the_sound_for_25_is_plang_as_it_has_a_factor_5() { #[test] #[ignore] -fn test_the_sound_for_27_is_pling_as_it_has_a_factor_3() { +fn the_sound_for_27_is_pling_as_it_has_a_factor_3() { let input = 27; let output = raindrops(input); let expected = "Pling"; @@ -118,7 +118,7 @@ fn test_the_sound_for_27_is_pling_as_it_has_a_factor_3() { #[test] #[ignore] -fn test_the_sound_for_35_is_plangplong_as_it_has_factors_5_and_7() { +fn the_sound_for_35_is_plangplong_as_it_has_factors_5_and_7() { let input = 35; let output = raindrops(input); let expected = "PlangPlong"; @@ -127,7 +127,7 @@ fn test_the_sound_for_35_is_plangplong_as_it_has_factors_5_and_7() { #[test] #[ignore] -fn test_the_sound_for_49_is_plong_as_it_has_a_factor_7() { +fn the_sound_for_49_is_plong_as_it_has_a_factor_7() { let input = 49; let output = raindrops(input); let expected = "Plong"; @@ -136,7 +136,7 @@ fn test_the_sound_for_49_is_plong_as_it_has_a_factor_7() { #[test] #[ignore] -fn test_the_sound_for_52_is_52() { +fn the_sound_for_52_is_52() { let input = 52; let output = raindrops(input); let expected = "52"; @@ -145,7 +145,7 @@ fn test_the_sound_for_52_is_52() { #[test] #[ignore] -fn test_the_sound_for_105_is_plingplangplong_as_it_has_factors_3_5_and_7() { +fn the_sound_for_105_is_plingplangplong_as_it_has_factors_3_5_and_7() { let input = 105; let output = raindrops(input); let expected = "PlingPlangPlong"; @@ -154,7 +154,7 @@ fn test_the_sound_for_105_is_plingplangplong_as_it_has_factors_3_5_and_7() { #[test] #[ignore] -fn test_the_sound_for_3125_is_plang_as_it_has_a_factor_5() { +fn the_sound_for_3125_is_plang_as_it_has_a_factor_5() { let input = 3125; let output = raindrops(input); let expected = "Plang"; diff --git a/exercises/practice/rectangles/.meta/test_template.tera b/exercises/practice/rectangles/.meta/test_template.tera index a75dcde9c..fed71856c 100644 --- a/exercises/practice/rectangles/.meta/test_template.tera +++ b/exercises/practice/rectangles/.meta/test_template.tera @@ -3,7 +3,7 @@ use rectangles::*; {% for test in cases %} #[test] #[ignore] -fn test_{{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { {% if test.input.strings | length > 1 -%} #[rustfmt::skip] {%- endif %} diff --git a/exercises/practice/rectangles/tests/rectangles.rs b/exercises/practice/rectangles/tests/rectangles.rs index 59950a834..954268284 100644 --- a/exercises/practice/rectangles/tests/rectangles.rs +++ b/exercises/practice/rectangles/tests/rectangles.rs @@ -1,7 +1,7 @@ use rectangles::*; #[test] -fn test_no_rows() { +fn no_rows() { let input = &[]; let output = count(input); let expected = 0; @@ -10,7 +10,7 @@ fn test_no_rows() { #[test] #[ignore] -fn test_no_columns() { +fn no_columns() { let input = &[""]; let output = count(input); let expected = 0; @@ -19,7 +19,7 @@ fn test_no_columns() { #[test] #[ignore] -fn test_no_rectangles() { +fn no_rectangles() { let input = &[" "]; let output = count(input); let expected = 0; @@ -28,7 +28,7 @@ fn test_no_rectangles() { #[test] #[ignore] -fn test_one_rectangle() { +fn one_rectangle() { #[rustfmt::skip] let input = &[ "+-+", @@ -42,7 +42,7 @@ fn test_one_rectangle() { #[test] #[ignore] -fn test_two_rectangles_without_shared_parts() { +fn two_rectangles_without_shared_parts() { #[rustfmt::skip] let input = &[ " +-+", @@ -58,7 +58,7 @@ fn test_two_rectangles_without_shared_parts() { #[test] #[ignore] -fn test_five_rectangles_with_shared_parts() { +fn five_rectangles_with_shared_parts() { #[rustfmt::skip] let input = &[ " +-+", @@ -74,7 +74,7 @@ fn test_five_rectangles_with_shared_parts() { #[test] #[ignore] -fn test_rectangle_of_height_1_is_counted() { +fn rectangle_of_height_1_is_counted() { #[rustfmt::skip] let input = &[ "+--+", @@ -87,7 +87,7 @@ fn test_rectangle_of_height_1_is_counted() { #[test] #[ignore] -fn test_rectangle_of_width_1_is_counted() { +fn rectangle_of_width_1_is_counted() { #[rustfmt::skip] let input = &[ "++", @@ -114,7 +114,7 @@ fn test_1x1_square_is_counted() { #[test] #[ignore] -fn test_only_complete_rectangles_are_counted() { +fn only_complete_rectangles_are_counted() { #[rustfmt::skip] let input = &[ " +-+", @@ -130,7 +130,7 @@ fn test_only_complete_rectangles_are_counted() { #[test] #[ignore] -fn test_rectangles_can_be_of_different_sizes() { +fn rectangles_can_be_of_different_sizes() { #[rustfmt::skip] let input = &[ "+------+----+", @@ -146,7 +146,7 @@ fn test_rectangles_can_be_of_different_sizes() { #[test] #[ignore] -fn test_corner_is_required_for_a_rectangle_to_be_complete() { +fn corner_is_required_for_a_rectangle_to_be_complete() { #[rustfmt::skip] let input = &[ "+------+----+", @@ -162,7 +162,7 @@ fn test_corner_is_required_for_a_rectangle_to_be_complete() { #[test] #[ignore] -fn test_large_input_with_many_rectangles() { +fn large_input_with_many_rectangles() { #[rustfmt::skip] let input = &[ "+---+--+----+", @@ -181,7 +181,7 @@ fn test_large_input_with_many_rectangles() { #[test] #[ignore] -fn test_rectangles_must_have_four_sides() { +fn rectangles_must_have_four_sides() { #[rustfmt::skip] let input = &[ "+-+ +-+", diff --git a/exercises/practice/reverse-string/.meta/test_template.tera b/exercises/practice/reverse-string/.meta/test_template.tera index 56b1c8e6b..1ddf9e050 100644 --- a/exercises/practice/reverse-string/.meta/test_template.tera +++ b/exercises/practice/reverse-string/.meta/test_template.tera @@ -6,7 +6,7 @@ use reverse_string::*; {% if test.description is starting_with("grapheme cluster") -%} #[cfg(feature = "grapheme")] {% endif -%} -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let input = {{ test.input.value | json_encode() }}; let output = reverse(input); let expected = {{ test.expected | json_encode() }}; diff --git a/exercises/practice/rna-transcription/.meta/test_template.tera b/exercises/practice/rna-transcription/.meta/test_template.tera index ab0ed6016..8f3f2b8ce 100644 --- a/exercises/practice/rna-transcription/.meta/test_template.tera +++ b/exercises/practice/rna-transcription/.meta/test_template.tera @@ -2,7 +2,7 @@ use rna_transcription::{Dna, Rna}; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let input = {{ test.input.dna | json_encode() }}; {% if test.property == "invalidDna" -%} let output = Dna::new(input); diff --git a/exercises/practice/robot-simulator/.meta/test_template.tera b/exercises/practice/robot-simulator/.meta/test_template.tera index 6581819e1..1b01e73a0 100644 --- a/exercises/practice/robot-simulator/.meta/test_template.tera +++ b/exercises/practice/robot-simulator/.meta/test_template.tera @@ -4,7 +4,7 @@ use robot_simulator::*; {% for test in test_group.cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { {%- if test.property == "create" %} let robot = Robot::new({{ test.input.position.x }}, {{ test.input.position.y }}, Direction::{{ test.input.direction | title }}); assert_eq!(robot.position(), ({{ test.expected.position.x }}, {{ test.expected.position.y }})); diff --git a/exercises/practice/roman-numerals/.meta/test_template.tera b/exercises/practice/roman-numerals/.meta/test_template.tera index c82367cd9..308f567d6 100644 --- a/exercises/practice/roman-numerals/.meta/test_template.tera +++ b/exercises/practice/roman-numerals/.meta/test_template.tera @@ -3,7 +3,7 @@ use roman_numerals::Roman; {% for test in cases %} #[test] #[ignore] -fn number_{{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let input = {{ test.input.number | json_encode() }}; let output = Roman::from(input).to_string(); let expected = {{ test.expected | json_encode() }}; diff --git a/exercises/practice/roman-numerals/tests/roman-numerals.rs b/exercises/practice/roman-numerals/tests/roman-numerals.rs index 34df41ae1..336b42e9d 100644 --- a/exercises/practice/roman-numerals/tests/roman-numerals.rs +++ b/exercises/practice/roman-numerals/tests/roman-numerals.rs @@ -1,7 +1,7 @@ use roman_numerals::Roman; #[test] -fn number_1_is_i() { +fn test_1_is_i() { let input = 1; let output = Roman::from(input).to_string(); let expected = "I"; @@ -10,7 +10,7 @@ fn number_1_is_i() { #[test] #[ignore] -fn number_2_is_ii() { +fn test_2_is_ii() { let input = 2; let output = Roman::from(input).to_string(); let expected = "II"; @@ -19,7 +19,7 @@ fn number_2_is_ii() { #[test] #[ignore] -fn number_3_is_iii() { +fn test_3_is_iii() { let input = 3; let output = Roman::from(input).to_string(); let expected = "III"; @@ -28,7 +28,7 @@ fn number_3_is_iii() { #[test] #[ignore] -fn number_4_is_iv() { +fn test_4_is_iv() { let input = 4; let output = Roman::from(input).to_string(); let expected = "IV"; @@ -37,7 +37,7 @@ fn number_4_is_iv() { #[test] #[ignore] -fn number_5_is_v() { +fn test_5_is_v() { let input = 5; let output = Roman::from(input).to_string(); let expected = "V"; @@ -46,7 +46,7 @@ fn number_5_is_v() { #[test] #[ignore] -fn number_6_is_vi() { +fn test_6_is_vi() { let input = 6; let output = Roman::from(input).to_string(); let expected = "VI"; @@ -55,7 +55,7 @@ fn number_6_is_vi() { #[test] #[ignore] -fn number_9_is_ix() { +fn test_9_is_ix() { let input = 9; let output = Roman::from(input).to_string(); let expected = "IX"; @@ -64,7 +64,7 @@ fn number_9_is_ix() { #[test] #[ignore] -fn number_16_is_xvi() { +fn test_16_is_xvi() { let input = 16; let output = Roman::from(input).to_string(); let expected = "XVI"; @@ -73,7 +73,7 @@ fn number_16_is_xvi() { #[test] #[ignore] -fn number_27_is_xxvii() { +fn test_27_is_xxvii() { let input = 27; let output = Roman::from(input).to_string(); let expected = "XXVII"; @@ -82,7 +82,7 @@ fn number_27_is_xxvii() { #[test] #[ignore] -fn number_48_is_xlviii() { +fn test_48_is_xlviii() { let input = 48; let output = Roman::from(input).to_string(); let expected = "XLVIII"; @@ -91,7 +91,7 @@ fn number_48_is_xlviii() { #[test] #[ignore] -fn number_49_is_xlix() { +fn test_49_is_xlix() { let input = 49; let output = Roman::from(input).to_string(); let expected = "XLIX"; @@ -100,7 +100,7 @@ fn number_49_is_xlix() { #[test] #[ignore] -fn number_59_is_lix() { +fn test_59_is_lix() { let input = 59; let output = Roman::from(input).to_string(); let expected = "LIX"; @@ -109,7 +109,7 @@ fn number_59_is_lix() { #[test] #[ignore] -fn number_66_is_lxvi() { +fn test_66_is_lxvi() { let input = 66; let output = Roman::from(input).to_string(); let expected = "LXVI"; @@ -118,7 +118,7 @@ fn number_66_is_lxvi() { #[test] #[ignore] -fn number_93_is_xciii() { +fn test_93_is_xciii() { let input = 93; let output = Roman::from(input).to_string(); let expected = "XCIII"; @@ -127,7 +127,7 @@ fn number_93_is_xciii() { #[test] #[ignore] -fn number_141_is_cxli() { +fn test_141_is_cxli() { let input = 141; let output = Roman::from(input).to_string(); let expected = "CXLI"; @@ -136,7 +136,7 @@ fn number_141_is_cxli() { #[test] #[ignore] -fn number_163_is_clxiii() { +fn test_163_is_clxiii() { let input = 163; let output = Roman::from(input).to_string(); let expected = "CLXIII"; @@ -145,7 +145,7 @@ fn number_163_is_clxiii() { #[test] #[ignore] -fn number_166_is_clxvi() { +fn test_166_is_clxvi() { let input = 166; let output = Roman::from(input).to_string(); let expected = "CLXVI"; @@ -154,7 +154,7 @@ fn number_166_is_clxvi() { #[test] #[ignore] -fn number_402_is_cdii() { +fn test_402_is_cdii() { let input = 402; let output = Roman::from(input).to_string(); let expected = "CDII"; @@ -163,7 +163,7 @@ fn number_402_is_cdii() { #[test] #[ignore] -fn number_575_is_dlxxv() { +fn test_575_is_dlxxv() { let input = 575; let output = Roman::from(input).to_string(); let expected = "DLXXV"; @@ -172,7 +172,7 @@ fn number_575_is_dlxxv() { #[test] #[ignore] -fn number_666_is_dclxvi() { +fn test_666_is_dclxvi() { let input = 666; let output = Roman::from(input).to_string(); let expected = "DCLXVI"; @@ -181,7 +181,7 @@ fn number_666_is_dclxvi() { #[test] #[ignore] -fn number_911_is_cmxi() { +fn test_911_is_cmxi() { let input = 911; let output = Roman::from(input).to_string(); let expected = "CMXI"; @@ -190,7 +190,7 @@ fn number_911_is_cmxi() { #[test] #[ignore] -fn number_1024_is_mxxiv() { +fn test_1024_is_mxxiv() { let input = 1024; let output = Roman::from(input).to_string(); let expected = "MXXIV"; @@ -199,7 +199,7 @@ fn number_1024_is_mxxiv() { #[test] #[ignore] -fn number_1666_is_mdclxvi() { +fn test_1666_is_mdclxvi() { let input = 1666; let output = Roman::from(input).to_string(); let expected = "MDCLXVI"; @@ -208,7 +208,7 @@ fn number_1666_is_mdclxvi() { #[test] #[ignore] -fn number_3000_is_mmm() { +fn test_3000_is_mmm() { let input = 3000; let output = Roman::from(input).to_string(); let expected = "MMM"; @@ -217,7 +217,7 @@ fn number_3000_is_mmm() { #[test] #[ignore] -fn number_3001_is_mmmi() { +fn test_3001_is_mmmi() { let input = 3001; let output = Roman::from(input).to_string(); let expected = "MMMI"; @@ -226,7 +226,7 @@ fn number_3001_is_mmmi() { #[test] #[ignore] -fn number_3888_is_mmmdccclxxxviii() { +fn test_3888_is_mmmdccclxxxviii() { let input = 3888; let output = Roman::from(input).to_string(); let expected = "MMMDCCCLXXXVIII"; @@ -235,7 +235,7 @@ fn number_3888_is_mmmdccclxxxviii() { #[test] #[ignore] -fn number_3999_is_mmmcmxcix() { +fn test_3999_is_mmmcmxcix() { let input = 3999; let output = Roman::from(input).to_string(); let expected = "MMMCMXCIX"; diff --git a/exercises/practice/rotational-cipher/.meta/test_template.tera b/exercises/practice/rotational-cipher/.meta/test_template.tera index 58f50b754..68c693cd3 100644 --- a/exercises/practice/rotational-cipher/.meta/test_template.tera +++ b/exercises/practice/rotational-cipher/.meta/test_template.tera @@ -2,7 +2,7 @@ use rotational_cipher as cipher; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let text = {{ test.input.text | json_encode() }}; let shift_key = {{ test.input.shiftKey | json_encode() }}; let output = cipher::rotate(text, shift_key); diff --git a/exercises/practice/run-length-encoding/.meta/test_template.tera b/exercises/practice/run-length-encoding/.meta/test_template.tera index 4616df818..8dbfee2e2 100644 --- a/exercises/practice/run-length-encoding/.meta/test_template.tera +++ b/exercises/practice/run-length-encoding/.meta/test_template.tera @@ -4,7 +4,7 @@ use run_length_encoding as rle; {% for test in test_group.cases %} #[test] #[ignore] -fn {{ test.property }}_{{ test.description | snake_case }}() { +fn {{ test.property }}_{{ test.description | make_ident }}() { let input = {{ test.input.string | json_encode() }}; {% if test.property == "consistency" -%} let output = rle::decode(&rle::encode(input)); diff --git a/exercises/practice/saddle-points/.meta/test_template.tera b/exercises/practice/saddle-points/.meta/test_template.tera index 1920506ce..ff597d3e0 100644 --- a/exercises/practice/saddle-points/.meta/test_template.tera +++ b/exercises/practice/saddle-points/.meta/test_template.tera @@ -7,7 +7,7 @@ fn find_sorted_saddle_points(input: &[Vec]) -> Vec<(usize, usize)> { {% for test in cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let input = &[{% for row in test.input.matrix %} vec!{{ row }}, {% endfor %}]; diff --git a/exercises/practice/say/.meta/test_template.tera b/exercises/practice/say/.meta/test_template.tera index ab79d73cd..477939969 100644 --- a/exercises/practice/say/.meta/test_template.tera +++ b/exercises/practice/say/.meta/test_template.tera @@ -3,7 +3,7 @@ use say::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let input = {{ test.input.number | fmt_num }}; let output = encode(input); let expected = {{ test.expected | json_encode() }}; diff --git a/exercises/practice/scrabble-score/.meta/test_template.tera b/exercises/practice/scrabble-score/.meta/test_template.tera index f09bc5067..d73cb83fb 100644 --- a/exercises/practice/scrabble-score/.meta/test_template.tera +++ b/exercises/practice/scrabble-score/.meta/test_template.tera @@ -3,7 +3,7 @@ use scrabble_score::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let input = {{ test.input.word | json_encode() }}; let output = score(input); let expected = {{ test.expected | json_encode() }}; diff --git a/exercises/practice/series/.meta/test_template.tera b/exercises/practice/series/.meta/test_template.tera index 25e725057..a093d0e2f 100644 --- a/exercises/practice/series/.meta/test_template.tera +++ b/exercises/practice/series/.meta/test_template.tera @@ -3,7 +3,7 @@ use series::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let input = {{ test.input.series | json_encode() }}; let length = {{ test.input.sliceLength | json_encode() }}; let output = series(input, length); diff --git a/exercises/practice/sieve/.meta/test_template.tera b/exercises/practice/sieve/.meta/test_template.tera index cd33602d8..fcd7aa4c5 100644 --- a/exercises/practice/sieve/.meta/test_template.tera +++ b/exercises/practice/sieve/.meta/test_template.tera @@ -3,7 +3,7 @@ use sieve::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let input = {{ test.input.limit | json_encode() }}; let output = primes_up_to(input); let expected = {{ test.expected | json_encode() }}; diff --git a/exercises/practice/space-age/.meta/test_template.tera b/exercises/practice/space-age/.meta/test_template.tera index a69f248ef..389c6dd1e 100644 --- a/exercises/practice/space-age/.meta/test_template.tera +++ b/exercises/practice/space-age/.meta/test_template.tera @@ -10,7 +10,7 @@ fn assert_in_delta(expected: f64, actual: f64) { {% for test in cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let seconds = {{ test.input.seconds | fmt_num }}; let duration = Duration::from(seconds); let output = {{ test.input.planet }}::years_during(&duration); diff --git a/exercises/practice/spiral-matrix/.meta/test_template.tera b/exercises/practice/spiral-matrix/.meta/test_template.tera index 65637ea9d..7db053341 100644 --- a/exercises/practice/spiral-matrix/.meta/test_template.tera +++ b/exercises/practice/spiral-matrix/.meta/test_template.tera @@ -3,7 +3,7 @@ use spiral_matrix::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let input = {{ test.input.size | json_encode() }}; let output = spiral_matrix(input); let expected: [[u32; {{ test.input.size }}]; {{ test.input.size }}] = [ diff --git a/exercises/practice/sublist/.meta/test_template.tera b/exercises/practice/sublist/.meta/test_template.tera index ed6181ece..b4f3540f5 100644 --- a/exercises/practice/sublist/.meta/test_template.tera +++ b/exercises/practice/sublist/.meta/test_template.tera @@ -3,7 +3,7 @@ use sublist::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let list_one: &[i32] = &{{ test.input.listOne | json_encode() }}; let list_two: &[i32] = &{{ test.input.listTwo | json_encode() }}; let output = sublist(list_one, list_two); diff --git a/exercises/practice/sum-of-multiples/.meta/test_template.tera b/exercises/practice/sum-of-multiples/.meta/test_template.tera index a4f690cbd..50776e114 100644 --- a/exercises/practice/sum-of-multiples/.meta/test_template.tera +++ b/exercises/practice/sum-of-multiples/.meta/test_template.tera @@ -3,7 +3,7 @@ use sum_of_multiples::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let factors = &{{ test.input.factors | json_encode() }}; let limit = {{ test.input.limit | fmt_num }}; let output = sum_of_multiples(limit, factors); diff --git a/exercises/practice/tournament/.meta/test_template.tera b/exercises/practice/tournament/.meta/test_template.tera index c6819eb5f..acc85890a 100644 --- a/exercises/practice/tournament/.meta/test_template.tera +++ b/exercises/practice/tournament/.meta/test_template.tera @@ -3,7 +3,7 @@ use tournament::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let input: &[&str] = &{{ test.input.rows | json_encode() }}; let input = input.join("\n"); let output = tally(&input); diff --git a/exercises/practice/triangle/.meta/test_template.tera b/exercises/practice/triangle/.meta/test_template.tera index 329f8ff95..cd13aa6c0 100644 --- a/exercises/practice/triangle/.meta/test_template.tera +++ b/exercises/practice/triangle/.meta/test_template.tera @@ -10,7 +10,7 @@ mod {{ test_group.description | split(pat=" ") | first }} { {% if test.description is containing("float") %} #[cfg(feature = "generic")] {% endif -%} -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let input = {{ test.input.sides | json_encode() }}; let output = Triangle::build(input).unwrap(); {%- if test.expected %} @@ -26,7 +26,7 @@ fn {{ test.description | snake_case }}() { {% if test.scenarios and test.scenarios is containing("floating-point") %} #[cfg(feature = "generic")] {% endif -%} -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let input = {{ test.input.sides | json_encode() }}; let output = Triangle::build(input).unwrap(); {%- if test.expected %} @@ -42,7 +42,7 @@ fn {{ test.description | snake_case }}() { {% if test.scenarios and test.scenarios is containing("floating-point") %} #[cfg(feature = "generic")] {% endif -%} -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let input = {{ test.input.sides | json_encode() }}; let output = Triangle::build(input).unwrap(); {%- if test.expected %} @@ -58,7 +58,7 @@ fn {{ test.description | snake_case }}() { {% if test.scenarios and test.scenarios is containing("floating-point") %} #[cfg(feature = "generic")] {% endif -%} -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let input = {{ test.input.sides | json_encode() }}; let output = Triangle::build(input); assert!(output.is_none()); diff --git a/exercises/practice/two-bucket/.meta/test_template.tera b/exercises/practice/two-bucket/.meta/test_template.tera index ebf9bb81e..959791f8b 100644 --- a/exercises/practice/two-bucket/.meta/test_template.tera +++ b/exercises/practice/two-bucket/.meta/test_template.tera @@ -11,7 +11,7 @@ use two_bucket::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let output = solve( {{ test.input.bucketOne }}, {{ test.input.bucketTwo }}, {{ test.input.goal }}, &{{ self::bucket(label=test.input.startBucket) }}, diff --git a/exercises/practice/variable-length-quantity/.meta/test_template.tera b/exercises/practice/variable-length-quantity/.meta/test_template.tera index f09cf6504..03131daca 100644 --- a/exercises/practice/variable-length-quantity/.meta/test_template.tera +++ b/exercises/practice/variable-length-quantity/.meta/test_template.tera @@ -4,7 +4,7 @@ use variable_length_quantity as vlq; {% for test in test_group.cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { {%- if test.property == "encode" %} let input = &[{% for integer in test.input.integers -%} {{ integer | fmt_num }}, diff --git a/exercises/practice/word-count/.meta/test_template.tera b/exercises/practice/word-count/.meta/test_template.tera index 6e313e057..931086036 100644 --- a/exercises/practice/word-count/.meta/test_template.tera +++ b/exercises/practice/word-count/.meta/test_template.tera @@ -3,7 +3,7 @@ use word_count::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let input = {{ test.input.sentence | json_encode() }}; let mut output = word_count(input); let expected = [{% for key, value in test.expected -%} diff --git a/exercises/practice/wordy/.meta/test_template.tera b/exercises/practice/wordy/.meta/test_template.tera index b6e729d01..33cebd2ae 100644 --- a/exercises/practice/wordy/.meta/test_template.tera +++ b/exercises/practice/wordy/.meta/test_template.tera @@ -6,7 +6,7 @@ use wordy::*; {% if test.property == "exponentials" -%} #[cfg(feature = "exponentials")] {% endif -%} -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let input = {{ test.input.question | json_encode() }}; let output = answer(input); let expected = {% if test.expected is object -%} diff --git a/exercises/practice/xorcism/.meta/test_template.tera b/exercises/practice/xorcism/.meta/test_template.tera index 58fdea30c..746d7f093 100644 --- a/exercises/practice/xorcism/.meta/test_template.tera +++ b/exercises/practice/xorcism/.meta/test_template.tera @@ -80,7 +80,7 @@ fn statefulness() { {% for test in cases %} -mod {{ test.description | snake_case }} { +mod {{ test.description | make_ident }} { use super::*; diff --git a/rust-tooling/generate/src/custom_filters.rs b/rust-tooling/generate/src/custom_filters.rs index 302cc8fa9..dcef8b45c 100644 --- a/rust-tooling/generate/src/custom_filters.rs +++ b/rust-tooling/generate/src/custom_filters.rs @@ -6,8 +6,7 @@ type Filter = fn(&Value, &HashMap) -> Result; pub static CUSTOM_FILTERS: &[(&str, Filter)] = &[ ("to_hex", to_hex), - ("snake_case", snake_case), - ("make_test_ident", make_test_ident), + ("make_ident", make_ident), ("fmt_num", fmt_num), ]; @@ -21,32 +20,19 @@ pub fn to_hex(value: &Value, _args: &HashMap) -> Result { Ok(serde_json::Value::String(format!("{:x}", value))) } -pub fn snake_case(value: &Value, _args: &HashMap) -> Result { +pub fn make_ident(value: &Value, _args: &HashMap) -> Result { let Some(value) = value.as_str() else { return Err(tera::Error::call_filter( - "snake_case filter expects a string", - "serde_json::value::Value::as_str", - )); - }; - Ok(serde_json::Value::String( - // slug is the same dependency tera uses for its builtin 'slugify' - slug::slugify(value).replace('-', "_"), - )) -} - -pub fn make_test_ident(value: &Value, _args: &HashMap) -> Result { - let value = snake_case(value, _args)?; - let Some(value) = value.as_str() else { - return Err(tera::Error::call_filter( - "make_test_ident filter expects a string", + "make_ident filter expects a string", "serde_json::value::Value::as_str", )); }; + let value = slug::slugify(value).replace('-', "_"); if !value.chars().next().unwrap_or_default().is_alphabetic() { // identifiers cannot start with digits etc. return Ok(Value::String(format!("test_{value}"))); } - Ok(Value::String(value.into())) + Ok(Value::String(value)) } pub fn fmt_num(value: &Value, _args: &HashMap) -> Result { diff --git a/rust-tooling/generate/templates/default_test_template.tera b/rust-tooling/generate/templates/default_test_template.tera index 448333d3f..d20dde1f6 100644 --- a/rust-tooling/generate/templates/default_test_template.tera +++ b/rust-tooling/generate/templates/default_test_template.tera @@ -3,7 +3,7 @@ use crate_name::*; {% for test in cases %} #[test] #[ignore] -fn {{ test.description | snake_case }}() { +fn {{ test.description | make_ident }}() { let input = {{ test.input | json_encode() }}; let output = function_name(input); let expected = {{ test.expected | json_encode() }}; From a7ed23c13a5769de771057c97d9947283c2822a7 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Thu, 15 Aug 2024 12:38:26 +0200 Subject: [PATCH 336/436] allergies: remove test util function (#1972) [no important files changed] part of https://github.com/exercism/rust/issues/1824 --- .../allergies/.meta/test_template.tera | 17 +------ .../practice/allergies/tests/allergies.rs | 44 +++++-------------- 2 files changed, 11 insertions(+), 50 deletions(-) diff --git a/exercises/practice/allergies/.meta/test_template.tera b/exercises/practice/allergies/.meta/test_template.tera index 9b050e434..3f222d638 100644 --- a/exercises/practice/allergies/.meta/test_template.tera +++ b/exercises/practice/allergies/.meta/test_template.tera @@ -1,19 +1,5 @@ use allergies::*; -fn compare_allergy_vectors(expected: &[Allergen], actual: &[Allergen]) { - for element in expected { - if !actual.contains(element) { - panic!("Allergen missing\n {element:?} should be in {actual:?}"); - } - } - - if actual.len() != expected.len() { - panic!( - "Allergy vectors are of different lengths\n expected {expected:?}\n got {actual:?}" - ); - } -} - {% for test_group in cases %} {% for test in test_group.cases %} #[test] @@ -35,8 +21,7 @@ fn {{ test.description | make_ident }}() { Allergen::{{ allergen | title }}, {% endfor %} ]; - - compare_allergy_vectors(expected, &allergies); + assert_eq!(&allergies, expected); {% endif -%} } {% endfor -%} diff --git a/exercises/practice/allergies/tests/allergies.rs b/exercises/practice/allergies/tests/allergies.rs index ab8185e7c..62795f5ed 100644 --- a/exercises/practice/allergies/tests/allergies.rs +++ b/exercises/practice/allergies/tests/allergies.rs @@ -1,19 +1,5 @@ use allergies::*; -fn compare_allergy_vectors(expected: &[Allergen], actual: &[Allergen]) { - for element in expected { - if !actual.contains(element) { - panic!("Allergen missing\n {element:?} should be in {actual:?}"); - } - } - - if actual.len() != expected.len() { - panic!( - "Allergy vectors are of different lengths\n expected {expected:?}\n got {actual:?}" - ); - } -} - #[test] fn not_allergic_to_anything_eggs() { @@ -338,8 +324,7 @@ fn allergic_to_everything_cats() { fn no_allergies() { let allergies = Allergies::new(0).allergies(); let expected = &[]; - - compare_allergy_vectors(expected, &allergies); + assert_eq!(&allergies, expected); } #[test] @@ -347,8 +332,7 @@ fn no_allergies() { fn just_eggs() { let allergies = Allergies::new(1).allergies(); let expected = &[Allergen::Eggs]; - - compare_allergy_vectors(expected, &allergies); + assert_eq!(&allergies, expected); } #[test] @@ -356,8 +340,7 @@ fn just_eggs() { fn just_peanuts() { let allergies = Allergies::new(2).allergies(); let expected = &[Allergen::Peanuts]; - - compare_allergy_vectors(expected, &allergies); + assert_eq!(&allergies, expected); } #[test] @@ -365,8 +348,7 @@ fn just_peanuts() { fn just_strawberries() { let allergies = Allergies::new(8).allergies(); let expected = &[Allergen::Strawberries]; - - compare_allergy_vectors(expected, &allergies); + assert_eq!(&allergies, expected); } #[test] @@ -374,8 +356,7 @@ fn just_strawberries() { fn eggs_and_peanuts() { let allergies = Allergies::new(3).allergies(); let expected = &[Allergen::Eggs, Allergen::Peanuts]; - - compare_allergy_vectors(expected, &allergies); + assert_eq!(&allergies, expected); } #[test] @@ -383,8 +364,7 @@ fn eggs_and_peanuts() { fn more_than_eggs_but_not_peanuts() { let allergies = Allergies::new(5).allergies(); let expected = &[Allergen::Eggs, Allergen::Shellfish]; - - compare_allergy_vectors(expected, &allergies); + assert_eq!(&allergies, expected); } #[test] @@ -398,8 +378,7 @@ fn lots_of_stuff() { Allergen::Pollen, Allergen::Cats, ]; - - compare_allergy_vectors(expected, &allergies); + assert_eq!(&allergies, expected); } #[test] @@ -416,8 +395,7 @@ fn everything() { Allergen::Pollen, Allergen::Cats, ]; - - compare_allergy_vectors(expected, &allergies); + assert_eq!(&allergies, expected); } #[test] @@ -433,8 +411,7 @@ fn no_allergen_score_parts() { Allergen::Pollen, Allergen::Cats, ]; - - compare_allergy_vectors(expected, &allergies); + assert_eq!(&allergies, expected); } #[test] @@ -442,6 +419,5 @@ fn no_allergen_score_parts() { fn no_allergen_score_parts_without_highest_valid_score() { let allergies = Allergies::new(257).allergies(); let expected = &[Allergen::Eggs]; - - compare_allergy_vectors(expected, &allergies); + assert_eq!(&allergies, expected); } From 359689359f136ce36a697ff33f67512eee2fe1b3 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Thu, 15 Aug 2024 12:38:36 +0200 Subject: [PATCH 337/436] crypto-square: sync (#1973) part of https://github.com/exercism/rust/issues/1824 --- .../crypto-square/.meta/Cargo-example.toml | 7 - .../practice/crypto-square/.meta/example.rs | 131 ++++-------------- .../crypto-square/.meta/test_template.tera | 11 ++ .../practice/crypto-square/.meta/tests.toml | 37 ++++- .../crypto-square/tests/crypto-square.rs | 85 +++++------- 5 files changed, 107 insertions(+), 164 deletions(-) delete mode 100644 exercises/practice/crypto-square/.meta/Cargo-example.toml create mode 100644 exercises/practice/crypto-square/.meta/test_template.tera diff --git a/exercises/practice/crypto-square/.meta/Cargo-example.toml b/exercises/practice/crypto-square/.meta/Cargo-example.toml deleted file mode 100644 index 5c7303093..000000000 --- a/exercises/practice/crypto-square/.meta/Cargo-example.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -edition = "2021" -name = "crypto-square" -version = "0.1.0" - -[dependencies] -itertools = "0.6.1" diff --git a/exercises/practice/crypto-square/.meta/example.rs b/exercises/practice/crypto-square/.meta/example.rs index 5a7619006..5d5feb4b0 100644 --- a/exercises/practice/crypto-square/.meta/example.rs +++ b/exercises/practice/crypto-square/.meta/example.rs @@ -1,117 +1,40 @@ -extern crate itertools; -use itertools::Itertools; - -/// Encrypt the input string using square cryptography pub fn encrypt(input: &str) -> String { - let prepared = prepare(input); - if prepared.is_empty() { + let mut input: Vec<_> = input + .to_ascii_lowercase() + .chars() + .filter(char::is_ascii_alphanumeric) + .collect(); + if input.is_empty() { return String::new(); } - let (cols, rows) = dimensions(prepared.len()); - - let mut output = String::with_capacity(input.len()); - for chunk_iterator in SquareIndexer::new(rows, cols).chunks(cols).into_iter() { - for ch_idx in chunk_iterator { - if ch_idx < prepared.len() { - output.push(prepared[ch_idx]); - } - } - output.push(' '); - } - - // we know there's one extra space at the end - output.pop(); - - output -} - -/// Construct a vector of characters from the given input. -/// -/// Constrain it to the allowed chars: lowercase ascii letters. -/// We construct a vector here because the length of the input -/// matters when constructing the output, so we need to know -/// how many input chars there are. We could treat it as a stream -/// and just stream twice, but collecting it into a vector works -/// equally well and might be a bit faster. -fn prepare(input: &str) -> Vec { - let mut output = Vec::with_capacity(input.len()); - - output.extend( - input - .chars() - .filter(|&c| c.is_ascii() && !c.is_whitespace() && !c.is_ascii_punctuation()) - .map(|c| c.to_ascii_lowercase()), - ); - - // add space padding to the end such that the actual string returned - // forms a perfect rectangle - let (r, c) = dimensions(output.len()); - output.resize(r * c, ' '); + let width = (input.len() as f64).sqrt().ceil() as usize; + let size = width * width; - output.shrink_to_fit(); - - output -} - -/// Get the dimensions of the appropriate bounding rectangle for this encryption -/// -/// To find `(rows, cols)` such that `cols >= rows && cols - rows <= 1`, we find -/// the least square greater than or equal to the message length. Its square root -/// is the cols. If the message length is a perfect square, `rows` is the same. -/// Otherwise, it is one less. -fn dimensions(length: usize) -> (usize, usize) { - let cols = (length as f64).sqrt().ceil() as usize; - let rows = if cols * cols == length { - cols + // skip last row if already empty + let last_row = if input.len() + width > size { + width } else { - cols - 1 + width - 1 }; - (rows, cols) -} -/// Iterator over the indices of the appropriate chars of the output. -/// -/// For a (2, 3) (r, c) grid, yields (0, 3, 1, 4, 2, 5). -/// Does no bounds checking or space insertion: that's handled elsewhere. -#[derive(Debug)] -struct SquareIndexer { - rows: usize, - cols: usize, - cur_row: usize, - cur_col: usize, - max_value: usize, -} + // padding + input.resize(size, ' '); -impl SquareIndexer { - fn new(rows: usize, cols: usize) -> SquareIndexer { - SquareIndexer { - rows, - cols, - cur_row: 0, - cur_col: 0, - max_value: rows * cols, - } - } -} + // prevent input from being moved into closure below + let input = &input; -impl Iterator for SquareIndexer { - type Item = usize; - fn next(&mut self) -> Option { - let value = self.cur_row + (self.cur_col * self.rows); - let output = if value < self.max_value && self.cur_row < self.rows { - Some(value) - } else { - None - }; + // transpose + let mut res: String = (0..width) + .flat_map(|col| { + (0..last_row) + .map(move |row| input[row * width + col]) + .chain(std::iter::once(' ')) + }) + .collect(); - // now increment internal state to next value - self.cur_col += 1; - if self.cur_col >= self.cols { - self.cur_col = 0; - self.cur_row += 1; - } + // trailing space separator + res.pop(); - output - } + res } diff --git a/exercises/practice/crypto-square/.meta/test_template.tera b/exercises/practice/crypto-square/.meta/test_template.tera new file mode 100644 index 000000000..9a2468871 --- /dev/null +++ b/exercises/practice/crypto-square/.meta/test_template.tera @@ -0,0 +1,11 @@ +use crypto_square::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let actual = encrypt({{ test.input.plaintext | json_encode() }}); + let expected = {{ test.expected | json_encode() }}; + assert_eq!(&actual, expected); +} +{% endfor -%} diff --git a/exercises/practice/crypto-square/.meta/tests.toml b/exercises/practice/crypto-square/.meta/tests.toml index be690e975..085d142ea 100644 --- a/exercises/practice/crypto-square/.meta/tests.toml +++ b/exercises/practice/crypto-square/.meta/tests.toml @@ -1,3 +1,34 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[407c3837-9aa7-4111-ab63-ec54b58e8e9f] +description = "empty plaintext results in an empty ciphertext" + +[aad04a25-b8bb-4304-888b-581bea8e0040] +description = "normalization results in empty plaintext" + +[64131d65-6fd9-4f58-bdd8-4a2370fb481d] +description = "Lowercase" + +[63a4b0ed-1e3c-41ea-a999-f6f26ba447d6] +description = "Remove spaces" + +[1b5348a1-7893-44c1-8197-42d48d18756c] +description = "Remove punctuation" + +[8574a1d3-4a08-4cec-a7c7-de93a164f41a] +description = "9 character plaintext results in 3 chunks of 3 characters" + +[a65d3fa1-9e09-43f9-bcec-7a672aec3eae] +description = "8 character plaintext results in 3 chunks, the last one with a trailing space" + +[fbcb0c6d-4c39-4a31-83f6-c473baa6af80] +description = "54 character plaintext results in 7 chunks, the last two with trailing spaces" diff --git a/exercises/practice/crypto-square/tests/crypto-square.rs b/exercises/practice/crypto-square/tests/crypto-square.rs index ebe67f3a9..7fac9531f 100644 --- a/exercises/practice/crypto-square/tests/crypto-square.rs +++ b/exercises/practice/crypto-square/tests/crypto-square.rs @@ -1,79 +1,64 @@ -use crypto_square::encrypt; +use crypto_square::*; -fn test(input: &str, output: &str) { - assert_eq!(&encrypt(input), output); +#[test] +fn empty_plaintext_results_in_an_empty_ciphertext() { + let actual = encrypt(""); + let expected = ""; + assert_eq!(&actual, expected); } #[test] -fn empty_input() { - test("", "") +#[ignore] +fn normalization_results_in_empty_plaintext() { + let actual = encrypt("... --- ..."); + let expected = ""; + assert_eq!(&actual, expected); } #[test] #[ignore] -fn encrypt_also_decrypts_square() { - // note that you only get the exact input back if: - // 1. no punctuation - // 2. even spacing - // 3. all lowercase - // 4. square input - let example = "lime anda coco anut"; - assert_eq!(example, &encrypt(&encrypt(example))); +fn lowercase() { + let actual = encrypt("A"); + let expected = "a"; + assert_eq!(&actual, expected); } #[test] #[ignore] -fn example() { - test( - "If man was meant to stay on the ground, god would have given us roots.", - "imtgdvs fearwer mayoogo anouuio ntnnlvt wttddes aohghn sseoau ", - ) +fn remove_spaces() { + let actual = encrypt(" b "); + let expected = "b"; + assert_eq!(&actual, expected); } #[test] #[ignore] -fn empty_last_line() { - test("congratulate", "crl oaa ntt gue") +fn remove_punctuation() { + let actual = encrypt("@1,%!"); + let expected = "1"; + assert_eq!(&actual, expected); } #[test] #[ignore] -fn spaces_are_reorganized() { - test("abet", "ae bt"); - test("a bet", "ae bt"); - test(" a b e t ", "ae bt"); +fn test_9_character_plaintext_results_in_3_chunks_of_3_characters() { + let actual = encrypt("This is fun!"); + let expected = "tsf hiu isn"; + assert_eq!(&actual, expected); } #[test] #[ignore] -fn everything_becomes_lowercase() { - test("caSe", "cs ae"); - test("cAsE", "cs ae"); - test("CASE", "cs ae"); +fn test_8_character_plaintext_results_in_3_chunks_the_last_one_with_a_trailing_space() { + let actual = encrypt("Chill out."); + let expected = "clu hlt io "; + assert_eq!(&actual, expected); } #[test] #[ignore] -fn long() { - test( - r#" -We choose to go to the moon. - -We choose to go to the moon in this decade and do the other things, -not because they are easy, but because they are hard, because that -goal will serve to organize and measure the best of our energies and -skills, because that challenge is one that we are willing to accept, -one we are unwilling to postpone, and one which we intend to win, -and the others, too. - --- John F. Kennedy, 12 September 1962 - "#, - &(String::from("womdbudlmecsgwdwob enooetbsenaotioihe ") - + "cwotcbeeaeunolnnnr henhaecrsrsealeaf1 ocieucavugetciwnk9 " - + "ohnosauerithcnhde6 sotteusteehaegitn2 eohhtseotsatptchn " - + "tsiehetohatwtohee oesrethrenceopwod gtdtyhagbdhanoety " - + "ooehaetaesaresih1 tgcirygnsklewtne2 ooaneaoitilweptrs " - + "ttdgerazoleiaoese hoesaeleflnlrnntp etanshwaosgleedot " - + "mhnoyainubeiuatoe oedtbrldreinnnojm "), - ) +fn test_54_character_plaintext_results_in_7_chunks_the_last_two_with_trailing_spaces() { + let actual = encrypt("If man was meant to stay on the ground, god would have given us roots."); + let expected = "imtgdvs fearwer mayoogo anouuio ntnnlvt wttddes aohghn sseoau "; + assert_eq!(&actual, expected); } From a8125d7de62fe15a0116799751b09fe4e47f9e9e Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Thu, 15 Aug 2024 18:46:50 +0200 Subject: [PATCH 338/436] dominoes: sync (#1974) [no important files changed] part of https://github.com/exercism/rust/issues/1824 --- .../dominoes/.meta/test_template.tera | 61 ++++++ exercises/practice/dominoes/.meta/tests.toml | 37 +++- exercises/practice/dominoes/tests/dominoes.rs | 182 ++++++++---------- 3 files changed, 173 insertions(+), 107 deletions(-) create mode 100644 exercises/practice/dominoes/.meta/test_template.tera diff --git a/exercises/practice/dominoes/.meta/test_template.tera b/exercises/practice/dominoes/.meta/test_template.tera new file mode 100644 index 000000000..22335e994 --- /dev/null +++ b/exercises/practice/dominoes/.meta/test_template.tera @@ -0,0 +1,61 @@ +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let input = &[ + {% for domino in test.input.dominoes %} + ({{ domino.0 }}, {{domino.1 }}), + {% endfor %} + ]; + {%- if test.expected %} + let output = dominoes::chain(input); + assert!(output.is_some()); + assert_correct(input, output.unwrap()); + {%- else %} + assert!(dominoes::chain(input).is_none()); + {%- endif %} +} +{% endfor -%} + +type Domino = (u8, u8); + +fn assert_correct(input: &[Domino], output: Vec) { + if input.len() != output.len() { + panic!("Length mismatch for input {input:?}, output {output:?}"); + } else if input.is_empty() { + // and thus output.is_empty() + return; + } + + let mut output_sorted = output + .iter() + .map(|&d| normalize(d)) + .collect::>(); + output_sorted.sort_unstable(); + let mut input_sorted = input.iter().map(|&d| normalize(d)).collect::>(); + input_sorted.sort_unstable(); + if input_sorted != output_sorted { + panic!("Domino mismatch for input {input:?}, output {output:?}"); + } + + // both input and output have at least 1 element + // This essentially puts the first element after the last one, thereby making it + // easy to check whether the domino chains "wraps around". + { + let mut n = output[0].1; + let iter = output.iter().skip(1).chain(output.iter().take(1)); + for &(first, second) in iter { + if n != first { + panic!("Chaining failure for input {input:?}, output {output:?}") + } + n = second + } + } +} + +fn normalize(d: Domino) -> Domino { + match d { + (m, n) if m > n => (n, m), + (m, n) => (m, n), + } +} diff --git a/exercises/practice/dominoes/.meta/tests.toml b/exercises/practice/dominoes/.meta/tests.toml index ff6932826..08c8e08d0 100644 --- a/exercises/practice/dominoes/.meta/tests.toml +++ b/exercises/practice/dominoes/.meta/tests.toml @@ -1,13 +1,41 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[31a673f2-5e54-49fe-bd79-1c1dae476c9c] +description = "empty input = empty output" + +[4f99b933-367b-404b-8c6d-36d5923ee476] +description = "singleton input = singleton output" [91122d10-5ec7-47cb-b759-033756375869] description = "singleton that can't be chained" +[be8bc26b-fd3d-440b-8e9f-d698a0623be3] +description = "three elements" + [99e615c6-c059-401c-9e87-ad7af11fea5c] description = "can reverse dominoes" +[51f0c291-5d43-40c5-b316-0429069528c9] +description = "can't be chained" + +[9a75e078-a025-4c23-8c3a-238553657f39] +description = "disconnected - simple" + +[0da0c7fe-d492-445d-b9ef-1f111f07a301] +description = "disconnected - double loop" + +[b6087ff0-f555-4ea0-a71c-f9d707c5994a] +description = "disconnected - single isolated" + [2174fbdc-8b48-4bac-9914-8090d06ef978] description = "need backtrack" @@ -16,3 +44,6 @@ description = "separate loops" [cd061538-6046-45a7-ace9-6708fe8f6504] description = "nine elements" + +[44704c7c-3adb-4d98-bd30-f45527cf8b49] +description = "separate three-domino loops" diff --git a/exercises/practice/dominoes/tests/dominoes.rs b/exercises/practice/dominoes/tests/dominoes.rs index 00bec57b2..1153a3b24 100644 --- a/exercises/practice/dominoes/tests/dominoes.rs +++ b/exercises/practice/dominoes/tests/dominoes.rs @@ -1,166 +1,89 @@ -use crate::CheckResult::*; - -type Domino = (u8, u8); - -#[derive(Debug)] -enum CheckResult { - GotInvalid, // chain returned None - Correct, - ChainingFailure(Vec), // failure to match the dots at the right side of one domino with - // the one on the left side of the next - LengthMismatch(Vec), - DominoMismatch(Vec), // different dominoes are used in input and output -} - -fn normalize(d: Domino) -> Domino { - match d { - (m, n) if m > n => (n, m), - (m, n) => (m, n), - } -} - -fn check(input: &[Domino]) -> CheckResult { - let output = match dominoes::chain(input) { - None => return GotInvalid, - Some(o) => o, - }; - if input.len() != output.len() { - return LengthMismatch(output); - } else if input.is_empty() { - // and thus output.is_empty() - return Correct; - } - - let mut output_sorted = output - .iter() - .map(|&d| normalize(d)) - .collect::>(); - output_sorted.sort_unstable(); - let mut input_sorted = input.iter().map(|&d| normalize(d)).collect::>(); - input_sorted.sort_unstable(); - if input_sorted != output_sorted { - return DominoMismatch(output); - } - - // both input and output have at least 1 element - // This essentially puts the first element after the last one, thereby making it - // easy to check whether the domino chains "wraps around". - let mut fail = false; - { - let mut n = output[0].1; - let iter = output.iter().skip(1).chain(output.iter().take(1)); - for &(first, second) in iter { - if n != first { - fail = true; - break; - } - n = second - } - } - if fail { - ChainingFailure(output) - } else { - Correct - } -} - -fn assert_correct(input: &[Domino]) { - match check(input) { - Correct => (), - GotInvalid => panic!("Unexpectedly got invalid on input {input:?}"), - ChainingFailure(output) => { - panic!("Chaining failure for input {input:?}, output {output:?}") - } - LengthMismatch(output) => { - panic!("Length mismatch for input {input:?}, output {output:?}") - } - DominoMismatch(output) => { - panic!("Domino mismatch for input {input:?}, output {output:?}") - } - } -} - #[test] fn empty_input_empty_output() { let input = &[]; - assert_eq!(dominoes::chain(input), Some(vec![])); + let output = dominoes::chain(input); + assert!(output.is_some()); + assert_correct(input, output.unwrap()); } #[test] #[ignore] fn singleton_input_singleton_output() { let input = &[(1, 1)]; - assert_correct(input); + let output = dominoes::chain(input); + assert!(output.is_some()); + assert_correct(input, output.unwrap()); } #[test] #[ignore] -fn singleton_that_cant_be_chained() { +fn singleton_that_can_t_be_chained() { let input = &[(1, 2)]; - assert_eq!(dominoes::chain(input), None); + assert!(dominoes::chain(input).is_none()); } #[test] #[ignore] -fn no_repeat_numbers() { +fn three_elements() { let input = &[(1, 2), (3, 1), (2, 3)]; - assert_correct(input); + let output = dominoes::chain(input); + assert!(output.is_some()); + assert_correct(input, output.unwrap()); } #[test] #[ignore] fn can_reverse_dominoes() { let input = &[(1, 2), (1, 3), (2, 3)]; - assert_correct(input); + let output = dominoes::chain(input); + assert!(output.is_some()); + assert_correct(input, output.unwrap()); } #[test] #[ignore] -fn no_chains() { +fn can_t_be_chained() { let input = &[(1, 2), (4, 1), (2, 3)]; - assert_eq!(dominoes::chain(input), None); + assert!(dominoes::chain(input).is_none()); } #[test] #[ignore] fn disconnected_simple() { let input = &[(1, 1), (2, 2)]; - assert_eq!(dominoes::chain(input), None); + assert!(dominoes::chain(input).is_none()); } #[test] #[ignore] fn disconnected_double_loop() { let input = &[(1, 2), (2, 1), (3, 4), (4, 3)]; - assert_eq!(dominoes::chain(input), None); + assert!(dominoes::chain(input).is_none()); } #[test] #[ignore] fn disconnected_single_isolated() { let input = &[(1, 2), (2, 3), (3, 1), (4, 4)]; - assert_eq!(dominoes::chain(input), None); + assert!(dominoes::chain(input).is_none()); } #[test] #[ignore] fn need_backtrack() { let input = &[(1, 2), (2, 3), (3, 1), (2, 4), (2, 4)]; - assert_correct(input); + let output = dominoes::chain(input); + assert!(output.is_some()); + assert_correct(input, output.unwrap()); } #[test] #[ignore] fn separate_loops() { let input = &[(1, 2), (2, 3), (3, 1), (1, 1), (2, 2), (3, 3)]; - assert_correct(input); -} - -#[test] -#[ignore] -fn pop_same_value_first() { - let input = &[(2, 3), (3, 1), (1, 1), (2, 2), (3, 3), (2, 1)]; - assert_correct(input); + let output = dominoes::chain(input); + assert!(output.is_some()); + assert_correct(input, output.unwrap()); } #[test] @@ -177,5 +100,56 @@ fn nine_elements() { (3, 4), (5, 6), ]; - assert_correct(input); + let output = dominoes::chain(input); + assert!(output.is_some()); + assert_correct(input, output.unwrap()); +} + +#[test] +#[ignore] +fn separate_three_domino_loops() { + let input = &[(1, 2), (2, 3), (3, 1), (4, 5), (5, 6), (6, 4)]; + assert!(dominoes::chain(input).is_none()); +} +type Domino = (u8, u8); + +fn assert_correct(input: &[Domino], output: Vec) { + if input.len() != output.len() { + panic!("Length mismatch for input {input:?}, output {output:?}"); + } else if input.is_empty() { + // and thus output.is_empty() + return; + } + + let mut output_sorted = output + .iter() + .map(|&d| normalize(d)) + .collect::>(); + output_sorted.sort_unstable(); + let mut input_sorted = input.iter().map(|&d| normalize(d)).collect::>(); + input_sorted.sort_unstable(); + if input_sorted != output_sorted { + panic!("Domino mismatch for input {input:?}, output {output:?}"); + } + + // both input and output have at least 1 element + // This essentially puts the first element after the last one, thereby making it + // easy to check whether the domino chains "wraps around". + { + let mut n = output[0].1; + let iter = output.iter().skip(1).chain(output.iter().take(1)); + for &(first, second) in iter { + if n != first { + panic!("Chaining failure for input {input:?}, output {output:?}") + } + n = second + } + } +} + +fn normalize(d: Domino) -> Domino { + match d { + (m, n) if m > n => (n, m), + (m, n) => (m, n), + } } From 2495f4d827766179b108f7756872e3e8577132a9 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Fri, 16 Aug 2024 06:54:27 +0200 Subject: [PATCH 339/436] grade-school: sync (#1976) part of https://github.com/exercism/rust/issues/1824 --- .../grade-school/.meta/additional-tests.json | 62 +++++++++++ .../practice/grade-school/.meta/example.rs | 7 ++ .../grade-school/.meta/test_template.tera | 46 ++++++++ .../practice/grade-school/.meta/tests.toml | 89 ++++++++++++++- .../grade-school/tests/grade-school.rs | 101 ++++++++++-------- 5 files changed, 258 insertions(+), 47 deletions(-) create mode 100644 exercises/practice/grade-school/.meta/additional-tests.json create mode 100644 exercises/practice/grade-school/.meta/test_template.tera diff --git a/exercises/practice/grade-school/.meta/additional-tests.json b/exercises/practice/grade-school/.meta/additional-tests.json new file mode 100644 index 000000000..c4c2c3ace --- /dev/null +++ b/exercises/practice/grade-school/.meta/additional-tests.json @@ -0,0 +1,62 @@ +[ + { + "uuid": "93f3415a-28f4-40e9-9df6-9d61bef26108", + "description": "grades_for_empty_school", + "comments": [ + "The original exercise design contains a `grades` method", + "which should not go untested." + ], + "property": "grades", + "input": { + "students": [] + }, + "expected": [] + }, + { + "uuid": "93f3415a-28f4-40e9-9df6-9d61bef26108", + "description": "grades_for_one_student", + "comments": [ + "The original exercise design contains a `grades` method", + "which should not go untested." + ], + "property": "grades", + "input": { + "students": [["Aimee", 2]] + }, + "expected": [2] + }, + { + "uuid": "93f3415a-28f4-40e9-9df6-9d61bef26108", + "description": "grades_for_several_students_are_sorted", + "comments": [ + "The original exercise design contains a `grades` method", + "which should not go untested." + ], + "property": "grades", + "input": { + "students": [ + ["Aimee", 2], + ["Logan", 7], + ["Blair", 4] + ] + }, + "expected": [2, 4, 7] + }, + { + "uuid": "93f3415a-28f4-40e9-9df6-9d61bef26108", + "description": "grades_when_several_students_have_the_same_grade", + "comments": [ + "The original exercise design contains a `grades` method", + "which should not go untested." + ], + "property": "grades", + "input": { + "students": [ + ["Aimee", 2], + ["Logan", 2], + ["Blair", 2] + ] + }, + "expected": [2] + } +] diff --git a/exercises/practice/grade-school/.meta/example.rs b/exercises/practice/grade-school/.meta/example.rs index 187535581..cacd65fa3 100644 --- a/exercises/practice/grade-school/.meta/example.rs +++ b/exercises/practice/grade-school/.meta/example.rs @@ -12,6 +12,13 @@ impl School { } pub fn add(&mut self, grade: u32, student: &str) { + if self + .grades + .iter() + .any(|(_, students)| students.iter().any(|s| s == student)) + { + return; // don't add duplicate student + } let entry = self.grades.entry(grade).or_default(); entry.push(student.to_string()); entry.sort_unstable(); diff --git a/exercises/practice/grade-school/.meta/test_template.tera b/exercises/practice/grade-school/.meta/test_template.tera new file mode 100644 index 000000000..c05d12f59 --- /dev/null +++ b/exercises/practice/grade-school/.meta/test_template.tera @@ -0,0 +1,46 @@ +use grade_school::*; + +{% for test in cases %} + +{% if test.property == "roster" %} + {# + The original exercise design does not include a method for getting + the roster. Excluding these tests doesn't seem too bad, it would be + difficult to implement this property incorrectly while getting the other + ones right. + #} + {% continue %} +{% endif%} +{% if test.property == "add" %} + {# + The original exercise design doesn't define the add method as fallible. + #} + {% continue %} +{% endif%} + +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let {% if test.input.students | length > 0 %} + mut + {% endif %} s = School::new(); + {% for student in test.input.students -%} + s.add({{ student.1 }}, {{ student.0 | json_encode() }}); + {% endfor -%} + {% if test.property == "grade" -%} + assert_eq!( + s.grade({{ test.input.desiredGrade }}), + {% if test.expected | length == 0 -%} + Vec::::new() + {% else -%} + vec!{{ test.expected | json_encode() }} + {% endif -%} + ) + {% elif test.property == "grades" -%} + assert_eq!( + s.grades(), + vec!{{ test.expected | json_encode() }} + ) + {% endif -%} +} +{% endfor -%} diff --git a/exercises/practice/grade-school/.meta/tests.toml b/exercises/practice/grade-school/.meta/tests.toml index be690e975..50c9e2e59 100644 --- a/exercises/practice/grade-school/.meta/tests.toml +++ b/exercises/practice/grade-school/.meta/tests.toml @@ -1,3 +1,86 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[a3f0fb58-f240-4723-8ddc-e644666b85cc] +description = "Roster is empty when no student is added" + +[9337267f-7793-4b90-9b4a-8e3978408824] +description = "Add a student" + +[6d0a30e4-1b4e-472e-8e20-c41702125667] +description = "Student is added to the roster" + +[73c3ca75-0c16-40d7-82f5-ed8fe17a8e4a] +description = "Adding multiple students in the same grade in the roster" + +[233be705-dd58-4968-889d-fb3c7954c9cc] +description = "Multiple students in the same grade are added to the roster" + +[87c871c1-6bde-4413-9c44-73d59a259d83] +description = "Cannot add student to same grade in the roster more than once" + +[c125dab7-2a53-492f-a99a-56ad511940d8] +description = "A student can't be in two different grades" +include = false + +[a0c7b9b8-0e89-47f8-8b4a-c50f885e79d1] +description = "A student can only be added to the same grade in the roster once" +include = false +reimplements = "c125dab7-2a53-492f-a99a-56ad511940d8" + +[d7982c4f-1602-49f6-a651-620f2614243a] +description = "Student not added to same grade in the roster more than once" +reimplements = "a0c7b9b8-0e89-47f8-8b4a-c50f885e79d1" + +[e70d5d8f-43a9-41fd-94a4-1ea0fa338056] +description = "Adding students in multiple grades" + +[75a51579-d1d7-407c-a2f8-2166e984e8ab] +description = "Students in multiple grades are added to the roster" + +[7df542f1-57ce-433c-b249-ff77028ec479] +description = "Cannot add same student to multiple grades in the roster" + +[6a03b61e-1211-4783-a3cc-fc7f773fba3f] +description = "A student cannot be added to more than one grade in the sorted roster" +include = false +reimplements = "c125dab7-2a53-492f-a99a-56ad511940d8" + +[c7ec1c5e-9ab7-4d3b-be5c-29f2f7a237c5] +description = "Student not added to multiple grades in the roster" +reimplements = "6a03b61e-1211-4783-a3cc-fc7f773fba3f" + +[d9af4f19-1ba1-48e7-94d0-dabda4e5aba6] +description = "Students are sorted by grades in the roster" + +[d9fb5bea-f5aa-4524-9d61-c158d8906807] +description = "Students are sorted by name in the roster" + +[180a8ff9-5b94-43fc-9db1-d46b4a8c93b6] +description = "Students are sorted by grades and then by name in the roster" + +[5e67aa3c-a3c6-4407-a183-d8fe59cd1630] +description = "Grade is empty if no students in the roster" + +[1e0cf06b-26e0-4526-af2d-a2e2df6a51d6] +description = "Grade is empty if no students in that grade" + +[2bfc697c-adf2-4b65-8d0f-c46e085f796e] +description = "Student not added to same grade more than once" + +[66c8e141-68ab-4a04-a15a-c28bc07fe6b9] +description = "Student not added to multiple grades" + +[c9c1fc2f-42e0-4d2c-b361-99271f03eda7] +description = "Student not added to other grade for multiple grades" + +[1bfbcef1-e4a3-49e8-8d22-f6f9f386187e] +description = "Students are sorted by name in a grade" diff --git a/exercises/practice/grade-school/tests/grade-school.rs b/exercises/practice/grade-school/tests/grade-school.rs index 3d8a2d51b..d2b1dde81 100644 --- a/exercises/practice/grade-school/tests/grade-school.rs +++ b/exercises/practice/grade-school/tests/grade-school.rs @@ -1,83 +1,96 @@ -use grade_school as school; +use grade_school::*; -fn to_owned(v: &[&str]) -> Vec { - v.iter().map(|s| s.to_string()).collect() +#[test] +fn grade_is_empty_if_no_students_in_the_roster() { + let s = School::new(); + assert_eq!(s.grade(1), Vec::::new()) } #[test] -fn grades_for_empty_school() { - let s = school::School::new(); - assert_eq!(s.grades(), vec![]); +#[ignore] +fn grade_is_empty_if_no_students_in_that_grade() { + let mut s = School::new(); + s.add(2, "Peter"); + s.add(2, "Zoe"); + s.add(2, "Alex"); + s.add(3, "Jim"); + assert_eq!(s.grade(1), Vec::::new()) } #[test] #[ignore] -fn grades_for_one_student() { - let mut s = school::School::new(); - s.add(2, "Aimee"); - assert_eq!(s.grades(), vec![2]); +fn student_not_added_to_same_grade_more_than_once() { + let mut s = School::new(); + s.add(2, "Blair"); + s.add(2, "James"); + s.add(2, "James"); + s.add(2, "Paul"); + assert_eq!(s.grade(2), vec!["Blair", "James", "Paul"]) } #[test] #[ignore] -fn grades_for_several_students_are_sorted() { - let mut s = school::School::new(); - s.add(2, "Aimee"); - s.add(7, "Logan"); - s.add(4, "Blair"); - assert_eq!(s.grades(), vec![2, 4, 7]); +fn student_not_added_to_multiple_grades() { + let mut s = School::new(); + s.add(2, "Blair"); + s.add(2, "James"); + s.add(3, "James"); + s.add(3, "Paul"); + assert_eq!(s.grade(2), vec!["Blair", "James"]) } #[test] #[ignore] -fn grades_when_several_students_have_the_same_grade() { - let mut s = school::School::new(); - s.add(2, "Aimee"); - s.add(2, "Logan"); +fn student_not_added_to_other_grade_for_multiple_grades() { + let mut s = School::new(); s.add(2, "Blair"); - assert_eq!(s.grades(), vec![2]); + s.add(2, "James"); + s.add(3, "James"); + s.add(3, "Paul"); + assert_eq!(s.grade(3), vec!["Paul"]) } #[test] #[ignore] -fn grade_for_empty_school() { - let s = school::School::new(); - assert_eq!(s.grade(1), Vec::::new()); +fn students_are_sorted_by_name_in_a_grade() { + let mut s = School::new(); + s.add(5, "Franklin"); + s.add(5, "Bradley"); + s.add(1, "Jeff"); + assert_eq!(s.grade(5), vec!["Bradley", "Franklin"]) } #[test] #[ignore] -fn grade_when_no_students_have_that_grade() { - let mut s = school::School::new(); - s.add(7, "Logan"); - assert_eq!(s.grade(1), Vec::::new()); +fn grades_for_empty_school() { + let s = School::new(); + assert_eq!(s.grades(), vec![]) } #[test] #[ignore] -fn grade_for_one_student() { - let mut s = school::School::new(); +fn grades_for_one_student() { + let mut s = School::new(); s.add(2, "Aimee"); - assert_eq!(s.grade(2), to_owned(&["Aimee"])); + assert_eq!(s.grades(), vec![2]) } #[test] #[ignore] -fn grade_returns_students_sorted_by_name() { - let mut s = school::School::new(); - s.add(2, "James"); - s.add(2, "Blair"); - s.add(2, "Paul"); - assert_eq!(s.grade(2), to_owned(&["Blair", "James", "Paul"])); +fn grades_for_several_students_are_sorted() { + let mut s = School::new(); + s.add(2, "Aimee"); + s.add(7, "Logan"); + s.add(4, "Blair"); + assert_eq!(s.grades(), vec![2, 4, 7]) } #[test] #[ignore] -fn add_students_to_different_grades() { - let mut s = school::School::new(); - s.add(3, "Chelsea"); - s.add(7, "Logan"); - assert_eq!(s.grades(), vec![3, 7]); - assert_eq!(s.grade(3), to_owned(&["Chelsea"])); - assert_eq!(s.grade(7), to_owned(&["Logan"])); +fn grades_when_several_students_have_the_same_grade() { + let mut s = School::new(); + s.add(2, "Aimee"); + s.add(2, "Logan"); + s.add(2, "Blair"); + assert_eq!(s.grades(), vec![2]) } From e5968ccc4917cb2eeac0cd86f561286344b0525b Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Fri, 16 Aug 2024 06:54:40 +0200 Subject: [PATCH 340/436] gigasecond: sync (#1975) [no important files changed] part of https://github.com/exercism/rust/issues/1824 The small util function for constructing a `DateTime` value was left as is because it's only noise and not necessary to understand what's going on in the test case. --- .../gigasecond/.meta/test_template.tera | 53 ++++++++++++++ .../practice/gigasecond/.meta/tests.toml | 33 ++++++++- .../practice/gigasecond/tests/gigasecond.rs | 73 ++++++++++--------- 3 files changed, 123 insertions(+), 36 deletions(-) create mode 100644 exercises/practice/gigasecond/.meta/test_template.tera diff --git a/exercises/practice/gigasecond/.meta/test_template.tera b/exercises/practice/gigasecond/.meta/test_template.tera new file mode 100644 index 000000000..e429b5276 --- /dev/null +++ b/exercises/practice/gigasecond/.meta/test_template.tera @@ -0,0 +1,53 @@ +{% for test in cases %} + +{# + Parsing datetime strings in a templating language + really makes you question your life choices. +#} +{% set date_and_time = test.input.moment | split(pat="T") %} +{% set date = date_and_time.0 | split(pat="-") %} +{% set year = date.0 | int %} +{% set month = date.1 | int %} +{% set day = date.2 | int %} +{% if date_and_time | length >= 2 %} + {% set time = date_and_time.1 | split(pat=":") %} + {% set hour = time.0 | int %} + {% set minute = time.1 | int %} + {% set second = time.2 | int %} +{% else %} + {% set hour = 0 %} + {% set minute = 0 %} + {% set second = 0 %} +{% endif %} + +{# + again for the expected datetime +#} +{% set date_and_time = test.expected | split(pat="T") %} +{% set date = date_and_time.0 | split(pat="-") %} +{% set e_year = date.0 | int %} +{% set e_month = date.1 | int %} +{% set e_day = date.2 | int %} +{% set time = date_and_time.1 | split(pat=":") %} +{% set e_hour = time.0 | int %} +{% set e_minute = time.1 | int %} +{% set e_second = time.2 | int %} + +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let start = datetime({{ year }},{{ month }},{{ day }},{{ hour }},{{ minute }},{{ second }}); + let actual = gigasecond::after(start); + let expected = datetime({{ e_year }},{{ e_month }},{{ e_day }},{{ e_hour }},{{ e_minute }},{{ e_second }}); + assert_eq!(actual, expected); +} +{% endfor %} + +fn datetime(year: i32, month: u8, day: u8, hour: u8, minute: u8, second: u8) -> time::PrimitiveDateTime { + use time::{Date, PrimitiveDateTime, Time}; + + PrimitiveDateTime::new( + Date::from_calendar_date(year, month.try_into().unwrap(), day).unwrap(), + Time::from_hms(hour, minute, second).unwrap(), + ) +} diff --git a/exercises/practice/gigasecond/.meta/tests.toml b/exercises/practice/gigasecond/.meta/tests.toml index be690e975..91a3c8e53 100644 --- a/exercises/practice/gigasecond/.meta/tests.toml +++ b/exercises/practice/gigasecond/.meta/tests.toml @@ -1,3 +1,30 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[92fbe71c-ea52-4fac-bd77-be38023cacf7] +description = "date only specification of time" + +[6d86dd16-6f7a-47be-9e58-bb9fb2ae1433] +description = "second test for date only specification of time" + +[77eb8502-2bca-4d92-89d9-7b39ace28dd5] +description = "third test for date only specification of time" + +[c9d89a7d-06f8-4e28-a305-64f1b2abc693] +description = "full time specified" + +[09d4e30e-728a-4b52-9005-be44a58d9eba] +description = "full time with day roll-over" + +[fcec307c-7529-49ab-b0fe-20309197618a] +description = "does not mutate the input" +include = false +comments = "Rust has immutability by default" diff --git a/exercises/practice/gigasecond/tests/gigasecond.rs b/exercises/practice/gigasecond/tests/gigasecond.rs index 52237cf8b..e29f5c123 100644 --- a/exercises/practice/gigasecond/tests/gigasecond.rs +++ b/exercises/practice/gigasecond/tests/gigasecond.rs @@ -1,52 +1,59 @@ -use time::PrimitiveDateTime as DateTime; - -/// Create a datetime from the given numeric point in time. -/// -/// Panics if any field is invalid. -fn dt(year: i32, month: u8, day: u8, hour: u8, minute: u8, second: u8) -> DateTime { - use time::{Date, Time}; - - DateTime::new( - Date::from_calendar_date(year, month.try_into().unwrap(), day).unwrap(), - Time::from_hms(hour, minute, second).unwrap(), - ) -} - #[test] -fn date() { - let start_date = dt(2011, 4, 25, 0, 0, 0); - - assert_eq!(gigasecond::after(start_date), dt(2043, 1, 1, 1, 46, 40)); +fn date_only_specification_of_time() { + let start = datetime(2011, 4, 25, 0, 0, 0); + let actual = gigasecond::after(start); + let expected = datetime(2043, 1, 1, 1, 46, 40); + assert_eq!(actual, expected); } #[test] #[ignore] -fn another_date() { - let start_date = dt(1977, 6, 13, 0, 0, 0); - - assert_eq!(gigasecond::after(start_date), dt(2009, 2, 19, 1, 46, 40)); +fn second_test_for_date_only_specification_of_time() { + let start = datetime(1977, 6, 13, 0, 0, 0); + let actual = gigasecond::after(start); + let expected = datetime(2009, 2, 19, 1, 46, 40); + assert_eq!(actual, expected); } #[test] #[ignore] -fn third_date() { - let start_date = dt(1959, 7, 19, 0, 0, 0); - - assert_eq!(gigasecond::after(start_date), dt(1991, 3, 27, 1, 46, 40)); +fn third_test_for_date_only_specification_of_time() { + let start = datetime(1959, 7, 19, 0, 0, 0); + let actual = gigasecond::after(start); + let expected = datetime(1991, 3, 27, 1, 46, 40); + assert_eq!(actual, expected); } #[test] #[ignore] -fn datetime() { - let start_date = dt(2015, 1, 24, 22, 0, 0); - - assert_eq!(gigasecond::after(start_date), dt(2046, 10, 2, 23, 46, 40)); +fn full_time_specified() { + let start = datetime(2015, 1, 24, 22, 0, 0); + let actual = gigasecond::after(start); + let expected = datetime(2046, 10, 2, 23, 46, 40); + assert_eq!(actual, expected); } #[test] #[ignore] -fn another_datetime() { - let start_date = dt(2015, 1, 24, 23, 59, 59); +fn full_time_with_day_roll_over() { + let start = datetime(2015, 1, 24, 23, 59, 59); + let actual = gigasecond::after(start); + let expected = datetime(2046, 10, 3, 1, 46, 39); + assert_eq!(actual, expected); +} - assert_eq!(gigasecond::after(start_date), dt(2046, 10, 3, 1, 46, 39)); +fn datetime( + year: i32, + month: u8, + day: u8, + hour: u8, + minute: u8, + second: u8, +) -> time::PrimitiveDateTime { + use time::{Date, PrimitiveDateTime, Time}; + + PrimitiveDateTime::new( + Date::from_calendar_date(year, month.try_into().unwrap(), day).unwrap(), + Time::from_hms(hour, minute, second).unwrap(), + ) } From 2d06f69ccdc5a3d6c0b2dc38ee7f273bb55c7441 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Fri, 16 Aug 2024 10:14:19 +0200 Subject: [PATCH 341/436] grains: sync (#1977) [no important files changed] part of https://github.com/exercism/rust/issues/1824 --- .../practice/grains/.meta/test_template.tera | 22 +++++++++ exercises/practice/grains/.meta/tests.toml | 2 + exercises/practice/grains/tests/grains.rs | 45 +++++++++---------- 3 files changed, 45 insertions(+), 24 deletions(-) create mode 100644 exercises/practice/grains/.meta/test_template.tera diff --git a/exercises/practice/grains/.meta/test_template.tera b/exercises/practice/grains/.meta/test_template.tera new file mode 100644 index 000000000..23590743b --- /dev/null +++ b/exercises/practice/grains/.meta/test_template.tera @@ -0,0 +1,22 @@ +use grains::*; + +{% for test in cases.0.cases %} +#[test] +#[ignore] +{% if test.expected is object -%} +#[should_panic] +{% endif -%} +fn {{ test.description | make_ident }}() { + {% if test.expected is number -%} + assert_eq!(square({{ test.input.square }}), {{ test.expected | fmt_num }}); + {% else %} + square({{ test.input.square }}); + {% endif -%} +} +{% endfor -%} + +#[test] +#[ignore] +fn returns_the_total_number_of_grains_on_the_board() { + assert_eq!(grains::total(), 18_446_744_073_709_551_615); +} diff --git a/exercises/practice/grains/.meta/tests.toml b/exercises/practice/grains/.meta/tests.toml index 442dbacd9..f1ef18b9b 100644 --- a/exercises/practice/grains/.meta/tests.toml +++ b/exercises/practice/grains/.meta/tests.toml @@ -28,6 +28,8 @@ description = "square 0 raises an exception" [61974483-eeb2-465e-be54-ca5dde366453] description = "negative square raises an exception" +include = false +comment = "u64 cannot be negative" [a95e4374-f32c-45a7-a10d-ffec475c012f] description = "square greater than 64 raises an exception" diff --git a/exercises/practice/grains/tests/grains.rs b/exercises/practice/grains/tests/grains.rs index 51e08b643..ff7b18b09 100644 --- a/exercises/practice/grains/tests/grains.rs +++ b/exercises/practice/grains/tests/grains.rs @@ -1,62 +1,59 @@ -fn process_square_case(input: u32, expected: u64) { - assert_eq!(grains::square(input), expected); -} +use grains::*; #[test] -fn one() { - process_square_case(1, 1); +fn grains_on_square_1() { + assert_eq!(square(1), 1); } #[test] #[ignore] -fn two() { - process_square_case(2, 2); +fn grains_on_square_2() { + assert_eq!(square(2), 2); } #[test] #[ignore] -fn three() { - process_square_case(3, 4); +fn grains_on_square_3() { + assert_eq!(square(3), 4); } #[test] #[ignore] -fn four() { - process_square_case(4, 8); +fn grains_on_square_4() { + assert_eq!(square(4), 8); } #[test] #[ignore] -fn sixteen() { - process_square_case(16, 32_768); +fn grains_on_square_16() { + assert_eq!(square(16), 32_768); } #[test] #[ignore] -fn thirty_two() { - process_square_case(32, 2_147_483_648); +fn grains_on_square_32() { + assert_eq!(square(32), 2_147_483_648); } #[test] #[ignore] -fn sixty_four() { - process_square_case(64, 9_223_372_036_854_775_808); +fn grains_on_square_64() { + assert_eq!(square(64), 9_223_372_036_854_775_808); } #[test] #[ignore] -#[should_panic(expected = "Square must be between 1 and 64")] -fn square_0_raises_an_exception() { - grains::square(0); +#[should_panic] +fn square_0_is_invalid() { + square(0); } #[test] #[ignore] -#[should_panic(expected = "Square must be between 1 and 64")] -fn square_greater_than_64_raises_an_exception() { - grains::square(65); +#[should_panic] +fn square_greater_than_64_is_invalid() { + square(65); } - #[test] #[ignore] fn returns_the_total_number_of_grains_on_the_board() { From 41f2551953079397af43ff7fd6eba882bfc69ea6 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Fri, 16 Aug 2024 10:14:33 +0200 Subject: [PATCH 342/436] hamming: sync (#1978) [no important files changed] part of https://github.com/exercism/rust/issues/1824 --- .../practice/hamming/.meta/test_template.tera | 16 +++++ exercises/practice/hamming/.meta/tests.toml | 70 ++++++++++++++++++- exercises/practice/hamming/tests/hamming.rs | 65 ++++------------- 3 files changed, 98 insertions(+), 53 deletions(-) create mode 100644 exercises/practice/hamming/.meta/test_template.tera diff --git a/exercises/practice/hamming/.meta/test_template.tera b/exercises/practice/hamming/.meta/test_template.tera new file mode 100644 index 000000000..3bb6cc252 --- /dev/null +++ b/exercises/practice/hamming/.meta/test_template.tera @@ -0,0 +1,16 @@ +use hamming::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + assert_eq!( + hamming_distance({{ test.input.strand1 | json_encode() }}, {{ test.input.strand2 | json_encode() }}), + {% if test.expected is object -%} + None + {% else -%} + Some({{ test.expected }}) + {% endif -%} + ); +} +{% endfor -%} diff --git a/exercises/practice/hamming/.meta/tests.toml b/exercises/practice/hamming/.meta/tests.toml index be690e975..5dc17ed4e 100644 --- a/exercises/practice/hamming/.meta/tests.toml +++ b/exercises/practice/hamming/.meta/tests.toml @@ -1,3 +1,67 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[f6dcb64f-03b0-4b60-81b1-3c9dbf47e887] +description = "empty strands" + +[54681314-eee2-439a-9db0-b0636c656156] +description = "single letter identical strands" + +[294479a3-a4c8-478f-8d63-6209815a827b] +description = "single letter different strands" + +[9aed5f34-5693-4344-9b31-40c692fb5592] +description = "long identical strands" + +[cd2273a5-c576-46c8-a52b-dee251c3e6e5] +description = "long different strands" + +[919f8ef0-b767-4d1b-8516-6379d07fcb28] +description = "disallow first strand longer" +include = false + +[b9228bb1-465f-4141-b40f-1f99812de5a8] +description = "disallow first strand longer" +reimplements = "919f8ef0-b767-4d1b-8516-6379d07fcb28" + +[8a2d4ed0-ead5-4fdd-924d-27c4cf56e60e] +description = "disallow second strand longer" +include = false + +[dab38838-26bb-4fff-acbe-3b0a9bfeba2d] +description = "disallow second strand longer" +reimplements = "8a2d4ed0-ead5-4fdd-924d-27c4cf56e60e" + +[5dce058b-28d4-4ca7-aa64-adfe4e17784c] +description = "disallow left empty strand" +include = false + +[db92e77e-7c72-499d-8fe6-9354d2bfd504] +description = "disallow left empty strand" +include = false +reimplements = "5dce058b-28d4-4ca7-aa64-adfe4e17784c" + +[b764d47c-83ff-4de2-ab10-6cfe4b15c0f3] +description = "disallow empty first strand" +reimplements = "db92e77e-7c72-499d-8fe6-9354d2bfd504" + +[38826d4b-16fb-4639-ac3e-ba027dec8b5f] +description = "disallow right empty strand" +include = false + +[920cd6e3-18f4-4143-b6b8-74270bb8f8a3] +description = "disallow right empty strand" +include = false +reimplements = "38826d4b-16fb-4639-ac3e-ba027dec8b5f" + +[9ab9262f-3521-4191-81f5-0ed184a5aa89] +description = "disallow empty second strand" +reimplements = "920cd6e3-18f4-4143-b6b8-74270bb8f8a3" diff --git a/exercises/practice/hamming/tests/hamming.rs b/exercises/practice/hamming/tests/hamming.rs index bcccb8311..b5fb7b437 100644 --- a/exercises/practice/hamming/tests/hamming.rs +++ b/exercises/practice/hamming/tests/hamming.rs @@ -1,89 +1,54 @@ -fn process_distance_case(strand_pair: [&str; 2], expected_distance: Option) { - assert_eq!( - hamming::hamming_distance(strand_pair[0], strand_pair[1]), - expected_distance - ); -} +use hamming::*; #[test] fn empty_strands() { - process_distance_case(["", ""], Some(0)); -} - -#[test] -#[ignore] -/// disallow first strand longer -fn disallow_first_strand_longer() { - process_distance_case(["AATG", "AAA"], None); + assert_eq!(hamming_distance("", ""), Some(0)); } #[test] #[ignore] -/// disallow second strand longer -fn disallow_second_strand_longer() { - process_distance_case(["ATA", "AGTG"], None); -} - -#[test] -#[ignore] -fn first_string_is_longer() { - process_distance_case(["AAA", "AA"], None); -} - -#[test] -#[ignore] -fn second_string_is_longer() { - process_distance_case(["A", "AA"], None); -} - -#[test] -#[ignore] -/// single letter identical strands fn single_letter_identical_strands() { - process_distance_case(["A", "A"], Some(0)); + assert_eq!(hamming_distance("A", "A"), Some(0)); } #[test] #[ignore] -/// small distance fn single_letter_different_strands() { - process_distance_case(["G", "T"], Some(1)); + assert_eq!(hamming_distance("G", "T"), Some(1)); } #[test] #[ignore] -/// long identical strands fn long_identical_strands() { - process_distance_case(["GGACTGAAATCTG", "GGACTGAAATCTG"], Some(0)); + assert_eq!(hamming_distance("GGACTGAAATCTG", "GGACTGAAATCTG"), Some(0)); } #[test] #[ignore] -fn no_difference_between_identical_strands() { - process_distance_case(["GGACTGA", "GGACTGA"], Some(0)); +fn long_different_strands() { + assert_eq!(hamming_distance("GGACGGATTCTG", "AGGACGGATTCT"), Some(9)); } #[test] #[ignore] -fn complete_hamming_distance_in_small_strand() { - process_distance_case(["ACT", "GGA"], Some(3)); +fn disallow_first_strand_longer() { + assert_eq!(hamming_distance("AATG", "AAA"), None); } #[test] #[ignore] -fn small_hamming_distance_in_the_middle_somewhere() { - process_distance_case(["GGACG", "GGTCG"], Some(1)); +fn disallow_second_strand_longer() { + assert_eq!(hamming_distance("ATA", "AGTG"), None); } #[test] #[ignore] -fn larger_distance() { - process_distance_case(["ACCAGGG", "ACTATGG"], Some(2)); +fn disallow_empty_first_strand() { + assert_eq!(hamming_distance("", "G"), None); } #[test] #[ignore] -/// large distance in off-by-one strand -fn long_different_strands() { - process_distance_case(["GGACGGATTCTG", "AGGACGGATTCT"], Some(9)); +fn disallow_empty_second_strand() { + assert_eq!(hamming_distance("G", ""), None); } From e1ca06de0a75a89be65ec5f3e484ea4688c2404b Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Fri, 16 Aug 2024 10:14:46 +0200 Subject: [PATCH 343/436] minesweeper: sync (#1980) [no important files changed] part of https://github.com/exercism/rust/issues/1824 --- .../minesweeper/.meta/test_template.tera | 33 +++++ .../practice/minesweeper/.meta/tests.toml | 19 ++- .../practice/minesweeper/tests/minesweeper.rs | 131 +++++++++++------- 3 files changed, 133 insertions(+), 50 deletions(-) create mode 100644 exercises/practice/minesweeper/.meta/test_template.tera diff --git a/exercises/practice/minesweeper/.meta/test_template.tera b/exercises/practice/minesweeper/.meta/test_template.tera new file mode 100644 index 000000000..451fbf988 --- /dev/null +++ b/exercises/practice/minesweeper/.meta/test_template.tera @@ -0,0 +1,33 @@ +use minesweeper::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + {% if test.input.minefield | length < 2 -%} + let input = &[ + {%- for line in test.input.minefield %} + {{ line | json_encode() }}, + {%- endfor %} + ]; + let expected{% if test.expected | length == 0 %}: &[&str]{% endif %} = &[ + {%- for line in test.expected %} + {{ line | json_encode() }}, + {%- endfor %} + ]; + {% else -%} + #[rustfmt::skip] + let (input, expected) = (&[ + {%- for line in test.input.minefield %} + {{ line | json_encode() }}, + {%- endfor %} + ], &[ + {%- for line in test.expected %} + {{ line | json_encode() }}, + {%- endfor %} + ]); + {% endif -%} + let actual = annotate(input); + assert_eq!(actual, expected); +} +{% endfor -%} diff --git a/exercises/practice/minesweeper/.meta/tests.toml b/exercises/practice/minesweeper/.meta/tests.toml index 83e6859da..2a1422224 100644 --- a/exercises/practice/minesweeper/.meta/tests.toml +++ b/exercises/practice/minesweeper/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [0c5ec4bd-dea7-4138-8651-1203e1cb9f44] description = "no rows" @@ -11,6 +18,9 @@ description = "no columns" [6fbf8f6d-a03b-42c9-9a58-b489e9235478] description = "no mines" +[61aff1c4-fb31-4078-acad-cd5f1e635655] +description = "minefield with only mines" + [84167147-c504-4896-85d7-246b01dea7c5] description = "mine surrounded by spaces" @@ -31,3 +41,6 @@ description = "vertical line, mines at edges" [4b098563-b7f3-401c-97c6-79dd1b708f34] description = "cross" + +[04a260f1-b40a-4e89-839e-8dd8525abe0e] +description = "large minefield" diff --git a/exercises/practice/minesweeper/tests/minesweeper.rs b/exercises/practice/minesweeper/tests/minesweeper.rs index 807ede4ae..dfa647dda 100644 --- a/exercises/practice/minesweeper/tests/minesweeper.rs +++ b/exercises/practice/minesweeper/tests/minesweeper.rs @@ -1,148 +1,183 @@ -use minesweeper::annotate; - -fn remove_annotations(board: &[&str]) -> Vec { - board.iter().map(|r| remove_annotations_in_row(r)).collect() -} - -fn remove_annotations_in_row(row: &str) -> String { - row.as_bytes() - .iter() - .map(|&ch| match ch { - b'*' => '*', - _ => ' ', - }) - .collect() -} - -fn run_test(test_case: &[&str]) { - let cleaned = remove_annotations(test_case); - let cleaned_strs = cleaned.iter().map(|r| &r[..]).collect::>(); - let expected = test_case.iter().map(|&r| r.to_string()).collect::>(); - assert_eq!(expected, annotate(&cleaned_strs)); -} +use minesweeper::*; #[test] fn no_rows() { - #[rustfmt::skip] - run_test(&[ - ]); + let input = &[]; + let expected: &[&str] = &[]; + let actual = annotate(input); + assert_eq!(actual, expected); } #[test] #[ignore] fn no_columns() { - #[rustfmt::skip] - run_test(&[ - "", - ]); + let input = &[""]; + let expected = &[""]; + let actual = annotate(input); + assert_eq!(actual, expected); } #[test] #[ignore] fn no_mines() { #[rustfmt::skip] - run_test(&[ + let (input, expected) = (&[ + " ", + " ", + " ", + ], &[ " ", " ", " ", ]); + let actual = annotate(input); + assert_eq!(actual, expected); } #[test] #[ignore] -fn board_with_only_mines() { +fn minefield_with_only_mines() { #[rustfmt::skip] - run_test(&[ + let (input, expected) = (&[ + "***", + "***", + "***", + ], &[ "***", "***", "***", ]); + let actual = annotate(input); + assert_eq!(actual, expected); } #[test] #[ignore] fn mine_surrounded_by_spaces() { #[rustfmt::skip] - run_test(&[ + let (input, expected) = (&[ + " ", + " * ", + " ", + ], &[ "111", "1*1", "111", ]); + let actual = annotate(input); + assert_eq!(actual, expected); } #[test] #[ignore] fn space_surrounded_by_mines() { #[rustfmt::skip] - run_test(&[ + let (input, expected) = (&[ + "***", + "* *", + "***", + ], &[ "***", "*8*", "***", ]); + let actual = annotate(input); + assert_eq!(actual, expected); } #[test] #[ignore] fn horizontal_line() { - #[rustfmt::skip] - run_test(&[ - "1*2*1", - ]); + let input = &[" * * "]; + let expected = &["1*2*1"]; + let actual = annotate(input); + assert_eq!(actual, expected); } #[test] #[ignore] fn horizontal_line_mines_at_edges() { - #[rustfmt::skip] - run_test(&[ - "*1 1*", - ]); + let input = &["* *"]; + let expected = &["*1 1*"]; + let actual = annotate(input); + assert_eq!(actual, expected); } #[test] #[ignore] fn vertical_line() { #[rustfmt::skip] - run_test(&[ + let (input, expected) = (&[ + " ", + "*", + " ", + "*", + " ", + ], &[ "1", "*", "2", "*", "1", ]); + let actual = annotate(input); + assert_eq!(actual, expected); } #[test] #[ignore] fn vertical_line_mines_at_edges() { #[rustfmt::skip] - run_test(&[ + let (input, expected) = (&[ + "*", + " ", + " ", + " ", + "*", + ], &[ "*", "1", " ", "1", "*", ]); + let actual = annotate(input); + assert_eq!(actual, expected); } #[test] #[ignore] fn cross() { #[rustfmt::skip] - run_test(&[ + let (input, expected) = (&[ + " * ", + " * ", + "*****", + " * ", + " * ", + ], &[ " 2*2 ", "25*52", "*****", "25*52", " 2*2 ", ]); + let actual = annotate(input); + assert_eq!(actual, expected); } #[test] #[ignore] -fn large_board() { +fn large_minefield() { #[rustfmt::skip] - run_test(&[ + let (input, expected) = (&[ + " * * ", + " * ", + " * ", + " * *", + " * * ", + " ", + ], &[ "1*22*1", "12*322", " 123*2", @@ -150,4 +185,6 @@ fn large_board() { "1*22*2", "111111", ]); + let actual = annotate(input); + assert_eq!(actual, expected); } From cbae1212901d8b84356d7d4d44f6b76a017d4a9e Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Fri, 16 Aug 2024 10:15:03 +0200 Subject: [PATCH 344/436] luhn: sync (#1979) [no important files changed] part of https://github.com/exercism/rust/issues/1824 --- exercises/practice/luhn/.docs/instructions.md | 3 +- .../practice/luhn/.meta/test_template.tera | 9 ++ exercises/practice/luhn/.meta/tests.toml | 79 +++++++++++++++- exercises/practice/luhn/tests/luhn.rs | 91 +++++++++---------- 4 files changed, 128 insertions(+), 54 deletions(-) create mode 100644 exercises/practice/luhn/.meta/test_template.tera diff --git a/exercises/practice/luhn/.docs/instructions.md b/exercises/practice/luhn/.docs/instructions.md index 8cbe791fc..49934c106 100644 --- a/exercises/practice/luhn/.docs/instructions.md +++ b/exercises/practice/luhn/.docs/instructions.md @@ -22,7 +22,8 @@ The first step of the Luhn algorithm is to double every second digit, starting f We will be doubling ```text -4_3_ 3_9_ 0_4_ 6_6_ +4539 3195 0343 6467 +↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ (double these) ``` If doubling the number results in a number greater than 9 then subtract 9 from the product. diff --git a/exercises/practice/luhn/.meta/test_template.tera b/exercises/practice/luhn/.meta/test_template.tera new file mode 100644 index 000000000..d726197ed --- /dev/null +++ b/exercises/practice/luhn/.meta/test_template.tera @@ -0,0 +1,9 @@ +use luhn::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + assert!({% if not test.expected %} ! {% endif %}is_valid({{ test.input.value | json_encode() }})); +} +{% endfor -%} diff --git a/exercises/practice/luhn/.meta/tests.toml b/exercises/practice/luhn/.meta/tests.toml index be690e975..c0be0c4d9 100644 --- a/exercises/practice/luhn/.meta/tests.toml +++ b/exercises/practice/luhn/.meta/tests.toml @@ -1,3 +1,76 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[792a7082-feb7-48c7-b88b-bbfec160865e] +description = "single digit strings can not be valid" + +[698a7924-64d4-4d89-8daa-32e1aadc271e] +description = "a single zero is invalid" + +[73c2f62b-9b10-4c9f-9a04-83cee7367965] +description = "a simple valid SIN that remains valid if reversed" + +[9369092e-b095-439f-948d-498bd076be11] +description = "a simple valid SIN that becomes invalid if reversed" + +[8f9f2350-1faf-4008-ba84-85cbb93ffeca] +description = "a valid Canadian SIN" + +[1cdcf269-6560-44fc-91f6-5819a7548737] +description = "invalid Canadian SIN" + +[656c48c1-34e8-4e60-9a5a-aad8a367810a] +description = "invalid credit card" + +[20e67fad-2121-43ed-99a8-14b5b856adb9] +description = "invalid long number with an even remainder" + +[7e7c9fc1-d994-457c-811e-d390d52fba5e] +description = "invalid long number with a remainder divisible by 5" + +[ad2a0c5f-84ed-4e5b-95da-6011d6f4f0aa] +description = "valid number with an even number of digits" + +[ef081c06-a41f-4761-8492-385e13c8202d] +description = "valid number with an odd number of spaces" + +[bef66f64-6100-4cbb-8f94-4c9713c5e5b2] +description = "valid strings with a non-digit added at the end become invalid" + +[2177e225-9ce7-40f6-b55d-fa420e62938e] +description = "valid strings with punctuation included become invalid" + +[ebf04f27-9698-45e1-9afe-7e0851d0fe8d] +description = "valid strings with symbols included become invalid" + +[08195c5e-ce7f-422c-a5eb-3e45fece68ba] +description = "single zero with space is invalid" + +[12e63a3c-f866-4a79-8c14-b359fc386091] +description = "more than a single zero is valid" + +[ab56fa80-5de8-4735-8a4a-14dae588663e] +description = "input digit 9 is correctly converted to output digit 9" + +[b9887ee8-8337-46c5-bc45-3bcab51bc36f] +description = "very long input is valid" + +[8a7c0e24-85ea-4154-9cf1-c2db90eabc08] +description = "valid luhn with an odd number of digits and non zero first digit" + +[39a06a5a-5bad-4e0f-b215-b042d46209b1] +description = "using ascii value for non-doubled non-digit isn't allowed" + +[f94cf191-a62f-4868-bc72-7253114aa157] +description = "using ascii value for doubled non-digit isn't allowed" + +[8b72ad26-c8be-49a2-b99c-bcc3bf631b33] +description = "non-numeric, non-space char in the middle with a sum that's divisible by 10 isn't allowed" diff --git a/exercises/practice/luhn/tests/luhn.rs b/exercises/practice/luhn/tests/luhn.rs index c2ce6839b..15fb8e8e6 100644 --- a/exercises/practice/luhn/tests/luhn.rs +++ b/exercises/practice/luhn/tests/luhn.rs @@ -1,141 +1,132 @@ use luhn::*; -fn process_valid_case(number: &str, is_luhn_expected: bool) { - assert_eq!(is_valid(number), is_luhn_expected); -} - #[test] fn single_digit_strings_can_not_be_valid() { - process_valid_case("1", false); + assert!(!is_valid("1")); } #[test] #[ignore] fn a_single_zero_is_invalid() { - process_valid_case("0", false); + assert!(!is_valid("0")); } #[test] #[ignore] fn a_simple_valid_sin_that_remains_valid_if_reversed() { - process_valid_case("059", true); + assert!(is_valid("059")); } #[test] #[ignore] fn a_simple_valid_sin_that_becomes_invalid_if_reversed() { - process_valid_case("59", true); + assert!(is_valid("59")); } #[test] #[ignore] fn a_valid_canadian_sin() { - process_valid_case("055 444 285", true); + assert!(is_valid("055 444 285")); } #[test] #[ignore] fn invalid_canadian_sin() { - process_valid_case("055 444 286", false); + assert!(!is_valid("055 444 286")); } #[test] #[ignore] fn invalid_credit_card() { - process_valid_case("8273 1232 7352 0569", false); + assert!(!is_valid("8273 1232 7352 0569")); } #[test] #[ignore] -fn valid_number_with_an_even_number_of_digits() { - process_valid_case("095 245 88", true); +fn invalid_long_number_with_an_even_remainder() { + assert!(!is_valid("1 2345 6789 1234 5678 9012")); } #[test] #[ignore] -fn strings_that_contain_non_digits_are_invalid() { - process_valid_case("055a 444 285", false); +fn invalid_long_number_with_a_remainder_divisible_by_5() { + assert!(!is_valid("1 2345 6789 1234 5678 9013")); } #[test] #[ignore] -fn valid_strings_with_punctuation_included_become_invalid() { - process_valid_case("055-444-285", false); +fn valid_number_with_an_even_number_of_digits() { + assert!(is_valid("095 245 88")); } #[test] #[ignore] -fn symbols_are_invalid() { - process_valid_case("055£ 444$ 285", false); +fn valid_number_with_an_odd_number_of_spaces() { + assert!(is_valid("234 567 891 234")); } #[test] #[ignore] -fn single_zero_with_space_is_invalid() { - process_valid_case(" 0", false); +fn valid_strings_with_a_non_digit_added_at_the_end_become_invalid() { + assert!(!is_valid("059a")); } #[test] #[ignore] -fn more_than_a_single_zero_is_valid() { - process_valid_case("0000 0", true); +fn valid_strings_with_punctuation_included_become_invalid() { + assert!(!is_valid("055-444-285")); } #[test] #[ignore] -fn input_digit_9_is_correctly_converted_to_output_digit_9() { - process_valid_case("091", true); +fn valid_strings_with_symbols_included_become_invalid() { + assert!(!is_valid("055# 444$ 285")); } #[test] #[ignore] -/// using ASCII value for doubled non-digit isn't allowed -/// Convert non-digits to their ASCII values and then offset them by 48 sometimes accidentally declare an invalid string to be valid. -/// This test is designed to avoid that solution. -fn using_ascii_value_for_doubled_nondigit_isnt_allowed() { - process_valid_case(":9", false); +fn single_zero_with_space_is_invalid() { + assert!(!is_valid(" 0")); } #[test] #[ignore] -/// valid strings with a non-digit added at the end become invalid -fn valid_strings_with_a_nondigit_added_at_the_end_become_invalid() { - process_valid_case("059a", false); +fn more_than_a_single_zero_is_valid() { + assert!(is_valid("0000 0")); } #[test] #[ignore] -/// valid strings with symbols included become invalid -fn valid_strings_with_symbols_included_become_invalid() { - process_valid_case("055# 444$ 285", false); +fn input_digit_9_is_correctly_converted_to_output_digit_9() { + assert!(is_valid("091")); } #[test] #[ignore] -/// using ASCII value for non-doubled non-digit isn't allowed -/// Convert non-digits to their ASCII values and then offset them by 48 sometimes accidentally declare an invalid string to be valid. -/// This test is designed to avoid that solution. -fn using_ascii_value_for_nondoubled_nondigit_isnt_allowed() { - process_valid_case("055b 444 285", false); +fn very_long_input_is_valid() { + assert!(is_valid("9999999999 9999999999 9999999999 9999999999")); } #[test] #[ignore] -/// valid number with an odd number of spaces -fn valid_number_with_an_odd_number_of_spaces() { - process_valid_case("234 567 891 234", true); +fn valid_luhn_with_an_odd_number_of_digits_and_non_zero_first_digit() { + assert!(is_valid("109")); +} + +#[test] +#[ignore] +fn using_ascii_value_for_non_doubled_non_digit_isn_t_allowed() { + assert!(!is_valid("055b 444 285")); } #[test] #[ignore] -/// non-numeric, non-space char in the middle with a sum that's divisible by 10 isn't allowed -fn invalid_char_in_middle_with_sum_divisible_by_10_isnt_allowed() { - process_valid_case("59%59", false); +fn using_ascii_value_for_doubled_non_digit_isn_t_allowed() { + assert!(!is_valid(":9")); } #[test] #[ignore] -/// unicode numeric characters are not allowed in a otherwise valid number -fn valid_strings_with_numeric_unicode_characters_become_invalid() { - process_valid_case("1249①", false); +fn non_numeric_non_space_char_in_the_middle_with_a_sum_that_s_divisible_by_10_isn_t_allowed() { + assert!(!is_valid("59%59")); } From 40fc5c8d1e2efdc922602985d89e1c8eb7a1bdf7 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Fri, 16 Aug 2024 21:18:31 +0200 Subject: [PATCH 345/436] nucleotide-count: sync (#1981) [no important files changed] part of https://github.com/exercism/rust/issues/1824 --- .../.meta/additional-tests.json | 72 +++++++++++++++ .../nucleotide-count/.meta/test_template.tera | 39 ++++++++ .../nucleotide-count/.meta/tests.toml | 28 +++++- .../tests/nucleotide-count.rs | 88 ++++++++----------- 4 files changed, 174 insertions(+), 53 deletions(-) create mode 100644 exercises/practice/nucleotide-count/.meta/additional-tests.json create mode 100644 exercises/practice/nucleotide-count/.meta/test_template.tera diff --git a/exercises/practice/nucleotide-count/.meta/additional-tests.json b/exercises/practice/nucleotide-count/.meta/additional-tests.json new file mode 100644 index 000000000..bad9e678a --- /dev/null +++ b/exercises/practice/nucleotide-count/.meta/additional-tests.json @@ -0,0 +1,72 @@ +[ + { + "uuid": "3e5c30a8-87e2-4845-a815-a49671ade970", + "description": "count_empty", + "comments": [ + "The exercise was designed with a `count` method", + "which should be tested as well." + ], + "property": "count", + "input": { + "nucleotide": "A", + "strand": "" + }, + "expected": "Ok(0)" + }, + { + "uuid": "3e5c30a8-87e2-4845-a815-a49671ade970", + "description": "count_invalid_nucleotide", + "comments": [ + "The exercise was designed with a `count` method", + "which should be tested as well." + ], + "property": "count", + "input": { + "nucleotide": "X", + "strand": "A" + }, + "expected": "Err('X')" + }, + { + "uuid": "3e5c30a8-87e2-4845-a815-a49671ade970", + "description": "count_invalid_dna", + "comments": [ + "The exercise was designed with a `count` method", + "which should be tested as well." + ], + "property": "count", + "input": { + "nucleotide": "A", + "strand": "AX" + }, + "expected": "Err('X')" + }, + { + "uuid": "3e5c30a8-87e2-4845-a815-a49671ade970", + "description": "count_repetitive_cytosine", + "comments": [ + "The exercise was designed with a `count` method", + "which should be tested as well." + ], + "property": "count", + "input": { + "nucleotide": "C", + "strand": "CCCCC" + }, + "expected": "Ok(5)" + }, + { + "uuid": "3e5c30a8-87e2-4845-a815-a49671ade970", + "description": "count_only_thymine", + "comments": [ + "The exercise was designed with a `count` method", + "which should be tested as well." + ], + "property": "count", + "input": { + "nucleotide": "T", + "strand": "GGGGGTAACCCGG" + }, + "expected": "Ok(1)" + } +] diff --git a/exercises/practice/nucleotide-count/.meta/test_template.tera b/exercises/practice/nucleotide-count/.meta/test_template.tera new file mode 100644 index 000000000..bd1d4baa1 --- /dev/null +++ b/exercises/practice/nucleotide-count/.meta/test_template.tera @@ -0,0 +1,39 @@ +use std::collections::HashMap; + +use nucleotide_count::*; + +{% for test in cases %} +{# + Additional tests are appended, but we want to test the `count` API first. +#} +{% if test.property != "count" %} + {% continue %} +{% endif %} + +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + assert_eq!(count('{{ test.input.nucleotide }}', {{ test.input.strand | json_encode() }}), {{ test.expected }}); +} +{% endfor %} + +{% for test in cases %} +{% if test.property == "count" %} + {% continue %} +{% endif %} + +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let output = nucleotide_counts({{ test.input.strand | json_encode() }}); + {% if "error" in test.expected -%} + assert!(output.is_err()); + {% else -%} + let mut expected = HashMap::new(); + {% for key, val in test.expected -%} + expected.insert('{{ key }}', {{ val }}); + {% endfor -%} + assert_eq!(output, Ok(expected)); + {% endif -%} +} +{% endfor %} diff --git a/exercises/practice/nucleotide-count/.meta/tests.toml b/exercises/practice/nucleotide-count/.meta/tests.toml index be690e975..7c55e53f2 100644 --- a/exercises/practice/nucleotide-count/.meta/tests.toml +++ b/exercises/practice/nucleotide-count/.meta/tests.toml @@ -1,3 +1,25 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[3e5c30a8-87e2-4845-a815-a49671ade970] +description = "empty strand" + +[a0ea42a6-06d9-4ac6-828c-7ccaccf98fec] +description = "can count one nucleotide in single-character input" + +[eca0d565-ed8c-43e7-9033-6cefbf5115b5] +description = "strand with repeated nucleotide" + +[40a45eac-c83f-4740-901a-20b22d15a39f] +description = "strand with multiple nucleotides" + +[b4c47851-ee9e-4b0a-be70-a86e343bd851] +description = "strand with invalid nucleotides" diff --git a/exercises/practice/nucleotide-count/tests/nucleotide-count.rs b/exercises/practice/nucleotide-count/tests/nucleotide-count.rs index 83b3bf5e2..e820b0d9e 100644 --- a/exercises/practice/nucleotide-count/tests/nucleotide-count.rs +++ b/exercises/practice/nucleotide-count/tests/nucleotide-count.rs @@ -1,100 +1,88 @@ -use nucleotide_count as dna; - use std::collections::HashMap; -fn process_nucleotidecounts_case(s: &str, pairs: &[(char, usize)]) { - // The reason for the awkward code in here is to ensure that the failure - // message for assert_eq! is as informative as possible. A simpler - // solution would simply check the length of the map, and then - // check for the presence and value of each key in the given pairs vector. - let mut m: HashMap = dna::nucleotide_counts(s).unwrap(); - for &(k, v) in pairs.iter() { - assert_eq!((k, m.remove(&k)), (k, Some(v))); - } - - // may fail with a message that clearly shows all extra pairs in the map - assert_eq!(m.iter().collect::>(), vec![]); -} - -#[test] -fn count_returns_result() { - assert!(dna::count('A', "").is_ok()); -} +use nucleotide_count::*; #[test] -#[ignore] fn count_empty() { - assert_eq!(dna::count('A', ""), Ok(0)); + assert_eq!(count('A', ""), Ok(0)); } #[test] #[ignore] fn count_invalid_nucleotide() { - assert_eq!(dna::count('X', "A"), Err('X')); + assert_eq!(count('X', "A"), Err('X')); } #[test] #[ignore] fn count_invalid_dna() { - assert_eq!(dna::count('A', "AX"), Err('X')); + assert_eq!(count('A', "AX"), Err('X')); } #[test] #[ignore] fn count_repetitive_cytosine() { - assert_eq!(dna::count('C', "CCCCC"), Ok(5)); + assert_eq!(count('C', "CCCCC"), Ok(5)); } #[test] #[ignore] fn count_only_thymine() { - assert_eq!(dna::count('T', "GGGGGTAACCCGG"), Ok(1)); -} - -#[test] -#[ignore] -fn counts_returns_result() { - assert!(dna::nucleotide_counts("ACGT").is_ok()); + assert_eq!(count('T', "GGGGGTAACCCGG"), Ok(1)); } #[test] #[ignore] fn empty_strand() { - process_nucleotidecounts_case("", &[('A', 0), ('T', 0), ('C', 0), ('G', 0)]); + let output = nucleotide_counts(""); + let mut expected = HashMap::new(); + expected.insert('A', 0); + expected.insert('C', 0); + expected.insert('G', 0); + expected.insert('T', 0); + assert_eq!(output, Ok(expected)); } #[test] #[ignore] -/// can count one nucleotide in single-character input -fn can_count_one_nucleotide_in_singlecharacter_input() { - process_nucleotidecounts_case("G", &[('A', 0), ('C', 0), ('G', 1), ('T', 0)]); +fn can_count_one_nucleotide_in_single_character_input() { + let output = nucleotide_counts("G"); + let mut expected = HashMap::new(); + expected.insert('A', 0); + expected.insert('C', 0); + expected.insert('G', 1); + expected.insert('T', 0); + assert_eq!(output, Ok(expected)); } #[test] #[ignore] fn strand_with_repeated_nucleotide() { - process_nucleotidecounts_case("GGGGGGG", &[('A', 0), ('T', 0), ('C', 0), ('G', 7)]); + let output = nucleotide_counts("GGGGGGG"); + let mut expected = HashMap::new(); + expected.insert('A', 0); + expected.insert('C', 0); + expected.insert('G', 7); + expected.insert('T', 0); + assert_eq!(output, Ok(expected)); } #[test] #[ignore] -/// strand with multiple nucleotides fn strand_with_multiple_nucleotides() { - process_nucleotidecounts_case( - "AGCTTTTCATTCTGACTGCAACGGGCAATATGTCTCTGTGTGGATTAAAAAAAGAGTGTCTGATAGCAGC", - &[('A', 20), ('T', 21), ('C', 12), ('G', 17)], - ); -} - -#[test] -#[ignore] -fn counts_invalid_nucleotide_results_in_err() { - assert_eq!(dna::nucleotide_counts("GGXXX"), Err('X')); + let output = + nucleotide_counts("AGCTTTTCATTCTGACTGCAACGGGCAATATGTCTCTGTGTGGATTAAAAAAAGAGTGTCTGATAGCAGC"); + let mut expected = HashMap::new(); + expected.insert('A', 20); + expected.insert('C', 12); + expected.insert('G', 17); + expected.insert('T', 21); + assert_eq!(output, Ok(expected)); } #[test] #[ignore] -/// strand with invalid nucleotides fn strand_with_invalid_nucleotides() { - assert_eq!(dna::nucleotide_counts("AGXXACT"), Err('X'),); + let output = nucleotide_counts("AGXXACT"); + assert!(output.is_err()); } From 4d2826097b9ca55c8ed3bf0f4fc43effddec12ec Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Fri, 16 Aug 2024 21:18:44 +0200 Subject: [PATCH 346/436] paasio: simplify tests (#1982) [no important files changed] part of https://github.com/exercism/rust/issues/1824 This is by no means perfect, but I attempted to make the test functions a little more concise and readable. This is desirable when the test code is shown to users online, while it wasn't a requirement when these tests were generated by macros. --- exercises/practice/paasio/tests/paasio.rs | 120 ++++++---------------- 1 file changed, 29 insertions(+), 91 deletions(-) diff --git a/exercises/practice/paasio/tests/paasio.rs b/exercises/practice/paasio/tests/paasio.rs index da984be88..6f75a96aa 100644 --- a/exercises/practice/paasio/tests/paasio.rs +++ b/exercises/practice/paasio/tests/paasio.rs @@ -19,8 +19,7 @@ mod read_string { #[ignore] fn read_passthrough() { let data = INPUT; - let len = |d: &[u8]| d.len(); - let size = len(data); + let size = data.len(); let mut reader = ReadStats::new(data); let mut buffer = Vec::with_capacity(size); @@ -40,24 +39,16 @@ mod read_string { #[ignore] fn read_chunks() { let data = INPUT; - let len = |d: &[u8]| d.len(); - let size = len(data); + let size = data.len(); let mut reader = ReadStats::new(data); let mut buffer = [0_u8; CHUNK_SIZE]; let mut chunks_read = 0; - while reader - .read(&mut buffer[..]) - .unwrap_or_else(|_| panic!("read failed at chunk {}", chunks_read + 1)) - > 0 - { + while reader.read(&mut buffer[..]).unwrap() > 0 { chunks_read += 1; } - assert_eq!( - size / CHUNK_SIZE + std::cmp::min(1, size % CHUNK_SIZE), - chunks_read - ); + assert_eq!(size / CHUNK_SIZE + 1.min(size % CHUNK_SIZE), chunks_read); // we read once more than the number of chunks, because the final // read returns 0 new bytes assert_eq!(1 + chunks_read, reader.reads()); @@ -68,24 +59,16 @@ mod read_string { #[ignore] fn read_buffered_chunks() { let data = INPUT; - let len = |d: &[u8]| d.len(); - let size = len(data); + let size = data.len(); let mut reader = BufReader::new(ReadStats::new(data)); let mut buffer = [0_u8; CHUNK_SIZE]; let mut chunks_read = 0; - while reader - .read(&mut buffer[..]) - .unwrap_or_else(|_| panic!("read failed at chunk {}", chunks_read + 1)) - > 0 - { + while reader.read(&mut buffer[..]).unwrap() > 0 { chunks_read += 1; } - assert_eq!( - size / CHUNK_SIZE + std::cmp::min(1, size % CHUNK_SIZE), - chunks_read - ); + assert_eq!(size / CHUNK_SIZE + 1.min(size % CHUNK_SIZE), chunks_read); // the BufReader should smooth out the reads, collecting into // a buffer and performing only two read operations: // the first collects everything into the buffer, @@ -107,8 +90,7 @@ mod write_string { #[ignore] fn write_passthrough() { let data = INPUT; - let len = |d: &[u8]| d.len(); - let size = len(data); + let size = data.len(); let mut writer = WriteStats::new(Vec::with_capacity(size)); let written = writer.write(data); assert!(written.is_ok()); @@ -122,8 +104,7 @@ mod write_string { #[ignore] fn sink_oneshot() { let data = INPUT; - let len = |d: &[u8]| d.len(); - let size = len(data); + let size = data.len(); let mut writer = WriteStats::new(io::sink()); let written = writer.write(data); assert!(written.is_ok()); @@ -136,8 +117,7 @@ mod write_string { #[ignore] fn sink_windowed() { let data = INPUT; - let len = |d: &[u8]| d.len(); - let size = len(data); + let size = data.len(); let mut writer = WriteStats::new(io::sink()); let mut chunk_count = 0; @@ -155,8 +135,7 @@ mod write_string { #[ignore] fn sink_buffered_windowed() { let data = INPUT; - let len = |d: &[u8]| d.len(); - let size = len(data); + let size = data.len(); let mut writer = BufWriter::new(WriteStats::new(io::sink())); for chunk in data.chunks(CHUNK_SIZE) { @@ -188,8 +167,7 @@ mod read_byte_literal { #[ignore] fn read_passthrough() { let data = INPUT; - let len = |d: &[u8]| d.len(); - let size = len(data); + let size = data.len(); let mut reader = ReadStats::new(data); let mut buffer = Vec::with_capacity(size); @@ -209,24 +187,16 @@ mod read_byte_literal { #[ignore] fn read_chunks() { let data = INPUT; - let len = |d: &[u8]| d.len(); - let size = len(data); + let size = data.len(); let mut reader = ReadStats::new(data); let mut buffer = [0_u8; CHUNK_SIZE]; let mut chunks_read = 0; - while reader - .read(&mut buffer[..]) - .unwrap_or_else(|_| panic!("read failed at chunk {}", chunks_read + 1)) - > 0 - { + while reader.read(&mut buffer[..]).unwrap() > 0 { chunks_read += 1; } - assert_eq!( - size / CHUNK_SIZE + std::cmp::min(1, size % CHUNK_SIZE), - chunks_read - ); + assert_eq!(size / CHUNK_SIZE + 1.min(size % CHUNK_SIZE), chunks_read); // we read once more than the number of chunks, because the final // read returns 0 new bytes assert_eq!(1 + chunks_read, reader.reads()); @@ -237,24 +207,16 @@ mod read_byte_literal { #[ignore] fn read_buffered_chunks() { let data = INPUT; - let len = |d: &[u8]| d.len(); - let size = len(data); + let size = data.len(); let mut reader = BufReader::new(ReadStats::new(data)); let mut buffer = [0_u8; CHUNK_SIZE]; let mut chunks_read = 0; - while reader - .read(&mut buffer[..]) - .unwrap_or_else(|_| panic!("read failed at chunk {}", chunks_read + 1)) - > 0 - { + while reader.read(&mut buffer[..]).unwrap() > 0 { chunks_read += 1; } - assert_eq!( - size / CHUNK_SIZE + std::cmp::min(1, size % CHUNK_SIZE), - chunks_read - ); + assert_eq!(size / CHUNK_SIZE + 1.min(size % CHUNK_SIZE), chunks_read); // the BufReader should smooth out the reads, collecting into // a buffer and performing only two read operations: // the first collects everything into the buffer, @@ -278,8 +240,7 @@ mod write_byte_literal { #[ignore] fn write_passthrough() { let data = INPUT; - let len = |d: &[u8]| d.len(); - let size = len(data); + let size = data.len(); let mut writer = WriteStats::new(Vec::with_capacity(size)); let written = writer.write(data); assert!(written.is_ok()); @@ -293,8 +254,7 @@ mod write_byte_literal { #[ignore] fn sink_oneshot() { let data = INPUT; - let len = |d: &[u8]| d.len(); - let size = len(data); + let size = data.len(); let mut writer = WriteStats::new(io::sink()); let written = writer.write(data); assert!(written.is_ok()); @@ -307,8 +267,7 @@ mod write_byte_literal { #[ignore] fn sink_windowed() { let data = INPUT; - let len = |d: &[u8]| d.len(); - let size = len(data); + let size = data.len(); let mut writer = WriteStats::new(io::sink()); let mut chunk_count = 0; @@ -326,8 +285,7 @@ mod write_byte_literal { #[ignore] fn sink_buffered_windowed() { let data = INPUT; - let len = |d: &[u8]| d.len(); - let size = len(data); + let size = data.len(); let mut writer = BufWriter::new(WriteStats::new(io::sink())); for chunk in data.chunks(CHUNK_SIZE) { @@ -357,9 +315,7 @@ mod read_file { #[ignore] fn read_passthrough() { let data = std::fs::File::open("Cargo.toml").expect("Cargo.toml must be present"); - let len = - |f: &::std::fs::File| f.metadata().expect("metadata must be present").len() as usize; - let size = len(&data); + let size = data.metadata().expect("metadata must be present").len() as usize; let mut reader = ReadStats::new(data); let mut buffer = Vec::with_capacity(size); @@ -379,25 +335,16 @@ mod read_file { #[ignore] fn read_chunks() { let data = std::fs::File::open("Cargo.toml").expect("Cargo.toml must be present"); - let len = - |f: &::std::fs::File| f.metadata().expect("metadata must be present").len() as usize; - let size = len(&data); + let size = data.metadata().expect("metadata must be present").len() as usize; let mut reader = ReadStats::new(data); let mut buffer = [0_u8; CHUNK_SIZE]; let mut chunks_read = 0; - while reader - .read(&mut buffer[..]) - .unwrap_or_else(|_| panic!("read failed at chunk {}", chunks_read + 1)) - > 0 - { + while reader.read(&mut buffer[..]).unwrap() > 0 { chunks_read += 1; } - assert_eq!( - size / CHUNK_SIZE + std::cmp::min(1, size % CHUNK_SIZE), - chunks_read - ); + assert_eq!(size / CHUNK_SIZE + 1.min(size % CHUNK_SIZE), chunks_read); // we read once more than the number of chunks, because the final // read returns 0 new bytes assert_eq!(1 + chunks_read, reader.reads()); @@ -408,25 +355,16 @@ mod read_file { #[ignore] fn read_buffered_chunks() { let data = std::fs::File::open("Cargo.toml").expect("Cargo.toml must be present"); - let len = - |f: &::std::fs::File| f.metadata().expect("metadata must be present").len() as usize; - let size = len(&data); + let size = data.metadata().expect("metadata must be present").len() as usize; let mut reader = BufReader::new(ReadStats::new(data)); let mut buffer = [0_u8; CHUNK_SIZE]; let mut chunks_read = 0; - while reader - .read(&mut buffer[..]) - .unwrap_or_else(|_| panic!("read failed at chunk {}", chunks_read + 1)) - > 0 - { + while reader.read(&mut buffer[..]).unwrap() > 0 { chunks_read += 1; } - assert_eq!( - size / CHUNK_SIZE + std::cmp::min(1, size % CHUNK_SIZE), - chunks_read - ); + assert_eq!(size / CHUNK_SIZE + 1.min(size % CHUNK_SIZE), chunks_read); // the BufReader should smooth out the reads, collecting into // a buffer and performing only two read operations: // the first collects everything into the buffer, From 9617af2219a35d117c34264d60947d0f73c53a5e Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Fri, 16 Aug 2024 21:18:59 +0200 Subject: [PATCH 347/436] robot-name: remove redundant tests (#1983) [no important files changed] part of https://github.com/exercism/rust/issues/1824 This is another case where I think it makes sense to actually keep the util function around. It produces a good error message and in combination with the body of the test function, users should have enough information. As the comment in the diff already states, the removed tests are redundant because the type system prevents such errors anyway. --- .../practice/robot-name/tests/robot-name.rs | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/exercises/practice/robot-name/tests/robot-name.rs b/exercises/practice/robot-name/tests/robot-name.rs index 011b3c0b2..abfeadd3f 100644 --- a/exercises/practice/robot-name/tests/robot-name.rs +++ b/exercises/practice/robot-name/tests/robot-name.rs @@ -12,27 +12,12 @@ fn assert_name_matches_pattern(n: &str) { ); } -fn assert_name_is_persistent(r: &robot::Robot) { - // The type system already proves this, but why not. - let n1 = r.name(); - let n2 = r.name(); - let n3 = r.name(); - assert_eq!(n1, n2); - assert_eq!(n2, n3); -} - #[test] fn name_should_match_expected_pattern() { let r = robot::Robot::new(); assert_name_matches_pattern(r.name()); } -#[test] -#[ignore] -fn name_is_persistent() { - assert_name_is_persistent(&robot::Robot::new()); -} - #[test] #[ignore] fn different_robots_have_different_names() { @@ -63,14 +48,6 @@ fn new_name_should_match_expected_pattern() { assert_name_matches_pattern(r.name()); } -#[test] -#[ignore] -fn new_name_is_persistent() { - let mut r = robot::Robot::new(); - r.reset_name(); - assert_name_is_persistent(&r); -} - #[test] #[ignore] fn new_name_is_different_from_old_name() { From d47d4074240b7ed18c336959d866c5b3df6a9802 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Fri, 16 Aug 2024 21:19:14 +0200 Subject: [PATCH 348/436] saddle-points: remove util function in tests (#1984) [no important files changed] part of https://github.com/exercism/rust/issues/1824 --- .../saddle-points/.meta/test_template.tera | 11 +++--- .../saddle-points/tests/saddle-points.rs | 34 +++++++++++-------- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/exercises/practice/saddle-points/.meta/test_template.tera b/exercises/practice/saddle-points/.meta/test_template.tera index ff597d3e0..69fb2559c 100644 --- a/exercises/practice/saddle-points/.meta/test_template.tera +++ b/exercises/practice/saddle-points/.meta/test_template.tera @@ -1,9 +1,5 @@ -// We don't care about order -fn find_sorted_saddle_points(input: &[Vec]) -> Vec<(usize, usize)> { - let mut result = saddle_points::find_saddle_points(input); - result.sort_unstable(); - result -} +use saddle_points::*; + {% for test in cases %} #[test] #[ignore] @@ -11,7 +7,8 @@ fn {{ test.description | make_ident }}() { let input = &[{% for row in test.input.matrix %} vec!{{ row }}, {% endfor %}]; - let output = find_sorted_saddle_points(input); + let mut output = find_saddle_points(input); + output.sort_unstable(); let expected = &[ {% for p in test.expected | sort(attribute = "column") | sort(attribute = "row") %} ({{ p.row - 1 }}, {{ p.column - 1 }}), diff --git a/exercises/practice/saddle-points/tests/saddle-points.rs b/exercises/practice/saddle-points/tests/saddle-points.rs index 730772d41..351eb7b3c 100644 --- a/exercises/practice/saddle-points/tests/saddle-points.rs +++ b/exercises/practice/saddle-points/tests/saddle-points.rs @@ -1,14 +1,10 @@ -// We don't care about order -fn find_sorted_saddle_points(input: &[Vec]) -> Vec<(usize, usize)> { - let mut result = saddle_points::find_saddle_points(input); - result.sort_unstable(); - result -} +use saddle_points::*; #[test] fn can_identify_single_saddle_point() { let input = &[vec![9, 8, 7], vec![5, 3, 2], vec![6, 6, 7]]; - let output = find_sorted_saddle_points(input); + let mut output = find_saddle_points(input); + output.sort_unstable(); let expected = &[(1, 0)]; assert_eq!(output, expected); } @@ -17,7 +13,8 @@ fn can_identify_single_saddle_point() { #[ignore] fn can_identify_that_empty_matrix_has_no_saddle_points() { let input = &[vec![]]; - let output = find_sorted_saddle_points(input); + let mut output = find_saddle_points(input); + output.sort_unstable(); let expected = &[]; assert_eq!(output, expected); } @@ -26,7 +23,8 @@ fn can_identify_that_empty_matrix_has_no_saddle_points() { #[ignore] fn can_identify_lack_of_saddle_points_when_there_are_none() { let input = &[vec![1, 2, 3], vec![3, 1, 2], vec![2, 3, 1]]; - let output = find_sorted_saddle_points(input); + let mut output = find_saddle_points(input); + output.sort_unstable(); let expected = &[]; assert_eq!(output, expected); } @@ -35,7 +33,8 @@ fn can_identify_lack_of_saddle_points_when_there_are_none() { #[ignore] fn can_identify_multiple_saddle_points_in_a_column() { let input = &[vec![4, 5, 4], vec![3, 5, 5], vec![1, 5, 4]]; - let output = find_sorted_saddle_points(input); + let mut output = find_saddle_points(input); + output.sort_unstable(); let expected = &[(0, 1), (1, 1), (2, 1)]; assert_eq!(output, expected); } @@ -44,7 +43,8 @@ fn can_identify_multiple_saddle_points_in_a_column() { #[ignore] fn can_identify_multiple_saddle_points_in_a_row() { let input = &[vec![6, 7, 8], vec![5, 5, 5], vec![7, 5, 6]]; - let output = find_sorted_saddle_points(input); + let mut output = find_saddle_points(input); + output.sort_unstable(); let expected = &[(1, 0), (1, 1), (1, 2)]; assert_eq!(output, expected); } @@ -53,7 +53,8 @@ fn can_identify_multiple_saddle_points_in_a_row() { #[ignore] fn can_identify_saddle_point_in_bottom_right_corner() { let input = &[vec![8, 7, 9], vec![6, 7, 6], vec![3, 2, 5]]; - let output = find_sorted_saddle_points(input); + let mut output = find_saddle_points(input); + output.sort_unstable(); let expected = &[(2, 2)]; assert_eq!(output, expected); } @@ -62,7 +63,8 @@ fn can_identify_saddle_point_in_bottom_right_corner() { #[ignore] fn can_identify_saddle_points_in_a_non_square_matrix() { let input = &[vec![3, 1, 3], vec![3, 2, 4]]; - let output = find_sorted_saddle_points(input); + let mut output = find_saddle_points(input); + output.sort_unstable(); let expected = &[(0, 0), (0, 2)]; assert_eq!(output, expected); } @@ -71,7 +73,8 @@ fn can_identify_saddle_points_in_a_non_square_matrix() { #[ignore] fn can_identify_that_saddle_points_in_a_single_column_matrix_are_those_with_the_minimum_value() { let input = &[vec![2], vec![1], vec![4], vec![1]]; - let output = find_sorted_saddle_points(input); + let mut output = find_saddle_points(input); + output.sort_unstable(); let expected = &[(1, 0), (3, 0)]; assert_eq!(output, expected); } @@ -80,7 +83,8 @@ fn can_identify_that_saddle_points_in_a_single_column_matrix_are_those_with_the_ #[ignore] fn can_identify_that_saddle_points_in_a_single_row_matrix_are_those_with_the_maximum_value() { let input = &[vec![2, 5, 3, 5]]; - let output = find_sorted_saddle_points(input); + let mut output = find_saddle_points(input); + output.sort_unstable(); let expected = &[(0, 1), (0, 3)]; assert_eq!(output, expected); } From 2303acbba50aee04495e4342cb2942fc4e04a857 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Wed, 21 Aug 2024 08:45:33 +0200 Subject: [PATCH 349/436] clock: sync (#1986) [no important files changed] --- .../practice/clock/.meta/test_template.tera | 53 ++++++ exercises/practice/clock/.meta/tests.toml | 169 +++++++++++++++++- exercises/practice/clock/tests/clock.rs | 88 ++++----- 3 files changed, 257 insertions(+), 53 deletions(-) create mode 100644 exercises/practice/clock/.meta/test_template.tera diff --git a/exercises/practice/clock/.meta/test_template.tera b/exercises/practice/clock/.meta/test_template.tera new file mode 100644 index 000000000..8d7fd0521 --- /dev/null +++ b/exercises/practice/clock/.meta/test_template.tera @@ -0,0 +1,53 @@ +use clock::*; + +// +// Clock Creation +// + +{% for test in cases.0.cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + assert_eq!(Clock::new({{ test.input.hour }}, {{ test.input.minute }}).to_string(), {{ test.expected | json_encode() }}); +} +{% endfor %} + +// +// Clock Math +// + +{% for test in cases.1.cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let clock = Clock::new({{ test.input.hour }}, {{ test.input.minute }}).add_minutes({{ test.input.value }}); + assert_eq!(clock.to_string(), {{ test.expected | json_encode() }}); +} +{% endfor %} + +{% for test in cases.2.cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let clock = Clock::new({{ test.input.hour }}, {{ test.input.minute }}).add_minutes(-{{ test.input.value }}); + assert_eq!(clock.to_string(), {{ test.expected | json_encode() }}); +} +{% endfor %} + +// +// Test Equality +// + +{% for test in cases.3.cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + {% set c1 = test.input.clock1 %} + {% set c2 = test.input.clock2 %} + {% if test.expected -%} + assert_eq!(Clock::new({{ c1.hour }}, {{ c1.minute }}), Clock::new({{ c2.hour }}, {{ c2.minute }})); + {% else -%} + assert_ne!(Clock::new({{ c1.hour }}, {{ c1.minute }}), Clock::new({{ c2.hour }}, {{ c2.minute }})); + {% endif -%} +} +{% endfor %} diff --git a/exercises/practice/clock/.meta/tests.toml b/exercises/practice/clock/.meta/tests.toml index be690e975..712c87bca 100644 --- a/exercises/practice/clock/.meta/tests.toml +++ b/exercises/practice/clock/.meta/tests.toml @@ -1,3 +1,166 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[a577bacc-106b-496e-9792-b3083ea8705e] +description = "Create a new clock with an initial time -> on the hour" + +[b5d0c360-3b88-489b-8e84-68a1c7a4fa23] +description = "Create a new clock with an initial time -> past the hour" + +[473223f4-65f3-46ff-a9f7-7663c7e59440] +description = "Create a new clock with an initial time -> midnight is zero hours" + +[ca95d24a-5924-447d-9a96-b91c8334725c] +description = "Create a new clock with an initial time -> hour rolls over" + +[f3826de0-0925-4d69-8ac8-89aea7e52b78] +description = "Create a new clock with an initial time -> hour rolls over continuously" + +[a02f7edf-dfd4-4b11-b21a-86de3cc6a95c] +description = "Create a new clock with an initial time -> sixty minutes is next hour" + +[8f520df6-b816-444d-b90f-8a477789beb5] +description = "Create a new clock with an initial time -> minutes roll over" + +[c75c091b-47ac-4655-8d40-643767fc4eed] +description = "Create a new clock with an initial time -> minutes roll over continuously" + +[06343ecb-cf39-419d-a3f5-dcbae0cc4c57] +description = "Create a new clock with an initial time -> hour and minutes roll over" + +[be60810e-f5d9-4b58-9351-a9d1e90e660c] +description = "Create a new clock with an initial time -> hour and minutes roll over continuously" + +[1689107b-0b5c-4bea-aad3-65ec9859368a] +description = "Create a new clock with an initial time -> hour and minutes roll over to exactly midnight" + +[d3088ee8-91b7-4446-9e9d-5e2ad6219d91] +description = "Create a new clock with an initial time -> negative hour" + +[77ef6921-f120-4d29-bade-80d54aa43b54] +description = "Create a new clock with an initial time -> negative hour rolls over" + +[359294b5-972f-4546-bb9a-a85559065234] +description = "Create a new clock with an initial time -> negative hour rolls over continuously" + +[509db8b7-ac19-47cc-bd3a-a9d2f30b03c0] +description = "Create a new clock with an initial time -> negative minutes" + +[5d6bb225-130f-4084-84fd-9e0df8996f2a] +description = "Create a new clock with an initial time -> negative minutes roll over" + +[d483ceef-b520-4f0c-b94a-8d2d58cf0484] +description = "Create a new clock with an initial time -> negative minutes roll over continuously" + +[1cd19447-19c6-44bf-9d04-9f8305ccb9ea] +description = "Create a new clock with an initial time -> negative sixty minutes is previous hour" + +[9d3053aa-4f47-4afc-bd45-d67a72cef4dc] +description = "Create a new clock with an initial time -> negative hour and minutes both roll over" + +[51d41fcf-491e-4ca0-9cae-2aa4f0163ad4] +description = "Create a new clock with an initial time -> negative hour and minutes both roll over continuously" + +[d098e723-ad29-4ef9-997a-2693c4c9d89a] +description = "Add minutes -> add minutes" + +[b6ec8f38-e53e-4b22-92a7-60dab1f485f4] +description = "Add minutes -> add no minutes" + +[efd349dd-0785-453e-9ff8-d7452a8e7269] +description = "Add minutes -> add to next hour" + +[749890f7-aba9-4702-acce-87becf4ef9fe] +description = "Add minutes -> add more than one hour" + +[da63e4c1-1584-46e3-8d18-c9dc802c1713] +description = "Add minutes -> add more than two hours with carry" + +[be167a32-3d33-4cec-a8bc-accd47ddbb71] +description = "Add minutes -> add across midnight" + +[6672541e-cdae-46e4-8be7-a820cc3be2a8] +description = "Add minutes -> add more than one day (1500 min = 25 hrs)" + +[1918050d-c79b-4cb7-b707-b607e2745c7e] +description = "Add minutes -> add more than two days" + +[37336cac-5ede-43a5-9026-d426cbe40354] +description = "Subtract minutes -> subtract minutes" + +[0aafa4d0-3b5f-4b12-b3af-e3a9e09c047b] +description = "Subtract minutes -> subtract to previous hour" + +[9b4e809c-612f-4b15-aae0-1df0acb801b9] +description = "Subtract minutes -> subtract more than an hour" + +[8b04bb6a-3d33-4e6c-8de9-f5de6d2c70d6] +description = "Subtract minutes -> subtract across midnight" + +[07c3bbf7-ce4d-4658-86e8-4a77b7a5ccd9] +description = "Subtract minutes -> subtract more than two hours" + +[90ac8a1b-761c-4342-9c9c-cdc3ed5db097] +description = "Subtract minutes -> subtract more than two hours with borrow" + +[2149f985-7136-44ad-9b29-ec023a97a2b7] +description = "Subtract minutes -> subtract more than one day (1500 min = 25 hrs)" + +[ba11dbf0-ac27-4acb-ada9-3b853ec08c97] +description = "Subtract minutes -> subtract more than two days" + +[f2fdad51-499f-4c9b-a791-b28c9282e311] +description = "Compare two clocks for equality -> clocks with same time" + +[5d409d4b-f862-4960-901e-ec430160b768] +description = "Compare two clocks for equality -> clocks a minute apart" + +[a6045fcf-2b52-4a47-8bb2-ef10a064cba5] +description = "Compare two clocks for equality -> clocks an hour apart" + +[66b12758-0be5-448b-a13c-6a44bce83527] +description = "Compare two clocks for equality -> clocks with hour overflow" + +[2b19960c-212e-4a71-9aac-c581592f8111] +description = "Compare two clocks for equality -> clocks with hour overflow by several days" + +[6f8c6541-afac-4a92-b0c2-b10d4e50269f] +description = "Compare two clocks for equality -> clocks with negative hour" + +[bb9d5a68-e324-4bf5-a75e-0e9b1f97a90d] +description = "Compare two clocks for equality -> clocks with negative hour that wraps" + +[56c0326d-565b-4d19-a26f-63b3205778b7] +description = "Compare two clocks for equality -> clocks with negative hour that wraps multiple times" + +[c90b9de8-ddff-4ffe-9858-da44a40fdbc2] +description = "Compare two clocks for equality -> clocks with minute overflow" + +[533a3dc5-59a7-491b-b728-a7a34fe325de] +description = "Compare two clocks for equality -> clocks with minute overflow by several days" + +[fff49e15-f7b7-4692-a204-0f6052d62636] +description = "Compare two clocks for equality -> clocks with negative minute" + +[605c65bb-21bd-43eb-8f04-878edf508366] +description = "Compare two clocks for equality -> clocks with negative minute that wraps" + +[b87e64ed-212a-4335-91fd-56da8421d077] +description = "Compare two clocks for equality -> clocks with negative minute that wraps multiple times" + +[822fbf26-1f3b-4b13-b9bf-c914816b53dd] +description = "Compare two clocks for equality -> clocks with negative hours and minutes" + +[e787bccd-cf58-4a1d-841c-ff80eaaccfaa] +description = "Compare two clocks for equality -> clocks with negative hours and minutes that wrap" + +[96969ca8-875a-48a1-86ae-257a528c44f5] +description = "Compare two clocks for equality -> full clock and zeroed clock" diff --git a/exercises/practice/clock/tests/clock.rs b/exercises/practice/clock/tests/clock.rs index 416a6f985..fbc4946dc 100644 --- a/exercises/practice/clock/tests/clock.rs +++ b/exercises/practice/clock/tests/clock.rs @@ -1,4 +1,4 @@ -use clock::Clock; +use clock::*; // // Clock Creation @@ -53,19 +53,19 @@ fn minutes_roll_over_continuously() { #[test] #[ignore] -fn hours_and_minutes_roll_over() { +fn hour_and_minutes_roll_over() { assert_eq!(Clock::new(25, 160).to_string(), "03:40"); } #[test] #[ignore] -fn hours_and_minutes_roll_over_continuously() { +fn hour_and_minutes_roll_over_continuously() { assert_eq!(Clock::new(201, 3001).to_string(), "11:01"); } #[test] #[ignore] -fn hours_and_minutes_roll_over_to_exactly_midnight() { +fn hour_and_minutes_roll_over_to_exactly_midnight() { assert_eq!(Clock::new(72, 8640).to_string(), "00:00"); } @@ -77,14 +77,14 @@ fn negative_hour() { #[test] #[ignore] -fn negative_hour_roll_over() { - assert_eq!(Clock::new(-25, 00).to_string(), "23:00"); +fn negative_hour_rolls_over() { + assert_eq!(Clock::new(-25, 0).to_string(), "23:00"); } #[test] #[ignore] -fn negative_hour_roll_over_continuously() { - assert_eq!(Clock::new(-91, 00).to_string(), "05:00"); +fn negative_hour_rolls_over_continuously() { + assert_eq!(Clock::new(-91, 0).to_string(), "05:00"); } #[test] @@ -107,16 +107,10 @@ fn negative_minutes_roll_over_continuously() { #[test] #[ignore] -fn negative_sixty_minutes_is_prev_hour() { +fn negative_sixty_minutes_is_previous_hour() { assert_eq!(Clock::new(2, -60).to_string(), "01:00"); } -#[test] -#[ignore] -fn negative_one_twenty_minutes_is_two_prev_hours() { - assert_eq!(Clock::new(1, -120).to_string(), "23:00"); -} - #[test] #[ignore] fn negative_hour_and_minutes_both_roll_over() { @@ -129,12 +123,6 @@ fn negative_hour_and_minutes_both_roll_over_continuously() { assert_eq!(Clock::new(-121, -5810).to_string(), "22:10"); } -#[test] -#[ignore] -fn zero_hour_and_negative_minutes() { - assert_eq!(Clock::new(0, -22).to_string(), "23:38"); -} - // // Clock Math // @@ -183,7 +171,7 @@ fn add_across_midnight() { #[test] #[ignore] -fn add_more_than_one_day() { +fn add_more_than_one_day_1500_min_25_hrs() { let clock = Clock::new(5, 32).add_minutes(1500); assert_eq!(clock.to_string(), "06:32"); } @@ -239,7 +227,7 @@ fn subtract_more_than_two_hours_with_borrow() { #[test] #[ignore] -fn subtract_more_than_one_day() { +fn subtract_more_than_one_day_1500_min_25_hrs() { let clock = Clock::new(5, 32).add_minutes(-1500); assert_eq!(clock.to_string(), "04:32"); } @@ -257,96 +245,96 @@ fn subtract_more_than_two_days() { #[test] #[ignore] -fn compare_clocks_for_equality() { +fn clocks_with_same_time() { assert_eq!(Clock::new(15, 37), Clock::new(15, 37)); } #[test] #[ignore] -fn compare_clocks_a_minute_apart() { +fn clocks_a_minute_apart() { assert_ne!(Clock::new(15, 36), Clock::new(15, 37)); } #[test] #[ignore] -fn compare_clocks_an_hour_apart() { +fn clocks_an_hour_apart() { assert_ne!(Clock::new(14, 37), Clock::new(15, 37)); } #[test] #[ignore] -fn compare_clocks_with_hour_overflow() { +fn clocks_with_hour_overflow() { assert_eq!(Clock::new(10, 37), Clock::new(34, 37)); } #[test] #[ignore] -fn compare_clocks_with_hour_overflow_by_several_days() { - assert_eq!(Clock::new(99, 11), Clock::new(3, 11)); +fn clocks_with_hour_overflow_by_several_days() { + assert_eq!(Clock::new(3, 11), Clock::new(99, 11)); } #[test] #[ignore] -fn compare_clocks_with_negative_hour() { - assert_eq!(Clock::new(-2, 40), Clock::new(22, 40)); +fn clocks_with_negative_hour() { + assert_eq!(Clock::new(22, 40), Clock::new(-2, 40)); } #[test] #[ignore] -fn compare_clocks_with_negative_hour_that_wraps() { - assert_eq!(Clock::new(-31, 3), Clock::new(17, 3)); +fn clocks_with_negative_hour_that_wraps() { + assert_eq!(Clock::new(17, 3), Clock::new(-31, 3)); } #[test] #[ignore] -fn compare_clocks_with_negative_hour_that_wraps_multiple_times() { - assert_eq!(Clock::new(-83, 49), Clock::new(13, 49)); +fn clocks_with_negative_hour_that_wraps_multiple_times() { + assert_eq!(Clock::new(13, 49), Clock::new(-83, 49)); } #[test] #[ignore] -fn compare_clocks_with_minutes_overflow() { - assert_eq!(Clock::new(0, 1441), Clock::new(0, 1)); +fn clocks_with_minute_overflow() { + assert_eq!(Clock::new(0, 1), Clock::new(0, 1441)); } #[test] #[ignore] -fn compare_clocks_with_minutes_overflow_by_several_days() { - assert_eq!(Clock::new(2, 4322), Clock::new(2, 2)); +fn clocks_with_minute_overflow_by_several_days() { + assert_eq!(Clock::new(2, 2), Clock::new(2, 4322)); } #[test] #[ignore] -fn compare_clocks_with_negative_minute() { - assert_eq!(Clock::new(3, -20), Clock::new(2, 40)); +fn clocks_with_negative_minute() { + assert_eq!(Clock::new(2, 40), Clock::new(3, -20)); } #[test] #[ignore] -fn compare_clocks_with_negative_minute_that_wraps() { - assert_eq!(Clock::new(5, -1490), Clock::new(4, 10)); +fn clocks_with_negative_minute_that_wraps() { + assert_eq!(Clock::new(4, 10), Clock::new(5, -1490)); } #[test] #[ignore] -fn compare_clocks_with_negative_minute_that_wraps_multiple() { - assert_eq!(Clock::new(6, -4305), Clock::new(6, 15)); +fn clocks_with_negative_minute_that_wraps_multiple_times() { + assert_eq!(Clock::new(6, 15), Clock::new(6, -4305)); } #[test] #[ignore] -fn compare_clocks_with_negative_hours_and_minutes() { - assert_eq!(Clock::new(-12, -268), Clock::new(7, 32)); +fn clocks_with_negative_hours_and_minutes() { + assert_eq!(Clock::new(7, 32), Clock::new(-12, -268)); } #[test] #[ignore] -fn compare_clocks_with_negative_hours_and_minutes_that_wrap() { - assert_eq!(Clock::new(-54, -11_513), Clock::new(18, 7)); +fn clocks_with_negative_hours_and_minutes_that_wrap() { + assert_eq!(Clock::new(18, 7), Clock::new(-54, -11513)); } #[test] #[ignore] -fn compare_full_clock_and_zeroed_clock() { +fn full_clock_and_zeroed_clock() { assert_eq!(Clock::new(24, 0), Clock::new(0, 0)); } From ed9efecc033b07c69e90baf8b92f6f28c3ead6cd Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Sun, 25 Aug 2024 19:23:41 +0200 Subject: [PATCH 350/436] dot-dsl: Remove dependency in test code (#1989) [no important files changed] --- exercises/practice/dot-dsl/Cargo.toml | 4 ++-- exercises/practice/dot-dsl/tests/dot-dsl.rs | 17 ++++++++--------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/exercises/practice/dot-dsl/Cargo.toml b/exercises/practice/dot-dsl/Cargo.toml index 81acbd4b2..9361866ea 100644 --- a/exercises/practice/dot-dsl/Cargo.toml +++ b/exercises/practice/dot-dsl/Cargo.toml @@ -3,5 +3,5 @@ edition = "2021" name = "dot-dsl" version = "0.1.0" -[dependencies] -maplit = "1.0.1" +[lints.clippy] +new_without_default = "allow" diff --git a/exercises/practice/dot-dsl/tests/dot-dsl.rs b/exercises/practice/dot-dsl/tests/dot-dsl.rs index 9bf673a87..54bd34958 100644 --- a/exercises/practice/dot-dsl/tests/dot-dsl.rs +++ b/exercises/practice/dot-dsl/tests/dot-dsl.rs @@ -1,7 +1,8 @@ +use std::collections::HashMap; + use dot_dsl::graph::graph_items::edge::Edge; use dot_dsl::graph::graph_items::node::Node; use dot_dsl::graph::Graph; -use maplit::hashmap; #[test] fn empty_graph() { @@ -81,9 +82,7 @@ fn graph_with_one_edge_with_keywords() { fn graph_with_one_attribute() { let graph = Graph::new().with_attrs(&[("foo", "1")]); - let expected_attrs = hashmap! { - "foo".to_string() => "1".to_string(), - }; + let expected_attrs = HashMap::from([("foo".to_string(), "1".to_string())]); assert!(graph.nodes.is_empty()); @@ -108,11 +107,11 @@ fn graph_with_attributes() { let attrs = vec![("foo", "1"), ("title", "Testing Attrs"), ("bar", "true")]; - let expected_attrs = hashmap! { - "foo".to_string() => "1".to_string(), - "title".to_string() => "Testing Attrs".to_string(), - "bar".to_string() => "true".to_string(), - }; + let expected_attrs = HashMap::from([ + ("foo".to_string(), "1".to_string()), + ("title".to_string(), "Testing Attrs".to_string()), + ("bar".to_string(), "true".to_string()), + ]); let graph = Graph::new() .with_nodes(&nodes) From 4d3483ca7f8a0656ef8a804f2161c6390cf2ea69 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Sun, 25 Aug 2024 19:24:47 +0200 Subject: [PATCH 351/436] Remove lazy_static (#1990) This crate is not needed anymore since all its functionality has been merged into the standard library over time. --- .../practice/pig-latin/.meta/Cargo-example.toml | 1 - exercises/practice/pig-latin/.meta/example.rs | 17 ++++++++--------- .../robot-name/.meta/Cargo-example.toml | 1 - exercises/practice/robot-name/.meta/example.rs | 12 ++++++------ 4 files changed, 14 insertions(+), 17 deletions(-) diff --git a/exercises/practice/pig-latin/.meta/Cargo-example.toml b/exercises/practice/pig-latin/.meta/Cargo-example.toml index 0d0b60aa4..42bcba31f 100644 --- a/exercises/practice/pig-latin/.meta/Cargo-example.toml +++ b/exercises/practice/pig-latin/.meta/Cargo-example.toml @@ -5,4 +5,3 @@ version = "1.0.0" [dependencies] regex = "0.2" -lazy_static = "1.4.0" diff --git a/exercises/practice/pig-latin/.meta/example.rs b/exercises/practice/pig-latin/.meta/example.rs index d3a26d20b..f6daf2cd0 100644 --- a/exercises/practice/pig-latin/.meta/example.rs +++ b/exercises/practice/pig-latin/.meta/example.rs @@ -1,6 +1,4 @@ -#[macro_use] -extern crate lazy_static; -extern crate regex; +use std::sync::LazyLock; use regex::Regex; @@ -9,12 +7,13 @@ use regex::Regex; pub fn translate_word(word: &str) -> String { // Prevent creation and compilation at every call. // These are compiled exactly once - lazy_static! { - // Detects if it starts with a vowel - static ref VOWEL: Regex = Regex::new(r"^([aeiou]|y[^aeiou]|xr)[a-z]*").unwrap(); - // Detects splits for initial consonants - static ref CONSONANTS: Regex = Regex::new(r"^([^aeiou]?qu|[^aeiou][^aeiouy]*)([a-z]*)").unwrap(); - } + + // Detects if it starts with a vowel + static VOWEL: LazyLock = + LazyLock::new(|| Regex::new(r"^([aeiou]|y[^aeiou]|xr)[a-z]*").unwrap()); + // Detects splits for initial consonants + static CONSONANTS: LazyLock = + LazyLock::new(|| Regex::new(r"^([^aeiou]?qu|[^aeiou][^aeiouy]*)([a-z]*)").unwrap()); if VOWEL.is_match(word) { String::from(word) + "ay" diff --git a/exercises/practice/robot-name/.meta/Cargo-example.toml b/exercises/practice/robot-name/.meta/Cargo-example.toml index be60f1e17..5713b6191 100644 --- a/exercises/practice/robot-name/.meta/Cargo-example.toml +++ b/exercises/practice/robot-name/.meta/Cargo-example.toml @@ -4,5 +4,4 @@ name = "robot-name" version = "0.0.0" [dependencies] -lazy_static = "1.4.0" rand = "0.3.12" diff --git a/exercises/practice/robot-name/.meta/example.rs b/exercises/practice/robot-name/.meta/example.rs index 07fbba8bf..8e79c0b78 100644 --- a/exercises/practice/robot-name/.meta/example.rs +++ b/exercises/practice/robot-name/.meta/example.rs @@ -1,11 +1,11 @@ -use lazy_static::lazy_static; +use std::{ + collections::HashSet, + sync::{LazyLock, Mutex}, +}; + use rand::{thread_rng, Rng}; -use std::collections::HashSet; -use std::sync::Mutex; -lazy_static! { - static ref NAMES: Mutex> = Mutex::new(HashSet::new()); -} +static NAMES: LazyLock>> = LazyLock::new(|| Mutex::new(HashSet::new())); pub struct Robot { name: String, From 8bb2f3655cd358c0bf79a5f660e40636801d5079 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Sun, 25 Aug 2024 20:27:53 +0200 Subject: [PATCH 352/436] Remove empty dependency tables (#1988) This is a preparation to make it easier to detect if an exercise doesn't use any dependencies. The Rust analyzer currently fails on such exercises, so we plan to disable it in those cases. Hopefully a better solution can be found later. --- exercises/concept/semi-structured-logs/Cargo.toml | 2 -- exercises/practice/accumulate/Cargo.toml | 2 -- exercises/practice/acronym/Cargo.toml | 2 -- exercises/practice/binary-search/Cargo.toml | 2 -- exercises/practice/book-store/.meta/Cargo-example.toml | 2 -- exercises/practice/book-store/Cargo.toml | 2 -- exercises/practice/clock/Cargo.toml | 2 -- exercises/practice/crypto-square/Cargo.toml | 2 -- exercises/practice/decimal/Cargo.toml | 2 -- exercises/practice/diamond/Cargo.toml | 2 -- exercises/practice/diffie-hellman/Cargo.toml | 2 -- exercises/practice/eliuds-eggs/Cargo.toml | 2 -- exercises/practice/high-scores/Cargo.toml | 2 -- exercises/practice/kindergarten-garden/Cargo.toml | 4 ---- exercises/practice/knapsack/Cargo.toml | 4 ---- exercises/practice/macros/Cargo.toml | 2 -- exercises/practice/matrix/Cargo.toml | 2 -- exercises/practice/nth-prime/Cargo.toml | 2 -- exercises/practice/palindrome-products/Cargo.toml | 2 -- exercises/practice/perfect-numbers/Cargo.toml | 2 -- exercises/practice/pig-latin/Cargo.toml | 2 -- exercises/practice/poker/Cargo.toml | 2 -- exercises/practice/prime-factors/Cargo.toml | 2 -- exercises/practice/proverb/Cargo.toml | 2 -- exercises/practice/pythagorean-triplet/Cargo.toml | 2 -- exercises/practice/reverse-string/Cargo.toml | 8 +++----- exercises/practice/run-length-encoding/Cargo.toml | 2 -- exercises/practice/saddle-points/Cargo.toml | 2 -- exercises/practice/scale-generator/Cargo.toml | 2 -- exercises/practice/series/Cargo.toml | 2 -- exercises/practice/two-fer/Cargo.toml | 2 -- exercises/practice/xorcism/Cargo.toml | 3 --- exercises/practice/yacht/Cargo.toml | 4 ---- rust-tooling/generate/src/lib.rs | 2 -- rust-tooling/utils/Cargo.toml | 4 ---- 35 files changed, 3 insertions(+), 82 deletions(-) diff --git a/exercises/concept/semi-structured-logs/Cargo.toml b/exercises/concept/semi-structured-logs/Cargo.toml index 166e966a1..5e8e3d53e 100644 --- a/exercises/concept/semi-structured-logs/Cargo.toml +++ b/exercises/concept/semi-structured-logs/Cargo.toml @@ -5,5 +5,3 @@ edition = "2021" [features] add-a-variant = [] - -[dependencies] diff --git a/exercises/practice/accumulate/Cargo.toml b/exercises/practice/accumulate/Cargo.toml index e9add962f..795e2befd 100644 --- a/exercises/practice/accumulate/Cargo.toml +++ b/exercises/practice/accumulate/Cargo.toml @@ -2,5 +2,3 @@ edition = "2021" name = "accumulate" version = "0.0.0" - -[dependencies] diff --git a/exercises/practice/acronym/Cargo.toml b/exercises/practice/acronym/Cargo.toml index d1db9db39..cc0c031d6 100644 --- a/exercises/practice/acronym/Cargo.toml +++ b/exercises/practice/acronym/Cargo.toml @@ -2,5 +2,3 @@ edition = "2021" name = "acronym" version = "1.7.0" - -[dependencies] diff --git a/exercises/practice/binary-search/Cargo.toml b/exercises/practice/binary-search/Cargo.toml index ec75216c6..5e8ac2fd9 100644 --- a/exercises/practice/binary-search/Cargo.toml +++ b/exercises/practice/binary-search/Cargo.toml @@ -3,8 +3,6 @@ edition = "2021" name = "binary-search" version = "1.3.0" -[dependencies] - [features] generic = [] diff --git a/exercises/practice/book-store/.meta/Cargo-example.toml b/exercises/practice/book-store/.meta/Cargo-example.toml index 50711c01a..767b9c1e8 100644 --- a/exercises/practice/book-store/.meta/Cargo-example.toml +++ b/exercises/practice/book-store/.meta/Cargo-example.toml @@ -2,5 +2,3 @@ edition = "2021" name = "book_store" version = "1.3.0" - -[dependencies] diff --git a/exercises/practice/book-store/Cargo.toml b/exercises/practice/book-store/Cargo.toml index 50711c01a..767b9c1e8 100644 --- a/exercises/practice/book-store/Cargo.toml +++ b/exercises/practice/book-store/Cargo.toml @@ -2,5 +2,3 @@ edition = "2021" name = "book_store" version = "1.3.0" - -[dependencies] diff --git a/exercises/practice/clock/Cargo.toml b/exercises/practice/clock/Cargo.toml index ca943d67c..5c1ff8daf 100644 --- a/exercises/practice/clock/Cargo.toml +++ b/exercises/practice/clock/Cargo.toml @@ -2,5 +2,3 @@ edition = "2021" name = "clock" version = "2.4.0" - -[dependencies] diff --git a/exercises/practice/crypto-square/Cargo.toml b/exercises/practice/crypto-square/Cargo.toml index 0363ae0c8..e617068eb 100644 --- a/exercises/practice/crypto-square/Cargo.toml +++ b/exercises/practice/crypto-square/Cargo.toml @@ -2,5 +2,3 @@ edition = "2021" name = "crypto-square" version = "0.1.0" - -[dependencies] diff --git a/exercises/practice/decimal/Cargo.toml b/exercises/practice/decimal/Cargo.toml index 6ddb62742..6f1d36ac7 100644 --- a/exercises/practice/decimal/Cargo.toml +++ b/exercises/practice/decimal/Cargo.toml @@ -2,5 +2,3 @@ edition = "2021" name = "decimal" version = "0.1.0" - -[dependencies] diff --git a/exercises/practice/diamond/Cargo.toml b/exercises/practice/diamond/Cargo.toml index f5357766f..26c54dcc3 100644 --- a/exercises/practice/diamond/Cargo.toml +++ b/exercises/practice/diamond/Cargo.toml @@ -2,5 +2,3 @@ edition = "2021" name = "diamond" version = "1.1.0" - -[dependencies] diff --git a/exercises/practice/diffie-hellman/Cargo.toml b/exercises/practice/diffie-hellman/Cargo.toml index c76e7ab34..5b588a15e 100644 --- a/exercises/practice/diffie-hellman/Cargo.toml +++ b/exercises/practice/diffie-hellman/Cargo.toml @@ -3,7 +3,5 @@ edition = "2021" name = "diffie-hellman" version = "0.1.0" -[dependencies] - [features] big-primes = [] diff --git a/exercises/practice/eliuds-eggs/Cargo.toml b/exercises/practice/eliuds-eggs/Cargo.toml index 5f9d055dc..b76c171e1 100644 --- a/exercises/practice/eliuds-eggs/Cargo.toml +++ b/exercises/practice/eliuds-eggs/Cargo.toml @@ -2,5 +2,3 @@ edition = "2021" name = "eliuds_eggs" version = "1.0.0" - -[dependencies] diff --git a/exercises/practice/high-scores/Cargo.toml b/exercises/practice/high-scores/Cargo.toml index 6e7360a66..78c9a491c 100644 --- a/exercises/practice/high-scores/Cargo.toml +++ b/exercises/practice/high-scores/Cargo.toml @@ -1,5 +1,3 @@ -[dependencies] - [package] edition = "2021" name = "high-scores" diff --git a/exercises/practice/kindergarten-garden/Cargo.toml b/exercises/practice/kindergarten-garden/Cargo.toml index e29f1102b..1c8ddf929 100644 --- a/exercises/practice/kindergarten-garden/Cargo.toml +++ b/exercises/practice/kindergarten-garden/Cargo.toml @@ -2,7 +2,3 @@ name = "kindergarten_garden" version = "0.1.0" edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] diff --git a/exercises/practice/knapsack/Cargo.toml b/exercises/practice/knapsack/Cargo.toml index 7b1d5d915..30ab86843 100644 --- a/exercises/practice/knapsack/Cargo.toml +++ b/exercises/practice/knapsack/Cargo.toml @@ -2,7 +2,3 @@ name = "knapsack" version = "0.1.0" edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] diff --git a/exercises/practice/macros/Cargo.toml b/exercises/practice/macros/Cargo.toml index 8c82d2e60..4497ae5d8 100644 --- a/exercises/practice/macros/Cargo.toml +++ b/exercises/practice/macros/Cargo.toml @@ -2,5 +2,3 @@ edition = "2021" name = "macros" version = "0.1.0" - -[dependencies] diff --git a/exercises/practice/matrix/Cargo.toml b/exercises/practice/matrix/Cargo.toml index 6ecb93ac8..7797f0f17 100644 --- a/exercises/practice/matrix/Cargo.toml +++ b/exercises/practice/matrix/Cargo.toml @@ -2,5 +2,3 @@ edition = "2021" name = "matrix" version = "1.0.0" - -[dependencies] diff --git a/exercises/practice/nth-prime/Cargo.toml b/exercises/practice/nth-prime/Cargo.toml index 11a62d103..e40ac1e89 100644 --- a/exercises/practice/nth-prime/Cargo.toml +++ b/exercises/practice/nth-prime/Cargo.toml @@ -2,5 +2,3 @@ edition = "2021" name = "nth_prime" version = "2.1.0" - -[dependencies] diff --git a/exercises/practice/palindrome-products/Cargo.toml b/exercises/practice/palindrome-products/Cargo.toml index a082817c1..987f9015b 100644 --- a/exercises/practice/palindrome-products/Cargo.toml +++ b/exercises/practice/palindrome-products/Cargo.toml @@ -2,5 +2,3 @@ edition = "2021" name = "palindrome-products" version = "1.2.0" - -[dependencies] diff --git a/exercises/practice/perfect-numbers/Cargo.toml b/exercises/practice/perfect-numbers/Cargo.toml index d24b1e132..39a1ec152 100644 --- a/exercises/practice/perfect-numbers/Cargo.toml +++ b/exercises/practice/perfect-numbers/Cargo.toml @@ -2,5 +2,3 @@ edition = "2021" name = "perfect_numbers" version = "1.1.0" - -[dependencies] diff --git a/exercises/practice/pig-latin/Cargo.toml b/exercises/practice/pig-latin/Cargo.toml index 7ae4cf403..d7ab70e86 100644 --- a/exercises/practice/pig-latin/Cargo.toml +++ b/exercises/practice/pig-latin/Cargo.toml @@ -2,5 +2,3 @@ edition = "2021" name = "pig-latin" version = "1.0.0" - -[dependencies] diff --git a/exercises/practice/poker/Cargo.toml b/exercises/practice/poker/Cargo.toml index d0c6bdcb6..208d0e94c 100644 --- a/exercises/practice/poker/Cargo.toml +++ b/exercises/practice/poker/Cargo.toml @@ -2,5 +2,3 @@ edition = "2021" name = "poker" version = "1.1.0" - -[dependencies] diff --git a/exercises/practice/prime-factors/Cargo.toml b/exercises/practice/prime-factors/Cargo.toml index 18940fcd9..44211ce91 100644 --- a/exercises/practice/prime-factors/Cargo.toml +++ b/exercises/practice/prime-factors/Cargo.toml @@ -2,5 +2,3 @@ edition = "2021" name = "prime_factors" version = "1.1.0" - -[dependencies] diff --git a/exercises/practice/proverb/Cargo.toml b/exercises/practice/proverb/Cargo.toml index eb3977050..8f9444d35 100644 --- a/exercises/practice/proverb/Cargo.toml +++ b/exercises/practice/proverb/Cargo.toml @@ -2,5 +2,3 @@ edition = "2021" name = "proverb" version = "1.1.0" - -[dependencies] diff --git a/exercises/practice/pythagorean-triplet/Cargo.toml b/exercises/practice/pythagorean-triplet/Cargo.toml index 517618ef1..27e28c0c3 100644 --- a/exercises/practice/pythagorean-triplet/Cargo.toml +++ b/exercises/practice/pythagorean-triplet/Cargo.toml @@ -1,5 +1,3 @@ -[dependencies] - [package] edition = "2021" name = "pythagorean_triplet" diff --git a/exercises/practice/reverse-string/Cargo.toml b/exercises/practice/reverse-string/Cargo.toml index 52b902c82..403e50ab1 100644 --- a/exercises/practice/reverse-string/Cargo.toml +++ b/exercises/practice/reverse-string/Cargo.toml @@ -1,9 +1,7 @@ -[dependencies] - -[features] -grapheme = [] - [package] edition = "2021" name = "reverse_string" version = "1.2.0" + +[features] +grapheme = [] diff --git a/exercises/practice/run-length-encoding/Cargo.toml b/exercises/practice/run-length-encoding/Cargo.toml index 6af6432b9..ab5768d1f 100644 --- a/exercises/practice/run-length-encoding/Cargo.toml +++ b/exercises/practice/run-length-encoding/Cargo.toml @@ -2,5 +2,3 @@ edition = "2021" name = "run-length-encoding" version = "1.1.0" - -[dependencies] diff --git a/exercises/practice/saddle-points/Cargo.toml b/exercises/practice/saddle-points/Cargo.toml index 9507ab2ad..968176620 100644 --- a/exercises/practice/saddle-points/Cargo.toml +++ b/exercises/practice/saddle-points/Cargo.toml @@ -2,5 +2,3 @@ edition = "2021" name = "saddle-points" version = "1.3.0" - -[dependencies] diff --git a/exercises/practice/scale-generator/Cargo.toml b/exercises/practice/scale-generator/Cargo.toml index 65ed9ae27..04a14757b 100644 --- a/exercises/practice/scale-generator/Cargo.toml +++ b/exercises/practice/scale-generator/Cargo.toml @@ -2,5 +2,3 @@ edition = "2021" name = "scale_generator" version = "2.0.0" - -[dependencies] diff --git a/exercises/practice/series/Cargo.toml b/exercises/practice/series/Cargo.toml index 4588185aa..6366609a3 100644 --- a/exercises/practice/series/Cargo.toml +++ b/exercises/practice/series/Cargo.toml @@ -2,5 +2,3 @@ edition = "2021" name = "series" version = "0.1.0" - -[dependencies] diff --git a/exercises/practice/two-fer/Cargo.toml b/exercises/practice/two-fer/Cargo.toml index c8075f3e9..e3345023e 100644 --- a/exercises/practice/two-fer/Cargo.toml +++ b/exercises/practice/two-fer/Cargo.toml @@ -2,5 +2,3 @@ edition = "2021" name = "twofer" version = "1.2.0" - -[dependencies] diff --git a/exercises/practice/xorcism/Cargo.toml b/exercises/practice/xorcism/Cargo.toml index f72738b0d..ee70fa468 100644 --- a/exercises/practice/xorcism/Cargo.toml +++ b/exercises/practice/xorcism/Cargo.toml @@ -3,8 +3,5 @@ name = "xorcism" version = "0.1.0" edition = "2021" - -[dependencies] - [features] io = [] diff --git a/exercises/practice/yacht/Cargo.toml b/exercises/practice/yacht/Cargo.toml index 138c413e0..2c3813008 100644 --- a/exercises/practice/yacht/Cargo.toml +++ b/exercises/practice/yacht/Cargo.toml @@ -2,7 +2,3 @@ name = "yacht" version = "0.1.0" edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] diff --git a/rust-tooling/generate/src/lib.rs b/rust-tooling/generate/src/lib.rs index 0c7d31aae..e397a586d 100644 --- a/rust-tooling/generate/src/lib.rs +++ b/rust-tooling/generate/src/lib.rs @@ -48,8 +48,6 @@ fn generate_manifest(crate_name: &str) -> String { "edition = \"2021\"\n", "name = \"{crate_name}\"\n", "version = \"1.0.0\"\n", - "\n", - "[dependencies]\n", ), crate_name = crate_name ) diff --git a/rust-tooling/utils/Cargo.toml b/rust-tooling/utils/Cargo.toml index a0cba9a27..bd645df12 100644 --- a/rust-tooling/utils/Cargo.toml +++ b/rust-tooling/utils/Cargo.toml @@ -2,7 +2,3 @@ name = "utils" version = "0.1.0" edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] From ab4e1c92383cbc28f23cadff873826d3c47ffb92 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Sun, 25 Aug 2024 20:28:09 +0200 Subject: [PATCH 353/436] simple-linked-list: clarify vector order (#1987) --- exercises/practice/simple-linked-list/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/exercises/practice/simple-linked-list/src/lib.rs b/exercises/practice/simple-linked-list/src/lib.rs index 42121a0ca..75a931155 100644 --- a/exercises/practice/simple-linked-list/src/lib.rs +++ b/exercises/practice/simple-linked-list/src/lib.rs @@ -49,13 +49,13 @@ impl FromIterator for SimpleLinkedList { // In general, it would be preferable to implement IntoIterator for SimpleLinkedList // instead of implementing an explicit conversion to a vector. This is because, together, // FromIterator and IntoIterator enable conversion between arbitrary collections. -// Given that implementation, converting to a vector is trivial: -// -// let vec: Vec<_> = simple_linked_list.into_iter().collect(); // // The reason this exercise's API includes an explicit conversion to Vec instead // of IntoIterator is that implementing that interface is fairly complicated, and // demands more of the student than we expect at this point in the track. +// +// Please note that the "front" of the linked list should correspond to the "back" +// of the vector as far as the tests are concerned. impl From> for Vec { fn from(mut _linked_list: SimpleLinkedList) -> Vec { From f187da437512dbb82943e3e2dc929a1e14d3be52 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Sat, 7 Sep 2024 18:16:17 +0200 Subject: [PATCH 354/436] ci(dependabot): change to monthly interval (#1991) This PR changes the interval for dependabot updates to monthly. Right now, the interval is usually set to daily, which results in (almost) daily notifications for Exercism maintainers, and frequent Docker updates to all tooling repos. To reduce the maintenance burden, we are lowering the frequency to monthly. Due to Exercism's setup, it is unlikely that dependabot issues in our repositories need to be handled with any urgency, so we consider this to be a wise tradeoff. For maintained track repos, maintainers can choose to close this PR unmerged. But for tooling repos and unmaintained tracks, we are requiring it to be merged. Thanks! --- .github/dependabot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ed8f4a432..234b07e76 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,4 +6,4 @@ updates: - package-ecosystem: 'github-actions' directory: '/' schedule: - interval: 'daily' + interval: 'monthly' From 86a5638862d52a53072773df14e8120a3554d5f6 Mon Sep 17 00:00:00 2001 From: Exercism Bot Date: Sat, 7 Sep 2024 18:23:08 +0200 Subject: [PATCH 355/436] =?UTF-8?q?=F0=9F=A4=96=20Sync=20org-wide=20files?= =?UTF-8?q?=20to=20upstream=20repo=20(#1992)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ℹ More info: https://github.com/exercism/org-wide-files/commit/fc1613760f6670850e29a593bbb5c9669edc23bd 👁 Tracking issue: https://github.com/exercism/org-wide-files/issues/394 --- .../ping-cross-track-maintainers-team.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .github/workflows/ping-cross-track-maintainers-team.yml diff --git a/.github/workflows/ping-cross-track-maintainers-team.yml b/.github/workflows/ping-cross-track-maintainers-team.yml new file mode 100644 index 000000000..b6ec9c566 --- /dev/null +++ b/.github/workflows/ping-cross-track-maintainers-team.yml @@ -0,0 +1,16 @@ +name: Ping cross-track maintainers team + +on: + pull_request_target: + types: + - opened + +permissions: + pull-requests: write + +jobs: + ping: + if: github.repository_owner == 'exercism' # Stops this job from running on forks + uses: exercism/github-actions/.github/workflows/ping-cross-track-maintainers-team.yml@main + secrets: + github_membership_token: ${{ secrets.COMMUNITY_CONTRIBUTIONS_WORKFLOW_TOKEN }} From 97db9c0bc9d0bbe0fd4890476cd24e11e222ac3a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Nov 2024 20:17:58 +0100 Subject: [PATCH 356/436] build(deps): bump actions/checkout from 4.1.7 to 4.2.2 (#1995) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/tests.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f76540358..e90440527 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,7 +18,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Fetch configlet run: ./bin/fetch-configlet @@ -32,7 +32,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Run markdown lint run: ./bin/lint_markdown.sh @@ -43,7 +43,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Run shellcheck uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # v2.0.0 @@ -58,7 +58,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Fetch origin/main run: git fetch --depth=1 origin main @@ -84,7 +84,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup toolchain uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 @@ -100,7 +100,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup toolchain uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 @@ -123,7 +123,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup toolchain uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 @@ -148,7 +148,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup nightly toolchain uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 From a7b2a752a1b133b762155b25b7dbbe9e41ece766 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Dec 2024 19:40:03 +0100 Subject: [PATCH 357/436] build(deps): bump anstream from 0.6.5 to 0.6.15 in /rust-tooling (#1996) --- rust-tooling/Cargo.lock | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/rust-tooling/Cargo.lock b/rust-tooling/Cargo.lock index 1d2c0cc53..2e98226bc 100644 --- a/rust-tooling/Cargo.lock +++ b/rust-tooling/Cargo.lock @@ -28,15 +28,16 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.5" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] @@ -513,6 +514,12 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itoa" version = "1.0.10" From a95f205a92015f333ea37f871a95825673bb49fc Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Wed, 4 Dec 2024 23:00:22 +0100 Subject: [PATCH 358/436] allergies: remove unconventional whitespace (#1997) Clippy started warning against this. [no important files changed] --- .../allergies/.meta/test_template.tera | 2 +- .../practice/allergies/tests/allergies.rs | 40 ------------------- 2 files changed, 1 insertion(+), 41 deletions(-) diff --git a/exercises/practice/allergies/.meta/test_template.tera b/exercises/practice/allergies/.meta/test_template.tera index 3f222d638..05e5615cd 100644 --- a/exercises/practice/allergies/.meta/test_template.tera +++ b/exercises/practice/allergies/.meta/test_template.tera @@ -5,7 +5,7 @@ use allergies::*; #[test] #[ignore] {%- if test.property == "allergicTo" %} -{# canonical data contains multiple cases named "allergic to everything" for different items #} +{#- canonical data contains multiple cases named "allergic to everything" for different items #} fn {{ test.description | make_ident }}_{{ test.input.item }}() { let allergies = Allergies::new({{ test.input.score }}); {%- if test.expected %} diff --git a/exercises/practice/allergies/tests/allergies.rs b/exercises/practice/allergies/tests/allergies.rs index 62795f5ed..35e31bba4 100644 --- a/exercises/practice/allergies/tests/allergies.rs +++ b/exercises/practice/allergies/tests/allergies.rs @@ -1,7 +1,6 @@ use allergies::*; #[test] - fn not_allergic_to_anything_eggs() { let allergies = Allergies::new(0); assert!(!allergies.is_allergic_to(&Allergen::Eggs)) @@ -9,7 +8,6 @@ fn not_allergic_to_anything_eggs() { #[test] #[ignore] - fn allergic_only_to_eggs_eggs() { let allergies = Allergies::new(1); assert!(allergies.is_allergic_to(&Allergen::Eggs)) @@ -17,7 +15,6 @@ fn allergic_only_to_eggs_eggs() { #[test] #[ignore] - fn allergic_to_eggs_and_something_else_eggs() { let allergies = Allergies::new(3); assert!(allergies.is_allergic_to(&Allergen::Eggs)) @@ -25,7 +22,6 @@ fn allergic_to_eggs_and_something_else_eggs() { #[test] #[ignore] - fn allergic_to_something_but_not_eggs_eggs() { let allergies = Allergies::new(2); assert!(!allergies.is_allergic_to(&Allergen::Eggs)) @@ -33,7 +29,6 @@ fn allergic_to_something_but_not_eggs_eggs() { #[test] #[ignore] - fn allergic_to_everything_eggs() { let allergies = Allergies::new(255); assert!(allergies.is_allergic_to(&Allergen::Eggs)) @@ -41,7 +36,6 @@ fn allergic_to_everything_eggs() { #[test] #[ignore] - fn not_allergic_to_anything_peanuts() { let allergies = Allergies::new(0); assert!(!allergies.is_allergic_to(&Allergen::Peanuts)) @@ -49,7 +43,6 @@ fn not_allergic_to_anything_peanuts() { #[test] #[ignore] - fn allergic_only_to_peanuts_peanuts() { let allergies = Allergies::new(2); assert!(allergies.is_allergic_to(&Allergen::Peanuts)) @@ -57,7 +50,6 @@ fn allergic_only_to_peanuts_peanuts() { #[test] #[ignore] - fn allergic_to_peanuts_and_something_else_peanuts() { let allergies = Allergies::new(7); assert!(allergies.is_allergic_to(&Allergen::Peanuts)) @@ -65,7 +57,6 @@ fn allergic_to_peanuts_and_something_else_peanuts() { #[test] #[ignore] - fn allergic_to_something_but_not_peanuts_peanuts() { let allergies = Allergies::new(5); assert!(!allergies.is_allergic_to(&Allergen::Peanuts)) @@ -73,7 +64,6 @@ fn allergic_to_something_but_not_peanuts_peanuts() { #[test] #[ignore] - fn allergic_to_everything_peanuts() { let allergies = Allergies::new(255); assert!(allergies.is_allergic_to(&Allergen::Peanuts)) @@ -81,7 +71,6 @@ fn allergic_to_everything_peanuts() { #[test] #[ignore] - fn not_allergic_to_anything_shellfish() { let allergies = Allergies::new(0); assert!(!allergies.is_allergic_to(&Allergen::Shellfish)) @@ -89,7 +78,6 @@ fn not_allergic_to_anything_shellfish() { #[test] #[ignore] - fn allergic_only_to_shellfish_shellfish() { let allergies = Allergies::new(4); assert!(allergies.is_allergic_to(&Allergen::Shellfish)) @@ -97,7 +85,6 @@ fn allergic_only_to_shellfish_shellfish() { #[test] #[ignore] - fn allergic_to_shellfish_and_something_else_shellfish() { let allergies = Allergies::new(14); assert!(allergies.is_allergic_to(&Allergen::Shellfish)) @@ -105,7 +92,6 @@ fn allergic_to_shellfish_and_something_else_shellfish() { #[test] #[ignore] - fn allergic_to_something_but_not_shellfish_shellfish() { let allergies = Allergies::new(10); assert!(!allergies.is_allergic_to(&Allergen::Shellfish)) @@ -113,7 +99,6 @@ fn allergic_to_something_but_not_shellfish_shellfish() { #[test] #[ignore] - fn allergic_to_everything_shellfish() { let allergies = Allergies::new(255); assert!(allergies.is_allergic_to(&Allergen::Shellfish)) @@ -121,7 +106,6 @@ fn allergic_to_everything_shellfish() { #[test] #[ignore] - fn not_allergic_to_anything_strawberries() { let allergies = Allergies::new(0); assert!(!allergies.is_allergic_to(&Allergen::Strawberries)) @@ -129,7 +113,6 @@ fn not_allergic_to_anything_strawberries() { #[test] #[ignore] - fn allergic_only_to_strawberries_strawberries() { let allergies = Allergies::new(8); assert!(allergies.is_allergic_to(&Allergen::Strawberries)) @@ -137,7 +120,6 @@ fn allergic_only_to_strawberries_strawberries() { #[test] #[ignore] - fn allergic_to_strawberries_and_something_else_strawberries() { let allergies = Allergies::new(28); assert!(allergies.is_allergic_to(&Allergen::Strawberries)) @@ -145,7 +127,6 @@ fn allergic_to_strawberries_and_something_else_strawberries() { #[test] #[ignore] - fn allergic_to_something_but_not_strawberries_strawberries() { let allergies = Allergies::new(20); assert!(!allergies.is_allergic_to(&Allergen::Strawberries)) @@ -153,7 +134,6 @@ fn allergic_to_something_but_not_strawberries_strawberries() { #[test] #[ignore] - fn allergic_to_everything_strawberries() { let allergies = Allergies::new(255); assert!(allergies.is_allergic_to(&Allergen::Strawberries)) @@ -161,7 +141,6 @@ fn allergic_to_everything_strawberries() { #[test] #[ignore] - fn not_allergic_to_anything_tomatoes() { let allergies = Allergies::new(0); assert!(!allergies.is_allergic_to(&Allergen::Tomatoes)) @@ -169,7 +148,6 @@ fn not_allergic_to_anything_tomatoes() { #[test] #[ignore] - fn allergic_only_to_tomatoes_tomatoes() { let allergies = Allergies::new(16); assert!(allergies.is_allergic_to(&Allergen::Tomatoes)) @@ -177,7 +155,6 @@ fn allergic_only_to_tomatoes_tomatoes() { #[test] #[ignore] - fn allergic_to_tomatoes_and_something_else_tomatoes() { let allergies = Allergies::new(56); assert!(allergies.is_allergic_to(&Allergen::Tomatoes)) @@ -185,7 +162,6 @@ fn allergic_to_tomatoes_and_something_else_tomatoes() { #[test] #[ignore] - fn allergic_to_something_but_not_tomatoes_tomatoes() { let allergies = Allergies::new(40); assert!(!allergies.is_allergic_to(&Allergen::Tomatoes)) @@ -193,7 +169,6 @@ fn allergic_to_something_but_not_tomatoes_tomatoes() { #[test] #[ignore] - fn allergic_to_everything_tomatoes() { let allergies = Allergies::new(255); assert!(allergies.is_allergic_to(&Allergen::Tomatoes)) @@ -201,7 +176,6 @@ fn allergic_to_everything_tomatoes() { #[test] #[ignore] - fn not_allergic_to_anything_chocolate() { let allergies = Allergies::new(0); assert!(!allergies.is_allergic_to(&Allergen::Chocolate)) @@ -209,7 +183,6 @@ fn not_allergic_to_anything_chocolate() { #[test] #[ignore] - fn allergic_only_to_chocolate_chocolate() { let allergies = Allergies::new(32); assert!(allergies.is_allergic_to(&Allergen::Chocolate)) @@ -217,7 +190,6 @@ fn allergic_only_to_chocolate_chocolate() { #[test] #[ignore] - fn allergic_to_chocolate_and_something_else_chocolate() { let allergies = Allergies::new(112); assert!(allergies.is_allergic_to(&Allergen::Chocolate)) @@ -225,7 +197,6 @@ fn allergic_to_chocolate_and_something_else_chocolate() { #[test] #[ignore] - fn allergic_to_something_but_not_chocolate_chocolate() { let allergies = Allergies::new(80); assert!(!allergies.is_allergic_to(&Allergen::Chocolate)) @@ -233,7 +204,6 @@ fn allergic_to_something_but_not_chocolate_chocolate() { #[test] #[ignore] - fn allergic_to_everything_chocolate() { let allergies = Allergies::new(255); assert!(allergies.is_allergic_to(&Allergen::Chocolate)) @@ -241,7 +211,6 @@ fn allergic_to_everything_chocolate() { #[test] #[ignore] - fn not_allergic_to_anything_pollen() { let allergies = Allergies::new(0); assert!(!allergies.is_allergic_to(&Allergen::Pollen)) @@ -249,7 +218,6 @@ fn not_allergic_to_anything_pollen() { #[test] #[ignore] - fn allergic_only_to_pollen_pollen() { let allergies = Allergies::new(64); assert!(allergies.is_allergic_to(&Allergen::Pollen)) @@ -257,7 +225,6 @@ fn allergic_only_to_pollen_pollen() { #[test] #[ignore] - fn allergic_to_pollen_and_something_else_pollen() { let allergies = Allergies::new(224); assert!(allergies.is_allergic_to(&Allergen::Pollen)) @@ -265,7 +232,6 @@ fn allergic_to_pollen_and_something_else_pollen() { #[test] #[ignore] - fn allergic_to_something_but_not_pollen_pollen() { let allergies = Allergies::new(160); assert!(!allergies.is_allergic_to(&Allergen::Pollen)) @@ -273,7 +239,6 @@ fn allergic_to_something_but_not_pollen_pollen() { #[test] #[ignore] - fn allergic_to_everything_pollen() { let allergies = Allergies::new(255); assert!(allergies.is_allergic_to(&Allergen::Pollen)) @@ -281,7 +246,6 @@ fn allergic_to_everything_pollen() { #[test] #[ignore] - fn not_allergic_to_anything_cats() { let allergies = Allergies::new(0); assert!(!allergies.is_allergic_to(&Allergen::Cats)) @@ -289,7 +253,6 @@ fn not_allergic_to_anything_cats() { #[test] #[ignore] - fn allergic_only_to_cats_cats() { let allergies = Allergies::new(128); assert!(allergies.is_allergic_to(&Allergen::Cats)) @@ -297,7 +260,6 @@ fn allergic_only_to_cats_cats() { #[test] #[ignore] - fn allergic_to_cats_and_something_else_cats() { let allergies = Allergies::new(192); assert!(allergies.is_allergic_to(&Allergen::Cats)) @@ -305,7 +267,6 @@ fn allergic_to_cats_and_something_else_cats() { #[test] #[ignore] - fn allergic_to_something_but_not_cats_cats() { let allergies = Allergies::new(64); assert!(!allergies.is_allergic_to(&Allergen::Cats)) @@ -313,7 +274,6 @@ fn allergic_to_something_but_not_cats_cats() { #[test] #[ignore] - fn allergic_to_everything_cats() { let allergies = Allergies::new(255); assert!(allergies.is_allergic_to(&Allergen::Cats)) From 45ad6618effef6529d792d3aba979461bc0379c4 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Wed, 4 Dec 2024 23:42:55 +0100 Subject: [PATCH 359/436] fix clippy warnings (#1998) [no important files changed] --- .../doubly-linked-list/tests/doubly-linked-list.rs | 2 +- exercises/practice/grep/tests/grep.rs | 2 +- exercises/practice/luhn-from/src/lib.rs | 6 +++--- exercises/practice/luhn-trait/.meta/example.rs | 2 +- exercises/practice/luhn-trait/src/lib.rs | 4 ++-- .../practice/matching-brackets/.meta/example.rs | 2 +- exercises/practice/poker/.meta/example.rs | 12 ++++++------ exercises/practice/react/.meta/example.rs | 2 +- exercises/practice/rectangles/.meta/example.rs | 2 +- exercises/practice/xorcism/.meta/example.rs | 4 ++-- 10 files changed, 19 insertions(+), 19 deletions(-) diff --git a/exercises/practice/doubly-linked-list/tests/doubly-linked-list.rs b/exercises/practice/doubly-linked-list/tests/doubly-linked-list.rs index 101ef6c21..c3985d23c 100644 --- a/exercises/practice/doubly-linked-list/tests/doubly-linked-list.rs +++ b/exercises/practice/doubly-linked-list/tests/doubly-linked-list.rs @@ -259,7 +259,7 @@ fn drop_no_double_frees() { use std::cell::Cell; struct DropCounter<'a>(&'a Cell); - impl<'a> Drop for DropCounter<'a> { + impl Drop for DropCounter<'_> { fn drop(&mut self) { let num = self.0.get(); self.0.set(num + 1); diff --git a/exercises/practice/grep/tests/grep.rs b/exercises/practice/grep/tests/grep.rs index e6358f42c..1154d2d69 100644 --- a/exercises/practice/grep/tests/grep.rs +++ b/exercises/practice/grep/tests/grep.rs @@ -483,7 +483,7 @@ impl<'a> Files<'a> { } } -impl<'a> Drop for Files<'a> { +impl Drop for Files<'_> { fn drop(&mut self) { for file_name in self.file_names { std::fs::remove_file(file_name) diff --git a/exercises/practice/luhn-from/src/lib.rs b/exercises/practice/luhn-from/src/lib.rs index f7c991220..169f7e666 100644 --- a/exercises/practice/luhn-from/src/lib.rs +++ b/exercises/practice/luhn-from/src/lib.rs @@ -8,11 +8,11 @@ impl Luhn { /// Here is the example of how the From trait could be implemented /// for the &str type. Naturally, you can implement this trait -/// by hand for the every other type presented in the test suite, +/// by hand for every other type presented in the test suite, /// but your solution will fail if a new type is presented. /// Perhaps there exists a better solution for this problem? -impl<'a> From<&'a str> for Luhn { - fn from(input: &'a str) -> Self { +impl From<&str> for Luhn { + fn from(input: &str) -> Self { todo!("From the given input '{input}' create a new Luhn struct."); } } diff --git a/exercises/practice/luhn-trait/.meta/example.rs b/exercises/practice/luhn-trait/.meta/example.rs index ed71406fd..d5ab8c444 100644 --- a/exercises/practice/luhn-trait/.meta/example.rs +++ b/exercises/practice/luhn-trait/.meta/example.rs @@ -20,7 +20,7 @@ impl Luhn for String { } } -impl<'a> Luhn for &'a str { +impl Luhn for &str { fn valid_luhn(&self) -> bool { String::from(*self).valid_luhn() } diff --git a/exercises/practice/luhn-trait/src/lib.rs b/exercises/practice/luhn-trait/src/lib.rs index 6b958fecf..ccf07bbf8 100644 --- a/exercises/practice/luhn-trait/src/lib.rs +++ b/exercises/practice/luhn-trait/src/lib.rs @@ -4,10 +4,10 @@ pub trait Luhn { /// Here is the example of how to implement custom Luhn trait /// for the &str type. Naturally, you can implement this trait -/// by hand for the every other type presented in the test suite, +/// by hand for every other type presented in the test suite, /// but your solution will fail if a new type is presented. /// Perhaps there exists a better solution for this problem? -impl<'a> Luhn for &'a str { +impl Luhn for &str { fn valid_luhn(&self) -> bool { todo!("Determine if '{self}' is a valid credit card number."); } diff --git a/exercises/practice/matching-brackets/.meta/example.rs b/exercises/practice/matching-brackets/.meta/example.rs index 2da590030..4a139dae9 100644 --- a/exercises/practice/matching-brackets/.meta/example.rs +++ b/exercises/practice/matching-brackets/.meta/example.rs @@ -9,7 +9,7 @@ struct Brackets { pairs: MatchingBrackets, } -impl<'a> From<&'a str> for Brackets { +impl From<&str> for Brackets { fn from(i: &str) -> Self { Brackets::new(i, None) } diff --git a/exercises/practice/poker/.meta/example.rs b/exercises/practice/poker/.meta/example.rs index 02b5ed78d..f563ae0a1 100644 --- a/exercises/practice/poker/.meta/example.rs +++ b/exercises/practice/poker/.meta/example.rs @@ -51,7 +51,7 @@ impl fmt::Display for Suit { } } -impl<'a> TryFrom<&'a str> for Suit { +impl TryFrom<&str> for Suit { type Error = &'static str; fn try_from(source: &str) -> Result { @@ -115,7 +115,7 @@ impl PartialOrd for Rank { } } -impl<'a> TryFrom<&'a str> for Rank { +impl TryFrom<&str> for Rank { type Error = &'static str; fn try_from(source: &str) -> Result { @@ -160,7 +160,7 @@ impl fmt::Display for Card { } } -impl<'a> TryFrom<&'a str> for Card { +impl TryFrom<&str> for Card { type Error = &'static str; fn try_from(source: &str) -> Result { @@ -262,7 +262,7 @@ struct Hand<'a> { hand_type: PokerHand, } -impl<'a> Hand<'a> { +impl Hand<'_> { fn cmp_high_card(&self, other: &Hand, card: usize) -> Ordering { let mut ordering = self.cards[card] .rank @@ -312,13 +312,13 @@ impl<'a> Hand<'a> { } } -impl<'a> fmt::Display for Hand<'a> { +impl fmt::Display for Hand<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.source) } } -impl<'a> PartialOrd for Hand<'a> { +impl PartialOrd for Hand<'_> { fn partial_cmp(&self, other: &Hand) -> Option { Some(self.hand_type.cmp(&other.hand_type).then_with(|| { use crate::PokerHand::*; diff --git a/exercises/practice/react/.meta/example.rs b/exercises/practice/react/.meta/example.rs index 199bb46d0..03dac050b 100644 --- a/exercises/practice/react/.meta/example.rs +++ b/exercises/practice/react/.meta/example.rs @@ -249,7 +249,7 @@ impl<'a, T: Copy + PartialEq> Reactor<'a, T> { } } -impl<'a, T: Copy + PartialEq> Default for Reactor<'a, T> { +impl Default for Reactor<'_, T> { fn default() -> Self { Self::new() } diff --git a/exercises/practice/rectangles/.meta/example.rs b/exercises/practice/rectangles/.meta/example.rs index ef18d07d7..38673622c 100644 --- a/exercises/practice/rectangles/.meta/example.rs +++ b/exercises/practice/rectangles/.meta/example.rs @@ -70,7 +70,7 @@ impl Area for RealArea { struct TransposedArea<'a>(&'a RealArea); // For vertical scanning -impl<'a> Area for TransposedArea<'a> { +impl Area for TransposedArea<'_> { #[allow(clippy::misnamed_getters)] fn width(&self) -> usize { self.0.height diff --git a/exercises/practice/xorcism/.meta/example.rs b/exercises/practice/xorcism/.meta/example.rs index ee5d6e0a0..6c2f7190b 100644 --- a/exercises/practice/xorcism/.meta/example.rs +++ b/exercises/practice/xorcism/.meta/example.rs @@ -110,7 +110,7 @@ where } } -impl<'a, W> Write for Writer<'a, W> +impl Write for Writer<'_, W> where W: Write, { @@ -149,7 +149,7 @@ where } } -impl<'a, R> Read for Reader<'a, R> +impl Read for Reader<'_, R> where R: Read, { From 6c321382019fa969401b8e923a5e12ff69065561 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Sat, 7 Dec 2024 18:18:33 +0100 Subject: [PATCH 360/436] accumulate: move important hint to instructions (#1999) --- exercises/practice/accumulate/.docs/hints.md | 4 ---- exercises/practice/accumulate/.docs/instructions.append.md | 6 ++++++ 2 files changed, 6 insertions(+), 4 deletions(-) create mode 100644 exercises/practice/accumulate/.docs/instructions.append.md diff --git a/exercises/practice/accumulate/.docs/hints.md b/exercises/practice/accumulate/.docs/hints.md index 082f60c16..6a6ecdf29 100644 --- a/exercises/practice/accumulate/.docs/hints.md +++ b/exercises/practice/accumulate/.docs/hints.md @@ -8,7 +8,3 @@ It may help to look at the Fn\* traits: Help with passing a closure into a function may be found in the ["closures as input parameters" section](https://doc.rust-lang.org/stable/rust-by-example/fn/closures/input_parameters.html) of [Rust by Example](https://doc.rust-lang.org/stable/rust-by-example/). - -The tests for this exercise will cause compile time errors, -if your function signature does not fit them, even when they're not run. -You may want to comment some tests out and generalize your solution piece by piece. diff --git a/exercises/practice/accumulate/.docs/instructions.append.md b/exercises/practice/accumulate/.docs/instructions.append.md new file mode 100644 index 000000000..3cdb30c2c --- /dev/null +++ b/exercises/practice/accumulate/.docs/instructions.append.md @@ -0,0 +1,6 @@ +# Instructions append + +## Workflow + +The tests for this exercise will cause compile time errors if your function signature does not fit them, even when they're not run. +You may want to comment some tests out and generalize your solution piece by piece. From d9d4d8d073978841fdc107db9723f7cdd1ff1ade Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Wed, 1 Jan 2025 18:03:17 +0100 Subject: [PATCH 361/436] sync docs (#2001) --- .../affine-cipher/.docs/instructions.md | 4 +-- .../pascals-triangle/.docs/instructions.md | 27 ++++++++++++++++--- .../pascals-triangle/.docs/introduction.md | 22 +++++++++++++++ .../practice/pig-latin/.docs/instructions.md | 4 +-- .../practice/poker/.docs/instructions.md | 2 +- exercises/practice/yacht/.meta/config.json | 2 +- 6 files changed, 52 insertions(+), 9 deletions(-) create mode 100644 exercises/practice/pascals-triangle/.docs/introduction.md diff --git a/exercises/practice/affine-cipher/.docs/instructions.md b/exercises/practice/affine-cipher/.docs/instructions.md index 26ce15342..4eff918de 100644 --- a/exercises/practice/affine-cipher/.docs/instructions.md +++ b/exercises/practice/affine-cipher/.docs/instructions.md @@ -18,10 +18,10 @@ E(x) = (ai + b) mod m Where: -- `i` is the letter's index from `0` to the length of the alphabet - 1 +- `i` is the letter's index from `0` to the length of the alphabet - 1. - `m` is the length of the alphabet. For the Roman alphabet `m` is `26`. -- `a` and `b` are integers which make the encryption key +- `a` and `b` are integers which make up the encryption key. Values `a` and `m` must be _coprime_ (or, _relatively prime_) for automatic decryption to succeed, i.e., they have number `1` as their only common factor (more information can be found in the [Wikipedia article about coprime integers][coprime-integers]). In case `a` is not coprime to `m`, your program should indicate that this is an error. diff --git a/exercises/practice/pascals-triangle/.docs/instructions.md b/exercises/practice/pascals-triangle/.docs/instructions.md index f55678593..0f58f0069 100644 --- a/exercises/practice/pascals-triangle/.docs/instructions.md +++ b/exercises/practice/pascals-triangle/.docs/instructions.md @@ -1,8 +1,20 @@ # Instructions -Compute Pascal's triangle up to a given number of rows. +Your task is to output the first N rows of Pascal's triangle. -In Pascal's Triangle each number is computed by adding the numbers to the right and left of the current position in the previous row. +[Pascal's triangle][wikipedia] is a triangular array of positive integers. + +In Pascal's triangle, the number of values in a row is equal to its row number (which starts at one). +Therefore, the first row has one value, the second row has two values, and so on. + +The first (topmost) row has a single value: `1`. +Subsequent rows' values are computed by adding the numbers directly to the right and left of the current position in the previous row. + +If the previous row does _not_ have a value to the left or right of the current position (which only happens for the leftmost and rightmost positions), treat that position's value as zero (effectively "ignoring" it in the summation). + +## Example + +Let's look at the first 5 rows of Pascal's Triangle: ```text 1 @@ -10,5 +22,14 @@ In Pascal's Triangle each number is computed by adding the numbers to the right 1 2 1 1 3 3 1 1 4 6 4 1 -# ... etc ``` + +The topmost row has one value, which is `1`. + +The leftmost and rightmost values have only one preceding position to consider, which is the position to its right respectively to its left. +With the topmost value being `1`, it follows from this that all the leftmost and rightmost values are also `1`. + +The other values all have two positions to consider. +For example, the fifth row's (`1 4 6 4 1`) middle value is `6`, as the values to its left and right in the preceding row are `3` and `3`: + +[wikipedia]: https://en.wikipedia.org/wiki/Pascal%27s_triangle diff --git a/exercises/practice/pascals-triangle/.docs/introduction.md b/exercises/practice/pascals-triangle/.docs/introduction.md new file mode 100644 index 000000000..60b8ec30d --- /dev/null +++ b/exercises/practice/pascals-triangle/.docs/introduction.md @@ -0,0 +1,22 @@ +# Introduction + +With the weather being great, you're not looking forward to spending an hour in a classroom. +Annoyed, you enter the class room, where you notice a strangely satisfying triangle shape on the blackboard. +Whilst waiting for your math teacher to arrive, you can't help but notice some patterns in the triangle: the outer values are all ones, each subsequent row has one more value than its previous row and the triangle is symmetrical. +Weird! + +Not long after you sit down, your teacher enters the room and explains that this triangle is the famous [Pascal's triangle][wikipedia]. + +Over the next hour, your teacher reveals some amazing things hidden in this triangle: + +- It can be used to compute how many ways you can pick K elements from N values. +- It contains the Fibonacci sequence. +- If you color odd and even numbers differently, you get a beautiful pattern called the [Sierpiński triangle][wikipedia-sierpinski-triangle]. + +The teacher implores you and your classmates to lookup other uses, and assures you that there are lots more! +At that moment, the school bell rings. +You realize that for the past hour, you were completely absorbed in learning about Pascal's triangle. +You quickly grab your laptop from your bag and go outside, ready to enjoy both the sunshine _and_ the wonders of Pascal's triangle. + +[wikipedia]: https://en.wikipedia.org/wiki/Pascal%27s_triangle +[wikipedia-sierpinski-triangle]: https://en.wikipedia.org/wiki/Sierpi%C5%84ski_triangle diff --git a/exercises/practice/pig-latin/.docs/instructions.md b/exercises/practice/pig-latin/.docs/instructions.md index 6c843080d..a9645ac23 100644 --- a/exercises/practice/pig-latin/.docs/instructions.md +++ b/exercises/practice/pig-latin/.docs/instructions.md @@ -19,7 +19,7 @@ For example: ## Rule 2 -If a word begins with a one or more consonants, first move those consonants to the end of the word and then add an `"ay"` sound to the end of the word. +If a word begins with one or more consonants, first move those consonants to the end of the word and then add an `"ay"` sound to the end of the word. For example: @@ -33,7 +33,7 @@ If a word starts with zero or more consonants followed by `"qu"`, first move tho For example: -- `"quick"` -> `"ickqu"` -> `"ay"` (starts with `"qu"`, no preceding consonants) +- `"quick"` -> `"ickqu"` -> `"ickquay"` (starts with `"qu"`, no preceding consonants) - `"square"` -> `"aresqu"` -> `"aresquay"` (starts with one consonant followed by `"qu`") ## Rule 4 diff --git a/exercises/practice/poker/.docs/instructions.md b/exercises/practice/poker/.docs/instructions.md index 492fc4c9e..107cd49d6 100644 --- a/exercises/practice/poker/.docs/instructions.md +++ b/exercises/practice/poker/.docs/instructions.md @@ -2,6 +2,6 @@ Pick the best hand(s) from a list of poker hands. -See [wikipedia][poker-hands] for an overview of poker hands. +See [Wikipedia][poker-hands] for an overview of poker hands. [poker-hands]: https://en.wikipedia.org/wiki/List_of_poker_hands diff --git a/exercises/practice/yacht/.meta/config.json b/exercises/practice/yacht/.meta/config.json index 91d47758d..9478b2f43 100644 --- a/exercises/practice/yacht/.meta/config.json +++ b/exercises/practice/yacht/.meta/config.json @@ -15,6 +15,6 @@ ] }, "blurb": "Score a single throw of dice in the game Yacht.", - "source": "James Kilfiger, using wikipedia", + "source": "James Kilfiger, using Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Yacht_(dice_game)" } From 0f32defef7b6b3ca415f796ef015337dbe2a5394 Mon Sep 17 00:00:00 2001 From: ellnix Date: Wed, 29 Jan 2025 15:16:50 +0100 Subject: [PATCH 362/436] Sublist: use args in todo! instead of naming with _ (#2004) This necessitated adding `std::fmt::Debug` as a constraint to `T`. All of the types in the tests are `i32` so this does not cause the tests to break. Corresponding forum post: https://forum.exercism.org/t/warn-explain-or-remove-variables/15285 --- exercises/practice/sublist/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/practice/sublist/src/lib.rs b/exercises/practice/sublist/src/lib.rs index 96b2c1a09..a83342a2c 100644 --- a/exercises/practice/sublist/src/lib.rs +++ b/exercises/practice/sublist/src/lib.rs @@ -6,6 +6,6 @@ pub enum Comparison { Unequal, } -pub fn sublist(_first_list: &[T], _second_list: &[T]) -> Comparison { - todo!("Determine if the first list is equal to, sublist of, superlist of or unequal to the second list."); +pub fn sublist(first_list: &[i32], second_list: &[i32]) -> Comparison { + todo!("Determine if the {first_list:?} is equal to, sublist of, superlist of or unequal to {second_list:?}."); } From 3295ea9c15d03dfde639801189457743d2a23b3e Mon Sep 17 00:00:00 2001 From: ellnix Date: Fri, 31 Jan 2025 20:32:22 +0100 Subject: [PATCH 363/436] palindrome-products: require factors in solution (#2006) There were two extra tests on the now-defunct `Palindrome::new` function. I felt those only made sense if this exercise still contained the newtype pattern. The decision to return a HashSet was done to communicate to the student that order does not matter. The order of the items inside the tuple pair is the same as the description of the exercise. An owned HashSet was chosen to allow for solutions that don't store a HashSet in Palindrome (or elsewhere) and would have trouble producing a reference to one. See the feedback: https://github.com/exercism/rust/pull/2006?#discussion_r1935384069 Corresponding forum post, which goes into more detail about why this change was necessary: https://forum.exercism.org/t/palindrome-products-description-mentions-factors/ --------- Co-authored-by: Remo Senekowitsch --- .../palindrome-products/.meta/example.rs | 63 ++++++--- .../.meta/test_template.tera | 46 +++---- .../practice/palindrome-products/src/lib.rs | 22 ++-- .../tests/palindrome-products.rs | 120 +++++++++--------- 4 files changed, 132 insertions(+), 119 deletions(-) diff --git a/exercises/practice/palindrome-products/.meta/example.rs b/exercises/practice/palindrome-products/.meta/example.rs index a39c87fda..43799fd5c 100644 --- a/exercises/practice/palindrome-products/.meta/example.rs +++ b/exercises/practice/palindrome-products/.meta/example.rs @@ -1,19 +1,30 @@ -/// `Palindrome` is a newtype which only exists when the contained value is a palindrome number in base ten. -/// -/// A struct with a single field which is used to constrain behavior like this is called a "newtype", and its use is -/// often referred to as the "newtype pattern". This is a fairly common pattern in Rust. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)] -pub struct Palindrome(u64); +use std::cmp::Ordering; +use std::collections::HashSet; + +#[derive(Debug, Clone)] +pub struct Palindrome { + value: u64, + factors: HashSet<(u64, u64)>, +} impl Palindrome { - /// Create a `Palindrome` only if `value` is in fact a palindrome when represented in base ten. Otherwise, `None`. - pub fn new(value: u64) -> Option { - is_palindrome(value).then_some(Palindrome(value)) + pub fn new(value: u64, first_factors: (u64, u64)) -> Palindrome { + Self { + value, + factors: HashSet::from([first_factors]), + } + } + + pub fn add_factor(&mut self, factor: (u64, u64)) -> bool { + self.factors.insert(factor) + } + + pub fn value(&self) -> u64 { + self.value } - /// Get the value of this palindrome. - pub fn into_inner(self) -> u64 { - self.0 + pub fn into_factors(self) -> HashSet<(u64, u64)> { + self.factors } } @@ -22,14 +33,28 @@ pub fn palindrome_products(min: u64, max: u64) -> Option<(Palindrome, Palindrome let mut pmax: Option = None; for i in min..=max { for j in i..=max { - if let Some(palindrome) = Palindrome::new(i * j) { - pmin = match pmin { - None => Some(palindrome), - Some(prev) => Some(prev.min(palindrome)), + let p = i * j; + if is_palindrome(p) { + pmin = match pmin.as_ref().map(|prev| prev.value.cmp(&p)) { + Some(Ordering::Less) => pmin, + Some(Ordering::Equal) => { + if i <= j { + pmin.as_mut().unwrap().add_factor((i, j)); + } + pmin + } + Some(Ordering::Greater) | None => Some(Palindrome::new(p, (i, j))), }; - pmax = match pmax { - None => Some(palindrome), - Some(prev) => Some(prev.max(palindrome)), + + pmax = match pmax.as_ref().map(|prev| prev.value.cmp(&p)) { + Some(Ordering::Greater) => pmax, + Some(Ordering::Equal) => { + if i <= j { + pmax.as_mut().unwrap().add_factor((i, j)); + } + pmax + } + Some(Ordering::Less) | None => Some(Palindrome::new(p, (i, j))), }; } } diff --git a/exercises/practice/palindrome-products/.meta/test_template.tera b/exercises/practice/palindrome-products/.meta/test_template.tera index e184921f5..626868776 100644 --- a/exercises/practice/palindrome-products/.meta/test_template.tera +++ b/exercises/practice/palindrome-products/.meta/test_template.tera @@ -1,42 +1,26 @@ use palindrome_products::*; - -{# - These first two custom test cases are for the object-oriented design of the exercise. - They don't fit the structure of the upstream tests, so they're implemented here. -#} - -#[test] -#[ignore] -/// test `Palindrome::new` with valid input -fn palindrome_new_return_some() { - for v in [1, 11, 121, 12321, 1234321, 123454321, 543212345] { - assert_eq!(Palindrome::new(v).expect("is a palindrome").into_inner(), v); - } -} - -#[test] -#[ignore] -/// test `Palindrome::new` with invalid input -fn palindrome_new_return_none() { - for v in [12, 2322, 23443, 1233211, 8932343] { - assert_eq!(Palindrome::new(v), None); - } -} +use std::collections::HashSet; {% for test in cases %} #[test] #[ignore] fn {{ test.description | make_ident }}() { - {%- if test.property == "smallest" %} - let output = palindrome_products({{ test.input.min }}, {{ test.input.max }}).map(|(min, _)| min.into_inner()); - {%- else %} - let output = palindrome_products({{ test.input.min }}, {{ test.input.max }}).map(|(_, max)| max.into_inner()); - {%- endif%} + let output = palindrome_products({{ test.input.min }}, {{ test.input.max }}); + {%- if test.expected.error is defined or not test.expected.value %} - let expected = None; + assert!(output.is_none()); {%- else %} - let expected = Some({{ test.expected.value }}); + assert!(output.is_some()); + + {% if test.property == "smallest" %} + let (pal, _) = output.unwrap(); + {%- else %} + let (_, pal) = output.unwrap(); + {%- endif%} + assert_eq!(pal.value(), {{ test.expected.value }}); + assert_eq!(pal.into_factors(), HashSet::from([ + {{- test.expected.factors | join(sep=", ") | replace(from="[", to="(") | replace(from="]", to=")") -}} + ])); {%- endif%} - assert_eq!(output, expected); } {% endfor -%} diff --git a/exercises/practice/palindrome-products/src/lib.rs b/exercises/practice/palindrome-products/src/lib.rs index 15d3fb609..f4bec67e7 100644 --- a/exercises/practice/palindrome-products/src/lib.rs +++ b/exercises/practice/palindrome-products/src/lib.rs @@ -1,19 +1,17 @@ -/// `Palindrome` is a newtype which only exists when the contained value is a palindrome number in base ten. -/// -/// A struct with a single field which is used to constrain behavior like this is called a "newtype", and its use is -/// often referred to as the "newtype pattern". This is a fairly common pattern in Rust. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Palindrome(u64); +use std::collections::HashSet; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Palindrome { + // TODO +} impl Palindrome { - /// Create a `Palindrome` only if `value` is in fact a palindrome when represented in base ten. Otherwise, `None`. - pub fn new(value: u64) -> Option { - todo!("if the value {value} is a palindrome return Some, otherwise return None"); + pub fn value(&self) -> u64 { + todo!("return the value of the palindrome") } - /// Get the value of this palindrome. - pub fn into_inner(self) -> u64 { - todo!("return inner value of a Palindrome"); + pub fn into_factors(self) -> HashSet<(u64, u64)> { + todo!("return the set of factors of the palindrome") } } diff --git a/exercises/practice/palindrome-products/tests/palindrome-products.rs b/exercises/practice/palindrome-products/tests/palindrome-products.rs index 711fb9339..d5c230058 100644 --- a/exercises/practice/palindrome-products/tests/palindrome-products.rs +++ b/exercises/practice/palindrome-products/tests/palindrome-products.rs @@ -1,122 +1,128 @@ use palindrome_products::*; +use std::collections::HashSet; #[test] -/// test `Palindrome::new` with valid input -fn palindrome_new_return_some() { - for v in [1, 11, 121, 12321, 1234321, 123454321, 543212345] { - assert_eq!(Palindrome::new(v).expect("is a palindrome").into_inner(), v); - } -} - -#[test] -#[ignore] -/// test `Palindrome::new` with invalid input -fn palindrome_new_return_none() { - for v in [12, 2322, 23443, 1233211, 8932343] { - assert_eq!(Palindrome::new(v), None); - } -} - -#[test] -#[ignore] fn find_the_smallest_palindrome_from_single_digit_factors() { - let output = palindrome_products(1, 9).map(|(min, _)| min.into_inner()); - let expected = Some(1); - assert_eq!(output, expected); + let output = palindrome_products(1, 9); + assert!(output.is_some()); + + let (pal, _) = output.unwrap(); + assert_eq!(pal.value(), 1); + assert_eq!(pal.into_factors(), HashSet::from([(1, 1)])); } #[test] #[ignore] fn find_the_largest_palindrome_from_single_digit_factors() { - let output = palindrome_products(1, 9).map(|(_, max)| max.into_inner()); - let expected = Some(9); - assert_eq!(output, expected); + let output = palindrome_products(1, 9); + assert!(output.is_some()); + + let (_, pal) = output.unwrap(); + assert_eq!(pal.value(), 9); + assert_eq!(pal.into_factors(), HashSet::from([(1, 9), (3, 3)])); } #[test] #[ignore] fn find_the_smallest_palindrome_from_double_digit_factors() { - let output = palindrome_products(10, 99).map(|(min, _)| min.into_inner()); - let expected = Some(121); - assert_eq!(output, expected); + let output = palindrome_products(10, 99); + assert!(output.is_some()); + + let (pal, _) = output.unwrap(); + assert_eq!(pal.value(), 121); + assert_eq!(pal.into_factors(), HashSet::from([(11, 11)])); } #[test] #[ignore] fn find_the_largest_palindrome_from_double_digit_factors() { - let output = palindrome_products(10, 99).map(|(_, max)| max.into_inner()); - let expected = Some(9009); - assert_eq!(output, expected); + let output = palindrome_products(10, 99); + assert!(output.is_some()); + + let (_, pal) = output.unwrap(); + assert_eq!(pal.value(), 9009); + assert_eq!(pal.into_factors(), HashSet::from([(91, 99)])); } #[test] #[ignore] fn find_the_smallest_palindrome_from_triple_digit_factors() { - let output = palindrome_products(100, 999).map(|(min, _)| min.into_inner()); - let expected = Some(10201); - assert_eq!(output, expected); + let output = palindrome_products(100, 999); + assert!(output.is_some()); + + let (pal, _) = output.unwrap(); + assert_eq!(pal.value(), 10201); + assert_eq!(pal.into_factors(), HashSet::from([(101, 101)])); } #[test] #[ignore] fn find_the_largest_palindrome_from_triple_digit_factors() { - let output = palindrome_products(100, 999).map(|(_, max)| max.into_inner()); - let expected = Some(906609); - assert_eq!(output, expected); + let output = palindrome_products(100, 999); + assert!(output.is_some()); + + let (_, pal) = output.unwrap(); + assert_eq!(pal.value(), 906609); + assert_eq!(pal.into_factors(), HashSet::from([(913, 993)])); } #[test] #[ignore] fn find_the_smallest_palindrome_from_four_digit_factors() { - let output = palindrome_products(1000, 9999).map(|(min, _)| min.into_inner()); - let expected = Some(1002001); - assert_eq!(output, expected); + let output = palindrome_products(1000, 9999); + assert!(output.is_some()); + + let (pal, _) = output.unwrap(); + assert_eq!(pal.value(), 1002001); + assert_eq!(pal.into_factors(), HashSet::from([(1001, 1001)])); } #[test] #[ignore] fn find_the_largest_palindrome_from_four_digit_factors() { - let output = palindrome_products(1000, 9999).map(|(_, max)| max.into_inner()); - let expected = Some(99000099); - assert_eq!(output, expected); + let output = palindrome_products(1000, 9999); + assert!(output.is_some()); + + let (_, pal) = output.unwrap(); + assert_eq!(pal.value(), 99000099); + assert_eq!(pal.into_factors(), HashSet::from([(9901, 9999)])); } #[test] #[ignore] fn empty_result_for_smallest_if_no_palindrome_in_the_range() { - let output = palindrome_products(1002, 1003).map(|(min, _)| min.into_inner()); - let expected = None; - assert_eq!(output, expected); + let output = palindrome_products(1002, 1003); + assert!(output.is_none()); } #[test] #[ignore] fn empty_result_for_largest_if_no_palindrome_in_the_range() { - let output = palindrome_products(15, 15).map(|(_, max)| max.into_inner()); - let expected = None; - assert_eq!(output, expected); + let output = palindrome_products(15, 15); + assert!(output.is_none()); } #[test] #[ignore] fn error_result_for_smallest_if_min_is_more_than_max() { - let output = palindrome_products(10000, 1).map(|(min, _)| min.into_inner()); - let expected = None; - assert_eq!(output, expected); + let output = palindrome_products(10000, 1); + assert!(output.is_none()); } #[test] #[ignore] fn error_result_for_largest_if_min_is_more_than_max() { - let output = palindrome_products(2, 1).map(|(_, max)| max.into_inner()); - let expected = None; - assert_eq!(output, expected); + let output = palindrome_products(2, 1); + assert!(output.is_none()); } #[test] #[ignore] fn smallest_product_does_not_use_the_smallest_factor() { - let output = palindrome_products(3215, 4000).map(|(min, _)| min.into_inner()); - let expected = Some(10988901); - assert_eq!(output, expected); + let output = palindrome_products(3215, 4000); + assert!(output.is_some()); + + let (pal, _) = output.unwrap(); + assert_eq!(pal.value(), 10988901); + assert_eq!(pal.into_factors(), HashSet::from([(3297, 3333)])); } From aab1538fdba98f4705e81f7ee3678532db5d3aa4 Mon Sep 17 00:00:00 2001 From: Jacob Marshall Date: Sat, 1 Feb 2025 10:12:10 +0000 Subject: [PATCH 364/436] Remove undefined variable from binary-search/dig_deeper (#2008) In the recursion section, `offset` is not defined. I updated it to match https://exercism.org/tracks/rust/exercises/binary-search/approaches/recursion. --- exercises/practice/binary-search/.approaches/introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/binary-search/.approaches/introduction.md b/exercises/practice/binary-search/.approaches/introduction.md index 212f6c84b..d2e2a5092 100644 --- a/exercises/practice/binary-search/.approaches/introduction.md +++ b/exercises/practice/binary-search/.approaches/introduction.md @@ -50,7 +50,7 @@ fn find, T: Ord>(array: U, key: T) -> Option { let mid = array.len() / 2; match array[mid].cmp(&key) { - Ordering::Equal => Some(offset + mid), + Ordering::Equal => Some(mid), Ordering::Greater => find(&array[..mid], key), Ordering::Less => find(&array[mid + 1..], key).map(|p| p + mid + 1), } From e4824c30d08ab99bb92fdcbe0d60d9d53f6c74ad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Feb 2025 11:14:58 +0100 Subject: [PATCH 365/436] build(deps): bump dtolnay/rust-toolchain (#2007) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/tests.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e90440527..6d0c8a063 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -64,7 +64,7 @@ jobs: run: git fetch --depth=1 origin main - name: Setup toolchain - uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 + uses: dtolnay/rust-toolchain@c5a29ddb4d9d194e7c84ec8c3fba61b1c31fee8c with: toolchain: ${{ matrix.rust }} @@ -87,7 +87,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup toolchain - uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 + uses: dtolnay/rust-toolchain@c5a29ddb4d9d194e7c84ec8c3fba61b1c31fee8c with: toolchain: stable @@ -103,7 +103,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup toolchain - uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 + uses: dtolnay/rust-toolchain@c5a29ddb4d9d194e7c84ec8c3fba61b1c31fee8c with: toolchain: stable @@ -126,7 +126,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup toolchain - uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 + uses: dtolnay/rust-toolchain@c5a29ddb4d9d194e7c84ec8c3fba61b1c31fee8c with: toolchain: stable components: clippy @@ -151,7 +151,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup nightly toolchain - uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8 + uses: dtolnay/rust-toolchain@c5a29ddb4d9d194e7c84ec8c3fba61b1c31fee8c with: toolchain: nightly From 72ed94976ce968a5aae64ec1218b9077fa23e718 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Thu, 6 Feb 2025 15:53:58 +0100 Subject: [PATCH 366/436] dot-dsl: allow owned and borrowed strings (#2010) forum discussion: https://forum.exercism.org/t/unnecessary-to-string-calls-in-the-test-file-of-dot-dsl/15525 [no important files changed] --- exercises/practice/dot-dsl/tests/dot-dsl.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/exercises/practice/dot-dsl/tests/dot-dsl.rs b/exercises/practice/dot-dsl/tests/dot-dsl.rs index 54bd34958..ad9a52fa1 100644 --- a/exercises/practice/dot-dsl/tests/dot-dsl.rs +++ b/exercises/practice/dot-dsl/tests/dot-dsl.rs @@ -82,7 +82,8 @@ fn graph_with_one_edge_with_keywords() { fn graph_with_one_attribute() { let graph = Graph::new().with_attrs(&[("foo", "1")]); - let expected_attrs = HashMap::from([("foo".to_string(), "1".to_string())]); + #[allow(clippy::useless_conversion, reason = "allow String and &str")] + let expected_attrs = HashMap::from([("foo".into(), "1".into())]); assert!(graph.nodes.is_empty()); @@ -107,10 +108,11 @@ fn graph_with_attributes() { let attrs = vec![("foo", "1"), ("title", "Testing Attrs"), ("bar", "true")]; + #[allow(clippy::useless_conversion, reason = "allow String and &str")] let expected_attrs = HashMap::from([ - ("foo".to_string(), "1".to_string()), - ("title".to_string(), "Testing Attrs".to_string()), - ("bar".to_string(), "true".to_string()), + ("foo".into(), "1".into()), + ("title".into(), "Testing Attrs".into()), + ("bar".into(), "true".into()), ]); let graph = Graph::new() From 25b0b66125e84028b7fa3b4bbccd553b043bed34 Mon Sep 17 00:00:00 2001 From: ellnix Date: Sat, 8 Feb 2025 23:51:16 +0100 Subject: [PATCH 367/436] atbash-cipher: Use Tera template for tests (#2012) [no important files changed] --- .../atbash-cipher/.meta/test_template.tera | 14 +++++ .../practice/atbash-cipher/.meta/tests.toml | 55 ++++++++++++++++++- .../atbash-cipher/tests/atbash-cipher.rs | 2 +- 3 files changed, 67 insertions(+), 4 deletions(-) create mode 100644 exercises/practice/atbash-cipher/.meta/test_template.tera diff --git a/exercises/practice/atbash-cipher/.meta/test_template.tera b/exercises/practice/atbash-cipher/.meta/test_template.tera new file mode 100644 index 000000000..c77c74649 --- /dev/null +++ b/exercises/practice/atbash-cipher/.meta/test_template.tera @@ -0,0 +1,14 @@ +use atbash_cipher as cipher; + +{% for group in cases %} +{% for test in group.cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + assert_eq!( + cipher::{{ test.property }}("{{ test.input.phrase }}"), + "{{ test.expected }}" + ); +} +{% endfor -%} +{% endfor -%} diff --git a/exercises/practice/atbash-cipher/.meta/tests.toml b/exercises/practice/atbash-cipher/.meta/tests.toml index be690e975..c082d07cc 100644 --- a/exercises/practice/atbash-cipher/.meta/tests.toml +++ b/exercises/practice/atbash-cipher/.meta/tests.toml @@ -1,3 +1,52 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[2f47ebe1-eab9-4d6b-b3c6-627562a31c77] +description = "encode -> encode yes" + +[b4ffe781-ea81-4b74-b268-cc58ba21c739] +description = "encode -> encode no" + +[10e48927-24ab-4c4d-9d3f-3067724ace00] +description = "encode -> encode OMG" + +[d59b8bc3-509a-4a9a-834c-6f501b98750b] +description = "encode -> encode spaces" + +[31d44b11-81b7-4a94-8b43-4af6a2449429] +description = "encode -> encode mindblowingly" + +[d503361a-1433-48c0-aae0-d41b5baa33ff] +description = "encode -> encode numbers" + +[79c8a2d5-0772-42d4-b41b-531d0b5da926] +description = "encode -> encode deep thought" + +[9ca13d23-d32a-4967-a1fd-6100b8742bab] +description = "encode -> encode all the letters" + +[bb50e087-7fdf-48e7-9223-284fe7e69851] +description = "decode -> decode exercism" + +[ac021097-cd5d-4717-8907-b0814b9e292c] +description = "decode -> decode a sentence" + +[18729de3-de74-49b8-b68c-025eaf77f851] +description = "decode -> decode numbers" + +[0f30325f-f53b-415d-ad3e-a7a4f63de034] +description = "decode -> decode all the letters" + +[39640287-30c6-4c8c-9bac-9d613d1a5674] +description = "decode -> decode with too many spaces" + +[b34edf13-34c0-49b5-aa21-0768928000d5] +description = "decode -> decode with no spaces" diff --git a/exercises/practice/atbash-cipher/tests/atbash-cipher.rs b/exercises/practice/atbash-cipher/tests/atbash-cipher.rs index 214af9fe3..140a7611e 100644 --- a/exercises/practice/atbash-cipher/tests/atbash-cipher.rs +++ b/exercises/practice/atbash-cipher/tests/atbash-cipher.rs @@ -94,6 +94,6 @@ fn decode_with_too_many_spaces() { fn decode_with_no_spaces() { assert_eq!( cipher::decode("zmlyhgzxovrhlugvmzhgvkkrmthglmv"), - "anobstacleisoftenasteppingstone", + "anobstacleisoftenasteppingstone" ); } From 8a92db19b9472e04677aceb73eac093830f252c3 Mon Sep 17 00:00:00 2001 From: Yan Huang Date: Sun, 9 Feb 2025 16:30:28 -0500 Subject: [PATCH 368/436] palindrome-products: improve inaccurate comment (#2013) --- exercises/practice/palindrome-products/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/palindrome-products/src/lib.rs b/exercises/practice/palindrome-products/src/lib.rs index f4bec67e7..5b46d9c21 100644 --- a/exercises/practice/palindrome-products/src/lib.rs +++ b/exercises/practice/palindrome-products/src/lib.rs @@ -17,6 +17,6 @@ impl Palindrome { pub fn palindrome_products(min: u64, max: u64) -> Option<(Palindrome, Palindrome)> { todo!( - "returns the minimum and maximum number of palindromes of the products of two factors in the range {min} to {max}" + "returns the minimum palindrome and maximum palindrome of the products of two factors in the range {min} to {max}" ); } From 0b6eb4fae96dc3dbaf242a3c0487bc7f1ec61cf5 Mon Sep 17 00:00:00 2001 From: wetzo <97407868+0xwetzo@users.noreply.github.com> Date: Mon, 10 Feb 2025 19:03:35 +0100 Subject: [PATCH 369/436] Luhn trait: update rust book url (#2014) Current rust book url is no longer valid. Updated with the active url of the trait page. --- exercises/practice/luhn-trait/.docs/instructions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/luhn-trait/.docs/instructions.md b/exercises/practice/luhn-trait/.docs/instructions.md index 7e203cbcb..cbbd4f98e 100644 --- a/exercises/practice/luhn-trait/.docs/instructions.md +++ b/exercises/practice/luhn-trait/.docs/instructions.md @@ -20,4 +20,4 @@ In "Luhn: Using the From Trait" you implemented a From trait, which also require Instead of creating a Struct just to perform the validation, what if you validated the primitives (i.e, String, u8, etc.) themselves? -In this exercise you'll create and implement a custom [trait](https://doc.rust-lang.org/book/2018-edition/ch10-02-traits.html) that performs the validation. +In this exercise you'll create and implement a custom [trait](https://doc.rust-lang.org/book/ch10-02-traits.html) that performs the validation. From 16697c956e6149c90039b0a311e8330a9ae937a8 Mon Sep 17 00:00:00 2001 From: ellnix Date: Wed, 12 Feb 2025 14:23:43 +0100 Subject: [PATCH 370/436] bowling: Sync tests and their names to canonical_data.json (#2015) Negative value test is disabled since Rust's type system makes failing that a compile-time error. [no important files changed] --- exercises/practice/bowling/.meta/tests.toml | 89 ++++++++++++++++++++- exercises/practice/bowling/tests/bowling.rs | 56 +++++++------ 2 files changed, 119 insertions(+), 26 deletions(-) diff --git a/exercises/practice/bowling/.meta/tests.toml b/exercises/practice/bowling/.meta/tests.toml index 7c35295ba..9b1bb4be8 100644 --- a/exercises/practice/bowling/.meta/tests.toml +++ b/exercises/practice/bowling/.meta/tests.toml @@ -1,10 +1,32 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[656ae006-25c2-438c-a549-f338e7ec7441] +description = "should be able to score a game with all zeros" + +[f85dcc56-cd6b-4875-81b3-e50921e3597b] +description = "should be able to score a game with no strikes or spares" + +[d1f56305-3ac2-4fe0-8645-0b37e3073e20] +description = "a spare followed by zeros is worth ten points" + +[0b8c8bb7-764a-4287-801a-f9e9012f8be4] +description = "points scored in the roll after a spare are counted twice" [4d54d502-1565-4691-84cd-f29a09c65bea] description = "consecutive spares each get a one roll bonus" +[e5c9cf3d-abbe-4b74-ad48-34051b2b08c0] +description = "a spare in the last frame gets a one roll bonus that is counted once" + [75269642-2b34-4b72-95a4-9be28ab16902] description = "a strike earns ten points in a frame with a single roll" @@ -14,8 +36,69 @@ description = "points scored in the two rolls after a strike are counted twice a [1635e82b-14ec-4cd1-bce4-4ea14bd13a49] description = "consecutive strikes each get the two roll bonus" +[e483e8b6-cb4b-4959-b310-e3982030d766] +description = "a strike in the last frame gets a two roll bonus that is counted once" + +[9d5c87db-84bc-4e01-8e95-53350c8af1f8] +description = "rolling a spare with the two roll bonus does not get a bonus roll" + +[576faac1-7cff-4029-ad72-c16bcada79b5] +description = "strikes with the two roll bonus do not get bonus rolls" + +[efb426ec-7e15-42e6-9b96-b4fca3ec2359] +description = "last two strikes followed by only last bonus with non strike points" + [72e24404-b6c6-46af-b188-875514c0377b] description = "a strike with the one roll bonus after a spare in the last frame does not get a bonus" +[62ee4c72-8ee8-4250-b794-234f1fec17b1] +description = "all strikes is a perfect game" + +[1245216b-19c6-422c-b34b-6e4012d7459f] +description = "rolls cannot score negative points" +include = false + +[5fcbd206-782c-4faa-8f3a-be5c538ba841] +description = "a roll cannot score more than 10 points" + +[fb023c31-d842-422d-ad7e-79ce1db23c21] +description = "two rolls in a frame cannot score more than 10 points" + +[6082d689-d677-4214-80d7-99940189381b] +description = "bonus roll after a strike in the last frame cannot score more than 10 points" + +[e9565fe6-510a-4675-ba6b-733a56767a45] +description = "two bonus rolls after a strike in the last frame cannot score more than 10 points" + +[2f6acf99-448e-4282-8103-0b9c7df99c3d] +description = "two bonus rolls after a strike in the last frame can score more than 10 points if one is a strike" + +[6380495a-8bc4-4cdb-a59f-5f0212dbed01] +description = "the second bonus rolls after a strike in the last frame cannot be a strike if the first one is not a strike" + +[2b2976ea-446c-47a3-9817-42777f09fe7e] +description = "second bonus roll after a strike in the last frame cannot score more than 10 points" + +[29220245-ac8d-463d-bc19-98a94cfada8a] +description = "an unstarted game cannot be scored" + +[4473dc5d-1f86-486f-bf79-426a52ddc955] +description = "an incomplete game cannot be scored" + +[2ccb8980-1b37-4988-b7d1-e5701c317df3] +description = "cannot roll if game already has ten frames" + +[4864f09b-9df3-4b65-9924-c595ed236f1b] +description = "bonus rolls for a strike in the last frame must be rolled before score can be calculated" + +[537f4e37-4b51-4d1c-97e2-986eb37b2ac1] +description = "both bonus rolls for a strike in the last frame must be rolled before score can be calculated" + +[8134e8c1-4201-4197-bf9f-1431afcde4b9] +description = "bonus roll for a spare in the last frame must be rolled before score can be calculated" + [9d4a9a55-134a-4bad-bae8-3babf84bd570] description = "cannot roll after bonus roll for spare" + +[d3e02652-a799-4ae3-b53b-68582cc604be] +description = "cannot roll after bonus rolls for strike" diff --git a/exercises/practice/bowling/tests/bowling.rs b/exercises/practice/bowling/tests/bowling.rs index 2c46d15df..140a00527 100644 --- a/exercises/practice/bowling/tests/bowling.rs +++ b/exercises/practice/bowling/tests/bowling.rs @@ -8,7 +8,7 @@ fn roll_returns_a_result() { #[test] #[ignore] -fn you_cannot_roll_more_than_ten_pins_in_a_single_roll() { +fn a_roll_cannot_score_more_than_10_points() { let mut game = BowlingGame::new(); assert_eq!(game.roll(11), Err(Error::NotEnoughPinsLeft)); @@ -16,7 +16,7 @@ fn you_cannot_roll_more_than_ten_pins_in_a_single_roll() { #[test] #[ignore] -fn a_game_score_is_some_if_ten_frames_have_been_rolled() { +fn should_be_able_to_score_a_game_with_all_zeros() { let mut game = BowlingGame::new(); for _ in 0..10 { @@ -29,7 +29,7 @@ fn a_game_score_is_some_if_ten_frames_have_been_rolled() { #[test] #[ignore] -fn you_cannot_score_a_game_with_no_rolls() { +fn an_unstarted_game_cannot_be_scored() { let game = BowlingGame::new(); assert_eq!(game.score(), None); @@ -37,7 +37,7 @@ fn you_cannot_score_a_game_with_no_rolls() { #[test] #[ignore] -fn a_game_score_is_none_if_fewer_than_ten_frames_have_been_rolled() { +fn an_incomplete_game_cannot_be_scored() { let mut game = BowlingGame::new(); for _ in 0..9 { @@ -50,7 +50,7 @@ fn a_game_score_is_none_if_fewer_than_ten_frames_have_been_rolled() { #[test] #[ignore] -fn a_roll_is_err_if_the_game_is_done() { +fn cannot_roll_if_game_already_has_ten_frames() { let mut game = BowlingGame::new(); for _ in 0..10 { @@ -75,7 +75,7 @@ fn twenty_zero_pin_rolls_scores_zero() { #[test] #[ignore] -fn ten_frames_without_a_strike_or_spare() { +fn should_be_able_to_score_a_game_with_no_strikes_or_spares() { let mut game = BowlingGame::new(); for _ in 0..10 { @@ -88,7 +88,7 @@ fn ten_frames_without_a_strike_or_spare() { #[test] #[ignore] -fn spare_in_the_first_frame_followed_by_zeros() { +fn a_spare_followed_by_zeros_is_worth_ten_points() { let mut game = BowlingGame::new(); let _ = game.roll(6); @@ -103,7 +103,7 @@ fn spare_in_the_first_frame_followed_by_zeros() { #[test] #[ignore] -fn points_scored_in_the_roll_after_a_spare_are_counted_twice_as_a_bonus() { +fn points_scored_in_the_roll_after_a_spare_are_counted_twice() { let mut game = BowlingGame::new(); let _ = game.roll(6); @@ -137,7 +137,7 @@ fn consecutive_spares_each_get_a_one_roll_bonus() { #[test] #[ignore] -fn if_the_last_frame_is_a_spare_you_get_one_extra_roll_that_is_scored_once() { +fn a_spare_in_the_last_frame_gets_a_one_roll_bonus_that_is_counted_once() { let mut game = BowlingGame::new(); for _ in 0..18 { @@ -217,7 +217,7 @@ fn a_strike_in_the_last_frame_earns_a_two_roll_bonus_that_is_counted_once() { #[test] #[ignore] -fn a_spare_with_the_two_roll_bonus_does_not_get_a_bonus_roll() { +fn rolling_a_spare_with_the_two_roll_bonus_does_not_get_a_bonus_roll() { let mut game = BowlingGame::new(); for _ in 0..18 { @@ -265,7 +265,7 @@ fn a_strike_with_the_one_roll_bonus_after_a_spare_in_the_last_frame_does_not_get #[test] #[ignore] -fn all_strikes_is_a_perfect_score_of_300() { +fn all_strikes_is_a_perfect_game() { let mut game = BowlingGame::new(); for _ in 0..12 { @@ -277,7 +277,7 @@ fn all_strikes_is_a_perfect_score_of_300() { #[test] #[ignore] -fn you_cannot_roll_more_than_ten_pins_in_a_single_frame() { +fn two_rolls_in_a_frame_cannot_score_more_than_10_points() { let mut game = BowlingGame::new(); assert!(game.roll(5).is_ok()); @@ -286,7 +286,7 @@ fn you_cannot_roll_more_than_ten_pins_in_a_single_frame() { #[test] #[ignore] -fn first_bonus_ball_after_a_final_strike_cannot_score_an_invalid_number_of_pins() { +fn bonus_roll_after_a_strike_in_the_last_frame_cannot_score_more_than_10_points() { let mut game = BowlingGame::new(); for _ in 0..18 { @@ -300,7 +300,7 @@ fn first_bonus_ball_after_a_final_strike_cannot_score_an_invalid_number_of_pins( #[test] #[ignore] -fn the_two_balls_after_a_final_strike_cannot_score_an_invalid_number_of_pins() { +fn two_bonus_rolls_after_a_strike_in_the_last_frame_cannot_score_more_than_10_points() { let mut game = BowlingGame::new(); for _ in 0..18 { @@ -315,7 +315,8 @@ fn the_two_balls_after_a_final_strike_cannot_score_an_invalid_number_of_pins() { #[test] #[ignore] -fn the_two_balls_after_a_final_strike_can_be_a_strike_and_non_strike() { +fn two_bonus_rolls_after_a_strike_in_the_last_frame_can_score_more_than_10_points_if_one_is_a_strike( +) { let mut game = BowlingGame::new(); for _ in 0..18 { @@ -330,7 +331,8 @@ fn the_two_balls_after_a_final_strike_can_be_a_strike_and_non_strike() { #[test] #[ignore] -fn the_two_balls_after_a_final_strike_cannot_be_a_non_strike_followed_by_a_strike() { +fn the_second_bonus_rolls_after_a_strike_in_the_last_frame_cannot_be_a_strike_if_the_first_one_is_not_a_strike( +) { let mut game = BowlingGame::new(); for _ in 0..18 { @@ -345,8 +347,7 @@ fn the_two_balls_after_a_final_strike_cannot_be_a_non_strike_followed_by_a_strik #[test] #[ignore] -fn second_bonus_ball_after_a_final_strike_cannot_score_an_invalid_number_of_pins_even_if_first_is_strike( -) { +fn second_bonus_roll_after_a_strike_in_the_last_frame_cannot_score_more_than_10_points() { let mut game = BowlingGame::new(); for _ in 0..18 { @@ -361,7 +362,7 @@ fn second_bonus_ball_after_a_final_strike_cannot_score_an_invalid_number_of_pins #[test] #[ignore] -fn if_the_last_frame_is_a_strike_you_cannot_score_before_the_extra_rolls_are_taken() { +fn bonus_rolls_for_a_strike_in_the_last_frame_must_be_rolled_before_score_can_be_calculated() { let mut game = BowlingGame::new(); for _ in 0..18 { @@ -371,19 +372,28 @@ fn if_the_last_frame_is_a_strike_you_cannot_score_before_the_extra_rolls_are_tak let _ = game.roll(10); assert_eq!(game.score(), None); +} - let _ = game.roll(10); +#[test] +#[ignore] +fn both_bonus_rolls_for_a_strike_in_the_last_frame_must_be_rolled_before_score_can_be_calculated() { + let mut game = BowlingGame::new(); - assert_eq!(game.score(), None); + for _ in 0..18 { + let _ = game.roll(0); + } let _ = game.roll(10); + let _ = game.roll(10); + assert_eq!(game.score(), None); + let _ = game.roll(10); assert!(game.score().is_some()); } #[test] #[ignore] -fn if_the_last_frame_is_a_spare_you_cannot_create_a_score_before_extra_roll_is_taken() { +fn bonus_roll_for_a_spare_in_the_last_frame_must_be_rolled_before_score_can_be_calculated() { let mut game = BowlingGame::new(); for _ in 0..18 { @@ -402,7 +412,7 @@ fn if_the_last_frame_is_a_spare_you_cannot_create_a_score_before_extra_roll_is_t #[test] #[ignore] -fn cannot_roll_after_bonus_roll_for_spare() { +fn cannot_roll_after_bonus_rolls_for_strike() { let mut game = BowlingGame::new(); for _ in 0..9 { From c5b8fdcac43029e9a7205d33b9c54c6ac1743e81 Mon Sep 17 00:00:00 2001 From: ellnix Date: Thu, 13 Feb 2025 11:06:27 +0100 Subject: [PATCH 371/436] simple-cipher: Sync tests.toml (#2016) Confirmed that all tests are defined, creating a Tera template is not really possible since canonical_data has some fields that are expressions rather than values. Besides many of the test cases have hilariously long descriptions which would not look good as function names. Link to canonical_data.json for convenience: https://github.com/exercism/problem-specifications/blob/47b3bb2a1c88bf93d540916477a3b71ff94e9964/exercises/simple-cipher/canonical-data.json [no important files changed] --- .../practice/simple-cipher/.meta/tests.toml | 49 +++++++++++++++++-- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/exercises/practice/simple-cipher/.meta/tests.toml b/exercises/practice/simple-cipher/.meta/tests.toml index be690e975..77e6571e4 100644 --- a/exercises/practice/simple-cipher/.meta/tests.toml +++ b/exercises/practice/simple-cipher/.meta/tests.toml @@ -1,3 +1,46 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[b8bdfbe1-bea3-41bb-a999-b41403f2b15d] +description = "Random key cipher -> Can encode" + +[3dff7f36-75db-46b4-ab70-644b3f38b81c] +description = "Random key cipher -> Can decode" + +[8143c684-6df6-46ba-bd1f-dea8fcb5d265] +description = "Random key cipher -> Is reversible. I.e., if you apply decode in a encoded result, you must see the same plaintext encode parameter as a result of the decode method" + +[defc0050-e87d-4840-85e4-51a1ab9dd6aa] +description = "Random key cipher -> Key is made only of lowercase letters" + +[565e5158-5b3b-41dd-b99d-33b9f413c39f] +description = "Substitution cipher -> Can encode" + +[d44e4f6a-b8af-4e90-9d08-fd407e31e67b] +description = "Substitution cipher -> Can decode" + +[70a16473-7339-43df-902d-93408c69e9d1] +description = "Substitution cipher -> Is reversible. I.e., if you apply decode in a encoded result, you must see the same plaintext encode parameter as a result of the decode method" + +[69a1458b-92a6-433a-a02d-7beac3ea91f9] +description = "Substitution cipher -> Can double shift encode" + +[21d207c1-98de-40aa-994f-86197ae230fb] +description = "Substitution cipher -> Can wrap on encode" + +[a3d7a4d7-24a9-4de6-bdc4-a6614ced0cb3] +description = "Substitution cipher -> Can wrap on decode" + +[e31c9b8c-8eb6-45c9-a4b5-8344a36b9641] +description = "Substitution cipher -> Can encode messages longer than the key" + +[93cfaae0-17da-4627-9a04-d6d1e1be52e3] +description = "Substitution cipher -> Can decode messages longer than the key" From e99b2959672d33a202cf48274ac39ba1a5c34fae Mon Sep 17 00:00:00 2001 From: ellnix Date: Thu, 13 Feb 2025 11:06:38 +0100 Subject: [PATCH 372/436] diamond: Sync tests and generate them with Tera (#2017) [no important files changed] --- .../practice/diamond/.meta/test_template.tera | 25 +++++++++++++++++ exercises/practice/diamond/.meta/tests.toml | 28 +++++++++++++++++-- exercises/practice/diamond/tests/diamond.rs | 10 +++---- 3 files changed, 55 insertions(+), 8 deletions(-) create mode 100644 exercises/practice/diamond/.meta/test_template.tera diff --git a/exercises/practice/diamond/.meta/test_template.tera b/exercises/practice/diamond/.meta/test_template.tera new file mode 100644 index 000000000..1b2188ede --- /dev/null +++ b/exercises/practice/diamond/.meta/test_template.tera @@ -0,0 +1,25 @@ +use diamond::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + {# + rustfmt will inline array literals under a minimum length + For us that means up to letter E + We also exclude A to keep it inlined + #} + {%- if test.input.letter is matching("[B-D]") %} + #[rustfmt::skip] + {%- endif %} + assert_eq!( + get_diamond('{{ test.input.letter }}'), + vec![ + {%- for line in test.expected %} + "{{ line }}"{% if test.expected | length > 1 %},{% endif %} + {%- endfor %} + ] + ); +} +{% endfor %} + diff --git a/exercises/practice/diamond/.meta/tests.toml b/exercises/practice/diamond/.meta/tests.toml index be690e975..4e7802ec8 100644 --- a/exercises/practice/diamond/.meta/tests.toml +++ b/exercises/practice/diamond/.meta/tests.toml @@ -1,3 +1,25 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[202fb4cc-6a38-4883-9193-a29d5cb92076] +description = "Degenerate case with a single 'A' row" + +[bd6a6d78-9302-42e9-8f60-ac1461e9abae] +description = "Degenerate case with no row containing 3 distinct groups of spaces" + +[af8efb49-14ed-447f-8944-4cc59ce3fd76] +description = "Smallest non-degenerate case with odd diamond side length" + +[e0c19a95-9888-4d05-86a0-fa81b9e70d1d] +description = "Smallest non-degenerate case with even diamond side length" + +[82ea9aa9-4c0e-442a-b07e-40204e925944] +description = "Largest possible diamond" diff --git a/exercises/practice/diamond/tests/diamond.rs b/exercises/practice/diamond/tests/diamond.rs index 2f18fc8a0..ce1bb2ade 100644 --- a/exercises/practice/diamond/tests/diamond.rs +++ b/exercises/practice/diamond/tests/diamond.rs @@ -1,13 +1,13 @@ use diamond::*; #[test] -fn a() { +fn degenerate_case_with_a_single_a_row() { assert_eq!(get_diamond('A'), vec!["A"]); } #[test] #[ignore] -fn b() { +fn degenerate_case_with_no_row_containing_3_distinct_groups_of_spaces() { #[rustfmt::skip] assert_eq!( get_diamond('B'), @@ -21,7 +21,7 @@ fn b() { #[test] #[ignore] -fn c() { +fn smallest_non_degenerate_case_with_odd_diamond_side_length() { #[rustfmt::skip] assert_eq!( get_diamond('C'), @@ -37,7 +37,7 @@ fn c() { #[test] #[ignore] -fn d() { +fn smallest_non_degenerate_case_with_even_diamond_side_length() { #[rustfmt::skip] assert_eq!( get_diamond('D'), @@ -55,7 +55,7 @@ fn d() { #[test] #[ignore] -fn e() { +fn largest_possible_diamond() { assert_eq!( get_diamond('Z'), vec![ From 5fbf043087b3f92546c93f047e1d4dc91880fc57 Mon Sep 17 00:00:00 2001 From: ellnix Date: Thu, 13 Feb 2025 19:44:58 +0100 Subject: [PATCH 373/436] etl: Sync tests and generate them with Tera (#2018) Also changed the tests to use `BTreeMap::from` instead of the custom functions for better clarity. [no important files changed] --- .../practice/etl/.meta/test_template.tera | 29 +++++++++++++++++ exercises/practice/etl/.meta/tests.toml | 25 +++++++++++++-- exercises/practice/etl/tests/etl.rs | 32 +++++++------------ 3 files changed, 63 insertions(+), 23 deletions(-) create mode 100644 exercises/practice/etl/.meta/test_template.tera diff --git a/exercises/practice/etl/.meta/test_template.tera b/exercises/practice/etl/.meta/test_template.tera new file mode 100644 index 000000000..40e64e518 --- /dev/null +++ b/exercises/practice/etl/.meta/test_template.tera @@ -0,0 +1,29 @@ +use std::collections::BTreeMap; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let input = BTreeMap::from( + [ + {% for value, letters in test.input.legacy %} + ({{value}}, vec![ + {% for letter in letters %} + '{{letter}}', + {% endfor %} + ]), + {% endfor %} + ] + ); + + let expected = BTreeMap::from( + [ + {% for letter, value in test.expected %} + ('{{letter}}', {{ value }}), + {% endfor %} + ] + ); + + assert_eq!(expected, etl::transform(&input)); +} +{% endfor %} diff --git a/exercises/practice/etl/.meta/tests.toml b/exercises/practice/etl/.meta/tests.toml index be690e975..e9371078c 100644 --- a/exercises/practice/etl/.meta/tests.toml +++ b/exercises/practice/etl/.meta/tests.toml @@ -1,3 +1,22 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[78a7a9f9-4490-4a47-8ee9-5a38bb47d28f] +description = "single letter" + +[60dbd000-451d-44c7-bdbb-97c73ac1f497] +description = "single score with multiple letters" + +[f5c5de0c-301f-4fdd-a0e5-df97d4214f54] +description = "multiple scores with multiple letters" + +[5db8ea89-ecb4-4dcd-902f-2b418cc87b9d] +description = "multiple scores with differing numbers of letters" diff --git a/exercises/practice/etl/tests/etl.rs b/exercises/practice/etl/tests/etl.rs index e1324990d..7e973d5a9 100644 --- a/exercises/practice/etl/tests/etl.rs +++ b/exercises/practice/etl/tests/etl.rs @@ -1,38 +1,38 @@ use std::collections::BTreeMap; #[test] -fn transform_one_value() { - let input = input_from(&[(1, vec!['A'])]); +fn single_letter() { + let input = BTreeMap::from([(1, vec!['A'])]); - let expected = expected_from(&[('a', 1)]); + let expected = BTreeMap::from([('a', 1)]); assert_eq!(expected, etl::transform(&input)); } #[test] #[ignore] -fn transform_more_values() { - let input = input_from(&[(1, vec!['A', 'E', 'I', 'O', 'U'])]); +fn single_score_with_multiple_letters() { + let input = BTreeMap::from([(1, vec!['A', 'E', 'I', 'O', 'U'])]); - let expected = expected_from(&[('a', 1), ('e', 1), ('i', 1), ('o', 1), ('u', 1)]); + let expected = BTreeMap::from([('a', 1), ('e', 1), ('i', 1), ('o', 1), ('u', 1)]); assert_eq!(expected, etl::transform(&input)); } #[test] #[ignore] -fn more_keys() { - let input = input_from(&[(1, vec!['A', 'E']), (2, vec!['D', 'G'])]); +fn multiple_scores_with_multiple_letters() { + let input = BTreeMap::from([(1, vec!['A', 'E']), (2, vec!['D', 'G'])]); - let expected = expected_from(&[('a', 1), ('e', 1), ('d', 2), ('g', 2)]); + let expected = BTreeMap::from([('a', 1), ('d', 2), ('e', 1), ('g', 2)]); assert_eq!(expected, etl::transform(&input)); } #[test] #[ignore] -fn full_dataset() { - let input = input_from(&[ +fn multiple_scores_with_differing_numbers_of_letters() { + let input = BTreeMap::from([ (1, vec!['A', 'E', 'I', 'O', 'U', 'L', 'N', 'R', 'S', 'T']), (2, vec!['D', 'G']), (3, vec!['B', 'C', 'M', 'P']), @@ -42,7 +42,7 @@ fn full_dataset() { (10, vec!['Q', 'Z']), ]); - let expected = expected_from(&[ + let expected = BTreeMap::from([ ('a', 1), ('b', 3), ('c', 3), @@ -73,11 +73,3 @@ fn full_dataset() { assert_eq!(expected, etl::transform(&input)); } - -fn input_from(v: &[(i32, Vec)]) -> BTreeMap> { - v.iter().cloned().collect() -} - -fn expected_from(v: &[(char, i32)]) -> BTreeMap { - v.iter().cloned().collect() -} From c791d951d0667c2ce2200e3fa19f50ae1562db14 Mon Sep 17 00:00:00 2001 From: ellnix Date: Fri, 14 Feb 2025 21:52:08 +0100 Subject: [PATCH 374/436] diffie-hellman: deprecate (#2020) See brief discussion in #2019 --- config.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config.json b/config.json index b2b8a98f2..31f02aa54 100644 --- a/config.json +++ b/config.json @@ -495,7 +495,8 @@ "difficulty": 1, "topics": [ "math" - ] + ], + "status": "deprecated" }, { "slug": "series", From 9105fe19566d9d4e99fbb071fc885608d31c4906 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Sun, 16 Feb 2025 10:10:20 +0100 Subject: [PATCH 375/436] sync problem-specifications (#2024) --- ...ut_pinned_problem_specifications_commit.sh | 2 +- exercises/practice/bob/.meta/tests.toml | 5 ++ exercises/practice/bob/tests/bob.rs | 18 +++---- exercises/practice/forth/.meta/tests.toml | 18 +++++++ exercises/practice/forth/tests/forth.rs | 48 +++++++++++++++++++ .../practice/grep/.meta/test_template.tera | 2 +- exercises/practice/pig-latin/.meta/tests.toml | 3 ++ .../practice/pig-latin/tests/pig-latin.rs | 9 ++++ 8 files changed, 94 insertions(+), 11 deletions(-) diff --git a/bin/checkout_pinned_problem_specifications_commit.sh b/bin/checkout_pinned_problem_specifications_commit.sh index 7f29be359..3acae44dd 100755 --- a/bin/checkout_pinned_problem_specifications_commit.sh +++ b/bin/checkout_pinned_problem_specifications_commit.sh @@ -3,7 +3,7 @@ set -euo pipefail cd "$(git rev-parse --show-toplevel)" -PINNED_COMMIT_HASH="685ec55d9388937bbb3cc836b52b3ce27f208f37" +PINNED_COMMIT_HASH="47b3bb2a1c88bf93d540916477a3b71ff94e9964" dir="$(./bin/get_problem_specifications_dir.sh)" diff --git a/exercises/practice/bob/.meta/tests.toml b/exercises/practice/bob/.meta/tests.toml index ea47d6bb0..5299e2895 100644 --- a/exercises/practice/bob/.meta/tests.toml +++ b/exercises/practice/bob/.meta/tests.toml @@ -71,6 +71,7 @@ description = "alternate silence" [66953780-165b-4e7e-8ce3-4bcb80b6385a] description = "multiple line question" +include = false [5371ef75-d9ea-4103-bcfa-2da973ddec1b] description = "starting with whitespace" @@ -83,3 +84,7 @@ description = "other whitespace" [12983553-8601-46a8-92fa-fcaa3bc4a2a0] description = "non-question ending with whitespace" + +[2c7278ac-f955-4eb4-bf8f-e33eb4116a15] +description = "multiple line question" +reimplements = "66953780-165b-4e7e-8ce3-4bcb80b6385a" diff --git a/exercises/practice/bob/tests/bob.rs b/exercises/practice/bob/tests/bob.rs index 4fa71681c..c94ef8734 100644 --- a/exercises/practice/bob/tests/bob.rs +++ b/exercises/practice/bob/tests/bob.rs @@ -131,15 +131,6 @@ fn alternate_silence() { assert_eq!(reply("\t\t\t\t\t\t\t\t\t\t"), "Fine. Be that way!"); } -#[test] -#[ignore] -fn multiple_line_question() { - assert_eq!( - reply("\nDoes this cryogenic chamber make me look fat?\nNo."), - "Whatever." - ); -} - #[test] #[ignore] fn starting_with_whitespace() { @@ -166,3 +157,12 @@ fn non_question_ending_with_whitespace() { "Whatever." ); } + +#[test] +#[ignore] +fn multiple_line_question() { + assert_eq!( + reply("\nDoes this cryogenic chamber make\n me look fat?"), + "Sure." + ); +} diff --git a/exercises/practice/forth/.meta/tests.toml b/exercises/practice/forth/.meta/tests.toml index c9c1d6377..d1e146a1e 100644 --- a/exercises/practice/forth/.meta/tests.toml +++ b/exercises/practice/forth/.meta/tests.toml @@ -24,6 +24,9 @@ description = "addition -> errors if there is nothing on the stack" [06efb9a4-817a-435e-b509-06166993c1b8] description = "addition -> errors if there is only one value on the stack" +[1e07a098-c5fa-4c66-97b2-3c81205dbc2f] +description = "addition -> more than two values on the stack" + [09687c99-7bbc-44af-8526-e402f997ccbf] description = "subtraction -> can subtract two numbers" @@ -33,6 +36,9 @@ description = "subtraction -> errors if there is nothing on the stack" [b3cee1b2-9159-418a-b00d-a1bb3765c23b] description = "subtraction -> errors if there is only one value on the stack" +[2c8cc5ed-da97-4cb1-8b98-fa7b526644f4] +description = "subtraction -> more than two values on the stack" + [5df0ceb5-922e-401f-974d-8287427dbf21] description = "multiplication -> can multiply two numbers" @@ -42,6 +48,9 @@ description = "multiplication -> errors if there is nothing on the stack" [8ba4b432-9f94-41e0-8fae-3b3712bd51b3] description = "multiplication -> errors if there is only one value on the stack" +[5cd085b5-deb1-43cc-9c17-6b1c38bc9970] +description = "multiplication -> more than two values on the stack" + [e74c2204-b057-4cff-9aa9-31c7c97a93f5] description = "division -> can divide two numbers" @@ -57,12 +66,21 @@ description = "division -> errors if there is nothing on the stack" [d5547f43-c2ff-4d5c-9cb0-2a4f6684c20d] description = "division -> errors if there is only one value on the stack" +[f224f3e0-b6b6-4864-81de-9769ecefa03f] +description = "division -> more than two values on the stack" + [ee28d729-6692-4a30-b9be-0d830c52a68c] description = "combined arithmetic -> addition and subtraction" [40b197da-fa4b-4aca-a50b-f000d19422c1] description = "combined arithmetic -> multiplication and division" +[f749b540-53aa-458e-87ec-a70797eddbcb] +description = "combined arithmetic -> multiplication and addition" + +[c8e5a4c2-f9bf-4805-9a35-3c3314e4989a] +description = "combined arithmetic -> addition and multiplication" + [c5758235-6eef-4bf6-ab62-c878e50b9957] description = "dup -> copies a value on the stack" diff --git a/exercises/practice/forth/tests/forth.rs b/exercises/practice/forth/tests/forth.rs index c6ed17ed5..887d381df 100644 --- a/exercises/practice/forth/tests/forth.rs +++ b/exercises/practice/forth/tests/forth.rs @@ -41,6 +41,14 @@ mod addition { let mut f = Forth::new(); assert_eq!(f.eval("1 +"), Err(Error::StackUnderflow)); } + + #[test] + #[ignore] + fn more_than_two_values_on_the_stack() { + let mut f = Forth::new(); + assert!(f.eval("1 2 3 +").is_ok()); + assert_eq!(f.stack(), [1, 5]); + } } mod subtraction { @@ -67,6 +75,14 @@ mod subtraction { let mut f = Forth::new(); assert_eq!(f.eval("1 -"), Err(Error::StackUnderflow)); } + + #[test] + #[ignore] + fn more_than_two_values_on_the_stack() { + let mut f = Forth::new(); + assert!(f.eval("1 12 3 -").is_ok()); + assert_eq!(f.stack(), [1, 9]); + } } mod multiplication { @@ -93,6 +109,14 @@ mod multiplication { let mut f = Forth::new(); assert_eq!(f.eval("1 *"), Err(Error::StackUnderflow)); } + + #[test] + #[ignore] + fn more_than_two_values_on_the_stack() { + let mut f = Forth::new(); + assert!(f.eval("1 2 3 *").is_ok()); + assert_eq!(f.stack(), [1, 6]); + } } mod division { @@ -134,6 +158,14 @@ mod division { let mut f = Forth::new(); assert_eq!(f.eval("1 /"), Err(Error::StackUnderflow)); } + + #[test] + #[ignore] + fn more_than_two_values_on_the_stack() { + let mut f = Forth::new(); + assert!(f.eval("1 12 3 /").is_ok()); + assert_eq!(f.stack(), [1, 4]); + } } mod combined_arithmetic { @@ -154,6 +186,22 @@ mod combined_arithmetic { assert!(f.eval("2 4 * 3 /").is_ok()); assert_eq!(f.stack(), [2]); } + + #[test] + #[ignore] + fn multiplication_and_addition() { + let mut f = Forth::new(); + assert!(f.eval("1 3 4 * +").is_ok()); + assert_eq!(f.stack(), [13]); + } + + #[test] + #[ignore] + fn addition_and_multiplication() { + let mut f = Forth::new(); + assert!(f.eval("1 3 4 + *").is_ok()); + assert_eq!(f.stack(), [7]); + } } mod dup { diff --git a/exercises/practice/grep/.meta/test_template.tera b/exercises/practice/grep/.meta/test_template.tera index e585a4ebd..2fce36eeb 100644 --- a/exercises/practice/grep/.meta/test_template.tera +++ b/exercises/practice/grep/.meta/test_template.tera @@ -132,7 +132,7 @@ impl<'a> Files<'a> { } } -impl<'a> Drop for Files<'a> { +impl Drop for Files<'_> { fn drop(&mut self) { for file_name in self.file_names { std::fs::remove_file(file_name) diff --git a/exercises/practice/pig-latin/.meta/tests.toml b/exercises/practice/pig-latin/.meta/tests.toml index c29168c5e..d524305b4 100644 --- a/exercises/practice/pig-latin/.meta/tests.toml +++ b/exercises/practice/pig-latin/.meta/tests.toml @@ -39,6 +39,9 @@ description = "first letter and ay are moved to the end of words that start with [bce94a7a-a94e-4e2b-80f4-b2bb02e40f71] description = "first letter and ay are moved to the end of words that start with consonants -> word beginning with q without a following u" +[e59dbbe8-ccee-4619-a8e9-ce017489bfc0] +description = "first letter and ay are moved to the end of words that start with consonants -> word beginning with consonant and vowel containing qu" + [c01e049a-e3e2-451c-bf8e-e2abb7e438b8] description = "some letter clusters are treated like a single consonant -> word beginning with ch" diff --git a/exercises/practice/pig-latin/tests/pig-latin.rs b/exercises/practice/pig-latin/tests/pig-latin.rs index c1acf29e3..55eb82ca3 100644 --- a/exercises/practice/pig-latin/tests/pig-latin.rs +++ b/exercises/practice/pig-latin/tests/pig-latin.rs @@ -89,6 +89,15 @@ fn word_beginning_with_q_without_a_following_u() { assert_eq!(output, expected); } +#[test] +#[ignore] +fn word_beginning_with_consonant_and_vowel_containing_qu() { + let input = "liquid"; + let output = translate(input); + let expected = "iquidlay"; + assert_eq!(output, expected); +} + #[test] #[ignore] fn word_beginning_with_ch() { From 58bc309dc92f061ff6322205baadceae6377a5e8 Mon Sep 17 00:00:00 2001 From: ellnix Date: Mon, 17 Feb 2025 22:45:14 +0100 Subject: [PATCH 376/436] list-ops: add exercise (#2023) Co-Authored-By: Remo Senekowitsch --- config.json | 13 + .../list-ops/.docs/instructions.append.md | 13 + .../practice/list-ops/.docs/instructions.md | 19 ++ exercises/practice/list-ops/.gitignore | 8 + exercises/practice/list-ops/.meta/config.json | 16 + exercises/practice/list-ops/.meta/example.rs | 154 +++++++++ .../list-ops/.meta/test_template.tera | 78 +++++ exercises/practice/list-ops/.meta/tests.toml | 106 ++++++ exercises/practice/list-ops/Cargo.toml | 4 + exercises/practice/list-ops/src/lib.rs | 69 ++++ exercises/practice/list-ops/tests/list-ops.rs | 301 ++++++++++++++++++ .../tests/no_underscore_prefixed_idents.rs | 1 + 12 files changed, 782 insertions(+) create mode 100644 exercises/practice/list-ops/.docs/instructions.append.md create mode 100644 exercises/practice/list-ops/.docs/instructions.md create mode 100644 exercises/practice/list-ops/.gitignore create mode 100644 exercises/practice/list-ops/.meta/config.json create mode 100644 exercises/practice/list-ops/.meta/example.rs create mode 100644 exercises/practice/list-ops/.meta/test_template.tera create mode 100644 exercises/practice/list-ops/.meta/tests.toml create mode 100644 exercises/practice/list-ops/Cargo.toml create mode 100644 exercises/practice/list-ops/src/lib.rs create mode 100644 exercises/practice/list-ops/tests/list-ops.rs diff --git a/config.json b/config.json index 31f02aa54..36946adf0 100644 --- a/config.json +++ b/config.json @@ -1194,6 +1194,19 @@ "traits" ] }, + { + "slug": "list-ops", + "name": "List Ops", + "uuid": "4b411941-d272-4b95-8c40-5816fecf27a2", + "practices": [], + "prerequisites": [], + "difficulty": 4, + "topics": [ + "generics", + "iterators", + "traits" + ] + }, { "slug": "phone-number", "name": "Phone Number", diff --git a/exercises/practice/list-ops/.docs/instructions.append.md b/exercises/practice/list-ops/.docs/instructions.append.md new file mode 100644 index 000000000..cb2de8100 --- /dev/null +++ b/exercises/practice/list-ops/.docs/instructions.append.md @@ -0,0 +1,13 @@ +# Instructions append + +## Implementing your own list operations + +Rust has a rich iterator system, using them to solve this exercise is trivial. +Please attempt to solve the exercise without reaching for iterator methods like +`map()`, `flatten()`, `filter()`, `count()`, `fold()`, `chain()`, `rev()` and similar. +The `next()` and `next_back()` methods do not count. + +## Avoiding allocations + +The exercise is solvable without introducing any additional memory allocations in the solution. +See if you can create your own iterator type instead of creating an iterator from a collection. diff --git a/exercises/practice/list-ops/.docs/instructions.md b/exercises/practice/list-ops/.docs/instructions.md new file mode 100644 index 000000000..ebc5dffed --- /dev/null +++ b/exercises/practice/list-ops/.docs/instructions.md @@ -0,0 +1,19 @@ +# Instructions + +Implement basic list operations. + +In functional languages list operations like `length`, `map`, and `reduce` are very common. +Implement a series of basic list operations, without using existing functions. + +The precise number and names of the operations to be implemented will be track dependent to avoid conflicts with existing names, but the general operations you will implement include: + +- `append` (_given two lists, add all items in the second list to the end of the first list_); +- `concatenate` (_given a series of lists, combine all items in all lists into one flattened list_); +- `filter` (_given a predicate and a list, return the list of all items for which `predicate(item)` is True_); +- `length` (_given a list, return the total number of items within it_); +- `map` (_given a function and a list, return the list of the results of applying `function(item)` on all items_); +- `foldl` (_given a function, a list, and initial accumulator, fold (reduce) each item into the accumulator from the left_); +- `foldr` (_given a function, a list, and an initial accumulator, fold (reduce) each item into the accumulator from the right_); +- `reverse` (_given a list, return a list with all the original items, but in reversed order_). + +Note, the ordering in which arguments are passed to the fold functions (`foldl`, `foldr`) is significant. diff --git a/exercises/practice/list-ops/.gitignore b/exercises/practice/list-ops/.gitignore new file mode 100644 index 000000000..db7f315c0 --- /dev/null +++ b/exercises/practice/list-ops/.gitignore @@ -0,0 +1,8 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ +**/*.rs.bk + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +Cargo.lock diff --git a/exercises/practice/list-ops/.meta/config.json b/exercises/practice/list-ops/.meta/config.json new file mode 100644 index 000000000..dd78f4222 --- /dev/null +++ b/exercises/practice/list-ops/.meta/config.json @@ -0,0 +1,16 @@ +{ + "authors": ["ellnix"], + "files": { + "solution": [ + "src/lib.rs", + "Cargo.toml" + ], + "test": [ + "tests/list-ops.rs" + ], + "example": [ + ".meta/example.rs" + ] + }, + "blurb": "Implement basic list operations." +} diff --git a/exercises/practice/list-ops/.meta/example.rs b/exercises/practice/list-ops/.meta/example.rs new file mode 100644 index 000000000..b32322a30 --- /dev/null +++ b/exercises/practice/list-ops/.meta/example.rs @@ -0,0 +1,154 @@ +pub fn append(a: I, b: I) -> impl Iterator +where + I: Iterator, +{ + struct Append> { + a: I, + b: I, + } + + impl> Iterator for Append { + type Item = T; + + fn next(&mut self) -> Option { + self.a.next().or_else(|| self.b.next()) + } + } + + Append { a, b } +} + +pub fn concat(list: I) -> impl Iterator +where + NI: Iterator, + I: Iterator, +{ + struct Concat, NI: Iterator, T> { + nested_list: I, + cur: Option, + } + + impl, NI: Iterator, T> Iterator for Concat { + type Item = T; + + fn next(&mut self) -> Option { + if let Some(nested_iterator) = self.cur.as_mut() { + if let Some(val) = nested_iterator.next() { + return Some(val); + } + } + + if let Some(next_nested) = self.nested_list.next() { + self.cur = Some(next_nested); + self.next() + } else { + None + } + } + } + + Concat { + nested_list: list, + cur: None, + } +} + +pub fn filter(list: I, predicate: F) -> impl Iterator +where + I: Iterator, + F: Fn(&T) -> bool, +{ + struct Filter, T, F: Fn(&T) -> bool> { + list: I, + predicate: F, + } + + impl Iterator for Filter + where + I: Iterator, + F: Fn(&T) -> bool, + { + type Item = T; + + fn next(&mut self) -> Option { + self.list.find(|val| (self.predicate)(val)) + } + } + + Filter { list, predicate } +} + +pub fn length, T>(list: I) -> usize { + let mut len = 0; + + for _ in list { + len += 1; + } + + len +} + +pub fn map(list: I, function: F) -> impl Iterator +where + I: Iterator, + F: Fn(T) -> U, +{ + struct Map, F: Fn(T) -> U, T, U> { + list: I, + function: F, + } + + impl, F: Fn(T) -> U, T, U> Iterator for Map { + type Item = U; + + fn next(&mut self) -> Option { + self.list.next().map(&self.function) + } + } + + Map { list, function } +} + +pub fn foldl(list: I, initial: U, function: F) -> U +where + I: Iterator, + F: Fn(U, T) -> U, +{ + let mut result = initial; + + for item in list { + result = (function)(result, item) + } + + result +} + +pub fn foldr(mut list: I, initial: U, function: F) -> U +where + I: DoubleEndedIterator, + F: Fn(U, T) -> U, +{ + let mut result = initial; + + while let Some(item) = list.next_back() { + result = (function)(result, item) + } + + result +} + +pub fn reverse, T>(list: I) -> impl Iterator { + struct Reverse, T> { + list: I, + } + + impl, T> Iterator for Reverse { + type Item = T; + + fn next(&mut self) -> Option { + self.list.next_back() + } + } + + Reverse { list } +} diff --git a/exercises/practice/list-ops/.meta/test_template.tera b/exercises/practice/list-ops/.meta/test_template.tera new file mode 100644 index 000000000..9250e335c --- /dev/null +++ b/exercises/practice/list-ops/.meta/test_template.tera @@ -0,0 +1,78 @@ +use list_ops::*; + +{% for group in cases %} + +{% set first_exercise = group.cases | first %} +{% if first_exercise.property == "concat" %} +#[allow(clippy::zero_repeat_side_effects)] +{%- endif %} +mod {{ first_exercise.property | make_ident }} { + use super::*; + +{% for test in group.cases %} + +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { +{% filter replace(from="[", to="vec![") %} + + {%- for arg, value in test.input -%} + {% if arg == "function" %}{% continue %}{% endif %} + {%- set is_empty = value is iterable and value | length == 0 -%} + + let {{arg}} = + {% if is_empty %} + {% if test.property is starting_with("fold") %} + [0.0f64; 0] + {% elif test.property == "concat" %} + [[0i32; 0]; 0] + {% else %} + [0i32; 0] + {% endif %} + {% elif test.property is starting_with("fold") %} + {% if value is number %} + {{ test.input[arg] }}.0 + {% else %} + {{ test.input[arg] | as_str | replace(from=",", to=".0,") | replace(from="]", to=".0]") | replace(from="[.0]", to="[]") }} + {% endif %} + {% else %} + {{ test.input[arg] }} + {% endif -%} + {% if value is iterable %} + .into_iter() + {% set first_item = value | first %} + {% if test.property == "concat" or first_item is iterable %} + .map(Vec::into_iter) + {% endif %} + {% endif %}; + {%- endfor -%} + + let output = {{ test.property }}( + {% for arg, value in test.input %} + {% if arg == "function" %} + {{ value | replace(from="(", to="|") | replace(from=")", to="|") | replace(from=" ->", to="") | replace(from="modulo", to="%")}}, + {% else %} + {{arg}}, + {% endif %} + {% endfor %} + ); + + let expected = {{ test.expected }}{% if test.property is starting_with("fold") %}.0{% endif %}; + + assert_eq!( + output + {% if test.expected is iterable %} + {% set first_item = test.expected | first %} + {% if test.property != "concat" and first_item is iterable %} + .map(|subiter| subiter.collect::>()) + {% endif %} + .collect::>() + {% endif %}, + expected + ); + +{% endfilter %} + } +{% endfor -%} +} +{% endfor -%} diff --git a/exercises/practice/list-ops/.meta/tests.toml b/exercises/practice/list-ops/.meta/tests.toml new file mode 100644 index 000000000..08b1edc04 --- /dev/null +++ b/exercises/practice/list-ops/.meta/tests.toml @@ -0,0 +1,106 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[485b9452-bf94-40f7-a3db-c3cf4850066a] +description = "append entries to a list and return the new list -> empty lists" + +[2c894696-b609-4569-b149-8672134d340a] +description = "append entries to a list and return the new list -> list to empty list" + +[e842efed-3bf6-4295-b371-4d67a4fdf19c] +description = "append entries to a list and return the new list -> empty list to list" + +[71dcf5eb-73ae-4a0e-b744-a52ee387922f] +description = "append entries to a list and return the new list -> non-empty lists" + +[28444355-201b-4af2-a2f6-5550227bde21] +description = "concatenate a list of lists -> empty list" + +[331451c1-9573-42a1-9869-2d06e3b389a9] +description = "concatenate a list of lists -> list of lists" + +[d6ecd72c-197f-40c3-89a4-aa1f45827e09] +description = "concatenate a list of lists -> list of nested lists" + +[0524fba8-3e0f-4531-ad2b-f7a43da86a16] +description = "filter list returning only values that satisfy the filter function -> empty list" + +[88494bd5-f520-4edb-8631-88e415b62d24] +description = "filter list returning only values that satisfy the filter function -> non-empty list" + +[1cf0b92d-8d96-41d5-9c21-7b3c37cb6aad] +description = "returns the length of a list -> empty list" + +[d7b8d2d9-2d16-44c4-9a19-6e5f237cb71e] +description = "returns the length of a list -> non-empty list" + +[c0bc8962-30e2-4bec-9ae4-668b8ecd75aa] +description = "return a list of elements whose values equal the list value transformed by the mapping function -> empty list" + +[11e71a95-e78b-4909-b8e4-60cdcaec0e91] +description = "return a list of elements whose values equal the list value transformed by the mapping function -> non-empty list" + +[613b20b7-1873-4070-a3a6-70ae5f50d7cc] +description = "folds (reduces) the given list from the left with a function -> empty list" +include = false + +[e56df3eb-9405-416a-b13a-aabb4c3b5194] +description = "folds (reduces) the given list from the left with a function -> direction independent function applied to non-empty list" +include = false + +[d2cf5644-aee1-4dfc-9b88-06896676fe27] +description = "folds (reduces) the given list from the left with a function -> direction dependent function applied to non-empty list" +include = false + +[36549237-f765-4a4c-bfd9-5d3a8f7b07d2] +description = "folds (reduces) the given list from the left with a function -> empty list" +reimplements = "613b20b7-1873-4070-a3a6-70ae5f50d7cc" + +[7a626a3c-03ec-42bc-9840-53f280e13067] +description = "folds (reduces) the given list from the left with a function -> direction independent function applied to non-empty list" +reimplements = "e56df3eb-9405-416a-b13a-aabb4c3b5194" + +[d7fcad99-e88e-40e1-a539-4c519681f390] +description = "folds (reduces) the given list from the left with a function -> direction dependent function applied to non-empty list" +reimplements = "d2cf5644-aee1-4dfc-9b88-06896676fe27" + +[aeb576b9-118e-4a57-a451-db49fac20fdc] +description = "folds (reduces) the given list from the right with a function -> empty list" +include = false + +[c4b64e58-313e-4c47-9c68-7764964efb8e] +description = "folds (reduces) the given list from the right with a function -> direction independent function applied to non-empty list" +include = false + +[be396a53-c074-4db3-8dd6-f7ed003cce7c] +description = "folds (reduces) the given list from the right with a function -> direction dependent function applied to non-empty list" +include = false + +[17214edb-20ba-42fc-bda8-000a5ab525b0] +description = "folds (reduces) the given list from the right with a function -> empty list" +reimplements = "aeb576b9-118e-4a57-a451-db49fac20fdc" + +[e1c64db7-9253-4a3d-a7c4-5273b9e2a1bd] +description = "folds (reduces) the given list from the right with a function -> direction independent function applied to non-empty list" +reimplements = "c4b64e58-313e-4c47-9c68-7764964efb8e" + +[8066003b-f2ff-437e-9103-66e6df474844] +description = "folds (reduces) the given list from the right with a function -> direction dependent function applied to non-empty list" +reimplements = "be396a53-c074-4db3-8dd6-f7ed003cce7c" + +[94231515-050e-4841-943d-d4488ab4ee30] +description = "reverse the elements of the list -> empty list" + +[fcc03d1e-42e0-4712-b689-d54ad761f360] +description = "reverse the elements of the list -> non-empty list" + +[40872990-b5b8-4cb8-9085-d91fc0d05d26] +description = "reverse the elements of the list -> list of lists is not flattened" diff --git a/exercises/practice/list-ops/Cargo.toml b/exercises/practice/list-ops/Cargo.toml new file mode 100644 index 000000000..eccd51cf6 --- /dev/null +++ b/exercises/practice/list-ops/Cargo.toml @@ -0,0 +1,4 @@ +[package] +name = "list-ops" +version = "0.1.0" +edition = "2021" diff --git a/exercises/practice/list-ops/src/lib.rs b/exercises/practice/list-ops/src/lib.rs new file mode 100644 index 000000000..359e75832 --- /dev/null +++ b/exercises/practice/list-ops/src/lib.rs @@ -0,0 +1,69 @@ +/// Yields each item of a and then each item of b +pub fn append(_a: I, _b: I) -> impl Iterator +where + I: Iterator, +{ + // this empty iterator silences a compiler complaint that + // () doesn't implement Iterator + std::iter::from_fn(|| todo!()) +} + +/// Combines all items in all nested iterators inside into one flattened iterator +pub fn concat(_nested_iter: I) -> impl Iterator +where + NI: Iterator, + I: Iterator, +{ + // this empty iterator silences a compiler complaint that + // () doesn't implement Iterator + std::iter::from_fn(|| todo!()) +} + +/// Returns an iterator of all items in iter for which `predicate(item)` is true +pub fn filter(_iter: I, _predicate: F) -> impl Iterator +where + I: Iterator, + F: Fn(&T) -> bool, +{ + // this empty iterator silences a compiler complaint that + // () doesn't implement Iterator + std::iter::from_fn(|| todo!()) +} + +pub fn length, T>(_iter: I) -> usize { + todo!("return the total number of items within iter") +} + +/// Returns an iterator of the results of applying `function(item)` on all iter items +pub fn map(_iter: I, _function: F) -> impl Iterator +where + I: Iterator, + F: Fn(T) -> U, +{ + // this empty iterator silences a compiler complaint that + // () doesn't implement Iterator + std::iter::from_fn(|| todo!()) +} + +pub fn foldl(mut _iter: I, _initial: U, _function: F) -> U +where + I: Iterator, + F: Fn(U, T) -> U, +{ + todo!("starting with initial, fold (reduce) each iter item into the accumulator from the left") +} + +pub fn foldr(mut _iter: I, _initial: U, _function: F) -> U +where + I: DoubleEndedIterator, + F: Fn(U, T) -> U, +{ + todo!("starting with initial, fold (reduce) each iter item into the accumulator from the right") +} + +/// Returns an iterator with all the original items, but in reverse order +pub fn reverse, T>(_iter: I) -> impl Iterator { + // this empty iterator silences a compiler complaint that + // () doesn't implement Iterator + std::iter::from_fn(|| todo!()) +} diff --git a/exercises/practice/list-ops/tests/list-ops.rs b/exercises/practice/list-ops/tests/list-ops.rs new file mode 100644 index 000000000..96c1c654d --- /dev/null +++ b/exercises/practice/list-ops/tests/list-ops.rs @@ -0,0 +1,301 @@ +use list_ops::*; + +mod append { + use super::*; + + #[test] + fn empty_lists() { + let list1 = vec![0i32; 0].into_iter(); + let list2 = vec![0i32; 0].into_iter(); + let output = append(list1, list2); + + let expected = vec![]; + + assert_eq!(output.collect::>(), expected); + } + + #[test] + #[ignore] + fn list_to_empty_list() { + let list1 = vec![0i32; 0].into_iter(); + let list2 = vec![1, 2, 3, 4].into_iter(); + let output = append(list1, list2); + + let expected = vec![1, 2, 3, 4]; + + assert_eq!(output.collect::>(), expected); + } + + #[test] + #[ignore] + fn empty_list_to_list() { + let list1 = vec![1, 2, 3, 4].into_iter(); + let list2 = vec![0i32; 0].into_iter(); + let output = append(list1, list2); + + let expected = vec![1, 2, 3, 4]; + + assert_eq!(output.collect::>(), expected); + } + + #[test] + #[ignore] + fn non_empty_lists() { + let list1 = vec![1, 2].into_iter(); + let list2 = vec![2, 3, 4, 5].into_iter(); + let output = append(list1, list2); + + let expected = vec![1, 2, 2, 3, 4, 5]; + + assert_eq!(output.collect::>(), expected); + } +} + +#[allow(clippy::zero_repeat_side_effects)] +mod concat { + use super::*; + + #[test] + #[ignore] + fn empty_list() { + let lists = vec![vec![0i32; 0]; 0].into_iter().map(Vec::into_iter); + let output = concat(lists); + + let expected = vec![]; + + assert_eq!(output.collect::>(), expected); + } + + #[test] + #[ignore] + fn list_of_lists() { + let lists = vec![vec![1, 2], vec![3], vec![], vec![4, 5, 6]] + .into_iter() + .map(Vec::into_iter); + let output = concat(lists); + + let expected = vec![1, 2, 3, 4, 5, 6]; + + assert_eq!(output.collect::>(), expected); + } + + #[test] + #[ignore] + fn list_of_nested_lists() { + let lists = vec![ + vec![vec![1], vec![2]], + vec![vec![3]], + vec![vec![]], + vec![vec![4, 5, 6]], + ] + .into_iter() + .map(Vec::into_iter); + let output = concat(lists); + + let expected = vec![vec![1], vec![2], vec![3], vec![], vec![4, 5, 6]]; + + assert_eq!(output.collect::>(), expected); + } +} + +mod filter { + use super::*; + + #[test] + #[ignore] + fn empty_list() { + let list = vec![0i32; 0].into_iter(); + let output = filter(list, |x| x % 2 == 1); + + let expected = vec![]; + + assert_eq!(output.collect::>(), expected); + } + + #[test] + #[ignore] + fn non_empty_list() { + let list = vec![1, 2, 3, 5].into_iter(); + let output = filter(list, |x| x % 2 == 1); + + let expected = vec![1, 3, 5]; + + assert_eq!(output.collect::>(), expected); + } +} + +mod length { + use super::*; + + #[test] + #[ignore] + fn empty_list() { + let list = vec![0i32; 0].into_iter(); + let output = length(list); + + let expected = 0; + + assert_eq!(output, expected); + } + + #[test] + #[ignore] + fn non_empty_list() { + let list = vec![1, 2, 3, 4].into_iter(); + let output = length(list); + + let expected = 4; + + assert_eq!(output, expected); + } +} + +mod map { + use super::*; + + #[test] + #[ignore] + fn empty_list() { + let list = vec![0i32; 0].into_iter(); + let output = map(list, |x| x + 1); + + let expected = vec![]; + + assert_eq!(output.collect::>(), expected); + } + + #[test] + #[ignore] + fn non_empty_list() { + let list = vec![1, 3, 5, 7].into_iter(); + let output = map(list, |x| x + 1); + + let expected = vec![2, 4, 6, 8]; + + assert_eq!(output.collect::>(), expected); + } +} + +mod foldl { + use super::*; + + #[test] + #[ignore] + fn empty_list() { + let list = vec![0.0f64; 0].into_iter(); + let initial = 2.0; + let output = foldl(list, initial, |acc, el| el * acc); + + let expected = 2.0; + + assert_eq!(output, expected); + } + + #[test] + #[ignore] + fn direction_independent_function_applied_to_non_empty_list() { + let list = vec![1.0, 2.0, 3.0, 4.0].into_iter(); + let initial = 5.0; + let output = foldl(list, initial, |acc, el| el + acc); + + let expected = 15.0; + + assert_eq!(output, expected); + } + + #[test] + #[ignore] + fn direction_dependent_function_applied_to_non_empty_list() { + let list = vec![1.0, 2.0, 3.0, 4.0].into_iter(); + let initial = 24.0; + let output = foldl(list, initial, |acc, el| el / acc); + + let expected = 64.0; + + assert_eq!(output, expected); + } +} + +mod foldr { + use super::*; + + #[test] + #[ignore] + fn empty_list() { + let list = vec![0.0f64; 0].into_iter(); + let initial = 2.0; + let output = foldr(list, initial, |acc, el| el * acc); + + let expected = 2.0; + + assert_eq!(output, expected); + } + + #[test] + #[ignore] + fn direction_independent_function_applied_to_non_empty_list() { + let list = vec![1.0, 2.0, 3.0, 4.0].into_iter(); + let initial = 5.0; + let output = foldr(list, initial, |acc, el| el + acc); + + let expected = 15.0; + + assert_eq!(output, expected); + } + + #[test] + #[ignore] + fn direction_dependent_function_applied_to_non_empty_list() { + let list = vec![1.0, 2.0, 3.0, 4.0].into_iter(); + let initial = 24.0; + let output = foldr(list, initial, |acc, el| el / acc); + + let expected = 9.0; + + assert_eq!(output, expected); + } +} + +mod reverse { + use super::*; + + #[test] + #[ignore] + fn empty_list() { + let list = vec![0i32; 0].into_iter(); + let output = reverse(list); + + let expected = vec![]; + + assert_eq!(output.collect::>(), expected); + } + + #[test] + #[ignore] + fn non_empty_list() { + let list = vec![1, 3, 5, 7].into_iter(); + let output = reverse(list); + + let expected = vec![7, 5, 3, 1]; + + assert_eq!(output.collect::>(), expected); + } + + #[test] + #[ignore] + fn list_of_lists_is_not_flattened() { + let list = vec![vec![1, 2], vec![3], vec![], vec![4, 5, 6]] + .into_iter() + .map(Vec::into_iter); + let output = reverse(list); + + let expected = vec![vec![4, 5, 6], vec![], vec![3], vec![1, 2]]; + + assert_eq!( + output + .map(|subiter| subiter.collect::>()) + .collect::>(), + expected + ); + } +} diff --git a/rust-tooling/ci-tests/tests/no_underscore_prefixed_idents.rs b/rust-tooling/ci-tests/tests/no_underscore_prefixed_idents.rs index c86814be5..c2cabcee9 100644 --- a/rust-tooling/ci-tests/tests/no_underscore_prefixed_idents.rs +++ b/rust-tooling/ci-tests/tests/no_underscore_prefixed_idents.rs @@ -47,6 +47,7 @@ use utils::fs::cd_into_repo_root; static EXCEPTIONS: &[&str] = &[ "accumulate", // has generics (not the stub, but the solution) + "list-ops", // has generics "circular-buffer", // has generics "custom-set", // has generics "doubly-linked-list", // has generics From 7252d3e3c0d5971ad1a89f37e46ab8fd73c7240d Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 18 Feb 2025 12:37:25 +0100 Subject: [PATCH 377/436] simplify generated gitignore files (#2026) Cargo has simplified its output a while ago, this is making us more consistent with that. Cargo doesn't add Cargo.lock anymore at all, neither for libraries nor for binaries. It still makes sense for us though, since our users shouldn't submit Cargo.lock to the test runner. --- exercises/concept/assembly-line/.gitignore | 8 +------- exercises/concept/csv-builder/.gitignore | 8 +------- exercises/concept/health-statistics/.gitignore | 8 +------- exercises/concept/low-power-embedded-game/.gitignore | 8 +------- .../concept/lucians-luscious-lasagna/.gitignore | 8 +------- exercises/concept/magazine-cutout/.gitignore | 8 +------- exercises/concept/resistor-color/.gitignore | 8 +------- exercises/concept/role-playing-game/.gitignore | 8 +------- exercises/concept/rpn-calculator/.gitignore | 8 +------- exercises/concept/semi-structured-logs/.gitignore | 8 +------- exercises/concept/short-fibonacci/.gitignore | 8 +------- exercises/practice/accumulate/.gitignore | 8 +------- exercises/practice/acronym/.gitignore | 8 +------- exercises/practice/affine-cipher/.gitignore | 8 +------- exercises/practice/all-your-base/.gitignore | 8 +------- exercises/practice/allergies/.gitignore | 8 +------- exercises/practice/alphametics/.gitignore | 8 +------- exercises/practice/anagram/.gitignore | 8 +------- exercises/practice/armstrong-numbers/.gitignore | 8 +------- exercises/practice/atbash-cipher/.gitignore | 8 +------- exercises/practice/beer-song/.gitignore | 8 +------- exercises/practice/binary-search/.gitignore | 8 +------- exercises/practice/bob/.gitignore | 8 +------- exercises/practice/book-store/.gitignore | 8 +------- exercises/practice/bowling/.gitignore | 8 +------- exercises/practice/circular-buffer/.gitignore | 8 +------- exercises/practice/clock/.gitignore | 8 +------- exercises/practice/collatz-conjecture/.gitignore | 8 +------- exercises/practice/crypto-square/.gitignore | 8 +------- exercises/practice/custom-set/.gitignore | 8 +------- exercises/practice/decimal/.gitignore | 8 +------- exercises/practice/diamond/.gitignore | 8 +------- exercises/practice/difference-of-squares/.gitignore | 8 +------- exercises/practice/diffie-hellman/.gitignore | 8 +------- exercises/practice/dominoes/.gitignore | 8 +------- exercises/practice/dot-dsl/.gitignore | 8 +------- exercises/practice/doubly-linked-list/.gitignore | 8 +------- exercises/practice/eliuds-eggs/.gitignore | 2 +- exercises/practice/etl/.gitignore | 8 +------- exercises/practice/fizzy/.gitignore | 8 +------- exercises/practice/forth/.gitignore | 8 +------- exercises/practice/gigasecond/.gitignore | 8 +------- exercises/practice/grade-school/.gitignore | 8 +------- exercises/practice/grains/.gitignore | 8 +------- exercises/practice/grep/.gitignore | 12 +----------- exercises/practice/hamming/.gitignore | 8 +------- exercises/practice/hello-world/.gitignore | 8 +------- exercises/practice/hexadecimal/.gitignore | 8 +------- exercises/practice/high-scores/.gitignore | 8 +------- exercises/practice/isbn-verifier/.gitignore | 8 +------- exercises/practice/isogram/.gitignore | 8 +------- exercises/practice/kindergarten-garden/.gitignore | 8 +------- exercises/practice/knapsack/.gitignore | 8 +------- exercises/practice/largest-series-product/.gitignore | 8 +------- exercises/practice/leap/.gitignore | 8 +------- exercises/practice/list-ops/.gitignore | 8 +------- exercises/practice/luhn-from/.gitignore | 8 +------- exercises/practice/luhn-trait/.gitignore | 8 +------- exercises/practice/luhn/.gitignore | 8 +------- exercises/practice/macros/.gitignore | 8 +------- exercises/practice/matching-brackets/.gitignore | 8 +------- exercises/practice/matrix/.gitignore | 2 +- exercises/practice/minesweeper/.gitignore | 8 +------- exercises/practice/nth-prime/.gitignore | 8 +------- exercises/practice/nucleotide-codons/.gitignore | 8 +------- exercises/practice/nucleotide-count/.gitignore | 8 +------- exercises/practice/ocr-numbers/.gitignore | 8 +------- exercises/practice/paasio/.gitignore | 8 +------- exercises/practice/palindrome-products/.gitignore | 8 +------- exercises/practice/pangram/.gitignore | 8 +------- .../practice/parallel-letter-frequency/.gitignore | 8 +------- exercises/practice/pascals-triangle/.gitignore | 8 +------- exercises/practice/perfect-numbers/.gitignore | 8 +------- exercises/practice/phone-number/.gitignore | 8 +------- exercises/practice/pig-latin/.gitignore | 8 +------- exercises/practice/poker/.gitignore | 8 +------- exercises/practice/prime-factors/.gitignore | 8 +------- exercises/practice/protein-translation/.gitignore | 8 +------- exercises/practice/proverb/.gitignore | 8 +------- exercises/practice/pythagorean-triplet/.gitignore | 8 +------- exercises/practice/queen-attack/.gitignore | 8 +------- exercises/practice/rail-fence-cipher/.gitignore | 8 +------- exercises/practice/raindrops/.gitignore | 8 +------- exercises/practice/react/.gitignore | 8 +------- exercises/practice/rectangles/.gitignore | 8 +------- exercises/practice/reverse-string/.gitignore | 8 +------- exercises/practice/rna-transcription/.gitignore | 8 +------- exercises/practice/robot-name/.gitignore | 8 +------- exercises/practice/robot-simulator/.gitignore | 8 +------- exercises/practice/roman-numerals/.gitignore | 8 +------- exercises/practice/rotational-cipher/.gitignore | 8 +------- exercises/practice/run-length-encoding/.gitignore | 8 +------- exercises/practice/saddle-points/.gitignore | 8 +------- exercises/practice/say/.gitignore | 8 +------- exercises/practice/scale-generator/.gitignore | 8 +------- exercises/practice/scrabble-score/.gitignore | 8 +------- exercises/practice/secret-handshake/.gitignore | 8 +------- exercises/practice/series/.gitignore | 8 +------- exercises/practice/sieve/.gitignore | 8 +------- exercises/practice/simple-cipher/.gitignore | 8 +------- exercises/practice/simple-linked-list/.gitignore | 8 +------- exercises/practice/space-age/.gitignore | 8 +------- exercises/practice/spiral-matrix/.gitignore | 8 +------- exercises/practice/sublist/.gitignore | 8 +------- exercises/practice/sum-of-multiples/.gitignore | 8 +------- exercises/practice/tournament/.gitignore | 8 +------- exercises/practice/triangle/.gitignore | 8 +------- exercises/practice/two-bucket/.gitignore | 8 +------- exercises/practice/two-fer/.gitignore | 8 +------- .../practice/variable-length-quantity/.gitignore | 8 +------- exercises/practice/word-count/.gitignore | 8 +------- exercises/practice/wordy/.gitignore | 8 +------- exercises/practice/xorcism/.gitignore | 8 +------- exercises/practice/yacht/.gitignore | 8 +------- rust-tooling/generate/src/lib.rs | 2 +- rust-tooling/models/src/problem_spec.rs | 1 + 116 files changed, 116 insertions(+), 791 deletions(-) diff --git a/exercises/concept/assembly-line/.gitignore b/exercises/concept/assembly-line/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/concept/assembly-line/.gitignore +++ b/exercises/concept/assembly-line/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/concept/csv-builder/.gitignore b/exercises/concept/csv-builder/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/concept/csv-builder/.gitignore +++ b/exercises/concept/csv-builder/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/concept/health-statistics/.gitignore b/exercises/concept/health-statistics/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/concept/health-statistics/.gitignore +++ b/exercises/concept/health-statistics/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/concept/low-power-embedded-game/.gitignore b/exercises/concept/low-power-embedded-game/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/concept/low-power-embedded-game/.gitignore +++ b/exercises/concept/low-power-embedded-game/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/concept/lucians-luscious-lasagna/.gitignore b/exercises/concept/lucians-luscious-lasagna/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/concept/lucians-luscious-lasagna/.gitignore +++ b/exercises/concept/lucians-luscious-lasagna/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/concept/magazine-cutout/.gitignore b/exercises/concept/magazine-cutout/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/concept/magazine-cutout/.gitignore +++ b/exercises/concept/magazine-cutout/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/concept/resistor-color/.gitignore b/exercises/concept/resistor-color/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/concept/resistor-color/.gitignore +++ b/exercises/concept/resistor-color/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/concept/role-playing-game/.gitignore b/exercises/concept/role-playing-game/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/concept/role-playing-game/.gitignore +++ b/exercises/concept/role-playing-game/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/concept/rpn-calculator/.gitignore b/exercises/concept/rpn-calculator/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/concept/rpn-calculator/.gitignore +++ b/exercises/concept/rpn-calculator/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/concept/semi-structured-logs/.gitignore b/exercises/concept/semi-structured-logs/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/concept/semi-structured-logs/.gitignore +++ b/exercises/concept/semi-structured-logs/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/concept/short-fibonacci/.gitignore b/exercises/concept/short-fibonacci/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/concept/short-fibonacci/.gitignore +++ b/exercises/concept/short-fibonacci/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/accumulate/.gitignore b/exercises/practice/accumulate/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/accumulate/.gitignore +++ b/exercises/practice/accumulate/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/acronym/.gitignore b/exercises/practice/acronym/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/acronym/.gitignore +++ b/exercises/practice/acronym/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/affine-cipher/.gitignore b/exercises/practice/affine-cipher/.gitignore index e130ceb2d..96ef6c0b9 100644 --- a/exercises/practice/affine-cipher/.gitignore +++ b/exercises/practice/affine-cipher/.gitignore @@ -1,8 +1,2 @@ -# Generated by exercism rust track exercise tool -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/all-your-base/.gitignore b/exercises/practice/all-your-base/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/all-your-base/.gitignore +++ b/exercises/practice/all-your-base/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/allergies/.gitignore b/exercises/practice/allergies/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/allergies/.gitignore +++ b/exercises/practice/allergies/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/alphametics/.gitignore b/exercises/practice/alphametics/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/alphametics/.gitignore +++ b/exercises/practice/alphametics/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/anagram/.gitignore b/exercises/practice/anagram/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/anagram/.gitignore +++ b/exercises/practice/anagram/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/armstrong-numbers/.gitignore b/exercises/practice/armstrong-numbers/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/armstrong-numbers/.gitignore +++ b/exercises/practice/armstrong-numbers/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/atbash-cipher/.gitignore b/exercises/practice/atbash-cipher/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/atbash-cipher/.gitignore +++ b/exercises/practice/atbash-cipher/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/beer-song/.gitignore b/exercises/practice/beer-song/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/beer-song/.gitignore +++ b/exercises/practice/beer-song/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/binary-search/.gitignore b/exercises/practice/binary-search/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/binary-search/.gitignore +++ b/exercises/practice/binary-search/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/bob/.gitignore b/exercises/practice/bob/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/bob/.gitignore +++ b/exercises/practice/bob/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/book-store/.gitignore b/exercises/practice/book-store/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/book-store/.gitignore +++ b/exercises/practice/book-store/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/bowling/.gitignore b/exercises/practice/bowling/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/bowling/.gitignore +++ b/exercises/practice/bowling/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/circular-buffer/.gitignore b/exercises/practice/circular-buffer/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/circular-buffer/.gitignore +++ b/exercises/practice/circular-buffer/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/clock/.gitignore b/exercises/practice/clock/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/clock/.gitignore +++ b/exercises/practice/clock/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/collatz-conjecture/.gitignore b/exercises/practice/collatz-conjecture/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/collatz-conjecture/.gitignore +++ b/exercises/practice/collatz-conjecture/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/crypto-square/.gitignore b/exercises/practice/crypto-square/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/crypto-square/.gitignore +++ b/exercises/practice/crypto-square/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/custom-set/.gitignore b/exercises/practice/custom-set/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/custom-set/.gitignore +++ b/exercises/practice/custom-set/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/decimal/.gitignore b/exercises/practice/decimal/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/decimal/.gitignore +++ b/exercises/practice/decimal/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/diamond/.gitignore b/exercises/practice/diamond/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/diamond/.gitignore +++ b/exercises/practice/diamond/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/difference-of-squares/.gitignore b/exercises/practice/difference-of-squares/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/difference-of-squares/.gitignore +++ b/exercises/practice/difference-of-squares/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/diffie-hellman/.gitignore b/exercises/practice/diffie-hellman/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/diffie-hellman/.gitignore +++ b/exercises/practice/diffie-hellman/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/dominoes/.gitignore b/exercises/practice/dominoes/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/dominoes/.gitignore +++ b/exercises/practice/dominoes/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/dot-dsl/.gitignore b/exercises/practice/dot-dsl/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/dot-dsl/.gitignore +++ b/exercises/practice/dot-dsl/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/doubly-linked-list/.gitignore b/exercises/practice/doubly-linked-list/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/doubly-linked-list/.gitignore +++ b/exercises/practice/doubly-linked-list/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/eliuds-eggs/.gitignore b/exercises/practice/eliuds-eggs/.gitignore index 4fffb2f89..96ef6c0b9 100644 --- a/exercises/practice/eliuds-eggs/.gitignore +++ b/exercises/practice/eliuds-eggs/.gitignore @@ -1,2 +1,2 @@ /target -/Cargo.lock +Cargo.lock diff --git a/exercises/practice/etl/.gitignore b/exercises/practice/etl/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/etl/.gitignore +++ b/exercises/practice/etl/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/fizzy/.gitignore b/exercises/practice/fizzy/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/fizzy/.gitignore +++ b/exercises/practice/fizzy/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/forth/.gitignore b/exercises/practice/forth/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/forth/.gitignore +++ b/exercises/practice/forth/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/gigasecond/.gitignore b/exercises/practice/gigasecond/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/gigasecond/.gitignore +++ b/exercises/practice/gigasecond/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/grade-school/.gitignore b/exercises/practice/grade-school/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/grade-school/.gitignore +++ b/exercises/practice/grade-school/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/grains/.gitignore b/exercises/practice/grains/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/grains/.gitignore +++ b/exercises/practice/grains/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/grep/.gitignore b/exercises/practice/grep/.gitignore index dfd29ee5b..96ef6c0b9 100644 --- a/exercises/practice/grep/.gitignore +++ b/exercises/practice/grep/.gitignore @@ -1,12 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock - -# Exercise generates some text files, and while it is meant to clean up after itself, -# it might not in the event of panics -*.txt diff --git a/exercises/practice/hamming/.gitignore b/exercises/practice/hamming/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/hamming/.gitignore +++ b/exercises/practice/hamming/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/hello-world/.gitignore b/exercises/practice/hello-world/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/hello-world/.gitignore +++ b/exercises/practice/hello-world/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/hexadecimal/.gitignore b/exercises/practice/hexadecimal/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/hexadecimal/.gitignore +++ b/exercises/practice/hexadecimal/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/high-scores/.gitignore b/exercises/practice/high-scores/.gitignore index e130ceb2d..96ef6c0b9 100644 --- a/exercises/practice/high-scores/.gitignore +++ b/exercises/practice/high-scores/.gitignore @@ -1,8 +1,2 @@ -# Generated by exercism rust track exercise tool -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/isbn-verifier/.gitignore b/exercises/practice/isbn-verifier/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/isbn-verifier/.gitignore +++ b/exercises/practice/isbn-verifier/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/isogram/.gitignore b/exercises/practice/isogram/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/isogram/.gitignore +++ b/exercises/practice/isogram/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/kindergarten-garden/.gitignore b/exercises/practice/kindergarten-garden/.gitignore index e24ae5981..96ef6c0b9 100644 --- a/exercises/practice/kindergarten-garden/.gitignore +++ b/exercises/practice/kindergarten-garden/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# Will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/knapsack/.gitignore b/exercises/practice/knapsack/.gitignore index e24ae5981..96ef6c0b9 100644 --- a/exercises/practice/knapsack/.gitignore +++ b/exercises/practice/knapsack/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# Will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/largest-series-product/.gitignore b/exercises/practice/largest-series-product/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/largest-series-product/.gitignore +++ b/exercises/practice/largest-series-product/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/leap/.gitignore b/exercises/practice/leap/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/leap/.gitignore +++ b/exercises/practice/leap/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/list-ops/.gitignore b/exercises/practice/list-ops/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/list-ops/.gitignore +++ b/exercises/practice/list-ops/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/luhn-from/.gitignore b/exercises/practice/luhn-from/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/luhn-from/.gitignore +++ b/exercises/practice/luhn-from/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/luhn-trait/.gitignore b/exercises/practice/luhn-trait/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/luhn-trait/.gitignore +++ b/exercises/practice/luhn-trait/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/luhn/.gitignore b/exercises/practice/luhn/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/luhn/.gitignore +++ b/exercises/practice/luhn/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/macros/.gitignore b/exercises/practice/macros/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/macros/.gitignore +++ b/exercises/practice/macros/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/matching-brackets/.gitignore b/exercises/practice/matching-brackets/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/matching-brackets/.gitignore +++ b/exercises/practice/matching-brackets/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/matrix/.gitignore b/exercises/practice/matrix/.gitignore index 4fffb2f89..96ef6c0b9 100644 --- a/exercises/practice/matrix/.gitignore +++ b/exercises/practice/matrix/.gitignore @@ -1,2 +1,2 @@ /target -/Cargo.lock +Cargo.lock diff --git a/exercises/practice/minesweeper/.gitignore b/exercises/practice/minesweeper/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/minesweeper/.gitignore +++ b/exercises/practice/minesweeper/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/nth-prime/.gitignore b/exercises/practice/nth-prime/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/nth-prime/.gitignore +++ b/exercises/practice/nth-prime/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/nucleotide-codons/.gitignore b/exercises/practice/nucleotide-codons/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/nucleotide-codons/.gitignore +++ b/exercises/practice/nucleotide-codons/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/nucleotide-count/.gitignore b/exercises/practice/nucleotide-count/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/nucleotide-count/.gitignore +++ b/exercises/practice/nucleotide-count/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/ocr-numbers/.gitignore b/exercises/practice/ocr-numbers/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/ocr-numbers/.gitignore +++ b/exercises/practice/ocr-numbers/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/paasio/.gitignore b/exercises/practice/paasio/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/paasio/.gitignore +++ b/exercises/practice/paasio/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/palindrome-products/.gitignore b/exercises/practice/palindrome-products/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/palindrome-products/.gitignore +++ b/exercises/practice/palindrome-products/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/pangram/.gitignore b/exercises/practice/pangram/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/pangram/.gitignore +++ b/exercises/practice/pangram/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/parallel-letter-frequency/.gitignore b/exercises/practice/parallel-letter-frequency/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/parallel-letter-frequency/.gitignore +++ b/exercises/practice/parallel-letter-frequency/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/pascals-triangle/.gitignore b/exercises/practice/pascals-triangle/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/pascals-triangle/.gitignore +++ b/exercises/practice/pascals-triangle/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/perfect-numbers/.gitignore b/exercises/practice/perfect-numbers/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/perfect-numbers/.gitignore +++ b/exercises/practice/perfect-numbers/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/phone-number/.gitignore b/exercises/practice/phone-number/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/phone-number/.gitignore +++ b/exercises/practice/phone-number/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/pig-latin/.gitignore b/exercises/practice/pig-latin/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/pig-latin/.gitignore +++ b/exercises/practice/pig-latin/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/poker/.gitignore b/exercises/practice/poker/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/poker/.gitignore +++ b/exercises/practice/poker/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/prime-factors/.gitignore b/exercises/practice/prime-factors/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/prime-factors/.gitignore +++ b/exercises/practice/prime-factors/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/protein-translation/.gitignore b/exercises/practice/protein-translation/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/protein-translation/.gitignore +++ b/exercises/practice/protein-translation/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/proverb/.gitignore b/exercises/practice/proverb/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/proverb/.gitignore +++ b/exercises/practice/proverb/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/pythagorean-triplet/.gitignore b/exercises/practice/pythagorean-triplet/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/pythagorean-triplet/.gitignore +++ b/exercises/practice/pythagorean-triplet/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/queen-attack/.gitignore b/exercises/practice/queen-attack/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/queen-attack/.gitignore +++ b/exercises/practice/queen-attack/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/rail-fence-cipher/.gitignore b/exercises/practice/rail-fence-cipher/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/rail-fence-cipher/.gitignore +++ b/exercises/practice/rail-fence-cipher/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/raindrops/.gitignore b/exercises/practice/raindrops/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/raindrops/.gitignore +++ b/exercises/practice/raindrops/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/react/.gitignore b/exercises/practice/react/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/react/.gitignore +++ b/exercises/practice/react/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/rectangles/.gitignore b/exercises/practice/rectangles/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/rectangles/.gitignore +++ b/exercises/practice/rectangles/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/reverse-string/.gitignore b/exercises/practice/reverse-string/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/reverse-string/.gitignore +++ b/exercises/practice/reverse-string/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/rna-transcription/.gitignore b/exercises/practice/rna-transcription/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/rna-transcription/.gitignore +++ b/exercises/practice/rna-transcription/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/robot-name/.gitignore b/exercises/practice/robot-name/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/robot-name/.gitignore +++ b/exercises/practice/robot-name/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/robot-simulator/.gitignore b/exercises/practice/robot-simulator/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/robot-simulator/.gitignore +++ b/exercises/practice/robot-simulator/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/roman-numerals/.gitignore b/exercises/practice/roman-numerals/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/roman-numerals/.gitignore +++ b/exercises/practice/roman-numerals/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/rotational-cipher/.gitignore b/exercises/practice/rotational-cipher/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/rotational-cipher/.gitignore +++ b/exercises/practice/rotational-cipher/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/run-length-encoding/.gitignore b/exercises/practice/run-length-encoding/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/run-length-encoding/.gitignore +++ b/exercises/practice/run-length-encoding/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/saddle-points/.gitignore b/exercises/practice/saddle-points/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/saddle-points/.gitignore +++ b/exercises/practice/saddle-points/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/say/.gitignore b/exercises/practice/say/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/say/.gitignore +++ b/exercises/practice/say/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/scale-generator/.gitignore b/exercises/practice/scale-generator/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/scale-generator/.gitignore +++ b/exercises/practice/scale-generator/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/scrabble-score/.gitignore b/exercises/practice/scrabble-score/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/scrabble-score/.gitignore +++ b/exercises/practice/scrabble-score/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/secret-handshake/.gitignore b/exercises/practice/secret-handshake/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/secret-handshake/.gitignore +++ b/exercises/practice/secret-handshake/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/series/.gitignore b/exercises/practice/series/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/series/.gitignore +++ b/exercises/practice/series/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/sieve/.gitignore b/exercises/practice/sieve/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/sieve/.gitignore +++ b/exercises/practice/sieve/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/simple-cipher/.gitignore b/exercises/practice/simple-cipher/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/simple-cipher/.gitignore +++ b/exercises/practice/simple-cipher/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/simple-linked-list/.gitignore b/exercises/practice/simple-linked-list/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/simple-linked-list/.gitignore +++ b/exercises/practice/simple-linked-list/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/space-age/.gitignore b/exercises/practice/space-age/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/space-age/.gitignore +++ b/exercises/practice/space-age/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/spiral-matrix/.gitignore b/exercises/practice/spiral-matrix/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/spiral-matrix/.gitignore +++ b/exercises/practice/spiral-matrix/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/sublist/.gitignore b/exercises/practice/sublist/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/sublist/.gitignore +++ b/exercises/practice/sublist/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/sum-of-multiples/.gitignore b/exercises/practice/sum-of-multiples/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/sum-of-multiples/.gitignore +++ b/exercises/practice/sum-of-multiples/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/tournament/.gitignore b/exercises/practice/tournament/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/tournament/.gitignore +++ b/exercises/practice/tournament/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/triangle/.gitignore b/exercises/practice/triangle/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/triangle/.gitignore +++ b/exercises/practice/triangle/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/two-bucket/.gitignore b/exercises/practice/two-bucket/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/two-bucket/.gitignore +++ b/exercises/practice/two-bucket/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/two-fer/.gitignore b/exercises/practice/two-fer/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/two-fer/.gitignore +++ b/exercises/practice/two-fer/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/variable-length-quantity/.gitignore b/exercises/practice/variable-length-quantity/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/variable-length-quantity/.gitignore +++ b/exercises/practice/variable-length-quantity/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/word-count/.gitignore b/exercises/practice/word-count/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/word-count/.gitignore +++ b/exercises/practice/word-count/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/wordy/.gitignore b/exercises/practice/wordy/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/wordy/.gitignore +++ b/exercises/practice/wordy/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/xorcism/.gitignore b/exercises/practice/xorcism/.gitignore index db7f315c0..96ef6c0b9 100644 --- a/exercises/practice/xorcism/.gitignore +++ b/exercises/practice/xorcism/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/exercises/practice/yacht/.gitignore b/exercises/practice/yacht/.gitignore index e24ae5981..96ef6c0b9 100644 --- a/exercises/practice/yacht/.gitignore +++ b/exercises/practice/yacht/.gitignore @@ -1,8 +1,2 @@ -# Generated by Cargo -# Will have compiled files and executables -/target/ -**/*.rs.bk - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock +/target Cargo.lock diff --git a/rust-tooling/generate/src/lib.rs b/rust-tooling/generate/src/lib.rs index e397a586d..8d2947467 100644 --- a/rust-tooling/generate/src/lib.rs +++ b/rust-tooling/generate/src/lib.rs @@ -38,7 +38,7 @@ pub fn new(slug: &str) -> Result { static GITIGNORE: &str = "\ /target -/Cargo.lock +Cargo.lock "; fn generate_manifest(crate_name: &str) -> String { diff --git a/rust-tooling/models/src/problem_spec.rs b/rust-tooling/models/src/problem_spec.rs index 2dfc39cc0..88e6af04f 100644 --- a/rust-tooling/models/src/problem_spec.rs +++ b/rust-tooling/models/src/problem_spec.rs @@ -13,6 +13,7 @@ pub struct CanonicalData { #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(deny_unknown_fields)] #[serde(untagged)] +#[allow(clippy::large_enum_variant)] pub enum TestCase { Single { #[serde(flatten)] From 8fbaa7ea3145781c46b68b96f2e8bd009188986d Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 18 Feb 2025 12:49:02 +0100 Subject: [PATCH 378/436] generate: don't crash on invalid templates (#2027) This ensures that all other files are still written to disk as expected. There is no risk of an invalid template being merged, because the generated fallback tests will always fail in CI. --------- Co-authored-by: ellnix --- rust-tooling/generate/src/lib.rs | 20 +++++++++++++++---- rust-tooling/generate/src/main.rs | 2 +- .../tests/tera_templates_are_in_sync.rs | 2 +- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/rust-tooling/generate/src/lib.rs b/rust-tooling/generate/src/lib.rs index 8d2947467..377678bbe 100644 --- a/rust-tooling/generate/src/lib.rs +++ b/rust-tooling/generate/src/lib.rs @@ -23,17 +23,22 @@ pub struct GeneratedExercise { pub tests: String, } -pub fn new(slug: &str) -> Result { +pub fn new(slug: &str) -> GeneratedExercise { let crate_name = slug.replace('-', "_"); - Ok(GeneratedExercise { + let tests = generate_tests(slug).unwrap_or_else(|e| { + eprintln!("WARNING: Failed to generate tests:\n{e:?}"); + FALLBACK_TESTS.into() + }); + + GeneratedExercise { gitignore: GITIGNORE.into(), manifest: generate_manifest(&crate_name), lib_rs: LIB_RS.into(), example: EXAMPLE_RS.into(), test_template: TEST_TEMPLATE.into(), - tests: generate_tests(slug)?, - }) + tests, + } } static GITIGNORE: &str = "\ @@ -67,6 +72,13 @@ pub fn TODO(input: TODO) -> TODO { static TEST_TEMPLATE: &str = include_str!("../templates/default_test_template.tera"); +static FALLBACK_TESTS: &str = "\ +#[test] +fn invalid_template() { + panic!(\"The exercise generator failed to produce valid tests from the template. Fix `.meta/test_template.tera`. To write tests manually you MUST delete the template.\"); +} +"; + fn remove_excluded_tests(cases: &mut Vec, excluded_tests: &[String]) { cases.retain(|case| match case { TestCase::Single { case } => !excluded_tests.contains(&case.uuid), diff --git a/rust-tooling/generate/src/main.rs b/rust-tooling/generate/src/main.rs index aa0b0fb65..024242567 100644 --- a/rust-tooling/generate/src/main.rs +++ b/rust-tooling/generate/src/main.rs @@ -79,7 +79,7 @@ fn make_configlet_generate_what_it_can(slug: &str) -> Result<()> { } fn generate_exercise_files(slug: &str, is_update: bool) -> Result<()> { - let exercise = generate::new(slug).context("failed to generate exercise")?; + let exercise = generate::new(slug); let exercise_path = PathBuf::from("exercises/practice").join(slug); diff --git a/rust-tooling/generate/tests/tera_templates_are_in_sync.rs b/rust-tooling/generate/tests/tera_templates_are_in_sync.rs index 62100c36f..d3b5c0f9d 100644 --- a/rust-tooling/generate/tests/tera_templates_are_in_sync.rs +++ b/rust-tooling/generate/tests/tera_templates_are_in_sync.rs @@ -14,7 +14,7 @@ fn tera_templates_are_in_sync() { let exercise_dir = path.parent().unwrap().parent().unwrap(); let slug = exercise_dir.file_name().unwrap().to_string_lossy(); - let generated = generate::new(&slug).unwrap(); + let generated = generate::new(&slug); let test_path = exercise_dir.join("tests").join(format!("{slug}.rs")); let on_disk = std::fs::read_to_string(test_path).unwrap(); From 13370483a6d9206284bd6a9733487c638c1d70ca Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 18 Feb 2025 13:00:05 +0100 Subject: [PATCH 379/436] generate: fill list of files in exercise config (#2028) --- rust-tooling/generate/src/main.rs | 34 +++++++++++++++++++++- rust-tooling/models/src/exercise_config.rs | 6 ++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/rust-tooling/generate/src/main.rs b/rust-tooling/generate/src/main.rs index 024242567..44a6db322 100644 --- a/rust-tooling/generate/src/main.rs +++ b/rust-tooling/generate/src/main.rs @@ -3,7 +3,10 @@ use std::path::PathBuf; use anyhow::{Context, Result}; use clap::Parser; use cli::{prompt_for_template_generation, AddArgs, FullAddArgs, UpdateArgs}; -use models::track_config::{self, TRACK_CONFIG}; +use models::{ + exercise_config::PracticeExercise, + track_config::{self, TRACK_CONFIG}, +}; mod cli; @@ -75,6 +78,35 @@ fn make_configlet_generate_what_it_can(slug: &str) -> Result<()> { if !status.success() { anyhow::bail!("configlet sync failed"); } + + if let Err(e) = ensure_exercise_files_are_filled(slug) { + eprintln!("WARNING: failed to ensure exercise files are filled:\n{e:?}"); + } + + Ok(()) +} + +fn ensure_exercise_files_are_filled(slug: &str) -> Result<()> { + let config_path = format!("exercises/practice/{slug}/.meta/config.json"); + let config = std::fs::read_to_string(&config_path).context("failed to read exercise config")?; + let mut config: PracticeExercise = + serde_json::from_str(&config).context("failed to deserialize exercise config")?; + + let ensure_filled = |list: &mut Vec, content: &str| { + if !list.iter().any(|s| s == content) { + list.push(content.into()) + } + }; + ensure_filled(&mut config.files.solution, "src/lib.rs"); + ensure_filled(&mut config.files.solution, "Cargo.toml"); + ensure_filled(&mut config.files.test, &format!("tests/{slug}.rs")); + ensure_filled(&mut config.files.example, ".meta/example.rs"); + + let mut config = + serde_json::to_string_pretty(&config).context("failed to deserialize config")?; + config.push('\n'); // trailing newline + std::fs::write(config_path, config).context("failed to write config")?; + Ok(()) } diff --git a/rust-tooling/models/src/exercise_config.rs b/rust-tooling/models/src/exercise_config.rs index d4d44e140..cdd23d954 100644 --- a/rust-tooling/models/src/exercise_config.rs +++ b/rust-tooling/models/src/exercise_config.rs @@ -31,13 +31,19 @@ pub struct ConceptFiles { #[serde(deny_unknown_fields)] pub struct PracticeExercise { pub authors: Vec, + #[serde(skip_serializing_if = "Option::is_none")] pub contributors: Option>, pub files: PracticeFiles, + #[serde(skip_serializing_if = "Option::is_none")] pub icon: Option, pub blurb: String, + #[serde(skip_serializing_if = "Option::is_none")] pub source: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub source_url: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub test_runner: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub custom: Option, } From bfa2229dedea85aa67b23bf3ce9971dc23ea8156 Mon Sep 17 00:00:00 2001 From: ellnix Date: Wed, 19 Feb 2025 12:22:18 +0100 Subject: [PATCH 380/436] add-exercise: Prompt for exercise author (#2029) Following discussion on #2028 Since we are now auto filling `$exercise/.meta/config.json`, it makes sense to prompt for author name so that the file doesn't have to be edited after the fact. --- rust-tooling/generate/src/cli.rs | 17 +++++++++++++++++ rust-tooling/generate/src/main.rs | 21 ++++++++++++++++----- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/rust-tooling/generate/src/cli.rs b/rust-tooling/generate/src/cli.rs index 0ded09527..bedf1541c 100644 --- a/rust-tooling/generate/src/cli.rs +++ b/rust-tooling/generate/src/cli.rs @@ -37,12 +37,16 @@ pub struct AddArgs { #[arg(short, long)] difficulty: Option, + + #[arg(short, long)] + author: Option, } pub struct FullAddArgs { pub slug: String, pub name: String, pub difficulty: track_config::Difficulty, + pub author: String, } impl AddArgs { @@ -60,10 +64,17 @@ impl AddArgs { None => prompt_for_difficulty()?, } .into(); + + let author = match self.author { + Some(author) => author, + None => prompt_for_author_name()?, + }; + Ok(FullAddArgs { slug, name, difficulty, + author, }) } } @@ -152,6 +163,12 @@ pub fn prompt_for_exercise_name(slug: &str) -> Result { .context("failed to prompt for exercise name") } +pub fn prompt_for_author_name() -> Result { + Text::new("What is your Github username? (author credit)") + .prompt() + .context("failed to prompt for author name") +} + /// Mostly a clone of the `Difficulty` enum from `models::track_config`. /// The purpose of this is that we can implement cli-specific traits in this crate. #[derive(Debug, Clone, Copy, clap::ValueEnum)] diff --git a/rust-tooling/generate/src/main.rs b/rust-tooling/generate/src/main.rs index 44a6db322..71e9bf1d6 100644 --- a/rust-tooling/generate/src/main.rs +++ b/rust-tooling/generate/src/main.rs @@ -26,6 +26,7 @@ fn add_exercise(args: AddArgs) -> Result<()> { slug, name, difficulty, + author, } = args.unwrap_args_or_prompt()?; let config = track_config::PracticeExercise::new(slug.clone(), name, difficulty); @@ -46,6 +47,11 @@ You can add practices, prerequisites and topics if you like." make_configlet_generate_what_it_can(&slug)?; + if let Err(e) = ensure_exercise_files_are_filled(&slug, (!author.is_empty()).then_some(&author)) + { + eprintln!("WARNING: failed to ensure exercise files are filled:\n{e:?}"); + } + let is_update = false; generate_exercise_files(&slug, is_update) } @@ -55,6 +61,11 @@ fn update_exercise(args: UpdateArgs) -> Result<()> { make_configlet_generate_what_it_can(&slug)?; + let author = None; + if let Err(e) = ensure_exercise_files_are_filled(&slug, author) { + eprintln!("WARNING: failed to ensure exercise files are filled:\n{e:?}"); + } + let is_update = true; generate_exercise_files(&slug, is_update) } @@ -79,14 +90,10 @@ fn make_configlet_generate_what_it_can(slug: &str) -> Result<()> { anyhow::bail!("configlet sync failed"); } - if let Err(e) = ensure_exercise_files_are_filled(slug) { - eprintln!("WARNING: failed to ensure exercise files are filled:\n{e:?}"); - } - Ok(()) } -fn ensure_exercise_files_are_filled(slug: &str) -> Result<()> { +fn ensure_exercise_files_are_filled(slug: &str, author: Option<&str>) -> Result<()> { let config_path = format!("exercises/practice/{slug}/.meta/config.json"); let config = std::fs::read_to_string(&config_path).context("failed to read exercise config")?; let mut config: PracticeExercise = @@ -102,6 +109,10 @@ fn ensure_exercise_files_are_filled(slug: &str) -> Result<()> { ensure_filled(&mut config.files.test, &format!("tests/{slug}.rs")); ensure_filled(&mut config.files.example, ".meta/example.rs"); + if let Some(author) = author { + ensure_filled(&mut config.authors, author); + } + let mut config = serde_json::to_string_pretty(&config).context("failed to deserialize config")?; config.push('\n'); // trailing newline From 16a072ee2cc19aa08edf735990ba32ca3f44b0c7 Mon Sep 17 00:00:00 2001 From: ellnix Date: Thu, 20 Feb 2025 17:58:25 +0100 Subject: [PATCH 381/436] beer-song: replace with bottle-song (#2021) --- config.json | 17 +- .../bottle-song/.docs/instructions.md | 57 +++++++ exercises/practice/bottle-song/.gitignore | 2 + .../practice/bottle-song/.meta/config.json | 20 +++ .../practice/bottle-song/.meta/example.rs | 39 +++++ .../bottle-song/.meta/test_template.tera | 21 +++ .../practice/bottle-song/.meta/tests.toml | 31 ++++ exercises/practice/bottle-song/Cargo.toml | 4 + exercises/practice/bottle-song/src/lib.rs | 3 + .../practice/bottle-song/tests/bottle-song.rs | 158 ++++++++++++++++++ 10 files changed, 347 insertions(+), 5 deletions(-) create mode 100644 exercises/practice/bottle-song/.docs/instructions.md create mode 100644 exercises/practice/bottle-song/.gitignore create mode 100644 exercises/practice/bottle-song/.meta/config.json create mode 100644 exercises/practice/bottle-song/.meta/example.rs create mode 100644 exercises/practice/bottle-song/.meta/test_template.tera create mode 100644 exercises/practice/bottle-song/.meta/tests.toml create mode 100644 exercises/practice/bottle-song/Cargo.toml create mode 100644 exercises/practice/bottle-song/src/lib.rs create mode 100644 exercises/practice/bottle-song/tests/bottle-song.rs diff --git a/config.json b/config.json index 36946adf0..b1ff5ff11 100644 --- a/config.json +++ b/config.json @@ -324,16 +324,23 @@ "slug": "beer-song", "name": "Beer Song", "uuid": "bb42bc3a-139d-4cab-8b3a-2eac2e1b77b6", - "practices": [ - "loops", - "strings" - ], + "practices": [], "prerequisites": [], "difficulty": 1, "topics": [ "case", "vectors" - ] + ], + "status": "deprecated" + }, + { + "slug": "bottle-song", + "name": "Bottle Song", + "uuid": "31e8185b-39d8-48c6-88a0-f302e2864f16", + "practices": [], + "prerequisites": [], + "difficulty": 1, + "topics": [] }, { "slug": "difference-of-squares", diff --git a/exercises/practice/bottle-song/.docs/instructions.md b/exercises/practice/bottle-song/.docs/instructions.md new file mode 100644 index 000000000..febdfc863 --- /dev/null +++ b/exercises/practice/bottle-song/.docs/instructions.md @@ -0,0 +1,57 @@ +# Instructions + +Recite the lyrics to that popular children's repetitive song: Ten Green Bottles. + +Note that not all verses are identical. + +```text +Ten green bottles hanging on the wall, +Ten green bottles hanging on the wall, +And if one green bottle should accidentally fall, +There'll be nine green bottles hanging on the wall. + +Nine green bottles hanging on the wall, +Nine green bottles hanging on the wall, +And if one green bottle should accidentally fall, +There'll be eight green bottles hanging on the wall. + +Eight green bottles hanging on the wall, +Eight green bottles hanging on the wall, +And if one green bottle should accidentally fall, +There'll be seven green bottles hanging on the wall. + +Seven green bottles hanging on the wall, +Seven green bottles hanging on the wall, +And if one green bottle should accidentally fall, +There'll be six green bottles hanging on the wall. + +Six green bottles hanging on the wall, +Six green bottles hanging on the wall, +And if one green bottle should accidentally fall, +There'll be five green bottles hanging on the wall. + +Five green bottles hanging on the wall, +Five green bottles hanging on the wall, +And if one green bottle should accidentally fall, +There'll be four green bottles hanging on the wall. + +Four green bottles hanging on the wall, +Four green bottles hanging on the wall, +And if one green bottle should accidentally fall, +There'll be three green bottles hanging on the wall. + +Three green bottles hanging on the wall, +Three green bottles hanging on the wall, +And if one green bottle should accidentally fall, +There'll be two green bottles hanging on the wall. + +Two green bottles hanging on the wall, +Two green bottles hanging on the wall, +And if one green bottle should accidentally fall, +There'll be one green bottle hanging on the wall. + +One green bottle hanging on the wall, +One green bottle hanging on the wall, +And if one green bottle should accidentally fall, +There'll be no green bottles hanging on the wall. +``` diff --git a/exercises/practice/bottle-song/.gitignore b/exercises/practice/bottle-song/.gitignore new file mode 100644 index 000000000..96ef6c0b9 --- /dev/null +++ b/exercises/practice/bottle-song/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/exercises/practice/bottle-song/.meta/config.json b/exercises/practice/bottle-song/.meta/config.json new file mode 100644 index 000000000..1dcfb0e91 --- /dev/null +++ b/exercises/practice/bottle-song/.meta/config.json @@ -0,0 +1,20 @@ +{ + "authors": [ + "ellnix" + ], + "files": { + "solution": [ + "src/lib.rs", + "Cargo.toml" + ], + "test": [ + "tests/bottle-song.rs" + ], + "example": [ + ".meta/example.rs" + ] + }, + "blurb": "Produce the lyrics to the popular children's repetitive song: Ten Green Bottles.", + "source": "Wikipedia", + "source_url": "https://en.wikipedia.org/wiki/Ten_Green_Bottles" +} diff --git a/exercises/practice/bottle-song/.meta/example.rs b/exercises/practice/bottle-song/.meta/example.rs new file mode 100644 index 000000000..4dc012617 --- /dev/null +++ b/exercises/practice/bottle-song/.meta/example.rs @@ -0,0 +1,39 @@ +pub fn recite(start_bottles: u32, take_down: u32) -> String { + (1..=take_down) + .map(|taken_down| { + let b = start_bottles - taken_down; + let b_word = number_to_eng_word(b).to_lowercase(); + let b_noun = if b == 1 { "bottle" } else { "bottles" }; + let prev_b = b + 1; + let prev_b_word = number_to_eng_word(prev_b); + let prev_b_noun = if prev_b == 1 { "bottle" } else { "bottles" }; + [ + format!("{prev_b_word} green {prev_b_noun} hanging on the wall"), + format!("{prev_b_word} green {prev_b_noun} hanging on the wall"), + "And if one green bottle should accidentally fall".into(), + format!("There'll be {b_word} green {b_noun} hanging on the wall."), + ] + .join(",\n") + }) + .collect::>() + .join("\n\n") + + "\n" +} + +fn number_to_eng_word(digit: u32) -> String { + match digit { + 0 => "No", + 1 => "One", + 2 => "Two", + 3 => "Three", + 4 => "Four", + 5 => "Five", + 6 => "Six", + 7 => "Seven", + 8 => "Eight", + 9 => "Nine", + 10 => "Ten", + _ => panic!("Didn't bother adding numbers past 10..."), + } + .to_string() +} diff --git a/exercises/practice/bottle-song/.meta/test_template.tera b/exercises/practice/bottle-song/.meta/test_template.tera new file mode 100644 index 000000000..f6e2796f3 --- /dev/null +++ b/exercises/practice/bottle-song/.meta/test_template.tera @@ -0,0 +1,21 @@ +use bottle_song::*; + +{% for group in cases %} +{% for subgroup in group.cases %} +{% for test in subgroup.cases %} + +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + assert_eq!( + recite({{ test.input.startBottles }}, {{ test.input.takeDown }}).trim(), + concat!( + {% for line in test.expected %} + "{{line}}{% if not loop.last %}\n{% endif %}", + {% endfor %} + ) + ); +} +{% endfor -%} +{% endfor -%} +{% endfor -%} diff --git a/exercises/practice/bottle-song/.meta/tests.toml b/exercises/practice/bottle-song/.meta/tests.toml new file mode 100644 index 000000000..1f6e40a37 --- /dev/null +++ b/exercises/practice/bottle-song/.meta/tests.toml @@ -0,0 +1,31 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[d4ccf8fc-01dc-48c0-a201-4fbeb30f2d03] +description = "verse -> single verse -> first generic verse" + +[0f0aded3-472a-4c64-b842-18d4f1f5f030] +description = "verse -> single verse -> last generic verse" + +[f61f3c97-131f-459e-b40a-7428f3ed99d9] +description = "verse -> single verse -> verse with 2 bottles" + +[05eadba9-5dbd-401e-a7e8-d17cc9baa8e0] +description = "verse -> single verse -> verse with 1 bottle" + +[a4a28170-83d6-4dc1-bd8b-319b6abb6a80] +description = "lyrics -> multiple verses -> first two verses" + +[3185d438-c5ac-4ce6-bcd3-02c9ff1ed8db] +description = "lyrics -> multiple verses -> last three verses" + +[28c1584a-0e51-4b65-9ae2-fbc0bf4bbb28] +description = "lyrics -> multiple verses -> all verses" diff --git a/exercises/practice/bottle-song/Cargo.toml b/exercises/practice/bottle-song/Cargo.toml new file mode 100644 index 000000000..2559e51b8 --- /dev/null +++ b/exercises/practice/bottle-song/Cargo.toml @@ -0,0 +1,4 @@ +[package] +edition = "2021" +name = "bottle_song" +version = "1.0.0" diff --git a/exercises/practice/bottle-song/src/lib.rs b/exercises/practice/bottle-song/src/lib.rs new file mode 100644 index 000000000..2033677b5 --- /dev/null +++ b/exercises/practice/bottle-song/src/lib.rs @@ -0,0 +1,3 @@ +pub fn recite(start_bottles: u32, take_down: u32) -> String { + todo!("Return the bottle song starting at {start_bottles} and taking down {take_down} bottles") +} diff --git a/exercises/practice/bottle-song/tests/bottle-song.rs b/exercises/practice/bottle-song/tests/bottle-song.rs new file mode 100644 index 000000000..e2f6d79fc --- /dev/null +++ b/exercises/practice/bottle-song/tests/bottle-song.rs @@ -0,0 +1,158 @@ +use bottle_song::*; + +#[test] +fn first_generic_verse() { + assert_eq!( + recite(10, 1).trim(), + concat!( + "Ten green bottles hanging on the wall,\n", + "Ten green bottles hanging on the wall,\n", + "And if one green bottle should accidentally fall,\n", + "There'll be nine green bottles hanging on the wall.", + ) + ); +} + +#[test] +#[ignore] +fn last_generic_verse() { + assert_eq!( + recite(3, 1).trim(), + concat!( + "Three green bottles hanging on the wall,\n", + "Three green bottles hanging on the wall,\n", + "And if one green bottle should accidentally fall,\n", + "There'll be two green bottles hanging on the wall.", + ) + ); +} + +#[test] +#[ignore] +fn verse_with_2_bottles() { + assert_eq!( + recite(2, 1).trim(), + concat!( + "Two green bottles hanging on the wall,\n", + "Two green bottles hanging on the wall,\n", + "And if one green bottle should accidentally fall,\n", + "There'll be one green bottle hanging on the wall.", + ) + ); +} + +#[test] +#[ignore] +fn verse_with_1_bottle() { + assert_eq!( + recite(1, 1).trim(), + concat!( + "One green bottle hanging on the wall,\n", + "One green bottle hanging on the wall,\n", + "And if one green bottle should accidentally fall,\n", + "There'll be no green bottles hanging on the wall.", + ) + ); +} + +#[test] +#[ignore] +fn first_two_verses() { + assert_eq!( + recite(10, 2).trim(), + concat!( + "Ten green bottles hanging on the wall,\n", + "Ten green bottles hanging on the wall,\n", + "And if one green bottle should accidentally fall,\n", + "There'll be nine green bottles hanging on the wall.\n", + "\n", + "Nine green bottles hanging on the wall,\n", + "Nine green bottles hanging on the wall,\n", + "And if one green bottle should accidentally fall,\n", + "There'll be eight green bottles hanging on the wall.", + ) + ); +} + +#[test] +#[ignore] +fn last_three_verses() { + assert_eq!( + recite(3, 3).trim(), + concat!( + "Three green bottles hanging on the wall,\n", + "Three green bottles hanging on the wall,\n", + "And if one green bottle should accidentally fall,\n", + "There'll be two green bottles hanging on the wall.\n", + "\n", + "Two green bottles hanging on the wall,\n", + "Two green bottles hanging on the wall,\n", + "And if one green bottle should accidentally fall,\n", + "There'll be one green bottle hanging on the wall.\n", + "\n", + "One green bottle hanging on the wall,\n", + "One green bottle hanging on the wall,\n", + "And if one green bottle should accidentally fall,\n", + "There'll be no green bottles hanging on the wall.", + ) + ); +} + +#[test] +#[ignore] +fn all_verses() { + assert_eq!( + recite(10, 10).trim(), + concat!( + "Ten green bottles hanging on the wall,\n", + "Ten green bottles hanging on the wall,\n", + "And if one green bottle should accidentally fall,\n", + "There'll be nine green bottles hanging on the wall.\n", + "\n", + "Nine green bottles hanging on the wall,\n", + "Nine green bottles hanging on the wall,\n", + "And if one green bottle should accidentally fall,\n", + "There'll be eight green bottles hanging on the wall.\n", + "\n", + "Eight green bottles hanging on the wall,\n", + "Eight green bottles hanging on the wall,\n", + "And if one green bottle should accidentally fall,\n", + "There'll be seven green bottles hanging on the wall.\n", + "\n", + "Seven green bottles hanging on the wall,\n", + "Seven green bottles hanging on the wall,\n", + "And if one green bottle should accidentally fall,\n", + "There'll be six green bottles hanging on the wall.\n", + "\n", + "Six green bottles hanging on the wall,\n", + "Six green bottles hanging on the wall,\n", + "And if one green bottle should accidentally fall,\n", + "There'll be five green bottles hanging on the wall.\n", + "\n", + "Five green bottles hanging on the wall,\n", + "Five green bottles hanging on the wall,\n", + "And if one green bottle should accidentally fall,\n", + "There'll be four green bottles hanging on the wall.\n", + "\n", + "Four green bottles hanging on the wall,\n", + "Four green bottles hanging on the wall,\n", + "And if one green bottle should accidentally fall,\n", + "There'll be three green bottles hanging on the wall.\n", + "\n", + "Three green bottles hanging on the wall,\n", + "Three green bottles hanging on the wall,\n", + "And if one green bottle should accidentally fall,\n", + "There'll be two green bottles hanging on the wall.\n", + "\n", + "Two green bottles hanging on the wall,\n", + "Two green bottles hanging on the wall,\n", + "And if one green bottle should accidentally fall,\n", + "There'll be one green bottle hanging on the wall.\n", + "\n", + "One green bottle hanging on the wall,\n", + "One green bottle hanging on the wall,\n", + "And if one green bottle should accidentally fall,\n", + "There'll be no green bottles hanging on the wall.", + ) + ); +} From e777561d5f70e89d308e4b4b455704b6a68b14c3 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Fri, 21 Feb 2025 23:55:23 +0100 Subject: [PATCH 382/436] replace ensure_stubs_compile with faster rust test (#2030) --- .github/workflows/tests.yml | 10 --- bin/ensure_stubs_compile.sh | 86 ------------------- justfile | 1 - .../ci-tests/tests/stubs_are_warning_free.rs | 1 + 4 files changed, 1 insertion(+), 97 deletions(-) delete mode 100755 bin/ensure_stubs_compile.sh diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6d0c8a063..d41ba6524 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -73,11 +73,6 @@ jobs: DENYWARNINGS: "1" run: ./bin/check_exercises.sh - - name: Ensure stubs compile - env: - DENYWARNINGS: "1" - run: ./bin/ensure_stubs_compile.sh - tests: name: Run repository tests runs-on: ubuntu-22.04 @@ -136,11 +131,6 @@ jobs: CLIPPY: true run: ./bin/check_exercises.sh - - name: Clippy stubs - env: - CLIPPY: true - run: ./bin/ensure_stubs_compile.sh - nightly-compilation: name: Check exercises on nightly (benchmark enabled) runs-on: ubuntu-22.04 diff --git a/bin/ensure_stubs_compile.sh b/bin/ensure_stubs_compile.sh deleted file mode 100755 index 08c700265..000000000 --- a/bin/ensure_stubs_compile.sh +++ /dev/null @@ -1,86 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail - -repo="$(git rev-parse --show-toplevel)" - -if [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then - git fetch --depth=1 origin main - changed_exercises="$( - git diff --diff-filter=d --name-only remotes/origin/main | - grep "exercises/" | - cut --delimiter '/' --fields -3 | - sort --unique || true - )" -else - # we want globbing and it does not actually assign locally - # shellcheck disable=SC2125 - changed_exercises="$repo"/exercises/*/* -fi - -if [ -z "$changed_exercises" ]; then - echo "No exercise was modified. The script is aborted." - - exit 0 -fi - -broken="" - -for dir in $changed_exercises; do - exercise=$(basename "$dir") - - config_file="$dir/.meta/config.json" - - # An exercise must have a .meta/config.json - if [ ! -f "$config_file" ]; then - continue - fi - - if jq --exit-status '.custom?."allowed-to-not-compile"?' "$config_file"; then - echo "$exercise's stub is allowed to not compile" - continue - fi - - # Backup tests and stub; this script will modify them. - cp -r "$dir/tests" "$dir/tests.orig" - cp "$dir/src/lib.rs" "$dir/lib.rs.orig" - - # In CI, we may have already compiled using the example solution. - # So we want to touch the src/lib.rs in some way, - # so that we surely recompile using the stub. - # Since we also want to deny warnings, that will also serve as the touch. - sed -i -e '1i #![deny(warnings)]' "$dir/src/lib.rs" - - # Deny warnings in the tests that may result from compiling the stubs. - # This helps avoid, for example, an overflowing literal warning - # that could be caused by a stub with a type that is too small. - sed -i -e '1i #![deny(warnings)]' "$dir"/tests/*.rs - - if [ -n "$CLIPPY" ]; then - echo "clippy $exercise..." - # We don't find it useful in general to have students implement Default, - # since we're generally not going to test it. - if ! ( - cd "$dir" && - cargo clippy --lib --tests --color always -- 2>clippy.log - ); then - cat "$dir/clippy.log" - broken="$broken\n$exercise" - else - # Just to show progress - echo "... OK" - fi - elif ! (cd "$dir" && cargo test --quiet --no-run); then - echo "$exercise's stub does not compile; please make it compile" - broken="$broken\n$exercise" - fi - - # Restore tests and stub. - mv "$dir/lib.rs.orig" "$dir/src/lib.rs" - rm -r "$dir/tests" - mv "$dir/tests.orig" "$dir/tests" -done - -if [ -n "$broken" ]; then - echo "Exercises that don't compile:$broken" - exit 1 -fi diff --git a/justfile b/justfile index 8151c2420..fe33e86f4 100644 --- a/justfile +++ b/justfile @@ -20,7 +20,6 @@ test: # TODO shellcheck ./bin/check_exercises.sh CLIPPY=true ./bin/check_exercises.sh - ./bin/ensure_stubs_compile.sh cd rust-tooling && cargo test # TODO format exercises diff --git a/rust-tooling/ci-tests/tests/stubs_are_warning_free.rs b/rust-tooling/ci-tests/tests/stubs_are_warning_free.rs index 94bef91c4..4e06addfe 100644 --- a/rust-tooling/ci-tests/tests/stubs_are_warning_free.rs +++ b/rust-tooling/ci-tests/tests/stubs_are_warning_free.rs @@ -31,6 +31,7 @@ fn stubs_are_warning_free() { .args([ "clippy", "--quiet", + "--tests", "--manifest-path", &manifest.display().to_string(), "--target-dir", From b6fb06b08f3fb2e553a8a483efbf515d77af0ad9 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Fri, 21 Feb 2025 23:55:57 +0100 Subject: [PATCH 383/436] run format_exercises.sh in parallel (#2031) --- bin/format_exercises.sh | 36 ++++++++++++++++++++---------------- justfile | 2 +- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/bin/format_exercises.sh b/bin/format_exercises.sh index e463f09be..34cfb2628 100755 --- a/bin/format_exercises.sh +++ b/bin/format_exercises.sh @@ -5,29 +5,33 @@ set -eo pipefail repo=$(git rev-parse --show-toplevel) +echo "Formatting exercise files..." + +format_one_exercise() { + exercise_dir="$1" + source_file_name="$2" + + cd "$exercise_dir" + config_file="$exercise_dir/.meta/config.json" + if jq --exit-status '.custom?."allowed-to-not-compile"?' "$config_file" &> /dev/null; then + exit 0 + fi + cargo fmt + file_name=".meta/$source_file_name.rs" + if [ -f "$file_name" ]; then + rustfmt "$file_name" + fi +} + # traverse either concept or practice exercise # directory and format Rust files format_exercises() { exercises_path="$repo/exercises/$1" source_file_name="$2" for exercise_dir in "$exercises_path"/*; do - ( - cd "$exercise_dir" - config_file="$exercise_dir/.meta/config.json" - if jq --exit-status '.custom?."allowed-to-not-compile"?' "$config_file"; then - exercise=$(basename "$exercise_dir") - echo "$exercise's stub is allowed to not compile" - # exit the subshell successfully to continue - # to the next exercise directory - exit 0 - fi - cargo fmt - file_name=".meta/$source_file_name.rs" - if [ -f "$file_name" ]; then - rustfmt "$file_name" - fi - ) + format_one_exercise "$exercise_dir" "$source_file_name" & done + wait } # https://github.com/exercism/docs/blob/main/anatomy/tracks/concept-exercises.md#file-exemplar-implementation format_exercises "concept" "exemplar" diff --git a/justfile b/justfile index fe33e86f4..6e94bfbca 100644 --- a/justfile +++ b/justfile @@ -21,7 +21,7 @@ test: ./bin/check_exercises.sh CLIPPY=true ./bin/check_exercises.sh cd rust-tooling && cargo test - # TODO format exercises + ./bin/format_exercises.sh ; git diff --exit-code add-exercise *args="": cd rust-tooling/generate; cargo run --quiet --release -- add {{ args }} From d9a34dca5f967f74421a5025aead550f17fe17e2 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Fri, 21 Feb 2025 23:56:39 +0100 Subject: [PATCH 384/436] update exercise's package manifest (#2033) scripted changes: - update to edition 2024 - bring name, version, edition in customary order - make sure all names use underscores instead of dashes - add comment to dependencies section pointing to available libraries - apply formatting of 2024 edition manual interventions: - fix crate names of all-your-base and two-fer - generator: update manifest template - fix exercises for the 2024 edition --- exercises/concept/assembly-line/Cargo.toml | 9 ++- exercises/concept/csv-builder/Cargo.toml | 7 +- .../concept/health-statistics/Cargo.toml | 7 +- .../low-power-embedded-game/Cargo.toml | 7 +- .../lucians-luscious-lasagna/Cargo.toml | 9 ++- exercises/concept/magazine-cutout/Cargo.toml | 7 +- exercises/concept/resistor-color/Cargo.toml | 9 ++- .../resistor-color/tests/resistor-color.rs | 6 +- .../concept/role-playing-game/Cargo.toml | 7 +- exercises/concept/rpn-calculator/Cargo.toml | 7 +- .../concept/semi-structured-logs/Cargo.toml | 7 +- .../tests/semi-structured-logs.rs | 2 +- exercises/concept/short-fibonacci/Cargo.toml | 7 +- exercises/practice/accumulate/Cargo.toml | 9 ++- exercises/practice/acronym/Cargo.toml | 9 ++- exercises/practice/affine-cipher/Cargo.toml | 11 ++- exercises/practice/all-your-base/Cargo.toml | 9 ++- exercises/practice/allergies/Cargo.toml | 9 ++- exercises/practice/allergies/src/lib.rs | 4 +- .../alphametics/.meta/Cargo-example.toml | 7 +- exercises/practice/alphametics/Cargo.toml | 9 ++- .../practice/alphametics/tests/alphametics.rs | 4 +- exercises/practice/anagram/Cargo.toml | 9 ++- .../practice/armstrong-numbers/Cargo.toml | 9 ++- exercises/practice/atbash-cipher/Cargo.toml | 11 ++- exercises/practice/beer-song/Cargo.toml | 11 ++- exercises/practice/binary-search/Cargo.toml | 11 ++- exercises/practice/bob/Cargo.toml | 9 ++- .../book-store/.meta/Cargo-example.toml | 9 ++- exercises/practice/book-store/Cargo.toml | 9 ++- .../practice/book-store/tests/book-store.rs | 4 +- exercises/practice/bottle-song/Cargo.toml | 9 ++- exercises/practice/bowling/Cargo.toml | 9 ++- exercises/practice/bowling/tests/bowling.rs | 8 +- exercises/practice/circular-buffer/Cargo.toml | 11 ++- exercises/practice/circular-buffer/src/lib.rs | 12 ++- exercises/practice/clock/Cargo.toml | 9 ++- .../practice/collatz-conjecture/Cargo.toml | 9 ++- exercises/practice/crypto-square/Cargo.toml | 9 ++- exercises/practice/custom-set/Cargo.toml | 11 ++- .../practice/decimal/.meta/Cargo-example.toml | 5 +- exercises/practice/decimal/Cargo.toml | 7 +- exercises/practice/diamond/Cargo.toml | 9 ++- .../practice/difference-of-squares/Cargo.toml | 11 ++- .../diffie-hellman/.meta/Cargo-example.toml | 7 +- exercises/practice/diffie-hellman/Cargo.toml | 9 ++- exercises/practice/dominoes/Cargo.toml | 9 ++- exercises/practice/dominoes/src/lib.rs | 4 +- exercises/practice/dot-dsl/Cargo.toml | 9 ++- .../doubly-linked-list/.meta/example.rs | 18 +++-- .../practice/doubly-linked-list/Cargo.toml | 11 ++- .../tests/step_4_leak_test_1.rs | 4 +- .../tests/step_4_leak_test_2.rs | 4 +- exercises/practice/eliuds-eggs/Cargo.toml | 9 ++- exercises/practice/etl/Cargo.toml | 9 ++- exercises/practice/fizzy/.meta/example.rs | 2 +- exercises/practice/fizzy/Cargo.toml | 9 ++- exercises/practice/fizzy/tests/fizzy.rs | 2 +- exercises/practice/forth/Cargo.toml | 9 ++- .../practice/forth/tests/alloc-attack.rs | 4 +- exercises/practice/gigasecond/Cargo.toml | 7 +- exercises/practice/grade-school/Cargo.toml | 11 ++- exercises/practice/grains/Cargo.toml | 9 ++- .../practice/grep/.meta/Cargo-example.toml | 13 +-- exercises/practice/grep/Cargo.toml | 13 +-- exercises/practice/hamming/Cargo.toml | 9 ++- exercises/practice/hello-world/Cargo.toml | 11 ++- exercises/practice/hexadecimal/Cargo.toml | 8 +- exercises/practice/high-scores/Cargo.toml | 11 ++- exercises/practice/isbn-verifier/Cargo.toml | 11 ++- exercises/practice/isogram/Cargo.toml | 9 ++- .../practice/kindergarten-garden/Cargo.toml | 7 +- exercises/practice/knapsack/Cargo.toml | 7 +- .../largest-series-product/Cargo.toml | 11 ++- exercises/practice/leap/Cargo.toml | 9 ++- exercises/practice/list-ops/Cargo.toml | 9 ++- exercises/practice/luhn-from/Cargo.toml | 11 ++- exercises/practice/luhn-trait/Cargo.toml | 11 ++- exercises/practice/luhn/Cargo.toml | 9 ++- exercises/practice/macros/Cargo.toml | 7 +- .../practice/matching-brackets/Cargo.toml | 11 ++- exercises/practice/matrix/Cargo.toml | 9 ++- exercises/practice/minesweeper/Cargo.toml | 9 ++- exercises/practice/minesweeper/src/lib.rs | 4 +- exercises/practice/nth-prime/Cargo.toml | 9 ++- .../practice/nucleotide-codons/Cargo.toml | 6 ++ .../practice/nucleotide-codons/src/lib.rs | 5 +- .../practice/nucleotide-count/Cargo.toml | 11 ++- exercises/practice/ocr-numbers/Cargo.toml | 11 ++- exercises/practice/paasio/Cargo.toml | 9 ++- .../practice/palindrome-products/Cargo.toml | 11 ++- exercises/practice/pangram/Cargo.toml | 9 ++- .../parallel-letter-frequency/Cargo.toml | 11 ++- .../practice/pascals-triangle/Cargo.toml | 11 ++- exercises/practice/perfect-numbers/Cargo.toml | 9 ++- exercises/practice/phone-number/Cargo.toml | 11 ++- .../pig-latin/.meta/Cargo-example.toml | 9 ++- exercises/practice/pig-latin/Cargo.toml | 11 ++- .../practice/poker/.meta/Cargo-example.toml | 7 +- exercises/practice/poker/Cargo.toml | 9 ++- exercises/practice/poker/tests/poker.rs | 4 +- exercises/practice/prime-factors/Cargo.toml | 9 ++- .../practice/protein-translation/Cargo.toml | 9 ++- .../practice/protein-translation/src/lib.rs | 4 +- exercises/practice/proverb/Cargo.toml | 9 ++- .../practice/pythagorean-triplet/Cargo.toml | 9 ++- .../practice/pythagorean-triplet/src/lib.rs | 4 +- exercises/practice/queen-attack/Cargo.toml | 11 ++- .../queen-attack/tests/queen-attack.rs | 4 +- .../practice/rail-fence-cipher/Cargo.toml | 9 ++- exercises/practice/raindrops/Cargo.toml | 9 ++- exercises/practice/react/Cargo.toml | 9 ++- exercises/practice/react/tests/react.rs | 80 ++++++++++++------- exercises/practice/rectangles/Cargo.toml | 9 ++- exercises/practice/rectangles/src/lib.rs | 4 +- exercises/practice/reverse-string/Cargo.toml | 9 ++- .../practice/rna-transcription/Cargo.toml | 11 ++- .../practice/rna-transcription/src/lib.rs | 8 +- .../robot-name/.meta/Cargo-example.toml | 9 ++- exercises/practice/robot-name/Cargo.toml | 11 ++- exercises/practice/robot-simulator/Cargo.toml | 11 ++- exercises/practice/roman-numerals/Cargo.toml | 11 ++- .../practice/rotational-cipher/Cargo.toml | 11 ++- .../practice/run-length-encoding/Cargo.toml | 11 ++- exercises/practice/saddle-points/Cargo.toml | 11 ++- exercises/practice/say/Cargo.toml | 9 ++- exercises/practice/scale-generator/Cargo.toml | 9 ++- exercises/practice/scrabble-score/Cargo.toml | 11 ++- .../practice/secret-handshake/Cargo.toml | 11 ++- exercises/practice/series/Cargo.toml | 7 +- exercises/practice/sieve/Cargo.toml | 9 ++- exercises/practice/simple-cipher/Cargo.toml | 9 ++- .../practice/simple-linked-list/Cargo.toml | 7 +- exercises/practice/space-age/Cargo.toml | 11 ++- exercises/practice/spiral-matrix/Cargo.toml | 11 ++- exercises/practice/sublist/Cargo.toml | 9 ++- exercises/practice/sublist/src/lib.rs | 4 +- .../practice/sum-of-multiples/Cargo.toml | 11 ++- exercises/practice/tournament/Cargo.toml | 9 ++- exercises/practice/triangle/Cargo.toml | 9 ++- exercises/practice/triangle/src/lib.rs | 4 +- exercises/practice/two-bucket/Cargo.toml | 11 ++- .../practice/two-bucket/tests/two-bucket.rs | 4 +- exercises/practice/two-fer/Cargo.toml | 9 ++- .../variable-length-quantity/Cargo.toml | 11 ++- exercises/practice/word-count/Cargo.toml | 11 ++- exercises/practice/wordy/Cargo.toml | 9 ++- exercises/practice/xorcism/Cargo.toml | 7 +- exercises/practice/yacht/Cargo.toml | 7 +- rust-tooling/generate/src/lib.rs | 18 +++-- 150 files changed, 1013 insertions(+), 357 deletions(-) diff --git a/exercises/concept/assembly-line/Cargo.toml b/exercises/concept/assembly-line/Cargo.toml index 5a7f2c4ca..b9ea4ccfa 100644 --- a/exercises/concept/assembly-line/Cargo.toml +++ b/exercises/concept/assembly-line/Cargo.toml @@ -1,4 +1,9 @@ [package] -name = "assembly-line" +name = "assembly_line" version = "0.1.0" -edition = "2021" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/concept/csv-builder/Cargo.toml b/exercises/concept/csv-builder/Cargo.toml index 29b5af9ee..cf8a4454f 100644 --- a/exercises/concept/csv-builder/Cargo.toml +++ b/exercises/concept/csv-builder/Cargo.toml @@ -1,7 +1,12 @@ [package] name = "csv_builder" version = "0.1.0" -edition = "2021" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] [lints.clippy] new_without_default = "allow" diff --git a/exercises/concept/health-statistics/Cargo.toml b/exercises/concept/health-statistics/Cargo.toml index 66b5fbc15..2374e2e21 100644 --- a/exercises/concept/health-statistics/Cargo.toml +++ b/exercises/concept/health-statistics/Cargo.toml @@ -1,4 +1,9 @@ [package] name = "health_statistics" version = "0.1.0" -edition = "2021" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/concept/low-power-embedded-game/Cargo.toml b/exercises/concept/low-power-embedded-game/Cargo.toml index 8cfaf6aae..f589a4f72 100644 --- a/exercises/concept/low-power-embedded-game/Cargo.toml +++ b/exercises/concept/low-power-embedded-game/Cargo.toml @@ -1,4 +1,9 @@ [package] name = "low_power_embedded_game" version = "0.1.0" -edition = "2021" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/concept/lucians-luscious-lasagna/Cargo.toml b/exercises/concept/lucians-luscious-lasagna/Cargo.toml index 6f8876654..88240adab 100644 --- a/exercises/concept/lucians-luscious-lasagna/Cargo.toml +++ b/exercises/concept/lucians-luscious-lasagna/Cargo.toml @@ -1,4 +1,9 @@ [package] -name = "lucians-luscious-lasagna" +name = "lucians_luscious_lasagna" version = "0.1.0" -edition = "2021" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/concept/magazine-cutout/Cargo.toml b/exercises/concept/magazine-cutout/Cargo.toml index 2ad12cef8..358fc33b7 100644 --- a/exercises/concept/magazine-cutout/Cargo.toml +++ b/exercises/concept/magazine-cutout/Cargo.toml @@ -1,4 +1,9 @@ [package] name = "magazine_cutout" version = "0.1.0" -edition = "2021" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/concept/resistor-color/Cargo.toml b/exercises/concept/resistor-color/Cargo.toml index 8c7709d35..04cab7865 100644 --- a/exercises/concept/resistor-color/Cargo.toml +++ b/exercises/concept/resistor-color/Cargo.toml @@ -1,8 +1,11 @@ [package] -edition = "2021" -name = "resistor-color" -version = "1.0.0" +name = "resistor_color" +version = "0.1.0" +edition = "2024" +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml [dependencies] int-enum = "0.5.0" enum-iterator = "1.2.0" diff --git a/exercises/concept/resistor-color/tests/resistor-color.rs b/exercises/concept/resistor-color/tests/resistor-color.rs index b0806797b..1c20e3217 100644 --- a/exercises/concept/resistor-color/tests/resistor-color.rs +++ b/exercises/concept/resistor-color/tests/resistor-color.rs @@ -1,4 +1,4 @@ -use resistor_color::{color_to_value, colors, value_to_color_string, ResistorColor}; +use resistor_color::{ResistorColor, color_to_value, colors, value_to_color_string}; #[test] fn black() { @@ -50,6 +50,8 @@ fn all_colors() { use ResistorColor::*; assert_eq!( colors(), - vec![Black, Brown, Red, Orange, Yellow, Green, Blue, Violet, Grey, White] + vec![ + Black, Brown, Red, Orange, Yellow, Green, Blue, Violet, Grey, White + ] ); } diff --git a/exercises/concept/role-playing-game/Cargo.toml b/exercises/concept/role-playing-game/Cargo.toml index ab9f24f21..4773e0985 100644 --- a/exercises/concept/role-playing-game/Cargo.toml +++ b/exercises/concept/role-playing-game/Cargo.toml @@ -1,4 +1,9 @@ [package] name = "role_playing_game" version = "0.1.0" -edition = "2021" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/concept/rpn-calculator/Cargo.toml b/exercises/concept/rpn-calculator/Cargo.toml index 99ae2cf09..c712676eb 100644 --- a/exercises/concept/rpn-calculator/Cargo.toml +++ b/exercises/concept/rpn-calculator/Cargo.toml @@ -1,4 +1,9 @@ [package] name = "rpn_calculator" version = "0.1.0" -edition = "2021" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/concept/semi-structured-logs/Cargo.toml b/exercises/concept/semi-structured-logs/Cargo.toml index 5e8e3d53e..a12cc4518 100644 --- a/exercises/concept/semi-structured-logs/Cargo.toml +++ b/exercises/concept/semi-structured-logs/Cargo.toml @@ -1,7 +1,12 @@ [package] name = "semi_structured_logs" version = "0.1.0" -edition = "2021" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] [features] add-a-variant = [] diff --git a/exercises/concept/semi-structured-logs/tests/semi-structured-logs.rs b/exercises/concept/semi-structured-logs/tests/semi-structured-logs.rs index 04168b139..65eb4d5c9 100644 --- a/exercises/concept/semi-structured-logs/tests/semi-structured-logs.rs +++ b/exercises/concept/semi-structured-logs/tests/semi-structured-logs.rs @@ -1,4 +1,4 @@ -use semi_structured_logs::{error, info, log, warn, LogLevel}; +use semi_structured_logs::{LogLevel, error, info, log, warn}; #[test] fn emits_info() { diff --git a/exercises/concept/short-fibonacci/Cargo.toml b/exercises/concept/short-fibonacci/Cargo.toml index d2ee550cc..1740abd20 100644 --- a/exercises/concept/short-fibonacci/Cargo.toml +++ b/exercises/concept/short-fibonacci/Cargo.toml @@ -1,4 +1,9 @@ [package] name = "short_fibonacci" version = "0.1.0" -edition = "2021" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/accumulate/Cargo.toml b/exercises/practice/accumulate/Cargo.toml index 795e2befd..05f075462 100644 --- a/exercises/practice/accumulate/Cargo.toml +++ b/exercises/practice/accumulate/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "accumulate" -version = "0.0.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/acronym/Cargo.toml b/exercises/practice/acronym/Cargo.toml index cc0c031d6..c83115be8 100644 --- a/exercises/practice/acronym/Cargo.toml +++ b/exercises/practice/acronym/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "acronym" -version = "1.7.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/affine-cipher/Cargo.toml b/exercises/practice/affine-cipher/Cargo.toml index 8f487a07f..979844792 100644 --- a/exercises/practice/affine-cipher/Cargo.toml +++ b/exercises/practice/affine-cipher/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "affine-cipher" -version = "2.0.0" +name = "affine_cipher" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/all-your-base/Cargo.toml b/exercises/practice/all-your-base/Cargo.toml index 8ecd11098..7e9f62613 100644 --- a/exercises/practice/all-your-base/Cargo.toml +++ b/exercises/practice/all-your-base/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "allyourbase" -version = "1.0.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/allergies/Cargo.toml b/exercises/practice/allergies/Cargo.toml index 7bd71fcf9..41adcdd93 100644 --- a/exercises/practice/allergies/Cargo.toml +++ b/exercises/practice/allergies/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "allergies" -version = "1.1.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/allergies/src/lib.rs b/exercises/practice/allergies/src/lib.rs index a2fd3088f..6ded730ed 100644 --- a/exercises/practice/allergies/src/lib.rs +++ b/exercises/practice/allergies/src/lib.rs @@ -22,6 +22,8 @@ impl Allergies { } pub fn allergies(&self) -> Vec { - todo!("Return the list of allergens contained within the score with which the Allergies struct was made."); + todo!( + "Return the list of allergens contained within the score with which the Allergies struct was made." + ); } } diff --git a/exercises/practice/alphametics/.meta/Cargo-example.toml b/exercises/practice/alphametics/.meta/Cargo-example.toml index a83ef229b..1c70f6e2f 100644 --- a/exercises/practice/alphametics/.meta/Cargo-example.toml +++ b/exercises/practice/alphametics/.meta/Cargo-example.toml @@ -1,8 +1,11 @@ [package] -edition = "2021" name = "alphametics" -version = "0.0.0" +version = "0.1.0" +edition = "2024" +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml [dependencies] itertools = "0.5" permutohedron = "0.2" diff --git a/exercises/practice/alphametics/Cargo.toml b/exercises/practice/alphametics/Cargo.toml index bef2c20db..f2bd83000 100644 --- a/exercises/practice/alphametics/Cargo.toml +++ b/exercises/practice/alphametics/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "alphametics" -version = "1.3.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/alphametics/tests/alphametics.rs b/exercises/practice/alphametics/tests/alphametics.rs index 06f2920bc..63817df8b 100644 --- a/exercises/practice/alphametics/tests/alphametics.rs +++ b/exercises/practice/alphametics/tests/alphametics.rs @@ -110,7 +110,9 @@ fn puzzle_with_ten_letters() { #[test] #[ignore] fn puzzle_with_ten_letters_and_199_addends() { - let answer = solve("THIS + A + FIRE + THEREFORE + FOR + ALL + HISTORIES + I + TELL + A + TALE + THAT + FALSIFIES + ITS + TITLE + TIS + A + LIE + THE + TALE + OF + THE + LAST + FIRE + HORSES + LATE + AFTER + THE + FIRST + FATHERS + FORESEE + THE + HORRORS + THE + LAST + FREE + TROLL + TERRIFIES + THE + HORSES + OF + FIRE + THE + TROLL + RESTS + AT + THE + HOLE + OF + LOSSES + IT + IS + THERE + THAT + SHE + STORES + ROLES + OF + LEATHERS + AFTER + SHE + SATISFIES + HER + HATE + OFF + THOSE + FEARS + A + TASTE + RISES + AS + SHE + HEARS + THE + LEAST + FAR + HORSE + THOSE + FAST + HORSES + THAT + FIRST + HEAR + THE + TROLL + FLEE + OFF + TO + THE + FOREST + THE + HORSES + THAT + ALERTS + RAISE + THE + STARES + OF + THE + OTHERS + AS + THE + TROLL + ASSAILS + AT + THE + TOTAL + SHIFT + HER + TEETH + TEAR + HOOF + OFF + TORSO + AS + THE + LAST + HORSE + FORFEITS + ITS + LIFE + THE + FIRST + FATHERS + HEAR + OF + THE + HORRORS + THEIR + FEARS + THAT + THE + FIRES + FOR + THEIR + FEASTS + ARREST + AS + THE + FIRST + FATHERS + RESETTLE + THE + LAST + OF + THE + FIRE + HORSES + THE + LAST + TROLL + HARASSES + THE + FOREST + HEART + FREE + AT + LAST + OF + THE + LAST + TROLL + ALL + OFFER + THEIR + FIRE + HEAT + TO + THE + ASSISTERS + FAR + OFF + THE + TROLL + FASTS + ITS + LIFE + SHORTER + AS + STARS + RISE + THE + HORSES + REST + SAFE + AFTER + ALL + SHARE + HOT + FISH + AS + THEIR + AFFILIATES + TAILOR + A + ROOFS + FOR + THEIR + SAFE == FORTRESSES"); + let answer = solve( + "THIS + A + FIRE + THEREFORE + FOR + ALL + HISTORIES + I + TELL + A + TALE + THAT + FALSIFIES + ITS + TITLE + TIS + A + LIE + THE + TALE + OF + THE + LAST + FIRE + HORSES + LATE + AFTER + THE + FIRST + FATHERS + FORESEE + THE + HORRORS + THE + LAST + FREE + TROLL + TERRIFIES + THE + HORSES + OF + FIRE + THE + TROLL + RESTS + AT + THE + HOLE + OF + LOSSES + IT + IS + THERE + THAT + SHE + STORES + ROLES + OF + LEATHERS + AFTER + SHE + SATISFIES + HER + HATE + OFF + THOSE + FEARS + A + TASTE + RISES + AS + SHE + HEARS + THE + LEAST + FAR + HORSE + THOSE + FAST + HORSES + THAT + FIRST + HEAR + THE + TROLL + FLEE + OFF + TO + THE + FOREST + THE + HORSES + THAT + ALERTS + RAISE + THE + STARES + OF + THE + OTHERS + AS + THE + TROLL + ASSAILS + AT + THE + TOTAL + SHIFT + HER + TEETH + TEAR + HOOF + OFF + TORSO + AS + THE + LAST + HORSE + FORFEITS + ITS + LIFE + THE + FIRST + FATHERS + HEAR + OF + THE + HORRORS + THEIR + FEARS + THAT + THE + FIRES + FOR + THEIR + FEASTS + ARREST + AS + THE + FIRST + FATHERS + RESETTLE + THE + LAST + OF + THE + FIRE + HORSES + THE + LAST + TROLL + HARASSES + THE + FOREST + HEART + FREE + AT + LAST + OF + THE + LAST + TROLL + ALL + OFFER + THEIR + FIRE + HEAT + TO + THE + ASSISTERS + FAR + OFF + THE + TROLL + FASTS + ITS + LIFE + SHORTER + AS + STARS + RISE + THE + HORSES + REST + SAFE + AFTER + ALL + SHARE + HOT + FISH + AS + THEIR + AFFILIATES + TAILOR + A + ROOFS + FOR + THEIR + SAFE == FORTRESSES", + ); let expected = [ ('A', 1), ('E', 0), diff --git a/exercises/practice/anagram/Cargo.toml b/exercises/practice/anagram/Cargo.toml index 5e06e4e55..f4381a85d 100644 --- a/exercises/practice/anagram/Cargo.toml +++ b/exercises/practice/anagram/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "anagram" -version = "0.0.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/armstrong-numbers/Cargo.toml b/exercises/practice/armstrong-numbers/Cargo.toml index 4949ea7fd..cb49ce6dc 100644 --- a/exercises/practice/armstrong-numbers/Cargo.toml +++ b/exercises/practice/armstrong-numbers/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "armstrong_numbers" -version = "1.1.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/atbash-cipher/Cargo.toml b/exercises/practice/atbash-cipher/Cargo.toml index 881ddf641..f2f21e973 100644 --- a/exercises/practice/atbash-cipher/Cargo.toml +++ b/exercises/practice/atbash-cipher/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "atbash-cipher" -version = "1.2.0" +name = "atbash_cipher" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/beer-song/Cargo.toml b/exercises/practice/beer-song/Cargo.toml index 11c518b4f..848f1827e 100644 --- a/exercises/practice/beer-song/Cargo.toml +++ b/exercises/practice/beer-song/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "beer-song" -version = "0.0.0" +name = "beer_song" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/binary-search/Cargo.toml b/exercises/practice/binary-search/Cargo.toml index 5e8ac2fd9..f7721fb90 100644 --- a/exercises/practice/binary-search/Cargo.toml +++ b/exercises/practice/binary-search/Cargo.toml @@ -1,7 +1,12 @@ [package] -edition = "2021" -name = "binary-search" -version = "1.3.0" +name = "binary_search" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] [features] generic = [] diff --git a/exercises/practice/bob/Cargo.toml b/exercises/practice/bob/Cargo.toml index 1ea91729c..f7ab5ddcc 100644 --- a/exercises/practice/bob/Cargo.toml +++ b/exercises/practice/bob/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "bob" -version = "1.6.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/book-store/.meta/Cargo-example.toml b/exercises/practice/book-store/.meta/Cargo-example.toml index 767b9c1e8..2178d1a4d 100644 --- a/exercises/practice/book-store/.meta/Cargo-example.toml +++ b/exercises/practice/book-store/.meta/Cargo-example.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "book_store" -version = "1.3.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/book-store/Cargo.toml b/exercises/practice/book-store/Cargo.toml index 767b9c1e8..2178d1a4d 100644 --- a/exercises/practice/book-store/Cargo.toml +++ b/exercises/practice/book-store/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "book_store" -version = "1.3.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/book-store/tests/book-store.rs b/exercises/practice/book-store/tests/book-store.rs index 944139343..d61a40e53 100644 --- a/exercises/practice/book-store/tests/book-store.rs +++ b/exercises/practice/book-store/tests/book-store.rs @@ -136,8 +136,8 @@ fn four_groups_of_four_are_cheaper_than_two_groups_each_of_five_and_three() { #[test] #[ignore] -fn check_that_groups_of_four_are_created_properly_even_when_there_are_more_groups_of_three_than_groups_of_five( -) { +fn check_that_groups_of_four_are_created_properly_even_when_there_are_more_groups_of_three_than_groups_of_five() + { let input = &[ 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 4, 5, 5, ]; diff --git a/exercises/practice/bottle-song/Cargo.toml b/exercises/practice/bottle-song/Cargo.toml index 2559e51b8..a133c667a 100644 --- a/exercises/practice/bottle-song/Cargo.toml +++ b/exercises/practice/bottle-song/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "bottle_song" -version = "1.0.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/bowling/Cargo.toml b/exercises/practice/bowling/Cargo.toml index e8ab79369..81023c878 100644 --- a/exercises/practice/bowling/Cargo.toml +++ b/exercises/practice/bowling/Cargo.toml @@ -1,7 +1,12 @@ [package] -edition = "2021" name = "bowling" -version = "1.2.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] [lints.clippy] new_without_default = "allow" diff --git a/exercises/practice/bowling/tests/bowling.rs b/exercises/practice/bowling/tests/bowling.rs index 140a00527..906568f2f 100644 --- a/exercises/practice/bowling/tests/bowling.rs +++ b/exercises/practice/bowling/tests/bowling.rs @@ -315,8 +315,8 @@ fn two_bonus_rolls_after_a_strike_in_the_last_frame_cannot_score_more_than_10_po #[test] #[ignore] -fn two_bonus_rolls_after_a_strike_in_the_last_frame_can_score_more_than_10_points_if_one_is_a_strike( -) { +fn two_bonus_rolls_after_a_strike_in_the_last_frame_can_score_more_than_10_points_if_one_is_a_strike() + { let mut game = BowlingGame::new(); for _ in 0..18 { @@ -331,8 +331,8 @@ fn two_bonus_rolls_after_a_strike_in_the_last_frame_can_score_more_than_10_point #[test] #[ignore] -fn the_second_bonus_rolls_after_a_strike_in_the_last_frame_cannot_be_a_strike_if_the_first_one_is_not_a_strike( -) { +fn the_second_bonus_rolls_after_a_strike_in_the_last_frame_cannot_be_a_strike_if_the_first_one_is_not_a_strike() + { let mut game = BowlingGame::new(); for _ in 0..18 { diff --git a/exercises/practice/circular-buffer/Cargo.toml b/exercises/practice/circular-buffer/Cargo.toml index 63c8be965..89de6cb01 100644 --- a/exercises/practice/circular-buffer/Cargo.toml +++ b/exercises/practice/circular-buffer/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "circular-buffer" -version = "1.1.0" +name = "circular_buffer" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/circular-buffer/src/lib.rs b/exercises/practice/circular-buffer/src/lib.rs index 63686c805..93b54abf7 100644 --- a/exercises/practice/circular-buffer/src/lib.rs +++ b/exercises/practice/circular-buffer/src/lib.rs @@ -22,11 +22,15 @@ impl CircularBuffer { } pub fn write(&mut self, _element: T) -> Result<(), Error> { - todo!("Write the passed element to the CircularBuffer or return FullBuffer error if CircularBuffer is full."); + todo!( + "Write the passed element to the CircularBuffer or return FullBuffer error if CircularBuffer is full." + ); } pub fn read(&mut self) -> Result { - todo!("Read the oldest element from the CircularBuffer or return EmptyBuffer error if CircularBuffer is empty."); + todo!( + "Read the oldest element from the CircularBuffer or return EmptyBuffer error if CircularBuffer is empty." + ); } pub fn clear(&mut self) { @@ -34,6 +38,8 @@ impl CircularBuffer { } pub fn overwrite(&mut self, _element: T) { - todo!("Write the passed element to the CircularBuffer, overwriting the existing elements if CircularBuffer is full."); + todo!( + "Write the passed element to the CircularBuffer, overwriting the existing elements if CircularBuffer is full." + ); } } diff --git a/exercises/practice/clock/Cargo.toml b/exercises/practice/clock/Cargo.toml index 5c1ff8daf..79c6ff8e7 100644 --- a/exercises/practice/clock/Cargo.toml +++ b/exercises/practice/clock/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "clock" -version = "2.4.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/collatz-conjecture/Cargo.toml b/exercises/practice/collatz-conjecture/Cargo.toml index dd64fa792..4463d57e8 100644 --- a/exercises/practice/collatz-conjecture/Cargo.toml +++ b/exercises/practice/collatz-conjecture/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "collatz_conjecture" -version = "1.2.1" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/crypto-square/Cargo.toml b/exercises/practice/crypto-square/Cargo.toml index e617068eb..b8e23929a 100644 --- a/exercises/practice/crypto-square/Cargo.toml +++ b/exercises/practice/crypto-square/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "crypto-square" +name = "crypto_square" version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/custom-set/Cargo.toml b/exercises/practice/custom-set/Cargo.toml index d595327c1..e56e5dd82 100644 --- a/exercises/practice/custom-set/Cargo.toml +++ b/exercises/practice/custom-set/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "custom-set" -version = "1.0.1" +name = "custom_set" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/decimal/.meta/Cargo-example.toml b/exercises/practice/decimal/.meta/Cargo-example.toml index 96fbf9772..e2a4c289f 100644 --- a/exercises/practice/decimal/.meta/Cargo-example.toml +++ b/exercises/practice/decimal/.meta/Cargo-example.toml @@ -1,8 +1,11 @@ [package] -edition = "2021" name = "decimal" version = "0.1.0" +edition = "2024" +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml [dependencies] num-bigint = "0.4.4" num-traits = "0.2.16" diff --git a/exercises/practice/decimal/Cargo.toml b/exercises/practice/decimal/Cargo.toml index 6f1d36ac7..3a34b5801 100644 --- a/exercises/practice/decimal/Cargo.toml +++ b/exercises/practice/decimal/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "decimal" version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/diamond/Cargo.toml b/exercises/practice/diamond/Cargo.toml index 26c54dcc3..436ce7a0b 100644 --- a/exercises/practice/diamond/Cargo.toml +++ b/exercises/practice/diamond/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "diamond" -version = "1.1.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/difference-of-squares/Cargo.toml b/exercises/practice/difference-of-squares/Cargo.toml index 71d56f826..6b0bc15cd 100644 --- a/exercises/practice/difference-of-squares/Cargo.toml +++ b/exercises/practice/difference-of-squares/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "difference-of-squares" -version = "1.2.0" +name = "difference_of_squares" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/diffie-hellman/.meta/Cargo-example.toml b/exercises/practice/diffie-hellman/.meta/Cargo-example.toml index 49b664725..54647854c 100644 --- a/exercises/practice/diffie-hellman/.meta/Cargo-example.toml +++ b/exercises/practice/diffie-hellman/.meta/Cargo-example.toml @@ -1,8 +1,11 @@ [package] -edition = "2021" -name = "diffie-hellman" +name = "diffie_hellman" version = "0.1.0" +edition = "2024" +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml [dependencies] rand = "0.3.18" diff --git a/exercises/practice/diffie-hellman/Cargo.toml b/exercises/practice/diffie-hellman/Cargo.toml index 5b588a15e..82e5d871d 100644 --- a/exercises/practice/diffie-hellman/Cargo.toml +++ b/exercises/practice/diffie-hellman/Cargo.toml @@ -1,7 +1,12 @@ [package] -edition = "2021" -name = "diffie-hellman" +name = "diffie_hellman" version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] [features] big-primes = [] diff --git a/exercises/practice/dominoes/Cargo.toml b/exercises/practice/dominoes/Cargo.toml index 1ecfccf0f..38fa62836 100644 --- a/exercises/practice/dominoes/Cargo.toml +++ b/exercises/practice/dominoes/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "dominoes" -version = "2.1.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/dominoes/src/lib.rs b/exercises/practice/dominoes/src/lib.rs index 3137bddc7..e1419acd4 100644 --- a/exercises/practice/dominoes/src/lib.rs +++ b/exercises/practice/dominoes/src/lib.rs @@ -1,3 +1,5 @@ pub fn chain(input: &[(u8, u8)]) -> Option> { - todo!("From the given input '{input:?}' construct a proper dominoes chain or return None if it is not possible."); + todo!( + "From the given input '{input:?}' construct a proper dominoes chain or return None if it is not possible." + ); } diff --git a/exercises/practice/dot-dsl/Cargo.toml b/exercises/practice/dot-dsl/Cargo.toml index 9361866ea..19072537d 100644 --- a/exercises/practice/dot-dsl/Cargo.toml +++ b/exercises/practice/dot-dsl/Cargo.toml @@ -1,7 +1,12 @@ [package] -edition = "2021" -name = "dot-dsl" +name = "dot_dsl" version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] [lints.clippy] new_without_default = "allow" diff --git a/exercises/practice/doubly-linked-list/.meta/example.rs b/exercises/practice/doubly-linked-list/.meta/example.rs index d9b325ac3..f5c067fb5 100644 --- a/exercises/practice/doubly-linked-list/.meta/example.rs +++ b/exercises/practice/doubly-linked-list/.meta/example.rs @@ -62,8 +62,10 @@ impl Node { // `left` and `right` must point to adjacent nodes unsafe fn link(mut left: NodePtr, mut right: NodePtr) { - left.as_mut().next = Some(right); - right.as_mut().prev = Some(left); + unsafe { + left.as_mut().next = Some(right); + right.as_mut().prev = Some(left); + } } } @@ -164,11 +166,13 @@ impl Cursor<'_, T> { // a mutable reference to its element. // `get_next` must return None or a pointer owned by the linked list unsafe fn _step(&mut self, get_next: impl Fn(&Node) -> OptNodePtr) -> Option<&mut T> { - // safe due to L1: All NodePtrs are valid - let new_pos = get_next(self.node?.as_ref())?; - self.node = Some(new_pos); - // returning a mutable reference is safe for the same reason peek_mut() is safe - Some(&mut (*new_pos.as_ptr()).element) + unsafe { + // safe due to L1: All NodePtrs are valid + let new_pos = get_next(self.node?.as_ref())?; + self.node = Some(new_pos); + // returning a mutable reference is safe for the same reason peek_mut() is safe + Some(&mut (*new_pos.as_ptr()).element) + } } pub fn take(&mut self) -> Option { diff --git a/exercises/practice/doubly-linked-list/Cargo.toml b/exercises/practice/doubly-linked-list/Cargo.toml index 87094f3ae..0a620d9a2 100644 --- a/exercises/practice/doubly-linked-list/Cargo.toml +++ b/exercises/practice/doubly-linked-list/Cargo.toml @@ -1,7 +1,12 @@ [package] -name = "doubly-linked-list" -version = "0.0.0" -edition = "2021" +name = "doubly_linked_list" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] [features] # check correct covariance and Send, Sync diff --git a/exercises/practice/doubly-linked-list/tests/step_4_leak_test_1.rs b/exercises/practice/doubly-linked-list/tests/step_4_leak_test_1.rs index 34df209a8..ea84c886a 100644 --- a/exercises/practice/doubly-linked-list/tests/step_4_leak_test_1.rs +++ b/exercises/practice/doubly-linked-list/tests/step_4_leak_test_1.rs @@ -34,7 +34,7 @@ struct Counter; unsafe impl GlobalAlloc for Counter { unsafe fn alloc(&self, layout: Layout) -> *mut u8 { - let ret = System.alloc(layout); + let ret = unsafe { System.alloc(layout) }; if !ret.is_null() { ALLOCATED.fetch_add(layout.size(), SeqCst); } @@ -42,7 +42,7 @@ unsafe impl GlobalAlloc for Counter { } unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { - System.dealloc(ptr, layout); + unsafe { System.dealloc(ptr, layout) }; ALLOCATED.fetch_sub(layout.size(), SeqCst); } } diff --git a/exercises/practice/doubly-linked-list/tests/step_4_leak_test_2.rs b/exercises/practice/doubly-linked-list/tests/step_4_leak_test_2.rs index c55b6b64c..12bbe0848 100644 --- a/exercises/practice/doubly-linked-list/tests/step_4_leak_test_2.rs +++ b/exercises/practice/doubly-linked-list/tests/step_4_leak_test_2.rs @@ -34,7 +34,7 @@ struct Counter; unsafe impl GlobalAlloc for Counter { unsafe fn alloc(&self, layout: Layout) -> *mut u8 { - let ret = System.alloc(layout); + let ret = unsafe { System.alloc(layout) }; if !ret.is_null() { ALLOCATED.fetch_add(layout.size(), SeqCst); } @@ -42,7 +42,7 @@ unsafe impl GlobalAlloc for Counter { } unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { - System.dealloc(ptr, layout); + unsafe { System.dealloc(ptr, layout) }; ALLOCATED.fetch_sub(layout.size(), SeqCst); } } diff --git a/exercises/practice/eliuds-eggs/Cargo.toml b/exercises/practice/eliuds-eggs/Cargo.toml index b76c171e1..ae51b9c42 100644 --- a/exercises/practice/eliuds-eggs/Cargo.toml +++ b/exercises/practice/eliuds-eggs/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "eliuds_eggs" -version = "1.0.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/etl/Cargo.toml b/exercises/practice/etl/Cargo.toml index 67624abea..a63ea7951 100644 --- a/exercises/practice/etl/Cargo.toml +++ b/exercises/practice/etl/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "etl" -version = "1.0.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/fizzy/.meta/example.rs b/exercises/practice/fizzy/.meta/example.rs index 28e2dee98..3543dcb12 100644 --- a/exercises/practice/fizzy/.meta/example.rs +++ b/exercises/practice/fizzy/.meta/example.rs @@ -39,7 +39,7 @@ where } pub fn apply_to(&self, item: T) -> String { - let Fizzy(ref matchers) = self; + let Fizzy(matchers) = self; let mut out = String::new(); for matcher in matchers { if (matcher.matcher)(item) { diff --git a/exercises/practice/fizzy/Cargo.toml b/exercises/practice/fizzy/Cargo.toml index 3992643de..0a1c3df13 100644 --- a/exercises/practice/fizzy/Cargo.toml +++ b/exercises/practice/fizzy/Cargo.toml @@ -1,7 +1,12 @@ [package] name = "fizzy" -version = "0.0.0" -edition = "2021" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] [lints.clippy] new_without_default = "allow" diff --git a/exercises/practice/fizzy/tests/fizzy.rs b/exercises/practice/fizzy/tests/fizzy.rs index 517fcc194..af84d6f91 100644 --- a/exercises/practice/fizzy/tests/fizzy.rs +++ b/exercises/practice/fizzy/tests/fizzy.rs @@ -92,7 +92,7 @@ fn minimal_generic_bounds() { impl fmt::Display for Fizzable { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let Fizzable(ref n) = self; + let Fizzable(n) = self; write!(f, "{n}") } } diff --git a/exercises/practice/forth/Cargo.toml b/exercises/practice/forth/Cargo.toml index 04e076d08..797644054 100644 --- a/exercises/practice/forth/Cargo.toml +++ b/exercises/practice/forth/Cargo.toml @@ -1,7 +1,12 @@ [package] -edition = "2021" name = "forth" -version = "1.7.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] [lints.clippy] new_without_default = "allow" diff --git a/exercises/practice/forth/tests/alloc-attack.rs b/exercises/practice/forth/tests/alloc-attack.rs index 861dfe733..0ea591a93 100644 --- a/exercises/practice/forth/tests/alloc-attack.rs +++ b/exercises/practice/forth/tests/alloc-attack.rs @@ -76,11 +76,11 @@ struct TrackingAllocator(A, AtomicU64); unsafe impl GlobalAlloc for TrackingAllocator { unsafe fn alloc(&self, layout: Layout) -> *mut u8 { self.1.fetch_add(layout.size() as u64, Ordering::SeqCst); - self.0.alloc(layout) + unsafe { self.0.alloc(layout) } } unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { - self.0.dealloc(ptr, layout); + unsafe { self.0.dealloc(ptr, layout) }; self.1.fetch_sub(layout.size() as u64, Ordering::SeqCst); } } diff --git a/exercises/practice/gigasecond/Cargo.toml b/exercises/practice/gigasecond/Cargo.toml index da110b7b2..826b09466 100644 --- a/exercises/practice/gigasecond/Cargo.toml +++ b/exercises/practice/gigasecond/Cargo.toml @@ -1,7 +1,10 @@ [package] -edition = "2021" name = "gigasecond" -version = "2.0.0" +version = "0.1.0" +edition = "2024" +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml [dependencies] time = "0.3" diff --git a/exercises/practice/grade-school/Cargo.toml b/exercises/practice/grade-school/Cargo.toml index c8f979a76..b31ba7356 100644 --- a/exercises/practice/grade-school/Cargo.toml +++ b/exercises/practice/grade-school/Cargo.toml @@ -1,7 +1,12 @@ [package] -edition = "2021" -name = "grade-school" -version = "0.0.0" +name = "grade_school" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] [lints.clippy] new_without_default = "allow" diff --git a/exercises/practice/grains/Cargo.toml b/exercises/practice/grains/Cargo.toml index 077822d61..d63ba538f 100644 --- a/exercises/practice/grains/Cargo.toml +++ b/exercises/practice/grains/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "grains" -version = "1.2.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/grep/.meta/Cargo-example.toml b/exercises/practice/grep/.meta/Cargo-example.toml index 367d28094..c11e06f02 100644 --- a/exercises/practice/grep/.meta/Cargo-example.toml +++ b/exercises/practice/grep/.meta/Cargo-example.toml @@ -1,8 +1,11 @@ +[package] +name = "grep" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml [dependencies] anyhow = "1.0" thiserror = "1.0" - -[package] -edition = "2021" -name = "grep" -version = "1.3.0" diff --git a/exercises/practice/grep/Cargo.toml b/exercises/practice/grep/Cargo.toml index fd0a6b754..90779a2c0 100644 --- a/exercises/practice/grep/Cargo.toml +++ b/exercises/practice/grep/Cargo.toml @@ -1,7 +1,10 @@ -[dependencies] -anyhow = "1.0" - [package] -edition = "2021" name = "grep" -version = "1.3.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] +anyhow = "1.0" diff --git a/exercises/practice/hamming/Cargo.toml b/exercises/practice/hamming/Cargo.toml index 0217a7a93..085746843 100644 --- a/exercises/practice/hamming/Cargo.toml +++ b/exercises/practice/hamming/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "hamming" -version = "2.2.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/hello-world/Cargo.toml b/exercises/practice/hello-world/Cargo.toml index face5dfd5..e780cd474 100644 --- a/exercises/practice/hello-world/Cargo.toml +++ b/exercises/practice/hello-world/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "hello-world" -version = "1.1.0" +name = "hello_world" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/hexadecimal/Cargo.toml b/exercises/practice/hexadecimal/Cargo.toml index 313084bc0..1840732aa 100644 --- a/exercises/practice/hexadecimal/Cargo.toml +++ b/exercises/practice/hexadecimal/Cargo.toml @@ -1,3 +1,9 @@ [package] name = "hexadecimal" -version = "0.0.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/high-scores/Cargo.toml b/exercises/practice/high-scores/Cargo.toml index 78c9a491c..1e3a7727f 100644 --- a/exercises/practice/high-scores/Cargo.toml +++ b/exercises/practice/high-scores/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "high-scores" -version = "4.0.0" +name = "high_scores" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/isbn-verifier/Cargo.toml b/exercises/practice/isbn-verifier/Cargo.toml index b355e387a..af7053701 100644 --- a/exercises/practice/isbn-verifier/Cargo.toml +++ b/exercises/practice/isbn-verifier/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "isbn-verifier" -version = "2.7.0" +name = "isbn_verifier" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/isogram/Cargo.toml b/exercises/practice/isogram/Cargo.toml index 671cd99d7..4af75dc7a 100644 --- a/exercises/practice/isogram/Cargo.toml +++ b/exercises/practice/isogram/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "isogram" -version = "1.3.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/kindergarten-garden/Cargo.toml b/exercises/practice/kindergarten-garden/Cargo.toml index 1c8ddf929..b5edfd55e 100644 --- a/exercises/practice/kindergarten-garden/Cargo.toml +++ b/exercises/practice/kindergarten-garden/Cargo.toml @@ -1,4 +1,9 @@ [package] name = "kindergarten_garden" version = "0.1.0" -edition = "2021" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/knapsack/Cargo.toml b/exercises/practice/knapsack/Cargo.toml index 30ab86843..71cc438ba 100644 --- a/exercises/practice/knapsack/Cargo.toml +++ b/exercises/practice/knapsack/Cargo.toml @@ -1,4 +1,9 @@ [package] name = "knapsack" version = "0.1.0" -edition = "2021" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/largest-series-product/Cargo.toml b/exercises/practice/largest-series-product/Cargo.toml index 16e2defe5..ee231d49d 100644 --- a/exercises/practice/largest-series-product/Cargo.toml +++ b/exercises/practice/largest-series-product/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "largest-series-product" -version = "1.2.0" +name = "largest_series_product" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/leap/Cargo.toml b/exercises/practice/leap/Cargo.toml index af7835945..fa2e07b33 100644 --- a/exercises/practice/leap/Cargo.toml +++ b/exercises/practice/leap/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "leap" -version = "1.6.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/list-ops/Cargo.toml b/exercises/practice/list-ops/Cargo.toml index eccd51cf6..2637d4f1c 100644 --- a/exercises/practice/list-ops/Cargo.toml +++ b/exercises/practice/list-ops/Cargo.toml @@ -1,4 +1,9 @@ [package] -name = "list-ops" +name = "list_ops" version = "0.1.0" -edition = "2021" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/luhn-from/Cargo.toml b/exercises/practice/luhn-from/Cargo.toml index 2254dfa38..d5437b4df 100644 --- a/exercises/practice/luhn-from/Cargo.toml +++ b/exercises/practice/luhn-from/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "luhn-from" -version = "0.0.0" +name = "luhn_from" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/luhn-trait/Cargo.toml b/exercises/practice/luhn-trait/Cargo.toml index 75fa75d93..ff34e58ee 100644 --- a/exercises/practice/luhn-trait/Cargo.toml +++ b/exercises/practice/luhn-trait/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "luhn-trait" -version = "0.0.0" +name = "luhn_trait" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/luhn/Cargo.toml b/exercises/practice/luhn/Cargo.toml index 2c4011487..4a36628c1 100644 --- a/exercises/practice/luhn/Cargo.toml +++ b/exercises/practice/luhn/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "luhn" -version = "1.6.1" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/macros/Cargo.toml b/exercises/practice/macros/Cargo.toml index 4497ae5d8..8161a0872 100644 --- a/exercises/practice/macros/Cargo.toml +++ b/exercises/practice/macros/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "macros" version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/matching-brackets/Cargo.toml b/exercises/practice/matching-brackets/Cargo.toml index 55b42afb7..3c9770b69 100644 --- a/exercises/practice/matching-brackets/Cargo.toml +++ b/exercises/practice/matching-brackets/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "matching-brackets" -version = "2.0.0" +name = "matching_brackets" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/matrix/Cargo.toml b/exercises/practice/matrix/Cargo.toml index 7797f0f17..1247a58c2 100644 --- a/exercises/practice/matrix/Cargo.toml +++ b/exercises/practice/matrix/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "matrix" -version = "1.0.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/minesweeper/Cargo.toml b/exercises/practice/minesweeper/Cargo.toml index 5b53e2644..e64ff1b08 100644 --- a/exercises/practice/minesweeper/Cargo.toml +++ b/exercises/practice/minesweeper/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "minesweeper" -version = "1.1.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/minesweeper/src/lib.rs b/exercises/practice/minesweeper/src/lib.rs index 55f516ca5..c19457694 100644 --- a/exercises/practice/minesweeper/src/lib.rs +++ b/exercises/practice/minesweeper/src/lib.rs @@ -1,3 +1,5 @@ pub fn annotate(minefield: &[&str]) -> Vec { - todo!("\nAnnotate each square of the given minefield with the number of mines that surround said square (blank if there are no surrounding mines):\n{minefield:#?}\n"); + todo!( + "\nAnnotate each square of the given minefield with the number of mines that surround said square (blank if there are no surrounding mines):\n{minefield:#?}\n" + ); } diff --git a/exercises/practice/nth-prime/Cargo.toml b/exercises/practice/nth-prime/Cargo.toml index e40ac1e89..ce719bcd8 100644 --- a/exercises/practice/nth-prime/Cargo.toml +++ b/exercises/practice/nth-prime/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "nth_prime" -version = "2.1.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/nucleotide-codons/Cargo.toml b/exercises/practice/nucleotide-codons/Cargo.toml index ff77d8534..a39d46657 100644 --- a/exercises/practice/nucleotide-codons/Cargo.toml +++ b/exercises/practice/nucleotide-codons/Cargo.toml @@ -1,3 +1,9 @@ [package] name = "nucleotide_codons" version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/nucleotide-codons/src/lib.rs b/exercises/practice/nucleotide-codons/src/lib.rs index 17a6829b3..29460af9b 100644 --- a/exercises/practice/nucleotide-codons/src/lib.rs +++ b/exercises/practice/nucleotide-codons/src/lib.rs @@ -23,7 +23,10 @@ impl<'a> CodonsInfo<'a> { } pub fn of_rna(&self, rna: &str) -> Result, Error> { - todo!("Return a list of protein names that correspond to the '{}' RNA string or Err if the RNA string is invalid", rna); + todo!( + "Return a list of protein names that correspond to the '{}' RNA string or Err if the RNA string is invalid", + rna + ); } } diff --git a/exercises/practice/nucleotide-count/Cargo.toml b/exercises/practice/nucleotide-count/Cargo.toml index 97d8007e1..2a1ef9ab3 100644 --- a/exercises/practice/nucleotide-count/Cargo.toml +++ b/exercises/practice/nucleotide-count/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "nucleotide-count" -version = "1.3.0" +name = "nucleotide_count" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/ocr-numbers/Cargo.toml b/exercises/practice/ocr-numbers/Cargo.toml index 2f0af9aea..e99b7d21b 100644 --- a/exercises/practice/ocr-numbers/Cargo.toml +++ b/exercises/practice/ocr-numbers/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "ocr-numbers" -version = "0.0.0" +name = "ocr_numbers" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/paasio/Cargo.toml b/exercises/practice/paasio/Cargo.toml index 91ee30182..a6924c751 100644 --- a/exercises/practice/paasio/Cargo.toml +++ b/exercises/practice/paasio/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "paasio" -version = "0.0.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/palindrome-products/Cargo.toml b/exercises/practice/palindrome-products/Cargo.toml index 987f9015b..6629a3933 100644 --- a/exercises/practice/palindrome-products/Cargo.toml +++ b/exercises/practice/palindrome-products/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "palindrome-products" -version = "1.2.0" +name = "palindrome_products" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/pangram/Cargo.toml b/exercises/practice/pangram/Cargo.toml index 1d64635aa..6a9e01b8f 100644 --- a/exercises/practice/pangram/Cargo.toml +++ b/exercises/practice/pangram/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "pangram" -version = "0.0.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/parallel-letter-frequency/Cargo.toml b/exercises/practice/parallel-letter-frequency/Cargo.toml index 8caaa081b..9885e321c 100644 --- a/exercises/practice/parallel-letter-frequency/Cargo.toml +++ b/exercises/practice/parallel-letter-frequency/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "parallel-letter-frequency" -version = "0.0.0" +name = "parallel_letter_frequency" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/pascals-triangle/Cargo.toml b/exercises/practice/pascals-triangle/Cargo.toml index 40e039fa8..c703118e0 100644 --- a/exercises/practice/pascals-triangle/Cargo.toml +++ b/exercises/practice/pascals-triangle/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "pascals-triangle" -version = "1.5.0" +name = "pascals_triangle" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/perfect-numbers/Cargo.toml b/exercises/practice/perfect-numbers/Cargo.toml index 39a1ec152..a096fd0dd 100644 --- a/exercises/practice/perfect-numbers/Cargo.toml +++ b/exercises/practice/perfect-numbers/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "perfect_numbers" -version = "1.1.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/phone-number/Cargo.toml b/exercises/practice/phone-number/Cargo.toml index a8fb61d48..e7dd2918d 100644 --- a/exercises/practice/phone-number/Cargo.toml +++ b/exercises/practice/phone-number/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "phone-number" -version = "1.6.1" +name = "phone_number" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/pig-latin/.meta/Cargo-example.toml b/exercises/practice/pig-latin/.meta/Cargo-example.toml index 42bcba31f..3a976347f 100644 --- a/exercises/practice/pig-latin/.meta/Cargo-example.toml +++ b/exercises/practice/pig-latin/.meta/Cargo-example.toml @@ -1,7 +1,10 @@ [package] -edition = "2021" -name = "pig-latin" -version = "1.0.0" +name = "pig_latin" +version = "0.1.0" +edition = "2024" +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml [dependencies] regex = "0.2" diff --git a/exercises/practice/pig-latin/Cargo.toml b/exercises/practice/pig-latin/Cargo.toml index d7ab70e86..4dc02d93f 100644 --- a/exercises/practice/pig-latin/Cargo.toml +++ b/exercises/practice/pig-latin/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "pig-latin" -version = "1.0.0" +name = "pig_latin" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/poker/.meta/Cargo-example.toml b/exercises/practice/poker/.meta/Cargo-example.toml index 5b66e0ebf..23533088f 100644 --- a/exercises/practice/poker/.meta/Cargo-example.toml +++ b/exercises/practice/poker/.meta/Cargo-example.toml @@ -1,7 +1,10 @@ [package] -edition = "2021" name = "poker" -version = "1.0.0" +version = "0.1.0" +edition = "2024" +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml [dependencies] counter = "0.5.2" diff --git a/exercises/practice/poker/Cargo.toml b/exercises/practice/poker/Cargo.toml index 208d0e94c..539ae7925 100644 --- a/exercises/practice/poker/Cargo.toml +++ b/exercises/practice/poker/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "poker" -version = "1.1.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/poker/tests/poker.rs b/exercises/practice/poker/tests/poker.rs index f15029ec7..484451145 100644 --- a/exercises/practice/poker/tests/poker.rs +++ b/exercises/practice/poker/tests/poker.rs @@ -153,8 +153,8 @@ fn both_hands_have_three_of_a_kind_tie_goes_to_highest_ranked_triplet() { #[test] #[ignore] -fn with_multiple_decks_two_players_can_have_same_three_of_a_kind_ties_go_to_highest_remaining_cards( -) { +fn with_multiple_decks_two_players_can_have_same_three_of_a_kind_ties_go_to_highest_remaining_cards() + { let input = &["5S AH AS 7C AD", "4S AH AS 8C AD"]; let output = winning_hands(input).into_iter().collect::>(); let expected = ["4S AH AS 8C AD"].into_iter().collect::>(); diff --git a/exercises/practice/prime-factors/Cargo.toml b/exercises/practice/prime-factors/Cargo.toml index 44211ce91..0c8df82f4 100644 --- a/exercises/practice/prime-factors/Cargo.toml +++ b/exercises/practice/prime-factors/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "prime_factors" -version = "1.1.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/protein-translation/Cargo.toml b/exercises/practice/protein-translation/Cargo.toml index ce59d44a8..82be0604b 100644 --- a/exercises/practice/protein-translation/Cargo.toml +++ b/exercises/practice/protein-translation/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "protein-translation" +name = "protein_translation" version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/protein-translation/src/lib.rs b/exercises/practice/protein-translation/src/lib.rs index ddbfc80dc..b5ba6a902 100644 --- a/exercises/practice/protein-translation/src/lib.rs +++ b/exercises/practice/protein-translation/src/lib.rs @@ -1,3 +1,5 @@ pub fn translate(rna: &str) -> Option> { - todo!("Return a list of protein names that correspond to the '{rna}' RNA string or None if the RNA string is invalid"); + todo!( + "Return a list of protein names that correspond to the '{rna}' RNA string or None if the RNA string is invalid" + ); } diff --git a/exercises/practice/proverb/Cargo.toml b/exercises/practice/proverb/Cargo.toml index 8f9444d35..3ea43d7a3 100644 --- a/exercises/practice/proverb/Cargo.toml +++ b/exercises/practice/proverb/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "proverb" -version = "1.1.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/pythagorean-triplet/Cargo.toml b/exercises/practice/pythagorean-triplet/Cargo.toml index 27e28c0c3..12d11014a 100644 --- a/exercises/practice/pythagorean-triplet/Cargo.toml +++ b/exercises/practice/pythagorean-triplet/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "pythagorean_triplet" -version = "1.0.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/pythagorean-triplet/src/lib.rs b/exercises/practice/pythagorean-triplet/src/lib.rs index 45aa4a457..e645ab1f3 100644 --- a/exercises/practice/pythagorean-triplet/src/lib.rs +++ b/exercises/practice/pythagorean-triplet/src/lib.rs @@ -1,5 +1,7 @@ use std::collections::HashSet; pub fn find(sum: u32) -> HashSet<[u32; 3]> { - todo!("Given the sum {sum}, return all possible Pythagorean triplets, which produce the said sum, or an empty HashSet if there are no such triplets. Note that you are expected to return triplets in [a, b, c] order, where a < b < c"); + todo!( + "Given the sum {sum}, return all possible Pythagorean triplets, which produce the said sum, or an empty HashSet if there are no such triplets. Note that you are expected to return triplets in [a, b, c] order, where a < b < c" + ); } diff --git a/exercises/practice/queen-attack/Cargo.toml b/exercises/practice/queen-attack/Cargo.toml index e642e0ae7..a2c251524 100644 --- a/exercises/practice/queen-attack/Cargo.toml +++ b/exercises/practice/queen-attack/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "queen-attack" -version = "2.2.0" +name = "queen_attack" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/queen-attack/tests/queen-attack.rs b/exercises/practice/queen-attack/tests/queen-attack.rs index b8e023c96..cb37dfcd5 100644 --- a/exercises/practice/queen-attack/tests/queen-attack.rs +++ b/exercises/practice/queen-attack/tests/queen-attack.rs @@ -92,8 +92,8 @@ fn can_attack_on_fourth_diagonal() { #[test] #[ignore] -fn cannot_attack_if_falling_diagonals_are_only_the_same_when_reflected_across_the_longest_falling_diagonal( -) { +fn cannot_attack_if_falling_diagonals_are_only_the_same_when_reflected_across_the_longest_falling_diagonal() + { let white_queen = Queen::new(ChessPosition::new(4, 1).unwrap()); let black_queen = Queen::new(ChessPosition::new(2, 5).unwrap()); assert!(!white_queen.can_attack(&black_queen)); diff --git a/exercises/practice/rail-fence-cipher/Cargo.toml b/exercises/practice/rail-fence-cipher/Cargo.toml index 82030937b..1e9644ee8 100644 --- a/exercises/practice/rail-fence-cipher/Cargo.toml +++ b/exercises/practice/rail-fence-cipher/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "rail_fence_cipher" -version = "1.1.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/raindrops/Cargo.toml b/exercises/practice/raindrops/Cargo.toml index 41c138c6b..a297d155b 100644 --- a/exercises/practice/raindrops/Cargo.toml +++ b/exercises/practice/raindrops/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "raindrops" -version = "1.1.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/react/Cargo.toml b/exercises/practice/react/Cargo.toml index 3d2ebf0ad..171fe4d01 100644 --- a/exercises/practice/react/Cargo.toml +++ b/exercises/practice/react/Cargo.toml @@ -1,7 +1,12 @@ [package] -edition = "2021" name = "react" -version = "2.0.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] [lints.clippy] new_without_default = "allow" diff --git a/exercises/practice/react/tests/react.rs b/exercises/practice/react/tests/react.rs index 1735e6632..3683a98f3 100644 --- a/exercises/practice/react/tests/react.rs +++ b/exercises/practice/react/tests/react.rs @@ -169,9 +169,11 @@ fn compute_cells_fire_callbacks() { let output = reactor .create_compute(&[CellId::Input(input)], |v| v[0] + 1) .unwrap(); - assert!(reactor - .add_callback(output, |v| cb.callback_called(v)) - .is_some()); + assert!( + reactor + .add_callback(output, |v| cb.callback_called(v)) + .is_some() + ); assert!(reactor.set_value(input, 3)); cb.expect_to_have_been_called_with(4); } @@ -226,9 +228,11 @@ fn callbacks_only_fire_on_change() { |v| if v[0] < 3 { 111 } else { 222 }, ) .unwrap(); - assert!(reactor - .add_callback(output, |v| cb.callback_called(v)) - .is_some()); + assert!( + reactor + .add_callback(output, |v| cb.callback_called(v)) + .is_some() + ); assert!(reactor.set_value(input, 2)); cb.expect_not_to_have_been_called(); @@ -245,9 +249,11 @@ fn callbacks_can_be_called_multiple_times() { let output = reactor .create_compute(&[CellId::Input(input)], |v| v[0] + 1) .unwrap(); - assert!(reactor - .add_callback(output, |v| cb.callback_called(v)) - .is_some()); + assert!( + reactor + .add_callback(output, |v| cb.callback_called(v)) + .is_some() + ); assert!(reactor.set_value(input, 2)); cb.expect_to_have_been_called_with(3); @@ -268,12 +274,16 @@ fn callbacks_can_be_called_from_multiple_cells() { let minus_one = reactor .create_compute(&[CellId::Input(input)], |v| v[0] - 1) .unwrap(); - assert!(reactor - .add_callback(plus_one, |v| cb1.callback_called(v)) - .is_some()); - assert!(reactor - .add_callback(minus_one, |v| cb2.callback_called(v)) - .is_some()); + assert!( + reactor + .add_callback(plus_one, |v| cb1.callback_called(v)) + .is_some() + ); + assert!( + reactor + .add_callback(minus_one, |v| cb2.callback_called(v)) + .is_some() + ); assert!(reactor.set_value(input, 10)); cb1.expect_to_have_been_called_with(11); @@ -296,18 +306,22 @@ fn callbacks_can_be_added_and_removed() { let callback = reactor .add_callback(output, |v| cb1.callback_called(v)) .unwrap(); - assert!(reactor - .add_callback(output, |v| cb2.callback_called(v)) - .is_some()); + assert!( + reactor + .add_callback(output, |v| cb2.callback_called(v)) + .is_some() + ); assert!(reactor.set_value(input, 31)); cb1.expect_to_have_been_called_with(32); cb2.expect_to_have_been_called_with(32); assert!(reactor.remove_callback(output, callback).is_ok()); - assert!(reactor - .add_callback(output, |v| cb3.callback_called(v)) - .is_some()); + assert!( + reactor + .add_callback(output, |v| cb3.callback_called(v)) + .is_some() + ); assert!(reactor.set_value(input, 41)); cb1.expect_not_to_have_been_called(); @@ -329,9 +343,11 @@ fn removing_a_callback_multiple_times_doesnt_interfere_with_other_callbacks() { let callback = reactor .add_callback(output, |v| cb1.callback_called(v)) .unwrap(); - assert!(reactor - .add_callback(output, |v| cb2.callback_called(v)) - .is_some()); + assert!( + reactor + .add_callback(output, |v| cb2.callback_called(v)) + .is_some() + ); // We want the first remove to be Ok, but the others should be errors. assert!(reactor.remove_callback(output, callback).is_ok()); for _ in 1..5 { @@ -367,9 +383,11 @@ fn callbacks_should_only_be_called_once_even_if_multiple_dependencies_change() { |v| v[0] * v[1], ) .unwrap(); - assert!(reactor - .add_callback(output, |v| cb.callback_called(v)) - .is_some()); + assert!( + reactor + .add_callback(output, |v| cb.callback_called(v)) + .is_some() + ); assert!(reactor.set_value(input, 4)); cb.expect_to_have_been_called_with(10); } @@ -392,9 +410,11 @@ fn callbacks_should_not_be_called_if_dependencies_change_but_output_value_doesnt |v| v[0] - v[1], ) .unwrap(); - assert!(reactor - .add_callback(always_two, |v| cb.callback_called(v)) - .is_some()); + assert!( + reactor + .add_callback(always_two, |v| cb.callback_called(v)) + .is_some() + ); for i in 2..5 { assert!(reactor.set_value(input, i)); cb.expect_not_to_have_been_called(); diff --git a/exercises/practice/rectangles/Cargo.toml b/exercises/practice/rectangles/Cargo.toml index 320d33678..27b83075f 100644 --- a/exercises/practice/rectangles/Cargo.toml +++ b/exercises/practice/rectangles/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "rectangles" -version = "1.1.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/rectangles/src/lib.rs b/exercises/practice/rectangles/src/lib.rs index 118113a8f..90fe2c037 100644 --- a/exercises/practice/rectangles/src/lib.rs +++ b/exercises/practice/rectangles/src/lib.rs @@ -1,3 +1,5 @@ pub fn count(lines: &[&str]) -> u32 { - todo!("\nDetermine the count of rectangles in the ASCII diagram represented by the following lines:\n{lines:#?}\n."); + todo!( + "\nDetermine the count of rectangles in the ASCII diagram represented by the following lines:\n{lines:#?}\n." + ); } diff --git a/exercises/practice/reverse-string/Cargo.toml b/exercises/practice/reverse-string/Cargo.toml index 403e50ab1..30529e8cb 100644 --- a/exercises/practice/reverse-string/Cargo.toml +++ b/exercises/practice/reverse-string/Cargo.toml @@ -1,7 +1,12 @@ [package] -edition = "2021" name = "reverse_string" -version = "1.2.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] [features] grapheme = [] diff --git a/exercises/practice/rna-transcription/Cargo.toml b/exercises/practice/rna-transcription/Cargo.toml index 6307cb6e1..acb68cde0 100644 --- a/exercises/practice/rna-transcription/Cargo.toml +++ b/exercises/practice/rna-transcription/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "rna-transcription" -version = "1.0.0" +name = "rna_transcription" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/rna-transcription/src/lib.rs b/exercises/practice/rna-transcription/src/lib.rs index 9a406c460..c235cfad3 100644 --- a/exercises/practice/rna-transcription/src/lib.rs +++ b/exercises/practice/rna-transcription/src/lib.rs @@ -6,7 +6,9 @@ pub struct Rna; impl Dna { pub fn new(dna: &str) -> Result { - todo!("Construct new Dna from '{dna}' string. If string contains invalid nucleotides return index of first invalid nucleotide"); + todo!( + "Construct new Dna from '{dna}' string. If string contains invalid nucleotides return index of first invalid nucleotide" + ); } pub fn into_rna(self) -> Rna { @@ -16,6 +18,8 @@ impl Dna { impl Rna { pub fn new(rna: &str) -> Result { - todo!("Construct new Rna from '{rna}' string. If string contains invalid nucleotides return index of first invalid nucleotide"); + todo!( + "Construct new Rna from '{rna}' string. If string contains invalid nucleotides return index of first invalid nucleotide" + ); } } diff --git a/exercises/practice/robot-name/.meta/Cargo-example.toml b/exercises/practice/robot-name/.meta/Cargo-example.toml index 5713b6191..bceec03b5 100644 --- a/exercises/practice/robot-name/.meta/Cargo-example.toml +++ b/exercises/practice/robot-name/.meta/Cargo-example.toml @@ -1,7 +1,10 @@ [package] -edition = "2021" -name = "robot-name" -version = "0.0.0" +name = "robot_name" +version = "0.1.0" +edition = "2024" +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml [dependencies] rand = "0.3.12" diff --git a/exercises/practice/robot-name/Cargo.toml b/exercises/practice/robot-name/Cargo.toml index 1893bb13c..d584df2f0 100644 --- a/exercises/practice/robot-name/Cargo.toml +++ b/exercises/practice/robot-name/Cargo.toml @@ -1,7 +1,12 @@ [package] -edition = "2021" -name = "robot-name" -version = "0.0.0" +name = "robot_name" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] [lints.clippy] new_without_default = "allow" diff --git a/exercises/practice/robot-simulator/Cargo.toml b/exercises/practice/robot-simulator/Cargo.toml index c82b015f6..996ffc36c 100644 --- a/exercises/practice/robot-simulator/Cargo.toml +++ b/exercises/practice/robot-simulator/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "robot-simulator" -version = "2.2.0" +name = "robot_simulator" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/roman-numerals/Cargo.toml b/exercises/practice/roman-numerals/Cargo.toml index e474f49cb..7d0f36fa7 100644 --- a/exercises/practice/roman-numerals/Cargo.toml +++ b/exercises/practice/roman-numerals/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "roman-numerals" -version = "1.0.0" +name = "roman_numerals" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/rotational-cipher/Cargo.toml b/exercises/practice/rotational-cipher/Cargo.toml index 2b00cbaeb..61e6d3c41 100644 --- a/exercises/practice/rotational-cipher/Cargo.toml +++ b/exercises/practice/rotational-cipher/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "rotational-cipher" -version = "1.0.0" +name = "rotational_cipher" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/run-length-encoding/Cargo.toml b/exercises/practice/run-length-encoding/Cargo.toml index ab5768d1f..838424e16 100644 --- a/exercises/practice/run-length-encoding/Cargo.toml +++ b/exercises/practice/run-length-encoding/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "run-length-encoding" -version = "1.1.0" +name = "run_length_encoding" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/saddle-points/Cargo.toml b/exercises/practice/saddle-points/Cargo.toml index 968176620..afe9ec54a 100644 --- a/exercises/practice/saddle-points/Cargo.toml +++ b/exercises/practice/saddle-points/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "saddle-points" -version = "1.3.0" +name = "saddle_points" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/say/Cargo.toml b/exercises/practice/say/Cargo.toml index 9338eb9cc..3a1bb171b 100644 --- a/exercises/practice/say/Cargo.toml +++ b/exercises/practice/say/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "say" -version = "1.2.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/scale-generator/Cargo.toml b/exercises/practice/scale-generator/Cargo.toml index 04a14757b..254b870d1 100644 --- a/exercises/practice/scale-generator/Cargo.toml +++ b/exercises/practice/scale-generator/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "scale_generator" -version = "2.0.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/scrabble-score/Cargo.toml b/exercises/practice/scrabble-score/Cargo.toml index 7de526ac3..eacff3dd8 100644 --- a/exercises/practice/scrabble-score/Cargo.toml +++ b/exercises/practice/scrabble-score/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "scrabble-score" -version = "1.1.0" +name = "scrabble_score" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/secret-handshake/Cargo.toml b/exercises/practice/secret-handshake/Cargo.toml index 029d1c91e..52f429426 100644 --- a/exercises/practice/secret-handshake/Cargo.toml +++ b/exercises/practice/secret-handshake/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "secret-handshake" -version = "1.1.0" +name = "secret_handshake" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/series/Cargo.toml b/exercises/practice/series/Cargo.toml index 6366609a3..a7aca7910 100644 --- a/exercises/practice/series/Cargo.toml +++ b/exercises/practice/series/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "series" version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/sieve/Cargo.toml b/exercises/practice/sieve/Cargo.toml index 7c35c45e1..bb52d8f89 100644 --- a/exercises/practice/sieve/Cargo.toml +++ b/exercises/practice/sieve/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "sieve" -version = "1.1.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/simple-cipher/Cargo.toml b/exercises/practice/simple-cipher/Cargo.toml index 5a2dcf498..bae81d0de 100644 --- a/exercises/practice/simple-cipher/Cargo.toml +++ b/exercises/practice/simple-cipher/Cargo.toml @@ -1,7 +1,10 @@ [package] -edition = "2021" -name = "simple-cipher" -version = "0.0.0" +name = "simple_cipher" +version = "0.1.0" +edition = "2024" +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml [dependencies] rand = "0.8" diff --git a/exercises/practice/simple-linked-list/Cargo.toml b/exercises/practice/simple-linked-list/Cargo.toml index 211831446..56df69ec3 100644 --- a/exercises/practice/simple-linked-list/Cargo.toml +++ b/exercises/practice/simple-linked-list/Cargo.toml @@ -1,7 +1,12 @@ [package] -edition = "2021" name = "simple_linked_list" version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] [lints.clippy] new_without_default = "allow" diff --git a/exercises/practice/space-age/Cargo.toml b/exercises/practice/space-age/Cargo.toml index 9640244bd..bbe6044c7 100644 --- a/exercises/practice/space-age/Cargo.toml +++ b/exercises/practice/space-age/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "space-age" -version = "1.2.0" +name = "space_age" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/spiral-matrix/Cargo.toml b/exercises/practice/spiral-matrix/Cargo.toml index f86f78135..49fc6c78a 100644 --- a/exercises/practice/spiral-matrix/Cargo.toml +++ b/exercises/practice/spiral-matrix/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "spiral-matrix" -version = "1.1.0" +name = "spiral_matrix" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/sublist/Cargo.toml b/exercises/practice/sublist/Cargo.toml index 6493981cf..c900a2e79 100644 --- a/exercises/practice/sublist/Cargo.toml +++ b/exercises/practice/sublist/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "sublist" -version = "0.0.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/sublist/src/lib.rs b/exercises/practice/sublist/src/lib.rs index a83342a2c..ac50a59cf 100644 --- a/exercises/practice/sublist/src/lib.rs +++ b/exercises/practice/sublist/src/lib.rs @@ -7,5 +7,7 @@ pub enum Comparison { } pub fn sublist(first_list: &[i32], second_list: &[i32]) -> Comparison { - todo!("Determine if the {first_list:?} is equal to, sublist of, superlist of or unequal to {second_list:?}."); + todo!( + "Determine if the {first_list:?} is equal to, sublist of, superlist of or unequal to {second_list:?}." + ); } diff --git a/exercises/practice/sum-of-multiples/Cargo.toml b/exercises/practice/sum-of-multiples/Cargo.toml index c87994430..be310bd03 100644 --- a/exercises/practice/sum-of-multiples/Cargo.toml +++ b/exercises/practice/sum-of-multiples/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "sum-of-multiples" -version = "1.5.0" +name = "sum_of_multiples" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/tournament/Cargo.toml b/exercises/practice/tournament/Cargo.toml index e9209b33a..49744661b 100644 --- a/exercises/practice/tournament/Cargo.toml +++ b/exercises/practice/tournament/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "tournament" -version = "1.4.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/triangle/Cargo.toml b/exercises/practice/triangle/Cargo.toml index b7f441c75..9bb92a6ca 100644 --- a/exercises/practice/triangle/Cargo.toml +++ b/exercises/practice/triangle/Cargo.toml @@ -1,7 +1,12 @@ [package] -edition = "2021" name = "triangle" -version = "0.0.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] [features] generic = [] diff --git a/exercises/practice/triangle/src/lib.rs b/exercises/practice/triangle/src/lib.rs index be8ce7a76..020b2a4cf 100644 --- a/exercises/practice/triangle/src/lib.rs +++ b/exercises/practice/triangle/src/lib.rs @@ -2,7 +2,9 @@ pub struct Triangle; impl Triangle { pub fn build(sides: [u64; 3]) -> Option { - todo!("Construct new Triangle from following sides: {sides:?}. Return None if the sides are invalid."); + todo!( + "Construct new Triangle from following sides: {sides:?}. Return None if the sides are invalid." + ); } pub fn is_equilateral(&self) -> bool { diff --git a/exercises/practice/two-bucket/Cargo.toml b/exercises/practice/two-bucket/Cargo.toml index 9f578e2aa..4feb06478 100644 --- a/exercises/practice/two-bucket/Cargo.toml +++ b/exercises/practice/two-bucket/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "two-bucket" -version = "1.4.0" +name = "two_bucket" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/two-bucket/tests/two-bucket.rs b/exercises/practice/two-bucket/tests/two-bucket.rs index bdf75162c..af845e9a4 100644 --- a/exercises/practice/two-bucket/tests/two-bucket.rs +++ b/exercises/practice/two-bucket/tests/two-bucket.rs @@ -61,8 +61,8 @@ fn measure_one_step_using_bucket_one_of_size_1_and_bucket_two_of_size_3_start_wi #[test] #[ignore] -fn measure_using_bucket_one_of_size_2_and_bucket_two_of_size_3_start_with_bucket_one_and_end_with_bucket_two( -) { +fn measure_using_bucket_one_of_size_2_and_bucket_two_of_size_3_start_with_bucket_one_and_end_with_bucket_two() + { let output = solve(2, 3, 3, &Bucket::One); let expected = Some(BucketStats { moves: 2, diff --git a/exercises/practice/two-fer/Cargo.toml b/exercises/practice/two-fer/Cargo.toml index e3345023e..31941433a 100644 --- a/exercises/practice/two-fer/Cargo.toml +++ b/exercises/practice/two-fer/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" name = "twofer" -version = "1.2.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/variable-length-quantity/Cargo.toml b/exercises/practice/variable-length-quantity/Cargo.toml index 6986c0c89..0b8caf3f8 100644 --- a/exercises/practice/variable-length-quantity/Cargo.toml +++ b/exercises/practice/variable-length-quantity/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "variable-length-quantity" -version = "1.2.0" +name = "variable_length_quantity" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/word-count/Cargo.toml b/exercises/practice/word-count/Cargo.toml index c2fab6f70..7ac73abec 100644 --- a/exercises/practice/word-count/Cargo.toml +++ b/exercises/practice/word-count/Cargo.toml @@ -1,4 +1,9 @@ [package] -edition = "2021" -name = "word-count" -version = "1.2.0" +name = "word_count" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/wordy/Cargo.toml b/exercises/practice/wordy/Cargo.toml index 92e0bbcf2..aad29393a 100644 --- a/exercises/practice/wordy/Cargo.toml +++ b/exercises/practice/wordy/Cargo.toml @@ -1,7 +1,12 @@ [package] -edition = "2021" name = "wordy" -version = "1.5.0" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] [features] exponentials = [] diff --git a/exercises/practice/xorcism/Cargo.toml b/exercises/practice/xorcism/Cargo.toml index ee70fa468..d1cacc9a7 100644 --- a/exercises/practice/xorcism/Cargo.toml +++ b/exercises/practice/xorcism/Cargo.toml @@ -1,7 +1,12 @@ [package] name = "xorcism" version = "0.1.0" -edition = "2021" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] [features] io = [] diff --git a/exercises/practice/yacht/Cargo.toml b/exercises/practice/yacht/Cargo.toml index 2c3813008..e28004466 100644 --- a/exercises/practice/yacht/Cargo.toml +++ b/exercises/practice/yacht/Cargo.toml @@ -1,4 +1,9 @@ [package] name = "yacht" version = "0.1.0" -edition = "2021" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/rust-tooling/generate/src/lib.rs b/rust-tooling/generate/src/lib.rs index 377678bbe..a80d7a1d0 100644 --- a/rust-tooling/generate/src/lib.rs +++ b/rust-tooling/generate/src/lib.rs @@ -48,13 +48,17 @@ Cargo.lock fn generate_manifest(crate_name: &str) -> String { format!( - concat!( - "[package]\n", - "edition = \"2021\"\n", - "name = \"{crate_name}\"\n", - "version = \"1.0.0\"\n", - ), - crate_name = crate_name + "\ +[package] +name = \"{crate_name}\" +version = \"0.1.0\" +edition = \"2024\" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] +" ) } From e141afb7f26c6141cc92aa71e9cc8628df6cfb05 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Sat, 22 Feb 2025 01:43:55 +0100 Subject: [PATCH 385/436] update rust-tooling to 2024 edition (#2034) --- rust-tooling/ci-tests/Cargo.toml | 2 +- rust-tooling/generate/Cargo.toml | 2 +- rust-tooling/generate/src/cli.rs | 2 +- rust-tooling/generate/src/lib.rs | 2 +- rust-tooling/generate/src/main.rs | 2 +- rust-tooling/models/Cargo.toml | 2 +- rust-tooling/utils/Cargo.toml | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/rust-tooling/ci-tests/Cargo.toml b/rust-tooling/ci-tests/Cargo.toml index ee58ce99a..b0ee68b51 100644 --- a/rust-tooling/ci-tests/Cargo.toml +++ b/rust-tooling/ci-tests/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "ci-tests" version = "0.1.0" -edition = "2021" +edition = "2024" description = "Tests to be run in CI to make sure the repo is in good shape" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/rust-tooling/generate/Cargo.toml b/rust-tooling/generate/Cargo.toml index d84109f47..2525ee049 100644 --- a/rust-tooling/generate/Cargo.toml +++ b/rust-tooling/generate/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "generate" version = "0.1.0" -edition = "2021" +edition = "2024" description = "Generates exercise boilerplate, especially test cases" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/rust-tooling/generate/src/cli.rs b/rust-tooling/generate/src/cli.rs index bedf1541c..4461e30c8 100644 --- a/rust-tooling/generate/src/cli.rs +++ b/rust-tooling/generate/src/cli.rs @@ -2,7 +2,7 @@ use anyhow::{Context, Result}; use clap::{Parser, Subcommand}; use convert_case::{Case, Casing}; use glob::glob; -use inquire::{validator::Validation, Select, Text}; +use inquire::{Select, Text, validator::Validation}; use models::track_config; #[derive(Parser)] diff --git a/rust-tooling/generate/src/lib.rs b/rust-tooling/generate/src/lib.rs index a80d7a1d0..13a24006b 100644 --- a/rust-tooling/generate/src/lib.rs +++ b/rust-tooling/generate/src/lib.rs @@ -9,7 +9,7 @@ use tera::Tera; use custom_filters::CUSTOM_FILTERS; use models::{ exercise_config::get_excluded_tests, - problem_spec::{get_additional_test_cases, get_canonical_data, TestCase}, + problem_spec::{TestCase, get_additional_test_cases, get_canonical_data}, }; mod custom_filters; diff --git a/rust-tooling/generate/src/main.rs b/rust-tooling/generate/src/main.rs index 71e9bf1d6..9684123b6 100644 --- a/rust-tooling/generate/src/main.rs +++ b/rust-tooling/generate/src/main.rs @@ -2,7 +2,7 @@ use std::path::PathBuf; use anyhow::{Context, Result}; use clap::Parser; -use cli::{prompt_for_template_generation, AddArgs, FullAddArgs, UpdateArgs}; +use cli::{AddArgs, FullAddArgs, UpdateArgs, prompt_for_template_generation}; use models::{ exercise_config::PracticeExercise, track_config::{self, TRACK_CONFIG}, diff --git a/rust-tooling/models/Cargo.toml b/rust-tooling/models/Cargo.toml index 253be62b3..1fcdebad4 100644 --- a/rust-tooling/models/Cargo.toml +++ b/rust-tooling/models/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "models" version = "0.1.0" -edition = "2021" +edition = "2024" description = "Data structures for exercism stuff" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/rust-tooling/utils/Cargo.toml b/rust-tooling/utils/Cargo.toml index bd645df12..b45993731 100644 --- a/rust-tooling/utils/Cargo.toml +++ b/rust-tooling/utils/Cargo.toml @@ -1,4 +1,4 @@ [package] name = "utils" version = "0.1.0" -edition = "2021" +edition = "2024" From 353ca9ff211ab27017a06138d4aa1179730f115f Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Sat, 22 Feb 2025 01:44:02 +0100 Subject: [PATCH 386/436] generate: format tests with edition 2024 (#2035) --- rust-tooling/generate/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-tooling/generate/src/lib.rs b/rust-tooling/generate/src/lib.rs index 13a24006b..fc58df20e 100644 --- a/rust-tooling/generate/src/lib.rs +++ b/rust-tooling/generate/src/lib.rs @@ -131,7 +131,7 @@ fn generate_tests(slug: &str) -> Result { let rendered = rendered.replacen("#[ignore]\n", "", 1); let mut child = Command::new("rustfmt") - .args(["--color=always"]) + .args(["--color=always", "--edition=2024"]) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) From 88c13fb21eacd6d4101b98f900e525fe1be3d1ca Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Sat, 22 Feb 2025 02:11:29 +0100 Subject: [PATCH 387/436] rename test files to snake_case (#2036) --- config.json | 2 +- exercises/concept/assembly-line/.meta/config.json | 2 +- .../tests/{assembly-line.rs => assembly_line.rs} | 0 exercises/concept/csv-builder/.meta/config.json | 2 +- .../csv-builder/tests/{csv-builder.rs => csv_builder.rs} | 0 exercises/concept/health-statistics/.meta/config.json | 2 +- .../tests/{health-statistics.rs => health_statistics.rs} | 0 .../concept/low-power-embedded-game/.meta/config.json | 2 +- ...-power-embedded-game.rs => low_power_embedded_game.rs} | 0 .../concept/lucians-luscious-lasagna/.meta/config.json | 2 +- ...ns-luscious-lasagna.rs => lucians_luscious_lasagna.rs} | 0 exercises/concept/magazine-cutout/.meta/config.json | 2 +- .../tests/{magazine-cutout.rs => magazine_cutout.rs} | 0 exercises/concept/resistor-color/.meta/config.json | 2 +- .../tests/{resistor-color.rs => resistor_color.rs} | 0 exercises/concept/role-playing-game/.meta/config.json | 2 +- .../tests/{role-playing-game.rs => role_playing_game.rs} | 0 exercises/concept/rpn-calculator/.meta/config.json | 2 +- .../tests/{rpn-calculator.rs => rpn_calculator.rs} | 0 exercises/concept/semi-structured-logs/.meta/config.json | 2 +- .../{semi-structured-logs.rs => semi_structured_logs.rs} | 0 exercises/concept/short-fibonacci/.meta/config.json | 2 +- .../tests/{short-fibonacci.rs => short_fibonacci.rs} | 0 exercises/practice/affine-cipher/.meta/config.json | 2 +- .../tests/{affine-cipher.rs => affine_cipher.rs} | 0 exercises/practice/all-your-base/.meta/config.json | 2 +- .../tests/{all-your-base.rs => all_your_base.rs} | 0 exercises/practice/armstrong-numbers/.meta/config.json | 2 +- .../tests/{armstrong-numbers.rs => armstrong_numbers.rs} | 0 exercises/practice/atbash-cipher/.meta/config.json | 2 +- .../tests/{atbash-cipher.rs => atbash_cipher.rs} | 0 exercises/practice/beer-song/.meta/config.json | 2 +- .../beer-song/tests/{beer-song.rs => beer_song.rs} | 0 exercises/practice/binary-search/.meta/config.json | 2 +- .../tests/{binary-search.rs => binary_search.rs} | 0 exercises/practice/book-store/.meta/config.json | 2 +- .../book-store/tests/{book-store.rs => book_store.rs} | 0 exercises/practice/bottle-song/.meta/config.json | 2 +- .../bottle-song/tests/{bottle-song.rs => bottle_song.rs} | 0 exercises/practice/circular-buffer/.meta/config.json | 2 +- .../tests/{circular-buffer.rs => circular_buffer.rs} | 0 exercises/practice/collatz-conjecture/.meta/config.json | 2 +- .../{collatz-conjecture.rs => collatz_conjecture.rs} | 0 exercises/practice/crypto-square/.meta/config.json | 2 +- .../tests/{crypto-square.rs => crypto_square.rs} | 0 exercises/practice/custom-set/.meta/config.json | 2 +- .../custom-set/tests/{custom-set.rs => custom_set.rs} | 0 .../practice/difference-of-squares/.meta/config.json | 2 +- ...{difference-of-squares.rs => difference_of_squares.rs} | 0 exercises/practice/diffie-hellman/.meta/config.json | 2 +- .../tests/{diffie-hellman.rs => diffie_hellman.rs} | 0 exercises/practice/dot-dsl/.meta/config.json | 2 +- .../practice/dot-dsl/tests/{dot-dsl.rs => dot_dsl.rs} | 0 exercises/practice/doubly-linked-list/.meta/config.json | 2 +- .../{doubly-linked-list.rs => doubly_linked_list.rs} | 0 exercises/practice/eliuds-eggs/.meta/config.json | 2 +- .../eliuds-eggs/tests/{eliuds-eggs.rs => eliuds_eggs.rs} | 0 exercises/practice/forth/.meta/config.json | 2 +- .../forth/tests/{alloc-attack.rs => alloc_attack.rs} | 0 exercises/practice/grade-school/.meta/config.json | 2 +- .../tests/{grade-school.rs => grade_school.rs} | 0 exercises/practice/hello-world/.meta/config.json | 2 +- .../hello-world/tests/{hello-world.rs => hello_world.rs} | 0 exercises/practice/high-scores/.meta/config.json | 2 +- .../high-scores/tests/{high-scores.rs => high_scores.rs} | 0 exercises/practice/isbn-verifier/.meta/config.json | 2 +- .../tests/{isbn-verifier.rs => isbn_verifier.rs} | 0 exercises/practice/kindergarten-garden/.meta/config.json | 2 +- .../{kindergarten-garden.rs => kindergarten_garden.rs} | 0 .../practice/largest-series-product/.meta/config.json | 2 +- ...argest-series-product.rs => largest_series_product.rs} | 0 exercises/practice/list-ops/.meta/config.json | 2 +- .../practice/list-ops/tests/{list-ops.rs => list_ops.rs} | 0 exercises/practice/luhn-from/.meta/config.json | 2 +- .../luhn-from/tests/{luhn-from.rs => luhn_from.rs} | 0 exercises/practice/luhn-trait/.meta/config.json | 2 +- .../luhn-trait/tests/{luhn-trait.rs => luhn_trait.rs} | 0 .../macros/tests/invalid/{comma-sep.rs => comma_sep.rs} | 0 .../tests/invalid/{double-commas.rs => double_commas.rs} | 0 .../tests/invalid/{leading-comma.rs => leading_comma.rs} | 0 .../invalid/{missing-argument.rs => missing_argument.rs} | 0 .../macros/tests/invalid/{no-comma.rs => no_comma.rs} | 0 .../macros/tests/invalid/{only-arrow.rs => only_arrow.rs} | 0 .../macros/tests/invalid/{only-comma.rs => only_comma.rs} | 0 .../invalid/{single-argument.rs => single_argument.rs} | 0 .../invalid/{triple-arguments.rs => triple_arguments.rs} | 0 .../macros/tests/invalid/{two-arrows.rs => two_arrows.rs} | 0 exercises/practice/matching-brackets/.meta/config.json | 2 +- .../tests/{matching-brackets.rs => matching_brackets.rs} | 0 exercises/practice/nth-prime/.meta/config.json | 2 +- .../nth-prime/tests/{nth-prime.rs => nth_prime.rs} | 0 exercises/practice/nucleotide-codons/.meta/config.json | 2 +- .../tests/{nucleotide-codons.rs => nucleotide_codons.rs} | 0 exercises/practice/nucleotide-count/.meta/config.json | 2 +- .../tests/{nucleotide-count.rs => nucleotide_count.rs} | 0 exercises/practice/ocr-numbers/.meta/config.json | 2 +- .../ocr-numbers/tests/{ocr-numbers.rs => ocr_numbers.rs} | 0 exercises/practice/palindrome-products/.meta/config.json | 2 +- .../{palindrome-products.rs => palindrome_products.rs} | 0 .../practice/parallel-letter-frequency/.meta/config.json | 2 +- ...l-letter-frequency.rs => parallel_letter_frequency.rs} | 0 exercises/practice/pascals-triangle/.meta/config.json | 2 +- .../tests/{pascals-triangle.rs => pascals_triangle.rs} | 0 exercises/practice/perfect-numbers/.meta/config.json | 2 +- .../tests/{perfect-numbers.rs => perfect_numbers.rs} | 0 exercises/practice/phone-number/.meta/config.json | 2 +- .../tests/{phone-number.rs => phone_number.rs} | 0 exercises/practice/pig-latin/.meta/config.json | 2 +- .../pig-latin/tests/{pig-latin.rs => pig_latin.rs} | 0 exercises/practice/prime-factors/.meta/config.json | 2 +- .../tests/{prime-factors.rs => prime_factors.rs} | 0 exercises/practice/protein-translation/.meta/config.json | 2 +- .../{protein-translation.rs => protein_translation.rs} | 0 exercises/practice/pythagorean-triplet/.meta/config.json | 2 +- .../{pythagorean-triplet.rs => pythagorean_triplet.rs} | 0 exercises/practice/queen-attack/.meta/config.json | 2 +- .../tests/{queen-attack.rs => queen_attack.rs} | 0 exercises/practice/rail-fence-cipher/.meta/config.json | 2 +- .../tests/{rail-fence-cipher.rs => rail_fence_cipher.rs} | 0 exercises/practice/reverse-string/.meta/config.json | 2 +- .../tests/{reverse-string.rs => reverse_string.rs} | 0 exercises/practice/rna-transcription/.meta/config.json | 2 +- .../tests/{rna-transcription.rs => rna_transcription.rs} | 0 exercises/practice/robot-name/.meta/config.json | 2 +- .../robot-name/tests/{robot-name.rs => robot_name.rs} | 0 exercises/practice/robot-simulator/.meta/config.json | 2 +- .../tests/{robot-simulator.rs => robot_simulator.rs} | 0 exercises/practice/roman-numerals/.meta/config.json | 2 +- .../tests/{roman-numerals.rs => roman_numerals.rs} | 0 exercises/practice/rotational-cipher/.meta/config.json | 2 +- .../tests/{rotational-cipher.rs => rotational_cipher.rs} | 0 exercises/practice/run-length-encoding/.meta/config.json | 2 +- .../{run-length-encoding.rs => run_length_encoding.rs} | 0 exercises/practice/saddle-points/.meta/config.json | 2 +- .../tests/{saddle-points.rs => saddle_points.rs} | 0 exercises/practice/scale-generator/.meta/config.json | 2 +- .../tests/{scale-generator.rs => scale_generator.rs} | 0 exercises/practice/scrabble-score/.meta/config.json | 2 +- .../tests/{scrabble-score.rs => scrabble_score.rs} | 0 exercises/practice/secret-handshake/.meta/config.json | 2 +- .../tests/{secret-handshake.rs => secret_handshake.rs} | 0 exercises/practice/simple-cipher/.meta/config.json | 2 +- .../tests/{simple-cipher.rs => simple_cipher.rs} | 0 exercises/practice/simple-linked-list/.meta/config.json | 2 +- .../{simple-linked-list.rs => simple_linked_list.rs} | 0 exercises/practice/space-age/.meta/config.json | 2 +- .../space-age/tests/{space-age.rs => space_age.rs} | 0 exercises/practice/spiral-matrix/.meta/config.json | 2 +- .../tests/{spiral-matrix.rs => spiral_matrix.rs} | 0 exercises/practice/sum-of-multiples/.meta/config.json | 2 +- .../tests/{sum-of-multiples.rs => sum_of_multiples.rs} | 0 exercises/practice/two-bucket/.meta/config.json | 2 +- .../two-bucket/tests/{two-bucket.rs => two_bucket.rs} | 0 exercises/practice/two-fer/.meta/config.json | 2 +- .../practice/two-fer/tests/{two-fer.rs => two_fer.rs} | 0 .../practice/variable-length-quantity/.meta/config.json | 2 +- ...ble-length-quantity.rs => variable_length_quantity.rs} | 0 exercises/practice/word-count/.meta/config.json | 2 +- .../word-count/tests/{word-count.rs => word_count.rs} | 0 rust-tooling/ci-tests/tests/count_ignores.rs | 3 ++- rust-tooling/generate/src/main.rs | 8 ++++++-- rust-tooling/generate/tests/tera_templates_are_in_sync.rs | 3 ++- 162 files changed, 85 insertions(+), 79 deletions(-) rename exercises/concept/assembly-line/tests/{assembly-line.rs => assembly_line.rs} (100%) rename exercises/concept/csv-builder/tests/{csv-builder.rs => csv_builder.rs} (100%) rename exercises/concept/health-statistics/tests/{health-statistics.rs => health_statistics.rs} (100%) rename exercises/concept/low-power-embedded-game/tests/{low-power-embedded-game.rs => low_power_embedded_game.rs} (100%) rename exercises/concept/lucians-luscious-lasagna/tests/{lucians-luscious-lasagna.rs => lucians_luscious_lasagna.rs} (100%) rename exercises/concept/magazine-cutout/tests/{magazine-cutout.rs => magazine_cutout.rs} (100%) rename exercises/concept/resistor-color/tests/{resistor-color.rs => resistor_color.rs} (100%) rename exercises/concept/role-playing-game/tests/{role-playing-game.rs => role_playing_game.rs} (100%) rename exercises/concept/rpn-calculator/tests/{rpn-calculator.rs => rpn_calculator.rs} (100%) rename exercises/concept/semi-structured-logs/tests/{semi-structured-logs.rs => semi_structured_logs.rs} (100%) rename exercises/concept/short-fibonacci/tests/{short-fibonacci.rs => short_fibonacci.rs} (100%) rename exercises/practice/affine-cipher/tests/{affine-cipher.rs => affine_cipher.rs} (100%) rename exercises/practice/all-your-base/tests/{all-your-base.rs => all_your_base.rs} (100%) rename exercises/practice/armstrong-numbers/tests/{armstrong-numbers.rs => armstrong_numbers.rs} (100%) rename exercises/practice/atbash-cipher/tests/{atbash-cipher.rs => atbash_cipher.rs} (100%) rename exercises/practice/beer-song/tests/{beer-song.rs => beer_song.rs} (100%) rename exercises/practice/binary-search/tests/{binary-search.rs => binary_search.rs} (100%) rename exercises/practice/book-store/tests/{book-store.rs => book_store.rs} (100%) rename exercises/practice/bottle-song/tests/{bottle-song.rs => bottle_song.rs} (100%) rename exercises/practice/circular-buffer/tests/{circular-buffer.rs => circular_buffer.rs} (100%) rename exercises/practice/collatz-conjecture/tests/{collatz-conjecture.rs => collatz_conjecture.rs} (100%) rename exercises/practice/crypto-square/tests/{crypto-square.rs => crypto_square.rs} (100%) rename exercises/practice/custom-set/tests/{custom-set.rs => custom_set.rs} (100%) rename exercises/practice/difference-of-squares/tests/{difference-of-squares.rs => difference_of_squares.rs} (100%) rename exercises/practice/diffie-hellman/tests/{diffie-hellman.rs => diffie_hellman.rs} (100%) rename exercises/practice/dot-dsl/tests/{dot-dsl.rs => dot_dsl.rs} (100%) rename exercises/practice/doubly-linked-list/tests/{doubly-linked-list.rs => doubly_linked_list.rs} (100%) rename exercises/practice/eliuds-eggs/tests/{eliuds-eggs.rs => eliuds_eggs.rs} (100%) rename exercises/practice/forth/tests/{alloc-attack.rs => alloc_attack.rs} (100%) rename exercises/practice/grade-school/tests/{grade-school.rs => grade_school.rs} (100%) rename exercises/practice/hello-world/tests/{hello-world.rs => hello_world.rs} (100%) rename exercises/practice/high-scores/tests/{high-scores.rs => high_scores.rs} (100%) rename exercises/practice/isbn-verifier/tests/{isbn-verifier.rs => isbn_verifier.rs} (100%) rename exercises/practice/kindergarten-garden/tests/{kindergarten-garden.rs => kindergarten_garden.rs} (100%) rename exercises/practice/largest-series-product/tests/{largest-series-product.rs => largest_series_product.rs} (100%) rename exercises/practice/list-ops/tests/{list-ops.rs => list_ops.rs} (100%) rename exercises/practice/luhn-from/tests/{luhn-from.rs => luhn_from.rs} (100%) rename exercises/practice/luhn-trait/tests/{luhn-trait.rs => luhn_trait.rs} (100%) rename exercises/practice/macros/tests/invalid/{comma-sep.rs => comma_sep.rs} (100%) rename exercises/practice/macros/tests/invalid/{double-commas.rs => double_commas.rs} (100%) rename exercises/practice/macros/tests/invalid/{leading-comma.rs => leading_comma.rs} (100%) rename exercises/practice/macros/tests/invalid/{missing-argument.rs => missing_argument.rs} (100%) rename exercises/practice/macros/tests/invalid/{no-comma.rs => no_comma.rs} (100%) rename exercises/practice/macros/tests/invalid/{only-arrow.rs => only_arrow.rs} (100%) rename exercises/practice/macros/tests/invalid/{only-comma.rs => only_comma.rs} (100%) rename exercises/practice/macros/tests/invalid/{single-argument.rs => single_argument.rs} (100%) rename exercises/practice/macros/tests/invalid/{triple-arguments.rs => triple_arguments.rs} (100%) rename exercises/practice/macros/tests/invalid/{two-arrows.rs => two_arrows.rs} (100%) rename exercises/practice/matching-brackets/tests/{matching-brackets.rs => matching_brackets.rs} (100%) rename exercises/practice/nth-prime/tests/{nth-prime.rs => nth_prime.rs} (100%) rename exercises/practice/nucleotide-codons/tests/{nucleotide-codons.rs => nucleotide_codons.rs} (100%) rename exercises/practice/nucleotide-count/tests/{nucleotide-count.rs => nucleotide_count.rs} (100%) rename exercises/practice/ocr-numbers/tests/{ocr-numbers.rs => ocr_numbers.rs} (100%) rename exercises/practice/palindrome-products/tests/{palindrome-products.rs => palindrome_products.rs} (100%) rename exercises/practice/parallel-letter-frequency/tests/{parallel-letter-frequency.rs => parallel_letter_frequency.rs} (100%) rename exercises/practice/pascals-triangle/tests/{pascals-triangle.rs => pascals_triangle.rs} (100%) rename exercises/practice/perfect-numbers/tests/{perfect-numbers.rs => perfect_numbers.rs} (100%) rename exercises/practice/phone-number/tests/{phone-number.rs => phone_number.rs} (100%) rename exercises/practice/pig-latin/tests/{pig-latin.rs => pig_latin.rs} (100%) rename exercises/practice/prime-factors/tests/{prime-factors.rs => prime_factors.rs} (100%) rename exercises/practice/protein-translation/tests/{protein-translation.rs => protein_translation.rs} (100%) rename exercises/practice/pythagorean-triplet/tests/{pythagorean-triplet.rs => pythagorean_triplet.rs} (100%) rename exercises/practice/queen-attack/tests/{queen-attack.rs => queen_attack.rs} (100%) rename exercises/practice/rail-fence-cipher/tests/{rail-fence-cipher.rs => rail_fence_cipher.rs} (100%) rename exercises/practice/reverse-string/tests/{reverse-string.rs => reverse_string.rs} (100%) rename exercises/practice/rna-transcription/tests/{rna-transcription.rs => rna_transcription.rs} (100%) rename exercises/practice/robot-name/tests/{robot-name.rs => robot_name.rs} (100%) rename exercises/practice/robot-simulator/tests/{robot-simulator.rs => robot_simulator.rs} (100%) rename exercises/practice/roman-numerals/tests/{roman-numerals.rs => roman_numerals.rs} (100%) rename exercises/practice/rotational-cipher/tests/{rotational-cipher.rs => rotational_cipher.rs} (100%) rename exercises/practice/run-length-encoding/tests/{run-length-encoding.rs => run_length_encoding.rs} (100%) rename exercises/practice/saddle-points/tests/{saddle-points.rs => saddle_points.rs} (100%) rename exercises/practice/scale-generator/tests/{scale-generator.rs => scale_generator.rs} (100%) rename exercises/practice/scrabble-score/tests/{scrabble-score.rs => scrabble_score.rs} (100%) rename exercises/practice/secret-handshake/tests/{secret-handshake.rs => secret_handshake.rs} (100%) rename exercises/practice/simple-cipher/tests/{simple-cipher.rs => simple_cipher.rs} (100%) rename exercises/practice/simple-linked-list/tests/{simple-linked-list.rs => simple_linked_list.rs} (100%) rename exercises/practice/space-age/tests/{space-age.rs => space_age.rs} (100%) rename exercises/practice/spiral-matrix/tests/{spiral-matrix.rs => spiral_matrix.rs} (100%) rename exercises/practice/sum-of-multiples/tests/{sum-of-multiples.rs => sum_of_multiples.rs} (100%) rename exercises/practice/two-bucket/tests/{two-bucket.rs => two_bucket.rs} (100%) rename exercises/practice/two-fer/tests/{two-fer.rs => two_fer.rs} (100%) rename exercises/practice/variable-length-quantity/tests/{variable-length-quantity.rs => variable_length_quantity.rs} (100%) rename exercises/practice/word-count/tests/{word-count.rs => word_count.rs} (100%) diff --git a/config.json b/config.json index b1ff5ff11..abcfac83b 100644 --- a/config.json +++ b/config.json @@ -24,7 +24,7 @@ "Cargo.toml" ], "test": [ - "tests/%{kebab_slug}.rs" + "tests/%{snake_slug}.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/concept/assembly-line/.meta/config.json b/exercises/concept/assembly-line/.meta/config.json index a8a9750a4..bbe7a5f86 100644 --- a/exercises/concept/assembly-line/.meta/config.json +++ b/exercises/concept/assembly-line/.meta/config.json @@ -9,7 +9,7 @@ "Cargo.toml" ], "test": [ - "tests/assembly-line.rs" + "tests/assembly_line.rs" ], "exemplar": [ ".meta/exemplar.rs" diff --git a/exercises/concept/assembly-line/tests/assembly-line.rs b/exercises/concept/assembly-line/tests/assembly_line.rs similarity index 100% rename from exercises/concept/assembly-line/tests/assembly-line.rs rename to exercises/concept/assembly-line/tests/assembly_line.rs diff --git a/exercises/concept/csv-builder/.meta/config.json b/exercises/concept/csv-builder/.meta/config.json index 0d8d3df92..5fd7bdc84 100644 --- a/exercises/concept/csv-builder/.meta/config.json +++ b/exercises/concept/csv-builder/.meta/config.json @@ -10,7 +10,7 @@ "Cargo.toml" ], "test": [ - "tests/csv-builder.rs" + "tests/csv_builder.rs" ], "exemplar": [ ".meta/exemplar.rs" diff --git a/exercises/concept/csv-builder/tests/csv-builder.rs b/exercises/concept/csv-builder/tests/csv_builder.rs similarity index 100% rename from exercises/concept/csv-builder/tests/csv-builder.rs rename to exercises/concept/csv-builder/tests/csv_builder.rs diff --git a/exercises/concept/health-statistics/.meta/config.json b/exercises/concept/health-statistics/.meta/config.json index c8660b9cc..7a860b7b2 100644 --- a/exercises/concept/health-statistics/.meta/config.json +++ b/exercises/concept/health-statistics/.meta/config.json @@ -8,7 +8,7 @@ "Cargo.toml" ], "test": [ - "tests/health-statistics.rs" + "tests/health_statistics.rs" ], "exemplar": [ ".meta/exemplar.rs" diff --git a/exercises/concept/health-statistics/tests/health-statistics.rs b/exercises/concept/health-statistics/tests/health_statistics.rs similarity index 100% rename from exercises/concept/health-statistics/tests/health-statistics.rs rename to exercises/concept/health-statistics/tests/health_statistics.rs diff --git a/exercises/concept/low-power-embedded-game/.meta/config.json b/exercises/concept/low-power-embedded-game/.meta/config.json index 73bb27fc1..1e96232ea 100644 --- a/exercises/concept/low-power-embedded-game/.meta/config.json +++ b/exercises/concept/low-power-embedded-game/.meta/config.json @@ -8,7 +8,7 @@ "Cargo.toml" ], "test": [ - "tests/low-power-embedded-game.rs" + "tests/low_power_embedded_game.rs" ], "exemplar": [ ".meta/exemplar.rs" diff --git a/exercises/concept/low-power-embedded-game/tests/low-power-embedded-game.rs b/exercises/concept/low-power-embedded-game/tests/low_power_embedded_game.rs similarity index 100% rename from exercises/concept/low-power-embedded-game/tests/low-power-embedded-game.rs rename to exercises/concept/low-power-embedded-game/tests/low_power_embedded_game.rs diff --git a/exercises/concept/lucians-luscious-lasagna/.meta/config.json b/exercises/concept/lucians-luscious-lasagna/.meta/config.json index 1a3417d76..129d188ee 100644 --- a/exercises/concept/lucians-luscious-lasagna/.meta/config.json +++ b/exercises/concept/lucians-luscious-lasagna/.meta/config.json @@ -9,7 +9,7 @@ "Cargo.toml" ], "test": [ - "tests/lucians-luscious-lasagna.rs" + "tests/lucians_luscious_lasagna.rs" ], "exemplar": [ ".meta/exemplar.rs" diff --git a/exercises/concept/lucians-luscious-lasagna/tests/lucians-luscious-lasagna.rs b/exercises/concept/lucians-luscious-lasagna/tests/lucians_luscious_lasagna.rs similarity index 100% rename from exercises/concept/lucians-luscious-lasagna/tests/lucians-luscious-lasagna.rs rename to exercises/concept/lucians-luscious-lasagna/tests/lucians_luscious_lasagna.rs diff --git a/exercises/concept/magazine-cutout/.meta/config.json b/exercises/concept/magazine-cutout/.meta/config.json index 4afc9ebc0..39e0a7101 100644 --- a/exercises/concept/magazine-cutout/.meta/config.json +++ b/exercises/concept/magazine-cutout/.meta/config.json @@ -8,7 +8,7 @@ "Cargo.toml" ], "test": [ - "tests/magazine-cutout.rs" + "tests/magazine_cutout.rs" ], "exemplar": [ ".meta/exemplar.rs" diff --git a/exercises/concept/magazine-cutout/tests/magazine-cutout.rs b/exercises/concept/magazine-cutout/tests/magazine_cutout.rs similarity index 100% rename from exercises/concept/magazine-cutout/tests/magazine-cutout.rs rename to exercises/concept/magazine-cutout/tests/magazine_cutout.rs diff --git a/exercises/concept/resistor-color/.meta/config.json b/exercises/concept/resistor-color/.meta/config.json index 7d5443d1d..2c08f12b0 100644 --- a/exercises/concept/resistor-color/.meta/config.json +++ b/exercises/concept/resistor-color/.meta/config.json @@ -11,7 +11,7 @@ "Cargo.toml" ], "test": [ - "tests/resistor-color.rs" + "tests/resistor_color.rs" ], "exemplar": [ ".meta/exemplar.rs" diff --git a/exercises/concept/resistor-color/tests/resistor-color.rs b/exercises/concept/resistor-color/tests/resistor_color.rs similarity index 100% rename from exercises/concept/resistor-color/tests/resistor-color.rs rename to exercises/concept/resistor-color/tests/resistor_color.rs diff --git a/exercises/concept/role-playing-game/.meta/config.json b/exercises/concept/role-playing-game/.meta/config.json index b7ab9560e..bb8817b10 100644 --- a/exercises/concept/role-playing-game/.meta/config.json +++ b/exercises/concept/role-playing-game/.meta/config.json @@ -12,7 +12,7 @@ "Cargo.toml" ], "test": [ - "tests/role-playing-game.rs" + "tests/role_playing_game.rs" ], "exemplar": [ ".meta/exemplar.rs" diff --git a/exercises/concept/role-playing-game/tests/role-playing-game.rs b/exercises/concept/role-playing-game/tests/role_playing_game.rs similarity index 100% rename from exercises/concept/role-playing-game/tests/role-playing-game.rs rename to exercises/concept/role-playing-game/tests/role_playing_game.rs diff --git a/exercises/concept/rpn-calculator/.meta/config.json b/exercises/concept/rpn-calculator/.meta/config.json index 61b4cca78..6f1a89c4f 100644 --- a/exercises/concept/rpn-calculator/.meta/config.json +++ b/exercises/concept/rpn-calculator/.meta/config.json @@ -8,7 +8,7 @@ "Cargo.toml" ], "test": [ - "tests/rpn-calculator.rs" + "tests/rpn_calculator.rs" ], "exemplar": [ ".meta/exemplar.rs" diff --git a/exercises/concept/rpn-calculator/tests/rpn-calculator.rs b/exercises/concept/rpn-calculator/tests/rpn_calculator.rs similarity index 100% rename from exercises/concept/rpn-calculator/tests/rpn-calculator.rs rename to exercises/concept/rpn-calculator/tests/rpn_calculator.rs diff --git a/exercises/concept/semi-structured-logs/.meta/config.json b/exercises/concept/semi-structured-logs/.meta/config.json index 6d9300eaa..0df38abeb 100644 --- a/exercises/concept/semi-structured-logs/.meta/config.json +++ b/exercises/concept/semi-structured-logs/.meta/config.json @@ -8,7 +8,7 @@ "Cargo.toml" ], "test": [ - "tests/semi-structured-logs.rs" + "tests/semi_structured_logs.rs" ], "exemplar": [ ".meta/exemplar.rs" diff --git a/exercises/concept/semi-structured-logs/tests/semi-structured-logs.rs b/exercises/concept/semi-structured-logs/tests/semi_structured_logs.rs similarity index 100% rename from exercises/concept/semi-structured-logs/tests/semi-structured-logs.rs rename to exercises/concept/semi-structured-logs/tests/semi_structured_logs.rs diff --git a/exercises/concept/short-fibonacci/.meta/config.json b/exercises/concept/short-fibonacci/.meta/config.json index 9da7ea638..e3b2fc09f 100644 --- a/exercises/concept/short-fibonacci/.meta/config.json +++ b/exercises/concept/short-fibonacci/.meta/config.json @@ -9,7 +9,7 @@ "Cargo.toml" ], "test": [ - "tests/short-fibonacci.rs" + "tests/short_fibonacci.rs" ], "exemplar": [ ".meta/exemplar.rs" diff --git a/exercises/concept/short-fibonacci/tests/short-fibonacci.rs b/exercises/concept/short-fibonacci/tests/short_fibonacci.rs similarity index 100% rename from exercises/concept/short-fibonacci/tests/short-fibonacci.rs rename to exercises/concept/short-fibonacci/tests/short_fibonacci.rs diff --git a/exercises/practice/affine-cipher/.meta/config.json b/exercises/practice/affine-cipher/.meta/config.json index 34bb0ac2b..19e73e177 100644 --- a/exercises/practice/affine-cipher/.meta/config.json +++ b/exercises/practice/affine-cipher/.meta/config.json @@ -16,7 +16,7 @@ "Cargo.toml" ], "test": [ - "tests/affine-cipher.rs" + "tests/affine_cipher.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/affine-cipher/tests/affine-cipher.rs b/exercises/practice/affine-cipher/tests/affine_cipher.rs similarity index 100% rename from exercises/practice/affine-cipher/tests/affine-cipher.rs rename to exercises/practice/affine-cipher/tests/affine_cipher.rs diff --git a/exercises/practice/all-your-base/.meta/config.json b/exercises/practice/all-your-base/.meta/config.json index 7e4b21957..99988fe66 100644 --- a/exercises/practice/all-your-base/.meta/config.json +++ b/exercises/practice/all-your-base/.meta/config.json @@ -26,7 +26,7 @@ "Cargo.toml" ], "test": [ - "tests/all-your-base.rs" + "tests/all_your_base.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/all-your-base/tests/all-your-base.rs b/exercises/practice/all-your-base/tests/all_your_base.rs similarity index 100% rename from exercises/practice/all-your-base/tests/all-your-base.rs rename to exercises/practice/all-your-base/tests/all_your_base.rs diff --git a/exercises/practice/armstrong-numbers/.meta/config.json b/exercises/practice/armstrong-numbers/.meta/config.json index da15665f3..6cc65ea5e 100644 --- a/exercises/practice/armstrong-numbers/.meta/config.json +++ b/exercises/practice/armstrong-numbers/.meta/config.json @@ -24,7 +24,7 @@ "Cargo.toml" ], "test": [ - "tests/armstrong-numbers.rs" + "tests/armstrong_numbers.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/armstrong-numbers/tests/armstrong-numbers.rs b/exercises/practice/armstrong-numbers/tests/armstrong_numbers.rs similarity index 100% rename from exercises/practice/armstrong-numbers/tests/armstrong-numbers.rs rename to exercises/practice/armstrong-numbers/tests/armstrong_numbers.rs diff --git a/exercises/practice/atbash-cipher/.meta/config.json b/exercises/practice/atbash-cipher/.meta/config.json index 82cfd2b8c..10a3184ec 100644 --- a/exercises/practice/atbash-cipher/.meta/config.json +++ b/exercises/practice/atbash-cipher/.meta/config.json @@ -27,7 +27,7 @@ "Cargo.toml" ], "test": [ - "tests/atbash-cipher.rs" + "tests/atbash_cipher.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/atbash-cipher/tests/atbash-cipher.rs b/exercises/practice/atbash-cipher/tests/atbash_cipher.rs similarity index 100% rename from exercises/practice/atbash-cipher/tests/atbash-cipher.rs rename to exercises/practice/atbash-cipher/tests/atbash_cipher.rs diff --git a/exercises/practice/beer-song/.meta/config.json b/exercises/practice/beer-song/.meta/config.json index dcd36935b..ae57f1965 100644 --- a/exercises/practice/beer-song/.meta/config.json +++ b/exercises/practice/beer-song/.meta/config.json @@ -32,7 +32,7 @@ "Cargo.toml" ], "test": [ - "tests/beer-song.rs" + "tests/beer_song.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/beer-song/tests/beer-song.rs b/exercises/practice/beer-song/tests/beer_song.rs similarity index 100% rename from exercises/practice/beer-song/tests/beer-song.rs rename to exercises/practice/beer-song/tests/beer_song.rs diff --git a/exercises/practice/binary-search/.meta/config.json b/exercises/practice/binary-search/.meta/config.json index b55dd3ed9..cc8c2d21f 100644 --- a/exercises/practice/binary-search/.meta/config.json +++ b/exercises/practice/binary-search/.meta/config.json @@ -22,7 +22,7 @@ "Cargo.toml" ], "test": [ - "tests/binary-search.rs" + "tests/binary_search.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/binary-search/tests/binary-search.rs b/exercises/practice/binary-search/tests/binary_search.rs similarity index 100% rename from exercises/practice/binary-search/tests/binary-search.rs rename to exercises/practice/binary-search/tests/binary_search.rs diff --git a/exercises/practice/book-store/.meta/config.json b/exercises/practice/book-store/.meta/config.json index 2e37770ab..2e9f38b6f 100644 --- a/exercises/practice/book-store/.meta/config.json +++ b/exercises/practice/book-store/.meta/config.json @@ -20,7 +20,7 @@ "Cargo.toml" ], "test": [ - "tests/book-store.rs" + "tests/book_store.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/book-store/tests/book-store.rs b/exercises/practice/book-store/tests/book_store.rs similarity index 100% rename from exercises/practice/book-store/tests/book-store.rs rename to exercises/practice/book-store/tests/book_store.rs diff --git a/exercises/practice/bottle-song/.meta/config.json b/exercises/practice/bottle-song/.meta/config.json index 1dcfb0e91..a0b30d504 100644 --- a/exercises/practice/bottle-song/.meta/config.json +++ b/exercises/practice/bottle-song/.meta/config.json @@ -8,7 +8,7 @@ "Cargo.toml" ], "test": [ - "tests/bottle-song.rs" + "tests/bottle_song.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/bottle-song/tests/bottle-song.rs b/exercises/practice/bottle-song/tests/bottle_song.rs similarity index 100% rename from exercises/practice/bottle-song/tests/bottle-song.rs rename to exercises/practice/bottle-song/tests/bottle_song.rs diff --git a/exercises/practice/circular-buffer/.meta/config.json b/exercises/practice/circular-buffer/.meta/config.json index 74871ba5c..7a40a5e30 100644 --- a/exercises/practice/circular-buffer/.meta/config.json +++ b/exercises/practice/circular-buffer/.meta/config.json @@ -29,7 +29,7 @@ "Cargo.toml" ], "test": [ - "tests/circular-buffer.rs" + "tests/circular_buffer.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/circular-buffer/tests/circular-buffer.rs b/exercises/practice/circular-buffer/tests/circular_buffer.rs similarity index 100% rename from exercises/practice/circular-buffer/tests/circular-buffer.rs rename to exercises/practice/circular-buffer/tests/circular_buffer.rs diff --git a/exercises/practice/collatz-conjecture/.meta/config.json b/exercises/practice/collatz-conjecture/.meta/config.json index f53fbab83..245457b6c 100644 --- a/exercises/practice/collatz-conjecture/.meta/config.json +++ b/exercises/practice/collatz-conjecture/.meta/config.json @@ -22,7 +22,7 @@ "Cargo.toml" ], "test": [ - "tests/collatz-conjecture.rs" + "tests/collatz_conjecture.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/collatz-conjecture/tests/collatz-conjecture.rs b/exercises/practice/collatz-conjecture/tests/collatz_conjecture.rs similarity index 100% rename from exercises/practice/collatz-conjecture/tests/collatz-conjecture.rs rename to exercises/practice/collatz-conjecture/tests/collatz_conjecture.rs diff --git a/exercises/practice/crypto-square/.meta/config.json b/exercises/practice/crypto-square/.meta/config.json index 4cf36b421..27e42b68b 100644 --- a/exercises/practice/crypto-square/.meta/config.json +++ b/exercises/practice/crypto-square/.meta/config.json @@ -24,7 +24,7 @@ "Cargo.toml" ], "test": [ - "tests/crypto-square.rs" + "tests/crypto_square.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/crypto-square/tests/crypto-square.rs b/exercises/practice/crypto-square/tests/crypto_square.rs similarity index 100% rename from exercises/practice/crypto-square/tests/crypto-square.rs rename to exercises/practice/crypto-square/tests/crypto_square.rs diff --git a/exercises/practice/custom-set/.meta/config.json b/exercises/practice/custom-set/.meta/config.json index 40567345a..db301405f 100644 --- a/exercises/practice/custom-set/.meta/config.json +++ b/exercises/practice/custom-set/.meta/config.json @@ -28,7 +28,7 @@ "Cargo.toml" ], "test": [ - "tests/custom-set.rs" + "tests/custom_set.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/custom-set/tests/custom-set.rs b/exercises/practice/custom-set/tests/custom_set.rs similarity index 100% rename from exercises/practice/custom-set/tests/custom-set.rs rename to exercises/practice/custom-set/tests/custom_set.rs diff --git a/exercises/practice/difference-of-squares/.meta/config.json b/exercises/practice/difference-of-squares/.meta/config.json index ac8053366..de21882e8 100644 --- a/exercises/practice/difference-of-squares/.meta/config.json +++ b/exercises/practice/difference-of-squares/.meta/config.json @@ -29,7 +29,7 @@ "Cargo.toml" ], "test": [ - "tests/difference-of-squares.rs" + "tests/difference_of_squares.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/difference-of-squares/tests/difference-of-squares.rs b/exercises/practice/difference-of-squares/tests/difference_of_squares.rs similarity index 100% rename from exercises/practice/difference-of-squares/tests/difference-of-squares.rs rename to exercises/practice/difference-of-squares/tests/difference_of_squares.rs diff --git a/exercises/practice/diffie-hellman/.meta/config.json b/exercises/practice/diffie-hellman/.meta/config.json index 37fb2d378..327e6ab3c 100644 --- a/exercises/practice/diffie-hellman/.meta/config.json +++ b/exercises/practice/diffie-hellman/.meta/config.json @@ -23,7 +23,7 @@ "Cargo.toml" ], "test": [ - "tests/diffie-hellman.rs" + "tests/diffie_hellman.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/diffie-hellman/tests/diffie-hellman.rs b/exercises/practice/diffie-hellman/tests/diffie_hellman.rs similarity index 100% rename from exercises/practice/diffie-hellman/tests/diffie-hellman.rs rename to exercises/practice/diffie-hellman/tests/diffie_hellman.rs diff --git a/exercises/practice/dot-dsl/.meta/config.json b/exercises/practice/dot-dsl/.meta/config.json index b01e83179..b46b71528 100644 --- a/exercises/practice/dot-dsl/.meta/config.json +++ b/exercises/practice/dot-dsl/.meta/config.json @@ -20,7 +20,7 @@ "Cargo.toml" ], "test": [ - "tests/dot-dsl.rs" + "tests/dot_dsl.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/dot-dsl/tests/dot-dsl.rs b/exercises/practice/dot-dsl/tests/dot_dsl.rs similarity index 100% rename from exercises/practice/dot-dsl/tests/dot-dsl.rs rename to exercises/practice/dot-dsl/tests/dot_dsl.rs diff --git a/exercises/practice/doubly-linked-list/.meta/config.json b/exercises/practice/doubly-linked-list/.meta/config.json index 5d7dcc88b..4b0db227d 100644 --- a/exercises/practice/doubly-linked-list/.meta/config.json +++ b/exercises/practice/doubly-linked-list/.meta/config.json @@ -16,7 +16,7 @@ "Cargo.toml" ], "test": [ - "tests/doubly-linked-list.rs" + "tests/doubly_linked_list.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/doubly-linked-list/tests/doubly-linked-list.rs b/exercises/practice/doubly-linked-list/tests/doubly_linked_list.rs similarity index 100% rename from exercises/practice/doubly-linked-list/tests/doubly-linked-list.rs rename to exercises/practice/doubly-linked-list/tests/doubly_linked_list.rs diff --git a/exercises/practice/eliuds-eggs/.meta/config.json b/exercises/practice/eliuds-eggs/.meta/config.json index 27c4e6f6f..dd2fdf2b5 100644 --- a/exercises/practice/eliuds-eggs/.meta/config.json +++ b/exercises/practice/eliuds-eggs/.meta/config.json @@ -6,7 +6,7 @@ "Cargo.toml" ], "test": [ - "tests/eliuds-eggs.rs" + "tests/eliuds_eggs.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/eliuds-eggs/tests/eliuds-eggs.rs b/exercises/practice/eliuds-eggs/tests/eliuds_eggs.rs similarity index 100% rename from exercises/practice/eliuds-eggs/tests/eliuds-eggs.rs rename to exercises/practice/eliuds-eggs/tests/eliuds_eggs.rs diff --git a/exercises/practice/forth/.meta/config.json b/exercises/practice/forth/.meta/config.json index 6abe60190..ec4175d59 100644 --- a/exercises/practice/forth/.meta/config.json +++ b/exercises/practice/forth/.meta/config.json @@ -34,7 +34,7 @@ ], "test": [ "tests/forth.rs", - "tests/alloc-attack.rs" + "tests/alloc_attack.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/forth/tests/alloc-attack.rs b/exercises/practice/forth/tests/alloc_attack.rs similarity index 100% rename from exercises/practice/forth/tests/alloc-attack.rs rename to exercises/practice/forth/tests/alloc_attack.rs diff --git a/exercises/practice/grade-school/.meta/config.json b/exercises/practice/grade-school/.meta/config.json index a097058bb..e08829488 100644 --- a/exercises/practice/grade-school/.meta/config.json +++ b/exercises/practice/grade-school/.meta/config.json @@ -31,7 +31,7 @@ "Cargo.toml" ], "test": [ - "tests/grade-school.rs" + "tests/grade_school.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/grade-school/tests/grade-school.rs b/exercises/practice/grade-school/tests/grade_school.rs similarity index 100% rename from exercises/practice/grade-school/tests/grade-school.rs rename to exercises/practice/grade-school/tests/grade_school.rs diff --git a/exercises/practice/hello-world/.meta/config.json b/exercises/practice/hello-world/.meta/config.json index a7701a5a2..157c4f41b 100644 --- a/exercises/practice/hello-world/.meta/config.json +++ b/exercises/practice/hello-world/.meta/config.json @@ -30,7 +30,7 @@ "Cargo.toml" ], "test": [ - "tests/hello-world.rs" + "tests/hello_world.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/hello-world/tests/hello-world.rs b/exercises/practice/hello-world/tests/hello_world.rs similarity index 100% rename from exercises/practice/hello-world/tests/hello-world.rs rename to exercises/practice/hello-world/tests/hello_world.rs diff --git a/exercises/practice/high-scores/.meta/config.json b/exercises/practice/high-scores/.meta/config.json index f36a7c36e..970c70d4e 100644 --- a/exercises/practice/high-scores/.meta/config.json +++ b/exercises/practice/high-scores/.meta/config.json @@ -14,7 +14,7 @@ "Cargo.toml" ], "test": [ - "tests/high-scores.rs" + "tests/high_scores.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/high-scores/tests/high-scores.rs b/exercises/practice/high-scores/tests/high_scores.rs similarity index 100% rename from exercises/practice/high-scores/tests/high-scores.rs rename to exercises/practice/high-scores/tests/high_scores.rs diff --git a/exercises/practice/isbn-verifier/.meta/config.json b/exercises/practice/isbn-verifier/.meta/config.json index 7f3d40f6c..52d9afa03 100644 --- a/exercises/practice/isbn-verifier/.meta/config.json +++ b/exercises/practice/isbn-verifier/.meta/config.json @@ -24,7 +24,7 @@ "Cargo.toml" ], "test": [ - "tests/isbn-verifier.rs" + "tests/isbn_verifier.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/isbn-verifier/tests/isbn-verifier.rs b/exercises/practice/isbn-verifier/tests/isbn_verifier.rs similarity index 100% rename from exercises/practice/isbn-verifier/tests/isbn-verifier.rs rename to exercises/practice/isbn-verifier/tests/isbn_verifier.rs diff --git a/exercises/practice/kindergarten-garden/.meta/config.json b/exercises/practice/kindergarten-garden/.meta/config.json index b1737f26a..7542c928d 100644 --- a/exercises/practice/kindergarten-garden/.meta/config.json +++ b/exercises/practice/kindergarten-garden/.meta/config.json @@ -8,7 +8,7 @@ "Cargo.toml" ], "test": [ - "tests/kindergarten-garden.rs" + "tests/kindergarten_garden.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/kindergarten-garden/tests/kindergarten-garden.rs b/exercises/practice/kindergarten-garden/tests/kindergarten_garden.rs similarity index 100% rename from exercises/practice/kindergarten-garden/tests/kindergarten-garden.rs rename to exercises/practice/kindergarten-garden/tests/kindergarten_garden.rs diff --git a/exercises/practice/largest-series-product/.meta/config.json b/exercises/practice/largest-series-product/.meta/config.json index 9e4b495a8..7dc3f10e5 100644 --- a/exercises/practice/largest-series-product/.meta/config.json +++ b/exercises/practice/largest-series-product/.meta/config.json @@ -23,7 +23,7 @@ "Cargo.toml" ], "test": [ - "tests/largest-series-product.rs" + "tests/largest_series_product.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/largest-series-product/tests/largest-series-product.rs b/exercises/practice/largest-series-product/tests/largest_series_product.rs similarity index 100% rename from exercises/practice/largest-series-product/tests/largest-series-product.rs rename to exercises/practice/largest-series-product/tests/largest_series_product.rs diff --git a/exercises/practice/list-ops/.meta/config.json b/exercises/practice/list-ops/.meta/config.json index dd78f4222..67205db2f 100644 --- a/exercises/practice/list-ops/.meta/config.json +++ b/exercises/practice/list-ops/.meta/config.json @@ -6,7 +6,7 @@ "Cargo.toml" ], "test": [ - "tests/list-ops.rs" + "tests/list_ops.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/list-ops/tests/list-ops.rs b/exercises/practice/list-ops/tests/list_ops.rs similarity index 100% rename from exercises/practice/list-ops/tests/list-ops.rs rename to exercises/practice/list-ops/tests/list_ops.rs diff --git a/exercises/practice/luhn-from/.meta/config.json b/exercises/practice/luhn-from/.meta/config.json index b1e6dff08..ab8da1029 100644 --- a/exercises/practice/luhn-from/.meta/config.json +++ b/exercises/practice/luhn-from/.meta/config.json @@ -23,7 +23,7 @@ "Cargo.toml" ], "test": [ - "tests/luhn-from.rs" + "tests/luhn_from.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/luhn-from/tests/luhn-from.rs b/exercises/practice/luhn-from/tests/luhn_from.rs similarity index 100% rename from exercises/practice/luhn-from/tests/luhn-from.rs rename to exercises/practice/luhn-from/tests/luhn_from.rs diff --git a/exercises/practice/luhn-trait/.meta/config.json b/exercises/practice/luhn-trait/.meta/config.json index a8b53df2e..9d4db11fa 100644 --- a/exercises/practice/luhn-trait/.meta/config.json +++ b/exercises/practice/luhn-trait/.meta/config.json @@ -25,7 +25,7 @@ "Cargo.toml" ], "test": [ - "tests/luhn-trait.rs" + "tests/luhn_trait.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/luhn-trait/tests/luhn-trait.rs b/exercises/practice/luhn-trait/tests/luhn_trait.rs similarity index 100% rename from exercises/practice/luhn-trait/tests/luhn-trait.rs rename to exercises/practice/luhn-trait/tests/luhn_trait.rs diff --git a/exercises/practice/macros/tests/invalid/comma-sep.rs b/exercises/practice/macros/tests/invalid/comma_sep.rs similarity index 100% rename from exercises/practice/macros/tests/invalid/comma-sep.rs rename to exercises/practice/macros/tests/invalid/comma_sep.rs diff --git a/exercises/practice/macros/tests/invalid/double-commas.rs b/exercises/practice/macros/tests/invalid/double_commas.rs similarity index 100% rename from exercises/practice/macros/tests/invalid/double-commas.rs rename to exercises/practice/macros/tests/invalid/double_commas.rs diff --git a/exercises/practice/macros/tests/invalid/leading-comma.rs b/exercises/practice/macros/tests/invalid/leading_comma.rs similarity index 100% rename from exercises/practice/macros/tests/invalid/leading-comma.rs rename to exercises/practice/macros/tests/invalid/leading_comma.rs diff --git a/exercises/practice/macros/tests/invalid/missing-argument.rs b/exercises/practice/macros/tests/invalid/missing_argument.rs similarity index 100% rename from exercises/practice/macros/tests/invalid/missing-argument.rs rename to exercises/practice/macros/tests/invalid/missing_argument.rs diff --git a/exercises/practice/macros/tests/invalid/no-comma.rs b/exercises/practice/macros/tests/invalid/no_comma.rs similarity index 100% rename from exercises/practice/macros/tests/invalid/no-comma.rs rename to exercises/practice/macros/tests/invalid/no_comma.rs diff --git a/exercises/practice/macros/tests/invalid/only-arrow.rs b/exercises/practice/macros/tests/invalid/only_arrow.rs similarity index 100% rename from exercises/practice/macros/tests/invalid/only-arrow.rs rename to exercises/practice/macros/tests/invalid/only_arrow.rs diff --git a/exercises/practice/macros/tests/invalid/only-comma.rs b/exercises/practice/macros/tests/invalid/only_comma.rs similarity index 100% rename from exercises/practice/macros/tests/invalid/only-comma.rs rename to exercises/practice/macros/tests/invalid/only_comma.rs diff --git a/exercises/practice/macros/tests/invalid/single-argument.rs b/exercises/practice/macros/tests/invalid/single_argument.rs similarity index 100% rename from exercises/practice/macros/tests/invalid/single-argument.rs rename to exercises/practice/macros/tests/invalid/single_argument.rs diff --git a/exercises/practice/macros/tests/invalid/triple-arguments.rs b/exercises/practice/macros/tests/invalid/triple_arguments.rs similarity index 100% rename from exercises/practice/macros/tests/invalid/triple-arguments.rs rename to exercises/practice/macros/tests/invalid/triple_arguments.rs diff --git a/exercises/practice/macros/tests/invalid/two-arrows.rs b/exercises/practice/macros/tests/invalid/two_arrows.rs similarity index 100% rename from exercises/practice/macros/tests/invalid/two-arrows.rs rename to exercises/practice/macros/tests/invalid/two_arrows.rs diff --git a/exercises/practice/matching-brackets/.meta/config.json b/exercises/practice/matching-brackets/.meta/config.json index 632203027..5ef329c30 100644 --- a/exercises/practice/matching-brackets/.meta/config.json +++ b/exercises/practice/matching-brackets/.meta/config.json @@ -27,7 +27,7 @@ "Cargo.toml" ], "test": [ - "tests/matching-brackets.rs" + "tests/matching_brackets.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/matching-brackets/tests/matching-brackets.rs b/exercises/practice/matching-brackets/tests/matching_brackets.rs similarity index 100% rename from exercises/practice/matching-brackets/tests/matching-brackets.rs rename to exercises/practice/matching-brackets/tests/matching_brackets.rs diff --git a/exercises/practice/nth-prime/.meta/config.json b/exercises/practice/nth-prime/.meta/config.json index 05d34136d..57d36cef3 100644 --- a/exercises/practice/nth-prime/.meta/config.json +++ b/exercises/practice/nth-prime/.meta/config.json @@ -25,7 +25,7 @@ "Cargo.toml" ], "test": [ - "tests/nth-prime.rs" + "tests/nth_prime.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/nth-prime/tests/nth-prime.rs b/exercises/practice/nth-prime/tests/nth_prime.rs similarity index 100% rename from exercises/practice/nth-prime/tests/nth-prime.rs rename to exercises/practice/nth-prime/tests/nth_prime.rs diff --git a/exercises/practice/nucleotide-codons/.meta/config.json b/exercises/practice/nucleotide-codons/.meta/config.json index 0d5d94be2..ca5356af3 100644 --- a/exercises/practice/nucleotide-codons/.meta/config.json +++ b/exercises/practice/nucleotide-codons/.meta/config.json @@ -28,7 +28,7 @@ "Cargo.toml" ], "test": [ - "tests/nucleotide-codons.rs" + "tests/nucleotide_codons.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/nucleotide-codons/tests/nucleotide-codons.rs b/exercises/practice/nucleotide-codons/tests/nucleotide_codons.rs similarity index 100% rename from exercises/practice/nucleotide-codons/tests/nucleotide-codons.rs rename to exercises/practice/nucleotide-codons/tests/nucleotide_codons.rs diff --git a/exercises/practice/nucleotide-count/.meta/config.json b/exercises/practice/nucleotide-count/.meta/config.json index cf74f9523..bae572c6c 100644 --- a/exercises/practice/nucleotide-count/.meta/config.json +++ b/exercises/practice/nucleotide-count/.meta/config.json @@ -30,7 +30,7 @@ "Cargo.toml" ], "test": [ - "tests/nucleotide-count.rs" + "tests/nucleotide_count.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/nucleotide-count/tests/nucleotide-count.rs b/exercises/practice/nucleotide-count/tests/nucleotide_count.rs similarity index 100% rename from exercises/practice/nucleotide-count/tests/nucleotide-count.rs rename to exercises/practice/nucleotide-count/tests/nucleotide_count.rs diff --git a/exercises/practice/ocr-numbers/.meta/config.json b/exercises/practice/ocr-numbers/.meta/config.json index 239448a21..13569f9d5 100644 --- a/exercises/practice/ocr-numbers/.meta/config.json +++ b/exercises/practice/ocr-numbers/.meta/config.json @@ -25,7 +25,7 @@ "Cargo.toml" ], "test": [ - "tests/ocr-numbers.rs" + "tests/ocr_numbers.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/ocr-numbers/tests/ocr-numbers.rs b/exercises/practice/ocr-numbers/tests/ocr_numbers.rs similarity index 100% rename from exercises/practice/ocr-numbers/tests/ocr-numbers.rs rename to exercises/practice/ocr-numbers/tests/ocr_numbers.rs diff --git a/exercises/practice/palindrome-products/.meta/config.json b/exercises/practice/palindrome-products/.meta/config.json index a82707d20..cb48e3db2 100644 --- a/exercises/practice/palindrome-products/.meta/config.json +++ b/exercises/practice/palindrome-products/.meta/config.json @@ -22,7 +22,7 @@ "Cargo.toml" ], "test": [ - "tests/palindrome-products.rs" + "tests/palindrome_products.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/palindrome-products/tests/palindrome-products.rs b/exercises/practice/palindrome-products/tests/palindrome_products.rs similarity index 100% rename from exercises/practice/palindrome-products/tests/palindrome-products.rs rename to exercises/practice/palindrome-products/tests/palindrome_products.rs diff --git a/exercises/practice/parallel-letter-frequency/.meta/config.json b/exercises/practice/parallel-letter-frequency/.meta/config.json index 8a69050b7..d94e74892 100644 --- a/exercises/practice/parallel-letter-frequency/.meta/config.json +++ b/exercises/practice/parallel-letter-frequency/.meta/config.json @@ -32,7 +32,7 @@ "Cargo.toml" ], "test": [ - "tests/parallel-letter-frequency.rs" + "tests/parallel_letter_frequency.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/parallel-letter-frequency/tests/parallel-letter-frequency.rs b/exercises/practice/parallel-letter-frequency/tests/parallel_letter_frequency.rs similarity index 100% rename from exercises/practice/parallel-letter-frequency/tests/parallel-letter-frequency.rs rename to exercises/practice/parallel-letter-frequency/tests/parallel_letter_frequency.rs diff --git a/exercises/practice/pascals-triangle/.meta/config.json b/exercises/practice/pascals-triangle/.meta/config.json index 750d670aa..2b272e4e7 100644 --- a/exercises/practice/pascals-triangle/.meta/config.json +++ b/exercises/practice/pascals-triangle/.meta/config.json @@ -23,7 +23,7 @@ "Cargo.toml" ], "test": [ - "tests/pascals-triangle.rs" + "tests/pascals_triangle.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/pascals-triangle/tests/pascals-triangle.rs b/exercises/practice/pascals-triangle/tests/pascals_triangle.rs similarity index 100% rename from exercises/practice/pascals-triangle/tests/pascals-triangle.rs rename to exercises/practice/pascals-triangle/tests/pascals_triangle.rs diff --git a/exercises/practice/perfect-numbers/.meta/config.json b/exercises/practice/perfect-numbers/.meta/config.json index b080f1119..c6db2e942 100644 --- a/exercises/practice/perfect-numbers/.meta/config.json +++ b/exercises/practice/perfect-numbers/.meta/config.json @@ -24,7 +24,7 @@ "Cargo.toml" ], "test": [ - "tests/perfect-numbers.rs" + "tests/perfect_numbers.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/perfect-numbers/tests/perfect-numbers.rs b/exercises/practice/perfect-numbers/tests/perfect_numbers.rs similarity index 100% rename from exercises/practice/perfect-numbers/tests/perfect-numbers.rs rename to exercises/practice/perfect-numbers/tests/perfect_numbers.rs diff --git a/exercises/practice/phone-number/.meta/config.json b/exercises/practice/phone-number/.meta/config.json index ce9e04ad6..8bea16007 100644 --- a/exercises/practice/phone-number/.meta/config.json +++ b/exercises/practice/phone-number/.meta/config.json @@ -33,7 +33,7 @@ "Cargo.toml" ], "test": [ - "tests/phone-number.rs" + "tests/phone_number.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/phone-number/tests/phone-number.rs b/exercises/practice/phone-number/tests/phone_number.rs similarity index 100% rename from exercises/practice/phone-number/tests/phone-number.rs rename to exercises/practice/phone-number/tests/phone_number.rs diff --git a/exercises/practice/pig-latin/.meta/config.json b/exercises/practice/pig-latin/.meta/config.json index 14dfe5f40..1d6631e0f 100644 --- a/exercises/practice/pig-latin/.meta/config.json +++ b/exercises/practice/pig-latin/.meta/config.json @@ -24,7 +24,7 @@ "Cargo.toml" ], "test": [ - "tests/pig-latin.rs" + "tests/pig_latin.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/pig-latin/tests/pig-latin.rs b/exercises/practice/pig-latin/tests/pig_latin.rs similarity index 100% rename from exercises/practice/pig-latin/tests/pig-latin.rs rename to exercises/practice/pig-latin/tests/pig_latin.rs diff --git a/exercises/practice/prime-factors/.meta/config.json b/exercises/practice/prime-factors/.meta/config.json index 1d2e5d6c6..d8ab1c7f5 100644 --- a/exercises/practice/prime-factors/.meta/config.json +++ b/exercises/practice/prime-factors/.meta/config.json @@ -24,7 +24,7 @@ "Cargo.toml" ], "test": [ - "tests/prime-factors.rs" + "tests/prime_factors.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/prime-factors/tests/prime-factors.rs b/exercises/practice/prime-factors/tests/prime_factors.rs similarity index 100% rename from exercises/practice/prime-factors/tests/prime-factors.rs rename to exercises/practice/prime-factors/tests/prime_factors.rs diff --git a/exercises/practice/protein-translation/.meta/config.json b/exercises/practice/protein-translation/.meta/config.json index ca68e8386..e89173fd0 100644 --- a/exercises/practice/protein-translation/.meta/config.json +++ b/exercises/practice/protein-translation/.meta/config.json @@ -26,7 +26,7 @@ "Cargo.toml" ], "test": [ - "tests/protein-translation.rs" + "tests/protein_translation.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/protein-translation/tests/protein-translation.rs b/exercises/practice/protein-translation/tests/protein_translation.rs similarity index 100% rename from exercises/practice/protein-translation/tests/protein-translation.rs rename to exercises/practice/protein-translation/tests/protein_translation.rs diff --git a/exercises/practice/pythagorean-triplet/.meta/config.json b/exercises/practice/pythagorean-triplet/.meta/config.json index 61415459f..7dc2b0783 100644 --- a/exercises/practice/pythagorean-triplet/.meta/config.json +++ b/exercises/practice/pythagorean-triplet/.meta/config.json @@ -27,7 +27,7 @@ "Cargo.toml" ], "test": [ - "tests/pythagorean-triplet.rs" + "tests/pythagorean_triplet.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/pythagorean-triplet/tests/pythagorean-triplet.rs b/exercises/practice/pythagorean-triplet/tests/pythagorean_triplet.rs similarity index 100% rename from exercises/practice/pythagorean-triplet/tests/pythagorean-triplet.rs rename to exercises/practice/pythagorean-triplet/tests/pythagorean_triplet.rs diff --git a/exercises/practice/queen-attack/.meta/config.json b/exercises/practice/queen-attack/.meta/config.json index da456c469..23eb7d588 100644 --- a/exercises/practice/queen-attack/.meta/config.json +++ b/exercises/practice/queen-attack/.meta/config.json @@ -25,7 +25,7 @@ "Cargo.toml" ], "test": [ - "tests/queen-attack.rs" + "tests/queen_attack.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/queen-attack/tests/queen-attack.rs b/exercises/practice/queen-attack/tests/queen_attack.rs similarity index 100% rename from exercises/practice/queen-attack/tests/queen-attack.rs rename to exercises/practice/queen-attack/tests/queen_attack.rs diff --git a/exercises/practice/rail-fence-cipher/.meta/config.json b/exercises/practice/rail-fence-cipher/.meta/config.json index 8807bd52e..4a060ab0f 100644 --- a/exercises/practice/rail-fence-cipher/.meta/config.json +++ b/exercises/practice/rail-fence-cipher/.meta/config.json @@ -17,7 +17,7 @@ "Cargo.toml" ], "test": [ - "tests/rail-fence-cipher.rs" + "tests/rail_fence_cipher.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/rail-fence-cipher/tests/rail-fence-cipher.rs b/exercises/practice/rail-fence-cipher/tests/rail_fence_cipher.rs similarity index 100% rename from exercises/practice/rail-fence-cipher/tests/rail-fence-cipher.rs rename to exercises/practice/rail-fence-cipher/tests/rail_fence_cipher.rs diff --git a/exercises/practice/reverse-string/.meta/config.json b/exercises/practice/reverse-string/.meta/config.json index 6fa8c43f1..dc879630e 100644 --- a/exercises/practice/reverse-string/.meta/config.json +++ b/exercises/practice/reverse-string/.meta/config.json @@ -26,7 +26,7 @@ "Cargo.toml" ], "test": [ - "tests/reverse-string.rs" + "tests/reverse_string.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/reverse-string/tests/reverse-string.rs b/exercises/practice/reverse-string/tests/reverse_string.rs similarity index 100% rename from exercises/practice/reverse-string/tests/reverse-string.rs rename to exercises/practice/reverse-string/tests/reverse_string.rs diff --git a/exercises/practice/rna-transcription/.meta/config.json b/exercises/practice/rna-transcription/.meta/config.json index e6d6f6967..b4a6c13bb 100644 --- a/exercises/practice/rna-transcription/.meta/config.json +++ b/exercises/practice/rna-transcription/.meta/config.json @@ -35,7 +35,7 @@ "Cargo.toml" ], "test": [ - "tests/rna-transcription.rs" + "tests/rna_transcription.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/rna-transcription/tests/rna-transcription.rs b/exercises/practice/rna-transcription/tests/rna_transcription.rs similarity index 100% rename from exercises/practice/rna-transcription/tests/rna-transcription.rs rename to exercises/practice/rna-transcription/tests/rna_transcription.rs diff --git a/exercises/practice/robot-name/.meta/config.json b/exercises/practice/robot-name/.meta/config.json index 41a1906b8..c5d2fa26d 100644 --- a/exercises/practice/robot-name/.meta/config.json +++ b/exercises/practice/robot-name/.meta/config.json @@ -34,7 +34,7 @@ "Cargo.toml" ], "test": [ - "tests/robot-name.rs" + "tests/robot_name.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/robot-name/tests/robot-name.rs b/exercises/practice/robot-name/tests/robot_name.rs similarity index 100% rename from exercises/practice/robot-name/tests/robot-name.rs rename to exercises/practice/robot-name/tests/robot_name.rs diff --git a/exercises/practice/robot-simulator/.meta/config.json b/exercises/practice/robot-simulator/.meta/config.json index 4439c09e8..4631702e8 100644 --- a/exercises/practice/robot-simulator/.meta/config.json +++ b/exercises/practice/robot-simulator/.meta/config.json @@ -23,7 +23,7 @@ "Cargo.toml" ], "test": [ - "tests/robot-simulator.rs" + "tests/robot_simulator.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/robot-simulator/tests/robot-simulator.rs b/exercises/practice/robot-simulator/tests/robot_simulator.rs similarity index 100% rename from exercises/practice/robot-simulator/tests/robot-simulator.rs rename to exercises/practice/robot-simulator/tests/robot_simulator.rs diff --git a/exercises/practice/roman-numerals/.meta/config.json b/exercises/practice/roman-numerals/.meta/config.json index b60c299ec..8be87b7bb 100644 --- a/exercises/practice/roman-numerals/.meta/config.json +++ b/exercises/practice/roman-numerals/.meta/config.json @@ -26,7 +26,7 @@ "Cargo.toml" ], "test": [ - "tests/roman-numerals.rs" + "tests/roman_numerals.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/roman-numerals/tests/roman-numerals.rs b/exercises/practice/roman-numerals/tests/roman_numerals.rs similarity index 100% rename from exercises/practice/roman-numerals/tests/roman-numerals.rs rename to exercises/practice/roman-numerals/tests/roman_numerals.rs diff --git a/exercises/practice/rotational-cipher/.meta/config.json b/exercises/practice/rotational-cipher/.meta/config.json index 8a3fa4382..48517941d 100644 --- a/exercises/practice/rotational-cipher/.meta/config.json +++ b/exercises/practice/rotational-cipher/.meta/config.json @@ -24,7 +24,7 @@ "Cargo.toml" ], "test": [ - "tests/rotational-cipher.rs" + "tests/rotational_cipher.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/rotational-cipher/tests/rotational-cipher.rs b/exercises/practice/rotational-cipher/tests/rotational_cipher.rs similarity index 100% rename from exercises/practice/rotational-cipher/tests/rotational-cipher.rs rename to exercises/practice/rotational-cipher/tests/rotational_cipher.rs diff --git a/exercises/practice/run-length-encoding/.meta/config.json b/exercises/practice/run-length-encoding/.meta/config.json index 51fa925e4..50cb9baf2 100644 --- a/exercises/practice/run-length-encoding/.meta/config.json +++ b/exercises/practice/run-length-encoding/.meta/config.json @@ -24,7 +24,7 @@ "Cargo.toml" ], "test": [ - "tests/run-length-encoding.rs" + "tests/run_length_encoding.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/run-length-encoding/tests/run-length-encoding.rs b/exercises/practice/run-length-encoding/tests/run_length_encoding.rs similarity index 100% rename from exercises/practice/run-length-encoding/tests/run-length-encoding.rs rename to exercises/practice/run-length-encoding/tests/run_length_encoding.rs diff --git a/exercises/practice/saddle-points/.meta/config.json b/exercises/practice/saddle-points/.meta/config.json index f9a4c9573..4cfbaf457 100644 --- a/exercises/practice/saddle-points/.meta/config.json +++ b/exercises/practice/saddle-points/.meta/config.json @@ -28,7 +28,7 @@ "Cargo.toml" ], "test": [ - "tests/saddle-points.rs" + "tests/saddle_points.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/saddle-points/tests/saddle-points.rs b/exercises/practice/saddle-points/tests/saddle_points.rs similarity index 100% rename from exercises/practice/saddle-points/tests/saddle-points.rs rename to exercises/practice/saddle-points/tests/saddle_points.rs diff --git a/exercises/practice/scale-generator/.meta/config.json b/exercises/practice/scale-generator/.meta/config.json index ef0c54851..309f3cce7 100644 --- a/exercises/practice/scale-generator/.meta/config.json +++ b/exercises/practice/scale-generator/.meta/config.json @@ -18,7 +18,7 @@ "Cargo.toml" ], "test": [ - "tests/scale-generator.rs" + "tests/scale_generator.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/scale-generator/tests/scale-generator.rs b/exercises/practice/scale-generator/tests/scale_generator.rs similarity index 100% rename from exercises/practice/scale-generator/tests/scale-generator.rs rename to exercises/practice/scale-generator/tests/scale_generator.rs diff --git a/exercises/practice/scrabble-score/.meta/config.json b/exercises/practice/scrabble-score/.meta/config.json index 4050334a9..9ae2fca18 100644 --- a/exercises/practice/scrabble-score/.meta/config.json +++ b/exercises/practice/scrabble-score/.meta/config.json @@ -28,7 +28,7 @@ "Cargo.toml" ], "test": [ - "tests/scrabble-score.rs" + "tests/scrabble_score.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/scrabble-score/tests/scrabble-score.rs b/exercises/practice/scrabble-score/tests/scrabble_score.rs similarity index 100% rename from exercises/practice/scrabble-score/tests/scrabble-score.rs rename to exercises/practice/scrabble-score/tests/scrabble_score.rs diff --git a/exercises/practice/secret-handshake/.meta/config.json b/exercises/practice/secret-handshake/.meta/config.json index 3ff65e858..88501c1fe 100644 --- a/exercises/practice/secret-handshake/.meta/config.json +++ b/exercises/practice/secret-handshake/.meta/config.json @@ -8,7 +8,7 @@ "Cargo.toml" ], "test": [ - "tests/secret-handshake.rs" + "tests/secret_handshake.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/secret-handshake/tests/secret-handshake.rs b/exercises/practice/secret-handshake/tests/secret_handshake.rs similarity index 100% rename from exercises/practice/secret-handshake/tests/secret-handshake.rs rename to exercises/practice/secret-handshake/tests/secret_handshake.rs diff --git a/exercises/practice/simple-cipher/.meta/config.json b/exercises/practice/simple-cipher/.meta/config.json index 2ae68a2d1..0c1b4e3c1 100644 --- a/exercises/practice/simple-cipher/.meta/config.json +++ b/exercises/practice/simple-cipher/.meta/config.json @@ -23,7 +23,7 @@ "Cargo.toml" ], "test": [ - "tests/simple-cipher.rs" + "tests/simple_cipher.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/simple-cipher/tests/simple-cipher.rs b/exercises/practice/simple-cipher/tests/simple_cipher.rs similarity index 100% rename from exercises/practice/simple-cipher/tests/simple-cipher.rs rename to exercises/practice/simple-cipher/tests/simple_cipher.rs diff --git a/exercises/practice/simple-linked-list/.meta/config.json b/exercises/practice/simple-linked-list/.meta/config.json index 9f1cd2d46..ad25f43fc 100644 --- a/exercises/practice/simple-linked-list/.meta/config.json +++ b/exercises/practice/simple-linked-list/.meta/config.json @@ -23,7 +23,7 @@ "Cargo.toml" ], "test": [ - "tests/simple-linked-list.rs" + "tests/simple_linked_list.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/simple-linked-list/tests/simple-linked-list.rs b/exercises/practice/simple-linked-list/tests/simple_linked_list.rs similarity index 100% rename from exercises/practice/simple-linked-list/tests/simple-linked-list.rs rename to exercises/practice/simple-linked-list/tests/simple_linked_list.rs diff --git a/exercises/practice/space-age/.meta/config.json b/exercises/practice/space-age/.meta/config.json index 6ec7a6be2..193c33e3a 100644 --- a/exercises/practice/space-age/.meta/config.json +++ b/exercises/practice/space-age/.meta/config.json @@ -28,7 +28,7 @@ "Cargo.toml" ], "test": [ - "tests/space-age.rs" + "tests/space_age.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/space-age/tests/space-age.rs b/exercises/practice/space-age/tests/space_age.rs similarity index 100% rename from exercises/practice/space-age/tests/space-age.rs rename to exercises/practice/space-age/tests/space_age.rs diff --git a/exercises/practice/spiral-matrix/.meta/config.json b/exercises/practice/spiral-matrix/.meta/config.json index 498e920ab..dd4ce7a40 100644 --- a/exercises/practice/spiral-matrix/.meta/config.json +++ b/exercises/practice/spiral-matrix/.meta/config.json @@ -20,7 +20,7 @@ "Cargo.toml" ], "test": [ - "tests/spiral-matrix.rs" + "tests/spiral_matrix.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/spiral-matrix/tests/spiral-matrix.rs b/exercises/practice/spiral-matrix/tests/spiral_matrix.rs similarity index 100% rename from exercises/practice/spiral-matrix/tests/spiral-matrix.rs rename to exercises/practice/spiral-matrix/tests/spiral_matrix.rs diff --git a/exercises/practice/sum-of-multiples/.meta/config.json b/exercises/practice/sum-of-multiples/.meta/config.json index dcd7e5dbd..c5c6c9ad6 100644 --- a/exercises/practice/sum-of-multiples/.meta/config.json +++ b/exercises/practice/sum-of-multiples/.meta/config.json @@ -27,7 +27,7 @@ "Cargo.toml" ], "test": [ - "tests/sum-of-multiples.rs" + "tests/sum_of_multiples.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/sum-of-multiples/tests/sum-of-multiples.rs b/exercises/practice/sum-of-multiples/tests/sum_of_multiples.rs similarity index 100% rename from exercises/practice/sum-of-multiples/tests/sum-of-multiples.rs rename to exercises/practice/sum-of-multiples/tests/sum_of_multiples.rs diff --git a/exercises/practice/two-bucket/.meta/config.json b/exercises/practice/two-bucket/.meta/config.json index bcc6a5e36..8097c543f 100644 --- a/exercises/practice/two-bucket/.meta/config.json +++ b/exercises/practice/two-bucket/.meta/config.json @@ -22,7 +22,7 @@ "Cargo.toml" ], "test": [ - "tests/two-bucket.rs" + "tests/two_bucket.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/two-bucket/tests/two-bucket.rs b/exercises/practice/two-bucket/tests/two_bucket.rs similarity index 100% rename from exercises/practice/two-bucket/tests/two-bucket.rs rename to exercises/practice/two-bucket/tests/two_bucket.rs diff --git a/exercises/practice/two-fer/.meta/config.json b/exercises/practice/two-fer/.meta/config.json index fb32da3a8..7b8e0d7de 100644 --- a/exercises/practice/two-fer/.meta/config.json +++ b/exercises/practice/two-fer/.meta/config.json @@ -24,7 +24,7 @@ "Cargo.toml" ], "test": [ - "tests/two-fer.rs" + "tests/two_fer.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/two-fer/tests/two-fer.rs b/exercises/practice/two-fer/tests/two_fer.rs similarity index 100% rename from exercises/practice/two-fer/tests/two-fer.rs rename to exercises/practice/two-fer/tests/two_fer.rs diff --git a/exercises/practice/variable-length-quantity/.meta/config.json b/exercises/practice/variable-length-quantity/.meta/config.json index 3051dc7a9..fcaf18202 100644 --- a/exercises/practice/variable-length-quantity/.meta/config.json +++ b/exercises/practice/variable-length-quantity/.meta/config.json @@ -26,7 +26,7 @@ "Cargo.toml" ], "test": [ - "tests/variable-length-quantity.rs" + "tests/variable_length_quantity.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/variable-length-quantity/tests/variable-length-quantity.rs b/exercises/practice/variable-length-quantity/tests/variable_length_quantity.rs similarity index 100% rename from exercises/practice/variable-length-quantity/tests/variable-length-quantity.rs rename to exercises/practice/variable-length-quantity/tests/variable_length_quantity.rs diff --git a/exercises/practice/word-count/.meta/config.json b/exercises/practice/word-count/.meta/config.json index 025f8d001..61b4d1903 100644 --- a/exercises/practice/word-count/.meta/config.json +++ b/exercises/practice/word-count/.meta/config.json @@ -34,7 +34,7 @@ "Cargo.toml" ], "test": [ - "tests/word-count.rs" + "tests/word_count.rs" ], "example": [ ".meta/example.rs" diff --git a/exercises/practice/word-count/tests/word-count.rs b/exercises/practice/word-count/tests/word_count.rs similarity index 100% rename from exercises/practice/word-count/tests/word-count.rs rename to exercises/practice/word-count/tests/word_count.rs diff --git a/rust-tooling/ci-tests/tests/count_ignores.rs b/rust-tooling/ci-tests/tests/count_ignores.rs index 4804effea..f0722c047 100644 --- a/rust-tooling/ci-tests/tests/count_ignores.rs +++ b/rust-tooling/ci-tests/tests/count_ignores.rs @@ -4,7 +4,8 @@ use models::exercise_config::get_all_exercise_paths; fn count_ignores() { for path in get_all_exercise_paths() { let slug = path.split('/').last().unwrap(); - let test_path = format!("{path}/tests/{slug}.rs"); + let snake_slug = slug.replace('-', "_"); + let test_path = format!("{path}/tests/{snake_slug}.rs"); let test_contents = std::fs::read_to_string(test_path).unwrap(); let num_tests = test_contents.matches("#[test]").count(); let num_ignores = test_contents.matches("#[ignore]").count(); diff --git a/rust-tooling/generate/src/main.rs b/rust-tooling/generate/src/main.rs index 9684123b6..7dc98abe4 100644 --- a/rust-tooling/generate/src/main.rs +++ b/rust-tooling/generate/src/main.rs @@ -99,6 +99,8 @@ fn ensure_exercise_files_are_filled(slug: &str, author: Option<&str>) -> Result< let mut config: PracticeExercise = serde_json::from_str(&config).context("failed to deserialize exercise config")?; + let snake_slug = slug.replace('-', "_"); + let ensure_filled = |list: &mut Vec, content: &str| { if !list.iter().any(|s| s == content) { list.push(content.into()) @@ -106,7 +108,7 @@ fn ensure_exercise_files_are_filled(slug: &str, author: Option<&str>) -> Result< }; ensure_filled(&mut config.files.solution, "src/lib.rs"); ensure_filled(&mut config.files.solution, "Cargo.toml"); - ensure_filled(&mut config.files.test, &format!("tests/{slug}.rs")); + ensure_filled(&mut config.files.test, &format!("tests/{snake_slug}.rs")); ensure_filled(&mut config.files.example, ".meta/example.rs"); if let Some(author) = author { @@ -142,10 +144,12 @@ fn generate_exercise_files(slug: &str, is_update: bool) -> Result<()> { std::fs::write(&template_path, exercise.test_template)?; } + let snake_slug = slug.replace('-', "_"); + std::fs::create_dir(exercise_path.join("tests")).ok(); if template_path.exists() { std::fs::write( - exercise_path.join(format!("tests/{slug}.rs")), + exercise_path.join(format!("tests/{snake_slug}.rs")), exercise.tests, )?; } diff --git a/rust-tooling/generate/tests/tera_templates_are_in_sync.rs b/rust-tooling/generate/tests/tera_templates_are_in_sync.rs index d3b5c0f9d..3485a81ed 100644 --- a/rust-tooling/generate/tests/tera_templates_are_in_sync.rs +++ b/rust-tooling/generate/tests/tera_templates_are_in_sync.rs @@ -16,7 +16,8 @@ fn tera_templates_are_in_sync() { let generated = generate::new(&slug); - let test_path = exercise_dir.join("tests").join(format!("{slug}.rs")); + let snake_slug = slug.replace('-', "_"); + let test_path = exercise_dir.join("tests").join(format!("{snake_slug}.rs")); let on_disk = std::fs::read_to_string(test_path).unwrap(); if generated.tests != on_disk { From 3114fb7236e6c078295fe491a12c8a878d0a0d18 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Sat, 22 Feb 2025 23:36:11 +0100 Subject: [PATCH 388/436] activate test tera_templates_are_in_sync in ci (#2037) It wasn't active before, because it was thought to be too slow. Turns out it's not that bad. --------- Co-authored-by: ellnix --- .github/workflows/tests.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d41ba6524..632dcc875 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -86,8 +86,14 @@ jobs: with: toolchain: stable + - name: Fetch configlet + run: ./bin/fetch-configlet + + - name: Checkout problem-specifications + run: ./bin/symlink_problem_specifications.sh + - name: Run tests - run: cd rust-tooling/ci-tests && cargo test + run: cd rust-tooling && cargo test rustformat: name: Check Rust Formatting From 6c526b63b36bec7e35fe12e5d4c62256b08dce43 Mon Sep 17 00:00:00 2001 From: ellnix Date: Mon, 24 Feb 2025 19:18:49 +0100 Subject: [PATCH 389/436] doubly-linked-list: Add blurb (#2040) Noticed this was missing. --- exercises/practice/doubly-linked-list/.meta/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/doubly-linked-list/.meta/config.json b/exercises/practice/doubly-linked-list/.meta/config.json index 4b0db227d..121a0b712 100644 --- a/exercises/practice/doubly-linked-list/.meta/config.json +++ b/exercises/practice/doubly-linked-list/.meta/config.json @@ -22,7 +22,7 @@ ".meta/example.rs" ] }, - "blurb": "linked-list", + "blurb": "Write a more advanced doubly linked list implementation with cursors and iteration.", "custom": { "allowed-to-not-compile": "Stub allowed to not compile because pre_implemented module cannot be resolved by rustfmt in exercism v3 track structure" } From e522e6b02b71514cfbcf6b67255bd7291ff39b82 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 25 Feb 2025 11:16:45 +0100 Subject: [PATCH 390/436] run shellcheck in local test script (#2038) `bin/` is the only directory where we have shell scripts. This will fail if shellcheck is not installed, but even the Ubuntu repos have it, so nobody should have trouble installing it. --- justfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/justfile b/justfile index 6e94bfbca..812ce78cf 100644 --- a/justfile +++ b/justfile @@ -17,7 +17,7 @@ uuid: test: just configlet lint ./bin/lint_markdown.sh - # TODO shellcheck + shellcheck bin/*.sh ./bin/check_exercises.sh CLIPPY=true ./bin/check_exercises.sh cd rust-tooling && cargo test From c8dd24149916fc956825468b83ecc16247cf2f4e Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Wed, 26 Feb 2025 20:20:59 +0100 Subject: [PATCH 391/436] doubly-linked-list: improve leak check (#2043) Previously the check may fail due to an underflow in the subtraction, rather than the assert. --- .../practice/doubly-linked-list/tests/step_4_leak_test_2.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/exercises/practice/doubly-linked-list/tests/step_4_leak_test_2.rs b/exercises/practice/doubly-linked-list/tests/step_4_leak_test_2.rs index 12bbe0848..46687d8b7 100644 --- a/exercises/practice/doubly-linked-list/tests/step_4_leak_test_2.rs +++ b/exercises/practice/doubly-linked-list/tests/step_4_leak_test_2.rs @@ -19,8 +19,7 @@ fn drop_no_leaks() { drop(list); let allocated_after = ALLOCATED.load(SeqCst); - let leaked_bytes = allocated_before - allocated_after; - assert!(leaked_bytes == 0); + assert_eq!(allocated_before, allocated_after); } // Defines a wrapper around the global allocator that counts allocations From 0844bc67ea506fffab45dced6daf8ce1b0e0a224 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Wed, 26 Feb 2025 20:44:03 +0100 Subject: [PATCH 392/436] macros: fix invalid file paths (#2042) This regression was introduced in: 88c13fb21eacd6d4101b98f900e525fe1be3d1ca [no important files changed] Co-authored-by: yjhtry <860622588@qq.com> --- .../practice/macros/tests/invalid/Cargo.toml | 40 +++++++++---------- exercises/practice/macros/tests/macros.rs | 22 +++++----- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/exercises/practice/macros/tests/invalid/Cargo.toml b/exercises/practice/macros/tests/invalid/Cargo.toml index 824de5628..ca30c0bae 100644 --- a/exercises/practice/macros/tests/invalid/Cargo.toml +++ b/exercises/practice/macros/tests/invalid/Cargo.toml @@ -15,41 +15,41 @@ path = "../../" default-features = false [[bin]] -name = "comma-sep-rs" -path = "comma-sep.rs" +name = "comma_sep" +path = "comma_sep.rs" [[bin]] -name = "double-commas-rs" -path = "double-commas.rs" +name = "double_commas" +path = "double_commas.rs" [[bin]] -name = "only-arrow-rs" -path = "only-arrow.rs" +name = "only_arrow" +path = "only_arrow.rs" [[bin]] -name = "only-comma-rs" -path = "only-comma.rs" +name = "only_comma" +path = "only_comma.rs" [[bin]] -name = "single-argument-rs" -path = "single-argument.rs" +name = "single_argument" +path = "single_argument.rs" [[bin]] -name = "triple-arguments-rs" -path = "triple-arguments.rs" +name = "triple_arguments" +path = "triple_arguments.rs" [[bin]] -name = "two-arrows-rs" -path = "two-arrows.rs" +name = "two_arrows" +path = "two_arrows.rs" [[bin]] -name = "leading-comma-rs" -path = "leading-comma.rs" +name = "leading_comma" +path = "leading_comma.rs" [[bin]] -name = "no-comma-rs" -path = "no-comma.rs" +name = "no_comma" +path = "no_comma.rs" [[bin]] -name = "missing-argument-rs" -path = "missing-argument.rs" +name = "missing_argument" +path = "missing_argument.rs" diff --git a/exercises/practice/macros/tests/macros.rs b/exercises/practice/macros/tests/macros.rs index 8e212399a..10c91283a 100644 --- a/exercises/practice/macros/tests/macros.rs +++ b/exercises/practice/macros/tests/macros.rs @@ -117,61 +117,61 @@ fn type_override() { #[test] #[ignore] fn compile_fails_comma_sep() { - simple_trybuild::compile_fail("comma-sep.rs"); + simple_trybuild::compile_fail("comma_sep.rs"); } #[test] #[ignore] fn compile_fails_double_commas() { - simple_trybuild::compile_fail("double-commas.rs"); + simple_trybuild::compile_fail("double_commas.rs"); } #[test] #[ignore] fn compile_fails_only_comma() { - simple_trybuild::compile_fail("only-comma.rs"); + simple_trybuild::compile_fail("only_comma.rs"); } #[test] #[ignore] fn compile_fails_single_argument() { - simple_trybuild::compile_fail("single-argument.rs"); + simple_trybuild::compile_fail("single_argument.rs"); } #[test] #[ignore] fn compile_fails_triple_arguments() { - simple_trybuild::compile_fail("triple-arguments.rs"); + simple_trybuild::compile_fail("triple_arguments.rs"); } #[test] #[ignore] fn compile_fails_only_arrow() { - simple_trybuild::compile_fail("only-arrow.rs"); + simple_trybuild::compile_fail("only_arrow.rs"); } #[test] #[ignore] fn compile_fails_two_arrows() { - simple_trybuild::compile_fail("two-arrows.rs"); + simple_trybuild::compile_fail("two_arrows.rs"); } #[test] #[ignore] fn compile_fails_leading_comma() { - simple_trybuild::compile_fail("leading-comma.rs"); + simple_trybuild::compile_fail("leading_comma.rs"); } #[test] #[ignore] fn compile_fails_no_comma() { - simple_trybuild::compile_fail("no-comma.rs"); + simple_trybuild::compile_fail("no_comma.rs"); } #[test] #[ignore] fn compile_fails_missing_argument() { - simple_trybuild::compile_fail("missing-argument.rs"); + simple_trybuild::compile_fail("missing_argument.rs"); } mod simple_trybuild { @@ -189,7 +189,7 @@ mod simple_trybuild { file_path.into_os_string() ); - let test_name = file_name.replace('.', "-"); + let test_name = file_name.strip_suffix(".rs").unwrap(); let macros_dir = ["..", "..", "target", "tests", "macros"] .iter() .collect::(); From eb69adcefb29ed34f19c62465f62af93fbc7b72b Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Thu, 27 Feb 2025 20:42:25 +0100 Subject: [PATCH 393/436] doubly-linked-list: check compilation in ci (#2044) The reason given for why this exercise was allowed to not compile doesn't seem to apply anymore. --- exercises/practice/doubly-linked-list/.meta/config.json | 5 +---- exercises/practice/doubly-linked-list/Cargo.toml | 4 ++++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/exercises/practice/doubly-linked-list/.meta/config.json b/exercises/practice/doubly-linked-list/.meta/config.json index 121a0b712..c0f7cdea0 100644 --- a/exercises/practice/doubly-linked-list/.meta/config.json +++ b/exercises/practice/doubly-linked-list/.meta/config.json @@ -22,8 +22,5 @@ ".meta/example.rs" ] }, - "blurb": "Write a more advanced doubly linked list implementation with cursors and iteration.", - "custom": { - "allowed-to-not-compile": "Stub allowed to not compile because pre_implemented module cannot be resolved by rustfmt in exercism v3 track structure" - } + "blurb": "Write a more advanced doubly linked list implementation with cursors and iteration." } diff --git a/exercises/practice/doubly-linked-list/Cargo.toml b/exercises/practice/doubly-linked-list/Cargo.toml index 0a620d9a2..4b3ee76bc 100644 --- a/exercises/practice/doubly-linked-list/Cargo.toml +++ b/exercises/practice/doubly-linked-list/Cargo.toml @@ -11,3 +11,7 @@ edition = "2024" [features] # check correct covariance and Send, Sync advanced = [] + +[lints.clippy] +drop_non_drop = "allow" # tests call drop before students have implemented it +new_without_default = "allow" From 7de855192536d7f38541ff41a2e64d3bea24377b Mon Sep 17 00:00:00 2001 From: ellnix Date: Fri, 28 Feb 2025 21:32:12 +0100 Subject: [PATCH 394/436] Sync all docs and metadata (#2045) say: Sync metadata leap: Sync metadata simple-cipher: Sync docs rna-transcription: Sync docs & metadata pythagorean-triplet: Sync docs & metadata protein-translation: Sync docs phone-number: Sync docs pascals-triangle: Sync docs luhn: Sync docs knapsack: Sync docs hamming: Sync docs & metadata grains: Sync docs & metadata grade-school: Sync docs eliuds-eggs: Sync docs dominoes: Sync docs atbash-cipher: Sync docs & metadata anagram: Sync docs sublist: Sync docs affine-cipher: Sync docs collatz-conjecture: Sync docs & metadata --- .../affine-cipher/.docs/instructions.md | 2 +- .../practice/anagram/.docs/instructions.md | 11 ++--- .../atbash-cipher/.docs/instructions.md | 2 +- .../practice/atbash-cipher/.meta/config.json | 2 +- .../collatz-conjecture/.docs/instructions.md | 28 +---------- .../collatz-conjecture/.docs/introduction.md | 28 +++++++++++ .../collatz-conjecture/.meta/config.json | 4 +- .../practice/dominoes/.docs/instructions.md | 4 +- .../practice/dominoes/.docs/introduction.md | 13 +++++ .../eliuds-eggs/.docs/introduction.md | 48 +++++++++++++------ .../grade-school/.docs/instructions.md | 20 ++++---- .../practice/grains/.docs/instructions.md | 14 ++---- .../practice/grains/.docs/introduction.md | 6 +++ exercises/practice/grains/.meta/config.json | 2 +- .../practice/hamming/.docs/instructions.md | 17 ++----- .../practice/hamming/.docs/introduction.md | 12 +++++ exercises/practice/hamming/.meta/config.json | 2 +- .../practice/knapsack/.docs/instructions.md | 8 ++-- .../practice/knapsack/.docs/introduction.md | 12 +++-- exercises/practice/leap/.meta/config.json | 2 +- exercises/practice/luhn/.docs/instructions.md | 8 ++-- exercises/practice/luhn/.docs/introduction.md | 11 +++++ .../pascals-triangle/.docs/introduction.md | 2 +- .../phone-number/.docs/introduction.md | 12 +++++ .../protein-translation/.docs/instructions.md | 8 ++-- .../pythagorean-triplet/.docs/instructions.md | 2 +- .../pythagorean-triplet/.docs/introduction.md | 19 ++++++++ .../pythagorean-triplet/.meta/config.json | 4 +- .../rna-transcription/.docs/instructions.md | 6 +-- .../rna-transcription/.meta/config.json | 2 +- exercises/practice/say/.meta/config.json | 2 +- .../simple-cipher/.docs/instructions.md | 10 ++-- .../practice/sublist/.docs/instructions.md | 4 +- 33 files changed, 203 insertions(+), 124 deletions(-) create mode 100644 exercises/practice/collatz-conjecture/.docs/introduction.md create mode 100644 exercises/practice/dominoes/.docs/introduction.md create mode 100644 exercises/practice/grains/.docs/introduction.md create mode 100644 exercises/practice/hamming/.docs/introduction.md create mode 100644 exercises/practice/luhn/.docs/introduction.md create mode 100644 exercises/practice/phone-number/.docs/introduction.md create mode 100644 exercises/practice/pythagorean-triplet/.docs/introduction.md diff --git a/exercises/practice/affine-cipher/.docs/instructions.md b/exercises/practice/affine-cipher/.docs/instructions.md index 4eff918de..f6329db93 100644 --- a/exercises/practice/affine-cipher/.docs/instructions.md +++ b/exercises/practice/affine-cipher/.docs/instructions.md @@ -4,7 +4,7 @@ Create an implementation of the affine cipher, an ancient encryption system crea The affine cipher is a type of monoalphabetic substitution cipher. Each character is mapped to its numeric equivalent, encrypted with a mathematical function and then converted to the letter relating to its new numeric value. -Although all monoalphabetic ciphers are weak, the affine cipher is much stronger than the atbash cipher, because it has many more keys. +Although all monoalphabetic ciphers are weak, the affine cipher is much stronger than the Atbash cipher, because it has many more keys. [//]: # " monoalphabetic as spelled by Merriam-Webster, compare to polyalphabetic " diff --git a/exercises/practice/anagram/.docs/instructions.md b/exercises/practice/anagram/.docs/instructions.md index a7298485b..dca24f526 100644 --- a/exercises/practice/anagram/.docs/instructions.md +++ b/exercises/practice/anagram/.docs/instructions.md @@ -1,13 +1,12 @@ # Instructions -Your task is to, given a target word and a set of candidate words, to find the subset of the candidates that are anagrams of the target. +Given a target word and one or more candidate words, your task is to find the candidates that are anagrams of the target. An anagram is a rearrangement of letters to form a new word: for example `"owns"` is an anagram of `"snow"`. A word is _not_ its own anagram: for example, `"stop"` is not an anagram of `"stop"`. -The target and candidates are words of one or more ASCII alphabetic characters (`A`-`Z` and `a`-`z`). -Lowercase and uppercase characters are equivalent: for example, `"PoTS"` is an anagram of `"sTOp"`, but `StoP` is not an anagram of `sTOp`. -The anagram set is the subset of the candidate set that are anagrams of the target (in any order). -Words in the anagram set should have the same letter case as in the candidate set. +The target word and candidate words are made up of one or more ASCII alphabetic characters (`A`-`Z` and `a`-`z`). +Lowercase and uppercase characters are equivalent: for example, `"PoTS"` is an anagram of `"sTOp"`, but `"StoP"` is not an anagram of `"sTOp"`. +The words you need to find should be taken from the candidate words, using the same letter case. -Given the target `"stone"` and candidates `"stone"`, `"tones"`, `"banana"`, `"tons"`, `"notes"`, `"Seton"`, the anagram set is `"tones"`, `"notes"`, `"Seton"`. +Given the target `"stone"` and the candidate words `"stone"`, `"tones"`, `"banana"`, `"tons"`, `"notes"`, and `"Seton"`, the anagram words you need to find are `"tones"`, `"notes"`, and `"Seton"`. diff --git a/exercises/practice/atbash-cipher/.docs/instructions.md b/exercises/practice/atbash-cipher/.docs/instructions.md index 21ca2ce0a..1e7627b1e 100644 --- a/exercises/practice/atbash-cipher/.docs/instructions.md +++ b/exercises/practice/atbash-cipher/.docs/instructions.md @@ -1,6 +1,6 @@ # Instructions -Create an implementation of the atbash cipher, an ancient encryption system created in the Middle East. +Create an implementation of the Atbash cipher, an ancient encryption system created in the Middle East. The Atbash cipher is a simple substitution cipher that relies on transposing all the letters in the alphabet such that the resulting alphabet is backwards. The first letter is replaced with the last letter, the second with the second-last, and so on. diff --git a/exercises/practice/atbash-cipher/.meta/config.json b/exercises/practice/atbash-cipher/.meta/config.json index 10a3184ec..fab26b4bd 100644 --- a/exercises/practice/atbash-cipher/.meta/config.json +++ b/exercises/practice/atbash-cipher/.meta/config.json @@ -33,7 +33,7 @@ ".meta/example.rs" ] }, - "blurb": "Create an implementation of the atbash cipher, an ancient encryption system created in the Middle East.", + "blurb": "Create an implementation of the Atbash cipher, an ancient encryption system created in the Middle East.", "source": "Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Atbash" } diff --git a/exercises/practice/collatz-conjecture/.docs/instructions.md b/exercises/practice/collatz-conjecture/.docs/instructions.md index ba060483e..af332a810 100644 --- a/exercises/practice/collatz-conjecture/.docs/instructions.md +++ b/exercises/practice/collatz-conjecture/.docs/instructions.md @@ -1,29 +1,3 @@ # Instructions -The Collatz Conjecture or 3x+1 problem can be summarized as follows: - -Take any positive integer n. -If n is even, divide n by 2 to get n / 2. -If n is odd, multiply n by 3 and add 1 to get 3n + 1. -Repeat the process indefinitely. -The conjecture states that no matter which number you start with, you will always reach 1 eventually. - -Given a number n, return the number of steps required to reach 1. - -## Examples - -Starting with n = 12, the steps would be as follows: - -0. 12 -1. 6 -2. 3 -3. 10 -4. 5 -5. 16 -6. 8 -7. 4 -8. 2 -9. 1 - -Resulting in 9 steps. -So for input n = 12, the return value would be 9. +Given a positive integer, return the number of steps it takes to reach 1 according to the rules of the Collatz Conjecture. diff --git a/exercises/practice/collatz-conjecture/.docs/introduction.md b/exercises/practice/collatz-conjecture/.docs/introduction.md new file mode 100644 index 000000000..c35bdeb67 --- /dev/null +++ b/exercises/practice/collatz-conjecture/.docs/introduction.md @@ -0,0 +1,28 @@ +# Introduction + +One evening, you stumbled upon an old notebook filled with cryptic scribbles, as though someone had been obsessively chasing an idea. +On one page, a single question stood out: **Can every number find its way to 1?** +It was tied to something called the **Collatz Conjecture**, a puzzle that has baffled thinkers for decades. + +The rules were deceptively simple. +Pick any positive integer. + +- If it's even, divide it by 2. +- If it's odd, multiply it by 3 and add 1. + +Then, repeat these steps with the result, continuing indefinitely. + +Curious, you picked number 12 to test and began the journey: + +12 ➜ 6 ➜ 3 ➜ 10 ➜ 5 ➜ 16 ➜ 8 ➜ 4 ➜ 2 ➜ 1 + +Counting from the second number (6), it took 9 steps to reach 1, and each time the rules repeated, the number kept changing. +At first, the sequence seemed unpredictable — jumping up, down, and all over. +Yet, the conjecture claims that no matter the starting number, we'll always end at 1. + +It was fascinating, but also puzzling. +Why does this always seem to work? +Could there be a number where the process breaks down, looping forever or escaping into infinity? +The notebook suggested solving this could reveal something profound — and with it, fame, [fortune][collatz-prize], and a place in history awaits whoever could unlock its secrets. + +[collatz-prize]: https://mathprize.net/posts/collatz-conjecture/ diff --git a/exercises/practice/collatz-conjecture/.meta/config.json b/exercises/practice/collatz-conjecture/.meta/config.json index 245457b6c..6972ca734 100644 --- a/exercises/practice/collatz-conjecture/.meta/config.json +++ b/exercises/practice/collatz-conjecture/.meta/config.json @@ -29,6 +29,6 @@ ] }, "blurb": "Calculate the number of steps to reach 1 using the Collatz conjecture.", - "source": "An unsolved problem in mathematics named after mathematician Lothar Collatz", - "source_url": "https://en.wikipedia.org/wiki/3x_%2B_1_problem" + "source": "Wikipedia", + "source_url": "https://en.wikipedia.org/wiki/Collatz_conjecture" } diff --git a/exercises/practice/dominoes/.docs/instructions.md b/exercises/practice/dominoes/.docs/instructions.md index 1ced9f644..75055b9e8 100644 --- a/exercises/practice/dominoes/.docs/instructions.md +++ b/exercises/practice/dominoes/.docs/instructions.md @@ -2,7 +2,9 @@ Make a chain of dominoes. -Compute a way to order a given set of dominoes in such a way that they form a correct domino chain (the dots on one half of a stone match the dots on the neighboring half of an adjacent stone) and that dots on the halves of the stones which don't have a neighbor (the first and last stone) match each other. +Compute a way to order a given set of domino stones so that they form a correct domino chain. +In the chain, the dots on one half of a stone must match the dots on the neighboring half of an adjacent stone. +Additionally, the dots on the halves of the stones without neighbors (the first and last stone) must match each other. For example given the stones `[2|1]`, `[2|3]` and `[1|3]` you should compute something like `[1|2] [2|3] [3|1]` or `[3|2] [2|1] [1|3]` or `[1|3] [3|2] [2|1]` etc, where the first and last numbers are the same. diff --git a/exercises/practice/dominoes/.docs/introduction.md b/exercises/practice/dominoes/.docs/introduction.md new file mode 100644 index 000000000..df248c211 --- /dev/null +++ b/exercises/practice/dominoes/.docs/introduction.md @@ -0,0 +1,13 @@ +# Introduction + +In Toyland, the trains are always busy delivering treasures across the city, from shiny marbles to rare building blocks. +The tracks they run on are made of colorful domino-shaped pieces, each marked with two numbers. +For the trains to move, the dominoes must form a perfect chain where the numbers match. + +Today, an urgent delivery of rare toys is on hold. +You've been handed a set of track pieces to inspect. +If they can form a continuous chain, the train will be on its way, bringing smiles across Toyland. +If not, the set will be discarded, and another will be tried. + +The toys are counting on you to solve this puzzle. +Will the dominoes connect the tracks and send the train rolling, or will the set be left behind? diff --git a/exercises/practice/eliuds-eggs/.docs/introduction.md b/exercises/practice/eliuds-eggs/.docs/introduction.md index 49eaffd8b..819897480 100644 --- a/exercises/practice/eliuds-eggs/.docs/introduction.md +++ b/exercises/practice/eliuds-eggs/.docs/introduction.md @@ -12,36 +12,54 @@ The position information encoding is calculated as follows: 2. Convert the number from binary to decimal. 3. Show the result on the display. -Example 1: +## Example 1 + +![Seven individual nest boxes arranged in a row whose first, third, fourth and seventh nests each have a single egg.](https://assets.exercism.org/images/exercises/eliuds-eggs/example-1-coop.svg) ```text -Chicken Coop: _ _ _ _ _ _ _ |E| |E|E| | |E| +``` + +### Resulting Binary + +![1011001](https://assets.exercism.org/images/exercises/eliuds-eggs/example-1-binary.svg) + +```text + _ _ _ _ _ _ _ +|1|0|1|1|0|0|1| +``` -Resulting Binary: - 1 0 1 1 0 0 1 +### Decimal number on the display -Decimal number on the display: 89 -Actual eggs in the coop: +### Actual eggs in the coop + 4 + +## Example 2 + +![Seven individual nest boxes arranged in a row where only the fourth nest has an egg.](https://assets.exercism.org/images/exercises/eliuds-eggs/example-2-coop.svg) + +```text + _ _ _ _ _ _ _ +| | | |E| | | | ``` -Example 2: +### Resulting Binary + +![0001000](https://assets.exercism.org/images/exercises/eliuds-eggs/example-2-binary.svg) ```text -Chicken Coop: - _ _ _ _ _ _ _ _ -| | | |E| | | | | + _ _ _ _ _ _ _ +|0|0|0|1|0|0|0| +``` -Resulting Binary: - 0 0 0 1 0 0 0 0 +### Decimal number on the display -Decimal number on the display: 16 -Actual eggs in the coop: +### Actual eggs in the coop + 1 -``` diff --git a/exercises/practice/grade-school/.docs/instructions.md b/exercises/practice/grade-school/.docs/instructions.md index 9a63e398d..3cb1b5d5f 100644 --- a/exercises/practice/grade-school/.docs/instructions.md +++ b/exercises/practice/grade-school/.docs/instructions.md @@ -1,21 +1,21 @@ # Instructions -Given students' names along with the grade that they are in, create a roster for the school. +Given students' names along with the grade they are in, create a roster for the school. In the end, you should be able to: -- Add a student's name to the roster for a grade +- Add a student's name to the roster for a grade: - "Add Jim to grade 2." - "OK." -- Get a list of all students enrolled in a grade +- Get a list of all students enrolled in a grade: - "Which students are in grade 2?" - - "We've only got Jim just now." + - "We've only got Jim right now." - Get a sorted list of all students in all grades. - Grades should sort as 1, 2, 3, etc., and students within a grade should be sorted alphabetically by name. - - "Who all is enrolled in school right now?" + Grades should be sorted as 1, 2, 3, etc., and students within a grade should be sorted alphabetically by name. + - "Who is enrolled in school right now?" - "Let me think. - We have Anna, Barb, and Charlie in grade 1, Alex, Peter, and Zoe in grade 2 and Jim in grade 5. - So the answer is: Anna, Barb, Charlie, Alex, Peter, Zoe and Jim" + We have Anna, Barb, and Charlie in grade 1, Alex, Peter, and Zoe in grade 2, and Jim in grade 5. + So the answer is: Anna, Barb, Charlie, Alex, Peter, Zoe, and Jim." -Note that all our students only have one name (It's a small town, what do you want?) and each student cannot be added more than once to a grade or the roster. -In fact, when a test attempts to add the same student more than once, your implementation should indicate that this is incorrect. +Note that all our students only have one name (it's a small town, what do you want?), and each student cannot be added more than once to a grade or the roster. +If a test attempts to add the same student more than once, your implementation should indicate that this is incorrect. diff --git a/exercises/practice/grains/.docs/instructions.md b/exercises/practice/grains/.docs/instructions.md index df479fc0a..f5b752a81 100644 --- a/exercises/practice/grains/.docs/instructions.md +++ b/exercises/practice/grains/.docs/instructions.md @@ -1,15 +1,11 @@ # Instructions -Calculate the number of grains of wheat on a chessboard given that the number on each square doubles. +Calculate the number of grains of wheat on a chessboard. -There once was a wise servant who saved the life of a prince. -The king promised to pay whatever the servant could dream up. -Knowing that the king loved chess, the servant told the king he would like to have grains of wheat. -One grain on the first square of a chess board, with the number of grains doubling on each successive square. +A chessboard has 64 squares. +Square 1 has one grain, square 2 has two grains, square 3 has four grains, and so on, doubling each time. -There are 64 squares on a chessboard (where square 1 has one grain, square 2 has two grains, and so on). +Write code that calculates: -Write code that shows: - -- how many grains were on a given square, and +- the number of grains on a given square - the total number of grains on the chessboard diff --git a/exercises/practice/grains/.docs/introduction.md b/exercises/practice/grains/.docs/introduction.md new file mode 100644 index 000000000..0df4f46f7 --- /dev/null +++ b/exercises/practice/grains/.docs/introduction.md @@ -0,0 +1,6 @@ +# Introduction + +There once was a wise servant who saved the life of a prince. +The king promised to pay whatever the servant could dream up. +Knowing that the king loved chess, the servant told the king he would like to have grains of wheat. +One grain on the first square of a chessboard, with the number of grains doubling on each successive square. diff --git a/exercises/practice/grains/.meta/config.json b/exercises/practice/grains/.meta/config.json index 21f042d6d..1488506a3 100644 --- a/exercises/practice/grains/.meta/config.json +++ b/exercises/practice/grains/.meta/config.json @@ -33,5 +33,5 @@ }, "blurb": "Calculate the number of grains of wheat on a chessboard given that the number on each square doubles.", "source": "The CodeRanch Cattle Drive, Assignment 6", - "source_url": "https://coderanch.com/wiki/718824/Grains" + "source_url": "https://web.archive.org/web/20240908084142/https://coderanch.com/wiki/718824/Grains" } diff --git a/exercises/practice/hamming/.docs/instructions.md b/exercises/practice/hamming/.docs/instructions.md index 020fdd02d..8f47a179e 100644 --- a/exercises/practice/hamming/.docs/instructions.md +++ b/exercises/practice/hamming/.docs/instructions.md @@ -1,26 +1,15 @@ # Instructions -Calculate the Hamming Distance between two DNA strands. +Calculate the Hamming distance between two DNA strands. -Your body is made up of cells that contain DNA. -Those cells regularly wear out and need replacing, which they achieve by dividing into daughter cells. -In fact, the average human body experiences about 10 quadrillion cell divisions in a lifetime! - -When cells divide, their DNA replicates too. -Sometimes during this process mistakes happen and single pieces of DNA get encoded with the incorrect information. -If we compare two strands of DNA and count the differences between them we can see how many mistakes occurred. -This is known as the "Hamming Distance". - -We read DNA using the letters C,A,G and T. +We read DNA using the letters C, A, G and T. Two strands might look like this: GAGCCTACTAACGGGAT CATCGTAATGACGGCCT ^ ^ ^ ^ ^ ^^ -They have 7 differences, and therefore the Hamming Distance is 7. - -The Hamming Distance is useful for lots of things in science, not just biology, so it's a nice phrase to be familiar with :) +They have 7 differences, and therefore the Hamming distance is 7. ## Implementation notes diff --git a/exercises/practice/hamming/.docs/introduction.md b/exercises/practice/hamming/.docs/introduction.md new file mode 100644 index 000000000..8419bf479 --- /dev/null +++ b/exercises/practice/hamming/.docs/introduction.md @@ -0,0 +1,12 @@ +# Introduction + +Your body is made up of cells that contain DNA. +Those cells regularly wear out and need replacing, which they achieve by dividing into daughter cells. +In fact, the average human body experiences about 10 quadrillion cell divisions in a lifetime! + +When cells divide, their DNA replicates too. +Sometimes during this process mistakes happen and single pieces of DNA get encoded with the incorrect information. +If we compare two strands of DNA and count the differences between them, we can see how many mistakes occurred. +This is known as the "Hamming distance". + +The Hamming distance is useful in many areas of science, not just biology, so it's a nice phrase to be familiar with :) diff --git a/exercises/practice/hamming/.meta/config.json b/exercises/practice/hamming/.meta/config.json index 4d90d1d72..4d4f72e47 100644 --- a/exercises/practice/hamming/.meta/config.json +++ b/exercises/practice/hamming/.meta/config.json @@ -35,7 +35,7 @@ ".meta/example.rs" ] }, - "blurb": "Calculate the Hamming difference between two DNA strands.", + "blurb": "Calculate the Hamming distance between two DNA strands.", "source": "The Calculating Point Mutations problem at Rosalind", "source_url": "https://rosalind.info/problems/hamm/" } diff --git a/exercises/practice/knapsack/.docs/instructions.md b/exercises/practice/knapsack/.docs/instructions.md index 3411db988..0ebf7914c 100644 --- a/exercises/practice/knapsack/.docs/instructions.md +++ b/exercises/practice/knapsack/.docs/instructions.md @@ -1,11 +1,11 @@ # Instructions -Your task is to determine which items to take so that the total value of his selection is maximized, taking into account the knapsack's carrying capacity. +Your task is to determine which items to take so that the total value of her selection is maximized, taking into account the knapsack's carrying capacity. Items will be represented as a list of items. Each item will have a weight and value. All values given will be strictly positive. -Bob can take only one of each item. +Lhakpa can take only one of each item. For example: @@ -21,5 +21,5 @@ Knapsack Maximum Weight: 10 ``` For the above, the first item has weight 5 and value 10, the second item has weight 4 and value 40, and so on. -In this example, Bob should take the second and fourth item to maximize his value, which, in this case, is 90. -He cannot get more than 90 as his knapsack has a weight limit of 10. +In this example, Lhakpa should take the second and fourth item to maximize her value, which, in this case, is 90. +She cannot get more than 90 as her knapsack has a weight limit of 10. diff --git a/exercises/practice/knapsack/.docs/introduction.md b/exercises/practice/knapsack/.docs/introduction.md index 9b2bed8b4..9ac9df596 100644 --- a/exercises/practice/knapsack/.docs/introduction.md +++ b/exercises/practice/knapsack/.docs/introduction.md @@ -1,8 +1,10 @@ # Introduction -Bob is a thief. -After months of careful planning, he finally manages to crack the security systems of a fancy store. +Lhakpa is a [Sherpa][sherpa] mountain guide and porter. +After months of careful planning, the expedition Lhakpa works for is about to leave. +She will be paid the value she carried to the base camp. -In front of him are many items, each with a value and weight. -Bob would gladly take all of the items, but his knapsack can only hold so much weight. -Bob has to carefully consider which items to take so that the total value of his selection is maximized. +In front of her are many items, each with a value and weight. +Lhakpa would gladly take all of the items, but her knapsack can only hold so much weight. + +[sherpa]: https://en.wikipedia.org/wiki/Sherpa_people#Mountaineering diff --git a/exercises/practice/leap/.meta/config.json b/exercises/practice/leap/.meta/config.json index d02f64ba1..95d0448e8 100644 --- a/exercises/practice/leap/.meta/config.json +++ b/exercises/practice/leap/.meta/config.json @@ -46,5 +46,5 @@ }, "blurb": "Determine whether a given year is a leap year.", "source": "CodeRanch Cattle Drive, Assignment 3", - "source_url": "https://coderanch.com/t/718816/Leap" + "source_url": "https://web.archive.org/web/20240907033714/https://coderanch.com/t/718816/Leap" } diff --git a/exercises/practice/luhn/.docs/instructions.md b/exercises/practice/luhn/.docs/instructions.md index 49934c106..5bbf007b0 100644 --- a/exercises/practice/luhn/.docs/instructions.md +++ b/exercises/practice/luhn/.docs/instructions.md @@ -1,12 +1,10 @@ # Instructions -Given a number determine whether or not it is valid per the Luhn formula. +Determine whether a credit card number is valid according to the [Luhn formula][luhn]. -The [Luhn algorithm][luhn] is a simple checksum formula used to validate a variety of identification numbers, such as credit card numbers and Canadian Social Insurance Numbers. +The number will be provided as a string. -The task is to check if a given string is valid. - -## Validating a Number +## Validating a number Strings of length 1 or less are not valid. Spaces are allowed in the input, but they should be stripped before checking. diff --git a/exercises/practice/luhn/.docs/introduction.md b/exercises/practice/luhn/.docs/introduction.md new file mode 100644 index 000000000..ec2bd709d --- /dev/null +++ b/exercises/practice/luhn/.docs/introduction.md @@ -0,0 +1,11 @@ +# Introduction + +At the Global Verification Authority, you've just been entrusted with a critical assignment. +Across the city, from online purchases to secure logins, countless operations rely on the accuracy of numerical identifiers like credit card numbers, bank account numbers, transaction codes, and tracking IDs. +The Luhn algorithm is a simple checksum formula used to ensure these numbers are valid and error-free. + +A batch of identifiers has just arrived on your desk. +All of them must pass the Luhn test to ensure they're legitimate. +If any fail, they'll be flagged as invalid, preventing errors or fraud, such as incorrect transactions or unauthorized access. + +Can you ensure this is done right? The integrity of many services depends on you. diff --git a/exercises/practice/pascals-triangle/.docs/introduction.md b/exercises/practice/pascals-triangle/.docs/introduction.md index 60b8ec30d..eab454e5a 100644 --- a/exercises/practice/pascals-triangle/.docs/introduction.md +++ b/exercises/practice/pascals-triangle/.docs/introduction.md @@ -13,7 +13,7 @@ Over the next hour, your teacher reveals some amazing things hidden in this tria - It contains the Fibonacci sequence. - If you color odd and even numbers differently, you get a beautiful pattern called the [Sierpiński triangle][wikipedia-sierpinski-triangle]. -The teacher implores you and your classmates to lookup other uses, and assures you that there are lots more! +The teacher implores you and your classmates to look up other uses, and assures you that there are lots more! At that moment, the school bell rings. You realize that for the past hour, you were completely absorbed in learning about Pascal's triangle. You quickly grab your laptop from your bag and go outside, ready to enjoy both the sunshine _and_ the wonders of Pascal's triangle. diff --git a/exercises/practice/phone-number/.docs/introduction.md b/exercises/practice/phone-number/.docs/introduction.md new file mode 100644 index 000000000..c4142c5af --- /dev/null +++ b/exercises/practice/phone-number/.docs/introduction.md @@ -0,0 +1,12 @@ +# Introduction + +You've joined LinkLine, a leading communications company working to ensure reliable connections for everyone. +The team faces a big challenge: users submit phone numbers in all sorts of formats — dashes, spaces, dots, parentheses, and even prefixes. +Some numbers are valid, while others are impossible to use. + +Your mission is to turn this chaos into order. +You'll clean up valid numbers, formatting them appropriately for use in the system. +At the same time, you'll identify and filter out any invalid entries. + +The success of LinkLine's operations depends on your ability to separate the useful from the unusable. +Are you ready to take on the challenge and keep the connections running smoothly? diff --git a/exercises/practice/protein-translation/.docs/instructions.md b/exercises/practice/protein-translation/.docs/instructions.md index 7dc34d2ed..44880802c 100644 --- a/exercises/practice/protein-translation/.docs/instructions.md +++ b/exercises/practice/protein-translation/.docs/instructions.md @@ -2,12 +2,12 @@ Translate RNA sequences into proteins. -RNA can be broken into three nucleotide sequences called codons, and then translated to a polypeptide like so: +RNA can be broken into three-nucleotide sequences called codons, and then translated to a protein like so: RNA: `"AUGUUUUCU"` => translates to Codons: `"AUG", "UUU", "UCU"` -=> which become a polypeptide with the following sequence => +=> which become a protein with the following sequence => Protein: `"Methionine", "Phenylalanine", "Serine"` @@ -27,9 +27,9 @@ Protein: `"Methionine", "Phenylalanine", "Serine"` Note the stop codon `"UAA"` terminates the translation and the final methionine is not translated into the protein sequence. -Below are the codons and resulting Amino Acids needed for the exercise. +Below are the codons and resulting amino acids needed for the exercise. -| Codon | Protein | +| Codon | Amino Acid | | :----------------- | :------------ | | AUG | Methionine | | UUU, UUC | Phenylalanine | diff --git a/exercises/practice/pythagorean-triplet/.docs/instructions.md b/exercises/practice/pythagorean-triplet/.docs/instructions.md index 1c1a8aea6..ced833d7a 100644 --- a/exercises/practice/pythagorean-triplet/.docs/instructions.md +++ b/exercises/practice/pythagorean-triplet/.docs/instructions.md @@ -1,4 +1,4 @@ -# Instructions +# Description A Pythagorean triplet is a set of three natural numbers, {a, b, c}, for which, diff --git a/exercises/practice/pythagorean-triplet/.docs/introduction.md b/exercises/practice/pythagorean-triplet/.docs/introduction.md new file mode 100644 index 000000000..3453c6ed4 --- /dev/null +++ b/exercises/practice/pythagorean-triplet/.docs/introduction.md @@ -0,0 +1,19 @@ +# Introduction + +You are an accomplished problem-solver, known for your ability to tackle the most challenging mathematical puzzles. +One evening, you receive an urgent letter from an inventor called the Triangle Tinkerer, who is working on a groundbreaking new project. +The letter reads: + +> Dear Mathematician, +> +> I need your help. +> I am designing a device that relies on the unique properties of Pythagorean triplets — sets of three integers that satisfy the equation a² + b² = c². +> This device will revolutionize navigation, but for it to work, I must program it with every possible triplet where the sum of a, b, and c equals a specific number, N. +> Calculating these triplets by hand would take me years, but I hear you are more than up to the task. +> +> Time is of the essence. +> The future of my invention — and perhaps even the future of mathematical innovation — rests on your ability to solve this problem. + +Motivated by the importance of the task, you set out to find all Pythagorean triplets that satisfy the condition. +Your work could have far-reaching implications, unlocking new possibilities in science and engineering. +Can you rise to the challenge and make history? diff --git a/exercises/practice/pythagorean-triplet/.meta/config.json b/exercises/practice/pythagorean-triplet/.meta/config.json index 7dc2b0783..d73e02617 100644 --- a/exercises/practice/pythagorean-triplet/.meta/config.json +++ b/exercises/practice/pythagorean-triplet/.meta/config.json @@ -33,7 +33,7 @@ ".meta/example.rs" ] }, - "blurb": "There exists exactly one Pythagorean triplet for which a + b + c = 1000. Find the triplet.", - "source": "Problem 9 at Project Euler", + "blurb": "Given an integer N, find all Pythagorean triplets for which a + b + c = N.", + "source": "A variation of Problem 9 from Project Euler", "source_url": "https://projecteuler.net/problem=9" } diff --git a/exercises/practice/rna-transcription/.docs/instructions.md b/exercises/practice/rna-transcription/.docs/instructions.md index 36da381f5..4dbfd3a27 100644 --- a/exercises/practice/rna-transcription/.docs/instructions.md +++ b/exercises/practice/rna-transcription/.docs/instructions.md @@ -1,12 +1,12 @@ # Instructions -Your task is determine the RNA complement of a given DNA sequence. +Your task is to determine the RNA complement of a given DNA sequence. Both DNA and RNA strands are a sequence of nucleotides. -The four nucleotides found in DNA are adenine (**A**), cytosine (**C**), guanine (**G**) and thymine (**T**). +The four nucleotides found in DNA are adenine (**A**), cytosine (**C**), guanine (**G**), and thymine (**T**). -The four nucleotides found in RNA are adenine (**A**), cytosine (**C**), guanine (**G**) and uracil (**U**). +The four nucleotides found in RNA are adenine (**A**), cytosine (**C**), guanine (**G**), and uracil (**U**). Given a DNA strand, its transcribed RNA strand is formed by replacing each nucleotide with its complement: diff --git a/exercises/practice/rna-transcription/.meta/config.json b/exercises/practice/rna-transcription/.meta/config.json index b4a6c13bb..f22ab44cc 100644 --- a/exercises/practice/rna-transcription/.meta/config.json +++ b/exercises/practice/rna-transcription/.meta/config.json @@ -41,7 +41,7 @@ ".meta/example.rs" ] }, - "blurb": "Given a DNA strand, return its RNA Complement Transcription.", + "blurb": "Given a DNA strand, return its RNA complement.", "source": "Hyperphysics", "source_url": "https://web.archive.org/web/20220408112140/http://hyperphysics.phy-astr.gsu.edu/hbase/Organic/transcription.html" } diff --git a/exercises/practice/say/.meta/config.json b/exercises/practice/say/.meta/config.json index 195df14da..28df68200 100644 --- a/exercises/practice/say/.meta/config.json +++ b/exercises/practice/say/.meta/config.json @@ -34,5 +34,5 @@ }, "blurb": "Given a number from 0 to 999,999,999,999, spell out that number in English.", "source": "A variation on the JavaRanch CattleDrive, Assignment 4", - "source_url": "https://coderanch.com/wiki/718804" + "source_url": "https://web.archive.org/web/20240907035912/https://coderanch.com/wiki/718804" } diff --git a/exercises/practice/simple-cipher/.docs/instructions.md b/exercises/practice/simple-cipher/.docs/instructions.md index 475af6182..337857442 100644 --- a/exercises/practice/simple-cipher/.docs/instructions.md +++ b/exercises/practice/simple-cipher/.docs/instructions.md @@ -11,14 +11,14 @@ If anyone wishes to decipher these, and get at their meaning, he must substitute Ciphers are very straight-forward algorithms that allow us to render text less readable while still allowing easy deciphering. They are vulnerable to many forms of cryptanalysis, but Caesar was lucky that his enemies were not cryptanalysts. -The Caesar Cipher was used for some messages from Julius Caesar that were sent afield. +The Caesar cipher was used for some messages from Julius Caesar that were sent afield. Now Caesar knew that the cipher wasn't very good, but he had one ally in that respect: almost nobody could read well. So even being a couple letters off was sufficient so that people couldn't recognize the few words that they did know. -Your task is to create a simple shift cipher like the Caesar Cipher. -This image is a great example of the Caesar Cipher: +Your task is to create a simple shift cipher like the Caesar cipher. +This image is a great example of the Caesar cipher: -![Caesar Cipher][img-caesar-cipher] +![Caesar cipher][img-caesar-cipher] For example: @@ -44,7 +44,7 @@ would return the obscured "ldpdsdqgdehdu" In the example above, we've set a = 0 for the key value. So when the plaintext is added to the key, we end up with the same message coming out. So "aaaa" is not an ideal key. -But if we set the key to "dddd", we would get the same thing as the Caesar Cipher. +But if we set the key to "dddd", we would get the same thing as the Caesar cipher. ## Step 3 diff --git a/exercises/practice/sublist/.docs/instructions.md b/exercises/practice/sublist/.docs/instructions.md index 7535931af..8228edc6c 100644 --- a/exercises/practice/sublist/.docs/instructions.md +++ b/exercises/practice/sublist/.docs/instructions.md @@ -8,8 +8,8 @@ Given any two lists `A` and `B`, determine if: - None of the above is true, thus lists `A` and `B` are unequal Specifically, list `A` is equal to list `B` if both lists have the same values in the same order. -List `A` is a superlist of `B` if `A` contains a sub-sequence of values equal to `B`. -List `A` is a sublist of `B` if `B` contains a sub-sequence of values equal to `A`. +List `A` is a superlist of `B` if `A` contains a contiguous sub-sequence of values equal to `B`. +List `A` is a sublist of `B` if `B` contains a contiguous sub-sequence of values equal to `A`. Examples: From 66cfb28442bffb33823b1dc5743e9b97afd54ec8 Mon Sep 17 00:00:00 2001 From: ellnix Date: Sat, 1 Mar 2025 22:22:19 +0100 Subject: [PATCH 395/436] matching-brackets: Sync tests and add test template (#2046) Some shenanigans was going on where tests were missing in `tests.toml` but were actually implemented in the test file. With this everything is consistent. Specifically fn early_incomplete_brackets() {} fn early_mismatched_brackets() {} --- .../.meta/test_template.tera | 12 ++++++++++ .../matching-brackets/.meta/tests.toml | 22 ++++++++++++++++--- .../tests/matching_brackets.rs | 14 ++++++++---- 3 files changed, 41 insertions(+), 7 deletions(-) create mode 100644 exercises/practice/matching-brackets/.meta/test_template.tera diff --git a/exercises/practice/matching-brackets/.meta/test_template.tera b/exercises/practice/matching-brackets/.meta/test_template.tera new file mode 100644 index 000000000..25bc9bb9b --- /dev/null +++ b/exercises/practice/matching-brackets/.meta/test_template.tera @@ -0,0 +1,12 @@ +use matching_brackets::brackets_are_balanced; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + assert!( + {% if test.expected == false %}!{% endif %} + brackets_are_balanced("{{ test.input.value | replace(from="\", to="\\") }}") + ); +} +{% endfor -%} diff --git a/exercises/practice/matching-brackets/.meta/tests.toml b/exercises/practice/matching-brackets/.meta/tests.toml index cc9e471a4..35a98a042 100644 --- a/exercises/practice/matching-brackets/.meta/tests.toml +++ b/exercises/practice/matching-brackets/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [81ec11da-38dd-442a-bcf9-3de7754609a5] description = "paired square brackets" @@ -41,12 +48,21 @@ description = "unpaired and nested brackets" [a0205e34-c2ac-49e6-a88a-899508d7d68e] description = "paired and wrong nested brackets" +[1d5c093f-fc84-41fb-8c2a-e052f9581602] +description = "paired and wrong nested brackets but innermost are correct" + [ef47c21b-bcfd-4998-844c-7ad5daad90a8] description = "paired and incomplete brackets" [a4675a40-a8be-4fc2-bc47-2a282ce6edbe] description = "too many closing brackets" +[a345a753-d889-4b7e-99ae-34ac85910d1a] +description = "early unexpected brackets" + +[21f81d61-1608-465a-b850-baa44c5def83] +description = "early mismatched brackets" + [99255f93-261b-4435-a352-02bdecc9bdf2] description = "math expression" diff --git a/exercises/practice/matching-brackets/tests/matching_brackets.rs b/exercises/practice/matching-brackets/tests/matching_brackets.rs index 6771e9572..4e95c0c6d 100644 --- a/exercises/practice/matching-brackets/tests/matching_brackets.rs +++ b/exercises/practice/matching-brackets/tests/matching_brackets.rs @@ -77,6 +77,12 @@ fn paired_and_wrong_nested_brackets() { assert!(!brackets_are_balanced("[({]})")); } +#[test] +#[ignore] +fn paired_and_wrong_nested_brackets_but_innermost_are_correct() { + assert!(!brackets_are_balanced("[({}])")); +} + #[test] #[ignore] fn paired_and_incomplete_brackets() { @@ -91,7 +97,7 @@ fn too_many_closing_brackets() { #[test] #[ignore] -fn early_incomplete_brackets() { +fn early_unexpected_brackets() { assert!(!brackets_are_balanced(")()")); } @@ -110,7 +116,7 @@ fn math_expression() { #[test] #[ignore] fn complex_latex_expression() { - let input = "\\left(\\begin{array}{cc} \\frac{1}{3} & x\\\\ \\mathrm{e}^{x} &... x^2 \ - \\end{array}\\right)"; - assert!(brackets_are_balanced(input)); + assert!(brackets_are_balanced( + "\\left(\\begin{array}{cc} \\frac{1}{3} & x\\\\ \\mathrm{e}^{x} &... x^2 \\end{array}\\right)" + )); } From 7b9ac8f8f65907d013401b777c0bb345ee8b3ead Mon Sep 17 00:00:00 2001 From: ellnix Date: Sat, 1 Mar 2025 22:25:09 +0100 Subject: [PATCH 396/436] difference_of_squares: Sync tests & add template (#2047) [no important files changed] --- .../.meta/test_template.tera | 26 ++++++++++++ .../difference-of-squares/.meta/tests.toml | 40 +++++++++++++++++-- 2 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 exercises/practice/difference-of-squares/.meta/test_template.tera diff --git a/exercises/practice/difference-of-squares/.meta/test_template.tera b/exercises/practice/difference-of-squares/.meta/test_template.tera new file mode 100644 index 000000000..7aa683dfd --- /dev/null +++ b/exercises/practice/difference-of-squares/.meta/test_template.tera @@ -0,0 +1,26 @@ +use difference_of_squares as squares; + +{% for group in cases %} +{% for test in group.cases %} + +{% if test.property == "squareOfSum" %} + {% set function = "square_of_sum" %} +{% elif test.property == "sumOfSquares" %} + {% set function = "sum_of_squares" %} +{% elif test.property == "differenceOfSquares" %} + {% set function = "difference" %} +{% endif %} + +{% filter replace(from="difference_of_squares", to="difference") %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + assert_eq!( + {{ test.expected | fmt_num }}, + squares::{{ function }}({{ test.input.number }}) + ); +} +{% endfilter %} + +{% endfor -%} +{% endfor -%} diff --git a/exercises/practice/difference-of-squares/.meta/tests.toml b/exercises/practice/difference-of-squares/.meta/tests.toml index be690e975..e54414c0a 100644 --- a/exercises/practice/difference-of-squares/.meta/tests.toml +++ b/exercises/practice/difference-of-squares/.meta/tests.toml @@ -1,3 +1,37 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[e46c542b-31fc-4506-bcae-6b62b3268537] +description = "Square the sum of the numbers up to the given number -> square of sum 1" + +[9b3f96cb-638d-41ee-99b7-b4f9c0622948] +description = "Square the sum of the numbers up to the given number -> square of sum 5" + +[54ba043f-3c35-4d43-86ff-3a41625d5e86] +description = "Square the sum of the numbers up to the given number -> square of sum 100" + +[01d84507-b03e-4238-9395-dd61d03074b5] +description = "Sum the squares of the numbers up to the given number -> sum of squares 1" + +[c93900cd-8cc2-4ca4-917b-dd3027023499] +description = "Sum the squares of the numbers up to the given number -> sum of squares 5" + +[94807386-73e4-4d9e-8dec-69eb135b19e4] +description = "Sum the squares of the numbers up to the given number -> sum of squares 100" + +[44f72ae6-31a7-437f-858d-2c0837adabb6] +description = "Subtract sum of squares from square of sums -> difference of squares 1" + +[005cb2bf-a0c8-46f3-ae25-924029f8b00b] +description = "Subtract sum of squares from square of sums -> difference of squares 5" + +[b1bf19de-9a16-41c0-a62b-1f02ecc0b036] +description = "Subtract sum of squares from square of sums -> difference of squares 100" From 74ce2e36987917959ef10194dfc20ff595262967 Mon Sep 17 00:00:00 2001 From: ellnix Date: Sun, 2 Mar 2025 23:43:10 +0100 Subject: [PATCH 397/436] high-scores: Sync tests & generate with template (#2048) --- .../high-scores/.meta/test_template.tera | 80 +++++++++++++++++ .../practice/high-scores/.meta/tests.toml | 49 ++++++++++- .../practice/high-scores/tests/high_scores.rs | 88 +++++++++++++++---- 3 files changed, 195 insertions(+), 22 deletions(-) create mode 100644 exercises/practice/high-scores/.meta/test_template.tera diff --git a/exercises/practice/high-scores/.meta/test_template.tera b/exercises/practice/high-scores/.meta/test_template.tera new file mode 100644 index 000000000..3d93408a6 --- /dev/null +++ b/exercises/practice/high-scores/.meta/test_template.tera @@ -0,0 +1,80 @@ +use high_scores::HighScores; + +{% for test_or_group in cases %} + +{# canonical_data for this exercise is cursed #} +{% if test_or_group.cases is defined %} + {% set group = test_or_group.cases %} +{% else %} + {% set group = [test_or_group] %} +{% endif %} + +{% for test in group %} + +{% set test_name = test.description | make_ident %} + +{# cryptic test names #} +{% if test.scenarios is iterable and "immutable" in test.scenarios %} + {% set test_name = test_name | replace(from="_after", to="_unchanged_after") %} +{% endif %} + +#[test] +#[ignore] +fn {{ test_name }}() { + {% if test.property is starting_with("score") %} + let expected = {{ test.input.scores }}; + let high_scores = HighScores::new(&expected); + {% else %} + let high_scores = HighScores::new(&{{ test.input.scores }}); + {% endif %} + + {% if test.property is ending_with("AfterTopThree") %} + let _ = high_scores.personal_top_three(); + {% elif test.property is ending_with("AfterBest") %} + let _ = high_scores.personal_best(); + {% endif %} + + + {% if test.property is starting_with("score") %} + assert_eq!(high_scores.scores(), &expected); + {% elif test.property is starting_with("latest") %} + assert_eq!(high_scores.latest(), Some({{ test.expected }})); + {% elif test.property == "personalBest" %} + assert_eq!(high_scores.personal_best(), Some({{ test.expected }})); + {% elif test.property == "personalTopThree" %} + assert_eq!(high_scores.personal_top_three(), vec!{{ test.expected }}); + {% else %} + // Causing a compiler error to bring test to attention + Unexpected property in high-scores test + {% endif %} +} +{% endfor %} +{% endfor %} + +{# + Unique rust tests using None and is_empty. + Don't make sense to have additional tests json file + since each of these would be an exception in the template + and it would be no simpler +#} + +#[test] +#[ignore] +fn latest_score_empty() { + let high_scores = HighScores::new(&[]); + assert_eq!(high_scores.latest(), None); +} + +#[test] +#[ignore] +fn personal_best_empty() { + let high_scores = HighScores::new(&[]); + assert_eq!(high_scores.personal_best(), None); +} + +#[test] +#[ignore] +fn personal_top_three_empty() { + let high_scores = HighScores::new(&[]); + assert!(high_scores.personal_top_three().is_empty()); +} diff --git a/exercises/practice/high-scores/.meta/tests.toml b/exercises/practice/high-scores/.meta/tests.toml index be690e975..7c9463380 100644 --- a/exercises/practice/high-scores/.meta/tests.toml +++ b/exercises/practice/high-scores/.meta/tests.toml @@ -1,3 +1,46 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[1035eb93-2208-4c22-bab8-fef06769a73c] +description = "List of scores" + +[6aa5dbf5-78fa-4375-b22c-ffaa989732d2] +description = "Latest score" + +[b661a2e1-aebf-4f50-9139-0fb817dd12c6] +description = "Personal best" + +[3d996a97-c81c-4642-9afc-80b80dc14015] +description = "Top 3 scores -> Personal top three from a list of scores" + +[1084ecb5-3eb4-46fe-a816-e40331a4e83a] +description = "Top 3 scores -> Personal top highest to lowest" + +[e6465b6b-5a11-4936-bfe3-35241c4f4f16] +description = "Top 3 scores -> Personal top when there is a tie" + +[f73b02af-c8fd-41c9-91b9-c86eaa86bce2] +description = "Top 3 scores -> Personal top when there are less than 3" + +[16608eae-f60f-4a88-800e-aabce5df2865] +description = "Top 3 scores -> Personal top when there is only one" + +[2df075f9-fec9-4756-8f40-98c52a11504f] +description = "Top 3 scores -> Latest score after personal top scores" + +[809c4058-7eb1-4206-b01e-79238b9b71bc] +description = "Top 3 scores -> Scores after personal top scores" + +[ddb0efc0-9a86-4f82-bc30-21ae0bdc6418] +description = "Top 3 scores -> Latest score after personal best" + +[6a0fd2d1-4cc4-46b9-a5bb-2fb667ca2364] +description = "Top 3 scores -> Scores after personal best" diff --git a/exercises/practice/high-scores/tests/high_scores.rs b/exercises/practice/high-scores/tests/high_scores.rs index cb62d7c64..93471a2a7 100644 --- a/exercises/practice/high-scores/tests/high_scores.rs +++ b/exercises/practice/high-scores/tests/high_scores.rs @@ -4,6 +4,7 @@ use high_scores::HighScores; fn list_of_scores() { let expected = [30, 50, 20, 70]; let high_scores = HighScores::new(&expected); + assert_eq!(high_scores.scores(), &expected); } @@ -11,65 +12,114 @@ fn list_of_scores() { #[ignore] fn latest_score() { let high_scores = HighScores::new(&[100, 0, 90, 30]); - assert_eq!(high_scores.latest(), Some(30)); -} -#[test] -#[ignore] -fn latest_score_empty() { - let high_scores = HighScores::new(&[]); - assert_eq!(high_scores.latest(), None); + assert_eq!(high_scores.latest(), Some(30)); } #[test] #[ignore] fn personal_best() { let high_scores = HighScores::new(&[40, 100, 70]); - assert_eq!(high_scores.personal_best(), Some(100)); -} -#[test] -#[ignore] -fn personal_best_empty() { - let high_scores = HighScores::new(&[]); - assert_eq!(high_scores.personal_best(), None); + assert_eq!(high_scores.personal_best(), Some(100)); } #[test] #[ignore] -fn personal_top_three() { +fn personal_top_three_from_a_list_of_scores() { let high_scores = HighScores::new(&[10, 30, 90, 30, 100, 20, 10, 0, 30, 40, 40, 70, 70]); + assert_eq!(high_scores.personal_top_three(), vec![100, 90, 70]); } #[test] #[ignore] -fn personal_top_three_highest_to_lowest() { +fn personal_top_highest_to_lowest() { let high_scores = HighScores::new(&[20, 10, 30]); + assert_eq!(high_scores.personal_top_three(), vec![30, 20, 10]); } #[test] #[ignore] -fn personal_top_three_with_tie() { +fn personal_top_when_there_is_a_tie() { let high_scores = HighScores::new(&[40, 20, 40, 30]); + assert_eq!(high_scores.personal_top_three(), vec![40, 40, 30]); } #[test] #[ignore] -fn personal_top_three_with_less_than_three_scores() { +fn personal_top_when_there_are_less_than_3() { let high_scores = HighScores::new(&[30, 70]); + assert_eq!(high_scores.personal_top_three(), vec![70, 30]); } #[test] #[ignore] -fn personal_top_three_only_one_score() { +fn personal_top_when_there_is_only_one() { let high_scores = HighScores::new(&[40]); + assert_eq!(high_scores.personal_top_three(), vec![40]); } +#[test] +#[ignore] +fn latest_score_unchanged_after_personal_top_scores() { + let high_scores = HighScores::new(&[70, 50, 20, 30]); + + let _ = high_scores.personal_top_three(); + + assert_eq!(high_scores.latest(), Some(30)); +} + +#[test] +#[ignore] +fn scores_unchanged_after_personal_top_scores() { + let expected = [30, 50, 20, 70]; + let high_scores = HighScores::new(&expected); + + let _ = high_scores.personal_top_three(); + + assert_eq!(high_scores.scores(), &expected); +} + +#[test] +#[ignore] +fn latest_score_unchanged_after_personal_best() { + let high_scores = HighScores::new(&[20, 70, 15, 25, 30]); + + let _ = high_scores.personal_best(); + + assert_eq!(high_scores.latest(), Some(30)); +} + +#[test] +#[ignore] +fn scores_unchanged_after_personal_best() { + let expected = [20, 70, 15, 25, 30]; + let high_scores = HighScores::new(&expected); + + let _ = high_scores.personal_best(); + + assert_eq!(high_scores.scores(), &expected); +} + +#[test] +#[ignore] +fn latest_score_empty() { + let high_scores = HighScores::new(&[]); + assert_eq!(high_scores.latest(), None); +} + +#[test] +#[ignore] +fn personal_best_empty() { + let high_scores = HighScores::new(&[]); + assert_eq!(high_scores.personal_best(), None); +} + #[test] #[ignore] fn personal_top_three_empty() { From 790b13080ffce9ca826b77017f9b04ab21e376bd Mon Sep 17 00:00:00 2001 From: ellnix Date: Mon, 3 Mar 2025 22:25:51 +0100 Subject: [PATCH 398/436] ocr_numbers & react: Sync missing tests (#2049) --- .../practice/ocr-numbers/.meta/tests.toml | 28 +++++++++++++++++-- exercises/practice/react/.meta/tests.toml | 22 +++++++++++++-- 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/exercises/practice/ocr-numbers/.meta/tests.toml b/exercises/practice/ocr-numbers/.meta/tests.toml index 7f40437ff..0d7a5b770 100644 --- a/exercises/practice/ocr-numbers/.meta/tests.toml +++ b/exercises/practice/ocr-numbers/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [5ee54e1a-b554-4bf3-a056-9a7976c3f7e8] description = "Recognizes 0" @@ -8,9 +15,21 @@ description = "Recognizes 0" [027ada25-17fd-4d78-aee6-35a19623639d] description = "Recognizes 1" +[3cce2dbd-01d9-4f94-8fae-419a822e89bb] +description = "Unreadable but correctly sized inputs return ?" + +[cb19b733-4e36-4cf9-a4a1-6e6aac808b9a] +description = "Input with a number of lines that is not a multiple of four raises an error" + +[235f7bd1-991b-4587-98d4-84206eec4cc6] +description = "Input with a number of columns that is not a multiple of three raises an error" + [4a841794-73c9-4da9-a779-1f9837faff66] description = "Recognizes 110101100" +[70c338f9-85b1-4296-a3a8-122901cdfde8] +description = "Garbled numbers in a string are replaced with ?" + [ea494ff4-3610-44d7-ab7e-72fdef0e0802] description = "Recognizes 2" @@ -37,3 +56,6 @@ description = "Recognizes 9" [f60cb04a-42be-494e-a535-3451c8e097a4] description = "Recognizes string of decimal numbers" + +[b73ecf8b-4423-4b36-860d-3710bdb8a491] +description = "Numbers separated by empty lines are recognized. Lines are joined by commas." diff --git a/exercises/practice/react/.meta/tests.toml b/exercises/practice/react/.meta/tests.toml index 8d443115a..9b29a0a0a 100644 --- a/exercises/practice/react/.meta/tests.toml +++ b/exercises/practice/react/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [c51ee736-d001-4f30-88d1-0c8e8b43cd07] description = "input cells have a value" @@ -23,6 +30,15 @@ description = "compute cells can depend on other compute cells" [abe33eaf-68ad-42a5-b728-05519ca88d2d] description = "compute cells fire callbacks" +[9e5cb3a4-78e5-4290-80f8-a78612c52db2] +description = "callback cells only fire on change" + +[ada17cb6-7332-448a-b934-e3d7495c13d3] +description = "callbacks do not report already reported values" + +[ac271900-ea5c-461c-9add-eeebcb8c03e5] +description = "callbacks can fire from multiple cells" + [95a82dcc-8280-4de3-a4cd-4f19a84e3d6f] description = "callbacks can be added and removed" From 4511cf6f92ff73f07715ef9bb0f1da386b1e25ee Mon Sep 17 00:00:00 2001 From: ellnix Date: Thu, 6 Mar 2025 12:08:44 +0100 Subject: [PATCH 399/436] largest-series-product: Sync tests & add template (#2052) --- .../.meta/test_template.tera | 26 ++++++++ .../largest-series-product/.meta/tests.toml | 66 ++++++++++++++++++- .../tests/largest_series_product.rs | 36 ++++++---- 3 files changed, 113 insertions(+), 15 deletions(-) create mode 100644 exercises/practice/largest-series-product/.meta/test_template.tera diff --git a/exercises/practice/largest-series-product/.meta/test_template.tera b/exercises/practice/largest-series-product/.meta/test_template.tera new file mode 100644 index 000000000..5fbeb4d91 --- /dev/null +++ b/exercises/practice/largest-series-product/.meta/test_template.tera @@ -0,0 +1,26 @@ +use largest_series_product::*; + +#[test] +#[ignore] +fn return_is_a_result() { + assert!(lsp("29", 2).is_ok()); +} + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + {% if test.expected.error is defined %} + {% if test.expected.error == "span must be smaller than string length" %} + {% set error_type = "SpanTooLong" %} + {% elif test.expected.error == "digits input must only contain digits" %} + {% set invalid_char = test.input.digits | split(pat="") | sort | last %} + {% set error_type = "InvalidDigit('" ~ invalid_char ~ "')" %} + {% endif %} + + assert_eq!(Err(Error::{{error_type}}), lsp("{{test.input.digits}}", {{test.input.span}})); + {% else %} + assert_eq!(Ok({{test.expected | fmt_num}}), lsp("{{test.input.digits}}", {{test.input.span}})); + {% endif %} +} +{% endfor -%} diff --git a/exercises/practice/largest-series-product/.meta/tests.toml b/exercises/practice/largest-series-product/.meta/tests.toml index be690e975..01f67feb3 100644 --- a/exercises/practice/largest-series-product/.meta/tests.toml +++ b/exercises/practice/largest-series-product/.meta/tests.toml @@ -1,3 +1,63 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[7c82f8b7-e347-48ee-8a22-f672323324d4] +description = "finds the largest product if span equals length" + +[88523f65-21ba-4458-a76a-b4aaf6e4cb5e] +description = "can find the largest product of 2 with numbers in order" + +[f1376b48-1157-419d-92c2-1d7e36a70b8a] +description = "can find the largest product of 2" + +[46356a67-7e02-489e-8fea-321c2fa7b4a4] +description = "can find the largest product of 3 with numbers in order" + +[a2dcb54b-2b8f-4993-92dd-5ce56dece64a] +description = "can find the largest product of 3" + +[673210a3-33cd-4708-940b-c482d7a88f9d] +description = "can find the largest product of 5 with numbers in order" + +[02acd5a6-3bbf-46df-8282-8b313a80a7c9] +description = "can get the largest product of a big number" + +[76dcc407-21e9-424c-a98e-609f269622b5] +description = "reports zero if the only digits are zero" + +[6ef0df9f-52d4-4a5d-b210-f6fae5f20e19] +description = "reports zero if all spans include zero" + +[5d81aaf7-4f67-4125-bf33-11493cc7eab7] +description = "rejects span longer than string length" + +[06bc8b90-0c51-4c54-ac22-3ec3893a079e] +description = "reports 1 for empty string and empty product (0 span)" + +[3ec0d92e-f2e2-4090-a380-70afee02f4c0] +description = "reports 1 for nonempty string and empty product (0 span)" + +[6d96c691-4374-4404-80ee-2ea8f3613dd4] +description = "rejects empty string and nonzero span" + +[7a38f2d6-3c35-45f6-8d6f-12e6e32d4d74] +description = "rejects invalid character in digits" + +[5fe3c0e5-a945-49f2-b584-f0814b4dd1ef] +description = "rejects negative span" +include = false +comment = "the function signature already prevents this (usize)" + +[c859f34a-9bfe-4897-9c2f-6d7f8598e7f0] +description = "rejects negative span" +include = false +reimplements = "5fe3c0e5-a945-49f2-b584-f0814b4dd1ef" +comment = "the function signature already prevents this (usize)" diff --git a/exercises/practice/largest-series-product/tests/largest_series_product.rs b/exercises/practice/largest-series-product/tests/largest_series_product.rs index c40c75b02..a96ef5694 100644 --- a/exercises/practice/largest-series-product/tests/largest_series_product.rs +++ b/exercises/practice/largest-series-product/tests/largest_series_product.rs @@ -7,43 +7,43 @@ fn return_is_a_result() { #[test] #[ignore] -fn find_the_largest_product_when_span_equals_length() { +fn finds_the_largest_product_if_span_equals_length() { assert_eq!(Ok(18), lsp("29", 2)); } #[test] #[ignore] -fn find_the_largest_product_of_two_with_numbers_in_order() { +fn can_find_the_largest_product_of_2_with_numbers_in_order() { assert_eq!(Ok(72), lsp("0123456789", 2)); } #[test] #[ignore] -fn find_the_largest_product_of_two_with_numbers_not_in_order() { +fn can_find_the_largest_product_of_2() { assert_eq!(Ok(48), lsp("576802143", 2)); } #[test] #[ignore] -fn find_the_largest_product_of_three_with_numbers_in_order() { +fn can_find_the_largest_product_of_3_with_numbers_in_order() { assert_eq!(Ok(504), lsp("0123456789", 3)); } #[test] #[ignore] -fn find_the_largest_product_of_three_with_numbers_not_in_order() { +fn can_find_the_largest_product_of_3() { assert_eq!(Ok(270), lsp("1027839564", 3)); } #[test] #[ignore] -fn find_the_largest_product_of_five_with_numbers_in_order() { +fn can_find_the_largest_product_of_5_with_numbers_in_order() { assert_eq!(Ok(15_120), lsp("0123456789", 5)); } #[test] #[ignore] -fn span_of_six_in_a_large_number() { +fn can_get_the_largest_product_of_a_big_number() { assert_eq!( Ok(23_520), lsp("73167176531330624919225119674426574742355349194934", 6) @@ -52,30 +52,42 @@ fn span_of_six_in_a_large_number() { #[test] #[ignore] -fn returns_zero_if_number_is_zeros() { +fn reports_zero_if_the_only_digits_are_zero() { assert_eq!(Ok(0), lsp("0000", 2)); } #[test] #[ignore] -fn returns_zero_if_all_products_are_zero() { +fn reports_zero_if_all_spans_include_zero() { assert_eq!(Ok(0), lsp("99099", 3)); } #[test] #[ignore] -fn a_span_is_longer_than_number_is_an_error() { +fn rejects_span_longer_than_string_length() { assert_eq!(Err(Error::SpanTooLong), lsp("123", 4)); } #[test] #[ignore] -fn empty_string_and_non_zero_span_is_an_error() { +fn reports_1_for_empty_string_and_empty_product_0_span() { + assert_eq!(Ok(1), lsp("", 0)); +} + +#[test] +#[ignore] +fn reports_1_for_nonempty_string_and_empty_product_0_span() { + assert_eq!(Ok(1), lsp("123", 0)); +} + +#[test] +#[ignore] +fn rejects_empty_string_and_nonzero_span() { assert_eq!(Err(Error::SpanTooLong), lsp("", 1)); } #[test] #[ignore] -fn a_string_with_non_digits_is_an_error() { +fn rejects_invalid_character_in_digits() { assert_eq!(Err(Error::InvalidDigit('a')), lsp("1234a5", 2)); } From 02dce7df8ce6d27c0fd526467343cc0c869f39cf Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Thu, 6 Mar 2025 16:57:55 +0100 Subject: [PATCH 400/436] list-ops: simplify generics (#2054) --- exercises/practice/list-ops/.meta/config.json | 6 ++- exercises/practice/list-ops/src/lib.rs | 39 ++++++++++--------- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/exercises/practice/list-ops/.meta/config.json b/exercises/practice/list-ops/.meta/config.json index 67205db2f..5590d9eb2 100644 --- a/exercises/practice/list-ops/.meta/config.json +++ b/exercises/practice/list-ops/.meta/config.json @@ -1,5 +1,9 @@ { - "authors": ["ellnix"], + "authors": [ + "ellnix", + "senekor", + "Morgane55440" + ], "files": { "solution": [ "src/lib.rs", diff --git a/exercises/practice/list-ops/src/lib.rs b/exercises/practice/list-ops/src/lib.rs index 359e75832..a67c02ac1 100644 --- a/exercises/practice/list-ops/src/lib.rs +++ b/exercises/practice/list-ops/src/lib.rs @@ -1,7 +1,8 @@ /// Yields each item of a and then each item of b -pub fn append(_a: I, _b: I) -> impl Iterator +pub fn append(_a: I, _b: J) -> impl Iterator where - I: Iterator, + I: Iterator, + J: Iterator, { // this empty iterator silences a compiler complaint that // () doesn't implement Iterator @@ -9,10 +10,10 @@ where } /// Combines all items in all nested iterators inside into one flattened iterator -pub fn concat(_nested_iter: I) -> impl Iterator +pub fn concat(_nested_iter: I) -> impl Iterator::Item> where - NI: Iterator, - I: Iterator, + I: Iterator, + I::Item: Iterator, { // this empty iterator silences a compiler complaint that // () doesn't implement Iterator @@ -20,49 +21,49 @@ where } /// Returns an iterator of all items in iter for which `predicate(item)` is true -pub fn filter(_iter: I, _predicate: F) -> impl Iterator +pub fn filter(_iter: I, _predicate: F) -> impl Iterator where - I: Iterator, - F: Fn(&T) -> bool, + I: Iterator, + F: Fn(&I::Item) -> bool, { // this empty iterator silences a compiler complaint that // () doesn't implement Iterator std::iter::from_fn(|| todo!()) } -pub fn length, T>(_iter: I) -> usize { +pub fn length(_iter: I) -> usize { todo!("return the total number of items within iter") } /// Returns an iterator of the results of applying `function(item)` on all iter items -pub fn map(_iter: I, _function: F) -> impl Iterator +pub fn map(_iter: I, _function: F) -> impl Iterator where - I: Iterator, - F: Fn(T) -> U, + I: Iterator, + F: Fn(I::Item) -> U, { // this empty iterator silences a compiler complaint that // () doesn't implement Iterator std::iter::from_fn(|| todo!()) } -pub fn foldl(mut _iter: I, _initial: U, _function: F) -> U +pub fn foldl(mut _iter: I, _initial: U, _function: F) -> U where - I: Iterator, - F: Fn(U, T) -> U, + I: Iterator, + F: Fn(U, I::Item) -> U, { todo!("starting with initial, fold (reduce) each iter item into the accumulator from the left") } -pub fn foldr(mut _iter: I, _initial: U, _function: F) -> U +pub fn foldr(mut _iter: I, _initial: U, _function: F) -> U where - I: DoubleEndedIterator, - F: Fn(U, T) -> U, + I: DoubleEndedIterator, + F: Fn(U, I::Item) -> U, { todo!("starting with initial, fold (reduce) each iter item into the accumulator from the right") } /// Returns an iterator with all the original items, but in reverse order -pub fn reverse, T>(_iter: I) -> impl Iterator { +pub fn reverse(_iter: I) -> impl Iterator { // this empty iterator silences a compiler complaint that // () doesn't implement Iterator std::iter::from_fn(|| todo!()) From 9d28f033cd219b368fdbe5a72a1b075a562dbb2e Mon Sep 17 00:00:00 2001 From: lbellomo Date: Sat, 8 Mar 2025 04:22:28 -0300 Subject: [PATCH 401/436] simple-cipher: upgrade rand to 0.9 (#2055) --- exercises/practice/simple-cipher/.meta/example.rs | 4 ++-- exercises/practice/simple-cipher/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/exercises/practice/simple-cipher/.meta/example.rs b/exercises/practice/simple-cipher/.meta/example.rs index fb18e9f9f..33a49501c 100644 --- a/exercises/practice/simple-cipher/.meta/example.rs +++ b/exercises/practice/simple-cipher/.meta/example.rs @@ -1,10 +1,10 @@ use rand::Rng; pub fn encode_random(s: &str) -> (String, String) { - let mut r = rand::thread_rng(); + let mut r = rand::rng(); let mut key = String::new(); for _ in 0..100 { - key.push(char::from(b'a' + r.gen_range(0..26))); + key.push(char::from(b'a' + r.random_range(0..26))); } let encoded = encode(&key, s); (key, encoded.unwrap()) diff --git a/exercises/practice/simple-cipher/Cargo.toml b/exercises/practice/simple-cipher/Cargo.toml index bae81d0de..718fbe537 100644 --- a/exercises/practice/simple-cipher/Cargo.toml +++ b/exercises/practice/simple-cipher/Cargo.toml @@ -7,4 +7,4 @@ edition = "2024" # The full list of available libraries is here: # https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml [dependencies] -rand = "0.8" +rand = "0.9" From db22ac69db6bbb9389b7fd644e101f3d113caead Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Sat, 8 Mar 2025 09:23:24 +0100 Subject: [PATCH 402/436] resistor-color: update dependencies (#2056) --- exercises/concept/resistor-color/.meta/exemplar.rs | 4 ++-- exercises/concept/resistor-color/Cargo.toml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/exercises/concept/resistor-color/.meta/exemplar.rs b/exercises/concept/resistor-color/.meta/exemplar.rs index 61f9baf05..ce7d9bf08 100644 --- a/exercises/concept/resistor-color/.meta/exemplar.rs +++ b/exercises/concept/resistor-color/.meta/exemplar.rs @@ -17,11 +17,11 @@ pub enum ResistorColor { } pub fn color_to_value(color: ResistorColor) -> usize { - color.int_value() + color.into() } pub fn value_to_color_string(value: usize) -> String { - ResistorColor::from_int(value) + ResistorColor::try_from(value) .map(|color| format!("{color:?}")) .unwrap_or_else(|_| "value out of range".into()) } diff --git a/exercises/concept/resistor-color/Cargo.toml b/exercises/concept/resistor-color/Cargo.toml index 04cab7865..c6bc363f4 100644 --- a/exercises/concept/resistor-color/Cargo.toml +++ b/exercises/concept/resistor-color/Cargo.toml @@ -7,5 +7,5 @@ edition = "2024" # The full list of available libraries is here: # https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml [dependencies] -int-enum = "0.5.0" -enum-iterator = "1.2.0" +enum-iterator = "2.0" +int-enum = "1.0" From 09dd021db7fbe5eb8039c667f7fc7d2c518cf6b0 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Sat, 8 Mar 2025 09:31:41 +0100 Subject: [PATCH 403/436] format example solutions with edition 2024 (#2057) --- bin/format_exercises.sh | 2 +- exercises/concept/resistor-color/.meta/exemplar.rs | 2 +- exercises/concept/rpn-calculator/.meta/exemplar.rs | 6 +----- exercises/practice/robot-name/.meta/example.rs | 2 +- exercises/practice/triangle/.meta/example.rs | 6 +----- exercises/practice/wordy/.meta/example.rs | 12 ++++++++++-- exercises/practice/yacht/.meta/example.rs | 6 +----- 7 files changed, 16 insertions(+), 20 deletions(-) diff --git a/bin/format_exercises.sh b/bin/format_exercises.sh index 34cfb2628..ef7b556c3 100755 --- a/bin/format_exercises.sh +++ b/bin/format_exercises.sh @@ -19,7 +19,7 @@ format_one_exercise() { cargo fmt file_name=".meta/$source_file_name.rs" if [ -f "$file_name" ]; then - rustfmt "$file_name" + rustfmt --edition 2024 "$file_name" fi } diff --git a/exercises/concept/resistor-color/.meta/exemplar.rs b/exercises/concept/resistor-color/.meta/exemplar.rs index ce7d9bf08..b8ecbde33 100644 --- a/exercises/concept/resistor-color/.meta/exemplar.rs +++ b/exercises/concept/resistor-color/.meta/exemplar.rs @@ -1,4 +1,4 @@ -use enum_iterator::{all, Sequence}; +use enum_iterator::{Sequence, all}; use int_enum::IntEnum; #[repr(usize)] diff --git a/exercises/concept/rpn-calculator/.meta/exemplar.rs b/exercises/concept/rpn-calculator/.meta/exemplar.rs index 30e4d9a18..2fc63b9ee 100644 --- a/exercises/concept/rpn-calculator/.meta/exemplar.rs +++ b/exercises/concept/rpn-calculator/.meta/exemplar.rs @@ -39,9 +39,5 @@ pub fn evaluate(inputs: &[CalculatorInput]) -> Option { } let output = stack.pop(); - if stack.is_empty() { - output - } else { - None - } + if stack.is_empty() { output } else { None } } diff --git a/exercises/practice/robot-name/.meta/example.rs b/exercises/practice/robot-name/.meta/example.rs index 8e79c0b78..487312a77 100644 --- a/exercises/practice/robot-name/.meta/example.rs +++ b/exercises/practice/robot-name/.meta/example.rs @@ -3,7 +3,7 @@ use std::{ sync::{LazyLock, Mutex}, }; -use rand::{thread_rng, Rng}; +use rand::{Rng, thread_rng}; static NAMES: LazyLock>> = LazyLock::new(|| Mutex::new(HashSet::new())); diff --git a/exercises/practice/triangle/.meta/example.rs b/exercises/practice/triangle/.meta/example.rs index 39f85574a..ea4c1d859 100644 --- a/exercises/practice/triangle/.meta/example.rs +++ b/exercises/practice/triangle/.meta/example.rs @@ -25,11 +25,7 @@ where pub fn build(sides: [T; 3]) -> Option> { let t = Triangle { sides }; - if t.valid_sides() { - Some(t) - } else { - None - } + if t.valid_sides() { Some(t) } else { None } } pub fn is_equilateral(&self) -> bool { diff --git a/exercises/practice/wordy/.meta/example.rs b/exercises/practice/wordy/.meta/example.rs index 81e201cec..44052ffef 100644 --- a/exercises/practice/wordy/.meta/example.rs +++ b/exercises/practice/wordy/.meta/example.rs @@ -22,7 +22,11 @@ fn apply_op<'a, 'b>(num1: i32, words: &'a [Token<'b>]) -> Option<(i32, &'a [Toke [Token::NonNumber("minus")] => Some(num1 - num2), [Token::NonNumber("multiplied"), Token::NonNumber("by")] => Some(num1 * num2), [Token::NonNumber("divided"), Token::NonNumber("by")] => Some(num1 / num2), - [Token::NonNumber("raised"), Token::NonNumber("to"), Token::NonNumber("the")] => { + [ + Token::NonNumber("raised"), + Token::NonNumber("to"), + Token::NonNumber("the"), + ] => { if Some(&Token::NonNumber("power")) == remainder.first() { remainder = remainder.get(1..)?; Some(num1.pow(num2 as u32)) @@ -56,7 +60,11 @@ pub fn answer(c: &str) -> Option { return None; } let mut result: i32 = match words[0..3] { - [Token::NonNumber("What"), Token::NonNumber("is"), Token::Number(i)] => i, + [ + Token::NonNumber("What"), + Token::NonNumber("is"), + Token::Number(i), + ] => i, _ => return None, }; let mut words = words.split_at(3).1; diff --git a/exercises/practice/yacht/.meta/example.rs b/exercises/practice/yacht/.meta/example.rs index f603fe731..03fa51fd4 100644 --- a/exercises/practice/yacht/.meta/example.rs +++ b/exercises/practice/yacht/.meta/example.rs @@ -71,11 +71,7 @@ fn get_straight(dice: Dice, straight_type: char) -> u8 { } let index: usize = if straight_type == 'l' { 5 } else { 0 }; - if scores[index] == 0 { - 30 - } else { - 0 - } + if scores[index] == 0 { 30 } else { 0 } } fn get_yacht(dice: Dice) -> u8 { From 4bbbdeb36d2feb54e79413bfb60a231d0645e5ee Mon Sep 17 00:00:00 2001 From: ellnix Date: Sun, 9 Mar 2025 23:29:15 +0100 Subject: [PATCH 404/436] parallel-letter-frequency: Sync tests & add missing (#2050) --- .../.meta/tests.toml | 54 ++++ .../tests/parallel_letter_frequency.rs | 305 +++++++++++++++--- 2 files changed, 322 insertions(+), 37 deletions(-) create mode 100644 exercises/practice/parallel-letter-frequency/.meta/tests.toml diff --git a/exercises/practice/parallel-letter-frequency/.meta/tests.toml b/exercises/practice/parallel-letter-frequency/.meta/tests.toml new file mode 100644 index 000000000..094b1dad2 --- /dev/null +++ b/exercises/practice/parallel-letter-frequency/.meta/tests.toml @@ -0,0 +1,54 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[c054d642-c1fa-4234-8007-9339f2337886] +description = "no texts" + +[818031be-49dc-4675-b2f9-c4047f638a2a] +description = "one text with one letter" + +[c0b81d1b-940d-4cea-9f49-8445c69c17ae] +description = "one text with multiple letters" + +[708ff1e0-f14a-43fd-adb5-e76750dcf108] +description = "two texts with one letter" + +[1b5c28bb-4619-4c9d-8db9-a4bb9c3bdca0] +description = "two texts with multiple letters" + +[6366e2b8-b84c-4334-a047-03a00a656d63] +description = "ignore letter casing" +rust_fn = "case_insensitivity" + +[92ebcbb0-9181-4421-a784-f6f5aa79f75b] +description = "ignore whitespace" + +[bc5f4203-00ce-4acc-a5fa-f7b865376fd9] +description = "ignore punctuation" +rust_fn = "punctuation_doesnt_count" + +[68032b8b-346b-4389-a380-e397618f6831] +description = "ignore numbers" +rust_fn = "numbers_dont_count" + +[aa9f97ac-3961-4af1-88e7-6efed1bfddfd] +description = "Unicode letters" + +[7b1da046-701b-41fc-813e-dcfb5ee51813] +description = "combination of lower- and uppercase letters, punctuation and white space" +rust_fn = "all_three_anthems_1_worker" + +[4727f020-df62-4dcf-99b2-a6e58319cb4f] +description = "large texts" + +[adf8e57b-8e54-4483-b6b8-8b32c115884c] +description = "many small texts" +rust_fn = "many_times_same_text" diff --git a/exercises/practice/parallel-letter-frequency/tests/parallel_letter_frequency.rs b/exercises/practice/parallel-letter-frequency/tests/parallel_letter_frequency.rs index 128b6c495..5ed2c1006 100644 --- a/exercises/practice/parallel-letter-frequency/tests/parallel_letter_frequency.rs +++ b/exercises/practice/parallel-letter-frequency/tests/parallel_letter_frequency.rs @@ -2,42 +2,6 @@ use std::collections::HashMap; use parallel_letter_frequency as frequency; -// Poem by Friedrich Schiller. The corresponding music is the European Anthem. -const ODE_AN_DIE_FREUDE: [&str; 8] = [ - "Freude schöner Götterfunken", - "Tochter aus Elysium,", - "Wir betreten feuertrunken,", - "Himmlische, dein Heiligtum!", - "Deine Zauber binden wieder", - "Was die Mode streng geteilt;", - "Alle Menschen werden Brüder,", - "Wo dein sanfter Flügel weilt.", -]; - -// Dutch national anthem -const WILHELMUS: [&str; 8] = [ - "Wilhelmus van Nassouwe", - "ben ik, van Duitsen bloed,", - "den vaderland getrouwe", - "blijf ik tot in den dood.", - "Een Prinse van Oranje", - "ben ik, vrij, onverveerd,", - "den Koning van Hispanje", - "heb ik altijd geëerd.", -]; - -// American national anthem -const STAR_SPANGLED_BANNER: [&str; 8] = [ - "O say can you see by the dawn's early light,", - "What so proudly we hailed at the twilight's last gleaming,", - "Whose broad stripes and bright stars through the perilous fight,", - "O'er the ramparts we watched, were so gallantly streaming?", - "And the rockets' red glare, the bombs bursting in air,", - "Gave proof through the night that our flag was still there;", - "O say does that star-spangled banner yet wave,", - "O'er the land of the free and the home of the brave?", -]; - #[test] fn no_texts() { assert_eq!(frequency::frequency(&[], 4), HashMap::new()); @@ -45,12 +9,41 @@ fn no_texts() { #[test] #[ignore] -fn one_letter() { +fn one_text_with_one_letter() { let mut hm = HashMap::new(); hm.insert('a', 1); assert_eq!(frequency::frequency(&["a"], 4), hm); } +#[test] +#[ignore] +fn one_text_with_multiple_letters() { + let mut hm = HashMap::new(); + hm.insert('b', 2); + hm.insert('c', 3); + hm.insert('d', 1); + assert_eq!(frequency::frequency(&["bbcccd"], 4), hm); +} + +#[test] +#[ignore] +fn two_texts_with_one_letter() { + let mut hm = HashMap::new(); + hm.insert('e', 1); + hm.insert('f', 1); + assert_eq!(frequency::frequency(&["e", "f"], 4), hm); +} + +#[test] +#[ignore] +fn two_texts_with_multiple_letters() { + let mut hm = HashMap::new(); + hm.insert('g', 2); + hm.insert('h', 3); + hm.insert('i', 1); + assert_eq!(frequency::frequency(&["ggh", "hhi"], 4), hm); +} + #[test] #[ignore] fn case_insensitivity() { @@ -66,6 +59,13 @@ fn many_empty_lines() { assert_eq!(frequency::frequency(&v[..], 4), HashMap::new()); } +#[test] +#[ignore] +fn ignore_whitespace() { + let v = [" ", "\t", "\r\n"]; + assert_eq!(frequency::frequency(&v[..], 4), HashMap::new()); +} + #[test] #[ignore] fn many_times_same_text() { @@ -89,10 +89,23 @@ fn numbers_dont_count() { assert!(!frequency::frequency(&["Testing, 1, 2, 3"], 4).contains_key(&'1')); } +#[test] +#[ignore] +fn unicode_letters() { + let mut hm = HashMap::new(); + hm.insert('本', 1); + hm.insert('φ', 1); + hm.insert('ほ', 1); + hm.insert('ø', 1); + let v = ["本", "φ", "ほ", "ø"]; + assert_eq!(frequency::frequency(&v, 4), hm); +} + #[test] #[ignore] fn all_three_anthems_1_worker() { let mut v = Vec::new(); + // These constants can be found under the last test if you wish to see them for anthem in [ODE_AN_DIE_FREUDE, WILHELMUS, STAR_SPANGLED_BANNER].iter() { for line in anthem.iter() { v.push(*line); @@ -129,3 +142,221 @@ fn non_integer_multiple_of_threads() { hm.insert('c', 999); assert_eq!(frequency::frequency(&v[..], 4), hm); } + +#[test] +#[ignore] +fn large_texts() { + let expected: HashMap = [ + ('a', 845), + ('b', 155), + ('c', 278), + ('d', 359), + ('e', 1143), + ('f', 222), + ('g', 187), + ('h', 507), + ('i', 791), + ('j', 12), + ('k', 67), + ('l', 423), + ('m', 288), + ('n', 833), + ('o', 791), + ('p', 197), + ('q', 8), + ('r', 432), + ('s', 700), + ('t', 1043), + ('u', 325), + ('v', 111), + ('w', 223), + ('x', 7), + ('y', 251), + ] + .into_iter() + .collect(); + + assert_eq!(frequency::frequency(&DOSTOEVSKY, 4), expected); +} + +// Poem by Friedrich Schiller. The corresponding music is the European Anthem. +const ODE_AN_DIE_FREUDE: [&str; 8] = [ + "Freude schöner Götterfunken", + "Tochter aus Elysium,", + "Wir betreten feuertrunken,", + "Himmlische, dein Heiligtum!", + "Deine Zauber binden wieder", + "Was die Mode streng geteilt;", + "Alle Menschen werden Brüder,", + "Wo dein sanfter Flügel weilt.", +]; + +// Dutch national anthem +const WILHELMUS: [&str; 8] = [ + "Wilhelmus van Nassouwe", + "ben ik, van Duitsen bloed,", + "den vaderland getrouwe", + "blijf ik tot in den dood.", + "Een Prinse van Oranje", + "ben ik, vrij, onverveerd,", + "den Koning van Hispanje", + "heb ik altijd geëerd.", +]; + +// American national anthem +const STAR_SPANGLED_BANNER: [&str; 8] = [ + "O say can you see by the dawn's early light,", + "What so proudly we hailed at the twilight's last gleaming,", + "Whose broad stripes and bright stars through the perilous fight,", + "O'er the ramparts we watched, were so gallantly streaming?", + "And the rockets' red glare, the bombs bursting in air,", + "Gave proof through the night that our flag was still there;", + "O say does that star-spangled banner yet wave,", + "O'er the land of the free and the home of the brave?", +]; + +const DOSTOEVSKY: [&str; 4] = [ + r#" + I am a sick man.... I am a spiteful man. I am an unattractive man. + I believe my liver is diseased. However, I know nothing at all about my disease, and do not + know for certain what ails me. I don't consult a doctor for it, + and never have, though I have a respect for medicine and doctors. + Besides, I am extremely superstitious, sufficiently so to respect medicine, + anyway (I am well-educated enough not to be superstitious, but I am superstitious). + No, I refuse to consult a doctor from spite. + That you probably will not understand. Well, I understand it, though. + Of course, I can't explain who it is precisely that I am mortifying in this case by my spite: + I am perfectly well aware that I cannot "pay out" the doctors by not consulting them; + I know better than anyone that by all this I am only injuring myself and no one else. + But still, if I don't consult a doctor it is from spite. + My liver is bad, well - let it get worse! + I have been going on like that for a long time - twenty years. Now I am forty. + I used to be in the government service, but am no longer. + I was a spiteful official. I was rude and took pleasure in being so. + I did not take bribes, you see, so I was bound to find a recompense in that, at least. + (A poor jest, but I will not scratch it out. I wrote it thinking it would sound very witty; + but now that I have seen myself that I only wanted to show off in a despicable way - + I will not scratch it out on purpose!) When petitioners used to come for + information to the table at which I sat, I used to grind my teeth at them, + and felt intense enjoyment when I succeeded in making anybody unhappy. + I almost did succeed. For the most part they were all timid people - of course, + they were petitioners. But of the uppish ones there was one officer in particular + I could not endure. He simply would not be humble, and clanked his sword in a disgusting way. + I carried on a feud with him for eighteen months over that sword. At last I got the better of him. + He left off clanking it. That happened in my youth, though. But do you know, + gentlemen, what was the chief point about my spite? Why, the whole point, + the real sting of it lay in the fact that continually, even in the moment of the acutest spleen, + I was inwardly conscious with shame that I was not only not a spiteful but not even an embittered man, + that I was simply scaring sparrows at random and amusing myself by it. + I might foam at the mouth, but bring me a doll to play with, give me a cup of tea with sugar in it, + and maybe I should be appeased. I might even be genuinely touched, + though probably I should grind my teeth at myself afterwards and lie awake at night with shame for + months after. That was my way. I was lying when I said just now that I was a spiteful official. + I was lying from spite. I was simply amusing myself with the petitioners and with the officer, + and in reality I never could become spiteful. I was conscious every moment in myself of many, + very many elements absolutely opposite to that. I felt them positively swarming in me, + these opposite elements. I knew that they had been swarming in me all my life and craving some outlet from me, + but I would not let them, would not let them, purposely would not let them come out. + They tormented me till I was ashamed: they drove me to convulsions and - sickened me, at last, + how they sickened me! + "#, + r#" + Gentlemen, I am joking, and I know myself that my jokes are not brilliant, + but you know one can take everything as a joke. I am, perhaps, jesting against the grain. + Gentlemen, I am tormented by questions; answer them for me. You, for instance, want to cure men of their + old habits and reform their will in accordance with science and good sense. + But how do you know, not only that it is possible, but also that it is + desirable to reform man in that way? And what leads you to the conclusion that man's + inclinations need reforming? In short, how do you know that such a reformation will be a benefit to man? + And to go to the root of the matter, why are you so positively convinced that not to act against + his real normal interests guaranteed by the conclusions of reason and arithmetic is certainly always + advantageous for man and must always be a law for mankind? So far, you know, + this is only your supposition. It may be the law of logic, but not the law of humanity. + You think, gentlemen, perhaps that I am mad? Allow me to defend myself. I agree that man + is pre-eminently a creative animal, predestined to strive consciously for an object and to engage in engineering - + that is, incessantly and eternally to make new roads, wherever + they may lead. But the reason why he wants sometimes to go off at a tangent may just be that he is + predestined to make the road, and perhaps, too, that however stupid the "direct" + practical man may be, the thought sometimes will occur to him that the road almost always does lead + somewhere, and that the destination it leads to is less important than the process + of making it, and that the chief thing is to save the well-conducted child from despising engineering, + and so giving way to the fatal idleness, which, as we all know, + is the mother of all the vices. Man likes to make roads and to create, that is a fact beyond dispute. + But why has he such a passionate love for destruction and chaos also? + Tell me that! But on that point I want to say a couple of words myself. May it not be that he loves + chaos and destruction (there can be no disputing that he does sometimes love it) + because he is instinctively afraid of attaining his object and completing the edifice he is constructing? + Who knows, perhaps he only loves that edifice from a distance, and is by no means + in love with it at close quarters; perhaps he only loves building it and does not want to live in it, + but will leave it, when completed, for the use of les animaux domestiques - + such as the ants, the sheep, and so on. Now the ants have quite a different taste. + They have a marvellous edifice of that pattern which endures for ever - the ant-heap. + With the ant-heap the respectable race of ants began and with the ant-heap they will probably end, + which does the greatest credit to their perseverance and good sense. But man is a frivolous and + incongruous creature, and perhaps, like a chess player, loves the process of the game, not the end of it. + And who knows (there is no saying with certainty), perhaps the only goal on earth + to which mankind is striving lies in this incessant process of attaining, in other words, + in life itself, and not in the thing to be attained, which must always be expressed as a formula, + as positive as twice two makes four, and such positiveness is not life, gentlemen, + but is the beginning of death. + "#, + r#" + But these are all golden dreams. Oh, tell me, who was it first announced, + who was it first proclaimed, that man only does nasty things because he does not know his own interests; + and that if he were enlightened, if his eyes were opened to his real normal interests, + man would at once cease to do nasty things, would at once become good and noble because, + being enlightened and understanding his real advantage, he would see his own advantage in the + good and nothing else, and we all know that not one man can, consciously, act against his own interests, + consequently, so to say, through necessity, he would begin doing good? Oh, the babe! Oh, the pure, + innocent child! Why, in the first place, when in all these thousands of years has there been a time + when man has acted only from his own interest? What is to be done with the millions of facts that bear + witness that men, consciously, that is fully understanding their real interests, have left them in the + background and have rushed headlong on another path, to meet peril and danger, + compelled to this course by nobody and by nothing, but, as it were, simply disliking the beaten track, + and have obstinately, wilfully, struck out another difficult, absurd way, seeking it almost in the darkness. + So, I suppose, this obstinacy and perversity were pleasanter to them than any advantage.... + Advantage! What is advantage? And will you take it upon yourself to define with perfect accuracy in what the + advantage of man consists? And what if it so happens that a man's advantage, sometimes, not only may, + but even must, consist in his desiring in certain cases what is harmful to himself and not advantageous. + And if so, if there can be such a case, the whole principle falls into dust. What do you think - + are there such cases? You laugh; laugh away, gentlemen, but only answer me: have man's advantages been + reckoned up with perfect certainty? Are there not some which not only have not been included but cannot + possibly be included under any classification? You see, you gentlemen have, to the best of my knowledge, + taken your whole register of human advantages from the averages of statistical figures and + politico-economical formulas. Your advantages are prosperity, wealth, freedom, peace - and so on, and so on. + So that the man who should, for instance, go openly and knowingly in opposition to all that list would to your thinking, + and indeed mine, too, of course, be an obscurantist or an absolute madman: would not he? But, you know, this is + what is surprising: why does it so happen that all these statisticians, sages and lovers of humanity, + when they reckon up human advantages invariably leave out one? They don't even take it into their reckoning + in the form in which it should be taken, and the whole reckoning depends upon that. It would be no greater matter, + they would simply have to take it, this advantage, and add it to the list. But the trouble is, that this strange + advantage does not fall under any classification and is not in place in any list. I have a friend for instance ... + Ech! gentlemen, but of course he is your friend, too; and indeed there is no one, no one to whom he is not a friend! + "#, + r#" + Yes, but here I come to a stop! Gentlemen, you must excuse me for being over-philosophical; + it's the result of forty years underground! Allow me to indulge my fancy. You see, gentlemen, reason is an excellent thing, + there's no disputing that, but reason is nothing but reason and satisfies only the rational side of man's nature, + while will is a manifestation of the whole life, that is, of the whole human life including reason and all the impulses. + And although our life, in this manifestation of it, is often worthless, yet it is life and not simply extracting square roots. + Here I, for instance, quite naturally want to live, in order to satisfy all my capacities for life, and not simply my capacity + for reasoning, that is, not simply one twentieth of my capacity for life. What does reason know? Reason only knows what it has + succeeded in learning (some things, perhaps, it will never learn; this is a poor comfort, but why not say so frankly?) + and human nature acts as a whole, with everything that is in it, consciously or unconsciously, and, even it if goes wrong, it lives. + I suspect, gentlemen, that you are looking at me with compassion; you tell me again that an enlightened and developed man, + such, in short, as the future man will be, cannot consciously desire anything disadvantageous to himself, that that can be proved mathematically. + I thoroughly agree, it can - by mathematics. But I repeat for the hundredth time, there is one case, one only, when man may consciously, purposely, + desire what is injurious to himself, what is stupid, very stupid - simply in order to have the right to desire for himself even what is very stupid + and not to be bound by an obligation to desire only what is sensible. Of course, this very stupid thing, this caprice of ours, may be in reality, + gentlemen, more advantageous for us than anything else on earth, especially in certain cases. And in particular it may be more advantageous than + any advantage even when it does us obvious harm, and contradicts the soundest conclusions of our reason concerning our advantage - + for in any circumstances it preserves for us what is most precious and most important - that is, our personality, our individuality. + Some, you see, maintain that this really is the most precious thing for mankind; choice can, of course, if it chooses, be in agreement + with reason; and especially if this be not abused but kept within bounds. It is profitable and some- times even praiseworthy. + But very often, and even most often, choice is utterly and stubbornly opposed to reason ... and ... and ... do you know that that, + too, is profitable, sometimes even praiseworthy? Gentlemen, let us suppose that man is not stupid. (Indeed one cannot refuse to suppose that, + if only from the one consideration, that, if man is stupid, then who is wise?) But if he is not stupid, he is monstrously ungrateful! + Phenomenally ungrateful. In fact, I believe that the best definition of man is the ungrateful biped. But that is not all, that is not his worst defect; + his worst defect is his perpetual moral obliquity, perpetual - from the days of the Flood to the Schleswig-Holstein period. + "#, +]; From b63191072aeb10adb68b425e83a6c6eb33cca06a Mon Sep 17 00:00:00 2001 From: ellnix Date: Mon, 24 Mar 2025 11:19:32 +0100 Subject: [PATCH 405/436] Sync exercise files with problem-specifications (#2061) No changes in tests, the updates in the instructions do not require any rust-specific appends as far as I can see. --- ...ut_pinned_problem_specifications_commit.sh | 2 +- .../affine-cipher/.docs/instructions.md | 2 +- .../saddle-points/.docs/instructions.md | 11 +-- .../practice/sieve/.docs/instructions.md | 75 +++++++++++++++++-- 4 files changed, 75 insertions(+), 15 deletions(-) diff --git a/bin/checkout_pinned_problem_specifications_commit.sh b/bin/checkout_pinned_problem_specifications_commit.sh index 3acae44dd..b59466798 100755 --- a/bin/checkout_pinned_problem_specifications_commit.sh +++ b/bin/checkout_pinned_problem_specifications_commit.sh @@ -3,7 +3,7 @@ set -euo pipefail cd "$(git rev-parse --show-toplevel)" -PINNED_COMMIT_HASH="47b3bb2a1c88bf93d540916477a3b71ff94e9964" +PINNED_COMMIT_HASH="1799950ecdc8273426de6426a5bed8c360c9f2c7" dir="$(./bin/get_problem_specifications_dir.sh)" diff --git a/exercises/practice/affine-cipher/.docs/instructions.md b/exercises/practice/affine-cipher/.docs/instructions.md index f6329db93..1603dbbce 100644 --- a/exercises/practice/affine-cipher/.docs/instructions.md +++ b/exercises/practice/affine-cipher/.docs/instructions.md @@ -20,7 +20,7 @@ Where: - `i` is the letter's index from `0` to the length of the alphabet - 1. - `m` is the length of the alphabet. - For the Roman alphabet `m` is `26`. + For the Latin alphabet `m` is `26`. - `a` and `b` are integers which make up the encryption key. Values `a` and `m` must be _coprime_ (or, _relatively prime_) for automatic decryption to succeed, i.e., they have number `1` as their only common factor (more information can be found in the [Wikipedia article about coprime integers][coprime-integers]). diff --git a/exercises/practice/saddle-points/.docs/instructions.md b/exercises/practice/saddle-points/.docs/instructions.md index c585568b4..f69cdab95 100644 --- a/exercises/practice/saddle-points/.docs/instructions.md +++ b/exercises/practice/saddle-points/.docs/instructions.md @@ -13,11 +13,12 @@ Or it might have one, or even several. Here is a grid that has exactly one candidate tree. ```text - 1 2 3 4 - |----------- -1 | 9 8 7 8 -2 | 5 3 2 4 <--- potential tree house at row 2, column 1, for tree with height 5 -3 | 6 6 7 1 + ↓ + 1 2 3 4 + |----------- + 1 | 9 8 7 8 +→ 2 |[5] 3 2 4 + 3 | 6 6 7 1 ``` - Row 2 has values 5, 3, 2, and 4. The largest value is 5. diff --git a/exercises/practice/sieve/.docs/instructions.md b/exercises/practice/sieve/.docs/instructions.md index 085c0a57d..71292e178 100644 --- a/exercises/practice/sieve/.docs/instructions.md +++ b/exercises/practice/sieve/.docs/instructions.md @@ -6,37 +6,96 @@ A prime number is a number larger than 1 that is only divisible by 1 and itself. For example, 2, 3, 5, 7, 11, and 13 are prime numbers. By contrast, 6 is _not_ a prime number as it not only divisible by 1 and itself, but also by 2 and 3. -To use the Sieve of Eratosthenes, you first create a list of all the numbers between 2 and your given number. -Then you repeat the following steps: +To use the Sieve of Eratosthenes, first, write out all the numbers from 2 up to and including your given number. +Then, follow these steps: -1. Find the next unmarked number in your list (skipping over marked numbers). +1. Find the next unmarked number (skipping over marked numbers). This is a prime number. 2. Mark all the multiples of that prime number as **not** prime. -You keep repeating these steps until you've gone through every number in your list. +Repeat the steps until you've gone through every number. At the end, all the unmarked numbers are prime. ~~~~exercism/note -The tests don't check that you've implemented the algorithm, only that you've come up with the correct list of primes. -To check you are implementing the Sieve correctly, a good first test is to check that you do not use division or remainder operations. +The Sieve of Eratosthenes marks off multiples of each prime using addition (repeatedly adding the prime) or multiplication (directly computing its multiples), rather than checking each number for divisibility. + +The tests don't check that you've implemented the algorithm, only that you've come up with the correct primes. ~~~~ ## Example Let's say you're finding the primes less than or equal to 10. -- List out 2, 3, 4, 5, 6, 7, 8, 9, 10, leaving them all unmarked. +- Write out 2, 3, 4, 5, 6, 7, 8, 9, 10, leaving them all unmarked. + + ```text + 2 3 4 5 6 7 8 9 10 + ``` + - 2 is unmarked and is therefore a prime. Mark 4, 6, 8 and 10 as "not prime". + + ```text + 2 3 [4] 5 [6] 7 [8] 9 [10] + ↑ + ``` + - 3 is unmarked and is therefore a prime. Mark 6 and 9 as not prime _(marking 6 is optional - as it's already been marked)_. + + ```text + 2 3 [4] 5 [6] 7 [8] [9] [10] + ↑ + ``` + - 4 is marked as "not prime", so we skip over it. + + ```text + 2 3 [4] 5 [6] 7 [8] [9] [10] + ↑ + ``` + - 5 is unmarked and is therefore a prime. Mark 10 as not prime _(optional - as it's already been marked)_. + + ```text + 2 3 [4] 5 [6] 7 [8] [9] [10] + ↑ + ``` + - 6 is marked as "not prime", so we skip over it. + + ```text + 2 3 [4] 5 [6] 7 [8] [9] [10] + ↑ + ``` + - 7 is unmarked and is therefore a prime. + + ```text + 2 3 [4] 5 [6] 7 [8] [9] [10] + ↑ + ``` + - 8 is marked as "not prime", so we skip over it. + + ```text + 2 3 [4] 5 [6] 7 [8] [9] [10] + ↑ + ``` + - 9 is marked as "not prime", so we skip over it. + + ```text + 2 3 [4] 5 [6] 7 [8] [9] [10] + ↑ + ``` + - 10 is marked as "not prime", so we stop as there are no more numbers to check. -You've examined all numbers and found 2, 3, 5, and 7 are still unmarked, which means they're the primes less than or equal to 10. + ```text + 2 3 [4] 5 [6] 7 [8] [9] [10] + ↑ + ``` + +You've examined all the numbers and found that 2, 3, 5, and 7 are still unmarked, meaning they're the primes less than or equal to 10. From dc78814c50266cf2c9a649af7f0f3f226c905b67 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Apr 2025 11:51:32 +0200 Subject: [PATCH 406/436] build(deps): bump dtolnay/rust-toolchain (#2063) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/tests.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 632dcc875..14c1076da 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -64,7 +64,7 @@ jobs: run: git fetch --depth=1 origin main - name: Setup toolchain - uses: dtolnay/rust-toolchain@c5a29ddb4d9d194e7c84ec8c3fba61b1c31fee8c + uses: dtolnay/rust-toolchain@56f84321dbccf38fb67ce29ab63e4754056677e0 with: toolchain: ${{ matrix.rust }} @@ -82,7 +82,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup toolchain - uses: dtolnay/rust-toolchain@c5a29ddb4d9d194e7c84ec8c3fba61b1c31fee8c + uses: dtolnay/rust-toolchain@56f84321dbccf38fb67ce29ab63e4754056677e0 with: toolchain: stable @@ -104,7 +104,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup toolchain - uses: dtolnay/rust-toolchain@c5a29ddb4d9d194e7c84ec8c3fba61b1c31fee8c + uses: dtolnay/rust-toolchain@56f84321dbccf38fb67ce29ab63e4754056677e0 with: toolchain: stable @@ -127,7 +127,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup toolchain - uses: dtolnay/rust-toolchain@c5a29ddb4d9d194e7c84ec8c3fba61b1c31fee8c + uses: dtolnay/rust-toolchain@56f84321dbccf38fb67ce29ab63e4754056677e0 with: toolchain: stable components: clippy @@ -147,7 +147,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup nightly toolchain - uses: dtolnay/rust-toolchain@c5a29ddb4d9d194e7c84ec8c3fba61b1c31fee8c + uses: dtolnay/rust-toolchain@56f84321dbccf38fb67ce29ab63e4754056677e0 with: toolchain: nightly From fdce433228cf42370634764318afd8a9138c6653 Mon Sep 17 00:00:00 2001 From: Roman Vlasenko Date: Wed, 16 Apr 2025 11:37:31 +0300 Subject: [PATCH 407/436] Fix Working with strings in Rust link (#2064) --- concepts/string-vs-str/links.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/concepts/string-vs-str/links.json b/concepts/string-vs-str/links.json index f6fcfc52b..06c5e6ec4 100644 --- a/concepts/string-vs-str/links.json +++ b/concepts/string-vs-str/links.json @@ -4,7 +4,7 @@ "description": "String types in RBE" }, { - "url": "https://fasterthanli.me/blog/2020/working-with-strings-in-rust/", + "url": "https://fasterthanli.me/articles/working-with-strings-in-rust", "description": "fasterthanli.me's Working with strings in Rust" }, { From 9bc0745dcdd65bfa1b2c68d48c9b614e0b8c2014 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Wed, 16 Apr 2025 10:59:29 +0200 Subject: [PATCH 408/436] Fix clippy warnings (#2065) --- exercises/practice/all-your-base/.meta/example.rs | 2 +- exercises/practice/dominoes/.meta/example.rs | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/exercises/practice/all-your-base/.meta/example.rs b/exercises/practice/all-your-base/.meta/example.rs index 1a8521fcd..3aca42c1d 100644 --- a/exercises/practice/all-your-base/.meta/example.rs +++ b/exercises/practice/all-your-base/.meta/example.rs @@ -36,7 +36,7 @@ pub enum Error { /// Notes: /// * The empty slice ( "[]" ) is equal to the number 0. /// * Never output leading 0 digits. However, your function must be able to -/// process input with leading 0 digits. +/// process input with leading 0 digits. /// pub fn convert>( digits: P, diff --git a/exercises/practice/dominoes/.meta/example.rs b/exercises/practice/dominoes/.meta/example.rs index bf166e810..93c2543e0 100644 --- a/exercises/practice/dominoes/.meta/example.rs +++ b/exercises/practice/dominoes/.meta/example.rs @@ -1,5 +1,3 @@ -use std::iter; - pub type Domino = (u8, u8); /// A table keeping track of available dominoes. @@ -14,7 +12,7 @@ struct AvailabilityTable { impl AvailabilityTable { fn new() -> AvailabilityTable { AvailabilityTable { - m: iter::repeat(0).take(6 * 6).collect(), + m: std::iter::repeat_n(0, 6 * 6).collect(), } } From a64178c229bb9cb3b9f2ba5c9fba16cbb7d03aee Mon Sep 17 00:00:00 2001 From: Garry Filakhtov Date: Thu, 17 Apr 2025 22:47:28 +1000 Subject: [PATCH 409/436] Fix appended instructions for Say exercise (#2066) The "Say" exercise contains the statement that is not true, at least no longer: > There is a -1 version of a test case, but it is commented out. If your function is implemented properly, the -1 test case should not compile. Remove this paragraph to avoid confusion. --- exercises/practice/say/.docs/instructions.append.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/exercises/practice/say/.docs/instructions.append.md b/exercises/practice/say/.docs/instructions.append.md index 41400ee83..ce243386b 100644 --- a/exercises/practice/say/.docs/instructions.append.md +++ b/exercises/practice/say/.docs/instructions.append.md @@ -8,11 +8,6 @@ errors for out of range, we are using Rust's strong type system to limit input. It is much easier to make a function deal with all valid inputs, rather than requiring the user of your module to handle errors. -There is a -1 version of a test case, but it is commented out. -If your function is implemented properly, the -1 test case should not compile. - -Adding 'and' into number text has not been implemented in test cases. - ### Extension Add capability of converting up to the max value for u64: `18_446_744_073_709_551_615`. From 82c4504dde2d5a277f0c748e9eadc8e135df6447 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Sat, 19 Apr 2025 21:11:51 +0200 Subject: [PATCH 410/436] etl: downgrade to easy difficulty (#2067) forum discussion: https://forum.exercism.org/t/rust-etl-exercise-is-a-bit-odd/16921 --- config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.json b/config.json index abcfac83b..a36553efd 100644 --- a/config.json +++ b/config.json @@ -679,7 +679,7 @@ "uuid": "0c8eeef7-4bab-4cf9-9047-c208b5618312", "practices": [], "prerequisites": [], - "difficulty": 4, + "difficulty": 1, "topics": [ "btree" ] From 5deb659b82533b975c361b4045b5540d01ae9952 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 May 2025 12:31:19 +0200 Subject: [PATCH 411/436] build(deps): bump dtolnay/rust-toolchain (#2068) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/tests.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 14c1076da..b18dffb49 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -64,7 +64,7 @@ jobs: run: git fetch --depth=1 origin main - name: Setup toolchain - uses: dtolnay/rust-toolchain@56f84321dbccf38fb67ce29ab63e4754056677e0 + uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b with: toolchain: ${{ matrix.rust }} @@ -82,7 +82,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup toolchain - uses: dtolnay/rust-toolchain@56f84321dbccf38fb67ce29ab63e4754056677e0 + uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b with: toolchain: stable @@ -104,7 +104,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup toolchain - uses: dtolnay/rust-toolchain@56f84321dbccf38fb67ce29ab63e4754056677e0 + uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b with: toolchain: stable @@ -127,7 +127,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup toolchain - uses: dtolnay/rust-toolchain@56f84321dbccf38fb67ce29ab63e4754056677e0 + uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b with: toolchain: stable components: clippy @@ -147,7 +147,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup nightly toolchain - uses: dtolnay/rust-toolchain@56f84321dbccf38fb67ce29ab63e4754056677e0 + uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b with: toolchain: nightly From 1f9f9ed85e2cc785b9d8e1b493ccb606195fcb14 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Thu, 26 Jun 2025 05:35:45 +0200 Subject: [PATCH 412/436] Ensure problem-specifications is symlinked (#2072) This is especially relevant for the add-exercise workflow, which will suggest slugs of unimplemented exercises from problem-specifications only if the symlink exists. Otherwise no suggestions are made without any hint that something went wrong. --- justfile | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/justfile b/justfile index 812ce78cf..e2a6042da 100644 --- a/justfile +++ b/justfile @@ -23,8 +23,11 @@ test: cd rust-tooling && cargo test ./bin/format_exercises.sh ; git diff --exit-code -add-exercise *args="": +symlink-problem-specifications: + @ [ -L problem-specifications ] || ./bin/symlink_problem_specifications.sh + +add-exercise *args="": symlink-problem-specifications cd rust-tooling/generate; cargo run --quiet --release -- add {{ args }} -update-exercise *args="": +update-exercise *args="": symlink-problem-specifications cd rust-tooling/generate; cargo run --quiet --release -- update {{ args }} From 00f740eee1f0247dfab1cb0599777999599b6fe4 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Thu, 26 Jun 2025 05:35:58 +0200 Subject: [PATCH 413/436] Update problem-specifications (#2073) --- ...ut_pinned_problem_specifications_commit.sh | 2 +- .../practice/crypto-square/.meta/tests.toml | 5 +++ .../crypto-square/tests/crypto_square.rs | 2 +- .../.meta/test_template.tera | 4 ++- .../largest-series-product/.meta/tests.toml | 10 ++++++ exercises/practice/wordy/.meta/tests.toml | 12 +++++++ exercises/practice/wordy/tests/wordy.rs | 36 +++++++++++++++++++ 7 files changed, 68 insertions(+), 3 deletions(-) diff --git a/bin/checkout_pinned_problem_specifications_commit.sh b/bin/checkout_pinned_problem_specifications_commit.sh index b59466798..b98e4c3b2 100755 --- a/bin/checkout_pinned_problem_specifications_commit.sh +++ b/bin/checkout_pinned_problem_specifications_commit.sh @@ -3,7 +3,7 @@ set -euo pipefail cd "$(git rev-parse --show-toplevel)" -PINNED_COMMIT_HASH="1799950ecdc8273426de6426a5bed8c360c9f2c7" +PINNED_COMMIT_HASH="c4043d661357fc2d7ad07b8359adc92d283a5a00" dir="$(./bin/get_problem_specifications_dir.sh)" diff --git a/exercises/practice/crypto-square/.meta/tests.toml b/exercises/practice/crypto-square/.meta/tests.toml index 085d142ea..94ef0819f 100644 --- a/exercises/practice/crypto-square/.meta/tests.toml +++ b/exercises/practice/crypto-square/.meta/tests.toml @@ -32,3 +32,8 @@ description = "8 character plaintext results in 3 chunks, the last one with a tr [fbcb0c6d-4c39-4a31-83f6-c473baa6af80] description = "54 character plaintext results in 7 chunks, the last two with trailing spaces" +include = false + +[33fd914e-fa44-445b-8f38-ff8fbc9fe6e6] +description = "54 character plaintext results in 8 chunks, the last two with trailing spaces" +reimplements = "fbcb0c6d-4c39-4a31-83f6-c473baa6af80" diff --git a/exercises/practice/crypto-square/tests/crypto_square.rs b/exercises/practice/crypto-square/tests/crypto_square.rs index 7fac9531f..dd12dc922 100644 --- a/exercises/practice/crypto-square/tests/crypto_square.rs +++ b/exercises/practice/crypto-square/tests/crypto_square.rs @@ -57,7 +57,7 @@ fn test_8_character_plaintext_results_in_3_chunks_the_last_one_with_a_trailing_s #[test] #[ignore] -fn test_54_character_plaintext_results_in_7_chunks_the_last_two_with_trailing_spaces() { +fn test_54_character_plaintext_results_in_8_chunks_the_last_two_with_trailing_spaces() { let actual = encrypt("If man was meant to stay on the ground, god would have given us roots."); let expected = "imtgdvs fearwer mayoogo anouuio ntnnlvt wttddes aohghn sseoau "; assert_eq!(&actual, expected); diff --git a/exercises/practice/largest-series-product/.meta/test_template.tera b/exercises/practice/largest-series-product/.meta/test_template.tera index 5fbeb4d91..46a74e3d4 100644 --- a/exercises/practice/largest-series-product/.meta/test_template.tera +++ b/exercises/practice/largest-series-product/.meta/test_template.tera @@ -11,11 +11,13 @@ fn return_is_a_result() { #[ignore] fn {{ test.description | make_ident }}() { {% if test.expected.error is defined %} - {% if test.expected.error == "span must be smaller than string length" %} + {% if test.expected.error == "span must not exceed string length" %} {% set error_type = "SpanTooLong" %} {% elif test.expected.error == "digits input must only contain digits" %} {% set invalid_char = test.input.digits | split(pat="") | sort | last %} {% set error_type = "InvalidDigit('" ~ invalid_char ~ "')" %} + {% else %} + {{ throw(message="unknown error: " ~ test.expected.error) }} {% endif %} assert_eq!(Err(Error::{{error_type}}), lsp("{{test.input.digits}}", {{test.input.span}})); diff --git a/exercises/practice/largest-series-product/.meta/tests.toml b/exercises/practice/largest-series-product/.meta/tests.toml index 01f67feb3..b6416b5a0 100644 --- a/exercises/practice/largest-series-product/.meta/tests.toml +++ b/exercises/practice/largest-series-product/.meta/tests.toml @@ -38,6 +38,11 @@ description = "reports zero if all spans include zero" [5d81aaf7-4f67-4125-bf33-11493cc7eab7] description = "rejects span longer than string length" +include = false + +[0ae1ce53-d9ba-41bb-827f-2fceb64f058b] +description = "rejects span longer than string length" +reimplements = "5d81aaf7-4f67-4125-bf33-11493cc7eab7" [06bc8b90-0c51-4c54-ac22-3ec3893a079e] description = "reports 1 for empty string and empty product (0 span)" @@ -47,6 +52,11 @@ description = "reports 1 for nonempty string and empty product (0 span)" [6d96c691-4374-4404-80ee-2ea8f3613dd4] description = "rejects empty string and nonzero span" +include = false + +[6cf66098-a6af-4223-aab1-26aeeefc7402] +description = "rejects empty string and nonzero span" +reimplements = "6d96c691-4374-4404-80ee-2ea8f3613dd4" [7a38f2d6-3c35-45f6-8d6f-12e6e32d4d74] description = "rejects invalid character in digits" diff --git a/exercises/practice/wordy/.meta/tests.toml b/exercises/practice/wordy/.meta/tests.toml index f812dfa98..a0a83ed0b 100644 --- a/exercises/practice/wordy/.meta/tests.toml +++ b/exercises/practice/wordy/.meta/tests.toml @@ -12,9 +12,21 @@ [88bf4b28-0de3-4883-93c7-db1b14aa806e] description = "just a number" +[18983214-1dfc-4ebd-ac77-c110dde699ce] +description = "just a zero" + +[607c08ee-2241-4288-916d-dae5455c87e6] +description = "just a negative number" + [bb8c655c-cf42-4dfc-90e0-152fcfd8d4e0] description = "addition" +[bb9f2082-171c-46ad-ad4e-c3f72087c1b5] +description = "addition with a left hand zero" + +[6fa05f17-405a-4742-80ae-5d1a8edb0d5d] +description = "addition with a right hand zero" + [79e49e06-c5ae-40aa-a352-7a3a01f70015] description = "more addition" diff --git a/exercises/practice/wordy/tests/wordy.rs b/exercises/practice/wordy/tests/wordy.rs index 6d5ded615..86b565c22 100644 --- a/exercises/practice/wordy/tests/wordy.rs +++ b/exercises/practice/wordy/tests/wordy.rs @@ -8,6 +8,24 @@ fn just_a_number() { assert_eq!(output, expected); } +#[test] +#[ignore] +fn just_a_zero() { + let input = "What is 0?"; + let output = answer(input); + let expected = Some(0); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn just_a_negative_number() { + let input = "What is -123?"; + let output = answer(input); + let expected = Some(-123); + assert_eq!(output, expected); +} + #[test] #[ignore] fn addition() { @@ -17,6 +35,24 @@ fn addition() { assert_eq!(output, expected); } +#[test] +#[ignore] +fn addition_with_a_left_hand_zero() { + let input = "What is 0 plus 2?"; + let output = answer(input); + let expected = Some(2); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn addition_with_a_right_hand_zero() { + let input = "What is 3 plus 0?"; + let output = answer(input); + let expected = Some(3); + assert_eq!(output, expected); +} + #[test] #[ignore] fn more_addition() { From 62ea8fd044cb17535ab8095daa417d5e8ec3377c Mon Sep 17 00:00:00 2001 From: Jagdish Prajapati Date: Tue, 1 Jul 2025 20:49:55 +0530 Subject: [PATCH 414/436] Create run-configlet-sync.yml (#2074) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### 🛠️ Automating Configlet Sync This PR introduces a workflow that automates `configlet sync` for test (only an issue is raised), docs, metadata, and filepaths. There are two jobs in the main workflow; we can skip either one of them. Let me know how you'd like to set it up! ⏱️ The workflow is currently scheduled to run on the **15th of every month** via cron. Let me know if you'd prefer a different schedule (e.g., weekly, the 1st of each month, etc.). This setup is already being used across multiple tracks!. For more context and discussion, see: 🔗 https://forum.exercism.org/t/automating-syncing-with-github-actions-final-testing-going-on-for-java-track-open-to-more-tracks/17807 --- .github/workflows/run-configlet-sync.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .github/workflows/run-configlet-sync.yml diff --git a/.github/workflows/run-configlet-sync.yml b/.github/workflows/run-configlet-sync.yml new file mode 100644 index 000000000..b49cbffe8 --- /dev/null +++ b/.github/workflows/run-configlet-sync.yml @@ -0,0 +1,10 @@ +name: Run Configlet Sync + +on: + workflow_dispatch: + schedule: + - cron: '0 0 15 * *' + +jobs: + call-gha-workflow: + uses: exercism/github-actions/.github/workflows/configlet-sync.yml@main From 01209f4c77a101ad4608ab998556605405357da9 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 1 Jul 2025 17:59:59 +0200 Subject: [PATCH 415/436] Fix clippy warnings (#2075) --- exercises/practice/armstrong-numbers/.meta/example.rs | 2 +- exercises/practice/clock/.meta/example.rs | 2 +- exercises/practice/decimal/.meta/example.rs | 6 +++--- exercises/practice/dominoes/.meta/example.rs | 2 +- exercises/practice/grep/.meta/example.rs | 2 +- exercises/practice/raindrops/.meta/example.rs | 2 +- exercises/practice/roman-numerals/.meta/example.rs | 2 +- exercises/practice/two-fer/.meta/example.rs | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/exercises/practice/armstrong-numbers/.meta/example.rs b/exercises/practice/armstrong-numbers/.meta/example.rs index a14413c63..3760123d6 100644 --- a/exercises/practice/armstrong-numbers/.meta/example.rs +++ b/exercises/practice/armstrong-numbers/.meta/example.rs @@ -1,5 +1,5 @@ pub fn is_armstrong_number(num: u32) -> bool { - let s = format!("{}", num); + let s = format!("{num}"); let l = s.len(); s.chars() .map(|c| c.to_digit(10).unwrap().pow(l as u32)) diff --git a/exercises/practice/clock/.meta/example.rs b/exercises/practice/clock/.meta/example.rs index af1ccfe1d..d3adbb350 100644 --- a/exercises/practice/clock/.meta/example.rs +++ b/exercises/practice/clock/.meta/example.rs @@ -9,7 +9,7 @@ impl fmt::Display for Clock { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let hours = self.minutes / 60; let mins = self.minutes % 60; - write!(f, "{:02}:{:02}", hours, mins) + write!(f, "{hours:02}:{mins:02}") } } diff --git a/exercises/practice/decimal/.meta/example.rs b/exercises/practice/decimal/.meta/example.rs index 8b44f6cee..a11f60f8f 100644 --- a/exercises/practice/decimal/.meta/example.rs +++ b/exercises/practice/decimal/.meta/example.rs @@ -159,12 +159,12 @@ impl fmt::Display for Decimal { // left-padded with zeroes let digits = format!("{:0>width$}", self.digits, width = self.decimal_index); if self.decimal_index == digits.len() { - write!(f, "0.{}", digits) + write!(f, "0.{digits}") } else if self.decimal_index == 0 { - write!(f, "{}", digits) + write!(f, "{digits}") } else { let (before_index, after_index) = digits.split_at(digits.len() - self.decimal_index); - write!(f, "{}.{}", before_index, after_index) + write!(f, "{before_index}.{after_index}") } } } diff --git a/exercises/practice/dominoes/.meta/example.rs b/exercises/practice/dominoes/.meta/example.rs index 93c2543e0..14ed1ef24 100644 --- a/exercises/practice/dominoes/.meta/example.rs +++ b/exercises/practice/dominoes/.meta/example.rs @@ -50,7 +50,7 @@ impl AvailabilityTable { } } else { // For this toy code hard explicit fail is best - panic!("remove for 0 stones: ({:?}, {:?})", x, y) + panic!("remove for 0 stones: ({x:?}, {y:?})") } } diff --git a/exercises/practice/grep/.meta/example.rs b/exercises/practice/grep/.meta/example.rs index 357162b0c..917f39ce6 100644 --- a/exercises/practice/grep/.meta/example.rs +++ b/exercises/practice/grep/.meta/example.rs @@ -88,7 +88,7 @@ pub fn grep(pattern: &str, flags: &Flags, files: &[&str]) -> Result, } if is_multiple_file_search { - result.insert_str(0, &format!("{}:", file_name)) + result.insert_str(0, &format!("{file_name}:")) } if flags.print_file_name { diff --git a/exercises/practice/raindrops/.meta/example.rs b/exercises/practice/raindrops/.meta/example.rs index 2ff3d312b..b580bd073 100644 --- a/exercises/practice/raindrops/.meta/example.rs +++ b/exercises/practice/raindrops/.meta/example.rs @@ -13,7 +13,7 @@ pub fn raindrops(n: u32) -> String { drops.push_str("Plong"); } if drops.is_empty() { - let s = format!("{}", n); + let s = format!("{n}"); drops.push_str(&s); } drops diff --git a/exercises/practice/roman-numerals/.meta/example.rs b/exercises/practice/roman-numerals/.meta/example.rs index 371974a4b..a1b91521b 100644 --- a/exercises/practice/roman-numerals/.meta/example.rs +++ b/exercises/practice/roman-numerals/.meta/example.rs @@ -36,7 +36,7 @@ impl fmt::Display for Roman { start -= numeric; } } - write!(f, "{}", result) + write!(f, "{result}") } } diff --git a/exercises/practice/two-fer/.meta/example.rs b/exercises/practice/two-fer/.meta/example.rs index 7b4221dd6..145b4ad15 100644 --- a/exercises/practice/two-fer/.meta/example.rs +++ b/exercises/practice/two-fer/.meta/example.rs @@ -1,6 +1,6 @@ pub fn twofer(name: &str) -> String { match name { "" => "One for you, one for me.".to_string(), - _ => format!("One for {}, one for me.", name), + _ => format!("One for {name}, one for me."), } } From d03a4e7915b1c86423ec37b4972c467ea68fa3e1 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Thu, 3 Jul 2025 22:26:54 +0200 Subject: [PATCH 416/436] Improve test no_underscore_prefixed_idents (#2078) * accumulate doesn't need to be in the exceptions list because it doesn't compile independent of the argument name and it is allowed to do so. * xorcism doesn't need to be in the exceptions list because the compiler doesn't actually complain about the unused field if the name doesn't have a prefix. This only applies to fields of type `PhantomData`. I believe this must be a recent addition. It's nice, because the compiler can know that `PhantomData` is obviously never read. * Fix some typos in the documentation of the test. --- exercises/practice/accumulate/.meta/config.json | 2 +- exercises/practice/accumulate/src/lib.rs | 4 ++-- exercises/practice/xorcism/src/lib.rs | 2 +- .../ci-tests/tests/no_underscore_prefixed_idents.rs | 6 ++---- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/exercises/practice/accumulate/.meta/config.json b/exercises/practice/accumulate/.meta/config.json index 6f756d434..3864fa9c9 100644 --- a/exercises/practice/accumulate/.meta/config.json +++ b/exercises/practice/accumulate/.meta/config.json @@ -34,6 +34,6 @@ "source": "Conversation with James Edward Gray II", "source_url": "http://graysoftinc.com/", "custom": { - "allowed-to-not-compile": "Stub doesn't compile because there is no type specified for _function and _closure arguments. This exercise teaches students about the use of function pointers and closures, therefore specifying the type of arguments for them would reduce learning." + "allowed-to-not-compile": "Stub doesn't compile because there is no type specified for the `function` argument. This exercise teaches students about the use of function pointers and closures, therefore specifying the type of arguments for them would reduce learning." } } diff --git a/exercises/practice/accumulate/src/lib.rs b/exercises/practice/accumulate/src/lib.rs index 2ed0e9e22..3c17aa1dd 100644 --- a/exercises/practice/accumulate/src/lib.rs +++ b/exercises/practice/accumulate/src/lib.rs @@ -1,4 +1,4 @@ -/// What should the type of _function be? -pub fn map(input: Vec, _function: ???) -> Vec { +/// What should the type of function be? +pub fn map(input: Vec, function: ???) -> Vec { todo!("Transform input vector {input:?} using passed function"); } diff --git a/exercises/practice/xorcism/src/lib.rs b/exercises/practice/xorcism/src/lib.rs index a471a03f1..a462fefbb 100644 --- a/exercises/practice/xorcism/src/lib.rs +++ b/exercises/practice/xorcism/src/lib.rs @@ -3,7 +3,7 @@ pub struct Xorcism<'a> { // This field is just to suppress compiler complaints; // feel free to delete it at any point. - _phantom: std::marker::PhantomData<&'a u8>, + phantom: std::marker::PhantomData<&'a u8>, } impl<'a> Xorcism<'a> { diff --git a/rust-tooling/ci-tests/tests/no_underscore_prefixed_idents.rs b/rust-tooling/ci-tests/tests/no_underscore_prefixed_idents.rs index c2cabcee9..7fa88b88a 100644 --- a/rust-tooling/ci-tests/tests/no_underscore_prefixed_idents.rs +++ b/rust-tooling/ci-tests/tests/no_underscore_prefixed_idents.rs @@ -21,7 +21,7 @@ //! //! ```rust //! pub fn fn_to_implement(parameter: i32) -> i32 { -//! todo!("use {parameter} to solve the exercise") +//! todo!("use {parameter:?} to solve the exercise") //! } //! ``` //! @@ -34,7 +34,7 @@ //! //! The second approach does have a disadvantage: it requires the parameter //! to be `Debug`. This is usually not a problem, most of our test inputs are -//! indedd `Debug`, but it becomes a problem with generics. If a parameter is +//! indeed `Debug`, but it becomes a problem with generics. If a parameter is //! generic, there must be a `Debug` bound on it. That would usually be //! fulfilled, but it could be confusing to the students why the bound exists. //! In that case, the first approach may be used as a fallback. @@ -46,7 +46,6 @@ use glob::glob; use utils::fs::cd_into_repo_root; static EXCEPTIONS: &[&str] = &[ - "accumulate", // has generics (not the stub, but the solution) "list-ops", // has generics "circular-buffer", // has generics "custom-set", // has generics @@ -57,7 +56,6 @@ static EXCEPTIONS: &[&str] = &[ "roman-numerals", // std::fmt::Formatter is not Debug "simple-linked-list", // has generics "sublist", // has generics - "xorcism", // has PhantomData ]; fn line_is_not_a_comment(line: &&str) -> bool { From d52ea8500aca698e2dbed95679fd8fced7991a29 Mon Sep 17 00:00:00 2001 From: keiravillekode Date: Sun, 6 Jul 2025 21:37:47 +1000 Subject: [PATCH 417/436] flower-field replaces minesweeper (#2079) --- config.json | 12 ++ .../flower-field/.docs/instructions.append.md | 10 + .../flower-field/.docs/instructions.md | 26 +++ .../flower-field/.docs/introduction.md | 7 + exercises/practice/flower-field/.gitignore | 2 + .../practice/flower-field/.meta/config.json | 39 ++++ .../practice/flower-field/.meta/example.rs | 66 ++++++ .../flower-field/.meta/test_template.tera | 33 +++ .../practice/flower-field/.meta/tests.toml | 46 +++++ exercises/practice/flower-field/Cargo.toml | 9 + exercises/practice/flower-field/src/lib.rs | 5 + .../flower-field/tests/flower_field.rs | 190 ++++++++++++++++++ 12 files changed, 445 insertions(+) create mode 100644 exercises/practice/flower-field/.docs/instructions.append.md create mode 100644 exercises/practice/flower-field/.docs/instructions.md create mode 100644 exercises/practice/flower-field/.docs/introduction.md create mode 100644 exercises/practice/flower-field/.gitignore create mode 100644 exercises/practice/flower-field/.meta/config.json create mode 100644 exercises/practice/flower-field/.meta/example.rs create mode 100644 exercises/practice/flower-field/.meta/test_template.tera create mode 100644 exercises/practice/flower-field/.meta/tests.toml create mode 100644 exercises/practice/flower-field/Cargo.toml create mode 100644 exercises/practice/flower-field/src/lib.rs create mode 100644 exercises/practice/flower-field/tests/flower_field.rs diff --git a/config.json b/config.json index a36553efd..f596be3f8 100644 --- a/config.json +++ b/config.json @@ -284,6 +284,17 @@ "generic_over_type" ] }, + { + "slug": "flower-field", + "name": "Flower Field", + "uuid": "117d6a25-960e-4d53-8347-a20490f60f36", + "practices": [], + "prerequisites": [], + "difficulty": 7, + "topics": [ + "board_state" + ] + }, { "slug": "minesweeper", "name": "Minesweeper", @@ -291,6 +302,7 @@ "practices": [], "prerequisites": [], "difficulty": 7, + "status": "deprecated", "topics": [ "board_state" ] diff --git a/exercises/practice/flower-field/.docs/instructions.append.md b/exercises/practice/flower-field/.docs/instructions.append.md new file mode 100644 index 000000000..51d0953a4 --- /dev/null +++ b/exercises/practice/flower-field/.docs/instructions.append.md @@ -0,0 +1,10 @@ +# Instructions append + +## Performance Hint + +All the inputs and outputs are in ASCII. +Rust `String`s and `&str` are utf8, so while one might expect `"Hello".chars()` to be simple, it actually has to check each char to see if it's 1, 2, 3 or 4 `u8`s long. +If we know a `&str` is ASCII then we can call `.as_bytes()` and refer to the underlying data as a `&[u8]` (byte slice). +Iterating over a slice of ASCII bytes is much quicker as there are no codepoints involved - every ASCII byte is one `u8` long. + +Can you complete the challenge without cloning the input? diff --git a/exercises/practice/flower-field/.docs/instructions.md b/exercises/practice/flower-field/.docs/instructions.md new file mode 100644 index 000000000..bbdae0c2c --- /dev/null +++ b/exercises/practice/flower-field/.docs/instructions.md @@ -0,0 +1,26 @@ +# Instructions + +Your task is to add flower counts to empty squares in a completed Flower Field garden. +The garden itself is a rectangle board composed of squares that are either empty (`' '`) or a flower (`'*'`). + +For each empty square, count the number of flowers adjacent to it (horizontally, vertically, diagonally). +If the empty square has no adjacent flowers, leave it empty. +Otherwise replace it with the count of adjacent flowers. + +For example, you may receive a 5 x 4 board like this (empty spaces are represented here with the '·' character for display on screen): + +```text +·*·*· +··*·· +··*·· +····· +``` + +Which your code should transform into this: + +```text +1*3*1 +13*31 +·2*2· +·111· +``` diff --git a/exercises/practice/flower-field/.docs/introduction.md b/exercises/practice/flower-field/.docs/introduction.md new file mode 100644 index 000000000..af9b61536 --- /dev/null +++ b/exercises/practice/flower-field/.docs/introduction.md @@ -0,0 +1,7 @@ +# Introduction + +[Flower Field][history] is a compassionate reimagining of the popular game Minesweeper. +The object of the game is to find all the flowers in the garden using numeric hints that indicate how many flowers are directly adjacent (horizontally, vertically, diagonally) to a square. +"Flower Field" shipped in regional versions of Microsoft Windows in Italy, Germany, South Korea, Japan and Taiwan. + +[history]: https://web.archive.org/web/20020409051321fw_/http://rcm.usr.dsi.unimi.it/rcmweb/fnm/ diff --git a/exercises/practice/flower-field/.gitignore b/exercises/practice/flower-field/.gitignore new file mode 100644 index 000000000..96ef6c0b9 --- /dev/null +++ b/exercises/practice/flower-field/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/exercises/practice/flower-field/.meta/config.json b/exercises/practice/flower-field/.meta/config.json new file mode 100644 index 000000000..aa8cb9c60 --- /dev/null +++ b/exercises/practice/flower-field/.meta/config.json @@ -0,0 +1,39 @@ +{ + "authors": [ + "EduardoBautista" + ], + "contributors": [ + "ashleygwilliams", + "coriolinus", + "cwhakes", + "EduardoBautista", + "efx", + "ErikSchierboom", + "ffflorian", + "IanWhitney", + "keiravillekode", + "kytrinyx", + "lutostag", + "mkantor", + "nfiles", + "petertseng", + "rofrol", + "stringparser", + "workingjubilee", + "xakon", + "ZapAnton" + ], + "files": { + "solution": [ + "src/lib.rs", + "Cargo.toml" + ], + "test": [ + "tests/flower_field.rs" + ], + "example": [ + ".meta/example.rs" + ] + }, + "blurb": "Mark all the flowers in a garden." +} diff --git a/exercises/practice/flower-field/.meta/example.rs b/exercises/practice/flower-field/.meta/example.rs new file mode 100644 index 000000000..830ecf5f9 --- /dev/null +++ b/exercises/practice/flower-field/.meta/example.rs @@ -0,0 +1,66 @@ +struct Board { + pieces: Vec>, + num_rows: usize, + num_cols: usize, +} + +impl Board { + fn annotated(&self) -> Vec { + (0..self.num_rows).map(|y| self.annotated_row(y)).collect() + } + + fn annotated_row(&self, y: usize) -> String { + self.pieces[y] + .iter() + .enumerate() + .map(|(x, &c)| { + if c == ' ' { + self.count_neighbouring_flowers_char(x, y) + } else { + c + } + }) + .collect::() + } + + fn count_neighbouring_flowers_char(&self, x: usize, y: usize) -> char { + let mut count = 0; + for x1 in neighbouring_points(x, self.num_cols) { + for y1 in neighbouring_points(y, self.num_rows) { + let piece = self.pieces[y1][x1]; + if piece == '*' { + count += 1; + } + } + } + if count == 0 { + ' ' + } else { + (b'0' + count) as char + } + } +} + +pub fn annotate(pieces: &[&str]) -> Vec { + if pieces.is_empty() { + return Vec::new(); + } + let pieces_vec = pieces.iter().map(|&r| r.chars().collect()).collect(); + Board { + pieces: pieces_vec, + num_rows: pieces.len(), + num_cols: pieces[0].len(), + } + .annotated() +} + +fn neighbouring_points(x: usize, limit: usize) -> Vec { + let mut offsets = vec![x]; + if x >= 1 { + offsets.push(x - 1); + } + if x + 2 <= limit { + offsets.push(x + 1); + } + offsets +} diff --git a/exercises/practice/flower-field/.meta/test_template.tera b/exercises/practice/flower-field/.meta/test_template.tera new file mode 100644 index 000000000..3d6298899 --- /dev/null +++ b/exercises/practice/flower-field/.meta/test_template.tera @@ -0,0 +1,33 @@ +use flower_field::*; + +{% for test in cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + {% if test.input.garden | length < 2 -%} + let input = &[ + {%- for line in test.input.garden %} + {{ line | json_encode() }}, + {%- endfor %} + ]; + let expected{% if test.expected | length == 0 %}: &[&str]{% endif %} = &[ + {%- for line in test.expected %} + {{ line | json_encode() }}, + {%- endfor %} + ]; + {% else -%} + #[rustfmt::skip] + let (input, expected) = (&[ + {%- for line in test.input.garden %} + {{ line | json_encode() }}, + {%- endfor %} + ], &[ + {%- for line in test.expected %} + {{ line | json_encode() }}, + {%- endfor %} + ]); + {% endif -%} + let actual = annotate(input); + assert_eq!(actual, expected); +} +{% endfor -%} diff --git a/exercises/practice/flower-field/.meta/tests.toml b/exercises/practice/flower-field/.meta/tests.toml new file mode 100644 index 000000000..c2b24fdaf --- /dev/null +++ b/exercises/practice/flower-field/.meta/tests.toml @@ -0,0 +1,46 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[237ff487-467a-47e1-9b01-8a891844f86c] +description = "no rows" + +[4b4134ec-e20f-439c-a295-664c38950ba1] +description = "no columns" + +[d774d054-bbad-4867-88ae-069cbd1c4f92] +description = "no flowers" + +[225176a0-725e-43cd-aa13-9dced501f16e] +description = "garden full of flowers" + +[3f345495-f1a5-4132-8411-74bd7ca08c49] +description = "flower surrounded by spaces" + +[6cb04070-4199-4ef7-a6fa-92f68c660fca] +description = "space surrounded by flowers" + +[272d2306-9f62-44fe-8ab5-6b0f43a26338] +description = "horizontal line" + +[c6f0a4b2-58d0-4bf6-ad8d-ccf4144f1f8e] +description = "horizontal line, flowers at edges" + +[a54e84b7-3b25-44a8-b8cf-1753c8bb4cf5] +description = "vertical line" + +[b40f42f5-dec5-4abc-b167-3f08195189c1] +description = "vertical line, flowers at edges" + +[58674965-7b42-4818-b930-0215062d543c] +description = "cross" + +[dd9d4ca8-9e68-4f78-a677-a2a70fd7a7b8] +description = "large garden" diff --git a/exercises/practice/flower-field/Cargo.toml b/exercises/practice/flower-field/Cargo.toml new file mode 100644 index 000000000..fc11c06f1 --- /dev/null +++ b/exercises/practice/flower-field/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "flower_field" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] diff --git a/exercises/practice/flower-field/src/lib.rs b/exercises/practice/flower-field/src/lib.rs new file mode 100644 index 000000000..965fb2b65 --- /dev/null +++ b/exercises/practice/flower-field/src/lib.rs @@ -0,0 +1,5 @@ +pub fn annotate(garden: &[&str]) -> Vec { + todo!( + "\nAnnotate each square of the given garden with the number of flowers that surround said square (blank if there are no surrounding flowers):\n{garden:#?}\n" + ); +} diff --git a/exercises/practice/flower-field/tests/flower_field.rs b/exercises/practice/flower-field/tests/flower_field.rs new file mode 100644 index 000000000..05cd8e66a --- /dev/null +++ b/exercises/practice/flower-field/tests/flower_field.rs @@ -0,0 +1,190 @@ +use flower_field::*; + +#[test] +fn no_rows() { + let input = &[]; + let expected: &[&str] = &[]; + let actual = annotate(input); + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn no_columns() { + let input = &[""]; + let expected = &[""]; + let actual = annotate(input); + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn no_flowers() { + #[rustfmt::skip] + let (input, expected) = (&[ + " ", + " ", + " ", + ], &[ + " ", + " ", + " ", + ]); + let actual = annotate(input); + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn garden_full_of_flowers() { + #[rustfmt::skip] + let (input, expected) = (&[ + "***", + "***", + "***", + ], &[ + "***", + "***", + "***", + ]); + let actual = annotate(input); + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn flower_surrounded_by_spaces() { + #[rustfmt::skip] + let (input, expected) = (&[ + " ", + " * ", + " ", + ], &[ + "111", + "1*1", + "111", + ]); + let actual = annotate(input); + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn space_surrounded_by_flowers() { + #[rustfmt::skip] + let (input, expected) = (&[ + "***", + "* *", + "***", + ], &[ + "***", + "*8*", + "***", + ]); + let actual = annotate(input); + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn horizontal_line() { + let input = &[" * * "]; + let expected = &["1*2*1"]; + let actual = annotate(input); + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn horizontal_line_flowers_at_edges() { + let input = &["* *"]; + let expected = &["*1 1*"]; + let actual = annotate(input); + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn vertical_line() { + #[rustfmt::skip] + let (input, expected) = (&[ + " ", + "*", + " ", + "*", + " ", + ], &[ + "1", + "*", + "2", + "*", + "1", + ]); + let actual = annotate(input); + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn vertical_line_flowers_at_edges() { + #[rustfmt::skip] + let (input, expected) = (&[ + "*", + " ", + " ", + " ", + "*", + ], &[ + "*", + "1", + " ", + "1", + "*", + ]); + let actual = annotate(input); + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn cross() { + #[rustfmt::skip] + let (input, expected) = (&[ + " * ", + " * ", + "*****", + " * ", + " * ", + ], &[ + " 2*2 ", + "25*52", + "*****", + "25*52", + " 2*2 ", + ]); + let actual = annotate(input); + assert_eq!(actual, expected); +} + +#[test] +#[ignore] +fn large_garden() { + #[rustfmt::skip] + let (input, expected) = (&[ + " * * ", + " * ", + " * ", + " * *", + " * * ", + " ", + ], &[ + "1*22*1", + "12*322", + " 123*2", + "112*4*", + "1*22*2", + "111111", + ]); + let actual = annotate(input); + assert_eq!(actual, expected); +} From 4b19aaebfe8b41091d3611dc65e0063693da4fc1 Mon Sep 17 00:00:00 2001 From: keiravillekode Date: Wed, 16 Jul 2025 01:23:30 +1000 Subject: [PATCH 418/436] ci uses ubuntu-24.04 (#2083) --- .github/workflows/tests.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b18dffb49..97c9f2b04 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,7 +14,7 @@ on: jobs: configlet: name: configlet lint - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Checkout code @@ -28,7 +28,7 @@ jobs: markdownlint: name: markdown lint - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Checkout code @@ -40,7 +40,7 @@ jobs: # stolen from https://raw.githubusercontent.com/exercism/github-actions/main/.github/workflows/shellcheck.yml shellcheck: name: Run shellcheck on scripts - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -50,7 +50,7 @@ jobs: compilation: name: Check compilation - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 strategy: matrix: @@ -75,7 +75,7 @@ jobs: tests: name: Run repository tests - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Checkout code @@ -97,7 +97,7 @@ jobs: rustformat: name: Check Rust Formatting - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Checkout code @@ -120,7 +120,7 @@ jobs: clippy: name: Clippy - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Checkout code @@ -139,7 +139,7 @@ jobs: nightly-compilation: name: Check exercises on nightly (benchmark enabled) - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 continue-on-error: true # It's okay if the nightly job fails steps: From a3e75d907006cb44204bc49d9ddf2847e38f429d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 15 Jul 2025 18:11:56 +0200 Subject: [PATCH 419/436] =?UTF-8?q?=F0=9F=A4=96=20Configlet=20sync:=20docs?= =?UTF-8?q?,=20metadata,=20and=20filepaths=20(#2082)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../practice/dot-dsl/.docs/instructions.md | 2 +- .../eliuds-eggs/.docs/introduction.md | 2 +- exercises/practice/luhn/.docs/instructions.md | 45 ++++++----- exercises/practice/luhn/.docs/introduction.md | 4 +- .../phone-number/.docs/instructions.md | 2 +- .../protein-translation/.docs/instructions.md | 47 +++++------ .../simple-cipher/.docs/instructions.md | 78 +++++++------------ .../practice/simple-cipher/.meta/config.json | 2 +- 8 files changed, 77 insertions(+), 105 deletions(-) diff --git a/exercises/practice/dot-dsl/.docs/instructions.md b/exercises/practice/dot-dsl/.docs/instructions.md index b3a63996d..5e65ebef9 100644 --- a/exercises/practice/dot-dsl/.docs/instructions.md +++ b/exercises/practice/dot-dsl/.docs/instructions.md @@ -22,7 +22,7 @@ Write a Domain Specific Language similar to the Graphviz dot language. Our DSL is similar to the Graphviz dot language in that our DSL will be used to create graph data structures. However, unlike the DOT Language, our DSL will be an internal DSL for use only in our language. -More information about the difference between internal and external DSLs can be found [here][fowler-dsl]. +[Learn more about the difference between internal and external DSLs][fowler-dsl]. [dsl]: https://en.wikipedia.org/wiki/Domain-specific_language [dot-language]: https://en.wikipedia.org/wiki/DOT_(graph_description_language) diff --git a/exercises/practice/eliuds-eggs/.docs/introduction.md b/exercises/practice/eliuds-eggs/.docs/introduction.md index 819897480..2b2e5c43d 100644 --- a/exercises/practice/eliuds-eggs/.docs/introduction.md +++ b/exercises/practice/eliuds-eggs/.docs/introduction.md @@ -58,7 +58,7 @@ The position information encoding is calculated as follows: ### Decimal number on the display -16 +8 ### Actual eggs in the coop diff --git a/exercises/practice/luhn/.docs/instructions.md b/exercises/practice/luhn/.docs/instructions.md index 5bbf007b0..7702c6bbb 100644 --- a/exercises/practice/luhn/.docs/instructions.md +++ b/exercises/practice/luhn/.docs/instructions.md @@ -1,6 +1,6 @@ # Instructions -Determine whether a credit card number is valid according to the [Luhn formula][luhn]. +Determine whether a number is valid according to the [Luhn formula][luhn]. The number will be provided as a string. @@ -10,54 +10,59 @@ Strings of length 1 or less are not valid. Spaces are allowed in the input, but they should be stripped before checking. All other non-digit characters are disallowed. -### Example 1: valid credit card number +## Examples -```text -4539 3195 0343 6467 -``` +### Valid credit card number -The first step of the Luhn algorithm is to double every second digit, starting from the right. -We will be doubling +The number to be checked is `4539 3195 0343 6467`. + +The first step of the Luhn algorithm is to start at the end of the number and double every second digit, beginning with the second digit from the right and moving left. ```text 4539 3195 0343 6467 ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ (double these) ``` -If doubling the number results in a number greater than 9 then subtract 9 from the product. -The results of our doubling: +If the result of doubling a digit is greater than 9, we subtract 9 from that result. +We end up with: ```text 8569 6195 0383 3437 ``` -Then sum all of the digits: +Finally, we sum all digits. +If the sum is evenly divisible by 10, the original number is valid. ```text -8+5+6+9+6+1+9+5+0+3+8+3+3+4+3+7 = 80 +8 + 5 + 6 + 9 + 6 + 1 + 9 + 5 + 0 + 3 + 8 + 3 + 3 + 4 + 3 + 7 = 80 ``` -If the sum is evenly divisible by 10, then the number is valid. -This number is valid! +80 is evenly divisible by 10, so number `4539 3195 0343 6467` is valid! + +### Invalid Canadian SIN + +The number to be checked is `066 123 478`. -### Example 2: invalid credit card number +We start at the end of the number and double every second digit, beginning with the second digit from the right and moving left. ```text -8273 1232 7352 0569 +066 123 478 + ↑ ↑ ↑ ↑ (double these) ``` -Double the second digits, starting from the right +If the result of doubling a digit is greater than 9, we subtract 9 from that result. +We end up with: ```text -7253 2262 5312 0539 +036 226 458 ``` -Sum the digits +We sum the digits: ```text -7+2+5+3+2+2+6+2+5+3+1+2+0+5+3+9 = 57 +0 + 3 + 6 + 2 + 2 + 6 + 4 + 5 + 8 = 36 ``` -57 is not evenly divisible by 10, so this number is not valid. +36 is not evenly divisible by 10, so number `066 123 478` is not valid! [luhn]: https://en.wikipedia.org/wiki/Luhn_algorithm diff --git a/exercises/practice/luhn/.docs/introduction.md b/exercises/practice/luhn/.docs/introduction.md index ec2bd709d..dee48006e 100644 --- a/exercises/practice/luhn/.docs/introduction.md +++ b/exercises/practice/luhn/.docs/introduction.md @@ -2,10 +2,10 @@ At the Global Verification Authority, you've just been entrusted with a critical assignment. Across the city, from online purchases to secure logins, countless operations rely on the accuracy of numerical identifiers like credit card numbers, bank account numbers, transaction codes, and tracking IDs. -The Luhn algorithm is a simple checksum formula used to ensure these numbers are valid and error-free. +The Luhn algorithm is a simple checksum formula used to help identify mistyped numbers. A batch of identifiers has just arrived on your desk. All of them must pass the Luhn test to ensure they're legitimate. -If any fail, they'll be flagged as invalid, preventing errors or fraud, such as incorrect transactions or unauthorized access. +If any fail, they'll be flagged as invalid, preventing mistakes such as incorrect transactions or failed account verifications. Can you ensure this is done right? The integrity of many services depends on you. diff --git a/exercises/practice/phone-number/.docs/instructions.md b/exercises/practice/phone-number/.docs/instructions.md index 62ba48e96..5d4d3739f 100644 --- a/exercises/practice/phone-number/.docs/instructions.md +++ b/exercises/practice/phone-number/.docs/instructions.md @@ -1,6 +1,6 @@ # Instructions -Clean up user-entered phone numbers so that they can be sent SMS messages. +Clean up phone numbers so that they can be sent SMS messages. The **North American Numbering Plan (NANP)** is a telephone numbering system used by many countries in North America like the United States, Canada or Bermuda. All NANP-countries share the same international country code: `1`. diff --git a/exercises/practice/protein-translation/.docs/instructions.md b/exercises/practice/protein-translation/.docs/instructions.md index 44880802c..35c953b11 100644 --- a/exercises/practice/protein-translation/.docs/instructions.md +++ b/exercises/practice/protein-translation/.docs/instructions.md @@ -1,36 +1,17 @@ # Instructions -Translate RNA sequences into proteins. +Your job is to translate RNA sequences into proteins. -RNA can be broken into three-nucleotide sequences called codons, and then translated to a protein like so: +RNA strands are made up of three-nucleotide sequences called **codons**. +Each codon translates to an **amino acid**. +When joined together, those amino acids make a protein. -RNA: `"AUGUUUUCU"` => translates to - -Codons: `"AUG", "UUU", "UCU"` -=> which become a protein with the following sequence => - -Protein: `"Methionine", "Phenylalanine", "Serine"` - -There are 64 codons which in turn correspond to 20 amino acids; however, all of the codon sequences and resulting amino acids are not important in this exercise. -If it works for one codon, the program should work for all of them. -However, feel free to expand the list in the test suite to include them all. - -There are also three terminating codons (also known as 'STOP' codons); if any of these codons are encountered (by the ribosome), all translation ends and the protein is terminated. - -All subsequent codons after are ignored, like this: - -RNA: `"AUGUUUUCUUAAAUG"` => - -Codons: `"AUG", "UUU", "UCU", "UAA", "AUG"` => - -Protein: `"Methionine", "Phenylalanine", "Serine"` - -Note the stop codon `"UAA"` terminates the translation and the final methionine is not translated into the protein sequence. - -Below are the codons and resulting amino acids needed for the exercise. +In the real world, there are 64 codons, which in turn correspond to 20 amino acids. +However, for this exercise, you’ll only use a few of the possible 64. +They are listed below: | Codon | Amino Acid | -| :----------------- | :------------ | +| ------------------ | ------------- | | AUG | Methionine | | UUU, UUC | Phenylalanine | | UUA, UUG | Leucine | @@ -40,6 +21,18 @@ Below are the codons and resulting amino acids needed for the exercise. | UGG | Tryptophan | | UAA, UAG, UGA | STOP | +For example, the RNA string “AUGUUUUCU” has three codons: “AUG”, “UUU” and “UCU”. +These map to Methionine, Phenylalanine, and Serine. + +## “STOP” Codons + +You’ll note from the table above that there are three **“STOP” codons**. +If you encounter any of these codons, ignore the rest of the sequence — the protein is complete. + +For example, “AUGUUUUCUUAAAUG” contains a STOP codon (“UAA”). +Once we reach that point, we stop processing. +We therefore only consider the part before it (i.e. “AUGUUUUCU”), not any further codons after it (i.e. “AUG”). + Learn more about [protein translation on Wikipedia][protein-translation]. [protein-translation]: https://en.wikipedia.org/wiki/Translation_(biology) diff --git a/exercises/practice/simple-cipher/.docs/instructions.md b/exercises/practice/simple-cipher/.docs/instructions.md index 337857442..afd0b57da 100644 --- a/exercises/practice/simple-cipher/.docs/instructions.md +++ b/exercises/practice/simple-cipher/.docs/instructions.md @@ -1,66 +1,40 @@ # Instructions -Implement a simple shift cipher like Caesar and a more secure substitution cipher. +Create an implementation of the [Vigenère cipher][wiki]. +The Vigenère cipher is a simple substitution cipher. -## Step 1 +## Cipher terminology -"If he had anything confidential to say, he wrote it in cipher, that is, by so changing the order of the letters of the alphabet, that not a word could be made out. -If anyone wishes to decipher these, and get at their meaning, he must substitute the fourth letter of the alphabet, namely D, for A, and so with the others." -—Suetonius, Life of Julius Caesar +A cipher is an algorithm used to encrypt, or encode, a string. +The unencrypted string is called the _plaintext_ and the encrypted string is called the _ciphertext_. +Converting plaintext to ciphertext is called _encoding_ while the reverse is called _decoding_. -Ciphers are very straight-forward algorithms that allow us to render text less readable while still allowing easy deciphering. -They are vulnerable to many forms of cryptanalysis, but Caesar was lucky that his enemies were not cryptanalysts. +In a _substitution cipher_, each plaintext letter is replaced with a ciphertext letter which is computed with the help of a _key_. +(Note, it is possible for replacement letter to be the same as the original letter.) -The Caesar cipher was used for some messages from Julius Caesar that were sent afield. -Now Caesar knew that the cipher wasn't very good, but he had one ally in that respect: almost nobody could read well. -So even being a couple letters off was sufficient so that people couldn't recognize the few words that they did know. +## Encoding details -Your task is to create a simple shift cipher like the Caesar cipher. -This image is a great example of the Caesar cipher: +In this cipher, the key is a series of lowercase letters, such as `"abcd"`. +Each letter of the plaintext is _shifted_ or _rotated_ by a distance based on a corresponding letter in the key. +An `"a"` in the key means a shift of 0 (that is, no shift). +A `"b"` in the key means a shift of 1. +A `"c"` in the key means a shift of 2, and so on. -![Caesar cipher][img-caesar-cipher] +The first letter of the plaintext uses the first letter of the key, the second letter of the plaintext uses the second letter of the key and so on. +If you run out of letters in the key before you run out of letters in the plaintext, start over from the start of the key again. -For example: +If the key only contains one letter, such as `"dddddd"`, then all letters of the plaintext are shifted by the same amount (three in this example), which would make this the same as a rotational cipher or shift cipher (sometimes called a Caesar cipher). +For example, the plaintext `"iamapandabear"` would become `"ldpdsdqgdehdu"`. -Giving "iamapandabear" as input to the encode function returns the cipher "ldpdsdqgdehdu". -Obscure enough to keep our message secret in transit. +If the key only contains the letter `"a"` (one or more times), the shift distance is zero and the ciphertext is the same as the plaintext. -When "ldpdsdqgdehdu" is put into the decode function it would return the original "iamapandabear" letting your friend read your original message. +Usually the key is more complicated than that, though! +If the key is `"abcd"` then letters of the plaintext would be shifted by a distance of 0, 1, 2, and 3. +If the plaintext is `"hello"`, we need 5 shifts so the key would wrap around, giving shift distances of 0, 1, 2, 3, and 0. +Applying those shifts to the letters of `"hello"` we get `"hfnoo"`. -## Step 2 +## Random keys -Shift ciphers quickly cease to be useful when the opposition commander figures them out. -So instead, let's try using a substitution cipher. -Try amending the code to allow us to specify a key and use that for the shift distance. +If no key is provided, generate a key which consists of at least 100 random lowercase letters from the Latin alphabet. -Here's an example: - -Given the key "aaaaaaaaaaaaaaaaaa", encoding the string "iamapandabear" -would return the original "iamapandabear". - -Given the key "ddddddddddddddddd", encoding our string "iamapandabear" -would return the obscured "ldpdsdqgdehdu" - -In the example above, we've set a = 0 for the key value. -So when the plaintext is added to the key, we end up with the same message coming out. -So "aaaa" is not an ideal key. -But if we set the key to "dddd", we would get the same thing as the Caesar cipher. - -## Step 3 - -The weakest link in any cipher is the human being. -Let's make your substitution cipher a little more fault tolerant by providing a source of randomness and ensuring that the key contains only lowercase letters. - -If someone doesn't submit a key at all, generate a truly random key of at least 100 lowercase characters in length. - -## Extensions - -Shift ciphers work by making the text slightly odd, but are vulnerable to frequency analysis. -Substitution ciphers help that, but are still very vulnerable when the key is short or if spaces are preserved. -Later on you'll see one solution to this problem in the exercise "crypto-square". - -If you want to go farther in this field, the questions begin to be about how we can exchange keys in a secure way. -Take a look at [Diffie-Hellman on Wikipedia][dh] for one of the first implementations of this scheme. - -[img-caesar-cipher]: https://upload.wikimedia.org/wikipedia/commons/thumb/4/4a/Caesar_cipher_left_shift_of_3.svg/320px-Caesar_cipher_left_shift_of_3.svg.png -[dh]: https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange +[wiki]: https://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher diff --git a/exercises/practice/simple-cipher/.meta/config.json b/exercises/practice/simple-cipher/.meta/config.json index 0c1b4e3c1..66439abf1 100644 --- a/exercises/practice/simple-cipher/.meta/config.json +++ b/exercises/practice/simple-cipher/.meta/config.json @@ -29,7 +29,7 @@ ".meta/example.rs" ] }, - "blurb": "Implement a simple shift cipher like Caesar and a more secure substitution cipher.", + "blurb": "Implement the Vigenère cipher, a simple substitution cipher.", "source": "Substitution Cipher at Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Substitution_cipher" } From 34cd147f780e9fe96ddb0272c80525f1dfa8d543 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Wed, 16 Jul 2025 09:17:25 +0200 Subject: [PATCH 420/436] Remove system for pinning problem-specifications (#2084) We now have CI workflows that raise issues when configlet finds something that needs updating. This is in conflict with the pinned problem-specifications approach. --- ...ut_pinned_problem_specifications_commit.sh | 17 ---------- bin/configlet_wrapper.sh | 29 ----------------- bin/symlink_problem_specifications.sh | 9 +++++- bin/update_problem_specifications.sh | 15 --------- justfile | 9 ++---- .../tests/tera_templates_are_in_sync.rs | 31 ------------------- 6 files changed, 11 insertions(+), 99 deletions(-) delete mode 100755 bin/checkout_pinned_problem_specifications_commit.sh delete mode 100755 bin/configlet_wrapper.sh delete mode 100755 bin/update_problem_specifications.sh delete mode 100644 rust-tooling/generate/tests/tera_templates_are_in_sync.rs diff --git a/bin/checkout_pinned_problem_specifications_commit.sh b/bin/checkout_pinned_problem_specifications_commit.sh deleted file mode 100755 index b98e4c3b2..000000000 --- a/bin/checkout_pinned_problem_specifications_commit.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -cd "$(git rev-parse --show-toplevel)" - -PINNED_COMMIT_HASH="c4043d661357fc2d7ad07b8359adc92d283a5a00" - -dir="$(./bin/get_problem_specifications_dir.sh)" - -[ -d "$dir" ] || ./bin/configlet info &> /dev/null # initial population of cache - -if ! git -C "$dir" checkout --quiet --detach "$PINNED_COMMIT_HASH" &> /dev/null -then - # maybe the pinned commit hash was updated and the cache has to be refreshed - ./bin/configlet info &> /dev/null - git -C "$dir" checkout --quiet --detach "$PINNED_COMMIT_HASH" -fi diff --git a/bin/configlet_wrapper.sh b/bin/configlet_wrapper.sh deleted file mode 100755 index ef58d28b9..000000000 --- a/bin/configlet_wrapper.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail - -# This wrapper makes sure the problem-specifications repository is checked out -# at the pinned commit and the --offline flag is set for the relevant commands. - -cd "$(git rev-parse --show-toplevel)" - -[ -f ./bin/configlet ] || ./bin/fetch-configlet - -if [ "$#" == 0 ] -then - ./bin/configlet - exit -fi - -cmd="$1" ; shift - -if ! [ "$cmd" == "create" ] && ! [ "$cmd" == "sync" ] && ! [ "$cmd" == "info" ] -then - # problem-specifications independent commands - ./bin/configlet "$cmd" "$@" - exit -fi - -./bin/checkout_pinned_problem_specifications_commit.sh - -set -x # show the added --offile flag -./bin/configlet "$cmd" --offline "$@" diff --git a/bin/symlink_problem_specifications.sh b/bin/symlink_problem_specifications.sh index 119579e03..15d698269 100755 --- a/bin/symlink_problem_specifications.sh +++ b/bin/symlink_problem_specifications.sh @@ -1,11 +1,18 @@ #!/usr/bin/env bash set -eo pipefail +# This script creates a symlink to the problem-specifications cache used +# by configlet. When working on an exercise, it is handy to have the +# problem-specifions files in reach. Symlinking to configlet's cache ensures +# you're always looking at the up-to-date files. + cd "$(git rev-parse --show-toplevel)" [ -e "problem-specifications" ] || ln -s "$(./bin/get_problem_specifications_dir.sh)" "problem-specifications" -./bin/checkout_pinned_problem_specifications_commit.sh +# ensure populated cache +[ -f ./bin/configlet ] || ./bin/fetch-configlet +./bin/configlet info &> /dev/null for exercise in exercises/practice/*; do name="$(basename "$exercise")" diff --git a/bin/update_problem_specifications.sh b/bin/update_problem_specifications.sh deleted file mode 100755 index bb32bd47f..000000000 --- a/bin/update_problem_specifications.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -cd "$(git rev-parse --show-toplevel)" - -./bin/checkout_pinned_problem_specifications_commit.sh - -dir="$(./bin/get_problem_specifications_dir.sh)" - -git -C "$dir" checkout --quiet main -git -C "$dir" pull --quiet - -new_commit_hash="$(git -C "$dir" rev-parse main)" - -sed -i "s/^PINNED_COMMIT_HASH=.*$/PINNED_COMMIT_HASH=\"$new_commit_hash\"/g" ./bin/checkout_pinned_problem_specifications_commit.sh diff --git a/justfile b/justfile index e2a6042da..fe7bfcf38 100644 --- a/justfile +++ b/justfile @@ -1,13 +1,10 @@ _default: just --list --unsorted -# configlet wrapper, uses pinned problem-specifications commit +# configlet (downloads if necessary) @configlet *args="": - ./bin/configlet_wrapper.sh {{ args }} - -# update the pinned commit hash -update-problem-specs: - ./bin/update_problem_specifications.sh + [ -f ./bin/configlet ] || ./bin/fetch-configlet + ./bin/configlet {{ args }} # generate a new uuid straight to your clipboard uuid: diff --git a/rust-tooling/generate/tests/tera_templates_are_in_sync.rs b/rust-tooling/generate/tests/tera_templates_are_in_sync.rs deleted file mode 100644 index 3485a81ed..000000000 --- a/rust-tooling/generate/tests/tera_templates_are_in_sync.rs +++ /dev/null @@ -1,31 +0,0 @@ -//! This test is not run in CI. Doing so would slow down CI noticeably, because -//! exercise generation depends on `tera`, which takes a while to compile. - -#![allow(clippy::unwrap_used, clippy::panic)] - -use glob::glob; -use utils::fs::cd_into_repo_root; - -#[test] -fn tera_templates_are_in_sync() { - cd_into_repo_root(); - for entry in glob("exercises/*/*/.meta/test_template.tera").unwrap() { - let path = entry.unwrap(); - let exercise_dir = path.parent().unwrap().parent().unwrap(); - let slug = exercise_dir.file_name().unwrap().to_string_lossy(); - - let generated = generate::new(&slug); - - let snake_slug = slug.replace('-', "_"); - let test_path = exercise_dir.join("tests").join(format!("{snake_slug}.rs")); - let on_disk = std::fs::read_to_string(test_path).unwrap(); - - if generated.tests != on_disk { - panic!( - " - The Tera template for exercise '{slug}' is not in sync. - Run 'just update-exercise --slug {slug}' to fix it.\n" - ) - } - } -} From 9e6ba3ec008a6a7ee4dfba27829fd1366bbbf11e Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Wed, 16 Jul 2025 09:17:52 +0200 Subject: [PATCH 421/436] Update tests of variable-length-quantity (#2086) closes #2081 --- .../variable-length-quantity/.meta/tests.toml | 15 +++++++ .../tests/variable_length_quantity.rs | 45 +++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/exercises/practice/variable-length-quantity/.meta/tests.toml b/exercises/practice/variable-length-quantity/.meta/tests.toml index c9af549fc..53be789a3 100644 --- a/exercises/practice/variable-length-quantity/.meta/tests.toml +++ b/exercises/practice/variable-length-quantity/.meta/tests.toml @@ -15,6 +15,9 @@ description = "Encode a series of integers, producing a series of bytes. -> zero [be44d299-a151-4604-a10e-d4b867f41540] description = "Encode a series of integers, producing a series of bytes. -> arbitrary single byte" +[890bc344-cb80-45af-b316-6806a6971e81] +description = "Encode a series of integers, producing a series of bytes. -> asymmetric single byte" + [ea399615-d274-4af6-bbef-a1c23c9e1346] description = "Encode a series of integers, producing a series of bytes. -> largest single byte" @@ -24,6 +27,9 @@ description = "Encode a series of integers, producing a series of bytes. -> smal [63955a49-2690-4e22-a556-0040648d6b2d] description = "Encode a series of integers, producing a series of bytes. -> arbitrary double byte" +[4977d113-251b-4d10-a3ad-2f5a7756bb58] +description = "Encode a series of integers, producing a series of bytes. -> asymmetric double byte" + [29da7031-0067-43d3-83a7-4f14b29ed97a] description = "Encode a series of integers, producing a series of bytes. -> largest double byte" @@ -33,6 +39,9 @@ description = "Encode a series of integers, producing a series of bytes. -> smal [5df0bc2d-2a57-4300-a653-a75ee4bd0bee] description = "Encode a series of integers, producing a series of bytes. -> arbitrary triple byte" +[6731045f-1e00-4192-b5ae-98b22e17e9f7] +description = "Encode a series of integers, producing a series of bytes. -> asymmetric triple byte" + [f51d8539-312d-4db1-945c-250222c6aa22] description = "Encode a series of integers, producing a series of bytes. -> largest triple byte" @@ -42,6 +51,9 @@ description = "Encode a series of integers, producing a series of bytes. -> smal [11ed3469-a933-46f1-996f-2231e05d7bb6] description = "Encode a series of integers, producing a series of bytes. -> arbitrary quadruple byte" +[b45ef770-cbba-48c2-bd3c-c6362679516e] +description = "Encode a series of integers, producing a series of bytes. -> asymmetric quadruple byte" + [d5f3f3c3-e0f1-4e7f-aad0-18a44f223d1c] description = "Encode a series of integers, producing a series of bytes. -> largest quadruple byte" @@ -51,6 +63,9 @@ description = "Encode a series of integers, producing a series of bytes. -> smal [5f34ff12-2952-4669-95fe-2d11b693d331] description = "Encode a series of integers, producing a series of bytes. -> arbitrary quintuple byte" +[9be46731-7cd5-415c-b960-48061cbc1154] +description = "Encode a series of integers, producing a series of bytes. -> asymmetric quintuple byte" + [7489694b-88c3-4078-9864-6fe802411009] description = "Encode a series of integers, producing a series of bytes. -> maximum 32-bit integer input" diff --git a/exercises/practice/variable-length-quantity/tests/variable_length_quantity.rs b/exercises/practice/variable-length-quantity/tests/variable_length_quantity.rs index e3f3dfe5f..b1b4e762d 100644 --- a/exercises/practice/variable-length-quantity/tests/variable_length_quantity.rs +++ b/exercises/practice/variable-length-quantity/tests/variable_length_quantity.rs @@ -17,6 +17,15 @@ fn arbitrary_single_byte() { assert_eq!(output, expected); } +#[test] +#[ignore] +fn asymmetric_single_byte() { + let input = &[83]; + let output = vlq::to_bytes(input); + let expected = vec![0x53]; + assert_eq!(output, expected); +} + #[test] #[ignore] fn largest_single_byte() { @@ -44,6 +53,15 @@ fn arbitrary_double_byte() { assert_eq!(output, expected); } +#[test] +#[ignore] +fn asymmetric_double_byte() { + let input = &[173]; + let output = vlq::to_bytes(input); + let expected = vec![0x81, 0x2d]; + assert_eq!(output, expected); +} + #[test] #[ignore] fn largest_double_byte() { @@ -71,6 +89,15 @@ fn arbitrary_triple_byte() { assert_eq!(output, expected); } +#[test] +#[ignore] +fn asymmetric_triple_byte() { + let input = &[120_220]; + let output = vlq::to_bytes(input); + let expected = vec![0x87, 0xab, 0x1c]; + assert_eq!(output, expected); +} + #[test] #[ignore] fn largest_triple_byte() { @@ -98,6 +125,15 @@ fn arbitrary_quadruple_byte() { assert_eq!(output, expected); } +#[test] +#[ignore] +fn asymmetric_quadruple_byte() { + let input = &[3_503_876]; + let output = vlq::to_bytes(input); + let expected = vec![0x81, 0xd5, 0xee, 0x4]; + assert_eq!(output, expected); +} + #[test] #[ignore] fn largest_quadruple_byte() { @@ -125,6 +161,15 @@ fn arbitrary_quintuple_byte() { assert_eq!(output, expected); } +#[test] +#[ignore] +fn asymmetric_quintuple_byte() { + let input = &[2_254_790_917]; + let output = vlq::to_bytes(input); + let expected = vec![0x88, 0xb3, 0x95, 0xc2, 0x5]; + assert_eq!(output, expected); +} + #[test] #[ignore] fn maximum_32_bit_integer_input() { From 54ab45ae9381dcd8ae501151efdf0ebcfdec05d9 Mon Sep 17 00:00:00 2001 From: Colin Pitrat Date: Sun, 3 Aug 2025 21:26:45 +0100 Subject: [PATCH 422/436] Point to the forum instead of GitHub for feedback and help (#2088) When on an exercise (for example [this one](https://exercism.org/tracks/rust/exercises/paasio/edit)) in the `Get help` tab, in the `Feedback, Issues, Pull Requests` paragraph, there's a link to GitHub `track repository` for `feedback about an exercise, or want to help implement new exercises` However, when such an issue is opened it is automatically closed, redirecting to this forum ([example](https://github.com/exercism/rust/issues/2087)). --------- Co-authored-by: Remo Senekowitsch --- exercises/shared/.docs/help.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/exercises/shared/.docs/help.md b/exercises/shared/.docs/help.md index d4f8c3f5d..471913f3c 100644 --- a/exercises/shared/.docs/help.md +++ b/exercises/shared/.docs/help.md @@ -11,7 +11,10 @@ Generally you should submit all files in which you implemented your solution (`s ## Feedback, Issues, Pull Requests -The GitHub [track repository][github] is the home for all of the Rust exercises. If you have feedback about an exercise, or want to help implement new exercises, head over there and create an issue. Members of the rust track team are happy to help! +Head to [the forum](https://forum.exercism.org/c/programming/rust/) and create a post to provide feedback about an exercise or if you want to help implement new exercises. +Members of the rust track team are happy to help! + +The GitHub [track repository][github] is the home for all of the Rust exercises. If you want to know more about Exercism, take a look at the [contribution guide]. From 818eb68d8eacd06c179148b68381d88f3abd6df2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 15 Aug 2025 06:10:43 +0200 Subject: [PATCH 423/436] =?UTF-8?q?=F0=9F=A4=96=20Configlet=20sync:=20docs?= =?UTF-8?q?,=20metadata,=20and=20filepaths=20(#2090)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR was generated automatically by a scheduled workflow. It includes updates from `configlet sync` for: - 📄 Documentation - 🧭 Metadata - 🗂️ Filepaths Please review and merge if everything looks good! Co-authored-by: senekor <54984957+senekor@users.noreply.github.com> --- exercises/practice/say/.docs/instructions.md | 52 +++---------------- exercises/practice/say/.docs/introduction.md | 6 +++ .../practice/triangle/.docs/instructions.md | 5 ++ 3 files changed, 19 insertions(+), 44 deletions(-) create mode 100644 exercises/practice/say/.docs/introduction.md diff --git a/exercises/practice/say/.docs/instructions.md b/exercises/practice/say/.docs/instructions.md index ad3d34778..3251c519a 100644 --- a/exercises/practice/say/.docs/instructions.md +++ b/exercises/practice/say/.docs/instructions.md @@ -1,48 +1,12 @@ # Instructions -Given a number from 0 to 999,999,999,999, spell out that number in English. +Given a number, your task is to express it in English words exactly as your friend should say it out loud. +Yaʻqūb expects to use numbers from 0 up to 999,999,999,999. -## Step 1 +Examples: -Handle the basic case of 0 through 99. - -If the input to the program is `22`, then the output should be `'twenty-two'`. - -Your program should complain loudly if given a number outside the blessed range. - -Some good test cases for this program are: - -- 0 -- 14 -- 50 -- 98 -- -1 -- 100 - -### Extension - -If you're on a Mac, shell out to Mac OS X's `say` program to talk out loud. -If you're on Linux or Windows, eSpeakNG may be available with the command `espeak`. - -## Step 2 - -Implement breaking a number up into chunks of thousands. - -So `1234567890` should yield a list like 1, 234, 567, and 890, while the far simpler `1000` should yield just 1 and 0. - -## Step 3 - -Now handle inserting the appropriate scale word between those chunks. - -So `1234567890` should yield `'1 billion 234 million 567 thousand 890'` - -The program must also report any values that are out of range. -It's fine to stop at "trillion". - -## Step 4 - -Put it all together to get nothing but plain English. - -`12345` should give `twelve thousand three hundred forty-five`. - -The program must also report any values that are out of range. +- 0 → zero +- 1 → one +- 12 → twelve +- 123 → one hundred twenty-three +- 1,234 → one thousand two hundred thirty-four diff --git a/exercises/practice/say/.docs/introduction.md b/exercises/practice/say/.docs/introduction.md new file mode 100644 index 000000000..abd22851e --- /dev/null +++ b/exercises/practice/say/.docs/introduction.md @@ -0,0 +1,6 @@ +# Introduction + +Your friend Yaʻqūb works the counter at the busiest deli in town, slicing, weighing, and wrapping orders for a never-ending line of hungry customers. +To keep things moving, each customer takes a numbered ticket when they arrive. + +When it’s time to call the next person, Yaʻqūb reads their number out loud, always in full English words to make sure everyone hears it clearly. diff --git a/exercises/practice/triangle/.docs/instructions.md b/exercises/practice/triangle/.docs/instructions.md index ac3900872..755cb8d19 100644 --- a/exercises/practice/triangle/.docs/instructions.md +++ b/exercises/practice/triangle/.docs/instructions.md @@ -13,6 +13,11 @@ A _scalene_ triangle has all sides of different lengths. For a shape to be a triangle at all, all sides have to be of length > 0, and the sum of the lengths of any two sides must be greater than or equal to the length of the third side. +~~~~exercism/note +We opted to not include tests for degenerate triangles (triangles that violate these rules) to keep things simpler. +You may handle those situations if you wish to do so, or safely ignore them. +~~~~ + In equations: Let `a`, `b`, and `c` be sides of the triangle. From 94813739f2c6190752bbd3568076a2929f6650cf Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 26 Aug 2025 16:11:03 +0200 Subject: [PATCH 424/436] Fix clippy warnings (#2091) --- .../practice/book-store/.meta/example.rs | 60 +++++++++---------- exercises/practice/list-ops/.meta/example.rs | 8 +-- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/exercises/practice/book-store/.meta/example.rs b/exercises/practice/book-store/.meta/example.rs index 0293a5c89..2875827e1 100644 --- a/exercises/practice/book-store/.meta/example.rs +++ b/exercises/practice/book-store/.meta/example.rs @@ -124,40 +124,40 @@ impl Iterator for DecomposeGroups { // then move the last item from the most populous group into a new group, alone, // and return let return_value = self.next.clone(); - if let Some(groups) = self.next.take() { - if !(groups.is_empty() || groups.iter().all(|g| g.0.borrow().len() == 1)) { - let mut hypothetical; - for mpg_book in groups[0].0.borrow().iter() { - for (idx, other_group) in groups[1..].iter().enumerate() { - if !other_group.0.borrow().contains(mpg_book) { - hypothetical = groups.clone(); - hypothetical[0].0.borrow_mut().remove(mpg_book); - hypothetical[1 + idx].0.borrow_mut().insert(*mpg_book); - hypothetical.sort(); - let hypothetical_hash = hash_of(&hypothetical); - if !self.prev_states.contains(&hypothetical_hash) { - self.prev_states.insert(hypothetical_hash); - self.next = Some(hypothetical); - return return_value; - } + if let Some(groups) = self.next.take() + && !(groups.is_empty() || groups.iter().all(|g| g.0.borrow().len() == 1)) + { + let mut hypothetical; + for mpg_book in groups[0].0.borrow().iter() { + for (idx, other_group) in groups[1..].iter().enumerate() { + if !other_group.0.borrow().contains(mpg_book) { + hypothetical = groups.clone(); + hypothetical[0].0.borrow_mut().remove(mpg_book); + hypothetical[1 + idx].0.borrow_mut().insert(*mpg_book); + hypothetical.sort(); + let hypothetical_hash = hash_of(&hypothetical); + if !self.prev_states.contains(&hypothetical_hash) { + self.prev_states.insert(hypothetical_hash); + self.next = Some(hypothetical); + return return_value; } } } - // we've gone through all the items of the most populous group, - // and none of them can be added to any other existing group. - // We need to create a new group; - let book = { - let backing_bt = groups[0].0.borrow(); - let mut book_iter = backing_bt.iter(); - *book_iter.next().unwrap() - }; - hypothetical = groups; - hypothetical[0].0.borrow_mut().remove(&book); - hypothetical.push(Group::new_containing(book)); - hypothetical.sort(); - self.prev_states.insert(hash_of(&hypothetical)); - self.next = Some(hypothetical); } + // we've gone through all the items of the most populous group, + // and none of them can be added to any other existing group. + // We need to create a new group; + let book = { + let backing_bt = groups[0].0.borrow(); + let mut book_iter = backing_bt.iter(); + *book_iter.next().unwrap() + }; + hypothetical = groups; + hypothetical[0].0.borrow_mut().remove(&book); + hypothetical.push(Group::new_containing(book)); + hypothetical.sort(); + self.prev_states.insert(hash_of(&hypothetical)); + self.next = Some(hypothetical); } return_value } diff --git a/exercises/practice/list-ops/.meta/example.rs b/exercises/practice/list-ops/.meta/example.rs index b32322a30..157b04dbe 100644 --- a/exercises/practice/list-ops/.meta/example.rs +++ b/exercises/practice/list-ops/.meta/example.rs @@ -32,10 +32,10 @@ where type Item = T; fn next(&mut self) -> Option { - if let Some(nested_iterator) = self.cur.as_mut() { - if let Some(val) = nested_iterator.next() { - return Some(val); - } + if let Some(nested_iterator) = self.cur.as_mut() + && let Some(val) = nested_iterator.next() + { + return Some(val); } if let Some(next_nested) = self.nested_list.next() { From db38a8039f8cede0e841d71f7b9b9c4895b636e1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Sep 2025 19:20:48 +0200 Subject: [PATCH 425/436] build(deps): bump dtolnay/rust-toolchain (#2092) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/tests.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 97c9f2b04..31e8c41f0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -64,7 +64,7 @@ jobs: run: git fetch --depth=1 origin main - name: Setup toolchain - uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b + uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 with: toolchain: ${{ matrix.rust }} @@ -82,7 +82,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup toolchain - uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b + uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 with: toolchain: stable @@ -104,7 +104,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup toolchain - uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b + uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 with: toolchain: stable @@ -127,7 +127,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup toolchain - uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b + uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 with: toolchain: stable components: clippy @@ -147,7 +147,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup nightly toolchain - uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b + uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 with: toolchain: nightly From d2972ac9533421da9b527dcce6f443f9d2709d23 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Sep 2025 19:27:44 +0200 Subject: [PATCH 426/436] build(deps): bump actions/checkout from 4.2.2 to 5.0.0 (#2093) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/tests.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 31e8c41f0..81d5adcc6 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,7 +18,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Fetch configlet run: ./bin/fetch-configlet @@ -32,7 +32,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Run markdown lint run: ./bin/lint_markdown.sh @@ -43,7 +43,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Run shellcheck uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # v2.0.0 @@ -58,7 +58,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Fetch origin/main run: git fetch --depth=1 origin main @@ -79,7 +79,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Setup toolchain uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 @@ -101,7 +101,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Setup toolchain uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 @@ -124,7 +124,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Setup toolchain uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 @@ -144,7 +144,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Setup nightly toolchain uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 From 64b1a0d4f07034fb6d02cf74c68340aab44bcdfc Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 9 Sep 2025 21:23:50 +0200 Subject: [PATCH 427/436] Add POV exercise (#2094) Co-authored-by: devcyjung --- config.json | 9 + .../practice/pov/.docs/instructions.append.md | 7 + exercises/practice/pov/.docs/instructions.md | 41 +++ exercises/practice/pov/.gitignore | 2 + exercises/practice/pov/.meta/config.json | 21 ++ exercises/practice/pov/.meta/example.rs | 84 ++++++ .../practice/pov/.meta/test_template.tera | 78 +++++ exercises/practice/pov/.meta/tests.toml | 55 ++++ exercises/practice/pov/Cargo.toml | 12 + exercises/practice/pov/src/lib.rs | 25 ++ exercises/practice/pov/tests/pov.rs | 277 ++++++++++++++++++ rust-tooling/generate/src/custom_filters.rs | 17 ++ 12 files changed, 628 insertions(+) create mode 100644 exercises/practice/pov/.docs/instructions.append.md create mode 100644 exercises/practice/pov/.docs/instructions.md create mode 100644 exercises/practice/pov/.gitignore create mode 100644 exercises/practice/pov/.meta/config.json create mode 100644 exercises/practice/pov/.meta/example.rs create mode 100644 exercises/practice/pov/.meta/test_template.tera create mode 100644 exercises/practice/pov/.meta/tests.toml create mode 100644 exercises/practice/pov/Cargo.toml create mode 100644 exercises/practice/pov/src/lib.rs create mode 100644 exercises/practice/pov/tests/pov.rs diff --git a/config.json b/config.json index f596be3f8..fa7e8283b 100644 --- a/config.json +++ b/config.json @@ -1404,6 +1404,15 @@ "macros_by_example" ] }, + { + "slug": "pov", + "name": "POV", + "uuid": "bf7b7309-3d34-4893-a584-f7742502e012", + "practices": [], + "prerequisites": [], + "difficulty": 10, + "topics": [] + }, { "slug": "poker", "name": "Poker", diff --git a/exercises/practice/pov/.docs/instructions.append.md b/exercises/practice/pov/.docs/instructions.append.md new file mode 100644 index 000000000..db8425a81 --- /dev/null +++ b/exercises/practice/pov/.docs/instructions.append.md @@ -0,0 +1,7 @@ +# Instructions append + +## Rust Specific Exercise Notes + +You are free to choose the internal representation of your tree. +However, watch out if you store the children in an ordered container. +Two trees must compare as equal independent of the children's order. diff --git a/exercises/practice/pov/.docs/instructions.md b/exercises/practice/pov/.docs/instructions.md new file mode 100644 index 000000000..0fdeed225 --- /dev/null +++ b/exercises/practice/pov/.docs/instructions.md @@ -0,0 +1,41 @@ +# Instructions + +Reparent a tree on a selected node. + +A [tree][wiki-tree] is a special type of [graph][wiki-graph] where all nodes are connected but there are no cycles. +That means, there is exactly one path to get from one node to another for any pair of nodes. + +This exercise is all about re-orientating a tree to see things from a different point of view. +For example family trees are usually presented from the ancestor's perspective: + +```text + +------0------+ + | | | + +-1-+ +-2-+ +-3-+ + | | | | | | + 4 5 6 7 8 9 +``` + +But there is no inherent direction in a tree. +The same information can be presented from the perspective of any other node in the tree, by pulling it up to the root and dragging its relationships along with it. +So the same tree from 6's perspective would look like: + +```text + 6 + | + +-----2-----+ + | | + 7 +-----0-----+ + | | + +-1-+ +-3-+ + | | | | + 4 5 8 9 +``` + +This lets us more simply describe the paths between two nodes. +So for example the path from 6-9 (which in the first tree goes up to the root and then down to a different leaf node) can be seen to follow the path 6-2-0-3-9. + +This exercise involves taking an input tree and re-orientating it from the point of view of one of the nodes. + +[wiki-graph]: https://en.wikipedia.org/wiki/Tree_(graph_theory) +[wiki-tree]: https://en.wikipedia.org/wiki/Graph_(discrete_mathematics) diff --git a/exercises/practice/pov/.gitignore b/exercises/practice/pov/.gitignore new file mode 100644 index 000000000..96ef6c0b9 --- /dev/null +++ b/exercises/practice/pov/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/exercises/practice/pov/.meta/config.json b/exercises/practice/pov/.meta/config.json new file mode 100644 index 000000000..dfab027d4 --- /dev/null +++ b/exercises/practice/pov/.meta/config.json @@ -0,0 +1,21 @@ +{ + "authors": [ + "devcyjung", + "senekor" + ], + "files": { + "solution": [ + "src/lib.rs", + "Cargo.toml" + ], + "test": [ + "tests/pov.rs" + ], + "example": [ + ".meta/example.rs" + ] + }, + "blurb": "Reparent a graph on a selected node.", + "source": "Adaptation of exercise from 4clojure", + "source_url": "https://github.com/oxalorg/4ever-clojure" +} diff --git a/exercises/practice/pov/.meta/example.rs b/exercises/practice/pov/.meta/example.rs new file mode 100644 index 000000000..bacef69f2 --- /dev/null +++ b/exercises/practice/pov/.meta/example.rs @@ -0,0 +1,84 @@ +use std::fmt::Debug; + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct Tree { + label: T, + children: Vec>>, +} + +impl Tree { + pub fn new(label: T) -> Self { + Self { + label, + children: Default::default(), + } + } + + pub fn with_child(mut self, child: Self) -> Self { + self.children.insert( + self.children + .binary_search_by(|c| c.label.cmp(&child.label)) + .unwrap_err(), + Box::new(child), + ); + self + } + + pub fn pov_from(&mut self, from: &T) -> bool { + self.pov_from_rec(from).is_some() + } + + fn pov_from_rec(&mut self, from: &T) -> Option> { + if &self.label == from { + return Some(Vec::new()); + } + + // Run `pov_from_rec` over all children, until finding the one where it + // worked. That also returns the list of indexes to traverse to find the + // insertion point for the old POV. + let (pos, mut index_list) = self + .children + .iter_mut() + .enumerate() + .find_map(|(i, child)| child.pov_from_rec(from).map(|index_list| (i, index_list)))?; + + // swap old and new POV + let mut old_pov = self.children.remove(pos); + std::mem::swap(self, &mut old_pov); + + // find parent of old POV + let mut parent_of_old_pov = self; + for i in index_list.iter().rev() { + parent_of_old_pov = &mut parent_of_old_pov.children[*i]; + } + + // put old POV into its new place + let new_idx = parent_of_old_pov + .children + .binary_search_by(|c| c.label.cmp(&old_pov.label)) + .unwrap_err(); + parent_of_old_pov.children.insert(new_idx, old_pov); + + // Record index of old POV such that other recursive calls can insert + // their old POV as the child of ours. + index_list.push(new_idx); + + Some(index_list) + } + + pub fn path_between<'a>(&'a mut self, from: &'a T, to: &'a T) -> Option> { + if !self.pov_from(to) { + return None; + } + self.path_from(from) + } + + fn path_from<'a>(&'a self, from: &'a T) -> Option> { + if &self.label == from { + return Some(vec![from]); + } + let mut path = self.children.iter().find_map(|c| c.path_from(from))?; + path.push(&self.label); + Some(path) + } +} diff --git a/exercises/practice/pov/.meta/test_template.tera b/exercises/practice/pov/.meta/test_template.tera new file mode 100644 index 000000000..a7b1afca8 --- /dev/null +++ b/exercises/practice/pov/.meta/test_template.tera @@ -0,0 +1,78 @@ +/// Forbid implementations that rely on Clone. +mod no_clone { + use pov::*; + + #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] + struct NotClone; + + #[test] + #[ignore] + fn doesnt_rely_on_clone() { + let mut tree = Tree::new(NotClone); + assert!(tree.pov_from(&NotClone)); + } +} + +/// Equality on trees must be independent of the order of children. +mod equality { + use pov::*; + use pretty_assertions::assert_eq; + + #[test] + #[ignore] + fn equality_is_order_independent() { + let a = Tree::new("root").with_child(Tree::new("left")).with_child(Tree::new("right")); + let b = Tree::new("root").with_child(Tree::new("right")).with_child(Tree::new("left")); + assert_eq!(a, b); + } +} + +{% macro render_tree(tree) -%} + Tree::new("{{ tree.label }}") + {%- if tree.children -%}{%- for child in tree.children -%} + .with_child({{ self::render_tree(tree=child) }}) + {%- endfor -%}{%- endif -%} +{%- endmacro -%} + +{%- macro render_vec(values) -%} +vec![ + {%- for value in values -%} + &"{{ value }}", + {%- endfor -%} +] +{%- endmacro -%} + +{% for test_group in cases %} +/// {{ test_group.description }} +mod {{ test_group.cases[0].property | make_ident }} { + use pov::*; + use pretty_assertions::assert_eq; + +{% for test in test_group.cases %} +#[test] +#[ignore] +fn {{ test.description | make_ident }}() { + let mut tree = {{ self::render_tree(tree=test.input.tree) }}; + {%- if test.property == "fromPov" -%} + {%- if not test.expected -%} + assert!(!tree.pov_from(&"{{ test.input.from }}")); + {%- else -%} + assert!(tree.pov_from(&"{{ test.input.from }}")); + let expected = {{ self::render_tree(tree=test.expected) }}; + assert_eq!(tree, expected); + {%- endif -%} + {%- elif test.property == "pathTo" -%} + let result = tree.path_between(&"{{ test.input.from }}", &"{{ test.input.to }}"); + {%- if not test.expected -%} + let expected: Option> = None; + {%- else -%} + let expected = Some({{ self::render_vec(values=test.expected) }}); + {%- endif -%} + assert_eq!(result, expected); + {%- else -%} + Invalid property: {{ test.property }} + {%- endif -%} +} +{% endfor %} +} +{% endfor %} diff --git a/exercises/practice/pov/.meta/tests.toml b/exercises/practice/pov/.meta/tests.toml new file mode 100644 index 000000000..bfa0bb630 --- /dev/null +++ b/exercises/practice/pov/.meta/tests.toml @@ -0,0 +1,55 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[1b3cd134-49ad-4a7d-8376-7087b7e70792] +description = "Reroot a tree so that its root is the specified node. -> Results in the same tree if the input tree is a singleton" + +[0778c745-0636-40de-9edd-25a8f40426f6] +description = "Reroot a tree so that its root is the specified node. -> Can reroot a tree with a parent and one sibling" + +[fdfdef0a-4472-4248-8bcf-19cf33f9c06e] +description = "Reroot a tree so that its root is the specified node. -> Can reroot a tree with a parent and many siblings" + +[cbcf52db-8667-43d8-a766-5d80cb41b4bb] +description = "Reroot a tree so that its root is the specified node. -> Can reroot a tree with new root deeply nested in tree" + +[e27fa4fa-648d-44cd-90af-d64a13d95e06] +description = "Reroot a tree so that its root is the specified node. -> Moves children of the new root to same level as former parent" + +[09236c7f-7c83-42cc-87a1-25afa60454a3] +description = "Reroot a tree so that its root is the specified node. -> Can reroot a complex tree with cousins" + +[f41d5eeb-8973-448f-a3b0-cc1e019a4193] +description = "Reroot a tree so that its root is the specified node. -> Errors if target does not exist in a singleton tree" + +[9dc0a8b3-df02-4267-9a41-693b6aff75e7] +description = "Reroot a tree so that its root is the specified node. -> Errors if target does not exist in a large tree" + +[02d1f1d9-428d-4395-b026-2db35ffa8f0a] +description = "Given two nodes, find the path between them -> Can find path to parent" + +[d0002674-fcfb-4cdc-9efa-bfc54e3c31b5] +description = "Given two nodes, find the path between them -> Can find path to sibling" + +[c9877cd1-0a69-40d4-b362-725763a5c38f] +description = "Given two nodes, find the path between them -> Can find path to cousin" + +[9fb17a82-2c14-4261-baa3-2f3f234ffa03] +description = "Given two nodes, find the path between them -> Can find path not involving root" + +[5124ed49-7845-46ad-bc32-97d5ac7451b2] +description = "Given two nodes, find the path between them -> Can find path from nodes other than x" + +[f52a183c-25cc-4c87-9fc9-0e7f81a5725c] +description = "Given two nodes, find the path between them -> Errors if destination does not exist" + +[f4fe18b9-b4a2-4bd5-a694-e179155c2149] +description = "Given two nodes, find the path between them -> Errors if source does not exist" diff --git a/exercises/practice/pov/Cargo.toml b/exercises/practice/pov/Cargo.toml new file mode 100644 index 000000000..bec58b6eb --- /dev/null +++ b/exercises/practice/pov/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "pov" +version = "0.1.0" +edition = "2024" + +# Not all libraries from crates.io are available in Exercism's test runner. +# The full list of available libraries is here: +# https://github.com/exercism/rust-test-runner/blob/main/local-registry/Cargo.toml +[dependencies] + +[dev-dependencies] +pretty_assertions = "*" diff --git a/exercises/practice/pov/src/lib.rs b/exercises/practice/pov/src/lib.rs new file mode 100644 index 000000000..9efef18cd --- /dev/null +++ b/exercises/practice/pov/src/lib.rs @@ -0,0 +1,25 @@ +use std::fmt::Debug; + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct Tree { + remove_this: std::marker::PhantomData, +} + +impl Tree { + pub fn new(label: T) -> Self { + todo!("Create a new tree with the given {label:?}"); + } + + /// Builder-method for constructing a tree with children + pub fn with_child(self, child: Self) -> Self { + todo!("Add {child:?} to the tree and return the tree"); + } + + pub fn pov_from(&mut self, from: &T) -> bool { + todo!("Reparent the tree with the label {from:?} as root"); + } + + pub fn path_between<'a>(&'a mut self, from: &'a T, to: &'a T) -> Option> { + todo!("Return the shortest path between {from:?} and {to:?}"); + } +} diff --git a/exercises/practice/pov/tests/pov.rs b/exercises/practice/pov/tests/pov.rs new file mode 100644 index 000000000..ab4d6577b --- /dev/null +++ b/exercises/practice/pov/tests/pov.rs @@ -0,0 +1,277 @@ +/// Forbid implementations that rely on Clone. +mod no_clone { + use pov::*; + + #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] + struct NotClone; + + #[test] + fn doesnt_rely_on_clone() { + let mut tree = Tree::new(NotClone); + assert!(tree.pov_from(&NotClone)); + } +} + +/// Equality on trees must be independent of the order of children. +mod equality { + use pov::*; + use pretty_assertions::assert_eq; + + #[test] + #[ignore] + fn equality_is_order_independent() { + let a = Tree::new("root") + .with_child(Tree::new("left")) + .with_child(Tree::new("right")); + let b = Tree::new("root") + .with_child(Tree::new("right")) + .with_child(Tree::new("left")); + assert_eq!(a, b); + } +} + +/// Reroot a tree so that its root is the specified node. +mod from_pov { + use pov::*; + use pretty_assertions::assert_eq; + + #[test] + #[ignore] + fn results_in_the_same_tree_if_the_input_tree_is_a_singleton() { + let mut tree = Tree::new("x"); + assert!(tree.pov_from(&"x")); + let expected = Tree::new("x"); + assert_eq!(tree, expected); + } + + #[test] + #[ignore] + fn can_reroot_a_tree_with_a_parent_and_one_sibling() { + let mut tree = Tree::new("parent") + .with_child(Tree::new("x")) + .with_child(Tree::new("sibling")); + assert!(tree.pov_from(&"x")); + let expected = + Tree::new("x").with_child(Tree::new("parent").with_child(Tree::new("sibling"))); + assert_eq!(tree, expected); + } + + #[test] + #[ignore] + fn can_reroot_a_tree_with_a_parent_and_many_siblings() { + let mut tree = Tree::new("parent") + .with_child(Tree::new("a")) + .with_child(Tree::new("x")) + .with_child(Tree::new("b")) + .with_child(Tree::new("c")); + assert!(tree.pov_from(&"x")); + let expected = Tree::new("x").with_child( + Tree::new("parent") + .with_child(Tree::new("a")) + .with_child(Tree::new("b")) + .with_child(Tree::new("c")), + ); + assert_eq!(tree, expected); + } + + #[test] + #[ignore] + fn can_reroot_a_tree_with_new_root_deeply_nested_in_tree() { + let mut tree = Tree::new("level-0").with_child(Tree::new("level-1").with_child( + Tree::new("level-2").with_child(Tree::new("level-3").with_child(Tree::new("x"))), + )); + assert!(tree.pov_from(&"x")); + let expected = Tree::new("x").with_child(Tree::new("level-3").with_child( + Tree::new("level-2").with_child(Tree::new("level-1").with_child(Tree::new("level-0"))), + )); + assert_eq!(tree, expected); + } + + #[test] + #[ignore] + fn moves_children_of_the_new_root_to_same_level_as_former_parent() { + let mut tree = Tree::new("parent").with_child( + Tree::new("x") + .with_child(Tree::new("kid-0")) + .with_child(Tree::new("kid-1")), + ); + assert!(tree.pov_from(&"x")); + let expected = Tree::new("x") + .with_child(Tree::new("kid-0")) + .with_child(Tree::new("kid-1")) + .with_child(Tree::new("parent")); + assert_eq!(tree, expected); + } + + #[test] + #[ignore] + fn can_reroot_a_complex_tree_with_cousins() { + let mut tree = Tree::new("grandparent") + .with_child( + Tree::new("parent") + .with_child( + Tree::new("x") + .with_child(Tree::new("kid-0")) + .with_child(Tree::new("kid-1")), + ) + .with_child(Tree::new("sibling-0")) + .with_child(Tree::new("sibling-1")), + ) + .with_child( + Tree::new("uncle") + .with_child(Tree::new("cousin-0")) + .with_child(Tree::new("cousin-1")), + ); + assert!(tree.pov_from(&"x")); + let expected = Tree::new("x") + .with_child(Tree::new("kid-1")) + .with_child(Tree::new("kid-0")) + .with_child( + Tree::new("parent") + .with_child(Tree::new("sibling-0")) + .with_child(Tree::new("sibling-1")) + .with_child( + Tree::new("grandparent").with_child( + Tree::new("uncle") + .with_child(Tree::new("cousin-0")) + .with_child(Tree::new("cousin-1")), + ), + ), + ); + assert_eq!(tree, expected); + } + + #[test] + #[ignore] + fn errors_if_target_does_not_exist_in_a_singleton_tree() { + let mut tree = Tree::new("x"); + assert!(!tree.pov_from(&"nonexistent")); + } + + #[test] + #[ignore] + fn errors_if_target_does_not_exist_in_a_large_tree() { + let mut tree = Tree::new("parent") + .with_child( + Tree::new("x") + .with_child(Tree::new("kid-0")) + .with_child(Tree::new("kid-1")), + ) + .with_child(Tree::new("sibling-0")) + .with_child(Tree::new("sibling-1")); + assert!(!tree.pov_from(&"nonexistent")); + } +} + +/// Given two nodes, find the path between them +mod path_to { + use pov::*; + use pretty_assertions::assert_eq; + + #[test] + #[ignore] + fn can_find_path_to_parent() { + let mut tree = Tree::new("parent") + .with_child(Tree::new("x")) + .with_child(Tree::new("sibling")); + let result = tree.path_between(&"x", &"parent"); + let expected = Some(vec![&"x", &"parent"]); + assert_eq!(result, expected); + } + + #[test] + #[ignore] + fn can_find_path_to_sibling() { + let mut tree = Tree::new("parent") + .with_child(Tree::new("a")) + .with_child(Tree::new("x")) + .with_child(Tree::new("b")) + .with_child(Tree::new("c")); + let result = tree.path_between(&"x", &"b"); + let expected = Some(vec![&"x", &"parent", &"b"]); + assert_eq!(result, expected); + } + + #[test] + #[ignore] + fn can_find_path_to_cousin() { + let mut tree = Tree::new("grandparent") + .with_child( + Tree::new("parent") + .with_child( + Tree::new("x") + .with_child(Tree::new("kid-0")) + .with_child(Tree::new("kid-1")), + ) + .with_child(Tree::new("sibling-0")) + .with_child(Tree::new("sibling-1")), + ) + .with_child( + Tree::new("uncle") + .with_child(Tree::new("cousin-0")) + .with_child(Tree::new("cousin-1")), + ); + let result = tree.path_between(&"x", &"cousin-1"); + let expected = Some(vec![&"x", &"parent", &"grandparent", &"uncle", &"cousin-1"]); + assert_eq!(result, expected); + } + + #[test] + #[ignore] + fn can_find_path_not_involving_root() { + let mut tree = Tree::new("grandparent").with_child( + Tree::new("parent") + .with_child(Tree::new("x")) + .with_child(Tree::new("sibling-0")) + .with_child(Tree::new("sibling-1")), + ); + let result = tree.path_between(&"x", &"sibling-1"); + let expected = Some(vec![&"x", &"parent", &"sibling-1"]); + assert_eq!(result, expected); + } + + #[test] + #[ignore] + fn can_find_path_from_nodes_other_than_x() { + let mut tree = Tree::new("parent") + .with_child(Tree::new("a")) + .with_child(Tree::new("x")) + .with_child(Tree::new("b")) + .with_child(Tree::new("c")); + let result = tree.path_between(&"a", &"c"); + let expected = Some(vec![&"a", &"parent", &"c"]); + assert_eq!(result, expected); + } + + #[test] + #[ignore] + fn errors_if_destination_does_not_exist() { + let mut tree = Tree::new("parent") + .with_child( + Tree::new("x") + .with_child(Tree::new("kid-0")) + .with_child(Tree::new("kid-1")), + ) + .with_child(Tree::new("sibling-0")) + .with_child(Tree::new("sibling-1")); + let result = tree.path_between(&"x", &"nonexistent"); + let expected: Option> = None; + assert_eq!(result, expected); + } + + #[test] + #[ignore] + fn errors_if_source_does_not_exist() { + let mut tree = Tree::new("parent") + .with_child( + Tree::new("x") + .with_child(Tree::new("kid-0")) + .with_child(Tree::new("kid-1")), + ) + .with_child(Tree::new("sibling-0")) + .with_child(Tree::new("sibling-1")); + let result = tree.path_between(&"nonexistent", &"x"); + let expected: Option> = None; + assert_eq!(result, expected); + } +} diff --git a/rust-tooling/generate/src/custom_filters.rs b/rust-tooling/generate/src/custom_filters.rs index dcef8b45c..755dba17b 100644 --- a/rust-tooling/generate/src/custom_filters.rs +++ b/rust-tooling/generate/src/custom_filters.rs @@ -27,6 +27,7 @@ pub fn make_ident(value: &Value, _args: &HashMap) -> Result) -> Result String { + let mut chars: Vec<_> = input.chars().collect(); + let mut i = 0; + while i + 1 < chars.len() { + let (left, right) = (chars[i], chars[i + 1]); + if right.is_ascii_uppercase() { + chars[i + 1] = right.to_ascii_lowercase(); + if left.is_ascii_alphabetic() { + chars.insert(i + 1, '_'); + } + } + i += 1; + } + chars.into_iter().collect() +} + pub fn fmt_num(value: &Value, _args: &HashMap) -> Result { let Some(value) = value.as_number() else { return Err(tera::Error::call_filter( From 2b7452eabc7b0b5193d5fa0ec968c69e51148b36 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 9 Sep 2025 23:48:11 +0200 Subject: [PATCH 428/436] Setup compilation cache for CI (#2095) --- .github/workflows/tests.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 81d5adcc6..53b8a2baf 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -86,6 +86,11 @@ jobs: with: toolchain: stable + - name: Setup compilation cache + uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0 + with: + workspaces: "rust-tooling -> rust-tooling/target" + - name: Fetch configlet run: ./bin/fetch-configlet From 8633954cfe6ccb16f07e3564c256f217b3a7eff9 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Mon, 15 Sep 2025 20:46:18 +0200 Subject: [PATCH 429/436] migrate from just to mise (#2098) --- docs/CONTRIBUTING.md | 8 ++++---- justfile | 30 --------------------------- mise.toml | 34 +++++++++++++++++++++++++++++++ rust-tooling/generate/src/main.rs | 3 ++- 4 files changed, 40 insertions(+), 35 deletions(-) delete mode 100644 justfile create mode 100644 mise.toml diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 51ca7dded..52af69b34 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -17,11 +17,11 @@ Some tooling is present as bash scripts in `bin/`. A lot more is present in `rust-tooling/`, which should be preferred for anything non-trivial. -There is also a [`justfile`](https://github.com/casey/just) +There is also a [`mise`](https://mise.jdx.dev/) configuration with a couple useful commands to interact with the repo. Feel free to extend it. -If you want to run CI tests locally, `just test` will get you quite far. +If you want to run CI tests locally, `mise run test` will get you quite far. ## Excluding tests and adding custom ones @@ -41,7 +41,7 @@ Please familiarize yourself with the [Exercism documentation about practice exer [Exercism documentation about practice exercises]: https://exercism.org/docs/building/tracks/practice-exercises -Run `just add-exercise` and you'll be prompted for the minimal +Run `mise run add-exercise` and you'll be prompted for the minimal information required to generate the exercise stub for you. After that, jump in the generated exercise and fill in any placeholders you find. This includes most notably: @@ -88,7 +88,7 @@ This includes their test suite and user-facing documentation. Before proposing changes here, check if they should be made in `problem-specifications` instead. -Run `just update-exercise` to update an exercise. +Run `mise run update-exercise` to update an exercise. This outsources most work to `configlet sync --update` and runs the test generator again. diff --git a/justfile b/justfile deleted file mode 100644 index fe7bfcf38..000000000 --- a/justfile +++ /dev/null @@ -1,30 +0,0 @@ -_default: - just --list --unsorted - -# configlet (downloads if necessary) -@configlet *args="": - [ -f ./bin/configlet ] || ./bin/fetch-configlet - ./bin/configlet {{ args }} - -# generate a new uuid straight to your clipboard -uuid: - just configlet uuid | tr -d '[:space:]' | wl-copy - -# simulate CI locally (WIP) -test: - just configlet lint - ./bin/lint_markdown.sh - shellcheck bin/*.sh - ./bin/check_exercises.sh - CLIPPY=true ./bin/check_exercises.sh - cd rust-tooling && cargo test - ./bin/format_exercises.sh ; git diff --exit-code - -symlink-problem-specifications: - @ [ -L problem-specifications ] || ./bin/symlink_problem_specifications.sh - -add-exercise *args="": symlink-problem-specifications - cd rust-tooling/generate; cargo run --quiet --release -- add {{ args }} - -update-exercise *args="": symlink-problem-specifications - cd rust-tooling/generate; cargo run --quiet --release -- update {{ args }} diff --git a/mise.toml b/mise.toml new file mode 100644 index 000000000..0bb095597 --- /dev/null +++ b/mise.toml @@ -0,0 +1,34 @@ +[tasks.test] +description = "simulate CI locally (WIP)" +run = [ + "mise run configlet lint", + "bin/lint_markdown.sh", + "shellcheck bin/*.sh", + "bin/check_exercises.sh", + "CLIPPY=true bin/check_exercises.sh", + "cd rust-tooling && cargo test", + "bin/format_exercises.sh ; git diff --exit-code", +] + +[tasks.add-exercise] +depends = "symlink-problem-specifications" +run = "cd rust-tooling/generate; cargo run --quiet --release -- add" + +[tasks.update-exercise] +depends = "symlink-problem-specifications" +run = "cd rust-tooling/generate; cargo run --quiet --release -- update" + +[tasks.configlet] +depends = "fetch-configlet" +quiet = true +run = "bin/configlet" + +[tasks.fetch-configlet] +hide = true +silent = true +run = "[ -f bin/configlet ] || bin/fetch-configlet" + +[tasks.symlink-problem-specifications] +hide = true +silent = true +run = "[ -L problem-specifications ] || bin/symlink_problem_specifications.sh" diff --git a/rust-tooling/generate/src/main.rs b/rust-tooling/generate/src/main.rs index 7dc98abe4..984223e3b 100644 --- a/rust-tooling/generate/src/main.rs +++ b/rust-tooling/generate/src/main.rs @@ -71,8 +71,9 @@ fn update_exercise(args: UpdateArgs) -> Result<()> { } fn make_configlet_generate_what_it_can(slug: &str) -> Result<()> { - let status = std::process::Command::new("just") + let status = std::process::Command::new("mise") .args([ + "run", "configlet", "sync", "--update", From 9eb4501f06cafd6d9841a617201ac8172d1816e1 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Mon, 15 Sep 2025 20:46:43 +0200 Subject: [PATCH 430/436] two-bucket: sync tests (#2097) closes #2096 --- .../practice/two-bucket/.meta/tests.toml | 6 +++++ .../practice/two-bucket/tests/two_bucket.rs | 24 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/exercises/practice/two-bucket/.meta/tests.toml b/exercises/practice/two-bucket/.meta/tests.toml index d6ff02f53..a3fe533ec 100644 --- a/exercises/practice/two-bucket/.meta/tests.toml +++ b/exercises/practice/two-bucket/.meta/tests.toml @@ -27,6 +27,12 @@ description = "Measure one step using bucket one of size 1 and bucket two of siz [eb329c63-5540-4735-b30b-97f7f4df0f84] description = "Measure using bucket one of size 2 and bucket two of size 3 - start with bucket one and end with bucket two" +[58d70152-bf2b-46bb-ad54-be58ebe94c03] +description = "Measure using bucket one much bigger than bucket two" + +[9dbe6499-caa5-4a58-b5ce-c988d71b8981] +description = "Measure using bucket one much smaller than bucket two" + [449be72d-b10a-4f4b-a959-ca741e333b72] description = "Not possible to reach the goal" diff --git a/exercises/practice/two-bucket/tests/two_bucket.rs b/exercises/practice/two-bucket/tests/two_bucket.rs index af845e9a4..f5ae69108 100644 --- a/exercises/practice/two-bucket/tests/two_bucket.rs +++ b/exercises/practice/two-bucket/tests/two_bucket.rs @@ -72,6 +72,30 @@ fn measure_using_bucket_one_of_size_2_and_bucket_two_of_size_3_start_with_bucket assert_eq!(output, expected); } +#[test] +#[ignore] +fn measure_using_bucket_one_much_bigger_than_bucket_two() { + let output = solve(5, 1, 2, &Bucket::One); + let expected = Some(BucketStats { + moves: 6, + goal_bucket: Bucket::One, + other_bucket: 1, + }); + assert_eq!(output, expected); +} + +#[test] +#[ignore] +fn measure_using_bucket_one_much_smaller_than_bucket_two() { + let output = solve(3, 15, 9, &Bucket::One); + let expected = Some(BucketStats { + moves: 6, + goal_bucket: Bucket::Two, + other_bucket: 0, + }); + assert_eq!(output, expected); +} + #[test] #[ignore] fn not_possible_to_reach_the_goal() { From 3d87f6eb65f46594812c7d756cfa17f109827e67 Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 30 Sep 2025 15:57:52 +0200 Subject: [PATCH 431/436] Do not hide test output in log file (#2100) Putting the clippy output in a log file is somewhat convenient for local use. But if there's a failure in CI that's difficult to reproduce locally, it only makes things harder to debug. The actual problem must've been that clippy used to be installed in the GitHub Actions environment by default (but not anymore), so we didn't notice that it's not explicitly requested in our workflow. --- .github/workflows/tests.yml | 1 + rust-tooling/ci-tests/tests/stubs_are_warning_free.rs | 9 +-------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 53b8a2baf..6913baf99 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -85,6 +85,7 @@ jobs: uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 with: toolchain: stable + components: clippy - name: Setup compilation cache uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0 diff --git a/rust-tooling/ci-tests/tests/stubs_are_warning_free.rs b/rust-tooling/ci-tests/tests/stubs_are_warning_free.rs index 4e06addfe..82708b045 100644 --- a/rust-tooling/ci-tests/tests/stubs_are_warning_free.rs +++ b/rust-tooling/ci-tests/tests/stubs_are_warning_free.rs @@ -73,12 +73,5 @@ fn stubs_are_warning_free() { if !log.is_empty() { std::fs::write("clippy.log", &log).expect("should write clippy.log"); } - assert!( - log.is_empty(), - " - ╔═════════════════════════════════════════╗ - ║ clippy found warnings, check clippy.log ║ - ╚═════════════════════════════════════════╝ - " - ); + assert!(log.is_empty(), "{log}"); } From 6f4d44c9f2499babbb59b3740877f75f74b06bbd Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Tue, 30 Sep 2025 16:02:33 +0200 Subject: [PATCH 432/436] paasio: Prevent non-unwinding panic in tests (#2099) Related forum post: https://forum.exercism.org/t/test-runner-fail-on-paas/19426 [no important files changed] --- exercises/practice/paasio/tests/paasio.rs | 34 +++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/exercises/practice/paasio/tests/paasio.rs b/exercises/practice/paasio/tests/paasio.rs index 6f75a96aa..7a8c0acc4 100644 --- a/exercises/practice/paasio/tests/paasio.rs +++ b/exercises/practice/paasio/tests/paasio.rs @@ -136,7 +136,22 @@ mod write_string { fn sink_buffered_windowed() { let data = INPUT; let size = data.len(); - let mut writer = BufWriter::new(WriteStats::new(io::sink())); + + // We store the inner writer in a separate variable so its destructor + // is called correctly. We then wrap a mutable reference to it in a + // buffered writer. The destructor of the buffered writer is suppressed, + // because it tries to flush the inner writer. (It doesn't do anything + // else, so it's fine to skip it.) This would cause a non-unwinding + // panic if the inner writer hasn't implemented `write` yet. The + // standard library implementation of `BufWriter` does try to keep track + // of a panic by the inner writer, but it's not perfect. We access the + // inner writer later with `.get_ref()`. If there is a panic in that + // situation, the buffered writer cannot observe and track it. + // + // Related forum discussion: + // https://forum.exercism.org/t/test-runner-fail-on-paas/19426 + let mut inner_writer = WriteStats::new(io::sink()); + let mut writer = std::mem::ManuallyDrop::new(BufWriter::new(&mut inner_writer)); for chunk in data.chunks(CHUNK_SIZE) { let written = writer.write(chunk); @@ -286,7 +301,22 @@ mod write_byte_literal { fn sink_buffered_windowed() { let data = INPUT; let size = data.len(); - let mut writer = BufWriter::new(WriteStats::new(io::sink())); + + // We store the inner writer in a separate variable so its destructor + // is called correctly. We then wrap a mutable reference to it in a + // buffered writer. The destructor of the buffered writer is suppressed, + // because it tries to flush the inner writer. (It doesn't do anything + // else, so it's fine to skip it.) This would cause a non-unwinding + // panic if the inner writer hasn't implemented `write` yet. The + // standard library implementation of `BufWriter` does try to keep track + // of a panic by the inner writer, but it's not perfect. We access the + // inner writer later with `.get_ref()`. If there is a panic in that + // situation, the buffered writer cannot observe and track it. + // + // Related forum discussion: + // https://forum.exercism.org/t/test-runner-fail-on-paas/19426 + let mut inner_writer = WriteStats::new(io::sink()); + let mut writer = std::mem::ManuallyDrop::new(BufWriter::new(&mut inner_writer)); for chunk in data.chunks(CHUNK_SIZE) { let written = writer.write(chunk); From c1010bb9a0c460b4b877f664c622b7c6b69174fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Oct 2025 11:57:55 +0200 Subject: [PATCH 433/436] build(deps): bump Swatinem/rust-cache from 2.8.0 to 2.8.1 (#2101) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6913baf99..f916c0158 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -88,7 +88,7 @@ jobs: components: clippy - name: Setup compilation cache - uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0 + uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1 with: workspaces: "rust-tooling -> rust-tooling/target" From 5fb310bb616ccf68b28dee82a15244bc65fcb86e Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Wed, 1 Oct 2025 13:05:18 +0200 Subject: [PATCH 434/436] Fix clippy warning (#2102) https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_multiple_of --- exercises/practice/leap/.meta/example.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/exercises/practice/leap/.meta/example.rs b/exercises/practice/leap/.meta/example.rs index 8549b184b..b4f5a3cb3 100644 --- a/exercises/practice/leap/.meta/example.rs +++ b/exercises/practice/leap/.meta/example.rs @@ -1,4 +1,3 @@ pub fn is_leap_year(year: u64) -> bool { - let has_factor = |n| year % n == 0; - has_factor(4) && (!has_factor(100) || has_factor(400)) + year.is_multiple_of(4) && (!year.is_multiple_of(100) || year.is_multiple_of(400)) } From 06f6b2d401ab3d0d31058fd1eb7360bb3f8103c3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 15 Oct 2025 04:22:24 +0200 Subject: [PATCH 435/436] =?UTF-8?q?=F0=9F=A4=96=20Configlet=20sync:=20docs?= =?UTF-8?q?,=20metadata,=20and=20filepaths=20(#2103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- exercises/practice/triangle/.docs/instructions.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/exercises/practice/triangle/.docs/instructions.md b/exercises/practice/triangle/.docs/instructions.md index 755cb8d19..e9b053dcd 100644 --- a/exercises/practice/triangle/.docs/instructions.md +++ b/exercises/practice/triangle/.docs/instructions.md @@ -14,7 +14,8 @@ A _scalene_ triangle has all sides of different lengths. For a shape to be a triangle at all, all sides have to be of length > 0, and the sum of the lengths of any two sides must be greater than or equal to the length of the third side. ~~~~exercism/note -We opted to not include tests for degenerate triangles (triangles that violate these rules) to keep things simpler. +_Degenerate triangles_ are triangles where the sum of the length of two sides is **equal** to the length of the third side, e.g. `1, 1, 2`. +We opted to not include tests for degenerate triangles in this exercise. You may handle those situations if you wish to do so, or safely ignore them. ~~~~ From 4d46bf58799c055a26312216809c9aa595a9fe0e Mon Sep 17 00:00:00 2001 From: Remo Senekowitsch Date: Wed, 15 Oct 2025 18:57:02 +0200 Subject: [PATCH 436/436] Fix clippy warnings (#2104) --- exercises/practice/nth-prime/.meta/example.rs | 2 +- exercises/practice/ocr-numbers/.meta/example.rs | 4 ++-- exercises/practice/perfect-numbers/.meta/example.rs | 2 +- exercises/practice/prime-factors/.meta/example.rs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/exercises/practice/nth-prime/.meta/example.rs b/exercises/practice/nth-prime/.meta/example.rs index ce9b07d6a..ba0986c29 100644 --- a/exercises/practice/nth-prime/.meta/example.rs +++ b/exercises/practice/nth-prime/.meta/example.rs @@ -1,7 +1,7 @@ fn is_prime(n: u32) -> bool { let mut i = 3; while (i * i) < (n + 1) { - if n % i == 0 { + if n.is_multiple_of(i) { return false; } i += 1; diff --git a/exercises/practice/ocr-numbers/.meta/example.rs b/exercises/practice/ocr-numbers/.meta/example.rs index 98153936f..5aea684df 100644 --- a/exercises/practice/ocr-numbers/.meta/example.rs +++ b/exercises/practice/ocr-numbers/.meta/example.rs @@ -8,12 +8,12 @@ pub enum Error { pub fn convert(input: &str) -> Result { let line_count = input.lines().count(); - if line_count % 4 != 0 { + if !line_count.is_multiple_of(4) { return Err(Error::InvalidRowCount(line_count)); } for line in input.lines() { let char_count = line.chars().count(); - if char_count % 3 != 0 { + if !char_count.is_multiple_of(3) { return Err(Error::InvalidColumnCount(char_count)); } } diff --git a/exercises/practice/perfect-numbers/.meta/example.rs b/exercises/practice/perfect-numbers/.meta/example.rs index 7fc827309..a2c6b3b3c 100644 --- a/exercises/practice/perfect-numbers/.meta/example.rs +++ b/exercises/practice/perfect-numbers/.meta/example.rs @@ -2,7 +2,7 @@ pub fn classify(num: u64) -> Option { if num == 0 { return None; } - let sum: u64 = (1..num).filter(|i| num % i == 0).sum(); + let sum: u64 = (1..num).filter(|i| num.is_multiple_of(*i)).sum(); Some(match sum.cmp(&num) { std::cmp::Ordering::Equal => Classification::Perfect, std::cmp::Ordering::Less => Classification::Deficient, diff --git a/exercises/practice/prime-factors/.meta/example.rs b/exercises/practice/prime-factors/.meta/example.rs index e8c6245da..52ab60979 100644 --- a/exercises/practice/prime-factors/.meta/example.rs +++ b/exercises/practice/prime-factors/.meta/example.rs @@ -3,7 +3,7 @@ pub fn factors(n: u64) -> Vec { let mut out: Vec = vec![]; let mut possible: u64 = 2; while val > 1 { - while val % possible == 0 { + while val.is_multiple_of(possible) { out.push(possible); val /= possible; }
Changelog

Sourced from mio's changelog.

0.8.11