Merge pull request #1959 from rust-lang/output

Improve output
This commit is contained in:
Mo 2024-04-25 14:43:28 +02:00 committed by GitHub
commit 88f27a5377
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 280 additions and 218 deletions

11
Cargo.lock generated
View file

@ -519,6 +519,16 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "os_pipe"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57119c3b893986491ec9aa85056780d3a0f3cf4da7cc09dd3650dbd6c6738fb9"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
version = "0.12.1" version = "0.12.1"
@ -677,6 +687,7 @@ dependencies = [
"crossterm", "crossterm",
"hashbrown", "hashbrown",
"notify-debouncer-mini", "notify-debouncer-mini",
"os_pipe",
"predicates", "predicates",
"ratatui", "ratatui",
"rustlings-macros", "rustlings-macros",

View file

@ -50,6 +50,7 @@ clap = { version = "4.5.4", features = ["derive"] }
crossterm = "0.27.0" crossterm = "0.27.0"
hashbrown = "0.14.3" hashbrown = "0.14.3"
notify-debouncer-mini = "0.4.1" notify-debouncer-mini = "0.4.1"
os_pipe = "1.1.5"
ratatui = "0.26.2" ratatui = "0.26.2"
rustlings-macros = { path = "rustlings-macros", version = "6.0.0-beta.0" } rustlings-macros = { path = "rustlings-macros", version = "6.0.0-beta.0" }
serde.workspace = true serde.workspace = true

143
info.toml
View file

@ -39,7 +39,7 @@ https://github.com/rust-lang/rustlings/blob/main/CONTRIBUTING.md
[[exercises]] [[exercises]]
name = "intro1" name = "intro1"
dir = "00_intro" dir = "00_intro"
mode = "run" test = false
# TODO: Fix hint # TODO: Fix hint
hint = """ hint = """
Remove the `I AM NOT DONE` comment in the `exercises/intro00/intro1.rs` file Remove the `I AM NOT DONE` comment in the `exercises/intro00/intro1.rs` file
@ -48,7 +48,7 @@ to move on to the next exercise."""
[[exercises]] [[exercises]]
name = "intro2" name = "intro2"
dir = "00_intro" dir = "00_intro"
mode = "run" test = false
hint = """ hint = """
The compiler is informing us that we've got the name of the print macro wrong, and has suggested an alternative.""" The compiler is informing us that we've got the name of the print macro wrong, and has suggested an alternative."""
@ -57,7 +57,7 @@ The compiler is informing us that we've got the name of the print macro wrong, a
[[exercises]] [[exercises]]
name = "variables1" name = "variables1"
dir = "01_variables" dir = "01_variables"
mode = "run" test = false
hint = """ hint = """
The declaration in the first line in the main function is missing a keyword The declaration in the first line in the main function is missing a keyword
that is needed in Rust to create a new variable binding.""" that is needed in Rust to create a new variable binding."""
@ -65,7 +65,7 @@ that is needed in Rust to create a new variable binding."""
[[exercises]] [[exercises]]
name = "variables2" name = "variables2"
dir = "01_variables" dir = "01_variables"
mode = "run" test = false
hint = """ hint = """
The compiler message is saying that Rust cannot infer the type that the The compiler message is saying that Rust cannot infer the type that the
variable binding `x` has with what is given here. variable binding `x` has with what is given here.
@ -84,7 +84,7 @@ What if `x` is the same type as `10`? What if it's a different type?"""
[[exercises]] [[exercises]]
name = "variables3" name = "variables3"
dir = "01_variables" dir = "01_variables"
mode = "run" test = false
hint = """ hint = """
Oops! In this exercise, we have a variable binding that we've created on in the Oops! In this exercise, we have a variable binding that we've created on in the
first line in the `main` function, and we're trying to use it in the next line, first line in the `main` function, and we're trying to use it in the next line,
@ -98,7 +98,7 @@ programming language -- thankfully the Rust compiler has caught this for us!"""
[[exercises]] [[exercises]]
name = "variables4" name = "variables4"
dir = "01_variables" dir = "01_variables"
mode = "run" test = false
hint = """ hint = """
In Rust, variable bindings are immutable by default. But here we're trying In Rust, variable bindings are immutable by default. But here we're trying
to reassign a different value to `x`! There's a keyword we can use to make to reassign a different value to `x`! There's a keyword we can use to make
@ -107,7 +107,7 @@ a variable binding mutable instead."""
[[exercises]] [[exercises]]
name = "variables5" name = "variables5"
dir = "01_variables" dir = "01_variables"
mode = "run" test = false
hint = """ hint = """
In `variables4` we already learned how to make an immutable variable mutable In `variables4` we already learned how to make an immutable variable mutable
using a special keyword. Unfortunately this doesn't help us much in this using a special keyword. Unfortunately this doesn't help us much in this
@ -125,7 +125,7 @@ Try to solve this exercise afterwards using this technique."""
[[exercises]] [[exercises]]
name = "variables6" name = "variables6"
dir = "01_variables" dir = "01_variables"
mode = "run" test = false
hint = """ hint = """
We know about variables and mutability, but there is another important type of We know about variables and mutability, but there is another important type of
variable available: constants. variable available: constants.
@ -145,7 +145,7 @@ https://doc.rust-lang.org/book/ch03-01-variables-and-mutability.html#constants
[[exercises]] [[exercises]]
name = "functions1" name = "functions1"
dir = "02_functions" dir = "02_functions"
mode = "run" test = false
hint = """ hint = """
This main function is calling a function that it expects to exist, but the This main function is calling a function that it expects to exist, but the
function doesn't exist. It expects this function to have the name `call_me`. function doesn't exist. It expects this function to have the name `call_me`.
@ -155,7 +155,7 @@ Sounds a lot like `main`, doesn't it?"""
[[exercises]] [[exercises]]
name = "functions2" name = "functions2"
dir = "02_functions" dir = "02_functions"
mode = "run" test = false
hint = """ hint = """
Rust requires that all parts of a function's signature have type annotations, Rust requires that all parts of a function's signature have type annotations,
but `call_me` is missing the type annotation of `num`.""" but `call_me` is missing the type annotation of `num`."""
@ -163,7 +163,7 @@ but `call_me` is missing the type annotation of `num`."""
[[exercises]] [[exercises]]
name = "functions3" name = "functions3"
dir = "02_functions" dir = "02_functions"
mode = "run" test = false
hint = """ hint = """
This time, the function *declaration* is okay, but there's something wrong This time, the function *declaration* is okay, but there's something wrong
with the place where we're calling the function.""" with the place where we're calling the function."""
@ -171,7 +171,7 @@ with the place where we're calling the function."""
[[exercises]] [[exercises]]
name = "functions4" name = "functions4"
dir = "02_functions" dir = "02_functions"
mode = "run" test = false
hint = """ hint = """
The error message points to the function `sale_price` and says it expects a type The error message points to the function `sale_price` and says it expects a type
after the `->`. This is where the function's return type should be -- take a after the `->`. This is where the function's return type should be -- take a
@ -180,7 +180,7 @@ look at the `is_even` function for an example!"""
[[exercises]] [[exercises]]
name = "functions5" name = "functions5"
dir = "02_functions" dir = "02_functions"
mode = "run" test = false
hint = """ hint = """
This is a really common error that can be fixed by removing one character. This is a really common error that can be fixed by removing one character.
It happens because Rust distinguishes between expressions and statements: It happens because Rust distinguishes between expressions and statements:
@ -199,7 +199,6 @@ They are not the same. There are two solutions:
[[exercises]] [[exercises]]
name = "if1" name = "if1"
dir = "03_if" dir = "03_if"
mode = "test"
hint = """ hint = """
It's possible to do this in one line if you would like! It's possible to do this in one line if you would like!
@ -215,7 +214,6 @@ Remember in Rust that:
[[exercises]] [[exercises]]
name = "if2" name = "if2"
dir = "03_if" dir = "03_if"
mode = "test"
hint = """ hint = """
For that first compiler error, it's important in Rust that each conditional For that first compiler error, it's important in Rust that each conditional
block returns the same type! To get the tests passing, you will need a couple block returns the same type! To get the tests passing, you will need a couple
@ -224,7 +222,6 @@ conditions checking different input values."""
[[exercises]] [[exercises]]
name = "if3" name = "if3"
dir = "03_if" dir = "03_if"
mode = "test"
hint = """ hint = """
In Rust, every arm of an `if` expression has to return the same type of value. In Rust, every arm of an `if` expression has to return the same type of value.
Make sure the type is consistent across all arms.""" Make sure the type is consistent across all arms."""
@ -234,7 +231,6 @@ Make sure the type is consistent across all arms."""
[[exercises]] [[exercises]]
name = "quiz1" name = "quiz1"
dir = "quizzes" dir = "quizzes"
mode = "test"
hint = "No hints this time ;)" hint = "No hints this time ;)"
# PRIMITIVE TYPES # PRIMITIVE TYPES
@ -242,19 +238,19 @@ hint = "No hints this time ;)"
[[exercises]] [[exercises]]
name = "primitive_types1" name = "primitive_types1"
dir = "04_primitive_types" dir = "04_primitive_types"
mode = "run" test = false
hint = "No hints this time ;)" hint = "No hints this time ;)"
[[exercises]] [[exercises]]
name = "primitive_types2" name = "primitive_types2"
dir = "04_primitive_types" dir = "04_primitive_types"
mode = "run" test = false
hint = "No hints this time ;)" hint = "No hints this time ;)"
[[exercises]] [[exercises]]
name = "primitive_types3" name = "primitive_types3"
dir = "04_primitive_types" dir = "04_primitive_types"
mode = "run" test = false
hint = """ hint = """
There's a shorthand to initialize Arrays with a certain size that does not There's a shorthand to initialize Arrays with a certain size that does not
require you to type in 100 items (but you certainly can if you want!). require you to type in 100 items (but you certainly can if you want!).
@ -270,7 +266,6 @@ for `a.len() >= 100`?"""
[[exercises]] [[exercises]]
name = "primitive_types4" name = "primitive_types4"
dir = "04_primitive_types" dir = "04_primitive_types"
mode = "test"
hint = """ hint = """
Take a look at the 'Understanding Ownership -> Slices -> Other Slices' section Take a look at the 'Understanding Ownership -> Slices -> Other Slices' section
of the book: https://doc.rust-lang.org/book/ch04-03-slices.html and use the of the book: https://doc.rust-lang.org/book/ch04-03-slices.html and use the
@ -285,7 +280,7 @@ https://doc.rust-lang.org/nomicon/coercions.html"""
[[exercises]] [[exercises]]
name = "primitive_types5" name = "primitive_types5"
dir = "04_primitive_types" dir = "04_primitive_types"
mode = "run" test = false
hint = """ hint = """
Take a look at the 'Data Types -> The Tuple Type' section of the book: Take a look at the 'Data Types -> The Tuple Type' section of the book:
https://doc.rust-lang.org/book/ch03-02-data-types.html#the-tuple-type https://doc.rust-lang.org/book/ch03-02-data-types.html#the-tuple-type
@ -298,7 +293,6 @@ of the tuple. You can do it!!"""
[[exercises]] [[exercises]]
name = "primitive_types6" name = "primitive_types6"
dir = "04_primitive_types" dir = "04_primitive_types"
mode = "test"
hint = """ hint = """
While you could use a destructuring `let` for the tuple here, try While you could use a destructuring `let` for the tuple here, try
indexing into it instead, as explained in the last example of the indexing into it instead, as explained in the last example of the
@ -311,7 +305,6 @@ Now you have another tool in your toolbox!"""
[[exercises]] [[exercises]]
name = "vecs1" name = "vecs1"
dir = "05_vecs" dir = "05_vecs"
mode = "test"
hint = """ hint = """
In Rust, there are two ways to define a Vector. In Rust, there are two ways to define a Vector.
1. One way is to use the `Vec::new()` function to create a new vector 1. One way is to use the `Vec::new()` function to create a new vector
@ -326,7 +319,6 @@ of the Rust book to learn more.
[[exercises]] [[exercises]]
name = "vecs2" name = "vecs2"
dir = "05_vecs" dir = "05_vecs"
mode = "test"
hint = """ hint = """
In the first function we are looping over the Vector and getting a reference to In the first function we are looping over the Vector and getting a reference to
one `element` at a time. one `element` at a time.
@ -349,7 +341,6 @@ What do you think is the more commonly used pattern under Rust developers?
[[exercises]] [[exercises]]
name = "move_semantics1" name = "move_semantics1"
dir = "06_move_semantics" dir = "06_move_semantics"
mode = "test"
hint = """ hint = """
So you've got the "cannot borrow immutable local variable `vec` as mutable" So you've got the "cannot borrow immutable local variable `vec` as mutable"
error on the line where we push an element to the vector, right? error on the line where we push an element to the vector, right?
@ -363,7 +354,6 @@ happens!"""
[[exercises]] [[exercises]]
name = "move_semantics2" name = "move_semantics2"
dir = "06_move_semantics" dir = "06_move_semantics"
mode = "test"
hint = """ hint = """
When running this exercise for the first time, you'll notice an error about When running this exercise for the first time, you'll notice an error about
"borrow of moved value". In Rust, when an argument is passed to a function and "borrow of moved value". In Rust, when an argument is passed to a function and
@ -384,7 +374,6 @@ try them all:
[[exercises]] [[exercises]]
name = "move_semantics3" name = "move_semantics3"
dir = "06_move_semantics" dir = "06_move_semantics"
mode = "test"
hint = """ hint = """
The difference between this one and the previous ones is that the first line The difference between this one and the previous ones is that the first line
of `fn fill_vec` that had `let mut vec = vec;` is no longer there. You can, of `fn fill_vec` that had `let mut vec = vec;` is no longer there. You can,
@ -394,7 +383,6 @@ an existing binding to be a mutable binding instead of an immutable one :)"""
[[exercises]] [[exercises]]
name = "move_semantics4" name = "move_semantics4"
dir = "06_move_semantics" dir = "06_move_semantics"
mode = "test"
hint = """ hint = """
Stop reading whenever you feel like you have enough direction :) Or try Stop reading whenever you feel like you have enough direction :) Or try
doing one step and then fixing the compiler errors that result! doing one step and then fixing the compiler errors that result!
@ -408,7 +396,6 @@ So the end goal is to:
[[exercises]] [[exercises]]
name = "move_semantics5" name = "move_semantics5"
dir = "06_move_semantics" dir = "06_move_semantics"
mode = "test"
hint = """ hint = """
Carefully reason about the range in which each mutable reference is in Carefully reason about the range in which each mutable reference is in
scope. Does it help to update the value of referent (`x`) immediately after scope. Does it help to update the value of referent (`x`) immediately after
@ -420,7 +407,7 @@ https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html#mutable-ref
[[exercises]] [[exercises]]
name = "move_semantics6" name = "move_semantics6"
dir = "06_move_semantics" dir = "06_move_semantics"
mode = "run" test = false
hint = """ hint = """
To find the answer, you can consult the book section "References and Borrowing": To find the answer, you can consult the book section "References and Borrowing":
https://doc.rust-lang.org/stable/book/ch04-02-references-and-borrowing.html https://doc.rust-lang.org/stable/book/ch04-02-references-and-borrowing.html
@ -441,7 +428,6 @@ Another hint: it has to do with the `&` character."""
[[exercises]] [[exercises]]
name = "structs1" name = "structs1"
dir = "07_structs" dir = "07_structs"
mode = "test"
hint = """ hint = """
Rust has more than one type of struct. Three actually, all variants are used to Rust has more than one type of struct. Three actually, all variants are used to
package related data together. package related data together.
@ -461,7 +447,6 @@ https://doc.rust-lang.org/book/ch05-01-defining-structs.html"""
[[exercises]] [[exercises]]
name = "structs2" name = "structs2"
dir = "07_structs" dir = "07_structs"
mode = "test"
hint = """ hint = """
Creating instances of structs is easy, all you need to do is assign some values Creating instances of structs is easy, all you need to do is assign some values
to its fields. to its fields.
@ -473,7 +458,6 @@ https://doc.rust-lang.org/stable/book/ch05-01-defining-structs.html#creating-ins
[[exercises]] [[exercises]]
name = "structs3" name = "structs3"
dir = "07_structs" dir = "07_structs"
mode = "test"
hint = """ hint = """
For `is_international`: What makes a package international? Seems related to For `is_international`: What makes a package international? Seems related to
the places it goes through right? the places it goes through right?
@ -489,13 +473,13 @@ https://doc.rust-lang.org/book/ch05-03-method-syntax.html"""
[[exercises]] [[exercises]]
name = "enums1" name = "enums1"
dir = "08_enums" dir = "08_enums"
mode = "run" test = false
hint = "No hints this time ;)" hint = "No hints this time ;)"
[[exercises]] [[exercises]]
name = "enums2" name = "enums2"
dir = "08_enums" dir = "08_enums"
mode = "run" test = false
hint = """ hint = """
You can create enumerations that have different variants with different types You can create enumerations that have different variants with different types
such as no data, anonymous structs, a single string, tuples, ...etc""" such as no data, anonymous structs, a single string, tuples, ...etc"""
@ -503,7 +487,6 @@ such as no data, anonymous structs, a single string, tuples, ...etc"""
[[exercises]] [[exercises]]
name = "enums3" name = "enums3"
dir = "08_enums" dir = "08_enums"
mode = "test"
hint = """ hint = """
As a first step, you can define enums to compile this code without errors. As a first step, you can define enums to compile this code without errors.
@ -517,7 +500,7 @@ to get value in the variant."""
[[exercises]] [[exercises]]
name = "strings1" name = "strings1"
dir = "09_strings" dir = "09_strings"
mode = "run" test = false
hint = """ hint = """
The `current_favorite_color` function is currently returning a string slice The `current_favorite_color` function is currently returning a string slice
with the `'static` lifetime. We know this because the data of the string lives with the `'static` lifetime. We know this because the data of the string lives
@ -531,7 +514,7 @@ another way that uses the `From` trait."""
[[exercises]] [[exercises]]
name = "strings2" name = "strings2"
dir = "09_strings" dir = "09_strings"
mode = "run" test = false
hint = """ hint = """
Yes, it would be really easy to fix this by just changing the value bound to Yes, it would be really easy to fix this by just changing the value bound to
`word` to be a string slice instead of a `String`, wouldn't it?? There is a way `word` to be a string slice instead of a `String`, wouldn't it?? There is a way
@ -546,7 +529,6 @@ https://doc.rust-lang.org/stable/book/ch15-02-deref.html#implicit-deref-coercion
[[exercises]] [[exercises]]
name = "strings3" name = "strings3"
dir = "09_strings" dir = "09_strings"
mode = "test"
hint = """ hint = """
There's tons of useful standard library functions for strings. Let's try and use some of them: There's tons of useful standard library functions for strings. Let's try and use some of them:
https://doc.rust-lang.org/std/string/struct.String.html#method.trim https://doc.rust-lang.org/std/string/struct.String.html#method.trim
@ -557,7 +539,7 @@ the string slice into an owned string, which you can then freely extend."""
[[exercises]] [[exercises]]
name = "strings4" name = "strings4"
dir = "09_strings" dir = "09_strings"
mode = "run" test = false
hint = "No hints this time ;)" hint = "No hints this time ;)"
# MODULES # MODULES
@ -565,7 +547,7 @@ hint = "No hints this time ;)"
[[exercises]] [[exercises]]
name = "modules1" name = "modules1"
dir = "10_modules" dir = "10_modules"
mode = "run" test = false
hint = """ hint = """
Everything is private in Rust by default-- but there's a keyword we can use Everything is private in Rust by default-- but there's a keyword we can use
to make something public! The compiler error should point to the thing that to make something public! The compiler error should point to the thing that
@ -574,7 +556,7 @@ needs to be public."""
[[exercises]] [[exercises]]
name = "modules2" name = "modules2"
dir = "10_modules" dir = "10_modules"
mode = "run" test = false
hint = """ hint = """
The delicious_snacks module is trying to present an external interface that is The delicious_snacks module is trying to present an external interface that is
different than its internal structure (the `fruits` and `veggies` modules and different than its internal structure (the `fruits` and `veggies` modules and
@ -586,7 +568,7 @@ Learn more at https://doc.rust-lang.org/book/ch07-04-bringing-paths-into-scope-w
[[exercises]] [[exercises]]
name = "modules3" name = "modules3"
dir = "10_modules" dir = "10_modules"
mode = "run" test = false
hint = """ hint = """
`UNIX_EPOCH` and `SystemTime` are declared in the `std::time` module. Add a `UNIX_EPOCH` and `SystemTime` are declared in the `std::time` module. Add a
`use` statement for these two to bring them into scope. You can use nested `use` statement for these two to bring them into scope. You can use nested
@ -597,7 +579,6 @@ paths or the glob operator to bring these two in using only one line."""
[[exercises]] [[exercises]]
name = "hashmaps1" name = "hashmaps1"
dir = "11_hashmaps" dir = "11_hashmaps"
mode = "test"
hint = """ hint = """
Hint 1: Take a look at the return type of the function to figure out Hint 1: Take a look at the return type of the function to figure out
the type for the `basket`. the type for the `basket`.
@ -609,7 +590,6 @@ Hint 2: Number of fruits should be at least 5. And you have to put
[[exercises]] [[exercises]]
name = "hashmaps2" name = "hashmaps2"
dir = "11_hashmaps" dir = "11_hashmaps"
mode = "test"
hint = """ hint = """
Use the `entry()` and `or_insert()` methods of `HashMap` to achieve this. Use the `entry()` and `or_insert()` methods of `HashMap` to achieve this.
Learn more at https://doc.rust-lang.org/stable/book/ch08-03-hash-maps.html#only-inserting-a-value-if-the-key-has-no-value Learn more at https://doc.rust-lang.org/stable/book/ch08-03-hash-maps.html#only-inserting-a-value-if-the-key-has-no-value
@ -618,7 +598,6 @@ Learn more at https://doc.rust-lang.org/stable/book/ch08-03-hash-maps.html#only-
[[exercises]] [[exercises]]
name = "hashmaps3" name = "hashmaps3"
dir = "11_hashmaps" dir = "11_hashmaps"
mode = "test"
hint = """ hint = """
Hint 1: Use the `entry()` and `or_insert()` methods of `HashMap` to insert Hint 1: Use the `entry()` and `or_insert()` methods of `HashMap` to insert
entries corresponding to each team in the scores table. entries corresponding to each team in the scores table.
@ -636,7 +615,6 @@ Learn more at https://doc.rust-lang.org/book/ch08-03-hash-maps.html#updating-a-v
[[exercises]] [[exercises]]
name = "quiz2" name = "quiz2"
dir = "quizzes" dir = "quizzes"
mode = "test"
hint = "No hints this time ;)" hint = "No hints this time ;)"
# OPTIONS # OPTIONS
@ -644,7 +622,6 @@ hint = "No hints this time ;)"
[[exercises]] [[exercises]]
name = "options1" name = "options1"
dir = "12_options" dir = "12_options"
mode = "test"
hint = """ hint = """
Options can have a `Some` value, with an inner value, or a `None` value, Options can have a `Some` value, with an inner value, or a `None` value,
without an inner value. without an inner value.
@ -656,7 +633,6 @@ it doesn't panic in your face later?"""
[[exercises]] [[exercises]]
name = "options2" name = "options2"
dir = "12_options" dir = "12_options"
mode = "test"
hint = """ hint = """
Check out: Check out:
@ -673,7 +649,7 @@ Also see `Option::flatten`
[[exercises]] [[exercises]]
name = "options3" name = "options3"
dir = "12_options" dir = "12_options"
mode = "run" test = false
hint = """ hint = """
The compiler says a partial move happened in the `match` statement. How can The compiler says a partial move happened in the `match` statement. How can
this be avoided? The compiler shows the correction needed. this be avoided? The compiler shows the correction needed.
@ -686,7 +662,6 @@ https://doc.rust-lang.org/std/keyword.ref.html"""
[[exercises]] [[exercises]]
name = "errors1" name = "errors1"
dir = "13_error_handling" dir = "13_error_handling"
mode = "test"
hint = """ hint = """
`Ok` and `Err` are the two variants of `Result`, so what the tests are saying `Ok` and `Err` are the two variants of `Result`, so what the tests are saying
is that `generate_nametag_text` should return a `Result` instead of an `Option`. is that `generate_nametag_text` should return a `Result` instead of an `Option`.
@ -702,7 +677,6 @@ To make this change, you'll need to:
[[exercises]] [[exercises]]
name = "errors2" name = "errors2"
dir = "13_error_handling" dir = "13_error_handling"
mode = "test"
hint = """ hint = """
One way to handle this is using a `match` statement on One way to handle this is using a `match` statement on
`item_quantity.parse::<i32>()` where the cases are `Ok(something)` and `item_quantity.parse::<i32>()` where the cases are `Ok(something)` and
@ -718,7 +692,7 @@ and give it a try!"""
[[exercises]] [[exercises]]
name = "errors3" name = "errors3"
dir = "13_error_handling" dir = "13_error_handling"
mode = "run" test = false
hint = """ hint = """
If other functions can return a `Result`, why shouldn't `main`? It's a fairly If other functions can return a `Result`, why shouldn't `main`? It's a fairly
common convention to return something like `Result<(), ErrorType>` from your common convention to return something like `Result<(), ErrorType>` from your
@ -730,7 +704,6 @@ positive results."""
[[exercises]] [[exercises]]
name = "errors4" name = "errors4"
dir = "13_error_handling" dir = "13_error_handling"
mode = "test"
hint = """ hint = """
`PositiveNonzeroInteger::new` is always creating a new instance and returning `PositiveNonzeroInteger::new` is always creating a new instance and returning
an `Ok` result. an `Ok` result.
@ -742,7 +715,7 @@ everything is... okay :)"""
[[exercises]] [[exercises]]
name = "errors5" name = "errors5"
dir = "13_error_handling" dir = "13_error_handling"
mode = "run" test = false
hint = """ hint = """
There are two different possible `Result` types produced within `main()`, which There are two different possible `Result` types produced within `main()`, which
are propagated using `?` operators. How do we declare a return type from are propagated using `?` operators. How do we declare a return type from
@ -766,7 +739,6 @@ https://doc.rust-lang.org/stable/rust-by-example/error/multiple_error_types/reen
[[exercises]] [[exercises]]
name = "errors6" name = "errors6"
dir = "13_error_handling" dir = "13_error_handling"
mode = "test"
hint = """ hint = """
This exercise uses a completed version of `PositiveNonzeroInteger` from This exercise uses a completed version of `PositiveNonzeroInteger` from
errors4. errors4.
@ -788,7 +760,7 @@ https://doc.rust-lang.org/std/result/enum.Result.html#method.map_err"""
[[exercises]] [[exercises]]
name = "generics1" name = "generics1"
dir = "14_generics" dir = "14_generics"
mode = "run" test = false
hint = """ hint = """
Vectors in Rust make use of generics to create dynamically sized arrays of any Vectors in Rust make use of generics to create dynamically sized arrays of any
type. type.
@ -798,7 +770,6 @@ You need to tell the compiler what type we are pushing onto this vector."""
[[exercises]] [[exercises]]
name = "generics2" name = "generics2"
dir = "14_generics" dir = "14_generics"
mode = "test"
hint = """ hint = """
Currently we are wrapping only values of type `u32`. Currently we are wrapping only values of type `u32`.
@ -812,7 +783,6 @@ If you are still stuck https://doc.rust-lang.org/stable/book/ch10-01-syntax.html
[[exercises]] [[exercises]]
name = "traits1" name = "traits1"
dir = "15_traits" dir = "15_traits"
mode = "test"
hint = """ hint = """
A discussion about Traits in Rust can be found at: A discussion about Traits in Rust can be found at:
https://doc.rust-lang.org/book/ch10-02-traits.html https://doc.rust-lang.org/book/ch10-02-traits.html
@ -821,7 +791,6 @@ https://doc.rust-lang.org/book/ch10-02-traits.html
[[exercises]] [[exercises]]
name = "traits2" name = "traits2"
dir = "15_traits" dir = "15_traits"
mode = "test"
hint = """ hint = """
Notice how the trait takes ownership of `self`, and returns `Self`. Notice how the trait takes ownership of `self`, and returns `Self`.
@ -834,7 +803,6 @@ the documentation at: https://doc.rust-lang.org/std/vec/struct.Vec.html"""
[[exercises]] [[exercises]]
name = "traits3" name = "traits3"
dir = "15_traits" dir = "15_traits"
mode = "test"
hint = """ hint = """
Traits can have a default implementation for functions. Structs that implement Traits can have a default implementation for functions. Structs that implement
the trait can then use the default version of these functions if they choose not the trait can then use the default version of these functions if they choose not
@ -846,7 +814,6 @@ See the documentation at: https://doc.rust-lang.org/book/ch10-02-traits.html#def
[[exercises]] [[exercises]]
name = "traits4" name = "traits4"
dir = "15_traits" dir = "15_traits"
mode = "test"
hint = """ hint = """
Instead of using concrete types as parameters you can use traits. Try replacing Instead of using concrete types as parameters you can use traits. Try replacing
the '??' with 'impl <what goes here?>' the '??' with 'impl <what goes here?>'
@ -857,7 +824,7 @@ See the documentation at: https://doc.rust-lang.org/book/ch10-02-traits.html#tra
[[exercises]] [[exercises]]
name = "traits5" name = "traits5"
dir = "15_traits" dir = "15_traits"
mode = "run" test = false
hint = """ hint = """
To ensure a parameter implements multiple traits use the '+ syntax'. Try To ensure a parameter implements multiple traits use the '+ syntax'. Try
replacing the '??' with 'impl <> + <>'. replacing the '??' with 'impl <> + <>'.
@ -870,7 +837,6 @@ See the documentation at: https://doc.rust-lang.org/book/ch10-02-traits.html#spe
[[exercises]] [[exercises]]
name = "quiz3" name = "quiz3"
dir = "quizzes" dir = "quizzes"
mode = "test"
hint = """ hint = """
To find the best solution to this challenge you're going to need to think back 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' to your knowledge of traits, specifically 'Trait Bound Syntax'
@ -882,7 +848,7 @@ You may also need this: `use std::fmt::Display;`."""
[[exercises]] [[exercises]]
name = "lifetimes1" name = "lifetimes1"
dir = "16_lifetimes" dir = "16_lifetimes"
mode = "run" test = false
hint = """ hint = """
Let the compiler guide you. Also take a look at the book if you need help: Let the compiler guide you. Also take a look at the book if you need help:
https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html""" https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html"""
@ -890,7 +856,7 @@ https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html"""
[[exercises]] [[exercises]]
name = "lifetimes2" name = "lifetimes2"
dir = "16_lifetimes" dir = "16_lifetimes"
mode = "run" test = false
hint = """ hint = """
Remember that the generic lifetime `'a` will get the concrete lifetime that is Remember that the generic lifetime `'a` will get the concrete lifetime that is
equal to the smaller of the lifetimes of `x` and `y`. equal to the smaller of the lifetimes of `x` and `y`.
@ -904,7 +870,7 @@ inner block:
[[exercises]] [[exercises]]
name = "lifetimes3" name = "lifetimes3"
dir = "16_lifetimes" dir = "16_lifetimes"
mode = "run" test = false
hint = """ hint = """
If you use a lifetime annotation in a struct's fields, where else does it need If you use a lifetime annotation in a struct's fields, where else does it need
to be added?""" to be added?"""
@ -914,7 +880,6 @@ to be added?"""
[[exercises]] [[exercises]]
name = "tests1" name = "tests1"
dir = "17_tests" dir = "17_tests"
mode = "test"
hint = """ hint = """
You don't even need to write any code to test -- you can just test values and You don't even need to write any code to test -- you can just test values and
run that, even though you wouldn't do that in real life. :) run that, even though you wouldn't do that in real life. :)
@ -929,7 +894,6 @@ ones pass, and which ones fail :)"""
[[exercises]] [[exercises]]
name = "tests2" name = "tests2"
dir = "17_tests" dir = "17_tests"
mode = "test"
hint = """ hint = """
Like the previous exercise, you don't need to write any code to get this test Like the previous exercise, you don't need to write any code to get this test
to compile and run. to compile and run.
@ -942,7 +906,6 @@ argument comes first and which comes second!"""
[[exercises]] [[exercises]]
name = "tests3" name = "tests3"
dir = "17_tests" dir = "17_tests"
mode = "test"
hint = """ hint = """
You can call a function right where you're passing arguments to `assert!`. So You can call a function right where you're passing arguments to `assert!`. So
you could do something like `assert!(having_fun())`. you could do something like `assert!(having_fun())`.
@ -953,7 +916,6 @@ what you're doing using `!`, like `assert!(!having_fun())`."""
[[exercises]] [[exercises]]
name = "tests4" name = "tests4"
dir = "17_tests" dir = "17_tests"
mode = "test"
hint = """ hint = """
We expect method `Rectangle::new()` to panic for negative values. We expect method `Rectangle::new()` to panic for negative values.
@ -967,7 +929,6 @@ https://doc.rust-lang.org/stable/book/ch11-01-writing-tests.html#checking-for-pa
[[exercises]] [[exercises]]
name = "iterators1" name = "iterators1"
dir = "18_iterators" dir = "18_iterators"
mode = "test"
hint = """ hint = """
Step 1: Step 1:
@ -990,7 +951,6 @@ https://doc.rust-lang.org/std/iter/trait.Iterator.html for some ideas.
[[exercises]] [[exercises]]
name = "iterators2" name = "iterators2"
dir = "18_iterators" dir = "18_iterators"
mode = "test"
hint = """ hint = """
Step 1: Step 1:
@ -1016,7 +976,6 @@ powerful and very general. Rust just needs to know the desired type."""
[[exercises]] [[exercises]]
name = "iterators3" name = "iterators3"
dir = "18_iterators" dir = "18_iterators"
mode = "test"
hint = """ hint = """
The `divide` function needs to return the correct error when even division is The `divide` function needs to return the correct error when even division is
not possible. not possible.
@ -1035,7 +994,6 @@ powerful! It can make the solution to this exercise infinitely easier."""
[[exercises]] [[exercises]]
name = "iterators4" name = "iterators4"
dir = "18_iterators" dir = "18_iterators"
mode = "test"
hint = """ hint = """
In an imperative language, you might write a `for` loop that updates a mutable In an imperative language, you might write a `for` loop that updates a mutable
variable. Or, you might write code utilizing recursion and a match clause. In variable. Or, you might write code utilizing recursion and a match clause. In
@ -1047,7 +1005,6 @@ Hint 2: Check out the `fold` and `rfold` methods!"""
[[exercises]] [[exercises]]
name = "iterators5" name = "iterators5"
dir = "18_iterators" dir = "18_iterators"
mode = "test"
hint = """ hint = """
The documentation for the `std::iter::Iterator` trait contains numerous methods The documentation for the `std::iter::Iterator` trait contains numerous methods
that would be helpful here. that would be helpful here.
@ -1066,7 +1023,6 @@ a different method that could make your code more compact than using `fold`."""
[[exercises]] [[exercises]]
name = "box1" name = "box1"
dir = "19_smart_pointers" dir = "19_smart_pointers"
mode = "test"
hint = """ hint = """
Step 1: Step 1:
@ -1090,7 +1046,6 @@ definition and try other types!
[[exercises]] [[exercises]]
name = "rc1" name = "rc1"
dir = "19_smart_pointers" dir = "19_smart_pointers"
mode = "test"
hint = """ hint = """
This is a straightforward exercise to use the `Rc<T>` type. Each `Planet` has This is a straightforward exercise to use the `Rc<T>` type. Each `Planet` has
ownership of the `Sun`, and uses `Rc::clone()` to increment the reference count ownership of the `Sun`, and uses `Rc::clone()` to increment the reference count
@ -1109,7 +1064,7 @@ See more at: https://doc.rust-lang.org/book/ch15-04-rc.html
[[exercises]] [[exercises]]
name = "arc1" name = "arc1"
dir = "19_smart_pointers" dir = "19_smart_pointers"
mode = "run" test = false
hint = """ hint = """
Make `shared_numbers` be an `Arc` from the numbers vector. Then, in order Make `shared_numbers` be an `Arc` from the numbers vector. Then, in order
to avoid creating a copy of `numbers`, you'll need to create `child_numbers` to avoid creating a copy of `numbers`, you'll need to create `child_numbers`
@ -1127,7 +1082,6 @@ https://doc.rust-lang.org/stable/book/ch16-00-concurrency.html
[[exercises]] [[exercises]]
name = "cow1" name = "cow1"
dir = "19_smart_pointers" dir = "19_smart_pointers"
mode = "test"
hint = """ hint = """
If `Cow` already owns the data it doesn't need to clone it when `to_mut()` is If `Cow` already owns the data it doesn't need to clone it when `to_mut()` is
called. called.
@ -1141,7 +1095,7 @@ on the `Cow` type.
[[exercises]] [[exercises]]
name = "threads1" name = "threads1"
dir = "20_threads" dir = "20_threads"
mode = "run" test = false
hint = """ hint = """
`JoinHandle` is a struct that is returned from a spawned thread: `JoinHandle` is a struct that is returned from a spawned thread:
https://doc.rust-lang.org/std/thread/fn.spawn.html https://doc.rust-lang.org/std/thread/fn.spawn.html
@ -1159,7 +1113,7 @@ https://doc.rust-lang.org/std/thread/struct.JoinHandle.html
[[exercises]] [[exercises]]
name = "threads2" name = "threads2"
dir = "20_threads" dir = "20_threads"
mode = "run" test = false
hint = """ hint = """
`Arc` is an Atomic Reference Counted pointer that allows safe, shared access `Arc` is an Atomic Reference Counted pointer that allows safe, shared access
to **immutable** data. But we want to *change* the number of `jobs_completed` to **immutable** data. But we want to *change* the number of `jobs_completed`
@ -1181,7 +1135,6 @@ https://doc.rust-lang.org/book/ch16-03-shared-state.html#sharing-a-mutext-betwee
[[exercises]] [[exercises]]
name = "threads3" name = "threads3"
dir = "20_threads" dir = "20_threads"
mode = "test"
hint = """ hint = """
An alternate way to handle concurrency between threads is to use an `mpsc` An alternate way to handle concurrency between threads is to use an `mpsc`
(multiple producer, single consumer) channel to communicate. (multiple producer, single consumer) channel to communicate.
@ -1200,7 +1153,7 @@ See https://doc.rust-lang.org/book/ch16-02-message-passing.html for more info.
[[exercises]] [[exercises]]
name = "macros1" name = "macros1"
dir = "21_macros" dir = "21_macros"
mode = "run" test = false
hint = """ hint = """
When you call a macro, you need to add something special compared to a When you call a macro, you need to add something special compared to a
regular function call. If you're stuck, take a look at what's inside regular function call. If you're stuck, take a look at what's inside
@ -1209,7 +1162,7 @@ regular function call. If you're stuck, take a look at what's inside
[[exercises]] [[exercises]]
name = "macros2" name = "macros2"
dir = "21_macros" dir = "21_macros"
mode = "run" test = false
hint = """ hint = """
Macros don't quite play by the same rules as the rest of Rust, in terms of Macros don't quite play by the same rules as the rest of Rust, in terms of
what's available where. what's available where.
@ -1220,7 +1173,7 @@ Unlike other things in Rust, the order of "where you define a macro" versus
[[exercises]] [[exercises]]
name = "macros3" name = "macros3"
dir = "21_macros" dir = "21_macros"
mode = "run" test = false
hint = """ hint = """
In order to use a macro outside of its module, you need to do something In order to use a macro outside of its module, you need to do something
special to the module to lift the macro out into its parent. special to the module to lift the macro out into its parent.
@ -1231,7 +1184,7 @@ exported macros, if you've seen any of those around."""
[[exercises]] [[exercises]]
name = "macros4" name = "macros4"
dir = "21_macros" dir = "21_macros"
mode = "run" test = false
hint = """ hint = """
You only need to add a single character to make this compile. You only need to add a single character to make this compile.
@ -1248,7 +1201,8 @@ https://veykril.github.io/tlborm/"""
[[exercises]] [[exercises]]
name = "clippy1" name = "clippy1"
dir = "22_clippy" dir = "22_clippy"
mode = "clippy" test = false
strict_clippy = true
hint = """ hint = """
Rust stores the highest precision version of any long or infinite precision Rust stores the highest precision version of any long or infinite precision
mathematical constants in the Rust standard library: mathematical constants in the Rust standard library:
@ -1264,14 +1218,16 @@ appropriate replacement constant from `std::f32::consts`..."""
[[exercises]] [[exercises]]
name = "clippy2" name = "clippy2"
dir = "22_clippy" dir = "22_clippy"
mode = "clippy" test = false
strict_clippy = true
hint = """ hint = """
`for` loops over `Option` values are more clearly expressed as an `if let`""" `for` loops over `Option` values are more clearly expressed as an `if let`"""
[[exercises]] [[exercises]]
name = "clippy3" name = "clippy3"
dir = "22_clippy" dir = "22_clippy"
mode = "clippy" test = false
strict_clippy = true
hint = "No hints this time!" hint = "No hints this time!"
# TYPE CONVERSIONS # TYPE CONVERSIONS
@ -1279,7 +1235,6 @@ hint = "No hints this time!"
[[exercises]] [[exercises]]
name = "using_as" name = "using_as"
dir = "23_conversions" dir = "23_conversions"
mode = "test"
hint = """ hint = """
Use the `as` operator to cast one of the operands in the last line of the Use the `as` operator to cast one of the operands in the last line of the
`average` function into the expected return type.""" `average` function into the expected return type."""
@ -1287,14 +1242,12 @@ Use the `as` operator to cast one of the operands in the last line of the
[[exercises]] [[exercises]]
name = "from_into" name = "from_into"
dir = "23_conversions" dir = "23_conversions"
mode = "test"
hint = """ hint = """
Follow the steps provided right before the `From` implementation""" Follow the steps provided right before the `From` implementation"""
[[exercises]] [[exercises]]
name = "from_str" name = "from_str"
dir = "23_conversions" dir = "23_conversions"
mode = "test"
hint = """ hint = """
The implementation of `FromStr` should return an `Ok` with a `Person` object, The implementation of `FromStr` should return an `Ok` with a `Person` object,
or an `Err` with an error if the string is not valid. or an `Err` with an error if the string is not valid.
@ -1315,7 +1268,6 @@ https://doc.rust-lang.org/stable/rust-by-example/error/multiple_error_types/reen
[[exercises]] [[exercises]]
name = "try_from_into" name = "try_from_into"
dir = "23_conversions" dir = "23_conversions"
mode = "test"
hint = """ hint = """
Follow the steps provided right before the `TryFrom` implementation. Follow the steps provided right before the `TryFrom` implementation.
You can also use the example at You can also use the example at
@ -1338,6 +1290,5 @@ Challenge: Can you make the `TryFrom` implementations generic over many integer
[[exercises]] [[exercises]]
name = "as_ref_mut" name = "as_ref_mut"
dir = "23_conversions" dir = "23_conversions"
mode = "test"
hint = """ hint = """
Add `AsRef<str>` or `AsMut<u32>` as a trait bound to the functions.""" Add `AsRef<str>` or `AsMut<u32>` as a trait bound to the functions."""

View file

@ -11,7 +11,12 @@ use std::{
process::{Command, Stdio}, process::{Command, Stdio},
}; };
use crate::{embedded::EMBEDDED_FILES, exercise::Exercise, info_file::ExerciseInfo, DEBUG_PROFILE}; use crate::{
embedded::EMBEDDED_FILES,
exercise::{Exercise, OUTPUT_CAPACITY},
info_file::ExerciseInfo,
DEBUG_PROFILE,
};
const STATE_FILE_NAME: &str = ".rustlings-state.txt"; const STATE_FILE_NAME: &str = ".rustlings-state.txt";
const BAD_INDEX_ERR: &str = "The current exercise index is higher than the number of exercises"; const BAD_INDEX_ERR: &str = "The current exercise index is higher than the number of exercises";
@ -107,7 +112,8 @@ impl AppState {
dir, dir,
name, name,
path, path,
mode: exercise_info.mode, test: exercise_info.test,
strict_clippy: exercise_info.strict_clippy,
hint, hint,
done: false, done: false,
} }
@ -302,12 +308,14 @@ impl AppState {
let Some(ind) = self.next_pending_exercise_ind() else { let Some(ind) = self.next_pending_exercise_ind() else {
writer.write_all(RERUNNING_ALL_EXERCISES_MSG)?; writer.write_all(RERUNNING_ALL_EXERCISES_MSG)?;
let mut output = Vec::with_capacity(OUTPUT_CAPACITY);
for (exercise_ind, exercise) in self.exercises().iter().enumerate() { for (exercise_ind, exercise) in self.exercises().iter().enumerate() {
writer.write_fmt(format_args!("Running {exercise} ... "))?; write!(writer, "Running {exercise} ... ")?;
writer.flush()?; writer.flush()?;
if !exercise.run()?.status.success() { let success = exercise.run(&mut output)?;
writer.write_fmt(format_args!("{}\n\n", "FAILED".red()))?; if !success {
writeln!(writer, "{}\n", "FAILED".red())?;
self.current_exercise_ind = exercise_ind; self.current_exercise_ind = exercise_ind;
@ -321,7 +329,7 @@ impl AppState {
return Ok(ExercisesProgress::Pending); return Ok(ExercisesProgress::Pending);
} }
writer.write_fmt(format_args!("{}\n", "ok".green()))?; writeln!(writer, "{}", "ok".green())?;
} }
writer.execute(Clear(ClearType::All))?; writer.execute(Clear(ClearType::All))?;

View file

@ -99,10 +99,15 @@ name = "???"
# Otherwise, the path is `exercises/NAME.rs` # Otherwise, the path is `exercises/NAME.rs`
# dir = "???" # dir = "???"
# The mode to run the exercise in. # Rustlings expects the exercise to contain tests and run them.
# The mode "test" (preferred) runs the exercise's tests. # You can optionally disable testing by setting `test` to `false` (the default is `true`).
# The mode "run" only checks if the exercise compiles and runs it. # In that case, the exercise will be considered done when it just successfully compiles.
mode = "test" # test = true
# Rustlings will always run Clippy on exercises.
# You can optionally set `strict_clippy` to `true` (the default is `false`) to only consider
# the exercise as done when there are no warnings left.
# strict_clippy = false
# A multi-line hint to be shown to users on request. # A multi-line hint to be shown to users on request.
hint = """???""" hint = """???"""

View file

@ -2,11 +2,55 @@ use anyhow::{Context, Result};
use crossterm::style::{style, StyledContent, Stylize}; use crossterm::style::{style, StyledContent, Stylize};
use std::{ use std::{
fmt::{self, Display, Formatter}, fmt::{self, Display, Formatter},
path::Path, io::{Read, Write},
process::{Command, Output}, process::{Command, Stdio},
}; };
use crate::{info_file::Mode, terminal_link::TerminalFileLink, DEBUG_PROFILE}; use crate::{in_official_repo, terminal_link::TerminalFileLink, DEBUG_PROFILE};
pub const OUTPUT_CAPACITY: usize = 1 << 14;
fn run_command(
mut cmd: Command,
cmd_description: &str,
output: &mut Vec<u8>,
stderr: bool,
) -> Result<bool> {
let (mut reader, writer) = os_pipe::pipe().with_context(|| {
format!("Failed to create a pipe to run the command `{cmd_description}``")
})?;
let (stdout, stderr) = if stderr {
(
Stdio::from(writer.try_clone().with_context(|| {
format!("Failed to clone the pipe writer for the command `{cmd_description}`")
})?),
Stdio::from(writer),
)
} else {
(Stdio::from(writer), Stdio::null())
};
let mut handle = cmd
.stdout(stdout)
.stderr(stderr)
.spawn()
.with_context(|| format!("Failed to run the command `{cmd_description}`"))?;
// Prevent pipe deadlock.
drop(cmd);
reader
.read_to_end(output)
.with_context(|| format!("Failed to read the output of the command `{cmd_description}`"))?;
output.push(b'\n');
handle
.wait()
.with_context(|| format!("Failed to wait on the command `{cmd_description}` to exit"))
.map(|status| status.success())
}
pub struct Exercise { pub struct Exercise {
pub dir: Option<&'static str>, pub dir: Option<&'static str>,
@ -14,21 +58,51 @@ pub struct Exercise {
pub name: &'static str, pub name: &'static str,
// Exercise's path // Exercise's path
pub path: &'static str, pub path: &'static str,
// The mode of the exercise pub test: bool,
pub mode: Mode, pub strict_clippy: bool,
// The hint text associated with the exercise // The hint text associated with the exercise
pub hint: String, pub hint: String,
pub done: bool, pub done: bool,
} }
impl Exercise { impl Exercise {
fn cargo_cmd(&self, command: &str, args: &[&str]) -> Result<Output> { fn run_bin(&self, output: &mut Vec<u8>) -> Result<bool> {
writeln!(output, "{}", "Output".underlined())?;
let bin_path = format!("target/debug/{}", self.name);
let success = run_command(Command::new(&bin_path), &bin_path, output, true)?;
if !success {
writeln!(
output,
"{}",
"The exercise didn't run successfully (nonzero exit code)"
.bold()
.red()
)?;
}
Ok(success)
}
fn cargo_cmd(
&self,
command: &str,
args: &[&str],
cmd_description: &str,
output: &mut Vec<u8>,
dev: bool,
stderr: bool,
) -> Result<bool> {
let mut cmd = Command::new("cargo"); let mut cmd = Command::new("cargo");
cmd.arg(command); cmd.arg(command);
// A hack to make `cargo run` work when developing Rustlings. // A hack to make `cargo run` work when developing Rustlings.
if DEBUG_PROFILE && Path::new("tests").exists() { if dev {
cmd.arg("--manifest-path").arg("dev/Cargo.toml"); cmd.arg("--manifest-path")
.arg("dev/Cargo.toml")
.arg("--target-dir")
.arg("target");
} }
cmd.arg("--color") cmd.arg("--color")
@ -36,30 +110,60 @@ impl Exercise {
.arg("-q") .arg("-q")
.arg("--bin") .arg("--bin")
.arg(self.name) .arg(self.name)
.args(args) .args(args);
.output()
.context("Failed to run Cargo") run_command(cmd, cmd_description, output, stderr)
} }
pub fn run(&self) -> Result<Output> { pub fn run(&self, output: &mut Vec<u8>) -> Result<bool> {
match self.mode { output.clear();
Mode::Run => self.cargo_cmd("run", &[]),
Mode::Test => self.cargo_cmd( // Developing the official Rustlings.
"test", let dev = DEBUG_PROFILE && in_official_repo();
&[
"--", let build_success = self.cargo_cmd("build", &[], "cargo build …", output, dev, true)?;
"--color", if !build_success {
"always", return Ok(false);
"--nocapture",
"--format",
"pretty",
],
),
Mode::Clippy => self.cargo_cmd(
"clippy",
&["--", "-D", "warnings", "-D", "clippy::float_cmp"],
),
} }
// Discard the output of `cargo build` because it will be shown again by the Cargo command.
output.clear();
let clippy_args: &[&str] = if self.strict_clippy {
&["--", "-D", "warnings"]
} else {
&[]
};
let clippy_success =
self.cargo_cmd("clippy", clippy_args, "cargo clippy …", output, dev, true)?;
if !clippy_success {
return Ok(false);
}
if !self.test {
return self.run_bin(output);
}
let test_success = self.cargo_cmd(
"test",
&[
"--",
"--color",
"always",
"--nocapture",
"--format",
"pretty",
],
"cargo test …",
output,
dev,
// Hide warnings because they are shown by Clippy.
false,
)?;
let run_success = self.run_bin(output)?;
Ok(test_success && run_success)
} }
pub fn terminal_link(&self) -> StyledContent<TerminalFileLink<'_>> { pub fn terminal_link(&self) -> StyledContent<TerminalFileLink<'_>> {

View file

@ -2,18 +2,6 @@ use anyhow::{bail, Context, Error, Result};
use serde::Deserialize; use serde::Deserialize;
use std::{fs, io::ErrorKind}; use std::{fs, io::ErrorKind};
// The mode of the exercise.
#[derive(Deserialize, Copy, Clone)]
#[serde(rename_all = "lowercase")]
pub enum Mode {
// The exercise should be compiled as a binary
Run,
// The exercise should be compiled as a test harness
Test,
// The exercise should be linted with clippy
Clippy,
}
// Deserialized from the `info.toml` file. // Deserialized from the `info.toml` file.
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct ExerciseInfo { pub struct ExerciseInfo {
@ -21,11 +9,17 @@ pub struct ExerciseInfo {
pub name: String, pub name: String,
// The exercise's directory inside the `exercises` directory // The exercise's directory inside the `exercises` directory
pub dir: Option<String>, pub dir: Option<String>,
// The mode of the exercise #[serde(default = "default_true")]
pub mode: Mode, pub test: bool,
#[serde(default)]
pub strict_clippy: bool,
// The hint text associated with the exercise // The hint text associated with the exercise
pub hint: String, pub hint: String,
} }
#[inline]
const fn default_true() -> bool {
true
}
impl ExerciseInfo { impl ExerciseInfo {
pub fn path(&self) -> String { pub fn path(&self) -> String {

View file

@ -231,8 +231,7 @@ impl<'a> UiState<'a> {
.context("Invalid selection index")?; .context("Invalid selection index")?;
let exercise_path = self.app_state.reset_exercise_by_ind(ind)?; let exercise_path = self.app_state.reset_exercise_by_ind(ind)?;
self.message write!(self.message, "The exercise {exercise_path} has been reset")?;
.write_fmt(format_args!("The exercise {exercise_path} has been reset"))?;
Ok(self.with_updated_rows()) Ok(self.with_updated_rows())
} }

View file

@ -75,10 +75,14 @@ enum Subcommands {
Dev(DevCommands), Dev(DevCommands),
} }
fn in_official_repo() -> bool {
Path::new("dev/rustlings-repo.txt").exists()
}
fn main() -> Result<()> { fn main() -> Result<()> {
let args = Args::parse(); let args = Args::parse();
if !DEBUG_PROFILE && Path::new("dev/rustlings-repo.txt").exists() { if !DEBUG_PROFILE && in_official_repo() {
bail!("{OLD_METHOD_ERR}"); bail!("{OLD_METHOD_ERR}");
} }

View file

@ -4,20 +4,19 @@ use std::io::{self, Write};
use crate::{ use crate::{
app_state::{AppState, ExercisesProgress}, app_state::{AppState, ExercisesProgress},
exercise::OUTPUT_CAPACITY,
terminal_link::TerminalFileLink, terminal_link::TerminalFileLink,
}; };
pub fn run(app_state: &mut AppState) -> Result<()> { pub fn run(app_state: &mut AppState) -> Result<()> {
let exercise = app_state.current_exercise(); let exercise = app_state.current_exercise();
let output = exercise.run()?; let mut output = Vec::with_capacity(OUTPUT_CAPACITY);
let success = exercise.run(&mut output)?;
let mut stdout = io::stdout().lock(); let mut stdout = io::stdout().lock();
stdout.write_all(&output.stdout)?; stdout.write_all(&output)?;
stdout.write_all(b"\n")?;
stdout.write_all(&output.stderr)?;
stdout.flush()?;
if !output.status.success() { if !success {
app_state.set_pending(app_state.current_exercise_ind())?; app_state.set_pending(app_state.current_exercise_ind())?;
bail!( bail!(
@ -26,11 +25,12 @@ pub fn run(app_state: &mut AppState) -> Result<()> {
); );
} }
stdout.write_fmt(format_args!( writeln!(
"{}{}\n", stdout,
"{}{}",
"✓ Successfully ran ".green(), "✓ Successfully ran ".green(),
exercise.path.green(), exercise.path.green(),
))?; )?;
if let Some(solution_path) = app_state.current_solution_path()? { if let Some(solution_path) = app_state.current_solution_path()? {
println!( println!(

View file

@ -51,7 +51,7 @@ pub fn watch(
// Otherwise, the file watcher exits. // Otherwise, the file watcher exits.
let _debouncer_guard = if let Some(exercise_paths) = notify_exercise_paths { let _debouncer_guard = if let Some(exercise_paths) = notify_exercise_paths {
let mut debouncer = new_debouncer( let mut debouncer = new_debouncer(
Duration::from_secs(1), Duration::from_millis(500),
DebounceEventHandler { DebounceEventHandler {
tx: tx.clone(), tx: tx.clone(),
exercise_paths, exercise_paths,

View file

@ -8,6 +8,7 @@ use std::io::{self, StdoutLock, Write};
use crate::{ use crate::{
app_state::{AppState, ExercisesProgress}, app_state::{AppState, ExercisesProgress},
exercise::OUTPUT_CAPACITY,
progress_bar::progress_bar, progress_bar::progress_bar,
terminal_link::TerminalFileLink, terminal_link::TerminalFileLink,
}; };
@ -21,8 +22,7 @@ enum DoneStatus {
pub struct WatchState<'a> { pub struct WatchState<'a> {
writer: StdoutLock<'a>, writer: StdoutLock<'a>,
app_state: &'a mut AppState, app_state: &'a mut AppState,
stdout: Option<Vec<u8>>, output: Vec<u8>,
stderr: Option<Vec<u8>>,
show_hint: bool, show_hint: bool,
done_status: DoneStatus, done_status: DoneStatus,
manual_run: bool, manual_run: bool,
@ -35,8 +35,7 @@ impl<'a> WatchState<'a> {
Self { Self {
writer, writer,
app_state, app_state,
stdout: None, output: Vec::with_capacity(OUTPUT_CAPACITY),
stderr: None,
show_hint: false, show_hint: false,
done_status: DoneStatus::Pending, done_status: DoneStatus::Pending,
manual_run, manual_run,
@ -51,11 +50,8 @@ impl<'a> WatchState<'a> {
pub fn run_current_exercise(&mut self) -> Result<()> { pub fn run_current_exercise(&mut self) -> Result<()> {
self.show_hint = false; self.show_hint = false;
let output = self.app_state.current_exercise().run()?; let success = self.app_state.current_exercise().run(&mut self.output)?;
self.stdout = Some(output.stdout); if success {
if output.status.success() {
self.stderr = None;
self.done_status = self.done_status =
if let Some(solution_path) = self.app_state.current_solution_path()? { if let Some(solution_path) = self.app_state.current_solution_path()? {
DoneStatus::DoneWithSolution(solution_path) DoneStatus::DoneWithSolution(solution_path)
@ -66,7 +62,6 @@ impl<'a> WatchState<'a> {
self.app_state self.app_state
.set_pending(self.app_state.current_exercise_ind())?; .set_pending(self.app_state.current_exercise_ind())?;
self.stderr = Some(output.stderr);
self.done_status = DoneStatus::Pending; self.done_status = DoneStatus::Pending;
} }
@ -93,19 +88,18 @@ impl<'a> WatchState<'a> {
self.writer.write_all(b"\n")?; self.writer.write_all(b"\n")?;
if self.manual_run { if self.manual_run {
self.writer.write_fmt(format_args!("{}un/", 'r'.bold()))?; write!(self.writer, "{}un/", 'r'.bold())?;
} }
if !matches!(self.done_status, DoneStatus::Pending) { if !matches!(self.done_status, DoneStatus::Pending) {
self.writer.write_fmt(format_args!("{}ext/", 'n'.bold()))?; write!(self.writer, "{}ext/", 'n'.bold())?;
} }
if !self.show_hint { if !self.show_hint {
self.writer.write_fmt(format_args!("{}int/", 'h'.bold()))?; write!(self.writer, "{}int/", 'h'.bold())?;
} }
self.writer write!(self.writer, "{}ist/{}uit? ", 'l'.bold(), 'q'.bold())?;
.write_fmt(format_args!("{}ist/{}uit? ", 'l'.bold(), 'q'.bold()))?;
self.writer.flush() self.writer.flush()
} }
@ -116,41 +110,35 @@ impl<'a> WatchState<'a> {
self.writer.execute(Clear(ClearType::All))?; self.writer.execute(Clear(ClearType::All))?;
if let Some(stdout) = &self.stdout { self.writer.write_all(&self.output)?;
self.writer.write_all(stdout)?;
self.writer.write_all(b"\n")?;
}
if let Some(stderr) = &self.stderr {
self.writer.write_all(stderr)?;
self.writer.write_all(b"\n")?;
}
self.writer.write_all(b"\n")?; self.writer.write_all(b"\n")?;
if self.show_hint { if self.show_hint {
self.writer.write_fmt(format_args!( writeln!(
"{}\n{}\n\n", self.writer,
"{}\n{}\n",
"Hint".bold().cyan().underlined(), "Hint".bold().cyan().underlined(),
self.app_state.current_exercise().hint, self.app_state.current_exercise().hint,
))?; )?;
} }
if !matches!(self.done_status, DoneStatus::Pending) { if !matches!(self.done_status, DoneStatus::Pending) {
self.writer.write_fmt(format_args!( writeln!(
"{}\n\n", self.writer,
"{}\n",
"Exercise done ✓ "Exercise done ✓
When you are done experimenting, enter `n` or `next` to go to the next exercise 🦀" When you are done experimenting, enter `n` or `next` to go to the next exercise 🦀"
.bold() .bold()
.green(), .green(),
))?; )?;
} }
if let DoneStatus::DoneWithSolution(solution_path) = &self.done_status { if let DoneStatus::DoneWithSolution(solution_path) = &self.done_status {
self.writer.write_fmt(format_args!( writeln!(
"A solution file can be found at {}\n\n", self.writer,
"A solution file can be found at {}\n",
style(TerminalFileLink(solution_path)).underlined().green() style(TerminalFileLink(solution_path)).underlined().green()
))?; )?;
} }
let line_width = size()?.0; let line_width = size()?.0;
@ -159,10 +147,11 @@ When you are done experimenting, enter `n` or `next` to go to the next exercise
self.app_state.exercises().len() as u16, self.app_state.exercises().len() as u16,
line_width, line_width,
)?; )?;
self.writer.write_fmt(format_args!( writeln!(
"{progress_bar}Current exercise: {}\n", self.writer,
"{progress_bar}Current exercise: {}",
self.app_state.current_exercise().terminal_link(), self.app_state.current_exercise().terminal_link(),
))?; )?;
self.show_prompt()?; self.show_prompt()?;

View file

@ -1,2 +1 @@
fn main() { fn main() {}
}

View file

@ -1,3 +1,5 @@
fn main() {}
#[test] #[test]
fn passing() { fn passing() {
asset!(true); asset!(true);

View file

@ -1,3 +1,5 @@
fn main() {}
#[test] #[test]
fn not_passing() { fn not_passing() {
assert!(false); assert!(false);

View file

@ -2,10 +2,9 @@ format_version = 1
[[exercises]] [[exercises]]
name = "compFailure" name = "compFailure"
mode = "run" test = false
hint = "" hint = ""
[[exercises]] [[exercises]]
name = "testFailure" name = "testFailure"
mode = "test"
hint = "Hello!" hint = "Hello!"

View file

@ -1,5 +1 @@
// fake_exercise fn main() {}
fn main() {
}

View file

@ -1,5 +1 @@
// fake_exercise fn main() {}
fn main() {
}

View file

@ -1,2 +1,4 @@
fn main() {}
#[test] #[test]
fn it_works() {} fn it_works() {}

View file

@ -2,15 +2,14 @@ format_version = 1
[[exercises]] [[exercises]]
name = "pending_exercise" name = "pending_exercise"
mode = "run" test = false
hint = """""" hint = """"""
[[exercises]] [[exercises]]
name = "pending_test_exercise" name = "pending_test_exercise"
mode = "test"
hint = """""" hint = """"""
[[exercises]] [[exercises]]
name = "finished_exercise" name = "finished_exercise"
mode = "run" test = false
hint = """""" hint = """"""

View file

@ -1,3 +1,5 @@
fn main() {}
#[test] #[test]
fn passing() { fn passing() {
println!("THIS TEST TOO SHALL PASS"); println!("THIS TEST TOO SHALL PASS");

View file

@ -2,10 +2,9 @@ format_version = 1
[[exercises]] [[exercises]]
name = "compSuccess" name = "compSuccess"
mode = "run" test = false
hint = """""" hint = """"""
[[exercises]] [[exercises]]
name = "testSuccess" name = "testSuccess"
mode = "test"
hint = """""" hint = """"""