Compare commits

..

4 commits

Author SHA1 Message Date
Kacper Poneta
aa2f089f51
Merge 59e8f70e55 into 0d258b9e96 2024-09-26 20:59:19 +05:30
mo8it
0d258b9e96 Update deps
Some checks are pending
Rustlings Tests / clippy (push) Waiting to run
Rustlings Tests / fmt (push) Waiting to run
Rustlings Tests / test (macOS-latest) (push) Waiting to run
Rustlings Tests / test (ubuntu-latest) (push) Waiting to run
Rustlings Tests / test (windows-latest) (push) Waiting to run
Rustlings Tests / dev-check (push) Waiting to run
Web / Build and deploy site and docs (push) Waiting to run
2024-09-26 12:28:48 +02:00
mo8it
d4fa61e435 Debounce file change events 2024-09-26 12:26:24 +02:00
mo8it
554301b8e9 Clear terminal before final check in watch mode 2024-09-24 16:12:44 +02:00
7 changed files with 114 additions and 41 deletions

32
Cargo.lock generated
View file

@ -95,9 +95,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.17" version = "4.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" checksum = "b0956a43b323ac1afaffc053ed5c4b7c1f1800bacd1683c353aabbb752515dd3"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@ -105,9 +105,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.17" version = "4.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" checksum = "4d72166dd41634086d5803a47eb71ae740e61d84709c36f3c34110173db3961b"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@ -117,9 +117,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.5.13" version = "4.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
@ -289,9 +289,9 @@ dependencies = [
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.158" version = "0.2.159"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5"
[[package]] [[package]]
name = "libredox" name = "libredox"
@ -434,9 +434,9 @@ dependencies = [
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.5.4" version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853" checksum = "355ae415ccd3a04315d3f8246e86d67689ea74d88d915576e1589a351062a13b"
dependencies = [ dependencies = [
"bitflags 2.6.0", "bitflags 2.6.0",
] ]
@ -536,9 +536,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_spanned" name = "serde_spanned"
version = "0.6.7" version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
dependencies = [ dependencies = [
"serde", "serde",
] ]
@ -620,9 +620,9 @@ dependencies = [
[[package]] [[package]]
name = "toml_edit" name = "toml_edit"
version = "0.22.21" version = "0.22.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b072cee73c449a636ffd6f32bd8de3a9f7119139aff882f44943ce2986dc5cf" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"serde", "serde",
@ -846,9 +846,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "0.6.18" version = "0.6.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]

View file

@ -20,7 +20,7 @@ rust-version = "1.80"
[workspace.dependencies] [workspace.dependencies]
serde = { version = "1.0.210", features = ["derive"] } serde = { version = "1.0.210", features = ["derive"] }
toml_edit = { version = "0.22.21", default-features = false, features = ["parse", "serde"] } toml_edit = { version = "0.22.22", default-features = false, features = ["parse", "serde"] }
[package] [package]
name = "rustlings" name = "rustlings"
@ -48,7 +48,7 @@ include = [
[dependencies] [dependencies]
ahash = { version = "0.8.11", default-features = false } ahash = { version = "0.8.11", default-features = false }
anyhow = "1.0.89" anyhow = "1.0.89"
clap = { version = "4.5.17", features = ["derive"] } clap = { version = "4.5.18", features = ["derive"] }
crossterm = { version = "0.28.1", default-features = false, features = ["windows", "events"] } crossterm = { version = "0.28.1", default-features = false, features = ["windows", "events"] }
notify = { version = "6.1.1", default-features = false, features = ["macos_fsevent"] } notify = { version = "6.1.1", default-features = false, features = ["macos_fsevent"] }
os_pipe = "1.2.1" os_pipe = "1.2.1"

View file

@ -381,7 +381,7 @@ impl AppState {
// Return the exercise index of the first pending exercise found. // Return the exercise index of the first pending exercise found.
fn check_all_exercises(&self, stdout: &mut StdoutLock) -> Result<Option<usize>> { fn check_all_exercises(&self, stdout: &mut StdoutLock) -> Result<Option<usize>> {
stdout.write_all(RERUNNING_ALL_EXERCISES_MSG)?; stdout.write_all(FINAL_CHECK_MSG)?;
let n_exercises = self.exercises.len(); let n_exercises = self.exercises.len();
let status = thread::scope(|s| { let status = thread::scope(|s| {
@ -441,7 +441,10 @@ impl AppState {
/// Mark the current exercise as done and move on to the next pending exercise if one exists. /// Mark the current exercise as done and move on to the next pending exercise if one exists.
/// If all exercises are marked as done, run all of them to make sure that they are actually /// If all exercises are marked as done, run all of them to make sure that they are actually
/// done. If an exercise which is marked as done fails, mark it as pending and continue on it. /// done. If an exercise which is marked as done fails, mark it as pending and continue on it.
pub fn done_current_exercise(&mut self, stdout: &mut StdoutLock) -> Result<ExercisesProgress> { pub fn done_current_exercise<const CLEAR_BEFORE_FINAL_CHECK: bool>(
&mut self,
stdout: &mut StdoutLock,
) -> Result<ExercisesProgress> {
let exercise = &mut self.exercises[self.current_exercise_ind]; let exercise = &mut self.exercises[self.current_exercise_ind];
if !exercise.done { if !exercise.done {
exercise.done = true; exercise.done = true;
@ -453,6 +456,12 @@ impl AppState {
return Ok(ExercisesProgress::NewPending); return Ok(ExercisesProgress::NewPending);
} }
if CLEAR_BEFORE_FINAL_CHECK {
clear_terminal(stdout)?;
} else {
stdout.write_all(b"\n")?;
}
if let Some(pending_exercise_ind) = self.check_all_exercises(stdout)? { if let Some(pending_exercise_ind) = self.check_all_exercises(stdout)? {
stdout.write_all(b"\n\n")?; stdout.write_all(b"\n\n")?;
@ -482,8 +491,7 @@ impl AppState {
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";
const STATE_FILE_HEADER: &[u8] = b"DON'T EDIT THIS FILE!\n\n"; const STATE_FILE_HEADER: &[u8] = b"DON'T EDIT THIS FILE!\n\n";
const RERUNNING_ALL_EXERCISES_MSG: &[u8] = b" const FINAL_CHECK_MSG: &[u8] = b"All exercises seem to be done.
All exercises seem to be done.
Recompiling and running all exercises to make sure that all of them are actually done. Recompiling and running all exercises to make sure that all of them are actually done.
"; ";
const FENISH_LINE: &str = "+----------------------------------------------------+ const FENISH_LINE: &str = "+----------------------------------------------------+

View file

@ -44,7 +44,7 @@ pub fn run(app_state: &mut AppState) -> Result<()> {
stdout.write_all(b"\n")?; stdout.write_all(b"\n")?;
} }
match app_state.done_current_exercise(&mut stdout)? { match app_state.done_current_exercise::<false>(&mut stdout)? {
ExercisesProgress::NewPending | ExercisesProgress::CurrentPending => { ExercisesProgress::NewPending | ExercisesProgress::CurrentPending => {
stdout.write_all(b"Next exercise: ")?; stdout.write_all(b"Next exercise: ")?;
app_state app_state

View file

@ -69,11 +69,11 @@ fn run_watch(
// Prevent dropping the guard until the end of the function. // Prevent dropping the guard until the end of the function.
// Otherwise, the file watcher exits. // Otherwise, the file watcher exits.
let _watcher_guard = if let Some(exercise_names) = notify_exercise_names { let _watcher_guard = if let Some(exercise_names) = notify_exercise_names {
let notify_event_handler =
NotifyEventHandler::build(watch_event_sender.clone(), exercise_names)?;
let mut watcher = RecommendedWatcher::new( let mut watcher = RecommendedWatcher::new(
NotifyEventHandler { notify_event_handler,
sender: watch_event_sender.clone(),
exercise_names,
},
Config::default().with_poll_interval(Duration::from_secs(1)), Config::default().with_poll_interval(Duration::from_secs(1)),
) )
.inspect_err(|_| eprintln!("{NOTIFY_ERR}"))?; .inspect_err(|_| eprintln!("{NOTIFY_ERR}"))?;

View file

@ -1,15 +1,71 @@
use anyhow::{Context, Result};
use notify::{ use notify::{
event::{MetadataKind, ModifyKind}, event::{AccessKind, AccessMode, MetadataKind, ModifyKind, RenameMode},
Event, EventKind, Event, EventKind,
}; };
use std::sync::{atomic::Ordering::Relaxed, mpsc::Sender}; use std::{
sync::{
atomic::Ordering::Relaxed,
mpsc::{sync_channel, RecvTimeoutError, Sender, SyncSender},
},
thread,
time::Duration,
};
use super::{WatchEvent, EXERCISE_RUNNING}; use super::{WatchEvent, EXERCISE_RUNNING};
const DEBOUNCE_DURATION: Duration = Duration::from_millis(200);
pub struct NotifyEventHandler { pub struct NotifyEventHandler {
pub sender: Sender<WatchEvent>, error_sender: Sender<WatchEvent>,
/// Used to report which exercise was modified. // Sends the index of the updated exercise.
pub exercise_names: &'static [&'static [u8]], update_sender: SyncSender<usize>,
// Used to report which exercise was modified.
exercise_names: &'static [&'static [u8]],
}
impl NotifyEventHandler {
pub fn build(
watch_event_sender: Sender<WatchEvent>,
exercise_names: &'static [&'static [u8]],
) -> Result<Self> {
let (update_sender, update_receiver) = sync_channel(0);
let error_sender = watch_event_sender.clone();
// Debouncer
thread::Builder::new()
.spawn(move || {
let mut exercise_updated = vec![false; exercise_names.len()];
loop {
match update_receiver.recv_timeout(DEBOUNCE_DURATION) {
Ok(exercise_ind) => exercise_updated[exercise_ind] = true,
Err(RecvTimeoutError::Timeout) => {
for (exercise_ind, updated) in exercise_updated.iter_mut().enumerate() {
if *updated {
if watch_event_sender
.send(WatchEvent::FileChange { exercise_ind })
.is_err()
{
break;
}
*updated = false;
}
}
}
Err(RecvTimeoutError::Disconnected) => break,
}
}
})
.context("Failed to spawn a thread to debounce file changes")?;
Ok(Self {
error_sender,
update_sender,
exercise_names,
})
}
} }
impl notify::EventHandler for NotifyEventHandler { impl notify::EventHandler for NotifyEventHandler {
@ -22,8 +78,8 @@ impl notify::EventHandler for NotifyEventHandler {
Ok(v) => v, Ok(v) => v,
Err(e) => { Err(e) => {
// An error occurs when the receiver is dropped. // An error occurs when the receiver is dropped.
// After dropping the receiver, the debouncer guard should also be dropped. // After dropping the receiver, the watcher guard should also be dropped.
let _ = self.sender.send(WatchEvent::NotifyErr(e)); let _ = self.error_sender.send(WatchEvent::NotifyErr(e));
return; return;
} }
}; };
@ -32,6 +88,10 @@ impl notify::EventHandler for NotifyEventHandler {
EventKind::Any => (), EventKind::Any => (),
EventKind::Modify(modify_kind) => match modify_kind { EventKind::Modify(modify_kind) => match modify_kind {
ModifyKind::Any | ModifyKind::Data(_) => (), ModifyKind::Any | ModifyKind::Data(_) => (),
ModifyKind::Name(rename_mode) => match rename_mode {
RenameMode::Any | RenameMode::To => (),
RenameMode::From | RenameMode::Both | RenameMode::Other => return,
},
ModifyKind::Metadata(metadata_kind) => match metadata_kind { ModifyKind::Metadata(metadata_kind) => match metadata_kind {
MetadataKind::Any | MetadataKind::WriteTime => (), MetadataKind::Any | MetadataKind::WriteTime => (),
MetadataKind::AccessTime MetadataKind::AccessTime
@ -40,12 +100,17 @@ impl notify::EventHandler for NotifyEventHandler {
| MetadataKind::Extended | MetadataKind::Extended
| MetadataKind::Other => return, | MetadataKind::Other => return,
}, },
ModifyKind::Name(_) | ModifyKind::Other => return, ModifyKind::Other => return,
}, },
EventKind::Access(_) EventKind::Access(access_kind) => match access_kind {
| EventKind::Create(_) AccessKind::Any => (),
| EventKind::Remove(_) AccessKind::Close(access_mode) => match access_mode {
| EventKind::Other => return, AccessMode::Any | AccessMode::Write => (),
AccessMode::Execute | AccessMode::Read | AccessMode::Other => return,
},
AccessKind::Read | AccessKind::Open(_) | AccessKind::Other => return,
},
EventKind::Create(_) | EventKind::Remove(_) | EventKind::Other => return,
} }
let _ = input_event let _ = input_event
@ -62,6 +127,6 @@ impl notify::EventHandler for NotifyEventHandler {
.iter() .iter()
.position(|exercise_name| *exercise_name == file_name_without_ext) .position(|exercise_name| *exercise_name == file_name_without_ext)
}) })
.try_for_each(|exercise_ind| self.sender.send(WatchEvent::FileChange { exercise_ind })); .try_for_each(|exercise_ind| self.update_sender.send(exercise_ind));
} }
} }

View file

@ -113,7 +113,7 @@ impl<'a> WatchState<'a> {
return Ok(ExercisesProgress::CurrentPending); return Ok(ExercisesProgress::CurrentPending);
} }
self.app_state.done_current_exercise(stdout) self.app_state.done_current_exercise::<true>(stdout)
} }
fn show_prompt(&self, stdout: &mut StdoutLock) -> io::Result<()> { fn show_prompt(&self, stdout: &mut StdoutLock) -> io::Result<()> {