1use ferroui_base::media::text_formatting::unicode::{Codepoint, GeneralCategory};
40use ferroui_base::utilities::CultureInfo;
41use std::fmt;
42
43const SPACE_CHAR: char = ' ';
44const DEFAULT_PROMPT_CHAR: char = '_';
45const NULL_PASSWORD_CHAR: char = '\0';
46const DEFAULT_ALLOW_PROMPT: bool = true;
47const INVALID_INDEX: i32 = -1;
48const EDIT_ANY: u8 = 0;
49const EDIT_UNASSIGNED: u8 = 1;
50const EDIT_ASSIGNED: u8 = 2;
51const FORWARD: bool = true;
52const BACKWARD: bool = false;
53
54#[repr(i32)]
58#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
59pub enum MaskedTextResultHint {
60 PositionOutOfRange = -55,
62 NonEditPosition = -54,
64 UnavailableEditPosition = -53,
66 PromptCharNotAllowed = -52,
68 InvalidInput = -51,
70 SignedDigitExpected = -5,
72 LetterExpected = -4,
74 DigitExpected = -3,
76 AlphanumericCharacterExpected = -2,
78 AsciiCharacterExpected = -1,
80 Unknown = 0,
82 CharacterEscaped = 1,
85 NoEffect = 2,
87 SideEffect = 3,
90 Success = 4,
92}
93
94#[derive(Clone, Debug)]
97struct MaskSeparators {
98 decimal_separator: String,
100 group_separator: String,
102 date_separator: String,
104 time_separator: String,
106 currency_symbol: String,
108}
109
110impl MaskSeparators {
111 fn of(culture: &CultureInfo) -> Self {
112 let number_format = culture.number_format();
113 let date_time_format = culture.date_time_format();
114 Self {
115 decimal_separator: number_format.number_decimal_separator().to_owned(),
116 group_separator: number_format.number_group_separator().to_owned(),
117 date_separator: date_time_format.date_separator().to_owned(),
118 time_separator: date_time_format.time_separator().to_owned(),
119 currency_symbol: number_format.currency_symbol().to_owned(),
120 }
121 }
122}
123
124#[cfg(test)]
126pub(crate) fn culture_with_separators(
127 name: &str,
128 decimal_separator: &str,
129 group_separator: &str,
130 date_separator: &str,
131 time_separator: &str,
132 currency_symbol: &str,
133) -> CultureInfo {
134 use ferroui_base::utilities::{DateTimeFormatInfo, NumberFormatInfo};
135
136 let mut number_format = NumberFormatInfo::new();
137 number_format.set_number_decimal_separator(decimal_separator);
138 number_format.set_number_group_separator(group_separator);
139 number_format.set_currency_symbol(currency_symbol);
140 let mut date_time_format = DateTimeFormatInfo::new();
141 date_time_format.set_date_separator(date_separator);
142 date_time_format.set_time_separator(time_separator);
143 CultureInfo::get_culture_info(name).with_number_format(number_format).with_date_time_format(date_time_format)
144}
145
146#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
148pub enum MaskedTextProviderError {
149 MaskNullOrEmpty,
151 MaskInvalidChar,
153 InvalidChar,
155 PasswordAndPromptCharEqual,
157}
158
159impl fmt::Display for MaskedTextProviderError {
160 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
161 f.write_str(match self {
162 Self::MaskNullOrEmpty => "The Mask value cannot be null or empty.",
163 Self::MaskInvalidChar => "The specified mask contains invalid characters.",
164 Self::InvalidChar => "The specified character value is not allowed for this property.",
165 Self::PasswordAndPromptCharEqual => "The PasswordChar and PromptChar values cannot be the same.",
166 })
167 }
168}
169
170impl std::error::Error for MaskedTextProviderError {}
171
172#[derive(Clone, Copy, Debug, PartialEq, Eq)]
174enum CaseConversion {
175 None,
176 ToLower,
177 ToUpper,
178}
179
180#[derive(Clone, Copy, Debug, PartialEq, Eq)]
182enum CharType {
183 EditOptional,
184 EditRequired,
185 Separator,
186 Literal,
187}
188
189#[derive(Clone, Copy, Debug)]
191struct CharDescriptor {
192 mask_position: usize,
194 case_conversion: CaseConversion,
195 char_type: CharType,
196 is_assigned: bool,
197}
198
199impl CharDescriptor {
200 #[inline]
201 fn is_edit_position(&self) -> bool {
202 matches!(self.char_type, CharType::EditRequired | CharType::EditOptional)
203 }
204
205 #[inline]
206 fn is_literal_position(&self) -> bool {
207 matches!(self.char_type, CharType::Literal | CharType::Separator)
208 }
209}
210
211#[derive(Clone, Debug)]
216pub struct MaskedTextProvider {
217 ascii_only: bool,
218 allow_prompt_as_input: bool,
219 include_prompt: bool,
220 include_literals: bool,
221 reset_on_prompt: bool,
222 reset_on_space: bool,
223 skip_literals: bool,
224 culture: CultureInfo,
225 separators: MaskSeparators,
226 test_string: Vec<char>,
228 assigned_char_count: i32,
229 required_char_count: i32,
230 required_edit_chars: i32,
231 optional_edit_chars: i32,
232 mask: String,
233 mask_chars: Vec<char>,
234 password_char: char,
235 prompt_char: char,
236 string_descriptor: Vec<CharDescriptor>,
238}
239
240impl MaskedTextProvider {
241 pub const INVALID_INDEX: i32 = INVALID_INDEX;
243
244 pub const DEFAULT_PASSWORD_CHAR: char = '*';
246
247 pub fn new(mask: &str) -> Result<Self, MaskedTextProviderError> {
251 Self::new_full(mask, None, DEFAULT_ALLOW_PROMPT, DEFAULT_PROMPT_CHAR, NULL_PASSWORD_CHAR, false)
252 }
253
254 pub fn new_with_ascii(mask: &str, restrict_to_ascii: bool) -> Result<Self, MaskedTextProviderError> {
256 Self::new_full(mask, None, DEFAULT_ALLOW_PROMPT, DEFAULT_PROMPT_CHAR, NULL_PASSWORD_CHAR, restrict_to_ascii)
257 }
258
259 pub fn new_with_culture(mask: &str, culture: Option<CultureInfo>) -> Result<Self, MaskedTextProviderError> {
261 Self::new_full(mask, culture, DEFAULT_ALLOW_PROMPT, DEFAULT_PROMPT_CHAR, NULL_PASSWORD_CHAR, false)
262 }
263
264 pub fn new_with_culture_ascii(
267 mask: &str,
268 culture: Option<CultureInfo>,
269 restrict_to_ascii: bool,
270 ) -> Result<Self, MaskedTextProviderError> {
271 Self::new_full(mask, culture, DEFAULT_ALLOW_PROMPT, DEFAULT_PROMPT_CHAR, NULL_PASSWORD_CHAR, restrict_to_ascii)
272 }
273
274 pub fn new_with_password(mask: &str, password_char: char, allow_prompt_as_input: bool) -> Result<Self, MaskedTextProviderError> {
277 Self::new_full(mask, None, allow_prompt_as_input, DEFAULT_PROMPT_CHAR, password_char, false)
278 }
279
280 pub fn new_with_culture_password(
283 mask: &str,
284 culture: Option<CultureInfo>,
285 password_char: char,
286 allow_prompt_as_input: bool,
287 ) -> Result<Self, MaskedTextProviderError> {
288 Self::new_full(mask, culture, allow_prompt_as_input, DEFAULT_PROMPT_CHAR, password_char, false)
289 }
290
291 pub fn new_full(
308 mask: &str,
309 culture: Option<CultureInfo>,
310 allow_prompt_as_input: bool,
311 prompt_char: char,
312 password_char: char,
313 restrict_to_ascii: bool,
314 ) -> Result<Self, MaskedTextProviderError> {
315 if mask.is_empty() {
316 return Err(MaskedTextProviderError::MaskNullOrEmpty);
317 }
318
319 if !mask.chars().all(is_printable_char) {
320 return Err(MaskedTextProviderError::MaskInvalidChar);
321 }
322
323 let culture = culture.unwrap_or_else(CultureInfo::current_culture);
324 let mut provider = Self {
325 ascii_only: restrict_to_ascii,
326 allow_prompt_as_input,
327 include_prompt: false,
329 include_literals: true,
330 reset_on_prompt: true,
331 reset_on_space: true,
332 skip_literals: true,
333 separators: MaskSeparators::of(&culture),
334 culture,
335 test_string: Vec::new(),
336 assigned_char_count: 0,
337 required_char_count: 0,
338 required_edit_chars: 0,
339 optional_edit_chars: 0,
340 mask: mask.to_owned(),
341 mask_chars: mask.chars().collect(),
342 password_char,
343 prompt_char,
344 string_descriptor: Vec::new(),
345 };
346
347 provider.initialize();
348 Ok(provider)
349 }
350
351 fn initialize(&mut self) {
354 let mut case_conversion = CaseConversion::None;
355 let mut escaped_char = false;
356 let mut char_type = CharType::Literal;
357 let mut buffer = [0u8; 4];
358
359 for mask_pos in 0..self.mask_chars.len() {
360 let mut ch = self.mask_chars[mask_pos];
361 let mut loc_symbol: Option<&str> = None;
363
364 if !escaped_char {
365 match ch {
366 '.' => {
368 loc_symbol = Some(&self.separators.decimal_separator);
369 char_type = CharType::Separator;
370 }
371 ',' => {
372 loc_symbol = Some(&self.separators.group_separator);
373 char_type = CharType::Separator;
374 }
375 ':' => {
376 loc_symbol = Some(&self.separators.time_separator);
377 char_type = CharType::Separator;
378 }
379 '/' => {
380 loc_symbol = Some(&self.separators.date_separator);
381 char_type = CharType::Separator;
382 }
383 '$' => {
384 loc_symbol = Some(&self.separators.currency_symbol);
385 char_type = CharType::Separator;
386 }
387
388 '<' => {
391 case_conversion = CaseConversion::ToLower;
392 continue;
393 }
394 '>' => {
395 case_conversion = CaseConversion::ToUpper;
396 continue;
397 }
398 '|' => {
399 case_conversion = CaseConversion::None;
400 continue;
401 }
402 '\\' => {
403 escaped_char = true;
405 char_type = CharType::Literal;
406 continue;
407 }
408
409 '0' | 'L' | '&' | 'A' => {
411 self.required_edit_chars += 1;
412 ch = self.prompt_char;
413 char_type = CharType::EditRequired;
414 }
415 '?' | '9' | '#' | 'C' | 'a' => {
416 self.optional_edit_chars += 1;
417 ch = self.prompt_char;
418 char_type = CharType::EditOptional;
419 }
420
421 _ => {
423 char_type = CharType::Literal;
424 }
425 }
426 } else {
427 escaped_char = false;
429 }
430
431 let mut descriptor = CharDescriptor {
432 mask_position: mask_pos,
433 case_conversion: CaseConversion::None,
434 char_type,
435 is_assigned: false,
436 };
437
438 if descriptor.is_edit_position() {
439 descriptor.case_conversion = case_conversion;
440 }
441
442 let symbol: &str = match loc_symbol {
443 Some(symbol) if char_type == CharType::Separator => symbol,
444 _ => ch.encode_utf8(&mut buffer),
445 };
446
447 for ch_val in symbol.chars() {
448 self.test_string.push(ch_val);
449 self.string_descriptor.push(descriptor);
450 }
451 }
452
453 self.test_string.shrink_to_fit();
454 self.string_descriptor.shrink_to_fit();
455 }
456
457 pub fn allow_prompt_as_input(&self) -> bool {
461 self.allow_prompt_as_input
462 }
463
464 pub fn assigned_edit_position_count(&self) -> i32 {
466 self.assigned_char_count
467 }
468
469 pub fn available_edit_position_count(&self) -> i32 {
471 self.edit_position_count() - self.assigned_char_count
472 }
473
474 pub fn culture(&self) -> &CultureInfo {
476 &self.culture
477 }
478
479 pub fn default_password_char() -> char {
481 Self::DEFAULT_PASSWORD_CHAR
482 }
483
484 pub fn edit_position_count(&self) -> i32 {
486 self.optional_edit_chars + self.required_edit_chars
487 }
488
489 pub fn edit_positions(&self) -> impl Iterator<Item = i32> + '_ {
491 self.string_descriptor
492 .iter()
493 .enumerate()
494 .filter(|(_, descriptor)| descriptor.is_edit_position())
495 .map(|(position, _)| position as i32)
496 }
497
498 pub fn include_literals(&self) -> bool {
501 self.include_literals
502 }
503
504 pub fn set_include_literals(&mut self, value: bool) {
506 self.include_literals = value;
507 }
508
509 pub fn include_prompt(&self) -> bool {
512 self.include_prompt
513 }
514
515 pub fn set_include_prompt(&mut self, value: bool) {
517 self.include_prompt = value;
518 }
519
520 pub fn ascii_only(&self) -> bool {
523 self.ascii_only
524 }
525
526 pub fn is_password(&self) -> bool {
528 self.password_char != NULL_PASSWORD_CHAR
529 }
530
531 pub fn set_is_password(&mut self, value: bool) {
534 if self.is_password() != value {
535 self.password_char = if value { Self::DEFAULT_PASSWORD_CHAR } else { NULL_PASSWORD_CHAR };
536 }
537 }
538
539 pub fn invalid_index() -> i32 {
541 INVALID_INDEX
542 }
543
544 pub fn last_assigned_position(&self) -> i32 {
547 self.find_assigned_edit_position_from(self.length() - 1, BACKWARD)
548 }
549
550 pub fn length(&self) -> i32 {
553 self.test_string.len() as i32
554 }
555
556 pub fn mask(&self) -> &str {
558 &self.mask
559 }
560
561 pub fn mask_completed(&self) -> bool {
563 self.required_char_count == self.required_edit_chars
564 }
565
566 pub fn mask_full(&self) -> bool {
568 self.assigned_char_count == self.edit_position_count()
569 }
570
571 pub fn password_char(&self) -> char {
573 self.password_char
574 }
575
576 pub fn set_password_char(&mut self, value: char) -> Result<(), MaskedTextProviderError> {
579 if value == self.prompt_char {
580 return Err(MaskedTextProviderError::PasswordAndPromptCharEqual);
582 }
583
584 if !Self::is_valid_password_char(value) {
585 return Err(MaskedTextProviderError::InvalidChar);
587 }
588
589 self.password_char = value;
590 Ok(())
591 }
592
593 pub fn prompt_char(&self) -> char {
595 self.prompt_char
596 }
597
598 pub fn set_prompt_char(&mut self, value: char) -> Result<(), MaskedTextProviderError> {
602 if value == self.password_char {
603 return Err(MaskedTextProviderError::PasswordAndPromptCharEqual);
605 }
606
607 if !is_printable_char(value) {
608 return Err(MaskedTextProviderError::InvalidChar);
609 }
610
611 if value != self.prompt_char {
612 self.prompt_char = value;
613
614 for position in 0..self.test_string.len() {
615 let descriptor = &self.string_descriptor[position];
616 if descriptor.is_edit_position() && !descriptor.is_assigned {
617 self.test_string[position] = value;
618 }
619 }
620 }
621
622 Ok(())
623 }
624
625 pub fn reset_on_prompt(&self) -> bool {
628 self.reset_on_prompt
629 }
630
631 pub fn set_reset_on_prompt(&mut self, value: bool) {
633 self.reset_on_prompt = value;
634 }
635
636 pub fn reset_on_space(&self) -> bool {
639 self.reset_on_space
640 }
641
642 pub fn set_reset_on_space(&mut self, value: bool) {
644 self.reset_on_space = value;
645 }
646
647 pub fn skip_literals(&self) -> bool {
650 self.skip_literals
651 }
652
653 pub fn set_skip_literals(&mut self, value: bool) {
655 self.skip_literals = value;
656 }
657
658 pub fn char_at(&self, index: i32) -> char {
664 if index < 0 || index >= self.length() {
665 panic!("index {index} is out of range of the test string (length {})", self.length());
666 }
667
668 self.test_string[index as usize]
669 }
670
671 pub fn add_char(&mut self, input: char) -> bool {
676 self.add_char_with_hint(input).0
677 }
678
679 pub fn add_char_with_hint(&mut self, input: char) -> (bool, i32, MaskedTextResultHint) {
683 let last_assigned_pos = self.last_assigned_position();
684
685 if last_assigned_pos == self.length() - 1 {
686 return (false, self.length(), MaskedTextResultHint::UnavailableEditPosition);
688 }
689
690 let test_position = self.find_edit_position_from(last_assigned_pos + 1, FORWARD);
692
693 if test_position == INVALID_INDEX {
694 return (false, self.length(), MaskedTextResultHint::UnavailableEditPosition);
695 }
696
697 let (result, hint) = self.test_set_char(input, test_position);
698 (result, test_position, hint)
699 }
700
701 pub fn add(&mut self, input: &str) -> bool {
704 self.add_with_hint(input).0
705 }
706
707 pub fn add_with_hint(&mut self, input: &str) -> (bool, i32, MaskedTextResultHint) {
710 let test_position = self.last_assigned_position() + 1;
711
712 if input.is_empty() {
713 return (true, test_position, MaskedTextResultHint::NoEffect);
715 }
716
717 self.test_set_string(input, test_position)
718 }
719
720 pub fn clear(&mut self) {
722 self.clear_with_hint();
723 }
724
725 pub fn clear_with_hint(&mut self) -> MaskedTextResultHint {
728 if self.assigned_char_count == 0 {
729 return MaskedTextResultHint::NoEffect;
730 }
731
732 for position in 0..self.length() {
733 self.reset_char(position);
734 }
735
736 MaskedTextResultHint::Success
737 }
738
739 pub fn find_assigned_edit_position_from(&self, position: i32, direction: bool) -> i32 {
742 if self.assigned_char_count == 0 {
743 return INVALID_INDEX;
744 }
745
746 let (start_position, end_position) = self.range_from(position, direction);
747 self.find_assigned_edit_position_in_range(start_position, end_position, direction)
748 }
749
750 pub fn find_assigned_edit_position_in_range(&self, start_position: i32, end_position: i32, direction: bool) -> i32 {
753 if self.assigned_char_count == 0 {
754 return INVALID_INDEX;
755 }
756
757 self.find_edit_position_in_range_status(start_position, end_position, direction, EDIT_ASSIGNED)
758 }
759
760 pub fn find_edit_position_from(&self, position: i32, direction: bool) -> i32 {
763 let (start_position, end_position) = self.range_from(position, direction);
764 self.find_edit_position_in_range(start_position, end_position, direction)
765 }
766
767 pub fn find_edit_position_in_range(&self, start_position: i32, end_position: i32, direction: bool) -> i32 {
770 self.find_position_in_range(start_position, end_position, direction, true)
771 }
772
773 fn find_edit_position_in_range_status(&self, mut start_position: i32, mut end_position: i32, direction: bool, assigned_status: u8) -> i32 {
775 loop {
777 let test_position = self.find_edit_position_in_range(start_position, end_position, direction);
778
779 if test_position == INVALID_INDEX {
780 break;
781 }
782
783 let is_assigned = self.string_descriptor[test_position as usize].is_assigned;
784
785 match assigned_status {
786 EDIT_UNASSIGNED => {
787 if !is_assigned {
788 return test_position;
789 }
790 }
791 EDIT_ASSIGNED => {
792 if is_assigned {
793 return test_position;
794 }
795 }
796 _ => return test_position,
797 }
798
799 if direction == FORWARD {
800 start_position += 1;
801 } else {
802 end_position -= 1;
803 }
804
805 if start_position > end_position {
806 break;
807 }
808 }
809
810 INVALID_INDEX
811 }
812
813 pub fn find_non_edit_position_from(&self, position: i32, direction: bool) -> i32 {
816 let (start_position, end_position) = self.range_from(position, direction);
817 self.find_non_edit_position_in_range(start_position, end_position, direction)
818 }
819
820 pub fn find_non_edit_position_in_range(&self, start_position: i32, end_position: i32, direction: bool) -> i32 {
823 self.find_position_in_range(start_position, end_position, direction, false)
824 }
825
826 fn find_position_in_range(&self, mut start_position: i32, mut end_position: i32, direction: bool, edit: bool) -> i32 {
828 if start_position < 0 {
829 start_position = 0;
830 }
831
832 if end_position >= self.length() {
833 end_position = self.length() - 1;
834 }
835
836 if start_position > end_position {
837 return INVALID_INDEX;
838 }
839
840 while start_position <= end_position {
841 let test_position = if direction == FORWARD {
842 start_position += 1;
843 start_position - 1
844 } else {
845 end_position -= 1;
846 end_position + 1
847 };
848
849 if self.string_descriptor[test_position as usize].is_edit_position() == edit {
850 return test_position;
851 }
852 }
853
854 INVALID_INDEX
855 }
856
857 pub fn find_unassigned_edit_position_from(&self, position: i32, direction: bool) -> i32 {
860 let (start_position, end_position) = self.range_from(position, direction);
861 self.find_edit_position_in_range_status(start_position, end_position, direction, EDIT_UNASSIGNED)
862 }
863
864 pub fn find_unassigned_edit_position_in_range(&self, mut start_position: i32, mut end_position: i32, direction: bool) -> i32 {
867 loop {
868 let position = self.find_edit_position_in_range_status(start_position, end_position, direction, EDIT_ANY);
869
870 if position == INVALID_INDEX {
871 return INVALID_INDEX;
872 }
873
874 if !self.string_descriptor[position as usize].is_assigned {
875 return position;
876 }
877
878 if direction == FORWARD {
879 start_position += 1;
880 } else {
881 end_position -= 1;
882 }
883 }
884 }
885
886 #[inline]
888 fn range_from(&self, position: i32, direction: bool) -> (i32, i32) {
889 if direction == FORWARD {
890 (position, self.length() - 1)
891 } else {
892 (0, position)
893 }
894 }
895
896 pub fn get_operation_result_from_hint(hint: MaskedTextResultHint) -> bool {
898 (hint as i32) > 0
899 }
900
901 pub fn insert_at_char(&mut self, input: char, position: i32) -> bool {
904 if position < 0 || position >= self.length() {
905 return false;
906 }
907
908 let mut buffer = [0u8; 4];
909 self.insert_at(input.encode_utf8(&mut buffer), position)
910 }
911
912 pub fn insert_at_char_with_hint(&mut self, input: char, position: i32) -> (bool, i32, MaskedTextResultHint) {
915 let mut buffer = [0u8; 4];
916 self.insert_at_with_hint(input.encode_utf8(&mut buffer), position)
917 }
918
919 pub fn insert_at(&mut self, input: &str, position: i32) -> bool {
922 self.insert_at_with_hint(input, position).0
923 }
924
925 pub fn insert_at_with_hint(&mut self, input: &str, position: i32) -> (bool, i32, MaskedTextResultHint) {
928 if position < 0 || position >= self.length() {
929 return (false, position, MaskedTextResultHint::PositionOutOfRange);
930 }
931
932 self.insert_at_int(input, position, false)
933 }
934
935 fn insert_at_int(&mut self, input: &str, position: i32, test_only: bool) -> (bool, i32, MaskedTextResultHint) {
938 debug_assert!(position >= 0 && position < self.length(), "input param out of range.");
939
940 if input.is_empty() {
941 return (true, position, MaskedTextResultHint::NoEffect);
943 }
944
945 let (result, mut test_position, mut result_hint) = self.test_string(input, position);
947 if !result {
948 return (false, test_position, result_hint);
949 }
950
951 let mut src_pos = self.find_edit_position_from(position, FORWARD);
954 let shift_needed = self.find_assigned_edit_position_in_range(src_pos, test_position, FORWARD) != INVALID_INDEX;
955 let last_assigned_pos = self.last_assigned_position();
956
957 if shift_needed && test_position == self.length() - 1 {
958 return (false, self.length(), MaskedTextResultHint::UnavailableEditPosition);
960 }
961
962 let mut dst_pos = self.find_edit_position_from(test_position + 1, FORWARD);
963
964 if shift_needed {
965 let mut temp_hint = MaskedTextResultHint::Unknown;
967
968 loop {
970 if dst_pos == INVALID_INDEX {
971 return (false, self.length(), MaskedTextResultHint::UnavailableEditPosition);
972 }
973
974 if self.string_descriptor[src_pos as usize].is_assigned {
976 let (ok, hint) = self.test_char(self.test_string[src_pos as usize], dst_pos);
977 temp_hint = hint;
978 if !ok {
979 test_position = dst_pos;
980 return (false, test_position, temp_hint);
981 }
982 }
983
984 if src_pos == last_assigned_pos {
985 break;
987 }
988
989 src_pos = self.find_edit_position_from(src_pos + 1, FORWARD);
990 dst_pos = self.find_edit_position_from(dst_pos + 1, FORWARD);
991 }
992
993 if temp_hint > result_hint {
994 result_hint = temp_hint;
995 }
996 }
997
998 if test_only {
999 return (true, test_position, result_hint);
1000 }
1001
1002 if shift_needed {
1005 while src_pos >= position {
1006 if self.string_descriptor[src_pos as usize].is_assigned {
1007 self.set_char(self.test_string[src_pos as usize], dst_pos);
1008 } else {
1009 self.reset_char(dst_pos);
1010 }
1011
1012 dst_pos = self.find_edit_position_from(dst_pos - 1, BACKWARD);
1013 src_pos = self.find_edit_position_from(src_pos - 1, BACKWARD);
1014 }
1015 }
1016
1017 self.set_string(input, position);
1019
1020 (true, test_position, result_hint)
1021 }
1022
1023 pub fn is_available_position(&self, position: i32) -> bool {
1025 if position < 0 || position >= self.length() {
1026 return false;
1027 }
1028
1029 let descriptor = &self.string_descriptor[position as usize];
1030 descriptor.is_edit_position() && !descriptor.is_assigned
1031 }
1032
1033 pub fn is_edit_position(&self, position: i32) -> bool {
1035 if position < 0 || position >= self.length() {
1036 return false;
1037 }
1038
1039 self.string_descriptor[position as usize].is_edit_position()
1040 }
1041
1042 pub fn is_valid_input_char(c: char) -> bool {
1045 is_printable_char(c)
1046 }
1047
1048 pub fn is_valid_mask_char(c: char) -> bool {
1051 is_printable_char(c)
1052 }
1053
1054 pub fn is_valid_password_char(c: char) -> bool {
1057 is_printable_char(c) || c == NULL_PASSWORD_CHAR
1058 }
1059
1060 pub fn remove(&mut self) -> bool {
1062 self.remove_with_hint().0
1063 }
1064
1065 pub fn remove_with_hint(&mut self) -> (bool, i32, MaskedTextResultHint) {
1068 let last_assigned_pos = self.last_assigned_position();
1069
1070 if last_assigned_pos == INVALID_INDEX {
1071 return (true, 0, MaskedTextResultHint::NoEffect);
1073 }
1074
1075 self.reset_char(last_assigned_pos);
1076 (true, last_assigned_pos, MaskedTextResultHint::Success)
1077 }
1078
1079 pub fn remove_at(&mut self, position: i32) -> bool {
1082 self.remove_at_range(position, position)
1083 }
1084
1085 pub fn remove_at_range(&mut self, start_position: i32, end_position: i32) -> bool {
1088 self.remove_at_range_with_hint(start_position, end_position).0
1089 }
1090
1091 pub fn remove_at_range_with_hint(&mut self, start_position: i32, end_position: i32) -> (bool, i32, MaskedTextResultHint) {
1095 if end_position >= self.length() {
1096 return (false, end_position, MaskedTextResultHint::PositionOutOfRange);
1097 }
1098
1099 if start_position < 0 || start_position > end_position {
1100 return (false, start_position, MaskedTextResultHint::PositionOutOfRange);
1101 }
1102
1103 self.remove_at_int(start_position, end_position, false)
1104 }
1105
1106 fn remove_at_int(&mut self, mut start_position: i32, end_position: i32, test_only: bool) -> (bool, i32, MaskedTextResultHint) {
1109 debug_assert!(
1110 start_position >= 0 && start_position <= end_position && end_position < self.length(),
1111 "Out of range input."
1112 );
1113
1114 let last_assigned_pos = self.last_assigned_position();
1117 let mut dst_pos = self.find_edit_position_in_range(start_position, end_position, FORWARD);
1118
1119 let mut result_hint = MaskedTextResultHint::NoEffect;
1120
1121 if dst_pos == INVALID_INDEX || dst_pos > last_assigned_pos {
1122 return (true, start_position, result_hint);
1124 }
1125
1126 let test_position = start_position;
1128
1129 let shift_needed = end_position < last_assigned_pos;
1131
1132 if self.find_assigned_edit_position_in_range(start_position, end_position, FORWARD) != INVALID_INDEX {
1135 result_hint = MaskedTextResultHint::Success;
1136 }
1137
1138 if shift_needed {
1139 let mut src_pos = self.find_edit_position_from(end_position + 1, FORWARD);
1141 let shift_start = src_pos;
1142
1143 start_position = dst_pos;
1145
1146 loop {
1147 let src_ch = self.test_string[src_pos as usize];
1148 let is_assigned = self.string_descriptor[src_pos as usize].is_assigned;
1149
1150 if src_ch != self.prompt_char || is_assigned {
1152 let (ok, test_hint) = self.test_char(src_ch, dst_pos);
1153 if !ok {
1154 return (false, dst_pos, test_hint);
1156 }
1157 }
1158
1159 if src_pos == last_assigned_pos {
1160 break;
1161 }
1162
1163 src_pos = self.find_edit_position_from(src_pos + 1, FORWARD);
1164 dst_pos = self.find_edit_position_from(dst_pos + 1, FORWARD);
1165 }
1166
1167 if MaskedTextResultHint::SideEffect > result_hint {
1170 result_hint = MaskedTextResultHint::SideEffect;
1171 }
1172
1173 if test_only {
1174 return (true, test_position, result_hint);
1175 }
1176
1177 src_pos = shift_start;
1179 dst_pos = start_position;
1180
1181 loop {
1182 let src_ch = self.test_string[src_pos as usize];
1183 let is_assigned = self.string_descriptor[src_pos as usize].is_assigned;
1184
1185 if src_ch == self.prompt_char && !is_assigned {
1187 self.reset_char(dst_pos);
1188 } else {
1189 self.set_char(src_ch, dst_pos);
1190 self.reset_char(src_pos);
1191 }
1192
1193 if src_pos == last_assigned_pos {
1194 break;
1195 }
1196
1197 src_pos = self.find_edit_position_from(src_pos + 1, FORWARD);
1198 dst_pos = self.find_edit_position_from(dst_pos + 1, FORWARD);
1199 }
1200
1201 start_position = dst_pos + 1;
1203 }
1204
1205 if start_position <= end_position {
1206 self.reset_string(start_position, end_position);
1207 }
1208
1209 (true, test_position, result_hint)
1210 }
1211
1212 pub fn replace_char(&mut self, input: char, position: i32) -> bool {
1215 self.replace_char_with_hint(input, position).0
1216 }
1217
1218 pub fn replace_char_with_hint(&mut self, input: char, position: i32) -> (bool, i32, MaskedTextResultHint) {
1221 if position < 0 || position >= self.length() {
1222 return (false, position, MaskedTextResultHint::PositionOutOfRange);
1223 }
1224
1225 let mut test_position = position;
1226
1227 if !self.test_escape_char(input, test_position) {
1229 test_position = self.find_edit_position_from(test_position, FORWARD);
1230 }
1231
1232 if test_position == INVALID_INDEX {
1233 return (false, position, MaskedTextResultHint::UnavailableEditPosition);
1234 }
1235
1236 let (result, hint) = self.test_set_char(input, test_position);
1237 (result, test_position, hint)
1238 }
1239
1240 pub fn replace_char_range_with_hint(&mut self, input: char, start_position: i32, end_position: i32) -> (bool, i32, MaskedTextResultHint) {
1243 if end_position >= self.length() {
1244 return (false, end_position, MaskedTextResultHint::PositionOutOfRange);
1245 }
1246
1247 if start_position < 0 || start_position > end_position {
1248 return (false, start_position, MaskedTextResultHint::PositionOutOfRange);
1249 }
1250
1251 if start_position == end_position {
1252 let (result, hint) = self.test_set_char(input, start_position);
1253 return (result, start_position, hint);
1254 }
1255
1256 let mut buffer = [0u8; 4];
1257 self.replace_range_with_hint(input.encode_utf8(&mut buffer), start_position, end_position)
1258 }
1259
1260 pub fn replace(&mut self, input: &str, position: i32) -> bool {
1264 self.replace_with_hint(input, position).0
1265 }
1266
1267 pub fn replace_with_hint(&mut self, input: &str, position: i32) -> (bool, i32, MaskedTextResultHint) {
1270 if position < 0 || position >= self.length() {
1271 return (false, position, MaskedTextResultHint::PositionOutOfRange);
1272 }
1273
1274 if input.is_empty() {
1275 return self.remove_at_range_with_hint(position, position);
1277 }
1278
1279 self.test_set_string(input, position)
1281 }
1282
1283 pub fn replace_range_with_hint(&mut self, input: &str, start_position: i32, end_position: i32) -> (bool, i32, MaskedTextResultHint) {
1288 if end_position >= self.length() {
1289 return (false, end_position, MaskedTextResultHint::PositionOutOfRange);
1290 }
1291
1292 if start_position < 0 || start_position > end_position {
1293 return (false, start_position, MaskedTextResultHint::PositionOutOfRange);
1294 }
1295
1296 if input.is_empty() {
1297 return self.remove_at_range_with_hint(start_position, end_position);
1299 }
1300
1301 let (result, mut test_position, mut result_hint) = self.test_string(input, start_position);
1308 if !result {
1309 return (false, test_position, result_hint);
1310 }
1311
1312 if self.assigned_char_count > 0 {
1313 if test_position < end_position {
1314 let (ok, temp_pos, temp_hint) = self.remove_at_int(test_position + 1, end_position, false);
1316 if !ok {
1317 return (false, temp_pos, temp_hint);
1318 }
1319
1320 if temp_hint == MaskedTextResultHint::Success && result_hint != temp_hint {
1322 result_hint = MaskedTextResultHint::SideEffect;
1323 }
1324 } else if test_position > end_position {
1325 let last_assigned_pos = self.last_assigned_position();
1328 let mut dst_pos = test_position + 1;
1329 let mut src_pos = end_position + 1;
1330
1331 loop {
1332 src_pos = self.find_edit_position_from(src_pos, FORWARD);
1333 dst_pos = self.find_edit_position_from(dst_pos, FORWARD);
1334
1335 if dst_pos == INVALID_INDEX {
1336 test_position = self.length();
1337 return (false, test_position, MaskedTextResultHint::UnavailableEditPosition);
1338 }
1339
1340 let (ok, temp_hint) = self.test_char(self.test_string[src_pos as usize], dst_pos);
1341 if !ok {
1342 return (false, dst_pos, temp_hint);
1343 }
1344
1345 if temp_hint == MaskedTextResultHint::Success && result_hint != temp_hint {
1347 result_hint = MaskedTextResultHint::Success;
1348 }
1349
1350 if src_pos == last_assigned_pos {
1351 break;
1352 }
1353
1354 src_pos += 1;
1355 dst_pos += 1;
1356 }
1357
1358 while dst_pos > test_position {
1360 self.set_char(self.test_string[src_pos as usize], dst_pos);
1361
1362 src_pos = self.find_edit_position_from(src_pos - 1, BACKWARD);
1363 dst_pos = self.find_edit_position_from(dst_pos - 1, BACKWARD);
1364 }
1365 }
1366
1367 }
1369
1370 self.set_string(input, start_position);
1372
1373 (true, test_position, result_hint)
1374 }
1375
1376 fn reset_char(&mut self, test_position: i32) {
1378 let descriptor = &mut self.string_descriptor[test_position as usize];
1379
1380 if descriptor.is_edit_position() && descriptor.is_assigned {
1381 descriptor.is_assigned = false;
1382 self.test_string[test_position as usize] = self.prompt_char;
1383 self.assigned_char_count -= 1;
1384
1385 if descriptor.char_type == CharType::EditRequired {
1386 self.required_char_count -= 1;
1387 }
1388
1389 debug_assert!(self.assigned_char_count >= 0, "Invalid count of assigned chars.");
1390 }
1391 }
1392
1393 fn reset_string(&mut self, mut start_position: i32, mut end_position: i32) {
1395 start_position = self.find_assigned_edit_position_from(start_position, FORWARD);
1396
1397 if start_position != INVALID_INDEX {
1398 end_position = self.find_assigned_edit_position_from(end_position, BACKWARD);
1399
1400 while start_position <= end_position {
1401 start_position = self.find_assigned_edit_position_from(start_position, FORWARD);
1402 self.reset_char(start_position);
1403 start_position += 1;
1404 }
1405 }
1406 }
1407
1408 pub fn set(&mut self, input: &str) -> bool {
1411 self.set_with_hint(input).0
1412 }
1413
1414 pub fn set_with_hint(&mut self, input: &str) -> (bool, i32, MaskedTextResultHint) {
1417 if input.is_empty() {
1418 let hint = self.clear_with_hint();
1420 return (true, 0, hint);
1421 }
1422
1423 let (result, test_position, result_hint) = self.test_set_string(input, 0);
1424 if !result {
1425 return (false, test_position, result_hint);
1426 }
1427
1428 let reset_pos = self.find_assigned_edit_position_from(test_position + 1, FORWARD);
1430
1431 if reset_pos != INVALID_INDEX {
1432 self.reset_string(reset_pos, self.length() - 1);
1433 }
1434
1435 (true, test_position, result_hint)
1436 }
1437
1438 fn set_char(&mut self, mut input: char, position: i32) {
1441 if self.test_escape_char(input, position) {
1444 self.reset_char(position);
1445 return;
1446 }
1447
1448 let descriptor = self.string_descriptor[position as usize];
1449 debug_assert!(!descriptor.is_literal_position(), "Setting char in literal position.");
1450
1451 if is_letter(input) {
1452 if is_upper(input) {
1453 if descriptor.case_conversion == CaseConversion::ToLower {
1454 input = to_lower(input);
1455 }
1456 } else if descriptor.case_conversion == CaseConversion::ToUpper {
1457 input = to_upper(input);
1459 }
1460 }
1461
1462 self.test_string[position as usize] = input;
1463
1464 if !descriptor.is_assigned {
1465 self.string_descriptor[position as usize].is_assigned = true;
1467 self.assigned_char_count += 1;
1468
1469 if descriptor.char_type == CharType::EditRequired {
1470 self.required_char_count += 1;
1471 }
1472 }
1473 }
1474
1475 fn set_string(&mut self, input: &str, mut test_position: i32) {
1478 for ch in input.chars() {
1479 if !self.test_escape_char(ch, test_position) {
1481 test_position = self.find_edit_position_from(test_position, FORWARD);
1482 }
1483
1484 self.set_char(ch, test_position);
1485 test_position += 1;
1486 }
1487 }
1488
1489 fn test_char(&self, input: char, position: i32) -> (bool, MaskedTextResultHint) {
1491 if !is_printable_char(input) {
1492 return (false, MaskedTextResultHint::InvalidInput);
1493 }
1494
1495 let descriptor = &self.string_descriptor[position as usize];
1496 let current = self.test_string[position as usize];
1497
1498 if descriptor.is_literal_position() {
1500 if self.skip_literals && input == current {
1501 return (true, MaskedTextResultHint::CharacterEscaped);
1502 }
1503
1504 return (false, MaskedTextResultHint::NonEditPosition);
1505 }
1506
1507 if input == self.prompt_char {
1508 if self.reset_on_prompt {
1509 return if descriptor.is_edit_position() && descriptor.is_assigned {
1511 (true, MaskedTextResultHint::SideEffect)
1513 } else {
1514 (true, MaskedTextResultHint::CharacterEscaped)
1515 };
1516 }
1517
1518 if !self.allow_prompt_as_input {
1520 return (false, MaskedTextResultHint::PromptCharNotAllowed);
1521 }
1522 }
1523
1524 if input == SPACE_CHAR && self.reset_on_space {
1525 return if descriptor.is_edit_position() && descriptor.is_assigned {
1526 (true, MaskedTextResultHint::SideEffect)
1528 } else {
1529 (true, MaskedTextResultHint::CharacterEscaped)
1530 };
1531 }
1532
1533 match self.mask_chars[descriptor.mask_position] {
1536 '#' => {
1538 if !is_digit(input) && input != '-' && input != '+' && input != SPACE_CHAR {
1539 return (false, MaskedTextResultHint::DigitExpected);
1540 }
1541 }
1542 '0' => {
1544 if !is_digit(input) {
1545 return (false, MaskedTextResultHint::DigitExpected);
1546 }
1547 }
1548 '9' => {
1550 if !is_digit(input) && input != SPACE_CHAR {
1551 return (false, MaskedTextResultHint::DigitExpected);
1552 }
1553 }
1554 'L' => {
1556 if !is_letter(input) {
1557 return (false, MaskedTextResultHint::LetterExpected);
1558 }
1559 if !is_ascii_letter(input) && self.ascii_only {
1560 return (false, MaskedTextResultHint::AsciiCharacterExpected);
1561 }
1562 }
1563 '?' => {
1565 if !is_letter(input) && input != SPACE_CHAR {
1566 return (false, MaskedTextResultHint::LetterExpected);
1567 }
1568 if !is_ascii_letter(input) && self.ascii_only {
1569 return (false, MaskedTextResultHint::AsciiCharacterExpected);
1570 }
1571 }
1572 '&' => {
1574 if !is_ascii(input) && self.ascii_only {
1575 return (false, MaskedTextResultHint::AsciiCharacterExpected);
1576 }
1577 }
1578 'C' => {
1580 if (!is_ascii(input) && self.ascii_only) && input != SPACE_CHAR {
1581 return (false, MaskedTextResultHint::AsciiCharacterExpected);
1582 }
1583 }
1584 'A' => {
1586 if !is_alphanumeric(input) {
1587 return (false, MaskedTextResultHint::AlphanumericCharacterExpected);
1588 }
1589 if !is_ascii_alphanumeric(input) && self.ascii_only {
1590 return (false, MaskedTextResultHint::AsciiCharacterExpected);
1591 }
1592 }
1593 'a' => {
1595 if !is_alphanumeric(input) && input != SPACE_CHAR {
1596 return (false, MaskedTextResultHint::AlphanumericCharacterExpected);
1597 }
1598 if !is_ascii_alphanumeric(input) && self.ascii_only {
1599 return (false, MaskedTextResultHint::AsciiCharacterExpected);
1600 }
1601 }
1602 _ => debug_assert!(false, "Invalid mask language character."),
1603 }
1604
1605 if input == current && descriptor.is_assigned {
1607 (true, MaskedTextResultHint::NoEffect)
1609 } else {
1610 (true, MaskedTextResultHint::Success)
1611 }
1612 }
1613
1614 fn test_escape_char(&self, input: char, position: i32) -> bool {
1618 let descriptor = &self.string_descriptor[position as usize];
1619
1620 if descriptor.is_literal_position() {
1621 return self.skip_literals && input == self.test_string[position as usize];
1623 }
1624
1625 (self.reset_on_prompt && input == self.prompt_char) || (self.reset_on_space && input == SPACE_CHAR)
1626 }
1627
1628 fn test_set_char(&mut self, input: char, position: i32) -> (bool, MaskedTextResultHint) {
1630 let (result, hint) = self.test_char(input, position);
1631
1632 if result {
1633 if hint == MaskedTextResultHint::Success || hint == MaskedTextResultHint::SideEffect {
1635 self.set_char(input, position);
1636 }
1637
1638 return (true, hint);
1639 }
1640
1641 (false, hint)
1642 }
1643
1644 fn test_set_string(&mut self, input: &str, position: i32) -> (bool, i32, MaskedTextResultHint) {
1646 let (result, test_position, hint) = self.test_string(input, position);
1647
1648 if result {
1649 self.set_string(input, position);
1650 }
1651
1652 (result, test_position, hint)
1653 }
1654
1655 fn test_string(&self, input: &str, position: i32) -> (bool, i32, MaskedTextResultHint) {
1659 debug_assert!(position >= 0, "Position out of range.");
1660
1661 let mut result_hint = MaskedTextResultHint::Unknown;
1662 let mut test_position = position;
1663
1664 if input.is_empty() {
1665 return (true, test_position, result_hint);
1667 }
1668
1669 for ch in input.chars() {
1672 if test_position >= self.length() {
1673 return (false, test_position, MaskedTextResultHint::UnavailableEditPosition);
1674 }
1675
1676 if !self.test_escape_char(ch, test_position) {
1678 test_position = self.find_edit_position_from(test_position, FORWARD);
1679
1680 if test_position == INVALID_INDEX {
1681 return (false, self.length(), MaskedTextResultHint::UnavailableEditPosition);
1682 }
1683 }
1684
1685 let (ok, temp_hint) = self.test_char(ch, test_position);
1687 if !ok {
1688 return (false, test_position, temp_hint);
1689 }
1690
1691 if temp_hint > result_hint {
1693 result_hint = temp_hint;
1694 }
1695
1696 test_position += 1;
1697 }
1698
1699 test_position -= 1;
1701
1702 (true, test_position, result_hint)
1703 }
1704
1705 pub fn to_display_string(&self) -> String {
1709 if !self.is_password() || self.assigned_char_count == 0 {
1710 return self.test_string.iter().collect();
1712 }
1713
1714 self.test_string
1716 .iter()
1717 .zip(&self.string_descriptor)
1718 .map(|(&ch, descriptor)| {
1719 if descriptor.is_edit_position() && descriptor.is_assigned {
1720 self.password_char
1721 } else {
1722 ch
1723 }
1724 })
1725 .collect()
1726 }
1727
1728 pub fn to_string_ignore_password(&self, ignore_password_char: bool) -> String {
1732 self.to_string_with(ignore_password_char, self.include_prompt, self.include_literals, 0, self.length())
1733 }
1734
1735 pub fn to_string_range(&self, start_position: i32, length: i32) -> String {
1739 self.to_string_with(true, self.include_prompt, self.include_literals, start_position, length)
1740 }
1741
1742 pub fn to_string_ignore_password_range(&self, ignore_password_char: bool, start_position: i32, length: i32) -> String {
1746 self.to_string_with(ignore_password_char, self.include_prompt, self.include_literals, start_position, length)
1747 }
1748
1749 pub fn to_string_include(&self, include_prompt: bool, include_literals: bool) -> String {
1752 self.to_string_with(true, include_prompt, include_literals, 0, self.length())
1753 }
1754
1755 pub fn to_string_include_range(&self, include_prompt: bool, include_literals: bool, start_position: i32, length: i32) -> String {
1758 self.to_string_with(true, include_prompt, include_literals, start_position, length)
1759 }
1760
1761 pub fn to_string_with(
1772 &self,
1773 ignore_password_char: bool,
1774 include_prompt: bool,
1775 include_literals: bool,
1776 mut start_position: i32,
1777 mut length: i32,
1778 ) -> String {
1779 if length <= 0 {
1780 return String::new();
1781 }
1782
1783 if start_position < 0 {
1784 start_position = 0;
1785 }
1786
1787 if start_position >= self.length() {
1788 return String::new();
1789 }
1790
1791 let max_length = self.length() - start_position;
1792
1793 if length > max_length {
1794 length = max_length;
1795 }
1796
1797 if (!self.is_password() || ignore_password_char) && include_prompt && include_literals {
1799 return self.test_string[start_position as usize..(start_position + length) as usize].iter().collect();
1801 }
1802
1803 let mut st = String::new();
1805 let mut last_position = start_position + length - 1;
1806
1807 if !include_prompt {
1808 let last_literal_pos = if include_literals {
1811 self.find_non_edit_position_in_range(start_position, last_position, BACKWARD)
1812 } else {
1813 INVALID_INDEX
1814 };
1815 let last_assigned_pos = self.find_assigned_edit_position_in_range(
1816 if last_literal_pos == INVALID_INDEX { start_position } else { last_literal_pos },
1817 last_position,
1818 BACKWARD,
1819 );
1820
1821 last_position = if last_assigned_pos != INVALID_INDEX { last_assigned_pos } else { last_literal_pos };
1823
1824 if last_position == INVALID_INDEX {
1825 return String::new();
1826 }
1827 }
1828
1829 for position in start_position..=last_position {
1830 let ch = self.test_string[position as usize];
1831 let descriptor = &self.string_descriptor[position as usize];
1832
1833 match descriptor.char_type {
1834 CharType::EditOptional | CharType::EditRequired => {
1835 if descriptor.is_assigned {
1836 if self.is_password() && !ignore_password_char {
1837 st.push(self.password_char);
1839 continue;
1840 }
1841 } else if !include_prompt {
1842 st.push(SPACE_CHAR);
1844 continue;
1845 }
1846
1847 st.push(ch);
1848 }
1849 CharType::Separator | CharType::Literal => {
1850 if include_literals {
1851 st.push(ch);
1852 }
1853 }
1854 }
1855 }
1856
1857 st
1858 }
1859
1860 pub fn verify_char(&self, input: char, position: i32) -> (bool, MaskedTextResultHint) {
1863 if position < 0 || position >= self.length() {
1864 return (false, MaskedTextResultHint::PositionOutOfRange);
1865 }
1866
1867 self.test_char(input, position)
1868 }
1869
1870 pub fn verify_escape_char(&self, input: char, position: i32) -> bool {
1873 if position < 0 || position >= self.length() {
1874 return false;
1875 }
1876
1877 self.test_escape_char(input, position)
1878 }
1879
1880 pub fn verify_string(&self, input: &str) -> bool {
1883 self.verify_string_with_hint(input).0
1884 }
1885
1886 pub fn verify_string_with_hint(&self, input: &str) -> (bool, i32, MaskedTextResultHint) {
1890 if input.is_empty() {
1891 return (true, 0, MaskedTextResultHint::NoEffect);
1893 }
1894
1895 self.test_string(input, 0)
1896 }
1897}
1898
1899impl fmt::Display for MaskedTextProvider {
1903 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1904 f.write_str(&self.to_string_with(true, self.include_prompt, self.include_literals, 0, self.length()))
1905 }
1906}
1907
1908impl std::ops::Index<i32> for MaskedTextProvider {
1911 type Output = char;
1912
1913 fn index(&self, index: i32) -> &char {
1914 if index < 0 || index >= self.length() {
1915 panic!("index {index} is out of range of the test string (length {})", self.length());
1916 }
1917
1918 &self.test_string[index as usize]
1919 }
1920}
1921
1922#[inline]
1927fn category(c: char) -> Option<GeneralCategory> {
1928 let value = c as u32;
1929 if value > 0xFFFF {
1930 return None;
1931 }
1932
1933 Some(Codepoint::new(value).general_category())
1934}
1935
1936fn is_letter(c: char) -> bool {
1938 if c.is_ascii() {
1939 return c.is_ascii_alphabetic();
1940 }
1941
1942 matches!(
1943 category(c),
1944 Some(
1945 GeneralCategory::UppercaseLetter
1946 | GeneralCategory::LowercaseLetter
1947 | GeneralCategory::TitlecaseLetter
1948 | GeneralCategory::ModifierLetter
1949 | GeneralCategory::OtherLetter
1950 )
1951 )
1952}
1953
1954fn is_digit(c: char) -> bool {
1956 if c.is_ascii() {
1957 return c.is_ascii_digit();
1958 }
1959
1960 category(c) == Some(GeneralCategory::DecimalNumber)
1961}
1962
1963fn is_upper(c: char) -> bool {
1965 if c.is_ascii() {
1966 return c.is_ascii_uppercase();
1967 }
1968
1969 category(c) == Some(GeneralCategory::UppercaseLetter)
1970}
1971
1972fn is_punctuation(c: char) -> bool {
1974 matches!(
1975 category(c),
1976 Some(
1977 GeneralCategory::ConnectorPunctuation
1978 | GeneralCategory::DashPunctuation
1979 | GeneralCategory::OpenPunctuation
1980 | GeneralCategory::ClosePunctuation
1981 | GeneralCategory::InitialPunctuation
1982 | GeneralCategory::FinalPunctuation
1983 | GeneralCategory::OtherPunctuation
1984 )
1985 )
1986}
1987
1988fn is_symbol(c: char) -> bool {
1990 matches!(
1991 category(c),
1992 Some(GeneralCategory::MathSymbol | GeneralCategory::CurrencySymbol | GeneralCategory::ModifierSymbol | GeneralCategory::OtherSymbol)
1993 )
1994}
1995
1996fn is_printable_char(c: char) -> bool {
1998 is_letter(c) || is_digit(c) || is_punctuation(c) || is_symbol(c) || c == SPACE_CHAR
1999}
2000
2001fn is_ascii(c: char) -> bool {
2003 ('!'..='~').contains(&c)
2004}
2005
2006fn is_ascii_alphanumeric(c: char) -> bool {
2007 c.is_ascii_alphanumeric()
2008}
2009
2010fn is_alphanumeric(c: char) -> bool {
2011 is_letter(c) || is_digit(c)
2012}
2013
2014fn is_ascii_letter(c: char) -> bool {
2015 c.is_ascii_alphabetic()
2016}
2017
2018fn to_lower(c: char) -> char {
2020 let mut mapped = c.to_lowercase();
2021 match (mapped.next(), mapped.next()) {
2022 (Some(lower), None) if (lower as u32) <= 0xFFFF => lower,
2023 _ => c,
2024 }
2025}
2026
2027fn to_upper(c: char) -> char {
2029 match c {
2033 '\u{1F80}'..='\u{1F87}' | '\u{1F90}'..='\u{1F97}' | '\u{1FA0}'..='\u{1FA7}' => {
2034 return char::from_u32(c as u32 + 8).unwrap_or(c);
2035 }
2036 '\u{1FB3}' | '\u{1FC3}' | '\u{1FF3}' => return char::from_u32(c as u32 + 9).unwrap_or(c),
2037 _ => {}
2038 }
2039
2040 let mut mapped = c.to_uppercase();
2041 match (mapped.next(), mapped.next()) {
2042 (Some(upper), None) if (upper as u32) <= 0xFFFF => upper,
2043 _ => c,
2044 }
2045}
2046
2047#[cfg(test)]
2048mod tests {
2049 use super::MaskedTextResultHint as H;
2052 use super::*;
2053
2054 fn provider(mask: &str) -> MaskedTextProvider {
2055 MaskedTextProvider::new(mask).unwrap()
2056 }
2057
2058 fn filled(mask: &str, input: &str) -> MaskedTextProvider {
2059 let mut p = provider(mask);
2060 assert!(p.add(input), "{input:?} does not fit {mask:?}");
2061 p
2062 }
2063
2064 fn en_us() -> CultureInfo {
2065 culture_with_separators("en-US", ".", ",", "/", ":", "$")
2066 }
2067
2068 #[test]
2071 fn result_hint_values() {
2072 assert_eq!(H::Unknown as i32, 0);
2073 assert_eq!(H::CharacterEscaped as i32, 1);
2074 assert_eq!(H::NoEffect as i32, 2);
2075 assert_eq!(H::SideEffect as i32, 3);
2076 assert_eq!(H::Success as i32, 4);
2077 assert_eq!(H::AsciiCharacterExpected as i32, -1);
2078 assert_eq!(H::AlphanumericCharacterExpected as i32, -2);
2079 assert_eq!(H::DigitExpected as i32, -3);
2080 assert_eq!(H::LetterExpected as i32, -4);
2081 assert_eq!(H::SignedDigitExpected as i32, -5);
2082 assert_eq!(H::InvalidInput as i32, -51);
2083 assert_eq!(H::PromptCharNotAllowed as i32, -52);
2084 assert_eq!(H::UnavailableEditPosition as i32, -53);
2085 assert_eq!(H::NonEditPosition as i32, -54);
2086 assert_eq!(H::PositionOutOfRange as i32, -55);
2087 }
2088
2089 #[test]
2090 fn get_operation_result_from_hint_is_true_for_positive_hints() {
2091 for hint in [H::CharacterEscaped, H::NoEffect, H::SideEffect, H::Success] {
2092 assert!(MaskedTextProvider::get_operation_result_from_hint(hint));
2093 }
2094 for hint in [H::Unknown, H::AsciiCharacterExpected, H::DigitExpected, H::InvalidInput, H::PositionOutOfRange] {
2095 assert!(!MaskedTextProvider::get_operation_result_from_hint(hint));
2096 }
2097 }
2098
2099 #[test]
2100 fn constructor_defaults() {
2101 let p = provider("00/00");
2102
2103 assert_eq!(p.mask(), "00/00");
2104 assert!(p.allow_prompt_as_input());
2105 assert!(!p.ascii_only());
2106 assert_eq!(p.prompt_char(), '_');
2107 assert_eq!(p.password_char(), '\0');
2108 assert!(!p.is_password());
2109 assert!(p.include_literals());
2110 assert!(!p.include_prompt());
2111 assert!(p.reset_on_prompt());
2112 assert!(p.reset_on_space());
2113 assert!(p.skip_literals());
2114 assert_eq!(p.culture(), &CultureInfo::current_culture());
2115 assert_eq!(p.length(), 5);
2116 assert_eq!(p.edit_position_count(), 4);
2117 assert_eq!(p.assigned_edit_position_count(), 0);
2118 assert_eq!(p.available_edit_position_count(), 4);
2119 assert_eq!(p.last_assigned_position(), -1);
2120 assert!(!p.mask_completed());
2121 assert!(!p.mask_full());
2122 assert_eq!(p.to_display_string(), "__/__");
2123 assert_eq!(p.to_string(), " /");
2124 assert_eq!(MaskedTextProvider::default_password_char(), '*');
2125 assert_eq!(MaskedTextProvider::DEFAULT_PASSWORD_CHAR, '*');
2126 assert_eq!(MaskedTextProvider::invalid_index(), -1);
2127 assert_eq!(MaskedTextProvider::INVALID_INDEX, -1);
2128 }
2129
2130 #[test]
2131 fn constructor_overloads() {
2132 assert!(MaskedTextProvider::new_with_ascii("LL", true).unwrap().ascii_only());
2133 assert_eq!(MaskedTextProvider::new_with_culture("$0", Some(en_us())).unwrap().to_display_string(), "$_");
2134 let p = MaskedTextProvider::new_with_culture_ascii("$0", Some(en_us()), true).unwrap();
2135 assert!(p.ascii_only());
2136 assert_eq!(p.culture(), &en_us());
2137
2138 let mut p = MaskedTextProvider::new_with_password("00", '*', false).unwrap();
2139 assert!(!p.allow_prompt_as_input());
2140 assert!(p.is_password());
2141 assert!(p.add("12"));
2142 assert_eq!(p.to_display_string(), "**");
2143 assert_eq!(p.to_string(), "12");
2144
2145 let p = MaskedTextProvider::new_with_culture_password("$0", Some(en_us()), '#', true).unwrap();
2146 assert_eq!(p.password_char(), '#');
2147 assert!(p.allow_prompt_as_input());
2148
2149 let p = MaskedTextProvider::new_full("00", None, false, '#', 'x', true).unwrap();
2150 assert_eq!(p.prompt_char(), '#');
2151 assert_eq!(p.password_char(), 'x');
2152 assert!(p.ascii_only());
2153 assert!(!p.allow_prompt_as_input());
2154 assert_eq!(p.to_display_string(), "##");
2155 }
2156
2157 #[test]
2158 fn constructor_rejects_empty_and_invalid_masks() {
2159 assert_eq!(MaskedTextProvider::new("").unwrap_err(), MaskedTextProviderError::MaskNullOrEmpty);
2160 assert_eq!(MaskedTextProvider::new("0\n").unwrap_err(), MaskedTextProviderError::MaskInvalidChar);
2161 assert_eq!(MaskedTextProvider::new("0\t0").unwrap_err(), MaskedTextProviderError::MaskInvalidChar);
2162 assert_eq!(MaskedTextProvider::new("0\u{1F600}").unwrap_err(), MaskedTextProviderError::MaskInvalidChar);
2164 assert!(!MaskedTextProviderError::MaskNullOrEmpty.to_string().is_empty());
2165 }
2166
2167 #[test]
2168 fn constructor_does_not_validate_prompt_and_password_chars() {
2169 let p = MaskedTextProvider::new_full("00", None, true, '*', '*', false).unwrap();
2171 assert_eq!(p.to_display_string(), "**");
2172 assert!(MaskedTextProvider::new_full("00", None, true, '\n', '\0', false).is_ok());
2173 assert!(MaskedTextProvider::new_full("00", None, true, '_', '\n', false).is_ok());
2174 }
2175
2176 #[test]
2177 fn valid_char_predicates() {
2178 for c in ['a', 'Z', '0', '-', '$', ' ', '_', '*', '\u{434}', '\u{A4}', '+', '(', '~'] {
2179 assert!(MaskedTextProvider::is_valid_input_char(c), "{c:?}");
2180 assert!(MaskedTextProvider::is_valid_mask_char(c), "{c:?}");
2181 assert!(MaskedTextProvider::is_valid_password_char(c), "{c:?}");
2182 }
2183 for c in ['\n', '\t', '\r', '\u{7F}', '\u{A0}', '\u{1F600}'] {
2184 assert!(!MaskedTextProvider::is_valid_input_char(c), "{c:?}");
2185 assert!(!MaskedTextProvider::is_valid_mask_char(c), "{c:?}");
2186 assert!(!MaskedTextProvider::is_valid_password_char(c), "{c:?}");
2187 }
2188 assert!(!MaskedTextProvider::is_valid_input_char('\0'));
2189 assert!(!MaskedTextProvider::is_valid_mask_char('\0'));
2190 assert!(MaskedTextProvider::is_valid_password_char('\0'));
2191 }
2192
2193 #[test]
2194 fn password_char_setter_validates() {
2195 let mut p = provider("00");
2196
2197 assert_eq!(p.set_password_char('_'), Err(MaskedTextProviderError::PasswordAndPromptCharEqual));
2198 assert_eq!(p.set_password_char('\n'), Err(MaskedTextProviderError::InvalidChar));
2199 assert_eq!(p.password_char(), '\0');
2200 assert_eq!(p.set_password_char('#'), Ok(()));
2201 assert!(p.is_password());
2202 assert_eq!(p.set_password_char('\0'), Ok(()));
2203 assert!(!p.is_password());
2204 }
2205
2206 #[test]
2207 fn is_password_setter_uses_default_password_char() {
2208 let mut p = provider("00");
2209
2210 p.set_is_password(true);
2211 assert_eq!(p.password_char(), '*');
2212 p.set_is_password(false);
2213 assert_eq!(p.password_char(), '\0');
2214
2215 p.set_password_char('x').unwrap();
2217 p.set_is_password(true);
2218 assert_eq!(p.password_char(), 'x');
2219 }
2220
2221 #[test]
2222 fn prompt_char_setter_validates_and_updates_unassigned_positions() {
2223 let mut p = MaskedTextProvider::new_full("00-00", None, true, '_', '*', false).unwrap();
2224 assert!(p.add("1"));
2225
2226 assert_eq!(p.set_prompt_char('*'), Err(MaskedTextProviderError::PasswordAndPromptCharEqual));
2227 assert_eq!(p.set_prompt_char('\n'), Err(MaskedTextProviderError::InvalidChar));
2228 assert_eq!(p.prompt_char(), '_');
2229
2230 assert_eq!(p.set_prompt_char('#'), Ok(()));
2231 assert_eq!(p.prompt_char(), '#');
2232 assert_eq!(p.to_string_include(true, true), "1#-##");
2233 assert_eq!(p.to_display_string(), "*#-##");
2234 }
2235
2236 #[test]
2237 fn flag_setters() {
2238 let mut p = provider("00");
2239 p.set_include_literals(false);
2240 p.set_include_prompt(true);
2241 p.set_reset_on_prompt(false);
2242 p.set_reset_on_space(false);
2243 p.set_skip_literals(false);
2244 assert!(!p.include_literals());
2245 assert!(p.include_prompt());
2246 assert!(!p.reset_on_prompt());
2247 assert!(!p.reset_on_space());
2248 assert!(!p.skip_literals());
2249 }
2250
2251 #[test]
2252 fn indexer_returns_the_test_string_characters() {
2253 let p = filled("00-00", "12");
2254 assert_eq!(p[0], '1');
2255 assert_eq!(p[2], '-');
2256 assert_eq!(p[4], '_');
2257 assert_eq!(p.char_at(1), '2');
2258 }
2259
2260 #[test]
2261 #[should_panic]
2262 fn indexer_panics_past_the_end() {
2263 let p = provider("00");
2264 let _ = p[2];
2265 }
2266
2267 #[test]
2268 #[should_panic]
2269 fn char_at_panics_for_negative_index() {
2270 let p = provider("00");
2271 let _ = p.char_at(-1);
2272 }
2273
2274 #[test]
2275 fn clone_is_independent_and_keeps_the_state() {
2276 let mut p = MaskedTextProvider::new_full("00-00", Some(en_us()), false, '#', '*', true).unwrap();
2277 p.set_reset_on_prompt(false);
2278 p.set_reset_on_space(false);
2279 p.set_skip_literals(false);
2280 p.set_include_literals(false);
2281 p.set_include_prompt(true);
2282 assert!(p.add("123"));
2283
2284 let mut clone = p.clone();
2285 assert_eq!(clone.mask(), "00-00");
2286 assert_eq!(clone.culture(), &en_us());
2287 assert_eq!(clone.prompt_char(), '#');
2288 assert_eq!(clone.password_char(), '*');
2289 assert!(clone.ascii_only());
2290 assert!(!clone.allow_prompt_as_input());
2291 assert!(!clone.reset_on_prompt());
2292 assert!(!clone.reset_on_space());
2293 assert!(!clone.skip_literals());
2294 assert!(!clone.include_literals());
2295 assert!(clone.include_prompt());
2296 assert_eq!(clone.to_string_include(true, true), "12-3#");
2297 assert_eq!(clone.assigned_edit_position_count(), 3);
2298
2299 assert!(clone.add("4"));
2300 assert_eq!(clone.to_string_include(true, true), "12-34");
2301 assert_eq!(p.to_string_include(true, true), "12-3#");
2302 }
2303
2304 #[test]
2307 fn mask_is_translated_into_the_test_string() {
2308 let p = provider("(999) 000-0000");
2309 assert_eq!(p.to_display_string(), "(___) ___-____");
2310 assert_eq!(p.edit_position_count(), 10);
2311 assert_eq!(p.edit_positions().collect::<Vec<_>>(), vec![1, 2, 3, 6, 7, 8, 10, 11, 12, 13]);
2312
2313 let p = provider(">LL<LL|LL");
2315 assert_eq!(p.length(), 6);
2316 assert_eq!(p.to_display_string(), "______");
2317
2318 let p = provider("\\00");
2320 assert_eq!(p.to_display_string(), "0_");
2321 assert_eq!(p.edit_positions().collect::<Vec<_>>(), vec![1]);
2322 let p = provider("0\\\\0\\L\\<");
2323 assert_eq!(p.to_display_string(), "_\\_L<");
2324 assert_eq!(p.edit_position_count(), 2);
2325
2326 let p = provider("xbc");
2328 assert_eq!(p.to_display_string(), "xbc");
2329 assert_eq!(p.edit_position_count(), 0);
2330 assert!(p.mask_completed());
2331 assert!(p.mask_full());
2332 }
2333
2334 #[test]
2335 fn required_and_optional_elements_drive_mask_completed_and_mask_full() {
2336 let mut p = provider("0L&A9#?Ca");
2338 assert_eq!(p.edit_position_count(), 9);
2339 assert!(!p.mask_completed());
2340
2341 assert!(p.add("1bc"));
2342 assert!(!p.mask_completed());
2343 assert!(p.add("d"));
2344 assert!(p.mask_completed());
2345 assert!(!p.mask_full());
2346 assert_eq!(p.available_edit_position_count(), 5);
2347
2348 assert!(p.add("5+efg"));
2349 assert!(p.mask_full());
2350 assert_eq!(p.to_display_string(), "1bcd5+efg");
2351 assert_eq!(p.available_edit_position_count(), 0);
2352
2353 assert!(p.remove());
2354 assert!(p.mask_completed());
2355 assert!(!p.mask_full());
2356 assert!(!p.remove_at(0));
2358 assert!(p.mask_completed());
2359 assert!(p.replace_char(' ', 0));
2361 assert!(!p.mask_completed());
2362 }
2363
2364 #[test]
2365 fn mask_completed_with_only_optional_positions() {
2366 let p = provider("99");
2367 assert!(p.mask_completed());
2368 assert!(!p.mask_full());
2369 }
2370
2371 const ELEMENTS: &str = "09#L?&CAa";
2373
2374 fn probe(ascii: bool) -> MaskedTextProvider {
2375 let mut p = MaskedTextProvider::new_with_ascii(ELEMENTS, ascii).unwrap();
2376 p.set_reset_on_space(false);
2378 p.set_reset_on_prompt(false);
2379 p
2380 }
2381
2382 #[test]
2383 fn verify_char_digit_elements() {
2384 let p = probe(false);
2385
2386 assert_eq!(p.verify_char('5', 0), (true, H::Success));
2388 assert_eq!(p.verify_char('a', 0), (false, H::DigitExpected));
2389 assert_eq!(p.verify_char(' ', 0), (false, H::DigitExpected));
2390 assert_eq!(p.verify_char('+', 0), (false, H::DigitExpected));
2391 assert_eq!(p.verify_char('\u{663}', 0), (true, H::Success));
2393
2394 assert_eq!(p.verify_char('5', 1), (true, H::Success));
2396 assert_eq!(p.verify_char(' ', 1), (true, H::Success));
2397 assert_eq!(p.verify_char('-', 1), (false, H::DigitExpected));
2398
2399 assert_eq!(p.verify_char('5', 2), (true, H::Success));
2401 assert_eq!(p.verify_char(' ', 2), (true, H::Success));
2402 assert_eq!(p.verify_char('+', 2), (true, H::Success));
2403 assert_eq!(p.verify_char('-', 2), (true, H::Success));
2404 assert_eq!(p.verify_char('a', 2), (false, H::DigitExpected));
2405 }
2406
2407 #[test]
2408 fn verify_char_letter_elements() {
2409 let p = probe(false);
2410
2411 assert_eq!(p.verify_char('a', 3), (true, H::Success));
2413 assert_eq!(p.verify_char('\u{434}', 3), (true, H::Success));
2414 assert_eq!(p.verify_char('1', 3), (false, H::LetterExpected));
2415 assert_eq!(p.verify_char(' ', 3), (false, H::LetterExpected));
2416
2417 assert_eq!(p.verify_char('a', 4), (true, H::Success));
2419 assert_eq!(p.verify_char(' ', 4), (true, H::Success));
2420 assert_eq!(p.verify_char('1', 4), (false, H::LetterExpected));
2421 }
2422
2423 #[test]
2424 fn verify_char_any_and_alphanumeric_elements() {
2425 let p = probe(false);
2426
2427 assert_eq!(p.verify_char('!', 5), (true, H::Success));
2429 assert_eq!(p.verify_char(' ', 5), (true, H::Success));
2430 assert_eq!(p.verify_char('\u{434}', 5), (true, H::Success));
2431
2432 assert_eq!(p.verify_char('!', 6), (true, H::Success));
2434 assert_eq!(p.verify_char(' ', 6), (true, H::Success));
2435
2436 assert_eq!(p.verify_char('a', 7), (true, H::Success));
2438 assert_eq!(p.verify_char('1', 7), (true, H::Success));
2439 assert_eq!(p.verify_char('\u{DC}', 7), (true, H::Success));
2440 assert_eq!(p.verify_char('!', 7), (false, H::AlphanumericCharacterExpected));
2441 assert_eq!(p.verify_char(' ', 7), (false, H::AlphanumericCharacterExpected));
2442
2443 assert_eq!(p.verify_char('a', 8), (true, H::Success));
2445 assert_eq!(p.verify_char(' ', 8), (true, H::Success));
2446 assert_eq!(p.verify_char('!', 8), (false, H::AlphanumericCharacterExpected));
2447 }
2448
2449 #[test]
2450 fn verify_char_ascii_only() {
2451 let p = probe(true);
2452
2453 assert_eq!(p.verify_char('\u{663}', 0), (true, H::Success));
2455
2456 assert_eq!(p.verify_char('a', 3), (true, H::Success));
2457 assert_eq!(p.verify_char('\u{434}', 3), (false, H::AsciiCharacterExpected));
2458 assert_eq!(p.verify_char('\u{434}', 4), (false, H::AsciiCharacterExpected));
2459 assert_eq!(p.verify_char(' ', 4), (false, H::AsciiCharacterExpected));
2461
2462 assert_eq!(p.verify_char('!', 5), (true, H::Success));
2463 assert_eq!(p.verify_char('\u{434}', 5), (false, H::AsciiCharacterExpected));
2464 assert_eq!(p.verify_char(' ', 5), (false, H::AsciiCharacterExpected));
2466 assert_eq!(p.verify_char(' ', 6), (true, H::Success));
2468 assert_eq!(p.verify_char('\u{434}', 6), (false, H::AsciiCharacterExpected));
2469
2470 assert_eq!(p.verify_char('z', 7), (true, H::Success));
2471 assert_eq!(p.verify_char('9', 7), (true, H::Success));
2472 assert_eq!(p.verify_char('\u{DC}', 7), (false, H::AsciiCharacterExpected));
2473 assert_eq!(p.verify_char('\u{DC}', 8), (false, H::AsciiCharacterExpected));
2474 assert_eq!(p.verify_char(' ', 8), (false, H::AsciiCharacterExpected));
2475 }
2476
2477 #[test]
2478 fn verify_char_position_and_input_validation() {
2479 let p = probe(false);
2480
2481 assert_eq!(p.verify_char('1', -1), (false, H::PositionOutOfRange));
2482 assert_eq!(p.verify_char('1', 9), (false, H::PositionOutOfRange));
2483 assert_eq!(p.verify_char('\n', 0), (false, H::InvalidInput));
2484 assert_eq!(p.verify_char('\0', 6), (false, H::InvalidInput));
2485 assert_eq!(p.verify_char('\u{1F600}', 6), (false, H::InvalidInput));
2486 }
2487
2488 #[test]
2489 fn verify_char_reports_no_effect_for_the_same_character() {
2490 let p = filled("00", "1");
2491 assert_eq!(p.verify_char('1', 0), (true, H::NoEffect));
2492 assert_eq!(p.verify_char('2', 0), (true, H::Success));
2493 assert_eq!(p.verify_char('1', 1), (true, H::Success));
2494 }
2495
2496 #[test]
2497 fn verify_char_on_literals() {
2498 let mut p = provider("00-00");
2499 assert_eq!(p.verify_char('-', 2), (true, H::CharacterEscaped));
2500 assert_eq!(p.verify_char('1', 2), (false, H::NonEditPosition));
2501 assert!(p.verify_escape_char('-', 2));
2502 assert!(!p.verify_escape_char('1', 2));
2503
2504 p.set_skip_literals(false);
2505 assert_eq!(p.verify_char('-', 2), (false, H::NonEditPosition));
2506 assert!(!p.verify_escape_char('-', 2));
2507 }
2508
2509 #[test]
2510 fn verify_escape_char_on_edit_positions() {
2511 let mut p = provider("00-00");
2512 assert!(p.verify_escape_char(' ', 0));
2513 assert!(p.verify_escape_char('_', 0));
2514 assert!(!p.verify_escape_char('1', 0));
2515 assert!(!p.verify_escape_char(' ', -1));
2516 assert!(!p.verify_escape_char(' ', 5));
2517
2518 p.set_reset_on_space(false);
2519 assert!(!p.verify_escape_char(' ', 0));
2520 assert!(p.verify_escape_char('_', 0));
2521 p.set_reset_on_prompt(false);
2522 assert!(!p.verify_escape_char('_', 0));
2523 }
2524
2525 #[test]
2526 fn case_conversion_modifiers() {
2527 let mut p = provider(">LL<LL|LL");
2528 assert!(p.add("abCDeF"));
2529 assert_eq!(p.to_display_string(), "ABcdeF");
2530
2531 let mut p = provider(">AAA");
2533 assert!(p.add("a1B"));
2534 assert_eq!(p.to_display_string(), "A1B");
2535
2536 let mut p = provider("<L>L");
2538 assert!(p.add("\u{DC}\u{434}"));
2539 assert_eq!(p.to_display_string(), "\u{FC}\u{414}");
2540
2541 let mut p = provider(">L<L");
2543 assert!(p.add("a"));
2544 assert_eq!(p.to_display_string(), "A_");
2545 assert!(p.insert_at("b", 0));
2546 assert_eq!(p.to_display_string(), "Ba");
2547 }
2548
2549 #[test]
2550 fn culture_separators() {
2551 assert_eq!(CultureInfo::current_culture(), CultureInfo::invariant_culture());
2552
2553 let p = MaskedTextProvider::new_with_culture("$0,000.00 00/00 00:00", Some(CultureInfo::invariant_culture())).unwrap();
2554 assert_eq!(p.to_display_string(), "\u{A4}_,___.__ __/__ __:__");
2555 assert_eq!(provider("$0").to_display_string(), "\u{A4}_");
2556
2557 let de = culture_with_separators("de-DE", ",", ".", ".", ":", "\u{20AC}");
2559 let p = MaskedTextProvider::new_with_culture("$0.0,0/0:0", Some(de)).unwrap();
2560 assert_eq!(p.to_display_string(), "\u{20AC}_,_._._:_");
2561 assert_eq!(p.length(), 10);
2562
2563 let ch = culture_with_separators("de-CH", ".", "'", ".", ":", "CHF");
2565 let mut p = MaskedTextProvider::new_with_culture("$0.0,0/0:0", Some(ch)).unwrap();
2566 assert_eq!(p.to_display_string(), "CHF_._'_._:_");
2567 assert_eq!(p.length(), 12);
2568 assert_eq!(p.edit_positions().collect::<Vec<_>>(), vec![3, 5, 7, 9, 11]);
2569 assert!(!p.is_edit_position(1));
2570 assert_eq!(p.find_non_edit_position_from(3, true), 4);
2571
2572 assert!(p.add("CHF1.2'3"));
2574 assert_eq!(p.to_display_string(), "CHF1.2'3._:_");
2575 assert_eq!(p.verify_char(',', 6), (false, H::NonEditPosition));
2576 }
2577
2578 #[test]
2579 fn escaped_separator_is_not_localised() {
2580 let de = culture_with_separators("de-DE", ",", ".", ".", ":", "\u{20AC}");
2581 let p = MaskedTextProvider::new_with_culture("0\\.0.0", Some(de)).unwrap();
2582 assert_eq!(p.to_display_string(), "_._,_");
2583 }
2584
2585 #[test]
2588 fn add_char_goes_after_the_last_assigned_position() {
2589 let mut p = provider("00-00");
2590
2591 assert_eq!(p.add_char_with_hint('1'), (true, 0, H::Success));
2592 assert_eq!(p.add_char_with_hint('2'), (true, 1, H::Success));
2593 assert_eq!(p.add_char_with_hint('3'), (true, 3, H::Success));
2595 assert_eq!(p.add_char_with_hint('a'), (false, 4, H::DigitExpected));
2596 assert_eq!(p.to_display_string(), "12-3_");
2597 assert!(p.add_char('4'));
2598 assert_eq!(p.add_char_with_hint('5'), (false, 5, H::UnavailableEditPosition));
2599 assert!(!p.add_char('5'));
2600 assert_eq!(p.to_display_string(), "12-34");
2601 }
2602
2603 #[test]
2604 fn add_char_without_edit_position_after_the_last_assigned() {
2605 let mut p = filled("00-", "12");
2606 assert_eq!(p.add_char_with_hint('3'), (false, 3, H::UnavailableEditPosition));
2607 }
2608
2609 #[test]
2610 fn add_char_escaped_space_assigns_nothing() {
2611 let mut p = provider("00");
2612 assert_eq!(p.add_char_with_hint(' '), (true, 0, H::CharacterEscaped));
2613 assert_eq!(p.assigned_edit_position_count(), 0);
2614 assert_eq!(p.add_char_with_hint('1'), (true, 0, H::Success));
2616 }
2617
2618 #[test]
2619 fn add_string() {
2620 let mut p = provider("00-00");
2621
2622 assert_eq!(p.add_with_hint("12-34"), (true, 4, H::Success));
2623 assert_eq!(p.to_display_string(), "12-34");
2624 assert!(p.mask_full());
2625 assert_eq!(p.add_with_hint(""), (true, 5, H::NoEffect));
2626 assert_eq!(p.add_with_hint("1"), (false, 5, H::UnavailableEditPosition));
2627
2628 let mut p = provider("00-00");
2630 assert_eq!(p.add_with_hint("1234"), (true, 4, H::Success));
2631 assert_eq!(p.to_display_string(), "12-34");
2632
2633 let mut p = provider("00-00");
2635 assert_eq!(p.add_with_hint("12"), (true, 1, H::Success));
2636 assert_eq!(p.add_with_hint("3"), (true, 3, H::Success));
2637 assert_eq!(p.to_display_string(), "12-3_");
2638 }
2639
2640 #[test]
2641 fn add_string_is_all_or_nothing() {
2642 let mut p = provider("00-00");
2643
2644 assert_eq!(p.add_with_hint("12a"), (false, 3, H::DigitExpected));
2645 assert_eq!(p.to_display_string(), "__-__");
2646 assert_eq!(p.add_with_hint("12345"), (false, 5, H::UnavailableEditPosition));
2647 assert_eq!(p.assigned_edit_position_count(), 0);
2648 assert!(!p.add("x"));
2649 }
2650
2651 #[test]
2652 fn add_string_without_skip_literals() {
2653 let mut p = provider("00-00");
2654 p.set_skip_literals(false);
2655
2656 assert_eq!(p.add_with_hint("12-34"), (false, 3, H::DigitExpected));
2657 assert_eq!(p.add_with_hint("1234"), (true, 4, H::Success));
2658 assert_eq!(p.to_display_string(), "12-34");
2659 }
2660
2661 #[test]
2664 fn clear_resets_all_positions() {
2665 let mut p = filled("00-00", "123");
2666
2667 assert_eq!(p.clear_with_hint(), H::Success);
2668 assert_eq!(p.to_display_string(), "__-__");
2669 assert_eq!(p.assigned_edit_position_count(), 0);
2670 assert!(!p.mask_completed());
2671 assert_eq!(p.clear_with_hint(), H::NoEffect);
2672 p.clear();
2673 assert_eq!(p.to_display_string(), "__-__");
2674 }
2675
2676 #[test]
2677 fn remove_resets_the_last_assigned_position() {
2678 let mut p = filled("00-00", "123");
2679
2680 assert_eq!(p.remove_with_hint(), (true, 3, H::Success));
2681 assert_eq!(p.to_display_string(), "12-__");
2682 assert!(p.remove());
2683 assert!(p.remove());
2684 assert_eq!(p.remove_with_hint(), (true, 0, H::NoEffect));
2685 assert_eq!(p.to_display_string(), "__-__");
2686 }
2687
2688 #[test]
2689 fn remove_at_shifts_the_following_characters_left() {
2690 let mut p = filled("00-00", "1234");
2691
2692 assert_eq!(p.remove_at_range_with_hint(0, 0), (true, 0, H::Success));
2693 assert_eq!(p.to_display_string(), "23-4_");
2694 assert!(p.remove_at(1));
2695 assert_eq!(p.to_display_string(), "24-__");
2696 }
2697
2698 #[test]
2699 fn remove_at_last_position_does_not_shift() {
2700 let mut p = provider("00:00:00.000");
2702 assert!(p.set("12:34:56.000"));
2703 assert!(p.remove_at(11));
2704 assert_eq!(p.to_display_string(), "12:34:56.00_");
2705 }
2706
2707 #[test]
2708 fn remove_at_range_across_a_literal() {
2709 let mut p = filled("00-00", "1234");
2710 assert_eq!(p.remove_at_range_with_hint(1, 3), (true, 1, H::Success));
2711 assert_eq!(p.to_display_string(), "14-__");
2712
2713 let mut p = filled("00-00", "1234");
2714 assert!(p.remove_at_range(0, 4));
2715 assert_eq!(p.to_display_string(), "__-__");
2716 }
2717
2718 #[test]
2719 fn remove_at_a_literal_has_no_effect() {
2720 let mut p = filled("00-00", "1234");
2721 assert_eq!(p.remove_at_range_with_hint(2, 2), (true, 2, H::NoEffect));
2722 assert_eq!(p.to_display_string(), "12-34");
2723 }
2724
2725 #[test]
2726 fn remove_at_after_the_last_assigned_position_has_no_effect() {
2727 let mut p = filled("00-00", "12");
2728 assert_eq!(p.remove_at_range_with_hint(3, 4), (true, 3, H::NoEffect));
2729 assert_eq!(p.to_display_string(), "12-__");
2730 }
2731
2732 #[test]
2733 fn remove_at_unassigned_position_shifts_as_a_side_effect() {
2734 let mut p = provider("000");
2735 assert!(p.replace_char('5', 2));
2736 assert_eq!(p.to_display_string(), "__5");
2737
2738 assert_eq!(p.remove_at_range_with_hint(0, 0), (true, 0, H::SideEffect));
2739 assert_eq!(p.to_display_string(), "_5_");
2740 }
2741
2742 #[test]
2743 fn remove_at_fails_when_a_shifted_character_does_not_fit() {
2744 let mut p = provider("0L");
2745 assert!(p.replace_char('a', 1));
2746 assert_eq!(p.to_display_string(), "_a");
2747
2748 assert_eq!(p.remove_at_range_with_hint(0, 0), (false, 0, H::DigitExpected));
2749 assert!(!p.remove_at(0));
2750 assert_eq!(p.to_display_string(), "_a");
2751 }
2752
2753 #[test]
2754 fn remove_at_position_validation() {
2755 let mut p = filled("00-00", "1234");
2756
2757 assert_eq!(p.remove_at_range_with_hint(0, 5), (false, 5, H::PositionOutOfRange));
2758 assert_eq!(p.remove_at_range_with_hint(-1, 2), (false, -1, H::PositionOutOfRange));
2759 assert_eq!(p.remove_at_range_with_hint(3, 2), (false, 3, H::PositionOutOfRange));
2760 assert!(!p.remove_at(5));
2761 assert!(!p.remove_at(-1));
2762 assert_eq!(p.to_display_string(), "12-34");
2763 }
2764
2765 #[test]
2768 fn insert_at_shifts_the_following_characters_right() {
2769 let mut p = filled("00000", "123");
2770
2771 assert_eq!(p.insert_at_with_hint("9", 1), (true, 1, H::Success));
2772 assert_eq!(p.to_display_string(), "1923_");
2773 assert_eq!(p.insert_at_char_with_hint('8', 0), (true, 0, H::Success));
2774 assert_eq!(p.to_display_string(), "81923");
2775 }
2776
2777 #[test]
2778 fn insert_at_shifts_across_literals() {
2779 let mut p = filled("00-00", "123");
2780 assert!(p.insert_at("9", 0));
2781 assert_eq!(p.to_display_string(), "91-23");
2782 }
2783
2784 #[test]
2785 fn insert_at_a_literal_uses_the_next_edit_position() {
2786 let mut p = filled("00-00", "123");
2787 assert_eq!(p.insert_at_with_hint("9", 2), (true, 3, H::Success));
2788 assert_eq!(p.to_display_string(), "12-93");
2789 }
2790
2791 #[test]
2792 fn insert_at_unassigned_position_does_not_shift() {
2793 let mut p = provider("000");
2794 assert_eq!(p.insert_at_with_hint("5", 1), (true, 1, H::Success));
2795 assert_eq!(p.to_display_string(), "_5_");
2796 assert!(p.insert_at_char('7', 2));
2797 assert_eq!(p.to_display_string(), "_57");
2798 }
2799
2800 #[test]
2801 fn insert_at_fails_without_room() {
2802 let mut p = filled("000", "123");
2803
2804 assert_eq!(p.insert_at_with_hint("9", 0), (false, 3, H::UnavailableEditPosition));
2805 assert_eq!(p.insert_at_with_hint("9", 2), (false, 3, H::UnavailableEditPosition));
2806 assert!(!p.insert_at_char('9', 1));
2807 assert_eq!(p.to_display_string(), "123");
2808 }
2809
2810 #[test]
2811 fn insert_at_fails_when_a_shifted_character_does_not_fit() {
2812 let mut p = filled("00LL", "12");
2813 assert_eq!(p.insert_at_with_hint("3", 0), (false, 2, H::LetterExpected));
2814 assert_eq!(p.to_display_string(), "12__");
2815 }
2816
2817 #[test]
2818 fn insert_at_fails_for_invalid_input() {
2819 let mut p = provider("00-00");
2820 assert_eq!(p.insert_at_with_hint("1a", 0), (false, 1, H::DigitExpected));
2821 assert_eq!(p.insert_at_with_hint("12345", 0), (false, 5, H::UnavailableEditPosition));
2822 assert_eq!(p.to_display_string(), "__-__");
2823 }
2824
2825 #[test]
2826 fn insert_at_empty_string_and_position_validation() {
2827 let mut p = provider("000");
2828
2829 assert_eq!(p.insert_at_with_hint("", 1), (true, 1, H::NoEffect));
2830 assert_eq!(p.insert_at_with_hint("1", 3), (false, 3, H::PositionOutOfRange));
2831 assert_eq!(p.insert_at_with_hint("1", -1), (false, -1, H::PositionOutOfRange));
2832 assert_eq!(p.insert_at_char_with_hint('1', 3), (false, 3, H::PositionOutOfRange));
2833 assert!(!p.insert_at_char('1', -1));
2834 assert!(!p.insert_at_char('1', 3));
2835 assert!(!p.insert_at("1", 3));
2836 }
2837
2838 #[test]
2839 fn insert_at_space_opens_a_gap() {
2840 let mut p = filled("0000", "123");
2842 assert_eq!(p.insert_at_with_hint(" ", 1), (true, 1, H::Success));
2843 assert_eq!(p.to_display_string(), "1_23");
2844
2845 let mut p = filled("000", "123");
2847 assert_eq!(p.insert_at_with_hint(" ", 1), (false, 3, H::UnavailableEditPosition));
2848 assert_eq!(p.to_display_string(), "123");
2849 }
2850
2851 #[test]
2854 fn replace_char_at_position() {
2855 let mut p = filled("00-00", "1234");
2856
2857 assert_eq!(p.replace_char_with_hint('9', 0), (true, 0, H::Success));
2858 assert_eq!(p.replace_char_with_hint('9', 0), (true, 0, H::NoEffect));
2859 assert_eq!(p.replace_char_with_hint('8', 2), (true, 3, H::Success));
2861 assert_eq!(p.replace_char_with_hint('-', 2), (true, 2, H::CharacterEscaped));
2863 assert_eq!(p.to_display_string(), "92-84");
2864 assert_eq!(p.replace_char_with_hint('a', 1), (false, 1, H::DigitExpected));
2865 assert_eq!(p.replace_char_with_hint('1', 5), (false, 5, H::PositionOutOfRange));
2866 assert_eq!(p.replace_char_with_hint('1', -1), (false, -1, H::PositionOutOfRange));
2867 assert!(!p.replace_char('a', 1));
2868 assert_eq!(p.to_display_string(), "92-84");
2869 }
2870
2871 #[test]
2872 fn replace_char_without_edit_position() {
2873 let mut p = provider("00-");
2874 assert_eq!(p.replace_char_with_hint('1', 2), (false, 2, H::UnavailableEditPosition));
2875 }
2876
2877 #[test]
2878 fn replace_string_at_position_does_not_shift() {
2879 let mut p = filled("00000", "12345");
2880
2881 assert_eq!(p.replace_with_hint("99", 1), (true, 2, H::Success));
2882 assert_eq!(p.to_display_string(), "19945");
2883 assert_eq!(p.replace_with_hint("", 1), (true, 1, H::Success));
2885 assert_eq!(p.to_display_string(), "1945_");
2886 assert_eq!(p.replace_with_hint("1a", 0), (false, 1, H::DigitExpected));
2887 assert_eq!(p.replace_with_hint("1", 5), (false, 5, H::PositionOutOfRange));
2888 assert!(!p.replace("123456", 0));
2889 assert!(p.replace("7", 4));
2890 assert_eq!(p.to_display_string(), "19457");
2891 }
2892
2893 #[test]
2894 fn replace_range_with_a_shorter_string_removes_the_rest() {
2895 let mut p = filled("00000", "12345");
2896 assert_eq!(p.replace_range_with_hint("9", 1, 3), (true, 1, H::Success));
2897 assert_eq!(p.to_display_string(), "195__");
2898
2899 let mut p = filled("00000", "12345");
2900 assert_eq!(p.replace_char_range_with_hint('9', 1, 3), (true, 1, H::Success));
2901 assert_eq!(p.to_display_string(), "195__");
2902 }
2903
2904 #[test]
2905 fn replace_range_with_a_longer_string_inserts_the_rest() {
2906 let mut p = filled("00000", "123");
2907 assert_eq!(p.replace_range_with_hint("789", 0, 0), (true, 2, H::Success));
2908 assert_eq!(p.to_display_string(), "78923");
2909
2910 let mut p = filled("00000", "1234");
2912 assert_eq!(p.replace_range_with_hint("789", 0, 0), (false, 5, H::UnavailableEditPosition));
2913 assert_eq!(p.to_display_string(), "1234_");
2914 }
2915
2916 #[test]
2917 fn replace_range_with_a_string_of_the_same_length() {
2918 let mut p = filled("00-00", "1234");
2919 assert_eq!(p.replace_range_with_hint("98", 1, 3), (true, 3, H::Success));
2920 assert_eq!(p.to_display_string(), "19-84");
2921 }
2922
2923 #[test]
2924 fn replace_range_with_an_empty_string_removes_the_range() {
2925 let mut p = filled("00000", "12345");
2926 assert_eq!(p.replace_range_with_hint("", 1, 2), (true, 1, H::Success));
2927 assert_eq!(p.to_display_string(), "145__");
2928 }
2929
2930 #[test]
2931 fn replace_char_range_of_one_position_sets_that_position() {
2932 let mut p = filled("00-00", "1234");
2933 assert_eq!(p.replace_char_range_with_hint('9', 1, 1), (true, 1, H::Success));
2934 assert_eq!(p.to_display_string(), "19-34");
2935 assert_eq!(p.replace_char_range_with_hint('9', 2, 2), (false, 2, H::NonEditPosition));
2937 }
2938
2939 #[test]
2940 fn replace_range_position_validation() {
2941 let mut p = filled("00000", "12345");
2942
2943 assert_eq!(p.replace_range_with_hint("1", 0, 5), (false, 5, H::PositionOutOfRange));
2944 assert_eq!(p.replace_range_with_hint("1", -1, 2), (false, -1, H::PositionOutOfRange));
2945 assert_eq!(p.replace_range_with_hint("1", 3, 2), (false, 3, H::PositionOutOfRange));
2946 assert_eq!(p.replace_char_range_with_hint('1', 0, 5), (false, 5, H::PositionOutOfRange));
2947 assert_eq!(p.replace_char_range_with_hint('1', 3, 2), (false, 3, H::PositionOutOfRange));
2948 assert_eq!(p.replace_range_with_hint("a", 1, 3), (false, 1, H::DigitExpected));
2949 assert_eq!(p.to_display_string(), "12345");
2950 }
2951
2952 #[test]
2955 fn set_replaces_the_whole_content() {
2956 let mut p = provider("00/00/0000");
2957
2958 assert_eq!(p.set_with_hint("12/10/2000"), (true, 9, H::Success));
2959 assert_eq!(p.to_display_string(), "12/10/2000");
2960 assert!(p.mask_full());
2961
2962 assert_eq!(p.set_with_hint("12/10/2000"), (true, 9, H::NoEffect));
2964
2965 assert_eq!(p.set_with_hint("3"), (true, 0, H::Success));
2967 assert_eq!(p.to_display_string(), "3_/__/____");
2968
2969 assert!(p.set("12102000"));
2970 assert_eq!(p.to_display_string(), "12/10/2000");
2971 }
2972
2973 #[test]
2974 fn set_empty_string_clears() {
2975 let mut p = filled("000", "12");
2976 assert_eq!(p.set_with_hint(""), (true, 0, H::Success));
2977 assert_eq!(p.to_display_string(), "___");
2978 assert_eq!(p.set_with_hint(""), (true, 0, H::NoEffect));
2979 }
2980
2981 #[test]
2982 fn set_invalid_text_keeps_the_previous_content() {
2983 let mut p = provider("000");
2984 assert!(p.set("123"));
2985 assert_eq!(p.set_with_hint("abc"), (false, 0, H::DigitExpected));
2986 assert!(!p.set("1234"));
2987 assert_eq!(p.to_display_string(), "123");
2988
2989 let mut p = provider("00:00:00.000");
2991 assert_eq!(p.set_with_hint("12:34:560000"), (false, 12, H::UnavailableEditPosition));
2992 assert_eq!(p.to_display_string(), "__:__:__.___");
2993 }
2994
2995 #[test]
2996 fn set_accepts_its_own_display_string() {
2997 let mut p = provider("00/00/0000");
2999 assert!(p.set("12/1"));
3000 let display = p.to_display_string();
3001 assert_eq!(display, "12/1_/____");
3002 assert_eq!(p.set_with_hint(&display), (true, 9, H::NoEffect));
3003 assert_eq!(p.to_display_string(), "12/1_/____");
3004 assert_eq!(p.assigned_edit_position_count(), 3);
3005 }
3006
3007 #[test]
3010 fn prompt_resets_the_position() {
3011 let mut p = filled("000", "123");
3012
3013 assert_eq!(p.verify_char('_', 1), (true, H::SideEffect));
3014 assert_eq!(p.replace_char_with_hint('_', 1), (true, 1, H::SideEffect));
3015 assert_eq!(p.to_display_string(), "1_3");
3016 assert_eq!(p.assigned_edit_position_count(), 2);
3017 assert!(!p.mask_completed());
3018 assert_eq!(p.replace_char_with_hint('_', 1), (true, 1, H::CharacterEscaped));
3019
3020 assert!(p.set("4_6"));
3021 assert_eq!(p.to_display_string(), "4_6");
3022 assert!(p.is_available_position(1));
3023 }
3024
3025 #[test]
3026 fn prompt_is_input_when_reset_on_prompt_is_off() {
3027 let mut p = provider("0CC");
3028 p.set_reset_on_prompt(false);
3029
3030 assert_eq!(p.replace_char_with_hint('_', 0), (false, 0, H::DigitExpected));
3032 assert_eq!(p.replace_char_with_hint('_', 1), (true, 1, H::Success));
3034 assert_eq!(p.to_display_string(), "___");
3035 assert_eq!(p.assigned_edit_position_count(), 1);
3036 assert!(!p.is_available_position(1));
3037 }
3038
3039 #[test]
3040 fn prompt_is_rejected_when_not_allowed_as_input() {
3041 let mut p = MaskedTextProvider::new_full("CC", None, false, '_', '\0', false).unwrap();
3042
3043 assert_eq!(p.replace_char_with_hint('_', 0), (true, 0, H::CharacterEscaped));
3045 p.set_reset_on_prompt(false);
3046 assert_eq!(p.replace_char_with_hint('_', 0), (false, 0, H::PromptCharNotAllowed));
3047 assert_eq!(p.add_with_hint("a_"), (false, 1, H::PromptCharNotAllowed));
3048 assert_eq!(p.assigned_edit_position_count(), 0);
3049 }
3050
3051 #[test]
3052 fn space_resets_the_position() {
3053 let mut p = filled("000", "123");
3054
3055 assert_eq!(p.replace_char_with_hint(' ', 0), (true, 0, H::SideEffect));
3056 assert_eq!(p.to_display_string(), "_23");
3057 assert_eq!(p.replace_char_with_hint(' ', 0), (true, 0, H::CharacterEscaped));
3058
3059 let mut p = provider("AA#00");
3061 assert_eq!(p.add_with_hint("S2 33"), (true, 4, H::Success));
3062 assert_eq!(p.to_display_string(), "S2_33");
3063 assert_eq!(p.assigned_edit_position_count(), 4);
3064 }
3065
3066 #[test]
3067 fn space_is_input_when_reset_on_space_is_off() {
3068 let mut p = provider("909");
3069 p.set_reset_on_space(false);
3070
3071 assert_eq!(p.replace_char_with_hint(' ', 1), (false, 1, H::DigitExpected));
3072 assert_eq!(p.replace_char_with_hint(' ', 0), (true, 0, H::Success));
3073 assert_eq!(p.to_display_string(), " __");
3074 assert_eq!(p.assigned_edit_position_count(), 1);
3075 assert_eq!(p.last_assigned_position(), 0);
3076 }
3077
3078 fn find_fixture() -> MaskedTextProvider {
3081 let mut p = filled("00-00", "12");
3083 assert!(p.replace_char('4', 4));
3084 assert_eq!(p.to_display_string(), "12-_4");
3085 p
3086 }
3087
3088 #[test]
3089 fn find_assigned_edit_position() {
3090 let p = find_fixture();
3091
3092 assert_eq!(p.find_assigned_edit_position_from(0, true), 0);
3093 assert_eq!(p.find_assigned_edit_position_from(2, true), 4);
3094 assert_eq!(p.find_assigned_edit_position_from(3, false), 1);
3095 assert_eq!(p.find_assigned_edit_position_from(5, true), -1);
3096 assert_eq!(p.find_assigned_edit_position_in_range(2, 3, true), -1);
3097 assert_eq!(p.find_assigned_edit_position_in_range(0, 4, false), 4);
3098 assert_eq!(p.find_assigned_edit_position_in_range(3, 1, true), -1);
3099 assert_eq!(p.last_assigned_position(), 4);
3100
3101 let empty = provider("00-00");
3102 assert_eq!(empty.find_assigned_edit_position_from(0, true), -1);
3103 assert_eq!(empty.find_assigned_edit_position_in_range(0, 4, false), -1);
3104 }
3105
3106 #[test]
3107 fn find_edit_position() {
3108 let p = find_fixture();
3109
3110 assert_eq!(p.find_edit_position_from(0, true), 0);
3111 assert_eq!(p.find_edit_position_from(2, true), 3);
3112 assert_eq!(p.find_edit_position_from(2, false), 1);
3113 assert_eq!(p.find_edit_position_from(5, true), -1);
3114 assert_eq!(p.find_edit_position_from(-3, true), 0);
3116 assert_eq!(p.find_edit_position_from(10, false), 4);
3117 assert_eq!(p.find_edit_position_from(-1, false), -1);
3118 assert_eq!(p.find_edit_position_in_range(2, 2, true), -1);
3119 assert_eq!(p.find_edit_position_in_range(1, 3, false), 3);
3120 assert_eq!(p.find_edit_position_in_range(-5, 50, true), 0);
3121 }
3122
3123 #[test]
3124 fn find_non_edit_position() {
3125 let p = find_fixture();
3126
3127 assert_eq!(p.find_non_edit_position_from(0, true), 2);
3128 assert_eq!(p.find_non_edit_position_from(4, false), 2);
3129 assert_eq!(p.find_non_edit_position_from(3, true), -1);
3130 assert_eq!(p.find_non_edit_position_from(1, false), -1);
3131 assert_eq!(p.find_non_edit_position_in_range(0, 1, true), -1);
3132 assert_eq!(p.find_non_edit_position_in_range(2, 2, false), 2);
3133 }
3134
3135 #[test]
3136 fn find_unassigned_edit_position() {
3137 let p = find_fixture();
3138
3139 assert_eq!(p.find_unassigned_edit_position_from(0, true), 3);
3140 assert_eq!(p.find_unassigned_edit_position_from(4, false), 3);
3141 assert_eq!(p.find_unassigned_edit_position_from(4, true), -1);
3142 assert_eq!(p.find_unassigned_edit_position_in_range(0, 1, true), -1);
3143 assert_eq!(p.find_unassigned_edit_position_in_range(0, 4, false), 3);
3144
3145 let empty = provider("00-00");
3146 assert_eq!(empty.find_unassigned_edit_position_from(2, true), 3);
3147 assert_eq!(empty.find_unassigned_edit_position_from(2, false), 1);
3148 }
3149
3150 #[test]
3151 fn edit_position_predicates() {
3152 let p = find_fixture();
3153
3154 assert_eq!(p.edit_positions().collect::<Vec<_>>(), vec![0, 1, 3, 4]);
3155 assert!(p.is_edit_position(0));
3156 assert!(!p.is_edit_position(2));
3157 assert!(p.is_edit_position(3));
3158 assert!(!p.is_edit_position(-1));
3159 assert!(!p.is_edit_position(5));
3160 assert!(p.is_available_position(3));
3161 assert!(!p.is_available_position(0));
3162 assert!(!p.is_available_position(2));
3163 assert!(!p.is_available_position(-1));
3164 assert!(!p.is_available_position(5));
3165 }
3166
3167 #[test]
3170 fn to_string_overloads() {
3171 let mut p = MaskedTextProvider::new_full("00/00", None, true, '_', '*', false).unwrap();
3172 assert!(p.add("12"));
3173
3174 assert_eq!(p.to_display_string(), "**/__");
3175 assert_eq!(p.to_string(), "12/");
3177 assert_eq!(p.to_string_ignore_password(true), "12/");
3178 assert_eq!(p.to_string_ignore_password(false), "**/");
3179 assert_eq!(p.to_string_include(true, true), "12/__");
3180 assert_eq!(p.to_string_include(false, true), "12/");
3181 assert_eq!(p.to_string_include(true, false), "12__");
3182 assert_eq!(p.to_string_include(false, false), "12");
3183 assert_eq!(p.to_string_range(1, 3), "2/");
3184 assert_eq!(p.to_string_ignore_password_range(false, 1, 3), "*/");
3185 assert_eq!(p.to_string_include_range(true, true, 1, 3), "2/_");
3186 assert_eq!(p.to_string_with(false, true, true, 0, 5), "**/__");
3187 assert_eq!(p.to_string_with(false, true, false, 0, 5), "**__");
3188 assert_eq!(p.to_string_with(false, false, false, 0, 5), "**");
3189
3190 p.set_include_prompt(true);
3191 assert_eq!(p.to_string(), "12/__");
3192 p.set_include_literals(false);
3193 assert_eq!(p.to_string(), "12__");
3194 assert_eq!(format!("{p}"), "12__");
3195 }
3196
3197 #[test]
3198 fn to_string_range_is_clamped() {
3199 let p = filled("00/00", "1234");
3200
3201 assert_eq!(p.to_string_include_range(true, true, 0, 0), "");
3202 assert_eq!(p.to_string_include_range(true, true, 0, -1), "");
3203 assert_eq!(p.to_string_include_range(true, true, 5, 1), "");
3204 assert_eq!(p.to_string_include_range(true, true, -2, 2), "12");
3205 assert_eq!(p.to_string_include_range(true, true, 3, 100), "34");
3206 assert_eq!(p.to_string_include_range(false, false, 1, 3), "23");
3207 }
3208
3209 #[test]
3210 fn to_string_without_prompt_replaces_gaps_with_spaces() {
3211 let mut p = provider("000-000");
3212 assert!(p.replace_char('1', 1));
3213 assert!(p.replace_char('2', 5));
3214
3215 assert_eq!(p.to_display_string(), "_1_-_2_");
3216 assert_eq!(p.to_string_include(false, true), " 1 - 2");
3218 assert_eq!(p.to_string_include(false, false), " 1 2");
3219 assert_eq!(p.to_string_include(true, false), "_1__2_");
3220 }
3221
3222 #[test]
3223 fn to_string_without_prompt_ends_at_the_last_literal_when_nothing_follows() {
3224 let p = filled("00/00/0000", "12");
3226 assert_eq!(p.to_string_include(false, true), "12/ /");
3227
3228 let empty = provider("00/00/0000");
3229 assert_eq!(empty.to_string_include(false, true), " / /");
3230 assert_eq!(empty.to_string_include(false, false), "");
3231 assert_eq!(provider("000").to_string_include(false, true), "");
3232 assert_eq!(filled("000", "1").to_string_include(false, true), "1");
3233 }
3234
3235 #[test]
3236 fn to_display_string_with_password_char() {
3237 let mut p = MaskedTextProvider::new_full("00/00/0000", None, true, '_', '*', false).unwrap();
3238 assert_eq!(p.to_display_string(), "__/__/____");
3239 assert!(p.insert_at("12102000", 0));
3240 assert_eq!(p.to_display_string(), "**/**/****");
3241 assert_eq!(p.to_string(), "12/10/2000");
3242
3243 let mut p = MaskedTextProvider::new_full("AA#00", None, true, '_', '*', false).unwrap();
3244 assert!(p.insert_at("S2 33", 0));
3245 assert_eq!(p.to_display_string(), "**_**");
3246 }
3247
3248 #[test]
3251 fn verify_string_does_not_change_anything() {
3252 let p = provider("00-00");
3253
3254 assert_eq!(p.verify_string_with_hint("12-34"), (true, 4, H::Success));
3255 assert_eq!(p.verify_string_with_hint("1234"), (true, 4, H::Success));
3256 assert_eq!(p.verify_string_with_hint(""), (true, 0, H::NoEffect));
3257 assert_eq!(p.verify_string_with_hint("1a"), (false, 1, H::DigitExpected));
3258 assert_eq!(p.verify_string_with_hint("123456"), (false, 5, H::UnavailableEditPosition));
3259 assert_eq!(p.verify_string_with_hint(" "), (true, 0, H::CharacterEscaped));
3260 assert_eq!(p.verify_string_with_hint("1\n"), (false, 1, H::InvalidInput));
3261 assert!(p.verify_string("12"));
3262 assert!(!p.verify_string("x"));
3263 assert_eq!(p.assigned_edit_position_count(), 0);
3264
3265 let p = filled("00-00", "12");
3266 assert_eq!(p.verify_string_with_hint("12"), (true, 1, H::NoEffect));
3267 assert_eq!(p.verify_string_with_hint("1 "), (true, 1, H::SideEffect));
3268 assert_eq!(p.to_display_string(), "12-__");
3269 }
3270
3271 fn type_text(p: &mut MaskedTextProvider, text: &str) -> String {
3275 let caret = p.find_edit_position_from(0, true);
3276 p.insert_at(text, caret);
3277 p.to_display_string()
3278 }
3279
3280 #[test]
3281 fn typed_text_follows_the_mask() {
3282 assert_eq!(type_text(&mut provider("00/00/0000"), "12102000"), "12/10/2000");
3283 assert_eq!(type_text(&mut provider("LLLL"), "\u{434}\u{431}s"), "\u{434}\u{431}s_");
3284 assert_eq!(type_text(&mut provider("AA#00"), "S2 33"), "S2_33");
3285 }
3286
3287 #[test]
3288 fn typed_text_is_hidden_by_the_password_char() {
3289 let password = |mask: &str| MaskedTextProvider::new_full(mask, None, true, '_', '*', false).unwrap();
3290
3291 assert_eq!(type_text(&mut password("00/00/0000"), "12102000"), "**/**/****");
3292 assert_eq!(type_text(&mut password("LLLL"), "\u{434}\u{431}s"), "***_");
3293 assert_eq!(type_text(&mut password("AA#00"), "S2 33"), "**_**");
3294 }
3295
3296 #[test]
3297 fn typed_non_ascii_text_is_rejected_when_ascii_only() {
3298 let ascii = |mask: &str| MaskedTextProvider::new_full(mask, None, true, '_', '\0', true).unwrap();
3299
3300 assert_eq!(type_text(&mut ascii("00/00/0000"), "12102000"), "12/10/2000");
3301 assert_eq!(type_text(&mut ascii("LLLL"), "\u{434}\u{431}s"), "____");
3302 assert_eq!(type_text(&mut ascii("AA"), "\u{DC}1"), "__");
3303 }
3304
3305 #[test]
3306 fn pasted_text_is_inserted_character_by_character() {
3307 let mut p = provider("00/00/0000");
3308 let mut caret = 0;
3309
3310 for item in "12x10-2000".chars() {
3311 let index = p.find_edit_position_from(caret, true);
3312 if p.insert_at_char(item, index) {
3313 caret = index + 1;
3314 }
3315 }
3316
3317 assert_eq!(p.to_display_string(), "12/10/2000");
3318 assert_eq!(p.find_edit_position_from(caret, true), -1);
3320 assert!(!p.insert_at_char('1', -1));
3321 }
3322
3323 #[test]
3324 fn selection_is_removed_on_reset_input() {
3325 let mut p = filled("00/00/0000", "12102000");
3327 assert!(p.remove_at_range(3, 6));
3328 assert_eq!(p.to_display_string(), "12/00/0___");
3329 }
3330
3331 #[test]
3332 fn upper_case_of_greek_letters_with_iota_subscript() {
3333 let mut p = provider(">LL");
3334 assert!(p.add("\u{1F80}\u{1FF3}"));
3335 assert_eq!(p.to_display_string(), "\u{1F88}\u{1FFC}");
3336 }
3337}