Metadata-Version: 2.4
Name: snipwise
Version: 1.1.0
Summary: Keep text snippets in sync across files in a project
Author-email: Toon Verstraelen <toon.verstraelen@ugent.be>
License-Expression: LGPL-3.0-or-later
Project-URL: Documentation, https://reproducible-reporting.github.io/snipwise/
Project-URL: Issues, https://github.com/reproducible-reporting/snipwise/issues
Project-URL: Source, https://github.com/reproducible-reporting/snipwise/
Project-URL: Changelog, https://reproducible-reporting.github.io/snipwise/changelog/
Keywords: snippets,duplication,consistency,single source of truth,DRY,don't repeat yourself,pre-commit,pre-commit-hook,markdown,documentation,readme,linter,text processing,json
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Natural Language :: English
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Documentation
Classifier: Topic :: Software Development :: Documentation
Classifier: Topic :: Software Development :: Pre-processors
Classifier: Topic :: Software Development :: Quality Assurance
Classifier: Topic :: Text Processing :: Markup :: Markdown
Classifier: Typing :: Typed
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: attrs>=23.1.0
Requires-Dist: cattrs>=23.2.3
Requires-Dist: jinja2>=3.1.2
Requires-Dist: markdown-it-py>=3.0.0
Provides-Extra: tests
Requires-Dist: pytest>=8.0.0; extra == "tests"
Requires-Dist: pytest-timeout>=2.3.1; extra == "tests"
Provides-Extra: dev
Requires-Dist: snipwise[tests]; extra == "dev"
Requires-Dist: reuse>=6.2.0; extra == "dev"
Requires-Dist: zensical>=0.0.57; extra == "dev"
Dynamic: license-file

<!--
SPDX-FileCopyrightText: 2026 Toon Verstraelen <Toon.Verstraelen@UGent.be>
SPDX-License-Identifier: LGPL-3.0-or-later
-->

# ✂️ Snipwise

[![release](https://github.com/reproducible-reporting/snipwise/actions/workflows/release.yaml/badge.svg?branch=main)](https://github.com/reproducible-reporting/snipwise/actions/workflows/release.yaml)
[![pytest](https://github.com/reproducible-reporting/snipwise/actions/workflows/pytest.yaml/badge.svg?branch=main)](https://github.com/reproducible-reporting/snipwise/actions/workflows/pytest.yaml)
[![zensical](https://github.com/reproducible-reporting/snipwise/actions/workflows/zensical.yaml/badge.svg?branch=main)](https://reproducible-reporting.github.io/snipwise/)
[![PyPI Version](https://img.shields.io/pypi/v/snipwise)](https://pypi.org/project/snipwise/)
![PyPI - Python Version](https://img.shields.io/pypi/pyversions/snipwise)
![LGPL-3 License](https://img.shields.io/badge/License-LGPL_v3-blue.svg)

<!-- README.md BEGIN intro -->
Snipwise keeps text snippets in sync across files in a project.
Provide a single source of truth for a fragment,
and Snipwise will copy it into every README, documentation page or configuration file
that repeats it, so that no copy has to be updated manually.

It is generally preferable to avoid redundant information,
but sometimes it is unavoidable.
The same paragraph on supported Python versions may belong in the README and in the documentation,
and the same project summary may be required by several metadata formats.
Snipwise works with any text format,
because you write the markers yourself in the comment syntax of the file at hand.
As a `pre-commit` hook, it keeps every commit consistent
instead of leaving stale copies adrift until somebody notices.
<!-- README.md END intro -->

## Simple Example

<!-- README.md BEGIN simple-example -->
Snipwise is configured by a `snipwise.md` file in the root of your project.
This example shows how to define a snippet in `snipwise.md`
and copy it into the `README.md` and `docs/installation.md` files.

`````markdown
# Snipwise Configuration

```toml
[[targets]]
patterns = ["README.md", "docs/*.md"]
```

## `python-versions`

```text
Snipwise requires Python 3.11 or later.
It is tested on Python 3.11, 3.12, 3.13 and 3.14.
```
`````

Both `README.md` and `docs/installation.md` then contain a pair of markers:

```markdown
<!-- snipwise.md BEGIN python-versions -->
<!-- snipwise.md END python-versions -->
```

Running `snipwise fix` writes the snippet between each pair of markers:

```markdown
<!-- snipwise.md BEGIN python-versions -->
Snipwise requires Python 3.11 or later.
It is tested on Python 3.11, 3.12, 3.13 and 3.14.
<!-- snipwise.md END python-versions -->
```

From then on, `snipwise` reports any file that has drifted away from `snipwise.md`,
and `snipwise fix` brings it back in line.
The snippet is stored in three places,
but only the copy in `snipwise.md` is meant to be edited.
The others are verified (and fixed) mechanically.

The basic idea is simple, but Snipwise also packs a number of convenient features
for when this simple approach falls short:

- A snippet may be transformed on its way into a file,
  so that it can serve different file formats,
  such as Markdown, LaTeX, plain text or source code.
- A file that cannot carry a comment can be reached with a regular expression
  or with a JSON pointer instead of a marker.
- If a snippet is naturally defined by a specific file,
  there is no need to copy it into `snipwise.md`.

<!-- README.md END simple-example -->

## Installation

<!-- README.md BEGIN installation -->

```bash
pip install snipwise
```

<!-- README.md END installation -->

## Documentation

The documentation lives at <https://reproducible-reporting.github.io/snipwise/>.

Start with these pages, the first three of which together are the reference for what Snipwise does:

- [The `snipwise.md` Configuration File][snipwisefile] defines snippets
  and says which files receive them, in what shape, and which copy is the original.
- [Snippet Scanners][scanners] covers the four ways a rule finds
  the regions of a file that it owns:
  the whole file, markers, a regular expression, or a JSON pointer.
- [Command-Line Interface][usage] covers the subcommands, the options,
  the exit codes and the `pre-commit` hook.
- [Snipwise in Practice][practice] discusses good practices,
  starting with which text is worth turning into a snippet.

For the development of Snipwise itself:

- [Development Environment][environment] is how to install, test and lint.
- [Code Architecture][architecture] is a map of the package.
- [Design Principles][design] records why the configuration
  has the shape it has, and is the document to read before changing it.
- [The changelog][changelog] lists the notable changes of every release.

  [snipwisefile]: https://reproducible-reporting.github.io/snipwise/snipwisefile/
  [scanners]: https://reproducible-reporting.github.io/snipwise/scanners/
  [usage]: https://reproducible-reporting.github.io/snipwise/usage/
  [practice]: https://reproducible-reporting.github.io/snipwise/practice/
  [environment]: https://reproducible-reporting.github.io/snipwise/environment/
  [architecture]: https://reproducible-reporting.github.io/snipwise/architecture/
  [design]: https://reproducible-reporting.github.io/snipwise/design/
  [changelog]: https://reproducible-reporting.github.io/snipwise/changelog/

<!-- README.md BEGIN trailing-sections -->

## Example Repositories

The following repositories use Snipwise to keep various snippets in sync:

- [Snipwise itself](https://github.com/reproducible-reporting/snipwise)
  uses [snipwise.md](https://github.com/reproducible-reporting/snipwise/blob/main/snipwise.md)
  to synchronize the list of dependencies
  and several sections shared by the README and the documentation home page.
- [StepUp Core](https://github.com/reproducible-reporting/stepup-core)
  uses [snipwise.md](https://github.com/reproducible-reporting/stepup-core/blob/main/snipwise.md)
  to synchronize a tagline, abstract and keywords across several files.

## Related Tools

Snipwise only copies text that you maintain by hand, and it never generates anything.
That is a deliberate limit,
so here is where to look when that limitation gets in the way.

- To embed the **output of a program**,
  [Cog](https://nedbatchelder.com/code/cog/) runs a piece of Python
  and writes what it prints back into the file that holds it.
  [embedme](https://www.npmjs.com/package/embedme) pulls the content of a source file
  into a fenced code block of a Markdown document.
- To **instantiate a project from a template**, and to keep it in step with that template later,
  there are [Cookiecutter](https://cookiecutter.readthedocs.io/) and
  [Copier](https://copier.readthedocs.io/).
  Snipwise has nothing to say about a file until that file carries markers of its own.
- To **include a fragment while a documentation site is built**, there are the
  [snippets](https://facelessuser.github.io/pymdown-extensions/extensions/snippets/)
  extension of PyMdown Extensions for MkDocs,
  and the `include` and `literalinclude` directives of Sphinx.
  What differs is where the text ends up.
  Those mechanisms resolve during a build, so the text exists only in the generated site,
  while Snipwise writes it into the file itself,
  where GitHub, PyPI and a plain text editor all show it.

## License

Snipwise is distributed under the terms of the
[LGPL-3.0-or-later](https://github.com/reproducible-reporting/snipwise/blob/main/LICENSE) license.
Logo based on [Google Noto Emoji](https://github.com/googlefonts/noto-emoji),
licensed under SIL OFL 1.1
<!-- README.md END trailing-sections -->
