Compare commits

..

19 commits

Author SHA1 Message Date
Kacper Poneta 57341c4156
Merge 59e8f70e55 into 64b2f18d92 2024-09-17 10:47:02 +08:00
Mo 64b2f18d92
Merge pull request #2103 from senekor/senk/kvuzvzqqkskk
Remove redundant enum definition task
2024-09-16 12:56:28 +02:00
Mo 2894f3c45c
Merge pull request #2110 from senekor/remo/skkynvtqxkoz
Make if2 less confusing
2024-09-16 12:54:20 +02:00
Mo 1bae2dcb00
Merge pull request #2109 from bri-rose/main
grammatical error in info.toml
2024-09-14 23:52:54 +02:00
Remo Senekowitsch b540c6df25 Make if2 less confusing
Some people would get stuck on this exercise, trying to understand the meaning
behind foo, fuzz, baz etc. Making the theme of the code make a little more sense
to humans should hopefully prevent people from getting confused by abstract and
non-sensical tests.
2024-09-14 10:03:52 +02:00
bri-rose 8b476e678a
Update info.toml
Fixed grammatical error, subject/verb agreement at line 124-125.
2024-09-13 10:23:05 -05:00
mo8it 47f8a0cbe5 Add rust-analyzer.toml on dev new 2024-09-13 16:39:28 +02:00
mo8it 9459eef032 Use Clippy with Rust-Analyzer 2024-09-13 16:38:53 +02:00
mo8it 5aaa8924a6 <s>earch isn't a typo 2024-09-13 15:07:53 +02:00
mo8it 4ffce1c297 Move lint to Rust lints 2024-09-13 14:59:34 +02:00
mo8it 0513660b05 Allow dead code for all exercises and solutions 2024-09-13 14:56:46 +02:00
mo8it 3947c4de28 Pause input while running an exercise 2024-09-12 17:46:06 +02:00
mo8it 664228ef8b Improve quit message 2024-09-12 17:46:06 +02:00
mo8it 234a61a3ee Update deps 2024-09-12 17:46:06 +02:00
mo8it 83d1275d72 Add missing # in comment 2024-09-12 17:46:06 +02:00
Mo 45abd7d59e
Merge pull request #2107 from alibektas/ratoml_for_rustlings
Add rust-analyzer.toml file
2024-09-12 15:49:31 +02:00
Ali Bektas 88e10a9e54 hardcode ratoml in init.rs 2024-09-12 15:46:09 +02:00
Ali Bektas 1f624d4c2a Add rust-analyzer.toml file 2024-09-12 15:26:40 +02:00
Remo Senekowitsch 9a25309c1c Remove redundant enum definition task
The exercise enums2.rs already contains a task where an identical enum
has to be defined.
2024-09-11 16:57:12 +02:00
23 changed files with 143 additions and 102 deletions

View file

@ -1,3 +1,6 @@
[default.extend-words]
"earch" = "earch" # Because of <s>earch in the list footer
[files]
extend-exclude = [
"CHANGELOG.md",

24
Cargo.lock generated
View file

@ -65,9 +65,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.86"
version = "1.0.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
checksum = "4e1496f8fb1fbf272686b8d37f523dab3e4a7443300055e74cdaa449f3114356"
[[package]]
name = "autocfg"
@ -460,18 +460,18 @@ dependencies = [
[[package]]
name = "redox_syscall"
version = "0.5.3"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4"
checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853"
dependencies = [
"bitflags 2.6.0",
]
[[package]]
name = "rustix"
version = "0.38.35"
version = "0.38.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f"
checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811"
dependencies = [
"bitflags 2.6.0",
"errno",
@ -530,18 +530,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde"
version = "1.0.209"
version = "1.0.210"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09"
checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.209"
version = "1.0.210"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170"
checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
dependencies = [
"proc-macro2",
"quote",
@ -659,9 +659,9 @@ dependencies = [
[[package]]
name = "unicode-ident"
version = "1.0.12"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
[[package]]
name = "utf8parse"

View file

@ -19,7 +19,7 @@ edition = "2021" # On Update: Update the edition of the `rustfmt` command that c
rust-version = "1.80"
[workspace.dependencies]
serde = { version = "1.0.209", features = ["derive"] }
serde = { version = "1.0.210", features = ["derive"] }
toml_edit = { version = "0.22.20", default-features = false, features = ["parse", "serde"] }
[package]
@ -47,7 +47,7 @@ include = [
[dependencies]
ahash = { version = "0.8.11", default-features = false }
anyhow = "1.0.86"
anyhow = "1.0.88"
clap = { version = "4.5.17", features = ["derive"] }
crossterm = { version = "0.28.1", default-features = false, features = ["windows", "events"] }
notify-debouncer-mini = { version = "0.4.1", default-features = false }
@ -58,7 +58,7 @@ serde.workspace = true
toml_edit.workspace = true
[target.'cfg(not(windows))'.dependencies]
rustix = { version = "0.38.35", default-features = false, features = ["std", "stdio", "termios"] }
rustix = { version = "0.38.37", default-features = false, features = ["std", "stdio", "termios"] }
[dev-dependencies]
tempfile = "3.12.0"

View file

@ -205,19 +205,21 @@ panic = "abort"
panic = "abort"
[lints.rust]
# You shouldn't write unsafe code in Rustlings
# You shouldn't write unsafe code in Rustlings!
unsafe_code = "forbid"
# You don't need unstable features in Rustlings and shouldn't rely on them while learning Rust
# You don't need unstable features in Rustlings and shouldn't rely on them while learning Rust.
unstable_features = "forbid"
# Dead code warnings can't be avoided in some exercises and might distract while learning.
dead_code = "allow"
[lints.clippy]
# You forgot a `todo!()`
# You forgot a `todo!()`!
todo = "forbid"
# This can only happen by mistake in Rustlings
# This can only happen by mistake in Rustlings.
empty_loop = "forbid"
# No infinite loops are needed in Rustlings
# No infinite loops are needed in Rustlings.
infinite_loop = "deny"
# You shouldn't leak memory while still learning Rust
# You shouldn't leak memory while still learning Rust!
mem_forget = "deny"
# Currently, there are no disallowed methods. This line avoids problems when developing Rustlings.
disallowed_methods = "allow"

View file

@ -1,7 +1,7 @@
// TODO: Fix the compiler error on this function.
fn foo_if_fizz(fizzish: &str) -> &str {
if fizzish == "fizz" {
"foo"
fn picky_eater(food: &str) -> &str {
if food == "strawberry" {
"Yummy!"
} else {
1
}
@ -18,18 +18,20 @@ mod tests {
use super::*;
#[test]
fn foo_for_fizz() {
// This means that calling `foo_if_fizz` with the argument "fizz" should return "foo".
assert_eq!(foo_if_fizz("fizz"), "foo");
fn yummy_food() {
// This means that calling `picky_eater` with the argument "food" should return "Yummy!".
assert_eq!(picky_eater("strawberry"), "Yummy!");
}
#[test]
fn bar_for_fuzz() {
assert_eq!(foo_if_fizz("fuzz"), "bar");
fn neutral_food() {
assert_eq!(picky_eater("potato"), "I guess I can eat that.");
}
#[test]
fn default_to_baz() {
assert_eq!(foo_if_fizz("literally anything"), "baz");
fn default_disliked_food() {
assert_eq!(picky_eater("broccoli"), "No thanks!");
assert_eq!(picky_eater("gummy bears"), "No thanks!");
assert_eq!(picky_eater("literally anything"), "No thanks!");
}
}

View file

@ -1,5 +1,3 @@
#![allow(dead_code)]
#[derive(Debug)]
struct Point {
x: u64,

View file

@ -4,7 +4,11 @@ struct Point {
}
enum Message {
// TODO: Implement the message variant types based on their usage below.
Resize { width: u64, height: u64 },
Move(Point),
Echo(String),
ChangeColor(u8, u8, u8),
Quit,
}
struct State {

View file

@ -1,7 +1,6 @@
// You can bring module paths into scopes and provide new names for them with
// the `use` and `as` keywords.
#[allow(dead_code)]
mod delicious_snacks {
// TODO: Add the following two `use` statements after fixing them.
// use self::fruits::PEAR as ???;

View file

@ -1,5 +1,3 @@
#![allow(dead_code)]
trait Licensed {
// TODO: Add a default implementation for `licensing_info` so that
// implementors like the two structs below can share that default behavior

View file

@ -8,7 +8,6 @@ use std::rc::Rc;
#[derive(Debug)]
struct Sun;
#[allow(dead_code)]
#[derive(Debug)]
enum Planet {
Mercury(Rc<Sun>),

View file

@ -122,7 +122,7 @@ dir = "01_variables"
test = false
hint = """
We know about variables and mutability, but there is another important type of
variables available: constants.
variable available: constants.
Constants are always immutable. They are declared with the keyword `const`
instead of `let`.

View file

@ -1,10 +1,10 @@
fn foo_if_fizz(fizzish: &str) -> &str {
if fizzish == "fizz" {
"foo"
} else if fizzish == "fuzz" {
"bar"
fn picky_eater(food: &str) -> &str {
if food == "strawberry" {
"Yummy!"
} else if food == "potato" {
"I guess I can eat that."
} else {
"baz"
"No thanks!"
}
}
@ -17,17 +17,19 @@ mod tests {
use super::*;
#[test]
fn foo_for_fizz() {
assert_eq!(foo_if_fizz("fizz"), "foo");
fn yummy_food() {
assert_eq!(picky_eater("strawberry"), "Yummy!");
}
#[test]
fn bar_for_fuzz() {
assert_eq!(foo_if_fizz("fuzz"), "bar");
fn neutral_food() {
assert_eq!(picky_eater("potato"), "I guess I can eat that.");
}
#[test]
fn default_to_baz() {
assert_eq!(foo_if_fizz("literally anything"), "baz");
fn default_disliked_food() {
assert_eq!(picky_eater("broccoli"), "No thanks!");
assert_eq!(picky_eater("gummy bears"), "No thanks!");
assert_eq!(picky_eater("literally anything"), "No thanks!");
}
}

View file

@ -1,5 +1,3 @@
#![allow(dead_code)]
#[derive(Debug)]
struct Point {
x: u64,

View file

@ -1,4 +1,3 @@
#[allow(dead_code)]
mod delicious_snacks {
// Added `pub` and used the expected alias after `as`.
pub use self::fruits::PEAR as fruit;

View file

@ -1,5 +1,3 @@
#![allow(dead_code)]
trait Licensed {
fn licensing_info(&self) -> String {
"Default license".to_string()

View file

@ -8,7 +8,6 @@ use std::rc::Rc;
#[derive(Debug)]
struct Sun;
#[allow(dead_code)]
#[derive(Debug)]
enum Planet {
Mercury(Rc<Sun>),

View file

@ -6,7 +6,7 @@ use std::{
process::Command,
};
use crate::CURRENT_FORMAT_VERSION;
use crate::{init::RUST_ANALYZER_TOML, CURRENT_FORMAT_VERSION};
// Create a directory relative to the current directory and print its path.
fn create_rel_dir(dir_name: &str, current_dir: &str) -> Result<()> {
@ -62,6 +62,8 @@ pub fn new(path: &Path, no_git: bool) -> Result<()> {
write_rel_file("README.md", &dir_path_str, README)?;
write_rel_file("rust-analyzer.toml", &dir_path_str, RUST_ANALYZER_TOML)?;
create_rel_dir(".vscode", &dir_path_str)?;
write_rel_file(
".vscode/extensions.json",

View file

@ -131,7 +131,7 @@ pub trait RunnableExercise {
let mut clippy_cmd = cmd_runner.cargo("clippy", bin_name, output.as_deref_mut());
// `--profile test` is required to also check code with `[cfg(test)]`.
// `--profile test` is required to also check code with `#[cfg(test)]`.
if FORCE_STRICT_CLIPPY || self.strict_clippy() {
clippy_cmd.args(["--profile", "test", "--", "-D", "warnings"]);
} else {

View file

@ -130,6 +130,9 @@ pub fn init() -> Result<()> {
fs::write("Cargo.toml", updated_cargo_toml)
.context("Failed to create the file `rustlings/Cargo.toml`")?;
fs::write("rust-analyzer.toml", RUST_ANALYZER_TOML)
.context("Failed to create the file `rustlings/rust-analyzer.toml`")?;
fs::write(".gitignore", GITIGNORE)
.context("Failed to create the file `rustlings/.gitignore`")?;
@ -169,6 +172,10 @@ const INIT_SOLUTION_FILE: &[u8] = b"fn main() {
}
";
pub const RUST_ANALYZER_TOML: &[u8] = br#"check.command = "clippy"
check.extraArgs = ["--profile", "test"]
"#;
const GITIGNORE: &[u8] = b"Cargo.lock
target/
.vscode/

View file

@ -1,4 +1,4 @@
use anyhow::{Context, Error, Result};
use anyhow::{Error, Result};
use notify_debouncer_mini::{
new_debouncer,
notify::{self, RecursiveMode},
@ -7,7 +7,6 @@ use std::{
io::{self, Write},
path::Path,
sync::mpsc::channel,
thread,
time::Duration,
};
@ -16,11 +15,7 @@ use crate::{
list,
};
use self::{
notify_event::NotifyEventHandler,
state::WatchState,
terminal_event::{terminal_event_handler, InputEvent},
};
use self::{notify_event::NotifyEventHandler, state::WatchState, terminal_event::InputEvent};
mod notify_event;
mod state;
@ -47,7 +42,7 @@ fn run_watch(
app_state: &mut AppState,
notify_exercise_names: Option<&'static [&'static [u8]]>,
) -> Result<WatchExit> {
let (tx, rx) = channel();
let (watch_event_sender, watch_event_receiver) = channel();
let mut manual_run = false;
// Prevent dropping the guard until the end of the function.
@ -56,7 +51,7 @@ fn run_watch(
let mut debouncer = new_debouncer(
Duration::from_millis(200),
NotifyEventHandler {
tx: tx.clone(),
sender: watch_event_sender.clone(),
exercise_names,
},
)
@ -72,16 +67,12 @@ fn run_watch(
None
};
let mut watch_state = WatchState::build(app_state, manual_run)?;
let mut watch_state = WatchState::build(app_state, watch_event_sender, manual_run)?;
let mut stdout = io::stdout().lock();
watch_state.run_current_exercise(&mut stdout)?;
thread::Builder::new()
.spawn(move || terminal_event_handler(tx, manual_run))
.context("Failed to spawn a thread to handle terminal events")?;
while let Ok(event) = rx.recv() {
while let Ok(event) = watch_event_receiver.recv() {
match event {
WatchEvent::Input(InputEvent::Next) => match watch_state.next_exercise(&mut stdout)? {
ExercisesProgress::AllDone => break,
@ -154,8 +145,9 @@ pub fn watch(
}
const QUIT_MSG: &[u8] = b"
We hope you're enjoying learning Rust!
If you want to continue working on the exercises at a later point, you can simply run `rustlings` again.
If you want to continue working on the exercises at a later point, you can simply run `rustlings` again in this directory.
";
const NOTIFY_ERR: &str = "

View file

@ -4,7 +4,7 @@ use std::sync::mpsc::Sender;
use super::WatchEvent;
pub struct NotifyEventHandler {
pub tx: Sender<WatchEvent>,
pub sender: Sender<WatchEvent>,
/// Used to report which exercise was modified.
pub exercise_names: &'static [&'static [u8]],
}
@ -47,6 +47,6 @@ impl notify_debouncer_mini::DebounceEventHandler for NotifyEventHandler {
// An error occurs when the receiver is dropped.
// After dropping the receiver, the debouncer guard should also be dropped.
let _ = self.tx.send(output_event);
let _ = self.sender.send(output_event);
}
}

View file

@ -5,7 +5,11 @@ use crossterm::{
},
terminal, QueueableCommand,
};
use std::io::{self, StdoutLock, Write};
use std::{
io::{self, StdoutLock, Write},
sync::mpsc::Sender,
thread,
};
use crate::{
app_state::{AppState, ExercisesProgress},
@ -14,6 +18,11 @@ use crate::{
term::progress_bar,
};
use super::{
terminal_event::{terminal_event_handler, InputPauseGuard},
WatchEvent,
};
#[derive(PartialEq, Eq)]
enum DoneStatus {
DoneWithSolution(String),
@ -31,11 +40,19 @@ pub struct WatchState<'a> {
}
impl<'a> WatchState<'a> {
pub fn build(app_state: &'a mut AppState, manual_run: bool) -> Result<Self> {
pub fn build(
app_state: &'a mut AppState,
watch_event_sender: Sender<WatchEvent>,
manual_run: bool,
) -> Result<Self> {
let term_width = terminal::size()
.context("Failed to get the terminal size")?
.0;
thread::Builder::new()
.spawn(move || terminal_event_handler(watch_event_sender, manual_run))
.context("Failed to spawn a thread to handle terminal events")?;
Ok(Self {
app_state,
output: Vec::with_capacity(OUTPUT_CAPACITY),
@ -47,6 +64,9 @@ impl<'a> WatchState<'a> {
}
pub fn run_current_exercise(&mut self, stdout: &mut StdoutLock) -> Result<()> {
// Ignore any input until running the exercise is done.
let _input_pause_guard = InputPauseGuard::scoped_pause();
self.show_hint = false;
writeln!(

View file

@ -1,8 +1,32 @@
use crossterm::event::{self, Event, KeyCode, KeyEventKind};
use std::sync::mpsc::Sender;
use std::sync::{
atomic::{AtomicBool, Ordering::Relaxed},
mpsc::Sender,
};
use super::WatchEvent;
static INPUT_PAUSED: AtomicBool = AtomicBool::new(false);
// Private unit type to force using the constructor function.
#[must_use = "When the guard is dropped, the input is unpaused"]
pub struct InputPauseGuard(());
impl InputPauseGuard {
#[inline]
pub fn scoped_pause() -> Self {
INPUT_PAUSED.store(true, Relaxed);
Self(())
}
}
impl Drop for InputPauseGuard {
#[inline]
fn drop(&mut self) {
INPUT_PAUSED.store(false, Relaxed);
}
}
pub enum InputEvent {
Run,
Next,
@ -11,46 +35,41 @@ pub enum InputEvent {
Quit,
}
pub fn terminal_event_handler(tx: Sender<WatchEvent>, manual_run: bool) {
let last_input_event = loop {
let terminal_event = match event::read() {
Ok(v) => v,
Err(e) => {
// If `send` returns an error, then the receiver is dropped and
// a shutdown has been already initialized.
let _ = tx.send(WatchEvent::TerminalEventErr(e));
return;
}
};
match terminal_event {
Event::Key(key) => {
pub fn terminal_event_handler(sender: Sender<WatchEvent>, manual_run: bool) {
let last_watch_event = loop {
match event::read() {
Ok(Event::Key(key)) => {
match key.kind {
KeyEventKind::Release | KeyEventKind::Repeat => continue,
KeyEventKind::Press => (),
}
if INPUT_PAUSED.load(Relaxed) {
continue;
}
let input_event = match key.code {
KeyCode::Char('n') => InputEvent::Next,
KeyCode::Char('h') => InputEvent::Hint,
KeyCode::Char('l') => break InputEvent::List,
KeyCode::Char('q') => break InputEvent::Quit,
KeyCode::Char('l') => break WatchEvent::Input(InputEvent::List),
KeyCode::Char('q') => break WatchEvent::Input(InputEvent::Quit),
KeyCode::Char('r') if manual_run => InputEvent::Run,
_ => continue,
};
if tx.send(WatchEvent::Input(input_event)).is_err() {
if sender.send(WatchEvent::Input(input_event)).is_err() {
return;
}
}
Event::Resize(width, _) => {
if tx.send(WatchEvent::TerminalResize { width }).is_err() {
Ok(Event::Resize(width, _)) => {
if sender.send(WatchEvent::TerminalResize { width }).is_err() {
return;
}
}
Event::FocusGained | Event::FocusLost | Event::Mouse(_) => continue,
Ok(Event::FocusGained | Event::FocusLost | Event::Mouse(_)) => continue,
Err(e) => break WatchEvent::TerminalEventErr(e),
}
};
let _ = tx.send(WatchEvent::Input(last_input_event));
let _ = sender.send(last_watch_event);
}