denebola 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/README.md +1 -1
- data/lib/denebola/anchor.rb +12 -3
- data/lib/denebola/edit.rb +17 -0
- data/lib/denebola/rope.rb +5 -10
- data/lib/denebola/version.rb +1 -1
- data/lib/denebola.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 28307dc01292f1215dcfb5466ba9fe182633346c622d69d336456983c00276e2
|
|
4
|
+
data.tar.gz: 24520e65a734fb920588210d4d9b829ef0ec0997178e0ab88b708d49af604ad1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8a29c867cdb4fa7f3e7d74c27f7556d8ecb50c08706f814d22c9ff43c90dfa776c4a4a502969a3708c67bdb9bccb8f6149c09d3656105ea97b3e925aaa378519
|
|
7
|
+
data.tar.gz: 36f141d074afb7bce936876e301df6d1884881c9806803619fea3d9f20f86aa0627c2ef5baaeb6c7bb5ea23041748d72c9d2891084bc9c92fce65f0ecd094edf
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.1 — 2026-09-16
|
|
4
|
+
|
|
5
|
+
- Keep same-offset batch edit ordering deterministic across ropes and anchors.
|
|
6
|
+
- Transform right-biased anchors after every insertion and replacement at their boundary.
|
|
7
|
+
|
|
3
8
|
## 0.2.0 — 2026-09-15
|
|
4
9
|
|
|
5
10
|
- Add `LazyRope` for bounded-memory access to very large UTF-8 files.
|
data/README.md
CHANGED
|
@@ -109,7 +109,7 @@ rope.apply_edits([[0...5, "Hi"], [6...11, "Ruby"]]).to_s
|
|
|
109
109
|
|
|
110
110
|
Ranges passed to `replace`, `apply_edits`, and `byteslice` address bytes in UTF-8. Bounds and codepoint boundaries are checked. Inclusive, exclusive, beginless, and endless Ruby ranges are accepted; invalid encodings, split codepoints, and overlapping batch edits raise exceptions.
|
|
111
111
|
|
|
112
|
-
`apply_edits` applies nonoverlapping edits against one original snapshot and
|
|
112
|
+
`apply_edits` applies nonoverlapping edits against one original snapshot, and `Anchor#transform` uses the same source order. Adjacent ranges are allowed; zero-length insertions at the same offset keep input order and precede a replacement beginning there. Input strings are copied or shared safely with Ruby's copy-on-write strings, so later mutation of the source cannot alter a rope.
|
|
113
113
|
|
|
114
114
|
Chunks preserve extended grapheme clusters when building or joining text. A single unusually long grapheme may exceed the configured chunk size; explicit byte slices and edits may operate between codepoints inside a grapheme.
|
|
115
115
|
|
data/lib/denebola/anchor.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "edit"
|
|
4
|
+
|
|
3
5
|
module Denebola
|
|
4
6
|
# A position transformed explicitly by edits against its current snapshot.
|
|
5
7
|
class Anchor
|
|
@@ -15,15 +17,22 @@ module Denebola
|
|
|
15
17
|
delta = 0
|
|
16
18
|
previous = 0
|
|
17
19
|
transformed = nil
|
|
18
|
-
|
|
20
|
+
normalized = edits.map do |range, text|
|
|
21
|
+
raise TypeError, "expected a Range of byte offsets" unless range.is_a?(Range)
|
|
19
22
|
start = range.begin
|
|
20
23
|
finish = range.end + (range.exclude_end? ? 0 : 1)
|
|
21
|
-
raise ArgumentError, "invalid
|
|
24
|
+
raise ArgumentError, "invalid edit" unless start.is_a?(Integer) && finish.is_a?(Integer) && start >= 0 && finish >= start
|
|
25
|
+
[start, finish, Edit.normalize_text(text)]
|
|
26
|
+
end
|
|
27
|
+
Edit.sort(normalized).each do |start, finish, text|
|
|
28
|
+
raise ArgumentError, "invalid or overlapping edits" if start < previous
|
|
22
29
|
previous = finish
|
|
23
30
|
if offset < start
|
|
24
31
|
transformed ||= offset + delta
|
|
25
32
|
elsif offset <= finish
|
|
26
|
-
|
|
33
|
+
position = start + delta
|
|
34
|
+
transformed = position + text.bytesize if bias == :right
|
|
35
|
+
transformed ||= position
|
|
27
36
|
end
|
|
28
37
|
delta += text.bytesize - (finish - start)
|
|
29
38
|
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Denebola
|
|
4
|
+
module Edit
|
|
5
|
+
def self.sort(edits)
|
|
6
|
+
edits.each_with_index.sort_by { |edit, index| [edit[0], edit[1], index] }.map!(&:first)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def self.normalize_text(text)
|
|
10
|
+
raise TypeError, "text must be a String" unless text.is_a?(String)
|
|
11
|
+
string = text.encoding == Encoding::UTF_8 ? text : text.encode(Encoding::UTF_8)
|
|
12
|
+
raise ArgumentError, "text must be valid UTF-8" unless string.valid_encoding?
|
|
13
|
+
string
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
private_constant :Edit
|
|
17
|
+
end
|
data/lib/denebola/rope.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "edit"
|
|
4
|
+
|
|
3
5
|
module Denebola
|
|
4
6
|
class Rope
|
|
5
7
|
DEFAULT_CHUNK_SIZE = 1024
|
|
@@ -22,7 +24,7 @@ module Denebola
|
|
|
22
24
|
def initialize(text = "", chunk_size: DEFAULT_CHUNK_SIZE, branching: Tree::DEFAULT_BRANCHING, tree: nil)
|
|
23
25
|
raise ArgumentError, "chunk_size must be an integer >= 4" unless chunk_size.is_a?(Integer) && chunk_size >= 4
|
|
24
26
|
@chunk_size = chunk_size
|
|
25
|
-
@tree = tree || Tree.new(chunks(normalize_text(text)), summary: TextSummary, branching: branching)
|
|
27
|
+
@tree = tree || Tree.new(chunks(Edit.normalize_text(text)), summary: TextSummary, branching: branching)
|
|
26
28
|
freeze
|
|
27
29
|
end
|
|
28
30
|
|
|
@@ -51,7 +53,7 @@ module Denebola
|
|
|
51
53
|
|
|
52
54
|
def replace(range, text)
|
|
53
55
|
start, finish = byte_bounds(range)
|
|
54
|
-
replacement = normalize_text(text)
|
|
56
|
+
replacement = Edit.normalize_text(text)
|
|
55
57
|
first_index, first, prefix = locate_byte_offset(start)
|
|
56
58
|
last_index, last, suffix = locate_byte_offset(finish)
|
|
57
59
|
if first_index.positive? && start == prefix.bytesize
|
|
@@ -76,7 +78,7 @@ module Denebola
|
|
|
76
78
|
|
|
77
79
|
# Ranges refer to the original snapshot. Adjacent edits are allowed; overlaps are rejected.
|
|
78
80
|
def apply_edits(edits)
|
|
79
|
-
normalized = edits.map { |range, text| [*byte_bounds(range), normalize_text(text)] }
|
|
81
|
+
normalized = Edit.sort(edits.map { |range, text| [*byte_bounds(range), Edit.normalize_text(text)] })
|
|
80
82
|
previous = 0
|
|
81
83
|
normalized.each do |start, finish, _|
|
|
82
84
|
raise ArgumentError, "overlapping edits" if start < previous
|
|
@@ -234,13 +236,6 @@ module Denebola
|
|
|
234
236
|
left.slice(0, left.size - 1).append(boundary).append(right.slice(1, right.size - 1))
|
|
235
237
|
end
|
|
236
238
|
|
|
237
|
-
def normalize_text(text)
|
|
238
|
-
raise TypeError, "text must be a String" unless text.is_a?(String)
|
|
239
|
-
string = text.encoding == Encoding::UTF_8 ? text : text.encode(Encoding::UTF_8)
|
|
240
|
-
raise ArgumentError, "text must be valid UTF-8" unless string.valid_encoding?
|
|
241
|
-
string
|
|
242
|
-
end
|
|
243
|
-
|
|
244
239
|
def chunks(text)
|
|
245
240
|
return [] if text.empty?
|
|
246
241
|
return [Chunk.new(text)] if text.bytesize <= chunk_size
|
data/lib/denebola/version.rb
CHANGED
data/lib/denebola.rb
CHANGED
|
@@ -7,6 +7,7 @@ require_relative "denebola/cursor"
|
|
|
7
7
|
require_relative "denebola/text_summary"
|
|
8
8
|
require_relative "denebola/point"
|
|
9
9
|
require_relative "denebola/dimensions"
|
|
10
|
+
require_relative "denebola/edit"
|
|
10
11
|
require_relative "denebola/rope"
|
|
11
12
|
require_relative "denebola/anchor"
|
|
12
13
|
require_relative "denebola/lazy_rope"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: denebola
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yudai Takada
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-16 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: Immutable text ropes plus bounded-memory, file-backed editing with line
|
|
14
14
|
and UTF-16 indexing.
|
|
@@ -25,6 +25,7 @@ files:
|
|
|
25
25
|
- lib/denebola/anchor.rb
|
|
26
26
|
- lib/denebola/cursor.rb
|
|
27
27
|
- lib/denebola/dimensions.rb
|
|
28
|
+
- lib/denebola/edit.rb
|
|
28
29
|
- lib/denebola/error.rb
|
|
29
30
|
- lib/denebola/lazy_rope.rb
|
|
30
31
|
- lib/denebola/point.rb
|