mirror of
https://github.com/soywod/himalaya.git
synced 2024-11-21 18:40:19 +00:00
wip: design basic tree using petgraph
This commit is contained in:
parent
7a951b4830
commit
90e12ddc51
8 changed files with 410 additions and 10 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -1396,7 +1396,6 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "email-lib"
|
name = "email-lib"
|
||||||
version = "0.24.1"
|
version = "0.24.1"
|
||||||
source = "git+https://git.sr.ht/~soywod/pimalaya#033ba2a2e193769e1272c9493aa1d6c975346eb5"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"advisory-lock",
|
"advisory-lock",
|
||||||
"async-ctrlc",
|
"async-ctrlc",
|
||||||
|
@ -1425,6 +1424,7 @@ dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"ouroboros",
|
"ouroboros",
|
||||||
"paste",
|
"paste",
|
||||||
|
"petgraph",
|
||||||
"pgp-lib",
|
"pgp-lib",
|
||||||
"process-lib",
|
"process-lib",
|
||||||
"rayon",
|
"rayon",
|
||||||
|
@ -2074,6 +2074,7 @@ dependencies = [
|
||||||
"mml-lib",
|
"mml-lib",
|
||||||
"oauth-lib",
|
"oauth-lib",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
"petgraph",
|
||||||
"process-lib",
|
"process-lib",
|
||||||
"secret-lib",
|
"secret-lib",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -2267,7 +2268,6 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "imap-client"
|
name = "imap-client"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/soywod/imap-flow.git?branch=session#599cedbf9facd7d04eaacef2ec6710ee7f7f9eff"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"imap-flow",
|
"imap-flow",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
@ -2297,7 +2297,6 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "imap-flow"
|
name = "imap-flow"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/soywod/imap-flow.git?branch=session#599cedbf9facd7d04eaacef2ec6710ee7f7f9eff"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bounded-static",
|
"bounded-static",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
@ -4577,7 +4576,6 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tag-generator"
|
name = "tag-generator"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/soywod/imap-flow.git?branch=session#599cedbf9facd7d04eaacef2ec6710ee7f7f9eff"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"imap-types",
|
"imap-types",
|
||||||
"rand",
|
"rand",
|
||||||
|
@ -4592,7 +4590,6 @@ checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tasks"
|
name = "tasks"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/soywod/imap-flow.git?branch=session#599cedbf9facd7d04eaacef2ec6710ee7f7f9eff"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"imap-flow",
|
"imap-flow",
|
||||||
"imap-types",
|
"imap-types",
|
||||||
|
|
10
Cargo.toml
10
Cargo.toml
|
@ -65,6 +65,7 @@ md5 = "0.7"
|
||||||
mml-lib = { version = "=1.0.12", default-features = false, features = ["derive"] }
|
mml-lib = { version = "=1.0.12", default-features = false, features = ["derive"] }
|
||||||
oauth-lib = "=0.1.1"
|
oauth-lib = "=0.1.1"
|
||||||
once_cell = "1.16"
|
once_cell = "1.16"
|
||||||
|
petgraph = "0.6"
|
||||||
process-lib = { version = "=0.4.2", features = ["derive"] }
|
process-lib = { version = "=0.4.2", features = ["derive"] }
|
||||||
secret-lib = { version = "=0.4.4", features = ["derive"] }
|
secret-lib = { version = "=0.4.4", features = ["derive"] }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
@ -86,8 +87,11 @@ uuid = { version = "0.8", features = ["v4"] }
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
# WIP: transition from `imap` to `imap-codec`
|
# WIP: transition from `imap` to `imap-codec`
|
||||||
email-lib = { git = "https://git.sr.ht/~soywod/pimalaya" }
|
email-lib = { path = "/home/soywod/sourcehut/pimalaya/email" }
|
||||||
imap-client = { git = "https://github.com/soywod/imap-flow.git", branch = "session" }
|
imap-client = { path = "/home/soywod/code/imap-flow/client" }
|
||||||
tasks = { git = "https://github.com/soywod/imap-flow.git", branch = "session" }
|
tasks = { path = "/home/soywod/code/imap-flow/tasks" }
|
||||||
|
# email-lib = { git = "https://git.sr.ht/~soywod/pimalaya" }
|
||||||
|
# imap-client = { git = "https://github.com/soywod/imap-flow.git", branch = "session" }
|
||||||
|
# tasks = { git = "https://github.com/soywod/imap-flow.git", branch = "session" }
|
||||||
imap-codec = { git = "https://github.com/duesee/imap-codec.git" }
|
imap-codec = { git = "https://github.com/duesee/imap-codec.git" }
|
||||||
imap-types = { git = "https://github.com/duesee/imap-codec.git" }
|
imap-types = { git = "https://github.com/duesee/imap-codec.git" }
|
||||||
|
|
|
@ -142,6 +142,14 @@ impl TomlAccountConfig {
|
||||||
.or(self.backend.as_ref())
|
.or(self.backend.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn thread_envelopes_kind(&self) -> Option<&BackendKind> {
|
||||||
|
self.envelope
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|envelope| envelope.thread.as_ref())
|
||||||
|
.and_then(|thread| thread.backend.as_ref())
|
||||||
|
.or(self.backend.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn watch_envelopes_kind(&self) -> Option<&BackendKind> {
|
pub fn watch_envelopes_kind(&self) -> Option<&BackendKind> {
|
||||||
self.envelope
|
self.envelope
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|
|
@ -3,6 +3,7 @@ pub(crate) mod wizard;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
|
use petgraph::graphmap::DiGraphMap;
|
||||||
use std::{fmt::Display, ops::Deref, sync::Arc};
|
use std::{fmt::Display, ops::Deref, sync::Arc};
|
||||||
|
|
||||||
#[cfg(feature = "imap")]
|
#[cfg(feature = "imap")]
|
||||||
|
@ -23,6 +24,7 @@ use email::{
|
||||||
envelope::{
|
envelope::{
|
||||||
get::GetEnvelope,
|
get::GetEnvelope,
|
||||||
list::{ListEnvelopes, ListEnvelopesOptions},
|
list::{ListEnvelopes, ListEnvelopesOptions},
|
||||||
|
thread::ThreadEnvelopes,
|
||||||
watch::WatchEnvelopes,
|
watch::WatchEnvelopes,
|
||||||
Id, SingleId,
|
Id, SingleId,
|
||||||
},
|
},
|
||||||
|
@ -337,6 +339,23 @@ impl email::backend::context::BackendContextBuilder for BackendContextBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn thread_envelopes(&self) -> Option<BackendFeature<Self::Context, dyn ThreadEnvelopes>> {
|
||||||
|
match self.toml_account_config.thread_envelopes_kind() {
|
||||||
|
#[cfg(feature = "imap")]
|
||||||
|
Some(BackendKind::Imap) => self.thread_envelopes_with_some(&self.imap),
|
||||||
|
#[cfg(all(feature = "imap", feature = "account-sync"))]
|
||||||
|
Some(BackendKind::ImapCache) => {
|
||||||
|
let f = self.imap_cache.as_ref()?.thread_envelopes()?;
|
||||||
|
Some(Arc::new(move |ctx| f(ctx.imap_cache.as_ref()?)))
|
||||||
|
}
|
||||||
|
#[cfg(feature = "maildir")]
|
||||||
|
Some(BackendKind::Maildir) => self.thread_envelopes_with_some(&self.maildir),
|
||||||
|
#[cfg(feature = "notmuch")]
|
||||||
|
Some(BackendKind::Notmuch) => self.thread_envelopes_with_some(&self.notmuch),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn watch_envelopes(&self) -> Option<BackendFeature<Self::Context, dyn WatchEnvelopes>> {
|
fn watch_envelopes(&self) -> Option<BackendFeature<Self::Context, dyn WatchEnvelopes>> {
|
||||||
match self.toml_account_config.watch_envelopes_kind() {
|
match self.toml_account_config.watch_envelopes_kind() {
|
||||||
#[cfg(feature = "imap")]
|
#[cfg(feature = "imap")]
|
||||||
|
@ -687,6 +706,19 @@ impl Backend {
|
||||||
Ok(envelopes)
|
Ok(envelopes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn thread_envelopes(
|
||||||
|
&self,
|
||||||
|
folder: &str,
|
||||||
|
opts: ListEnvelopesOptions,
|
||||||
|
) -> Result<DiGraphMap<u32, u32>> {
|
||||||
|
let backend_kind = self.toml_account_config.thread_envelopes_kind();
|
||||||
|
let id_mapper = self.build_id_mapper(folder, backend_kind)?;
|
||||||
|
let envelopes = self.backend.thread_envelopes(folder, opts).await?;
|
||||||
|
// let envelopes =
|
||||||
|
// Envelopes::from_backend(&self.backend.account_config, &id_mapper, envelopes)?;
|
||||||
|
Ok(envelopes)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn add_flags(&self, folder: &str, ids: &[usize], flags: &Flags) -> Result<()> {
|
pub async fn add_flags(&self, folder: &str, ids: &[usize], flags: &Flags) -> Result<()> {
|
||||||
let backend_kind = self.toml_account_config.add_flags_kind();
|
let backend_kind = self.toml_account_config.add_flags_kind();
|
||||||
let id_mapper = self.build_id_mapper(folder, backend_kind)?;
|
let id_mapper = self.build_id_mapper(folder, backend_kind)?;
|
||||||
|
|
|
@ -244,6 +244,7 @@ impl TomlConfig {
|
||||||
}),
|
}),
|
||||||
envelope: config.envelope.map(|c| EnvelopeConfig {
|
envelope: config.envelope.map(|c| EnvelopeConfig {
|
||||||
list: c.list.map(|c| c.remote),
|
list: c.list.map(|c| c.remote),
|
||||||
|
thread: c.thread.map(|c| c.remote),
|
||||||
watch: c.watch.map(|c| c.remote),
|
watch: c.watch.map(|c| c.remote),
|
||||||
#[cfg(feature = "account-sync")]
|
#[cfg(feature = "account-sync")]
|
||||||
sync: c.sync,
|
sync: c.sync,
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
pub mod list;
|
pub mod list;
|
||||||
|
pub mod thread;
|
||||||
pub mod watch;
|
pub mod watch;
|
||||||
|
|
||||||
use color_eyre::Result;
|
|
||||||
use clap::Subcommand;
|
use clap::Subcommand;
|
||||||
|
use color_eyre::Result;
|
||||||
|
|
||||||
use crate::{config::TomlConfig, printer::Printer};
|
use crate::{config::TomlConfig, printer::Printer};
|
||||||
|
|
||||||
use self::{list::ListEnvelopesCommand, watch::WatchEnvelopesCommand};
|
use self::{
|
||||||
|
list::ListEnvelopesCommand, thread::ThreadEnvelopesCommand, watch::WatchEnvelopesCommand,
|
||||||
|
};
|
||||||
|
|
||||||
/// Manage envelopes.
|
/// Manage envelopes.
|
||||||
///
|
///
|
||||||
|
@ -19,6 +22,9 @@ pub enum EnvelopeSubcommand {
|
||||||
#[command(alias = "lst")]
|
#[command(alias = "lst")]
|
||||||
List(ListEnvelopesCommand),
|
List(ListEnvelopesCommand),
|
||||||
|
|
||||||
|
#[command()]
|
||||||
|
Thread(ThreadEnvelopesCommand),
|
||||||
|
|
||||||
#[command()]
|
#[command()]
|
||||||
Watch(WatchEnvelopesCommand),
|
Watch(WatchEnvelopesCommand),
|
||||||
}
|
}
|
||||||
|
@ -28,6 +34,7 @@ impl EnvelopeSubcommand {
|
||||||
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||||
match self {
|
match self {
|
||||||
Self::List(cmd) => cmd.execute(printer, config).await,
|
Self::List(cmd) => cmd.execute(printer, config).await,
|
||||||
|
Self::Thread(cmd) => cmd.execute(printer, config).await,
|
||||||
Self::Watch(cmd) => cmd.execute(printer, config).await,
|
Self::Watch(cmd) => cmd.execute(printer, config).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
330
src/email/envelope/command/thread.rs
Normal file
330
src/email/envelope/command/thread.rs
Normal file
|
@ -0,0 +1,330 @@
|
||||||
|
use ariadne::{Color, Label, Report, ReportKind, Source};
|
||||||
|
use clap::Parser;
|
||||||
|
use color_eyre::Result;
|
||||||
|
use email::{
|
||||||
|
backend::feature::BackendFeatureSource,
|
||||||
|
email::search_query,
|
||||||
|
envelope::list::ListEnvelopesOptions,
|
||||||
|
search_query::{filter::SearchEmailsFilterQuery, SearchEmailsQuery},
|
||||||
|
};
|
||||||
|
use petgraph::{graphmap::DiGraphMap, visit::IntoNodeIdentifiers, Direction};
|
||||||
|
use std::{
|
||||||
|
collections::{HashMap, HashSet},
|
||||||
|
io::Write,
|
||||||
|
process::exit,
|
||||||
|
};
|
||||||
|
use tracing::info;
|
||||||
|
|
||||||
|
#[cfg(feature = "account-sync")]
|
||||||
|
use crate::cache::arg::disable::CacheDisableFlag;
|
||||||
|
use crate::{
|
||||||
|
account::arg::name::AccountNameFlag, backend::Backend, config::TomlConfig,
|
||||||
|
folder::arg::name::FolderNameOptionalFlag, printer::Printer,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Thread all envelopes.
|
||||||
|
///
|
||||||
|
/// This command allows you to thread all envelopes included in the
|
||||||
|
/// given folder.
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct ThreadEnvelopesCommand {
|
||||||
|
#[command(flatten)]
|
||||||
|
pub folder: FolderNameOptionalFlag,
|
||||||
|
|
||||||
|
/// The page number.
|
||||||
|
///
|
||||||
|
/// The page number starts from 1 (which is the default). Giving a
|
||||||
|
/// page number to big will result in a out of bound error.
|
||||||
|
#[arg(long, short, value_name = "NUMBER", default_value = "1")]
|
||||||
|
pub page: usize,
|
||||||
|
|
||||||
|
/// The page size.
|
||||||
|
///
|
||||||
|
/// Determine the amount of envelopes a page should contain.
|
||||||
|
#[arg(long, short = 's', value_name = "NUMBER")]
|
||||||
|
pub page_size: Option<usize>,
|
||||||
|
|
||||||
|
#[cfg(feature = "account-sync")]
|
||||||
|
#[command(flatten)]
|
||||||
|
pub cache: CacheDisableFlag,
|
||||||
|
|
||||||
|
#[command(flatten)]
|
||||||
|
pub account: AccountNameFlag,
|
||||||
|
|
||||||
|
/// The maximum width the table should not exceed.
|
||||||
|
///
|
||||||
|
/// This argument will force the table not to exceed the given
|
||||||
|
/// width in pixels. Columns may shrink with ellipsis in order to
|
||||||
|
/// fit the width.
|
||||||
|
#[arg(long = "max-width", short = 'w')]
|
||||||
|
#[arg(name = "table_max_width", value_name = "PIXELS")]
|
||||||
|
pub table_max_width: Option<u16>,
|
||||||
|
|
||||||
|
/// The thread envelopes filter and sort query.
|
||||||
|
///
|
||||||
|
/// The query can be a filter query, a sort query or both
|
||||||
|
/// together.
|
||||||
|
///
|
||||||
|
/// A filter query is composed of operators and conditions. There
|
||||||
|
/// is 3 operators and 8 conditions:
|
||||||
|
///
|
||||||
|
/// • not <condition> → filter envelopes that do not match the
|
||||||
|
/// condition
|
||||||
|
///
|
||||||
|
/// • <condition> and <condition> → filter envelopes that match
|
||||||
|
/// both conditions
|
||||||
|
///
|
||||||
|
/// • <condition> or <condition> → filter envelopes that match
|
||||||
|
/// one of the conditions
|
||||||
|
///
|
||||||
|
/// ◦ date <yyyy-mm-dd> → filter envelopes that match the given
|
||||||
|
/// date
|
||||||
|
///
|
||||||
|
/// ◦ before <yyyy-mm-dd> → filter envelopes with date strictly
|
||||||
|
/// before the given one
|
||||||
|
///
|
||||||
|
/// ◦ after <yyyy-mm-dd> → filter envelopes with date stricly
|
||||||
|
/// after the given one
|
||||||
|
///
|
||||||
|
/// ◦ from <pattern> → filter envelopes with senders matching the
|
||||||
|
/// given pattern
|
||||||
|
///
|
||||||
|
/// ◦ to <pattern> → filter envelopes with recipients matching
|
||||||
|
/// the given pattern
|
||||||
|
///
|
||||||
|
/// ◦ subject <pattern> → filter envelopes with subject matching
|
||||||
|
/// the given pattern
|
||||||
|
///
|
||||||
|
/// ◦ body <pattern> → filter envelopes with text bodies matching
|
||||||
|
/// the given pattern
|
||||||
|
///
|
||||||
|
/// ◦ flag <flag> → filter envelopes matching the given flag
|
||||||
|
///
|
||||||
|
/// A sort query starts by "order by", and is composed of kinds
|
||||||
|
/// and orders. There is 4 kinds and 2 orders:
|
||||||
|
///
|
||||||
|
/// • date [order] → sort envelopes by date
|
||||||
|
///
|
||||||
|
/// • from [order] → sort envelopes by sender
|
||||||
|
///
|
||||||
|
/// • to [order] → sort envelopes by recipient
|
||||||
|
///
|
||||||
|
/// • subject [order] → sort envelopes by subject
|
||||||
|
///
|
||||||
|
/// ◦ <kind> asc → sort envelopes by the given kind in ascending
|
||||||
|
/// order
|
||||||
|
///
|
||||||
|
/// ◦ <kind> desc → sort envelopes by the given kind in
|
||||||
|
/// descending order
|
||||||
|
///
|
||||||
|
/// Examples:
|
||||||
|
///
|
||||||
|
/// subject foo and body bar → filter envelopes containing "foo"
|
||||||
|
/// in their subject and "bar" in their text bodies
|
||||||
|
///
|
||||||
|
/// order by date desc subject → sort envelopes by descending date
|
||||||
|
/// (most recent first), then by ascending subject
|
||||||
|
///
|
||||||
|
/// subject foo and body bar order by date desc subject →
|
||||||
|
/// combination of the 2 previous examples
|
||||||
|
#[arg(allow_hyphen_values = true, trailing_var_arg = true)]
|
||||||
|
pub query: Option<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ThreadEnvelopesCommand {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
folder: Default::default(),
|
||||||
|
page: 1,
|
||||||
|
page_size: Default::default(),
|
||||||
|
#[cfg(feature = "account-sync")]
|
||||||
|
cache: Default::default(),
|
||||||
|
account: Default::default(),
|
||||||
|
query: Default::default(),
|
||||||
|
table_max_width: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ThreadEnvelopesCommand {
|
||||||
|
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||||
|
info!("executing thread envelopes command");
|
||||||
|
|
||||||
|
let (toml_account_config, account_config) = config.clone().into_account_configs(
|
||||||
|
self.account.name.as_deref(),
|
||||||
|
#[cfg(feature = "account-sync")]
|
||||||
|
self.cache.disable,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let folder = &self.folder.name;
|
||||||
|
let page = 1.max(self.page) - 1;
|
||||||
|
let page_size = self
|
||||||
|
.page_size
|
||||||
|
.unwrap_or_else(|| account_config.get_envelope_thread_page_size());
|
||||||
|
|
||||||
|
let thread_envelopes_kind = toml_account_config.thread_envelopes_kind();
|
||||||
|
|
||||||
|
let backend = Backend::new(
|
||||||
|
toml_account_config.clone(),
|
||||||
|
account_config.clone(),
|
||||||
|
thread_envelopes_kind,
|
||||||
|
|builder| builder.set_thread_envelopes(BackendFeatureSource::Context),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// let query = self
|
||||||
|
// .query
|
||||||
|
// .map(|query| query.join(" ").parse::<SearchEmailsQuery>());
|
||||||
|
// let query = match query {
|
||||||
|
// None => None,
|
||||||
|
// Some(Ok(query)) => Some(query),
|
||||||
|
// Some(Err(main_err)) => {
|
||||||
|
// let source = "query";
|
||||||
|
// let search_query::error::Error::ParseError(errs, query) = &main_err;
|
||||||
|
// for err in errs {
|
||||||
|
// Report::build(ReportKind::Error, source, err.span().start)
|
||||||
|
// .with_message(main_err.to_string())
|
||||||
|
// .with_label(
|
||||||
|
// Label::new((source, err.span().into_range()))
|
||||||
|
// .with_message(err.reason().to_string())
|
||||||
|
// .with_color(Color::Red),
|
||||||
|
// )
|
||||||
|
// .finish()
|
||||||
|
// .eprint((source, Source::from(&query)))
|
||||||
|
// .unwrap();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// exit(0)
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
let opts = ListEnvelopesOptions {
|
||||||
|
page,
|
||||||
|
page_size,
|
||||||
|
query: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let graph = backend.thread_envelopes(folder, opts).await?;
|
||||||
|
|
||||||
|
println!("graph: {graph:#?}");
|
||||||
|
|
||||||
|
let mut stdout = std::io::stdout();
|
||||||
|
write_tree(&mut stdout, &graph, 0, String::new(), 0)?;
|
||||||
|
stdout.flush()?;
|
||||||
|
|
||||||
|
// printer.print_table(envelopes, self.table_max_width)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_tree(
|
||||||
|
w: &mut impl std::io::Write,
|
||||||
|
graph: &DiGraphMap<u32, u32>,
|
||||||
|
parent: u32,
|
||||||
|
pad: String,
|
||||||
|
weight: u32,
|
||||||
|
) -> std::io::Result<()> {
|
||||||
|
let edges = graph
|
||||||
|
.all_edges()
|
||||||
|
.filter_map(|(a, b, w)| {
|
||||||
|
if a == parent && *w == weight {
|
||||||
|
Some(b)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
writeln!(w, "{parent}")?;
|
||||||
|
|
||||||
|
let edges_count = edges.len();
|
||||||
|
for (i, b) in edges.into_iter().enumerate() {
|
||||||
|
let is_last = edges_count == i + 1;
|
||||||
|
let (x, y) = if is_last {
|
||||||
|
(' ', '└')
|
||||||
|
} else {
|
||||||
|
('│', '├')
|
||||||
|
};
|
||||||
|
write!(w, "{pad}{y}─ ")?;
|
||||||
|
let pad = format!("{pad}{x} ");
|
||||||
|
write_tree(w, graph, b, pad, weight + 1)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use petgraph::graphmap::DiGraphMap;
|
||||||
|
|
||||||
|
use super::write_tree;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tree_1() {
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
let mut graph = DiGraphMap::new();
|
||||||
|
graph.add_edge(0, 1, 0);
|
||||||
|
graph.add_edge(0, 2, 0);
|
||||||
|
graph.add_edge(0, 3, 0);
|
||||||
|
|
||||||
|
write_tree(&mut buf, &graph, 0, String::new(), 0).unwrap();
|
||||||
|
let buf = String::from_utf8_lossy(&buf);
|
||||||
|
|
||||||
|
let expected = "
|
||||||
|
0
|
||||||
|
├─ 1
|
||||||
|
├─ 2
|
||||||
|
└─ 3
|
||||||
|
";
|
||||||
|
assert_eq!(expected.trim_start(), buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tree_2() {
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
let mut graph = DiGraphMap::new();
|
||||||
|
graph.add_edge(0, 1, 0);
|
||||||
|
graph.add_edge(1, 2, 1);
|
||||||
|
graph.add_edge(1, 3, 1);
|
||||||
|
|
||||||
|
write_tree(&mut buf, &graph, 0, String::new(), 0).unwrap();
|
||||||
|
let buf = String::from_utf8_lossy(&buf);
|
||||||
|
|
||||||
|
let expected = "
|
||||||
|
0
|
||||||
|
└─ 1
|
||||||
|
├─ 2
|
||||||
|
└─ 3
|
||||||
|
";
|
||||||
|
assert_eq!(expected.trim_start(), buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tree_3() {
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
let mut graph = DiGraphMap::new();
|
||||||
|
graph.add_edge(0, 1, 0);
|
||||||
|
graph.add_edge(1, 2, 1);
|
||||||
|
graph.add_edge(2, 22, 2);
|
||||||
|
graph.add_edge(1, 3, 1);
|
||||||
|
graph.add_edge(0, 4, 0);
|
||||||
|
graph.add_edge(4, 5, 1);
|
||||||
|
graph.add_edge(5, 6, 2);
|
||||||
|
|
||||||
|
write_tree(&mut buf, &graph, 0, String::new(), 0).unwrap();
|
||||||
|
let buf = String::from_utf8_lossy(&buf);
|
||||||
|
|
||||||
|
let expected = "
|
||||||
|
0
|
||||||
|
├─ 1
|
||||||
|
│ ├─ 2
|
||||||
|
│ │ └─ 22
|
||||||
|
│ └─ 3
|
||||||
|
└─ 4
|
||||||
|
└─ 5
|
||||||
|
└─ 6
|
||||||
|
";
|
||||||
|
assert_eq!(expected.trim_start(), buf)
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,6 +8,7 @@ use crate::backend::BackendKind;
|
||||||
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
|
||||||
pub struct EnvelopeConfig {
|
pub struct EnvelopeConfig {
|
||||||
pub list: Option<ListEnvelopesConfig>,
|
pub list: Option<ListEnvelopesConfig>,
|
||||||
|
pub thread: Option<ThreadEnvelopesConfig>,
|
||||||
pub watch: Option<WatchEnvelopesConfig>,
|
pub watch: Option<WatchEnvelopesConfig>,
|
||||||
pub get: Option<GetEnvelopeConfig>,
|
pub get: Option<GetEnvelopeConfig>,
|
||||||
#[cfg(feature = "account-sync")]
|
#[cfg(feature = "account-sync")]
|
||||||
|
@ -54,6 +55,26 @@ impl ListEnvelopesConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
|
||||||
|
pub struct ThreadEnvelopesConfig {
|
||||||
|
pub backend: Option<BackendKind>,
|
||||||
|
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub remote: email::envelope::thread::config::EnvelopeThreadConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ThreadEnvelopesConfig {
|
||||||
|
pub fn get_used_backends(&self) -> HashSet<&BackendKind> {
|
||||||
|
let mut kinds = HashSet::default();
|
||||||
|
|
||||||
|
if let Some(kind) = &self.backend {
|
||||||
|
kinds.insert(kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
kinds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
|
||||||
pub struct WatchEnvelopesConfig {
|
pub struct WatchEnvelopesConfig {
|
||||||
pub backend: Option<BackendKind>,
|
pub backend: Option<BackendKind>,
|
||||||
|
|
Loading…
Reference in a new issue