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.4.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.4-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.4-cp313-cp313-macosx_11_0_arm64.whl (1.9 MB view details)

Uploaded CPython 3.13macOS 11.0+ ARM64

exhash-0.3.4-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.4-cp312-cp312-macosx_11_0_arm64.whl (1.9 MB view details)

Uploaded CPython 3.12macOS 11.0+ ARM64

exhash-0.3.4-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.4-cp311-cp311-macosx_11_0_arm64.whl (1.9 MB view details)

Uploaded CPython 3.11macOS 11.0+ ARM64

exhash-0.3.4-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.4-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.4.tar.gz.

File metadata

  • Download URL: exhash-0.3.4.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.4.tar.gz
Algorithm Hash digest
SHA256 e66b082b67ac540fbbe07fe35e62e80dc9800767117115332bbb79266c08ae46
MD5 5458834256eda3933c473276e7439cd1
BLAKE2b-256 8b823d888c093fa32a5a0e356102318bf4221f545a1ab6893d3082cc469655ec

See more details on using hashes here.

Provenance

The following attestation bundles were made for exhash-0.3.4.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.4-cp313-cp313-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for exhash-0.3.4-cp313-cp313-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 4d56d819d7f402852eb3357ce54870bc0a2ccd55ad2c4047fb1195a1bd33c43e
MD5 442dd2fa4a1fb462ce0b901e23f6fcd7
BLAKE2b-256 f0fdc70dacd6fbaf02a30ce435f04d962ba7d120c36e86a016739df8068f058d

See more details on using hashes here.

Provenance

The following attestation bundles were made for exhash-0.3.4-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.4-cp313-cp313-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for exhash-0.3.4-cp313-cp313-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 eec1e0a8ffc471d93cb711aa4af5ca6f7b761515f920d6be7134f9d78f5cfa2f
MD5 ea5254ceba0982d4bcb517dc6550c3d4
BLAKE2b-256 2bc3658e6e33e812b076149914d96de5f1c95f39c48dcc1880b51d2028730b22

See more details on using hashes here.

Provenance

The following attestation bundles were made for exhash-0.3.4-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.4-cp312-cp312-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for exhash-0.3.4-cp312-cp312-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 d41e29bc9500fc35c56595f3ab5307796baae31430777fc8783d1d03fe2011c1
MD5 faf88f4cfb70e9a991bf44178894d1fc
BLAKE2b-256 183dfcc7ef2e82913a4ee74504ce1888fb8be10b0974fd238fe3c089f3e2410b

See more details on using hashes here.

Provenance

The following attestation bundles were made for exhash-0.3.4-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.4-cp312-cp312-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for exhash-0.3.4-cp312-cp312-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 c56035ecb454ecc02c6d72ea77198a081c4af4db3a54bcdcb5113709e96eda4b
MD5 b6c3ea6d4c3a95dea0652e8a704d6b6e
BLAKE2b-256 b63ca4313fcfd2c992917b69de966399a3de3cc475e6c8de6e37454c22d8cc4b

See more details on using hashes here.

Provenance

The following attestation bundles were made for exhash-0.3.4-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.4-cp311-cp311-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for exhash-0.3.4-cp311-cp311-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 39266ba673a35574715b91178ff521fd6e815830c1c71b83cfdccea61857c306
MD5 adf5a290c1247aeafa98aa6b14121ec6
BLAKE2b-256 3a02941391085c19dd7e995f6ae7239dbf25f16eda30f47bbb213d47d7c077e7

See more details on using hashes here.

Provenance

The following attestation bundles were made for exhash-0.3.4-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.4-cp311-cp311-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for exhash-0.3.4-cp311-cp311-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 a352f851dd704c66483ca5d9ff02138c80c65d3cd2ac40b133ff75e2e3ea2c6d
MD5 768a218a2397b6dbf4864b5cef7a2bc2
BLAKE2b-256 66ecd21aef0627be6e6273d00fdf4f8c8309943178dfc8d73d05a530e62ec53e

See more details on using hashes here.

Provenance

The following attestation bundles were made for exhash-0.3.4-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.4-cp310-cp310-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for exhash-0.3.4-cp310-cp310-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 77e81e5bc4c71306ea39e54e79f57cb086c9766e69c4c21070618b102dbc30bb
MD5 d4883ce342d115bc150c600f57752f4e
BLAKE2b-256 029133d44934f0e375476a68c4fd70fe47fa32ad6d450d5097bc04b94f000ff4

See more details on using hashes here.

Provenance

The following attestation bundles were made for exhash-0.3.4-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.4-cp310-cp310-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for exhash-0.3.4-cp310-cp310-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 82b0cb2364b4fb9bc5829b3328d1c519bc584430fe2ba9b9082342e668afc053
MD5 4275c390b6c879090a0c1c93008cf4e1
BLAKE2b-256 82af6e4ff43275985c54913b0bcc8161c07f578960ad32d3041a2861585ad33f

See more details on using hashes here.

Provenance

The following attestation bundles were made for exhash-0.3.4-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