mirror of
https://github.com/nushell/nushell.git
synced 2025-06-12 04:56:56 +02:00
Implement mail commands
This commit is contained in:
parent
3ed97e68dc
commit
f15fe84df6
@ -453,6 +453,10 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
|||||||
JobKill,
|
JobKill,
|
||||||
JobId,
|
JobId,
|
||||||
Job,
|
Job,
|
||||||
|
MailSend,
|
||||||
|
MailRecv,
|
||||||
|
MailClear,
|
||||||
|
Mail,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(all(unix, feature = "os"))]
|
#[cfg(all(unix, feature = "os"))]
|
||||||
|
34
crates/nu-command/src/experimental/mail.rs
Normal file
34
crates/nu-command/src/experimental/mail.rs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
use nu_engine::{command_prelude::*, get_full_help};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Mail;
|
||||||
|
|
||||||
|
impl Command for Mail {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"mail"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("mail")
|
||||||
|
.category(Category::Strings)
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::String)])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
"Various commands for job communication."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extra_description(&self) -> &str {
|
||||||
|
"You must use one of the following subcommands. Using this command as-is will only produce this help message."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
Ok(Value::string(get_full_help(self, engine_state, stack), call.head).into_pipeline_data())
|
||||||
|
}
|
||||||
|
}
|
58
crates/nu-command/src/experimental/mail_clear.rs
Normal file
58
crates/nu-command/src/experimental/mail_clear.rs
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
use nu_engine::command_prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct MailClear;
|
||||||
|
|
||||||
|
impl Command for MailClear {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"mail clear"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
"Clear mailbox."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extra_description(&self) -> &str {
|
||||||
|
r#"
|
||||||
|
This command removes all messages in the mailbox of the current job.
|
||||||
|
If a message is received while this command is executing, it may also be discarded.
|
||||||
|
"#
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
|
Signature::build("mail flush")
|
||||||
|
.category(Category::Experimental)
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||||
|
.allow_variants_without_examples(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
_stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let mut mailbox = engine_state
|
||||||
|
.current_job
|
||||||
|
.mailbox
|
||||||
|
.lock()
|
||||||
|
.expect("failed to acquire lock");
|
||||||
|
|
||||||
|
mailbox.clear();
|
||||||
|
|
||||||
|
Ok(Value::nothing(call.head).into_pipeline_data())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![Example {
|
||||||
|
example: "let id = job spawn { mail recv | save sent.txt }; 'hi' | mail send $id",
|
||||||
|
description: "Send a message to a newly spawned job",
|
||||||
|
result: None,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
119
crates/nu-command/src/experimental/mail_recv.rs
Normal file
119
crates/nu-command/src/experimental/mail_recv.rs
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
use std::{sync::mpsc::RecvTimeoutError, time::Duration};
|
||||||
|
|
||||||
|
use nu_engine::command_prelude::*;
|
||||||
|
use nu_protocol::engine::Tag;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct MailRecv;
|
||||||
|
|
||||||
|
const CTRL_C_CHECK_INTERVAL: Duration = Duration::from_millis(100);
|
||||||
|
|
||||||
|
impl Command for MailRecv {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"mail recv"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
"Read a message from the mailbox"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extra_description(&self) -> &str {
|
||||||
|
r#"When messages are sent to the current process, they get stored in what is called the "mailbox".
|
||||||
|
This commands reads and returns a message from the mailbox, in a first-in-first-out fashion.
|
||||||
|
j
|
||||||
|
Messages may have numeric flags attatched to them. This commands supports filtering out messages that do not satisfy a given tag, by using the `tag` flag.
|
||||||
|
If no tag is specified, this command will accept any message.
|
||||||
|
|
||||||
|
If no message with the specified tag (if any) is available in the mailbox, this command will block the current thread until one arrives.
|
||||||
|
By default this command block indefinitely until a matching message arrives, but a timeout duration can be specified.
|
||||||
|
|
||||||
|
Note: When using par-each, only one thread at a time can utilize this command.
|
||||||
|
In the case of two or more threads running this command, they will wait until other threads are done using it,
|
||||||
|
in no particular order, regardless of the specified timeout parameter.
|
||||||
|
"#
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
|
Signature::build("mail recv")
|
||||||
|
.category(Category::Experimental)
|
||||||
|
.named("tag", SyntaxShape::Int, "A tag for the message", None)
|
||||||
|
.named(
|
||||||
|
"timeout",
|
||||||
|
SyntaxShape::Duration,
|
||||||
|
"The maximum duration to wait for",
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.input_output_types(vec![(Type::Nothing, Type::Any)])
|
||||||
|
.allow_variants_without_examples(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec!["receive"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
|
||||||
|
let tag_arg: Option<Spanned<i64>> = call.get_flag(engine_state, stack, "tag")?;
|
||||||
|
|
||||||
|
if let Some(tag) = tag_arg {
|
||||||
|
if tag.item < 0 {
|
||||||
|
return Err(ShellError::NeedsPositiveValue { span: tag.span });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let tag = tag_arg.map(|it| it.item as Tag);
|
||||||
|
|
||||||
|
let duration: Option<i64> = call.get_flag(engine_state, stack, "timeout")?;
|
||||||
|
|
||||||
|
let timeout = duration.map(|it| Duration::from_nanos(it as u64));
|
||||||
|
|
||||||
|
let mut mailbox = engine_state
|
||||||
|
.current_job
|
||||||
|
.mailbox
|
||||||
|
.lock()
|
||||||
|
.expect("failed to acquire lock");
|
||||||
|
|
||||||
|
if let Some(timeout) = timeout {
|
||||||
|
let value = mailbox
|
||||||
|
.recv_timeout(tag, timeout)
|
||||||
|
.map_err(|error| match error {
|
||||||
|
RecvTimeoutError::Timeout => ShellError::RecvTimeout { span: head },
|
||||||
|
|
||||||
|
// if the channel was disconnected, it means this job was removed from the job
|
||||||
|
// table, so it was killed/interrupted
|
||||||
|
RecvTimeoutError::Disconnected => ShellError::Interrupted { span: head },
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(value.into_pipeline_data())
|
||||||
|
} else {
|
||||||
|
loop {
|
||||||
|
if engine_state.signals().interrupted() {
|
||||||
|
return Err(ShellError::Interrupted { span: head });
|
||||||
|
}
|
||||||
|
|
||||||
|
match mailbox.recv_timeout(tag, CTRL_C_CHECK_INTERVAL) {
|
||||||
|
Ok(value) => return Ok(value.into_pipeline_data()),
|
||||||
|
Err(RecvTimeoutError::Timeout) => {} // try again
|
||||||
|
Err(RecvTimeoutError::Disconnected) => {
|
||||||
|
return Err(ShellError::Interrupted { span: head })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![Example {
|
||||||
|
example: "mail recv",
|
||||||
|
description: "Block the current thread while no message arrives",
|
||||||
|
result: None,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
113
crates/nu-command/src/experimental/mail_send.rs
Normal file
113
crates/nu-command/src/experimental/mail_send.rs
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
use nu_engine::command_prelude::*;
|
||||||
|
use nu_protocol::{engine::Tag, JobId};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct MailSend;
|
||||||
|
|
||||||
|
impl Command for MailSend {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"mail send"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
"Send a message to a job"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extra_description(&self) -> &str {
|
||||||
|
r#"
|
||||||
|
This command sends a message to a background job, which can then read sent messages
|
||||||
|
in a first-in-first-out fashion with `mail recv`. When it does so, it may additionally specify a numeric filter tag,
|
||||||
|
in which case it will only read messages sent with the exact same filter tag.
|
||||||
|
|
||||||
|
A message can be any nushell value, and streams are always collected before being sent.
|
||||||
|
|
||||||
|
This command never blocks.
|
||||||
|
"#
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
|
Signature::build("mail send")
|
||||||
|
.category(Category::Experimental)
|
||||||
|
.required(
|
||||||
|
"id",
|
||||||
|
SyntaxShape::Int,
|
||||||
|
"The id of the job to send the message to",
|
||||||
|
)
|
||||||
|
.named("tag", SyntaxShape::Int, "A tag for the message", None)
|
||||||
|
.input_output_types(vec![(Type::Any, Type::Nothing)])
|
||||||
|
.allow_variants_without_examples(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
|
||||||
|
let id_arg: Spanned<i64> = call.req(engine_state, stack, 0)?;
|
||||||
|
let tag_arg: Option<Spanned<i64>> = call.get_flag(engine_state, stack, "tag")?;
|
||||||
|
|
||||||
|
let id = id_arg.item;
|
||||||
|
|
||||||
|
if id < 0 {
|
||||||
|
return Err(ShellError::NeedsPositiveValue { span: id_arg.span });
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(tag) = tag_arg {
|
||||||
|
if tag.item < 0 {
|
||||||
|
return Err(ShellError::NeedsPositiveValue { span: tag.span });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let tag = tag_arg.map(|it| it.item as Tag);
|
||||||
|
|
||||||
|
let value = input.into_value(head)?;
|
||||||
|
|
||||||
|
if id == 0 {
|
||||||
|
engine_state
|
||||||
|
.root_job_sender
|
||||||
|
.send((tag, value))
|
||||||
|
.expect("this should NEVER happen.");
|
||||||
|
} else {
|
||||||
|
let jobs = engine_state.jobs.lock().expect("failed to acquire lock");
|
||||||
|
|
||||||
|
if let Some(job) = jobs.lookup(JobId::new(id as usize)) {
|
||||||
|
match job {
|
||||||
|
nu_protocol::engine::Job::Thread(thread_job) => {
|
||||||
|
// it is ok to send this value while holding the lock, because
|
||||||
|
// mail channels are always unbounded, so this send never blocks
|
||||||
|
let _ = thread_job.sender.send((tag, value));
|
||||||
|
}
|
||||||
|
nu_protocol::engine::Job::Frozen(_) => {
|
||||||
|
return Err(ShellError::NeedThreadJob {
|
||||||
|
id: id as usize,
|
||||||
|
span: id_arg.span,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(ShellError::JobNotFound {
|
||||||
|
id: id as usize,
|
||||||
|
span: id_arg.span,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Value::nothing(head).into_pipeline_data())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![Example {
|
||||||
|
example: "let id = job spawn { mail recv | save sent.txt }; 'hi' | mail send $id",
|
||||||
|
description: "Send a message to a newly spawned job",
|
||||||
|
result: None,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,11 @@ mod job_kill;
|
|||||||
mod job_list;
|
mod job_list;
|
||||||
mod job_spawn;
|
mod job_spawn;
|
||||||
|
|
||||||
|
mod mail;
|
||||||
|
mod mail_clear;
|
||||||
|
mod mail_recv;
|
||||||
|
mod mail_send;
|
||||||
|
|
||||||
#[cfg(all(unix, feature = "os"))]
|
#[cfg(all(unix, feature = "os"))]
|
||||||
mod job_unfreeze;
|
mod job_unfreeze;
|
||||||
|
|
||||||
@ -15,5 +20,10 @@ pub use job_kill::JobKill;
|
|||||||
pub use job_list::JobList;
|
pub use job_list::JobList;
|
||||||
pub use job_spawn::JobSpawn;
|
pub use job_spawn::JobSpawn;
|
||||||
|
|
||||||
|
pub use mail::Mail;
|
||||||
|
pub use mail_clear::MailClear;
|
||||||
|
pub use mail_recv::MailRecv;
|
||||||
|
pub use mail_send::MailSend;
|
||||||
|
|
||||||
#[cfg(all(unix, feature = "os"))]
|
#[cfg(all(unix, feature = "os"))]
|
||||||
pub use job_unfreeze::JobUnfreeze;
|
pub use job_unfreeze::JobUnfreeze;
|
||||||
|
@ -1360,6 +1360,27 @@ On Windows, this would be %USERPROFILE%\AppData\Roaming"#
|
|||||||
span: Span,
|
span: Span,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
#[error("The job {id} is not a thread job")]
|
||||||
|
#[diagnostic(
|
||||||
|
code(nu::shell::os_disabled),
|
||||||
|
help("The operation you tried to perform cannot be done on this kind of job")
|
||||||
|
)]
|
||||||
|
NeedThreadJob {
|
||||||
|
id: usize,
|
||||||
|
#[label = "not a thread job"]
|
||||||
|
span: Span,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[error("No message was received in the requested time interval")]
|
||||||
|
#[diagnostic(
|
||||||
|
code(nu::shell::os_disabled),
|
||||||
|
help("No message arrived within the specified time limit")
|
||||||
|
)]
|
||||||
|
RecvTimeout {
|
||||||
|
#[label = "timeout"]
|
||||||
|
span: Span,
|
||||||
|
},
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
#[diagnostic(transparent)]
|
#[diagnostic(transparent)]
|
||||||
ChainedError(ChainedError),
|
ChainedError(ChainedError),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user