Skip to content

Commit ab7feb6

Browse files
committed
fixes #9
1 parent 424689f commit ab7feb6

6 files changed

Lines changed: 72 additions & 44 deletions

File tree

python/exhash/__init__.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@ def lnhash(lineno:int, line:str) -> str:
1111
return _lnhash(lineno, line)
1212

1313

14-
def lnhashview(text:str) -> list[str]:
15-
'Return lines formatted as ``lineno|hash| content`` for each line in ``text``.'
16-
return _lnhashview(text)
14+
def lnhashview(text:str, start:int=None, end:int=None) -> list[str]:
15+
'Return lines formatted as ``lineno|hash| content``. Optional 1-based ``start``/``end`` filter the range.'
16+
return _lnhashview(text, start, end)
1717

1818

19-
def lnhashview_file(path:str) -> list[str]:
20-
'Return lines formatted as ``lineno|hash| content`` for each line in file at ``path``.'
21-
return _lnhashview(Path(path).read_text())
19+
def lnhashview_file(path:str, start:int=None, end:int=None) -> list[str]:
20+
'Return lines formatted as ``lineno|hash| content`` for file at ``path``. Optional 1-based ``start``/``end`` filter the range.'
21+
return _lnhashview(Path(path).read_text(), start, end)
2222

2323

2424
def exhash_result(results:list[dict]) -> str:

src/bin/lnhashview.rs

Lines changed: 14 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::env;
22
use std::fs;
33
use std::process;
44

5-
use exhash::format_lnhash;
5+
use exhash::lnhashview;
66

77
fn usage() {
88
eprintln!(
@@ -73,42 +73,24 @@ fn main() {
7373
return;
7474
}
7575

76-
let (start_line, end_line) = match (start, end) {
77-
(None, None) => (1, lines.len()),
78-
(Some(s), None) => (s, s),
79-
(Some(s), Some(e)) => (s, e),
76+
// When only start is given, CLI shows just that one line
77+
let (start, end) = match (start, end) {
78+
(Some(s), None) => (Some(s), Some(s)),
8079
(None, Some(_)) => {
8180
eprintln!("error: end_line requires start_line");
8281
process::exit(2);
8382
}
83+
other => other,
8484
};
8585

86-
if start_line == 0 {
87-
eprintln!("error: start_line is 1-based (must be >= 1)");
88-
process::exit(2);
89-
}
90-
91-
if end_line < start_line {
92-
eprintln!("error: end_line must be >= start_line");
93-
process::exit(2);
94-
}
95-
96-
if end_line > lines.len() {
97-
eprintln!(
98-
"error: end_line {end_line} is beyond EOF (file has {} line(s))",
99-
lines.len()
100-
);
101-
process::exit(2);
102-
}
103-
104-
for (idx, line) in lines
105-
.iter()
106-
.enumerate()
107-
.skip(start_line - 1)
108-
.take(end_line - start_line + 1)
109-
{
110-
let lineno = idx + 1;
111-
let lnh = format_lnhash(lineno, line);
112-
println!("{lnh} {line}");
86+
let result = match lnhashview(&lines, start, end) {
87+
Ok(r) => r,
88+
Err(e) => {
89+
eprintln!("error: {e}");
90+
process::exit(2);
91+
}
92+
};
93+
for line in result {
94+
println!("{line}");
11395
}
11496
}

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ mod parse;
1111
mod python;
1212

1313
pub use engine::{edit_text, edit_text_with_sw, EditResult};
14-
pub use lnhash::{format_lnhash, line_hash_u16, parse_lnhash, LnHash};
14+
pub use lnhash::{format_lnhash, line_hash_u16, lnhashview, parse_lnhash, LnHash};
1515
pub use parse::{parse_commands_from_args, parse_commands_from_script, parse_commands_from_strs, Address, Command, Subcommand};
1616

1717
#[derive(Debug, Clone)]

src/lnhash.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,24 @@ pub fn format_lnhash(lineno: usize, line: &str) -> String {
2525
format!("{}|{:04x}|", lineno, line_hash_u16(line))
2626
}
2727

28+
/// Format lines as `lineno|hash| content` for a range of lines.
29+
/// `start` and `end` are 1-based inclusive. Pass `None` for defaults (1 and len).
30+
/// Returns an error if start is 0, end < start, or end > number of lines.
31+
pub fn lnhashview(lines: &[&str], start: Option<usize>, end: Option<usize>) -> Result<Vec<String>, EditError> {
32+
if lines.is_empty() { return Ok(vec![]); }
33+
let s = start.unwrap_or(1);
34+
let e = end.unwrap_or(lines.len());
35+
if s == 0 { return Err(EditError::new("start_line is 1-based (must be >= 1)")); }
36+
if e < s { return Err(EditError::new("end_line must be >= start_line")); }
37+
if e > lines.len() {
38+
return Err(EditError::new(format!("end_line {} is beyond EOF (file has {} line(s))", e, lines.len())));
39+
}
40+
Ok(lines.iter().enumerate()
41+
.skip(s - 1).take(e - s + 1)
42+
.map(|(i, l)| format!("{} {}", format_lnhash(i + 1, l), l))
43+
.collect())
44+
}
45+
2846
/// Parse a `lineno|hash|` address.
2947
pub fn parse_lnhash(s: &str) -> Result<LnHash, EditError> {
3048
let (lh, rest) = parse_lnhash_prefix(s)?;

src/python.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ fn line_hash(line: &str) -> String { format!("{:04x}", crate::line_hash_u16(line
2828
fn lnhash(lineno: usize, line: &str) -> String { crate::format_lnhash(lineno, line) }
2929

3030
#[pyfunction]
31-
fn lnhashview(text: &str) -> Vec<String> {
32-
text.lines()
33-
.enumerate()
34-
.map(|(i, l)| format!("{} {}", crate::format_lnhash(i + 1, l), l))
35-
.collect()
31+
#[pyo3(signature = (text, start=None, end=None))]
32+
fn lnhashview(text: &str, start: Option<usize>, end: Option<usize>) -> PyResult<Vec<String>> {
33+
let lines: Vec<&str> = text.lines().collect();
34+
crate::lnhashview(&lines, start, end)
35+
.map_err(|e| PyValueError::new_err(e.to_string()))
3636
}
3737

3838
#[pyfunction]

tests/test_exhash.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,3 +235,31 @@ def test_exhash_file_inplace_no_change_on_error(tmp_path):
235235
f.write_text("foo\nbar\n")
236236
with pytest.raises(ValueError): exhash_file(str(f), ["99|ffff|s/x/y/"], inplace=True)
237237
assert f.read_text() == "foo\nbar\n"
238+
239+
def test_lnhashview_start_end():
240+
lines = lnhashview("a\nb\nc\nd", start=2, end=3)
241+
assert len(lines) == 2
242+
assert "b" in lines[0]
243+
assert "c" in lines[1]
244+
assert lines[0].startswith("2|")
245+
assert lines[1].startswith("3|")
246+
247+
def test_lnhashview_start_only():
248+
lines = lnhashview("a\nb\nc", start=2)
249+
assert len(lines) == 2
250+
assert lines[0].startswith("2|")
251+
252+
def test_lnhashview_end_only():
253+
lines = lnhashview("a\nb\nc", end=2)
254+
assert len(lines) == 2
255+
assert lines[0].startswith("1|")
256+
assert lines[1].startswith("2|")
257+
258+
def test_lnhashview_file_start_end(tmp_path):
259+
from exhash import lnhashview_file
260+
f = tmp_path / "test.txt"
261+
f.write_text("a\nb\nc\nd\n")
262+
lines = lnhashview_file(str(f), start=2, end=3)
263+
assert len(lines) == 2
264+
assert lines[0].startswith("2|")
265+
assert lines[1].startswith("3|")

0 commit comments

Comments
 (0)