validation2 example now working and displaying the full potential of the feature2 being implemented
This commit is contained in:
724
canvas/examples/validation_2.rs
Normal file
724
canvas/examples/validation_2.rs
Normal file
@@ -0,0 +1,724 @@
|
|||||||
|
// examples/validation_patterns_tui.rs
|
||||||
|
//! TUI Example demonstrating position-based pattern filtering
|
||||||
|
//!
|
||||||
|
//! Run with: cargo run --example validation_patterns_tui --features "validation,gui"
|
||||||
|
|
||||||
|
// REQUIRE validation and gui features - example won't compile without them
|
||||||
|
#[cfg(not(all(feature = "validation", feature = "gui")))]
|
||||||
|
compile_error!(
|
||||||
|
"This example requires the 'validation' and 'gui' features. \
|
||||||
|
Run with: cargo run --example validation_patterns_tui --features \"validation,gui\""
|
||||||
|
);
|
||||||
|
|
||||||
|
use std::io;
|
||||||
|
use canvas::ValidationResult;
|
||||||
|
use crossterm::{
|
||||||
|
event::{
|
||||||
|
self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyModifiers,
|
||||||
|
},
|
||||||
|
execute,
|
||||||
|
terminal::{
|
||||||
|
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use ratatui::{
|
||||||
|
backend::{Backend, CrosstermBackend},
|
||||||
|
layout::{Constraint, Direction, Layout, Rect},
|
||||||
|
style::{Color, Style},
|
||||||
|
text::{Line, Span},
|
||||||
|
widgets::{Block, Borders, Paragraph, Wrap},
|
||||||
|
Frame, Terminal,
|
||||||
|
};
|
||||||
|
|
||||||
|
use canvas::{
|
||||||
|
canvas::{
|
||||||
|
gui::render_canvas_default,
|
||||||
|
modes::AppMode,
|
||||||
|
},
|
||||||
|
DataProvider, FormEditor,
|
||||||
|
ValidationConfig, ValidationConfigBuilder, PatternFilters, PositionFilter, PositionRange, CharacterFilter,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Enhanced FormEditor for pattern validation demonstration
|
||||||
|
struct PatternValidationFormEditor<D: DataProvider> {
|
||||||
|
editor: FormEditor<D>,
|
||||||
|
debug_message: String,
|
||||||
|
command_buffer: String,
|
||||||
|
validation_enabled: bool,
|
||||||
|
field_switch_blocked: bool,
|
||||||
|
block_reason: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D: DataProvider> PatternValidationFormEditor<D> {
|
||||||
|
fn new(data_provider: D) -> Self {
|
||||||
|
let mut editor = FormEditor::new(data_provider);
|
||||||
|
|
||||||
|
// Enable validation by default
|
||||||
|
editor.set_validation_enabled(true);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
editor,
|
||||||
|
debug_message: "🔍 Pattern Validation Demo - Try typing in different fields!".to_string(),
|
||||||
|
command_buffer: String::new(),
|
||||||
|
validation_enabled: true,
|
||||||
|
field_switch_blocked: false,
|
||||||
|
block_reason: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === COMMAND BUFFER HANDLING ===
|
||||||
|
fn clear_command_buffer(&mut self) {
|
||||||
|
self.command_buffer.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_to_command_buffer(&mut self, ch: char) {
|
||||||
|
self.command_buffer.push(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_command_buffer(&self) -> &str {
|
||||||
|
&self.command_buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_pending_command(&self) -> bool {
|
||||||
|
!self.command_buffer.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
// === VALIDATION CONTROL ===
|
||||||
|
fn toggle_validation(&mut self) {
|
||||||
|
self.validation_enabled = !self.validation_enabled;
|
||||||
|
self.editor.set_validation_enabled(self.validation_enabled);
|
||||||
|
|
||||||
|
if self.validation_enabled {
|
||||||
|
self.debug_message = "✅ Pattern Validation ENABLED".to_string();
|
||||||
|
} else {
|
||||||
|
self.debug_message = "❌ Pattern Validation DISABLED".to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === MOVEMENT ===
|
||||||
|
fn move_left(&mut self) {
|
||||||
|
self.editor.move_left();
|
||||||
|
self.field_switch_blocked = false;
|
||||||
|
self.block_reason = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_right(&mut self) {
|
||||||
|
self.editor.move_right();
|
||||||
|
self.field_switch_blocked = false;
|
||||||
|
self.block_reason = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_up(&mut self) {
|
||||||
|
match self.editor.move_up() {
|
||||||
|
Ok(()) => {
|
||||||
|
self.update_field_validation_status();
|
||||||
|
self.field_switch_blocked = false;
|
||||||
|
self.block_reason = None;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
self.field_switch_blocked = true;
|
||||||
|
self.block_reason = Some(e.to_string());
|
||||||
|
self.debug_message = format!("🚫 Field switch blocked: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_down(&mut self) {
|
||||||
|
match self.editor.move_down() {
|
||||||
|
Ok(()) => {
|
||||||
|
self.update_field_validation_status();
|
||||||
|
self.field_switch_blocked = false;
|
||||||
|
self.block_reason = None;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
self.field_switch_blocked = true;
|
||||||
|
self.block_reason = Some(e.to_string());
|
||||||
|
self.debug_message = format!("🚫 Field switch blocked: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_line_start(&mut self) {
|
||||||
|
self.editor.move_line_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_line_end(&mut self) {
|
||||||
|
self.editor.move_line_end();
|
||||||
|
}
|
||||||
|
|
||||||
|
// === MODE TRANSITIONS ===
|
||||||
|
fn enter_edit_mode(&mut self) {
|
||||||
|
self.editor.enter_edit_mode();
|
||||||
|
self.debug_message = "✏️ INSERT MODE - Type to test pattern validation".to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enter_append_mode(&mut self) {
|
||||||
|
self.editor.enter_append_mode();
|
||||||
|
self.debug_message = "✏️ INSERT (append) - Pattern validation active".to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exit_edit_mode(&mut self) {
|
||||||
|
self.editor.exit_edit_mode();
|
||||||
|
self.debug_message = "🔒 NORMAL MODE".to_string();
|
||||||
|
self.update_field_validation_status();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert_char(&mut self, ch: char) -> anyhow::Result<()> {
|
||||||
|
let result = self.editor.insert_char(ch);
|
||||||
|
if result.is_ok() {
|
||||||
|
// Show real-time validation feedback
|
||||||
|
if let Some(validation_result) = self.editor.current_field_validation() {
|
||||||
|
match validation_result {
|
||||||
|
ValidationResult::Valid => {
|
||||||
|
self.debug_message = "✅ Valid character".to_string();
|
||||||
|
}
|
||||||
|
ValidationResult::Warning { message } => {
|
||||||
|
self.debug_message = format!("⚠️ Warning: {}", message);
|
||||||
|
}
|
||||||
|
ValidationResult::Error { message } => {
|
||||||
|
self.debug_message = format!("❌ Error: {}", message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(result?)
|
||||||
|
}
|
||||||
|
|
||||||
|
// === DELETE OPERATIONS ===
|
||||||
|
fn delete_backward(&mut self) -> anyhow::Result<()> {
|
||||||
|
let result = self.editor.delete_backward();
|
||||||
|
if result.is_ok() {
|
||||||
|
self.debug_message = "⌫ Deleted character".to_string();
|
||||||
|
}
|
||||||
|
Ok(result?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_forward(&mut self) -> anyhow::Result<()> {
|
||||||
|
let result = self.editor.delete_forward();
|
||||||
|
if result.is_ok() {
|
||||||
|
self.debug_message = "⌦ Deleted character".to_string();
|
||||||
|
}
|
||||||
|
Ok(result?)
|
||||||
|
}
|
||||||
|
|
||||||
|
// === DELEGATE TO ORIGINAL EDITOR ===
|
||||||
|
fn current_field(&self) -> usize {
|
||||||
|
self.editor.current_field()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cursor_position(&self) -> usize {
|
||||||
|
self.editor.cursor_position()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mode(&self) -> AppMode {
|
||||||
|
self.editor.mode()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_text(&self) -> &str {
|
||||||
|
self.editor.current_text()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data_provider(&self) -> &D {
|
||||||
|
self.editor.data_provider()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ui_state(&self) -> &canvas::EditorState {
|
||||||
|
self.editor.ui_state()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_mode(&mut self, mode: AppMode) {
|
||||||
|
self.editor.set_mode(mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_field(&mut self) {
|
||||||
|
match self.editor.next_field() {
|
||||||
|
Ok(()) => {
|
||||||
|
self.update_field_validation_status();
|
||||||
|
self.field_switch_blocked = false;
|
||||||
|
self.block_reason = None;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
self.field_switch_blocked = true;
|
||||||
|
self.block_reason = Some(e.to_string());
|
||||||
|
self.debug_message = format!("🚫 Cannot move to next field: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prev_field(&mut self) {
|
||||||
|
match self.editor.prev_field() {
|
||||||
|
Ok(()) => {
|
||||||
|
self.update_field_validation_status();
|
||||||
|
self.field_switch_blocked = false;
|
||||||
|
self.block_reason = None;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
self.field_switch_blocked = true;
|
||||||
|
self.block_reason = Some(e.to_string());
|
||||||
|
self.debug_message = format!("🚫 Cannot move to previous field: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === STATUS AND DEBUG ===
|
||||||
|
fn set_debug_message(&mut self, msg: String) {
|
||||||
|
self.debug_message = msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug_message(&self) -> &str {
|
||||||
|
&self.debug_message
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_field_validation_status(&mut self) {
|
||||||
|
if !self.validation_enabled {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(result) = self.editor.current_field_validation() {
|
||||||
|
match result {
|
||||||
|
ValidationResult::Valid => {
|
||||||
|
self.debug_message = format!("Field {}: ✅ Valid", self.editor.current_field() + 1);
|
||||||
|
}
|
||||||
|
ValidationResult::Warning { message } => {
|
||||||
|
self.debug_message = format!("Field {}: ⚠️ {}", self.editor.current_field() + 1, message);
|
||||||
|
}
|
||||||
|
ValidationResult::Error { message } => {
|
||||||
|
self.debug_message = format!("Field {}: ❌ {}", self.editor.current_field() + 1, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.debug_message = format!("Field {}: 🔍 Not validated yet", self.editor.current_field() + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_validation_status(&self) -> String {
|
||||||
|
if !self.validation_enabled {
|
||||||
|
return "❌ DISABLED".to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.field_switch_blocked {
|
||||||
|
return "🚫 SWITCH BLOCKED".to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
let summary = self.editor.validation_summary();
|
||||||
|
if summary.has_errors() {
|
||||||
|
format!("❌ {} ERRORS", summary.error_fields)
|
||||||
|
} else if summary.has_warnings() {
|
||||||
|
format!("⚠️ {} WARNINGS", summary.warning_fields)
|
||||||
|
} else if summary.validated_fields > 0 {
|
||||||
|
format!("✅ {} VALID", summary.valid_fields)
|
||||||
|
} else {
|
||||||
|
"🔍 READY".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Demo form with pattern-based validation
|
||||||
|
struct PatternValidationData {
|
||||||
|
fields: Vec<(String, String)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PatternValidationData {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
fields: vec![
|
||||||
|
("🚗 License Plate (AB123)".to_string(), "".to_string()),
|
||||||
|
("📞 Phone (123-456-7890)".to_string(), "".to_string()),
|
||||||
|
("💳 Credit Card (1234-5678-9012-3456)".to_string(), "".to_string()),
|
||||||
|
("🆔 Custom ID (AB123def)".to_string(), "".to_string()),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DataProvider for PatternValidationData {
|
||||||
|
fn field_count(&self) -> usize {
|
||||||
|
self.fields.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn field_name(&self, index: usize) -> &str {
|
||||||
|
&self.fields[index].0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn field_value(&self, index: usize) -> &str {
|
||||||
|
&self.fields[index].1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_field_value(&mut self, index: usize, value: String) {
|
||||||
|
self.fields[index].1 = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pattern validation configuration per field
|
||||||
|
fn validation_config(&self, field_index: usize) -> Option<ValidationConfig> {
|
||||||
|
match field_index {
|
||||||
|
0 => {
|
||||||
|
// License plate: AB123 (2 letters, 3 numbers)
|
||||||
|
let license_plate_pattern = PatternFilters::new()
|
||||||
|
.add_filter(PositionFilter::new(
|
||||||
|
PositionRange::Range(0, 1),
|
||||||
|
CharacterFilter::Alphabetic,
|
||||||
|
))
|
||||||
|
.add_filter(PositionFilter::new(
|
||||||
|
PositionRange::Range(2, 4),
|
||||||
|
CharacterFilter::Numeric,
|
||||||
|
));
|
||||||
|
|
||||||
|
Some(ValidationConfigBuilder::new()
|
||||||
|
.with_pattern_filters(license_plate_pattern)
|
||||||
|
.build())
|
||||||
|
}
|
||||||
|
1 => {
|
||||||
|
// Phone number: 123-456-7890
|
||||||
|
let phone_pattern = PatternFilters::new()
|
||||||
|
.add_filter(PositionFilter::new(
|
||||||
|
PositionRange::Multiple(vec![0,1,2,4,5,6,8,9,10,11]),
|
||||||
|
CharacterFilter::Numeric,
|
||||||
|
))
|
||||||
|
.add_filter(PositionFilter::new(
|
||||||
|
PositionRange::Multiple(vec![3, 7]),
|
||||||
|
CharacterFilter::Exact('-'),
|
||||||
|
));
|
||||||
|
|
||||||
|
Some(ValidationConfigBuilder::new()
|
||||||
|
.with_pattern_filters(phone_pattern)
|
||||||
|
.build())
|
||||||
|
}
|
||||||
|
2 => {
|
||||||
|
// Credit card: 1234-5678-9012-3456
|
||||||
|
let credit_card_pattern = PatternFilters::new()
|
||||||
|
.add_filter(PositionFilter::new(
|
||||||
|
PositionRange::Multiple(vec![0,1,2,3,5,6,7,8,10,11,12,13,15,16,17,18]),
|
||||||
|
CharacterFilter::Numeric,
|
||||||
|
))
|
||||||
|
.add_filter(PositionFilter::new(
|
||||||
|
PositionRange::Multiple(vec![4, 9, 14]),
|
||||||
|
CharacterFilter::Exact('-'),
|
||||||
|
));
|
||||||
|
|
||||||
|
Some(ValidationConfigBuilder::new()
|
||||||
|
.with_pattern_filters(credit_card_pattern)
|
||||||
|
.build())
|
||||||
|
}
|
||||||
|
3 => {
|
||||||
|
// Custom ID: First 2 letters, rest alphanumeric
|
||||||
|
let custom_id_pattern = PatternFilters::new()
|
||||||
|
.add_filter(PositionFilter::new(
|
||||||
|
PositionRange::Range(0, 1),
|
||||||
|
CharacterFilter::Alphabetic,
|
||||||
|
))
|
||||||
|
.add_filter(PositionFilter::new(
|
||||||
|
PositionRange::From(2),
|
||||||
|
CharacterFilter::Alphanumeric,
|
||||||
|
));
|
||||||
|
|
||||||
|
Some(ValidationConfigBuilder::new()
|
||||||
|
.with_pattern_filters(custom_id_pattern)
|
||||||
|
.build())
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle key presses with pattern validation commands
|
||||||
|
fn handle_key_press(
|
||||||
|
key: KeyCode,
|
||||||
|
modifiers: KeyModifiers,
|
||||||
|
editor: &mut PatternValidationFormEditor<PatternValidationData>,
|
||||||
|
) -> anyhow::Result<bool> {
|
||||||
|
let mode = editor.mode();
|
||||||
|
|
||||||
|
// Quit handling
|
||||||
|
if (key == KeyCode::Char('q') && modifiers.contains(KeyModifiers::CONTROL))
|
||||||
|
|| (key == KeyCode::Char('c') && modifiers.contains(KeyModifiers::CONTROL))
|
||||||
|
|| key == KeyCode::F(10)
|
||||||
|
{
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
match (mode, key, modifiers) {
|
||||||
|
// === MODE TRANSITIONS ===
|
||||||
|
(AppMode::ReadOnly, KeyCode::Char('i'), _) => {
|
||||||
|
editor.enter_edit_mode();
|
||||||
|
editor.clear_command_buffer();
|
||||||
|
}
|
||||||
|
(AppMode::ReadOnly, KeyCode::Char('a'), _) => {
|
||||||
|
editor.enter_append_mode();
|
||||||
|
editor.clear_command_buffer();
|
||||||
|
}
|
||||||
|
(AppMode::ReadOnly, KeyCode::Char('A'), _) => {
|
||||||
|
editor.move_line_end();
|
||||||
|
editor.enter_edit_mode();
|
||||||
|
editor.clear_command_buffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Escape: Exit edit mode
|
||||||
|
(_, KeyCode::Esc, _) => {
|
||||||
|
if mode == AppMode::Edit {
|
||||||
|
editor.exit_edit_mode();
|
||||||
|
} else {
|
||||||
|
editor.clear_command_buffer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === VALIDATION COMMANDS ===
|
||||||
|
(AppMode::ReadOnly, KeyCode::F(1), _) => {
|
||||||
|
editor.toggle_validation();
|
||||||
|
}
|
||||||
|
|
||||||
|
// === MOVEMENT ===
|
||||||
|
(AppMode::ReadOnly, KeyCode::Char('h'), _) | (AppMode::ReadOnly, KeyCode::Left, _) => {
|
||||||
|
editor.move_left();
|
||||||
|
editor.clear_command_buffer();
|
||||||
|
}
|
||||||
|
(AppMode::ReadOnly, KeyCode::Char('l'), _) | (AppMode::ReadOnly, KeyCode::Right, _) => {
|
||||||
|
editor.move_right();
|
||||||
|
editor.clear_command_buffer();
|
||||||
|
}
|
||||||
|
(AppMode::ReadOnly, KeyCode::Char('j'), _) | (AppMode::ReadOnly, KeyCode::Down, _) => {
|
||||||
|
editor.move_down();
|
||||||
|
editor.clear_command_buffer();
|
||||||
|
}
|
||||||
|
(AppMode::ReadOnly, KeyCode::Char('k'), _) | (AppMode::ReadOnly, KeyCode::Up, _) => {
|
||||||
|
editor.move_up();
|
||||||
|
editor.clear_command_buffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
// === EDIT MODE MOVEMENT ===
|
||||||
|
(AppMode::Edit, KeyCode::Left, _) => {
|
||||||
|
editor.move_left();
|
||||||
|
}
|
||||||
|
(AppMode::Edit, KeyCode::Right, _) => {
|
||||||
|
editor.move_right();
|
||||||
|
}
|
||||||
|
(AppMode::Edit, KeyCode::Up, _) => {
|
||||||
|
editor.move_up();
|
||||||
|
}
|
||||||
|
(AppMode::Edit, KeyCode::Down, _) => {
|
||||||
|
editor.move_down();
|
||||||
|
}
|
||||||
|
|
||||||
|
// === DELETE OPERATIONS ===
|
||||||
|
(AppMode::Edit, KeyCode::Backspace, _) => {
|
||||||
|
editor.delete_backward()?;
|
||||||
|
}
|
||||||
|
(AppMode::Edit, KeyCode::Delete, _) => {
|
||||||
|
editor.delete_forward()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// === TAB NAVIGATION ===
|
||||||
|
(_, KeyCode::Tab, _) => {
|
||||||
|
editor.next_field();
|
||||||
|
}
|
||||||
|
(_, KeyCode::BackTab, _) => {
|
||||||
|
editor.prev_field();
|
||||||
|
}
|
||||||
|
|
||||||
|
// === CHARACTER INPUT ===
|
||||||
|
(AppMode::Edit, KeyCode::Char(c), m) if !m.contains(KeyModifiers::CONTROL) => {
|
||||||
|
editor.insert_char(c)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// === DEBUG/INFO COMMANDS ===
|
||||||
|
(AppMode::ReadOnly, KeyCode::Char('?'), _) => {
|
||||||
|
let summary = editor.editor.validation_summary();
|
||||||
|
editor.set_debug_message(format!(
|
||||||
|
"Field {}/{}, Pos {}, Mode: {:?}, Validation: {} fields configured, {} validated",
|
||||||
|
editor.current_field() + 1,
|
||||||
|
editor.data_provider().field_count(),
|
||||||
|
editor.cursor_position(),
|
||||||
|
editor.mode(),
|
||||||
|
summary.total_fields,
|
||||||
|
summary.validated_fields
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {
|
||||||
|
if editor.has_pending_command() {
|
||||||
|
editor.clear_command_buffer();
|
||||||
|
editor.set_debug_message("Invalid command sequence".to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_app<B: Backend>(
|
||||||
|
terminal: &mut Terminal<B>,
|
||||||
|
mut editor: PatternValidationFormEditor<PatternValidationData>,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
loop {
|
||||||
|
terminal.draw(|f| ui(f, &editor))?;
|
||||||
|
|
||||||
|
if let Event::Key(key) = event::read()? {
|
||||||
|
match handle_key_press(key.code, key.modifiers, &mut editor) {
|
||||||
|
Ok(should_continue) => {
|
||||||
|
if !should_continue {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
editor.set_debug_message(format!("Error: {}", e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ui(f: &mut Frame, editor: &PatternValidationFormEditor<PatternValidationData>) {
|
||||||
|
let chunks = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints([Constraint::Min(8), Constraint::Length(12)])
|
||||||
|
.split(f.area());
|
||||||
|
|
||||||
|
render_enhanced_canvas(f, chunks[0], editor);
|
||||||
|
render_validation_status(f, chunks[1], editor);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_enhanced_canvas(
|
||||||
|
f: &mut Frame,
|
||||||
|
area: Rect,
|
||||||
|
editor: &PatternValidationFormEditor<PatternValidationData>,
|
||||||
|
) {
|
||||||
|
render_canvas_default(f, area, &editor.editor);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_validation_status(
|
||||||
|
f: &mut Frame,
|
||||||
|
area: Rect,
|
||||||
|
editor: &PatternValidationFormEditor<PatternValidationData>,
|
||||||
|
) {
|
||||||
|
let chunks = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints([
|
||||||
|
Constraint::Length(3), // Status bar
|
||||||
|
Constraint::Length(4), // Validation summary
|
||||||
|
Constraint::Length(5), // Help
|
||||||
|
])
|
||||||
|
.split(area);
|
||||||
|
|
||||||
|
// Status bar with validation information
|
||||||
|
let mode_text = match editor.mode() {
|
||||||
|
AppMode::Edit => "INSERT",
|
||||||
|
AppMode::ReadOnly => "NORMAL",
|
||||||
|
_ => "OTHER",
|
||||||
|
};
|
||||||
|
|
||||||
|
let validation_status = editor.get_validation_status();
|
||||||
|
let status_text = if editor.has_pending_command() {
|
||||||
|
format!("-- {} -- {} [{}] | Pattern Validation: {}",
|
||||||
|
mode_text, editor.debug_message(), editor.get_command_buffer(), validation_status)
|
||||||
|
} else {
|
||||||
|
format!("-- {} -- {} | Pattern Validation: {}",
|
||||||
|
mode_text, editor.debug_message(), validation_status)
|
||||||
|
};
|
||||||
|
|
||||||
|
let status = Paragraph::new(Line::from(Span::raw(status_text)))
|
||||||
|
.block(Block::default().borders(Borders::ALL).title("🔍 Pattern Validation Status"));
|
||||||
|
|
||||||
|
f.render_widget(status, chunks[0]);
|
||||||
|
|
||||||
|
// Validation summary with field switching info
|
||||||
|
let summary = editor.editor.validation_summary();
|
||||||
|
let summary_text = if editor.validation_enabled {
|
||||||
|
let switch_info = if editor.field_switch_blocked {
|
||||||
|
format!("\n🚫 Field switching blocked: {}",
|
||||||
|
editor.block_reason.as_deref().unwrap_or("Unknown reason"))
|
||||||
|
} else {
|
||||||
|
"\n✅ Field switching allowed".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
format!(
|
||||||
|
"📊 Pattern Validation Summary: {} fields configured, {} validated{}\n\
|
||||||
|
✅ Valid: {} ⚠️ Warnings: {} ❌ Errors: {} 📈 Progress: {:.0}%",
|
||||||
|
summary.total_fields,
|
||||||
|
summary.validated_fields,
|
||||||
|
switch_info,
|
||||||
|
summary.valid_fields,
|
||||||
|
summary.warning_fields,
|
||||||
|
summary.error_fields,
|
||||||
|
summary.completion_percentage() * 100.0
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
"❌ Pattern validation is currently DISABLED\nPress F1 to enable validation".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
let summary_style = if summary.has_errors() {
|
||||||
|
Style::default().fg(Color::Red)
|
||||||
|
} else if summary.has_warnings() {
|
||||||
|
Style::default().fg(Color::Yellow)
|
||||||
|
} else {
|
||||||
|
Style::default().fg(Color::Green)
|
||||||
|
};
|
||||||
|
|
||||||
|
let validation_summary = Paragraph::new(summary_text)
|
||||||
|
.block(Block::default().borders(Borders::ALL).title("📈 Pattern Validation Overview"))
|
||||||
|
.style(summary_style)
|
||||||
|
.wrap(Wrap { trim: true });
|
||||||
|
|
||||||
|
f.render_widget(validation_summary, chunks[1]);
|
||||||
|
|
||||||
|
// Pattern-specific help text
|
||||||
|
let help_text = match editor.mode() {
|
||||||
|
AppMode::ReadOnly => {
|
||||||
|
"🔍 PATTERN VALIDATION DEMO: Each field has specific character patterns!\n\
|
||||||
|
License Plate: 2 letters + 3 numbers (AB123)\n\
|
||||||
|
Phone: Numbers with dashes at positions 3 and 7 (123-456-7890)\n\
|
||||||
|
Credit Card: Number groups separated by dashes (1234-5678-9012-3456)\n\
|
||||||
|
Custom ID: 2 letters + alphanumeric (AB123def)\n\
|
||||||
|
Movement: hjkl/arrows=move, Tab/Shift+Tab=fields, i/a=insert, F1=toggle validation"
|
||||||
|
}
|
||||||
|
AppMode::Edit => {
|
||||||
|
"✏️ INSERT MODE - Type to test pattern validation!\n\
|
||||||
|
Pattern validation will reject characters that don't match the expected pattern\n\
|
||||||
|
arrows=move, Backspace/Del=delete, Esc=normal, Tab=next field"
|
||||||
|
}
|
||||||
|
_ => "🔍 Pattern Validation Demo Active!"
|
||||||
|
};
|
||||||
|
|
||||||
|
let help = Paragraph::new(help_text)
|
||||||
|
.block(Block::default().borders(Borders::ALL).title("🚀 Pattern Validation Commands"))
|
||||||
|
.style(Style::default().fg(Color::Gray))
|
||||||
|
.wrap(Wrap { trim: true });
|
||||||
|
|
||||||
|
f.render_widget(help, chunks[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
// Print feature status
|
||||||
|
println!("🔍 Canvas Pattern Validation TUI Demo");
|
||||||
|
println!("✅ validation feature: ENABLED");
|
||||||
|
println!("✅ gui feature: ENABLED");
|
||||||
|
println!("🚀 Pattern-based validation: ACTIVE");
|
||||||
|
println!("📊 Try typing in fields with different patterns!");
|
||||||
|
println!();
|
||||||
|
|
||||||
|
enable_raw_mode()?;
|
||||||
|
let mut stdout = io::stdout();
|
||||||
|
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
|
||||||
|
let backend = CrosstermBackend::new(stdout);
|
||||||
|
let mut terminal = Terminal::new(backend)?;
|
||||||
|
|
||||||
|
let data = PatternValidationData::new();
|
||||||
|
let editor = PatternValidationFormEditor::new(data);
|
||||||
|
|
||||||
|
let res = run_app(&mut terminal, editor);
|
||||||
|
|
||||||
|
disable_raw_mode()?;
|
||||||
|
execute!(
|
||||||
|
terminal.backend_mut(),
|
||||||
|
LeaveAlternateScreen,
|
||||||
|
DisableMouseCapture
|
||||||
|
)?;
|
||||||
|
terminal.show_cursor()?;
|
||||||
|
|
||||||
|
if let Err(err) = res {
|
||||||
|
println!("{:?}", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("🔍 Pattern validation demo completed!");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -1,290 +0,0 @@
|
|||||||
// examples/validation_patterns.rs
|
|
||||||
//! Example demonstrating position-based pattern filtering
|
|
||||||
//!
|
|
||||||
//! Run with: cargo run --example validation_patterns --features validation
|
|
||||||
|
|
||||||
use canvas::{
|
|
||||||
prelude::*,
|
|
||||||
validation::{ValidationConfigBuilder, PatternFilters, PositionFilter, PositionRange, CharacterFilter},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct DocumentForm {
|
|
||||||
license_plate: String,
|
|
||||||
phone_number: String,
|
|
||||||
credit_card: String,
|
|
||||||
custom_id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DocumentForm {
|
|
||||||
fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
license_plate: String::new(),
|
|
||||||
phone_number: String::new(),
|
|
||||||
credit_card: String::new(),
|
|
||||||
custom_id: String::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DataProvider for DocumentForm {
|
|
||||||
fn field_count(&self) -> usize {
|
|
||||||
4
|
|
||||||
}
|
|
||||||
|
|
||||||
fn field_name(&self, index: usize) -> &str {
|
|
||||||
match index {
|
|
||||||
0 => "License Plate",
|
|
||||||
1 => "Phone Number",
|
|
||||||
2 => "Credit Card",
|
|
||||||
3 => "Custom ID",
|
|
||||||
_ => "",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn field_value(&self, index: usize) -> &str {
|
|
||||||
match index {
|
|
||||||
0 => &self.license_plate,
|
|
||||||
1 => &self.phone_number,
|
|
||||||
2 => &self.credit_card,
|
|
||||||
3 => &self.custom_id,
|
|
||||||
_ => "",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_field_value(&mut self, index: usize, value: String) {
|
|
||||||
match index {
|
|
||||||
0 => self.license_plate = value,
|
|
||||||
1 => self.phone_number = value,
|
|
||||||
2 => self.credit_card = value,
|
|
||||||
3 => self.custom_id = value,
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validation_config(&self, field_index: usize) -> Option<ValidationConfig> {
|
|
||||||
match field_index {
|
|
||||||
0 => {
|
|
||||||
// License plate: AB123 (2 letters, 3 numbers) - USER DEFINED
|
|
||||||
let license_plate_pattern = PatternFilters::new()
|
|
||||||
.add_filter(PositionFilter::new(
|
|
||||||
PositionRange::Range(0, 1),
|
|
||||||
CharacterFilter::Alphabetic,
|
|
||||||
))
|
|
||||||
.add_filter(PositionFilter::new(
|
|
||||||
PositionRange::Range(2, 4),
|
|
||||||
CharacterFilter::Numeric,
|
|
||||||
));
|
|
||||||
|
|
||||||
Some(ValidationConfigBuilder::new()
|
|
||||||
.with_pattern_filters(license_plate_pattern)
|
|
||||||
.build())
|
|
||||||
}
|
|
||||||
1 => {
|
|
||||||
// Phone number: 123-456-7890 - USER DEFINED
|
|
||||||
let phone_pattern = PatternFilters::new()
|
|
||||||
.add_filter(PositionFilter::new(
|
|
||||||
PositionRange::Multiple(vec![0,1,2,4,5,6,8,9,10,11]),
|
|
||||||
CharacterFilter::Numeric,
|
|
||||||
))
|
|
||||||
.add_filter(PositionFilter::new(
|
|
||||||
PositionRange::Multiple(vec![3, 7]),
|
|
||||||
CharacterFilter::Exact('-'),
|
|
||||||
));
|
|
||||||
|
|
||||||
Some(ValidationConfigBuilder::new()
|
|
||||||
.with_pattern_filters(phone_pattern)
|
|
||||||
.build())
|
|
||||||
}
|
|
||||||
2 => {
|
|
||||||
// Credit card: 1234-5678-9012-3456 - USER DEFINED
|
|
||||||
let credit_card_pattern = PatternFilters::new()
|
|
||||||
.add_filter(PositionFilter::new(
|
|
||||||
PositionRange::Multiple(vec![0,1,2,3,5,6,7,8,10,11,12,13,15,16,17,18]),
|
|
||||||
CharacterFilter::Numeric,
|
|
||||||
))
|
|
||||||
.add_filter(PositionFilter::new(
|
|
||||||
PositionRange::Multiple(vec![4, 9, 14]),
|
|
||||||
CharacterFilter::Exact('-'),
|
|
||||||
));
|
|
||||||
|
|
||||||
Some(ValidationConfigBuilder::new()
|
|
||||||
.with_pattern_filters(credit_card_pattern)
|
|
||||||
.build())
|
|
||||||
}
|
|
||||||
3 => {
|
|
||||||
// Custom ID: First 2 letters, rest alphanumeric - USER DEFINED
|
|
||||||
let custom_id_pattern = PatternFilters::new()
|
|
||||||
.add_filter(PositionFilter::new(
|
|
||||||
PositionRange::Range(0, 1),
|
|
||||||
CharacterFilter::Alphabetic,
|
|
||||||
))
|
|
||||||
.add_filter(PositionFilter::new(
|
|
||||||
PositionRange::From(2),
|
|
||||||
CharacterFilter::Alphanumeric,
|
|
||||||
));
|
|
||||||
|
|
||||||
Some(ValidationConfigBuilder::new()
|
|
||||||
.with_pattern_filters(custom_id_pattern)
|
|
||||||
.build())
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
println!("🎯 Canvas Pattern Filtering Demo");
|
|
||||||
println!("=================================");
|
|
||||||
println!();
|
|
||||||
|
|
||||||
let form = DocumentForm::new();
|
|
||||||
let mut editor = FormEditor::new(form);
|
|
||||||
|
|
||||||
println!("📋 Form initialized with USER-DEFINED pattern validation rules:");
|
|
||||||
for i in 0..editor.data_provider().field_count() {
|
|
||||||
let field_name = editor.data_provider().field_name(i);
|
|
||||||
println!(" • {}: Position-based pattern filtering (user-defined)", field_name);
|
|
||||||
}
|
|
||||||
println!();
|
|
||||||
|
|
||||||
// Test License Plate (Field 0)
|
|
||||||
println!("1. Testing USER-DEFINED License Plate pattern (AB123 - 2 letters, 3 numbers):");
|
|
||||||
|
|
||||||
// Valid license plate
|
|
||||||
println!(" Entering valid license plate 'AB123':");
|
|
||||||
for ch in "AB123".chars() {
|
|
||||||
match editor.insert_char(ch) {
|
|
||||||
Ok(_) => println!(" '{}' ✓ accepted", ch),
|
|
||||||
Err(e) => println!(" '{}' ✗ rejected: {}", ch, e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
println!(" Result: '{}'", editor.current_text());
|
|
||||||
println!();
|
|
||||||
|
|
||||||
// Clear and test invalid pattern
|
|
||||||
editor.clear_current_field();
|
|
||||||
println!(" Testing invalid pattern 'A1123':");
|
|
||||||
for (i, ch) in "A1123".chars().enumerate() {
|
|
||||||
match editor.insert_char(ch) {
|
|
||||||
Ok(_) => println!(" Position {}: '{}' ✓ accepted", i, ch),
|
|
||||||
Err(e) => println!(" Position {}: '{}' ✗ rejected: {}", i, ch, e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
println!(" Result: '{}'", editor.current_text());
|
|
||||||
println!();
|
|
||||||
|
|
||||||
// Move to phone number field
|
|
||||||
editor.move_to_next_field()?;
|
|
||||||
|
|
||||||
// Test Phone Number (Field 1)
|
|
||||||
println!("2. Testing USER-DEFINED Phone Number pattern (123-456-7890):");
|
|
||||||
|
|
||||||
// Valid phone number
|
|
||||||
println!(" Entering valid phone number '123-456-7890':");
|
|
||||||
for (i, ch) in "123-456-7890".chars().enumerate() {
|
|
||||||
match editor.insert_char(ch) {
|
|
||||||
Ok(_) => println!(" Position {}: '{}' ✓ accepted", i, ch),
|
|
||||||
Err(e) => println!(" Position {}: '{}' ✗ rejected: {}", i, ch, e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
println!(" Result: '{}'", editor.current_text());
|
|
||||||
println!();
|
|
||||||
|
|
||||||
// Move to credit card field
|
|
||||||
editor.move_to_next_field()?;
|
|
||||||
|
|
||||||
// Test Credit Card (Field 2)
|
|
||||||
println!("3. Testing USER-DEFINED Credit Card pattern (1234-5678-9012-3456):");
|
|
||||||
|
|
||||||
// Valid credit card (first few characters)
|
|
||||||
println!(" Entering valid credit card start '1234-56':");
|
|
||||||
for (i, ch) in "1234-56".chars().enumerate() {
|
|
||||||
match editor.insert_char(ch) {
|
|
||||||
Ok(_) => println!(" Position {}: '{}' ✓ accepted", i, ch),
|
|
||||||
Err(e) => println!(" Position {}: '{}' ✗ rejected: {}", i, ch, e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
println!(" Result: '{}'", editor.current_text());
|
|
||||||
println!();
|
|
||||||
|
|
||||||
// Test invalid character at dash position
|
|
||||||
println!(" Testing invalid character at dash position:");
|
|
||||||
editor.clear_current_field();
|
|
||||||
for (i, ch) in "1234A56".chars().enumerate() {
|
|
||||||
match editor.insert_char(ch) {
|
|
||||||
Ok(_) => println!(" Position {}: '{}' ✓ accepted", i, ch),
|
|
||||||
Err(e) => println!(" Position {}: '{}' ✗ rejected: {}", i, ch, e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
println!(" Result: '{}'", editor.current_text());
|
|
||||||
println!();
|
|
||||||
|
|
||||||
// Move to custom ID field
|
|
||||||
editor.move_to_next_field()?;
|
|
||||||
|
|
||||||
// Test Custom ID (Field 3)
|
|
||||||
println!("4. Testing USER-DEFINED Custom ID pattern (2 letters + alphanumeric):");
|
|
||||||
|
|
||||||
// Valid custom ID
|
|
||||||
println!(" Entering valid custom ID 'AB123def':");
|
|
||||||
for (i, ch) in "AB123def".chars().enumerate() {
|
|
||||||
match editor.insert_char(ch) {
|
|
||||||
Ok(_) => println!(" Position {}: '{}' ✓ accepted", i, ch),
|
|
||||||
Err(e) => println!(" Position {}: '{}' ✗ rejected: {}", i, ch, e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
println!(" Result: '{}'", editor.current_text());
|
|
||||||
println!();
|
|
||||||
|
|
||||||
// Test invalid pattern
|
|
||||||
editor.clear_current_field();
|
|
||||||
println!(" Testing invalid pattern '1B123def' (number in first position):");
|
|
||||||
for (i, ch) in "1B123def".chars().enumerate() {
|
|
||||||
match editor.insert_char(ch) {
|
|
||||||
Ok(_) => println!(" Position {}: '{}' ✓ accepted", i, ch),
|
|
||||||
Err(e) => println!(" Position {}: '{}' ✗ rejected: {}", i, ch, e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
println!(" Result: '{}'", editor.current_text());
|
|
||||||
println!();
|
|
||||||
|
|
||||||
// Show validation summary
|
|
||||||
println!("📊 Final validation summary:");
|
|
||||||
let summary = editor.validation_summary();
|
|
||||||
println!(" Total fields with validation: {}", summary.total_fields);
|
|
||||||
println!(" Validated fields: {}", summary.validated_fields);
|
|
||||||
println!(" Valid fields: {}", summary.valid_fields);
|
|
||||||
println!(" Fields with warnings: {}", summary.warning_fields);
|
|
||||||
println!(" Fields with errors: {}", summary.error_fields);
|
|
||||||
println!();
|
|
||||||
|
|
||||||
// Show field-by-field status
|
|
||||||
println!("📝 Field-by-field validation status:");
|
|
||||||
for i in 0..editor.data_provider().field_count() {
|
|
||||||
let field_name = editor.data_provider().field_name(i);
|
|
||||||
let field_value = editor.data_provider().field_value(i);
|
|
||||||
|
|
||||||
if let Some(result) = editor.field_validation(i) {
|
|
||||||
println!(" {} [{}]: {} - {:?}",
|
|
||||||
field_name,
|
|
||||||
field_value,
|
|
||||||
if result.is_acceptable() { "✓" } else { "✗" },
|
|
||||||
result
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
println!(" {} [{}]: (not validated)", field_name, field_value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
println!();
|
|
||||||
println!("✨ USER-DEFINED Pattern filtering demo completed!");
|
|
||||||
println!("Key Features Demonstrated:");
|
|
||||||
println!(" • Position-specific character filtering (USER DEFINES PATTERNS)");
|
|
||||||
println!(" • Library provides CharacterFilter: Alphabetic, Numeric, Alphanumeric, Exact, OneOf, Custom");
|
|
||||||
println!(" • User defines all patterns using library's building blocks");
|
|
||||||
println!(" • Real-time validation during typing");
|
|
||||||
println!(" • Flexible position ranges (single, range, from, multiple)");
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
//! Position-based pattern filtering for validation
|
//! Position-based pattern filtering for validation
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// A filter that applies to specific character positions in a field
|
/// A filter that applies to specific character positions in a field
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -26,7 +27,6 @@ pub enum PositionRange {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Types of character filters that can be applied
|
/// Types of character filters that can be applied
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum CharacterFilter {
|
pub enum CharacterFilter {
|
||||||
/// Allow only alphabetic characters (a-z, A-Z)
|
/// Allow only alphabetic characters (a-z, A-Z)
|
||||||
Alphabetic,
|
Alphabetic,
|
||||||
@@ -39,7 +39,34 @@ pub enum CharacterFilter {
|
|||||||
/// Allow any character from the provided set
|
/// Allow any character from the provided set
|
||||||
OneOf(Vec<char>),
|
OneOf(Vec<char>),
|
||||||
/// Custom user-defined filter function
|
/// Custom user-defined filter function
|
||||||
Custom(Box<dyn Fn(char) -> bool + Send + Sync>),
|
Custom(Arc<dyn Fn(char) -> bool + Send + Sync>),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manual implementations for Debug and Clone
|
||||||
|
impl std::fmt::Debug for CharacterFilter {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
CharacterFilter::Alphabetic => write!(f, "Alphabetic"),
|
||||||
|
CharacterFilter::Numeric => write!(f, "Numeric"),
|
||||||
|
CharacterFilter::Alphanumeric => write!(f, "Alphanumeric"),
|
||||||
|
CharacterFilter::Exact(ch) => write!(f, "Exact('{}')", ch),
|
||||||
|
CharacterFilter::OneOf(chars) => write!(f, "OneOf({:?})", chars),
|
||||||
|
CharacterFilter::Custom(_) => write!(f, "Custom(<function>)"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for CharacterFilter {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
match self {
|
||||||
|
CharacterFilter::Alphabetic => CharacterFilter::Alphabetic,
|
||||||
|
CharacterFilter::Numeric => CharacterFilter::Numeric,
|
||||||
|
CharacterFilter::Alphanumeric => CharacterFilter::Alphanumeric,
|
||||||
|
CharacterFilter::Exact(ch) => CharacterFilter::Exact(*ch),
|
||||||
|
CharacterFilter::OneOf(chars) => CharacterFilter::OneOf(chars.clone()),
|
||||||
|
CharacterFilter::Custom(func) => CharacterFilter::Custom(Arc::clone(func)),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PositionRange {
|
impl PositionRange {
|
||||||
@@ -290,7 +317,7 @@ mod tests {
|
|||||||
let pattern = PatternFilters::new()
|
let pattern = PatternFilters::new()
|
||||||
.add_filter(PositionFilter::new(
|
.add_filter(PositionFilter::new(
|
||||||
PositionRange::From(0),
|
PositionRange::From(0),
|
||||||
CharacterFilter::Custom(Box::new(|c| c.is_lowercase())),
|
CharacterFilter::Custom(Arc::new(|c| c.is_lowercase())),
|
||||||
));
|
));
|
||||||
|
|
||||||
assert!(pattern.validate_text("hello").is_ok());
|
assert!(pattern.validate_text("hello").is_ok());
|
||||||
|
|||||||
Reference in New Issue
Block a user