Skip to main content

Verified line-addressed file editor using lnhash addresses

Project description

exhash — Verified Line-Addressed File Editor

exhash combines Can Bölük's very clever line number + hash editing system with the powerful and expressive syntax of the classic ex editor.

Install via pip to get both a convenient Python API, and native CLI binaries:

pip install exhash

Or install just the CLI binaries via cargo:

cargo install exhash

lnhash format

We refer to an lnhash as a tag of the form lineno|hash|, where hash is the lower 16 bits of Rust's DefaultHasher over the line content.

Address forms:

  • lineno|hash| — hash-verified address
  • $ — last line (no hash)
  • % — whole file (1,$, no hashes)

CLI

The native Rust binaries are installed into your PATH via pip.

View

# Shows every line prefixed with its lnhash
lnhashview path/to/file.txt
# Optional line number range to show
lnhashview path/to/file.txt 10 20

If end is past EOF, lnhashview returns through the last available line instead of failing.

Edit

# Substitute on one line
exhash file.txt '12|abcd|s/foo/bar/g'

# Transliterate characters on one line
exhash file.txt '12|abcd|y/abc/ABC/'

# Append multiline text (terminated by a single dot)
exhash file.txt '12|abcd|a' <<'EOF'
new line 1
new line 2
.
EOF

# Dry-run
exhash --dry-run file.txt '12|abcd|d'

# Set shift width for < and >
exhash --sw 2 file.txt '12|abcd|>1'

# Last line and whole file shorthands (no hash)
exhash file.txt '$d'
exhash file.txt '%j'

# Move a line to EOF using $ as the destination
exhash file.txt '12|abcd|m$'

# Create a missing file by treating it as empty input
exhash new.txt '0|0000|a' <<'EOF'
first line
.
EOF

Substitute uses Rust regex syntax:

  • Pattern syntax is from regex
  • Replacement syntax is from regex::Replacer, e.g. $1, $0, ${name}
  • \/ escapes the command delimiter in pattern/replacement
  • Custom delimiters: s, y, g, g!, and v all accept any non-alphanumeric char as delimiter instead of /, e.g. s@pat@rep@, g@pat@cmd. Each command in a combo picks its own delimiter independently: g@a/b@s/old/new/
  • Literal newlines in pattern/replacement are supported (joins/splits lines as needed)
  • Transliteration uses y/src/dst/ and requires source/destination to have equal character counts

When passing multiple commands, each command's lnhashes are verified immediately before that command runs.

For a/i/c commands, provide the text block on stdin:

printf "new line 1\nnew line 2\n.\n" | exhash file.txt "2|beef|a"

If the file does not exist and the command set is valid on empty input, exhash treats it as an empty file and writes the result. For example, 0|0000|a can create a new file.

Stdin filter mode

cat file.txt | exhash --stdin - '1|abcd|s/foo/bar/'

In --stdin mode, multiline a/i/c text blocks are not available.

Python API

from exhash import exhash, exhash_file, lnhash, lnhashview, lnhashview_file, line_hash

Viewing

text = "foo\nbar\n"
view = lnhashview(text)                        # ["1|a1b2|  foo", "2|c3d4|  bar"]
view = lnhashview_file("f.py", start=1, end=260) # end past EOF is clamped

Editing

exhash(text, cmds, sw=4) takes the text and a required iterable of command strings (use [] for no-op). sw controls how far < and > shift. For multiline a/i/c commands, include the inserted text in the same command string using newline characters, e.g. ["12|abcd|c\nnew line 1\nnew line 2"]. Do not use . terminators, and do not split the text block into separate cmds entries. If you include a final . line, it is inserted literally and exhash emits a warning.

addr = lnhash(1, "foo")  # "1|a1b2|"
res = exhash(text, [f"{addr}s/foo/baz/"])
print(res["lines"])    # ["baz", "bar"]
print(res["modified"]) # [1]

# Multiple commands
a1, a2 = lnhash(1, "foo"), lnhash(2, "bar")
res = exhash(text, [f"{a1}s/foo/FOO/", f"{a2}s/bar/BAR/"])

# Hashes are checked just-in-time per command.
# If earlier commands change/shift a later target line, recompute lnhash first.

# Append multiline text in the same command string (no dot terminator)
res = exhash(text, [f"{addr}a\nnew line 1\nnew line 2"])

# Wrong for the Python API: the trailing "." would be inserted literally
# res = exhash(text, [f"{addr}a\nnew line 1\nnew line 2\n."])

# Also wrong: do not split the inserted text into separate cmds entries
# res = exhash(text, [f"{addr}a", "new line 1", "new line 2"])

# Change shift width for < and >
res = exhash(text, [f"{addr}>1"], sw=2)

# Custom delimiters (useful when pattern/replacement contains /)
res = exhash(text, [f"{addr}s|foo|bar|"])

# Literal newlines in pattern/replacement (joins/splits lines)
a1, a2 = lnhash(1, "foo"), lnhash(2, "bar")
res = exhash("foo\nbar\n", [f"{a1},{a2}s/foo\nbar/replaced/"])

File helpers

lnhashview_file reads directly from one file path. exhash_file(path, cmds, sw=4, inplace=False) uses path as the default file context for unqualified addresses, and also accepts file-qualified source and m/t destination addresses:

view = lnhashview_file("file.py")

# Returns FileSetEditResult, files unchanged
res = exhash_file("file.py", [f"{addr}s/foo/bar/"])
print(res.changed)          # ["file.py"]
print(res["file.py"].lines)
print(res.format_diff())    # includes --- file.py / +++ file.py headers

# With inplace=True, writes changed files after every command succeeds
# and returns the combined diff string.
diff = exhash_file("file.py", [f"{addr}s/foo/bar/"], inplace=True)

# Missing files are treated as empty only when the command is valid on empty input.
diff = exhash_file("new.py", ["0|0000|a\nprint('hi')"], inplace=True)

# File-qualified addresses can edit or transfer lines across files.
cmds = [
    "src/a.py:24|8f12|,38|c0de|m src/b.py:$",
    r"src/a.py:5|91aa|s/from \.b import old/from \.b import helper/",
]
diff = exhash_file("src/a.py", cmds, inplace=True)

A file prefix is separated from the address with :. Escape literal colons in filenames as \: and literal backslashes as \\.

exhash_file(..., inplace=False) returns a FileSetEditResult:

  • res.files — dict of path to FileEditResult
  • res.changed — changed paths, in first-touch order
  • res.default_path — the default path passed to exhash_file
  • res[path] — shorthand for res.files[path]
  • res.format_diff(context=1) — combined diff with --- path / +++ path headers

Pyskill

The package registers exhash.skill as a pyskill exposing the primary Python APIs with LLM-oriented workflow docs. Use doc(exhash.skill) after importing it through a pyskills host.

EditResult

exhash() returns an EditResult with attributes (also accessible via res["key"]):

  • lines — list of output lines
  • hashes — lnhash for each output line
  • modified — 1-based line numbers of modified/added lines
  • deleted — 1-based line numbers of removed lines (in original)
  • origins — for each output line, the 1-based original line number (None if inserted)

res.format_diff(context=1) returns a unified-diff-style summary showing only changed lines with context:

res = exhash(text, [f"{addr}s/foo/baz/"])
print(res.format_diff())
# --- original
# +++ modified
# -1|a1b2|  foo
# +1|c3d4|  baz
#  2|e5f6|  bar

Tests

cargo test && pytest -q

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

exhash-0.3.5.tar.gz (37.6 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

exhash-0.3.5-cp313-cp313-manylinux_2_34_x86_64.whl (2.2 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.34+ x86-64

exhash-0.3.5-cp313-cp313-macosx_11_0_arm64.whl (1.9 MB view details)

Uploaded CPython 3.13macOS 11.0+ ARM64

exhash-0.3.5-cp312-cp312-manylinux_2_34_x86_64.whl (2.2 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.34+ x86-64

exhash-0.3.5-cp312-cp312-macosx_11_0_arm64.whl (1.9 MB view details)

Uploaded CPython 3.12macOS 11.0+ ARM64

exhash-0.3.5-cp311-cp311-manylinux_2_34_x86_64.whl (2.2 MB view details)

Uploaded CPython 3.11manylinux: glibc 2.34+ x86-64

exhash-0.3.5-cp311-cp311-macosx_11_0_arm64.whl (1.9 MB view details)

Uploaded CPython 3.11macOS 11.0+ ARM64

exhash-0.3.5-cp310-cp310-manylinux_2_34_x86_64.whl (2.2 MB view details)

Uploaded CPython 3.10manylinux: glibc 2.34+ x86-64

exhash-0.3.5-cp310-cp310-macosx_11_0_arm64.whl (1.9 MB view details)

Uploaded CPython 3.10macOS 11.0+ ARM64

File details

Details for the file exhash-0.3.5.tar.gz.

File metadata

  • Download URL: exhash-0.3.5.tar.gz
  • Upload date:
  • Size: 37.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for exhash-0.3.5.tar.gz
Algorithm Hash digest
SHA256 5ea4a1a380934906989f34a71381d5b41cd9f4da44167505a709863dcdc4213a
MD5 9b81460ab8cd52b814822940410b4d7e
BLAKE2b-256 49be21441b4e9f4c4e11c598d9b51b997e06765968ad969c3930b951a7e72bbc

See more details on using hashes here.

Provenance

The following attestation bundles were made for exhash-0.3.5.tar.gz:

Publisher: ci.yml on AnswerDotAI/exhash

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file exhash-0.3.5-cp313-cp313-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for exhash-0.3.5-cp313-cp313-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 12aadd7a3a7026cb258a928943e6c1739de3a966711be64081e5161e1422190d
MD5 d79724e9feae776bfa722dd8c5ddb794
BLAKE2b-256 1a3b91ea412315fbb9329e3a1fc65a4dc1a0108aef5f376f0617d422c74e233b

See more details on using hashes here.

Provenance

The following attestation bundles were made for exhash-0.3.5-cp313-cp313-manylinux_2_34_x86_64.whl:

Publisher: ci.yml on AnswerDotAI/exhash

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file exhash-0.3.5-cp313-cp313-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for exhash-0.3.5-cp313-cp313-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 25aa1b00e7a51ea3dc57906f9abec5c13f819677fbc1e09176f4c7322967769c
MD5 aa8554335bb6757513709382838d43e8
BLAKE2b-256 b322c621f8adf136d8e4b14405573d4611b1ab15f8fda622eb33c5a8332cd616

See more details on using hashes here.

Provenance

The following attestation bundles were made for exhash-0.3.5-cp313-cp313-macosx_11_0_arm64.whl:

Publisher: ci.yml on AnswerDotAI/exhash

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file exhash-0.3.5-cp312-cp312-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for exhash-0.3.5-cp312-cp312-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 a935161608dc16015f5e0008811b7790dd5f8002f3b91c55ea19d3116339b5a8
MD5 072a6104f77d2799aacf8cd79f1c0015
BLAKE2b-256 675a03cb298612a5e1301e28cea635df29af2667d57985f0a63f1e0fb53c87b9

See more details on using hashes here.

Provenance

The following attestation bundles were made for exhash-0.3.5-cp312-cp312-manylinux_2_34_x86_64.whl:

Publisher: ci.yml on AnswerDotAI/exhash

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file exhash-0.3.5-cp312-cp312-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for exhash-0.3.5-cp312-cp312-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 596b016abc37ac24523eae73d3cceec4f916154f8802402dfddd78a9e893987e
MD5 143c230151b92a41941bd4ed99a44417
BLAKE2b-256 f925ac88919a78da260e2780e275e5cf1e9093c783ace9a1c82f4cc8cde63045

See more details on using hashes here.

Provenance

The following attestation bundles were made for exhash-0.3.5-cp312-cp312-macosx_11_0_arm64.whl:

Publisher: ci.yml on AnswerDotAI/exhash

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file exhash-0.3.5-cp311-cp311-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for exhash-0.3.5-cp311-cp311-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 49fc5854794be9780f08a9cdebbde1bb9efbd37610f29324fb95882044c3edf3
MD5 65955c56aaae237bf0d0dd0cdad17184
BLAKE2b-256 db2797e9897c8e66396b32af52feb6100f92c25b3d7ac38c4d6a9da4d1f30085

See more details on using hashes here.

Provenance

The following attestation bundles were made for exhash-0.3.5-cp311-cp311-manylinux_2_34_x86_64.whl:

Publisher: ci.yml on AnswerDotAI/exhash

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file exhash-0.3.5-cp311-cp311-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for exhash-0.3.5-cp311-cp311-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 6527025216cf7dc2cd36d260f45ec3843883804da7ca437ef634939f32838622
MD5 c6f717305f4938f178feadaec5777be2
BLAKE2b-256 03cfaa1c218f50d239e98255029062ca7c131018af0f668d880882f89f1aea74

See more details on using hashes here.

Provenance

The following attestation bundles were made for exhash-0.3.5-cp311-cp311-macosx_11_0_arm64.whl:

Publisher: ci.yml on AnswerDotAI/exhash

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file exhash-0.3.5-cp310-cp310-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for exhash-0.3.5-cp310-cp310-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 35b93c3311061b0b32c2ede1e442edc76770e9a049358396cb0308cd65fe50a2
MD5 2cf36ffb5a9002e648a368cd152b4ace
BLAKE2b-256 9c740e7c507532119259db3982ea33ba2b82d2bc3c37ad635ccbbe5e3e8b4e23

See more details on using hashes here.

Provenance

The following attestation bundles were made for exhash-0.3.5-cp310-cp310-manylinux_2_34_x86_64.whl:

Publisher: ci.yml on AnswerDotAI/exhash

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file exhash-0.3.5-cp310-cp310-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for exhash-0.3.5-cp310-cp310-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 25abad3906a48b96c558b20c49671106c84fbde55c12caee12983550807f3cc0
MD5 14602fb0d989040d79d9b611c7112444
BLAKE2b-256 71bd02488041e72c732165d88895c491fee82159e872f89e695dd6a35c8a9fec

See more details on using hashes here.

Provenance

The following attestation bundles were made for exhash-0.3.5-cp310-cp310-macosx_11_0_arm64.whl:

Publisher: ci.yml on AnswerDotAI/exhash

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page