diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 850539756b..3630501db3 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -116,6 +116,7 @@ pub fn create_default_context() -> EngineState { Select, Shuffle, Size, + Sleep, Source, Split, SplitChars, diff --git a/crates/nu-command/src/platform/mod.rs b/crates/nu-command/src/platform/mod.rs index 0d2cdbdbdc..0a8fc97265 100644 --- a/crates/nu-command/src/platform/mod.rs +++ b/crates/nu-command/src/platform/mod.rs @@ -1,3 +1,5 @@ mod clear; +mod sleep; pub use clear::Clear; +pub use sleep::Sleep; diff --git a/crates/nu-command/src/platform/sleep.rs b/crates/nu-command/src/platform/sleep.rs new file mode 100644 index 0000000000..9591de75a3 --- /dev/null +++ b/crates/nu-command/src/platform/sleep.rs @@ -0,0 +1,108 @@ +use nu_engine::CallExt; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ + Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Value, +}; +use std::{ + sync::atomic::Ordering, + thread, + time::{Duration, Instant}, +}; + +const CTRL_C_CHECK_INTERVAL: Duration = Duration::from_millis(100); + +#[derive(Clone)] +pub struct Sleep; + +impl Command for Sleep { + fn name(&self) -> &str { + "sleep" + } + + fn usage(&self) -> &str { + "Delay for a specified amount of time." + } + + fn signature(&self) -> Signature { + Signature::build("sleep") + .required("duration", SyntaxShape::Duration, "time to sleep") + .rest("rest", SyntaxShape::Duration, "additional time") + .category(Category::Platform) + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + fn duration_from_i64(val: i64) -> Duration { + Duration::from_nanos(if val < 0 { 0 } else { val as u64 }) + } + + let duration: i64 = call.req(engine_state, stack, 0)?; + let rest: Vec = call.rest(engine_state, stack, 1)?; + + let total_dur = + duration_from_i64(duration) + rest.into_iter().map(duration_from_i64).sum::(); + + let ctrlc_ref = &engine_state.ctrlc.clone(); + let start = Instant::now(); + loop { + thread::sleep(CTRL_C_CHECK_INTERVAL); + if start.elapsed() >= total_dur { + break; + } + + if let Some(ctrlc) = ctrlc_ref { + if ctrlc.load(Ordering::SeqCst) { + break; + } + } + } + + Ok(Value::Nothing { span: call.head }.into_pipeline_data()) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Sleep for 1sec", + example: "sleep 1sec", + result: None, + }, + Example { + description: "Sleep for 3sec", + example: "sleep 1sec 1sec 1sec", + result: None, + }, + Example { + description: "Send output after 1sec", + example: "sleep 1sec; echo done", + result: Some(Value::test_string("done")), + }, + ] + } +} + +#[cfg(test)] +mod tests { + use super::Sleep; + + #[test] + fn examples_work_as_expected() { + use crate::test_examples; + use std::time::Instant; + + let start = Instant::now(); + test_examples(Sleep {}); + + let elapsed = start.elapsed(); + + // only examples with actual output are run + assert!(elapsed >= std::time::Duration::from_secs(1)); + assert!(elapsed < std::time::Duration::from_secs(2)); + } +}