multi-line-replacer


Namemulti-line-replacer JSON
Version 1.1.0 PyPI version JSON
download
home_pageNone
SummaryA CLI utility for replacing multi-line strings in files. Supports textual replacements with wildcard matching indentation-awareness.
upload_time2025-07-09 19:18:07
maintainerNone
docs_urlNone
authorNone
requires_python>=3.9
licenseNone
keywords replacements cli command-line utility multi-line string wildcard indentation-aware
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Multi-Line Replacer (mlr)

*Copyright 2025 Caleb Evans*  
*Released under the MIT license*

[![tests](https://github.com/caleb531/multi-line-replacer/actions/workflows/tests.yml/badge.svg)](https://github.com/caleb531/multi-line-replacer/actions/workflows/tests.yml)
[![Coverage Status](https://coveralls.io/repos/caleb531/multi-line-replacer/badge.svg?branch=main)](https://coveralls.io/r/caleb531/multi-line-replacer?branch=main)

Multi-Line Replacer (mlr) is a CLI utility for replacing multi-line hunks of
strings across one or more files. Matching is mostly textual, but wildcard
matching is supported, and replacements are indentation-aware.

## Installation

You can install multi-line-replacer via your preferred global package manager
for Python:

```sh
# via pip
pip3 install multi-line-replacer
```

```sh
# via uv
uv tool install multi-line-replacer
```

```sh
# via pipx
pipx install multi-line-replacer
```

## Usage

The workflow takes one or more files on which to run replacements, and then one
or more "replacement rule" files with the `-r` flag:

```sh
mlr .github/workflows/*.yml -r example-rules/uv-gha.md
```

Each replacement rule must be a Markdown file with one or more pairs of GFM
fenced code blocks ([see documentation][gfm-docs]). Every odd code block
represents the target text to replace, and every even code block represents the
textual replacement. All other Markdown formatting is ignored, so feel free to
add headings, explainer text, or anything else!

````md
This rule replaces flake8 with ruff in a Github Actions linting workflow.

## flake8

```yml
- name: Run flake8
  run: flake8 MATCH_UNTIL_END_OF_LINE
```

## ruff

```yml
- name: Run ruff
  run: |
    uv run ruff check .
    uv run ruff format --check .
```
````

> [!NOTE]
The language specifier at the start of each code block is ignored by the
utility. Still, it is highly recommended to specify so that syntax highlighting
is enabled in your editor (i.e. it's for you, not the tool).

### Wildcard Matching

There are two special wildcard variables:

- `MATCH_UNTIL_END_OF_LINE` (`[^\n]*`)
- `MATCH_ALL_BETWEEN` (`[^\n]*?`)

These variables can be used anywhere in any code block representing the target
text to match. Because these names are unique enough, word boundaries are not
required around them (e.g. `vMATCH_UNTIL_END_OF_LINE` is allowed).

### Environment Variables

You can also access environment variable values anywhere in your target text or
replacement text. To do so, simply specify the name of your environment variable
prefixed with `MATCH_ENV_` (e.g. if your environment variable is `FOO_BAR`, you
would write `MATCH_ENV_FOO_BAR` into your rule file).

For instance, suppose you wish to upgrade the build system across a number of
Python projects. If you define `PROJECT_BUILD_SYSTEM`, `PROJECT_BUILD_BACKEND`,
and `PROJECT_PKG_NAME`, you could write a rule file to use them like so:

````md
## setuptools build-system

```toml
[build-system]
requires = ["MATCH_ENV_PROJECT_BUILD_SYSTEM"]
build-backend = "MATCH_ENV_PROJECT_BUILD_BACKEND"
```

# uv_build build-system

```toml
[build-system]
requires = ["uv_build>=0.7.19,<0.8.0"]
build-backend = "uv_build"

[tool.uv.build-backend]
module-name = "MATCH_ENV_PROJECT_PKG_NAME"
module-root = ""
```
````

### Removing Lines

If you don't want to replace the text with anything, simply use an empty fenced
code block for the replacement block in your rule file.

````md
## clone with submodules

```yml
with:
  submodules: recursive
```

## disable submodule detection

```yml
```
````

### More Examples

To better understand the expected rules format and what's allowed, please see
the `example-rules` directory.

[gfm-docs]: https://github.github.com/gfm/#fenced-code-blocks

## About

Multi-Line Replacer was built as my solution to an intermediate need I had while
writing a large migration script. I had 17 Python projects using old tooling,
and the script was written to migrate these projects to [uv][uv] and
[ruff][ruff].

Part of this migration process necessitated performing textual replacements on
multi-line hunks of code. Regular expressions and editors like VS Code could
somewhat achieve this, although they required escaping special characters and
carefully specifying indentation. In other words, those tools proved to be too
rigid and inflexible.

Given these constraints, I conceived of a utility that could perform multi-line
replacements with a friendlier authoring experience and greater indentation
awareness. The implementation took several iterations to achieve positive
results, but by the end, it contributed significantly to the successful
migration of all 17 projects. From there, I decided to release it to the world
as a more flexible and automated system for replacing multi-line hunks of code.

[uv]: https://docs.astral.sh/uv/
[ruff]: https://docs.astral.sh/ruff/

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "multi-line-replacer",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.9",
    "maintainer_email": "Caleb Evans <caleb@calebevans.me>",
    "keywords": "replacements, cli, command-line, utility, multi-line, string, wildcard, indentation-aware",
    "author": null,
    "author_email": "Caleb Evans <caleb@calebevans.me>",
    "download_url": "https://files.pythonhosted.org/packages/a0/60/1f5de95ea1fa2be30f8d3e5d8ed6ed8b38f1ca682bb1f04ec9ad2e215166/multi_line_replacer-1.1.0.tar.gz",
    "platform": null,
    "description": "# Multi-Line Replacer (mlr)\n\n*Copyright 2025 Caleb Evans*  \n*Released under the MIT license*\n\n[![tests](https://github.com/caleb531/multi-line-replacer/actions/workflows/tests.yml/badge.svg)](https://github.com/caleb531/multi-line-replacer/actions/workflows/tests.yml)\n[![Coverage Status](https://coveralls.io/repos/caleb531/multi-line-replacer/badge.svg?branch=main)](https://coveralls.io/r/caleb531/multi-line-replacer?branch=main)\n\nMulti-Line Replacer (mlr) is a CLI utility for replacing multi-line hunks of\nstrings across one or more files. Matching is mostly textual, but wildcard\nmatching is supported, and replacements are indentation-aware.\n\n## Installation\n\nYou can install multi-line-replacer via your preferred global package manager\nfor Python:\n\n```sh\n# via pip\npip3 install multi-line-replacer\n```\n\n```sh\n# via uv\nuv tool install multi-line-replacer\n```\n\n```sh\n# via pipx\npipx install multi-line-replacer\n```\n\n## Usage\n\nThe workflow takes one or more files on which to run replacements, and then one\nor more \"replacement rule\" files with the `-r` flag:\n\n```sh\nmlr .github/workflows/*.yml -r example-rules/uv-gha.md\n```\n\nEach replacement rule must be a Markdown file with one or more pairs of GFM\nfenced code blocks ([see documentation][gfm-docs]). Every odd code block\nrepresents the target text to replace, and every even code block represents the\ntextual replacement. All other Markdown formatting is ignored, so feel free to\nadd headings, explainer text, or anything else!\n\n````md\nThis rule replaces flake8 with ruff in a Github Actions linting workflow.\n\n## flake8\n\n```yml\n- name: Run flake8\n  run: flake8 MATCH_UNTIL_END_OF_LINE\n```\n\n## ruff\n\n```yml\n- name: Run ruff\n  run: |\n    uv run ruff check .\n    uv run ruff format --check .\n```\n````\n\n> [!NOTE]\nThe language specifier at the start of each code block is ignored by the\nutility. Still, it is highly recommended to specify so that syntax highlighting\nis enabled in your editor (i.e. it's for you, not the tool).\n\n### Wildcard Matching\n\nThere are two special wildcard variables:\n\n- `MATCH_UNTIL_END_OF_LINE` (`[^\\n]*`)\n- `MATCH_ALL_BETWEEN` (`[^\\n]*?`)\n\nThese variables can be used anywhere in any code block representing the target\ntext to match. Because these names are unique enough, word boundaries are not\nrequired around them (e.g. `vMATCH_UNTIL_END_OF_LINE` is allowed).\n\n### Environment Variables\n\nYou can also access environment variable values anywhere in your target text or\nreplacement text. To do so, simply specify the name of your environment variable\nprefixed with `MATCH_ENV_` (e.g. if your environment variable is `FOO_BAR`, you\nwould write `MATCH_ENV_FOO_BAR` into your rule file).\n\nFor instance, suppose you wish to upgrade the build system across a number of\nPython projects. If you define `PROJECT_BUILD_SYSTEM`, `PROJECT_BUILD_BACKEND`,\nand `PROJECT_PKG_NAME`, you could write a rule file to use them like so:\n\n````md\n## setuptools build-system\n\n```toml\n[build-system]\nrequires = [\"MATCH_ENV_PROJECT_BUILD_SYSTEM\"]\nbuild-backend = \"MATCH_ENV_PROJECT_BUILD_BACKEND\"\n```\n\n# uv_build build-system\n\n```toml\n[build-system]\nrequires = [\"uv_build>=0.7.19,<0.8.0\"]\nbuild-backend = \"uv_build\"\n\n[tool.uv.build-backend]\nmodule-name = \"MATCH_ENV_PROJECT_PKG_NAME\"\nmodule-root = \"\"\n```\n````\n\n### Removing Lines\n\nIf you don't want to replace the text with anything, simply use an empty fenced\ncode block for the replacement block in your rule file.\n\n````md\n## clone with submodules\n\n```yml\nwith:\n  submodules: recursive\n```\n\n## disable submodule detection\n\n```yml\n```\n````\n\n### More Examples\n\nTo better understand the expected rules format and what's allowed, please see\nthe `example-rules` directory.\n\n[gfm-docs]: https://github.github.com/gfm/#fenced-code-blocks\n\n## About\n\nMulti-Line Replacer was built as my solution to an intermediate need I had while\nwriting a large migration script. I had 17 Python projects using old tooling,\nand the script was written to migrate these projects to [uv][uv] and\n[ruff][ruff].\n\nPart of this migration process necessitated performing textual replacements on\nmulti-line hunks of code. Regular expressions and editors like VS Code could\nsomewhat achieve this, although they required escaping special characters and\ncarefully specifying indentation. In other words, those tools proved to be too\nrigid and inflexible.\n\nGiven these constraints, I conceived of a utility that could perform multi-line\nreplacements with a friendlier authoring experience and greater indentation\nawareness. The implementation took several iterations to achieve positive\nresults, but by the end, it contributed significantly to the successful\nmigration of all 17 projects. From there, I decided to release it to the world\nas a more flexible and automated system for replacing multi-line hunks of code.\n\n[uv]: https://docs.astral.sh/uv/\n[ruff]: https://docs.astral.sh/ruff/\n",
    "bugtrack_url": null,
    "license": null,
    "summary": "A CLI utility for replacing multi-line strings in files. Supports textual replacements with wildcard matching indentation-awareness.",
    "version": "1.1.0",
    "project_urls": {
        "changelog": "https://github.com/caleb531/multi-line-replacer/releases",
        "documentation": "https://github.com/caleb531/multi-line-replacer#readme",
        "homepage": "https://github.com/caleb531/multi-line-replacer",
        "repository": "https://github.com/caleb531/multi-line-replacer"
    },
    "split_keywords": [
        "replacements",
        " cli",
        " command-line",
        " utility",
        " multi-line",
        " string",
        " wildcard",
        " indentation-aware"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "b0ec57278e2e40f193c0641e37f0c338e88a93df1d60db4159503cb3405e9a89",
                "md5": "c955c2024ca5eeefaec9046615258b2a",
                "sha256": "2b2181aeedd51a9b0495ae79391d948a2076b3fa78cc4e3bee31a3690d166453"
            },
            "downloads": -1,
            "filename": "multi_line_replacer-1.1.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "c955c2024ca5eeefaec9046615258b2a",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.9",
            "size": 8179,
            "upload_time": "2025-07-09T19:18:07",
            "upload_time_iso_8601": "2025-07-09T19:18:07.099992Z",
            "url": "https://files.pythonhosted.org/packages/b0/ec/57278e2e40f193c0641e37f0c338e88a93df1d60db4159503cb3405e9a89/multi_line_replacer-1.1.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "a0601f5de95ea1fa2be30f8d3e5d8ed6ed8b38f1ca682bb1f04ec9ad2e215166",
                "md5": "1c9f1d7c32adeb7e40738d8571098491",
                "sha256": "603930e4772e2307ac4ccd665c7ffee7675898ecc548255d12f67b5bf2e6fc4f"
            },
            "downloads": -1,
            "filename": "multi_line_replacer-1.1.0.tar.gz",
            "has_sig": false,
            "md5_digest": "1c9f1d7c32adeb7e40738d8571098491",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.9",
            "size": 8890,
            "upload_time": "2025-07-09T19:18:07",
            "upload_time_iso_8601": "2025-07-09T19:18:07.813694Z",
            "url": "https://files.pythonhosted.org/packages/a0/60/1f5de95ea1fa2be30f8d3e5d8ed6ed8b38f1ca682bb1f04ec9ad2e215166/multi_line_replacer-1.1.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-07-09 19:18:07",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "caleb531",
    "github_project": "multi-line-replacer",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "multi-line-replacer"
}
        
Elapsed time: 1.57471s