Compare commits

...

8 commits

Author SHA1 Message Date
Kacper Poneta
445544f4ff
Merge 59e8f70e55 into 0c79f2ea3e 2024-09-27 17:17:42 +02:00
mo8it
0c79f2ea3e Reset in prompt with confirmation
Some checks failed
Rustlings Tests / clippy (push) Has been cancelled
Rustlings Tests / fmt (push) Has been cancelled
Rustlings Tests / test (macOS-latest) (push) Has been cancelled
Rustlings Tests / test (ubuntu-latest) (push) Has been cancelled
Rustlings Tests / test (windows-latest) (push) Has been cancelled
Rustlings Tests / dev-check (push) Has been cancelled
Web / Build and deploy site and docs (push) Has been cancelled
2024-09-26 18:15:45 +02:00
mo8it
0e9eb9e87e Replace three dots with dot in hint 2024-09-26 18:05:05 +02:00
mo8it
59e8f70e55 Format code 2024-07-12 18:31:23 +02:00
mo8it
4c8365fe88 Update dev/Cargo.toml 2024-07-12 18:25:01 +02:00
Kacper Poneta
52af0674c1 changed the task to make it more appropriate 2024-07-12 18:14:40 +02:00
Kacper Poneta
938b90e5f2 very small solution update 2024-07-11 22:55:48 +02:00
Kacper Poneta
55cc8584bd added exercise 2024-07-11 22:53:38 +02:00
7 changed files with 210 additions and 16 deletions

View file

@ -116,6 +116,8 @@ bin = [
{ name = "generics1_sol", path = "../solutions/14_generics/generics1.rs" }, { name = "generics1_sol", path = "../solutions/14_generics/generics1.rs" },
{ name = "generics2", path = "../exercises/14_generics/generics2.rs" }, { name = "generics2", path = "../exercises/14_generics/generics2.rs" },
{ name = "generics2_sol", path = "../solutions/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", path = "../exercises/15_traits/traits1.rs" },
{ name = "traits1_sol", path = "../solutions/15_traits/traits1.rs" }, { name = "traits1_sol", path = "../solutions/15_traits/traits1.rs" },
{ name = "traits2", path = "../exercises/15_traits/traits2.rs" }, { name = "traits2", path = "../exercises/15_traits/traits2.rs" },

View 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);
}
}

View file

@ -749,6 +749,17 @@ hint = """
Related section in The Book: Related section in The Book:
https://doc.rust-lang.org/book/ch10-01-syntax.html#in-method-definitions""" 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 # TRAITS
[[exercises]] [[exercises]]
@ -1144,7 +1155,7 @@ constants, but clippy recognizes those imprecise mathematical constants as a
source of potential error. source of potential error.
See the suggestions of the Clippy warning in the compile output and use the See the suggestions of the Clippy warning in the compile output and use the
appropriate replacement constant from `std::f32::consts`...""" appropriate replacement constant from `std::f32::consts`."""
[[exercises]] [[exercises]]
name = "clippy2" name = "clippy2"

View 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);
}
}

View file

@ -100,13 +100,14 @@ fn run_watch(
ExercisesProgress::NewPending => watch_state.run_current_exercise(&mut stdout)?, ExercisesProgress::NewPending => watch_state.run_current_exercise(&mut stdout)?,
ExercisesProgress::CurrentPending => (), ExercisesProgress::CurrentPending => (),
}, },
WatchEvent::Input(InputEvent::Run) => watch_state.run_current_exercise(&mut stdout)?,
WatchEvent::Input(InputEvent::Hint) => watch_state.show_hint(&mut stdout)?, WatchEvent::Input(InputEvent::Hint) => watch_state.show_hint(&mut stdout)?,
WatchEvent::Input(InputEvent::List) => return Ok(WatchExit::List), WatchEvent::Input(InputEvent::List) => return Ok(WatchExit::List),
WatchEvent::Input(InputEvent::Reset) => watch_state.reset_exercise(&mut stdout)?,
WatchEvent::Input(InputEvent::Quit) => { WatchEvent::Input(InputEvent::Quit) => {
stdout.write_all(QUIT_MSG)?; stdout.write_all(QUIT_MSG)?;
break; break;
} }
WatchEvent::Input(InputEvent::Run) => watch_state.run_current_exercise(&mut stdout)?,
WatchEvent::FileChange { exercise_ind } => { WatchEvent::FileChange { exercise_ind } => {
watch_state.handle_file_change(exercise_ind, &mut stdout)?; watch_state.handle_file_change(exercise_ind, &mut stdout)?;
} }

View file

@ -6,8 +6,8 @@ use crossterm::{
terminal, QueueableCommand, terminal, QueueableCommand,
}; };
use std::{ use std::{
io::{self, StdoutLock, Write}, io::{self, Read, StdoutLock, Write},
sync::mpsc::Sender, sync::mpsc::{sync_channel, Sender, SyncSender},
thread, thread,
}; };
@ -34,6 +34,7 @@ pub struct WatchState<'a> {
done_status: DoneStatus, done_status: DoneStatus,
manual_run: bool, manual_run: bool,
term_width: u16, term_width: u16,
terminal_event_unpause_sender: SyncSender<()>,
} }
impl<'a> WatchState<'a> { impl<'a> WatchState<'a> {
@ -46,8 +47,16 @@ impl<'a> WatchState<'a> {
.context("Failed to get the terminal size")? .context("Failed to get the terminal size")?
.0; .0;
let (terminal_event_unpause_sender, terminal_event_unpause_receiver) = sync_channel(0);
thread::Builder::new() thread::Builder::new()
.spawn(move || terminal_event_handler(watch_event_sender, manual_run)) .spawn(move || {
terminal_event_handler(
watch_event_sender,
terminal_event_unpause_receiver,
manual_run,
)
})
.context("Failed to spawn a thread to handle terminal events")?; .context("Failed to spawn a thread to handle terminal events")?;
Ok(Self { Ok(Self {
@ -57,6 +66,7 @@ impl<'a> WatchState<'a> {
done_status: DoneStatus::Pending, done_status: DoneStatus::Pending,
manual_run, manual_run,
term_width, term_width,
terminal_event_unpause_sender,
}) })
} }
@ -95,6 +105,44 @@ impl<'a> WatchState<'a> {
Ok(()) Ok(())
} }
pub fn reset_exercise(&mut self, stdout: &mut StdoutLock) -> Result<()> {
clear_terminal(stdout)?;
stdout.write_all(b"Resetting will undo all your changes to the file ")?;
stdout.write_all(self.app_state.current_exercise().path.as_bytes())?;
stdout.write_all(b"\nReset (y/n)? ")?;
stdout.flush()?;
{
let mut stdin = io::stdin().lock();
let mut answer = [0];
loop {
stdin
.read_exact(&mut answer)
.context("Failed to read the user's input")?;
match answer[0] {
b'y' | b'Y' => {
self.app_state.reset_current_exercise()?;
// The file watcher reruns the exercise otherwise.
if self.manual_run {
self.run_current_exercise(stdout)?;
}
}
b'n' | b'N' => self.render(stdout)?,
_ => continue,
}
break;
}
}
self.terminal_event_unpause_sender.send(())?;
Ok(())
}
pub fn handle_file_change( pub fn handle_file_change(
&mut self, &mut self,
exercise_ind: usize, exercise_ind: usize,
@ -117,13 +165,6 @@ impl<'a> WatchState<'a> {
} }
fn show_prompt(&self, stdout: &mut StdoutLock) -> io::Result<()> { fn show_prompt(&self, stdout: &mut StdoutLock) -> io::Result<()> {
if self.manual_run {
stdout.queue(SetAttribute(Attribute::Bold))?;
stdout.write_all(b"r")?;
stdout.queue(ResetColor)?;
stdout.write_all(b":run / ")?;
}
if self.done_status != DoneStatus::Pending { if self.done_status != DoneStatus::Pending {
stdout.queue(SetAttribute(Attribute::Bold))?; stdout.queue(SetAttribute(Attribute::Bold))?;
stdout.write_all(b"n")?; stdout.write_all(b"n")?;
@ -135,6 +176,13 @@ impl<'a> WatchState<'a> {
stdout.write_all(b" / ")?; stdout.write_all(b" / ")?;
} }
if self.manual_run {
stdout.queue(SetAttribute(Attribute::Bold))?;
stdout.write_all(b"r")?;
stdout.queue(ResetColor)?;
stdout.write_all(b":run / ")?;
}
if !self.show_hint { if !self.show_hint {
stdout.queue(SetAttribute(Attribute::Bold))?; stdout.queue(SetAttribute(Attribute::Bold))?;
stdout.write_all(b"h")?; stdout.write_all(b"h")?;
@ -147,6 +195,11 @@ impl<'a> WatchState<'a> {
stdout.queue(ResetColor)?; stdout.queue(ResetColor)?;
stdout.write_all(b":list / ")?; stdout.write_all(b":list / ")?;
stdout.queue(SetAttribute(Attribute::Bold))?;
stdout.write_all(b"x")?;
stdout.queue(ResetColor)?;
stdout.write_all(b":reset / ")?;
stdout.queue(SetAttribute(Attribute::Bold))?; stdout.queue(SetAttribute(Attribute::Bold))?;
stdout.write_all(b"q")?; stdout.write_all(b"q")?;
stdout.queue(ResetColor)?; stdout.queue(ResetColor)?;

View file

@ -1,17 +1,25 @@
use crossterm::event::{self, Event, KeyCode, KeyEventKind}; use crossterm::event::{self, Event, KeyCode, KeyEventKind};
use std::sync::{atomic::Ordering::Relaxed, mpsc::Sender}; use std::sync::{
atomic::Ordering::Relaxed,
mpsc::{Receiver, Sender},
};
use super::{WatchEvent, EXERCISE_RUNNING}; use super::{WatchEvent, EXERCISE_RUNNING};
pub enum InputEvent { pub enum InputEvent {
Run,
Next, Next,
Run,
Hint, Hint,
List, List,
Reset,
Quit, Quit,
} }
pub fn terminal_event_handler(sender: Sender<WatchEvent>, manual_run: bool) { pub fn terminal_event_handler(
sender: Sender<WatchEvent>,
unpause_receiver: Receiver<()>,
manual_run: bool,
) {
let last_watch_event = loop { let last_watch_event = loop {
match event::read() { match event::read() {
Ok(Event::Key(key)) => { Ok(Event::Key(key)) => {
@ -26,10 +34,22 @@ pub fn terminal_event_handler(sender: Sender<WatchEvent>, manual_run: bool) {
let input_event = match key.code { let input_event = match key.code {
KeyCode::Char('n') => InputEvent::Next, KeyCode::Char('n') => InputEvent::Next,
KeyCode::Char('r') if manual_run => InputEvent::Run,
KeyCode::Char('h') => InputEvent::Hint, KeyCode::Char('h') => InputEvent::Hint,
KeyCode::Char('l') => break WatchEvent::Input(InputEvent::List), KeyCode::Char('l') => break WatchEvent::Input(InputEvent::List),
KeyCode::Char('x') => {
if sender.send(WatchEvent::Input(InputEvent::Reset)).is_err() {
return;
}
// Pause input until quitting the confirmation prompt.
if unpause_receiver.recv().is_err() {
return;
};
continue;
}
KeyCode::Char('q') => break WatchEvent::Input(InputEvent::Quit), KeyCode::Char('q') => break WatchEvent::Input(InputEvent::Quit),
KeyCode::Char('r') if manual_run => InputEvent::Run,
_ => continue, _ => continue,
}; };