mod calc;
mod date;
mod merge;
mod number;
mod percent;
mod range;
mod special;
pub use crate::error::{AfAlert, AfColor, AfEffects, AlertMessage, Error};
pub use calc::{
SimpleOp, af_make_number, af_simple, af_simple_calculate, af_simple_calculate_texts,
af_split_field_list,
};
pub use date::{
DATE_FORMATS, TIME_FORMATS, af_date_format, af_date_format_ex, af_date_keystroke,
af_date_keystroke_ex, af_parse_date_ex, af_time_format, af_time_format_ex, af_time_keystroke,
af_time_keystroke_ex,
};
pub use merge::{af_extract_nums, af_merge_change};
pub use number::{af_number_format, af_number_keystroke};
pub use percent::{af_percent_format, af_percent_keystroke};
pub use range::af_range_validate;
pub use special::{
af_special_format, af_special_keystroke, af_special_keystroke_ex, is_reserved_mask_char,
mask_satisfied,
};
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
#[error("{error}")]
pub struct Thrown {
pub error: Error,
pub effects: AfEffects,
}
impl Thrown {
#[must_use]
pub fn bare(error: Error) -> Self {
Self {
error,
effects: AfEffects::none(),
}
}
#[must_use]
pub fn alerting(error: Error, caller: &'static str, message: AlertMessage) -> Self {
Self {
error,
effects: AfEffects::alert(caller, message),
}
}
}
impl From<Error> for Thrown {
fn from(error: Error) -> Self {
Self::bare(error)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct AfValue {
pub text: String,
}
impl AfValue {
#[must_use]
pub fn new(text: impl Into<String>) -> Self {
Self { text: text.into() }
}
}
impl From<&str> for AfValue {
fn from(s: &str) -> Self {
Self {
text: s.to_string(),
}
}
}
impl From<String> for AfValue {
fn from(text: String) -> Self {
Self { text }
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AfOutcome {
Formatted(String),
Rejected,
Unchanged,
}
#[derive(Debug, Clone, PartialEq)]
pub struct AfFormat {
pub outcome: AfOutcome,
pub effects: AfEffects,
}
impl AfFormat {
#[must_use]
pub fn formatted(value: impl Into<String>) -> Self {
Self {
outcome: AfOutcome::Formatted(value.into()),
effects: AfEffects::none(),
}
}
#[must_use]
pub fn unchanged() -> Self {
Self {
outcome: AfOutcome::Unchanged,
effects: AfEffects::none(),
}
}
#[must_use]
pub fn rejected(caller: &'static str, message: AlertMessage) -> Self {
Self {
outcome: AfOutcome::Rejected,
effects: AfEffects::alert(caller, message),
}
}
#[must_use]
pub fn with_color(mut self, color: crate::error::AfColor) -> Self {
self.effects.text_color = Some(color);
self
}
#[must_use]
pub fn alert(&self) -> Option<&AfAlert> {
match self.effects.alerts.as_slice() {
[only] => Some(only),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Keystroke {
pub value: String,
pub change: String,
pub sel_start: i32,
pub sel_end: i32,
pub will_commit: bool,
pub field_full: bool,
}
impl Keystroke {
#[must_use]
pub fn commit(value: impl Into<String>) -> Self {
Self {
value: value.into(),
will_commit: true,
..Self::default()
}
}
#[must_use]
pub fn insert(value: impl Into<String>, change: impl Into<String>, caret: i32) -> Self {
Self {
value: value.into(),
change: change.into(),
sel_start: caret,
sel_end: caret,
will_commit: false,
field_full: false,
}
}
#[must_use]
pub fn replace(
value: impl Into<String>,
change: impl Into<String>,
sel_start: i32,
sel_end: i32,
) -> Self {
Self {
value: value.into(),
change: change.into(),
sel_start,
sel_end,
will_commit: false,
field_full: false,
}
}
#[must_use]
pub fn merged(&self) -> String {
if self.will_commit {
self.value.clone()
} else {
merge_change(&self.value, &self.change, self.sel_start, self.sel_end)
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum KeystrokeOutcome {
Accept {
value: Option<String>,
change: Option<String>,
},
Reject,
}
#[derive(Debug, Clone, PartialEq)]
pub struct KeystrokeResult {
pub outcome: KeystrokeOutcome,
pub effects: AfEffects,
}
impl KeystrokeResult {
#[must_use]
pub fn accept() -> Self {
Self {
outcome: KeystrokeOutcome::Accept {
value: None,
change: None,
},
effects: AfEffects::none(),
}
}
#[must_use]
pub fn accept_value(value: impl Into<String>) -> Self {
Self {
outcome: KeystrokeOutcome::Accept {
value: Some(value.into()),
change: None,
},
effects: AfEffects::none(),
}
}
#[must_use]
pub fn accept_change(change: impl Into<String>) -> Self {
Self {
outcome: KeystrokeOutcome::Accept {
value: None,
change: Some(change.into()),
},
effects: AfEffects::none(),
}
}
#[must_use]
pub fn reject() -> Self {
Self {
outcome: KeystrokeOutcome::Reject,
effects: AfEffects::none(),
}
}
#[must_use]
pub fn reject_with(caller: &'static str, message: AlertMessage) -> Self {
Self {
outcome: KeystrokeOutcome::Reject,
effects: AfEffects::alert(caller, message),
}
}
#[must_use]
pub fn is_reject(&self) -> bool {
matches!(self.outcome, KeystrokeOutcome::Reject)
}
#[must_use]
pub fn value(&self) -> Option<&str> {
match &self.outcome {
KeystrokeOutcome::Accept { value, .. } => value.as_deref(),
KeystrokeOutcome::Reject => None,
}
}
#[must_use]
pub fn change(&self) -> Option<&str> {
match &self.outcome {
KeystrokeOutcome::Accept { change, .. } => change.as_deref(),
KeystrokeOutcome::Reject => None,
}
}
#[must_use]
pub fn alert(&self) -> Option<&AfAlert> {
match self.effects.alerts.as_slice() {
[only] => Some(only),
_ => None,
}
}
}
#[must_use]
pub fn merge_change(value: &str, change: &str, sel_start: i32, sel_end: i32) -> String {
let chars: Vec<char> = value.chars().collect();
let prefix: String = match usize::try_from(sel_start) {
Ok(start) => chars.iter().take(start).collect(),
Err(_) => value.to_string(),
};
let postfix: String = match usize::try_from(sel_end) {
Ok(end) if end < chars.len() => chars.iter().skip(end).collect(),
_ => String::new(),
};
let mut out = prefix;
out.push_str(change);
out.push_str(&postfix);
out
}
#[must_use]
pub(crate) fn within_bounds_or_zero(value: i32, size: usize) -> usize {
if value >= 0
&& let Ok(v) = usize::try_from(value)
&& v < size
{
v
} else {
0
}
}
#[must_use]
pub(crate) fn valid_style_or_zero(style: i32) -> i32 {
let idx = within_bounds_or_zero(style, 4);
i32::try_from(idx).unwrap_or(0)
}
pub(crate) fn is_style_with_digit_separator(style: i32) -> bool {
style == 0 || style == 2
}
pub(crate) fn digit_separator_for_style(style: i32) -> char {
if style == 0 { ',' } else { '.' }
}
pub(crate) fn is_style_with_apostrophe_separator(style: i32) -> bool {
style >= 4
}
pub(crate) fn is_style_with_comma_decimal_mark(style: i32) -> bool {
style == 2 || style == 3
}
pub(crate) fn decimal_mark_for_style(style: i32) -> char {
if is_style_with_comma_decimal_mark(style) {
','
} else {
'.'
}
}