diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index cf94acf..0000000 --- a/.coveragerc +++ /dev/null @@ -1,3 +0,0 @@ -[run] -relative_files = True -branch = True \ No newline at end of file diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..a4ef7e3 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,46 @@ +# Git +.git +.gitignore +.github + +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +venv/ +.venv/ +env/ +ENV/ +htmlcov/ +.coverage +.coverage.* +.pytest_cache/ +.ruff_cache/ + +# Editor directories and files +.idea/ +.vscode/ +*.swp +*.swo +*~ + +# OS specific +.DS_Store +Thumbs.db + +# Docker +Dockerfile* +docker-compose* +.dockerignore + +# Documentation +README.md +LICENSE +docs/ + +# CI/CD +.gitlab-ci.yml +.travis.yml +.circleci/ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8dec230..da2fa53 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,40 +1,55 @@ -name: CI tests +name: CI Workflow -on: - push: - branches: - - main - pull_request: - branches: - - main +on: [push, pull_request] jobs: - python-test: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: 3.12 - - - name: Test the tree_view_cli package - run: | - python3.12 -m venv env - source env/bin/activate - python3.12 -m pip install --upgrade pip - pip install -r test_requirements.txt - coverage run -m pytest - coverage xml -o coverage.xml - deactivate - rm -rf env - - name: SonarCloud Scan - uses: SonarSource/sonarcloud-github-action@master - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} \ No newline at end of file + lint: + name: Lint and Format + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + enable-cache: true + cache-dependency-glob: "uv.lock" + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version-file: "pyproject.toml" + + - name: Install the project + run: uv sync --dev + + - name: Run linting + run: uv run ruff check + + - name: Run formatting check + run: uv run ruff format --check + + test: + name: Run Tests + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + enable-cache: true + cache-dependency-glob: "uv.lock" + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version-file: "pyproject.toml" + + - name: Install the project + run: uv sync --all-extras --dev + + - name: Run tests + run: uv run pytest tests diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index 702e729..0000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,92 +0,0 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# -name: "CodeQL Advanced" - -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - schedule: - - cron: '40 13 * * 3' - -jobs: - analyze: - name: Analyze (${{ matrix.language }}) - # Runner size impacts CodeQL analysis time. To learn more, please see: - # - https://gh.io/recommended-hardware-resources-for-running-codeql - # - https://gh.io/supported-runners-and-hardware-resources - # - https://gh.io/using-larger-runners (GitHub.com only) - # Consider using larger runners or machines with greater resources for possible analysis time improvements. - runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} - permissions: - # required for all workflows - security-events: write - - # required to fetch internal or private CodeQL packs - packages: read - - # only required for workflows in private repositories - actions: read - contents: read - - strategy: - fail-fast: false - matrix: - include: - - language: python - build-mode: none - # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' - # Use `c-cpp` to analyze code written in C, C++ or both - # Use 'java-kotlin' to analyze code written in Java, Kotlin or both - # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both - # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, - # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. - # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how - # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 - with: - languages: ${{ matrix.language }} - build-mode: ${{ matrix.build-mode }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - - # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs - # queries: security-extended,security-and-quality - - # If the analyze step fails for one of the languages you are analyzing with - # "We were unable to automatically build your code", modify the matrix above - # to set the build mode to "manual" for that language. Then modify this step - # to build your code. - # â„šī¸ Command-line programs to run using the OS shell. - # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun - - if: matrix.build-mode == 'manual' - shell: bash - run: | - echo 'If you are using a "manual" build mode for one or more of the' \ - 'languages you are analyzing, replace this with the commands to build' \ - 'your code, for example:' - echo ' make bootstrap' - echo ' make release' - exit 1 - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 - with: - category: "/language:${{matrix.language}}" diff --git a/.gitignore b/.gitignore index 82f9275..a504c20 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,12 @@ +# Created by https://www.toptal.com/developers/gitignore/api/python,visualstudiocode +# Edit at https://www.toptal.com/developers/gitignore?templates=python,visualstudiocode + +### Python ### # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class - +**/__pycache__ # C extensions *.so @@ -21,7 +25,7 @@ sdist/ var/ wheels/ share/python-wheels/ -*.egg-info/ +**/*.egg-info/ .installed.cfg *.egg MANIFEST @@ -106,10 +110,8 @@ ipython_config.py #pdm.lock # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it # in version control. -# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +# https://pdm.fming.dev/#use-with-ide .pdm.toml -.pdm-python -.pdm-build/ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm __pypackages__/ @@ -160,3 +162,36 @@ cython_debug/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ + +### Python Patch ### +# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration +poetry.toml + +# ruff +.ruff_cache/ + +# LSP config files +pyrightconfig.json + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +# End of https://www.toptal.com/developers/gitignore/api/python,visualstudiocode + +# Generated using ignr.py - github.com/Antrikshy/ignr.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..f041518 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,19 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + +- repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.9.9 + hooks: + # Run the linter. + - id: ruff + # Run the formatter. + - id: ruff-format diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..24ee5b1 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..e67335f --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,13 @@ +{ + "configurations": [ + { + "name": "Python: Debug Tests", + "type": "debugpy", + "request": "launch", + "program": "${file}", + "purpose": ["debug-test"], + "console": "integratedTerminal", + "justMyCode": false + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 3e99ede..12090ec 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,21 @@ { + "[python]": { + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll": "explicit", + "source.organizeImports": "explicit" + }, + "editor.defaultFormatter": "charliermarsh.ruff" + }, + "files.exclude": { + "**/*.pyc": {"when": "$(basename).py"}, + "**/__pycache__": true, + "**/.pytest_cache": true, + "**/.ruff_cache": true + }, "python.testing.pytestArgs": [ - "." + "tests" ], "python.testing.unittestEnabled": false, "python.testing.pytestEnabled": true -} \ No newline at end of file +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..97d399e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,39 @@ +# Use the official astral-sh/uv Python image for a faster build +FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim + +# Set working directory +WORKDIR /app + +# Enable optimization settings +ENV UV_COMPILE_BYTECODE=1 +ENV UV_LINK_MODE=copy +ENV UV_NO_INSTALLER_METADATA=1 + +# First, install only the dependencies to leverage Docker cache +RUN --mount=type=cache,target=/root/.cache/uv \ + --mount=type=bind,source=uv.lock,target=uv.lock \ + --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ + uv sync --frozen --no-install-project + +# Now copy the application code and complete the installation +ADD . /app +RUN --mount=type=cache,target=/root/.cache/uv \ + uv sync --frozen --compile-bytecode + +# Create non-root user for security +RUN useradd -m appuser && \ + chown -R appuser:appuser /app + +# Place executables in the environment at the front of the path +ENV PATH="/app/.venv/bin:$PATH" + +RUN mkdir -p /app/htmlcov && \ + chown -R appuser:appuser /app/htmlcov && \ + chmod -R 777 /app/htmlcov + +# Switch to non-root user +USER appuser + +# Reset the entrypoint and set the command +ENTRYPOINT [] +CMD ["python", "tree_view_cli.py", "."] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..bfd5d79 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) [2025] [Kaio Silva] + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..11c8088 --- /dev/null +++ b/Makefile @@ -0,0 +1,32 @@ +.PHONY: install-uv up down + +install-uv: + @echo "Installing Astral UV..." + @if command -v curl >/dev/null 2>&1; then \ + curl -LsSf https://astral.sh/uv/install.sh | sh; \ + elif command -v wget >/dev/null 2>&1; then \ + wget -qO- https://astral.sh/uv/install.sh | sh; \ + else \ + echo "Error: Neither curl nor wget is available. Please install one of them."; \ + exit 1; \ + fi + @echo "Astral UV installed successfully." + +check-uv: + @uv --version || (echo "Astral UV is not installed or not in PATH" && exit 1) + +sync: + @uv sync + +editable: + @uv pip install -e . + +up: + docker compose up -d --build + +down: + docker compose down --remove-orphans --rmi local --volumes + +all: install-uv check-uv sync editable up + +.DEFAULT_GOAL := up diff --git a/README.Docker.md b/README.Docker.md new file mode 100644 index 0000000..89bd225 --- /dev/null +++ b/README.Docker.md @@ -0,0 +1,80 @@ +# Docker Setup for tree-view-cli + +This document explains how to use Docker with the tree-view-cli project, including both development and production workflows. + +## Development Workflow + +The Docker environment is configured for optimal development experience with: + +- Fast dependency installation using uv +- Efficient layer caching for quick rebuilds +- Volume mounts for live code editing +- Separate services for testing and coverage viewing + +### Starting the Development Environment + +Build and start the development environment: + +If you enviroment has Make +```bash +make up +``` + +Or +```bash +docker compose up --build +``` + +This will: +1. Build the Docker image with your application +2. Start two services: + - `tree-view-cli`: Serves test coverage reports at http://localhost:8000 + - `dev`: Runs the test suite + +For teardown the containers +```bash +make down +``` + +Or +```bash +docker compose down --remove-orphans --rmi local --volumes +``` + +### Working with the Environment + +- Edit code locally, and changes will be reflected immediately in the container +- Test results and coverage reports are stored in a persistent Docker volume +- Resource limits prevent the container from consuming excessive resources + +## Production Deployment + +For production deployment, the Dockerfile is optimized for security and efficiency: + +- Uses the official astral-sh/uv Python image +- Implements proper dependency caching +- Runs as a non-root user for enhanced security +- Includes bytecode compilation for better performance +- Minimizes image size through optimal layer management + +### Building for Production + +Build the production image: + +```bash +docker build -t tree-view-cli . +``` + +If deploying to a different architecture (e.g., from Apple Silicon to x86): + +```bash +docker build --platform=linux/amd64 -t tree-view-cli . +``` + +## References + +- [Docker Python Guide](https://docs.docker.com/language/python/) +- [uv Documentation](https://github.com/astral-sh/uv/) +- [Docker Compose Reference](https://docs.docker.com/compose/compose-file/) +- [Docker Build Reference](https://docs.docker.com/engine/reference/commandline/build/) +- [Docker Security Best Practices](https://docs.docker.com/develop/security-best-practices/) diff --git a/README.md b/README.md index b892b13..5789b6a 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,17 @@ # tree-view-cli -`tree-view-cli` is a Python script that generates a tree-like representation of a directory structure. It's designed to be simple, fast, and customizable, providing an easy way to visualize file system hierarchies directly from the command line. +A fast, customizable command-line tool for generating tree-like representations of directory structures, with intelligent handling of .gitignore rules and cross-platform compatibility. + +- This project is a fork from [chrisw-org/tree-view-cli](https://github.com/chrisw-org/tree-view-cli.git) + ## Features -- Generate a tree view of any directory -- Respect `.gitignore` rules, providing an accurate representation of version-controlled projects -- Customizable depth limit -- Alphabetical sorting of files and directories -- Cyclic reference detection to prevent infinite loops -- Cross-platform compatibility (works on any system with Python 3.6+) +- 🚀 **Fast execution**: Optimized Python implementation for quick directory processing +- 🔍 **Gitignore support**: Automatically respects .gitignore rules to match version-controlled projects +- 🌲 **Customizable depth**: Control how many levels of directories to display +- 🔄 **Cyclic detection**: Prevents infinite loops from symbolic links +- đŸ–Ĩī¸ **Cross-platform**: Works consistently across Windows, macOS, and Linux ## Requirements @@ -17,27 +19,43 @@ ## Installation -1. Clone this repository or download the `tree_view_cli.py` script. -2. Make the script executable (on Unix-like systems): +### Automatic by Astral uv + +```bash +uv tool install "git+https://github.com/kaiosilva-dataeng/tree-view-cli.git@main" +``` + +### Manual + +```bash +# Clone the repository +git clone https://github.com/kaiosilva-dataeng/tree-view-cli.git +cd tree-view-cli + +# Install dependencies using uv +uv sync +``` - ```bash - chmod +x tree_view_cli.py - ``` +### Troubleshooting Project Installation Issues -3. Optionally, you can add the script's directory to your PATH for easier access. +In case the project is not installed automatically, please refer to the following steps to resolve the issue: + +```bash +uv tool install . +``` ## Usage Basic usage: ```bash -python tree_view_cli.py /path/to/directory +tree-view /path/to/directory ``` -To limit the depth of the tree: +Limiting directory depth: ```bash -python tree_view_cli.py /path/to/directory --max-depth 2 +tree-view /path/to/directory --max-depth 2 ``` ### Options @@ -61,36 +79,101 @@ my-project/ └── requirements.txt ``` -Note: The output will exclude files and directories specified in .gitignore files. - ## Development -### Running Tests - -To run the tests, you'll need pytest installed. You can install it with: +### Setting Up Development Environment ```bash -pip install pytest +# Create and activate a virtual environment +uv venv +source .venv/bin/activate # On Windows: .venv\Scripts\activate + +# Install development dependencies +uv sync --dev ``` -Then, run the tests with: +### Running Tests ```bash -pytest test_tree_view_cli.py +# Run tests +task test + +# Run linting +task lint + +# Format code +task format +``` + +### Development Tasks + +The project uses `taskipy` to manage common development tasks: + +- `task lint`: Run Ruff linting checks +- `task format`: Format code with Ruff +- `task run`: Run the tree-view-cli on the current directory +- `task test`: Run the test suite with coverage reporting + +### Docker Support + +For Docker-based development and deployment, see [Docker Setup](README.Docker.md). + +The Docker setup provides: +- Multi-stage builds for efficient image size +- Development environment with hot reloading +- Separate services for testing and coverage viewing +- Security-focused production configuration + +## CI/CD + +This project uses GitHub Actions for continuous integration. The workflow includes: +- Linting and code formatting checks using Ruff +- Running tests with pytest +- Coverage reporting + +## Project Structure + +``` +tree-view-cli/ +├── .github +│ └── workflows +│ └── ci.yml # GitHub Actions CI workflow +├── .vscode # VS Code configuration +├── src +│ └── tree_view_cli +│ └── tree_view_cli.py # Main application module +├── tests +│ ├── conftest.py # Test fixtures and setup +│ └── test_tree_view_cli.py # Test suite +├── .dockerignore # Files to exclude from Docker context +├── .gitignore # Files to exclude from git +├── .pre-commit-config.yaml # Pre-commit hooks configuration +├── .python-version # Python version specification +├── compose.yaml # Docker Compose configuration +├── Dockerfile # Docker configuration +├── LICENSE # Project license +├── Makefile # Make commands for Docker +├── pyproject.toml # Project metadata and dependencies +├── README.Docker.md # Docker-specific documentation +├── README.md # Project documentation +└── uv.lock # Dependency lock file ``` ## Contributing Contributions are welcome! Please feel free to submit a Pull Request. -## License - -This project is open source and available under the [MIT License](LICENSE). +1. Fork the repository +2. Create your feature branch: `git checkout -b feature/amazing-feature` +3. Commit your changes: `git commit -m 'Add some amazing feature'` +4. Push to the branch: `git push origin feature/amazing-feature` +5. Open a Pull Request -## Contact +## License -If you have any questions or encounter any issues, please feel free to open an issue in this repository. +This project is licensed under the [MIT License](LICENSE) - see the LICENSE file for details. ---- +## Acknowledgments -Happy tree viewing! \ No newline at end of file +- Thanks to the Python community for the robust standard library functions that make this tool possible +- Inspired by the Unix `tree` command, but with additional features for modern development workflows diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..56ccd2a --- /dev/null +++ b/compose.yaml @@ -0,0 +1,46 @@ +services: + tree-view-cli: + build: + context: . + dockerfile: Dockerfile + volumes: + # Mount source for development + - ./:/app + # Don't override Python modules + - /app/.venv/ + # Mount coverage reports separately + - htmlcov:/app/htmlcov + environment: + - PYTHONUNBUFFERED=1 + - PYTHONDONTWRITEBYTECODE=1 + ports: + - "8000:8000" + mem_limit: 512m + cpus: 0.5 + security_opt: + - no-new-privileges:true + user: appuser + working_dir: /app/htmlcov + entrypoint: ["python", "-m", "http.server", "8000"] + depends_on: + - dev + + # Development service + dev: + build: + context: . + dockerfile: Dockerfile + volumes: + - ./:/app + - /app/.venv/ + - htmlcov:/app/htmlcov + environment: + - PYTHONUNBUFFERED=1 + - PYTHONDONTWRITEBYTECODE=1 + security_opt: + - no-new-privileges:true + user: appuser + command: ["task", "test"] + +volumes: + htmlcov: diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..3160c68 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,54 @@ +[project] +name = "tree-view-cli" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.13" +dependencies = [] + +[project.scripts] +tree-view = "tree_view_cli.tree_view_cli:main" + +[dependency-groups] +dev = [ + "pre-commit>=4.1.0", + "pytest>=8.3.5", + "pytest-cov>=6.0.0", + "ruff>=0.9.9", + "taskipy>=1.14.1", +] + +[tool.ruff] +line-length = 79 + +[tool.ruff.lint] +# (I)-> iSort, (F)->PyFlakes, (E,W)-> PyCodeStyle,(PL)->Pylint, (PT)->Pytest-style +select = ['I', 'F', 'E', 'W', 'PL', 'PT'] + +[tool.ruff.lint.per-file-ignores] +"__init__.py" = ["E402"] +"test_*.py" = ["E501"] +"conftest.py" = ["E501"] + +[tool.ruff.format] +quote-style = 'single' + +[tool.pytest.ini_options] +pythonpath = "." +addopts = '-p no:warnings' + +[tool.taskipy.tasks] +lint = 'ruff check' +pre_format = 'ruff check --fix' +format = 'ruff format' +run = 'python src/tree_view_cli/tree_view_cli.py .' +pre_test = 'task lint' +test = 'pytest -s -x --cov=src/tree_view_cli -vv' +post_test = 'coverage html' + +[tool.uv] +config-settings = { editable_mode = "compat" } + +[build-system] +requires = ["setuptools>=42", "wheel"] +build-backend = "setuptools.build_meta" diff --git a/sonar-project.properties b/sonar-project.properties deleted file mode 100644 index 0fd39c2..0000000 --- a/sonar-project.properties +++ /dev/null @@ -1,15 +0,0 @@ -sonar.projectKey=chrisw-org_tree-view-cli -sonar.organization=chrisw-org - -# This is the name and version displayed in the SonarCloud UI. -#sonar.projectName=tree-view-cli -#sonar.projectVersion=1.0 - - -# Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. -#sonar.sources=. - -# Encoding of the source code. Default is default system encoding -#sonar.sourceEncoding=UTF-8 -sonar.python.version=3.12 -sonar.python.coverage.reportPaths=coverage.xml \ No newline at end of file diff --git a/tree_view_cli.py b/src/tree_view_cli/tree_view_cli.py old mode 100755 new mode 100644 similarity index 72% rename from tree_view_cli.py rename to src/tree_view_cli/tree_view_cli.py index fbf4624..12ff1dd --- a/tree_view_cli.py +++ b/src/tree_view_cli/tree_view_cli.py @@ -1,8 +1,7 @@ -import os import argparse -from pathlib import Path -import fnmatch import re +from pathlib import Path + class DirectoryTreeGenerator: def __init__(self, root_dir, max_depth=float('inf')): @@ -17,7 +16,13 @@ def load_gitignore_patterns(self): gitignore_file = current_dir / '.gitignore' if gitignore_file.is_file(): with open(gitignore_file, 'r') as f: - patterns.extend([line.strip() for line in f if line.strip() and not line.startswith('#')]) + patterns.extend( + [ + line.strip() + for line in f + if line.strip() and not line.startswith('#') + ] + ) current_dir = current_dir.parent return patterns @@ -33,7 +38,7 @@ def gitignore_pattern_to_regex(self, pattern: str) -> str: """ # Escape special characters pattern = re.escape(pattern) - + # Replace escaped wildcards with regex equivalents pattern = pattern.replace(r'\*\*', '.*') pattern = pattern.replace(r'\*', '[^/]*') @@ -46,12 +51,12 @@ def gitignore_pattern_to_regex(self, pattern: str) -> str: else: # Add start and end anchors for non-directory patterns pattern = '^' + pattern + '$' - + return pattern def should_ignore(self, path: Path) -> bool: """ - Determine if a given path should be ignored based on .gitignore patterns. + Determine a given path should be ignored based on .gitignore patterns. Args: path (Path): The path to check. @@ -66,10 +71,10 @@ def should_ignore(self, path: Path) -> bool: rel_path_str = str(rel_path) # Check if the path is the .git directory or inside it - if rel_path_str == ".git" or rel_path_str.startswith(".git/"): + if rel_path_str == '.git' or rel_path_str.startswith('.git/'): return True - # Check if the relative path matches any pattern in the .gitignore patterns + # Check the relative path matches any pattern in the .gitignore for pattern in self.gitignore_patterns: # Convert the pattern to a regex regex = self.gitignore_pattern_to_regex(pattern) @@ -80,53 +85,66 @@ def should_ignore(self, path: Path) -> bool: return False def generate_tree(self): - print(f"{self.root_dir.name}/") - self._generate_tree(self.root_dir, "", 0, set()) + print(f'{self.root_dir.name}/') + self._generate_tree(self.root_dir, '', 0, set()) def _generate_tree(self, directory, prefix, depth, visited): if depth >= self.max_depth: return - - entries = sorted(directory.iterdir(), key=lambda x: (x.is_file(), x.name.lower())) - + + entries = sorted( + directory.iterdir(), key=lambda x: (x.is_file(), x.name.lower()) + ) + for i, entry in enumerate(entries): if self.should_ignore(entry): continue - + is_last = i == len(entries) - 1 current_prefix = self._get_current_prefix(is_last) - print(f"{prefix}{current_prefix}{entry.name}") - + print(f'{prefix}{current_prefix}{entry.name}') + if entry.is_dir() and not entry.is_symlink(): self._handle_directory(entry, prefix, is_last, depth, visited) - + def _get_current_prefix(self, is_last): - return "└── " if is_last else "├── " - + return '└── ' if is_last else '├── ' + def _handle_directory(self, entry, prefix, is_last, depth, visited): canonical_path = entry.resolve() if canonical_path in visited: self._print_cyclic_reference(prefix, entry.name, is_last) return - + new_prefix = self._get_new_prefix(prefix, is_last) new_visited = visited.union({canonical_path}) self._generate_tree(entry, new_prefix, depth + 1, new_visited) - + def _print_cyclic_reference(self, prefix, name, is_last): - print(f"{prefix}{' ' if is_last else '│ '}[Cyclic reference to {name}]") - + print( + f'{prefix}{" " if is_last else "│ "}[Cyclic reference to {name}]' # noqa: E501 + ) + def _get_new_prefix(self, prefix, is_last): - return prefix + (" " if is_last else "│ ") + return prefix + (' ' if is_last else '│ ') + def main(): - parser = argparse.ArgumentParser(description="Generate a directory tree.") - parser.add_argument("directory", help="The directory to generate the tree for.") - parser.add_argument("--max-depth", type=int, default=float('inf'), help="Maximum depth to traverse.") + parser = argparse.ArgumentParser(description='Generate a directory tree.') + parser.add_argument( + 'directory', help='The directory to generate the tree for.' + ) + parser.add_argument( + '--max-depth', + type=int, + default=float('inf'), + help='Maximum depth to traverse.', + ) args = parser.parse_args() tree_generator = DirectoryTreeGenerator(args.directory, args.max_depth) tree_generator.generate_tree() -if __name__ == "__main__": - main() \ No newline at end of file + +if __name__ == '__main__': + main() diff --git a/test_requirements.txt b/test_requirements.txt deleted file mode 100644 index fea764c..0000000 --- a/test_requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -pytest -coverage \ No newline at end of file diff --git a/test_tree_view_cli.py b/test_tree_view_cli.py deleted file mode 100644 index ddccc13..0000000 --- a/test_tree_view_cli.py +++ /dev/null @@ -1,82 +0,0 @@ -import pytest -import tempfile -import os -from pathlib import Path -from tree_view_cli import DirectoryTreeGenerator - -@pytest.fixture -def temp_directory(): - with tempfile.TemporaryDirectory() as tmpdirname: - yield Path(tmpdirname) - -def create_file_structure(root): - (root / "dir1" / "subdir1").mkdir(parents=True) - (root / "dir1" / "subdir2").mkdir(parents=True) - (root / "dir1" / "file1.txt").touch() - (root / "dir1" / "subdir1" / "file2.txt").touch() - (root / "dir1" / "subdir2" / "file3.txt").touch() - -def test_basic_structure(temp_directory, capsys): - create_file_structure(temp_directory) - tree_gen = DirectoryTreeGenerator(temp_directory) - tree_gen.generate_tree() - captured = capsys.readouterr() - assert "dir1" in captured.out - assert "subdir1" in captured.out - assert "subdir2" in captured.out - assert "file1.txt" in captured.out - assert "file2.txt" in captured.out - assert "file3.txt" in captured.out - -def test_max_depth(temp_directory, capsys): - create_file_structure(temp_directory) - tree_gen = DirectoryTreeGenerator(temp_directory, max_depth=1) - tree_gen.generate_tree() - captured = capsys.readouterr() - assert "dir1" in captured.out - assert "subdir1" not in captured.out - assert "file2.txt" not in captured.out - -def test_gitignore_file(temp_directory, capsys): - create_file_structure(temp_directory) - with open(temp_directory / ".gitignore", "w") as f: - f.write("**/file1.txt\n") - tree_gen = DirectoryTreeGenerator(temp_directory) - tree_gen.generate_tree() - captured = capsys.readouterr() - assert "file1.txt" not in captured.out - assert "file2.txt" in captured.out - -def test_gitignore_directory(temp_directory, capsys): - create_file_structure(temp_directory) - with open(temp_directory / ".gitignore", "w") as f: - f.write("dir1/\n") - tree_gen = DirectoryTreeGenerator(temp_directory) - tree_gen.generate_tree() - captured = capsys.readouterr() - assert "dir1" not in captured.out - -def test_gitignore_subdirectory(temp_directory, capsys): - create_file_structure(temp_directory) - with open(temp_directory / ".gitignore", "w") as f: - f.write("**/subdir1\n") - tree_gen = DirectoryTreeGenerator(temp_directory) - tree_gen.generate_tree() - captured = capsys.readouterr() - assert "subdir1" not in captured.out - assert "subdir2" in captured.out - -def test_empty_directory(temp_directory, capsys): - tree_gen = DirectoryTreeGenerator(temp_directory) - tree_gen.generate_tree() - captured = capsys.readouterr() - assert captured.out.strip() == temp_directory.name + "/" - -def test_git_directory_excluded(temp_directory, capsys): - create_file_structure(temp_directory) - (temp_directory / ".git").mkdir() - tree_gen = DirectoryTreeGenerator(temp_directory) - tree_gen.generate_tree() - captured = capsys.readouterr() - assert "dir1" in captured.out - assert ".git" not in captured.out \ No newline at end of file diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..8dc01a5 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,22 @@ +import tempfile +from pathlib import Path + +import pytest + + +def create_file_structure( + root, +): + (root / 'dir1' / 'subdir1').mkdir(parents=True) + (root / 'dir1' / 'subdir2').mkdir(parents=True) + (root / 'dir1' / 'file1.txt').touch() + (root / 'dir1' / 'subdir1' / 'file2.txt').touch() + (root / 'dir1' / 'subdir2' / 'file3.txt').touch() + + +@pytest.fixture +def temp_directory(): + with tempfile.TemporaryDirectory() as tmpdirname: + root = Path(tmpdirname) + create_file_structure(root=root) + yield Path(tmpdirname) diff --git a/tests/test_tree_view_cli.py b/tests/test_tree_view_cli.py new file mode 100644 index 0000000..a7260c8 --- /dev/null +++ b/tests/test_tree_view_cli.py @@ -0,0 +1,72 @@ +import tempfile +from pathlib import Path + +from src.tree_view_cli.tree_view_cli import DirectoryTreeGenerator + + +def test_basic_structure(temp_directory, capsys): + tree_gen = DirectoryTreeGenerator(temp_directory) + tree_gen.generate_tree() + captured = capsys.readouterr() + assert 'dir1' in captured.out + assert 'subdir1' in captured.out + assert 'subdir2' in captured.out + assert 'file1.txt' in captured.out + assert 'file2.txt' in captured.out + assert 'file3.txt' in captured.out + + +def test_max_depth(temp_directory, capsys): + tree_gen = DirectoryTreeGenerator(temp_directory, max_depth=1) + tree_gen.generate_tree() + captured = capsys.readouterr() + assert 'dir1' in captured.out + assert 'subdir1' not in captured.out + assert 'file2.txt' not in captured.out + + +def test_gitignore_file(temp_directory, capsys): + with open(temp_directory / '.gitignore', 'w', encoding='utf-8') as f: + f.write('**/file1.txt\n') + tree_gen = DirectoryTreeGenerator(temp_directory) + tree_gen.generate_tree() + captured = capsys.readouterr() + assert 'file1.txt' not in captured.out + assert 'file2.txt' in captured.out + + +def test_gitignore_directory(temp_directory, capsys): + with open(temp_directory / '.gitignore', 'w', encoding='utf-8') as f: + f.write('dir1/\n') + tree_gen = DirectoryTreeGenerator(temp_directory) + tree_gen.generate_tree() + captured = capsys.readouterr() + assert 'dir1' not in captured.out + + +def test_gitignore_subdirectory(temp_directory, capsys): + with open(temp_directory / '.gitignore', 'w', encoding='utf-8') as f: + f.write('**/subdir1\n') + tree_gen = DirectoryTreeGenerator(temp_directory) + tree_gen.generate_tree() + captured = capsys.readouterr() + assert 'subdir1' not in captured.out + assert 'subdir2' in captured.out + + +def test_empty_directory(capsys): + with tempfile.TemporaryDirectory() as empty_temp_directory: + empty_temp = Path(empty_temp_directory) + tree_gen = DirectoryTreeGenerator(empty_temp) + tree_gen.generate_tree() + captured = capsys.readouterr() + assert captured.out.strip() == empty_temp.name + '/' + + +def test_git_directory_excluded(temp_directory, capsys): + (temp_directory / '.git').mkdir() + tree_gen = DirectoryTreeGenerator(temp_directory) + tree_gen.generate_tree() + captured = capsys.readouterr() + assert 'dir1' in captured.out + assert '.git' not in captured.out diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..66d2aa9 --- /dev/null +++ b/uv.lock @@ -0,0 +1,305 @@ +version = 1 +revision = 1 +requires-python = ">=3.13" + +[[package]] +name = "cfgv" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249 }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "coverage" +version = "7.6.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0c/d6/2b53ab3ee99f2262e6f0b8369a43f6d66658eab45510331c0b3d5c8c4272/coverage-7.6.12.tar.gz", hash = "sha256:48cfc4641d95d34766ad41d9573cc0f22a48aa88d22657a1fe01dca0dbae4de2", size = 805941 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/89/1adf3e634753c0de3dad2f02aac1e73dba58bc5a3a914ac94a25b2ef418f/coverage-7.6.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:488c27b3db0ebee97a830e6b5a3ea930c4a6e2c07f27a5e67e1b3532e76b9ef1", size = 208673 }, + { url = "https://files.pythonhosted.org/packages/ce/64/92a4e239d64d798535c5b45baac6b891c205a8a2e7c9cc8590ad386693dc/coverage-7.6.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d1095bbee1851269f79fd8e0c9b5544e4c00c0c24965e66d8cba2eb5bb535fd", size = 208945 }, + { url = "https://files.pythonhosted.org/packages/b4/d0/4596a3ef3bca20a94539c9b1e10fd250225d1dec57ea78b0867a1cf9742e/coverage-7.6.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0533adc29adf6a69c1baa88c3d7dbcaadcffa21afbed3ca7a225a440e4744bf9", size = 242484 }, + { url = "https://files.pythonhosted.org/packages/1c/ef/6fd0d344695af6718a38d0861408af48a709327335486a7ad7e85936dc6e/coverage-7.6.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53c56358d470fa507a2b6e67a68fd002364d23c83741dbc4c2e0680d80ca227e", size = 239525 }, + { url = "https://files.pythonhosted.org/packages/0c/4b/373be2be7dd42f2bcd6964059fd8fa307d265a29d2b9bcf1d044bcc156ed/coverage-7.6.12-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64cbb1a3027c79ca6310bf101014614f6e6e18c226474606cf725238cf5bc2d4", size = 241545 }, + { url = "https://files.pythonhosted.org/packages/a6/7d/0e83cc2673a7790650851ee92f72a343827ecaaea07960587c8f442b5cd3/coverage-7.6.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:79cac3390bfa9836bb795be377395f28410811c9066bc4eefd8015258a7578c6", size = 241179 }, + { url = "https://files.pythonhosted.org/packages/ff/8c/566ea92ce2bb7627b0900124e24a99f9244b6c8c92d09ff9f7633eb7c3c8/coverage-7.6.12-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9b148068e881faa26d878ff63e79650e208e95cf1c22bd3f77c3ca7b1d9821a3", size = 239288 }, + { url = "https://files.pythonhosted.org/packages/7d/e4/869a138e50b622f796782d642c15fb5f25a5870c6d0059a663667a201638/coverage-7.6.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8bec2ac5da793c2685ce5319ca9bcf4eee683b8a1679051f8e6ec04c4f2fd7dc", size = 241032 }, + { url = "https://files.pythonhosted.org/packages/ae/28/a52ff5d62a9f9e9fe9c4f17759b98632edd3a3489fce70154c7d66054dd3/coverage-7.6.12-cp313-cp313-win32.whl", hash = "sha256:200e10beb6ddd7c3ded322a4186313d5ca9e63e33d8fab4faa67ef46d3460af3", size = 211315 }, + { url = "https://files.pythonhosted.org/packages/bc/17/ab849b7429a639f9722fa5628364c28d675c7ff37ebc3268fe9840dda13c/coverage-7.6.12-cp313-cp313-win_amd64.whl", hash = "sha256:2b996819ced9f7dbb812c701485d58f261bef08f9b85304d41219b1496b591ef", size = 212099 }, + { url = "https://files.pythonhosted.org/packages/d2/1c/b9965bf23e171d98505eb5eb4fb4d05c44efd256f2e0f19ad1ba8c3f54b0/coverage-7.6.12-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:299cf973a7abff87a30609879c10df0b3bfc33d021e1adabc29138a48888841e", size = 209511 }, + { url = "https://files.pythonhosted.org/packages/57/b3/119c201d3b692d5e17784fee876a9a78e1b3051327de2709392962877ca8/coverage-7.6.12-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4b467a8c56974bf06e543e69ad803c6865249d7a5ccf6980457ed2bc50312703", size = 209729 }, + { url = "https://files.pythonhosted.org/packages/52/4e/a7feb5a56b266304bc59f872ea07b728e14d5a64f1ad3a2cc01a3259c965/coverage-7.6.12-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2458f275944db8129f95d91aee32c828a408481ecde3b30af31d552c2ce284a0", size = 253988 }, + { url = "https://files.pythonhosted.org/packages/65/19/069fec4d6908d0dae98126aa7ad08ce5130a6decc8509da7740d36e8e8d2/coverage-7.6.12-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a9d8be07fb0832636a0f72b80d2a652fe665e80e720301fb22b191c3434d924", size = 249697 }, + { url = "https://files.pythonhosted.org/packages/1c/da/5b19f09ba39df7c55f77820736bf17bbe2416bbf5216a3100ac019e15839/coverage-7.6.12-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14d47376a4f445e9743f6c83291e60adb1b127607a3618e3185bbc8091f0467b", size = 252033 }, + { url = "https://files.pythonhosted.org/packages/1e/89/4c2750df7f80a7872267f7c5fe497c69d45f688f7b3afe1297e52e33f791/coverage-7.6.12-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b95574d06aa9d2bd6e5cc35a5bbe35696342c96760b69dc4287dbd5abd4ad51d", size = 251535 }, + { url = "https://files.pythonhosted.org/packages/78/3b/6d3ae3c1cc05f1b0460c51e6f6dcf567598cbd7c6121e5ad06643974703c/coverage-7.6.12-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:ecea0c38c9079570163d663c0433a9af4094a60aafdca491c6a3d248c7432827", size = 249192 }, + { url = "https://files.pythonhosted.org/packages/6e/8e/c14a79f535ce41af7d436bbad0d3d90c43d9e38ec409b4770c894031422e/coverage-7.6.12-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2251fabcfee0a55a8578a9d29cecfee5f2de02f11530e7d5c5a05859aa85aee9", size = 250627 }, + { url = "https://files.pythonhosted.org/packages/cb/79/b7cee656cfb17a7f2c1b9c3cee03dd5d8000ca299ad4038ba64b61a9b044/coverage-7.6.12-cp313-cp313t-win32.whl", hash = "sha256:eb5507795caabd9b2ae3f1adc95f67b1104971c22c624bb354232d65c4fc90b3", size = 212033 }, + { url = "https://files.pythonhosted.org/packages/b6/c3/f7aaa3813f1fa9a4228175a7bd368199659d392897e184435a3b66408dd3/coverage-7.6.12-cp313-cp313t-win_amd64.whl", hash = "sha256:f60a297c3987c6c02ffb29effc70eadcbb412fe76947d394a1091a3615948e2f", size = 213240 }, + { url = "https://files.pythonhosted.org/packages/fb/b2/f655700e1024dec98b10ebaafd0cedbc25e40e4abe62a3c8e2ceef4f8f0a/coverage-7.6.12-py3-none-any.whl", hash = "sha256:eb8668cfbc279a536c633137deeb9435d2962caec279c3f8cf8b91fff6ff8953", size = 200552 }, +] + +[[package]] +name = "distlib" +version = "0.3.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/dd/1bec4c5ddb504ca60fc29472f3d27e8d4da1257a854e1d96742f15c1d02d/distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403", size = 613923 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973 }, +] + +[[package]] +name = "filelock" +version = "3.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/9c/0b15fb47b464e1b663b1acd1253a062aa5feecb07d4e597daea542ebd2b5/filelock-3.17.0.tar.gz", hash = "sha256:ee4e77401ef576ebb38cd7f13b9b28893194acc20a8e68e18730ba9c0e54660e", size = 18027 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/ec/00d68c4ddfedfe64159999e5f8a98fb8442729a63e2077eb9dcd89623d27/filelock-3.17.0-py3-none-any.whl", hash = "sha256:533dc2f7ba78dc2f0f531fc6c4940addf7b70a481e269a5a3b93be94ffbe8338", size = 16164 }, +] + +[[package]] +name = "identify" +version = "2.6.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/fa/5eb460539e6f5252a7c5a931b53426e49258cde17e3d50685031c300a8fd/identify-2.6.8.tar.gz", hash = "sha256:61491417ea2c0c5c670484fd8abbb34de34cdae1e5f39a73ee65e48e4bb663fc", size = 99249 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/8c/4bfcab2d8286473b8d83ea742716f4b79290172e75f91142bc1534b05b9a/identify-2.6.8-py2.py3-none-any.whl", hash = "sha256:83657f0f766a3c8d0eaea16d4ef42494b39b34629a4b3192a9d020d349b3e255", size = 99109 }, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, +] + +[[package]] +name = "mslex" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/97/7022667073c99a0fe028f2e34b9bf76b49a611afd21b02527fbfd92d4cd5/mslex-1.3.0.tar.gz", hash = "sha256:641c887d1d3db610eee2af37a8e5abda3f70b3006cdfd2d0d29dc0d1ae28a85d", size = 11583 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/f2/66bd65ca0139675a0d7b18f0bada6e12b51a984e41a76dbe44761bf1b3ee/mslex-1.3.0-py3-none-any.whl", hash = "sha256:c7074b347201b3466fc077c5692fbce9b5f62a63a51f537a53fbbd02eff2eea4", size = 7820 }, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, +] + +[[package]] +name = "packaging" +version = "24.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, +] + +[[package]] +name = "platformdirs" +version = "4.3.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 }, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, +] + +[[package]] +name = "pre-commit" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2a/13/b62d075317d8686071eb843f0bb1f195eb332f48869d3c31a4c6f1e063ac/pre_commit-4.1.0.tar.gz", hash = "sha256:ae3f018575a588e30dfddfab9a05448bfbd6b73d78709617b5a2b853549716d4", size = 193330 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/b3/df14c580d82b9627d173ceea305ba898dca135feb360b6d84019d0803d3b/pre_commit-4.1.0-py2.py3-none-any.whl", hash = "sha256:d29e7cb346295bcc1cc75fc3e92e343495e3ea0196c9ec6ba53f49f10ab6ae7b", size = 220560 }, +] + +[[package]] +name = "psutil" +version = "6.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/5a/07871137bb752428aa4b659f910b399ba6f291156bdea939be3e96cae7cb/psutil-6.1.1.tar.gz", hash = "sha256:cf8496728c18f2d0b45198f06895be52f36611711746b7f30c464b422b50e2f5", size = 508502 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/99/ca79d302be46f7bdd8321089762dd4476ee725fce16fc2b2e1dbba8cac17/psutil-6.1.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:fc0ed7fe2231a444fc219b9c42d0376e0a9a1a72f16c5cfa0f68d19f1a0663e8", size = 247511 }, + { url = "https://files.pythonhosted.org/packages/0b/6b/73dbde0dd38f3782905d4587049b9be64d76671042fdcaf60e2430c6796d/psutil-6.1.1-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:0bdd4eab935276290ad3cb718e9809412895ca6b5b334f5a9111ee6d9aff9377", size = 248985 }, + { url = "https://files.pythonhosted.org/packages/17/38/c319d31a1d3f88c5b79c68b3116c129e5133f1822157dd6da34043e32ed6/psutil-6.1.1-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6e06c20c05fe95a3d7302d74e7097756d4ba1247975ad6905441ae1b5b66003", size = 284488 }, + { url = "https://files.pythonhosted.org/packages/9c/39/0f88a830a1c8a3aba27fededc642da37613c57cbff143412e3536f89784f/psutil-6.1.1-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97f7cb9921fbec4904f522d972f0c0e1f4fabbdd4e0287813b21215074a0f160", size = 287477 }, + { url = "https://files.pythonhosted.org/packages/47/da/99f4345d4ddf2845cb5b5bd0d93d554e84542d116934fde07a0c50bd4e9f/psutil-6.1.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33431e84fee02bc84ea36d9e2c4a6d395d479c9dd9bba2376c1f6ee8f3a4e0b3", size = 289017 }, + { url = "https://files.pythonhosted.org/packages/38/53/bd755c2896f4461fd4f36fa6a6dcb66a88a9e4b9fd4e5b66a77cf9d4a584/psutil-6.1.1-cp37-abi3-win32.whl", hash = "sha256:eaa912e0b11848c4d9279a93d7e2783df352b082f40111e078388701fd479e53", size = 250602 }, + { url = "https://files.pythonhosted.org/packages/7b/d7/7831438e6c3ebbfa6e01a927127a6cb42ad3ab844247f3c5b96bea25d73d/psutil-6.1.1-cp37-abi3-win_amd64.whl", hash = "sha256:f35cfccb065fff93529d2afb4a2e89e363fe63ca1e4a5da22b603a85833c2649", size = 254444 }, +] + +[[package]] +name = "pytest" +version = "8.3.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 }, +] + +[[package]] +name = "pytest-cov" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/be/45/9b538de8cef30e17c7b45ef42f538a94889ed6a16f2387a6c89e73220651/pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0", size = 66945 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/3b/48e79f2cd6a61dbbd4807b4ed46cb564b4fd50a76166b1c4ea5c1d9e2371/pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35", size = 22949 }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, +] + +[[package]] +name = "ruff" +version = "0.9.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/c3/418441a8170e8d53d05c0b9dad69760dbc7b8a12c10dbe6db1e1205d2377/ruff-0.9.9.tar.gz", hash = "sha256:0062ed13f22173e85f8f7056f9a24016e692efeea8704d1a5e8011b8aa850933", size = 3717448 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/c3/2c4afa9ba467555d074b146d9aed0633a56ccdb900839fb008295d037b89/ruff-0.9.9-py3-none-linux_armv6l.whl", hash = "sha256:628abb5ea10345e53dff55b167595a159d3e174d6720bf19761f5e467e68d367", size = 10027252 }, + { url = "https://files.pythonhosted.org/packages/33/d1/439e58487cf9eac26378332e25e7d5ade4b800ce1eec7dc2cfc9b0d7ca96/ruff-0.9.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b6cd1428e834b35d7493354723543b28cc11dc14d1ce19b685f6e68e07c05ec7", size = 10840721 }, + { url = "https://files.pythonhosted.org/packages/50/44/fead822c38281ba0122f1b76b460488a175a9bd48b130650a6fb6dbcbcf9/ruff-0.9.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5ee162652869120ad260670706f3cd36cd3f32b0c651f02b6da142652c54941d", size = 10161439 }, + { url = "https://files.pythonhosted.org/packages/11/ae/d404a2ab8e61ddf6342e09cc6b7f7846cce6b243e45c2007dbe0ca928a5d/ruff-0.9.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3aa0f6b75082c9be1ec5a1db78c6d4b02e2375c3068438241dc19c7c306cc61a", size = 10336264 }, + { url = "https://files.pythonhosted.org/packages/6a/4e/7c268aa7d84cd709fb6f046b8972313142cffb40dfff1d2515c5e6288d54/ruff-0.9.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:584cc66e89fb5f80f84b05133dd677a17cdd86901d6479712c96597a3f28e7fe", size = 9908774 }, + { url = "https://files.pythonhosted.org/packages/cc/26/c618a878367ef1b76270fd027ca93692657d3f6122b84ba48911ef5f2edc/ruff-0.9.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abf3369325761a35aba75cd5c55ba1b5eb17d772f12ab168fbfac54be85cf18c", size = 11428127 }, + { url = "https://files.pythonhosted.org/packages/d7/9a/c5588a93d9bfed29f565baf193fe802fa676a0c837938137ea6cf0576d8c/ruff-0.9.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:3403a53a32a90ce929aa2f758542aca9234befa133e29f4933dcef28a24317be", size = 12133187 }, + { url = "https://files.pythonhosted.org/packages/3e/ff/e7980a7704a60905ed7e156a8d73f604c846d9bd87deda9cabfa6cba073a/ruff-0.9.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:18454e7fa4e4d72cffe28a37cf6a73cb2594f81ec9f4eca31a0aaa9ccdfb1590", size = 11602937 }, + { url = "https://files.pythonhosted.org/packages/24/78/3690444ad9e3cab5c11abe56554c35f005b51d1d118b429765249095269f/ruff-0.9.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fadfe2c88724c9617339f62319ed40dcdadadf2888d5afb88bf3adee7b35bfb", size = 13771698 }, + { url = "https://files.pythonhosted.org/packages/6e/bf/e477c2faf86abe3988e0b5fd22a7f3520e820b2ee335131aca2e16120038/ruff-0.9.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6df104d08c442a1aabcfd254279b8cc1e2cbf41a605aa3e26610ba1ec4acf0b0", size = 11249026 }, + { url = "https://files.pythonhosted.org/packages/f7/82/cdaffd59e5a8cb5b14c408c73d7a555a577cf6645faaf83e52fe99521715/ruff-0.9.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d7c62939daf5b2a15af48abbd23bea1efdd38c312d6e7c4cedf5a24e03207e17", size = 10220432 }, + { url = "https://files.pythonhosted.org/packages/fe/a4/2507d0026225efa5d4412b6e294dfe54725a78652a5c7e29e6bd0fc492f3/ruff-0.9.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:9494ba82a37a4b81b6a798076e4a3251c13243fc37967e998efe4cce58c8a8d1", size = 9874602 }, + { url = "https://files.pythonhosted.org/packages/d5/be/f3aab1813846b476c4bcffe052d232244979c3cd99d751c17afb530ca8e4/ruff-0.9.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:4efd7a96ed6d36ef011ae798bf794c5501a514be369296c672dab7921087fa57", size = 10851212 }, + { url = "https://files.pythonhosted.org/packages/8b/45/8e5fd559bea0d2f57c4e12bf197a2fade2fac465aa518284f157dfbca92b/ruff-0.9.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ab90a7944c5a1296f3ecb08d1cbf8c2da34c7e68114b1271a431a3ad30cb660e", size = 11327490 }, + { url = "https://files.pythonhosted.org/packages/42/55/e6c90f13880aeef327746052907e7e930681f26a164fe130ddac28b08269/ruff-0.9.9-py3-none-win32.whl", hash = "sha256:6b4c376d929c25ecd6d87e182a230fa4377b8e5125a4ff52d506ee8c087153c1", size = 10227912 }, + { url = "https://files.pythonhosted.org/packages/35/b2/da925693cb82a1208aa34966c0f36cb222baca94e729dd22a587bc22d0f3/ruff-0.9.9-py3-none-win_amd64.whl", hash = "sha256:837982ea24091d4c1700ddb2f63b7070e5baec508e43b01de013dc7eff974ff1", size = 11355632 }, + { url = "https://files.pythonhosted.org/packages/31/d8/de873d1c1b020d668d8ec9855d390764cb90cf8f6486c0983da52be8b7b7/ruff-0.9.9-py3-none-win_arm64.whl", hash = "sha256:3ac78f127517209fe6d96ab00f3ba97cafe38718b23b1db3e96d8b2d39e37ddf", size = 10435860 }, +] + +[[package]] +name = "taskipy" +version = "1.14.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama" }, + { name = "mslex", marker = "sys_platform == 'win32'" }, + { name = "psutil" }, + { name = "tomli", marker = "python_full_version < '4.0'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/44/572261df3db9c6c3332f8618fafeb07a578fd18b06673c73f000f3586749/taskipy-1.14.1.tar.gz", hash = "sha256:410fbcf89692dfd4b9f39c2b49e1750b0a7b81affd0e2d7ea8c35f9d6a4774ed", size = 14475 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/97/4e4cfb1391c81e926bebe3d68d5231b5dbc3bb41c6ba48349e68a881462d/taskipy-1.14.1-py3-none-any.whl", hash = "sha256:6e361520f29a0fd2159848e953599f9c75b1d0b047461e4965069caeb94908f1", size = 13052 }, +] + +[[package]] +name = "tomli" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708 }, + { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582 }, + { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543 }, + { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691 }, + { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170 }, + { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530 }, + { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666 }, + { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954 }, + { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724 }, + { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383 }, + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 }, +] + +[[package]] +name = "tree-view-cli" +version = "0.1.0" +source = { editable = "." } + +[package.dev-dependencies] +dev = [ + { name = "pre-commit" }, + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "ruff" }, + { name = "taskipy" }, +] + +[package.metadata] + +[package.metadata.requires-dev] +dev = [ + { name = "pre-commit", specifier = ">=4.1.0" }, + { name = "pytest", specifier = ">=8.3.5" }, + { name = "pytest-cov", specifier = ">=6.0.0" }, + { name = "ruff", specifier = ">=0.9.9" }, + { name = "taskipy", specifier = ">=1.14.1" }, +] + +[[package]] +name = "virtualenv" +version = "20.29.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f1/88/dacc875dd54a8acadb4bcbfd4e3e86df8be75527116c91d8f9784f5e9cab/virtualenv-20.29.2.tar.gz", hash = "sha256:fdaabebf6d03b5ba83ae0a02cfe96f48a716f4fae556461d180825866f75b728", size = 4320272 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/fa/849483d56773ae29740ae70043ad88e068f98a6401aa819b5d6bee604683/virtualenv-20.29.2-py3-none-any.whl", hash = "sha256:febddfc3d1ea571bdb1dc0f98d7b45d24def7428214d4fb73cc486c9568cce6a", size = 4301478 }, +]