163 lines
5.9 KiB
Python
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}'"
|
|
)
|