//! Listen to external events in your application. mod tracker; pub use tracker::Tracker; use futures::stream::BoxStream; /// A request to listen to external events. /// /// Besides performing async actions on demand with [`Command`], most /// applications also need to listen to external events passively. /// /// A [`Subscription`] is normally provided to some runtime, like a [`Command`], /// and it will generate events as long as the user keeps requesting it. /// /// For instance, you can use a [`Subscription`] to listen to a WebSocket /// connection, keyboard presses, mouse events, time ticks, etc. /// /// This type is normally aliased by runtimes with a specific `Event` and/or /// `Hasher`. /// /// [`Command`]: ../struct.Command.html /// [`Subscription`]: struct.Subscription.html pub struct Subscription { recipes: Vec>>, } impl Subscription where H: std::hash::Hasher, { /// Returns an empty [`Subscription`] that will not produce any output. /// /// [`Subscription`]: struct.Subscription.html pub fn none() -> Self { Self { recipes: Vec::new(), } } /// Creates a [`Subscription`] from a [`Recipe`] describing it. /// /// [`Subscription`]: struct.Subscription.html /// [`Recipe`]: trait.Recipe.html pub fn from_recipe( recipe: impl Recipe + 'static, ) -> Self { Self { recipes: vec![Box::new(recipe)], } } /// Batches all the provided subscriptions and returns the resulting /// [`Subscription`]. /// /// [`Subscription`]: struct.Subscription.html pub fn batch( subscriptions: impl IntoIterator>, ) -> Self { Self { recipes: subscriptions .into_iter() .flat_map(|subscription| subscription.recipes) .collect(), } } /// Returns the different recipes of the [`Subscription`]. /// /// [`Subscription`]: struct.Subscription.html pub fn recipes(self) -> Vec>> { self.recipes } /// Transforms the [`Subscription`] output with the given function. /// /// [`Subscription`]: struct.Subscription.html pub fn map( mut self, f: impl Fn(O) -> A + Send + Sync + 'static, ) -> Subscription where H: 'static, E: 'static, O: 'static, A: 'static, { let function = std::sync::Arc::new(f); Subscription { recipes: self .recipes .drain(..) .map(|recipe| { Box::new(Map::new(recipe, function.clone())) as Box> }) .collect(), } } } impl std::fmt::Debug for Subscription { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Subscription").finish() } } /// The description of a [`Subscription`]. /// /// A [`Recipe`] is the internal definition of a [`Subscription`]. It is used /// by runtimes to run and identify subscriptions. You can use it to create your /// own! /// /// [`Subscription`]: struct.Subscription.html /// [`Recipe`]: trait.Recipe.html pub trait Recipe { /// The events that will be produced by a [`Subscription`] with this /// [`Recipe`]. /// /// [`Subscription`]: struct.Subscription.html /// [`Recipe`]: trait.Recipe.html type Output; /// Hashes the [`Recipe`]. /// /// This is used by runtimes to uniquely identify a [`Subscription`]. /// /// [`Subscription`]: struct.Subscription.html /// [`Recipe`]: trait.Recipe.html fn hash(&self, state: &mut Hasher); /// Executes the [`Recipe`] and produces the stream of events of its /// [`Subscription`]. /// /// It receives some stream of generic events, which is normally defined by /// shells. /// /// [`Subscription`]: struct.Subscription.html /// [`Recipe`]: trait.Recipe.html fn stream( self: Box, input: BoxStream<'static, Event>, ) -> BoxStream<'static, Self::Output>; } struct Map { recipe: Box>, mapper: std::sync::Arc B + Send + Sync>, } impl Map { fn new( recipe: Box>, mapper: std::sync::Arc B + Send + Sync + 'static>, ) -> Self { Map { recipe, mapper } } } impl Recipe for Map where A: 'static, B: 'static, H: std::hash::Hasher, { type Output = B; fn hash(&self, state: &mut H) { use std::hash::Hash; std::any::TypeId::of::().hash(state); self.recipe.hash(state); } fn stream( self: Box, input: BoxStream<'static, E>, ) -> futures::stream::BoxStream<'static, Self::Output> { use futures::StreamExt; let mapper = self.mapper; self.recipe .stream(input) .map(move |element| mapper(element)) .boxed() } }