use anyhow::{Result, bail};
use pimalaya_cli::{prompt, spinner::Spinner};
#[cfg(any(feature = "imap", feature = "msgraph"))]
use crate::config::SourceBackendConfig;
#[cfg(feature = "dav")]
use crate::dav::client::DavKind;
#[cfg(feature = "dav")]
use crate::wizard::dav;
#[cfg(feature = "imap")]
use crate::wizard::imap_smtp;
#[cfg(feature = "msgraph")]
use crate::wizard::msgraph;
#[cfg(any(feature = "imap", feature = "msgraph", feature = "dav"))]
use crate::wizard::search::DiscoveredKind;
use crate::{
config::{AccountConfig, SourceConfig},
wizard::search::{self, Discovered},
};
const EMAIL_PROMPT: &str = "Email address:";
pub const CONFIG_SAMPLE_URL: &str =
"https://github.com/pimalaya/neverest/blob/master/config.sample.toml";
pub fn run() -> Result<(String, AccountConfig)> {
let email = prompt_email()?;
let account_name = default_account_name(&email);
let source = configure(&account_name, &email)?;
Ok((account_name, AccountConfig::with_source(source)))
}
fn prompt_email() -> Result<String> {
let input = prompt::text(EMAIL_PROMPT, None)?;
let input = input.trim();
if input.is_empty() {
bail!("Empty input: enter an email address");
}
Ok(match input.contains('@') {
true => input.to_string(),
false => format!("@{input}"),
})
}
pub fn configure(account_name: &str, email: &str) -> Result<SourceConfig> {
let spinner = Spinner::start("Searching for server settings");
let mut found = search::search(email)?;
search::retain_supported(&mut found);
if found.is_empty() {
spinner.failure("No configuration found");
return stop_undiscovered(email);
}
spinner.success(format!("Found {} configuration(s)", found.len()));
let default = found.first().cloned();
let choice = prompt::item("Choose a configuration:", found, default)?;
dispatch(account_name, email, choice)
}
#[cfg_attr(
all(feature = "imap", feature = "msgraph"),
allow(unreachable_patterns)
)]
#[cfg_attr(not(feature = "imap"), allow(unused_variables))]
fn dispatch(account_name: &str, email: &str, choice: Discovered) -> Result<SourceConfig> {
match &choice.kind {
#[cfg(feature = "imap")]
DiscoveredKind::ImapSmtp { .. } => {
let (imap, smtp) = imap_smtp::configure_discovered(account_name, email, &choice)?;
Ok(SourceConfig {
backend: SourceBackendConfig::Imap(imap),
smtp,
})
}
#[cfg(feature = "msgraph")]
DiscoveredKind::Msgraph => Ok(SourceConfig::new(SourceBackendConfig::Msgraph(
msgraph::configure(account_name)?,
))),
#[cfg(feature = "dav")]
DiscoveredKind::Carddav { url } => Ok(SourceConfig::new(dav::configure(
account_name,
DavKind::Card,
url,
&choice,
)?)),
#[cfg(feature = "dav")]
DiscoveredKind::Caldav { url } => Ok(SourceConfig::new(dav::configure(
account_name,
DavKind::Cal,
url,
&choice,
)?)),
kind => bail!("Configuration {kind:?} is not supported by this build"),
}
}
fn stop_undiscovered(email: &str) -> Result<SourceConfig> {
bail!(
"Could not automatically discover a configuration for {email}.\n\n\
Write your account configuration by hand instead, starting from the \
documented sample:\n {CONFIG_SAMPLE_URL}"
)
}
pub fn default_account_name(email: &str) -> String {
let domain = match email.rsplit_once('@') {
Some((_, domain)) => domain,
None => email,
};
let label = domain.split('.').next().unwrap_or(domain);
match label.is_empty() {
true => String::from("default"),
false => label.to_string(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn account_name_defaults_to_the_first_domain_label() {
assert_eq!(default_account_name("clement.douin@posteo.net"), "posteo");
assert_eq!(default_account_name("alice@mail.example.co.uk"), "mail");
assert_eq!(default_account_name("@posteo.net"), "posteo");
assert_eq!(default_account_name("posteo.net"), "posteo");
assert_eq!(default_account_name("@"), "default");
}
}