use std::fmt;
use crate::nanoargs::parser::ArgParser;
use crate::nanoargs::validators::Validator;
/// Definition of a boolean flag (e.g. `--verbose` / `-v`).
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FlagDef {
/// Long name used as `--name`.
pub long: String,
/// Optional single-character short form used as `-c`.
pub short: Option<char>,
/// Human-readable description shown in help text.
pub description: String,
/// When `true`, this flag is omitted from help text but still parsed.
pub hidden: bool,
}
/// Definition of a key-value option (e.g. `--output FILE` / `-o FILE`).
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct OptionDef {
/// Long name used as `--name`.
pub long: String,
/// Optional single-character short form used as `-c`.
pub short: Option<char>,
/// Placeholder shown in help text (e.g. `FILE`, `NUM`).
pub placeholder: String,
/// Human-readable description shown in help text.
pub description: String,
/// When `true`, parsing fails if this option is not provided.
pub required: bool,
/// Default value used when the option is absent from CLI and env.
pub default: Option<String>,
/// Environment variable name to check as a fallback.
pub env_var: Option<String>,
/// When `true`, repeated occurrences are collected into a `Vec`.
pub multi: bool,
/// When `true`, this option is omitted from help text but still parsed.
pub hidden: bool,
/// Optional value validator invoked during parsing.
pub validator: Option<Validator>,
}
/// Definition of a positional argument (e.g. `<input>` or `[extra]`).
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PositionalDef {
/// Name shown in usage line and help text.
pub name: String,
/// Human-readable description shown in help text.
pub description: String,
/// When `true`, parsing fails if this positional is not provided.
pub required: bool,
/// Default value used when the positional is absent from CLI args.
pub default: Option<String>,
/// When `true`, this positional collects all remaining arguments.
pub multi: bool,
/// Optional value validator invoked during parsing.
pub validator: Option<Validator>,
}
/// Definition of a subcommand: a name, description, and its own [`ArgParser`].
#[derive(Clone, Debug, PartialEq)]
pub struct SubcommandDef {
/// Subcommand name as typed on the command line.
pub name: String,
/// Human-readable description shown in help text.
pub description: String,
/// Independent parser that handles arguments after the subcommand name.
pub parser: ArgParser,
}
/// Definition of an argument group: at least one member must be provided.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GroupDef {
/// Human-readable name for the group (used in error messages and help text).
pub name: String,
/// Long names of the member arguments (flags or options).
pub members: Vec<String>,
}
/// Definition of a conflict set: at most one member may be provided.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ConflictDef {
/// Human-readable name for the conflict set (used in error messages and help text).
pub name: String,
/// Long names of the mutually exclusive arguments (flags or options).
pub members: Vec<String>,
}
/// Errors produced during argument parsing.
///
/// The `HelpRequested` and `VersionRequested` variants carry the formatted
/// text that should be printed to stdout. All other variants represent actual
/// errors and carry a descriptive message suitable for display.
///
/// Implements [`std::fmt::Display`] and [`std::error::Error`]. When the
/// `color` feature is enabled, the `Display` output includes ANSI styling.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ParseError {
/// A required argument was not provided. Contains the argument name.
MissingRequired(String),
/// An option was provided without a value. Contains the option name.
MissingValue(String),
/// An unrecognized argument was encountered. Contains the raw token.
UnknownArgument(String),
/// A structural error in the parser definition. Contains a description.
InvalidFormat(String),
/// The `-h` / `--help` flag was encountered. Contains the formatted help text.
HelpRequested(String),
/// The `-V` / `--version` flag was encountered. Contains the formatted version text.
VersionRequested(String),
/// A subcommand was expected but none was provided. Contains available names.
NoSubcommand(String),
/// An unrecognized subcommand was provided. Contains the unknown name.
UnknownSubcommand(String),
/// A non-multi option was provided more than once. Contains the option name.
DuplicateOption(String),
/// A command-line argument contained bytes that are not valid UTF-8.
/// Contains the lossy representation.
InvalidUtf8(String),
/// A value failed validation. Contains the argument name and error message.
ValidationFailed {
/// The argument name (e.g. option long name or positional name).
name: String,
/// The human-readable validation error message.
message: String,
},
/// An argument group constraint was violated: none of the members were provided.
GroupViolation {
/// The group name.
group: String,
/// Long names of the group's member arguments.
members: Vec<String>,
},
/// A conflict set constraint was violated: two or more mutually exclusive members were provided.
ConflictViolation {
/// The conflict set name.
conflict: String,
/// Long names of the conflicting arguments that were provided.
provided: Vec<String>,
},
}
// ── Leaf helpers for ParseError ────────────────────────────────────────────
/// Returns the "error: " prefix. Color styling is not supported in this
/// vendored build, so this returns an empty string (plain output).
fn error_prefix() -> String {
String::new()
}
/// Returns the argument string unchanged (no color styling).
fn yellow_arg(s: &str) -> String {
s.to_string()
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ParseError::HelpRequested(text) | ParseError::VersionRequested(text) => {
write!(f, "{text}")
}
ParseError::MissingRequired(name) => {
write!(
f,
"{}missing required argument: {}",
error_prefix(),
yellow_arg(name)
)
}
ParseError::MissingValue(name) => {
write!(
f,
"{}missing value for option: {}",
error_prefix(),
yellow_arg(&format!("--{name}"))
)
}
ParseError::UnknownArgument(token) => {
write!(
f,
"{}unknown argument: {}",
error_prefix(),
yellow_arg(token)
)
}
ParseError::InvalidFormat(msg) => {
write!(f, "{}invalid format: {msg}", error_prefix())
}
ParseError::NoSubcommand(names) => {
write!(
f,
"{}no subcommand provided. Available: {names}",
error_prefix()
)
}
ParseError::UnknownSubcommand(name) => {
write!(
f,
"{}unknown subcommand: {}",
error_prefix(),
yellow_arg(name)
)
}
ParseError::DuplicateOption(name) => {
write!(
f,
"{}option {} was provided more than once (use .multi() to allow repeats)",
error_prefix(),
yellow_arg(&format!("--{name}"))
)
}
ParseError::InvalidUtf8(lossy) => {
write!(
f,
"{}argument is not valid UTF-8: {}",
error_prefix(),
yellow_arg(lossy)
)
}
ParseError::ValidationFailed { name, message } => {
write!(
f,
"{}validation failed for {}: {message}",
error_prefix(),
yellow_arg(name)
)
}
ParseError::GroupViolation { group, members } => {
let member_list: Vec<String> = members
.iter()
.map(|m| yellow_arg(&format!("--{m}")))
.collect();
write!(
f,
"{}at least one of the following is required (group '{group}'): {}",
error_prefix(),
member_list.join(", ")
)
}
ParseError::ConflictViolation { conflict, provided } => {
let arg_list: Vec<String> = provided
.iter()
.map(|m| yellow_arg(&format!("--{m}")))
.collect();
let joined = if arg_list.len() == 2 {
format!("{} and {}", arg_list[0], arg_list[1])
} else {
arg_list.join(", ")
};
write!(
f,
"{}conflicting arguments ('{conflict}'): {joined} cannot be used together",
error_prefix(),
)
}
}
}
}
impl std::error::Error for ParseError {}