use crate::ClarResult;
use crate::errors::{ClarDiagnostic, ClarDiagnosticResult, err_exceeded_max_option_occurrences};
use crate::evaluator::{
Evaluator, Value, ev_argument, ev_command_and_then, ev_consumed, ev_long_option, ev_one, ev_option_terminator, ev_sequence, ev_short_option, ev_stop_on_one,
ev_zero_or_more_options,
};
use crate::lexer::{Lexer, Token};
use crate::matches::ClarMatches;
use crate::messages::{get_cfg_error_text, get_error_text};
use crate::model::{ClarArgument, ClarCommand, ClarDefinition, ClarItem, ClarOption, ClarTerminator};
use crate::path::ClarPath;
use antex::ColorMode;
use std::collections::{HashMap, VecDeque};
#[derive(Debug, Clone)]
pub struct Clar {
app: String,
description: Option<String>,
definition: ClarDefinition,
cm: ColorMode,
}
impl Clar {
pub fn new(app: impl AsRef<str>) -> Self {
Self {
app: app.as_ref().to_string(),
description: None,
definition: ClarDefinition::default(),
cm: ColorMode::default(),
}
}
pub fn description(mut self, description: impl AsRef<str>) -> Self {
self.description = Some(description.as_ref().to_string());
self
}
pub fn terminator(mut self, option_terminator: ClarTerminator) -> Self {
self.definition.terminator(option_terminator);
self
}
pub fn options<O>(mut self, options: O) -> Self
where
O: IntoIterator<Item = ClarOption>,
{
self.definition.options(options);
self
}
pub fn options_terminator<O>(mut self, options: O, option_terminator: ClarTerminator) -> Self
where
O: IntoIterator<Item = ClarOption>,
{
self.definition.options_terminator(options, option_terminator);
self
}
pub fn arguments<A>(mut self, arguments: A) -> Self
where
A: IntoIterator<Item = ClarArgument>,
{
self.definition.arguments(arguments);
self
}
pub fn arguments_terminator<A>(mut self, arguments: A, option_terminator: ClarTerminator) -> Self
where
A: IntoIterator<Item = ClarArgument>,
{
self.definition.arguments_terminator(arguments, option_terminator);
self
}
pub fn options_arguments<O, A>(mut self, options: O, arguments: A) -> Self
where
O: IntoIterator<Item = ClarOption>,
A: IntoIterator<Item = ClarArgument>,
{
self.definition.options_arguments(options, arguments);
self
}
pub fn options_arguments_terminator<O, A>(mut self, options: O, arguments: A, option_terminator: ClarTerminator) -> Self
where
O: IntoIterator<Item = ClarOption>,
A: IntoIterator<Item = ClarArgument>,
{
self.definition.options_arguments_terminator(options, arguments, option_terminator);
self
}
pub fn commands<C>(mut self, commands: C) -> Self
where
C: IntoIterator<Item = ClarCommand>,
{
self.definition.commands(commands);
self
}
pub fn options_commands<O, C>(mut self, options: O, commands: C) -> Self
where
O: IntoIterator<Item = ClarOption>,
C: IntoIterator<Item = ClarCommand>,
{
self.definition.options_commands(options, commands);
self
}
pub fn resolve<I, S>(mut self, input: I) -> ClarDiagnosticResult<ClarMatches>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
self.definition.validate().map_err(|reason| {
let text = get_cfg_error_text(&reason, self.cm);
ClarDiagnostic::new(reason, text)
})?;
let mut tokens: VecDeque<Token> = Lexer::default().parse(input).map_err(|reason| {
let text = get_error_text(&reason, &self.app, &self.definition, &[], self.cm);
ClarDiagnostic::new(reason, text)
})?;
let evaluator = ev_consumed(self.build_evaluator(&self.definition));
let mut values = vec![];
let result = evaluator(&mut tokens, &mut values);
let names = if let Some(Value::Command(path)) = values.iter().find(|value| matches!(value, Value::Command(_))) {
path.parent_names()
} else {
&[]
};
match result {
Ok(_) => {
self.validate_values(&values).map_err(|reason| {
let text = get_error_text(&reason, &self.app, &self.definition, names, self.cm);
ClarDiagnostic::new(reason, text)
})?;
Ok(ClarMatches::new(self.app, self.description, values, self.definition, self.cm))
}
Err(reason) => {
let text = get_error_text(&reason, &self.app, &self.definition, names, self.cm);
Err(ClarDiagnostic::new(reason, text))
}
}
}
fn build_evaluator(&self, definition: &ClarDefinition) -> Evaluator {
let mut evaluators = vec![];
for item in definition.items() {
match item {
ClarItem::Options(options) => {
let mut options_evaluators = vec![];
let mut standalone_options_evaluators = vec![];
for option in options {
if option.is_standalone() {
if let Some(short_label) = option.get_short_label() {
standalone_options_evaluators.push(ev_short_option(*short_label, option.into()))
}
if let Some(long_label) = option.get_long_label() {
standalone_options_evaluators.push(ev_long_option(long_label, option.into()))
}
} else {
if let Some(short_label) = option.get_short_label() {
options_evaluators.push(ev_short_option(*short_label, option.into()))
}
if let Some(long_label) = option.get_long_label() {
options_evaluators.push(ev_long_option(long_label, option.into()))
}
}
}
evaluators.push(ev_stop_on_one(standalone_options_evaluators));
evaluators.push(ev_zero_or_more_options(options_evaluators));
}
ClarItem::Commands(commands) => {
let mut command_evaluators = vec![];
for command in commands {
command_evaluators.push(ev_command_and_then(command.into(), self.build_evaluator(command.get_definition())));
}
evaluators.push(ev_one(command_evaluators));
}
ClarItem::Arguments(arguments) => {
for argument in arguments {
evaluators.push(ev_argument(argument.into()));
}
}
ClarItem::Terminator(option_terminator) => evaluators.push(ev_option_terminator(option_terminator.into())),
}
}
ev_sequence(evaluators)
}
fn validate_values(&self, values: &[Value]) -> ClarResult<()> {
self.expect_max_option_occurrences(values)?;
Ok(())
}
fn expect_max_option_occurrences(&self, values: &[Value]) -> ClarResult<()> {
let mut totals: HashMap<ClarPath, usize> = HashMap::new();
let mut paths = vec![];
for value in values {
match value {
Value::ShortOption(path, _) | Value::LongOption(path, _) => {
totals.entry(path.clone()).and_modify(|count| *count += 1).or_insert(1);
paths.push(path.clone());
}
_ => {}
}
}
let options = self.definition.get_options();
for path in &paths {
let occurrences = *totals.get(path).unwrap(); let option = options.get(path).unwrap(); let max = option.get_max_occurrences();
if occurrences > max {
return Err(err_exceeded_max_option_occurrences(option.get_synopsis(), max, occurrences));
}
}
Ok(())
}
}