Files
Claude 529f97e87e Add module-level docstrings to all modules (closes #18)
Added one-line module docstrings to config.py, file_utils.py,
load_description_node.py, and random_line_from_text_node.py to
improve navigability and IDE support.

https://claude.ai/code/session_01JBWyFr5shvPXUPmZkX3Nhg
2026-03-23 18:18:49 +00:00

84 lines
3.1 KiB
Python

"""File I/O utilities shared across ComfyUI Feroc custom nodes."""
import logging
import os
import random
from typing import Optional
try:
from .config import DESCRIPTION_PATH, TEXT_FILE_EXTENSION, FALLBACK_FILE
except ImportError:
from config import DESCRIPTION_PATH, TEXT_FILE_EXTENSION, FALLBACK_FILE
logger = logging.getLogger(__name__)
def read_text_file(path: str, encoding: str = 'utf-8') -> str:
"""Read a text file and return its content. Returns empty string on error."""
try:
with open(path, 'r', encoding=encoding) as f:
return f.read()
except FileNotFoundError:
logger.warning("File not found: %s", path)
return ""
except OSError as e:
logger.error("Error reading file %s: %s", path, e)
return ""
def read_random_line(path: str, rng: random.Random) -> Optional[str]:
"""Return a random non-empty line from a file. Returns None if file is empty or missing."""
try:
with open(path, 'r', encoding='utf-8') as f:
lines = [line.rstrip('\n') for line in f if line.strip()]
if lines:
return rng.choice(lines)
logger.warning("No non-empty lines found in: %s", path)
return None
except FileNotFoundError:
logger.warning("File not found, skipping: %s", path)
return None
except OSError as e:
logger.error("Error reading file %s: %s", path, e)
return None
def _ensure_example_exists() -> None:
"""Create examples/example.txt if there are no .txt files anywhere under descriptions."""
for root, dirs, files in os.walk(DESCRIPTION_PATH):
if any(f.endswith(TEXT_FILE_EXTENSION) for f in files):
return
example_dir = os.path.join(DESCRIPTION_PATH, "examples")
os.makedirs(example_dir, exist_ok=True)
placeholder_path = os.path.join(example_dir, "example.txt")
if not os.path.exists(placeholder_path):
with open(placeholder_path, 'w', encoding='utf-8') as f:
f.write("Put your description files in subdirectories under 'descriptions'.")
def list_all_description_files() -> list[str]:
"""Return a sorted list of all .txt files relative to DESCRIPTION_PATH, including extension."""
results = []
for root, dirs, files in os.walk(DESCRIPTION_PATH):
for file in files:
if file.lower().endswith(TEXT_FILE_EXTENSION):
rel_dir = os.path.relpath(root, DESCRIPTION_PATH)
if rel_dir == ".":
entry = file
else:
rel_dir_fixed = rel_dir.replace("\\", "/")
entry = rel_dir_fixed + "/" + file
results.append(entry)
if not results:
_ensure_example_exists()
return [FALLBACK_FILE]
return sorted(results)
def normalize_description_path(file_path: str) -> str:
"""Normalize a file path relative to DESCRIPTION_PATH, appending .txt extension."""
s = str(file_path).strip().replace("\\", "/")
if s.endswith(TEXT_FILE_EXTENSION):
s = s[:-len(TEXT_FILE_EXTENSION)]
parts = [p for p in s.split("/") if p]
return os.path.join(DESCRIPTION_PATH, *parts) + TEXT_FILE_EXTENSION