use std::path::PathBuf;
use clap::{Args, Parser, Subcommand, command};
use dotenv_analyzer::LintKind;
use dotenv_schema::DotEnvSchema;
use crate::{CheckOptions, DiffOptions, FixOptions, Result};
const HELP_TEMPLATE: &str = "
{before-help}{name} {version}
{author-with-newline}{about-with-newline}
{usage-heading} {usage}
{all-args}{after-help}
";
#[derive(Parser)]
#[command(
version,
about,
author,
help_template = HELP_TEMPLATE,
styles = clap::builder::Styles::styled()
.header(clap::builder::styling::AnsiColor::Yellow.on_default())
.usage(clap::builder::styling::AnsiColor::Yellow.on_default())
.literal(clap::builder::styling::AnsiColor::Cyan.on_default())
.placeholder(clap::builder::styling::AnsiColor::Blue.on_default())
.context(clap::builder::styling::AnsiColor::Green.on_default())
)]
struct Cli {
#[command(subcommand)]
command: Command,
#[arg(long, global = true)]
plain: bool,
#[arg(short, long, global = true)]
quiet: bool,
}
#[derive(Subcommand)]
enum Command {
Check {
#[arg(
num_args(1..),
required = true,
)]
files: Vec<PathBuf>,
#[command(flatten)]
common: CommonArgs,
#[arg(short('s'), long, value_name = "PATH")]
schema: Option<PathBuf>,
#[cfg(feature = "update-informer")]
#[arg(long, env = "DOTENV_LINTER_SKIP_UPDATES")]
skip_updates: bool,
},
Fix {
#[arg(
num_args(1..),
required = true,
)]
files: Vec<PathBuf>,
#[command(flatten)]
common: CommonArgs,
#[arg(long)]
no_backup: bool,
#[arg(long)]
dry_run: bool,
},
Diff {
#[arg(
num_args(1..),
required = true,
)]
files: Vec<PathBuf>,
},
}
#[derive(Args)]
struct CommonArgs {
#[arg(short = 'e', long, value_name = "PATH")]
exclude: Vec<PathBuf>,
#[arg(
short,
long,
value_name = "CHECK_NAME",
value_delimiter = ',',
env = "DOTENV_LINTER_IGNORE_CHECKS"
)]
ignore_checks: Vec<LintKind>,
#[arg(short, long)]
recursive: bool,
}
pub fn run() -> Result<i32> {
#[cfg(windows)]
colored::control::set_virtual_terminal(true).ok();
let cli = Cli::parse();
let current_dir = std::env::current_dir()?;
if cli.plain {
colored::control::set_override(false);
}
match cli.command {
Command::Check {
files,
common,
schema,
#[cfg(feature = "update-informer")]
skip_updates: not_check_updates,
} => {
let mut dotenv_schema = None;
if let Some(path) = schema {
dotenv_schema = match DotEnvSchema::load(path) {
Ok(schema) => Some(schema),
Err(err) => {
println!("Error loading schema: {err}");
std::process::exit(1);
}
};
}
let total_warnings = crate::check(
&CheckOptions {
files: files.iter().collect(),
ignore_checks: common.ignore_checks,
exclude: common.exclude.iter().collect(),
recursive: common.recursive,
quiet: cli.quiet,
schema: dotenv_schema,
},
¤t_dir,
)?;
#[cfg(feature = "update-informer")]
if !not_check_updates && !cli.quiet {
crate::check_for_updates();
}
if total_warnings == 0 {
return Ok(0);
}
}
Command::Fix {
files,
common,
no_backup,
dry_run,
} => {
crate::fix(
&FixOptions {
files: files.iter().collect(),
ignore_checks: common.ignore_checks,
exclude: common.exclude.iter().collect(),
recursive: common.recursive,
quiet: cli.quiet,
no_backup,
dry_run,
},
¤t_dir,
)?;
return Ok(0);
}
Command::Diff { files } => {
let total_warnings = crate::diff(
&DiffOptions {
files: files.iter().collect(),
quiet: cli.quiet,
},
¤t_dir,
)?;
if total_warnings == 0 {
return Ok(0);
}
}
}
Ok(1)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn verify_cli() {
use clap::CommandFactory;
Cli::command().debug_assert();
}
}