use std::{
env,
path::{Path, PathBuf},
slice, vec,
};
use anyhow::{self, Context};
use clap::{Args, Parser, Subcommand, ValueEnum};
use itertools::Itertools;
#[macro_export]
macro_rules! editorconfig_version {
() => {
"0.17.2"
};
}
#[derive(Parser)]
#[command(
version = concat!(
clap::crate_version!(),
" - EditorConfig Specification Version ",
editorconfig_version!()
),
about,
long_about = None
)]
#[command(propagate_version = true, max_term_width = 100)]
pub struct Cli {
#[command(subcommand)]
pub command: Commands,
}
#[derive(Subcommand)]
pub enum Commands {
#[command(flatten)]
Lib(LibCommands),
License(LicenseArgs),
}
#[derive(Subcommand)]
pub enum LibCommands {
Check(CommandArgs),
Fix(CommandArgs),
Status(CommandArgs),
}
impl LibCommands {
pub fn get_args(&self) -> &CommandArgs {
match self {
Self::Check(args) => args,
Self::Fix(args) => args,
Self::Status(args) => args,
}
}
}
#[derive(Args)]
#[command(next_line_help = true)]
pub struct CommandArgs {
targets: Option<Vec<PathBuf>>,
#[command(flatten)]
verbosity: clap_verbosity_flag::Verbosity,
#[command(flatten)]
pub editorconfig_args: EditorConfigArgs,
#[command(flatten)]
pub ignore_args: IgnoreArgs,
}
#[derive(Args)]
#[command(next_help_heading = "Disable EditorConfig properties")]
pub struct EditorConfigArgs {
#[arg(short = 'c', long = "disable-charset", action = clap::ArgAction::SetFalse, help =
"Disables that the charset property of EditorConfig is considered for any file. \
Determining the actual charset of files can be faulty. Use this option for such cases.")]
pub charset: bool,
#[arg(short = 'e', long = "disable-end-of-line", action = clap::ArgAction::SetFalse, help =
"Disables that the end_of_line property of EditorConfig is considered for any file.")]
pub end_of_line: bool,
#[arg(short = 't', long = "disable-trim-trailing-whitespace", action = clap::ArgAction::SetFalse, help =
"Disables that the trim_trailing_whitespace property of EditorConfig is considered for any file.")]
pub trim_trailing_whitespace: bool,
#[arg(short = 'n', long = "disable-insert-final-newline", action = clap::ArgAction::SetFalse, help =
"Disables that the insert_final_newline property of EditorConfig is considered for any file.")]
pub insert_final_newline: bool,
#[arg(short = 'i', long = "disable-indentation-handling", action = clap::ArgAction::SetFalse, help =
"Disables that the indentation properties of EditorConfig \
(i.e., indent_style, indent_size, tab_width) are considered for any file.")]
pub indentation_handling: bool,
#[arg(short = 's', long = "disable-spelling-language", action = clap::ArgAction::SetFalse, help =
"Disables that the spelling_language property of EditorConfig is considered in any .editorconfig file.")]
pub spelling_language: bool,
}
#[derive(Args)]
#[command(next_help_heading = "Control which files are ignored")]
pub struct IgnoreArgs {
#[arg(short = 'd', long = "ignore-hidden", action = clap::ArgAction::SetFalse, help =
"Ignore hidden files and directories (starting with a dot). Default is not to ignore them, \
except for the .git directory with the meta data of a git repository.")]
pub hidden: bool,
#[arg(short = 'g', long = "no-git-settings", action = clap::ArgAction::SetFalse, help =
"Ignore all git settings about ignored files. Default is that in a git repository \
(i.e., root contains .git directory) the git settings are respected \
(i.e., .gitignore and .git/info/exclude files, the git config option core.excludesFile \
and ignoring the .git directory even if hidden directories are not ignored).")]
pub git_settings: bool,
#[arg(short = 'f', long)]
pub ignore_file: Option<PathBuf>,
}
impl CommandArgs {
pub fn new<P: AsRef<Path>>(
target: Option<P>,
editorconfig_args: EditorConfigArgs,
ignore_args: IgnoreArgs,
) -> CommandArgs {
let targets = target.as_ref().map(|p| slice::from_ref(p));
CommandArgs::new_internal(targets, editorconfig_args, ignore_args)
}
pub fn new_multi_target<P: AsRef<Path>>(
targets: &[P],
editorconfig_args: EditorConfigArgs,
ignore_args: IgnoreArgs,
) -> Option<CommandArgs> {
if targets.is_empty() {
None
} else {
Some(CommandArgs::new_internal(
Some(targets),
editorconfig_args,
ignore_args,
))
}
}
fn new_internal<P: AsRef<Path>>(
targets: Option<&[P]>,
editorconfig_args: EditorConfigArgs,
ignore_args: IgnoreArgs,
) -> CommandArgs {
CommandArgs {
targets: targets.map(|s| s.iter().map(|p| p.as_ref().to_path_buf()).collect_vec()),
verbosity: clap_verbosity_flag::Verbosity::new(0, 0),
editorconfig_args,
ignore_args,
}
}
pub(crate) fn targets(&self) -> anyhow::Result<Vec<PathBuf>> {
match self.targets.as_ref() {
Some(t) => Ok(t.iter().map(|p| p.to_path_buf()).collect_vec()),
None => env::current_dir()
.with_context(|| "Current working directory no accessible!")
.map(|p| vec![p]),
}
}
pub fn verbosity(&self) -> clap_verbosity_flag::Verbosity {
self.verbosity
}
}
#[derive(Args)]
#[command(next_line_help = true)]
pub struct LicenseArgs {
#[arg(short, long, default_value_t = MarkdownFormat::Auto, value_enum)]
pub format: MarkdownFormat,
}
#[derive(Clone, ValueEnum)]
pub enum MarkdownFormat {
Auto,
Paginate,
Pretty,
Plain,
}