exhash

Verified Line-Addressed File Editor

View the Project on GitHub AnswerDotAI/exhash

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. exhash is just like ex, except that addresses must be in lnhash format. Addresses like %, ., etc are not permitted.

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

Edit

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

# 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'

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"

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, lnhash, lnhashview, line_hash

Viewing

text = "foo\nbar\n"
view = lnhashview(text)  # ["1|a1b2|  foo", "2|c3d4|  bar"]

Editing

exhash(text, *cmds) takes the text and one or more command strings. For a/i/c commands, lines after the command are the text block (no . terminator needed):

addr = lnhash(1, "foo")  # "1|a1b2|"
res = exhash(text, f"{addr}s/foo/baz/")
print(res.text())      # "baz\nbar"
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/")

# Append multiline text (no dot terminator)
res = exhash(text, f"{addr}a\nnew line 1\nnew line 2")

EditResult

Tests

cargo test && pytest -q