tuyere/
command.rs

1//! Commands for side effects.
2
3use std::future::Future;
4use std::pin::Pin;
5
6/// A command that can be returned from `update`.
7///
8/// Commands represent side effects that should happen after
9/// updating the model.
10pub struct Command<M> {
11    pub(crate) kind: CommandKind<M>,
12}
13
14pub(crate) enum CommandKind<M> {
15    /// No operation.
16    None,
17    /// Quit the application.
18    Quit,
19    /// A batch of commands.
20    Batch(Vec<Command<M>>),
21    /// An async task that produces a message.
22    #[allow(dead_code)]
23    Task(Pin<Box<dyn Future<Output = M> + Send + 'static>>),
24    /// A simple message to send.
25    Message(M),
26}
27
28impl<M> Command<M> {
29    /// Create a command that does nothing.
30    #[must_use]
31    pub fn none() -> Self {
32        Self {
33            kind: CommandKind::None,
34        }
35    }
36
37    /// Create a command that quits the application.
38    #[must_use]
39    pub fn quit() -> Self {
40        Self {
41            kind: CommandKind::Quit,
42        }
43    }
44
45    /// Create a command that sends a message.
46    #[must_use]
47    pub fn message(msg: M) -> Self {
48        Self {
49            kind: CommandKind::Message(msg),
50        }
51    }
52
53    /// Batch multiple commands together.
54    #[must_use]
55    pub fn batch(commands: Vec<Command<M>>) -> Self {
56        Self {
57            kind: CommandKind::Batch(commands),
58        }
59    }
60
61    /// Create a command from an async task.
62    ///
63    /// The task will be spawned and its result will be sent as a message.
64    pub fn perform<F, Fut>(task: F) -> Self
65    where
66        F: FnOnce() -> Fut,
67        Fut: Future<Output = M> + Send + 'static,
68        M: Send + 'static,
69    {
70        Self {
71            kind: CommandKind::Task(Box::pin(task())),
72        }
73    }
74
75    /// Check if this is a quit command.
76    #[must_use]
77    pub fn is_quit(&self) -> bool {
78        matches!(self.kind, CommandKind::Quit)
79    }
80
81    /// Check if this is a no-op command.
82    #[must_use]
83    pub fn is_none(&self) -> bool {
84        matches!(self.kind, CommandKind::None)
85    }
86}
87
88impl<M> Default for Command<M> {
89    fn default() -> Self {
90        Self::none()
91    }
92}
93
94/// Alias for Command (matches bubbletea naming).
95pub type Cmd<M> = Command<M>;