{"id":23503175,"url":"https://github.com/kpfleming/jinjanator-plugins","last_synced_at":"2025-06-25T21:33:55.403Z","repository":{"id":183502186,"uuid":"670278885","full_name":"kpfleming/jinjanator-plugins","owner":"kpfleming","description":"Package which provides the plugin API for the Jinjanator tool","archived":false,"fork":false,"pushed_at":"2025-03-22T10:52:31.000Z","size":76,"stargazers_count":2,"open_issues_count":1,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-05-27T04:07:44.968Z","etag":null,"topics":["jinja2","jinja2-cli","pluggy","python","python3"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kpfleming.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":".github/CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":"kpfleming","liberapay":"kpfleming"}},"created_at":"2023-07-24T17:29:27.000Z","updated_at":"2025-03-22T10:52:33.000Z","dependencies_parsed_at":null,"dependency_job_id":"ea1c8243-df1f-404b-9ebe-12970757ea51","html_url":"https://github.com/kpfleming/jinjanator-plugins","commit_stats":null,"previous_names":["kpfleming/jinjanator-plugins"],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/kpfleming/jinjanator-plugins","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kpfleming%2Fjinjanator-plugins","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kpfleming%2Fjinjanator-plugins/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kpfleming%2Fjinjanator-plugins/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kpfleming%2Fjinjanator-plugins/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kpfleming","download_url":"https://codeload.github.com/kpfleming/jinjanator-plugins/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kpfleming%2Fjinjanator-plugins/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":259521511,"owners_count":22870448,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["jinja2","jinja2-cli","pluggy","python","python3"],"created_at":"2024-12-25T08:15:55.185Z","updated_at":"2025-06-25T21:33:55.383Z","avatar_url":"https://github.com/kpfleming.png","language":"Python","funding_links":["https://github.com/sponsors/kpfleming","https://liberapay.com/kpfleming"],"categories":[],"sub_categories":[],"readme":"# jinjanator-plugins\n\n\u003ca href=\"https://opensource.org\"\u003e\u003cimg height=\"150\" align=\"left\" src=\"https://opensource.org/files/OSIApprovedCropped.png\" alt=\"Open Source Initiative Approved License logo\"\u003e\u003c/a\u003e\n[![CI](https://github.com/kpfleming/jinjanator-plugins/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/kpfleming/jinjanator-plugins/actions/workflows/ci.yml)\n[![Python](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/release/python-3920/)\n[![License - Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-9400d3.svg)](https://spdx.org/licenses/Apache-2.0.html)\n[![Code Style - Black](https://img.shields.io/badge/Code%20Style-Black-000000.svg)](https://github.com/psf/black)\n[![Types - Mypy](https://img.shields.io/badge/Types-Mypy-blue.svg)](https://github.com/python/mypy)\n[![Code Quality - Ruff](https://img.shields.io/badge/Code%20Quality-Ruff-red.svg)](https://github.com/astral-sh/ruff)\n[![Project Management - Hatch](https://img.shields.io/badge/Project%20Management-Hatch-purple.svg)](https://github.com/pypa/hatch)\n[![Testing - Pytest](https://img.shields.io/badge/Testing-Pytest-orange.svg)](https://github.com/pytest-dev/pytest)\n\nThis repo contains `jinjanator-plugins`, a set of types and decorators\nwhich can be used to implement plugins for the\n[jinjanator](https://github.com/kpfleming/jinjanator) tool.\n\nOpen Source software: [Apache License 2.0](https://spdx.org/licenses/Apache-2.0.html)\n\n## \u0026nbsp;\n\u003c!-- fancy-readme start --\u003e\n\njinjanator can be extended through the use of plugins; these are\nPython packages installed into the same environment as the tool\nitself, which use special markers to 'hook' into various\nfeatures. There is a minimal example in the\n[plugin_example](plugin_example) directory which demonstrates three of\nthe four possible hooks.\n\nFor a more complete example, intended for publication, check out the\n[jinjanator-plugin-ansible](https://github.com/kpfleming/jinjanator-plugin-ansible)\nrepository.\n\nPlugins can provide:\n\n* *formats*: data parsers used to extract data from input files.\n\n* *filters*: functions used in Jinja2 templates to transform data.\n\n* *tests*: functions used in Jinja2 templates to make decisions in\n  conditional logic.\n\n* *globals*: functions used in Jinja2 templates to obtain data from\n  external sources.\n\n* *extensions*: Jinja2 extensions that can add extra filters, tests,\n  globals or even extend the parser.\n\nFor more details on the functionality and requirements for 'filters',\n'tests', 'globals', and 'extensions', refer to the Jinja2 documentation.\n\n## Installation\n\nNormally there is no need to install this package; instead it should\nbe listed as one of the dependencies for the plugin package itself.\n\nNote: It is *strongly* recommended to pin the dependency for this\npackage to a specific version, or if not, a \"year.release\" version\nrange (like \"23.2.*\"). Failure to pin to a very narrow range of\nversions may result in breakage of a plugin when the API is changed in\na non-backward-compatible fashion.\n\nThis is somewhat similar to *semantic versioning*, except not that :)\n\n## Basic structure\n\nA minimal plugin consists of two files:\n\n* `pyproject.toml`: provides package and project information, and\n  build instructions\n\n* Python source: provides functions to implement the desired features,\n  and hook markers to plug them into jinjanator\n\n### pyproject.toml\n\n```toml\n[build-system]\nbuild-backend = \"setuptools.build_meta\"\nrequires = [\n  \"setuptools\u003e=61\",\n]\n\n[project]\nname = \"jinjanator-plugin-example\"\nversion = \"0.0.0\"\ndependencies = [\n  \"jinjanator-plugins==23.3.0\",\n]\n\n[tool.setuptools]\npy-modules = [\"jinjanator_plugin_example\"]\n\n[project.entry-points.jinjanator]\nexample = \"jinjanator_plugin_example\"\n```\n\n#### build-system\n\nAny PEP517-compatible build system can be used, for this simple\nexample `setuptools` is being used.\n\n#### project\n\nThe `name` and `version` are required to build a package containing\nthe plugin. Note the *specific version dependency* on the\n`jinjanator-plugins` package.\n\n#### tool.setuptools\n\nThis section tells `setuptools` to include a single source file (not a\npackage directory) into the distribution. Note that this is a *module\nname*, not a *file name*, so there is no `.py` extension.\n\n#### project.entry-points.jinjanator\n\nThis is the first part of the 'magic' mechanism which allows\njinjanator to find the plugin. The entry here (which can use any name,\nbut should be related to the project name in order to avoid conflicts)\ncreates an 'entry point' which jinjanator can use to find the plugin;\nthe value is the name of the module which jinjanator should import to\nfind the plugin's hooks (and must match the name specified in the\n`tool.setuptools` section).\n\n### jinjanator_plugin_example.py\n\n```python\nimport codecs\n\nfrom jinjanator_plugins import (\n    FormatOptionUnknownError,\n    FormatOptionUnsupportedError,\n    FormatOptionValueError,\n    plugin_filters_hook,\n    plugin_formats_hook,\n    plugin_identity_hook,\n    plugin_tests_hook,\n    plugin_extensions_hook,\n)\n\n\ndef rot13_filter(value):\n    return codecs.encode(value, \"rot13\")\n\n\ndef is_len12_test(value):\n    return len(value) == 12\n\n\nclass SpamFormat:\n    name = \"spam\"\n    suffixes = (\".spam\",)\n    option_names = (\"ham\",)\n\n    def __init__(self, options):\n        self.ham = False\n\n        if options:\n            for option in options:\n                if option == \"ham\":\n                    self.ham = True\n\n    def parse(self, data_string):\n        if self.ham:\n            return {\n                \"ham\": \"ham\",\n                \"cheese\": \"ham and cheese\",\n                \"potatoes\": \"ham and potatoes\",\n            }\n\n        return {\n            \"spam\": \"spam\",\n            \"cheese\": \"spam and cheese\",\n            \"potatoes\": \"spam and potatoes\",\n        }\n\n\n@plugin_identity_hook\ndef plugin_identities():\n    return \"example\"\n\n\n@plugin_filters_hook\ndef plugin_filters():\n    return {\"rot13\": rot13_filter}\n\n\n@plugin_tests_hook\ndef plugin_tests():\n    return {\"len12\": is_len12_test}\n\n\n@plugin_formats_hook\ndef plugin_formats():\n    return {SpamFormat.name: SpamFormat}\n\n\n@plugin_extensions_hook\ndef plugin_extensions():\n    return ['jinja2.ext.debug']\n```\n\nNote that the real example makes use of type annotations, but they\nhave been removed here for simplicity.\n\n#### Imports\n\nThe imports from `jinjanator_plugins` are necessary for the plugin to:\n* Mark the hooks it wishes to use to provide additional features.\n* Construct one (or more) `Format` objects to describe the formats it\n  supports, if any.\n* Raise option-related exceptions from its format function, if any.\n\n#### rot13_filter\n\nA simple filter function which applies the `rot13` transformation to\nthe string value it receives.\n\n#### is_len12_test\n\nA simple test function which returns `True` if the value it receives\nhas length 12.\n\n#### SpamFormat\n\nA class providing a simple format function which ignores the content\nprovided (which jinjanator would have read from a data file), and\ninstead returns one of two canned responses based on whether the `ham`\noption has been provided by the user.\n\nThe `parse` method is the function which does the work; the `__init__`\nmethod handles options provided by the user; the class attributes\nprovide details of the format.\n\n#### plugin_identities\n\nThe hook function which will be called by jinjanator to allow this\nplugin to identify itself; the `@plugin_identities_hook` decorator\nmarks the function so that it will be found.\n\nThe function must return a string, which can contain any information\nneeded to identify the plugin. This should include the plugin's name\nand version, and can include versions of any packages on which it\ndepends.\n\nThis string will be included in the output generated by `jinjanate\n--version`, so it should not include any newlines or other formatting\ncharacters.\n\nNote that the function *must* be named `plugin_identities`; it is the\nsecond part of the 'magic' mechanism mentioned above.\n\n#### plugin_filters\n\nThe hook function which will be called by jinjanator to allow this\nplugin to register any filter functions it provides; the\n`@plugin_filters_hook` decorator marks the function so that it will be\nfound.\n\nThe function must return a dictionary, with each key being a filter\nfunction name (the name which will be used in the Jinja2 template to\ninvoke the function) and the corresponding value being a reference to\nthe function itself.\n\nNote that the function *must* be named `plugin_filters`; it is the\nsecond part of the 'magic' mechanism mentioned above.\n\n#### plugin_tests\n\nThe hook function which will be called by jinjanator to allow this\nplugin to register any test functions it provides; the\n`@plugin_tests_hook` decorator marks the function so that it will be\nfound.\n\nThe function must return a dictionary, with each key being a test\nfunction name (the name which will be used in the Jinja2 template to\ninvoke the function) and the corresponding value being a reference to\nthe function itself.\n\nNote that the function *must* be named `plugin_tests`; it is the\nsecond part of the 'magic' mechanism mentioned above.\n\n#### plugin_formats\n\nThe hook function which will be called by jinjanator to allow this\nplugin to register any format functions it provides; the\n`@plugin_formats_hook` decorator marks the function so that it will be\nfound.\n\nThe function must return a dictionary, with each key being a format\nfunction name (the name which will be used in the `--format` argument\nto jinjanator, if needed) and the corresponding value being a class\nwhich implements the requirements of the `Format` protocol (defined in\n[__init__.py](src/jinjanator_plugins/__init__.py)).\n\nIn particular these requirements include:\n\n* a class attribute called `name` which contains the name of the\n  format (for use in error messages)\n\n* a class attribute called 'suffixes' which contains a (possibly\n  empty) list of file suffixes which can be matched to this format\n  during format auto-detection\n\n* a class attribute called 'option_names' which contains a (possibly\n  empty) list of options which the user can provide to modify the\n  parser's behavior\n\n* a constructor method (`__init__`) which accepts a (possibly empty)\n  list of options provided by the user, and performs any validation\n  needed on them, storing the results in the `self` object\n\n* a `parse` method which accepts a (possibly empty) string containing\n  the input data, and parses it according to the format's needs, using\n  any previously-validated options stored in the `self` object\n\nNote that the hook function *must* be named `plugin_formats`; it is the\nsecond part of the 'magic' mechanism mentioned above.\n\nFormat classes can accept 'options' to modify their behavior, and\nshould raise the exceptions listed below, when needed, to inform the\nuser if one of the provided options does not meet the format's\nrequirements.\n\n* `FormatOptionUnknownError` will be raised automatically by the\n  jinjanator CLI based on the content of the `option_names` attribute of\n  the format class.\n\n* `FormatOptionUnsupportedError` should be raised when a provided\n  option is not supported in combination with the other provided\n  options or with the parsed data.\n\n* `FormatOptionValueError` should be raised when a provided option has\n  a value that is not valid.\n\n#### plugin_extensions\n\nThe hook function which will be called by jinjanator to allow this\nplugin to register any additional Jinja2 extensions; the\n`@plugin_extensions_hook` decorator marks the function so that it will be\nfound.\n\nThe function must return a list, with each entry being a string (the name\nof a Jinja2 extension that should be added to the Jinja2 Environment).\n\nThose strings should correspond to extensions that themselves are available\nin your python installation and available for Jinja2 to locate.\n\nSome examples:\n* Jinja2 own debug extension: `jinja2.ext.debug`\n* [Jinja-Markdown](https://github.com/jpsca/jinja-markdown): `jinja_markdown.MarkdownExtension`\n* [jinja-markdown2](https://github.com/mkbabb/jinja-markdown2): `jinja2_markdown.MarkdownExtension`\n* https://github.com/topics/jinja2-extension etc...\n\nNote that the function *must* be named `plugin_extensions`; it is the\nsecond part of the 'magic' mechanism mentioned above.\n\u003c!-- fancy-readme end --\u003e\n\n## Chat\n\nIf you'd like to chat with the jinjanator community, join us on\n[Matrix](https://matrix.to/#/#jinjanator:km6g.us)!\n\n## Credits\n\n[\"Standing on the shoulders of\ngiants\"](https://en.wikipedia.org/wiki/Standing_on_the_shoulders_of_giants)\ncould not be more true than it is in the Python community; this\nproject relies on many wonderful tools and libraries produced by the\nglobal open source software community, in addition to Python\nitself. I've listed many of them below, but if I've overlooked any\nplease do not be offended :-)\n\n* [Attrs](https://pypi.org/project/attrs)\n* [Black](https://pypi.org/project/black)\n* [Hatch-Fancy-PyPI-Readme](https://pypi.org/project/hatch-fancy-pypi-readme)\n* [Hatch](https://pypi.org/project/hatch)\n* [Mypy](https://pypi.org/project/mypy)\n* [Pluggy](https://pypi.org/project/pluggy)\n* [pyproject-fmt](https://pypi.org/project/pyproject-fmt)\n* [Pytest](https://pypi.org/project/pytest)\n* [Ruff](https://pypi.org/project/ruff)\n* [Towncrier](https://pypi.org/project/towncrier)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkpfleming%2Fjinjanator-plugins","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkpfleming%2Fjinjanator-plugins","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkpfleming%2Fjinjanator-plugins/lists"}