Metadata-Version: 2.1
Name: gird
Version: 1.2.5
Summary: Make-like build tool & task runner for Python
Home-page: https://github.com/truhanen/gird
License: MIT
Author: Tuukka Ruhanen
Author-email: tuukka.t.ruhanen@gmail.com
Requires-Python: >=3.8,<4.0
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Topic :: Software Development :: Build Tools
Project-URL: Repository, https://github.com/truhanen/gird
Description-Content-Type: text/markdown

[//]: # (This README.md is autogenerated from README_template.md with the script
         render_readme.py)

# Gird

Gird is a lightweight & general-purpose [Make][make]-like build tool & task
runner for Python.

[make]: https://www.gnu.org/software/make/

Gird can be used to manage any project where some tasks need to be executed
automatically when some dependencies are updated. The goal of Gird is to combine
the following features.

- A simple, expressive, and intuitive rule definition and execution scheme very
  close to Make.
- Configuration in Python, allowing straightforward and familiar usage, without
  the need for a dedicated rule definition syntax.
- Ability to take advantage of Python's flexibility and possibility to easily
  integrate with Python libraries and tools.
- Emphasis on API simplicity & ease of use.

## Installation

Install Gird from PyPI with `pip install gird`, or from sources with
`pip install .`.

### Requirements

Gird is built & tested for Python versions 3.8 & above.

Gird requires [Make][make] to be available on the system. Most versions of Make
will do, as long as they support the `.PHONY` & `.ONESHELL` special targets.

## Usage

Define "rules" in *girdfile.py*. Depending on the composition of the rule
definition, a rule can, for example,

- define a recipe to run a task, e.g., to update a target file,
- define prerequisites for the target, such as dependency files or other rules,
  and
- use Python functions for more complex target & recipe functionality.

A rule is invoked by `gird <target_name>`. To list all targets, run
`gird --list`.

### Example girdfile.py

This is the girdfile.py of the project itself.

```python
from itertools import chain
from pathlib import Path

from gird import Phony, rule
from scripts import assert_readme_updated, get_wheel_path, render_readme

WHEEL_PATH = get_wheel_path()

rule_pytest = rule(
    target=Phony("pytest"),
    recipe="pytest",
    help="Run pytest.",
)

rule_check_formatting = rule(
    target=Phony("check_formatting"),
    recipe=[
        "black --check gird scripts girdfile.py",
        "isort --check gird scripts girdfile.py",
    ],
    help="Check formatting with Black & isort.",
)

rule_check_readme_updated = rule(
    target=Phony("check_readme_updated"),
    recipe=assert_readme_updated,
    help="Check that README.md is updated based on README_template.md.",
)

rules_test = [
    rule_pytest,
    rule_check_formatting,
    rule_check_readme_updated,
]

rule(
    target=Phony("test"),
    deps=rules_test,
    help="\n".join(f"- {rule.help}" for rule in rules_test),
)

rule(
    target=Path("README.md"),
    deps=list(
        chain(
            *(Path(path).iterdir() for path in ("scripts", "gird")),
            [Path("girdfile.py")],
        ),
    ),
    recipe=render_readme,
    help="Render README.md based on README_template.md.",
)

rule(
    target=WHEEL_PATH,
    recipe="poetry build --format wheel",
    help="Build distribution packages for the current version.",
)

rule(
    target=Phony("publish"),
    deps=WHEEL_PATH,
    recipe=f"twine upload --repository gird {WHEEL_PATH}",
    help="Publish packages of the current version to PyPI.",
)
```

Respective output from `gird --list`:

```
pytest
    Run pytest.
check_formatting
    Check formatting with Black & isort.
check_readme_updated
    Check that README.md is updated based on README_template.md.
test
    - Run pytest.
    - Check formatting with Black & isort.
    - Check that README.md is updated based on README_template.md.
README.md
    Render README.md based on README_template.md.
dist/gird-1.2.5-py3-none-any.whl
    Build distribution packages for the current version.
publish
    Publish packages of the current version to PyPI.
```

### Example rules

A rule with files as its target & dependency. When the rule is invoked, the
recipe is executed only if the dependency file has been or will be updated,
or if the target file doesn't exist.

```python
import pathlib
import gird
wheel = pathlib.Path("package.whl")

rule_build = gird.rule(
    target=wheel,
    deps=pathlib.Path("module.py"),
    recipe=f"python -m build --wheel",
)
```

A (phony) rule with no target file. Phony rules are always executed when
invoked.

```python
rule_test = gird.rule(
    target=gird.Phony("test"),
    deps=wheel,
    recipe="pytest",
)
```

A rule with other rules as dependencies, to group multiple rules together,
and to set the order of execution between rules.

```python
gird.rule(
    target=gird.Phony("all"),
    deps=[
        rule_test,
        rule_build,
    ],
)
```

A rule with a Python function recipe.

```python
import json
JSON1 = pathlib.Path("file1.json")
JSON2 = pathlib.Path("file2.json")

def create_target():
     JSON2.write_text(
         json.dumps(
             json.loads(
                 JSON1.read_text()
             ).update(value2="value2")
         )
     )

gird.rule(
    target=JSON2,
    deps=JSON1,
    recipe=create_target,
)
```

A Python function as a dependency to arbitrarily trigger rules.

```python
import datetime
EPOCH = datetime.datetime(2030, 1, 1)

@gird.dep
def unconditional_until_epoch():
    """Return the "updated" state of this dependency. Here, render a
    depending target outdated (trigger its recipe to be executed) always
    before EPOCH.
    """
    return datetime.datetime.now() < EPOCH

gird.rule(
    target=JSON2,
    deps=[JSON1, unconditional_until_epoch],
    recipe=create_target,
)
```

Compound recipes for, e.g., setup & teardown. All subrecipes of a rule are
run in a single shell instance.

```python
gird.rule(
    target=JSON2,
    deps=JSON1,
    recipe=[
        "export VALUE2=value2",
        create_target,
        "unset VALUE2",
    ],
)
```

Define rules in a loop, or however you like.

```python
rules = [
    gird.rule(
        target=source.with_suffix(".json.gz"),
        deps=source,
        recipe=f"gzip -k {source.resolve()}",
    )
    for source in [JSON1, JSON2]
]

```

## Implementation of Gird

Internally, Gird generates Makefiles & uses Make to run tasks, but interacting
with Make in any way isn't obligatory when using Gird. In the future, Make as a
dependency of Gird might be replaced altogether.

