diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-07-27 23:54:29 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-07-27 23:54:29 +0200 |
| commit | 2f20d41921a4ed08d0a7b16246215e0eee5ac512 (patch) | |
| tree | e09dc26734f16a2285ee3436dcadbe5ccbaf3de9 /internal/bible/ref.go | |
| parent | e6eba32facf520d202f7941f43206e0f95141452 (diff) | |
| download | lectio-2f20d41921a4ed08d0a7b16246215e0eee5ac512.tar.gz lectio-2f20d41921a4ed08d0a7b16246215e0eee5ac512.zip | |
feat(bible): resolve and display cross-chapter verse ranges (N:M-P:Q)
Lookup now detects a verse-tail range that crosses a chapter boundary
("M-P:Q", e.g. "30-28:7" in "Sirach 27:30-28:7") and fetches chapter N
from verse M to its end, any whole chapters between, and chapter P from
verse 1 through Q, instead of misreading the tail as a plain from-to
range and coming up empty. SplitRef expands the same pattern inside a
comma list so a trailing group after the jump (e.g. the ",8-10" in
"Malachi 1:14-2:2,8-10") resolves against the new current chapter.
FormatRef renders the range intact as "<sigla> N:M-P:Q" instead of
splitting off a fake new-chapter marker from the embedded "P:" and
mangling the display (previously "Sir 27:7").
No sentinel value is ever stored in a ref string; it only bounds an
internal verse-filter loop in Lookup.
Diffstat (limited to 'internal/bible/ref.go')
| -rw-r--r-- | internal/bible/ref.go | 62 |
1 files changed, 54 insertions, 8 deletions
diff --git a/internal/bible/ref.go b/internal/bible/ref.go index c88e722..d0a615d 100644 --- a/internal/bible/ref.go +++ b/internal/bible/ref.go @@ -8,8 +8,24 @@ import ( var refRe = regexp.MustCompile(`^(.*?)\s+(\d+):(.+)$`) +// crossChapRangeRe matches a verse group whose range crosses a chapter +// boundary: "M-P:Q" -- from verse M of the chapter it opens in, through verse +// Q of chapter P (e.g. "30-28:7" inside "Sirach 27:30-28:7"). Capture groups: +// 1=M (from-verse), 2=P (to-chapter), 3=Q (to-verse). +var crossChapRangeRe = regexp.MustCompile(`^(\d+)-(\d+):(\d+)$`) + +// noUpperBound stands in for "through the end of the chapter" when filtering +// verses in Lookup. It is only ever used to bound a loop over verses that +// already exist in the corpus -- it never appears in a ref string, so it +// cannot leak into SplitRef's output or FormatRef's display. +const noUpperBound = 1<<31 - 1 + // SplitRef splits a ref whose verse list mixes single verses and ranges into -// one ref per group (the kjv tools reject a mixed list in a single query). +// one ref per group (the kjv tools reject a mixed list in a single query). A +// group may itself be a cross-chapter range ("M-P:Q"); it is kept as one +// group (Lookup expands it), but it also updates the chapter that later +// bare-verse groups in the list belong to (e.g. "Malachi 1:14-2:2,8-10" -> +// ["Malachi 1:14-2:2", "Malachi 2:8-10"]). func SplitRef(ref string) []string { m := refRe.FindStringSubmatch(ref) if m == nil { @@ -20,16 +36,23 @@ func SplitRef(ref string) []string { return []string{ref} } var out []string + cur := chap // chapter the next bare (no ":") group belongs to for _, g := range strings.Split(verses, ",") { g = strings.TrimSpace(g) if g == "" { continue } + if cm := crossChapRangeRe.FindStringSubmatch(g); cm != nil { + out = append(out, book+" "+cur+":"+g) + cur = cm[2] // groups after this one belong to chapter P + continue + } if strings.Contains(g, ":") { out = append(out, book+" "+g) - } else { - out = append(out, book+" "+chap+":"+g) + cur = g[:strings.IndexByte(g, ':')] + continue } + out = append(out, book+" "+cur+":"+g) } return out } @@ -51,12 +74,35 @@ func Lookup(version, ref string) ([]Verse, []string) { continue } chap, _ := strconv.Atoi(m[2]) - from, to := verseRange(m[3]) found := false - for _, v := range Verses(version, book, chap) { - if v.Verse >= from && v.Verse <= to { - verses = append(verses, v) - found = true + if cm := crossChapRangeRe.FindStringSubmatch(m[3]); cm != nil { + // "M-P:Q": chapter `chap` from verse M to its end, any whole + // chapters in between, then chapter P from verse 1 through Q. + from, _ := strconv.Atoi(cm[1]) + toChap, _ := strconv.Atoi(cm[2]) + toVerse, _ := strconv.Atoi(cm[3]) + for c := chap; c <= toChap; c++ { + lo, hi := 1, noUpperBound + if c == chap { + lo = from + } + if c == toChap { + hi = toVerse + } + for _, v := range Verses(version, book, c) { + if v.Verse >= lo && v.Verse <= hi { + verses = append(verses, v) + found = true + } + } + } + } else { + from, to := verseRange(m[3]) + for _, v := range Verses(version, book, chap) { + if v.Verse >= from && v.Verse <= to { + verses = append(verses, v) + found = true + } } } if !found { |
