mirror of
https://github.com/soywod/himalaya.git
synced 2024-11-22 02:50:19 +00:00
improve global options, add account config sync-folders-strategy
This commit is contained in:
parent
22fb1b8dee
commit
fb324878fa
13 changed files with 194 additions and 43 deletions
29
CHANGELOG.md
29
CHANGELOG.md
|
@ -12,6 +12,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Added `create` and `delete` folder commands [sourcehut#54].
|
||||
- Added generated completions and man pages to releases
|
||||
[sourcehut#43].
|
||||
- Added new account config option `sync-folders-strategy` which allows
|
||||
to choose a folders synchronization strategy [sourcehut#59]:
|
||||
|
||||
- `sync-folders-strategy = "all"`: synchronize all existing folders
|
||||
for the current account
|
||||
- `sync-folders-strategy.include = ["folder1", "folder2", …]`:
|
||||
synchronize only the given folders for the current account
|
||||
- `sync-folders-strategy.exclude = ["folder1", "folder2", …]`:
|
||||
synchronizes all folders except the given ones for the current
|
||||
account
|
||||
|
||||
Also added new `account sync` arguments that override the account
|
||||
config option:
|
||||
|
||||
- `-A|--all-folders`: include all folders to the synchronization.
|
||||
- `-F|--include-folder`: include given folders to the
|
||||
synchronization. They can be repeated `-F folder1 folder2` or `-F
|
||||
folder1 -F folder2`.
|
||||
- `-x|--exclude-folder`: exclude given folders from the
|
||||
synchronization. They can be repeated `-x folder1 folder2` or `-x
|
||||
folder1 -F folder2`.
|
||||
|
||||
### Changed
|
||||
|
||||
- Made global options truly global, which means they can be used
|
||||
everywhere (not only *before* commands but also *after*)
|
||||
[sourcehut#60].
|
||||
|
||||
### Fixed
|
||||
|
||||
|
@ -679,3 +706,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
[sourcehut#43]: https://todo.sr.ht/~soywod/pimalaya/43
|
||||
[sourcehut#54]: https://todo.sr.ht/~soywod/pimalaya/54
|
||||
[sourcehut#59]: https://todo.sr.ht/~soywod/pimalaya/59
|
||||
[sourcehut#60]: https://todo.sr.ht/~soywod/pimalaya/60
|
||||
|
|
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -804,7 +804,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "himalaya-lib"
|
||||
version = "0.6.0"
|
||||
source = "git+https://git.sr.ht/~soywod/himalaya-lib?branch=develop#26d3b9e74c978f6dc94689bff809d48fe5ba4c85"
|
||||
source = "git+https://git.sr.ht/~soywod/himalaya-lib?branch=develop#9617be67bcec1f1a80924288be34d3476a260890"
|
||||
dependencies = [
|
||||
"ammonia",
|
||||
"chrono",
|
||||
|
|
|
@ -11,7 +11,7 @@ email-writing-sign-cmd = "gpg -o - -saq"
|
|||
email-writing-encrypt-cmd = "gpg -o - -eqar <recipient>"
|
||||
|
||||
[example]
|
||||
default = false
|
||||
default = true
|
||||
display-name = "Display NAME (gmail)"
|
||||
email = "display.name@gmail.local"
|
||||
|
||||
|
@ -36,6 +36,7 @@ smtp-starttls = false
|
|||
|
||||
sync = true
|
||||
sync-dir = "/tmp/sync/gmail"
|
||||
sync-folders-strategy.only = ["INBOX"]
|
||||
|
||||
[example.folder-aliases]
|
||||
inbox = "INBOX"
|
||||
|
|
7
src/cache/args.rs
vendored
7
src/cache/args.rs
vendored
|
@ -8,8 +8,13 @@ const ARG_DISABLE_CACHE: &str = "disable-cache";
|
|||
/// the user to disable any sort of cache.
|
||||
pub fn arg() -> Arg {
|
||||
Arg::new(ARG_DISABLE_CACHE)
|
||||
.long("disable-cache")
|
||||
.help("Disable any sort of cache")
|
||||
.long_help(
|
||||
"Disable any sort of cache. The action depends on
|
||||
the command it applies on.",
|
||||
)
|
||||
.long("disable-cache")
|
||||
.global(true)
|
||||
.action(ArgAction::SetTrue)
|
||||
}
|
||||
|
||||
|
|
|
@ -8,9 +8,10 @@ const ARG_CONFIG: &str = "config";
|
|||
/// user to customize the config file path.
|
||||
pub fn arg() -> Arg {
|
||||
Arg::new(ARG_CONFIG)
|
||||
.help("Set a custom configuration file path")
|
||||
.long("config")
|
||||
.short('c')
|
||||
.help("Forces a specific config file path")
|
||||
.global(true)
|
||||
.value_name("PATH")
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use himalaya_lib::{
|
||||
EmailHooks, EmailSender, EmailTextPlainFormat, MaildirConfig, SendmailConfig, SmtpConfig,
|
||||
folder::sync::Strategy as SyncFoldersStrategy, EmailHooks, EmailSender, EmailTextPlainFormat,
|
||||
MaildirConfig, SendmailConfig, SmtpConfig,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::HashSet, path::PathBuf};
|
||||
|
||||
#[cfg(feature = "imap-backend")]
|
||||
use himalaya_lib::ImapConfig;
|
||||
|
@ -111,3 +111,15 @@ pub struct EmailHooksDef {
|
|||
/// Represents the hook called just before sending an email.
|
||||
pub pre_send: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
|
||||
#[serde(remote = "SyncFoldersStrategy", rename_all = "kebab-case")]
|
||||
pub enum SyncFoldersStrategyDef {
|
||||
#[default]
|
||||
All,
|
||||
#[serde(alias = "only")]
|
||||
Include(HashSet<String>),
|
||||
#[serde(alias = "except")]
|
||||
#[serde(alias = "ignore")]
|
||||
Exclude(HashSet<String>),
|
||||
}
|
||||
|
|
|
@ -2,9 +2,11 @@
|
|||
|
||||
use anyhow::Result;
|
||||
use clap::{Arg, ArgAction, ArgMatches, Command};
|
||||
use himalaya_lib::folder::sync::Strategy as SyncFoldersStrategy;
|
||||
use log::info;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::ui::table;
|
||||
use crate::{folder, ui::table};
|
||||
|
||||
const ARG_ACCOUNT: &str = "account";
|
||||
const ARG_DRY_RUN: &str = "dry-run";
|
||||
|
@ -20,7 +22,7 @@ pub enum Cmd {
|
|||
/// Represents the list accounts command.
|
||||
List(table::args::MaxTableWidth),
|
||||
/// Represents the sync account command.
|
||||
Sync(DryRun),
|
||||
Sync(Option<SyncFoldersStrategy>, DryRun),
|
||||
}
|
||||
|
||||
/// Represents the account command matcher.
|
||||
|
@ -29,7 +31,22 @@ pub fn matches(m: &ArgMatches) -> Result<Option<Cmd>> {
|
|||
if let Some(m) = m.subcommand_matches(CMD_SYNC) {
|
||||
info!("sync account subcommand matched");
|
||||
let dry_run = parse_dry_run_arg(m);
|
||||
Some(Cmd::Sync(dry_run))
|
||||
let include = folder::args::parse_include_arg(m);
|
||||
let exclude = folder::args::parse_exclude_arg(m);
|
||||
let folders_strategy = if let Some(folder) = folder::args::parse_source_arg(m) {
|
||||
Some(SyncFoldersStrategy::Include(HashSet::from_iter([
|
||||
folder.to_owned()
|
||||
])))
|
||||
} else if !include.is_empty() {
|
||||
Some(SyncFoldersStrategy::Include(include.to_owned()))
|
||||
} else if !exclude.is_empty() {
|
||||
Some(SyncFoldersStrategy::Exclude(exclude))
|
||||
} else if folder::args::parse_all_arg(m) {
|
||||
Some(SyncFoldersStrategy::All)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Some(Cmd::Sync(folders_strategy, dry_run))
|
||||
} else if let Some(m) = m.subcommand_matches(CMD_LIST) {
|
||||
info!("list accounts subcommand matched");
|
||||
let max_table_width = table::args::parse_max_width(m);
|
||||
|
@ -55,6 +72,13 @@ pub fn subcmd() -> Command {
|
|||
.arg(table::args::max_width()),
|
||||
Command::new(CMD_SYNC)
|
||||
.about("Synchronize the given account locally")
|
||||
.arg(folder::args::all_arg("Synchronize all folders"))
|
||||
.arg(folder::args::include_arg(
|
||||
"Synchronize only the given folders",
|
||||
))
|
||||
.arg(folder::args::exclude_arg(
|
||||
"Synchronize all folders except the given ones",
|
||||
))
|
||||
.arg(dry_run()),
|
||||
])
|
||||
}
|
||||
|
@ -63,9 +87,10 @@ pub fn subcmd() -> Command {
|
|||
/// the user to select a different account than the default one.
|
||||
pub fn arg() -> Arg {
|
||||
Arg::new(ARG_ACCOUNT)
|
||||
.help("Set the account")
|
||||
.long("account")
|
||||
.short('a')
|
||||
.help("Select a specific account by name")
|
||||
.global(true)
|
||||
.value_name("STRING")
|
||||
}
|
||||
|
||||
|
|
|
@ -4,8 +4,11 @@
|
|||
//! account in the accounts section of the user configuration file.
|
||||
|
||||
use himalaya_lib::{
|
||||
AccountConfig, BackendConfig, EmailHooks, EmailSender, EmailTextPlainFormat, MaildirConfig,
|
||||
folder::sync::Strategy as SyncFoldersStrategy, AccountConfig, BackendConfig, EmailHooks,
|
||||
EmailSender, EmailTextPlainFormat, MaildirConfig,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
|
||||
#[cfg(feature = "imap-backend")]
|
||||
use himalaya_lib::ImapConfig;
|
||||
|
@ -13,9 +16,6 @@ use himalaya_lib::ImapConfig;
|
|||
#[cfg(feature = "notmuch-backend")]
|
||||
use himalaya_lib::NotmuchConfig;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
|
||||
use crate::config::{prelude::*, DeserializedConfig};
|
||||
|
||||
/// Represents all existing kind of account config.
|
||||
|
@ -104,6 +104,8 @@ pub struct DeserializedBaseAccountConfig {
|
|||
#[serde(default)]
|
||||
pub sync: bool,
|
||||
pub sync_dir: Option<PathBuf>,
|
||||
#[serde(default, with = "SyncFoldersStrategyDef")]
|
||||
pub sync_folders_strategy: SyncFoldersStrategy,
|
||||
}
|
||||
|
||||
impl DeserializedBaseAccountConfig {
|
||||
|
@ -207,6 +209,7 @@ impl DeserializedBaseAccountConfig {
|
|||
},
|
||||
sync: self.sync,
|
||||
sync_dir: self.sync_dir.clone(),
|
||||
sync_folders_strategy: self.sync_folders_strategy.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,10 @@
|
|||
//! This module gathers all account actions triggered by the CLI.
|
||||
|
||||
use anyhow::Result;
|
||||
use himalaya_lib::{AccountConfig, Backend, BackendSyncBuilder, BackendSyncProgressEvent};
|
||||
use himalaya_lib::{
|
||||
folder::sync::Strategy as SyncFoldersStrategy, AccountConfig, Backend, BackendSyncBuilder,
|
||||
BackendSyncProgressEvent,
|
||||
};
|
||||
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
|
||||
use log::{info, trace};
|
||||
|
||||
|
@ -43,15 +46,17 @@ pub fn sync<P: Printer>(
|
|||
account_config: &AccountConfig,
|
||||
printer: &mut P,
|
||||
backend: &dyn Backend,
|
||||
folder: &Option<String>,
|
||||
folders_strategy: Option<SyncFoldersStrategy>,
|
||||
dry_run: bool,
|
||||
) -> Result<()> {
|
||||
info!("entering the sync accounts handler");
|
||||
trace!("dry run: {}", dry_run);
|
||||
trace!("dry run: {dry_run}");
|
||||
trace!("folders strategy: {folders_strategy:#?}");
|
||||
|
||||
let mut sync_builder = BackendSyncBuilder::new(account_config);
|
||||
if let Some(folder) = folder {
|
||||
sync_builder = sync_builder.only_folder(folder);
|
||||
|
||||
if let Some(strategy) = folders_strategy {
|
||||
sync_builder = sync_builder.folders_strategy(strategy);
|
||||
}
|
||||
|
||||
if dry_run {
|
||||
|
|
|
@ -3,12 +3,17 @@
|
|||
//! This module provides subcommands, arguments and a command matcher
|
||||
//! related to the folder domain.
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{self, Arg, ArgMatches, Command};
|
||||
use clap::{self, Arg, ArgAction, ArgMatches, Command};
|
||||
use log::{debug, info};
|
||||
|
||||
use crate::ui::table;
|
||||
|
||||
const ARG_ALL: &str = "all";
|
||||
const ARG_EXCLUDE: &str = "exclude";
|
||||
const ARG_INCLUDE: &str = "include";
|
||||
const ARG_SOURCE: &str = "source";
|
||||
const ARG_TARGET: &str = "target";
|
||||
const CMD_CREATE: &str = "create";
|
||||
|
@ -74,9 +79,10 @@ pub fn subcmd() -> Command {
|
|||
/// Represents the source folder argument.
|
||||
pub fn source_arg() -> Arg {
|
||||
Arg::new(ARG_SOURCE)
|
||||
.help("Set the source folder")
|
||||
.long("folder")
|
||||
.short('f')
|
||||
.help("Specifies the source folder")
|
||||
.global(true)
|
||||
.value_name("SOURCE")
|
||||
}
|
||||
|
||||
|
@ -85,6 +91,70 @@ pub fn parse_source_arg(matches: &ArgMatches) -> Option<&str> {
|
|||
matches.get_one::<String>(ARG_SOURCE).map(String::as_str)
|
||||
}
|
||||
|
||||
/// Represents the all folders argument.
|
||||
pub fn all_arg(help: &'static str) -> Arg {
|
||||
Arg::new(ARG_ALL)
|
||||
.help(help)
|
||||
.long("all-folders")
|
||||
.alias("all")
|
||||
.short('A')
|
||||
.action(ArgAction::SetTrue)
|
||||
.conflicts_with(ARG_SOURCE)
|
||||
.conflicts_with(ARG_INCLUDE)
|
||||
.conflicts_with(ARG_EXCLUDE)
|
||||
}
|
||||
|
||||
/// Represents the all folders argument parser.
|
||||
pub fn parse_all_arg(m: &ArgMatches) -> bool {
|
||||
m.get_flag(ARG_ALL)
|
||||
}
|
||||
|
||||
/// Represents the folders to include argument.
|
||||
pub fn include_arg(help: &'static str) -> Arg {
|
||||
Arg::new(ARG_INCLUDE)
|
||||
.help(help)
|
||||
.long("include-folder")
|
||||
.alias("only")
|
||||
.short('F')
|
||||
.value_name("FOLDER")
|
||||
.num_args(1..)
|
||||
.action(ArgAction::Append)
|
||||
.conflicts_with(ARG_SOURCE)
|
||||
.conflicts_with(ARG_ALL)
|
||||
.conflicts_with(ARG_EXCLUDE)
|
||||
}
|
||||
|
||||
/// Represents the folders to include argument parser.
|
||||
pub fn parse_include_arg(m: &ArgMatches) -> HashSet<String> {
|
||||
m.get_many::<String>(ARG_INCLUDE)
|
||||
.unwrap_or_default()
|
||||
.map(ToOwned::to_owned)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Represents the folders to exclude argument.
|
||||
pub fn exclude_arg(help: &'static str) -> Arg {
|
||||
Arg::new(ARG_EXCLUDE)
|
||||
.help(help)
|
||||
.long("exclude-folder")
|
||||
.alias("except")
|
||||
.short('x')
|
||||
.value_name("FOLDER")
|
||||
.num_args(1..)
|
||||
.action(ArgAction::Append)
|
||||
.conflicts_with(ARG_SOURCE)
|
||||
.conflicts_with(ARG_ALL)
|
||||
.conflicts_with(ARG_INCLUDE)
|
||||
}
|
||||
|
||||
/// Represents the folders to exclude argument parser.
|
||||
pub fn parse_exclude_arg(m: &ArgMatches) -> HashSet<String> {
|
||||
m.get_many::<String>(ARG_EXCLUDE)
|
||||
.unwrap_or_default()
|
||||
.map(ToOwned::to_owned)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Represents the target folder argument.
|
||||
pub fn target_arg() -> Arg {
|
||||
Arg::new(ARG_TARGET)
|
||||
|
|
|
@ -122,11 +122,7 @@ fn main() -> Result<()> {
|
|||
Some(account::args::Cmd::List(max_width)) => {
|
||||
return account::handlers::list(max_width, &account_config, &config, &mut printer);
|
||||
}
|
||||
Some(account::args::Cmd::Sync(dry_run)) => {
|
||||
let folder = match folder {
|
||||
Some(folder) => Some(account_config.folder_alias(folder)?),
|
||||
None => None,
|
||||
};
|
||||
Some(account::args::Cmd::Sync(folders_strategy, dry_run)) => {
|
||||
let backend = BackendBuilder::new()
|
||||
.sessions_pool_size(8)
|
||||
.disable_cache(true)
|
||||
|
@ -135,7 +131,7 @@ fn main() -> Result<()> {
|
|||
&account_config,
|
||||
&mut printer,
|
||||
backend.as_ref(),
|
||||
&folder,
|
||||
folders_strategy,
|
||||
dry_run,
|
||||
)?;
|
||||
backend.close()?;
|
||||
|
|
|
@ -30,11 +30,14 @@ pub fn matches(m: &ArgMatches) -> Result<Option<Cmd>> {
|
|||
/// Man subcommands.
|
||||
pub fn subcmd() -> Command {
|
||||
Command::new(CMD_MAN)
|
||||
.about("Generates all man pages to the specified directory.")
|
||||
.about("Generate all man pages to the given directory")
|
||||
.arg(
|
||||
Arg::new(ARG_DIR)
|
||||
.help("Directory where to generate man files")
|
||||
.long_help("Represents the directory where all man files of all commands and subcommands should be generated in.")
|
||||
.help("Directory to generate man files in")
|
||||
.long_help(
|
||||
"Represents the directory where all man files of
|
||||
all commands and subcommands should be generated in.",
|
||||
)
|
||||
.required(true),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -11,22 +11,23 @@ pub(crate) const ARG_OUTPUT: &str = "output";
|
|||
pub fn args() -> Vec<Arg> {
|
||||
vec![
|
||||
Arg::new(ARG_OUTPUT)
|
||||
.help("Defines the output format")
|
||||
.help("Set the output format")
|
||||
.long("output")
|
||||
.short('o')
|
||||
.global(true)
|
||||
.value_name("FMT")
|
||||
.value_parser(["plain", "json"])
|
||||
.default_value("plain"),
|
||||
Arg::new(ARG_COLOR)
|
||||
.help("Controls when to use colors.")
|
||||
.help("Control when to use colors.")
|
||||
.long_help(
|
||||
"
|
||||
This flag controls when to use colors. The default setting is 'auto', which
|
||||
means himalaya will try to guess when to use colors. For example, if himalaya is
|
||||
printing to a terminal, then it will use colors, but if it is redirected to a
|
||||
file or a pipe, then it will suppress color output. himalaya will suppress color
|
||||
output in some other circumstances as well. For example, if the TERM
|
||||
environment variable is not set or set to 'dumb', then himalaya will not use
|
||||
"This flag controls when to use colors. The default
|
||||
setting is 'auto', which means himalaya will try to guess when to use
|
||||
colors. For example, if himalaya is printing to a terminal, then it
|
||||
will use colors, but if it is redirected to a file or a pipe, then it
|
||||
will suppress color output. himalaya will suppress color output in
|
||||
some other circumstances as well. For example, if the TERM environment
|
||||
variable is not set or set to 'dumb', then himalaya will not use
|
||||
colors.
|
||||
|
||||
The possible values for this flag are:
|
||||
|
@ -34,11 +35,11 @@ The possible values for this flag are:
|
|||
never Colors will never be used.
|
||||
auto The default. himalaya tries to be smart.
|
||||
always Colors will always be used regardless of where output is sent.
|
||||
ansi Like 'always', but emits ANSI escapes (even in a Windows console).
|
||||
",
|
||||
ansi Like 'always', but emits ANSI escapes (even in a Windows console).",
|
||||
)
|
||||
.long("color")
|
||||
.short('C')
|
||||
.global(true)
|
||||
.value_parser(["never", "auto", "always", "ansi"])
|
||||
.default_value("auto")
|
||||
.value_name("WHEN"),
|
||||
|
|
Loading…
Reference in a new issue