Spaces:
Sleeping
Sleeping
| # filename: text_stats.py | |
| import string | |
| import unicodedata | |
| import tiktoken | |
| import os | |
| # -------------------- | |
| # CONFIG | |
| # -------------------- | |
| # -------------------- | |
| # CONFIG | |
| # -------------------- | |
| import sys | |
| if len(sys.argv) > 1: | |
| FILE_PATH = sys.argv[1] | |
| else: | |
| FILE_PATH = r"C:\Users\dcanb\.gemini\antigravity\scratch\wcs3\sessions\capture_20260123_1503_www_vap_org_tr" | |
| N_TOP_BIGGEST = 3 | |
| M_TOP_SMALLEST = 3 | |
| # Filter settings | |
| INCLUDE_TYPES = [".py", ".json", ".md", ".txt", ".js", ".jsonl"] # List of extensions like [".py", ".json"] or ["all"] | |
| EXCLUDE_TYPES = [".pyc", ".png", ".jpg", ".bin"] # List of extensions to skip like [".json"] | |
| MIN_FILE_SIZE = "10b" # e.g. "500b", "1kb", "0b" for no limit | |
| MAX_FILE_SIZE = "100mb" # Skip files larger than this | |
| # Exclude directories | |
| EXCLUDE_DIRS = {".git", "__pycache__", ".idea", "node_modules", ".vscode", "capture", "captures", "logs", "data", "venv", "env", "global_knowledge"} | |
| # -------------------- | |
| # HELPERS | |
| # -------------------- | |
| def parse_size(size_str: str) -> int: | |
| """Parses strings like '1kb', '2mb', '500b' into bytes.""" | |
| size_str = size_str.lower().strip() | |
| if not size_str or size_str == "0b": | |
| return 0 | |
| units = {"kb": 1024, "mb": 1024**2, "gb": 1024**3, "b": 1} | |
| for unit, factor in units.items(): | |
| if size_str.endswith(unit): | |
| try: | |
| number = float(size_str.replace(unit, "").strip()) | |
| return int(number * factor) | |
| except ValueError: | |
| pass | |
| return 0 | |
| def format_bytes(b): | |
| if b < 0: return "0.00 B" | |
| for u in ["B", "KB", "MB", "GB"]: | |
| if b < 1024: return f"{b:.2f} {u}" | |
| b /= 1024 | |
| return f"{b:.2f} TB" | |
| def fmt(n: float | int) -> str: | |
| return f"{n:,.2f}".replace(",", ".") if isinstance(n, float) else f"{n:,}".replace(",", ".") | |
| # -------------------- | |
| # READ FILE / DIRECTORY | |
| # -------------------- | |
| text = "" | |
| file_stats = [] # Store (name, size, tokens) | |
| min_bytes = parse_size(MIN_FILE_SIZE) | |
| if os.path.isfile(FILE_PATH): | |
| print(f"Reading file: {FILE_PATH}") | |
| ext = os.path.splitext(FILE_PATH)[1].lower() | |
| size = os.path.getsize(FILE_PATH) | |
| # Check filters | |
| is_included = "all" in INCLUDE_TYPES or ext in [t.lower() for t in INCLUDE_TYPES] | |
| is_excluded = ext in [t.lower() for t in EXCLUDE_TYPES] | |
| is_big_enough = size >= min_bytes | |
| if is_included and not is_excluded and is_big_enough: | |
| with open(FILE_PATH, "r", encoding="utf-8", errors="ignore") as f: | |
| content = f.read() | |
| text = content | |
| enc = tiktoken.get_encoding("cl100k_base") | |
| tokens = len(enc.encode(content)) | |
| file_stats.append((os.path.basename(FILE_PATH), size, tokens)) | |
| elif os.path.isdir(FILE_PATH): | |
| max_bytes = parse_size(MAX_FILE_SIZE) | |
| print(f"Reading directory: {FILE_PATH}") | |
| print(f"Filters: Include={INCLUDE_TYPES}, Exclude={EXCLUDE_TYPES}") | |
| print(f"Size Constraints: Min={MIN_FILE_SIZE}, Max={MAX_FILE_SIZE}") | |
| print(f"Ignoring Dirs: {EXCLUDE_DIRS}") | |
| file_count = 0 | |
| enc = tiktoken.get_encoding("cl100k_base") | |
| # Walk the directory | |
| for root, dirs, files in os.walk(FILE_PATH): | |
| # Skip common non-code/garbage directories | |
| dirs[:] = [d for d in dirs if d not in EXCLUDE_DIRS] | |
| for file in files: | |
| ext = os.path.splitext(file)[1].lower() | |
| full_path = os.path.join(root, file) | |
| try: | |
| size = os.path.getsize(full_path) | |
| # Filter Logic | |
| is_included = "all" in INCLUDE_TYPES or ext in [t.lower() for t in INCLUDE_TYPES] | |
| is_excluded = ext in [t.lower() for t in EXCLUDE_TYPES] | |
| is_big_enough = size >= min_bytes | |
| is_small_enough = (max_bytes == 0) or (size <= max_bytes) | |
| if is_included and not is_excluded and is_big_enough and is_small_enough: | |
| # Print progress for potentially slow files (e.g. > 100KB) | |
| if size > 100 * 1024: | |
| print(f"Processing large file: {file} ({format_bytes(size)})") | |
| with open(full_path, "r", encoding="utf-8", errors="ignore") as f: | |
| content = f.read() | |
| text += content + "\n" # Add newline between files | |
| tokens = len(enc.encode(content)) | |
| file_stats.append((file, size, tokens)) | |
| file_count += 1 | |
| elif not is_small_enough: | |
| print(f"Skipping too large: {file} ({format_bytes(size)})") | |
| except Exception as e: | |
| print(f"Skipping {file}: {e}") | |
| print(f"Processed {file_count} files.") | |
| else: | |
| print(f"Error: Path not found - {FILE_PATH}") | |
| exit() | |
| lines = text.splitlines() | |
| # -------------------- | |
| # BASIC STATS | |
| # -------------------- | |
| char_count = len(text) | |
| line_count = len(lines) | |
| word_count = len(text.split()) | |
| # -------------------- | |
| # UNICODE STATS | |
| # -------------------- | |
| letters = uppercase = lowercase = accented = 0 | |
| digits = punctuation = symbols = math_symbols = currency_symbols = emojis = whitespace = 0 | |
| punct_set = set(string.punctuation) | |
| for ch in text: | |
| cat = unicodedata.category(ch) | |
| if ch.isalpha(): | |
| letters += 1 | |
| if ch.isupper(): | |
| uppercase += 1 | |
| elif ch.islower(): | |
| lowercase += 1 | |
| if "WITH" in unicodedata.name(ch, ""): | |
| accented += 1 | |
| elif ch.isdigit(): | |
| digits += 1 | |
| elif ch in punct_set: | |
| punctuation += 1 | |
| elif cat.startswith("S"): | |
| symbols += 1 | |
| if cat == "Sm": | |
| math_symbols += 1 | |
| elif cat == "Sc": | |
| currency_symbols += 1 | |
| elif cat == "So": | |
| emojis += 1 | |
| elif ch.isspace(): | |
| whitespace += 1 | |
| # -------------------- | |
| # LINE STATS | |
| # -------------------- | |
| line_lengths = [len(line) for line in lines] | |
| empty_lines = sum(1 for l in lines if l == "") | |
| whitespace_only_lines = sum(1 for l in lines if l.strip() == "" and l != "") | |
| longest_line = max(line_lengths) if lines else 0 | |
| avg_line_len = int(sum(line_lengths) / line_count) if line_count else 0 | |
| # -------------------- | |
| # TOKENIZATION | |
| # -------------------- | |
| enc = tiktoken.get_encoding("cl100k_base") | |
| input_tokens = len(enc.encode(text)) | |
| # -------------------- | |
| # OUTPUT | |
| # -------------------- | |
| print("Stats") | |
| print("-" * 60) | |
| print("Lines:", fmt(line_count)) | |
| print(" Empty:", fmt(empty_lines)) | |
| print(" Whitespace-only:", fmt(whitespace_only_lines)) | |
| print(" Longest line:", fmt(longest_line)) | |
| print(" Avg line length:", fmt(avg_line_len)) | |
| print("-" * 60) | |
| print("Words:", fmt(word_count)) | |
| print("Characters:", fmt(char_count)) | |
| print("Letters:", fmt(letters)) | |
| print(" Uppercase:", fmt(uppercase)) | |
| print(" Lowercase:", fmt(lowercase)) | |
| print(" Accented:", fmt(accented)) | |
| print("Digits:", fmt(digits)) | |
| print("Punctuation:", fmt(punctuation)) | |
| print("Symbols:", fmt(symbols)) | |
| print(" Math:", fmt(math_symbols)) | |
| print(" Currency:", fmt(currency_symbols)) | |
| print("Emojis:", fmt(emojis)) | |
| print("Whitespace chars:", fmt(whitespace)) | |
| print("-" * 60) | |
| print("TikToken tokens (cl100k_base):", fmt(input_tokens)) | |
| # -------------------- | |
| # RANKED FILE STATS | |
| # -------------------- | |
| def format_bytes(b): | |
| for u in ["B", "KB", "MB"]: | |
| if b < 1024: return f"{b:.2f} {u}" | |
| b /= 1024 | |
| if file_stats: | |
| # Sort by size | |
| sorted_by_size = sorted(file_stats, key=lambda x: x[1], reverse=True) | |
| print("\nTop Biggest Files (Size)") | |
| print("-" * 40) | |
| for name, size, tokens in sorted_by_size[:N_TOP_BIGGEST]: | |
| print(f"{name:<25} | {format_bytes(size):>10} | {fmt(tokens):>8} tokens") | |
| print(f"\nTop Smallest Files (Size)") | |
| print("-" * 40) | |
| for name, size, tokens in sorted_by_size[-M_TOP_SMALLEST:]: | |
| # Reverse the smallest list if you want it ascending | |
| pass | |
| # Let's do it properly: | |
| smallest = sorted(file_stats, key=lambda x: x[1]) | |
| for name, size, tokens in smallest[:M_TOP_SMALLEST]: | |
| print(f"{name:<25} | {format_bytes(size):>10} | {fmt(tokens):>8} tokens") | |