Files
falyx/falyx/validators.py

163 lines
5.9 KiB
Python

# Falyx CLI Framework — (c) 2025 rtj.dev LLC — MIT Licensed
"""validators.py"""
from typing import KeysView, Sequence
from prompt_toolkit.validation import ValidationError, Validator
def int_range_validator(minimum: int, maximum: int) -> Validator:
"""Validator for integer ranges."""
def validate(text: str) -> bool:
try:
value = int(text)
if not minimum <= value <= maximum:
return False
return True
except ValueError:
return False
return Validator.from_callable(
validate,
error_message=f"Invalid input. Enter a number between {minimum} and {maximum}.",
)
def key_validator(keys: Sequence[str] | KeysView[str]) -> Validator:
"""Validator for key inputs."""
def validate(text: str) -> bool:
if text.upper() not in [key.upper() for key in keys]:
return False
return True
return Validator.from_callable(
validate, error_message=f"Invalid input. Available keys: {', '.join(keys)}."
)
def yes_no_validator() -> Validator:
"""Validator for yes/no inputs."""
def validate(text: str) -> bool:
if text.upper() not in ["Y", "N"]:
return False
return True
return Validator.from_callable(validate, error_message="Enter 'Y', 'y' or 'N', 'n'.")
def words_validator(
keys: Sequence[str] | KeysView[str], error_message: str | None = None
) -> Validator:
"""Validator for specific word inputs."""
def validate(text: str) -> bool:
if text.upper() not in [key.upper() for key in keys]:
return False
return True
if error_message is None:
error_message = f"Invalid input. Choices: {{{', '.join(keys)}}}."
return Validator.from_callable(validate, error_message=error_message)
def word_validator(word: str) -> Validator:
"""Validator for specific word inputs."""
def validate(text: str) -> bool:
if text.upper().strip() == "N":
return True
return text.upper().strip() == word.upper()
return Validator.from_callable(validate, error_message=f"Enter '{word}' or 'N', 'n'.")
class MultiIndexValidator(Validator):
def __init__(
self,
minimum: int,
maximum: int,
number_selections: int | str,
separator: str,
allow_duplicates: bool,
cancel_key: str,
) -> None:
self.minimum = minimum
self.maximum = maximum
self.number_selections = number_selections
self.separator = separator
self.allow_duplicates = allow_duplicates
self.cancel_key = cancel_key
super().__init__()
def validate(self, document):
selections = [
index.strip() for index in document.text.strip().split(self.separator)
]
if not selections or selections == [""]:
raise ValidationError(message="Select at least 1 item.")
if self.cancel_key in selections and len(selections) == 1:
return
elif self.cancel_key in selections:
raise ValidationError(message="Cancel key must be selected alone.")
for selection in selections:
try:
index = int(selection)
if not self.minimum <= index <= self.maximum:
raise ValidationError(
message=f"Invalid selection: {selection}. Select a number between {self.minimum} and {self.maximum}."
)
except ValueError:
raise ValidationError(
message=f"Invalid selection: {selection}. Select a number between {self.minimum} and {self.maximum}."
)
if not self.allow_duplicates and selections.count(selection) > 1:
raise ValidationError(message=f"Duplicate selection: {selection}")
if isinstance(self.number_selections, int):
if self.number_selections == 1 and len(selections) > 1:
raise ValidationError(message="Invalid selection. Select only 1 item.")
if len(selections) != self.number_selections:
raise ValidationError(
message=f"Select exactly {self.number_selections} items separated by '{self.separator}'"
)
class MultiKeyValidator(Validator):
def __init__(
self,
keys: Sequence[str] | KeysView[str],
number_selections: int | str,
separator: str,
allow_duplicates: bool,
cancel_key: str,
) -> None:
self.keys = keys
self.separator = separator
self.number_selections = number_selections
self.allow_duplicates = allow_duplicates
self.cancel_key = cancel_key
super().__init__()
def validate(self, document):
selections = [key.strip() for key in document.text.strip().split(self.separator)]
if not selections or selections == [""]:
raise ValidationError(message="Select at least 1 item.")
if self.cancel_key in selections and len(selections) == 1:
return
elif self.cancel_key in selections:
raise ValidationError(message="Cancel key must be selected alone.")
for selection in selections:
if selection.upper() not in [key.upper() for key in self.keys]:
raise ValidationError(message=f"Invalid selection: {selection}")
if not self.allow_duplicates and selections.count(selection) > 1:
raise ValidationError(message=f"Duplicate selection: {selection}")
if isinstance(self.number_selections, int):
if self.number_selections == 1 and len(selections) > 1:
raise ValidationError(message="Invalid selection. Select only 1 item.")
if len(selections) != self.number_selections:
raise ValidationError(
message=f"Select exactly {self.number_selections} items separated by '{self.separator}'"
)