mirror of
https://github.com/rust-lang/rustlings.git
synced 2025-12-26 00:11:49 +02:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3309a01b5e | ||
|
|
5a03e46d69 | ||
|
|
bf95a34553 | ||
|
|
33e501981a | ||
|
|
4ee9a0f6b9 | ||
|
|
d6e26bb350 | ||
|
|
f6a37e28ba | ||
|
|
a2f66e6a67 | ||
|
|
b92e7b968f | ||
|
|
6c2630afcf | ||
|
|
86506fa5fd | ||
|
|
96098d228a | ||
|
|
d59dde320b | ||
|
|
fb7d3bf35d | ||
|
|
50272a58e2 | ||
|
|
93f60d3f8d | ||
|
|
97bf2469b6 | ||
|
|
2088dfbaf6 | ||
|
|
e889c5bb60 | ||
|
|
44d609816b | ||
|
|
99ea2cbba7 | ||
|
|
0aff5340b5 | ||
|
|
6f44cb1dd2 | ||
|
|
52a29aa84b |
@@ -1506,6 +1506,42 @@
|
||||
"contributions": [
|
||||
"content"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "magnusrodseth",
|
||||
"name": "Magnus Rødseth",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/59113973?v=4",
|
||||
"profile": "http://magnusrodseth.vercel.app",
|
||||
"contributions": [
|
||||
"content"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "rubiesonthesky",
|
||||
"name": "rubiesonthesky",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/2591240?v=4",
|
||||
"profile": "https://github.com/rubiesonthesky",
|
||||
"contributions": [
|
||||
"content"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "GabrielBianconi",
|
||||
"name": "Gabriel Bianconi",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1275491?v=4",
|
||||
"profile": "http://www.gabrielbianconi.com/",
|
||||
"contributions": [
|
||||
"content"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Kodylow",
|
||||
"name": "Kody Low",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/74332828?v=4",
|
||||
"profile": "https://github.com/Kodylow",
|
||||
"contributions": [
|
||||
"content"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 8,
|
||||
|
||||
@@ -214,6 +214,10 @@ authors.
|
||||
<td align="center"><a href="https://github.com/markusboehme"><img src="https://avatars.githubusercontent.com/u/5074759?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Markus Boehme</b></sub></a><br /><a href="https://github.com/rust-lang/rustlings/commits?author=markusboehme" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/nico-vromans"><img src="https://avatars.githubusercontent.com/u/48183857?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Nico Vromans</b></sub></a><br /><a href="#content-nico-vromans" title="Content">🖋</a></td>
|
||||
<td align="center"><a href="https://github.com/vostok92"><img src="https://avatars.githubusercontent.com/u/540339?v=4?s=100" width="100px;" alt=""/><br /><sub><b>vostok92</b></sub></a><br /><a href="#content-vostok92" title="Content">🖋</a></td>
|
||||
<td align="center"><a href="http://magnusrodseth.vercel.app"><img src="https://avatars.githubusercontent.com/u/59113973?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Magnus Rødseth</b></sub></a><br /><a href="#content-magnusrodseth" title="Content">🖋</a></td>
|
||||
<td align="center"><a href="https://github.com/rubiesonthesky"><img src="https://avatars.githubusercontent.com/u/2591240?v=4?s=100" width="100px;" alt=""/><br /><sub><b>rubiesonthesky</b></sub></a><br /><a href="#content-rubiesonthesky" title="Content">🖋</a></td>
|
||||
<td align="center"><a href="http://www.gabrielbianconi.com/"><img src="https://avatars.githubusercontent.com/u/1275491?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Gabriel Bianconi</b></sub></a><br /><a href="#content-GabrielBianconi" title="Content">🖋</a></td>
|
||||
<td align="center"><a href="https://github.com/Kodylow"><img src="https://avatars.githubusercontent.com/u/74332828?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kody Low</b></sub></a><br /><a href="#content-Kodylow" title="Content">🖋</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
17
CHANGELOG.md
17
CHANGELOG.md
@@ -1,3 +1,20 @@
|
||||
<a name="5.2.0"></a>
|
||||
## 5.2.0 (2022-08-27)
|
||||
|
||||
#### Added
|
||||
|
||||
- Added a `reset` command
|
||||
|
||||
#### Changed
|
||||
|
||||
- **options2**: Convert the exercise to use tests
|
||||
|
||||
#### Fixed
|
||||
|
||||
- **threads3**: Fixed a typo
|
||||
- **quiz1**: Adjusted the explanations to be consistent with
|
||||
the tests
|
||||
|
||||
<a name="5.1.1"></a>
|
||||
## 5.1.1 (2022-08-17)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rustlings"
|
||||
version = "5.1.1"
|
||||
version = "5.2.0"
|
||||
authors = ["Liv <mokou@fastmail.com>", "Carol (Nichols || Goulding) <carol.nichols@gmail.com>"]
|
||||
edition = "2021"
|
||||
|
||||
|
||||
@@ -3,23 +3,34 @@
|
||||
|
||||
// I AM NOT DONE
|
||||
|
||||
fn main() {
|
||||
let optional_word = Some(String::from("rustlings"));
|
||||
// TODO: Make this an if let statement whose value is "Some" type
|
||||
word = optional_word {
|
||||
println!("The word is: {}", word);
|
||||
} else {
|
||||
println!("The optional word doesn't contain anything");
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn simple_option() {
|
||||
let target = "rustlings";
|
||||
let optional_target = Some(target);
|
||||
|
||||
// TODO: Make this an if let statement whose value is "Some" type
|
||||
word = optional_target {
|
||||
assert_eq!(word, target);
|
||||
}
|
||||
}
|
||||
|
||||
let mut optional_integers_vec: Vec<Option<i8>> = Vec::new();
|
||||
for x in 1..10 {
|
||||
optional_integers_vec.push(Some(x));
|
||||
}
|
||||
#[test]
|
||||
fn layered_option() {
|
||||
let mut range = 10;
|
||||
let mut optional_integers: Vec<Option<i8>> = Vec::new();
|
||||
for i in 0..(range + 1) {
|
||||
optional_integers.push(Some(i));
|
||||
}
|
||||
|
||||
// TODO: make this a while let statement - remember that vector.pop also adds another layer of Option<T>
|
||||
// You can stack `Option<T>`'s into while let and if let
|
||||
integer = optional_integers_vec.pop() {
|
||||
println!("current value: {}", integer);
|
||||
// TODO: make this a while let statement - remember that vector.pop also adds another layer of Option<T>
|
||||
// You can stack `Option<T>`'s into while let and if let
|
||||
integer = optional_integers.pop() {
|
||||
assert_eq!(integer, range);
|
||||
range -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
// - If
|
||||
|
||||
// Mary is buying apples. One apple usually costs 2 Rustbucks, but if you buy
|
||||
// more than 40 at once, each apple only costs 1! Write a function that calculates
|
||||
// 40 or more at once, each apple only costs 1! Write a function that calculates
|
||||
// the price of an order of apples given the quantity bought. No hints this time!
|
||||
|
||||
// I AM NOT DONE
|
||||
|
||||
@@ -545,7 +545,7 @@ is the easiest, but how do you do it safely so that it doesn't panic in your fac
|
||||
[[exercises]]
|
||||
name = "options2"
|
||||
path = "exercises/options/options2.rs"
|
||||
mode = "compile"
|
||||
mode = "test"
|
||||
hint = """
|
||||
check out:
|
||||
https://doc.rust-lang.org/rust-by-example/flow_control/if_let.html
|
||||
@@ -1012,7 +1012,7 @@ hint = """
|
||||
An alternate way to handle concurrency between threads is to use
|
||||
a mpsc (multiple producer, single consumer) channel to communicate.
|
||||
With both a sending end and a receiving end, it's possible to
|
||||
send values in one thread and receieve them in another.
|
||||
send values in one thread and receive them in another.
|
||||
Multiple producers are possible by using clone() to create a duplicate
|
||||
of the original sending end.
|
||||
See https://doc.rust-lang.org/book/ch16-02-message-passing.html for more info.
|
||||
|
||||
77
src/main.rs
77
src/main.rs
@@ -1,6 +1,6 @@
|
||||
use crate::exercise::{Exercise, ExerciseList};
|
||||
use crate::project::RustAnalyzerProject;
|
||||
use crate::run::run;
|
||||
use crate::run::{reset, run};
|
||||
use crate::verify::verify;
|
||||
use argh::FromArgs;
|
||||
use console::Emoji;
|
||||
@@ -26,7 +26,7 @@ mod run;
|
||||
mod verify;
|
||||
|
||||
// In sync with crate version
|
||||
const VERSION: &str = "5.1.1";
|
||||
const VERSION: &str = "5.2.0";
|
||||
|
||||
#[derive(FromArgs, PartialEq, Debug)]
|
||||
/// Rustlings is a collection of small exercises to get you used to writing and reading Rust code
|
||||
@@ -47,6 +47,7 @@ enum Subcommands {
|
||||
Verify(VerifyArgs),
|
||||
Watch(WatchArgs),
|
||||
Run(RunArgs),
|
||||
Reset(ResetArgs),
|
||||
Hint(HintArgs),
|
||||
List(ListArgs),
|
||||
Lsp(LspArgs),
|
||||
@@ -71,6 +72,15 @@ struct RunArgs {
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[derive(FromArgs, PartialEq, Debug)]
|
||||
#[argh(subcommand, name = "reset")]
|
||||
/// Resets a single exercise using "git stash -- <filename>"
|
||||
struct ResetArgs {
|
||||
#[argh(positional)]
|
||||
/// the name of the exercise
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[derive(FromArgs, PartialEq, Debug)]
|
||||
#[argh(subcommand, name = "hint")]
|
||||
/// Returns a hint for the given exercise
|
||||
@@ -85,7 +95,6 @@ struct HintArgs {
|
||||
/// Enable rust-analyzer for exercises
|
||||
struct LspArgs {}
|
||||
|
||||
|
||||
#[derive(FromArgs, PartialEq, Debug)]
|
||||
#[argh(subcommand, name = "list")]
|
||||
/// Lists the exercises available in Rustlings
|
||||
@@ -164,7 +173,9 @@ fn main() {
|
||||
"Pending"
|
||||
};
|
||||
let solve_cond = {
|
||||
(e.looks_done() && subargs.solved) || (!e.looks_done() && subargs.unsolved) || (!subargs.solved && !subargs.unsolved)
|
||||
(e.looks_done() && subargs.solved)
|
||||
|| (!e.looks_done() && subargs.unsolved)
|
||||
|| (!subargs.solved && !subargs.unsolved)
|
||||
};
|
||||
if solve_cond && (filter_cond || subargs.filter.is_none()) {
|
||||
let line = if subargs.paths {
|
||||
@@ -205,6 +216,12 @@ fn main() {
|
||||
run(exercise, verbose).unwrap_or_else(|_| std::process::exit(1));
|
||||
}
|
||||
|
||||
Subcommands::Reset(subargs) => {
|
||||
let exercise = find_exercise(&subargs.name, &exercises);
|
||||
|
||||
reset(exercise).unwrap_or_else(|_| std::process::exit(1));
|
||||
}
|
||||
|
||||
Subcommands::Hint(subargs) => {
|
||||
let exercise = find_exercise(&subargs.name, &exercises);
|
||||
|
||||
@@ -212,7 +229,8 @@ fn main() {
|
||||
}
|
||||
|
||||
Subcommands::Verify(_subargs) => {
|
||||
verify(&exercises, (0, exercises.len()), verbose).unwrap_or_else(|_| std::process::exit(1));
|
||||
verify(&exercises, (0, exercises.len()), verbose)
|
||||
.unwrap_or_else(|_| std::process::exit(1));
|
||||
}
|
||||
|
||||
Subcommands::Lsp(_subargs) => {
|
||||
@@ -236,12 +254,18 @@ fn main() {
|
||||
|
||||
Subcommands::Watch(_subargs) => match watch(&exercises, verbose) {
|
||||
Err(e) => {
|
||||
println!("Error: Could not watch your progress. Error message was {:?}.", e);
|
||||
println!(
|
||||
"Error: Could not watch your progress. Error message was {:?}.",
|
||||
e
|
||||
);
|
||||
println!("Most likely you've run out of disk space or your 'inotify limit' has been reached.");
|
||||
std::process::exit(1);
|
||||
}
|
||||
Ok(WatchStatus::Finished) => {
|
||||
println!("{emoji} All exercises completed! {emoji}", emoji = Emoji("🎉", "★"));
|
||||
println!(
|
||||
"{emoji} All exercises completed! {emoji}",
|
||||
emoji = Emoji("🎉", "★")
|
||||
);
|
||||
println!("\n{}\n", FENISH_LINE);
|
||||
}
|
||||
Ok(WatchStatus::Unfinished) => {
|
||||
@@ -252,8 +276,10 @@ fn main() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn spawn_watch_shell(failed_exercise_hint: &Arc<Mutex<Option<String>>>, should_quit: Arc<AtomicBool>) {
|
||||
fn spawn_watch_shell(
|
||||
failed_exercise_hint: &Arc<Mutex<Option<String>>>,
|
||||
should_quit: Arc<AtomicBool>,
|
||||
) {
|
||||
let failed_exercise_hint = Arc::clone(failed_exercise_hint);
|
||||
println!("Welcome to watch mode! You can type 'help' to get an overview of the commands you can use here.");
|
||||
thread::spawn(move || loop {
|
||||
@@ -290,16 +316,22 @@ fn spawn_watch_shell(failed_exercise_hint: &Arc<Mutex<Option<String>>>, should_q
|
||||
|
||||
fn find_exercise<'a>(name: &str, exercises: &'a [Exercise]) -> &'a Exercise {
|
||||
if name.eq("next") {
|
||||
exercises.iter().find(|e| !e.looks_done()).unwrap_or_else(|| {
|
||||
println!("🎉 Congratulations! You have done all the exercises!");
|
||||
println!("🔚 There are no more exercises to do next!");
|
||||
std::process::exit(1)
|
||||
})
|
||||
exercises
|
||||
.iter()
|
||||
.find(|e| !e.looks_done())
|
||||
.unwrap_or_else(|| {
|
||||
println!("🎉 Congratulations! You have done all the exercises!");
|
||||
println!("🔚 There are no more exercises to do next!");
|
||||
std::process::exit(1)
|
||||
})
|
||||
} else {
|
||||
exercises.iter().find(|e| e.name == name).unwrap_or_else(|| {
|
||||
println!("No exercise found for '{}'!", name);
|
||||
std::process::exit(1)
|
||||
})
|
||||
exercises
|
||||
.iter()
|
||||
.find(|e| e.name == name)
|
||||
.unwrap_or_else(|| {
|
||||
println!("No exercise found for '{}'!", name);
|
||||
std::process::exit(1)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,8 +369,13 @@ fn watch(exercises: &[Exercise], verbose: bool) -> notify::Result<WatchStatus> {
|
||||
let filepath = b.as_path().canonicalize().unwrap();
|
||||
let pending_exercises = exercises
|
||||
.iter()
|
||||
.find(|e| filepath.ends_with(&e.path)).into_iter()
|
||||
.chain(exercises.iter().filter(|e| !e.looks_done() && !filepath.ends_with(&e.path)));
|
||||
.find(|e| filepath.ends_with(&e.path))
|
||||
.into_iter()
|
||||
.chain(
|
||||
exercises
|
||||
.iter()
|
||||
.filter(|e| !e.looks_done() && !filepath.ends_with(&e.path)),
|
||||
);
|
||||
let num_done = exercises.iter().filter(|e| e.looks_done()).count();
|
||||
clear_screen();
|
||||
match verify(pending_exercises, (num_done, exercises.len()), verbose) {
|
||||
|
||||
15
src/run.rs
15
src/run.rs
@@ -1,3 +1,5 @@
|
||||
use std::process::Command;
|
||||
|
||||
use crate::exercise::{Exercise, Mode};
|
||||
use crate::verify::test;
|
||||
use indicatif::ProgressBar;
|
||||
@@ -15,6 +17,19 @@ pub fn run(exercise: &Exercise, verbose: bool) -> Result<(), ()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Resets the exercise by stashing the changes.
|
||||
pub fn reset(exercise: &Exercise) -> Result<(), ()> {
|
||||
let command = Command::new("git")
|
||||
.args(["stash", "--"])
|
||||
.arg(&exercise.path)
|
||||
.spawn();
|
||||
|
||||
match command {
|
||||
Ok(_) => Ok(()),
|
||||
Err(_) => Err(()),
|
||||
}
|
||||
}
|
||||
|
||||
// Invoke the rust compiler on the path of the given exercise
|
||||
// and run the ensuing binary.
|
||||
// This is strictly for non-test binaries, so output is displayed
|
||||
|
||||
@@ -110,6 +110,27 @@ fn run_single_test_no_exercise() {
|
||||
.code(1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reset_single_exercise() {
|
||||
Command::cargo_bin("rustlings")
|
||||
.unwrap()
|
||||
.args(&["reset", "intro1"])
|
||||
.assert()
|
||||
.code(0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reset_no_exercise() {
|
||||
Command::cargo_bin("rustlings")
|
||||
.unwrap()
|
||||
.arg("reset")
|
||||
.assert()
|
||||
.code(1)
|
||||
.stderr(predicates::str::contains(
|
||||
"positional arguments not provided",
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_hint_for_single_test() {
|
||||
Command::cargo_bin("rustlings")
|
||||
@@ -126,7 +147,7 @@ fn all_exercises_require_confirmation() {
|
||||
for exercise in glob("exercises/**/*.rs").unwrap() {
|
||||
let path = exercise.unwrap();
|
||||
if path.file_name().unwrap() == "mod.rs" {
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
let source = {
|
||||
let mut file = File::open(&path).unwrap();
|
||||
|
||||
Reference in New Issue
Block a user