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

Compare commits

...

52 Commits
1.1.0 ... 1.3.0

Author SHA1 Message Date
liv
7fca1d28af 1.3.0 2019-06-05 12:37:02 +02:00
bors
9c3d765403 Auto merge of #167 - rust-lang:fix/remove-highlighting, r=komaeda
Remove highlighting and syntect

Closes #166 and #122
2019-06-05 10:23:37 +00:00
liv
bb652ceb91 Commit Cargo.lock file 2019-06-05 12:22:41 +02:00
liv
62696f5819 Remove highlighting and syntect 2019-06-05 12:18:50 +02:00
bors
41170ce341 Auto merge of #164 - HanKruiger:master, r=komaeda
Fix broken link
2019-05-26 14:07:16 +00:00
bors
e2092a4ddd Auto merge of #165 - gushroom:fix-outdated-links, r=komaeda
fixed outdated links

The links had the 2018 book in them and they were outdated.
2019-05-26 13:58:24 +00:00
gushroom
fd1441d122 fixed outdated links 2019-05-26 14:57:56 +02:00
HanKruiger
e1422c6443 Fix broken link 2019-05-26 14:45:04 +02:00
bors
1f07fd4150 Auto merge of #163 - briankung:add_structs, r=komaeda
Adds a simple exercise for structures

Thanks for rustlings! Here's a small contribution in return.
2019-05-25 13:40:15 +00:00
Brian Kung
9b92aa08ae Adds a simple exercise for structures 2019-05-25 06:39:58 -05:00
bors
8bf8cbbd61 Auto merge of #162 - c-rustacean:rustfmt-and-ws-fixes, r=komaeda
errorsn.rs: Separate also the hints from each other to avoid accidental viewing
2019-05-23 11:01:15 +00:00
Eddy Petrisor
f507844102 errorsn.rs: Separate also the hints from each other to avoid accidental viewing
Signed-off-by: Eddy Petrisor <eddy.petrisor@gmail.com>
2019-05-23 12:21:42 +03:00
bors
fffbb60ed9 Auto merge of #161 - c-rustacean:rustfmt-and-ws-fixes, r=komaeda
Rustfmt and ws fixes
2019-05-22 12:29:20 +00:00
Eddy Petrisor
9aec4abc4d rustfmt the exercises
Signed-off-by: Eddy Petrisor <eddy.petrisor@gmail.com>
2019-05-22 14:50:40 +03:00
Eddy Petrisor
a53b3f199f iterator3.rs: whitespace fixes
Signed-off-by: Eddy Petrisor <eddy.petrisor@gmail.com>
2019-05-22 14:50:40 +03:00
Eddy Petrisor
d6d696b66a errorsn.rs: Separate hints from code, so hints are not accidentally seen
Signed-off-by: Eddy Petrisor <eddy.petrisor@gmail.com>
2019-05-22 14:50:39 +03:00
Eddy Petrisor
ca6bf966dd Cargo fmt the rustlings application code
Signed-off-by: Eddy Petrisor <eddy.petrisor@gmail.com>
2019-05-22 14:50:23 +03:00
liv
5a9f8860ca Add not passing integration test (#154)
Add not passing integration test
2019-05-22 10:53:46 +02:00
liv
5423bc66a9 Update errors1.rs - Add Result type signature as it is difficult for new comers to understand Generics and Error all at once. (#157)
Update errors1.rs - Add Result type signature as it is difficult for new comers to understand Generics and Error all at once.
2019-05-22 10:53:18 +02:00
Julien Bisconti
187d2ad226 Update errors1.rs
Add Result type signature as it is difficult for new comers to understand Generics and Error all at once
2019-05-12 14:54:37 +02:00
Denys Smirnov
7cf0d5d15e Add not passing integration test 2019-05-09 20:17:38 +03:00
liv
1f2ee8cb62 1.2.2 2019-05-07 12:32:50 +02:00
liv
35c3d0b3fc Revert --nocapture flag
This closes #149 and #152
2019-05-07 12:31:02 +02:00
liv
0279294972 1.2.1 2019-04-22 19:12:30 +08:00
liv
7eddee6f7a add a slightly more helpful error message 2019-04-22 18:43:39 +08:00
liv
f2c48cfac5 fix the --nocapture functionality 2019-04-22 18:42:32 +08:00
liv
6ae0a00211 1.2.0 2019-04-22 13:09:28 +08:00
liv
bfcf38c8bc damn it 2019-04-22 07:05:46 +02:00
liv
9e328da641 use -- --nocapture when testing 2019-04-22 05:49:23 +02:00
bors
e336d04c79 Auto merge of #144 - yvan-sraka:patch-0, r=komaeda
Add errors to exercises that compile without user changes

Hi !

I played a bit with rustlings, and I felt that some exercises were incorrect because they passed the tests without me needing to edit the files!

This gave me the feeling that the exercise was skiped! Especially when I use `rustlings watch`, it is easy to miss an exercise because the compilation error that is displayed is the one of the next exercise ...

It is easy to identify "broken" exercises with:

```bash
% find exercises -name "*.rs" | xargs -n 1 rustlings run
...
 Successfully ran exercises/move_semantics/move_semantics4.rs
 Successfully tested exercises/test2.rs
```

My suggestion is to make sure that these files trigger a compilation error by adding a simple syntax error (e.g. with `???` in the code that must change) so that our Rustacean can then play with it!
2019-04-22 01:50:48 +00:00
Yvan Sraka
a71bc62c29 Add errors to exercises that compile without user changes 2019-04-22 00:09:30 +02:00
liv
4b0b7093e5 1.1.1 2019-04-14 18:29:32 +02:00
bors
8387de64d3 Auto merge of #143 - cjpearce:fix-exercise-path-matching, r=komaeda
Canonicalize paths to fix path matching

This PR should fix #126. The main solution to the issue was using `canonicalize()` on the paths we create for the exercises from `info.toml` and any user-specified paths, so that path `ends_with` matching will work correctly.

As adding calls to the canonicalize function everywhere requires unwrapping, I also decided to extract a struct representing an exercise and use serde to deserialize the paths from the `info.toml` file up front. I also tried to move the path handling out into the `exercise.rs` file and down into `main.rs` so that it doesn't create as much clutter. There was already a lot of unwrapping and path handling in the other files and I felt like it was getting a bit too repetitive.

If the approach is going too far (too many changes etc.) I'm happy to try to produce a smaller PR that fixes the bug without any refactoring.
2019-04-13 16:32:02 +00:00
Chris Pearce
77de6e5d6a Clean up test includes for File and Path 2019-04-12 23:14:15 +01:00
Chris Pearce
8c867a001a Remove unwrap on canonicalize result 2019-04-12 22:24:13 +01:00
Chris Pearce
d01a71f7de Extract exercise struct to encapsulate path logic 2019-04-12 08:58:25 +01:00
bors
04d1d4c00e Auto merge of #142 - diodfr:patch-1, r=komaeda
Fix links by deleting book version
2019-04-08 20:05:34 +00:00
Diod FR
d7e58ee1af Fix links by deleting book version 2019-04-08 22:02:04 +02:00
bors
ffb165ce26 Auto merge of #140 - cjpearce:fix/test-race-condition, r=komaeda
Fix intermittent test failure caused by race condition

First public pull request 😬

There's an intermittent integration test failure when you use multiple test threads (at least for me on a mac). I narrowed it down to two tests each spawning a process using `Command` which then try to compile the same file at the same time. If the timing doesn't work out, they both try to compile, and then one process runs `clean` before the other can run the executable - causing a panic.

![Screenshot 2019-04-07 at 19 54 55](https://user-images.githubusercontent.com/3453268/55688324-20520980-596f-11e9-8474-5215d61a4387.png)

You can prevent it from happening by running with a single thread (`cargo test -- --test-threads=1`), because the `Command` blocks. That's not a particularly good solution though because it's not something you can configure in `Cargo.toml`.

I considered making the affected tests just run serially, but it occurred to me that this could also happen if someone accidentally runs rustlings in watch mode in two terminals without realising it. I wound't consider this that unlikely given it's a tool for learning.

I fixed it by ensuring that the executables made from separate processes don't conflict by appending a process id to the output executable name. I also extracted the commands into a single file next to `clean` so that we don't have to repeat the generated file name everywhere and risk missing something.
2019-04-07 22:37:34 +00:00
Chris Pearce
65cb09eb2e Update ci test command to allow multithreaded tests 2019-04-07 21:23:02 +01:00
bors
78552ebd7a Auto merge of #141 - cjpearce:fix/run-panics-on-compile-fail, r=komaeda
Stop run from panicking when compile fails

Currently if you use the `rustlings run` command and your program fails to compile, rustlings will panic while trying to exit.

First I've added a couple of integration tests to cover this case, which also meant moving a few tests so that the new fixtures didn't cause `verify_all_success` to fail.

Then I noticed that the existing integration tests that test for failure pass even when rustlings panics, preventing the new tests from failing. I've updated the integration tests to distinguish between when rustlings has failed in the way that we want (exit code 1) rather than a panic (exit code 101).

Finally I fixed the actual panic, which was just caused by unwrapping when rustlings should probably be exiting cleanly.
2019-04-07 20:11:22 +00:00
Chris Pearce
0c7bd12372 Fix test failing due to panic 2019-04-07 20:13:04 +01:00
Chris Pearce
3d11d7685b Modify integration tests to fail on panic 2019-04-07 20:13:04 +01:00
Chris Pearce
592ae6b4d2 Add process id to temp file name 2019-04-07 17:28:51 +01:00
Chris Pearce
4fa79ee02f Extract command builders into util 2019-04-07 17:26:01 +01:00
bors
fbd0ccbd5b Auto merge of #134 - rust-lang:fix/windows-paths, r=komaeda
fix watch command path execution

@hades32 @guttume could you test whether this works on windows by checking out the branch locally and running `cargo run watch`?
2019-04-03 09:37:37 +00:00
komaeda
8c008a0e7d Merge pull request #137 from mgeier/patch-1
Fix order of true/false in tests for executables
2019-03-28 12:11:35 +01:00
Matthias Geier
11fe19d08a Fix order of true/false in tests for executables
1b3469f236 has fixed the tests themselves, but now the original error shows itself.
2019-03-28 11:53:29 +01:00
liv
1b3469f236 make installation command checks more thorough 2019-03-28 10:51:54 +01:00
liv
022921168d fix watch command path execution 2019-03-27 10:58:56 +01:00
komaeda
c6765eb3eb Merge pull request #133 from zacanger/bug/permissions
Fix permissions on exercise files
2019-03-24 15:44:34 +01:00
zacanger
c5a374fbf2 Fix permissions on source files 2019-03-23 14:19:42 -06:00
70 changed files with 1596 additions and 176 deletions

1
.gitignore vendored
View File

@@ -1,5 +1,4 @@
*.swp
target/
**/*.rs.bk
Cargo.lock
.DS_Store

View File

@@ -3,7 +3,7 @@ rust:
- stable
- beta
- nightly
script: cargo test --verbose -- --test-threads=1
script: cargo test --verbose
matrix:
allow_failures:
- rust: nightly

1152
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package]
name = "rustlings"
version = "1.1.0"
version = "1.3.0"
authors = ["Olivia <819880950@qq.com>", "Carol (Nichols || Goulding) <carol.nichols@gmail.com"]
edition = "2018"
@@ -8,9 +8,9 @@ edition = "2018"
clap = "2.32.0"
indicatif = "0.9.0"
console = "0.6.2"
syntect = "3.0.2"
notify = "4.0.0"
toml = "0.4.10"
serde = {version = "1.0.10", features = ["derive"]}
[[bin]]
name = "rustlings"

View File

@@ -1,6 +1,6 @@
Thanks for installing `rustlings`!
Thanks for installing Rustlings!
## Is this your first time?
Is this your first time?
Let's make sure you're up to speed:
- You have Rust installed, preferably via `rustup`
@@ -9,9 +9,7 @@ Let's make sure you're up to speed:
- You have installed Rust language support for your editor
- You have locally installed the `rustlings` command by running:
```sh
cargo install --path .
```
If you've done all of this (or even most of it), congrats! You're ready
to start working with Rust.

View File

@@ -1,5 +1,5 @@
For this exercise check out the sections:
- [Error Handling](https://doc.rust-lang.org/book/2018-edition/ch09-02-recoverable-errors-with-result.html)
- [Generics](https://doc.rust-lang.org/book/2018-edition/ch10-01-syntax.html)
- [Error Handling](https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html)
- [Generics](https://doc.rust-lang.org/book/ch10-01-syntax.html)
of the Rust Book.
of the Rust Book.

2
exercises/error_handling/errors1.rs Executable file → Normal file
View File

@@ -63,7 +63,7 @@ mod tests {
// `Option`.
// To make this change, you'll need to:
// - update the return type in the function signature to be a Result that
// - update the return type in the function signature to be a Result<String, String> that
// could be the variants `Ok(String)` and `Err(String)`
// - change the body of the function to return `Ok(stuff)` where it currently
// returns `Some(stuff)`

5
exercises/error_handling/errors2.rs Executable file → Normal file
View File

@@ -32,10 +32,7 @@ mod tests {
#[test]
fn item_quantity_is_a_valid_number() {
assert_eq!(
total_cost("34"),
Ok(171)
);
assert_eq!(total_cost("34"), Ok(171));
}
#[test]

0
exercises/error_handling/errors3.rs Executable file → Normal file
View File

115
exercises/error_handling/errorsn.rs Executable file → Normal file
View File

@@ -65,7 +65,7 @@ fn test_ioerror() {
assert_eq!("uh-oh!", read_and_validate(&mut b).unwrap_err().to_string());
}
#[derive(PartialEq,Debug)]
#[derive(PartialEq, Debug)]
struct PositiveNonzeroInteger(u64);
impl PositiveNonzeroInteger {
@@ -83,11 +83,14 @@ impl PositiveNonzeroInteger {
#[test]
fn test_positive_nonzero_integer_creation() {
assert!(PositiveNonzeroInteger::new(10).is_ok());
assert_eq!(Err(CreationError::Negative), PositiveNonzeroInteger::new(-10));
assert_eq!(
Err(CreationError::Negative),
PositiveNonzeroInteger::new(-10)
);
assert_eq!(Err(CreationError::Zero), PositiveNonzeroInteger::new(0));
}
#[derive(PartialEq,Debug)]
#[derive(PartialEq, Debug)]
enum CreationError {
Negative,
Zero,
@@ -108,16 +111,84 @@ impl error::Error for CreationError {
}
}
// First hint: To figure out what type should go where the ??? is, take a look
// at the test helper function `test_with_str`, since it returns whatever
// `read_and_validate` returns and`test_with_str` has its signature fully
// specified.
// Next hint: There are three places in `read_and_validate` that we call a
// function that returns a `Result` (that is, the functions might fail).
// Apply the `?` operator on those calls so that we return immediately from
// `read_and_validate` if those function calls fail.
// Another hint: under the hood, the `?` operator calls `From::from`
// on the error value to convert it to a boxed trait object, a Box<error::Error>,
// which is polymorphic-- that means that lots of different kinds of errors
@@ -126,12 +197,50 @@ impl error::Error for CreationError {
// Check out this section of the book:
// https://doc.rust-lang.org/stable/book/second-edition/ch09-02-recoverable-errors-with-result.html#a-shortcut-for-propagating-errors-the--operator
// Another another hint: Note that because the `?` operator returns
// the *unwrapped* value in the `Ok` case, if we want to return a `Result` from
// `read_and_validate` for *its* success case, we'll have to rewrap a value
// that we got from the return value of a `?`ed call in an `Ok`-- this will
// look like `Ok(something)`.
// Another another another hint: `Result`s must be "used", that is, you'll
// get a warning if you don't handle a `Result` that you get in your
// function. Read more about that in the `std::result` module docs:

5
exercises/error_handling/option1.rs Executable file → Normal file
View File

@@ -11,7 +11,10 @@ fn main() {
println!("The last item in the list is {:?}", last);
let second_to_last = list.pop().unwrap();
println!("The second-to-last item in the list is {:?}", second_to_last);
println!(
"The second-to-last item in the list is {:?}",
second_to_last
);
}

9
exercises/error_handling/result1.rs Executable file → Normal file
View File

@@ -1,10 +1,10 @@
// result1.rs
// Make this test pass! Scroll down for hints :)
#[derive(PartialEq,Debug)]
#[derive(PartialEq, Debug)]
struct PositiveNonzeroInteger(u64);
#[derive(PartialEq,Debug)]
#[derive(PartialEq, Debug)]
enum CreationError {
Negative,
Zero,
@@ -19,7 +19,10 @@ impl PositiveNonzeroInteger {
#[test]
fn test_creation() {
assert!(PositiveNonzeroInteger::new(10).is_ok());
assert_eq!(Err(CreationError::Negative), PositiveNonzeroInteger::new(-10));
assert_eq!(
Err(CreationError::Negative),
PositiveNonzeroInteger::new(-10)
);
assert_eq!(Err(CreationError::Zero), PositiveNonzeroInteger::new(0));
}

0
exercises/functions/functions1.rs Executable file → Normal file
View File

0
exercises/functions/functions2.rs Executable file → Normal file
View File

0
exercises/functions/functions3.rs Executable file → Normal file
View File

0
exercises/functions/functions4.rs Executable file → Normal file
View File

0
exercises/functions/functions5.rs Executable file → Normal file
View File

0
exercises/if/if1.rs Executable file → Normal file
View File

0
exercises/macros/macros1.rs Executable file → Normal file
View File

0
exercises/macros/macros2.rs Executable file → Normal file
View File

0
exercises/macros/macros3.rs Executable file → Normal file
View File

0
exercises/macros/macros4.rs Executable file → Normal file
View File

View File

@@ -4,4 +4,4 @@ In this section we'll give you an introduction to Rust's module system.
#### Book Sections
- [The Module System](https://doc.rust-lang.org/stable/book/ch07-02-modules-and-use-to-control-scope-and-privacy.html)
- [The Module System](https://doc.rust-lang.org/stable/book/ch07-02-defining-modules-to-control-scope-and-privacy.html)

0
exercises/modules/modules1.rs Executable file → Normal file
View File

0
exercises/modules/modules2.rs Executable file → Normal file
View File

0
exercises/move_semantics/move_semantics1.rs Executable file → Normal file
View File

0
exercises/move_semantics/move_semantics2.rs Executable file → Normal file
View File

0
exercises/move_semantics/move_semantics3.rs Executable file → Normal file
View File

3
exercises/move_semantics/move_semantics4.rs Executable file → Normal file
View File

@@ -16,7 +16,8 @@ fn main() {
}
fn fill_vec(vec: Vec<i32>) -> Vec<i32> {
// `fill_vec()` no longer take `vec: Vec<i32>` as argument
fn fill_vec() -> Vec<i32> {
let mut vec = vec;
vec.push(22);

0
exercises/primitive_types/primitive_types1.rs Executable file → Normal file
View File

0
exercises/primitive_types/primitive_types2.rs Executable file → Normal file
View File

0
exercises/primitive_types/primitive_types3.rs Executable file → Normal file
View File

0
exercises/primitive_types/primitive_types4.rs Executable file → Normal file
View File

0
exercises/primitive_types/primitive_types5.rs Executable file → Normal file
View File

0
exercises/primitive_types/primitive_types6.rs Executable file → Normal file
View File

View File

@@ -1,5 +1,5 @@
For the Arc exercise check out the chapter [Shared-State Concurrency](https://doc.rust-lang.org/book/2018-edition/ch16-03-shared-state.html) of the Rust Book.
For the Arc exercise check out the chapter [Shared-State Concurrency](https://doc.rust-lang.org/book/ch16-03-shared-state.html) of the Rust Book.
For the Iterator exercise check out the chapters [Iterator](https://doc.rust-lang.org/book/2018-edition/ch13-02-iterators.html) of the Rust Book and the [Iterator documentation](https://doc.rust-lang.org/stable/std/iter/trait.Iterator.htmlj).
For the Iterator exercise check out the chapters [Iterator](https://doc.rust-lang.org/book/ch13-02-iterators.html) of the Rust Book and the [Iterator documentation](https://doc.rust-lang.org/stable/std/iter/).
Do not adjust your monitors-- iterators 1 and 2 are indeed missing. Iterator 3 is a bit challenging so we're leaving space for some exercises to lead up to it!

0
exercises/standard_library_types/arc1.rs Executable file → Normal file
View File

4
exercises/standard_library_types/iterator3.rs Executable file → Normal file
View File

@@ -114,7 +114,7 @@ mod tests {
// Minor hint: In each of the two cases in the match in main, you can create x with either
// Minor hint: In each of the two cases in the match in main, you can create x with either
// a 'turbofish' or by hinting the type of x to the compiler. You may try both.
@@ -143,5 +143,5 @@ mod tests {
// Major hint: Have a look at the Iter trait and at the explanation of its collect function.
// Major hint: Have a look at the Iter trait and at the explanation of its collect function.
// Especially the part about Result is interesting.

0
exercises/standard_library_types/iterators4.rs Executable file → Normal file
View File

0
exercises/strings/strings1.rs Executable file → Normal file
View File

0
exercises/strings/strings2.rs Executable file → Normal file
View File

View File

@@ -0,0 +1,7 @@
### Strings
Rust has three struct types: a classic c struct, a tuple struct, and a unit struct.
#### Book Sections
- [Structures](https://doc.rust-lang.org/rust-by-example/custom_types/structs.html)

View File

@@ -0,0 +1,46 @@
// structs1.rs
// Address all the TODOs to make the tests pass!
struct ColorClassicStruct {
// TODO: Something goes here
}
struct ColorTupleStruct(/* TODO: Something goes here */);
struct ColorUnitStruct;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn classic_c_structs() {
// TODO: Instantiate a classic c struct!
// let green =
assert_eq!(green.name, "green");
assert_eq!(green.hex, "#00FF00");
}
#[test]
fn tuple_structs() {
// TODO: Instantiate a tuple struct!
// For more fun, use the field initialization shorthand.
// let green =
assert_eq!(green.0, "green");
assert_eq!(green.1, "#00FF00");
}
#[test]
fn unit_structs() {
// TODO: Instantiate a unit struct!
// let green =
if let ColorUnitStruct = green {
assert!(true);
} else {
assert!(false);
}
}
}

7
exercises/test2.rs Executable file → Normal file
View File

@@ -17,6 +17,11 @@ mod tests {
#[test]
fn returns_twice_of_positive_numbers() {
assert_eq!(4, 4);
assert_eq!(times_two(4), ???);
}
#[test]
fn returns_twice_of_negative_numbers() {
// TODO write an assert for `times_two(-4)`
}
}

0
exercises/test3.rs Executable file → Normal file
View File

0
exercises/tests/tests1.rs Executable file → Normal file
View File

0
exercises/tests/tests2.rs Executable file → Normal file
View File

0
exercises/tests/tests3.rs Executable file → Normal file
View File

View File

@@ -1 +1 @@
For this exercise check out the [Dining Philosophers example](https://doc.rust-lang.org/1.4.0/book/dining-philosophers.html) and the chapter [Concurrency](https://doc.rust-lang.org/book/2018-edition/ch16-01-threads.html) of the Rust Book.
For this exercise check out the [Dining Philosophers example](https://doc.rust-lang.org/1.4.0/book/dining-philosophers.html) and the chapter [Concurrency](https://doc.rust-lang.org/book/ch16-01-threads.html) of the Rust Book.

0
exercises/threads/threads1.rs Executable file → Normal file
View File

0
exercises/variables/variables1.rs Executable file → Normal file
View File

0
exercises/variables/variables2.rs Executable file → Normal file
View File

0
exercises/variables/variables3.rs Executable file → Normal file
View File

0
exercises/variables/variables4.rs Executable file → Normal file
View File

View File

@@ -76,6 +76,12 @@ mode = "compile"
path = "exercises/primitive_types/primitive_types6.rs"
mode = "compile"
# STRUCTS
[[exercises]]
path = "exercises/structs/structs1.rs"
mode = "test"
# TESTS
[[exercises]]

View File

@@ -3,31 +3,31 @@
echo "Let's get you set up with Rustlings!"
echo "Checking requirements..."
if [ -x "$(git)" ]
if [ -x "$(command -v git)" ]
then
echo "SUCCESS: Git is installed"
else
echo "WARNING: Git does not seem to be installed."
echo "Please download Git using your package manager or over https://git-scm.com/!"
exit 1
else
echo "SUCCESS: Git is installed"
fi
if [ -x "$(rustc)" ]
if [ -x "$(command -v rustc)" ]
then
echo "SUCCESS: Rust is installed"
else
echo "WARNING: Rust does not seem to be installed."
echo "Please download Rust using https://rustup.rs!"
exit 1
else
echo "SUCCESS: Rust is installed"
fi
if [ -x "$(cargo)" ]
if [ -x "$(command -v cargo)" ]
then
echo "SUCCESS: Cargo is installed"
else
echo "WARNING: Cargo does not seem to be installed."
echo "Please download Rust and Cargo using https://rustup.rs!"
exit 1
else
echo "SUCCESS: Cargo is installed"
fi
# Function that compares two versions strings v1 and v2 given in arguments (e.g 1.31 and 1.33.0).

79
src/exercise.rs Normal file
View File

@@ -0,0 +1,79 @@
use serde::Deserialize;
use std::fmt::{self, Display, Formatter};
use std::fs::remove_file;
use std::path::PathBuf;
use std::process::{self, Command, Output};
const RUSTC_COLOR_ARGS: &[&str] = &["--color", "always"];
fn temp_file() -> String {
format!("./temp_{}", process::id())
}
#[derive(Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Mode {
Compile,
Test,
}
#[derive(Deserialize)]
pub struct ExerciseList {
pub exercises: Vec<Exercise>,
}
#[derive(Deserialize)]
pub struct Exercise {
pub path: PathBuf,
pub mode: Mode,
}
impl Exercise {
pub fn compile(&self) -> Output {
match self.mode {
Mode::Compile => Command::new("rustc")
.args(&[self.path.to_str().unwrap(), "-o", &temp_file()])
.args(RUSTC_COLOR_ARGS)
.output(),
Mode::Test => Command::new("rustc")
.args(&["--test", self.path.to_str().unwrap(), "-o", &temp_file()])
.args(RUSTC_COLOR_ARGS)
.output(),
}
.expect("Failed to run 'compile' command.")
}
pub fn run(&self) -> Output {
Command::new(&temp_file())
.output()
.expect("Failed to run 'run' command")
}
pub fn clean(&self) {
let _ignored = remove_file(&temp_file());
}
}
impl Display for Exercise {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.path.to_str().unwrap())
}
}
#[cfg(test)]
mod test {
use super::*;
use std::fs::File;
use std::path::Path;
#[test]
fn test_clean() {
File::create(&temp_file()).unwrap();
let exercise = Exercise {
path: PathBuf::from("example.rs"),
mode: Mode::Test,
};
exercise.clean();
assert!(!Path::new(&temp_file()).exists());
}
}

View File

@@ -1,20 +1,17 @@
use crate::exercise::{Exercise, ExerciseList};
use crate::run::run;
use crate::verify::verify;
use clap::{crate_version, App, Arg, SubCommand};
use notify::DebouncedEvent;
use notify::{RecommendedWatcher, RecursiveMode, Watcher};
use std::ffi::OsStr;
use std::io::BufRead;
use std::fs;
use std::path::Path;
use std::sync::mpsc::channel;
use std::time::Duration;
use syntect::easy::HighlightFile;
use syntect::highlighting::{Style, ThemeSet};
use syntect::parsing::SyntaxSet;
use syntect::util::as_24_bit_terminal_escaped;
mod exercise;
mod run;
mod util;
mod verify;
fn main() {
@@ -33,9 +30,6 @@ fn main() {
)
.get_matches();
let ss = SyntaxSet::load_defaults_newlines();
let ts = ThemeSet::load_defaults();
if None == matches.subcommand_name() {
println!();
println!(r#" welcome to... "#);
@@ -53,44 +47,57 @@ fn main() {
"{} must be run from the rustlings directory",
std::env::current_exe().unwrap().to_str().unwrap()
);
println!("Try `cd rustlings/`!");
std::process::exit(1);
}
if let Some(matches) = matches.subcommand_matches("run") {
run(matches.clone()).unwrap();
let toml_str = &fs::read_to_string("info.toml").unwrap();
let exercises = toml::from_str::<ExerciseList>(toml_str).unwrap().exercises;
if let Some(ref matches) = matches.subcommand_matches("run") {
let filename = matches.value_of("file").unwrap_or_else(|| {
println!("Please supply a file name!");
std::process::exit(1);
});
let matching_exercise = |e: &&Exercise| {
Path::new(filename)
.canonicalize()
.map(|p| p.ends_with(&e.path))
.unwrap_or(false)
};
let exercise = exercises.iter().find(matching_exercise).unwrap_or_else(|| {
println!("No exercise found for your file name!");
std::process::exit(1)
});
run(&exercise).unwrap_or_else(|_| std::process::exit(1));
}
if matches.subcommand_matches("verify").is_some() {
match verify(None) {
Ok(_) => {}
Err(_) => std::process::exit(1),
}
verify(&exercises).unwrap_or_else(|_| std::process::exit(1));
}
if matches.subcommand_matches("watch").is_some() {
watch().unwrap();
watch(&exercises).unwrap();
}
if matches.subcommand_name().is_none() {
let mut highlighter =
HighlightFile::new("default_out.md", &ss, &ts.themes["base16-eighties.dark"]).unwrap();
for maybe_line in highlighter.reader.lines() {
let line = maybe_line.unwrap();
let regions: Vec<(Style, &str)> = highlighter.highlight_lines.highlight(&line, &ss);
println!("{}", as_24_bit_terminal_escaped(&regions[..], true));
}
let text = fs::read_to_string("default_out.txt").unwrap();
println!("{}", text);
}
println!("\x1b[0m");
}
fn watch() -> notify::Result<()> {
fn watch(exercises: &[Exercise]) -> notify::Result<()> {
let (tx, rx) = channel();
let mut watcher: RecommendedWatcher = Watcher::new(tx, Duration::from_secs(2))?;
watcher.watch("./exercises", RecursiveMode::Recursive)?;
watcher.watch(Path::new("./exercises"), RecursiveMode::Recursive)?;
let _ignored = verify(None);
let _ignored = verify(exercises.iter());
loop {
match rx.recv() {
@@ -98,7 +105,11 @@ fn watch() -> notify::Result<()> {
DebouncedEvent::Create(b) | DebouncedEvent::Chmod(b) | DebouncedEvent::Write(b) => {
if b.extension() == Some(OsStr::new("rs")) {
println!("----------**********----------\n");
let _ignored = verify(Some(b.as_path().to_str().unwrap()));
let filepath = b.as_path().canonicalize().unwrap();
let exercise = exercises
.iter()
.skip_while(|e| !filepath.ends_with(&e.path));
let _ignored = verify(exercise);
}
}
_ => {}

View File

@@ -1,60 +1,40 @@
use crate::util::clean;
use crate::exercise::{Exercise, Mode};
use crate::verify::test;
use console::{style, Emoji};
use indicatif::ProgressBar;
use std::fs;
use std::process::Command;
use toml::Value;
pub fn run(matches: clap::ArgMatches) -> Result<(), ()> {
if let Some(filename) = matches.value_of("file") {
let toml: Value = fs::read_to_string("info.toml").unwrap().parse().unwrap();
let tomlvec: &Vec<Value> = toml.get("exercises").unwrap().as_array().unwrap();
let mut exercises = tomlvec.clone();
exercises.retain(|i| i.get("path").unwrap().as_str().unwrap() == filename);
if exercises.is_empty() {
println!("No exercise found for your filename!");
std::process::exit(1);
}
let exercise: &Value = &exercises[0];
match exercise.get("mode").unwrap().as_str().unwrap() {
"test" => test(exercise.get("path").unwrap().as_str().unwrap())?,
"compile" => compile_and_run(exercise.get("path").unwrap().as_str().unwrap())?,
_ => (),
}
Ok(())
} else {
panic!("Please supply a filename!");
pub fn run(exercise: &Exercise) -> Result<(), ()> {
match exercise.mode {
Mode::Test => test(exercise)?,
Mode::Compile => compile_and_run(exercise)?,
}
Ok(())
}
pub fn compile_and_run(filename: &str) -> Result<(), ()> {
pub fn compile_and_run(exercise: &Exercise) -> Result<(), ()> {
let progress_bar = ProgressBar::new_spinner();
progress_bar.set_message(format!("Compiling {}...", filename).as_str());
progress_bar.set_message(format!("Compiling {}...", exercise).as_str());
progress_bar.enable_steady_tick(100);
let compilecmd = Command::new("rustc")
.args(&[filename, "-o", "temp", "--color", "always"])
.output()
.expect("fail");
progress_bar.set_message(format!("Running {}...", filename).as_str());
let compilecmd = exercise.compile();
progress_bar.set_message(format!("Running {}...", exercise).as_str());
if compilecmd.status.success() {
let runcmd = Command::new("./temp").output().expect("fail");
let runcmd = exercise.run();
progress_bar.finish_and_clear();
if runcmd.status.success() {
println!("{}", String::from_utf8_lossy(&runcmd.stdout));
let formatstr = format!("{} Successfully ran {}", Emoji("", ""), filename);
let formatstr = format!("{} Successfully ran {}", Emoji("", ""), exercise);
println!("{}", style(formatstr).green());
clean();
exercise.clean();
Ok(())
} else {
println!("{}", String::from_utf8_lossy(&runcmd.stdout));
println!("{}", String::from_utf8_lossy(&runcmd.stderr));
let formatstr = format!("{} Ran {} with errors", Emoji("⚠️ ", "!"), filename);
let formatstr = format!("{} Ran {} with errors", Emoji("⚠️ ", "!"), exercise);
println!("{}", style(formatstr).red());
clean();
exercise.clean();
Err(())
}
} else {
@@ -62,11 +42,11 @@ pub fn compile_and_run(filename: &str) -> Result<(), ()> {
let formatstr = format!(
"{} Compilation of {} failed! Compiler error message:\n",
Emoji("⚠️ ", "!"),
filename
exercise
);
println!("{}", style(formatstr).red());
println!("{}", String::from_utf8_lossy(&compilecmd.stderr));
clean();
exercise.clean();
Err(())
}
}

View File

@@ -1,12 +0,0 @@
use std::fs::remove_file;
pub fn clean() {
let _ignored = remove_file("temp");
}
#[test]
fn test_clean() {
std::fs::File::create("temp").unwrap();
clean();
assert!(!std::path::Path::new("temp").exists());
}

View File

@@ -1,93 +1,71 @@
use crate::util::clean;
use crate::exercise::{Exercise, Mode};
use console::{style, Emoji};
use indicatif::ProgressBar;
use std::fs;
use std::process::Command;
use toml::Value;
pub fn verify(start_at: Option<&str>) -> Result<(), ()> {
let toml: Value = fs::read_to_string("info.toml").unwrap().parse().unwrap();
let tomlvec: &Vec<Value> = toml.get("exercises").unwrap().as_array().unwrap();
let mut hit_start_at = false;
for i in tomlvec {
let path = i.get("path").unwrap().as_str().unwrap();
if let Some(start_at) = start_at {
if start_at.ends_with(path) {
hit_start_at = true;
} else if !hit_start_at {
continue;
}
}
match i.get("mode").unwrap().as_str().unwrap() {
"test" => test(path)?,
"compile" => compile_only(path)?,
_ => (),
pub fn verify<'a>(start_at: impl IntoIterator<Item = &'a Exercise>) -> Result<(), ()> {
for exercise in start_at {
match exercise.mode {
Mode::Test => test(&exercise)?,
Mode::Compile => compile_only(&exercise)?,
}
}
Ok(())
}
fn compile_only(filename: &str) -> Result<(), ()> {
fn compile_only(exercise: &Exercise) -> Result<(), ()> {
let progress_bar = ProgressBar::new_spinner();
progress_bar.set_message(format!("Compiling {}...", filename).as_str());
progress_bar.set_message(format!("Compiling {}...", exercise).as_str());
progress_bar.enable_steady_tick(100);
let compilecmd = Command::new("rustc")
.args(&[filename, "-o", "temp", "--color", "always"])
.output()
.expect("fail");
let compile_output = exercise.compile();
progress_bar.finish_and_clear();
if compilecmd.status.success() {
if compile_output.status.success() {
let formatstr = format!(
"{} Successfully compiled {}!",
Emoji("", ""),
filename
exercise
);
println!("{}", style(formatstr).green());
clean();
exercise.clean();
Ok(())
} else {
let formatstr = format!(
"{} Compilation of {} failed! Compiler error message:\n",
Emoji("⚠️ ", "!"),
filename
exercise
);
println!("{}", style(formatstr).red());
println!("{}", String::from_utf8_lossy(&compilecmd.stderr));
clean();
println!("{}", String::from_utf8_lossy(&compile_output.stderr));
exercise.clean();
Err(())
}
}
pub fn test(filename: &str) -> Result<(), ()> {
pub fn test(exercise: &Exercise) -> Result<(), ()> {
let progress_bar = ProgressBar::new_spinner();
progress_bar.set_message(format!("Testing {}...", filename).as_str());
progress_bar.set_message(format!("Testing {}...", exercise).as_str());
progress_bar.enable_steady_tick(100);
let testcmd = Command::new("rustc")
.args(&["--test", filename, "-o", "temp", "--color", "always"])
.output()
.expect("fail");
if testcmd.status.success() {
progress_bar.set_message(format!("Running {}...", filename).as_str());
let runcmd = Command::new("./temp").output().expect("fail");
let compile_output = exercise.compile();
if compile_output.status.success() {
progress_bar.set_message(format!("Running {}...", exercise).as_str());
let runcmd = exercise.run();
progress_bar.finish_and_clear();
if runcmd.status.success() {
let formatstr = format!("{} Successfully tested {}!", Emoji("", ""), filename);
let formatstr = format!("{} Successfully tested {}!", Emoji("", ""), exercise);
println!("{}", style(formatstr).green());
clean();
exercise.clean();
Ok(())
} else {
let formatstr = format!(
"{} Testing of {} failed! Please try again. Here's the output:",
Emoji("⚠️ ", "!"),
filename
exercise
);
println!("{}", style(formatstr).red());
println!("{}", String::from_utf8_lossy(&runcmd.stdout));
clean();
exercise.clean();
Err(())
}
} else {
@@ -95,11 +73,11 @@ pub fn test(filename: &str) -> Result<(), ()> {
let formatstr = format!(
"{} Compiling of {} failed! Please try again. Here's the output:",
Emoji("⚠️ ", "!"),
filename
exercise
);
println!("{}", style(formatstr).red());
println!("{}", String::from_utf8_lossy(&testcmd.stderr));
clean();
println!("{}", String::from_utf8_lossy(&compile_output.stderr));
exercise.clean();
Err(())
}
}

View File

@@ -0,0 +1,3 @@
fn main() {
let
}

View File

@@ -0,0 +1,7 @@
[[exercises]]
path = "compFailure.rs"
mode = "compile"
[[exercises]]
path = "testFailure.rs"
mode = "test"

View File

@@ -0,0 +1,4 @@
#[test]
fn passing() {
asset!(true);
}

View File

@@ -0,0 +1,4 @@
#[test]
fn not_passing() {
assert!(false);
}

View File

@@ -13,7 +13,7 @@ fn fails_when_in_wrong_dir() {
.unwrap()
.current_dir("tests/")
.assert()
.failure();
.code(1);
}
#[test]
@@ -21,31 +21,71 @@ fn verify_all_success() {
Command::cargo_bin("rustlings")
.unwrap()
.arg("v")
.current_dir("tests/fixture/")
.current_dir("tests/fixture/success")
.assert()
.success();
}
#[test]
fn verify_all_failure() {
Command::cargo_bin("rustlings")
.unwrap()
.arg("v")
.current_dir("tests/fixture/failure")
.assert()
.code(1);
}
#[test]
fn run_single_compile_success() {
Command::cargo_bin("rustlings")
.unwrap()
.args(&["r", "compSuccess.rs"])
.current_dir("tests/fixture/")
.current_dir("tests/fixture/success/")
.assert()
.success();
}
#[test]
fn run_single_compile_failure() {
Command::cargo_bin("rustlings")
.unwrap()
.args(&["r", "compFailure.rs"])
.current_dir("tests/fixture/failure/")
.assert()
.code(1);
}
#[test]
fn run_single_test_success() {
Command::cargo_bin("rustlings")
.unwrap()
.args(&["r", "testSuccess.rs"])
.current_dir("tests/fixture/")
.current_dir("tests/fixture/success/")
.assert()
.success();
}
#[test]
fn run_single_test_failure() {
Command::cargo_bin("rustlings")
.unwrap()
.args(&["r", "testFailure.rs"])
.current_dir("tests/fixture/failure/")
.assert()
.code(1);
}
#[test]
fn run_single_test_not_passed() {
Command::cargo_bin("rustlings")
.unwrap()
.args(&["r", "testNotPassed.rs"])
.current_dir("tests/fixture/failure/")
.assert()
.code(1);
}
#[test]
fn run_single_test_no_filename() {
Command::cargo_bin("rustlings")
@@ -53,7 +93,7 @@ fn run_single_test_no_filename() {
.arg("r")
.current_dir("tests/fixture/")
.assert()
.failure();
.code(1);
}
#[test]
@@ -61,7 +101,7 @@ fn run_single_test_no_exercise() {
Command::cargo_bin("rustlings")
.unwrap()
.args(&["r", "compNoExercise.rs"])
.current_dir("tests/fixture/")
.current_dir("tests/fixture/failure")
.assert()
.failure();
.code(1);
}