mirror of
https://github.com/rust-lang/rustlings.git
synced 2024-12-26 00:00:03 +03:00
Compare commits
9 commits
57341c4156
...
58a08cd5e8
Author | SHA1 | Date | |
---|---|---|---|
58a08cd5e8 | |||
4e4b65711a | |||
89c40ba256 | |||
e56ae6d651 | |||
59e8f70e55 | |||
4c8365fe88 | |||
52af0674c1 | |||
938b90e5f2 | |||
55cc8584bd |
36
Cargo.lock
generated
36
Cargo.lock
generated
|
@ -65,9 +65,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.88"
|
||||
version = "1.0.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e1496f8fb1fbf272686b8d37f523dab3e4a7443300055e74cdaa449f3114356"
|
||||
checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
|
@ -139,21 +139,6 @@ version = "1.0.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
|
||||
|
||||
[[package]]
|
||||
name = "crossterm"
|
||||
version = "0.28.1"
|
||||
|
@ -379,7 +364,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"crossbeam-channel",
|
||||
"filetime",
|
||||
"fsevent-sys",
|
||||
"inotify",
|
||||
|
@ -391,16 +375,6 @@ dependencies = [
|
|||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "notify-debouncer-mini"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d40b221972a1fc5ef4d858a2f671fb34c75983eb385463dff3780eeff6a9d43"
|
||||
dependencies = [
|
||||
"log",
|
||||
"notify",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.19.0"
|
||||
|
@ -488,7 +462,7 @@ dependencies = [
|
|||
"anyhow",
|
||||
"clap",
|
||||
"crossterm",
|
||||
"notify-debouncer-mini",
|
||||
"notify",
|
||||
"os_pipe",
|
||||
"rustix",
|
||||
"rustlings-macros",
|
||||
|
@ -646,9 +620,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.20"
|
||||
version = "0.22.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d"
|
||||
checksum = "3b072cee73c449a636ffd6f32bd8de3a9f7119139aff882f44943ce2986dc5cf"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
|
|
|
@ -20,7 +20,7 @@ rust-version = "1.80"
|
|||
|
||||
[workspace.dependencies]
|
||||
serde = { version = "1.0.210", features = ["derive"] }
|
||||
toml_edit = { version = "0.22.20", default-features = false, features = ["parse", "serde"] }
|
||||
toml_edit = { version = "0.22.21", default-features = false, features = ["parse", "serde"] }
|
||||
|
||||
[package]
|
||||
name = "rustlings"
|
||||
|
@ -47,10 +47,10 @@ include = [
|
|||
|
||||
[dependencies]
|
||||
ahash = { version = "0.8.11", default-features = false }
|
||||
anyhow = "1.0.88"
|
||||
anyhow = "1.0.89"
|
||||
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 }
|
||||
notify = { version = "6.1.1", default-features = false, features = ["macos_fsevent"] }
|
||||
os_pipe = "1.2.1"
|
||||
rustlings-macros = { path = "rustlings-macros", version = "=6.3.0" }
|
||||
serde_json = "1.0.128"
|
||||
|
|
|
@ -116,6 +116,8 @@ bin = [
|
|||
{ name = "generics1_sol", path = "../solutions/14_generics/generics1.rs" },
|
||||
{ name = "generics2", path = "../exercises/14_generics/generics2.rs" },
|
||||
{ name = "generics2_sol", path = "../solutions/14_generics/generics2.rs" },
|
||||
{ name = "generics3", path = "../exercises/14_generics/generics3.rs" },
|
||||
{ name = "generics3_sol", path = "../solutions/14_generics/generics3.rs" },
|
||||
{ name = "traits1", path = "../exercises/15_traits/traits1.rs" },
|
||||
{ name = "traits1_sol", path = "../solutions/15_traits/traits1.rs" },
|
||||
{ name = "traits2", path = "../exercises/15_traits/traits2.rs" },
|
||||
|
|
54
exercises/14_generics/generics3.rs
Normal file
54
exercises/14_generics/generics3.rs
Normal file
|
@ -0,0 +1,54 @@
|
|||
// generics3.rs
|
||||
// Execute `rustlings hint generics3` or use the `hint` watch subcommand for a hint.
|
||||
|
||||
// This function should take an array of `Option` elements and returns array of not None elements
|
||||
// TODO fix this function signature
|
||||
fn into_dispose_nulls(list: Vec<Option<&str>>) -> Vec<&str> {
|
||||
list.into_iter().flatten().collect()
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn store_str_on_list() {
|
||||
let names_list = vec![Some("maria"), Some("jacob"), None, Some("kacper"), None];
|
||||
let only_values = into_dispose_nulls(names_list);
|
||||
assert_eq!(only_values.len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn store_numbers_on_list() {
|
||||
let numbers_list = vec![Some(1), Some(2), None, Some(3)];
|
||||
let only_values = into_dispose_nulls(numbers_list);
|
||||
assert_eq!(only_values.len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn store_custom_type_on_list() {
|
||||
#[allow(dead_code)]
|
||||
struct Rectangle {
|
||||
width: i32,
|
||||
height: i32,
|
||||
}
|
||||
impl Rectangle {
|
||||
fn new(width: i32, height: i32) -> Self {
|
||||
Self { width, height }
|
||||
}
|
||||
}
|
||||
|
||||
let custom_list = vec![
|
||||
Some(Rectangle::new(1, 2)),
|
||||
None,
|
||||
None,
|
||||
Some(Rectangle::new(3, 4)),
|
||||
];
|
||||
let only_values = into_dispose_nulls(custom_list);
|
||||
assert_eq!(only_values.len(), 2);
|
||||
}
|
||||
}
|
|
@ -749,6 +749,17 @@ hint = """
|
|||
Related section in The Book:
|
||||
https://doc.rust-lang.org/book/ch10-01-syntax.html#in-method-definitions"""
|
||||
|
||||
[[exercises]]
|
||||
name = "generics3"
|
||||
dir = "14_generics"
|
||||
hint = """
|
||||
Vectors in Rust use generics to create dynamically-sized arrays of any type.
|
||||
The `into_dispose_nulls` function takes a vector as an argument, but only accepts vectors that store the &str type.
|
||||
To allow the function to accept vectors that store any type, you can leverage your knowledge about generics.
|
||||
If you're unsure how to proceed, please refer to the Rust Book at:
|
||||
https://doc.rust-lang.org/book/ch10-01-syntax.html#in-function-definitions.
|
||||
"""
|
||||
|
||||
# TRAITS
|
||||
|
||||
[[exercises]]
|
||||
|
|
53
solutions/14_generics/generics3.rs
Normal file
53
solutions/14_generics/generics3.rs
Normal file
|
@ -0,0 +1,53 @@
|
|||
// generics3.rs
|
||||
// Execute `rustlings hint generics3` or use the `hint` watch subcommand for a hint.
|
||||
|
||||
// Here we added generic type `T` to function signature
|
||||
// Now this function can be used with vector of any
|
||||
fn into_dispose_nulls<T>(list: Vec<Option<T>>) -> Vec<T> {
|
||||
list.into_iter().flatten().collect()
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// You can optionally experiment here.
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn store_str_on_list() {
|
||||
let names_list = vec![Some("maria"), Some("jacob"), None, Some("kacper"), None];
|
||||
let only_values = into_dispose_nulls(names_list);
|
||||
assert_eq!(only_values.len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn store_numbers_on_list() {
|
||||
let numbers_list = vec![Some(1), Some(2), None, Some(3)];
|
||||
let only_values = into_dispose_nulls(numbers_list);
|
||||
assert_eq!(only_values.len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn store_custom_type_on_list() {
|
||||
struct Rectangle {
|
||||
width: i32,
|
||||
height: i32,
|
||||
}
|
||||
impl Rectangle {
|
||||
fn new(width: i32, height: i32) -> Self {
|
||||
Self { width, height }
|
||||
}
|
||||
}
|
||||
|
||||
let custom_list = vec![
|
||||
Some(Rectangle::new(1, 2)),
|
||||
None,
|
||||
None,
|
||||
Some(Rectangle::new(3, 4)),
|
||||
];
|
||||
let only_values = into_dispose_nulls(custom_list);
|
||||
assert_eq!(only_values.len(), 2);
|
||||
}
|
||||
}
|
43
src/watch.rs
43
src/watch.rs
|
@ -1,12 +1,12 @@
|
|||
use anyhow::{Error, Result};
|
||||
use notify_debouncer_mini::{
|
||||
new_debouncer,
|
||||
notify::{self, RecursiveMode},
|
||||
};
|
||||
use notify::{Config, RecommendedWatcher, RecursiveMode, Watcher};
|
||||
use std::{
|
||||
io::{self, Write},
|
||||
path::Path,
|
||||
sync::mpsc::channel,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering::Relaxed},
|
||||
mpsc::channel,
|
||||
},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
|
@ -21,6 +21,27 @@ mod notify_event;
|
|||
mod state;
|
||||
mod terminal_event;
|
||||
|
||||
static EXERCISE_RUNNING: 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 {
|
||||
EXERCISE_RUNNING.store(true, Relaxed);
|
||||
Self(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for InputPauseGuard {
|
||||
#[inline]
|
||||
fn drop(&mut self) {
|
||||
EXERCISE_RUNNING.store(false, Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
enum WatchEvent {
|
||||
Input(InputEvent),
|
||||
FileChange { exercise_ind: usize },
|
||||
|
@ -47,21 +68,21 @@ fn run_watch(
|
|||
let mut manual_run = false;
|
||||
// Prevent dropping the guard until the end of the function.
|
||||
// Otherwise, the file watcher exits.
|
||||
let _debouncer_guard = if let Some(exercise_names) = notify_exercise_names {
|
||||
let mut debouncer = new_debouncer(
|
||||
Duration::from_millis(200),
|
||||
let _watcher_guard = if let Some(exercise_names) = notify_exercise_names {
|
||||
let mut watcher = RecommendedWatcher::new(
|
||||
NotifyEventHandler {
|
||||
sender: watch_event_sender.clone(),
|
||||
exercise_names,
|
||||
},
|
||||
Config::default().with_poll_interval(Duration::from_secs(1)),
|
||||
)
|
||||
.inspect_err(|_| eprintln!("{NOTIFY_ERR}"))?;
|
||||
debouncer
|
||||
.watcher()
|
||||
|
||||
watcher
|
||||
.watch(Path::new("exercises"), RecursiveMode::Recursive)
|
||||
.inspect_err(|_| eprintln!("{NOTIFY_ERR}"))?;
|
||||
|
||||
Some(debouncer)
|
||||
Some(watcher)
|
||||
} else {
|
||||
manual_run = true;
|
||||
None
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
use notify_debouncer_mini::{DebounceEventResult, DebouncedEventKind};
|
||||
use std::sync::mpsc::Sender;
|
||||
use notify::{
|
||||
event::{MetadataKind, ModifyKind},
|
||||
Event, EventKind,
|
||||
};
|
||||
use std::sync::{atomic::Ordering::Relaxed, mpsc::Sender};
|
||||
|
||||
use super::WatchEvent;
|
||||
use super::{WatchEvent, EXERCISE_RUNNING};
|
||||
|
||||
pub struct NotifyEventHandler {
|
||||
pub sender: Sender<WatchEvent>,
|
||||
|
@ -9,44 +12,56 @@ pub struct NotifyEventHandler {
|
|||
pub exercise_names: &'static [&'static [u8]],
|
||||
}
|
||||
|
||||
impl notify_debouncer_mini::DebounceEventHandler for NotifyEventHandler {
|
||||
fn handle_event(&mut self, input_event: DebounceEventResult) {
|
||||
let output_event = match input_event {
|
||||
Ok(input_event) => {
|
||||
let Some(exercise_ind) = input_event
|
||||
.iter()
|
||||
.filter_map(|input_event| {
|
||||
if input_event.kind != DebouncedEventKind::Any {
|
||||
return None;
|
||||
}
|
||||
impl notify::EventHandler for NotifyEventHandler {
|
||||
fn handle_event(&mut self, input_event: notify::Result<Event>) {
|
||||
if EXERCISE_RUNNING.load(Relaxed) {
|
||||
return;
|
||||
}
|
||||
|
||||
let file_name = input_event.path.file_name()?.to_str()?.as_bytes();
|
||||
|
||||
if file_name.len() < 4 {
|
||||
return None;
|
||||
}
|
||||
let (file_name_without_ext, ext) = file_name.split_at(file_name.len() - 3);
|
||||
|
||||
if ext != b".rs" {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.exercise_names
|
||||
.iter()
|
||||
.position(|exercise_name| *exercise_name == file_name_without_ext)
|
||||
})
|
||||
.min()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
WatchEvent::FileChange { exercise_ind }
|
||||
let input_event = match input_event {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
// An error occurs when the receiver is dropped.
|
||||
// After dropping the receiver, the debouncer guard should also be dropped.
|
||||
let _ = self.sender.send(WatchEvent::NotifyErr(e));
|
||||
return;
|
||||
}
|
||||
Err(e) => WatchEvent::NotifyErr(e),
|
||||
};
|
||||
|
||||
// An error occurs when the receiver is dropped.
|
||||
// After dropping the receiver, the debouncer guard should also be dropped.
|
||||
let _ = self.sender.send(output_event);
|
||||
match input_event.kind {
|
||||
EventKind::Any => (),
|
||||
EventKind::Modify(modify_kind) => match modify_kind {
|
||||
ModifyKind::Any | ModifyKind::Data(_) => (),
|
||||
ModifyKind::Metadata(metadata_kind) => match metadata_kind {
|
||||
MetadataKind::Any | MetadataKind::WriteTime => (),
|
||||
MetadataKind::AccessTime
|
||||
| MetadataKind::Permissions
|
||||
| MetadataKind::Ownership
|
||||
| MetadataKind::Extended
|
||||
| MetadataKind::Other => return,
|
||||
},
|
||||
ModifyKind::Name(_) | ModifyKind::Other => return,
|
||||
},
|
||||
EventKind::Access(_)
|
||||
| EventKind::Create(_)
|
||||
| EventKind::Remove(_)
|
||||
| EventKind::Other => return,
|
||||
}
|
||||
|
||||
let _ = input_event
|
||||
.paths
|
||||
.into_iter()
|
||||
.filter_map(|path| {
|
||||
let file_name = path.file_name()?.to_str()?.as_bytes();
|
||||
|
||||
let [file_name_without_ext @ .., b'.', b'r', b's'] = file_name else {
|
||||
return None;
|
||||
};
|
||||
|
||||
self.exercise_names
|
||||
.iter()
|
||||
.position(|exercise_name| *exercise_name == file_name_without_ext)
|
||||
})
|
||||
.try_for_each(|exercise_ind| self.sender.send(WatchEvent::FileChange { exercise_ind }));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,10 +18,7 @@ use crate::{
|
|||
term::progress_bar,
|
||||
};
|
||||
|
||||
use super::{
|
||||
terminal_event::{terminal_event_handler, InputPauseGuard},
|
||||
WatchEvent,
|
||||
};
|
||||
use super::{terminal_event::terminal_event_handler, InputPauseGuard, WatchEvent};
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
enum DoneStatus {
|
||||
|
@ -103,14 +100,10 @@ impl<'a> WatchState<'a> {
|
|||
exercise_ind: usize,
|
||||
stdout: &mut StdoutLock,
|
||||
) -> Result<()> {
|
||||
// Don't skip exercises on file changes to avoid confusion from missing exercises.
|
||||
// Skipping exercises must be explicit in the interactive list.
|
||||
// But going back to an earlier exercise on file change is fine.
|
||||
if self.app_state.current_exercise_ind() < exercise_ind {
|
||||
if self.app_state.current_exercise_ind() != exercise_ind {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.app_state.set_current_exercise_ind(exercise_ind)?;
|
||||
self.run_current_exercise(stdout)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,31 +1,7 @@
|
|||
use crossterm::event::{self, Event, KeyCode, KeyEventKind};
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, Ordering::Relaxed},
|
||||
mpsc::Sender,
|
||||
};
|
||||
use std::sync::{atomic::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);
|
||||
}
|
||||
}
|
||||
use super::{WatchEvent, EXERCISE_RUNNING};
|
||||
|
||||
pub enum InputEvent {
|
||||
Run,
|
||||
|
@ -44,7 +20,7 @@ pub fn terminal_event_handler(sender: Sender<WatchEvent>, manual_run: bool) {
|
|||
KeyEventKind::Press => (),
|
||||
}
|
||||
|
||||
if INPUT_PAUSED.load(Relaxed) {
|
||||
if EXERCISE_RUNNING.load(Relaxed) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue