Skip to content

Commit

Permalink
Add: Use rich library for improved UI #334
Browse files Browse the repository at this point in the history
Allow plugins to report progress which gets displayed via https://github.com/Textualize/rich/
  • Loading branch information
bjoernricks committed Aug 2, 2022
2 parents 6e1bcc9 + 9f6f7e0 commit 3c44e2a
Show file tree
Hide file tree
Showing 6 changed files with 259 additions and 132 deletions.
40 changes: 22 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ Welcome to **autohooks**!
- [Requirements](#requirements)
- [Modes](#modes)
- [Pythonpath Mode](#pythonpath-mode)
- [Pipenv Mode](#pipenv-mode)
- [Poetry Mode](#poetry-mode)
- [Pipenv Mode](#pipenv-mode)
- [Installing autohooks](#installing-autohooks)
- [1. Choosing an autohooks Mode](#1-choosing-an-autohooks-mode)
- [2. Installing the autohooks Python Package into the Current Environment](#2-installing-the-autohooks-python-package-into-the-current-environment)
Expand All @@ -34,7 +34,7 @@ Welcome to **autohooks**!

## Why?

Several outstanding libraries for managing and executing git hooks exist already.
Several outstanding libraries for managing and executing git hooks exist already.
To name a few: [husky](https://github.com/typicode/husky),
[lint-staged](https://github.com/okonet/lint-staged),
[precise-commits](https://github.com/nrwl/precise-commits) or
Expand All @@ -49,7 +49,7 @@ itself and does not install them in the current environment.

autohooks is a pure python library that installs a minimal
[executable git hook](https://github.com/greenbone/autohooks/blob/main/autohooks/precommit/template).
It allows the decision of how to maintain the hook dependencies
It allows the decision of how to maintain the hook dependencies
by supporting different modes.

## Requirements
Expand All @@ -61,8 +61,8 @@ Python 3.7+ is required for autohooks.
Currently three modes for using autohooks are supported:

* `pythonpath`
* `pipenv`
* `poetry`
* `pipenv`

These modes handle how autohooks, the plugins and their dependencies are loaded
during git hook execution.
Expand All @@ -89,9 +89,9 @@ In the `pythonpath` mode, the user has to install autohooks, the desired
plugins and their dependencies into the [PYTHONPATH](https://docs.python.org/3/library/sys.html#sys.path)
manually.

This can be achieved by running `pip install --user autohooks ...` to put them
This can be achieved by running `python3 -m pip install --user autohooks ...` to put them
into the installation directory of the [current user](https://docs.python.org/3/library/site.html#site.USER_SITE)
or with `pip install authooks ...` for a system wide installation.
or with `python3 -m pip install autohooks ...` for a system wide installation.

Alternatively, a [virtual environment](https://packaging.python.org/tutorials/installing-packages/#creating-and-using-virtual-environments)
could be used separating the installation from the global and user wide
Expand All @@ -103,14 +103,6 @@ environment but activating the environment has to be done manually.
Therefore it is even possible to run different versions of autohooks by
using the `pythonpath` mode and switching to a virtual environment.

### Pipenv Mode

In the `pipenv` mode [pipenv] is used to run autohooks in a dedicated virtual
environment. Pipenv uses a lock file to install exact versions. Therefore the
installation is deterministic and reliable between different developer setups.
In contrast to the `pythonpath` mode the activation of the virtual environment
provided by [pipenv] is done automatically in the background.

### Poetry Mode

Like with the [pipenv mode](#pipenv-mode), it is possible to run autohooks in a
Expand All @@ -120,6 +112,14 @@ executing the autohooks based git commit hook.

Using the `poetry` mode is highly recommended.

### Pipenv Mode

In the `pipenv` mode [pipenv] is used to run autohooks in a dedicated virtual
environment. Pipenv uses a lock file to install exact versions. Therefore the
installation is deterministic and reliable between different developer setups.
In contrast to the `pythonpath` mode the activation of the virtual environment
provided by [pipenv] is done automatically in the background.

## Installing autohooks

Four steps are necessary for installing autohooks:
Expand Down Expand Up @@ -221,28 +221,32 @@ The activation can always be verified by running `autohooks check`.

* Python code linting via [pylint](https://github.com/greenbone/autohooks-plugin-pylint)

* Python code linting via [flake8](https://github.com/greenbone/autohooks-plugin-flake8)

* Python import sorting via [isort](https://github.com/greenbone/autohooks-plugin-isort)

* Running tests via [pytest](https://github.com/greenbone/autohooks-plugin-pytest/)

## Howto: Writing a Plugin

Plugins need to be available in the
[Python import path](https://docs.python.org/3/reference/import.html). The
easiest way to achieve this is uploading a plugin to [PyPI](https://pypi.org/)
and installing it via [pip] or [pipenv].
and installing it via [pip] or [poetry].

Alternatively, a plugin can also be put into a *.autohooks* directory in the root
directory of the git repository where the hooks should be executed.

An autohooks plugin is a Python module which provides a **precommit** function.
The function must accept arbitrary keywords because the keywords are likely to
change in future. Therefore using **\*\*kwargs** is highly recommended.
Currently only a *config* keyword argument is passed to the precommit function.
Currently *config* and *report_progress* keyword arguments are passed to the
precommit function.

Example:

```python3
def precommit(**kwargs):
config = kwargs.get('config')
def precommit(config=None, report_progress=None, **kwargs):
```

The config can be used to receive settings from the *pyproject.toml* file, e.g.,
Expand Down
52 changes: 47 additions & 5 deletions autohooks/precommit/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@
import sys
from contextlib import contextmanager
from types import ModuleType
from typing import Generator
from typing import Generator, Optional

from autohooks.config import load_config_from_pyproject_toml
from autohooks.hooks import PreCommitHook
from autohooks.settings import Mode
from autohooks.terminal import Terminal, _set_terminal
from autohooks.terminal import Progress, Terminal, _set_terminal
from autohooks.utils import get_project_autohooks_plugins_path


Expand Down Expand Up @@ -75,6 +75,38 @@ def check_hook_mode(term: Terminal, config_mode: Mode, hook_mode: Mode) -> None:
)


class ReportProgress:
"""
A class to report progress of a plugin
"""

def __init__(self, progress: Progress, task_id: int) -> None:
self._progress = progress
self._task_id = task_id

def init(self, total: int) -> None:
"""
Init the progress with the total number to process
Args:
total: Most of the time this should be the number of files to
process.
"""
self._progress.update(self._task_id, total=total)

def update(self, advance: Optional[int] = 1) -> None:
"""
Update the number of already processed steps/items/files.
This increases the progress indicator.
Args:
advance: Number of steps/items/files the progress advanced. By
default 1.
"""
self._progress.advance(self._task_id, advance)


def run() -> int:
term = Terminal()

Expand All @@ -97,10 +129,11 @@ def run() -> int:

term.bold_info("autohooks => pre-commit")

with autohooks_module_path(), term.indent():
with autohooks_module_path(), term.indent(), Progress(
terminal=term
) as progress:
for name in config.get_pre_commit_script_names():
term.info(f"Running {name}")

with term.indent():
try:
plugin = load_plugin(name)
Expand All @@ -111,15 +144,24 @@ def run() -> int:
)
return 1

task_id = progress.add_task(
f"Running {name}", total=None, name=name
)
report_progress = ReportProgress(progress, task_id)
if has_precommit_parameters(plugin):
retval = plugin.precommit(config=config.get_config())
retval = plugin.precommit(
config=config.get_config(),
report_progress=report_progress,
)
else:
term.warning(
"precommit function without kwargs is deprecated. "
f"Please update {name} to a newer version."
)
retval = plugin.precommit()

progress.finish_task(task_id)

if retval:
return retval

Expand Down
21 changes: 20 additions & 1 deletion autohooks/terminal.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,15 @@

from typing import Optional

from pontos.terminal.terminal import ConsoleTerminal as Terminal
from pontos.terminal.rich import RichTerminal as Terminal
from pontos.terminal.terminal import Signs
from rich.progress import BarColumn
from rich.progress import Progress as RichProgress
from rich.progress import SpinnerColumn, TaskProgressColumn, TextColumn

__all__ = (
"Terminal",
"Progress",
"Signs",
"bold_info",
"error",
Expand Down Expand Up @@ -74,3 +78,18 @@ def _set_terminal(term: Optional[Terminal] = None) -> Terminal:
else:
__term = term
return __term


class Progress(RichProgress):
def __init__(self, terminal: Terminal) -> None:
super().__init__(
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
BarColumn(),
TaskProgressColumn(),
console=terminal._console,
transient=True,
)

def finish_task(self, task_id):
self.update(task_id, total=1, advance=1)
Loading

0 comments on commit 3c44e2a

Please sign in to comment.