diff --git a/PYTHON/downloadCats/generate_cats.py b/PYTHON/downloadCats/generate_cats.py index cd11851..8af095a 100644 --- a/PYTHON/downloadCats/generate_cats.py +++ b/PYTHON/downloadCats/generate_cats.py @@ -1,9 +1,12 @@ import json +import logging import os from pathlib import Path import requests +logging.basicConfig(level=logging.INFO) + requests_send = 0 while requests_send < 90: res = requests.get("https://api.thecatapi.com/v1/images/search?limit=100&api_key=") @@ -28,7 +31,7 @@ while requests_send < 90: with open(image_path, "wb") as file: file.write(response.content) - print(f"Saved {url} as {image_path}") + logging.info(f"Saved {url} as {image_path}") except requests.exceptions.RequestException as e: - print(f"Failed to download {url}: {e}") + logging.exception(f"Failed to download {url}: {e}") diff --git a/PYTHON/extractLinks/main.py b/PYTHON/extractLinks/main.py index 7769548..6923b73 100755 --- a/PYTHON/extractLinks/main.py +++ b/PYTHON/extractLinks/main.py @@ -12,9 +12,12 @@ from __future__ import annotations import argparse from html.parser import HTMLParser +import logging import os from urllib.parse import urlparse +logging.basicConfig(level=logging.INFO) + class _HrefParser(HTMLParser): def __init__(self) -> None: @@ -84,7 +87,7 @@ def main() -> int: with open(out_path, "w", encoding="utf-8") as f: f.writelines(f"*{host}*\n" for host in hosts) - print(f"Wrote {len(hosts)} host(s) to {out_path}") + logging.info(f"Wrote {len(hosts)} host(s) to {out_path}") return 0 diff --git a/PYTHON/keyboardCoop/main.py b/PYTHON/keyboardCoop/main.py index 8e514a8..c12a9c9 100644 --- a/PYTHON/keyboardCoop/main.py +++ b/PYTHON/keyboardCoop/main.py @@ -1,10 +1,13 @@ import json +import logging import os import random import sys import pygame +logging.basicConfig(level=logging.INFO) + # Initialize Pygame pygame.init() @@ -95,7 +98,9 @@ class KeyboardCoopGame: # Convert to set for faster lookup (we only need the keys) return set(dictionary_data.keys()) except FileNotFoundError: - print("Warning: words_dictionary.json not found, using fallback dictionary") + logging.warning( + "words_dictionary.json not found, using fallback dictionary" + ) # Fallback to a smaller dictionary if file not found return { "cat", @@ -149,9 +154,8 @@ class KeyboardCoopGame: "good", } except json.JSONDecodeError: - print( - "Warning: Error reading words_dictionary.json, " - "using fallback dictionary" + logging.warning( + "Error reading words_dictionary.json, " "using fallback dictionary" ) return { "cat", diff --git a/PYTHON/lichess_bot/tools/generate_blunder_tests.py b/PYTHON/lichess_bot/tools/generate_blunder_tests.py index 81e431a..044fdf9 100755 --- a/PYTHON/lichess_bot/tools/generate_blunder_tests.py +++ b/PYTHON/lichess_bot/tools/generate_blunder_tests.py @@ -35,6 +35,7 @@ from __future__ import annotations from dataclasses import dataclass import io +import logging import os import re import sys @@ -42,6 +43,8 @@ import sys import chess import chess.pgn +logging.basicConfig(level=logging.INFO) + @dataclass class Blunder: @@ -316,30 +319,34 @@ def _process_single_log(log_path: str) -> int: with open(log_path, encoding="utf-8") as fh: text = fh.read() except FileNotFoundError: - print(f"Log file not found: {log_path}") + logging.exception(f"Log file not found: {log_path}") return 2 try: blunders = parse_columns_for_blunders(text) except Exception as e: - print(f"Error parsing Columns in {os.path.basename(log_path)}: {e}") + logging.exception(f"Error parsing Columns in {os.path.basename(log_path)}: {e}") return 2 if not blunders: - print(f"No blunders found in Columns section: {os.path.basename(log_path)}") + logging.warning( + f"No blunders found in Columns section: {os.path.basename(log_path)}" + ) return 1 pgn_text = extract_pgn(text) if not pgn_text: - print(f"No PGN section found: {os.path.basename(log_path)}") + logging.warning(f"No PGN section found: {os.path.basename(log_path)}") return 1 try: cases = fen_and_uci_for_blunders(pgn_text, blunders) except Exception as e: - print(f"Error converting SAN to UCI in {os.path.basename(log_path)}: {e}") + logging.exception( + f"Error converting SAN to UCI in {os.path.basename(log_path)}: {e}" + ) return 2 if not cases: - print( + logging.warning( f"Failed to reconstruct any blunder positions " f"from PGN: {os.path.basename(log_path)}" ) @@ -355,7 +362,7 @@ def _process_single_log(log_path: str) -> int: ) unified = os.path.abspath(unified) added = append_cases_to_unified_test(unified, cases) - print( + logging.info( f"Appended {added} new blunder checks to " f"{os.path.relpath(unified)} (game {game_id})." ) @@ -369,7 +376,7 @@ def main(argv: list[str]) -> int: # No argument: process all logs in past_games if len(argv) == 1: if not os.path.isdir(past_dir): - print(f"No past_games directory found at {past_dir}") + logging.error(f"No past_games directory found at {past_dir}") return 2 logs = [ os.path.join(past_dir, name) @@ -377,7 +384,7 @@ def main(argv: list[str]) -> int: if re.match(r"lichess_bot_game_[A-Za-z0-9]+\.log$", name) ] if not logs: - print(f"No logs found in {past_dir}") + logging.warning(f"No logs found in {past_dir}") return 1 # Sort by mtime ascending for determinism logs.sort(key=lambda p: os.path.getmtime(p)) @@ -386,7 +393,7 @@ def main(argv: list[str]) -> int: rc = _process_single_log(lp) if rc == 0: ok += 1 - print( + logging.info( f"Processed {len(logs)} logs from {past_dir}, " f"succeeded: {ok}, failed: {len(logs) - ok}" ) @@ -407,7 +414,7 @@ def main(argv: list[str]) -> int: candidate_path = maybe if not candidate_path: - print("Usage: generate_blunder_tests.py [|]") + logging.info("Usage: generate_blunder_tests.py [|]") return 2 return _process_single_log(candidate_path) diff --git a/PYTHON/randomJPG/generateJpeg.py b/PYTHON/randomJPG/generateJpeg.py index a8a19b3..6a62abc 100644 --- a/PYTHON/randomJPG/generateJpeg.py +++ b/PYTHON/randomJPG/generateJpeg.py @@ -1,10 +1,13 @@ import argparse from datetime import datetime +import logging import os import random from PIL import Image +logging.basicConfig(level=logging.INFO) + def generate_bloated_jpeg( size, color_list, block_size, output_path, quality, image_index, folder @@ -120,13 +123,15 @@ if __name__ == "__main__": folder = f"generated_images_{timestamp}" # Display used parameters - print(f"Generating {args.num_images} image(s) with the following parameters:") - print(f" Size: {args.size}") - print(f" Colors: {args.colors}") - print(f" Block size: {args.block_size}") - print(f" Base output path: {args.output_path}") - print(f" Quality: {args.quality}") - print(f" Output folder: {folder}") + logging.info( + f"Generating {args.num_images} image(s) with the following parameters:" + ) + logging.info(f" Size: {args.size}") + logging.info(f" Colors: {args.colors}") + logging.info(f" Block size: {args.block_size}") + logging.info(f" Base output path: {args.output_path}") + logging.info(f" Quality: {args.quality}") + logging.info(f" Output folder: {folder}") # Generate the specified number of images for i in range(1, args.num_images + 1): @@ -139,4 +144,4 @@ if __name__ == "__main__": i, folder, ) - print(f"Image {i} saved to {os.path.abspath(output_path)}") + logging.info(f"Image {i} saved to {os.path.abspath(output_path)}") diff --git a/PYTHON/randomize_numbers/random_digits.py b/PYTHON/randomize_numbers/random_digits.py index 3e98ed1..bf1727b 100644 --- a/PYTHON/randomize_numbers/random_digits.py +++ b/PYTHON/randomize_numbers/random_digits.py @@ -1,8 +1,11 @@ import contextlib +import logging import random import re import sys +logging.basicConfig(level=logging.INFO) + def randomize_numbers(numbers, min_percentage=1, max_percentage=20): randomized_numbers = [] @@ -38,7 +41,7 @@ def parse_input(input_string): if __name__ == "__main__": if len(sys.argv) < 2: - print( + logging.info( "Usage: python random_digits.py ... " "[min_percentage max_percentage]" ) @@ -67,9 +70,9 @@ if __name__ == "__main__": format_str = f".{decimal_counts[i]}f" formatted_numbers.append(float(format(num, format_str))) - print("Original numbers:", numbers) - print("Randomized numbers:", formatted_numbers) + logging.info(f"Original numbers: {numbers}") + logging.info(f"Randomized numbers: {formatted_numbers}") except ValueError as e: - print(f"Error: {e}") - print("Please provide valid numbers and percentages.") + logging.exception(f"Error: {e}") + logging.exception("Please provide valid numbers and percentages.") sys.exit(1) diff --git a/PYTHON/scapeWebsite/scrape_comics.py b/PYTHON/scapeWebsite/scrape_comics.py index 3180fb1..5031911 100644 --- a/PYTHON/scapeWebsite/scrape_comics.py +++ b/PYTHON/scapeWebsite/scrape_comics.py @@ -1,4 +1,5 @@ import argparse +import logging import os from urllib.parse import urlparse @@ -6,6 +7,8 @@ import requests from selenium import webdriver from selenium.webdriver.common.by import By +logging.basicConfig(level=logging.INFO) + # Initialize argument parser to accept the website URL as an argument parser = argparse.ArgumentParser(description="Download images from a comic website.") parser.add_argument( @@ -18,7 +21,7 @@ driver = webdriver.Chrome() # Open the website from the passed argument url = args.url -print(f"Opening the website: {url}") +logging.info(f"Opening the website: {url}") driver.get(url) @@ -29,13 +32,13 @@ def download_image(url): # Check if the image already exists if os.path.exists(image_name): - print(f"Image {image_name} already exists, skipping download.") + logging.info(f"Image {image_name} already exists, skipping download.") return False - print(f"Downloading image from URL: {url}") + logging.info(f"Downloading image from URL: {url}") img_data = requests.get(url).content with open(image_name, "wb") as handler: handler.write(img_data) - print(f"Image {image_name} downloaded successfully") + logging.info(f"Image {image_name} downloaded successfully") return True @@ -43,14 +46,14 @@ def download_image(url): count = 1 while True: - print(f"Processing image {count}...") + logging.info(f"Processing image {count}...") # Find the image element by its ID image_element = driver.find_element(By.ID, "cc-comic") # Get the image URL from the 'src' attribute image_url = image_element.get_attribute("src") - print(f"Found image URL: {image_url}") + logging.info(f"Found image URL: {image_url}") # Download the image if it doesn't already exist if download_image(image_url): @@ -58,7 +61,7 @@ while True: # Try to find the 'Next' button by its class try: - print("Clicking the 'Next' button to load the next image...") + logging.info("Clicking the 'Next' button to load the next image...") next_button = driver.find_element(By.CSS_SELECTOR, "a.cc-next") # Navigate to the URL in the 'href' of the next button @@ -67,9 +70,9 @@ while True: except: # If the 'Next' button is not found, it means we've reached the last image - print("No 'Next' button found. Reached the end of images.") + logging.info("No 'Next' button found. Reached the end of images.") break # Close the browser -print("All images processed, closing the browser.") +logging.info("All images processed, closing the browser.") driver.quit() diff --git a/PYTHON/screen_locker/screen_lock.py b/PYTHON/screen_locker/screen_lock.py index d1b3390..d2d4043 100755 --- a/PYTHON/screen_locker/screen_lock.py +++ b/PYTHON/screen_locker/screen_lock.py @@ -5,10 +5,13 @@ Requires user to log their workout to unlock the screen. from datetime import datetime import json +import logging import os import sys import tkinter as tk +logging.basicConfig(level=logging.INFO) + class ScreenLocker: def __init__(self, demo_mode=True): @@ -18,7 +21,7 @@ class ScreenLocker: # Check if already logged today if self.has_logged_today(): - print("Workout already logged today. Skipping screen lock.") + logging.info("Workout already logged today. Skipping screen lock.") sys.exit(0) self.root = tk.Tk() @@ -632,7 +635,7 @@ class ScreenLocker: with open(self.log_file, "w") as f: json.dump(logs, f, indent=2) except OSError as e: - print(f"Warning: Could not save workout log: {e}") + logging.warning(f"Could not save workout log: {e}") def close(self): self.root.destroy() diff --git a/PYTHON/stockfish_analysis/analyze_chess_game.py b/PYTHON/stockfish_analysis/analyze_chess_game.py index 617512f..8894790 100755 --- a/PYTHON/stockfish_analysis/analyze_chess_game.py +++ b/PYTHON/stockfish_analysis/analyze_chess_game.py @@ -24,6 +24,7 @@ from __future__ import annotations import argparse import contextlib import io +import logging import multiprocessing import os import re @@ -39,10 +40,8 @@ try: import chess.engine import chess.pgn except Exception: # pragma: no cover - print("Missing dependency. Please install python-chess:", file=sys.stderr) - print( - " pip install -r PYTHON/stockfish_analysis/requirements.txt", file=sys.stderr - ) + logging.exception("Missing dependency. Please install python-chess:") + logging.exception(" pip install -r PYTHON/stockfish_analysis/requirements.txt") raise @@ -202,6 +201,7 @@ def _auto_hash_mb(threads_wanted: int, engine_options) -> int: def main(): + logging.basicConfig(level=logging.INFO, format="%(message)s") ap = argparse.ArgumentParser( description="Analyze a chess game's moves with Stockfish and rate each move." ) @@ -256,7 +256,7 @@ def main(): args = ap.parse_args() if not os.path.isfile(args.file): - print(f"Input not found: {args.file}", file=sys.stderr) + logging.error(f"Input not found: {args.file}") sys.exit(1) with open(args.file, encoding="utf-8", errors="replace") as f: @@ -264,22 +264,21 @@ def main(): pgn_text = extract_pgn_text(raw) if not pgn_text: - print("Could not locate PGN text in the file.", file=sys.stderr) + logging.error("Could not locate PGN text in the file.") sys.exit(2) game = chess.pgn.read_game(io.StringIO(pgn_text)) if game is None: - print("Failed to parse PGN.", file=sys.stderr) + logging.error("Failed to parse PGN.") sys.exit(3) # Prepare engine try: engine = chess.engine.SimpleEngine.popen_uci([args.engine]) except FileNotFoundError: - print(f"Could not launch engine at: {args.engine}", file=sys.stderr) - print( - "Ensure Stockfish is installed and in PATH, or specify with --engine.", - file=sys.stderr, + logging.exception(f"Could not launch engine at: {args.engine}") + logging.exception( + "Ensure Stockfish is installed and in PATH, or specify with --engine." ) sys.exit(4) @@ -348,13 +347,13 @@ def main(): limit = chess.engine.Limit(time=max(0.05, args.time)) board = game.board() - print("Game:") + logging.info("Game:") white = game.headers.get("White", "White") black = game.headers.get("Black", "Black") result = game.headers.get("Result", "*") - print(f" {white} vs {black} Result: {result}") - print() - print( + logging.info(f" {white} vs {black} Result: {result}") + logging.info("") + logging.info( "Columns: ply side move played_eval best_eval loss class best_suggestion" ) # Brief performance summary (best-effort) @@ -371,12 +370,14 @@ def main(): except Exception: hash_show = None if hash_show is not None: - print( + logging.info( f"Using engine options: Threads={thr_show}, " f"Hash={hash_show} MB, MultiPV={effective_mpv}" ) else: - print(f"Using engine options: Threads={thr_show}, " f"MultiPV={effective_mpv}") + logging.info( + f"Using engine options: Threads={thr_show}, MultiPV={effective_mpv}" + ) ply = 1 try: @@ -385,7 +386,7 @@ def main(): if args.last_move_only: # Walk to the last move in the main line and analyze only that ply. if not node.variations: - print("No moves found in the game.") + logging.warning("No moves found in the game.") else: while node.variations: move_node = node.variations[0] @@ -486,7 +487,7 @@ def main(): classification = classify_cp_loss(cp_loss) side = "W" if mover_white else "B" - print( + logging.info( f"{ply:>3} {side} {san:<8} " f"{fmt_eval(played_cp, played_mate):>10} " f"{fmt_eval(best_cp, best_mate):>9} " @@ -601,7 +602,7 @@ def main(): classification = classify_cp_loss(cp_loss) side = "W" if mover_white else "B" - print( + logging.info( f"{ply:>3} {side} {san:<8} " f"{fmt_eval(played_cp, played_mate):>10} " f"{fmt_eval(best_cp, best_mate):>9} " diff --git a/PYTHON/tagDivider/tagDivider.py b/PYTHON/tagDivider/tagDivider.py index 7564720..f098628 100644 --- a/PYTHON/tagDivider/tagDivider.py +++ b/PYTHON/tagDivider/tagDivider.py @@ -1,3 +1,4 @@ +import logging import os # for: os.getcwd; os.mkdir; os.listdir; from os import path # for: os.path.abspath import shutil # for: shutil.move @@ -6,6 +7,8 @@ import shutil # for: shutil.move # cv2.waitKey; cv2.destroyAllWindows; cv2.IMREAD_COLOR import cv2 +logging.basicConfig(level=logging.INFO) + IMAGE_EXTENSION = ( ".bmp", ".dib", @@ -54,7 +57,7 @@ for filename in os.listdir( if (filename.lower()).endswith( IMAGE_EXTENSION ): # If the file name ends with image extension - print(filename) + logging.info(filename) image = cv2.imread(filename, cv2.IMREAD_COLOR) window_name = filename.split(".")[0] cv2.namedWindow(window_name) # Window name is the same as image file name diff --git a/poker-modifier-app/poker_modifier_app.py b/poker-modifier-app/poker_modifier_app.py index fb43e81..5f155e4 100644 --- a/poker-modifier-app/poker_modifier_app.py +++ b/poker-modifier-app/poker_modifier_app.py @@ -1,7 +1,10 @@ +import logging import random import tkinter as tk from tkinter import ttk +logging.basicConfig(level=logging.INFO) + class PokerModifierApp: def __init__(self): @@ -792,21 +795,21 @@ class PokerModifierApp: self.debug_mode = self.debug_var.get() if self.debug_mode: self.force_endgame_button.pack(side=tk.LEFT, padx=(0, 10)) - print("🐛 Debug mode enabled") + logging.debug("Debug mode enabled") else: self.force_endgame_button.pack_forget() self.force_endgame = False - print("🐛 Debug mode disabled") + logging.debug("Debug mode disabled") def toggle_force_endgame(self): """Toggle forced endgame mode for testing.""" self.force_endgame = not self.force_endgame if self.force_endgame: self.force_endgame_button.config(text="Stop Force Endgame", bg="#4CAF50") - print("🎯 Forcing endgame modifiers") + logging.debug("Forcing endgame modifiers") else: self.force_endgame_button.config(text="Force Endgame", bg="#ff6b6b") - print("🎯 Normal modifier selection restored") + logging.debug("Normal modifier selection restored") def is_endgame(self): """Determine if we're in endgame phase.""" @@ -957,7 +960,7 @@ class PokerModifierApp: if self.debug_mode: self.force_endgame_button.config(text="Force Endgame", bg="#ff6b6b") - print("🔄 Game reset to initial state") + logging.info("Game reset to initial state") def add_modifier(self, name, description): """Add a new modifier to the list.""" @@ -985,13 +988,17 @@ class PokerModifierApp: def run(self): """Start the application.""" - print("🃏 Texas Hold'em Modifier App started!") - print("Available methods: app.get_stats(), app.add_modifier(name, description)") - print("Debug features: Toggle debug mode to access force endgame controls") - print(f"Default game length: {self.total_game_rounds} rounds") + logging.info("Texas Hold'em Modifier App started!") + logging.info( + "Available methods: app.get_stats(), app.add_modifier(name, description)" + ) + logging.info( + "Debug features: Toggle debug mode to access force endgame controls" + ) + logging.info(f"Default game length: {self.total_game_rounds} rounds") endgame_pct = int(self.endgame_threshold * 100) endgame_rounds = int(self.total_game_rounds * self.endgame_threshold) - print(f"Endgame threshold: {endgame_pct}% ({endgame_rounds} rounds)") + logging.info(f"Endgame threshold: {endgame_pct}% ({endgame_rounds} rounds)") self.root.mainloop() diff --git a/pyproject.toml b/pyproject.toml index 2c78c4d..5fbc6fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,6 @@ requires-python = ">=3.10" # RUFF - Extremely fast Python linter and formatter (written in Rust) # ============================================================================ [tool.ruff] -line-length = 88 target-version = "py310" # Include all Python files include = ["*.py", "**/*.py"] @@ -28,14 +27,15 @@ exclude = [ select = ["ALL"] # Ignores for rules that are too strict for this mixed script repository ignore = [ + # D203 vs D211 conflict - we use D211 (no blank line before class docstring) "D203", # 1 blank line required before class docstring (conflicts with D211) + # D212 vs D213 conflict - we use D212 (summary on first line after """) "D213", # Multi-line docstring summary should start at second line (conflicts with D212) "COM812", # Trailing comma missing (conflicts with formatter) "ISC001", # Implicit string concatenation (conflicts with formatter) "ANN101", # Missing type annotation for self (deprecated) "ANN102", # Missing type annotation for cls (deprecated) # Relaxed for script-heavy repository - "T201", # print found - these are scripts, print is expected "D100", # Missing docstring in public module - scripts don't need module docstrings "D101", # Missing docstring in public class - relaxed "D102", # Missing docstring in public method - relaxed