diff --git a/.github/workflows/pages.yaml b/.github/workflows/pages.yaml index 781d7011..4fc6ceee 100644 --- a/.github/workflows/pages.yaml +++ b/.github/workflows/pages.yaml @@ -1,37 +1,89 @@ -name: pages -on: [push, pull_request] +--- + +name: GitHub Pages +on: + # The website will be deployed when any of these branches is pushed to: + push: + branches: + - main + - training_material_wip + # Perform builds without uploads for pull requests: + pull_request: jobs: - pages: + build: + name: Build runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + branch: + - main + - training_material_wip steps: - name: Checkout repository - uses: actions/checkout@v1 + uses: actions/checkout@v3 + with: + ref: "${{ matrix.branch }}" - name: Set up Ruby 2.6 - uses: actions/setup-ruby@v1 + uses: ruby/setup-ruby@v1 with: ruby-version: 2.6 + bundler-cache: true - name: Set up Node 16 uses: actions/setup-node@v3 with: node-version: 16 - - name: Install Dependencies - run: | - npm ci - gem install bundler - bundle install --jobs 4 --retry 3 + - name: Install Node dependencies + run: npm ci - name: Build - run: | - PATH="$(pwd)/node_modules/.bin:$PATH" ./rake + run: PATH="$(pwd)/node_modules/.bin:$PATH" ./rake - - name: Deploy - uses: ferrous-systems/shared-github-actions/github-pages@main + - name: Upload content + uses: actions/upload-artifact@v3 with: + name: "content-${{ matrix.branch }}" path: target/ - token: ${{ secrets.GITHUB_TOKEN }} - if: github.event_name == 'push' && github.ref == 'refs/heads/main' + retention-days: 1 + + publish: + name: Publish + runs-on: ubuntu-latest + + needs: [build] + if: github.event_name == 'push' + + permissions: + pages: write + id-token: write + + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + steps: + - name: Download contents for the main branch + uses: actions/download-artifact@v3 + with: + name: content-main + path: target/ + + - name: Download contents for the training_material_wip branch + uses: actions/download-artifact@v3 + with: + name: content-training_material_wip + path: target/next/ + + - name: Upload GitHub Pages content + uses: actions/upload-pages-artifact@v1 + with: + path: target/ + + - name: Deploy to GitHub Pages + uses: actions/deploy-pages@v1 + id: deployment diff --git a/assignments/_templates/rustlatin/Cargo.lock b/assignments/_templates/rustlatin/Cargo.lock new file mode 100644 index 00000000..51efe7e8 --- /dev/null +++ b/assignments/_templates/rustlatin/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "rustlatin" +version = "0.1.0" diff --git a/assignments/_templates/rustlatin/Cargo.toml b/assignments/_templates/rustlatin/Cargo.toml new file mode 100644 index 00000000..e593ca8b --- /dev/null +++ b/assignments/_templates/rustlatin/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "rustlatin" +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/assignments/_templates/rustlatin/src/lib_1.rs b/assignments/_templates/rustlatin/src/lib_1.rs new file mode 100644 index 00000000..9a843c92 --- /dev/null +++ b/assignments/_templates/rustlatin/src/lib_1.rs @@ -0,0 +1,30 @@ +const VOWELS: [char; 5] = ['a', 'e', 'i', 'o', 'u']; +// ^^^^^^^^^ The vowls are contained in an array, because the length never changes. +// It's a global const because it will not be modified in any way and only +// serves as a reference. + +fn rustlatin(sentence: &str) -> Vec<_> { + // ^^^^^^^ The correct return type needs to be added by you, + // depending on what the vector's exact type is. + + let mut collection_of_words = Vec::new(); + // ^^^^^^^^^^^^ When you first open this file RA is not able to infer + // the type of this vector. Once you do the implementation, + // the type should appear here automatically. + + // Your implementation goes here: + // Iterate over the sentence to split it into words. + // Push the words into the vector. + // Correct the return type of the vector + + + collection_of_words +} + + +#[test] +fn correct_splitting(){ + assert_eq!(vec!["This", "sentence", "needs", "to", "be", "split"], rustlatin("This sentence needs to be split")) + +} + diff --git a/assignments/_templates/rustlatin/src/lib_2.rs b/assignments/_templates/rustlatin/src/lib_2.rs new file mode 100644 index 00000000..439eb852 --- /dev/null +++ b/assignments/_templates/rustlatin/src/lib_2.rs @@ -0,0 +1,21 @@ +const VOWELS: [char; 5] = ['a', 'e', 'i', 'o', 'u']; + +fn rustlatin(sentence: &str) -> Vec<_> { + // ^^^^^^^ The correct return type needs to be added by you, + // depending on what the vector's exact type is. + let mut collection_of_words = Vec::new(); + + for word in sentence.split(' ') { + // Your implementation goes here: + // Add the suffix "rs" to each word before pushing it to the vector + // Correct the return type of the function. + + }; + collection_of_words +} + +#[test] +fn concatenated(){ + assert_eq!( vec!["dors", "yours", "likers", "rustrs"], rustlatin("do you like rust")) +} + diff --git a/assignments/_templates/rustlatin/src/lib_3.rs b/assignments/_templates/rustlatin/src/lib_3.rs new file mode 100644 index 00000000..ae0cce57 --- /dev/null +++ b/assignments/_templates/rustlatin/src/lib_3.rs @@ -0,0 +1,22 @@ +const VOWELS: [char; 5] = ['a', 'e', 'i', 'o', 'u']; + +fn rustlatin(sentence: &str) -> Vec<_> { + // ^^^^^^^ The correct return type needs to be added by you, + // depending on what the vector's exact type is. + let mut collection_of_chars = Vec::new(); + + for word in sentence.split(' ') { + // Your implementation goes here: + // Add the first char of each word to the vector. + // Correct the return type of the vector. + + }; + collection_of_chars +} + + +#[test] +fn return_the_char(){ + assert_eq!(vec!['n', 't', 'd', 'b', 'i', 'a', 'r', 'v'], rustlatin("note the difference between iterator and return values")) +} + diff --git a/assignments/_templates/rustlatin/src/lib_4.rs b/assignments/_templates/rustlatin/src/lib_4.rs new file mode 100644 index 00000000..14ab29fd --- /dev/null +++ b/assignments/_templates/rustlatin/src/lib_4.rs @@ -0,0 +1,46 @@ + +const VOWELS: [char; 5] = ['a', 'e', 'i', 'o', 'u']; + +fn rustlatin(sentence: &str) -> String { + + let mut collection_of_words = Vec::new(); + + for word in sentence.split(' ') { + let first_char = word.chars().next().unwrap(); + // Your implementation goes here + // pushes the latinized words into the vector + }; + collection_of_words.join(" ") +} + +// fn latinize() goes here +// adds prefix "sr" and suffix "rs" according to the rules + + + +#[test] +fn test_latinizer() { + assert_eq!(latinize("rust"), "rustrs"); + assert_eq!(latinize("helps"), "helpsrs"); + assert_eq!(latinize("you"), "yours"); + assert_eq!(latinize("avoid"), "sravoid"); + +} + +#[test] +fn correct_translation() { + // Why can we compare `&str` and `String` here? + // https://doc.rust-lang.org/stable/std/string/struct.String.html#impl-PartialEq%3C%26%27a%20str%3E + assert_eq!( + "rustrs helpsrs yours sravoid sra lotrs srof srirritating bugsrs", + rustlatin("rust helps you avoid a lot of irritating bugs") + ) +} + +#[test] +fn incorrect() { + assert_ne!( + "this shouldrs not workrs", + rustlatin("this should not work") + ) +} diff --git a/assignments/rustlatin.adoc b/assignments/rustlatin.adoc index 301aa91a..5e2fd6e4 100644 --- a/assignments/rustlatin.adoc +++ b/assignments/rustlatin.adoc @@ -1,116 +1,160 @@ = Exercise: rustlatin :source-language: rust -In this exercise we will implement a Rust-y, simpler variant of https://en.wikipedia.org/wiki/Pig_Latin[Pig Latin] +In this exercise we will implement a Rust-y, simpler variant of https://en.wikipedia.org/wiki/Pig_Latin[Pig Latin]: Depending on if a word starts with a vowel or not, either a suffix or a prefix is added to the word -You will learn: +== Learning Goals -* How to create a Rust library -* How to deal with `Strings` and splitting -* How to deal with `char` of a `String` -* An early taste of Iterators -* How to define Globals -* Arrays vs Vectors -* See Rust compiler's type inference in action -* Most common way to do string concatenation +You will learn how to: -== Specification +* create a Rust library +* split a `&str` at specified `char` +* get single `char` out of a `&str` +* iterate over a `&str` +* define Globals +* compare a value to the content of an array +* use the Rust compiler's type inference to your advantage +* to concatenate `&str` +* return the content of a `Vec` as `String`. + +== Prerequisists + +You must be able to +* define variables as mutable +* use for loop +* use an if/else construction +* read Rust documentation +* define a function with signature and return type +* define arrays and vectors +* distinguish between `String` and `&str` + + +== Tasks For this exercise we define * the Vowels of English alphabet -> `['a', 'e', 'i', 'o', 'u']` * a sentence is a collection of ASCII characters with words that are separated by a white space -For any given sentence, you have to modify each word of the sentence using the following logic: +Implement a function that splits a sentence into its words, and adds a suffix or prefix to them according to the following rules: * If the word begins with a vowel -> add prefix “sr” to the word * If the word does not begin with a vowel -> add suffix “rs” to the word -== Tasks +The function returns a `String` containing the modified words. + +In order to learn as much as possible we recommend following the step-by-step solution. + -=== Step 1 +=== Getting started -Create a new `lib` and name it `rustlatin`. +Find the exercise skeletton in trainingmaterial/assignments/_templates/rustlatin + +The folder contains each step as it's own numbered `lib.rs` file. Each file contains starter code and a test that needs to pass in order for the step to be considered complete. + +=== Rust Analyzer +A part of this exercise is seeing type inference in action and to use it to help to determine the type the funktion is going to return. To make sure the file can be indexed by Rust Analyzer, make sure +* that the `rustlatin` folder is your rootfolder in VSCoderename +* rename the file you're currently working on to `lib.rs`. Name it back to it's numbered version when you are finished. + + +== Step-by-step-Solution + +=== Step 1: Splitting a sentence and pushing its words into a vector. + +Iterate over the sentence to split it into words. Use the white space as separator. This can be done with the https://doc.rust-lang.org/std/primitive.str.html#method.split[`.split()`] method, where the separator character `' '` goes into the paranthesis. This method returns an iterator over substrings of the string slice. In Rust, iterators are lazy, that means just calling `.split()` on a `&str` doesn't do anything by itself. It needs to be in combination with something that advances the iteration, such as a `for` loop, or a manual advancement such as the `.next()` method. These will yield the actual object you want to use. + + https://doc.rust-lang.org/std/vec/struct.Vec.html#method.push[Push] each word into the vector `collection_of_words`. Add the correct return type to the function signature. +Run the test to see if it passes. .Click to see the solution [%collapsible] ==== -[source,bash] +[source,rust] ---- -cargo new --lib rustlatin +fn rustlatin(sentence: &str) -> Vec<&str> { + let mut collection_of_words = Vec::new(); + + for word in sentence.split(' ') { + collection_of_words.push(word); + }; + + collection_of_words +} ---- ==== -=== Step 2 +=== Step 2 Concatenating String types. -Create a global variable in the file that defines the Vowels as specified above +After iterating over the sentence to split it into words, add the suffix `"rs"` to each word before pushing it to the vector. To concatenate two `&str` the first needs to be turned into the owned type with `.to_owned()`. Then `String` and `&str` can be added using `+`. Add the correct return type to the function signature. +Run the test to see if it passes. .Click to see the solution [%collapsible] ==== [source,rust] ---- -const VOWELS: [char; 5] = ['a', 'e', 'i', 'o', 'u']; +fn rustlatin(sentence: &str) -> Vec { + let mut collection_of_words = Vec::new(); + + for word in sentence.split(' ') { + collection_of_mod_words.push(word.to_owned() + "rs") + + }; + collection_of_words +} ---- ==== -=== Step 3 +=== Step 3: Iterating over a word to return the first character. +After iterating over the sentence to split it into words, add the first character of each word to the vector. -Copy paste this skeleton of the function +Check the Rust documentation on the https://doc.rust-lang.org/std/primitive.str.html#[primitive str Type] for a method that returns an iterator over the `chars` of a `&str`. The `char` type is a unicode scalar value that represents a single character. -[source,rust] ----- -fn rustlatin(sentence: String) -> String { - unimplemented!() -} ----- +Since iterators don't do anything by themselves, it needs to be advanced first, with the `.next()` method. This method returns an `Option(Self::Item)`, where `Self::Item` is the `char` in this case. You don't need to handle it with pattern matching in this case, a simple `unwrap()` will do, as a `None` is not expected to happen. + +Add the correct return type to the function signature. +Run the test to see if it passes. .Click to see the sample solution [%collapsible] ==== [source,rust] ---- -fn rustlatin(sentence: &str) -> String { - let mut new_words = Vec::new(); +fn rustlatin(sentence: &str) -> Vec { + let mut collection_of_chars = Vec::new(); + for word in sentence.split(' ') { - let first_char_of_word = word.chars().next().unwrap(); - if VOWELS.contains(&first_char_of_word) { - new_words.push("sr".to_string() + word); - } else { - new_words.push(word.to_string() + "rs"); - } - } - - new_words.join(" ") + let first_char = word.chars().next().unwrap(); + collection_of_chars.push(first_char); + }; + collection_of_chars } ---- ==== -=== Step 4 +=== Step 4: Putting everything together: Comparing values and returning the content of the vector as `String`. -Add tests +Add another function that checks if the first character of each word is a vowel. https://doc.rust-lang.org/std/primitive.slice.html#method.contains[contains()] is the method to help you with this. It adds the prefix or suffix to the word according to the rules above. + +Call the function in each iteration. + +In `fn rustlatin` return the content of the vector as `String`. +Run the tests to see if they pass. .Click to see the hints/solutions for tests [%collapsible] ==== [source,rust] ---- -#[test] -fn correct_translation() { - // Why can we compare `&str` and `String` here? - // https://doc.rust-lang.org/stable/std/string/struct.String.html#impl-PartialEq%3C%26%27a%20str%3E - assert_eq!( - "rustrs helpsrs yours sravoid sra lotrs srof srirritating bugsrs", - rustlatin("rust helps you avoid a lot of irritating bugs") - ) -} -#[test] -fn incorrect() { - assert_ne!( - "this shouldrs not workrs", - rustlatin("this should not work") - ) +fn latinize(word: &str) -> String { + let first_char_of_word = word.chars().next().unwrap(); + if VOWELS.contains(&first_char_of_word) { + "sr".to_string() + word + } else { + word.to_string() + "rs" + } } ---- ==== diff --git a/assignments/solutions/rustlatin/Cargo.lock b/assignments/solutions/rustlatin/Cargo.lock new file mode 100644 index 00000000..51efe7e8 --- /dev/null +++ b/assignments/solutions/rustlatin/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "rustlatin" +version = "0.1.0" diff --git a/assignments/solutions/rustlatin/Cargo.toml b/assignments/solutions/rustlatin/Cargo.toml new file mode 100644 index 00000000..e593ca8b --- /dev/null +++ b/assignments/solutions/rustlatin/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "rustlatin" +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/assignments/solutions/rustlatin/src/lib_1.rs b/assignments/solutions/rustlatin/src/lib_1.rs new file mode 100644 index 00000000..0a3b36c5 --- /dev/null +++ b/assignments/solutions/rustlatin/src/lib_1.rs @@ -0,0 +1,18 @@ +const VOWELS: [char; 5] = ['a', 'e', 'i', 'o', 'u']; + +fn rustlatin(sentence: &str) -> Vec<&str> { + let mut collection_of_words = Vec::new(); + + for word in sentence.split(' ') { + collection_of_words.push(word); + }; + + collection_of_words +} + + +#[test] +fn correct_splitting(){ + assert_eq!(vec!["This", "sentence", "needs", "to", "be", "split"], rustlatin("This sentence needs to be split")) + +} diff --git a/assignments/solutions/rustlatin/src/lib_2.rs b/assignments/solutions/rustlatin/src/lib_2.rs new file mode 100644 index 00000000..0bb0bf55 --- /dev/null +++ b/assignments/solutions/rustlatin/src/lib_2.rs @@ -0,0 +1,20 @@ +use std::str::Chars; + +const VOWELS: [char; 5] = ['a', 'e', 'i', 'o', 'u']; + +fn rustlatin(sentence: &str) -> Vec { + let mut collection_of_words = Vec::new(); + + for word in sentence.split(' ') { + collection_of_words.push(word.to_owned() + "rs") + + }; + collection_of_words +} + + +#[test] +fn concatenated(){ + assert_eq!( vec!["dors", "yours", "likers", "rustrs"], rustlatin("do you like rust")) +} + diff --git a/assignments/solutions/rustlatin/src/lib_3.rs b/assignments/solutions/rustlatin/src/lib_3.rs new file mode 100644 index 00000000..852e89b2 --- /dev/null +++ b/assignments/solutions/rustlatin/src/lib_3.rs @@ -0,0 +1,20 @@ +use std::str::Chars; + +const VOWELS: [char; 5] = ['a', 'e', 'i', 'o', 'u']; + +fn rustlatin(sentence: &str) -> Vec { + let mut collection_of_words = Vec::new(); + + for word in sentence.split(' ') { + let first_char = word.chars().next().unwrap(); + collection_of_words.push(first_char); + }; + words + +} + +#[test] +fn return_the_char(){ + assert_eq!(vec!['n', 't', 'd', 'b', 'i', 'a', 'r', 'v'], rustlatin("note the difference between iterator and return values")) +} + diff --git a/assignments/solutions/rustlatin/src/lib_4.rs b/assignments/solutions/rustlatin/src/lib_4.rs new file mode 100644 index 00000000..77c10ff8 --- /dev/null +++ b/assignments/solutions/rustlatin/src/lib_4.rs @@ -0,0 +1,49 @@ +const VOWELS: [char; 5] = ['a', 'e', 'i', 'o', 'u']; + +fn rustlatin(sentence: &str) -> String { + let mut collection_of_words = Vec::new(); + + for word in sentence.split(' ') { + collection_of_words.push(latinize(word)); + + }; + collection_of_words.join(" ") +} + +fn latinize(word: &str) -> String { + let first_char_of_word = word.chars().next().unwrap(); + if VOWELS.contains(&first_char_of_word) { + "sr".to_string() + word + } else { + word.to_string() + "rs" + } +} + +#[test] +fn test_latinizer() { + assert_eq!(latinize("rust"), "rustrs"); + assert_eq!(latinize("helps"), "helpsrs"); + assert_eq!(latinize("you"), "yours"); + assert_eq!(latinize("avoid"), "sravoid"); + +} + + + +#[test] +fn correct_translation() { + // Why can we compare `&str` and `String` here? + // https://doc.rust-lang.org/stable/std/string/struct.String.html#impl-PartialEq%3C%26%27a%20str%3E + assert_eq!( + "rustrs helpsrs yours sravoid sra lotrs srof srirritating bugsrs", + rustlatin("rust helps you avoid a lot of irritating bugs") + ) +} + +#[test] +fn incorrect() { + assert_ne!( + "this shouldrs not workrs", + rustlatin("this should not work") + ) +}