Development Guide¶
This guide is for developers who want to contribute to or extend Recursivist.
Setting Up a Development Environment¶
Prerequisites¶
- Python 3.10 or higher
- Git
- Optionally uv for fast environment and dependency management
Clone and Install¶
git clone https://github.com/ArmaanjeetSandhu/recursivist.git
cd recursivist
# Create and activate a virtual environment (uv shown; venv works too)
uv venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
# Install in editable mode with development dependencies
uv pip install -e ".[dev]"
Editable mode means source changes take effect without reinstalling.
Install Pre-commit Hooks¶
The hooks run Ruff (lint and format) and the type checkers on every commit.
Project Structure¶
Recursivist is organized into small, focused modules:
recursivist/
├── __init__.py # Package metadata and version
├── __main__.py # `python -m recursivist` entry point
├── _models.py # FileEntry (a NamedTuple), FileEntry.from_raw and .coerce
├── cli.py # Typer-based command-line interface
├── flags.py # DisplayOptions and command-line-order flag resolution
├── scanner.py # Directory traversal -> nested structure dict
├── tree.py # Rich tree rendering (build_tree, display_tree)
├── compare.py # Side-by-side comparison and rendering
├── filtering.py # should_exclude, compile_regex_patterns, parse_ignore_file
├── sorting.py # sort_files_by_type, sort_files_by_similarity
├── metrics.py # Lines of code, size, mtime, and formatting
├── colors.py # generate_color_for_extension
├── icons.py # get_icon (emoji and Nerd Font)
├── git_status.py # get_git_status
├── config.py # load_config, save_config, get_config_path
└── exporters/
├── __init__.py # get_exporter factory and the _EXPORTERS registry
├── base.py # BaseExporter
├── txt.py # TxtExporter
├── json.py # JsonExporter
├── html.py # HtmlExporter
├── markdown.py # MarkdownExporter
├── svg.py # SvgExporter
└── rst.py # RstExporter
See the API Reference for the public functions of each module.
Development Workflow¶
- Create a branch:
- Make your changes.
- Run the test suite:
- Commit (pre-commit hooks run automatically):
- Push and open a pull request.
Code Style and Checks¶
Recursivist uses Ruff for linting and formatting, and both mypy (strict) and pyright for type checking. The Nox sessions wrap these:
nox -s lint # Ruff check + format
nox -s typecheck # mypy and pyright
nox -s tests # pytest across supported Python versions
nox -s docs # build the documentation
You can also run the tools directly:
Extending Recursivist¶
Add a New Command¶
Add a Typer command in cli.py and delegate to the appropriate module:
@app.command()
def your_command(
directory: Path = typer.Argument(".", help="Directory path to process"),
):
"""One-line summary shown in --help.
A longer description with usage details.
"""
...
Implement the underlying logic in a focused module (or a new one), and add tests.
Add a New Export Format¶
Exporters live in recursivist/exporters/ and subclass BaseExporter, which stores the structure and display options and defines the export(output_path) method to override.
- Create
recursivist/exporters/your_format.py:
from .base import BaseExporter
class YourFormatExporter(BaseExporter):
def export(self, output_path: str) -> None:
with open(output_path, "w", encoding="utf-8") as f:
# Build output from self.structure and self.root_name,
# honoring the resolved display options exposed by BaseExporter:
# self.sort_key, self.metrics (ordered), self.show_git_status,
# self.icon_style, and self.show_full_path as appropriate.
...
- Register it in
recursivist/exporters/__init__.pyby importing the class and adding it to the_EXPORTERSmap:
from .your_format import YourFormatExporter
_EXPORTERS = {
# existing entries...
"your_format": YourFormatExporter,
}
- Add the format to the
--formatoption incli.pyand add tests.
Add a New File Statistic¶
To add a metric beyond lines of code, size, and mtime:
- Collect it in
get_directory_structure(scanner.py) and add a flag to enable it. - Thread it through
FileEntryin_models.pyand the formatting helpers inmetrics.py. - Register the metric and its flags in
flags.pyso they resolve intoDisplayOptions(a sorting flag, a display-only flag, or both). - Surface it in
build_tree(tree.py), the exporters, andcompare.py. - Add the CLI options in
cli.pyand wire them intoresolve_display_options.
Extend Pattern Matching¶
Pattern logic lives in filtering.py. To support a new pattern type, extend should_exclude, add a flag in cli.py, document it, and add tests.
Customize Colorization¶
Per-extension colors come from generate_color_for_extension in colors.py. To give common extensions fixed colors, add a lookup table and consult it before falling back to the derived color:
These colors are tuned for a dark terminal background. Renderers that draw onto a known background pass them through ensure_contrast in colors.py first, which darkens or lightens a color (preserving its hue) until it meets a WCAG contrast ratio. The HTML exporter holds every color it emits to WCAG_AAA_NORMAL_TEXT (7:1), so a custom palette stays accessible without needing to be hand-checked.
Debugging¶
Run any command with --verbose for DEBUG-level logging:
Use the built-in breakpoint() to drop into the debugger, or set breakpoints in your IDE.
Documentation¶
Use Google-style docstrings for public functions, classes, and methods — the API Reference is generated from them via mkdocstrings. Build the docs locally with:
Keep command help text in cli.py in sync when you add or change options.
Release Process¶
Recursivist follows Semantic Versioning: MAJOR for incompatible API changes, MINOR for backwards-compatible features, PATCH for backwards-compatible fixes.
- Update the version in
pyproject.toml. - Commit and push to
main. Thetag-releaseworkflow detects the version change and creates and pushes the matching Git tag automatically. - Maintainers build and upload to PyPI:
Performance Notes¶
For large directory trees: filter early (exclude heavy directories), be mindful that --sort-by-loc reads every file, and profile hotspots with cProfile when needed: