Module: Docsmith::Comments::Anchor
- Defined in:
- lib/docsmith/comments/anchor.rb
Overview
Builds and migrates range anchors for inline comments. An anchor captures character offsets and a content hash of the selected text so the comment can be relocated when content changes between versions.
Constant Summary collapse
- ACTIVE =
"active"- DRIFTED =
"drifted"- ORPHANED =
"orphaned"
Class Method Summary collapse
-
.build(content, start_offset:, end_offset:) ⇒ Hash
Builds anchor_data for a new range comment.
-
.migrate(content, anchor_data) ⇒ Hash
Attempts to migrate an existing anchor to new version content.
Class Method Details
.build(content, start_offset:, end_offset:) ⇒ Hash
Builds anchor_data for a new range comment.
21 22 23 24 25 26 27 28 29 30 |
# File 'lib/docsmith/comments/anchor.rb', line 21 def self.build(content, start_offset:, end_offset:) anchored_text = content[start_offset...end_offset].to_s { start_offset: start_offset, end_offset: end_offset, content_hash: Digest::SHA256.hexdigest(anchored_text), anchored_text: anchored_text, status: ACTIVE } end |
.migrate(content, anchor_data) ⇒ Hash
Attempts to migrate an existing anchor to new version content.
Strategy:
- Try exact offset — if SHA256 of text at same offsets matches, return ACTIVE.
- Search the full content for the original anchored text — return DRIFTED with new offsets.
- If not found anywhere, return ORPHANED.
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
# File 'lib/docsmith/comments/anchor.rb', line 42 def self.migrate(content, anchor_data) start_off = anchor_data["start_offset"] end_off = anchor_data["end_offset"] original_hash = anchor_data["content_hash"] original_text = anchor_data["anchored_text"] # An anchor missing its offsets or its captured text cannot be relocated. # Previously this reached content[nil...nil] / content.index(nil) and # raised TypeError out of a migration loop. return anchor_data.merge("status" => ORPHANED) if original_text.nil? || original_text.empty? # 1. Exact offset check candidate = start_off.nil? || end_off.nil? ? "" : content[start_off...end_off].to_s return anchor_data.merge("status" => ACTIVE) if Digest::SHA256.hexdigest(candidate) == original_hash # 2. Full-text search for relocated text idx = content.index(original_text) if idx new_end = idx + original_text.length return anchor_data.merge( "start_offset" => idx, "end_offset" => new_end, "status" => DRIFTED ) end # 3. Orphaned — text no longer exists anchor_data.merge("status" => ORPHANED) end |