From df4804923fba192deb52ab107c20ffad907a9219 Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Mon, 28 Aug 2023 21:15:04 +0200
Subject: [PATCH 01/53] remove ngram
The use case for this is extremely rare,
to the point where I think it's more benefitial
to keep the repository simple by deleting it.
---
.gitignore | 1 -
bin/generator-utils/utils.sh | 19 +++++--------------
util/ngram/Cargo.lock | 16 ----------------
util/ngram/Cargo.toml | 9 ---------
util/ngram/build | 2 --
util/ngram/src/main.rs | 26 --------------------------
6 files changed, 5 insertions(+), 68 deletions(-)
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/.gitignore b/.gitignore
index edfd68168..72154aeca 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,7 +7,6 @@ bin/configlet
bin/configlet.exe
bin/exercise
bin/exercise.exe
-bin/generator-utils/ngram
bin/generator-utils/escape_double_quotes
exercises/*/*/Cargo.lock
exercises/*/*/clippy.log
diff --git a/bin/generator-utils/utils.sh b/bin/generator-utils/utils.sh
index 227a9bcd8..7179f6d14 100755
--- a/bin/generator-utils/utils.sh
+++ b/bin/generator-utils/utils.sh
@@ -53,24 +53,15 @@ check_exercise_existence() {
# 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')
+ 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/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 00cd03d027fb07409c53a077d68b171f73bee81c Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Mon, 28 Aug 2023 21:23:45 +0200
Subject: [PATCH 02/53] fix clean_topics_vs_practices.py
---
bin/clean_topics_vs_practices.py | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/bin/clean_topics_vs_practices.py b/bin/clean_topics_vs_practices.py
index ed3b55d01..4220ba04a 100755
--- a/bin/clean_topics_vs_practices.py
+++ b/bin/clean_topics_vs_practices.py
@@ -9,7 +9,7 @@ def main():
concepts = {c['slug'] for c in config['concepts']}
for practice_exercise in config['exercises']['practice']:
- if practice_exercise['topics'] is None:
+ if 'topics' not in practice_exercise or practice_exercise['topics'] is None:
continue
practice_exercise['practices'].extend((topic for topic in practice_exercise['topics'] if topic in concepts))
@@ -25,6 +25,9 @@ def main():
practice_exercise['topics'].append(concept)
for practice_exercise in config['exercises']['practice']:
+ if 'topics' not in practice_exercise:
+ continue
+
practice_exercise['practices'].sort()
if practice_exercise['topics'] is not None:
From dc6d0a11bc42a18615930b9e2a319bce9b582993 Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Mon, 28 Aug 2023 21:42:34 +0200
Subject: [PATCH 03/53] remove generate_tests
This is super complicated and unmaintainable.
---
.gitignore | 1 -
bin/generate_tests | 61 -------------------
bin/test_template | 6 --
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 -----------
11 files changed, 176 deletions(-)
delete mode 100755 bin/generate_tests
delete mode 100644 bin/test_template
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
diff --git a/.gitignore b/.gitignore
index 72154aeca..a6ab6c99f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,7 +7,6 @@ bin/configlet
bin/configlet.exe
bin/exercise
bin/exercise.exe
-bin/generator-utils/escape_double_quotes
exercises/*/*/Cargo.lock
exercises/*/*/clippy.log
canonical_data.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/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/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);
-}
From 42891bc82a2b784fd1c0051f0587b4d7d776e654 Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Mon, 28 Aug 2023 21:57:32 +0200
Subject: [PATCH 04/53] remove unnecessary tests
All of these tests are there to prevent people from making mistakes,
when the better approach is to automate that work.
---
.github/workflows/tests.yml | 12 --------
_test/check_uuids.sh | 15 ----------
_test/count_ignores.sh | 40 -------------------------
_test/ensure_lib_src_rs_exist.sh | 51 --------------------------------
bin/.shellcheckrc | 3 --
bin/lint_tool_file_names.sh | 17 -----------
6 files changed, 138 deletions(-)
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 100644 bin/.shellcheckrc
delete mode 100755 bin/lint_tool_file_names.sh
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index b3618e495..92ecc29b9 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -21,18 +21,6 @@ jobs:
- name: Checkout code
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.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
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/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/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
From 9a6f4708bc409bec11269902f7db557c60af9458 Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Mon, 28 Aug 2023 22:39:31 +0200
Subject: [PATCH 05/53] add crate to de-/serialize the track config
Should come in handy as reference, for tests and generation.
Also fix a couple inconsistencies in the config.
---
Cargo.lock | 89 +++++++++++++++++++
Cargo.toml | 10 +++
config.json | 13 +--
dev/crates/track-config/Cargo.toml | 12 +++
dev/crates/track-config/src/lib.rs | 93 ++++++++++++++++++++
dev/crates/track-config/tests/deserialize.rs | 11 +++
6 files changed, 223 insertions(+), 5 deletions(-)
create mode 100644 Cargo.lock
create mode 100644 Cargo.toml
create mode 100644 dev/crates/track-config/Cargo.toml
create mode 100644 dev/crates/track-config/src/lib.rs
create mode 100644 dev/crates/track-config/tests/deserialize.rs
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 000000000..ead96b001
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,89 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "itoa"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
+
+[[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 = "ryu"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
+
+[[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 = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "track-config"
+version = "0.1.0"
+dependencies = [
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 000000000..eb01ceff5
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,10 @@
+[workspace]
+members = ["dev/crates/*"]
+
+[workspace.package]
+edition = "2021"
+
+[workspace.dependencies]
+serde = { version = "1.0.188", features = ["derive"] }
+serde_json = "1.0.105"
+track-config = { path = "dev/crates/track-config" }
diff --git a/config.json b/config.json
index dd533207c..0711e3590 100644
--- a/config.json
+++ b/config.json
@@ -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/dev/crates/track-config/Cargo.toml b/dev/crates/track-config/Cargo.toml
new file mode 100644
index 000000000..88df3c07e
--- /dev/null
+++ b/dev/crates/track-config/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "track-config"
+version = "0.1.0"
+edition.workspace = true
+
+description = "De-/Serialization of track configuration"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+serde.workspace = true
+serde_json.workspace = true
diff --git a/dev/crates/track-config/src/lib.rs b/dev/crates/track-config/src/lib.rs
new file mode 100644
index 000000000..23a08ea63
--- /dev/null
+++ b/dev/crates/track-config/src/lib.rs
@@ -0,0 +1,93 @@
+//! This crate provides a data structure for the track configuration.
+//! It is capable of serializing and deserializing the configuration,
+//! for example with `serde_json`.
+//!
+//! Some definitions are not yet perfectly precise,
+//! because we don't anticipate they will be needed much.
+//! Feel free to improve this if need be.
+
+use std::collections::HashMap;
+
+use serde::{Deserialize, Serialize};
+
+#[derive(Serialize, Deserialize)]
+#[serde(deny_unknown_fields)]
+pub struct TrackConfig {
+ pub language: String,
+ pub slug: String,
+ pub active: bool,
+ pub status: HashMap,
+ pub blurb: String,
+ pub version: u8,
+ pub online_editor: OnlineEditorConfig,
+ pub test_runner: HashMap,
+ pub files: HashMap>,
+ pub exercises: ExercisesConfig,
+ pub concepts: Vec,
+ pub key_features: Vec,
+ pub tags: Vec,
+}
+
+#[derive(Serialize, Deserialize)]
+#[serde(deny_unknown_fields)]
+pub struct OnlineEditorConfig {
+ pub indent_style: String,
+ pub indent_size: u8,
+ pub highlightjs_language: String,
+}
+
+#[derive(Serialize, Deserialize)]
+#[serde(deny_unknown_fields)]
+pub struct ExercisesConfig {
+ pub concept: Vec,
+ pub practice: Vec,
+ pub foregone: Vec,
+}
+
+#[derive(Serialize, Deserialize)]
+#[serde(deny_unknown_fields)]
+pub struct ConceptExerciseConfig {
+ pub slug: String,
+ pub uuid: String,
+ pub name: String,
+ pub difficulty: u8,
+ pub concepts: Vec,
+ pub prerequisites: Vec,
+ pub status: String,
+}
+
+#[derive(Serialize, Deserialize)]
+#[serde(rename_all = "lowercase")]
+pub enum PracticeExerciseStatus {
+ Deprecated,
+}
+
+#[derive(Serialize, Deserialize)]
+#[serde(deny_unknown_fields)]
+pub struct PracticeExerciseConfig {
+ pub slug: String,
+ pub name: String,
+ pub uuid: String,
+ pub practices: Vec,
+ pub prerequisites: Vec,
+ pub difficulty: u8,
+ pub topics: Vec,
+ #[serde(default)]
+ pub status: Option,
+}
+
+#[derive(Serialize, Deserialize)]
+#[serde(deny_unknown_fields)]
+pub struct ConceptConfig {
+ pub uuid: String,
+ pub slug: String,
+ pub name: String,
+}
+
+#[derive(Serialize, Deserialize)]
+#[serde(deny_unknown_fields)]
+pub struct KeyFeatureConfig {
+ pub icon: String,
+ pub title: String,
+ pub content: String,
+}
diff --git a/dev/crates/track-config/tests/deserialize.rs b/dev/crates/track-config/tests/deserialize.rs
new file mode 100644
index 000000000..8a5868465
--- /dev/null
+++ b/dev/crates/track-config/tests/deserialize.rs
@@ -0,0 +1,11 @@
+//! Make sure we can actually deserialize the track config
+
+use track_config::TrackConfig;
+
+static CONFIG: &str = include_str!("../../../../config.json");
+
+#[test]
+fn test_deserialize() {
+ let _: TrackConfig = serde_json::from_str(CONFIG)
+ .expect("should be able to deserialize the actual track config");
+}
From f6c1acad99967b51356eafa9e16c250edf98ec69 Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Mon, 28 Aug 2023 22:49:54 +0200
Subject: [PATCH 06/53] delete random python script
This is completely undocumented.
I guess it's sorting some stuff in the config.
Why? I don't know.
---
bin/clean_topics_vs_practices.py | 45 --------------------------------
1 file changed, 45 deletions(-)
delete mode 100755 bin/clean_topics_vs_practices.py
diff --git a/bin/clean_topics_vs_practices.py b/bin/clean_topics_vs_practices.py
deleted file mode 100755
index 4220ba04a..000000000
--- a/bin/clean_topics_vs_practices.py
+++ /dev/null
@@ -1,45 +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 'topics' not in practice_exercise or 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']:
- if 'topics' not in practice_exercise:
- continue
-
- 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()
From 8020cff4b286a1bec58cf65db3f92e8de04d5d23 Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Mon, 28 Aug 2023 22:54:46 +0200
Subject: [PATCH 07/53] remove windows specific documentation
This is not the place to teach Windows users how to run Bash scripts.
---
README.md | 2 --
_test/WINDOWS_README.md | 44 -----------------------------------------
2 files changed, 46 deletions(-)
delete mode 100644 _test/WINDOWS_README.md
diff --git a/README.md b/README.md
index 31a9c76eb..5db93fe22 100644
--- a/README.md
+++ b/README.md
@@ -49,8 +49,6 @@ Before submitting your pull request, you'll want to verify the changes in two wa
* Run all the tests for the Rust exercises
* Run an Exercism-specific linter to verify the track
-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.
-
### 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:
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.
From 9be57b151f978084e921775ca55ca04a2d185148 Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Mon, 28 Aug 2023 22:59:15 +0200
Subject: [PATCH 08/53] remove outdated documentation
---
README.md | 2 --
1 file changed, 2 deletions(-)
diff --git a/README.md b/README.md
index 5db93fe22..30a20ddc9 100644
--- a/README.md
+++ b/README.md
@@ -67,8 +67,6 @@ Please see the documentation about [adding new exercises](https://github.com/exe
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).
From a82b27938edaea5129be4e1563289d1a77a60062 Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Mon, 28 Aug 2023 23:07:45 +0200
Subject: [PATCH 09/53] move tooling and test scripts to dev/scripts
makes the repo structure less confusing
---
.github/CODEOWNERS | 3 +--
.github/workflows/tests.yml | 24 +++++++++----------
.gitignore | 5 +---
{bin => dev/scripts}/add_practice_exercise | 18 +++++++-------
{_test => dev/scripts}/check_exercises.sh | 14 +++++------
.../scripts}/check_exercises_for_authors.sh | 0
.../scripts}/ensure_stubs_compile.sh | 0
{bin => dev/scripts}/fetch-configlet | 0
{bin => dev/scripts}/fetch_canonical_data | 2 +-
.../scripts/format_exercises.sh | 0
.../scripts}/generator-utils/colors.sh | 0
.../scripts}/generator-utils/prompts.sh | 4 ++--
.../scripts}/generator-utils/templates.sh | 4 ++--
{bin => dev/scripts}/generator-utils/utils.sh | 4 ++--
{bin => dev/scripts}/lint_markdown.sh | 0
{bin => dev/scripts}/lint_trailing_spaces.sh | 0
.../scripts}/remove_trailing_whitespace.sh | 0
.../scripts/test_exercise.sh | 0
.../scripts}/verify_exercise_difficulties.sh | 0
docs/maintaining.md | 3 +--
20 files changed, 38 insertions(+), 43 deletions(-)
rename {bin => dev/scripts}/add_practice_exercise (88%)
rename {_test => dev/scripts}/check_exercises.sh (84%)
rename {_test => dev/scripts}/check_exercises_for_authors.sh (100%)
rename {_test => dev/scripts}/ensure_stubs_compile.sh (100%)
rename {bin => dev/scripts}/fetch-configlet (100%)
rename {bin => dev/scripts}/fetch_canonical_data (89%)
rename bin/format_exercises => dev/scripts/format_exercises.sh (100%)
rename {bin => dev/scripts}/generator-utils/colors.sh (100%)
rename {bin => dev/scripts}/generator-utils/prompts.sh (91%)
rename {bin => dev/scripts}/generator-utils/templates.sh (97%)
rename {bin => dev/scripts}/generator-utils/utils.sh (94%)
rename {bin => dev/scripts}/lint_markdown.sh (100%)
rename {bin => dev/scripts}/lint_trailing_spaces.sh (100%)
rename {bin => dev/scripts}/remove_trailing_whitespace.sh (100%)
rename bin/test-exercise => dev/scripts/test_exercise.sh (100%)
rename {_test => dev/scripts}/verify_exercise_difficulties.sh (100%)
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 20408047c..0e9612a36 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -2,5 +2,4 @@
.github/CODEOWNERS @exercism/maintainers-admin
# Changes to `fetch-configlet` should be made in the `exercism/configlet` repo
-bin/fetch-configlet @exercism/maintainers-admin
-
+dev/scripts/fetch-configlet @exercism/maintainers-admin
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 92ecc29b9..e2bd3046f 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -22,13 +22,13 @@ jobs:
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- name: Verify exercise difficulties
- run: ./_test/verify_exercise_difficulties.sh
+ run: ./dev/scripts/verify_exercise_difficulties.sh
- name: Check exercises for authors
- run: ./_test/check_exercises_for_authors.sh
+ run: ./dev/scripts/check_exercises_for_authors.sh
- name: Ensure relevant files do not have trailing whitespace
- run: ./bin/lint_trailing_spaces.sh
+ run: ./dev/scripts/lint_trailing_spaces.sh
configlet:
name: configlet lint
@@ -46,10 +46,10 @@ jobs:
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- name: Fetch configlet
- run: ./bin/fetch-configlet
+ run: ./dev/scripts/fetch-configlet
- name: Lint configlet
- run: ./bin/configlet lint
+ run: ./dev/scripts/configlet lint
markdownlint:
name: markdown lint
@@ -64,7 +64,7 @@ jobs:
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- name: Run markdown lint
- run: ./bin/lint_markdown.sh
+ run: ./dev/scripts/lint_markdown.sh
# stolen from https://raw.githubusercontent.com/exercism/github-actions/main/.github/workflows/shellcheck.yml
shellcheck:
@@ -109,13 +109,13 @@ jobs:
- name: Check exercises
env:
DENYWARNINGS: ${{ matrix.deny_warnings }}
- run: ./_test/check_exercises.sh
+ run: ./dev/scripts/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
+ run: ./dev/scripts/ensure_stubs_compile.sh
continue-on-error: ${{ matrix.rust == 'beta' && matrix.deny_warnings == '1' }}
@@ -136,7 +136,7 @@ jobs:
run: rustfmt --version
- name: Format
- run: bin/format_exercises
+ run: dev/scripts/format_exercises
- name: Diff
run: |
@@ -175,12 +175,12 @@ jobs:
- name: Clippy tests
env:
CLIPPY: true
- run: ./_test/check_exercises.sh
+ run: ./dev/scripts/check_exercises.sh
- name: Clippy stubs
env:
CLIPPY: true
- run: ./_test/ensure_stubs_compile.sh
+ run: ./dev/scripts/ensure_stubs_compile.sh
nightly-compilation:
name: Check exercises on nightly (benchmark enabled)
@@ -206,4 +206,4 @@ jobs:
- name: Check exercises
env:
BENCHMARK: '1'
- run: ./_test/check_exercises.sh
+ run: ./dev/scripts/check_exercises.sh
diff --git a/.gitignore b/.gitignore
index a6ab6c99f..989dbc46f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,10 +3,7 @@
.DS_Store
**/target
tmp
-bin/configlet
-bin/configlet.exe
-bin/exercise
-bin/exercise.exe
+bin
exercises/*/*/Cargo.lock
exercises/*/*/clippy.log
canonical_data.json
diff --git a/bin/add_practice_exercise b/dev/scripts/add_practice_exercise
similarity index 88%
rename from bin/add_practice_exercise
rename to dev/scripts/add_practice_exercise
index a9068e431..2928dfe4e 100755
--- a/bin/add_practice_exercise
+++ b/dev/scripts/add_practice_exercise
@@ -1,23 +1,23 @@
#!/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=dev/scripts/generator-utils/utils.sh
+# shellcheck source=dev/scripts/generator-utils/templates.sh
+# shellcheck source=dev/scripts/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
+source ./dev/scripts/generator-utils/utils.sh
+source ./dev/scripts/generator-utils/prompts.sh
+source ./dev/scripts/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]"
+ echo "Usage: dev/scripts/add_practice_exercise [difficulty] [author-github-handle]"
exit 1
fi
@@ -38,7 +38,7 @@ command -v curl >/dev/null 2>&1 || {
}
# Build configlet
-bin/fetch-configlet
+dev/scripts/fetch-configlet
message "success" "Fetched configlet successfully!"
# Check if exercise exists in configlet info or in config.json
@@ -48,7 +48,7 @@ check_exercise_existence "$1"
slug="$1"
# Fetch canonical data
-canonical_json=$(bin/fetch_canonical_data "$slug")
+canonical_json=$(dev/scripts/fetch_canonical_data "$slug")
has_canonical_data=true
if [ "${canonical_json}" == "404: Not Found" ]; then
diff --git a/_test/check_exercises.sh b/dev/scripts/check_exercises.sh
similarity index 84%
rename from _test/check_exercises.sh
rename to dev/scripts/check_exercises.sh
index 5ec830c38..ccba26834 100755
--- a/_test/check_exercises.sh
+++ b/dev/scripts/check_exercises.sh
@@ -2,12 +2,12 @@
# 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"
+if [ ! -f "./dev/scripts/test-exercise" ]; then
+ echo "dev/scripts/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"
+if [ ! -x "./dev/scripts/test-exercise" ]; then
+ echo "dev/scripts/test-exercise does not have its executable bit set"
exit 1
fi
@@ -19,7 +19,7 @@ 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 dev/scripts/check_exercises.sh
if [ -n "$BENCHMARK" ]; then
target_dir=benches
else
@@ -70,11 +70,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
+ ./dev/scripts/test-exercise "$directory" --quiet --no-run
return_code=$((return_code | $?))
else
# Run the test and get the status
- ./bin/test-exercise "$directory" $release
+ ./dev/scripts/test-exercise "$directory" $release
return_code=$((return_code | $?))
fi
done
diff --git a/_test/check_exercises_for_authors.sh b/dev/scripts/check_exercises_for_authors.sh
similarity index 100%
rename from _test/check_exercises_for_authors.sh
rename to dev/scripts/check_exercises_for_authors.sh
diff --git a/_test/ensure_stubs_compile.sh b/dev/scripts/ensure_stubs_compile.sh
similarity index 100%
rename from _test/ensure_stubs_compile.sh
rename to dev/scripts/ensure_stubs_compile.sh
diff --git a/bin/fetch-configlet b/dev/scripts/fetch-configlet
similarity index 100%
rename from bin/fetch-configlet
rename to dev/scripts/fetch-configlet
diff --git a/bin/fetch_canonical_data b/dev/scripts/fetch_canonical_data
similarity index 89%
rename from bin/fetch_canonical_data
rename to dev/scripts/fetch_canonical_data
index 4226a4b92..6ef6e0021 100755
--- a/bin/fetch_canonical_data
+++ b/dev/scripts/fetch_canonical_data
@@ -7,7 +7,7 @@ set -euo pipefail
if [ $# -ne 1 ]; then
- echo "Usage: bin/fetch_canonical_data "
+ echo "Usage: dev/scripts/fetch_canonical_data "
exit 1
fi
diff --git a/bin/format_exercises b/dev/scripts/format_exercises.sh
similarity index 100%
rename from bin/format_exercises
rename to dev/scripts/format_exercises.sh
diff --git a/bin/generator-utils/colors.sh b/dev/scripts/generator-utils/colors.sh
similarity index 100%
rename from bin/generator-utils/colors.sh
rename to dev/scripts/generator-utils/colors.sh
diff --git a/bin/generator-utils/prompts.sh b/dev/scripts/generator-utils/prompts.sh
similarity index 91%
rename from bin/generator-utils/prompts.sh
rename to dev/scripts/generator-utils/prompts.sh
index fdec283f8..5a2226eed 100644
--- a/bin/generator-utils/prompts.sh
+++ b/dev/scripts/generator-utils/prompts.sh
@@ -1,9 +1,9 @@
#!/usr/bin/env bash
# see comment in utils.sh
-# shellcheck source=bin/generator-utils/colors.sh
+# shellcheck source=dev/scripts/generator-utils/colors.sh
# shellcheck source=./colors.sh
-source ./bin/generator-utils/colors.sh
+source ./dev/scripts/generator-utils/colors.sh
get_exercise_difficulty() {
read -rp "Difficulty of exercise (1-10): " exercise_difficulty
diff --git a/bin/generator-utils/templates.sh b/dev/scripts/generator-utils/templates.sh
similarity index 97%
rename from bin/generator-utils/templates.sh
rename to dev/scripts/generator-utils/templates.sh
index 8be909592..6878e8751 100755
--- a/bin/generator-utils/templates.sh
+++ b/dev/scripts/generator-utils/templates.sh
@@ -1,9 +1,9 @@
#!/usr/bin/env bash
# see comment in utils.sh
-# shellcheck source=bin/generator-utils/utils.sh
+# shellcheck source=dev/scripts/generator-utils/utils.sh
# shellcheck source=./utils.sh
-source ./bin/generator-utils/utils.sh
+source ./dev/scripts/generator-utils/utils.sh
create_fn_name() {
local slug=$1
diff --git a/bin/generator-utils/utils.sh b/dev/scripts/generator-utils/utils.sh
similarity index 94%
rename from bin/generator-utils/utils.sh
rename to dev/scripts/generator-utils/utils.sh
index 7179f6d14..b77a41f30 100755
--- a/bin/generator-utils/utils.sh
+++ b/dev/scripts/generator-utils/utils.sh
@@ -4,9 +4,9 @@
# 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=dev/scripts/generator-utils/colors.sh
# shellcheck source=./colors.sh
-source ./bin/generator-utils/colors.sh
+source ./dev/scripts/generator-utils/colors.sh
message() {
local flag=$1
diff --git a/bin/lint_markdown.sh b/dev/scripts/lint_markdown.sh
similarity index 100%
rename from bin/lint_markdown.sh
rename to dev/scripts/lint_markdown.sh
diff --git a/bin/lint_trailing_spaces.sh b/dev/scripts/lint_trailing_spaces.sh
similarity index 100%
rename from bin/lint_trailing_spaces.sh
rename to dev/scripts/lint_trailing_spaces.sh
diff --git a/bin/remove_trailing_whitespace.sh b/dev/scripts/remove_trailing_whitespace.sh
similarity index 100%
rename from bin/remove_trailing_whitespace.sh
rename to dev/scripts/remove_trailing_whitespace.sh
diff --git a/bin/test-exercise b/dev/scripts/test_exercise.sh
similarity index 100%
rename from bin/test-exercise
rename to dev/scripts/test_exercise.sh
diff --git a/_test/verify_exercise_difficulties.sh b/dev/scripts/verify_exercise_difficulties.sh
similarity index 100%
rename from _test/verify_exercise_difficulties.sh
rename to dev/scripts/verify_exercise_difficulties.sh
diff --git a/docs/maintaining.md b/docs/maintaining.md
index d794de3e9..ad826ae40 100644
--- a/docs/maintaining.md
+++ b/docs/maintaining.md
@@ -4,8 +4,7 @@ This document captures informal policies, tips, and topics useful to maintaining
## Internal Tooling
-We have a number of scripts for CI tests.
-They live in `bin/` and `_test/`.
+We have a number of scripts for CI tests in `dev/scripts/`.
## Internal Tooling Style Guide
From 4156158ad252ad9e7b5c0243bc0c323e996048f8 Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Mon, 28 Aug 2023 23:19:20 +0200
Subject: [PATCH 10/53] delete exercise generation scripts
The scope of these scripts is too big for Bash.
They will be rewritten in Rust,
with functionality to synchronize with problem-specs.
---
dev/scripts/add_practice_exercise | 112 ----------------
dev/scripts/generator-utils/colors.sh | 13 --
dev/scripts/generator-utils/prompts.sh | 40 ------
dev/scripts/generator-utils/templates.sh | 157 -----------------------
dev/scripts/generator-utils/utils.sh | 67 ----------
5 files changed, 389 deletions(-)
delete mode 100755 dev/scripts/add_practice_exercise
delete mode 100644 dev/scripts/generator-utils/colors.sh
delete mode 100644 dev/scripts/generator-utils/prompts.sh
delete mode 100755 dev/scripts/generator-utils/templates.sh
delete mode 100755 dev/scripts/generator-utils/utils.sh
diff --git a/dev/scripts/add_practice_exercise b/dev/scripts/add_practice_exercise
deleted file mode 100755
index 2928dfe4e..000000000
--- a/dev/scripts/add_practice_exercise
+++ /dev/null
@@ -1,112 +0,0 @@
-#!/usr/bin/env bash
-
-# see comment in generator-utils/utils.sh
-# shellcheck source=dev/scripts/generator-utils/utils.sh
-# shellcheck source=dev/scripts/generator-utils/templates.sh
-# shellcheck source=dev/scripts/generator-utils/prompts.sh
-# shellcheck source=./generator-utils/utils.sh
-# shellcheck source=./generator-utils/prompts.sh
-# shellcheck source=./generator-utils/templates.sh
-
-source ./dev/scripts/generator-utils/utils.sh
-source ./dev/scripts/generator-utils/prompts.sh
-source ./dev/scripts/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: dev/scripts/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
-dev/scripts/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=$(dev/scripts/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/dev/scripts/generator-utils/colors.sh b/dev/scripts/generator-utils/colors.sh
deleted file mode 100644
index d3c5f9339..000000000
--- a/dev/scripts/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/dev/scripts/generator-utils/prompts.sh b/dev/scripts/generator-utils/prompts.sh
deleted file mode 100644
index 5a2226eed..000000000
--- a/dev/scripts/generator-utils/prompts.sh
+++ /dev/null
@@ -1,40 +0,0 @@
-#!/usr/bin/env bash
-
-# see comment in utils.sh
-# shellcheck source=dev/scripts/generator-utils/colors.sh
-# shellcheck source=./colors.sh
-source ./dev/scripts/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/dev/scripts/generator-utils/templates.sh b/dev/scripts/generator-utils/templates.sh
deleted file mode 100755
index 6878e8751..000000000
--- a/dev/scripts/generator-utils/templates.sh
+++ /dev/null
@@ -1,157 +0,0 @@
-#!/usr/bin/env bash
-
-# see comment in utils.sh
-# shellcheck source=dev/scripts/generator-utils/utils.sh
-# shellcheck source=./utils.sh
-source ./dev/scripts/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/dev/scripts/generator-utils/utils.sh b/dev/scripts/generator-utils/utils.sh
deleted file mode 100755
index b77a41f30..000000000
--- a/dev/scripts/generator-utils/utils.sh
+++ /dev/null
@@ -1,67 +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=dev/scripts/generator-utils/colors.sh
-# shellcheck source=./colors.sh
-source ./dev/scripts/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!"
- exit 1
- fi
-}
From 3062e2aa10ee34a52af38a98730cfe91f462cbc1 Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Tue, 29 Aug 2023 08:19:17 +0200
Subject: [PATCH 11/53] update installation structions
Both rustfmt and clippy are installed by default these days.
I definitely think these tools should be recommended to students,
but the installation page is not the best place for that.
---
docs/INSTALLATION.md | 42 +++---------------------------------------
1 file changed, 3 insertions(+), 39 deletions(-)
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
From 93d34b302316b62eeab376257eaa1a38005e3979 Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Tue, 29 Aug 2023 08:35:32 +0200
Subject: [PATCH 12/53] unify contributing documentation
---
docs/CONTRIBUTING.md | 77 +++++++++++++++++++++++---------------------
docs/maintaining.md | 51 -----------------------------
2 files changed, 40 insertions(+), 88 deletions(-)
delete mode 100644 docs/maintaining.md
diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md
index 94af02384..13b5a9cde 100644
--- a/docs/CONTRIBUTING.md
+++ b/docs/CONTRIBUTING.md
@@ -1,39 +1,42 @@
# 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.
-
-> A work of art is never finished, merely abandoned.
->
-> -- Paul Valéry
-
-Nonetheless, feel free to peruse what we have written thus far!
-
-*Contributions welcome :)*
-
-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.
-
-## Ways to Contribute
-
-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
-
-## Merging Philosophy
-
-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.
-
-For example, [#1175](https://github.com/exercism/rust/pull/1175) fixed a single typo in a single file after minutes of collaboration.
-
-It was a small, short pull request with one logical change.
-
-[#653](https://github.com/exercism/rust/pull/653) introduced the doubly linked list exercise after months of collaboration.
-
-It was a big, long-running pull request with one logical change.
+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 agreed upon on the forum.
+
+[the Exercism forum]: https://forum.exercism.org/
+
+As one of the many tracks in Exercism,
+contributions should observe Exercism standards like the [Code of Conduct].
+This document introduces the ways you can help
+and what maintainers expect of contributors.
+
+[Code of Conduct]: https://exercism.org/code-of-conduct
+
+## General policies
+
+- Pull requests should adhere to [Exercism's PR guidelines].
+
+[Exercism's PR guidelines]: https://exercism.org/docs/community/being-a-good-community-member/pull-requests
+
+## Internal Tooling Style Guide
+
+- 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
+- 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
+ ```
diff --git a/docs/maintaining.md b/docs/maintaining.md
deleted file mode 100644
index ad826ae40..000000000
--- a/docs/maintaining.md
+++ /dev/null
@@ -1,51 +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 in `dev/scripts/`.
-
-## 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.
From df5bc08db6e2a083d53742f520af7bb3af13a330 Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Sat, 2 Sep 2023 20:15:07 +0200
Subject: [PATCH 13/53] make shell scripts more robust
```sh
git rev-parse --show-toplevel
```
will always work, whereas relative paths may break when a script is
moved to a different location in the repository.
---
dev/scripts/check_exercises.sh | 2 +-
dev/scripts/check_exercises_for_authors.sh | 2 +-
dev/scripts/ensure_stubs_compile.sh | 2 +-
dev/scripts/format_exercises.sh | 6 +++---
dev/scripts/verify_exercise_difficulties.sh | 2 +-
5 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/dev/scripts/check_exercises.sh b/dev/scripts/check_exercises.sh
index ccba26834..720d702d9 100755
--- a/dev/scripts/check_exercises.sh
+++ b/dev/scripts/check_exercises.sh
@@ -26,7 +26,7 @@ else
target_dir=tests
fi
-repo=$(cd "$(dirname "$0")/.." && pwd)
+repo=$(git rev-parse --show-toplevel)
if [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then
files="$(
diff --git a/dev/scripts/check_exercises_for_authors.sh b/dev/scripts/check_exercises_for_authors.sh
index d3c3fb10a..00d2972c0 100755
--- a/dev/scripts/check_exercises_for_authors.sh
+++ b/dev/scripts/check_exercises_for_authors.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
-repo=$(cd "$(dirname "$0")/.." && pwd)
+repo=$(git rev-parse --show-toplevel)
if grep -rnw "$repo/exercises/" --include="*.toml" -e "authors"; then
echo "Found 'authors' field in exercises";
diff --git a/dev/scripts/ensure_stubs_compile.sh b/dev/scripts/ensure_stubs_compile.sh
index 6f154ae31..623c2d1cd 100755
--- a/dev/scripts/ensure_stubs_compile.sh
+++ b/dev/scripts/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/dev/scripts/format_exercises.sh b/dev/scripts/format_exercises.sh
index 7798baea7..8d8fa2d71 100755
--- a/dev/scripts/format_exercises.sh
+++ b/dev/scripts/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/dev/scripts/verify_exercise_difficulties.sh b/dev/scripts/verify_exercise_difficulties.sh
index e7bb6179f..2e08faaa7 100755
--- a/dev/scripts/verify_exercise_difficulties.sh
+++ b/dev/scripts/verify_exercise_difficulties.sh
@@ -2,7 +2,7 @@
set -e
-repo=$(cd "$(dirname "$0")/.." && pwd)
+repo=$(git rev-parse --show-toplevel)
config=$repo/config.json
es=0
From e5e6555e2ef178de2bf4d3babafcdbb62acde8d5 Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Sat, 2 Sep 2023 20:34:31 +0200
Subject: [PATCH 14/53] replace shell script with a Rust test
---
dev/crates/track-config/src/lib.rs | 9 ++-
dev/crates/track-config/tests/difficulties.rs | 21 +++++++
dev/scripts/verify_exercise_difficulties.sh | 56 -------------------
3 files changed, 29 insertions(+), 57 deletions(-)
create mode 100644 dev/crates/track-config/tests/difficulties.rs
delete mode 100755 dev/scripts/verify_exercise_difficulties.sh
diff --git a/dev/crates/track-config/src/lib.rs b/dev/crates/track-config/src/lib.rs
index 23a08ea63..48d01ce30 100644
--- a/dev/crates/track-config/src/lib.rs
+++ b/dev/crates/track-config/src/lib.rs
@@ -44,6 +44,13 @@ pub struct ExercisesConfig {
pub foregone: Vec,
}
+#[derive(Serialize, Deserialize)]
+#[serde(rename_all = "lowercase")]
+pub enum ConceptExerciseStatus {
+ Active,
+ Wip,
+}
+
#[derive(Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ConceptExerciseConfig {
@@ -53,7 +60,7 @@ pub struct ConceptExerciseConfig {
pub difficulty: u8,
pub concepts: Vec,
pub prerequisites: Vec,
- pub status: String,
+ pub status: ConceptExerciseStatus,
}
#[derive(Serialize, Deserialize)]
diff --git a/dev/crates/track-config/tests/difficulties.rs b/dev/crates/track-config/tests/difficulties.rs
new file mode 100644
index 000000000..e978d3fa0
--- /dev/null
+++ b/dev/crates/track-config/tests/difficulties.rs
@@ -0,0 +1,21 @@
+//! Make sure exercise difficulties are set correctly
+
+use track_config::TrackConfig;
+
+static CONFIG: &str = include_str!("../../../../config.json");
+
+#[test]
+fn test_difficulties_are_valid() {
+ let config: TrackConfig = serde_json::from_str(CONFIG).unwrap();
+
+ let mut difficulties = config
+ .exercises
+ .concept
+ .iter()
+ .map(|e| e.difficulty)
+ .chain(config.exercises.practice.iter().map(|e| e.difficulty));
+
+ if difficulties.any(|d| !matches!(d, 1 | 4 | 7 | 10)) {
+ panic!("Exercises with invalid difficulty (must be in {{1, 4, 7, 10}})")
+ }
+}
diff --git a/dev/scripts/verify_exercise_difficulties.sh b/dev/scripts/verify_exercise_difficulties.sh
deleted file mode 100755
index 2e08faaa7..000000000
--- a/dev/scripts/verify_exercise_difficulties.sh
+++ /dev/null
@@ -1,56 +0,0 @@
-#!/usr/bin/env bash
-
-set -e
-
-repo=$(git rev-parse --show-toplevel)
-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
From 8b45c7302c6c4a31e14d261f6f93620a08a47c15 Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Sat, 2 Sep 2023 20:41:30 +0200
Subject: [PATCH 15/53] delete fetch_canonical_data
One could simply run `configlet sync` anc read from
`~/.cache/exercism/configlet/problem-specifications` instead.
---
dev/scripts/fetch_canonical_data | 27 ---------------------------
1 file changed, 27 deletions(-)
delete mode 100755 dev/scripts/fetch_canonical_data
diff --git a/dev/scripts/fetch_canonical_data b/dev/scripts/fetch_canonical_data
deleted file mode 100755
index 6ef6e0021..000000000
--- a/dev/scripts/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: dev/scripts/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/${slug}/canonical-data.json"
From fdfe1cf36537f39a10bde96474e6578f43b6b7bb Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Sat, 2 Sep 2023 21:22:36 +0200
Subject: [PATCH 16/53] replace another shell script with a Rust test
---
Cargo.lock | 14 +++++++++
Cargo.toml | 2 ++
dev/crates/repo-conventions/Cargo.toml | 11 +++++++
dev/crates/repo-conventions/src/lib.rs | 2 ++
.../tests/no_authors_in_cargo_toml.rs | 30 +++++++++++++++++++
dev/crates/track-config/Cargo.toml | 1 +
dev/crates/track-config/src/lib.rs | 8 ++++-
dev/crates/track-config/tests/deserialize.rs | 11 -------
dev/crates/track-config/tests/difficulties.rs | 17 +++++------
dev/scripts/check_exercises_for_authors.sh | 8 -----
10 files changed, 74 insertions(+), 30 deletions(-)
create mode 100644 dev/crates/repo-conventions/Cargo.toml
create mode 100644 dev/crates/repo-conventions/src/lib.rs
create mode 100644 dev/crates/repo-conventions/tests/no_authors_in_cargo_toml.rs
delete mode 100644 dev/crates/track-config/tests/deserialize.rs
delete mode 100755 dev/scripts/check_exercises_for_authors.sh
diff --git a/Cargo.lock b/Cargo.lock
index ead96b001..50099b4b3 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -8,6 +8,12 @@ version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
+[[package]]
+name = "once_cell"
+version = "1.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
+
[[package]]
name = "proc-macro2"
version = "1.0.66"
@@ -26,6 +32,13 @@ dependencies = [
"proc-macro2",
]
+[[package]]
+name = "repo-conventions"
+version = "0.1.0"
+dependencies = [
+ "track-config",
+]
+
[[package]]
name = "ryu"
version = "1.0.15"
@@ -78,6 +91,7 @@ dependencies = [
name = "track-config"
version = "0.1.0"
dependencies = [
+ "once_cell",
"serde",
"serde_json",
]
diff --git a/Cargo.toml b/Cargo.toml
index eb01ceff5..2c904f232 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,10 +1,12 @@
[workspace]
+resolver = "2"
members = ["dev/crates/*"]
[workspace.package]
edition = "2021"
[workspace.dependencies]
+once_cell = "1.18.0"
serde = { version = "1.0.188", features = ["derive"] }
serde_json = "1.0.105"
track-config = { path = "dev/crates/track-config" }
diff --git a/dev/crates/repo-conventions/Cargo.toml b/dev/crates/repo-conventions/Cargo.toml
new file mode 100644
index 000000000..b1212e68d
--- /dev/null
+++ b/dev/crates/repo-conventions/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "repo-conventions"
+version = "0.1.0"
+edition.workspace = true
+
+description = "Tests to enforce various repository conventions"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+track-config.workspace = true
diff --git a/dev/crates/repo-conventions/src/lib.rs b/dev/crates/repo-conventions/src/lib.rs
new file mode 100644
index 000000000..e57e93543
--- /dev/null
+++ b/dev/crates/repo-conventions/src/lib.rs
@@ -0,0 +1,2 @@
+//! This is just a stub.
+//! Without it, the tests don't run.
diff --git a/dev/crates/repo-conventions/tests/no_authors_in_cargo_toml.rs b/dev/crates/repo-conventions/tests/no_authors_in_cargo_toml.rs
new file mode 100644
index 000000000..54ebc4868
--- /dev/null
+++ b/dev/crates/repo-conventions/tests/no_authors_in_cargo_toml.rs
@@ -0,0 +1,30 @@
+use track_config::TRACK_CONFIG;
+
+/// 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 crate_dir = env!("CARGO_MANIFEST_DIR");
+
+ let cargo_toml_paths = TRACK_CONFIG
+ .exercises
+ .concept
+ .iter()
+ .map(|e| format!("concept/{}", e.slug))
+ .chain(
+ TRACK_CONFIG
+ .exercises
+ .practice
+ .iter()
+ .map(|e| format!("practice/{}", e.slug)),
+ )
+ .map(|s| format!("{crate_dir}/../../../exercises/{s}/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/dev/crates/track-config/Cargo.toml b/dev/crates/track-config/Cargo.toml
index 88df3c07e..473aaef61 100644
--- a/dev/crates/track-config/Cargo.toml
+++ b/dev/crates/track-config/Cargo.toml
@@ -8,5 +8,6 @@ description = "De-/Serialization of track configuration"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
+once_cell.workspace = true
serde.workspace = true
serde_json.workspace = true
diff --git a/dev/crates/track-config/src/lib.rs b/dev/crates/track-config/src/lib.rs
index 48d01ce30..e6dd9770d 100644
--- a/dev/crates/track-config/src/lib.rs
+++ b/dev/crates/track-config/src/lib.rs
@@ -1,15 +1,21 @@
//! This crate provides a data structure for the track configuration.
//! It is capable of serializing and deserializing the configuration,
//! for example with `serde_json`.
-//!
+//!
//! Some definitions are not yet perfectly precise,
//! because we don't anticipate they will be needed much.
//! Feel free to improve this if need be.
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(Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TrackConfig {
diff --git a/dev/crates/track-config/tests/deserialize.rs b/dev/crates/track-config/tests/deserialize.rs
deleted file mode 100644
index 8a5868465..000000000
--- a/dev/crates/track-config/tests/deserialize.rs
+++ /dev/null
@@ -1,11 +0,0 @@
-//! Make sure we can actually deserialize the track config
-
-use track_config::TrackConfig;
-
-static CONFIG: &str = include_str!("../../../../config.json");
-
-#[test]
-fn test_deserialize() {
- let _: TrackConfig = serde_json::from_str(CONFIG)
- .expect("should be able to deserialize the actual track config");
-}
diff --git a/dev/crates/track-config/tests/difficulties.rs b/dev/crates/track-config/tests/difficulties.rs
index e978d3fa0..365d7b93a 100644
--- a/dev/crates/track-config/tests/difficulties.rs
+++ b/dev/crates/track-config/tests/difficulties.rs
@@ -1,21 +1,18 @@
//! Make sure exercise difficulties are set correctly
-use track_config::TrackConfig;
-
-static CONFIG: &str = include_str!("../../../../config.json");
+use track_config::TRACK_CONFIG;
#[test]
fn test_difficulties_are_valid() {
- let config: TrackConfig = serde_json::from_str(CONFIG).unwrap();
-
- let mut difficulties = config
+ let mut difficulties = TRACK_CONFIG
.exercises
.concept
.iter()
.map(|e| e.difficulty)
- .chain(config.exercises.practice.iter().map(|e| e.difficulty));
+ .chain(TRACK_CONFIG.exercises.practice.iter().map(|e| e.difficulty));
- if difficulties.any(|d| !matches!(d, 1 | 4 | 7 | 10)) {
- panic!("Exercises with invalid difficulty (must be in {{1, 4, 7, 10}})")
- }
+ assert!(
+ difficulties.all(|d| matches!(d, 1 | 4 | 7 | 10)),
+ "exercises must have a difficulty of 1, 4, 7, or 10"
+ )
}
diff --git a/dev/scripts/check_exercises_for_authors.sh b/dev/scripts/check_exercises_for_authors.sh
deleted file mode 100755
index 00d2972c0..000000000
--- a/dev/scripts/check_exercises_for_authors.sh
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/usr/bin/env bash
-
-repo=$(git rev-parse --show-toplevel)
-
-if grep -rnw "$repo/exercises/" --include="*.toml" -e "authors"; then
- echo "Found 'authors' field in exercises";
- exit 1;
-fi
From fd7f090af8922274a0315e71e40b9ee8c6019685 Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Sat, 2 Sep 2023 21:25:27 +0200
Subject: [PATCH 17/53] remove editorconfig
In terms of code style, we should just go with the default for
any specific language.
Rust has rustfmt.
Shell scripts should be kept to a minimum such that it doesn't matter.
---
.editorconfig | 10 ----------
1 file changed, 10 deletions(-)
delete mode 100644 .editorconfig
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
-
From f2ecac41e62cef9a108b9027aedb1798547a866d Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Sat, 2 Sep 2023 22:05:37 +0200
Subject: [PATCH 18/53] update scripting guidelines
Some become unimportant when Rust is recommended for non-trivial stuff.
Some are now tested, so they don't need to be mentioned explicitly.
---
.github/CODEOWNERS | 4 +-
.github/workflows/tests.yml | 2 +-
.gitignore | 1 -
.../repo-conventions/tests/bash_scripts.rs | 47 +++++++++++++++++++
.../{fetch-configlet => fetch_configlet.sh} | 0
docs/CONTRIBUTING.md | 19 ++------
6 files changed, 53 insertions(+), 20 deletions(-)
create mode 100644 dev/crates/repo-conventions/tests/bash_scripts.rs
rename dev/scripts/{fetch-configlet => fetch_configlet.sh} (100%)
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 0e9612a36..6b66adb8c 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -1,5 +1,5 @@
# Code owners
.github/CODEOWNERS @exercism/maintainers-admin
-# Changes to `fetch-configlet` should be made in the `exercism/configlet` repo
-dev/scripts/fetch-configlet @exercism/maintainers-admin
+# Changes to `fetch_configlet.sh` should be made in the `exercism/configlet` repo
+dev/scripts/fetch_configlet.sh @exercism/maintainers-admin
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index e2bd3046f..1233f5bd2 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -46,7 +46,7 @@ jobs:
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- name: Fetch configlet
- run: ./dev/scripts/fetch-configlet
+ run: ./dev/scripts/fetch_configlet.sh
- name: Lint configlet
run: ./dev/scripts/configlet lint
diff --git a/.gitignore b/.gitignore
index 989dbc46f..99def85a3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,5 +6,4 @@ tmp
bin
exercises/*/*/Cargo.lock
exercises/*/*/clippy.log
-canonical_data.json
.vscode
diff --git a/dev/crates/repo-conventions/tests/bash_scripts.rs b/dev/crates/repo-conventions/tests/bash_scripts.rs
new file mode 100644
index 000000000..568ece50d
--- /dev/null
+++ b/dev/crates/repo-conventions/tests/bash_scripts.rs
@@ -0,0 +1,47 @@
+use std::path::PathBuf;
+
+/// Runs a function for each bash script in the scripts directory.
+/// The function is passed the path of the script.
+fn for_all_scripts(f: fn(PathBuf)) {
+ let crate_dir = env!("CARGO_MANIFEST_DIR");
+ let scripts_dir = format!("{crate_dir}/../../scripts");
+
+ for entry in std::fs::read_dir(scripts_dir).unwrap() {
+ f(entry.unwrap().path())
+ }
+}
+
+#[test]
+fn test_file_extension() {
+ for_all_scripts(|path| {
+ let file_name = path.file_name().unwrap().to_str().unwrap();
+ 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();
+ assert!(
+ file_name.to_lowercase() == file_name && !file_name.contains('-'),
+ "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();
+ 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/dev/scripts/fetch-configlet b/dev/scripts/fetch_configlet.sh
similarity index 100%
rename from dev/scripts/fetch-configlet
rename to dev/scripts/fetch_configlet.sh
diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md
index 13b5a9cde..b775188cd 100644
--- a/docs/CONTRIBUTING.md
+++ b/docs/CONTRIBUTING.md
@@ -21,22 +21,9 @@ and what maintainers expect of contributors.
## Internal Tooling Style Guide
-- 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
-- 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
+- Prefer Rust tests over shell scripts for anything non-trivial.
+ `dev/crates/repo-conventions` is a great place for general-purpose tests.
+- Bash scripts should set the following options at the top:
```bash
set -eo pipefail
```
From 58c96c38cfd51bc0ffaeab5b873266a4b5f60f5a Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Sat, 2 Sep 2023 22:37:53 +0200
Subject: [PATCH 19/53] cleanup README
---
README.md | 74 ++++++++++++++++++++++++++++++-------------------------
1 file changed, 40 insertions(+), 34 deletions(-)
diff --git a/README.md b/README.md
index 30a20ddc9..2513c3a46 100644
--- a/README.md
+++ b/README.md
@@ -7,6 +7,9 @@ Exercism exercises in Rust
## Contributing
Check out our [contributor documentation](docs/CONTRIBUTING.md).
+Most importantly, all contributions should be coordinated on the [forum].
+
+[forum]: https://forum.exercism.org/
## Exercise Tests
@@ -31,17 +34,6 @@ fn test_second_and_past_tests_ignored() {
}
```
-## 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).
-
-## 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).
-
-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
Before submitting your pull request, you'll want to verify the changes in two ways:
@@ -51,7 +43,8 @@ Before submitting your pull request, you'll want to verify the changes in two wa
### 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:
+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)
@@ -60,6 +53,7 @@ Please note that the README of every exercise is formed using several templates,
- 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.
+(After having discussed your suggestions on the [forum], that is.)
## Contributing a New Exercise
@@ -71,32 +65,44 @@ Note that:
- 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.
+- Each exercise should have at least the following structure:
+ ```
+ exercise-name
+ ┣━ src
+ ┃ ┗━ lib.rs // an empty file or with exercise stubs
+ ┣━ tests
+ ┃ ┗━ exercise_name.rs // a test suite
+ ┣━ 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)
+ ```
- 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.
+ Use `todo!()` 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".
+TODO automate setting of version field for problem-spec exercises
- 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.
+ 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.
From 97a0212b7f560dfd64d0a0d33c8fa6290139904d Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Sat, 2 Sep 2023 23:09:04 +0200
Subject: [PATCH 20/53] enforce no trailing whitespace
---
Cargo.lock | 51 +++++++++++++++++++
Cargo.toml | 1 +
README.md | 9 ++--
concepts/methods/about.md | 2 +-
concepts/references/about.md | 4 +-
dev/crates/repo-conventions/Cargo.toml | 1 +
.../tests/trailing_whitespace.rs | 35 +++++++++++++
dev/scripts/lint_trailing_spaces.sh | 14 -----
dev/scripts/remove_trailing_whitespace.sh | 4 --
docs/RESOURCES.md | 2 +-
.../magazine-cutout/.docs/instructions.md | 2 +-
.../role-playing-game/.docs/introduction.md | 8 +--
.../short-fibonacci/.docs/instructions.md | 4 +-
.../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 ++--
28 files changed, 177 insertions(+), 94 deletions(-)
create mode 100644 dev/crates/repo-conventions/tests/trailing_whitespace.rs
delete mode 100755 dev/scripts/lint_trailing_spaces.sh
delete mode 100755 dev/scripts/remove_trailing_whitespace.sh
diff --git a/Cargo.lock b/Cargo.lock
index 50099b4b3..7326e6769 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -37,6 +37,7 @@ name = "repo-conventions"
version = "0.1.0"
dependencies = [
"track-config",
+ "walkdir",
]
[[package]]
@@ -45,6 +46,15 @@ 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 = "serde"
version = "1.0.188"
@@ -101,3 +111,44 @@ name = "unicode-ident"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
+
+[[package]]
+name = "walkdir"
+version = "2.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[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"
diff --git a/Cargo.toml b/Cargo.toml
index 2c904f232..ccadd427c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -10,3 +10,4 @@ once_cell = "1.18.0"
serde = { version = "1.0.188", features = ["derive"] }
serde_json = "1.0.105"
track-config = { path = "dev/crates/track-config" }
+walkdir = "2.3.3"
diff --git a/README.md b/README.md
index 2513c3a46..97e8fcee7 100644
--- a/README.md
+++ b/README.md
@@ -38,8 +38,8 @@ fn test_second_and_past_tests_ignored() {
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
+- Run all the tests for the Rust exercises
+- Run an Exercism-specific linter to verify the track
### On modifying the exercises' README
@@ -66,6 +66,7 @@ Note that:
- 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 at least the following structure:
+
```
exercise-name
┣━ src
@@ -85,8 +86,8 @@ Note that:
TODO automate setting of version field for problem-spec exercises
-- An exercise may contain `.meta/hints.md`. This is optional and will appear after the normal exercise
- instructions if present.
+- 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.
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/dev/crates/repo-conventions/Cargo.toml b/dev/crates/repo-conventions/Cargo.toml
index b1212e68d..60943533f 100644
--- a/dev/crates/repo-conventions/Cargo.toml
+++ b/dev/crates/repo-conventions/Cargo.toml
@@ -9,3 +9,4 @@ description = "Tests to enforce various repository conventions"
[dependencies]
track-config.workspace = true
+walkdir.workspace = true
diff --git a/dev/crates/repo-conventions/tests/trailing_whitespace.rs b/dev/crates/repo-conventions/tests/trailing_whitespace.rs
new file mode 100644
index 000000000..da2dc16db
--- /dev/null
+++ b/dev/crates/repo-conventions/tests/trailing_whitespace.rs
@@ -0,0 +1,35 @@
+use std::path::Path;
+
+use walkdir::WalkDir;
+
+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() {
+ let crate_dir = env!("CARGO_MANIFEST_DIR");
+ let repo_dir = format!("{crate_dir}/../../..");
+
+ for entry in WalkDir::new(repo_dir) {
+ let entry = entry.unwrap();
+ if !entry.file_type().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/dev/scripts/lint_trailing_spaces.sh b/dev/scripts/lint_trailing_spaces.sh
deleted file mode 100755
index 16f021a14..000000000
--- a/dev/scripts/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/dev/scripts/remove_trailing_whitespace.sh b/dev/scripts/remove_trailing_whitespace.sh
deleted file mode 100755
index 5a93912f7..000000000
--- a/dev/scripts/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/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/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/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)
-
From ed35a11922bdf9c99f82dba73fa0f38a5275dafe Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Sun, 3 Sep 2023 05:53:40 +0200
Subject: [PATCH 21/53] remove CI step now covered by tests
---
.github/workflows/tests.yml | 18 ------------------
1 file changed, 18 deletions(-)
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 1233f5bd2..a8b259f95 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -12,24 +12,6 @@ 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@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
-
- - name: Verify exercise difficulties
- run: ./dev/scripts/verify_exercise_difficulties.sh
-
- - name: Check exercises for authors
- run: ./dev/scripts/check_exercises_for_authors.sh
-
- - name: Ensure relevant files do not have trailing whitespace
- run: ./dev/scripts/lint_trailing_spaces.sh
-
configlet:
name: configlet lint
runs-on: ubuntu-latest
From 89ea1cfe79ac0cc765e46925bf24a8868f439903 Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Sun, 3 Sep 2023 06:18:45 +0200
Subject: [PATCH 22/53] run markdownlint on more files
---
README.md | 4 ++--
dev/scripts/lint_markdown.sh | 9 +++++++--
docs/CONTRIBUTING.md | 6 ++----
3 files changed, 11 insertions(+), 8 deletions(-)
diff --git a/README.md b/README.md
index 97e8fcee7..1c37e9099 100644
--- a/README.md
+++ b/README.md
@@ -17,7 +17,7 @@ At the most basic level, Exercism is all about the tests. You can read more abou
Test files should use the following format:
-```
+```rust
extern crate exercise_name;
use exercise_name::*;
@@ -67,7 +67,7 @@ Note that:
- Each exercise should have at least the following structure:
- ```
+ ```txt
exercise-name
┣━ src
┃ ┗━ lib.rs // an empty file or with exercise stubs
diff --git a/dev/scripts/lint_markdown.sh b/dev/scripts/lint_markdown.sh
index 6f3592c7c..3dfdd6922 100755
--- a/dev/scripts/lint_markdown.sh
+++ b/dev/scripts/lint_markdown.sh
@@ -1,3 +1,8 @@
#!/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 \
+ ./*.md \
+ docs/*.md \
+ concepts/**/*.md \
+ exercises/**/*.md
diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md
index b775188cd..e76c58f47 100644
--- a/docs/CONTRIBUTING.md
+++ b/docs/CONTRIBUTING.md
@@ -22,8 +22,6 @@ and what maintainers expect of contributors.
## Internal Tooling Style Guide
- Prefer Rust tests over shell scripts for anything non-trivial.
- `dev/crates/repo-conventions` is a great place for general-purpose tests.
+ `dev/crates/repo-conventions` is a great place for general-purpose tests.
- Bash scripts should set the following options at the top:
- ```bash
- set -eo pipefail
- ```
+ `set -eo pipefail`
From 7db467a4ffe9acbac14e0899b0422d9decfca201 Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Sun, 3 Sep 2023 06:29:41 +0200
Subject: [PATCH 23/53] cleanup tests workflow
---
.github/workflows/tests.yml | 53 +++----------------------------------
1 file changed, 4 insertions(+), 49 deletions(-)
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index a8b259f95..d9e0c20bc 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -17,13 +17,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- # Checks out default branch locally so that it is available to the scripts.
- - name: Checkout main
- 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@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
@@ -38,10 +31,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- - name: Checkout main
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- with:
- ref: main
- name: Checkout code
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
@@ -50,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
@@ -58,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@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@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
@@ -87,18 +65,15 @@ jobs:
with:
toolchain: ${{ matrix.rust }}
- # run scripts as steps
- name: Check exercises
env:
- DENYWARNINGS: ${{ matrix.deny_warnings }}
+ DENYWARNINGS: '1'
run: ./dev/scripts/check_exercises.sh
- continue-on-error: ${{ matrix.rust == 'beta' && matrix.deny_warnings == '1' }}
- name: Ensure stubs compile
env:
- DENYWARNINGS: ${{ matrix.deny_warnings }}
+ DENYWARNINGS: '1'
run: ./dev/scripts/ensure_stubs_compile.sh
- continue-on-error: ${{ matrix.rust == 'beta' && matrix.deny_warnings == '1' }}
rustformat:
@@ -114,11 +89,8 @@ jobs:
with:
toolchain: stable
- - name: Rust Format Version
- run: rustfmt --version
-
- name: Format
- run: dev/scripts/format_exercises
+ run: ./dev/scripts/format_exercises
- name: Diff
run: |
@@ -136,11 +108,6 @@ jobs:
rust: ["stable", "beta"]
steps:
- - name: Checkout main
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- with:
- ref: main
-
- name: Checkout code
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
@@ -149,11 +116,6 @@ 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
@@ -170,13 +132,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@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@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
From f8d461ca58db1d6bb5a883a77774098fc93c1e86 Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Sun, 3 Sep 2023 07:00:03 +0200
Subject: [PATCH 24/53] improve snake_case script name test
---
dev/crates/repo-conventions/tests/bash_scripts.rs | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/dev/crates/repo-conventions/tests/bash_scripts.rs b/dev/crates/repo-conventions/tests/bash_scripts.rs
index 568ece50d..25b66510e 100644
--- a/dev/crates/repo-conventions/tests/bash_scripts.rs
+++ b/dev/crates/repo-conventions/tests/bash_scripts.rs
@@ -25,9 +25,14 @@ fn test_file_extension() {
#[test]
fn test_snake_case_name() {
for_all_scripts(|path| {
- let file_name = path.file_name().unwrap().to_str().unwrap();
+ let file_name = path
+ .file_name()
+ .unwrap()
+ .to_str()
+ .unwrap()
+ .trim_end_matches(".sh");
assert!(
- file_name.to_lowercase() == file_name && !file_name.contains('-'),
+ file_name.chars().all(|c| c.is_ascii_lowercase() || c == '_'),
"name of '{file_name}' should be snake_case"
);
})
From 27f97ce4e5e3d5ff576e1b09c7227db1c4898bcd Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Sun, 3 Sep 2023 21:56:40 +0200
Subject: [PATCH 25/53] add Rust test for number of ignored tests
---
Cargo.lock | 15 +++-
Cargo.toml | 3 +-
dev/crates/exercise-config/Cargo.toml | 13 +++
dev/crates/exercise-config/src/lib.rs | 84 +++++++++++++++++++
.../exercise-config/tests/count_ignores.rs | 35 ++++++++
.../exercise-config/tests/deserialize.rs | 20 +++++
dev/crates/repo-conventions/Cargo.toml | 4 +-
.../tests/no_authors_in_cargo_toml.rs | 18 +---
dev/crates/track-config/Cargo.toml | 4 +-
dev/crates/track-config/src/lib.rs | 1 +
10 files changed, 173 insertions(+), 24 deletions(-)
create mode 100644 dev/crates/exercise-config/Cargo.toml
create mode 100644 dev/crates/exercise-config/src/lib.rs
create mode 100644 dev/crates/exercise-config/tests/count_ignores.rs
create mode 100644 dev/crates/exercise-config/tests/deserialize.rs
diff --git a/Cargo.lock b/Cargo.lock
index 7326e6769..513624acc 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2,6 +2,15 @@
# It is not intended for manual editing.
version = 3
+[[package]]
+name = "exercise_config"
+version = "0.1.0"
+dependencies = [
+ "serde",
+ "serde_json",
+ "track_config",
+]
+
[[package]]
name = "itoa"
version = "1.0.9"
@@ -33,10 +42,10 @@ dependencies = [
]
[[package]]
-name = "repo-conventions"
+name = "repo_conventions"
version = "0.1.0"
dependencies = [
- "track-config",
+ "exercise_config",
"walkdir",
]
@@ -98,7 +107,7 @@ dependencies = [
]
[[package]]
-name = "track-config"
+name = "track_config"
version = "0.1.0"
dependencies = [
"once_cell",
diff --git a/Cargo.toml b/Cargo.toml
index ccadd427c..50fd1d6b1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,8 +6,9 @@ members = ["dev/crates/*"]
edition = "2021"
[workspace.dependencies]
+exercise_config = { path = "dev/crates/exercise-config" }
once_cell = "1.18.0"
serde = { version = "1.0.188", features = ["derive"] }
serde_json = "1.0.105"
-track-config = { path = "dev/crates/track-config" }
+track_config = { path = "dev/crates/track-config" }
walkdir = "2.3.3"
diff --git a/dev/crates/exercise-config/Cargo.toml b/dev/crates/exercise-config/Cargo.toml
new file mode 100644
index 000000000..36a4613b6
--- /dev/null
+++ b/dev/crates/exercise-config/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "exercise_config"
+version = "0.1.0"
+edition.workspace = true
+
+description = "De-/Serialization of /.meta/config.json"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+serde.workspace = true
+serde_json.workspace = true
+track_config.workspace = true
diff --git a/dev/crates/exercise-config/src/lib.rs b/dev/crates/exercise-config/src/lib.rs
new file mode 100644
index 000000000..ccf6cd7c8
--- /dev/null
+++ b/dev/crates/exercise-config/src/lib.rs
@@ -0,0 +1,84 @@
+//! This crate 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 track_config::TRACK_CONFIG;
+
+#[derive(Serialize, Deserialize)]
+#[serde(deny_unknown_fields)]
+pub struct ConceptExerciseConfig {
+ pub authors: Vec,
+ pub contributors: Option>,
+ pub files: ConceptFilesConfig,
+ pub icon: Option,
+ pub blurb: String,
+ pub source: Option,
+ pub source_url: Option,
+ pub test_runner: Option,
+}
+
+#[derive(Serialize, Deserialize)]
+#[serde(deny_unknown_fields)]
+pub struct ConceptFilesConfig {
+ pub solution: Vec,
+ pub test: Vec,
+ pub exemplar: Vec,
+}
+
+#[derive(Serialize, Deserialize)]
+#[serde(deny_unknown_fields)]
+pub struct PracticeExerciseConfig {
+ pub authors: Vec,
+ pub contributors: Option>,
+ pub files: PracticeFilesConfig,
+ pub icon: Option,
+ pub blurb: String,
+ pub source: Option,
+ pub source_url: Option,
+ pub test_runner: Option,
+ pub custom: Option,
+}
+
+#[derive(Serialize, Deserialize)]
+#[serde(deny_unknown_fields)]
+pub struct PracticeFilesConfig {
+ pub solution: Vec,
+ pub test: Vec,
+ pub example: Vec,
+}
+
+#[derive(Serialize, Deserialize)]
+#[serde(deny_unknown_fields)]
+pub struct CustomConfig {
+ #[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())
+}
diff --git a/dev/crates/exercise-config/tests/count_ignores.rs b/dev/crates/exercise-config/tests/count_ignores.rs
new file mode 100644
index 000000000..d19fef42d
--- /dev/null
+++ b/dev/crates/exercise-config/tests/count_ignores.rs
@@ -0,0 +1,35 @@
+use exercise_config::{
+ get_all_concept_exercise_paths, get_all_practice_exercise_paths, PracticeExerciseConfig,
+};
+
+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 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: PracticeExerciseConfig =
+ 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/dev/crates/exercise-config/tests/deserialize.rs b/dev/crates/exercise-config/tests/deserialize.rs
new file mode 100644
index 000000000..1fd2dae5e
--- /dev/null
+++ b/dev/crates/exercise-config/tests/deserialize.rs
@@ -0,0 +1,20 @@
+use exercise_config::{
+ get_all_concept_exercise_paths, get_all_practice_exercise_paths, ConceptExerciseConfig,
+ PracticeExerciseConfig,
+};
+
+#[test]
+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();
+ let _: ConceptExerciseConfig = 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 _: PracticeExerciseConfig = serde_json::from_str(config_contents.as_str())
+ .expect("should deserialize practice exercise config");
+ }
+}
diff --git a/dev/crates/repo-conventions/Cargo.toml b/dev/crates/repo-conventions/Cargo.toml
index 60943533f..63e64f1d3 100644
--- a/dev/crates/repo-conventions/Cargo.toml
+++ b/dev/crates/repo-conventions/Cargo.toml
@@ -1,5 +1,5 @@
[package]
-name = "repo-conventions"
+name = "repo_conventions"
version = "0.1.0"
edition.workspace = true
@@ -8,5 +8,5 @@ description = "Tests to enforce various repository conventions"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
-track-config.workspace = true
+exercise_config.workspace = true
walkdir.workspace = true
diff --git a/dev/crates/repo-conventions/tests/no_authors_in_cargo_toml.rs b/dev/crates/repo-conventions/tests/no_authors_in_cargo_toml.rs
index 54ebc4868..24fd0411e 100644
--- a/dev/crates/repo-conventions/tests/no_authors_in_cargo_toml.rs
+++ b/dev/crates/repo-conventions/tests/no_authors_in_cargo_toml.rs
@@ -1,24 +1,10 @@
-use track_config::TRACK_CONFIG;
+use 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 crate_dir = env!("CARGO_MANIFEST_DIR");
-
- let cargo_toml_paths = TRACK_CONFIG
- .exercises
- .concept
- .iter()
- .map(|e| format!("concept/{}", e.slug))
- .chain(
- TRACK_CONFIG
- .exercises
- .practice
- .iter()
- .map(|e| format!("practice/{}", e.slug)),
- )
- .map(|s| format!("{crate_dir}/../../../exercises/{s}/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();
diff --git a/dev/crates/track-config/Cargo.toml b/dev/crates/track-config/Cargo.toml
index 473aaef61..6d232b88f 100644
--- a/dev/crates/track-config/Cargo.toml
+++ b/dev/crates/track-config/Cargo.toml
@@ -1,9 +1,9 @@
[package]
-name = "track-config"
+name = "track_config"
version = "0.1.0"
edition.workspace = true
-description = "De-/Serialization of track configuration"
+description = "De-/Serialization of config.json"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
diff --git a/dev/crates/track-config/src/lib.rs b/dev/crates/track-config/src/lib.rs
index e6dd9770d..8a29b23d5 100644
--- a/dev/crates/track-config/src/lib.rs
+++ b/dev/crates/track-config/src/lib.rs
@@ -5,6 +5,7 @@
//! Some definitions are not yet perfectly precise,
//! because we don't anticipate they will be needed much.
//! Feel free to improve this if need be.
+//! (e.g. replace `String` with an enum of possible values)
use std::collections::HashMap;
From 4e4c338b9a32e0f11aa36b23e18006a562b9c043 Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Sun, 3 Sep 2023 22:31:03 +0200
Subject: [PATCH 26/53] run repository tests in CI
---
.github/workflows/tests.yml | 23 +++++++++++++++----
dev/crates/exercise-config/src/lib.rs | 10 ++++----
.../exercise-config/tests/count_ignores.rs | 2 +-
.../exercise-config/tests/deserialize.rs | 2 +-
dev/crates/track-config/src/lib.rs | 18 +++++++--------
5 files changed, 35 insertions(+), 20 deletions(-)
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index d9e0c20bc..7c77f7f85 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:
@@ -67,14 +67,29 @@ jobs:
- name: Check exercises
env:
- DENYWARNINGS: '1'
+ DENYWARNINGS: "1"
run: ./dev/scripts/check_exercises.sh
- name: Ensure stubs compile
env:
- DENYWARNINGS: '1'
+ DENYWARNINGS: "1"
run: ./dev/scripts/ensure_stubs_compile.sh
+ tests:
+ name: Run repository tests
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
+
+ - name: Setup toolchain
+ uses: dtolnay/rust-toolchain@0e66bd3e6b38ec0ad5312288c83e47c143e6b09e
+ with:
+ toolchain: stable
+
+ - name: Run tests
+ run: cargo test --workspace
rustformat:
name: Check Rust Formatting
@@ -142,5 +157,5 @@ jobs:
- name: Check exercises
env:
- BENCHMARK: '1'
+ BENCHMARK: "1"
run: ./dev/scripts/check_exercises.sh
diff --git a/dev/crates/exercise-config/src/lib.rs b/dev/crates/exercise-config/src/lib.rs
index ccf6cd7c8..af6ec76ae 100644
--- a/dev/crates/exercise-config/src/lib.rs
+++ b/dev/crates/exercise-config/src/lib.rs
@@ -5,7 +5,7 @@
use serde::{Deserialize, Serialize};
use track_config::TRACK_CONFIG;
-#[derive(Serialize, Deserialize)]
+#[derive(Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ConceptExerciseConfig {
pub authors: Vec,
@@ -18,7 +18,7 @@ pub struct ConceptExerciseConfig {
pub test_runner: Option,
}
-#[derive(Serialize, Deserialize)]
+#[derive(Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ConceptFilesConfig {
pub solution: Vec,
@@ -26,7 +26,7 @@ pub struct ConceptFilesConfig {
pub exemplar: Vec,
}
-#[derive(Serialize, Deserialize)]
+#[derive(Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PracticeExerciseConfig {
pub authors: Vec,
@@ -40,7 +40,7 @@ pub struct PracticeExerciseConfig {
pub custom: Option,
}
-#[derive(Serialize, Deserialize)]
+#[derive(Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PracticeFilesConfig {
pub solution: Vec,
@@ -48,7 +48,7 @@ pub struct PracticeFilesConfig {
pub example: Vec,
}
-#[derive(Serialize, Deserialize)]
+#[derive(Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct CustomConfig {
#[serde(rename = "allowed-to-not-compile")]
diff --git a/dev/crates/exercise-config/tests/count_ignores.rs b/dev/crates/exercise-config/tests/count_ignores.rs
index d19fef42d..c88061741 100644
--- a/dev/crates/exercise-config/tests/count_ignores.rs
+++ b/dev/crates/exercise-config/tests/count_ignores.rs
@@ -16,7 +16,7 @@ fn assert_one_less_ignore_than_tests(path: &str) {
}
#[test]
-fn count_ignores() {
+fn test_count_ignores() {
for path in get_all_concept_exercise_paths() {
assert_one_less_ignore_than_tests(&path);
}
diff --git a/dev/crates/exercise-config/tests/deserialize.rs b/dev/crates/exercise-config/tests/deserialize.rs
index 1fd2dae5e..48392dbe3 100644
--- a/dev/crates/exercise-config/tests/deserialize.rs
+++ b/dev/crates/exercise-config/tests/deserialize.rs
@@ -4,7 +4,7 @@ use exercise_config::{
};
#[test]
-fn deserialize_all() {
+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();
diff --git a/dev/crates/track-config/src/lib.rs b/dev/crates/track-config/src/lib.rs
index 8a29b23d5..e6fc2185d 100644
--- a/dev/crates/track-config/src/lib.rs
+++ b/dev/crates/track-config/src/lib.rs
@@ -17,7 +17,7 @@ pub static TRACK_CONFIG: Lazy = Lazy::new(|| {
serde_json::from_str(config).expect("should deserialize the track config")
});
-#[derive(Serialize, Deserialize)]
+#[derive(Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TrackConfig {
pub language: String,
@@ -35,7 +35,7 @@ pub struct TrackConfig {
pub tags: Vec,
}
-#[derive(Serialize, Deserialize)]
+#[derive(Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct OnlineEditorConfig {
pub indent_style: String,
@@ -43,7 +43,7 @@ pub struct OnlineEditorConfig {
pub highlightjs_language: String,
}
-#[derive(Serialize, Deserialize)]
+#[derive(Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ExercisesConfig {
pub concept: Vec,
@@ -51,14 +51,14 @@ pub struct ExercisesConfig {
pub foregone: Vec,
}
-#[derive(Serialize, Deserialize)]
+#[derive(Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ConceptExerciseStatus {
Active,
Wip,
}
-#[derive(Serialize, Deserialize)]
+#[derive(Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ConceptExerciseConfig {
pub slug: String,
@@ -70,13 +70,13 @@ pub struct ConceptExerciseConfig {
pub status: ConceptExerciseStatus,
}
-#[derive(Serialize, Deserialize)]
+#[derive(Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum PracticeExerciseStatus {
Deprecated,
}
-#[derive(Serialize, Deserialize)]
+#[derive(Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PracticeExerciseConfig {
pub slug: String,
@@ -90,7 +90,7 @@ pub struct PracticeExerciseConfig {
pub status: Option,
}
-#[derive(Serialize, Deserialize)]
+#[derive(Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ConceptConfig {
pub uuid: String,
@@ -98,7 +98,7 @@ pub struct ConceptConfig {
pub name: String,
}
-#[derive(Serialize, Deserialize)]
+#[derive(Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct KeyFeatureConfig {
pub icon: String,
From 95c60719dfccfec804b6d8686ebc1f98e451aeab Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Mon, 4 Sep 2023 07:02:32 +0200
Subject: [PATCH 27/53] merge rust tooling into a single package
---
Cargo.lock | 24 +++--------------
Cargo.toml | 13 +--------
dev/crates/exercise-config/Cargo.toml | 13 ---------
.../exercise-config/tests/deserialize.rs | 20 --------------
dev/crates/repo-conventions/Cargo.toml | 12 ---------
dev/crates/repo-conventions/src/lib.rs | 2 --
dev/crates/track-config/Cargo.toml | 13 ---------
dev/rust-tooling/Cargo.toml | 12 +++++++++
.../src/exercise_config.rs} | 27 +++++++++++++++----
dev/rust-tooling/src/lib.rs | 2 ++
.../src/track_config.rs} | 4 +--
.../tests/bash_script_conventions.rs} | 2 +-
.../tests/count_ignores.rs | 2 +-
.../tests/difficulties.rs | 2 +-
.../tests/no_authors_in_cargo_toml.rs | 2 +-
.../tests/no_trailing_whitespace.rs} | 2 +-
16 files changed, 48 insertions(+), 104 deletions(-)
delete mode 100644 dev/crates/exercise-config/Cargo.toml
delete mode 100644 dev/crates/exercise-config/tests/deserialize.rs
delete mode 100644 dev/crates/repo-conventions/Cargo.toml
delete mode 100644 dev/crates/repo-conventions/src/lib.rs
delete mode 100644 dev/crates/track-config/Cargo.toml
create mode 100644 dev/rust-tooling/Cargo.toml
rename dev/{crates/exercise-config/src/lib.rs => rust-tooling/src/exercise_config.rs} (66%)
create mode 100644 dev/rust-tooling/src/lib.rs
rename dev/{crates/track-config/src/lib.rs => rust-tooling/src/track_config.rs} (95%)
rename dev/{crates/repo-conventions/tests/bash_scripts.rs => rust-tooling/tests/bash_script_conventions.rs} (95%)
rename dev/{crates/exercise-config => rust-tooling}/tests/count_ignores.rs (97%)
rename dev/{crates/track-config => rust-tooling}/tests/difficulties.rs (90%)
rename dev/{crates/repo-conventions => rust-tooling}/tests/no_authors_in_cargo_toml.rs (89%)
rename dev/{crates/repo-conventions/tests/trailing_whitespace.rs => rust-tooling/tests/no_trailing_whitespace.rs} (94%)
diff --git a/Cargo.lock b/Cargo.lock
index 513624acc..06a4cfeb6 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2,15 +2,6 @@
# It is not intended for manual editing.
version = 3
-[[package]]
-name = "exercise_config"
-version = "0.1.0"
-dependencies = [
- "serde",
- "serde_json",
- "track_config",
-]
-
[[package]]
name = "itoa"
version = "1.0.9"
@@ -42,10 +33,12 @@ dependencies = [
]
[[package]]
-name = "repo_conventions"
+name = "rust-tooling"
version = "0.1.0"
dependencies = [
- "exercise_config",
+ "once_cell",
+ "serde",
+ "serde_json",
"walkdir",
]
@@ -106,15 +99,6 @@ dependencies = [
"unicode-ident",
]
-[[package]]
-name = "track_config"
-version = "0.1.0"
-dependencies = [
- "once_cell",
- "serde",
- "serde_json",
-]
-
[[package]]
name = "unicode-ident"
version = "1.0.11"
diff --git a/Cargo.toml b/Cargo.toml
index 50fd1d6b1..49e081816 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,14 +1,3 @@
[workspace]
resolver = "2"
-members = ["dev/crates/*"]
-
-[workspace.package]
-edition = "2021"
-
-[workspace.dependencies]
-exercise_config = { path = "dev/crates/exercise-config" }
-once_cell = "1.18.0"
-serde = { version = "1.0.188", features = ["derive"] }
-serde_json = "1.0.105"
-track_config = { path = "dev/crates/track-config" }
-walkdir = "2.3.3"
+members = ["dev/rust-tooling"]
diff --git a/dev/crates/exercise-config/Cargo.toml b/dev/crates/exercise-config/Cargo.toml
deleted file mode 100644
index 36a4613b6..000000000
--- a/dev/crates/exercise-config/Cargo.toml
+++ /dev/null
@@ -1,13 +0,0 @@
-[package]
-name = "exercise_config"
-version = "0.1.0"
-edition.workspace = true
-
-description = "De-/Serialization of /.meta/config.json"
-
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
-[dependencies]
-serde.workspace = true
-serde_json.workspace = true
-track_config.workspace = true
diff --git a/dev/crates/exercise-config/tests/deserialize.rs b/dev/crates/exercise-config/tests/deserialize.rs
deleted file mode 100644
index 48392dbe3..000000000
--- a/dev/crates/exercise-config/tests/deserialize.rs
+++ /dev/null
@@ -1,20 +0,0 @@
-use exercise_config::{
- get_all_concept_exercise_paths, get_all_practice_exercise_paths, ConceptExerciseConfig,
- PracticeExerciseConfig,
-};
-
-#[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 _: ConceptExerciseConfig = 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 _: PracticeExerciseConfig = serde_json::from_str(config_contents.as_str())
- .expect("should deserialize practice exercise config");
- }
-}
diff --git a/dev/crates/repo-conventions/Cargo.toml b/dev/crates/repo-conventions/Cargo.toml
deleted file mode 100644
index 63e64f1d3..000000000
--- a/dev/crates/repo-conventions/Cargo.toml
+++ /dev/null
@@ -1,12 +0,0 @@
-[package]
-name = "repo_conventions"
-version = "0.1.0"
-edition.workspace = true
-
-description = "Tests to enforce various repository conventions"
-
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
-[dependencies]
-exercise_config.workspace = true
-walkdir.workspace = true
diff --git a/dev/crates/repo-conventions/src/lib.rs b/dev/crates/repo-conventions/src/lib.rs
deleted file mode 100644
index e57e93543..000000000
--- a/dev/crates/repo-conventions/src/lib.rs
+++ /dev/null
@@ -1,2 +0,0 @@
-//! This is just a stub.
-//! Without it, the tests don't run.
diff --git a/dev/crates/track-config/Cargo.toml b/dev/crates/track-config/Cargo.toml
deleted file mode 100644
index 6d232b88f..000000000
--- a/dev/crates/track-config/Cargo.toml
+++ /dev/null
@@ -1,13 +0,0 @@
-[package]
-name = "track_config"
-version = "0.1.0"
-edition.workspace = true
-
-description = "De-/Serialization of config.json"
-
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
-[dependencies]
-once_cell.workspace = true
-serde.workspace = true
-serde_json.workspace = true
diff --git a/dev/rust-tooling/Cargo.toml b/dev/rust-tooling/Cargo.toml
new file mode 100644
index 000000000..efade6319
--- /dev/null
+++ b/dev/rust-tooling/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "rust-tooling"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+once_cell = "1.18.0"
+serde = { version = "1.0.188", features = ["derive"] }
+serde_json = "1.0.105"
+walkdir = "2.3.3"
diff --git a/dev/crates/exercise-config/src/lib.rs b/dev/rust-tooling/src/exercise_config.rs
similarity index 66%
rename from dev/crates/exercise-config/src/lib.rs
rename to dev/rust-tooling/src/exercise_config.rs
index af6ec76ae..72e57fa4d 100644
--- a/dev/crates/exercise-config/src/lib.rs
+++ b/dev/rust-tooling/src/exercise_config.rs
@@ -1,9 +1,10 @@
-//! This crate provides a data structure for exercise configuration stored
-//! in `.meta/config`. It is capable of serializing and deserializing th
+//! 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 track_config::TRACK_CONFIG;
+
+use crate::track_config::TRACK_CONFIG;
#[derive(Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
@@ -66,7 +67,7 @@ pub fn get_all_concept_exercise_paths() -> impl Iterator
- {
.exercises
.concept
.iter()
- .map(move |e| format!("{crate_dir}/../../../exercises/concept/{}", e.slug))
+ .map(move |e| format!("{crate_dir}/../../exercises/concept/{}", e.slug))
}
pub fn get_all_practice_exercise_paths() -> impl Iterator
- {
@@ -76,9 +77,25 @@ pub fn get_all_practice_exercise_paths() -> impl Iterator
- {
.exercises
.practice
.iter()
- .map(move |e| format!("{crate_dir}/../../../exercises/practice/{}", e.slug))
+ .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 _: ConceptExerciseConfig = 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 _: PracticeExerciseConfig = serde_json::from_str(config_contents.as_str())
+ .expect("should deserialize practice exercise config");
+ }
+}
diff --git a/dev/rust-tooling/src/lib.rs b/dev/rust-tooling/src/lib.rs
new file mode 100644
index 000000000..5bbb5d7d4
--- /dev/null
+++ b/dev/rust-tooling/src/lib.rs
@@ -0,0 +1,2 @@
+pub mod exercise_config;
+pub mod track_config;
diff --git a/dev/crates/track-config/src/lib.rs b/dev/rust-tooling/src/track_config.rs
similarity index 95%
rename from dev/crates/track-config/src/lib.rs
rename to dev/rust-tooling/src/track_config.rs
index e6fc2185d..a571a0056 100644
--- a/dev/crates/track-config/src/lib.rs
+++ b/dev/rust-tooling/src/track_config.rs
@@ -1,4 +1,4 @@
-//! This crate provides a data structure for the track configuration.
+//! This module provides a data structure for the track configuration.
//! It is capable of serializing and deserializing the configuration,
//! for example with `serde_json`.
//!
@@ -13,7 +13,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/dev/crates/repo-conventions/tests/bash_scripts.rs b/dev/rust-tooling/tests/bash_script_conventions.rs
similarity index 95%
rename from dev/crates/repo-conventions/tests/bash_scripts.rs
rename to dev/rust-tooling/tests/bash_script_conventions.rs
index 25b66510e..b70e027a4 100644
--- a/dev/crates/repo-conventions/tests/bash_scripts.rs
+++ b/dev/rust-tooling/tests/bash_script_conventions.rs
@@ -4,7 +4,7 @@ use std::path::PathBuf;
/// The function is passed the path of the script.
fn for_all_scripts(f: fn(PathBuf)) {
let crate_dir = env!("CARGO_MANIFEST_DIR");
- let scripts_dir = format!("{crate_dir}/../../scripts");
+ let scripts_dir = format!("{crate_dir}/../scripts");
for entry in std::fs::read_dir(scripts_dir).unwrap() {
f(entry.unwrap().path())
diff --git a/dev/crates/exercise-config/tests/count_ignores.rs b/dev/rust-tooling/tests/count_ignores.rs
similarity index 97%
rename from dev/crates/exercise-config/tests/count_ignores.rs
rename to dev/rust-tooling/tests/count_ignores.rs
index c88061741..12ba61cc0 100644
--- a/dev/crates/exercise-config/tests/count_ignores.rs
+++ b/dev/rust-tooling/tests/count_ignores.rs
@@ -1,4 +1,4 @@
-use exercise_config::{
+use rust_tooling::exercise_config::{
get_all_concept_exercise_paths, get_all_practice_exercise_paths, PracticeExerciseConfig,
};
diff --git a/dev/crates/track-config/tests/difficulties.rs b/dev/rust-tooling/tests/difficulties.rs
similarity index 90%
rename from dev/crates/track-config/tests/difficulties.rs
rename to dev/rust-tooling/tests/difficulties.rs
index 365d7b93a..a89559148 100644
--- a/dev/crates/track-config/tests/difficulties.rs
+++ b/dev/rust-tooling/tests/difficulties.rs
@@ -1,6 +1,6 @@
//! Make sure exercise difficulties are set correctly
-use track_config::TRACK_CONFIG;
+use rust_tooling::track_config::TRACK_CONFIG;
#[test]
fn test_difficulties_are_valid() {
diff --git a/dev/crates/repo-conventions/tests/no_authors_in_cargo_toml.rs b/dev/rust-tooling/tests/no_authors_in_cargo_toml.rs
similarity index 89%
rename from dev/crates/repo-conventions/tests/no_authors_in_cargo_toml.rs
rename to dev/rust-tooling/tests/no_authors_in_cargo_toml.rs
index 24fd0411e..3a83e5482 100644
--- a/dev/crates/repo-conventions/tests/no_authors_in_cargo_toml.rs
+++ b/dev/rust-tooling/tests/no_authors_in_cargo_toml.rs
@@ -1,4 +1,4 @@
-use exercise_config::get_all_exercise_paths;
+use rust_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.
diff --git a/dev/crates/repo-conventions/tests/trailing_whitespace.rs b/dev/rust-tooling/tests/no_trailing_whitespace.rs
similarity index 94%
rename from dev/crates/repo-conventions/tests/trailing_whitespace.rs
rename to dev/rust-tooling/tests/no_trailing_whitespace.rs
index da2dc16db..beedcad53 100644
--- a/dev/crates/repo-conventions/tests/trailing_whitespace.rs
+++ b/dev/rust-tooling/tests/no_trailing_whitespace.rs
@@ -15,7 +15,7 @@ fn contains_trailing_whitespace(p: &Path) -> bool {
#[test]
fn test_no_trailing_whitespace() {
let crate_dir = env!("CARGO_MANIFEST_DIR");
- let repo_dir = format!("{crate_dir}/../../..");
+ let repo_dir = format!("{crate_dir}/../..");
for entry in WalkDir::new(repo_dir) {
let entry = entry.unwrap();
From 8abb209748ce7eff94d234e8c09d7262bebc5faf Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Mon, 4 Sep 2023 21:08:12 +0200
Subject: [PATCH 28/53] fix script references after rename
---
dev/scripts/check_exercises.sh | 14 +++++++-------
dev/scripts/test_exercise.sh | 2 +-
2 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/dev/scripts/check_exercises.sh b/dev/scripts/check_exercises.sh
index 720d702d9..56bbf29ee 100755
--- a/dev/scripts/check_exercises.sh
+++ b/dev/scripts/check_exercises.sh
@@ -1,13 +1,13 @@
#!/usr/bin/env bash
-# test for existence and executability of the test-exercise script
+# test for existence and executability of the test_exercise.sh script
# this depends on that
-if [ ! -f "./dev/scripts/test-exercise" ]; then
- echo "dev/scripts/test-exercise does not exist"
+if [ ! -f "./dev/scripts/test_exercise.sh" ]; then
+ echo "dev/scripts/test_exercise.sh does not exist"
exit 1
fi
-if [ ! -x "./dev/scripts/test-exercise" ]; then
- echo "dev/scripts/test-exercise does not have its executable bit set"
+if [ ! -x "./dev/scripts/test_exercise.sh" ]; then
+ echo "dev/scripts/test_exercise.sh does not have its executable bit set"
exit 1
fi
@@ -70,11 +70,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.
- ./dev/scripts/test-exercise "$directory" --quiet --no-run
+ ./dev/scripts/test_exercise.sh "$directory" --quiet --no-run
return_code=$((return_code | $?))
else
# Run the test and get the status
- ./dev/scripts/test-exercise "$directory" $release
+ ./dev/scripts/test_exercise.sh "$directory" $release
return_code=$((return_code | $?))
fi
done
diff --git a/dev/scripts/test_exercise.sh b/dev/scripts/test_exercise.sh
index ee8d3040e..2e1c04b26 100755
--- a/dev/scripts/test_exercise.sh
+++ b/dev/scripts/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='.'
From b49726476d93f628b8a7a44f9e91c58fe3821d9a Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Mon, 4 Sep 2023 21:08:31 +0200
Subject: [PATCH 29/53] include exercises in workspace for LSP support
---
Cargo.lock | 633 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
Cargo.toml | 16 +-
2 files changed, 647 insertions(+), 2 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 06a4cfeb6..6b42ef430 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2,18 +2,388 @@
# It is not intended for manual editing.
version = 3
+[[package]]
+name = "acronym"
+version = "1.7.0"
+
+[[package]]
+name = "affine-cipher"
+version = "2.0.0"
+
+[[package]]
+name = "allergies"
+version = "1.1.0"
+
+[[package]]
+name = "allyourbase"
+version = "1.0.0"
+
+[[package]]
+name = "alphametics"
+version = "1.3.0"
+
+[[package]]
+name = "anagram"
+version = "0.0.0"
+
+[[package]]
+name = "anyhow"
+version = "1.0.75"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
+
+[[package]]
+name = "armstrong_numbers"
+version = "1.1.0"
+
+[[package]]
+name = "assembly-line"
+version = "0.1.0"
+
+[[package]]
+name = "atbash-cipher"
+version = "1.2.0"
+
+[[package]]
+name = "beer-song"
+version = "0.0.0"
+
+[[package]]
+name = "binary-search"
+version = "1.3.0"
+
+[[package]]
+name = "bob"
+version = "1.6.0"
+
+[[package]]
+name = "book_store"
+version = "1.3.0"
+
+[[package]]
+name = "bowling"
+version = "1.2.0"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "circular-buffer"
+version = "1.1.0"
+
+[[package]]
+name = "collatz_conjecture"
+version = "1.2.1"
+
+[[package]]
+name = "crypto-square"
+version = "0.1.0"
+
+[[package]]
+name = "csv_builder"
+version = "0.1.0"
+
+[[package]]
+name = "custom-set"
+version = "1.0.1"
+
+[[package]]
+name = "deranged"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946"
+
+[[package]]
+name = "diamond"
+version = "1.1.0"
+
+[[package]]
+name = "difference-of-squares"
+version = "1.2.0"
+
+[[package]]
+name = "diffie-hellman"
+version = "0.1.0"
+
+[[package]]
+name = "dominoes"
+version = "2.1.0"
+
+[[package]]
+name = "doubly-linked-list"
+version = "0.0.0"
+
+[[package]]
+name = "enum-iterator"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7add3873b5dd076766ee79c8e406ad1a472c385476b9e38849f8eec24f1be689"
+dependencies = [
+ "enum-iterator-derive",
+]
+
+[[package]]
+name = "enum-iterator-derive"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eecf8589574ce9b895052fa12d69af7a233f99e6107f5cb8dd1044f2a17bfdcb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.29",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
+
+[[package]]
+name = "etl"
+version = "1.0.0"
+
+[[package]]
+name = "fizzy"
+version = "0.0.0"
+
+[[package]]
+name = "forth"
+version = "1.7.0"
+
+[[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 = "gigasecond"
+version = "2.0.0"
+dependencies = [
+ "time",
+]
+
+[[package]]
+name = "grade-school"
+version = "0.0.0"
+
+[[package]]
+name = "grains"
+version = "1.2.0"
+
+[[package]]
+name = "grep"
+version = "1.3.0"
+dependencies = [
+ "anyhow",
+]
+
+[[package]]
+name = "hamming"
+version = "2.2.0"
+
+[[package]]
+name = "hashbrown"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
+
+[[package]]
+name = "health_statistics"
+version = "0.1.0"
+
+[[package]]
+name = "hello-world"
+version = "1.1.0"
+
+[[package]]
+name = "hexadecimal"
+version = "0.0.0"
+
+[[package]]
+name = "high-scores"
+version = "4.0.0"
+
+[[package]]
+name = "indexmap"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
+dependencies = [
+ "equivalent",
+ "hashbrown",
+]
+
+[[package]]
+name = "int-enum"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cff87d3cc4b79b4559e3c75068d64247284aceb6a038bd4bb38387f3f164476d"
+dependencies = [
+ "int-enum-impl",
+]
+
+[[package]]
+name = "int-enum-impl"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df1f2f068675add1a3fc77f5f5ab2e29290c841ee34d151abc007bce902e5d34"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "isbn-verifier"
+version = "2.7.0"
+
+[[package]]
+name = "isogram"
+version = "1.3.0"
+
[[package]]
name = "itoa"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
+[[package]]
+name = "kindergarten_garden"
+version = "0.1.0"
+
+[[package]]
+name = "knapsack"
+version = "0.1.0"
+
+[[package]]
+name = "largest-series-product"
+version = "1.2.0"
+
+[[package]]
+name = "leap"
+version = "1.6.0"
+
+[[package]]
+name = "libc"
+version = "0.2.147"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
+
+[[package]]
+name = "low_power_embedded_game"
+version = "0.1.0"
+
+[[package]]
+name = "lucians-luscious-lasagna"
+version = "0.1.0"
+
+[[package]]
+name = "luhn"
+version = "1.6.1"
+
+[[package]]
+name = "magazine_cutout"
+version = "0.1.0"
+
+[[package]]
+name = "matching-brackets"
+version = "2.0.0"
+
+[[package]]
+name = "memchr"
+version = "2.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
+
+[[package]]
+name = "minesweeper"
+version = "1.1.0"
+
+[[package]]
+name = "nth_prime"
+version = "2.1.0"
+
+[[package]]
+name = "nucleotide-count"
+version = "1.3.0"
+
+[[package]]
+name = "nucleotide_codons"
+version = "0.1.0"
+
+[[package]]
+name = "ocr-numbers"
+version = "0.0.0"
+
[[package]]
name = "once_cell"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
+[[package]]
+name = "paasio"
+version = "0.0.0"
+
+[[package]]
+name = "palindrome-products"
+version = "1.2.0"
+
+[[package]]
+name = "pangram"
+version = "0.0.0"
+
+[[package]]
+name = "pascals-triangle"
+version = "1.5.0"
+
+[[package]]
+name = "perfect_numbers"
+version = "1.1.0"
+
+[[package]]
+name = "phone-number"
+version = "1.6.1"
+
+[[package]]
+name = "pig-latin"
+version = "1.0.0"
+
+[[package]]
+name = "poker"
+version = "1.1.0"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
+[[package]]
+name = "prime_factors"
+version = "1.1.0"
+
+[[package]]
+name = "proc-macro-crate"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
+dependencies = [
+ "once_cell",
+ "toml_edit",
+]
+
[[package]]
name = "proc-macro2"
version = "1.0.66"
@@ -23,6 +393,22 @@ dependencies = [
"unicode-ident",
]
+[[package]]
+name = "protein-translation"
+version = "0.1.0"
+
+[[package]]
+name = "proverb"
+version = "1.1.0"
+
+[[package]]
+name = "pythagorean_triplet"
+version = "1.0.0"
+
+[[package]]
+name = "queen-attack"
+version = "2.2.0"
+
[[package]]
name = "quote"
version = "1.0.33"
@@ -32,6 +418,96 @@ dependencies = [
"proc-macro2",
]
+[[package]]
+name = "rail_fence_cipher"
+version = "1.1.0"
+
+[[package]]
+name = "raindrops"
+version = "1.1.0"
+
+[[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 = "react"
+version = "2.0.0"
+
+[[package]]
+name = "rectangles"
+version = "1.1.0"
+
+[[package]]
+name = "resistor-color"
+version = "1.0.0"
+dependencies = [
+ "enum-iterator",
+ "int-enum",
+]
+
+[[package]]
+name = "reverse_string"
+version = "1.2.0"
+
+[[package]]
+name = "rna-transcription"
+version = "1.0.0"
+
+[[package]]
+name = "robot-name"
+version = "0.0.0"
+
+[[package]]
+name = "robot-simulator"
+version = "2.2.0"
+
+[[package]]
+name = "role_playing_game"
+version = "0.1.0"
+
+[[package]]
+name = "roman-numerals"
+version = "1.0.0"
+
+[[package]]
+name = "rotational-cipher"
+version = "1.0.0"
+
+[[package]]
+name = "rpn_calculator"
+version = "0.1.0"
+
+[[package]]
+name = "run-length-encoding"
+version = "1.1.0"
+
[[package]]
name = "rust-tooling"
version = "0.1.0"
@@ -48,6 +524,10 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
+[[package]]
+name = "saddle-points"
+version = "1.3.0"
+
[[package]]
name = "same-file"
version = "1.0.6"
@@ -57,6 +537,26 @@ dependencies = [
"winapi-util",
]
+[[package]]
+name = "say"
+version = "1.2.0"
+
+[[package]]
+name = "scale_generator"
+version = "2.0.0"
+
+[[package]]
+name = "scrabble-score"
+version = "1.1.0"
+
+[[package]]
+name = "secret-handshake"
+version = "1.1.0"
+
+[[package]]
+name = "semi_structured_logs"
+version = "0.1.0"
+
[[package]]
name = "serde"
version = "1.0.188"
@@ -74,7 +574,7 @@ checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.29",
]
[[package]]
@@ -88,6 +588,56 @@ dependencies = [
"serde",
]
+[[package]]
+name = "series"
+version = "0.1.0"
+
+[[package]]
+name = "short_fibonacci"
+version = "0.1.0"
+
+[[package]]
+name = "sieve"
+version = "1.1.0"
+
+[[package]]
+name = "simple-cipher"
+version = "0.0.0"
+dependencies = [
+ "rand",
+]
+
+[[package]]
+name = "simple_linked_list"
+version = "0.1.0"
+
+[[package]]
+name = "space-age"
+version = "1.2.0"
+
+[[package]]
+name = "spiral-matrix"
+version = "1.1.0"
+
+[[package]]
+name = "sublist"
+version = "0.0.0"
+
+[[package]]
+name = "sum-of-multiples"
+version = "1.5.0"
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
[[package]]
name = "syn"
version = "2.0.29"
@@ -99,12 +649,66 @@ dependencies = [
"unicode-ident",
]
+[[package]]
+name = "time"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17f6bb557fd245c28e6411aa56b6403c689ad95061f50e4be16c274e70a17e48"
+dependencies = [
+ "deranged",
+ "serde",
+ "time-core",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
+
+[[package]]
+name = "toml_datetime"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
+
+[[package]]
+name = "toml_edit"
+version = "0.19.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a"
+dependencies = [
+ "indexmap",
+ "toml_datetime",
+ "winnow",
+]
+
+[[package]]
+name = "tournament"
+version = "1.4.0"
+
+[[package]]
+name = "triangle"
+version = "0.0.0"
+
+[[package]]
+name = "two-bucket"
+version = "1.4.0"
+
+[[package]]
+name = "twofer"
+version = "1.2.0"
+
[[package]]
name = "unicode-ident"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
+[[package]]
+name = "variable-length-quantity"
+version = "1.2.0"
+
[[package]]
name = "walkdir"
version = "2.3.3"
@@ -115,6 +719,12 @@ dependencies = [
"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 = "winapi"
version = "0.3.9"
@@ -145,3 +755,24 @@ 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 = "winnow"
+version = "0.5.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "word-count"
+version = "1.2.0"
+
+[[package]]
+name = "wordy"
+version = "1.5.0"
+
+[[package]]
+name = "yacht"
+version = "0.1.0"
diff --git a/Cargo.toml b/Cargo.toml
index 49e081816..5e33f7680 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,3 +1,17 @@
[workspace]
resolver = "2"
-members = ["dev/rust-tooling"]
+members = ["dev/rust-tooling", "exercises/concept/*", "exercises/practice/*"]
+# exclude those exercises that are allowed to not compile
+exclude = [
+ "exercises/practice/accumulate",
+ "exercises/practice/clock",
+ "exercises/practice/decimal",
+ "exercises/practice/dot-dsl",
+ "exercises/practice/luhn-from",
+ "exercises/practice/luhn-trait",
+ "exercises/practice/macros",
+ "exercises/practice/xorcism",
+ # this is not allowed to not compile, but it has benchmarks, which are a
+ # nightly feature and rust-analyzer is not configured for that.
+ "exercises/practice/parallel-letter-frequency",
+]
From 232117ac978f9d5d2ff7e2e55324204092c7c4c6 Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Mon, 4 Sep 2023 21:23:21 +0200
Subject: [PATCH 30/53] don't run tests for stubbed exercises in CI
---
.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 7c77f7f85..9d3bc50cf 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -89,7 +89,7 @@ jobs:
toolchain: stable
- name: Run tests
- run: cargo test --workspace
+ run: cargo test --package rust-tooling
rustformat:
name: Check Rust Formatting
From e837156e10b35d018bb65c84f77ceb2da1f06415 Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Mon, 4 Sep 2023 22:11:18 +0200
Subject: [PATCH 31/53] add deserialization for canoncial data
---
dev/rust-tooling/src/lib.rs | 1 +
dev/rust-tooling/src/problem_spec.rs | 60 ++++++++++++++++++++++++++++
dev/rust-tooling/src/track_config.rs | 6 +++
3 files changed, 67 insertions(+)
create mode 100644 dev/rust-tooling/src/problem_spec.rs
diff --git a/dev/rust-tooling/src/lib.rs b/dev/rust-tooling/src/lib.rs
index 5bbb5d7d4..e328f5d71 100644
--- a/dev/rust-tooling/src/lib.rs
+++ b/dev/rust-tooling/src/lib.rs
@@ -1,2 +1,3 @@
+pub mod problem_spec;
pub mod exercise_config;
pub mod track_config;
diff --git a/dev/rust-tooling/src/problem_spec.rs b/dev/rust-tooling/src/problem_spec.rs
new file mode 100644
index 000000000..df9bc337b
--- /dev/null
+++ b/dev/rust-tooling/src/problem_spec.rs
@@ -0,0 +1,60 @@
+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,
+}
+
+/// Ignored because the problem-specifications repository may not be present.
+#[test]
+#[ignore]
+fn test_deserialize_all() {
+ let spec_dir =
+ env!("HOME").to_string() + "/.cache/exercism/configlet/problem-specifications/exercises";
+ for entry in walkdir::WalkDir::new(spec_dir)
+ .into_iter()
+ .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/dev/rust-tooling/src/track_config.rs b/dev/rust-tooling/src/track_config.rs
index a571a0056..c7ac1d763 100644
--- a/dev/rust-tooling/src/track_config.rs
+++ b/dev/rust-tooling/src/track_config.rs
@@ -105,3 +105,9 @@ pub struct KeyFeatureConfig {
pub title: String,
pub content: String,
}
+
+#[test]
+fn test_deserialize() {
+ // force deserialization of lazy static
+ assert!(TRACK_CONFIG.active, "should deserialize track config");
+}
From 5655b3e9f09f67ac327d88248edcd36a58eaf947 Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Wed, 6 Sep 2023 19:59:45 +0200
Subject: [PATCH 32/53] automate adding new exercises to track config
---
.gitignore | 2 +-
Cargo.lock | 299 ++++++++++++++++++
config.json | 12 +-
dev/rust-tooling/Cargo.toml | 4 +
.../src/bin/add_practice_exercise.rs | 143 +++++++++
dev/rust-tooling/src/track_config.rs | 42 ++-
6 files changed, 490 insertions(+), 12 deletions(-)
create mode 100644 dev/rust-tooling/src/bin/add_practice_exercise.rs
diff --git a/.gitignore b/.gitignore
index 99def85a3..85ff88b05 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,7 +3,7 @@
.DS_Store
**/target
tmp
-bin
+/bin
exercises/*/*/Cargo.lock
exercises/*/*/clippy.log
.vscode
diff --git a/Cargo.lock b/Cargo.lock
index 6b42ef430..4e8a95c49 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -44,6 +44,12 @@ version = "0.1.0"
name = "atbash-cipher"
version = "1.2.0"
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
[[package]]
name = "beer-song"
version = "0.0.0"
@@ -52,6 +58,12 @@ version = "0.0.0"
name = "binary-search"
version = "1.3.0"
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
[[package]]
name = "bob"
version = "1.6.0"
@@ -78,6 +90,31 @@ version = "1.1.0"
name = "collatz_conjecture"
version = "1.2.1"
+[[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-square"
version = "0.1.0"
@@ -116,6 +153,12 @@ version = "2.1.0"
name = "doubly-linked-list"
version = "0.0.0"
+[[package]]
+name = "dyn-clone"
+version = "1.0.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbfc4744c1b8f2a09adc0e55242f60b1af195d88596bd8700be74418c056c555"
+
[[package]]
name = "enum-iterator"
version = "1.4.1"
@@ -172,6 +215,12 @@ dependencies = [
"time",
]
+[[package]]
+name = "glob"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
+
[[package]]
name = "grade-school"
version = "0.0.0"
@@ -223,6 +272,22 @@ dependencies = [
"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 = "int-enum"
version = "0.5.0"
@@ -270,6 +335,12 @@ version = "0.1.0"
name = "largest-series-product"
version = "1.2.0"
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
[[package]]
name = "leap"
version = "1.6.0"
@@ -280,6 +351,22 @@ version = "0.2.147"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
+[[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 = "low_power_embedded_game"
version = "0.1.0"
@@ -310,6 +397,27 @@ checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
name = "minesweeper"
version = "1.1.0"
+[[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 = "nth_prime"
version = "2.1.0"
@@ -344,6 +452,29 @@ version = "1.2.0"
name = "pangram"
version = "0.0.0"
+[[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 = "pascals-triangle"
version = "1.5.0"
@@ -464,6 +595,15 @@ version = "2.0.0"
name = "rectangles"
version = "1.1.0"
+[[package]]
+name = "redox_syscall"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
+dependencies = [
+ "bitflags",
+]
+
[[package]]
name = "resistor-color"
version = "1.0.0"
@@ -512,9 +652,13 @@ version = "1.1.0"
name = "rust-tooling"
version = "0.1.0"
dependencies = [
+ "glob",
+ "inquire",
"once_cell",
"serde",
"serde_json",
+ "tap",
+ "uuid",
"walkdir",
]
@@ -545,6 +689,12 @@ version = "1.2.0"
name = "scale_generator"
version = "2.0.0"
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
[[package]]
name = "scrabble-score"
version = "1.1.0"
@@ -600,6 +750,36 @@ version = "0.1.0"
name = "sieve"
version = "1.1.0"
+[[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 = "simple-cipher"
version = "0.0.0"
@@ -611,6 +791,12 @@ dependencies = [
name = "simple_linked_list"
version = "0.1.0"
+[[package]]
+name = "smallvec"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9"
+
[[package]]
name = "space-age"
version = "1.2.0"
@@ -649,6 +835,32 @@ dependencies = [
"unicode-ident",
]
+[[package]]
+name = "tap"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
+
+[[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 2.0.29",
+]
+
[[package]]
name = "time"
version = "0.3.28"
@@ -705,6 +917,27 @@ 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 = "variable-length-quantity"
version = "1.2.0"
@@ -756,6 +989,72 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+[[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"
+
[[package]]
name = "winnow"
version = "0.5.15"
diff --git a/config.json b/config.json
index 0711e3590..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"
],
diff --git a/dev/rust-tooling/Cargo.toml b/dev/rust-tooling/Cargo.toml
index efade6319..b9940cb3e 100644
--- a/dev/rust-tooling/Cargo.toml
+++ b/dev/rust-tooling/Cargo.toml
@@ -6,7 +6,11 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
+glob = "0.3.1"
+inquire = "0.6.2"
once_cell = "1.18.0"
serde = { version = "1.0.188", features = ["derive"] }
serde_json = "1.0.105"
+tap = "1.0.1"
+uuid = { version = "1.4.1", features = ["v4"] }
walkdir = "2.3.3"
diff --git a/dev/rust-tooling/src/bin/add_practice_exercise.rs b/dev/rust-tooling/src/bin/add_practice_exercise.rs
new file mode 100644
index 000000000..7afbb8a18
--- /dev/null
+++ b/dev/rust-tooling/src/bin/add_practice_exercise.rs
@@ -0,0 +1,143 @@
+use std::{fmt::Display, ops::Deref, process::Command};
+
+use glob::glob;
+use inquire::{validator::Validation, Select, Text};
+use once_cell::sync::Lazy;
+use rust_tooling::track_config::{self, TRACK_CONFIG};
+use tap::prelude::*;
+
+static SPEC_DIR: Lazy = Lazy::new(|| {
+ format!(
+ "{}/.cache/exercism/configlet/problem-specifications",
+ env!("HOME")
+ )
+});
+
+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 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 is_kebab_case(s: &str) -> bool {
+ s.chars().all(|c| c.is_ascii_lowercase() || c == '-')
+}
+
+fn main() {
+ // many other functions rely on this
+ cd_into_repo_root();
+
+ update_problem_spec_cache();
+
+ 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(&format!("{}/exercises/*", SPEC_DIR.deref()))
+ .unwrap()
+ .filter_map(Result::ok)
+ .map(|path| path.file_name().unwrap().to_str().unwrap().to_string())
+ .filter(|e| !implemented_exercises.contains(e))
+ .collect::>();
+
+ let slug = Text::new("What's the slug of your exercise?")
+ .with_autocomplete(move |input: &_| {
+ Ok(unimplemented_with_spec
+ .clone()
+ .tap_ref_mut(|v: &mut Vec<_>| v.retain(|e| e.starts_with(input))))
+ })
+ .with_validator(|input: &_| {
+ if is_kebab_case(input) {
+ Ok(Validation::Valid)
+ } else {
+ Ok(Validation::Invalid(
+ "The slug must be in kebab-case.".into(),
+ ))
+ }
+ })
+ .prompt()
+ .unwrap();
+
+ let name = Text::new("What's the name of your exercise?")
+ .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::PracticeExerciseConfig::new(slug, 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."
+ );
+}
+
+/// Changes the current working directory to the root of the repository.
+fn cd_into_repo_root() {
+ let repo_path = Command::new("git")
+ .args(["rev-parse", "--show-toplevel"])
+ .output()
+ .unwrap()
+ .stdout
+ .pipe(|stdout| String::from_utf8(stdout).unwrap().trim().to_string());
+ std::env::set_current_dir(repo_path).unwrap();
+}
+
+/// Populates ~/.cache/exercism/configlet/problem-specifications
+///
+/// configlet manages a cache of the problem specifications repository.
+/// So, instead of fetching the problem specs every time over the network,
+/// we can just reuse configlet's cache.
+/// We just need to make sure that the cache is up-to-date.
+fn update_problem_spec_cache() {
+ std::process::Command::new("./bin/configlet")
+ .args(["sync"])
+ .output()
+ .unwrap();
+}
diff --git a/dev/rust-tooling/src/track_config.rs b/dev/rust-tooling/src/track_config.rs
index c7ac1d763..177dd0a96 100644
--- a/dev/rust-tooling/src/track_config.rs
+++ b/dev/rust-tooling/src/track_config.rs
@@ -2,8 +2,7 @@
//! It is capable of serializing and deserializing the configuration,
//! for example with `serde_json`.
//!
-//! Some definitions are not yet perfectly precise,
-//! because we don't anticipate they will be needed much.
+//! 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)
@@ -23,18 +22,27 @@ pub struct TrackConfig {
pub language: String,
pub slug: String,
pub active: bool,
- pub status: HashMap,
+ pub status: StatusConfig,
pub blurb: String,
pub version: u8,
pub online_editor: OnlineEditorConfig,
pub test_runner: HashMap,
- pub files: HashMap>,
+ pub files: FilesConfig,
pub exercises: ExercisesConfig,
pub concepts: Vec,
pub key_features: Vec,
pub tags: Vec,
}
+#[derive(Clone, Serialize, Deserialize)]
+#[serde(deny_unknown_fields)]
+pub struct StatusConfig {
+ pub concept_exercises: bool,
+ pub test_runner: bool,
+ pub representer: bool,
+ pub analyzer: bool,
+}
+
#[derive(Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct OnlineEditorConfig {
@@ -43,6 +51,15 @@ pub struct OnlineEditorConfig {
pub highlightjs_language: String,
}
+#[derive(Clone, Serialize, Deserialize)]
+#[serde(deny_unknown_fields)]
+pub struct FilesConfig {
+ pub solution: Vec,
+ pub test: Vec,
+ pub example: Vec,
+ pub exemplar: Vec,
+}
+
#[derive(Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ExercisesConfig {
@@ -86,10 +103,25 @@ pub struct PracticeExerciseConfig {
pub prerequisites: Vec,
pub difficulty: u8,
pub topics: Vec,
- #[serde(default)]
+ #[serde(skip_serializing_if = "Option::is_none")]
pub status: Option,
}
+impl PracticeExerciseConfig {
+ 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 {
From 219abf94863eab2eca96bcdcbd324be20b49fb7c Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Sat, 9 Sep 2023 09:47:46 +0200
Subject: [PATCH 33/53] Add problem-specifications as submodule
---
.gitmodules | 3 +++
problem-specifications | 1 +
2 files changed, 4 insertions(+)
create mode 100644 .gitmodules
create mode 160000 problem-specifications
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/problem-specifications b/problem-specifications
new file mode 160000
index 000000000..d2229dedf
--- /dev/null
+++ b/problem-specifications
@@ -0,0 +1 @@
+Subproject commit d2229dedfa6c6a6bb7d98dc49548d9ae06d0a848
From 5b2357b72ac73a3bfe5ef6609a8a2346ddeaec94 Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Sat, 9 Sep 2023 10:14:36 +0200
Subject: [PATCH 34/53] remove implicit dependency on configlet cache
---
Cargo.lock | 28 ++++++++---------
dev/rust-tooling/Cargo.toml | 2 +-
.../src/bin/add_practice_exercise.rs | 25 ++++++----------
dev/rust-tooling/src/bin_utils.rs | 14 +++++++++
dev/rust-tooling/src/lib.rs | 1 +
dev/rust-tooling/src/problem_spec.rs | 6 ++--
dev/rust-tooling/src/track_config.rs | 30 +++++++++----------
dev/rust-tooling/tests/count_ignores.rs | 2 +-
dev/rust-tooling/tests/difficulties.rs | 2 +-
.../tests/no_authors_in_cargo_toml.rs | 2 +-
10 files changed, 59 insertions(+), 53 deletions(-)
create mode 100644 dev/rust-tooling/src/bin_utils.rs
diff --git a/Cargo.lock b/Cargo.lock
index 4e8a95c49..6fe03e0ee 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -189,6 +189,20 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
name = "etl"
version = "1.0.0"
+[[package]]
+name = "exercism_tooling"
+version = "0.1.0"
+dependencies = [
+ "glob",
+ "inquire",
+ "once_cell",
+ "serde",
+ "serde_json",
+ "tap",
+ "uuid",
+ "walkdir",
+]
+
[[package]]
name = "fizzy"
version = "0.0.0"
@@ -648,20 +662,6 @@ version = "0.1.0"
name = "run-length-encoding"
version = "1.1.0"
-[[package]]
-name = "rust-tooling"
-version = "0.1.0"
-dependencies = [
- "glob",
- "inquire",
- "once_cell",
- "serde",
- "serde_json",
- "tap",
- "uuid",
- "walkdir",
-]
-
[[package]]
name = "ryu"
version = "1.0.15"
diff --git a/dev/rust-tooling/Cargo.toml b/dev/rust-tooling/Cargo.toml
index b9940cb3e..2bcf210c1 100644
--- a/dev/rust-tooling/Cargo.toml
+++ b/dev/rust-tooling/Cargo.toml
@@ -1,5 +1,5 @@
[package]
-name = "rust-tooling"
+name = "exercism_tooling"
version = "0.1.0"
edition = "2021"
diff --git a/dev/rust-tooling/src/bin/add_practice_exercise.rs b/dev/rust-tooling/src/bin/add_practice_exercise.rs
index 7afbb8a18..40c68067d 100644
--- a/dev/rust-tooling/src/bin/add_practice_exercise.rs
+++ b/dev/rust-tooling/src/bin/add_practice_exercise.rs
@@ -1,9 +1,12 @@
-use std::{fmt::Display, ops::Deref, process::Command};
+use std::{fmt::Display, ops::Deref};
+use exercism_tooling::{
+ bin_utils,
+ track_config::{self, TRACK_CONFIG},
+};
use glob::glob;
use inquire::{validator::Validation, Select, Text};
use once_cell::sync::Lazy;
-use rust_tooling::track_config::{self, TRACK_CONFIG};
use tap::prelude::*;
static SPEC_DIR: Lazy = Lazy::new(|| {
@@ -48,8 +51,9 @@ fn is_kebab_case(s: &str) -> bool {
}
fn main() {
- // many other functions rely on this
- cd_into_repo_root();
+ // Many other functions rely on this.
+ // We are doing a very script-like thing here after all.
+ bin_utils::cd_into_repo_root();
update_problem_spec_cache();
@@ -102,7 +106,7 @@ fn main() {
.unwrap()
.into();
- let config = track_config::PracticeExerciseConfig::new(slug, name, difficulty);
+ let config = track_config::PracticeExercise::new(slug, name, difficulty);
let mut track_config = TRACK_CONFIG.clone();
track_config.exercises.practice.push(config);
@@ -118,17 +122,6 @@ You can add practices, prerequisites and topics if you like."
);
}
-/// Changes the current working directory to the root of the repository.
-fn cd_into_repo_root() {
- let repo_path = Command::new("git")
- .args(["rev-parse", "--show-toplevel"])
- .output()
- .unwrap()
- .stdout
- .pipe(|stdout| String::from_utf8(stdout).unwrap().trim().to_string());
- std::env::set_current_dir(repo_path).unwrap();
-}
-
/// Populates ~/.cache/exercism/configlet/problem-specifications
///
/// configlet manages a cache of the problem specifications repository.
diff --git a/dev/rust-tooling/src/bin_utils.rs b/dev/rust-tooling/src/bin_utils.rs
new file mode 100644
index 000000000..a217f2aee
--- /dev/null
+++ b/dev/rust-tooling/src/bin_utils.rs
@@ -0,0 +1,14 @@
+//! This module contains utilities for working with the filesystem.
+//! I/O is performed at the edge of an application.
+//! This module does contain lots of I/O, to be used by executables.
+
+/// 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/dev/rust-tooling/src/lib.rs b/dev/rust-tooling/src/lib.rs
index e328f5d71..dd3459939 100644
--- a/dev/rust-tooling/src/lib.rs
+++ b/dev/rust-tooling/src/lib.rs
@@ -1,3 +1,4 @@
pub mod problem_spec;
pub mod exercise_config;
pub mod track_config;
+pub mod bin_utils;
diff --git a/dev/rust-tooling/src/problem_spec.rs b/dev/rust-tooling/src/problem_spec.rs
index df9bc337b..59d5f3962 100644
--- a/dev/rust-tooling/src/problem_spec.rs
+++ b/dev/rust-tooling/src/problem_spec.rs
@@ -38,12 +38,10 @@ pub struct SingleTestCase {
pub expected: serde_json::Value,
}
-/// Ignored because the problem-specifications repository may not be present.
#[test]
-#[ignore]
fn test_deserialize_all() {
- let spec_dir =
- env!("HOME").to_string() + "/.cache/exercism/configlet/problem-specifications/exercises";
+ crate::bin_utils::cd_into_repo_root();
+ let spec_dir = "problem-specifications/exercises";
for entry in walkdir::WalkDir::new(spec_dir)
.into_iter()
.filter_map(|e| e.ok())
diff --git a/dev/rust-tooling/src/track_config.rs b/dev/rust-tooling/src/track_config.rs
index 177dd0a96..fc0bda868 100644
--- a/dev/rust-tooling/src/track_config.rs
+++ b/dev/rust-tooling/src/track_config.rs
@@ -22,21 +22,21 @@ pub struct TrackConfig {
pub language: String,
pub slug: String,
pub active: bool,
- pub status: StatusConfig,
+ pub status: Status,
pub blurb: String,
pub version: u8,
- pub online_editor: OnlineEditorConfig,
+ pub online_editor: OnlineEditor,
pub test_runner: HashMap,
- pub files: FilesConfig,
- pub exercises: ExercisesConfig,
+ pub files: Files,
+ pub exercises: Exercises,
pub concepts: Vec,
- pub key_features: Vec,
+ pub key_features: Vec,
pub tags: Vec,
}
#[derive(Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
-pub struct StatusConfig {
+pub struct Status {
pub concept_exercises: bool,
pub test_runner: bool,
pub representer: bool,
@@ -45,7 +45,7 @@ pub struct StatusConfig {
#[derive(Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
-pub struct OnlineEditorConfig {
+pub struct OnlineEditor {
pub indent_style: String,
pub indent_size: u8,
pub highlightjs_language: String,
@@ -53,7 +53,7 @@ pub struct OnlineEditorConfig {
#[derive(Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
-pub struct FilesConfig {
+pub struct Files {
pub solution: Vec,
pub test: Vec,
pub example: Vec,
@@ -62,9 +62,9 @@ pub struct FilesConfig {
#[derive(Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
-pub struct ExercisesConfig {
- pub concept: Vec,
- pub practice: Vec,
+pub struct Exercises {
+ pub concept: Vec,
+ pub practice: Vec,
pub foregone: Vec,
}
@@ -77,7 +77,7 @@ pub enum ConceptExerciseStatus {
#[derive(Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
-pub struct ConceptExerciseConfig {
+pub struct ConceptExercise {
pub slug: String,
pub uuid: String,
pub name: String,
@@ -95,7 +95,7 @@ pub enum PracticeExerciseStatus {
#[derive(Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
-pub struct PracticeExerciseConfig {
+pub struct PracticeExercise {
pub slug: String,
pub name: String,
pub uuid: String,
@@ -107,7 +107,7 @@ pub struct PracticeExerciseConfig {
pub status: Option,
}
-impl PracticeExerciseConfig {
+impl PracticeExercise {
pub fn new(slug: String, name: String, difficulty: u8) -> Self {
Self {
slug,
@@ -132,7 +132,7 @@ pub struct ConceptConfig {
#[derive(Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
-pub struct KeyFeatureConfig {
+pub struct KeyFeature {
pub icon: String,
pub title: String,
pub content: String,
diff --git a/dev/rust-tooling/tests/count_ignores.rs b/dev/rust-tooling/tests/count_ignores.rs
index 12ba61cc0..a2a010098 100644
--- a/dev/rust-tooling/tests/count_ignores.rs
+++ b/dev/rust-tooling/tests/count_ignores.rs
@@ -1,4 +1,4 @@
-use rust_tooling::exercise_config::{
+use exercism_tooling::exercise_config::{
get_all_concept_exercise_paths, get_all_practice_exercise_paths, PracticeExerciseConfig,
};
diff --git a/dev/rust-tooling/tests/difficulties.rs b/dev/rust-tooling/tests/difficulties.rs
index a89559148..18c96bc03 100644
--- a/dev/rust-tooling/tests/difficulties.rs
+++ b/dev/rust-tooling/tests/difficulties.rs
@@ -1,6 +1,6 @@
//! Make sure exercise difficulties are set correctly
-use rust_tooling::track_config::TRACK_CONFIG;
+use exercism_tooling::track_config::TRACK_CONFIG;
#[test]
fn test_difficulties_are_valid() {
diff --git a/dev/rust-tooling/tests/no_authors_in_cargo_toml.rs b/dev/rust-tooling/tests/no_authors_in_cargo_toml.rs
index 3a83e5482..c8428ce34 100644
--- a/dev/rust-tooling/tests/no_authors_in_cargo_toml.rs
+++ b/dev/rust-tooling/tests/no_authors_in_cargo_toml.rs
@@ -1,4 +1,4 @@
-use rust_tooling::exercise_config::get_all_exercise_paths;
+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.
From dc81164be4676bc091f877074c805fca509b8058 Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Sat, 9 Sep 2023 14:37:12 +0200
Subject: [PATCH 35/53] move scripts back into bin directory
Looking at other exercism repos, this looks like the standard.
---
.github/CODEOWNERS | 5 +-
.github/workflows/tests.yml | 20 +-
.gitignore | 2 +-
Cargo.toml | 17 -
{dev/scripts => bin}/check_exercises.sh | 14 +-
{dev/scripts => bin}/ensure_stubs_compile.sh | 0
.../fetch_configlet.sh => bin/fetch-configlet | 0
{dev/scripts => bin}/format_exercises.sh | 0
{dev/scripts => bin}/lint_markdown.sh | 0
{dev/scripts => bin}/test_exercise.sh | 0
Cargo.lock => rust-tooling/Cargo.lock | 614 +-----------------
{dev/rust-tooling => rust-tooling}/Cargo.toml | 0
.../src/bin/add_practice_exercise.rs | 0
.../src/bin_utils.rs | 2 +-
.../src/exercise_config.rs | 4 +-
{dev/rust-tooling => rust-tooling}/src/lib.rs | 0
.../src/problem_spec.rs | 0
.../src/track_config.rs | 2 +-
.../tests/bash_script_conventions.rs | 31 +-
.../tests/count_ignores.rs | 0
.../tests/difficulties.rs | 0
.../tests/no_authors_in_cargo_toml.rs | 0
.../tests/no_trailing_whitespace.rs | 6 +-
23 files changed, 60 insertions(+), 657 deletions(-)
delete mode 100644 Cargo.toml
rename {dev/scripts => bin}/check_exercises.sh (83%)
rename {dev/scripts => bin}/ensure_stubs_compile.sh (100%)
rename dev/scripts/fetch_configlet.sh => bin/fetch-configlet (100%)
rename {dev/scripts => bin}/format_exercises.sh (100%)
rename {dev/scripts => bin}/lint_markdown.sh (100%)
rename {dev/scripts => bin}/test_exercise.sh (100%)
rename Cargo.lock => rust-tooling/Cargo.lock (54%)
rename {dev/rust-tooling => rust-tooling}/Cargo.toml (100%)
rename {dev/rust-tooling => rust-tooling}/src/bin/add_practice_exercise.rs (100%)
rename {dev/rust-tooling => rust-tooling}/src/bin_utils.rs (97%)
rename {dev/rust-tooling => rust-tooling}/src/exercise_config.rs (95%)
rename {dev/rust-tooling => rust-tooling}/src/lib.rs (100%)
rename {dev/rust-tooling => rust-tooling}/src/problem_spec.rs (100%)
rename {dev/rust-tooling => rust-tooling}/src/track_config.rs (98%)
rename {dev/rust-tooling => rust-tooling}/tests/bash_script_conventions.rs (63%)
rename {dev/rust-tooling => rust-tooling}/tests/count_ignores.rs (100%)
rename {dev/rust-tooling => rust-tooling}/tests/difficulties.rs (100%)
rename {dev/rust-tooling => rust-tooling}/tests/no_authors_in_cargo_toml.rs (100%)
rename {dev/rust-tooling => rust-tooling}/tests/no_trailing_whitespace.rs (84%)
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 6b66adb8c..20408047c 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -1,5 +1,6 @@
# Code owners
.github/CODEOWNERS @exercism/maintainers-admin
-# Changes to `fetch_configlet.sh` should be made in the `exercism/configlet` repo
-dev/scripts/fetch_configlet.sh @exercism/maintainers-admin
+# Changes to `fetch-configlet` should be made in the `exercism/configlet` repo
+bin/fetch-configlet @exercism/maintainers-admin
+
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 9d3bc50cf..8c2df6120 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -21,10 +21,10 @@ jobs:
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- name: Fetch configlet
- run: ./dev/scripts/fetch_configlet.sh
+ run: ./bin/fetch_configlet.sh
- name: Lint configlet
- run: ./dev/scripts/configlet lint
+ run: ./bin/configlet lint
markdownlint:
name: markdown lint
@@ -35,7 +35,7 @@ jobs:
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- name: Run markdown lint
- run: ./dev/scripts/lint_markdown.sh
+ run: ./bin/lint_markdown.sh
# stolen from https://raw.githubusercontent.com/exercism/github-actions/main/.github/workflows/shellcheck.yml
shellcheck:
@@ -68,12 +68,12 @@ jobs:
- name: Check exercises
env:
DENYWARNINGS: "1"
- run: ./dev/scripts/check_exercises.sh
+ run: ./bin/check_exercises.sh
- name: Ensure stubs compile
env:
DENYWARNINGS: "1"
- run: ./dev/scripts/ensure_stubs_compile.sh
+ run: ./bin/ensure_stubs_compile.sh
tests:
name: Run repository tests
@@ -89,7 +89,7 @@ jobs:
toolchain: stable
- name: Run tests
- run: cargo test --package rust-tooling
+ run: cd rust-tooling && cargo test
rustformat:
name: Check Rust Formatting
@@ -105,7 +105,7 @@ jobs:
toolchain: stable
- name: Format
- run: ./dev/scripts/format_exercises
+ run: ./bin/format_exercises
- name: Diff
run: |
@@ -134,12 +134,12 @@ jobs:
- name: Clippy tests
env:
CLIPPY: true
- run: ./dev/scripts/check_exercises.sh
+ run: ./bin/check_exercises.sh
- name: Clippy stubs
env:
CLIPPY: true
- run: ./dev/scripts/ensure_stubs_compile.sh
+ run: ./bin/ensure_stubs_compile.sh
nightly-compilation:
name: Check exercises on nightly (benchmark enabled)
@@ -158,4 +158,4 @@ jobs:
- name: Check exercises
env:
BENCHMARK: "1"
- run: ./dev/scripts/check_exercises.sh
+ run: ./bin/check_exercises.sh
diff --git a/.gitignore b/.gitignore
index 85ff88b05..edcc454ae 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,7 +3,7 @@
.DS_Store
**/target
tmp
-/bin
+/bin/configlet
exercises/*/*/Cargo.lock
exercises/*/*/clippy.log
.vscode
diff --git a/Cargo.toml b/Cargo.toml
deleted file mode 100644
index 5e33f7680..000000000
--- a/Cargo.toml
+++ /dev/null
@@ -1,17 +0,0 @@
-[workspace]
-resolver = "2"
-members = ["dev/rust-tooling", "exercises/concept/*", "exercises/practice/*"]
-# exclude those exercises that are allowed to not compile
-exclude = [
- "exercises/practice/accumulate",
- "exercises/practice/clock",
- "exercises/practice/decimal",
- "exercises/practice/dot-dsl",
- "exercises/practice/luhn-from",
- "exercises/practice/luhn-trait",
- "exercises/practice/macros",
- "exercises/practice/xorcism",
- # this is not allowed to not compile, but it has benchmarks, which are a
- # nightly feature and rust-analyzer is not configured for that.
- "exercises/practice/parallel-letter-frequency",
-]
diff --git a/dev/scripts/check_exercises.sh b/bin/check_exercises.sh
similarity index 83%
rename from dev/scripts/check_exercises.sh
rename to bin/check_exercises.sh
index 56bbf29ee..eb7026bde 100755
--- a/dev/scripts/check_exercises.sh
+++ b/bin/check_exercises.sh
@@ -2,12 +2,12 @@
# test for existence and executability of the test_exercise.sh script
# this depends on that
-if [ ! -f "./dev/scripts/test_exercise.sh" ]; then
- echo "dev/scripts/test_exercise.sh does not exist"
+if [ ! -f "./bin/test_exercise.sh" ]; then
+ echo "bin/test_exercise.sh does not exist"
exit 1
fi
-if [ ! -x "./dev/scripts/test_exercise.sh" ]; then
- echo "dev/scripts/test_exercise.sh does not have its executable bit set"
+if [ ! -x "./bin/test_exercise.sh" ]; then
+ echo "bin/test_exercise.sh does not have its executable bit set"
exit 1
fi
@@ -19,7 +19,7 @@ if [ -z "$DENYWARNINGS" ] && [ -z "$CLIPPY" ]; then
fi
# can't benchmark with a stable compiler; to bench, use
-# $ BENCHMARK=1 rustup run nightly dev/scripts/check_exercises.sh
+# $ BENCHMARK=1 rustup run nightly bin/check_exercises.sh
if [ -n "$BENCHMARK" ]; then
target_dir=benches
else
@@ -70,11 +70,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.
- ./dev/scripts/test_exercise.sh "$directory" --quiet --no-run
+ ./bin/test_exercise.sh "$directory" --quiet --no-run
return_code=$((return_code | $?))
else
# Run the test and get the status
- ./dev/scripts/test_exercise.sh "$directory" $release
+ ./bin/test_exercise.sh "$directory" $release
return_code=$((return_code | $?))
fi
done
diff --git a/dev/scripts/ensure_stubs_compile.sh b/bin/ensure_stubs_compile.sh
similarity index 100%
rename from dev/scripts/ensure_stubs_compile.sh
rename to bin/ensure_stubs_compile.sh
diff --git a/dev/scripts/fetch_configlet.sh b/bin/fetch-configlet
similarity index 100%
rename from dev/scripts/fetch_configlet.sh
rename to bin/fetch-configlet
diff --git a/dev/scripts/format_exercises.sh b/bin/format_exercises.sh
similarity index 100%
rename from dev/scripts/format_exercises.sh
rename to bin/format_exercises.sh
diff --git a/dev/scripts/lint_markdown.sh b/bin/lint_markdown.sh
similarity index 100%
rename from dev/scripts/lint_markdown.sh
rename to bin/lint_markdown.sh
diff --git a/dev/scripts/test_exercise.sh b/bin/test_exercise.sh
similarity index 100%
rename from dev/scripts/test_exercise.sh
rename to bin/test_exercise.sh
diff --git a/Cargo.lock b/rust-tooling/Cargo.lock
similarity index 54%
rename from Cargo.lock
rename to rust-tooling/Cargo.lock
index 6fe03e0ee..26089c89b 100644
--- a/Cargo.lock
+++ b/rust-tooling/Cargo.lock
@@ -2,94 +2,24 @@
# It is not intended for manual editing.
version = 3
-[[package]]
-name = "acronym"
-version = "1.7.0"
-
-[[package]]
-name = "affine-cipher"
-version = "2.0.0"
-
-[[package]]
-name = "allergies"
-version = "1.1.0"
-
-[[package]]
-name = "allyourbase"
-version = "1.0.0"
-
-[[package]]
-name = "alphametics"
-version = "1.3.0"
-
-[[package]]
-name = "anagram"
-version = "0.0.0"
-
-[[package]]
-name = "anyhow"
-version = "1.0.75"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
-
-[[package]]
-name = "armstrong_numbers"
-version = "1.1.0"
-
-[[package]]
-name = "assembly-line"
-version = "0.1.0"
-
-[[package]]
-name = "atbash-cipher"
-version = "1.2.0"
-
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
-[[package]]
-name = "beer-song"
-version = "0.0.0"
-
-[[package]]
-name = "binary-search"
-version = "1.3.0"
-
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
-[[package]]
-name = "bob"
-version = "1.6.0"
-
-[[package]]
-name = "book_store"
-version = "1.3.0"
-
-[[package]]
-name = "bowling"
-version = "1.2.0"
-
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
-[[package]]
-name = "circular-buffer"
-version = "1.1.0"
-
-[[package]]
-name = "collatz_conjecture"
-version = "1.2.1"
-
[[package]]
name = "crossterm"
version = "0.25.0"
@@ -115,80 +45,12 @@ dependencies = [
"winapi",
]
-[[package]]
-name = "crypto-square"
-version = "0.1.0"
-
-[[package]]
-name = "csv_builder"
-version = "0.1.0"
-
-[[package]]
-name = "custom-set"
-version = "1.0.1"
-
-[[package]]
-name = "deranged"
-version = "0.3.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946"
-
-[[package]]
-name = "diamond"
-version = "1.1.0"
-
-[[package]]
-name = "difference-of-squares"
-version = "1.2.0"
-
-[[package]]
-name = "diffie-hellman"
-version = "0.1.0"
-
-[[package]]
-name = "dominoes"
-version = "2.1.0"
-
-[[package]]
-name = "doubly-linked-list"
-version = "0.0.0"
-
[[package]]
name = "dyn-clone"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbfc4744c1b8f2a09adc0e55242f60b1af195d88596bd8700be74418c056c555"
-[[package]]
-name = "enum-iterator"
-version = "1.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7add3873b5dd076766ee79c8e406ad1a472c385476b9e38849f8eec24f1be689"
-dependencies = [
- "enum-iterator-derive",
-]
-
-[[package]]
-name = "enum-iterator-derive"
-version = "1.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eecf8589574ce9b895052fa12d69af7a233f99e6107f5cb8dd1044f2a17bfdcb"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.29",
-]
-
-[[package]]
-name = "equivalent"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
-
-[[package]]
-name = "etl"
-version = "1.0.0"
-
[[package]]
name = "exercism_tooling"
version = "0.1.0"
@@ -203,14 +65,6 @@ dependencies = [
"walkdir",
]
-[[package]]
-name = "fizzy"
-version = "0.0.0"
-
-[[package]]
-name = "forth"
-version = "1.7.0"
-
[[package]]
name = "getrandom"
version = "0.2.10"
@@ -222,70 +76,12 @@ dependencies = [
"wasi",
]
-[[package]]
-name = "gigasecond"
-version = "2.0.0"
-dependencies = [
- "time",
-]
-
[[package]]
name = "glob"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
-[[package]]
-name = "grade-school"
-version = "0.0.0"
-
-[[package]]
-name = "grains"
-version = "1.2.0"
-
-[[package]]
-name = "grep"
-version = "1.3.0"
-dependencies = [
- "anyhow",
-]
-
-[[package]]
-name = "hamming"
-version = "2.2.0"
-
-[[package]]
-name = "hashbrown"
-version = "0.14.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
-
-[[package]]
-name = "health_statistics"
-version = "0.1.0"
-
-[[package]]
-name = "hello-world"
-version = "1.1.0"
-
-[[package]]
-name = "hexadecimal"
-version = "0.0.0"
-
-[[package]]
-name = "high-scores"
-version = "4.0.0"
-
-[[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"
@@ -302,63 +98,18 @@ dependencies = [
"unicode-width",
]
-[[package]]
-name = "int-enum"
-version = "0.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cff87d3cc4b79b4559e3c75068d64247284aceb6a038bd4bb38387f3f164476d"
-dependencies = [
- "int-enum-impl",
-]
-
-[[package]]
-name = "int-enum-impl"
-version = "0.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "df1f2f068675add1a3fc77f5f5ab2e29290c841ee34d151abc007bce902e5d34"
-dependencies = [
- "proc-macro-crate",
- "proc-macro2",
- "quote",
- "syn 1.0.109",
-]
-
-[[package]]
-name = "isbn-verifier"
-version = "2.7.0"
-
-[[package]]
-name = "isogram"
-version = "1.3.0"
-
[[package]]
name = "itoa"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
-[[package]]
-name = "kindergarten_garden"
-version = "0.1.0"
-
-[[package]]
-name = "knapsack"
-version = "0.1.0"
-
-[[package]]
-name = "largest-series-product"
-version = "1.2.0"
-
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
-[[package]]
-name = "leap"
-version = "1.6.0"
-
[[package]]
name = "libc"
version = "0.2.147"
@@ -381,36 +132,6 @@ version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
-[[package]]
-name = "low_power_embedded_game"
-version = "0.1.0"
-
-[[package]]
-name = "lucians-luscious-lasagna"
-version = "0.1.0"
-
-[[package]]
-name = "luhn"
-version = "1.6.1"
-
-[[package]]
-name = "magazine_cutout"
-version = "0.1.0"
-
-[[package]]
-name = "matching-brackets"
-version = "2.0.0"
-
-[[package]]
-name = "memchr"
-version = "2.6.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
-
-[[package]]
-name = "minesweeper"
-version = "1.1.0"
-
[[package]]
name = "mio"
version = "0.8.8"
@@ -432,40 +153,12 @@ dependencies = [
"unicode-segmentation",
]
-[[package]]
-name = "nth_prime"
-version = "2.1.0"
-
-[[package]]
-name = "nucleotide-count"
-version = "1.3.0"
-
-[[package]]
-name = "nucleotide_codons"
-version = "0.1.0"
-
-[[package]]
-name = "ocr-numbers"
-version = "0.0.0"
-
[[package]]
name = "once_cell"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
-[[package]]
-name = "paasio"
-version = "0.0.0"
-
-[[package]]
-name = "palindrome-products"
-version = "1.2.0"
-
-[[package]]
-name = "pangram"
-version = "0.0.0"
-
[[package]]
name = "parking_lot"
version = "0.12.1"
@@ -489,46 +182,6 @@ dependencies = [
"windows-targets",
]
-[[package]]
-name = "pascals-triangle"
-version = "1.5.0"
-
-[[package]]
-name = "perfect_numbers"
-version = "1.1.0"
-
-[[package]]
-name = "phone-number"
-version = "1.6.1"
-
-[[package]]
-name = "pig-latin"
-version = "1.0.0"
-
-[[package]]
-name = "poker"
-version = "1.1.0"
-
-[[package]]
-name = "ppv-lite86"
-version = "0.2.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
-
-[[package]]
-name = "prime_factors"
-version = "1.1.0"
-
-[[package]]
-name = "proc-macro-crate"
-version = "1.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
-dependencies = [
- "once_cell",
- "toml_edit",
-]
-
[[package]]
name = "proc-macro2"
version = "1.0.66"
@@ -538,22 +191,6 @@ dependencies = [
"unicode-ident",
]
-[[package]]
-name = "protein-translation"
-version = "0.1.0"
-
-[[package]]
-name = "proverb"
-version = "1.1.0"
-
-[[package]]
-name = "pythagorean_triplet"
-version = "1.0.0"
-
-[[package]]
-name = "queen-attack"
-version = "2.2.0"
-
[[package]]
name = "quote"
version = "1.0.33"
@@ -563,52 +200,6 @@ dependencies = [
"proc-macro2",
]
-[[package]]
-name = "rail_fence_cipher"
-version = "1.1.0"
-
-[[package]]
-name = "raindrops"
-version = "1.1.0"
-
-[[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 = "react"
-version = "2.0.0"
-
-[[package]]
-name = "rectangles"
-version = "1.1.0"
-
[[package]]
name = "redox_syscall"
version = "0.3.5"
@@ -618,60 +209,12 @@ dependencies = [
"bitflags",
]
-[[package]]
-name = "resistor-color"
-version = "1.0.0"
-dependencies = [
- "enum-iterator",
- "int-enum",
-]
-
-[[package]]
-name = "reverse_string"
-version = "1.2.0"
-
-[[package]]
-name = "rna-transcription"
-version = "1.0.0"
-
-[[package]]
-name = "robot-name"
-version = "0.0.0"
-
-[[package]]
-name = "robot-simulator"
-version = "2.2.0"
-
-[[package]]
-name = "role_playing_game"
-version = "0.1.0"
-
-[[package]]
-name = "roman-numerals"
-version = "1.0.0"
-
-[[package]]
-name = "rotational-cipher"
-version = "1.0.0"
-
-[[package]]
-name = "rpn_calculator"
-version = "0.1.0"
-
-[[package]]
-name = "run-length-encoding"
-version = "1.1.0"
-
[[package]]
name = "ryu"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
-[[package]]
-name = "saddle-points"
-version = "1.3.0"
-
[[package]]
name = "same-file"
version = "1.0.6"
@@ -681,32 +224,12 @@ dependencies = [
"winapi-util",
]
-[[package]]
-name = "say"
-version = "1.2.0"
-
-[[package]]
-name = "scale_generator"
-version = "2.0.0"
-
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
-[[package]]
-name = "scrabble-score"
-version = "1.1.0"
-
-[[package]]
-name = "secret-handshake"
-version = "1.1.0"
-
-[[package]]
-name = "semi_structured_logs"
-version = "0.1.0"
-
[[package]]
name = "serde"
version = "1.0.188"
@@ -724,7 +247,7 @@ checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.29",
+ "syn",
]
[[package]]
@@ -738,18 +261,6 @@ dependencies = [
"serde",
]
-[[package]]
-name = "series"
-version = "0.1.0"
-
-[[package]]
-name = "short_fibonacci"
-version = "0.1.0"
-
-[[package]]
-name = "sieve"
-version = "1.1.0"
-
[[package]]
name = "signal-hook"
version = "0.3.17"
@@ -780,55 +291,17 @@ dependencies = [
"libc",
]
-[[package]]
-name = "simple-cipher"
-version = "0.0.0"
-dependencies = [
- "rand",
-]
-
-[[package]]
-name = "simple_linked_list"
-version = "0.1.0"
-
[[package]]
name = "smallvec"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9"
-[[package]]
-name = "space-age"
-version = "1.2.0"
-
-[[package]]
-name = "spiral-matrix"
-version = "1.1.0"
-
-[[package]]
-name = "sublist"
-version = "0.0.0"
-
-[[package]]
-name = "sum-of-multiples"
-version = "1.5.0"
-
-[[package]]
-name = "syn"
-version = "1.0.109"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-ident",
-]
-
[[package]]
name = "syn"
-version = "2.0.29"
+version = "2.0.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a"
+checksum = "718fa2415bcb8d8bd775917a1bf12a7931b6dfa890753378538118181e0cb398"
dependencies = [
"proc-macro2",
"quote",
@@ -858,59 +331,9 @@ checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.29",
-]
-
-[[package]]
-name = "time"
-version = "0.3.28"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "17f6bb557fd245c28e6411aa56b6403c689ad95061f50e4be16c274e70a17e48"
-dependencies = [
- "deranged",
- "serde",
- "time-core",
-]
-
-[[package]]
-name = "time-core"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
-
-[[package]]
-name = "toml_datetime"
-version = "0.6.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
-
-[[package]]
-name = "toml_edit"
-version = "0.19.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a"
-dependencies = [
- "indexmap",
- "toml_datetime",
- "winnow",
+ "syn",
]
-[[package]]
-name = "tournament"
-version = "1.4.0"
-
-[[package]]
-name = "triangle"
-version = "0.0.0"
-
-[[package]]
-name = "two-bucket"
-version = "1.4.0"
-
-[[package]]
-name = "twofer"
-version = "1.2.0"
-
[[package]]
name = "unicode-ident"
version = "1.0.11"
@@ -938,15 +361,11 @@ dependencies = [
"getrandom",
]
-[[package]]
-name = "variable-length-quantity"
-version = "1.2.0"
-
[[package]]
name = "walkdir"
-version = "2.3.3"
+version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698"
+checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
dependencies = [
"same-file",
"winapi-util",
@@ -1054,24 +473,3 @@ name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
-
-[[package]]
-name = "winnow"
-version = "0.5.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc"
-dependencies = [
- "memchr",
-]
-
-[[package]]
-name = "word-count"
-version = "1.2.0"
-
-[[package]]
-name = "wordy"
-version = "1.5.0"
-
-[[package]]
-name = "yacht"
-version = "0.1.0"
diff --git a/dev/rust-tooling/Cargo.toml b/rust-tooling/Cargo.toml
similarity index 100%
rename from dev/rust-tooling/Cargo.toml
rename to rust-tooling/Cargo.toml
diff --git a/dev/rust-tooling/src/bin/add_practice_exercise.rs b/rust-tooling/src/bin/add_practice_exercise.rs
similarity index 100%
rename from dev/rust-tooling/src/bin/add_practice_exercise.rs
rename to rust-tooling/src/bin/add_practice_exercise.rs
diff --git a/dev/rust-tooling/src/bin_utils.rs b/rust-tooling/src/bin_utils.rs
similarity index 97%
rename from dev/rust-tooling/src/bin_utils.rs
rename to rust-tooling/src/bin_utils.rs
index a217f2aee..6ab5b4c88 100644
--- a/dev/rust-tooling/src/bin_utils.rs
+++ b/rust-tooling/src/bin_utils.rs
@@ -9,6 +9,6 @@
/// 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("../..");
+ let repo_root_dir = std::path::PathBuf::from(RUST_TOOLING_DIR).join("..");
std::env::set_current_dir(repo_root_dir).unwrap();
}
diff --git a/dev/rust-tooling/src/exercise_config.rs b/rust-tooling/src/exercise_config.rs
similarity index 95%
rename from dev/rust-tooling/src/exercise_config.rs
rename to rust-tooling/src/exercise_config.rs
index 72e57fa4d..b84835bd0 100644
--- a/dev/rust-tooling/src/exercise_config.rs
+++ b/rust-tooling/src/exercise_config.rs
@@ -67,7 +67,7 @@ pub fn get_all_concept_exercise_paths() -> impl Iterator
- {
.exercises
.concept
.iter()
- .map(move |e| format!("{crate_dir}/../../exercises/concept/{}", e.slug))
+ .map(move |e| format!("{crate_dir}/../exercises/concept/{}", e.slug))
}
pub fn get_all_practice_exercise_paths() -> impl Iterator
- {
@@ -77,7 +77,7 @@ pub fn get_all_practice_exercise_paths() -> impl Iterator
- {
.exercises
.practice
.iter()
- .map(move |e| format!("{crate_dir}/../../exercises/practice/{}", e.slug))
+ .map(move |e| format!("{crate_dir}/../exercises/practice/{}", e.slug))
}
pub fn get_all_exercise_paths() -> impl Iterator
- {
diff --git a/dev/rust-tooling/src/lib.rs b/rust-tooling/src/lib.rs
similarity index 100%
rename from dev/rust-tooling/src/lib.rs
rename to rust-tooling/src/lib.rs
diff --git a/dev/rust-tooling/src/problem_spec.rs b/rust-tooling/src/problem_spec.rs
similarity index 100%
rename from dev/rust-tooling/src/problem_spec.rs
rename to rust-tooling/src/problem_spec.rs
diff --git a/dev/rust-tooling/src/track_config.rs b/rust-tooling/src/track_config.rs
similarity index 98%
rename from dev/rust-tooling/src/track_config.rs
rename to rust-tooling/src/track_config.rs
index fc0bda868..01c425982 100644
--- a/dev/rust-tooling/src/track_config.rs
+++ b/rust-tooling/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/dev/rust-tooling/tests/bash_script_conventions.rs b/rust-tooling/tests/bash_script_conventions.rs
similarity index 63%
rename from dev/rust-tooling/tests/bash_script_conventions.rs
rename to rust-tooling/tests/bash_script_conventions.rs
index b70e027a4..bef738922 100644
--- a/dev/rust-tooling/tests/bash_script_conventions.rs
+++ b/rust-tooling/tests/bash_script_conventions.rs
@@ -1,12 +1,13 @@
use std::path::PathBuf;
-/// Runs a function for each bash script in the scripts directory.
+use exercism_tooling::bin_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)) {
- let crate_dir = env!("CARGO_MANIFEST_DIR");
- let scripts_dir = format!("{crate_dir}/../scripts");
+ bin_utils::cd_into_repo_root();
- for entry in std::fs::read_dir(scripts_dir).unwrap() {
+ for entry in std::fs::read_dir("bin").unwrap() {
f(entry.unwrap().path())
}
}
@@ -15,6 +16,12 @@ fn for_all_scripts(f: fn(PathBuf)) {
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"
@@ -31,8 +38,16 @@ fn test_snake_case_name() {
.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.chars().all(|c| c.is_ascii_lowercase() || c == '_'),
+ file_name
+ .chars()
+ .all(|c| c.is_ascii_lowercase() || c == '_'),
"name of '{file_name}' should be snake_case"
);
})
@@ -43,6 +58,12 @@ fn test_snake_case_name() {
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"),
diff --git a/dev/rust-tooling/tests/count_ignores.rs b/rust-tooling/tests/count_ignores.rs
similarity index 100%
rename from dev/rust-tooling/tests/count_ignores.rs
rename to rust-tooling/tests/count_ignores.rs
diff --git a/dev/rust-tooling/tests/difficulties.rs b/rust-tooling/tests/difficulties.rs
similarity index 100%
rename from dev/rust-tooling/tests/difficulties.rs
rename to rust-tooling/tests/difficulties.rs
diff --git a/dev/rust-tooling/tests/no_authors_in_cargo_toml.rs b/rust-tooling/tests/no_authors_in_cargo_toml.rs
similarity index 100%
rename from dev/rust-tooling/tests/no_authors_in_cargo_toml.rs
rename to rust-tooling/tests/no_authors_in_cargo_toml.rs
diff --git a/dev/rust-tooling/tests/no_trailing_whitespace.rs b/rust-tooling/tests/no_trailing_whitespace.rs
similarity index 84%
rename from dev/rust-tooling/tests/no_trailing_whitespace.rs
rename to rust-tooling/tests/no_trailing_whitespace.rs
index beedcad53..59491da9f 100644
--- a/dev/rust-tooling/tests/no_trailing_whitespace.rs
+++ b/rust-tooling/tests/no_trailing_whitespace.rs
@@ -1,5 +1,6 @@
use std::path::Path;
+use exercism_tooling::bin_utils;
use walkdir::WalkDir;
fn contains_trailing_whitespace(p: &Path) -> bool {
@@ -14,10 +15,9 @@ fn contains_trailing_whitespace(p: &Path) -> bool {
#[test]
fn test_no_trailing_whitespace() {
- let crate_dir = env!("CARGO_MANIFEST_DIR");
- let repo_dir = format!("{crate_dir}/../..");
+ bin_utils::cd_into_repo_root();
- for entry in WalkDir::new(repo_dir) {
+ for entry in WalkDir::new(".") {
let entry = entry.unwrap();
if !entry.file_type().is_file() {
continue;
From bd54f00d2d0acbe2bf9abe5a595b2b44a2e6195b Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Sat, 9 Sep 2023 16:06:32 +0200
Subject: [PATCH 36/53] use convert_case crate for string case handling
---
justfile | 8 +++++
rust-tooling/Cargo.lock | 10 +++++++
rust-tooling/Cargo.toml | 1 +
rust-tooling/src/bin/add_practice_exercise.rs | 30 ++++++-------------
rust-tooling/src/exercise_config.rs | 20 ++++++-------
.../src/{bin_utils.rs => fs_utils.rs} | 4 +--
rust-tooling/src/lib.rs | 2 +-
rust-tooling/src/problem_spec.rs | 5 ++--
rust-tooling/tests/bash_script_conventions.rs | 9 +++---
rust-tooling/tests/count_ignores.rs | 5 ++--
rust-tooling/tests/no_trailing_whitespace.rs | 4 +--
11 files changed, 50 insertions(+), 48 deletions(-)
create mode 100644 justfile
rename rust-tooling/src/{bin_utils.rs => fs_utils.rs} (72%)
diff --git a/justfile b/justfile
new file mode 100644
index 000000000..a227c9381
--- /dev/null
+++ b/justfile
@@ -0,0 +1,8 @@
+_default:
+ just --list --unsorted
+
+test:
+ cd rust-tooling && cargo test
+
+add-practice-exercise:
+ cd rust-tooling && cargo run --bin add_practice_exercise
diff --git a/rust-tooling/Cargo.lock b/rust-tooling/Cargo.lock
index 26089c89b..1240d366e 100644
--- a/rust-tooling/Cargo.lock
+++ b/rust-tooling/Cargo.lock
@@ -20,6 +20,15 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+[[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 = "crossterm"
version = "0.25.0"
@@ -55,6 +64,7 @@ checksum = "bbfc4744c1b8f2a09adc0e55242f60b1af195d88596bd8700be74418c056c555"
name = "exercism_tooling"
version = "0.1.0"
dependencies = [
+ "convert_case",
"glob",
"inquire",
"once_cell",
diff --git a/rust-tooling/Cargo.toml b/rust-tooling/Cargo.toml
index 2bcf210c1..06fd6d5e2 100644
--- a/rust-tooling/Cargo.toml
+++ b/rust-tooling/Cargo.toml
@@ -6,6 +6,7 @@ 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"
inquire = "0.6.2"
once_cell = "1.18.0"
diff --git a/rust-tooling/src/bin/add_practice_exercise.rs b/rust-tooling/src/bin/add_practice_exercise.rs
index 40c68067d..d944cb510 100644
--- a/rust-tooling/src/bin/add_practice_exercise.rs
+++ b/rust-tooling/src/bin/add_practice_exercise.rs
@@ -1,21 +1,14 @@
-use std::{fmt::Display, ops::Deref};
+use std::fmt::Display;
+use convert_case::{Case, Casing};
use exercism_tooling::{
- bin_utils,
+ fs_utils,
track_config::{self, TRACK_CONFIG},
};
use glob::glob;
use inquire::{validator::Validation, Select, Text};
-use once_cell::sync::Lazy;
use tap::prelude::*;
-static SPEC_DIR: Lazy = Lazy::new(|| {
- format!(
- "{}/.cache/exercism/configlet/problem-specifications",
- env!("HOME")
- )
-});
-
enum Difficulty {
Easy,
Medium,
@@ -46,14 +39,8 @@ impl Display for Difficulty {
}
}
-fn is_kebab_case(s: &str) -> bool {
- s.chars().all(|c| c.is_ascii_lowercase() || c == '-')
-}
-
fn main() {
- // Many other functions rely on this.
- // We are doing a very script-like thing here after all.
- bin_utils::cd_into_repo_root();
+ fs_utils::cd_into_repo_root();
update_problem_spec_cache();
@@ -64,7 +51,7 @@ fn main() {
.map(|path| path.file_name().unwrap().to_str().unwrap().to_string())
.collect::>();
- let unimplemented_with_spec = glob(&format!("{}/exercises/*", SPEC_DIR.deref()))
+ let unimplemented_with_spec = glob("problem-specifications/exercises/*")
.unwrap()
.filter_map(Result::ok)
.map(|path| path.file_name().unwrap().to_str().unwrap().to_string())
@@ -77,8 +64,8 @@ fn main() {
.clone()
.tap_ref_mut(|v: &mut Vec<_>| v.retain(|e| e.starts_with(input))))
})
- .with_validator(|input: &_| {
- if is_kebab_case(input) {
+ .with_validator(|input: &str| {
+ if input.is_case(Case::Kebab) {
Ok(Validation::Valid)
} else {
Ok(Validation::Invalid(
@@ -117,7 +104,8 @@ fn main() {
std::fs::write("config.json", new_config).unwrap();
println!(
- "Added your exercise to config.json.
+ "\
+Added your exercise to config.json.
You can add practices, prerequisites and topics if you like."
);
}
diff --git a/rust-tooling/src/exercise_config.rs b/rust-tooling/src/exercise_config.rs
index b84835bd0..753b306aa 100644
--- a/rust-tooling/src/exercise_config.rs
+++ b/rust-tooling/src/exercise_config.rs
@@ -8,10 +8,10 @@ use crate::track_config::TRACK_CONFIG;
#[derive(Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
-pub struct ConceptExerciseConfig {
+pub struct ConceptExercise {
pub authors: Vec,
pub contributors: Option>,
- pub files: ConceptFilesConfig,
+ pub files: ConceptFiles,
pub icon: Option,
pub blurb: String,
pub source: Option,
@@ -21,7 +21,7 @@ pub struct ConceptExerciseConfig {
#[derive(Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
-pub struct ConceptFilesConfig {
+pub struct ConceptFiles {
pub solution: Vec,
pub test: Vec,
pub exemplar: Vec,
@@ -29,21 +29,21 @@ pub struct ConceptFilesConfig {
#[derive(Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
-pub struct PracticeExerciseConfig {
+pub struct PracticeExercise {
pub authors: Vec,
pub contributors: Option>,
- pub files: PracticeFilesConfig,
+ pub files: PracticeFiles,
pub icon: Option,
pub blurb: String,
pub source: Option,
pub source_url: Option,
pub test_runner: Option,
- pub custom: Option,
+ pub custom: Option,
}
#[derive(Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
-pub struct PracticeFilesConfig {
+pub struct PracticeFiles {
pub solution: Vec,
pub test: Vec,
pub example: Vec,
@@ -51,7 +51,7 @@ pub struct PracticeFilesConfig {
#[derive(Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
-pub struct CustomConfig {
+pub struct Custom {
#[serde(rename = "allowed-to-not-compile")]
pub allowed_to_not_compile: Option,
#[serde(rename = "test-in-release-mode")]
@@ -89,13 +89,13 @@ 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 _: ConceptExerciseConfig = serde_json::from_str(config_contents.as_str())
+ 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 _: PracticeExerciseConfig = serde_json::from_str(config_contents.as_str())
+ let _: PracticeExercise = serde_json::from_str(config_contents.as_str())
.expect("should deserialize practice exercise config");
}
}
diff --git a/rust-tooling/src/bin_utils.rs b/rust-tooling/src/fs_utils.rs
similarity index 72%
rename from rust-tooling/src/bin_utils.rs
rename to rust-tooling/src/fs_utils.rs
index 6ab5b4c88..6af30496a 100644
--- a/rust-tooling/src/bin_utils.rs
+++ b/rust-tooling/src/fs_utils.rs
@@ -1,6 +1,4 @@
-//! This module contains utilities for working with the filesystem.
-//! I/O is performed at the edge of an application.
-//! This module does contain lots of I/O, to be used by executables.
+//! This module contains utilities for working with the files in this repo.
/// Changes the current working directory to the root of the repository.
///
diff --git a/rust-tooling/src/lib.rs b/rust-tooling/src/lib.rs
index dd3459939..8b9afa9c0 100644
--- a/rust-tooling/src/lib.rs
+++ b/rust-tooling/src/lib.rs
@@ -1,4 +1,4 @@
pub mod problem_spec;
pub mod exercise_config;
pub mod track_config;
-pub mod bin_utils;
+pub mod fs_utils;
diff --git a/rust-tooling/src/problem_spec.rs b/rust-tooling/src/problem_spec.rs
index 59d5f3962..49ce5f7e1 100644
--- a/rust-tooling/src/problem_spec.rs
+++ b/rust-tooling/src/problem_spec.rs
@@ -40,9 +40,8 @@ pub struct SingleTestCase {
#[test]
fn test_deserialize_all() {
- crate::bin_utils::cd_into_repo_root();
- let spec_dir = "problem-specifications/exercises";
- for entry in walkdir::WalkDir::new(spec_dir)
+ crate::fs_utils::cd_into_repo_root();
+ for entry in walkdir::WalkDir::new("problem-specifications/exercises")
.into_iter()
.filter_map(|e| e.ok())
.filter(|e| e.file_name().to_str().unwrap() == "canonical-data.json")
diff --git a/rust-tooling/tests/bash_script_conventions.rs b/rust-tooling/tests/bash_script_conventions.rs
index bef738922..ff95feb88 100644
--- a/rust-tooling/tests/bash_script_conventions.rs
+++ b/rust-tooling/tests/bash_script_conventions.rs
@@ -1,11 +1,12 @@
use std::path::PathBuf;
-use exercism_tooling::bin_utils;
+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)) {
- bin_utils::cd_into_repo_root();
+ fs_utils::cd_into_repo_root();
for entry in std::fs::read_dir("bin").unwrap() {
f(entry.unwrap().path())
@@ -45,9 +46,7 @@ fn test_snake_case_name() {
}
assert!(
- file_name
- .chars()
- .all(|c| c.is_ascii_lowercase() || c == '_'),
+ file_name.is_case(Case::Snake),
"name of '{file_name}' should be snake_case"
);
})
diff --git a/rust-tooling/tests/count_ignores.rs b/rust-tooling/tests/count_ignores.rs
index a2a010098..4a78d04ca 100644
--- a/rust-tooling/tests/count_ignores.rs
+++ b/rust-tooling/tests/count_ignores.rs
@@ -1,5 +1,5 @@
use exercism_tooling::exercise_config::{
- get_all_concept_exercise_paths, get_all_practice_exercise_paths, PracticeExerciseConfig,
+ get_all_concept_exercise_paths, get_all_practice_exercise_paths, PracticeExercise,
};
fn assert_one_less_ignore_than_tests(path: &str) {
@@ -23,8 +23,7 @@ fn test_count_ignores() {
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: PracticeExerciseConfig =
- serde_json::from_str(config_contents.as_str()).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;
diff --git a/rust-tooling/tests/no_trailing_whitespace.rs b/rust-tooling/tests/no_trailing_whitespace.rs
index 59491da9f..e9d817a66 100644
--- a/rust-tooling/tests/no_trailing_whitespace.rs
+++ b/rust-tooling/tests/no_trailing_whitespace.rs
@@ -1,6 +1,6 @@
use std::path::Path;
-use exercism_tooling::bin_utils;
+use exercism_tooling::fs_utils;
use walkdir::WalkDir;
fn contains_trailing_whitespace(p: &Path) -> bool {
@@ -15,7 +15,7 @@ fn contains_trailing_whitespace(p: &Path) -> bool {
#[test]
fn test_no_trailing_whitespace() {
- bin_utils::cd_into_repo_root();
+ fs_utils::cd_into_repo_root();
for entry in WalkDir::new(".") {
let entry = entry.unwrap();
From 893388b0d2d41edf896124f37ffc0fb7ad0ccb25 Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Sat, 9 Sep 2023 18:22:23 +0200
Subject: [PATCH 37/53] use configlet to generate some files
---
justfile | 16 ++++-
rust-tooling/Cargo.lock | 7 --
rust-tooling/Cargo.toml | 1 -
rust-tooling/src/bin/add_practice_exercise.rs | 69 +++++++++++++------
4 files changed, 63 insertions(+), 30 deletions(-)
diff --git a/justfile b/justfile
index a227c9381..a1e87b0a2 100644
--- a/justfile
+++ b/justfile
@@ -4,5 +4,19 @@ _default:
test:
cd rust-tooling && cargo test
+# configlet wrapper, uses problem-specifications submodule
+configlet *args="":
+ @[ -f bin/configlet ] || bin/fetch-configlet
+ ./bin/configlet {{ args }}
+
+# TODO update all if no exercise specified
+update exercise:
+ @just configlet sync --update --yes --docs --metadata --tests include --exercise {{ exercise }}
+
add-practice-exercise:
- cd rust-tooling && cargo run --bin add_practice_exercise
+ cd rust-tooling && cargo run --quiet --bin add_practice_exercise
+
+# TODO remove. resets result of add-practice-exercise.
+clean:
+ git restore config.json
+ git clean -- exercises/practice
diff --git a/rust-tooling/Cargo.lock b/rust-tooling/Cargo.lock
index 1240d366e..b4df90967 100644
--- a/rust-tooling/Cargo.lock
+++ b/rust-tooling/Cargo.lock
@@ -70,7 +70,6 @@ dependencies = [
"once_cell",
"serde",
"serde_json",
- "tap",
"uuid",
"walkdir",
]
@@ -318,12 +317,6 @@ dependencies = [
"unicode-ident",
]
-[[package]]
-name = "tap"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
-
[[package]]
name = "thiserror"
version = "1.0.48"
diff --git a/rust-tooling/Cargo.toml b/rust-tooling/Cargo.toml
index 06fd6d5e2..819c0ab97 100644
--- a/rust-tooling/Cargo.toml
+++ b/rust-tooling/Cargo.toml
@@ -12,6 +12,5 @@ inquire = "0.6.2"
once_cell = "1.18.0"
serde = { version = "1.0.188", features = ["derive"] }
serde_json = "1.0.105"
-tap = "1.0.1"
uuid = { version = "1.4.1", features = ["v4"] }
walkdir = "2.3.3"
diff --git a/rust-tooling/src/bin/add_practice_exercise.rs b/rust-tooling/src/bin/add_practice_exercise.rs
index d944cb510..af07d68aa 100644
--- a/rust-tooling/src/bin/add_practice_exercise.rs
+++ b/rust-tooling/src/bin/add_practice_exercise.rs
@@ -1,5 +1,3 @@
-use std::fmt::Display;
-
use convert_case::{Case, Casing};
use exercism_tooling::{
fs_utils,
@@ -7,7 +5,6 @@ use exercism_tooling::{
};
use glob::glob;
use inquire::{validator::Validation, Select, Text};
-use tap::prelude::*;
enum Difficulty {
Easy,
@@ -28,7 +25,7 @@ impl From for u8 {
}
}
-impl Display for Difficulty {
+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)"),
@@ -42,8 +39,15 @@ impl Display for Difficulty {
fn main() {
fs_utils::cd_into_repo_root();
- update_problem_spec_cache();
+ let slug = add_entry_to_track_config();
+
+ make_configlet_generate_what_it_can(&slug);
+}
+/// 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())
@@ -58,18 +62,30 @@ fn main() {
.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: &_| {
- Ok(unimplemented_with_spec
- .clone()
- .tap_ref_mut(|v: &mut Vec<_>| v.retain(|e| e.starts_with(input))))
+ let mut slugs = unimplemented_with_spec.clone();
+ slugs.retain(|e| e.starts_with(input));
+ Ok(slugs)
})
.with_validator(|input: &str| {
- if input.is_case(Case::Kebab) {
+ 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(
- "The slug must be in kebab-case.".into(),
+ "An exercise with this slug already exists.".into(),
))
}
})
@@ -77,6 +93,7 @@ fn main() {
.unwrap();
let name = Text::new("What's the name of your exercise?")
+ .with_initial_value(&slug.to_case(Case::Title))
.prompt()
.unwrap();
@@ -93,7 +110,7 @@ fn main() {
.unwrap()
.into();
- let config = track_config::PracticeExercise::new(slug, name, difficulty);
+ let config = track_config::PracticeExercise::new(slug.clone(), name, difficulty);
let mut track_config = TRACK_CONFIG.clone();
track_config.exercises.practice.push(config);
@@ -108,17 +125,27 @@ fn main() {
Added your exercise to config.json.
You can add practices, prerequisites and topics if you like."
);
+
+ slug
}
-/// Populates ~/.cache/exercism/configlet/problem-specifications
-///
-/// configlet manages a cache of the problem specifications repository.
-/// So, instead of fetching the problem specs every time over the network,
-/// we can just reuse configlet's cache.
-/// We just need to make sure that the cache is up-to-date.
-fn update_problem_spec_cache() {
- std::process::Command::new("./bin/configlet")
- .args(["sync"])
- .output()
+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");
+ }
}
From 4433f5cb524b1c8be278c210728bbaa0c1ba89fc Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Sat, 9 Sep 2023 21:02:00 +0200
Subject: [PATCH 38/53] generate exercise files
---
bin/check_exercises.sh | 11 --
rust-tooling/src/bin/add_practice_exercise.rs | 28 +++-
rust-tooling/src/exercise_generation.rs | 121 ++++++++++++++++++
rust-tooling/src/lib.rs | 1 +
rust-tooling/src/problem_spec.rs | 16 ++-
5 files changed, 164 insertions(+), 13 deletions(-)
create mode 100644 rust-tooling/src/exercise_generation.rs
diff --git a/bin/check_exercises.sh b/bin/check_exercises.sh
index eb7026bde..0b69cfea7 100755
--- a/bin/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.sh script
-# this depends on that
-if [ ! -f "./bin/test_exercise.sh" ]; then
- echo "bin/test_exercise.sh does not exist"
- exit 1
-fi
-if [ ! -x "./bin/test_exercise.sh" ]; then
- echo "bin/test_exercise.sh 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.
diff --git a/rust-tooling/src/bin/add_practice_exercise.rs b/rust-tooling/src/bin/add_practice_exercise.rs
index af07d68aa..b7dc5d4fb 100644
--- a/rust-tooling/src/bin/add_practice_exercise.rs
+++ b/rust-tooling/src/bin/add_practice_exercise.rs
@@ -1,6 +1,8 @@
+use std::path::PathBuf;
+
use convert_case::{Case, Casing};
use exercism_tooling::{
- fs_utils,
+ exercise_generation, fs_utils,
track_config::{self, TRACK_CONFIG},
};
use glob::glob;
@@ -42,6 +44,8 @@ fn main() {
let slug = add_entry_to_track_config();
make_configlet_generate_what_it_can(&slug);
+
+ generate_exercise_files(&slug);
}
/// Interactively prompts the user for required fields in the track config
@@ -149,3 +153,25 @@ fn make_configlet_generate_what_it_can(slug: &str) {
panic!("configlet sync failed");
}
}
+
+fn generate_exercise_files(slug: &str) {
+ let exercise = exercise_generation::new(slug);
+
+ let exercise_path = PathBuf::from("exercises/practice").join(slug);
+
+ 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")).unwrap();
+ 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::create_dir(exercise_path.join("tests")).unwrap();
+ let crate_name = slug.replace('-', "_");
+ let mut test_file = exercise.test_header;
+ test_file += &exercise.test_cases;
+ std::fs::write(
+ exercise_path.join(format!("tests/{crate_name}.rs")),
+ test_file,
+ )
+ .unwrap();
+}
diff --git a/rust-tooling/src/exercise_generation.rs b/rust-tooling/src/exercise_generation.rs
new file mode 100644
index 000000000..da00c246d
--- /dev/null
+++ b/rust-tooling/src/exercise_generation.rs
@@ -0,0 +1,121 @@
+use convert_case::{Case, Casing};
+
+use crate::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_header: String,
+ pub test_cases: String,
+}
+
+pub fn new(slug: &str) -> GeneratedExercise {
+ let crate_name = slug.replace('-', "_");
+ let canonical_data = get_canonical_data(slug);
+ GeneratedExercise {
+ gitignore: GITIGNORE.into(),
+ manifest: generate_manifest(&crate_name),
+ lib_rs: generate_lib_rs(&crate_name),
+ example: EXAMPLE_RS.into(),
+ test_header: generate_test_header(&crate_name),
+ test_cases: generate_tests(canonical_data.cases),
+ }
+}
+
+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) -> String {
+ format!(
+ concat!(
+ "pub fn TODO(input: TODO) -> TODO {{\n",
+ " todo!(\"use {{input}} to implement {crate_name}\")\n",
+ "}}\n",
+ ),
+ crate_name = crate_name
+ )
+}
+
+static EXAMPLE_RS: &str = "\
+pub fn TODO(input: TODO) -> TODO {
+ TODO
+}
+";
+
+fn generate_test_header(crate_name: &str) -> String {
+ format!(
+ concat!(
+ "use {crate_name}::*;\n",
+ "\n",
+ "fn process(input: TODO, expected: TODO) -> bool {{\n",
+ " TODO\n",
+ "}}\n",
+ "\n",
+ "// The following test cases are generated from problem-specifications.\n",
+ "// If you'd like to improve the test suite, you can do it over there.\n",
+ "// https://github.com/exercism/problem-specifications/\n",
+ ),
+ crate_name = crate_name
+ )
+}
+
+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_single_test_case(case: SingleTestCase, is_first: bool) -> String {
+ // TODO generate tests with an author-provided template (Tera?)
+ let fn_name = case.description.to_case(Case::Snake);
+ format!(
+ concat!(
+ "\n",
+ "#[test]\n",
+ "{ignore}",
+ "fn {fn_name}() {{\n",
+ " process({input}, {expected})\n",
+ "}}\n",
+ ),
+ ignore = if is_first { "" } else { "#[ignore]\n" },
+ fn_name = fn_name,
+ input = case.input,
+ expected = case.expected,
+ )
+}
+
+fn generate_tests(cases: Vec) -> String {
+ let mut single_cases = Vec::new();
+ extend_single_cases(&mut single_cases, cases);
+
+ let mut buffer = String::new();
+
+ buffer.push_str(generate_single_test_case(single_cases.remove(0), true).as_str());
+
+ for case in single_cases {
+ buffer.push_str(generate_single_test_case(case, false).as_str());
+ }
+
+ buffer
+}
diff --git a/rust-tooling/src/lib.rs b/rust-tooling/src/lib.rs
index 8b9afa9c0..727eaca9a 100644
--- a/rust-tooling/src/lib.rs
+++ b/rust-tooling/src/lib.rs
@@ -1,4 +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
index 49ce5f7e1..0c8158c33 100644
--- a/rust-tooling/src/problem_spec.rs
+++ b/rust-tooling/src/problem_spec.rs
@@ -38,8 +38,22 @@ pub struct SingleTestCase {
pub expected: serde_json::Value,
}
+pub fn get_canonical_data(slug: &str) -> CanonicalData {
+ crate::fs_utils::cd_into_repo_root();
+ 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_all() {
+fn test_deserialize_canonical_data() {
crate::fs_utils::cd_into_repo_root();
for entry in walkdir::WalkDir::new("problem-specifications/exercises")
.into_iter()
From 4d9fc3469b76dd9b0ea98056717c98abce1e6765 Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Sat, 9 Sep 2023 21:42:54 +0200
Subject: [PATCH 39/53] enable updating an exercise with the generator
---
justfile | 11 +++--
...ctice_exercise.rs => generate_exercise.rs} | 41 +++++++++++++++----
2 files changed, 37 insertions(+), 15 deletions(-)
rename rust-tooling/src/bin/{add_practice_exercise.rs => generate_exercise.rs} (80%)
diff --git a/justfile b/justfile
index a1e87b0a2..8e482b032 100644
--- a/justfile
+++ b/justfile
@@ -9,14 +9,13 @@ configlet *args="":
@[ -f bin/configlet ] || bin/fetch-configlet
./bin/configlet {{ args }}
-# TODO update all if no exercise specified
-update exercise:
- @just configlet sync --update --yes --docs --metadata --tests include --exercise {{ exercise }}
-
add-practice-exercise:
- cd rust-tooling && cargo run --quiet --bin 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
+ git restore config.json exercises/practice
git clean -- exercises/practice
diff --git a/rust-tooling/src/bin/add_practice_exercise.rs b/rust-tooling/src/bin/generate_exercise.rs
similarity index 80%
rename from rust-tooling/src/bin/add_practice_exercise.rs
rename to rust-tooling/src/bin/generate_exercise.rs
index b7dc5d4fb..9d32bc82b 100644
--- a/rust-tooling/src/bin/add_practice_exercise.rs
+++ b/rust-tooling/src/bin/generate_exercise.rs
@@ -41,11 +41,32 @@ impl std::fmt::Display for Difficulty {
fn main() {
fs_utils::cd_into_repo_root();
- let slug = add_entry_to_track_config();
+ 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);
+ 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
@@ -154,18 +175,20 @@ fn make_configlet_generate_what_it_can(slug: &str) {
}
}
-fn generate_exercise_files(slug: &str) {
+fn generate_exercise_files(slug: &str, is_update: bool) {
let exercise = exercise_generation::new(slug);
let exercise_path = PathBuf::from("exercises/practice").join(slug);
- 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")).unwrap();
- 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();
+ 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();
+ }
- std::fs::create_dir(exercise_path.join("tests")).unwrap();
+ std::fs::create_dir(exercise_path.join("tests")).ok();
let crate_name = slug.replace('-', "_");
let mut test_file = exercise.test_header;
test_file += &exercise.test_cases;
From 0d0ebafaf8ea9141c362f45b25624038ec19764b Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Sat, 9 Sep 2023 22:19:38 +0200
Subject: [PATCH 40/53] generate tests according to .meta/tests.toml
---
rust-tooling/src/exercise_config.rs | 21 +++++++++++++++++++++
rust-tooling/src/exercise_generation.rs | 12 ++++++++----
2 files changed, 29 insertions(+), 4 deletions(-)
diff --git a/rust-tooling/src/exercise_config.rs b/rust-tooling/src/exercise_config.rs
index 753b306aa..29e33df2e 100644
--- a/rust-tooling/src/exercise_config.rs
+++ b/rust-tooling/src/exercise_config.rs
@@ -99,3 +99,24 @@ fn test_deserialize_all() {
.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 {
+ crate::fs_utils::cd_into_repo_root();
+ 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
+}
diff --git a/rust-tooling/src/exercise_generation.rs b/rust-tooling/src/exercise_generation.rs
index da00c246d..9bb917ed9 100644
--- a/rust-tooling/src/exercise_generation.rs
+++ b/rust-tooling/src/exercise_generation.rs
@@ -1,6 +1,6 @@
use convert_case::{Case, Casing};
-use crate::problem_spec::{get_canonical_data, SingleTestCase, TestCase};
+use crate::{problem_spec::{get_canonical_data, SingleTestCase, TestCase}, exercise_config::get_excluded_tests};
pub struct GeneratedExercise {
pub gitignore: String,
@@ -13,14 +13,14 @@ pub struct GeneratedExercise {
pub fn new(slug: &str) -> GeneratedExercise {
let crate_name = slug.replace('-', "_");
- let canonical_data = get_canonical_data(slug);
+
GeneratedExercise {
gitignore: GITIGNORE.into(),
manifest: generate_manifest(&crate_name),
lib_rs: generate_lib_rs(&crate_name),
example: EXAMPLE_RS.into(),
test_header: generate_test_header(&crate_name),
- test_cases: generate_tests(canonical_data.cases),
+ test_cases: generate_tests(slug),
}
}
@@ -105,9 +105,13 @@ fn generate_single_test_case(case: SingleTestCase, is_first: bool) -> String {
)
}
-fn generate_tests(cases: Vec) -> String {
+fn generate_tests(slug: &str) -> String {
+ let cases = get_canonical_data(slug).cases;
+ let excluded_tests = get_excluded_tests(slug);
+
let mut single_cases = Vec::new();
extend_single_cases(&mut single_cases, cases);
+ single_cases.retain(|case| !excluded_tests.contains(&case.uuid));
let mut buffer = String::new();
From b4376f35a999eec31474d3d47b72219f53d6e9f2 Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Sun, 10 Sep 2023 00:27:53 +0200
Subject: [PATCH 41/53] generate test cases with Tera tempaltes
---
rust-tooling/Cargo.lock | 605 ++++++++++++++++++++
rust-tooling/Cargo.toml | 1 +
rust-tooling/src/bin/generate_exercise.rs | 9 +-
rust-tooling/src/default_test_template.tera | 12 +
rust-tooling/src/exercise_config.rs | 7 +-
rust-tooling/src/exercise_generation.rs | 71 +--
rust-tooling/src/problem_spec.rs | 1 -
7 files changed, 651 insertions(+), 55 deletions(-)
create mode 100644 rust-tooling/src/default_test_template.tera
diff --git a/rust-tooling/Cargo.lock b/rust-tooling/Cargo.lock
index b4df90967..a8ca4639c 100644
--- a/rust-tooling/Cargo.lock
+++ b/rust-tooling/Cargo.lock
@@ -2,6 +2,30 @@
# 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"
@@ -14,12 +38,80 @@ 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"
@@ -29,6 +121,21 @@ 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"
@@ -54,6 +161,32 @@ 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"
@@ -70,10 +203,27 @@ dependencies = [
"once_cell",
"serde",
"serde_json",
+ "tera",
"uuid",
"walkdir",
]
+[[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"
@@ -91,6 +241,79 @@ 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 = "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 = "inquire"
version = "0.6.2"
@@ -113,6 +336,15 @@ 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"
@@ -125,6 +357,12 @@ 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"
@@ -141,6 +379,12 @@ 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"
@@ -162,6 +406,15 @@ 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"
@@ -191,6 +444,110 @@ dependencies = [
"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"
@@ -209,6 +566,36 @@ 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"
@@ -218,6 +605,35 @@ 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"
@@ -270,6 +686,17 @@ dependencies = [
"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"
@@ -300,6 +727,21 @@ 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"
@@ -317,6 +759,28 @@ dependencies = [
"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"
@@ -337,6 +801,78 @@ dependencies = [
"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"
@@ -364,6 +900,12 @@ 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"
@@ -380,6 +922,60 @@ 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"
@@ -411,6 +1007,15 @@ 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"
diff --git a/rust-tooling/Cargo.toml b/rust-tooling/Cargo.toml
index 819c0ab97..b4b250532 100644
--- a/rust-tooling/Cargo.toml
+++ b/rust-tooling/Cargo.toml
@@ -12,5 +12,6 @@ inquire = "0.6.2"
once_cell = "1.18.0"
serde = { version = "1.0.188", features = ["derive"] }
serde_json = "1.0.105"
+tera = "1.19.1"
uuid = { version = "1.4.1", features = ["v4"] }
walkdir = "2.3.3"
diff --git a/rust-tooling/src/bin/generate_exercise.rs b/rust-tooling/src/bin/generate_exercise.rs
index 9d32bc82b..4a7d08022 100644
--- a/rust-tooling/src/bin/generate_exercise.rs
+++ b/rust-tooling/src/bin/generate_exercise.rs
@@ -188,13 +188,16 @@ fn generate_exercise_files(slug: &str, is_update: bool) {
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();
let crate_name = slug.replace('-', "_");
- let mut test_file = exercise.test_header;
- test_file += &exercise.test_cases;
std::fs::write(
exercise_path.join(format!("tests/{crate_name}.rs")),
- test_file,
+ exercise.tests,
)
.unwrap();
}
diff --git a/rust-tooling/src/default_test_template.tera b/rust-tooling/src/default_test_template.tera
new file mode 100644
index 000000000..e48fb0892
--- /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 | replace(from=" ", to="_") }}() {
+ let input = {{ test.input }};
+ let output = {{ crate_name }}::TODO(input);
+ let expected = {{ test.expected }};
+ assert_eq!(output, expected);
+}
+{% endfor -%}
diff --git a/rust-tooling/src/exercise_config.rs b/rust-tooling/src/exercise_config.rs
index 29e33df2e..f8b170356 100644
--- a/rust-tooling/src/exercise_config.rs
+++ b/rust-tooling/src/exercise_config.rs
@@ -3,6 +3,7 @@
//! configuration, for example with `serde_json`.
use serde::{Deserialize, Serialize};
+use tera::Tera;
use crate::track_config::TRACK_CONFIG;
@@ -102,7 +103,6 @@ fn test_deserialize_all() {
/// Returns the uuids of the tests excluded in .meta/tests.toml
pub fn get_excluded_tests(slug: &str) -> Vec {
- crate::fs_utils::cd_into_repo_root();
let path = std::path::PathBuf::from("exercises/practice")
.join(slug)
.join(".meta/tests.toml");
@@ -120,3 +120,8 @@ 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/exercise_generation.rs b/rust-tooling/src/exercise_generation.rs
index 9bb917ed9..f6e46ff28 100644
--- a/rust-tooling/src/exercise_generation.rs
+++ b/rust-tooling/src/exercise_generation.rs
@@ -1,14 +1,17 @@
-use convert_case::{Case, Casing};
+use tera::Context;
-use crate::{problem_spec::{get_canonical_data, SingleTestCase, TestCase}, exercise_config::get_excluded_tests};
+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_header: String,
- pub test_cases: String,
+ pub test_template: String,
+ pub tests: String,
}
pub fn new(slug: &str) -> GeneratedExercise {
@@ -19,8 +22,8 @@ pub fn new(slug: &str) -> GeneratedExercise {
manifest: generate_manifest(&crate_name),
lib_rs: generate_lib_rs(&crate_name),
example: EXAMPLE_RS.into(),
- test_header: generate_test_header(&crate_name),
- test_cases: generate_tests(slug),
+ test_template: TEST_TEMPLATE.into(),
+ tests: generate_tests(slug),
}
}
@@ -47,7 +50,7 @@ fn generate_lib_rs(crate_name: &str) -> String {
format!(
concat!(
"pub fn TODO(input: TODO) -> TODO {{\n",
- " todo!(\"use {{input}} to implement {crate_name}\")\n",
+ " todo!(\"use {{input}} to implement {crate_name}\")\n",
"}}\n",
),
crate_name = crate_name
@@ -60,22 +63,7 @@ pub fn TODO(input: TODO) -> TODO {
}
";
-fn generate_test_header(crate_name: &str) -> String {
- format!(
- concat!(
- "use {crate_name}::*;\n",
- "\n",
- "fn process(input: TODO, expected: TODO) -> bool {{\n",
- " TODO\n",
- "}}\n",
- "\n",
- "// The following test cases are generated from problem-specifications.\n",
- "// If you'd like to improve the test suite, you can do it over there.\n",
- "// https://github.com/exercism/problem-specifications/\n",
- ),
- crate_name = crate_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 {
@@ -86,40 +74,23 @@ fn extend_single_cases(single_cases: &mut Vec, cases: Vec String {
- // TODO generate tests with an author-provided template (Tera?)
- let fn_name = case.description.to_case(Case::Snake);
- format!(
- concat!(
- "\n",
- "#[test]\n",
- "{ignore}",
- "fn {fn_name}() {{\n",
- " process({input}, {expected})\n",
- "}}\n",
- ),
- ignore = if is_first { "" } else { "#[ignore]\n" },
- fn_name = fn_name,
- input = case.input,
- expected = case.expected,
- )
-}
-
fn generate_tests(slug: &str) -> 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 buffer = String::new();
-
- buffer.push_str(generate_single_test_case(single_cases.remove(0), true).as_str());
-
- for case in single_cases {
- buffer.push_str(generate_single_test_case(case, false).as_str());
- }
+ let mut context = Context::new();
+ context.insert("crate_name", &slug.replace('-', "_"));
+ context.insert("cases", &single_cases);
- buffer
+ template.render("test_template.tera", &context).unwrap()
}
diff --git a/rust-tooling/src/problem_spec.rs b/rust-tooling/src/problem_spec.rs
index 0c8158c33..58b5437fc 100644
--- a/rust-tooling/src/problem_spec.rs
+++ b/rust-tooling/src/problem_spec.rs
@@ -39,7 +39,6 @@ pub struct SingleTestCase {
}
pub fn get_canonical_data(slug: &str) -> CanonicalData {
- crate::fs_utils::cd_into_repo_root();
let path = std::path::PathBuf::from("problem-specifications/exercises")
.join(slug)
.join("canonical-data.json");
From 615028e4b9cb8565bba973533f29bf522ead8b76 Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Sun, 10 Sep 2023 00:29:05 +0200
Subject: [PATCH 42/53] update acronym exercise using test generator
---
.../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 +++++++++----------
4 files changed, 69 insertions(+), 46 deletions(-)
create mode 100644 exercises/practice/acronym/.meta/test_template.tera
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..36437457f
--- /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 | replace(from=" ", to="_") }}() {
+ let input = "{{ test.input.phrase }}";
+ let output = acronym::abbreviate(input);
+ let expected = "{{ test.expected }}";
+ 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);
}
From 90c6878e89ac8ba3ba609ff58fbf02abef75b55f Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Sun, 10 Sep 2023 00:49:38 +0200
Subject: [PATCH 43/53] fix CI
---
.github/workflows/tests.yml | 2 +-
rust-tooling/Cargo.lock | 2 +-
rust-tooling/Cargo.toml | 2 +-
rust-tooling/src/problem_spec.rs | 3 +--
rust-tooling/tests/no_trailing_whitespace.rs | 5 ++---
5 files changed, 6 insertions(+), 8 deletions(-)
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index ff72fd6a2..c55d24207 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -105,7 +105,7 @@ jobs:
toolchain: stable
- name: Format
- run: ./bin/format_exercises
+ run: ./bin/format_exercises.sh
- name: Diff
run: |
diff --git a/rust-tooling/Cargo.lock b/rust-tooling/Cargo.lock
index a8ca4639c..8e4063cf5 100644
--- a/rust-tooling/Cargo.lock
+++ b/rust-tooling/Cargo.lock
@@ -199,13 +199,13 @@ version = "0.1.0"
dependencies = [
"convert_case",
"glob",
+ "ignore",
"inquire",
"once_cell",
"serde",
"serde_json",
"tera",
"uuid",
- "walkdir",
]
[[package]]
diff --git a/rust-tooling/Cargo.toml b/rust-tooling/Cargo.toml
index b4b250532..8d66f49e4 100644
--- a/rust-tooling/Cargo.toml
+++ b/rust-tooling/Cargo.toml
@@ -8,10 +8,10 @@ edition = "2021"
[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 = "1.0.105"
tera = "1.19.1"
uuid = { version = "1.4.1", features = ["v4"] }
-walkdir = "2.3.3"
diff --git a/rust-tooling/src/problem_spec.rs b/rust-tooling/src/problem_spec.rs
index 58b5437fc..073b8ba97 100644
--- a/rust-tooling/src/problem_spec.rs
+++ b/rust-tooling/src/problem_spec.rs
@@ -54,8 +54,7 @@ pub fn get_canonical_data(slug: &str) -> CanonicalData {
#[test]
fn test_deserialize_canonical_data() {
crate::fs_utils::cd_into_repo_root();
- for entry in walkdir::WalkDir::new("problem-specifications/exercises")
- .into_iter()
+ 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/tests/no_trailing_whitespace.rs b/rust-tooling/tests/no_trailing_whitespace.rs
index e9d817a66..540c160b9 100644
--- a/rust-tooling/tests/no_trailing_whitespace.rs
+++ b/rust-tooling/tests/no_trailing_whitespace.rs
@@ -1,7 +1,6 @@
use std::path::Path;
use exercism_tooling::fs_utils;
-use walkdir::WalkDir;
fn contains_trailing_whitespace(p: &Path) -> bool {
let contents = std::fs::read_to_string(p).unwrap();
@@ -17,9 +16,9 @@ fn contains_trailing_whitespace(p: &Path) -> bool {
fn test_no_trailing_whitespace() {
fs_utils::cd_into_repo_root();
- for entry in WalkDir::new(".") {
+ for entry in ignore::Walk::new("./") {
let entry = entry.unwrap();
- if !entry.file_type().is_file() {
+ if !entry.file_type().is_some_and(|t| t.is_file()) {
continue;
}
let path = entry.path();
From 9c1a9548b8ed2938d03fe8f91dcc2505bd128230 Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Sun, 10 Sep 2023 00:52:08 +0200
Subject: [PATCH 44/53] fix CI again
---
.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 c55d24207..7efc36b87 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -21,7 +21,7 @@ jobs:
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
- name: Fetch configlet
- run: ./bin/fetch_configlet.sh
+ run: ./bin/fetch-configlet
- name: Lint configlet
run: ./bin/configlet lint
From 7b41e08e49a94a59a2f0b220020e72b7e107354b Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Sun, 10 Sep 2023 09:41:44 +0200
Subject: [PATCH 45/53] move contributing documentation
from README to CONTRIBUTING
---
README.md | 97 -----------------------------------------
docs/CONTRIBUTING.md | 100 +++++++++++++++++++++++++++++++++++++------
justfile | 13 ++++--
3 files changed, 96 insertions(+), 114 deletions(-)
diff --git a/README.md b/README.md
index 1c37e9099..928142a4e 100644
--- a/README.md
+++ b/README.md
@@ -10,100 +10,3 @@ Check out our [contributor documentation](docs/CONTRIBUTING.md).
Most importantly, all contributions should be coordinated on the [forum].
[forum]: https://forum.exercism.org/
-
-## 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).
-
-Test files should use the following format:
-
-```rust
-extern crate exercise_name;
-
-use exercise_name::*;
-
-#[test]
-fn test_descriptive_name() {
- assert_eq!(exercise_function(1), 1);
-}
-
-#[test]
-#[ignore]
-fn test_second_and_past_tests_ignored() {
- assert_ne!(exercise_function(1), 2);
-}
-```
-
-### Verifying your Change
-
-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
-
-### 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.
-(After having discussed your suggestions on the [forum], that is.)
-
-## 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:
-
-- 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 at least the following structure:
-
- ```txt
- exercise-name
- ┣━ src
- ┃ ┗━ lib.rs // an empty file or with exercise stubs
- ┣━ tests
- ┃ ┗━ exercise_name.rs // a test suite
- ┣━ 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)
- ```
-
-- 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 `todo!()` 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.
-
-TODO automate setting of version field for problem-spec exercises
-
-- 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.
diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md
index e76c58f47..a0eb8e3a8 100644
--- a/docs/CONTRIBUTING.md
+++ b/docs/CONTRIBUTING.md
@@ -2,26 +2,98 @@
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 agreed upon on the forum.
+Contributions are very welcome if they are coordinated on the forum.
[the Exercism forum]: https://forum.exercism.org/
-As one of the many tracks in Exercism,
-contributions should observe Exercism standards like the [Code of Conduct].
-This document introduces the ways you can help
-and what maintainers expect of contributors.
+## General policies
-[Code of Conduct]: https://exercism.org/code-of-conduct
+- [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).
-## General policies
+## Tooling
+
+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)
+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.
+
+## Creating a new exercise
+
+Please familiarize yourself with the [Exercism documentation about practice exercises].
+
+[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
+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`
+
+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.
-- Pull requests should adhere to [Exercism's PR guidelines].
+Run `just update-practice-exercise` to update an exercise.
+This outsources most work to `configlet sync --update`
+and runs the test generator again.
-[Exercism's PR guidelines]: https://exercism.org/docs/community/being-a-good-community-member/pull-requests
+## Syllabus
-## Internal Tooling Style Guide
+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.
-- Prefer Rust tests over shell scripts for anything non-trivial.
- `dev/crates/repo-conventions` is a great place for general-purpose tests.
-- Bash scripts should set the following options at the top:
- `set -eo pipefail`
+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/justfile b/justfile
index 8e482b032..eb2895877 100644
--- a/justfile
+++ b/justfile
@@ -1,14 +1,21 @@
_default:
just --list --unsorted
-test:
- cd rust-tooling && cargo test
-
# 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
From 963a340c97c7e5f3b234603c37f8c056f9ebbaf1 Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Sun, 10 Sep 2023 10:05:47 +0200
Subject: [PATCH 46/53] steal beautiful README from python track
---
README.md | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 83 insertions(+), 7 deletions(-)
diff --git a/README.md b/README.md
index 928142a4e..437bb162e 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,88 @@
-# Exercism Rust Track
+
-[](https://github.com/exercism/rust/actions?query=workflow%3ACI+branch%3Amain)
+
+Exercism Rust Track
-Exercism exercises in Rust
+ [](https://forum.exercism.org)
+ [](https://exercism.org/blog/freeing-our-maintainers)
+ [](https://github.com/exercism/rust/actions?query=workflow%3ACI+branch%3Amain)
-## Contributing
+
-Check out our [contributor documentation](docs/CONTRIBUTING.md).
-Most importantly, all contributions should be coordinated on the [forum].
+Hi. 👋🏽 👋 **We are happy you are here.** 🎉 🌟
-[forum]: https://forum.exercism.org/
+
+
+**`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.
+
+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.
+
+
+
+
+
+
+
+
+
+🌟🌟 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]
+
+
+
+
+
+
+We 💛 💙 our community.
+**`But our maintainers are not accepting community contributions at this time.`**
+Please read this [community blog post][freeing-maintainers] for details.
+
+
+
+
+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._
+
+
+
+
+✨ 🦄 _**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_)
+
+
+
+
+## Exercism Rust Track License
+
+This repository uses the [MIT License](/LICENSE).
+
+[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/
From eb98f1dd589fd92ad750b344d1eca836c244505b Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Sun, 10 Sep 2023 10:12:00 +0200
Subject: [PATCH 47/53] allow README.md to disobey markdownlint
a small price to pay for such a beautiful README.
---
bin/lint_markdown.sh | 1 -
1 file changed, 1 deletion(-)
diff --git a/bin/lint_markdown.sh b/bin/lint_markdown.sh
index 3dfdd6922..52b75c2ab 100755
--- a/bin/lint_markdown.sh
+++ b/bin/lint_markdown.sh
@@ -2,7 +2,6 @@
set -eo pipefail
npx markdownlint-cli \
- ./*.md \
docs/*.md \
concepts/**/*.md \
exercises/**/*.md
From 8cacd6bf260045cc23c2b02e92b2872284a7de0f Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Sun, 10 Sep 2023 10:19:53 +0200
Subject: [PATCH 48/53] fix trailing whitespace in README
---
README.md | 21 ++++++++++-----------
1 file changed, 10 insertions(+), 11 deletions(-)
diff --git a/README.md b/README.md
index 437bb162e..6487c01f5 100644
--- a/README.md
+++ b/README.md
@@ -29,33 +29,32 @@ Students are exepcted to learn the language themselves, for example with the [of
-🌟🌟 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]
+🌟🌟 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]
-We 💛 💙 our community.
-**`But our maintainers are not accepting community contributions at this time.`**
+We 💛 💙 our community.
+**`But our maintainers are not accepting community contributions at this time.`**
Please read this [community blog post][freeing-maintainers] for details.
-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].
+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._
-✨ 🦄 _**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]
+✨ 🦄 _**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_)
From 173f87d09fad2f98a6a752875e4009f11cdbc245 Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Mon, 11 Sep 2023 22:17:08 +0200
Subject: [PATCH 49/53] generate correct function names in tests
Previously, the test function contained a hardcoded TODO
which had to be adjusted by the exercise author.
Now, the template references a variable fn_names,
which was previously read from lib.rs.
Meaning that, once the lib.rs stub is completed, the tests will
be generated with the correct function names.
---
rust-tooling/src/bin/generate_exercise.rs | 19 +++++++++++-
rust-tooling/src/default_test_template.tera | 4 +--
rust-tooling/src/exercise_generation.rs | 34 +++++++++++++--------
3 files changed, 41 insertions(+), 16 deletions(-)
diff --git a/rust-tooling/src/bin/generate_exercise.rs b/rust-tooling/src/bin/generate_exercise.rs
index 4a7d08022..28a61433a 100644
--- a/rust-tooling/src/bin/generate_exercise.rs
+++ b/rust-tooling/src/bin/generate_exercise.rs
@@ -176,7 +176,13 @@ fn make_configlet_generate_what_it_can(slug: &str) {
}
fn generate_exercise_files(slug: &str, is_update: bool) {
- let exercise = exercise_generation::new(slug);
+ 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);
@@ -201,3 +207,14 @@ 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| 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
index e48fb0892..ff383eb55 100644
--- a/rust-tooling/src/default_test_template.tera
+++ b/rust-tooling/src/default_test_template.tera
@@ -3,9 +3,9 @@
{% if loop.index != 1 -%}
#[ignore]
{% endif -%}
-fn {{ test.description | replace(from=" ", to="_") }}() {
+fn {{ test.description | lower | replace(from=" ", to="_") }}() {
let input = {{ test.input }};
- let output = {{ crate_name }}::TODO(input);
+ let output = {{ crate_name }}::{{ fn_names[0] }}(input);
let expected = {{ test.expected }};
assert_eq!(output, expected);
}
diff --git a/rust-tooling/src/exercise_generation.rs b/rust-tooling/src/exercise_generation.rs
index f6e46ff28..028776a61 100644
--- a/rust-tooling/src/exercise_generation.rs
+++ b/rust-tooling/src/exercise_generation.rs
@@ -14,16 +14,17 @@ pub struct GeneratedExercise {
pub tests: String,
}
-pub fn new(slug: &str) -> GeneratedExercise {
+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),
- example: EXAMPLE_RS.into(),
+ 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),
+ tests: generate_tests(slug, fn_names),
}
}
@@ -46,22 +47,28 @@ fn generate_manifest(crate_name: &str) -> String {
)
}
-fn generate_lib_rs(crate_name: &str) -> String {
+fn generate_lib_rs(crate_name: &str, fn_name: &str) -> String {
format!(
concat!(
- "pub fn TODO(input: TODO) -> TODO {{\n",
+ "pub fn {fn_name}(input: TODO) -> TODO {{\n",
" todo!(\"use {{input}} to implement {crate_name}\")\n",
"}}\n",
),
- crate_name = crate_name
+ fn_name = fn_name,
+ crate_name = crate_name,
)
}
-static EXAMPLE_RS: &str = "\
-pub fn TODO(input: TODO) -> TODO {
- TODO
+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");
@@ -74,7 +81,7 @@ fn extend_single_cases(single_cases: &mut Vec, cases: Vec String {
+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();
@@ -90,7 +97,8 @@ fn generate_tests(slug: &str) -> String {
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()
+ template.render("test_template.tera", &context).unwrap().trim_start().into()
}
From 85f1202212df7863617b4b6afc96b2f7e413c063 Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Mon, 11 Sep 2023 23:05:18 +0200
Subject: [PATCH 50/53] generate test files in kebab-case
I'm not sure why this is the current convention in this repo.
Rust files are always snake_case.
I'll leave it for now, but might refactor at some point
if I can't find the reason.
---
rust-tooling/src/bin/generate_exercise.rs | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/rust-tooling/src/bin/generate_exercise.rs b/rust-tooling/src/bin/generate_exercise.rs
index 28a61433a..aec5db1e1 100644
--- a/rust-tooling/src/bin/generate_exercise.rs
+++ b/rust-tooling/src/bin/generate_exercise.rs
@@ -200,9 +200,8 @@ fn generate_exercise_files(slug: &str, is_update: bool) {
}
std::fs::create_dir(exercise_path.join("tests")).ok();
- let crate_name = slug.replace('-', "_");
std::fs::write(
- exercise_path.join(format!("tests/{crate_name}.rs")),
+ exercise_path.join(format!("tests/{slug}.rs")),
exercise.tests,
)
.unwrap();
From 9b5c1e7b47c2c58bf089f0a40cbf32e443227076 Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Tue, 12 Sep 2023 20:09:38 +0200
Subject: [PATCH 51/53] apply improved test template to acronym
---
exercises/practice/acronym/.meta/test_template.tera | 8 ++++----
rust-tooling/src/default_test_template.tera | 4 ++--
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/exercises/practice/acronym/.meta/test_template.tera b/exercises/practice/acronym/.meta/test_template.tera
index 36437457f..464acfbe9 100644
--- a/exercises/practice/acronym/.meta/test_template.tera
+++ b/exercises/practice/acronym/.meta/test_template.tera
@@ -3,10 +3,10 @@
{% if loop.index != 1 -%}
#[ignore]
{% endif -%}
-fn {{ test.description | replace(from=" ", to="_") }}() {
- let input = "{{ test.input.phrase }}";
- let output = acronym::abbreviate(input);
- let expected = "{{ test.expected }}";
+fn {{ test.description | lower | 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/rust-tooling/src/default_test_template.tera b/rust-tooling/src/default_test_template.tera
index ff383eb55..38c4ce7bc 100644
--- a/rust-tooling/src/default_test_template.tera
+++ b/rust-tooling/src/default_test_template.tera
@@ -4,9 +4,9 @@
#[ignore]
{% endif -%}
fn {{ test.description | lower | replace(from=" ", to="_") }}() {
- let input = {{ test.input }};
+ let input = {{ test.input | json_encode() }};
let output = {{ crate_name }}::{{ fn_names[0] }}(input);
- let expected = {{ test.expected }};
+ let expected = {{ test.expected | json_encode() }};
assert_eq!(output, expected);
}
{% endfor -%}
From df6dc30ce2b34826e0f0cb26212e5b8b9c7af260 Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Tue, 12 Sep 2023 20:15:15 +0200
Subject: [PATCH 52/53] preserve property order with serde_json
---
rust-tooling/Cargo.lock | 23 +++++++++++++++++++++++
rust-tooling/Cargo.toml | 2 +-
2 files changed, 24 insertions(+), 1 deletion(-)
diff --git a/rust-tooling/Cargo.lock b/rust-tooling/Cargo.lock
index 8e4063cf5..ee7edff04 100644
--- a/rust-tooling/Cargo.lock
+++ b/rust-tooling/Cargo.lock
@@ -193,6 +193,12 @@ 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"
@@ -265,6 +271,12 @@ dependencies = [
"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"
@@ -314,6 +326,16 @@ dependencies = [
"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"
@@ -681,6 +703,7 @@ version = "1.0.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360"
dependencies = [
+ "indexmap",
"itoa",
"ryu",
"serde",
diff --git a/rust-tooling/Cargo.toml b/rust-tooling/Cargo.toml
index 8d66f49e4..a60bfb872 100644
--- a/rust-tooling/Cargo.toml
+++ b/rust-tooling/Cargo.toml
@@ -12,6 +12,6 @@ ignore = "0.4.20"
inquire = "0.6.2"
once_cell = "1.18.0"
serde = { version = "1.0.188", features = ["derive"] }
-serde_json = "1.0.105"
+serde_json = { version = "1.0.105", features = ["preserve_order"] }
tera = "1.19.1"
uuid = { version = "1.4.1", features = ["v4"] }
From 9a4a9e7e569fe1cdbdf21e127e4877b8fdf36cb5 Mon Sep 17 00:00:00 2001
From: Remo Senekowitsch
Date: Tue, 12 Sep 2023 20:40:51 +0200
Subject: [PATCH 53/53] improve test template
Some test descriptions have commas and other shenanigans in them.
`slugify` solves all those problems with one filter.
---
exercises/practice/acronym/.meta/test_template.tera | 2 +-
rust-tooling/src/default_test_template.tera | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/exercises/practice/acronym/.meta/test_template.tera b/exercises/practice/acronym/.meta/test_template.tera
index 464acfbe9..c8de609e1 100644
--- a/exercises/practice/acronym/.meta/test_template.tera
+++ b/exercises/practice/acronym/.meta/test_template.tera
@@ -3,7 +3,7 @@
{% if loop.index != 1 -%}
#[ignore]
{% endif -%}
-fn {{ test.description | lower | replace(from=" ", to="_") }}() {
+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() }};
diff --git a/rust-tooling/src/default_test_template.tera b/rust-tooling/src/default_test_template.tera
index 38c4ce7bc..39eb4727f 100644
--- a/rust-tooling/src/default_test_template.tera
+++ b/rust-tooling/src/default_test_template.tera
@@ -3,7 +3,7 @@
{% if loop.index != 1 -%}
#[ignore]
{% endif -%}
-fn {{ test.description | lower | replace(from=" ", to="_") }}() {
+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() }};