mirror of
https://github.com/soywod/himalaya.git
synced 2024-11-24 20:10:23 +00:00
refactor man and completion with clap derive api
This commit is contained in:
parent
7a10a7fc25
commit
d2308221d7
22 changed files with 270 additions and 233 deletions
30
Cargo.lock
generated
30
Cargo.lock
generated
|
@ -470,6 +470,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -493,6 +494,18 @@ dependencies = [
|
|||
"clap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.39",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.6.0"
|
||||
|
@ -1063,12 +1076,12 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.8.4"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3"
|
||||
checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"humantime",
|
||||
"is-terminal",
|
||||
"log",
|
||||
"regex",
|
||||
"termcolor",
|
||||
|
@ -2371,6 +2384,17 @@ version = "2.9.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
|
||||
dependencies = [
|
||||
"hermit-abi 0.3.3",
|
||||
"rustix",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.9"
|
||||
|
|
|
@ -60,6 +60,7 @@ version = "0.4.24"
|
|||
|
||||
[dependencies.clap]
|
||||
version = "4.0"
|
||||
features = ["derive"]
|
||||
|
||||
[dependencies.clap_complete]
|
||||
version = "4.0"
|
||||
|
@ -80,7 +81,7 @@ version = "4.0.0"
|
|||
version = "0.2.4"
|
||||
|
||||
[dependencies.env_logger]
|
||||
version = "0.8"
|
||||
version = "0.10"
|
||||
|
||||
[dependencies.erased-serde]
|
||||
version = "0.3"
|
||||
|
|
|
@ -33,12 +33,20 @@ pub enum Cmd {
|
|||
/// Represents the account command matcher.
|
||||
pub fn matches(m: &ArgMatches) -> Result<Option<Cmd>> {
|
||||
let cmd = if let Some(m) = m.subcommand_matches(CMD_ACCOUNT) {
|
||||
if let Some(m) = m.subcommand_matches(CMD_SYNC) {
|
||||
if let Some(m) = m.subcommand_matches(CMD_CONFIGURE) {
|
||||
info!("configure account subcommand matched");
|
||||
let reset = parse_reset_flag(m);
|
||||
Some(Cmd::Configure(reset))
|
||||
} 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);
|
||||
Some(Cmd::List(max_table_width))
|
||||
} else if let Some(m) = m.subcommand_matches(CMD_SYNC) {
|
||||
info!("sync account subcommand matched");
|
||||
let dry_run = parse_dry_run_arg(m);
|
||||
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) {
|
||||
let folders_strategy = if let Some(folder) = folder::args::parse_global_source_arg(m) {
|
||||
Some(FolderSyncStrategy::Include(HashSet::from_iter([
|
||||
folder.to_owned()
|
||||
])))
|
||||
|
@ -52,17 +60,8 @@ pub fn matches(m: &ArgMatches) -> Result<Option<Cmd>> {
|
|||
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);
|
||||
Some(Cmd::List(max_table_width))
|
||||
} else if let Some(m) = m.subcommand_matches(CMD_CONFIGURE) {
|
||||
info!("configure account subcommand matched");
|
||||
let reset = parse_reset_flag(m);
|
||||
Some(Cmd::Configure(reset))
|
||||
} else {
|
||||
info!("no account subcommand matched, falling back to subcommand list");
|
||||
Some(Cmd::List(None))
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
|
@ -75,10 +74,26 @@ pub fn matches(m: &ArgMatches) -> Result<Option<Cmd>> {
|
|||
pub fn subcmd() -> Command {
|
||||
Command::new(CMD_ACCOUNT)
|
||||
.about("Subcommand to manage accounts")
|
||||
.subcommands([
|
||||
.long_about("Subcommand to manage accounts like configure, list or sync")
|
||||
.aliases(["accounts", "acc"])
|
||||
.subcommand_required(true)
|
||||
.arg_required_else_help(true)
|
||||
.subcommand(
|
||||
Command::new(CMD_CONFIGURE)
|
||||
.about("Configure the given account")
|
||||
.aliases(["config", "conf", "cfg"])
|
||||
.arg(reset_flag())
|
||||
.arg(folder::args::source_arg(
|
||||
"Define the account to be configured",
|
||||
)),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new(CMD_LIST)
|
||||
.about("List all accounts from the config file")
|
||||
.about("List all accounts")
|
||||
.long_about("List all accounts that are set up in the configuration file")
|
||||
.arg(table::args::max_width()),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new(CMD_SYNC)
|
||||
.about("Synchronize the given account locally")
|
||||
.arg(folder::args::all_arg("Synchronize all folders"))
|
||||
|
@ -89,26 +104,27 @@ pub fn subcmd() -> Command {
|
|||
"Synchronize all folders except the given ones",
|
||||
))
|
||||
.arg(dry_run()),
|
||||
Command::new(CMD_CONFIGURE)
|
||||
.about("Configure the current selected account")
|
||||
.aliases(["config", "conf", "cfg"])
|
||||
.arg(reset_flag()),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
/// Represents the user account name argument. This argument allows
|
||||
/// the user to select a different account than the default one.
|
||||
pub fn arg() -> Arg {
|
||||
Arg::new(ARG_ACCOUNT)
|
||||
.help("Set the account")
|
||||
pub fn global_args() -> impl IntoIterator<Item = Arg> {
|
||||
[Arg::new(ARG_ACCOUNT)
|
||||
.help("Override the default account")
|
||||
.long_help(
|
||||
"Override the default account
|
||||
|
||||
The given account will be used by default for all other commands (when applicable).",
|
||||
)
|
||||
.long("account")
|
||||
.short('a')
|
||||
.global(true)
|
||||
.value_name("STRING")
|
||||
.value_name("name")]
|
||||
}
|
||||
|
||||
/// Represents the user account name argument parser.
|
||||
pub fn parse_arg(matches: &ArgMatches) -> Option<&str> {
|
||||
pub fn parse_global_arg(matches: &ArgMatches) -> Option<&str> {
|
||||
matches.get_one::<String>(ARG_ACCOUNT).map(String::as_str)
|
||||
}
|
||||
|
||||
|
|
17
src/cache/args.rs
vendored
17
src/cache/args.rs
vendored
|
@ -6,19 +6,24 @@ const ARG_DISABLE_CACHE: &str = "disable-cache";
|
|||
|
||||
/// Represents the disable cache flag argument. This argument allows
|
||||
/// the user to disable any sort of cache.
|
||||
pub fn arg() -> Arg {
|
||||
Arg::new(ARG_DISABLE_CACHE)
|
||||
pub fn global_args() -> impl IntoIterator<Item = Arg> {
|
||||
[Arg::new(ARG_DISABLE_CACHE)
|
||||
.help("Disable any sort of cache")
|
||||
.long_help(
|
||||
"Disable any sort of cache. The action depends on
|
||||
the command it applies on.",
|
||||
"Disable any sort of cache.
|
||||
|
||||
The action depends on commands it apply on. For example, when listing
|
||||
envelopes using the IMAP backend, this flag will ensure that envelopes
|
||||
are fetched from the IMAP server and not from the synchronized local
|
||||
Maildir.",
|
||||
)
|
||||
.long("disable-cache")
|
||||
.alias("no-cache")
|
||||
.global(true)
|
||||
.action(ArgAction::SetTrue)
|
||||
.action(ArgAction::SetTrue)]
|
||||
}
|
||||
|
||||
/// Represents the disable cache flag parser.
|
||||
pub fn parse_disable_cache_flag(m: &ArgMatches) -> bool {
|
||||
pub fn parse_disable_cache_arg(m: &ArgMatches) -> bool {
|
||||
m.get_flag(ARG_DISABLE_CACHE)
|
||||
}
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
//! Module related to completion CLI.
|
||||
//!
|
||||
//! This module provides subcommands and a command matcher related to completion.
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{value_parser, Arg, ArgMatches, Command};
|
||||
use clap_complete::Shell;
|
||||
use log::debug;
|
||||
|
||||
const ARG_SHELL: &str = "shell";
|
||||
const CMD_COMPLETION: &str = "completion";
|
||||
|
||||
type SomeShell = Shell;
|
||||
|
||||
/// Completion commands.
|
||||
pub enum Cmd {
|
||||
/// Generate completion script for the given shell.
|
||||
Generate(SomeShell),
|
||||
}
|
||||
|
||||
/// Completion command matcher.
|
||||
pub fn matches(m: &ArgMatches) -> Result<Option<Cmd>> {
|
||||
if let Some(m) = m.subcommand_matches(CMD_COMPLETION) {
|
||||
let shell = m.get_one::<Shell>(ARG_SHELL).cloned().unwrap();
|
||||
debug!("shell: {:?}", shell);
|
||||
return Ok(Some(Cmd::Generate(shell)));
|
||||
};
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Completion subcommands.
|
||||
pub fn subcmd() -> Command {
|
||||
Command::new(CMD_COMPLETION)
|
||||
.about("Generate the completion script for the given shell")
|
||||
.args(&[Arg::new(ARG_SHELL)
|
||||
.value_parser(value_parser!(Shell))
|
||||
.required(true)])
|
||||
}
|
10
src/completion/command.rs
Normal file
10
src/completion/command.rs
Normal file
|
@ -0,0 +1,10 @@
|
|||
use clap::{value_parser, Parser};
|
||||
use clap_complete::Shell;
|
||||
|
||||
/// Print completion script for the given shell to stdout
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Generate {
|
||||
/// Shell that completion script should be generated for
|
||||
#[arg(value_parser = value_parser!(Shell))]
|
||||
pub shell: Shell,
|
||||
}
|
18
src/completion/handler.rs
Normal file
18
src/completion/handler.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
use anyhow::Result;
|
||||
use clap::Command;
|
||||
use clap_complete::Shell;
|
||||
use std::io::stdout;
|
||||
|
||||
use crate::printer::Printer;
|
||||
|
||||
pub fn generate(printer: &mut impl Printer, mut cmd: Command, shell: Shell) -> Result<()> {
|
||||
let name = cmd.get_name().to_string();
|
||||
|
||||
clap_complete::generate(shell, &mut cmd, name, &mut stdout());
|
||||
|
||||
printer.print(format!(
|
||||
"Shell script successfully generated for shell {shell}!"
|
||||
))?;
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
//! Module related to completion handling.
|
||||
//!
|
||||
//! This module gathers all completion commands.
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Command;
|
||||
use clap_complete::Shell;
|
||||
use std::io::stdout;
|
||||
|
||||
/// Generates completion script from the given [`clap::App`] for the given shell slice.
|
||||
pub fn generate<'a>(mut cmd: Command, shell: Shell) -> Result<()> {
|
||||
let name = cmd.get_name().to_string();
|
||||
clap_complete::generate(shell, &mut cmd, name, &mut stdout());
|
||||
Ok(())
|
||||
}
|
|
@ -1,8 +1,2 @@
|
|||
//! Module related to shell completion.
|
||||
//!
|
||||
//! This module allows users to generate autocompletion scripts for
|
||||
//! their shells. You can see the list of available shells directly on
|
||||
//! the clap's [docs.rs](https://docs.rs/clap/2.33.3/clap/enum.Shell.html).
|
||||
|
||||
pub mod args;
|
||||
pub mod handlers;
|
||||
pub mod command;
|
||||
pub mod handler;
|
||||
|
|
|
@ -6,16 +6,21 @@ const ARG_CONFIG: &str = "config";
|
|||
|
||||
/// Represents the config file path argument. This argument allows the
|
||||
/// user to customize the config file path.
|
||||
pub fn arg() -> Arg {
|
||||
Arg::new(ARG_CONFIG)
|
||||
.help("Set a custom configuration file path")
|
||||
pub fn global_args() -> impl IntoIterator<Item = Arg> {
|
||||
[Arg::new(ARG_CONFIG)
|
||||
.help("Override the configuration file path")
|
||||
.long_help(
|
||||
"Override the configuration file path
|
||||
|
||||
If the file under the given path does not exist, the wizard will propose to create it.",
|
||||
)
|
||||
.long("config")
|
||||
.short('c')
|
||||
.global(true)
|
||||
.value_name("PATH")
|
||||
.value_name("path")]
|
||||
}
|
||||
|
||||
/// Represents the config file path argument parser.
|
||||
pub fn parse_arg(matches: &ArgMatches) -> Option<&str> {
|
||||
pub fn parse_global_arg(matches: &ArgMatches) -> Option<&str> {
|
||||
matches.get_one::<String>(ARG_CONFIG).map(String::as_str)
|
||||
}
|
||||
|
|
|
@ -43,7 +43,8 @@ pub fn matches(m: &ArgMatches) -> Result<Option<Cmd>> {
|
|||
/// Represents the envelope subcommand.
|
||||
pub fn subcmd() -> Command {
|
||||
Command::new(CMD_ENVELOPE)
|
||||
.about("Manage envelopes")
|
||||
.about("Subcommand to manage envelopes")
|
||||
.long_about("Subcommand to manage envelopes like list")
|
||||
.subcommands([Command::new(CMD_LIST)
|
||||
.alias("lst")
|
||||
.about("List envelopes")
|
||||
|
|
|
@ -57,7 +57,8 @@ pub fn matches(m: &ArgMatches) -> Result<Option<Cmd>> {
|
|||
/// Represents the flag subcommand.
|
||||
pub fn subcmd() -> Command {
|
||||
Command::new(CMD_FLAG)
|
||||
.about("Manage flags")
|
||||
.about("Subcommand to manage flags")
|
||||
.long_about("Subcommand to manage flags like add, set or remove")
|
||||
.subcommand_required(true)
|
||||
.arg_required_else_help(true)
|
||||
.subcommand(
|
||||
|
|
|
@ -120,7 +120,8 @@ pub fn matches(m: &ArgMatches) -> Result<Option<Cmd>> {
|
|||
/// Represents the email subcommands.
|
||||
pub fn subcmd() -> Command {
|
||||
Command::new(CMD_MESSAGE)
|
||||
.about("Manage messages")
|
||||
.about("Subcommand to manage messages")
|
||||
.long_about("Subcommand to manage messages like read, write, reply or send")
|
||||
.aliases(["msg"])
|
||||
.subcommand_required(true)
|
||||
.arg_required_else_help(true)
|
||||
|
|
|
@ -73,7 +73,8 @@ pub fn matches<'a>(m: &'a ArgMatches) -> Result<Option<Cmd<'a>>> {
|
|||
pub fn subcmd() -> Command {
|
||||
Command::new(CMD_TPL)
|
||||
.alias("tpl")
|
||||
.about("Manage templates")
|
||||
.about("Subcommand to manage templates")
|
||||
.long_about("Subcommand to manage templates like write, reply, send or save")
|
||||
.subcommand_required(true)
|
||||
.arg_required_else_help(true)
|
||||
.subcommand(
|
||||
|
|
|
@ -14,6 +14,7 @@ use crate::ui::table;
|
|||
const ARG_ALL: &str = "all";
|
||||
const ARG_EXCLUDE: &str = "exclude";
|
||||
const ARG_INCLUDE: &str = "include";
|
||||
const ARG_GLOBAL_SOURCE: &str = "global-source";
|
||||
const ARG_SOURCE: &str = "source";
|
||||
const ARG_TARGET: &str = "target";
|
||||
const CMD_CREATE: &str = "create";
|
||||
|
@ -61,7 +62,8 @@ pub fn matches(m: &ArgMatches) -> Result<Option<Cmd>> {
|
|||
/// Represents the folder subcommand.
|
||||
pub fn subcmd() -> Command {
|
||||
Command::new(CMD_FOLDER)
|
||||
.about("Manage folders")
|
||||
.about("Subcommand to manage folders")
|
||||
.long_about("Subcommand to manage folders like list, expunge or delete")
|
||||
.subcommands([
|
||||
Command::new(CMD_EXPUNGE).about("Delete emails marked for deletion"),
|
||||
Command::new(CMD_CREATE)
|
||||
|
@ -77,16 +79,31 @@ pub fn subcmd() -> Command {
|
|||
}
|
||||
|
||||
/// Represents the source folder argument.
|
||||
pub fn source_arg() -> Arg {
|
||||
Arg::new(ARG_SOURCE)
|
||||
.help("Set the source folder")
|
||||
pub fn global_args() -> impl IntoIterator<Item = Arg> {
|
||||
[Arg::new(ARG_GLOBAL_SOURCE)
|
||||
.help("Override the default INBOX folder")
|
||||
.long_help(
|
||||
"Override the default INBOX folder.
|
||||
|
||||
The given folder will be used by default for all other commands (when
|
||||
applicable).",
|
||||
)
|
||||
.long("folder")
|
||||
.short('f')
|
||||
.global(true)
|
||||
.value_name("SOURCE")
|
||||
.value_name("name")]
|
||||
}
|
||||
|
||||
pub fn parse_global_source_arg(matches: &ArgMatches) -> Option<&str> {
|
||||
matches
|
||||
.get_one::<String>(ARG_GLOBAL_SOURCE)
|
||||
.map(String::as_str)
|
||||
}
|
||||
|
||||
pub fn source_arg(help: &'static str) -> Arg {
|
||||
Arg::new(ARG_SOURCE).help(help).value_name("name")
|
||||
}
|
||||
|
||||
/// Represents the source folder argument parser.
|
||||
pub fn parse_source_arg(matches: &ArgMatches) -> Option<&str> {
|
||||
matches.get_one::<String>(ARG_SOURCE).map(String::as_str)
|
||||
}
|
||||
|
|
79
src/main.rs
79
src/main.rs
|
@ -1,6 +1,7 @@
|
|||
use ::email::account::{config::DEFAULT_INBOX_FOLDER, sync::AccountSyncBuilder};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use clap::Command;
|
||||
use clap::{Command, CommandFactory, Parser, Subcommand};
|
||||
use env_logger::{Builder as LoggerBuilder, Env, DEFAULT_FILTER_ENV};
|
||||
use log::{debug, warn};
|
||||
use std::env;
|
||||
use url::Url;
|
||||
|
@ -15,20 +16,18 @@ use himalaya::{
|
|||
template,
|
||||
};
|
||||
|
||||
fn create_app() -> Command {
|
||||
fn _create_app() -> Command {
|
||||
Command::new(env!("CARGO_PKG_NAME"))
|
||||
.version(env!("CARGO_PKG_VERSION"))
|
||||
.about(env!("CARGO_PKG_DESCRIPTION"))
|
||||
.author(env!("CARGO_PKG_AUTHORS"))
|
||||
.propagate_version(true)
|
||||
.infer_subcommands(true)
|
||||
.arg(config::args::arg())
|
||||
.arg(account::args::arg())
|
||||
.arg(cache::args::arg())
|
||||
.args(output::args::args())
|
||||
.arg(folder::args::source_arg())
|
||||
.subcommand(completion::args::subcmd())
|
||||
.subcommand(man::args::subcmd())
|
||||
.args(config::args::global_args())
|
||||
.args(account::args::global_args())
|
||||
.args(folder::args::global_args())
|
||||
.args(cache::args::global_args())
|
||||
.args(output::args::global_args())
|
||||
.subcommand(account::args::subcmd())
|
||||
.subcommand(folder::args::subcmd())
|
||||
.subcommand(envelope::args::subcmd())
|
||||
|
@ -38,7 +37,7 @@ fn create_app() -> Command {
|
|||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
async fn _old_main() -> Result<()> {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
if let Err((_, err)) = coredump::register_panic_handler() {
|
||||
warn!("cannot register custom panic handler: {err}");
|
||||
|
@ -63,32 +62,13 @@ async fn main() -> Result<()> {
|
|||
return message::handlers::mailto(&account_config, &backend, &mut printer, &url).await;
|
||||
}
|
||||
|
||||
let app = create_app();
|
||||
let app = _create_app();
|
||||
let m = app.get_matches();
|
||||
|
||||
// check completionetion command before configs
|
||||
// https://github.com/soywod/himalaya/issues/115
|
||||
#[allow(clippy::single_match)]
|
||||
match completion::args::matches(&m)? {
|
||||
Some(completion::args::Cmd::Generate(shell)) => {
|
||||
return completion::handlers::generate(create_app(), shell);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
// check also man command before configs
|
||||
#[allow(clippy::single_match)]
|
||||
match man::args::matches(&m)? {
|
||||
Some(man::args::Cmd::GenerateAll(dir)) => {
|
||||
return man::handlers::generate(dir, create_app());
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let some_config_path = config::args::parse_arg(&m);
|
||||
let some_account_name = account::args::parse_arg(&m);
|
||||
let disable_cache = cache::args::parse_disable_cache_flag(&m);
|
||||
let folder = folder::args::parse_source_arg(&m);
|
||||
let some_config_path = config::args::parse_global_arg(&m);
|
||||
let some_account_name = account::args::parse_global_arg(&m);
|
||||
let disable_cache = cache::args::parse_disable_cache_arg(&m);
|
||||
let folder = folder::args::parse_global_source_arg(&m);
|
||||
|
||||
let toml_config = TomlConfig::from_some_path_or_default(some_config_path).await?;
|
||||
|
||||
|
@ -362,3 +342,34 @@ async fn main() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(name= "himalaya", author, version, about, long_about = None, propagate_version = true)]
|
||||
struct Cli {
|
||||
#[command(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
enum Commands {
|
||||
Man(man::command::Generate),
|
||||
#[command(aliases = ["completions", "compl", "comp"])]
|
||||
Completion(completion::command::Generate),
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
LoggerBuilder::new()
|
||||
.parse_env(Env::new().filter_or(DEFAULT_FILTER_ENV, "warn"))
|
||||
.format_timestamp(None)
|
||||
.init();
|
||||
|
||||
let mut printer = StdoutPrinter::default();
|
||||
|
||||
match Cli::parse().command {
|
||||
Commands::Man(cmd) => man::handler::generate(&mut printer, Cli::command(), cmd.dir),
|
||||
Commands::Completion(cmd) => {
|
||||
completion::handler::generate(&mut printer, Cli::command(), cmd.shell)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
//! Module related to man CLI.
|
||||
//!
|
||||
//! This module provides subcommands and a command matcher related to
|
||||
//! man.
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{Arg, ArgMatches, Command};
|
||||
use log::debug;
|
||||
|
||||
const ARG_DIR: &str = "dir";
|
||||
const CMD_MAN: &str = "man";
|
||||
|
||||
/// Man commands.
|
||||
pub enum Cmd<'a> {
|
||||
/// Generates all man pages to the specified directory.
|
||||
GenerateAll(&'a str),
|
||||
}
|
||||
|
||||
/// Man command matcher.
|
||||
pub fn matches(m: &ArgMatches) -> Result<Option<Cmd>> {
|
||||
if let Some(m) = m.subcommand_matches(CMD_MAN) {
|
||||
let dir = m.get_one::<String>(ARG_DIR).map(String::as_str).unwrap();
|
||||
debug!("directory: {}", dir);
|
||||
return Ok(Some(Cmd::GenerateAll(dir)));
|
||||
};
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Man subcommands.
|
||||
pub fn subcmd() -> Command {
|
||||
Command::new(CMD_MAN)
|
||||
.about("Generate all man pages to the given directory")
|
||||
.arg(
|
||||
Arg::new(ARG_DIR)
|
||||
.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),
|
||||
)
|
||||
}
|
22
src/man/command.rs
Normal file
22
src/man/command.rs
Normal file
|
@ -0,0 +1,22 @@
|
|||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use shellexpand_utils::{canonicalize, expand};
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Generate all man pages to the given directory
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Generate {
|
||||
/// Directory where man files should be generated in
|
||||
#[arg(value_parser = dir_parser)]
|
||||
pub dir: PathBuf,
|
||||
}
|
||||
|
||||
/// Parse the given [`str`] as [`PathBuf`].
|
||||
///
|
||||
/// The path is first shell expanded, then canonicalized (if
|
||||
/// applicable).
|
||||
fn dir_parser(path: &str) -> Result<PathBuf, String> {
|
||||
expand::try_path(path)
|
||||
.map(canonicalize::path)
|
||||
.map_err(|err| err.to_string())
|
||||
}
|
35
src/man/handler.rs
Normal file
35
src/man/handler.rs
Normal file
|
@ -0,0 +1,35 @@
|
|||
use anyhow::Result;
|
||||
use clap::Command;
|
||||
use clap_mangen::Man;
|
||||
use std::{fs, path::PathBuf};
|
||||
|
||||
use crate::printer::Printer;
|
||||
|
||||
pub fn generate(printer: &mut impl Printer, cmd: Command, dir: PathBuf) -> Result<()> {
|
||||
let cmd_name = cmd.get_name().to_string();
|
||||
let subcmds = cmd.get_subcommands().cloned().collect::<Vec<_>>();
|
||||
let subcmds_len = subcmds.len() + 1;
|
||||
|
||||
let mut buffer = Vec::new();
|
||||
Man::new(cmd).render(&mut buffer)?;
|
||||
|
||||
fs::create_dir_all(&dir)?;
|
||||
printer.print_log(format!("Generating man page for command {cmd_name}…"))?;
|
||||
fs::write(dir.join(format!("{}.1", cmd_name)), buffer)?;
|
||||
|
||||
for subcmd in subcmds {
|
||||
let subcmd_name = subcmd.get_name().to_string();
|
||||
|
||||
let mut buffer = Vec::new();
|
||||
Man::new(subcmd).render(&mut buffer)?;
|
||||
|
||||
printer.print_log(format!("Generating man page for subcommand {subcmd_name}…"))?;
|
||||
fs::write(dir.join(format!("{}-{}.1", cmd_name, subcmd_name)), buffer)?;
|
||||
}
|
||||
|
||||
printer.print(format!(
|
||||
"Successfully generated {subcmds_len} man page(s) in {dir:?}!"
|
||||
))?;
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
//! Module related to man handling.
|
||||
//!
|
||||
//! This module gathers all man commands.
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Command;
|
||||
use clap_mangen::Man;
|
||||
use std::{fs, path::PathBuf};
|
||||
|
||||
/// Generates all man pages of all subcommands in the given directory.
|
||||
pub fn generate(dir: &str, cmd: Command) -> Result<()> {
|
||||
let mut buffer = Vec::new();
|
||||
let cmd_name = cmd.get_name().to_string();
|
||||
let subcmds = cmd.get_subcommands().cloned().collect::<Vec<_>>();
|
||||
Man::new(cmd).render(&mut buffer)?;
|
||||
fs::write(PathBuf::from(dir).join(format!("{}.1", cmd_name)), buffer)?;
|
||||
|
||||
for subcmd in subcmds {
|
||||
let mut buffer = Vec::new();
|
||||
let subcmd_name = subcmd.get_name().to_string();
|
||||
Man::new(subcmd).render(&mut buffer)?;
|
||||
fs::write(
|
||||
PathBuf::from(dir).join(format!("{}-{}.1", cmd_name, subcmd_name)),
|
||||
buffer,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,2 +1,2 @@
|
|||
pub mod args;
|
||||
pub mod handlers;
|
||||
pub mod command;
|
||||
pub mod handler;
|
||||
|
|
|
@ -8,27 +8,28 @@ pub(crate) const ARG_COLOR: &str = "color";
|
|||
pub(crate) const ARG_OUTPUT: &str = "output";
|
||||
|
||||
/// Output arguments.
|
||||
pub fn args() -> Vec<Arg> {
|
||||
vec![
|
||||
pub fn global_args() -> impl IntoIterator<Item = Arg> {
|
||||
[
|
||||
Arg::new(ARG_OUTPUT)
|
||||
.help("Set the output format")
|
||||
.help("Define the output format")
|
||||
.long("output")
|
||||
.short('o')
|
||||
.global(true)
|
||||
.value_name("FMT")
|
||||
.value_name("format")
|
||||
.value_parser(["plain", "json"])
|
||||
.default_value("plain"),
|
||||
Arg::new(ARG_COLOR)
|
||||
.help("Control 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
|
||||
colors.
|
||||
"Control 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:
|
||||
|
||||
|
@ -42,6 +43,6 @@ ansi Like 'always', but emits ANSI escapes (even in a Windows console).",
|
|||
.global(true)
|
||||
.value_parser(["never", "auto", "always", "ansi"])
|
||||
.default_value("auto")
|
||||
.value_name("WHEN"),
|
||||
.value_name("mode"),
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue