Metadata-Version: 2.4
Name: jinja-multilint
Version: 0.1.2
Summary: Jinja2 template linter with multi-ecosystem filter awareness (Salt, Ansible, Home Assistant)
Project-URL: Homepage, https://gitlab.com/ggiesen/jinja-multilint
Project-URL: Repository, https://gitlab.com/ggiesen/jinja-multilint
Project-URL: Issues, https://gitlab.com/ggiesen/jinja-multilint/-/issues
Project-URL: Changelog, https://gitlab.com/ggiesen/jinja-multilint/-/blob/master/CHANGELOG.md
Author-email: "Gary T. Giesen" <ggiesen@giesen.me>
License-Expression: MPL-2.0
License-File: LICENSE
Keywords: ansible,home-assistant,jinja2,lint,salt,template
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Quality Assurance
Classifier: Topic :: Text Processing :: Markup
Requires-Python: >=3.10
Requires-Dist: jinja2>=3.0
Requires-Dist: pyyaml>=6.0
Provides-Extra: dev
Requires-Dist: black>=24.0; extra == 'dev'
Requires-Dist: build>=1.0; extra == 'dev'
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: ruff>=0.4.0; extra == 'dev'
Requires-Dist: twine>=5.0; extra == 'dev'
Description-Content-Type: text/markdown

# jinja-multilint

A Jinja2 template linter with ecosystem-aware filter and test name validation.

Built-in support for **Salt**, **Ansible**, and **Home Assistant**. Extensible via
plugins for any other Jinja2-based ecosystem, internal organization, or custom
filter library.

## Why

`jinja-multilint` fills a specific gap: **lightweight, ecosystem-aware
validation of Jinja2 filter and test names**, without requiring the full
runtime of the target ecosystem.

It is *not* a replacement for ecosystem-specialized linters like `ansible-lint`
or for full configuration validators like Home Assistant's `check_config`.
See [Related tools](#related-tools) for honest positioning of when to use
this versus alternatives.

## Install

```bash
pip install jinja-multilint
```

Requires Python 3.10+.

## Quick start

Lint a Salt template:

```bash
jinja-multilint --ecosystem salt template.sls
```

With a config file:

```yaml
# .jinja-multilint.yaml
ecosystems:
  - salt
filters:
  - my_custom_filter
extensions:
  - jinja2.ext.do
```

```bash
jinja-multilint template.sls
```

## Modes

```
--mode strict   # unknown filter/test → error (default)
--mode warn     # unknown filter/test → stderr warning, exit 0
--mode lax      # skip filter/test checks; only validate syntax
```

Syntax errors always fail regardless of mode.

## Configuration

Auto-detected files in current working directory (first match wins):

1. `.jinja-multilint.yaml` or `.jinja-multilint.yml`
2. `.jinja-multilint-filters` (flat list, one filter name per line; legacy
   compatibility for migrating from drm/jinja2-lint setups)

Override with `--config PATH`.

Full YAML schema:

```yaml
mode: strict                    # strict | warn | lax (default: strict)

ecosystems:                     # built-in or third-party plugin names
  - salt
  - ansible
  - my_org                      # third-party, see Plugins below

filters:                        # additional custom filter names
  - my_filter_one
  - my_filter_two

tests:                          # additional custom test names
  - my_test_one

extensions:                     # Jinja2 extension import paths
  - jinja2.ext.do
  - jinja2.ext.loopcontrols
```

CLI flags extend (do not replace) config values.

## CI/CD usage

### GitLab CI

```yaml
lint:jinja:
  image: python:3.12
  script:
    - pip install jinja-multilint
    - find . -type f -name "*.sls" -exec jinja-multilint --ecosystem salt {} +
```

### Bitbucket Pipelines

```yaml
- step:
    name: 'Lint-Jinja'
    image: python:3.12
    script:
      - pip install jinja-multilint
      - find . -type f -name "*.sls" -exec jinja-multilint --ecosystem salt {} +
```

### GitHub Actions

```yaml
- name: Lint Jinja templates
  run: |
    pip install jinja-multilint
    find . -type f -name "*.sls" -exec jinja-multilint --ecosystem salt {} +
```

### With a third-party plugin from PyPI

```yaml
script:
  - pip install jinja-multilint jinja-multilint-my-org
  - find . -type f -name "*.j2" -exec jinja-multilint --ecosystem my_org {} +
```

### With an internal plugin from a private package registry

```yaml
script:
  - pip install --extra-index-url $PRIVATE_INDEX_URL jinja-multilint jinja-multilint-internal
  - jinja-multilint --ecosystem salt --ecosystem internal templates/
```

### With an in-repo plugin

```yaml
script:
  - pip install jinja-multilint
  - pip install -e ./tooling/my-jinja-plugin
  - jinja-multilint --ecosystem my_thing templates/
```

### Pre-commit

```yaml
# .pre-commit-config.yaml
- repo: local
  hooks:
    - id: jinja-multilint
      name: Lint Jinja templates
      entry: jinja-multilint --ecosystem salt
      language: python
      additional_dependencies: [jinja-multilint]
      files: '\.(sls|jinja|j2)$'
```

## Plugins

A plugin is a Python package that registers an ecosystem under the
`jinja_multilint.ecosystems` entry-point group. The plugin module exposes:

- `FILTERS` — iterable of Jinja filter name strings
- `TESTS` — iterable of Jinja test name strings (used with `is`)
- `EXTENSIONS` — iterable of Jinja2 extension import paths (optional)

### Minimal plugin example

A plugin package called `jinja-multilint-myorg`:

```
jinja-multilint-myorg/
├── pyproject.toml
└── jml_myorg/
    └── __init__.py
```

`pyproject.toml`:

```toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "jinja-multilint-myorg"
version = "0.1.0"

[project.entry-points."jinja_multilint.ecosystems"]
myorg = "jml_myorg"
```

`jml_myorg/__init__.py`:

```python
FILTERS = frozenset({
    "myorg_custom_filter",
    "myorg_another_filter",
})

TESTS = frozenset({
    "myorg_test",
})

EXTENSIONS = []  # optional; defaults to empty
```

After `pip install ./jinja-multilint-myorg`, users reference the new ecosystem
by name (the entry-point key) in config or CLI:

```yaml
ecosystems:
  - myorg
```

Plugins can ship any subset of the three exports. A plugin that only defines
custom tests can omit `FILTERS` and `EXTENSIONS`.

### What's in the box

| Plugin name        | Source                                       |
| ------------------ | -------------------------------------------- |
| `salt`             | Salt's documented Jinja extensions           |
| `ansible`          | Ansible core filter/test set (no collections)|
| `home_assistant`   | Home Assistant template filters/tests        |

## Scope

`jinja-multilint` validates:

- Template syntax (via Jinja2's parser)
- Filter and test names against the configured allow-list / ecosystem plugins

It does NOT validate:

- Filter or test argument count or names
- Filter behavior or return types

Schema-aware filter signature validation may be added as opt-in plugin
metadata in a future release. The plugin API is forward-compatible with this
change — existing plugins will continue to work.

## Related tools

`jinja-multilint` occupies a specific niche. Other tools cover overlapping or
complementary ground; in many cases you should use those instead of (or in
addition to) this one.

### Style linters (complementary; run alongside)

- **[aristanetworks/j2lint](https://github.com/aristanetworks/j2lint)**
  (Apache 2.0): Jinja2 style and formatting rules — spaces around operators,
  variable naming conventions, statement indentation. Complements
  `jinja-multilint`: it checks how templates *look*, this checks whether
  filter and test names are *valid*. Both can run in the same CI pipeline.

### Ecosystem-specialized linters / validators

When you're working exclusively within one ecosystem, the specialized tool is
usually more thorough than `jinja-multilint` for that ecosystem and should be
preferred (or used alongside).

| Tool | License | Covers | When to prefer it |
| --- | --- | --- | --- |
| **[ansible-lint](https://github.com/ansible/ansible-lint)** | GPL-3.0+ | Ansible playbooks/roles/tasks — YAML, idioms, FQCN, module argument validation, and embedded Jinja2 with Ansible's filter set | Pure Ansible project. Strictly more thorough than us for Ansible content. |
| **[salt-lint](https://github.com/warpnet/salt-lint)** | MIT | Salt state file YAML, common antipatterns, SLS structure | Salt projects — but salt-lint does **not** validate Jinja2 filter names, so run it *alongside* `jinja-multilint`, not instead. |
| **Home Assistant `check_config`** | Apache 2.0 | Full HA config validation including template rendering against live state | You have HA running locally and want true render-time validation. Not suitable for lightweight CI without an HA install. |
| **[dbt parse](https://docs.getdbt.com/reference/commands/parse)** / `dbt compile` | Apache 2.0 | dbt model SQL + Jinja templates | Pure dbt project with dbt installed and a profile configured. |
| **[drm/jinja2-lint](https://github.com/drm/jinja2-lint)** | DBAD | Generic Jinja2 syntax + user-supplied filter list | The original syntax checker that inspired this project. DBAD license makes it awkward for downstream redistribution; no ecosystem knowledge built-in. |

### Decision guide

**Use a specialized linter when:**

- Your repo targets one ecosystem (just Ansible, just HA, just dbt).
- You need ecosystem-specific rules beyond filter name validation (idiomatic
  patterns, module argument checks, naming conventions).
- The specialized tool can run in your environment without overhead concerns.

**Use `jinja-multilint` when:**

- You work in Salt — salt-lint doesn't validate Jinja2 filter names, and this
  tool fills that gap.
- Your repo spans multiple ecosystems (e.g., Salt states plus some Ansible
  plus custom Jinja2 templates).
- You need lightweight CI checks without pulling in the full Ansible / HA /
  dbt runtime — `pip install jinja-multilint` and you're done.
- You have organization-specific custom filters and want allow-list
  validation without the heavyweight ecosystem tools' opinionated overhead.
- You need to write a plugin for a custom Jinja2 application that isn't
  covered by any specialized tool.

**Use them together when:**

- You have a Salt project: `salt-lint` for SLS structure, `jinja-multilint`
  for filter names, `aristanetworks/j2lint` for Jinja style.
- You have an Ansible project but also custom Jinja2 templates outside
  Ansible: `ansible-lint` for the Ansible bits, `jinja-multilint` for the
  custom ones.

## License

[MPL 2.0](LICENSE)
