Source code for scaffold_kit.checklist

"""Generates a checklist of files in a directory.

This module scans the current directory and creates a text-based checklist of
all files, excluding those specified in an ignore file (e.g., `.gitignore`).
Each file in the checklist is marked with an '[x]' if it contains content or
'[ ]' if it is empty. The final checklist is saved to a file and printed to
the console.

Usage:
    To run this script, navigate to your project's root directory and execute it
    as a module:

    $ uv run python -m scaffold_kit.checklist
"""

from __future__ import annotations

import os
import sys

from pathlib import Path
from typing import Optional

from scaffold_kit.config import (
    CHECKLIST_DIRECTORY,
    CHECKLIST_FILE,
    IGNORE_FILE,
)
from scaffold_kit.utils.ignore_parser import IgnoreParser
from scaffold_kit.utils.string_utils import slugify


[docs] def file_is_empty(file_path: str) -> bool: """Checks if a file is empty by checking its size. Args: file_path: The path to the file. Returns: True if the file's size is 0 bytes, False otherwise. """ try: # Use os.stat to get file size directly without opening the file. return os.stat(file_path).st_size == 0 except OSError as e: print(f"Error getting file stats for {file_path}: {e}", file=sys.stderr) return False
[docs] def walk_sorted(base: Path, root: Path | None = None) -> list[str]: """Walks a directory tree and returns relative paths in hierarchical order. Directories appear before files within each directory, both sorted case-insensitively. Parent directories are listed before their children. Args: base: The base path to walk. root: The root used to compute relative paths. Defaults to the base. Returns: A list of relative paths as strings in stable, hierarchical order. """ if root is None: root = base results: list[str] = [] # Sort entries: directories first, then files, both alphabetically # (casefold). entries = sorted( base.iterdir(), key=lambda p: (p.is_file(), p.name.casefold()), ) for entry in entries: rel = str(entry.relative_to(root)) results.append(rel) if entry.is_dir(): results.extend(walk_sorted(entry, root)) return results
# pylint: disable=too-many-locals
[docs] def generate_checklist( ignore_file: str = ".gitignore", output_file: str = "checklist.txt", output_dir: Optional[str] = None, ): """Generates a checklist of files and directories in the current directory. The traversal is hierarchical. Within each directory, entries are sorted case-insensitively with directories first and then files. Directories are always marked "[x]". Files are marked "[ ]" if empty and "[x]" otherwise. Paths matching patterns from the ignore file are skipped. The checklist is written to the output location and printed to stdout. Args: ignore_file: Name of the ignore file with patterns (e.g., ".gitignore"). output_file: Name of the output checklist file. output_dir: Directory for the output file. Created if it does not exist. Raises: OSError: If writing the checklist file fails due to I/O errors. """ parser = IgnoreParser.from_file(ignore_file) # Walk from current directory, hierarchical & sorted all_paths = walk_sorted(Path.cwd()) lines = [] for path in all_paths: p = Path(path) if parser.matches(path): continue if p.is_dir(): # Directories always non-empty lines.append(f"[x] {path}/") else: mark = " " if file_is_empty(str(p)) else "x" lines.append(f"[{mark}] {path}") content = "\n".join(lines) if output_dir and not os.path.exists(output_dir): os.makedirs(output_dir) print(f"Directory '{output_dir}' created successfully!") output_filename = ( f"{slugify(Path(output_file).stem)}{Path(output_file).suffix}" ) output_path = Path(output_dir) / output_filename print(f"Writing {output_path}...") with open(output_path, "w", encoding="utf-8") as f: f.write(content) print(f"{content}\n") print(f"\nSuccessfully wrote checklist to {output_path}")
[docs] def main(): """Main entry point to run the checklist generation process.""" generate_checklist( ignore_file=IGNORE_FILE, output_file=CHECKLIST_FILE, output_dir=CHECKLIST_DIRECTORY, )
if __name__ == "__main__": main()