diff --git a/crates/synd_term/src/application/builder.rs b/crates/synd_term/src/application/builder.rs new file mode 100644 index 00000000..787b3bed --- /dev/null +++ b/crates/synd_term/src/application/builder.rs @@ -0,0 +1,149 @@ +use crate::{ + application::{Application, Authenticator, Cache, Config}, + client::Client, + config::Categories, + terminal::Terminal, + ui::theme::Theme, +}; + +pub struct ApplicationBuilder< + Terminal = (), + Client = (), + Categories = (), + Cache = (), + Config = (), + Theme = (), +> { + pub(super) terminal: Terminal, + pub(super) client: Client, + pub(super) categories: Categories, + pub(super) cache: Cache, + pub(super) config: Config, + pub(super) theme: Theme, + + pub(super) authenticator: Option, +} + +impl Default for ApplicationBuilder { + fn default() -> Self { + Self { + terminal: (), + client: (), + categories: (), + cache: (), + config: (), + theme: (), + authenticator: None, + } + } +} + +impl ApplicationBuilder<(), T1, T2, T3, T4, T5> { + #[must_use] + pub fn terminal(self, terminal: Terminal) -> ApplicationBuilder { + ApplicationBuilder { + terminal, + client: self.client, + categories: self.categories, + cache: self.cache, + config: self.config, + theme: self.theme, + authenticator: self.authenticator, + } + } +} + +impl ApplicationBuilder { + #[must_use] + pub fn client(self, client: Client) -> ApplicationBuilder { + ApplicationBuilder { + terminal: self.terminal, + client, + categories: self.categories, + cache: self.cache, + config: self.config, + theme: self.theme, + authenticator: self.authenticator, + } + } +} + +impl ApplicationBuilder { + #[must_use] + pub fn categories( + self, + categories: Categories, + ) -> ApplicationBuilder { + ApplicationBuilder { + terminal: self.terminal, + client: self.client, + categories, + cache: self.cache, + config: self.config, + theme: self.theme, + authenticator: self.authenticator, + } + } +} + +impl ApplicationBuilder { + #[must_use] + pub fn cache(self, cache: Cache) -> ApplicationBuilder { + ApplicationBuilder { + terminal: self.terminal, + client: self.client, + categories: self.categories, + cache, + config: self.config, + theme: self.theme, + authenticator: self.authenticator, + } + } +} + +impl ApplicationBuilder { + #[must_use] + pub fn config(self, config: Config) -> ApplicationBuilder { + ApplicationBuilder { + terminal: self.terminal, + client: self.client, + categories: self.categories, + cache: self.cache, + config, + theme: self.theme, + authenticator: self.authenticator, + } + } +} + +impl ApplicationBuilder { + #[must_use] + pub fn theme(self, theme: Theme) -> ApplicationBuilder { + ApplicationBuilder { + terminal: self.terminal, + client: self.client, + categories: self.categories, + cache: self.cache, + config: self.config, + theme, + authenticator: self.authenticator, + } + } +} + +impl ApplicationBuilder { + #[must_use] + pub fn authenticator(self, authenticator: Authenticator) -> Self { + Self { + authenticator: Some(authenticator), + ..self + } + } +} + +impl ApplicationBuilder { + #[must_use] + pub fn build(self) -> Application { + Application::new(self) + } +} diff --git a/crates/synd_term/src/application/mod.rs b/crates/synd_term/src/application/mod.rs index 3397c30c..8975fbe3 100644 --- a/crates/synd_term/src/application/mod.rs +++ b/crates/synd_term/src/application/mod.rs @@ -56,6 +56,9 @@ use flags::Should; mod cache; pub use cache::Cache; +mod builder; +pub use builder::ApplicationBuilder; + pub(crate) mod event; enum Screen { @@ -107,23 +110,35 @@ pub struct Application { } impl Application { - pub fn new(terminal: Terminal, client: Client, categories: Categories, cache: Cache) -> Self { - Application::with(terminal, client, categories, Config::default(), cache) + /// Construct `ApplicationBuilder` + pub fn builder() -> ApplicationBuilder { + ApplicationBuilder::default() } - pub fn with( - terminal: Terminal, - client: Client, - categories: Categories, - config: Config, - cache: Cache, + /// Construct `Application` from builder. + /// Configure keymaps for terminal use + fn new( + builder: ApplicationBuilder, ) -> Self { - let mut keymaps = Keymaps::default(); - keymaps.enable(KeymapId::Global); - keymaps.enable(KeymapId::Login); + let ApplicationBuilder { + terminal, + client, + categories, + cache, + config, + theme, + authenticator, + } = builder; + + let key_handlers = { + let mut keymaps = Keymaps::default(); + keymaps.enable(KeymapId::Global); + keymaps.enable(KeymapId::Login); - let mut key_handlers = event::KeyHandlers::new(); - key_handlers.push(event::KeyHandler::Keymaps(keymaps)); + let mut key_handlers = event::KeyHandlers::new(); + key_handlers.push(event::KeyHandler::Keymaps(keymaps)); + key_handlers + }; Self { clock: Box::new(SystemClock), @@ -132,10 +147,10 @@ impl Application { jobs: Jobs::new(), components: Components::new(), interactor: Interactor::new(), - authenticator: Authenticator::new(), + authenticator: authenticator.unwrap_or_else(Authenticator::new), in_flight: InFlight::new().with_throbber_timer_interval(config.throbber_timer_interval), cache, - theme: Theme::default(), + theme, idle_timer: Box::pin(tokio::time::sleep(config.idle_timer_interval)), screen: Screen::Login, config, @@ -146,19 +161,6 @@ impl Application { } } - #[must_use] - pub fn with_theme(self, theme: Theme) -> Self { - Self { theme, ..self } - } - - #[must_use] - pub fn with_authenticator(self, authenticator: Authenticator) -> Self { - Self { - authenticator, - ..self - } - } - fn now(&self) -> DateTime { self.clock.now() } diff --git a/crates/synd_term/src/main.rs b/crates/synd_term/src/main.rs index 8196dd5f..bf504583 100644 --- a/crates/synd_term/src/main.rs +++ b/crates/synd_term/src/main.rs @@ -1,4 +1,4 @@ -use std::{path::PathBuf, time::Duration}; +use std::{future, path::PathBuf, time::Duration}; use anyhow::Context as _; use crossterm::event::EventStream; @@ -59,9 +59,7 @@ fn init_tracing(log_path: Option) -> anyhow::Result Ok(guard) } -// Construct and configure application -#[allow(clippy::unused_async)] -async fn init_app( +fn build_app( endpoint: Url, timeout: Duration, palette: Palette, @@ -71,19 +69,22 @@ async fn init_app( }: FeedOptions, cache_dir: PathBuf, ) -> anyhow::Result { - let terminal = Terminal::new().context("Failed to construct terminal")?; - let client = Client::new(endpoint, timeout).context("Failed to construct client")?; - let categories = categories - .map(Categories::load) - .transpose()? - .unwrap_or_else(Categories::default_toml); - let config = Config { - entries_limit, - ..Default::default() - }; - let cache = Cache::new(cache_dir); - let app = Application::with(terminal, client, categories, config, cache) - .with_theme(Theme::with_palette(&palette.into())); + let app = Application::builder() + .terminal(Terminal::new().context("Failed to construct terminal")?) + .client(Client::new(endpoint, timeout).context("Failed to construct client")?) + .categories( + categories + .map(Categories::load) + .transpose()? + .unwrap_or_else(Categories::default_toml), + ) + .config(Config { + entries_limit, + ..Default::default() + }) + .cache(Cache::new(cache_dir)) + .theme(Theme::with_palette(&palette.into())) + .build(); Ok(app) } @@ -102,6 +103,7 @@ async fn main() { palette, } = cli::parse(); + // Subcommand logs to the terminal, tui writes logs to a file. let log = if command.is_some() { None } else { Some(log) }; let _guard = init_tracing(log).unwrap(); @@ -117,12 +119,18 @@ async fn main() { let mut event_stream = EventStream::new(); - if let Err(err) = init_app(endpoint, client_timeout, palette, feed, cache_dir) - .and_then(|app| { - tracing::info!("Running..."); - app.run(&mut event_stream) - }) - .await + if let Err(err) = future::ready(build_app( + endpoint, + client_timeout, + palette, + feed, + cache_dir, + )) + .and_then(|app| { + tracing::info!("Running..."); + app.run(&mut event_stream) + }) + .await { error!("{err:?}"); std::process::exit(1); diff --git a/crates/synd_term/tests/test/helper.rs b/crates/synd_term/tests/test/helper.rs index ab0a2fb4..9738bf03 100644 --- a/crates/synd_term/tests/test/helper.rs +++ b/crates/synd_term/tests/test/helper.rs @@ -80,9 +80,16 @@ impl TestCase { }; // to isolate the state for each test let cache = Cache::new(cache_dir); - Application::with(terminal, client, Categories::default_toml(), config, cache) - .with_theme(Theme::default()) - .with_authenticator(authenticator) + + Application::builder() + .terminal(terminal) + .client(client) + .categories(Categories::default_toml()) + .config(config) + .cache(cache) + .theme(Theme::default()) + .authenticator(authenticator) + .build() }; Ok(application)