use std::borrow::Cow;
use std::fmt::{Debug, Display, Error, Formatter};
use std::ops::{Bound, RangeBounds};
use std::str::FromStr;
use crate::context::ScopeContext;
use crate::date::Date;
use crate::everything::Everything;
#[cfg(feature = "hoi4")]
use crate::hoi4::variables::validate_variable;
use crate::item::Item;
use crate::report::{ErrorKey, Severity, report};
use crate::scopes::Scopes;
use crate::token::Token;
use crate::trigger::validate_target;
#[cfg(feature = "imperator")]
use crate::trigger::validate_target_ok_this;
use crate::validate::validate_identifier;
pub struct ValueValidator<'a> {
value: Cow<'a, Token>,
data: &'a Everything,
validated: bool,
max_severity: Severity,
}
impl Debug for ValueValidator<'_> {
fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> {
f.debug_struct("ValueValidator")
.field("value", &self.value)
.field("validated", &self.validated)
.field("max_severity", &self.max_severity)
.finish()
}
}
impl<'a> ValueValidator<'a> {
pub fn new(value: &'a Token, data: &'a Everything) -> Self {
Self { value: Cow::Borrowed(value), data, validated: false, max_severity: Severity::Fatal }
}
#[allow(dead_code)]
pub fn new_owned(value: Token, data: &'a Everything) -> Self {
Self { value: Cow::Owned(value), data, validated: false, max_severity: Severity::Fatal }
}
#[allow(dead_code)]
pub fn data(&self) -> &Everything {
self.data
}
pub fn set_max_severity(&mut self, max_severity: Severity) {
self.max_severity = max_severity;
}
#[allow(dead_code)]
fn value_validator(&self, token: Token) -> Self {
let mut vd = ValueValidator::new_owned(token, self.data);
vd.set_max_severity(self.max_severity);
vd
}
pub fn value(&self) -> &Token {
&self.value
}
pub fn accept(&mut self) {
self.validated = true;
}
#[allow(dead_code)]
pub fn identifier(&mut self, kind: &'static str) {
if self.validated {
return;
}
self.validated = true;
validate_identifier(&self.value, kind, Severity::Error.at_most(self.max_severity));
}
pub fn item(&mut self, itype: Item) {
if self.validated {
return;
}
self.validated = true;
self.data.verify_exists_max_sev(itype, &self.value, self.max_severity);
}
#[cfg(feature = "ck3")]
#[allow(dead_code)]
pub fn icon(&mut self, define: &str, suffix: &str) {
if self.validated {
return;
}
self.validated = true;
self.data.verify_icon(define, &self.value, suffix);
}
#[cfg(feature = "ck3")] pub fn item_used_with_suffix(&mut self, itype: Item, sfx: &str) {
let implied = format!("{}{sfx}", self.value);
self.data.mark_used(itype, &implied);
}
#[allow(dead_code)]
pub fn implied_localization_sc(&mut self, pfx: &str, sfx: &str, sc: &mut ScopeContext) {
let implied = format!("{pfx}{}{sfx}", self.value);
self.data.validate_localization_sc(&implied, sc);
}
#[allow(dead_code)]
pub fn maybe_item(&mut self, itype: Item) -> bool {
if self.data.item_exists(itype, self.value.as_str()) {
self.validated = true;
true
} else {
false
}
}
#[cfg(feature = "vic3")] pub fn maybe_prefix_item(&mut self, pfx: &str, itype: Item) -> bool {
if let Some(value) = self.value.as_str().strip_prefix(pfx) {
if self.data.item_exists(itype, value) {
self.validated = true;
return true;
}
}
false
}
#[cfg(feature = "ck3")] pub fn dir_file(&mut self, path: &str) {
if self.validated {
return;
}
self.validated = true;
let pathname = format!("{path}/{}", self.value);
self.data.verify_exists_implied(Item::File, &pathname, &self.value);
}
#[must_use]
#[allow(dead_code)]
pub fn split(&mut self, c: char) -> Vec<ValueValidator<'_>> {
self.validated = true;
self.value.split(c).into_iter().map(|value| self.value_validator(value)).collect()
}
#[allow(dead_code)] pub fn target(&mut self, sc: &mut ScopeContext, outscopes: Scopes) {
if self.validated {
return;
}
self.validated = true;
validate_target(&self.value, self.data, sc, outscopes);
}
#[cfg(feature = "imperator")]
pub fn target_ok_this(&mut self, sc: &mut ScopeContext, outscopes: Scopes) {
if self.validated {
return;
}
self.validated = true;
validate_target_ok_this(&self.value, self.data, sc, outscopes);
}
#[allow(dead_code)] pub fn item_or_target(&mut self, sc: &mut ScopeContext, itype: Item, outscopes: Scopes) {
if self.validated {
return;
}
self.validated = true;
if !self.data.item_exists(itype, self.value.as_str()) {
validate_target(&self.value, self.data, sc, outscopes);
}
}
#[allow(dead_code)]
pub fn bool(&mut self) {
if self.validated {
return;
}
let sev = Severity::Error.at_most(self.max_severity);
self.validated = true;
if !self.value.lowercase_is("yes") && !self.value.lowercase_is("no") {
report(ErrorKey::Validation, sev).msg("expected yes or no").loc(self).push();
}
}
#[allow(dead_code)]
pub fn maybe_bool(&mut self) {
if self.validated {
return;
}
if self.value.lowercase_is("yes") || self.value.lowercase_is("no") {
self.validated = true;
}
}
pub fn integer(&mut self) {
if self.validated {
return;
}
self.validated = true;
self.value.expect_integer();
}
#[allow(dead_code)]
pub fn maybe_integer(&mut self) {
if self.validated {
return;
}
if self.value.is_integer() {
self.validated = true;
}
}
#[allow(dead_code)]
pub fn integer_range<R: RangeBounds<i64>>(&mut self, range: R) {
if self.validated {
return;
}
let sev = Severity::Error.at_most(self.max_severity);
self.validated = true;
if let Some(i) = self.value.expect_integer() {
if !range.contains(&i) {
let low = match range.start_bound() {
Bound::Unbounded => None,
Bound::Included(&n) => Some(n),
Bound::Excluded(&n) => Some(n + 1),
};
let high = match range.end_bound() {
Bound::Unbounded => None,
Bound::Included(&n) => Some(n),
Bound::Excluded(&n) => Some(n - 1),
};
let msg;
if let (Some(low), Some(high)) = (low, high) {
msg = format!("should be between {low} and {high} (inclusive)");
} else if let Some(low) = low {
msg = format!("should be at least {low}");
} else if let Some(high) = high {
msg = format!("should be at most {high}");
} else {
unreachable!(); }
report(ErrorKey::Range, sev).msg(msg).loc(self).push();
}
}
}
#[allow(dead_code)] pub fn numeric(&mut self) {
if self.validated {
return;
}
self.validated = true;
self.value.expect_number();
}
#[cfg(feature = "vic3")]
pub fn numeric_range<R: RangeBounds<f64>>(&mut self, range: R) {
if self.validated {
return;
}
let sev = Severity::Error.at_most(self.max_severity);
self.validated = true;
if let Some(f) = self.value.expect_number() {
if !range.contains(&f) {
let low = match range.start_bound() {
Bound::Unbounded => None,
Bound::Included(&f) => Some(format!("{f} (inclusive)")),
Bound::Excluded(&f) => Some(format!("{f}")),
};
let high = match range.end_bound() {
Bound::Unbounded => None,
Bound::Included(&f) => Some(format!("{f} (inclusive)")),
Bound::Excluded(&f) => Some(format!("{f}")),
};
let msg;
if let (Some(low), Some(high)) = (low.as_ref(), high.as_ref()) {
msg = format!("should be between {low} and {high}");
} else if let Some(low) = low {
msg = format!("should be at least {low}");
} else if let Some(high) = high {
msg = format!("should be at most {high}");
} else {
unreachable!(); }
report(ErrorKey::Range, sev).msg(msg).loc(self).push();
}
}
}
#[allow(dead_code)] pub fn precise_numeric(&mut self) {
if self.validated {
return;
}
self.validated = true;
self.value.expect_precise_number();
}
#[allow(dead_code)] pub fn date(&mut self) {
if self.validated {
return;
}
let sev = Severity::Error.at_most(self.max_severity);
self.validated = true;
if Date::from_str(self.value.as_str()).is_err() {
let msg = "expected date value";
report(ErrorKey::Validation, sev).msg(msg).loc(self).push();
}
}
#[allow(dead_code)]
pub fn choice(&mut self, choices: &[&str]) {
if self.validated {
return;
}
self.validated = true;
let sev = Severity::Error.at_most(self.max_severity);
if !choices.contains(&self.value.as_str()) {
let msg = format!("expected one of {}", choices.join(", "));
report(ErrorKey::Choice, sev).msg(msg).loc(self).push();
}
}
#[cfg(feature = "hoi4")]
pub fn variable(&mut self, sc: &mut ScopeContext) {
if self.validated {
return;
}
self.validated = true;
let sev = Severity::Error.at_most(self.max_severity);
validate_variable(&self.value, self.data, sc, sev);
}
pub fn maybe_is(&mut self, s: &str) -> bool {
if self.value.is(s) {
self.validated = true;
true
} else {
false
}
}
pub fn warn_unvalidated(&mut self) {
if !self.validated {
let sev = Severity::Error.at_most(self.max_severity);
let msg = format!("unknown value `{}`", self.value);
report(ErrorKey::Validation, sev).msg(msg).loc(self).push();
}
}
}
impl Drop for ValueValidator<'_> {
fn drop(&mut self) {
self.warn_unvalidated();
}
}
impl Display for ValueValidator<'_> {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
Display::fmt(&self.value, f)
}
}