Skip to content

Commit b35baa0

Browse files
FEAT: Adding support for multiple icons per game entry in ./main.py
1 parent 1ba12b7 commit b35baa0

1 file changed

Lines changed: 61 additions & 45 deletions

File tree

Games Collection Manager/main.py

Lines changed: 61 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ class BackgroundColors: # Colors for the terminal
110110
FIXED_METADATA_BLOCK_STRIPPED = tuple(line.strip() for line in FIXED_METADATA_BLOCK) # Stripped version for safe parser comparisons
111111

112112
# Regex Constants:
113-
GAME_LINE_REGEX = re.compile(r"^-\s+(.+?)\s+(\d{4})\.\s*(✅|❓|📀|📦)?$") # Pattern matching a valid normalized game line
113+
GAME_LINE_REGEX = re.compile(r"^-\s+(.+?)\s+(\d{4})\.\s*((?:✅|❓|📀|🧹|📦)(?:\s+(?:✅|❓|📀|🧹|📦))*)?$") # Pattern matching a valid normalized game line with optional multi-icon suffix
114114
YEAR_EXTRACT_REGEX = re.compile(r"(\d{4})") # Pattern extracting a 4-digit year from raw text
115115

116116
# Functions Definitions:
@@ -450,22 +450,23 @@ def normalize_game_line(raw_line: str, filepath: str, console_name: str) -> str:
450450
body = body[1:].strip() # Remove "-" prefix and trim
451451

452452

453-
icon = "" # Initialize icon as absent
454-
if body.endswith(ICON_OWNED): # Detect trailing owned icon
455-
icon = ICON_OWNED # Record owned icon
456-
body = body[: -len(ICON_OWNED)].strip() # Strip icon from body and trim
457-
elif body.endswith(ICON_MAYBE): # Detect trailing maybe icon
458-
icon = ICON_MAYBE # Record maybe icon
459-
body = body[: -len(ICON_MAYBE)].strip() # Strip icon from body and trim
460-
elif body.endswith(ICON_DAMAGED): # Detect trailing damaged icon
461-
icon = ICON_DAMAGED # Record damaged icon
462-
body = body[: -len(ICON_DAMAGED)].strip() # Strip icon from body and trim
463-
elif body.endswith(ICON_BOX_DAMAGED): # Detect trailing box damaged icon
464-
icon = ICON_BOX_DAMAGED # Record box damaged icon
465-
body = body[: -len(ICON_BOX_DAMAGED)].strip() # Strip icon from body and trim
466-
elif body.endswith(ICON_NEEDS_CLEANING): # Detect trailing needs cleaning icon
467-
icon = ICON_NEEDS_CLEANING # Record needs cleaning icon
468-
body = body[: -len(ICON_NEEDS_CLEANING)].strip() # Strip icon from body and trim
453+
reversed_icons = [] # Collect detected trailing icons from right to left
454+
while True: # Keep peeling recognized trailing icons until none remain
455+
matched_icon = None # Track whether the current pass found a trailing icon
456+
457+
for icon in VALID_ICONS: # Try each supported icon as a trailing suffix
458+
if body.endswith(icon): # Verify icon appears at the current line tail
459+
prefix = body[: -len(icon)] # Extract content before the candidate icon
460+
if prefix == "" or prefix.endswith(" "): # Verify icon token is separated from previous text
461+
matched_icon = icon # Record matched trailing icon token
462+
body = prefix.strip() # Remove matched icon token and trim remaining body
463+
reversed_icons.append(icon) # Store icon in reverse order as extracted
464+
break # Stop current icon scan after first valid match
465+
466+
if matched_icon is None: # Stop when no additional trailing icon is found
467+
break # Exit peel loop
468+
469+
icons = list(reversed(reversed_icons)) # Restore left-to-right icon order
469470

470471
if body.endswith("."): # Strip trailing period to isolate name+year region
471472
body = body[:-1].strip() # Remove period and trim
@@ -485,34 +486,49 @@ def normalize_game_line(raw_line: str, filepath: str, console_name: str) -> str:
485486
print(f"{BackgroundColors.YELLOW}Warning: Missing game name in entry — skipping. File: {BackgroundColors.CYAN}{filepath}{BackgroundColors.YELLOW}, Console: {BackgroundColors.CYAN}{console_name}{BackgroundColors.YELLOW}, Line: {BackgroundColors.CYAN}{raw_line.strip()}{Style.RESET_ALL}") # Log warning with context
486487
return "" # Return empty to signal skip
487488

488-
if icon: # Build normalized line with icon when present
489-
return f"- {game_name} {year}. {icon}" # Return canonical format with icon
489+
if icons: # Build normalized line with icon suffix when present
490+
return f"- {game_name} {year}. {' '.join(icons)}" # Return canonical format with multi-icon suffix
490491
return f"- {game_name} {year}." # Return canonical format without icon
491492

492493
except Exception as e: # Catch unexpected errors during normalization
493494
print(f"{BackgroundColors.RED}Error normalizing game line {BackgroundColors.CYAN}{raw_line.strip()}{BackgroundColors.RED}: {e}{Style.RESET_ALL}") # Log error
494495
return "" # Return empty to signal skip
495496

496497

498+
def parse_game_icons(normalized_line: str) -> list:
499+
"""
500+
Extract all icons from an already-normalized game line.
501+
502+
:param normalized_line: A normalized game line in canonical format.
503+
:return: List of icon strings in appearance order, or empty list if absent.
504+
"""
505+
506+
try: # Wrap extraction logic to ensure safe parsing behavior
507+
if "." not in normalized_line: # Verify canonical delimiter exists before parsing tail tokens
508+
return [] # Return empty when no canonical suffix can exist
509+
510+
suffix = normalized_line.split(".", 1)[1].strip() # Extract suffix region after canonical year delimiter
511+
512+
if suffix == "": # Verify suffix contains at least one token
513+
return [] # Return empty when no icon suffix is present
514+
515+
tokens = suffix.split() # Split suffix into whitespace-separated tokens
516+
return [token for token in tokens if token in VALID_ICONS] # Return all valid icon tokens preserving order
517+
518+
except Exception: # Catch unexpected parsing errors
519+
return [] # Return empty list on failure
520+
521+
497522
def parse_game_icon(normalized_line: str) -> str:
498523
"""
499-
Extract the icon from an already-normalized game line.
524+
Extract the last icon from an already-normalized game line.
500525
501526
:param normalized_line: A normalized game line in canonical format.
502-
:return: The icon string (ICON_OWNED or ICON_MAYBE) or empty string if absent.
527+
:return: The last icon string if present, otherwise empty string.
503528
"""
504529

505-
if normalized_line.endswith(ICON_OWNED): # Detect owned icon at line end
506-
return ICON_OWNED # Return owned icon
507-
if normalized_line.endswith(ICON_MAYBE): # Detect maybe icon at line end
508-
return ICON_MAYBE # Return maybe icon
509-
if normalized_line.endswith(ICON_DAMAGED): # Detect damaged/needs fixing icon at line end
510-
return ICON_DAMAGED # Return damaged icon
511-
if normalized_line.endswith(ICON_BOX_DAMAGED): # Detect box damaged icon at line end
512-
return ICON_BOX_DAMAGED # Return box damaged icon
513-
if normalized_line.endswith(ICON_NEEDS_CLEANING): # Detect needs cleaning icon at line end
514-
return ICON_NEEDS_CLEANING # Return needs cleaning icon
515-
return "" # Return empty when no icon is present
530+
icons = parse_game_icons(normalized_line) # Parse all icon tokens for this normalized line
531+
return icons[-1] if icons else "" # Return last icon when present
516532

517533

518534
def parse_console_sections(lines: list, filepath: str) -> list:
@@ -604,8 +620,8 @@ def compute_console_counters(games: list) -> tuple:
604620
"""
605621

606622
total = len(games) # Total is the count of all valid game entries
607-
# Only count as owned if the icon is ICON_OWNED, ICON_DAMAGED, ICON_NEEDS_CLEANING, or ICON_BOX_DAMAGED
608-
owned = sum(1 for line in games if parse_game_icon(line) in (ICON_OWNED, ICON_DAMAGED, ICON_NEEDS_CLEANING, ICON_BOX_DAMAGED))
623+
owned_icons = (ICON_OWNED, ICON_DAMAGED, ICON_NEEDS_CLEANING, ICON_BOX_DAMAGED) # Define icon set that marks a game as owned
624+
owned = sum(1 for line in games if any(icon in owned_icons for icon in parse_game_icons(line))) # Count a game as owned when any ownership icon exists
609625
return owned, total # Return computed counters as a tuple
610626

611627

@@ -648,17 +664,17 @@ def format_txt_output(sections: list) -> str:
648664
total_needs_cleaning_icon = 0
649665
for section in sorted_sections:
650666
for line in section['games']:
651-
icon = parse_game_icon(line)
652-
if icon == ICON_OWNED:
653-
total_owned_icon += 1
654-
elif icon == ICON_DAMAGED:
655-
total_damaged_icon += 1
656-
elif icon == ICON_BOX_DAMAGED:
657-
total_box_damaged_icon += 1
658-
elif icon == ICON_MAYBE:
659-
total_unsure_icon += 1
660-
elif icon == ICON_NEEDS_CLEANING:
661-
total_needs_cleaning_icon += 1
667+
for icon in parse_game_icons(line): # Count every icon token found on the game line
668+
if icon == ICON_OWNED:
669+
total_owned_icon += 1
670+
elif icon == ICON_DAMAGED:
671+
total_damaged_icon += 1
672+
elif icon == ICON_BOX_DAMAGED:
673+
total_box_damaged_icon += 1
674+
elif icon == ICON_MAYBE:
675+
total_unsure_icon += 1
676+
elif icon == ICON_NEEDS_CLEANING:
677+
total_needs_cleaning_icon += 1
662678

663679
output_lines.append(title_line)
664680
output_lines.append("") # Insert blank line between title and owned/total counters block

0 commit comments

Comments
 (0)