use std::path::{Path, PathBuf};
use clap::{Parser, ValueEnum};
use sibling::{Error, Result};
use crate::LogLevel;
#[derive(Debug, Parser)]
#[clap(
version,
author,
about,
arg_required_else_help = true,
after_help = "Exit status:
0 the next directory was found (printed to stdout),
1 no more sibling directory was found,
2 the given command line arguments were wrong, and
3 the command failed (the reason is printed to stderr)."
)]
pub struct SiblingOpts {
#[clap(flatten)]
pub(crate) cli_opts: CliOpts,
#[clap(flatten)]
pub(crate) init_script: InitOpts,
#[clap(flatten)]
pub(crate) log_opts: LogOpts,
#[cfg(debug_assertions)]
#[clap(flatten)]
pub(crate) compopts: CompletionOpts,
}
#[derive(Debug, Parser)]
pub(crate) struct CliOpts {
#[clap(flatten)]
pub(crate) p_opts: PrintingOpts,
#[clap(flatten)]
pub(crate) nexter_opts: NexterOpts,
#[clap(flatten)]
pub(crate) base_opts: BaseOpts,
}
impl SiblingOpts {
pub fn init(&mut self) {
self.log_opts.init();
}
}
#[derive(Parser, Debug)]
pub(crate) struct LogOpts {
#[arg(
long,
help = "set the log level",
value_enum,
default_value_t = LogLevel::Warn,
value_name = "LEVEL",
ignore_case = true
)]
pub log: LogLevel,
}
impl LogOpts {
pub fn init(&self) {
use LogLevel::{Debug, Error, Info, Trace, Warn};
if std::env::var_os("RUST_LOG").is_none() {
unsafe {
match self.log {
Error => std::env::set_var("RUST_LOG", "error"),
Warn => std::env::set_var("RUST_LOG", "warn"),
Info => std::env::set_var("RUST_LOG", "info"),
Debug => std::env::set_var("RUST_LOG", "debug"),
Trace => std::env::set_var("RUST_LOG", "trace"),
};
}
}
env_logger::init();
log::info!("Log level set to {:?}", self.log);
}
}
#[derive(Parser, Debug)]
pub(crate) struct InitOpts {
#[arg(
long,
help = "generate the initialize script for the shell [possible values: bash, zsh, fish, powershell, elvish]",
value_name = "SHELL",
hide = true,
default_missing_value = "bash"
)]
pub init: Option<String>,
}
#[derive(Parser, Debug)]
pub(crate) struct NexterOpts {
#[arg(
short,
long,
help = "specify the number of times to execute sibling. The negative count traverses in the opposite direction, and 0 means the current directory",
value_name = "COUNT",
default_value_t = 1,
allow_negative_numbers = true
)]
pub step: i32,
#[arg(short = 't', long = "type", help = "specify the nexter type", value_enum, default_value_t = sibling::NexterType::Next, value_name = "TYPE", ignore_case = true)]
pub nexter: sibling::NexterType,
}
#[derive(Parser, Debug)]
pub(crate) struct BaseOpts {
#[arg(
short = 'a',
long,
help = "Set the targets to all directories from the given list. By default, the sibling skips non-existent directories.",
default_value_t = false
)]
pub all: bool,
#[arg(
short = 'b',
alias = "parent",
long,
help = "specify the parent directory of DIR (default: the parent directory of DIR)",
value_name = "DIR"
)]
pub base_path: Option<PathBuf>,
#[arg(
long,
help = "specify the action when the current directory is not in the target directories, such as DIR which is not in --base-path",
value_enum,
default_value_t = sibling::factory::NotOnDirs::Error,
value_name = "ACTION",
ignore_case = true
)]
pub not_on_dirs: sibling::factory::NotOnDirs,
#[arg(index = 1, help = "the directory to find its siblings, or the file of directory list", value_name = "DIR|FILE", default_value = ".")]
pub input: String,
}
impl BaseOpts {
pub fn config(&self) -> Result<sibling::factory::Config> {
let target = self.target_path();
let base = match &self.base_path {
Some(base) => base.clone(),
None => match target.parent() {
Some(parent) => parent.to_path_buf(),
None => return Err(Error::NoParent(target)),
},
};
log::debug!("target: {}, base: {}", target.display(), base.display());
if target.is_dir() {
Ok(sibling::factory::Config::new_with_wd(base, self.all, target).not_on_dirs(self.not_on_dirs))
} else {
Ok(sibling::factory::Config::new(base, self.all).not_on_dirs(self.not_on_dirs))
}
}
fn target_path(&self) -> PathBuf {
let path = PathBuf::from(&self.input);
if is_parent_obvious(&path) {
path
} else {
std::fs::canonicalize(&path).unwrap_or(path)
}
}
}
fn is_parent_obvious(path: &Path) -> bool {
use std::path::Component::{CurDir, ParentDir};
let has_parent = path.parent().is_some_and(|p| !p.as_os_str().is_empty());
has_parent && !path.components().any(|c| c == CurDir || c == ParentDir)
}
#[cfg(debug_assertions)]
#[derive(Parser, Debug)]
pub(crate) struct CompletionOpts {
#[arg(
long = "generate-completion-files",
help = "Generate completion files",
hide = true
)]
pub(crate) completion: bool,
#[arg(
long = "completion-out-dir",
value_name = "DIR",
default_value = "assets/completions",
help = "Output directory of completion files",
hide = true
)]
pub(crate) dest: PathBuf,
}
#[derive(Debug, Clone, PartialEq, Eq, ValueEnum)]
pub enum Format {
Json,
Csv,
List,
Default,
}
#[derive(Debug, Parser)]
pub(crate) struct PrintingOpts {
#[arg(
short, long,
help = "print the result in the specified format",
default_value_t = Format::Default,
value_enum,
)]
pub format: Format,
#[arg(
short = 'A',
long,
help = "print the directory name in the absolute path",
default_value_t = false
)]
pub absolute: bool,
#[arg(
short,
long,
help = "print the progress of traversing directories",
default_value_t = false
)]
pub progress: bool,
}