mirror of
https://github.com/rust-lang/rustlings.git
synced 2025-12-26 00:11:49 +02:00
Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7fca1d28af | ||
|
|
9c3d765403 | ||
|
|
bb652ceb91 | ||
|
|
62696f5819 | ||
|
|
41170ce341 | ||
|
|
e2092a4ddd | ||
|
|
fd1441d122 | ||
|
|
e1422c6443 | ||
|
|
1f07fd4150 | ||
|
|
9b92aa08ae | ||
|
|
8bf8cbbd61 | ||
|
|
f507844102 | ||
|
|
fffbb60ed9 | ||
|
|
9aec4abc4d | ||
|
|
a53b3f199f | ||
|
|
d6d696b66a | ||
|
|
ca6bf966dd | ||
|
|
5a9f8860ca | ||
|
|
5423bc66a9 | ||
|
|
187d2ad226 | ||
|
|
7cf0d5d15e | ||
|
|
1f2ee8cb62 | ||
|
|
35c3d0b3fc | ||
|
|
0279294972 | ||
|
|
7eddee6f7a | ||
|
|
f2c48cfac5 | ||
|
|
6ae0a00211 | ||
|
|
bfcf38c8bc | ||
|
|
9e328da641 | ||
|
|
e336d04c79 | ||
|
|
a71bc62c29 | ||
|
|
4b0b7093e5 | ||
|
|
8387de64d3 | ||
|
|
77de6e5d6a | ||
|
|
8c867a001a | ||
|
|
d01a71f7de | ||
|
|
04d1d4c00e | ||
|
|
d7e58ee1af | ||
|
|
ffb165ce26 | ||
|
|
65cb09eb2e | ||
|
|
78552ebd7a | ||
|
|
0c7bd12372 | ||
|
|
3d11d7685b | ||
|
|
592ae6b4d2 | ||
|
|
4fa79ee02f | ||
|
|
fbd0ccbd5b | ||
|
|
8c008a0e7d | ||
|
|
11fe19d08a | ||
|
|
1b3469f236 | ||
|
|
022921168d | ||
|
|
c6765eb3eb | ||
|
|
c5a374fbf2 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,5 +1,4 @@
|
||||
*.swp
|
||||
target/
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
||||
.DS_Store
|
||||
|
||||
@@ -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
1152
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
|
||||
@@ -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.
|
||||
@@ -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
2
exercises/error_handling/errors1.rs
Executable file → Normal 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
5
exercises/error_handling/errors2.rs
Executable file → Normal 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
0
exercises/error_handling/errors3.rs
Executable file → Normal file
115
exercises/error_handling/errorsn.rs
Executable file → Normal file
115
exercises/error_handling/errorsn.rs
Executable file → Normal 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
5
exercises/error_handling/option1.rs
Executable file → Normal 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
9
exercises/error_handling/result1.rs
Executable file → Normal 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
0
exercises/functions/functions1.rs
Executable file → Normal file
0
exercises/functions/functions2.rs
Executable file → Normal file
0
exercises/functions/functions2.rs
Executable file → Normal file
0
exercises/functions/functions3.rs
Executable file → Normal file
0
exercises/functions/functions3.rs
Executable file → Normal file
0
exercises/functions/functions4.rs
Executable file → Normal file
0
exercises/functions/functions4.rs
Executable file → Normal file
0
exercises/functions/functions5.rs
Executable file → Normal file
0
exercises/functions/functions5.rs
Executable file → Normal file
0
exercises/if/if1.rs
Executable file → Normal file
0
exercises/if/if1.rs
Executable file → Normal file
0
exercises/macros/macros1.rs
Executable file → Normal file
0
exercises/macros/macros1.rs
Executable file → Normal file
0
exercises/macros/macros2.rs
Executable file → Normal file
0
exercises/macros/macros2.rs
Executable file → Normal file
0
exercises/macros/macros3.rs
Executable file → Normal file
0
exercises/macros/macros3.rs
Executable file → Normal file
0
exercises/macros/macros4.rs
Executable file → Normal file
0
exercises/macros/macros4.rs
Executable file → Normal 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
0
exercises/modules/modules1.rs
Executable file → Normal file
0
exercises/modules/modules2.rs
Executable file → Normal file
0
exercises/modules/modules2.rs
Executable file → Normal file
0
exercises/move_semantics/move_semantics1.rs
Executable file → Normal file
0
exercises/move_semantics/move_semantics1.rs
Executable file → Normal file
0
exercises/move_semantics/move_semantics2.rs
Executable file → Normal file
0
exercises/move_semantics/move_semantics2.rs
Executable file → Normal file
0
exercises/move_semantics/move_semantics3.rs
Executable file → Normal file
0
exercises/move_semantics/move_semantics3.rs
Executable file → Normal file
3
exercises/move_semantics/move_semantics4.rs
Executable file → Normal file
3
exercises/move_semantics/move_semantics4.rs
Executable file → Normal 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
0
exercises/primitive_types/primitive_types1.rs
Executable file → Normal file
0
exercises/primitive_types/primitive_types2.rs
Executable file → Normal file
0
exercises/primitive_types/primitive_types2.rs
Executable file → Normal file
0
exercises/primitive_types/primitive_types3.rs
Executable file → Normal file
0
exercises/primitive_types/primitive_types3.rs
Executable file → Normal file
0
exercises/primitive_types/primitive_types4.rs
Executable file → Normal file
0
exercises/primitive_types/primitive_types4.rs
Executable file → Normal file
0
exercises/primitive_types/primitive_types5.rs
Executable file → Normal file
0
exercises/primitive_types/primitive_types5.rs
Executable file → Normal file
0
exercises/primitive_types/primitive_types6.rs
Executable file → Normal file
0
exercises/primitive_types/primitive_types6.rs
Executable file → Normal 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
0
exercises/standard_library_types/arc1.rs
Executable file → Normal file
4
exercises/standard_library_types/iterator3.rs
Executable file → Normal file
4
exercises/standard_library_types/iterator3.rs
Executable file → Normal 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
0
exercises/standard_library_types/iterators4.rs
Executable file → Normal file
0
exercises/strings/strings1.rs
Executable file → Normal file
0
exercises/strings/strings1.rs
Executable file → Normal file
0
exercises/strings/strings2.rs
Executable file → Normal file
0
exercises/strings/strings2.rs
Executable file → Normal file
7
exercises/structs/README.md
Normal file
7
exercises/structs/README.md
Normal 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)
|
||||
46
exercises/structs/structs1.rs
Normal file
46
exercises/structs/structs1.rs
Normal 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
7
exercises/test2.rs
Executable file → Normal 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
0
exercises/test3.rs
Executable file → Normal file
0
exercises/tests/tests1.rs
Executable file → Normal file
0
exercises/tests/tests1.rs
Executable file → Normal file
0
exercises/tests/tests2.rs
Executable file → Normal file
0
exercises/tests/tests2.rs
Executable file → Normal file
0
exercises/tests/tests3.rs
Executable file → Normal file
0
exercises/tests/tests3.rs
Executable file → Normal 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
0
exercises/threads/threads1.rs
Executable file → Normal file
0
exercises/variables/variables1.rs
Executable file → Normal file
0
exercises/variables/variables1.rs
Executable file → Normal file
0
exercises/variables/variables2.rs
Executable file → Normal file
0
exercises/variables/variables2.rs
Executable file → Normal file
0
exercises/variables/variables3.rs
Executable file → Normal file
0
exercises/variables/variables3.rs
Executable file → Normal file
0
exercises/variables/variables4.rs
Executable file → Normal file
0
exercises/variables/variables4.rs
Executable file → Normal 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]]
|
||||
|
||||
18
install.sh
18
install.sh
@@ -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
79
src/exercise.rs
Normal 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());
|
||||
}
|
||||
}
|
||||
65
src/main.rs
65
src/main.rs
@@ -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(®ions[..], 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);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
||||
56
src/run.rs
56
src/run.rs
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
12
src/util.rs
12
src/util.rs
@@ -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());
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
3
tests/fixture/failure/compFailure.rs
Normal file
3
tests/fixture/failure/compFailure.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
let
|
||||
}
|
||||
7
tests/fixture/failure/info.toml
Normal file
7
tests/fixture/failure/info.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
[[exercises]]
|
||||
path = "compFailure.rs"
|
||||
mode = "compile"
|
||||
|
||||
[[exercises]]
|
||||
path = "testFailure.rs"
|
||||
mode = "test"
|
||||
4
tests/fixture/failure/testFailure.rs
Normal file
4
tests/fixture/failure/testFailure.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
#[test]
|
||||
fn passing() {
|
||||
asset!(true);
|
||||
}
|
||||
4
tests/fixture/failure/testNotPassed.rs
Normal file
4
tests/fixture/failure/testNotPassed.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
#[test]
|
||||
fn not_passing() {
|
||||
assert!(false);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user