Files
AirlockTools/services/selector.py
T
Zarithas 89db386ffe Major refactor: security enhancements, modularization, config integration, reduced Parquet reliance
- Migrated codebase to class-based architecture for better modularity and maintainability
- Introduced system_config.json for centralized configuration (required for runtime)
- Added structured working directories for improved file organization
- Significantly reduced reliance on Parquet; replaced with alternative data handling
- Implemented security improvements across modules
- Several TODOs remain in the main script for future enhancements
- Linter formatting affected readability in some files (e.g., utils); cleanup is on the agenda
2025-10-05 22:20:42 -04:00

121 lines
4.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Copyright (C) 2025 James Brotosky, Brandon Wickline
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import logging
from typing import Any, List, Optional, Union
logger = logging.getLogger(__name__)
class Selector:
@staticmethod
def select_objects(
objects: List[Any],
allow_multiple: bool = False,
prompt_each: bool = False
) -> Union[Optional[Any], List[Any]]:
if not objects:
logger.warning("No objects available for selection.")
return None
# Sort objects alphabetically by their 'name' attribute
sorted_objects = sorted(objects, key=lambda obj: getattr(obj, "name", str(obj)).lower())
# Display in 4 columns with extra spacing
num_columns = 4
rows = (len(sorted_objects) + num_columns - 1) // num_columns
print("\nAvailable Choices:")
for row in range(rows):
line = ""
for col in range(num_columns):
idx = row + col * rows
if idx < len(sorted_objects):
obj = sorted_objects[idx]
name = getattr(obj, "name", str(obj))
line += f"{idx + 1}: {name:<30}"
print(line)
selected = []
if allow_multiple:
while True:
choice = input("Select an object by number (or Q to finish): ").strip().lower()
if choice == "q":
break
try:
index = int(choice)
if 1 <= index <= len(sorted_objects):
obj = sorted_objects[index - 1]
if obj not in selected:
selected.append(obj)
if prompt_each:
logger.info(f"Selected: {getattr(obj, 'name', str(obj))}")
else:
logger.warning("Object already selected.")
else:
logger.warning("Selection out of range. Try again.")
except ValueError:
logger.warning("Invalid input. Enter a number or 'Q' to quit.")
return selected if selected else None
else:
try:
choice = int(input("Select one object by number: "))
if 1 <= choice <= len(sorted_objects):
selected_obj = sorted_objects[choice - 1]
logger.info(f"Selected: {getattr(selected_obj, 'name', str(selected_obj))}")
return selected_obj
else:
logger.warning("Selection out of range.")
except ValueError:
logger.warning("Invalid input.")
return None
@staticmethod
def select_value(
prompt: str,
value_type: type = int,
valid_range: Optional[tuple] = None,
allow_quit: bool = False
) -> Optional[Any]:
while True:
user_input = input(prompt).strip().lower()
if allow_quit and user_input == "q":
logger.info("User opted to quit value selection.")
return None
try:
value = value_type(user_input)
if valid_range:
min_val, max_val = valid_range
if not (min_val <= value <= max_val):
logger.warning(f"Value out of range ({min_val}{max_val}).")
continue
logger.info(f"User selected value: {value}")
return value
except ValueError:
logger.warning(f"Invalid input. Expected a {value_type.__name__}.")
@staticmethod
def confirm(prompt: str = "Are you sure? (Y/N): ") -> bool:
while True:
response = input(prompt).strip().lower()
if response in ["y", "yes"]:
logger.info("User confirmed action.")
return True
elif response in ["n", "no"]:
logger.info("User declined action.")
return False
else:
logger.warning("Invalid confirmation input. Expected 'Y' or 'N'.")