mirror of
https://github.com/rust-lang/rustlings.git
synced 2025-12-26 00:11:49 +02:00
Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ff3f7ae13 | ||
|
|
2b80f6ed41 | ||
|
|
3ab084a421 | ||
|
|
495174ff7c | ||
|
|
b049fa2c84 | ||
|
|
052f0aa7d4 | ||
|
|
60393999b8 | ||
|
|
b3a3351e8e | ||
|
|
86b5c08b9b | ||
|
|
dab90f7b91 | ||
|
|
71d31256a6 | ||
|
|
3b6d5c3aaa | ||
|
|
7ce42941ea | ||
|
|
3f8171475c | ||
|
|
b135b589e0 | ||
|
|
05ca3d77f7 | ||
|
|
6deee7e3e9 | ||
|
|
9dc404077a | ||
|
|
30e6af6069 | ||
|
|
9788496a85 | ||
|
|
6d3a412d47 | ||
|
|
700b236f4d | ||
|
|
bc22ec382f | ||
|
|
135e5d47a7 | ||
|
|
0f8001ea44 | ||
|
|
5b6e23c323 | ||
|
|
29b30ec946 | ||
|
|
76be5e4e99 | ||
|
|
8b9479071c | ||
|
|
3d9b03c52b | ||
|
|
a03d9655a8 | ||
|
|
1e2fd9c92f | ||
|
|
7e8530b21f | ||
|
|
98358597a9 | ||
|
|
8064facbb8 | ||
|
|
f981dcfde4 | ||
|
|
bbf8922ef7 | ||
|
|
43dc31193a |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -2,3 +2,6 @@
|
||||
target/
|
||||
**/*.rs.bk
|
||||
.DS_Store
|
||||
*.pdb
|
||||
exercises/clippy/Cargo.toml
|
||||
exercises/clippy/Cargo.lock
|
||||
|
||||
11
.travis.yml
11
.travis.yml
@@ -1,11 +0,0 @@
|
||||
language: rust
|
||||
rust:
|
||||
- stable
|
||||
- beta
|
||||
- nightly
|
||||
script: cargo test --verbose
|
||||
matrix:
|
||||
allow_failures:
|
||||
- rust: nightly
|
||||
fast_finish: true
|
||||
cache: cargo
|
||||
34
CHANGELOG.md
34
CHANGELOG.md
@@ -1,3 +1,37 @@
|
||||
<a name="3.0.0"></a>
|
||||
## 3.0.0 (2020-04-11)
|
||||
|
||||
#### Breaking Changes
|
||||
|
||||
* make "compile" exercises print output (#278) ([3b6d5c](https://github.com/fmoko/rustlings/commit/3b6d5c3aaa27a242a832799eb66e96897d26fde3))
|
||||
|
||||
#### Bug Fixes
|
||||
|
||||
* **primitive_types:** revert primitive_types4 (#296) ([b3a3351e](https://github.com/rust-lang/rustlings/commit/b3a3351e8e6a0bdee07077d7b0382953821649ae))
|
||||
* **run:** compile clippy exercise files (#295) ([3ab084a4](https://github.com/rust-lang/rustlings/commit/3ab084a421c0f140ae83bf1fc3f47b39342e7373))
|
||||
* **conversions:**
|
||||
* add additional test to meet exercise rules (#284) ([bc22ec3](https://github.com/fmoko/rustlings/commit/bc22ec382f843347333ef1301fc1bad773657f38))
|
||||
* remove duplicate not done comment (#292) ([dab90f](https://github.com/fmoko/rustlings/commit/dab90f7b91a6000fe874e3d664f244048e5fa342))
|
||||
* don't hardcode documentation version for traits (#288) ([30e6af](https://github.com/fmoko/rustlings/commit/30e6af60690c326fb5d3a9b7335f35c69c09137d))
|
||||
|
||||
#### Features
|
||||
|
||||
* add Option2 exercise (#290) ([86b5c08b](https://github.com/rust-lang/rustlings/commit/86b5c08b9bea1576127a7c5f599f5752072c087d))
|
||||
* add excercise for option (#282) ([135e5d47](https://github.com/rust-lang/rustlings/commit/135e5d47a7c395aece6f6022117fb20c82f2d3d4))
|
||||
* add new exercises for generics (#280) ([76be5e4e](https://github.com/rust-lang/rustlings/commit/76be5e4e991160f5fd9093f03ee2ba260e8f7229))
|
||||
* **ci:** add buildkite config ([b049fa2c](https://github.com/rust-lang/rustlings/commit/b049fa2c84dba0f0c8906ac44e28fd45fba51a71))
|
||||
|
||||
<a name="2.2.1"></a>
|
||||
### 2.2.1 (2020-02-27)
|
||||
|
||||
#### Bug Fixes
|
||||
|
||||
* Re-add cloning the repo to install scripts ([3d9b03c5](https://github.com/rust-lang/rustlings/commit/3d9b03c52b8dc51b140757f6fd25ad87b5782ef5))
|
||||
|
||||
#### Features
|
||||
|
||||
* Add clippy lints (#269) ([1e2fd9c9](https://github.com/rust-lang/rustlings/commit/1e2fd9c92f8cd6e389525ca1a999fca4c90b5921))
|
||||
|
||||
<a name="2.2.0"></a>
|
||||
## 2.2.0 (2020-02-25)
|
||||
|
||||
|
||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -592,7 +592,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustlings"
|
||||
version = "2.2.0"
|
||||
version = "2.2.1"
|
||||
dependencies = [
|
||||
"assert_cmd 0.11.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "rustlings"
|
||||
version = "2.2.0"
|
||||
authors = ["Marisa <mokou@posteo.de>", "Carol (Nichols || Goulding) <carol.nichols@gmail.com"]
|
||||
version = "3.0.0"
|
||||
authors = ["Marisa <mokou@posteo.de>", "Carol (Nichols || Goulding) <carol.nichols@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||

|
||||
|
||||
# rustlings 🦀❤️
|
||||
# rustlings 🦀❤️ [](https://buildkite.com/mokou/rustlings)
|
||||
|
||||
Greetings and welcome to `rustlings`. This project contains small exercises to get you used to reading and writing Rust code. This includes reading and responding to compiler messages!
|
||||
|
||||
@@ -54,7 +54,7 @@ Basically: Clone the repository, checkout to the latest tag, run `cargo install`
|
||||
```bash
|
||||
git clone https://github.com/rust-lang/rustlings
|
||||
cd rustlings
|
||||
git checkout tags/2.2.0 # or whatever the latest version is (find out at https://github.com/rust-lang/rustlings/releases/latest)
|
||||
git checkout tags/3.0.0 # or whatever the latest version is (find out at https://github.com/rust-lang/rustlings/releases/latest)
|
||||
cargo install --force --path .
|
||||
```
|
||||
|
||||
|
||||
5
buildkite.yml
Normal file
5
buildkite.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
steps:
|
||||
- label: "Test with stable"
|
||||
command: rustup run stable cargo test
|
||||
- label: "Test with beta"
|
||||
command: rustup run beta cargo test
|
||||
8
exercises/clippy/README.md
Normal file
8
exercises/clippy/README.md
Normal file
@@ -0,0 +1,8 @@
|
||||
### Clippy
|
||||
|
||||
The Clippy tool is a collection of lints to analyze your code so you can catch common mistakes and improve your Rust code.
|
||||
|
||||
If you used the installation script for Rustlings, Clippy should be already installed.
|
||||
If not you can install it manually via `rustup component add clippy`.
|
||||
|
||||
For more information about Clippy lints, please see [their documentation page](https://rust-lang.github.io/rust-clippy/master/).
|
||||
15
exercises/clippy/clippy1.rs
Normal file
15
exercises/clippy/clippy1.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
// clippy1.rs
|
||||
// The Clippy tool is a collection of lints to analyze your code
|
||||
// so you can catch common mistakes and improve your Rust code.
|
||||
//
|
||||
// Execute `rustlings hint clippy1` for hints :)
|
||||
|
||||
// I AM NOT DONE
|
||||
|
||||
fn main() {
|
||||
let x = 1.2331f64;
|
||||
let y = 1.2332f64;
|
||||
if y != x {
|
||||
println!("Success!");
|
||||
}
|
||||
}
|
||||
13
exercises/clippy/clippy2.rs
Normal file
13
exercises/clippy/clippy2.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
// clippy2.rs
|
||||
// Make me compile! Execute `rustlings hint clippy2` for hints :)
|
||||
|
||||
// I AM NOT DONE
|
||||
|
||||
fn main() {
|
||||
let mut res = 42;
|
||||
let option = Some(12);
|
||||
for x in option {
|
||||
res += x;
|
||||
}
|
||||
println!("{}", res);
|
||||
}
|
||||
@@ -9,7 +9,6 @@ fn byte_counter<T>(arg: T) -> usize {
|
||||
arg.as_ref().as_bytes().len()
|
||||
}
|
||||
|
||||
// I AM NOT DONE
|
||||
// Obtain the number of characters (not bytes) in the given argument
|
||||
// Add the AsRef trait appropriately as a trait bound
|
||||
fn char_counter<T>(arg: T) -> usize {
|
||||
|
||||
@@ -70,4 +70,11 @@ mod tests {
|
||||
assert_eq!(p.name, "Mark");
|
||||
assert_eq!(p.age, 20);
|
||||
}
|
||||
#[test]
|
||||
fn test_bad_age() {
|
||||
// Test that "Mark.twenty" will return the default person due to an error in parsing age
|
||||
let p = Person::from("Mark,twenty");
|
||||
assert_eq!(p.name, "John");
|
||||
assert_eq!(p.age, 30);
|
||||
}
|
||||
}
|
||||
|
||||
7
exercises/generics/README.md
Normal file
7
exercises/generics/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
### Generics
|
||||
|
||||
In this section you'll learn about saving yourself many lines of code with generics!
|
||||
|
||||
### Book Sections
|
||||
|
||||
- [Generic Data Types](https://doc.rust-lang.org/stable/book/ch10-01-syntax.html)
|
||||
10
exercises/generics/generics1.rs
Normal file
10
exercises/generics/generics1.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
// This shopping list program isn't compiling!
|
||||
// Use your knowledge of generics to fix it.
|
||||
|
||||
// I AM NOT DONE
|
||||
|
||||
fn main() {
|
||||
let mut shopping_list: Vec<?> = Vec::new();
|
||||
shopping_list.push("milk");
|
||||
}
|
||||
|
||||
30
exercises/generics/generics2.rs
Normal file
30
exercises/generics/generics2.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
// This powerful wrapper provides the ability to store a positive integer value.
|
||||
// Rewrite it using generics so that it supports wrapping ANY type.
|
||||
|
||||
// I AM NOT DONE
|
||||
struct Wrapper<u32> {
|
||||
value: u32
|
||||
}
|
||||
|
||||
impl<u32> Wrapper<u32> {
|
||||
pub fn new(value: u32) -> Self {
|
||||
Wrapper { value }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn store_u32_in_wrapper() {
|
||||
assert_eq!(Wrapper::new(42).value, 42);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn store_str_in_wrapper() {
|
||||
// TODO: Delete this assert and uncomment the one below once you have finished the exercise.
|
||||
assert!(false);
|
||||
// assert_eq!(Wrapper::new("Foo").value, "Foo");
|
||||
}
|
||||
}
|
||||
46
exercises/generics/generics3.rs
Normal file
46
exercises/generics/generics3.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
// An imaginary magical school has a new report card generation system written in rust!
|
||||
// Currently the system only supports creating report cards where the student's grade
|
||||
// is represented numerically (e.g. 1.0 -> 5.5). However, the school also issues alphabetical grades
|
||||
// (A+ -> F-) and needs to be able to print both types of report card!
|
||||
|
||||
// Make the necessary code changes to support alphabetical report cards, thereby making the second
|
||||
// test pass.
|
||||
|
||||
// I AM NOT DONE
|
||||
pub struct ReportCard {
|
||||
pub grade: f32,
|
||||
pub student_name: String,
|
||||
pub student_age: u8,
|
||||
}
|
||||
|
||||
impl ReportCard {
|
||||
pub fn print(&self) -> String {
|
||||
format!("{} ({}) - achieved a grade of {}", &self.student_name, &self.student_age, &self.grade)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn generate_numeric_report_card() {
|
||||
let report_card = ReportCard {
|
||||
grade: 2.1,
|
||||
student_name: "Tom Wriggle".to_string(),
|
||||
student_age: 12,
|
||||
};
|
||||
assert_eq!(report_card.print(), "Tom Wriggle (12) - achieved a grade of 2.1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generate_alphabetic_report_card() {
|
||||
// TODO: Make sure to change the grade here after you finish the exercise.
|
||||
let report_card = ReportCard {
|
||||
grade: 2.1,
|
||||
student_name: "Gary Plotter".to_string(),
|
||||
student_age: 11,
|
||||
};
|
||||
assert_eq!(report_card.print(), "Gary Plotter (11) - achieved a grade of A+");
|
||||
}
|
||||
}
|
||||
9
exercises/option/README.md
Normal file
9
exercises/option/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
### Option
|
||||
|
||||
#### Book Sections
|
||||
|
||||
To learn about Option<T>, check out these links:
|
||||
|
||||
- [Option Enum Format](https://doc.rust-lang.org/stable/book/ch10-01-syntax.html#in-enum-definitions)
|
||||
- [Option Module Documentation](https://doc.rust-lang.org/std/option/)
|
||||
- [Option Enum Documentation](https://doc.rust-lang.org/std/option/enum.Option.html)
|
||||
23
exercises/option/option1.rs
Normal file
23
exercises/option/option1.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
// option1.rs
|
||||
// Make me compile! Execute `rustlings hint option1` for hints
|
||||
|
||||
// I AM NOT DONE
|
||||
|
||||
// you can modify anything EXCEPT for this function's sig
|
||||
fn print_number(maybe_number: Option<u16>) {
|
||||
println!("printing: {}", maybe_number.unwrap());
|
||||
}
|
||||
|
||||
fn main() {
|
||||
print_number(13);
|
||||
print_number(99);
|
||||
|
||||
let mut numbers: [Option<u16>; 5];
|
||||
for iter in 0..5 {
|
||||
let number_to_add: u16 = {
|
||||
((iter * 5) + 2) / (4 * 16)
|
||||
};
|
||||
|
||||
numbers[iter] = number_to_add;
|
||||
}
|
||||
}
|
||||
25
exercises/option/option2.rs
Normal file
25
exercises/option/option2.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
// option2.rs
|
||||
// Make me compile! Execute `rustlings hint option2` for hints
|
||||
|
||||
// I AM NOT DONE
|
||||
|
||||
fn main() {
|
||||
let optional_value = Some(String::from("rustlings"));
|
||||
// Make this an if let statement whose value is "Some" type
|
||||
value = optional_value {
|
||||
println!("the value of optional value is: {}", value);
|
||||
} else {
|
||||
println!("The optional value doesn't contain anything!");
|
||||
}
|
||||
|
||||
let mut optional_values_vec: Vec<Option<i8>> = Vec::new();
|
||||
for x in 1..10 {
|
||||
optional_values_vec.push(Some(x));
|
||||
}
|
||||
|
||||
// make this a while let statement - remember that vector.pop also adds another layer of Option<T>
|
||||
// You can stack `Option<T>`'s into while let and if let
|
||||
value = optional_values_vec.pop() {
|
||||
println!("current value: {}", value);
|
||||
}
|
||||
}
|
||||
84
info.toml
84
info.toml
@@ -511,14 +511,32 @@ https://doc.rust-lang.org/std/result/#results-must-be-used"""
|
||||
|
||||
[[exercises]]
|
||||
name = "option1"
|
||||
path = "exercises/error_handling/option1.rs"
|
||||
mode = "test"
|
||||
path = "exercises/option/option1.rs"
|
||||
mode = "compile"
|
||||
hint = """
|
||||
Try using a `match` statement where the arms are `Some(thing)` and `None`.
|
||||
Or set a default value to print out if you get `None` by using the
|
||||
function `unwrap_or`.
|
||||
Or use an `if let` statement on the result of `pop()` to both destructure
|
||||
a `Some` value and only print out something if we have a value!"""
|
||||
Check out some functions of Option:
|
||||
is_some
|
||||
is_none
|
||||
unwrap
|
||||
|
||||
and:
|
||||
pattern matching
|
||||
"""
|
||||
|
||||
[[exercises]]
|
||||
name = "option2"
|
||||
path = "exercises/option/option2.rs"
|
||||
mode = "compile"
|
||||
hint = """
|
||||
check out:
|
||||
https://doc.rust-lang.org/rust-by-example/flow_control/if_let.html
|
||||
https://doc.rust-lang.org/rust-by-example/flow_control/while_let.html
|
||||
|
||||
Remember that Options can be stacked in if let and while let.
|
||||
For example: Some(Some(variable)) = variable2
|
||||
|
||||
|
||||
"""
|
||||
|
||||
[[exercises]]
|
||||
name = "result1"
|
||||
@@ -529,6 +547,22 @@ hint = """
|
||||
It should be doing some checking, returning an `Err` result if those checks fail, and only
|
||||
returning an `Ok` result if those checks determine that everything is... okay :)"""
|
||||
|
||||
# CLIPPY
|
||||
|
||||
[[exercises]]
|
||||
name = "clippy1"
|
||||
path = "exercises/clippy/clippy1.rs"
|
||||
mode = "clippy"
|
||||
hint = """
|
||||
Floating point calculations are usually imprecise, so asking if two values are exactly equal is asking for trouble"""
|
||||
|
||||
[[exercises]]
|
||||
name = "clippy2"
|
||||
path = "exercises/clippy/clippy2.rs"
|
||||
mode = "clippy"
|
||||
hint = """
|
||||
`for` loops over Option values are more clearly expressed as an `if let`"""
|
||||
|
||||
# STANDARD LIBRARY TYPES
|
||||
|
||||
[[exercises]]
|
||||
@@ -594,7 +628,7 @@ path = "exercises/traits/traits1.rs"
|
||||
mode = "test"
|
||||
hint = """
|
||||
A discussion about Traits in Rust can be found at:
|
||||
https://doc.rust-lang.org/1.30.0/book/second-edition/ch10-02-traits.html
|
||||
https://doc.rust-lang.org/book/ch10-02-traits.html
|
||||
"""
|
||||
|
||||
[[exercises]]
|
||||
@@ -608,6 +642,38 @@ Try mutating the incoming string vector.
|
||||
Vectors provide suitable methods for adding an element at the end. See
|
||||
the documentation at: https://doc.rust-lang.org/std/vec/struct.Vec.html"""
|
||||
|
||||
# Generics
|
||||
|
||||
[[exercises]]
|
||||
name = "generics1"
|
||||
path = "exercises/generics/generics1.rs"
|
||||
mode = "compile"
|
||||
hint = """
|
||||
Vectors in rust make use of generics to create dynamically sized arrays of any type.
|
||||
You need to tell the compiler what type we are pushing onto this vector."""
|
||||
|
||||
[[exercises]]
|
||||
name = "generics2"
|
||||
path = "exercises/generics/generics2.rs"
|
||||
mode = "test"
|
||||
hint = """
|
||||
Think carefully about what we need to do here. Currently we are wrapping only values of
|
||||
type 'u32'. Maybe we need to update the explicit references to this data type somehow?
|
||||
"""
|
||||
|
||||
[[exercises]]
|
||||
name = "generics3"
|
||||
path = "exercises/generics/generics3.rs"
|
||||
mode = "test"
|
||||
hint = """
|
||||
To find the best solution to this challenge you're going to need to think back to your
|
||||
knowledge of traits, specifically Trait Bound Syntax - you may also need this: "use std::fmt::Display;"
|
||||
|
||||
This is definitely harder than the last two exercises! You need to think about not only making the
|
||||
ReportCard struct generic, but also the correct property - you will need to change the implementation
|
||||
of the struct slightly too...you can do it!
|
||||
"""
|
||||
|
||||
# THREADS
|
||||
|
||||
[[exercises]]
|
||||
@@ -685,4 +751,4 @@ path = "exercises/conversions/from_str.rs"
|
||||
mode = "test"
|
||||
hint = """
|
||||
If you've already solved try_from_into.rs, then this is almost a copy-paste.
|
||||
Otherwise, go ahead and solve try_from_into.rs first."""
|
||||
Otherwise, go ahead and solve try_from_into.rs first."""
|
||||
|
||||
@@ -72,6 +72,7 @@ if (!($LASTEXITCODE -eq 0)) {
|
||||
# but anyone running pwsh 5 will have to pass the argument.
|
||||
$version = Invoke-WebRequest -UseBasicParsing https://api.github.com/repos/rust-lang/rustlings/releases/latest `
|
||||
| ConvertFrom-Json | Select-Object -ExpandProperty tag_name
|
||||
|
||||
Write-Host "Checking out version $version..."
|
||||
Set-Location $path
|
||||
git checkout -q tags/$version
|
||||
@@ -82,4 +83,12 @@ if (!(Get-Command rustlings -ErrorAction SilentlyContinue)) {
|
||||
Write-Host "WARNING: Please check that you have '~/.cargo/bin' in your PATH environment variable!"
|
||||
}
|
||||
|
||||
# Checking whether Clippy is installed.
|
||||
# Due to a bug in Cargo, this must be done with Rustup: https://github.com/rust-lang/rustup/issues/1514
|
||||
$clippy = (rustup component list | Select-String "clippy" | Select-String "installed") | Out-String
|
||||
if (!$clippy) {
|
||||
Write-Host "Installing the 'cargo-clippy' executable..."
|
||||
rustup component add clippy
|
||||
}
|
||||
|
||||
Write-Host "All done! Run 'rustlings' to get started."
|
||||
|
||||
13
install.sh
13
install.sh
@@ -87,6 +87,8 @@ echo "Cloning Rustlings at $Path..."
|
||||
git clone -q https://github.com/rust-lang/rustlings $Path
|
||||
|
||||
Version=$(curl -s https://api.github.com/repos/rust-lang/rustlings/releases/latest | python -c "import json,sys;obj=json.load(sys.stdin);print(obj['tag_name']);")
|
||||
CargoBin="${CARGO_HOME:-$HOME/.cargo}/bin"
|
||||
|
||||
echo "Checking out version $Version..."
|
||||
cd $Path
|
||||
git checkout -q tags/$Version
|
||||
@@ -96,7 +98,16 @@ cargo install --force --path .
|
||||
|
||||
if ! [ -x "$(command -v rustlings)" ]
|
||||
then
|
||||
echo "WARNING: Please check that you have '~/.cargo/bin' in your PATH environment variable!"
|
||||
echo "WARNING: Please check that you have '$CargoBin' in your PATH environment variable!"
|
||||
fi
|
||||
|
||||
# Checking whether Clippy is installed.
|
||||
# Due to a bug in Cargo, this must be done with Rustup: https://github.com/rust-lang/rustup/issues/1514
|
||||
Clippy=$(rustup component list | grep "clippy" | grep "installed")
|
||||
if [ -z "$Clippy" ]
|
||||
then
|
||||
echo "Installing the 'cargo-clippy' executable..."
|
||||
rustup component add clippy
|
||||
fi
|
||||
|
||||
echo "All done! Run 'rustlings' to get started."
|
||||
|
||||
117
src/exercise.rs
117
src/exercise.rs
@@ -1,14 +1,15 @@
|
||||
use regex::Regex;
|
||||
use serde::Deserialize;
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::fs::{remove_file, File};
|
||||
use std::fs::{self, remove_file, File};
|
||||
use std::io::Read;
|
||||
use std::path::PathBuf;
|
||||
use std::process::{self, Command, Output};
|
||||
use std::process::{self, Command};
|
||||
|
||||
const RUSTC_COLOR_ARGS: &[&str] = &["--color", "always"];
|
||||
const I_AM_DONE_REGEX: &str = r"(?m)^\s*///?\s*I\s+AM\s+NOT\s+DONE";
|
||||
const CONTEXT: usize = 2;
|
||||
const CLIPPY_CARGO_TOML_PATH: &str = "./exercises/clippy/Cargo.toml";
|
||||
|
||||
fn temp_file() -> String {
|
||||
format!("./temp_{}", process::id())
|
||||
@@ -19,6 +20,7 @@ fn temp_file() -> String {
|
||||
pub enum Mode {
|
||||
Compile,
|
||||
Test,
|
||||
Clippy,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -47,9 +49,34 @@ pub struct ContextLine {
|
||||
pub important: bool,
|
||||
}
|
||||
|
||||
pub struct CompiledExercise<'a> {
|
||||
exercise: &'a Exercise,
|
||||
_handle: FileHandle,
|
||||
}
|
||||
|
||||
impl<'a> CompiledExercise<'a> {
|
||||
pub fn run(&self) -> Result<ExerciseOutput, ExerciseOutput> {
|
||||
self.exercise.run()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ExerciseOutput {
|
||||
pub stdout: String,
|
||||
pub stderr: String,
|
||||
}
|
||||
|
||||
struct FileHandle;
|
||||
|
||||
impl Drop for FileHandle {
|
||||
fn drop(&mut self) {
|
||||
clean();
|
||||
}
|
||||
}
|
||||
|
||||
impl Exercise {
|
||||
pub fn compile(&self) -> Output {
|
||||
match self.mode {
|
||||
pub fn compile(&self) -> Result<CompiledExercise, ExerciseOutput> {
|
||||
let cmd = match self.mode {
|
||||
Mode::Compile => Command::new("rustc")
|
||||
.args(&[self.path.to_str().unwrap(), "-o", &temp_file()])
|
||||
.args(RUSTC_COLOR_ARGS)
|
||||
@@ -58,18 +85,75 @@ impl Exercise {
|
||||
.args(&["--test", self.path.to_str().unwrap(), "-o", &temp_file()])
|
||||
.args(RUSTC_COLOR_ARGS)
|
||||
.output(),
|
||||
Mode::Clippy => {
|
||||
let cargo_toml = format!(
|
||||
r#"[package]
|
||||
name = "{}"
|
||||
version = "0.0.1"
|
||||
edition = "2018"
|
||||
[[bin]]
|
||||
name = "{}"
|
||||
path = "{}.rs""#,
|
||||
self.name, self.name, self.name
|
||||
);
|
||||
fs::write(CLIPPY_CARGO_TOML_PATH, cargo_toml)
|
||||
.expect("Failed to write 📎 Clippy 📎 Cargo.toml file.");
|
||||
// To support the ability to run the clipy exercises, build
|
||||
// an executable, in addition to running clippy. With a
|
||||
// compilation failure, this would silently fail. But we expect
|
||||
// clippy to reflect the same failure while compiling later.
|
||||
Command::new("rustc")
|
||||
.args(&[self.path.to_str().unwrap(), "-o", &temp_file()])
|
||||
.args(RUSTC_COLOR_ARGS)
|
||||
.output()
|
||||
.expect("Failed to compile!");
|
||||
// Due to an issue with Clippy, a cargo clean is required to catch all lints.
|
||||
// See https://github.com/rust-lang/rust-clippy/issues/2604
|
||||
// This is already fixed on master branch. See this issue to track merging into Cargo:
|
||||
// https://github.com/rust-lang/rust-clippy/issues/3837
|
||||
Command::new("cargo")
|
||||
.args(&["clean", "--manifest-path", CLIPPY_CARGO_TOML_PATH])
|
||||
.args(RUSTC_COLOR_ARGS)
|
||||
.output()
|
||||
.expect("Failed to run 'cargo clean'");
|
||||
Command::new("cargo")
|
||||
.args(&["clippy", "--manifest-path", CLIPPY_CARGO_TOML_PATH])
|
||||
.args(RUSTC_COLOR_ARGS)
|
||||
.args(&["--", "-D", "warnings"])
|
||||
.output()
|
||||
}
|
||||
}
|
||||
.expect("Failed to run 'compile' command.");
|
||||
|
||||
if cmd.status.success() {
|
||||
Ok(CompiledExercise {
|
||||
exercise: &self,
|
||||
_handle: FileHandle,
|
||||
})
|
||||
} else {
|
||||
clean();
|
||||
Err(ExerciseOutput {
|
||||
stdout: String::from_utf8_lossy(&cmd.stdout).to_string(),
|
||||
stderr: String::from_utf8_lossy(&cmd.stderr).to_string(),
|
||||
})
|
||||
}
|
||||
.expect("Failed to run 'compile' command.")
|
||||
}
|
||||
|
||||
pub fn run(&self) -> Output {
|
||||
Command::new(&temp_file())
|
||||
fn run(&self) -> Result<ExerciseOutput, ExerciseOutput> {
|
||||
let cmd = Command::new(&temp_file())
|
||||
.output()
|
||||
.expect("Failed to run 'run' command")
|
||||
}
|
||||
.expect("Failed to run 'run' command");
|
||||
|
||||
pub fn clean(&self) {
|
||||
let _ignored = remove_file(&temp_file());
|
||||
let output = ExerciseOutput {
|
||||
stdout: String::from_utf8_lossy(&cmd.stdout).to_string(),
|
||||
stderr: String::from_utf8_lossy(&cmd.stderr).to_string(),
|
||||
};
|
||||
|
||||
if cmd.status.success() {
|
||||
Ok(output)
|
||||
} else {
|
||||
Err(output)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn state(&self) -> State {
|
||||
@@ -121,6 +205,10 @@ impl Display for Exercise {
|
||||
}
|
||||
}
|
||||
|
||||
fn clean() {
|
||||
let _ignored = remove_file(&temp_file());
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
@@ -131,11 +219,12 @@ mod test {
|
||||
File::create(&temp_file()).unwrap();
|
||||
let exercise = Exercise {
|
||||
name: String::from("example"),
|
||||
path: PathBuf::from("example.rs"),
|
||||
mode: Mode::Test,
|
||||
path: PathBuf::from("tests/fixture/state/pending_exercise.rs"),
|
||||
mode: Mode::Compile,
|
||||
hint: String::from(""),
|
||||
};
|
||||
exercise.clean();
|
||||
let compiled = exercise.compile().unwrap();
|
||||
drop(compiled);
|
||||
assert!(!Path::new(&temp_file()).exists());
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,9 @@ use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
#[macro_use]
|
||||
mod ui;
|
||||
|
||||
mod exercise;
|
||||
mod run;
|
||||
mod verify;
|
||||
|
||||
55
src/run.rs
55
src/run.rs
@@ -1,52 +1,51 @@
|
||||
use crate::exercise::{Exercise, Mode};
|
||||
use crate::verify::test;
|
||||
use console::{style, Emoji};
|
||||
use indicatif::ProgressBar;
|
||||
|
||||
pub fn run(exercise: &Exercise) -> Result<(), ()> {
|
||||
match exercise.mode {
|
||||
Mode::Test => test(exercise)?,
|
||||
Mode::Compile => compile_and_run(exercise)?,
|
||||
Mode::Clippy => compile_and_run(exercise)?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn compile_and_run(exercise: &Exercise) -> Result<(), ()> {
|
||||
fn compile_and_run(exercise: &Exercise) -> Result<(), ()> {
|
||||
let progress_bar = ProgressBar::new_spinner();
|
||||
progress_bar.set_message(format!("Compiling {}...", exercise).as_str());
|
||||
progress_bar.enable_steady_tick(100);
|
||||
|
||||
let compilecmd = exercise.compile();
|
||||
let compilation_result = exercise.compile();
|
||||
let compilation = match compilation_result {
|
||||
Ok(compilation) => compilation,
|
||||
Err(output) => {
|
||||
progress_bar.finish_and_clear();
|
||||
warn!(
|
||||
"Compilation of {} failed!, Compiler error message:\n",
|
||||
exercise
|
||||
);
|
||||
println!("{}", output.stderr);
|
||||
return Err(());
|
||||
}
|
||||
};
|
||||
|
||||
progress_bar.set_message(format!("Running {}...", exercise).as_str());
|
||||
if compilecmd.status.success() {
|
||||
let runcmd = exercise.run();
|
||||
progress_bar.finish_and_clear();
|
||||
let result = compilation.run();
|
||||
progress_bar.finish_and_clear();
|
||||
|
||||
if runcmd.status.success() {
|
||||
println!("{}", String::from_utf8_lossy(&runcmd.stdout));
|
||||
let formatstr = format!("{} Successfully ran {}", Emoji("✅", "✓"), exercise);
|
||||
println!("{}", style(formatstr).green());
|
||||
exercise.clean();
|
||||
match result {
|
||||
Ok(output) => {
|
||||
println!("{}", output.stdout);
|
||||
success!("Successfully ran {}", exercise);
|
||||
Ok(())
|
||||
} else {
|
||||
println!("{}", String::from_utf8_lossy(&runcmd.stdout));
|
||||
println!("{}", String::from_utf8_lossy(&runcmd.stderr));
|
||||
}
|
||||
Err(output) => {
|
||||
println!("{}", output.stdout);
|
||||
println!("{}", output.stderr);
|
||||
|
||||
let formatstr = format!("{} Ran {} with errors", Emoji("⚠️ ", "!"), exercise);
|
||||
println!("{}", style(formatstr).red());
|
||||
exercise.clean();
|
||||
warn!("Ran {} with errors", exercise);
|
||||
Err(())
|
||||
}
|
||||
} else {
|
||||
progress_bar.finish_and_clear();
|
||||
let formatstr = format!(
|
||||
"{} Compilation of {} failed! Compiler error message:\n",
|
||||
Emoji("⚠️ ", "!"),
|
||||
exercise
|
||||
);
|
||||
println!("{}", style(formatstr).red());
|
||||
println!("{}", String::from_utf8_lossy(&compilecmd.stderr));
|
||||
exercise.clean();
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
23
src/ui.rs
Normal file
23
src/ui.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
macro_rules! warn {
|
||||
($fmt:literal, $ex:expr) => {{
|
||||
use console::{style, Emoji};
|
||||
let formatstr = format!($fmt, $ex);
|
||||
println!(
|
||||
"{} {}",
|
||||
style(Emoji("⚠️ ", "!")).red(),
|
||||
style(formatstr).red()
|
||||
);
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! success {
|
||||
($fmt:literal, $ex:expr) => {{
|
||||
use console::{style, Emoji};
|
||||
let formatstr = format!($fmt, $ex);
|
||||
println!(
|
||||
"{} {}",
|
||||
style(Emoji("✅", "✓")).green(),
|
||||
style(formatstr).green()
|
||||
);
|
||||
}};
|
||||
}
|
||||
146
src/verify.rs
146
src/verify.rs
@@ -1,12 +1,13 @@
|
||||
use crate::exercise::{Exercise, Mode, State};
|
||||
use console::{style, Emoji};
|
||||
use crate::exercise::{CompiledExercise, Exercise, Mode, State};
|
||||
use console::style;
|
||||
use indicatif::ProgressBar;
|
||||
|
||||
pub fn verify<'a>(start_at: impl IntoIterator<Item = &'a Exercise>) -> Result<(), &'a Exercise> {
|
||||
for exercise in start_at {
|
||||
let compile_result = match exercise.mode {
|
||||
Mode::Test => compile_and_test_interactively(&exercise),
|
||||
Mode::Compile => compile_only(&exercise),
|
||||
Mode::Test => compile_and_test(&exercise, RunMode::Interactive),
|
||||
Mode::Compile => compile_and_run_interactively(&exercise),
|
||||
Mode::Clippy => compile_only(&exercise),
|
||||
};
|
||||
if !compile_result.unwrap_or(false) {
|
||||
return Err(exercise);
|
||||
@@ -15,8 +16,13 @@ pub fn verify<'a>(start_at: impl IntoIterator<Item = &'a Exercise>) -> Result<()
|
||||
Ok(())
|
||||
}
|
||||
|
||||
enum RunMode {
|
||||
Interactive,
|
||||
NonInteractive,
|
||||
}
|
||||
|
||||
pub fn test(exercise: &Exercise) -> Result<(), ()> {
|
||||
compile_and_test(exercise, true)?;
|
||||
compile_and_test(exercise, RunMode::NonInteractive)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -24,73 +30,89 @@ fn compile_only(exercise: &Exercise) -> Result<bool, ()> {
|
||||
let progress_bar = ProgressBar::new_spinner();
|
||||
progress_bar.set_message(format!("Compiling {}...", exercise).as_str());
|
||||
progress_bar.enable_steady_tick(100);
|
||||
let compile_output = exercise.compile();
|
||||
|
||||
let _ = compile(&exercise, &progress_bar)?;
|
||||
progress_bar.finish_and_clear();
|
||||
if compile_output.status.success() {
|
||||
let formatstr = format!("{} Successfully compiled {}!", Emoji("✅", "✓"), exercise);
|
||||
println!("{}", style(formatstr).green());
|
||||
exercise.clean();
|
||||
Ok(prompt_for_completion(&exercise))
|
||||
} else {
|
||||
let formatstr = format!(
|
||||
"{} Compilation of {} failed! Compiler error message:\n",
|
||||
Emoji("⚠️ ", "!"),
|
||||
exercise
|
||||
);
|
||||
println!("{}", style(formatstr).red());
|
||||
println!("{}", String::from_utf8_lossy(&compile_output.stderr));
|
||||
exercise.clean();
|
||||
Err(())
|
||||
}
|
||||
|
||||
success!("Successfully compiled {}!", exercise);
|
||||
Ok(prompt_for_completion(&exercise, None))
|
||||
}
|
||||
|
||||
fn compile_and_test_interactively(exercise: &Exercise) -> Result<bool, ()> {
|
||||
compile_and_test(exercise, false)
|
||||
fn compile_and_run_interactively(exercise: &Exercise) -> Result<bool, ()> {
|
||||
let progress_bar = ProgressBar::new_spinner();
|
||||
progress_bar.set_message(format!("Compiling {}...", exercise).as_str());
|
||||
progress_bar.enable_steady_tick(100);
|
||||
|
||||
let compilation = compile(&exercise, &progress_bar)?;
|
||||
|
||||
progress_bar.set_message(format!("Running {}...", exercise).as_str());
|
||||
let result = compilation.run();
|
||||
progress_bar.finish_and_clear();
|
||||
|
||||
let output = match result {
|
||||
Ok(output) => output,
|
||||
Err(output) => {
|
||||
warn!("Ran {} with errors", exercise);
|
||||
println!("{}", output.stdout);
|
||||
return Err(());
|
||||
}
|
||||
};
|
||||
|
||||
success!("Successfully ran {}!", exercise);
|
||||
|
||||
Ok(prompt_for_completion(&exercise, Some(output.stdout)))
|
||||
}
|
||||
|
||||
fn compile_and_test(exercise: &Exercise, skip_prompt: bool) -> Result<bool, ()> {
|
||||
fn compile_and_test(exercise: &Exercise, run_mode: RunMode) -> Result<bool, ()> {
|
||||
let progress_bar = ProgressBar::new_spinner();
|
||||
progress_bar.set_message(format!("Testing {}...", exercise).as_str());
|
||||
progress_bar.enable_steady_tick(100);
|
||||
|
||||
let compile_output = exercise.compile();
|
||||
if compile_output.status.success() {
|
||||
progress_bar.set_message(format!("Running {}...", exercise).as_str());
|
||||
let compilation = compile(exercise, &progress_bar)?;
|
||||
let result = compilation.run();
|
||||
progress_bar.finish_and_clear();
|
||||
|
||||
let runcmd = exercise.run();
|
||||
progress_bar.finish_and_clear();
|
||||
|
||||
if runcmd.status.success() {
|
||||
let formatstr = format!("{} Successfully tested {}!", Emoji("✅", "✓"), exercise);
|
||||
println!("{}", style(formatstr).green());
|
||||
exercise.clean();
|
||||
Ok(skip_prompt || prompt_for_completion(exercise))
|
||||
} else {
|
||||
let formatstr = format!(
|
||||
"{} Testing of {} failed! Please try again. Here's the output:",
|
||||
Emoji("⚠️ ", "!"),
|
||||
match result {
|
||||
Ok(_) => {
|
||||
success!("Successfully tested {}", &exercise);
|
||||
if let RunMode::Interactive = run_mode {
|
||||
Ok(prompt_for_completion(&exercise, None))
|
||||
} else {
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
Err(output) => {
|
||||
warn!(
|
||||
"Testing of {} failed! Please try again. Here's the output:",
|
||||
exercise
|
||||
);
|
||||
println!("{}", style(formatstr).red());
|
||||
println!("{}", String::from_utf8_lossy(&runcmd.stdout));
|
||||
exercise.clean();
|
||||
println!("{}", output.stdout);
|
||||
Err(())
|
||||
}
|
||||
} else {
|
||||
progress_bar.finish_and_clear();
|
||||
let formatstr = format!(
|
||||
"{} Compiling of {} failed! Please try again. Here's the output:",
|
||||
Emoji("⚠️ ", "!"),
|
||||
exercise
|
||||
);
|
||||
println!("{}", style(formatstr).red());
|
||||
println!("{}", String::from_utf8_lossy(&compile_output.stderr));
|
||||
exercise.clean();
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
fn prompt_for_completion(exercise: &Exercise) -> bool {
|
||||
fn compile<'a, 'b>(
|
||||
exercise: &'a Exercise,
|
||||
progress_bar: &'b ProgressBar,
|
||||
) -> Result<CompiledExercise<'a>, ()> {
|
||||
let compilation_result = exercise.compile();
|
||||
|
||||
match compilation_result {
|
||||
Ok(compilation) => Ok(compilation),
|
||||
Err(output) => {
|
||||
progress_bar.finish_and_clear();
|
||||
warn!(
|
||||
"Compiling of {} failed! Please try again. Here's the output:",
|
||||
exercise
|
||||
);
|
||||
println!("{}", output.stderr);
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn prompt_for_completion(exercise: &Exercise, prompt_output: Option<String>) -> bool {
|
||||
let context = match exercise.state() {
|
||||
State::Done => return true,
|
||||
State::Pending(context) => context,
|
||||
@@ -99,11 +121,21 @@ fn prompt_for_completion(exercise: &Exercise) -> bool {
|
||||
let success_msg = match exercise.mode {
|
||||
Mode::Compile => "The code is compiling!",
|
||||
Mode::Test => "The code is compiling, and the tests pass!",
|
||||
Mode::Clippy => "The code is compiling, and 📎 Clippy 📎 is happy!",
|
||||
};
|
||||
|
||||
println!("");
|
||||
println!("🎉 🎉 {} 🎉 🎉", success_msg);
|
||||
println!("");
|
||||
|
||||
if let Some(output) = prompt_output {
|
||||
println!("Output:");
|
||||
println!("{}", separator());
|
||||
println!("{}", output);
|
||||
println!("{}", separator());
|
||||
println!("");
|
||||
}
|
||||
|
||||
println!("You can keep working on this exercise,");
|
||||
println!(
|
||||
"or jump into the next one by removing the {} comment:",
|
||||
@@ -127,3 +159,7 @@ fn prompt_for_completion(exercise: &Exercise) -> bool {
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn separator() -> console::StyledObject<&'static str> {
|
||||
style("====================").bold()
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ fn verify_all_success() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_all_failure() {
|
||||
fn verify_fails_if_some_fails() {
|
||||
Command::cargo_bin("rustlings")
|
||||
.unwrap()
|
||||
.arg("v")
|
||||
|
||||
Reference in New Issue
Block a user