1
0
mirror of https://github.com/rust-lang/rustlings.git synced 2025-12-26 00:11:49 +02:00

Compare commits

..

38 Commits
2.2.0 ... 3.0.0

Author SHA1 Message Date
mokou
2ff3f7ae13 3.0.0 2020-04-12 00:01:08 +02:00
fmoko
2b80f6ed41 chore: Remove duplicate option1 exercise 2020-04-11 17:27:10 +02:00
Rohan Jain
3ab084a421 fix(run): compile clippy exercise files
Additionally to running clippy, also compile the exercise file so that
`rustling run clippy1` works after a successful completion of the
exercise.

closes #291

Signed-off-by: Rohan Jain <crodjer@gmail.com>
2020-04-11 08:58:23 +02:00
mokou
495174ff7c chore: remove travis ci
And add a buildkite build status badge
2020-04-08 12:27:28 +02:00
mokou
b049fa2c84 feat(ci): add buildkite config 2020-04-08 12:08:37 +02:00
fmoko
052f0aa7d4 Merge pull request #292 from Tuxified/patch-1
Remove duplicate not done comment
2020-04-08 11:23:49 +02:00
Tonći Galić
60393999b8 remove bottom comment instead of top 2020-04-08 11:00:11 +02:00
Roberto Vidal
b3a3351e8e fix: revert primitive_types4 (#296) 2020-04-08 10:42:35 +02:00
Sanjay K
86b5c08b9b feat: Add Option2 exercise (#290)
* added option2

* changed up the exercise, modified the help section

* Update exercises/option/option2.rs

Co-Authored-By: fmoko <mokou@posteo.net>

* Update exercises/option/option2.rs

Co-Authored-By: fmoko <mokou@posteo.net>

* Update exercises/option/option2.rs

Co-Authored-By: fmoko <mokou@posteo.net>

Co-authored-by: fmoko <mokou@posteo.net>
2020-04-07 20:16:10 +02:00
Tonći Galić
dab90f7b91 Remove duplicate not done comment
As indicated in #259 , I found it confusing to have 2 comments as the code wouldn't compile unless I solved both issues (I used the script from #281 to remove a comment and use `:wn` to go to next exercise, hence this tripped me).
2020-04-07 17:00:45 +02:00
fmoko
71d31256a6 Merge pull request #278 from jrvidal/output-mode
feature: adds "output" mode, resolves #270
2020-04-07 11:26:48 +02:00
Roberto Vidal
3b6d5c3aaa feature: makes "compile" exercise print output, resolves #270
When running "compile"-mode exercises in interactive `verify` mode,
we print their output when we prompt the learner if they want to
continue. This improves the "experimentation" experience, since
trying different things does produce a visible change.
2020-04-06 16:17:14 +02:00
fmoko
7ce42941ea Merge pull request #282 from sanjaykdragon/master
feat: added option exercise
2020-04-05 15:58:09 +02:00
Sanjay K
3f8171475c updated info.toml 2020-04-05 09:45:07 -04:00
fmoko
b135b589e0 Merge pull request #280 from sjmann/generics-exercises
feat: added generics exercises
2020-04-05 14:40:34 +02:00
Sanjay K
05ca3d77f7 Merge branch 'master' of https://github.com/sanjaykdragon/rustlings 2020-04-02 08:41:14 -04:00
Sanjay K
6deee7e3e9 fixed spacing 2020-04-02 08:40:59 -04:00
fmoko
9dc404077a Merge pull request #288 from vjousse/fix-traits-doc
chore(TRAITS1): don't hardcode documentation version
2020-03-26 21:16:52 +01:00
Vincent Jousse
30e6af6069 Don't hardcode documentation version for traits 2020-03-26 15:22:22 +01:00
Sanjay K
9788496a85 Update option1.rs 2020-03-11 13:44:41 -04:00
Sanjay K
6d3a412d47 Update option1.rs 2020-03-11 13:44:10 -04:00
bors
700b236f4d Auto merge of #284 - sl4m:update-from-into-ex, r=fmoko
Adds additional test to meet exercise rules

This PR adds an additional test to meet this exercise [rule](https://github.com/rust-lang/rustlings/blob/master/exercises/conversions/from_into.rs#L33).
2020-03-11 15:06:23 +00:00
skim
bc22ec382f adds additional test to meet exercise rules 2020-03-11 07:58:04 -07:00
Sanjay K
135e5d47a7 feat: added excercise for option 2020-03-05 15:52:54 -05:00
sjmann
0f8001ea44 add I AM NOT DONE comments 2020-02-28 00:31:55 +00:00
sjmann
5b6e23c323 removed artifact from manual testing 2020-02-28 00:29:30 +00:00
sjmann
29b30ec946 Merge branch 'master' of https://github.com/sjmann/rustlings into generics-exercises 2020-02-28 00:19:45 +00:00
sjmann
76be5e4e99 feat: added new exercises for generics 2020-02-28 00:09:08 +00:00
mokou
8b9479071c 2.2.1 2020-02-27 19:22:55 +01:00
mokou
3d9b03c52b fix: Re-add cloning the repo to install scripts 2020-02-27 19:19:31 +01:00
bors
a03d9655a8 Auto merge of #269 - Tarnadas:master, r=fmoko
feat: Add clippy lints

This is a feature PR which adds the possiblity to create clippy exercises.

Clippy has many awesome linting rules, which can give a deeper understanding about the Rust programming language, therefor I made this PR.
2020-02-26 14:22:51 +00:00
Mario Reder
1e2fd9c92f feat: Add clippy lints
- adds a new 'clippy' category for exercises
- clippy exercises should throw no warnings
- install script now also installs clippy

is related to https://github.com/rust-lang/rust-clippy/issues/2604
2020-02-26 14:07:07 +01:00
bors
7e8530b21f Auto merge of #271 - jrvidal:refactor, r=fmoko
refactor: exercise evaluation

After working a bit on #270, I realized that it'd be useful to first perform a minor refactor of exercise evaluation.

* Now we have standard methods to compile + execute that return `Result`s.
* Success/failure messages are standardized.
2020-02-26 11:48:01 +00:00
bors
98358597a9 Auto merge of #277 - sjmann:update-ignore, r=fmoko
chore: update gitignore to ignore pdb files

Pr for issue #275
2020-02-26 10:46:53 +00:00
sjmann
8064facbb8 chore: update gitignore to ignore pdb files 2020-02-26 10:43:13 +00:00
sjmann
f981dcfde4 Merge branch 'master' of https://github.com/sjmann/rustlings 2020-02-26 10:38:50 +00:00
sjmann
bbf8922ef7 Merge remote-tracking branch 'upstream/master' 2020-02-25 10:00:38 +00:00
Roberto Vidal
43dc31193a refactor: exercise evaluation
Exercise evaluation (compilation + execution) now uses Results
Success/failure messages are standardized
2020-02-20 20:27:05 +01:00
28 changed files with 584 additions and 125 deletions

3
.gitignore vendored
View File

@@ -2,3 +2,6 @@
target/
**/*.rs.bk
.DS_Store
*.pdb
exercises/clippy/Cargo.toml
exercises/clippy/Cargo.lock

View File

@@ -1,11 +0,0 @@
language: rust
rust:
- stable
- beta
- nightly
script: cargo test --verbose
matrix:
allow_failures:
- rust: nightly
fast_finish: true
cache: cargo

View File

@@ -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
View File

@@ -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)",

View File

@@ -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]

View File

@@ -1,6 +1,6 @@
![crab pet](https://i.imgur.com/LbZJgmm.gif)
# rustlings 🦀❤️
# rustlings 🦀❤️ [![Build status](https://badge.buildkite.com/7af93d81dc522c67a1ec8e33ff5705861b1cb36360b774807f.svg)](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
View 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

View 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/).

View 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!");
}
}

View 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);
}

View File

@@ -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 {

View File

@@ -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);
}
}

View 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)

View 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");
}

View 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");
}
}

View 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+");
}
}

View 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)

View 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;
}
}

View 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);
}
}

View File

@@ -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."""

View File

@@ -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."

View File

@@ -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."

View File

@@ -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());
}

View File

@@ -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;

View File

@@ -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
View 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()
);
}};
}

View File

@@ -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()
}

View File

@@ -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")