Class: Canopus::LazyRope
- Inherits:
-
Object
- Object
- Canopus::LazyRope
- Defined in:
- lib/canopus/lazy_rope.rb,
sig/canopus.rbs
Overview
Read-only UTF-8 rope facade. Newline offsets are packed; document bytes stay on disk and a bounded LRU serves positioned pages independently of IO position.
Constant Summary collapse
- PAGE_SIZE =
65_536
Instance Attribute Summary collapse
-
#bytesize ⇒ Integer
readonly
Returns the value of attribute bytesize.
-
#cached_bytes ⇒ Integer
readonly
Returns the value of attribute cached_bytes.
-
#line_ending ⇒ String
readonly
Returns the value of attribute line_ending.
Instance Method Summary collapse
- #apply_edits ⇒ bot
- #byteslice(offset, count = nil) ⇒ Object
- #check_invariants! ⇒ true
- #close ⇒ void
- #each_chunk {|arg0| ... } ⇒ Object
- #empty? ⇒ Boolean
-
#initialize(path, page_limit: 32) ⇒ LazyRope
constructor
A new instance of LazyRope.
- #lazy? ⇒ Boolean
- #length ⇒ Integer
- #line(row) ⇒ String
- #line_count ⇒ Integer
- #line_end(row) ⇒ Integer
- #line_start(row) ⇒ Integer
- #line_window(row, from: 0, max_bytes: 16_384) ⇒ [String, Integer]
- #offset_at(point) ⇒ Integer
- #offset_at_utf16(units) ⇒ Integer
- #offset_at_utf16_point(point) ⇒ Integer
- #point_at(offset) ⇒ Denebola::Point
- #to_s ⇒ String
- #utf16_length ⇒ Integer
- #utf16_offset_at(offset) ⇒ Integer
- #utf16_point_at(offset) ⇒ Denebola::Point
Constructor Details
#initialize(path, page_limit: 32) ⇒ LazyRope
Returns a new instance of LazyRope.
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
# File 'lib/canopus/lazy_rope.rb', line 9 def initialize(path, page_limit: 32) raise ArgumentError, "page limit must be positive" unless page_limit.is_a?(Integer) && page_limit.positive? @file = File.open(path, File::RDONLY | File::BINARY | File::SHARE_DELETE) @positioned_read, @read_lock = true, Mutex.new @stamp = stamp @origin = @file.size >= 3 && read_at(3, 0) == "\xEF\xBB\xBF".b ? 3 : 0 @bytesize = @file.size - @origin @pages, @page_limit, @cached_bytes = {}, page_limit, 0 @starts = [0].pack("Q<") @checkpoints = [0, 0, 0].pack("Q<3") @length = @utf16_length = 0 index_lines @starts.freeze @checkpoints.freeze @line_ending ||= "\n" rescue StandardError @file&.close raise end |
Instance Attribute Details
#bytesize ⇒ Integer (readonly)
Returns the value of attribute bytesize.
8 9 10 |
# File 'lib/canopus/lazy_rope.rb', line 8 def bytesize @bytesize end |
#cached_bytes ⇒ Integer (readonly)
Returns the value of attribute cached_bytes.
8 9 10 |
# File 'lib/canopus/lazy_rope.rb', line 8 def cached_bytes @cached_bytes end |
#line_ending ⇒ String (readonly)
Returns the value of attribute line_ending.
8 9 10 |
# File 'lib/canopus/lazy_rope.rb', line 8 def line_ending @line_ending end |
Instance Method Details
#apply_edits ⇒ bot
115 |
# File 'lib/canopus/lazy_rope.rb', line 115 def apply_edits(*) = raise(Error, "large file is read-only") |
#byteslice(offset, count) ⇒ Denebola::Rope #byteslice(range) ⇒ Denebola::Rope
57 58 59 60 61 62 63 64 65 66 67 68 |
# File 'lib/canopus/lazy_rope.rb', line 57 def byteslice(offset, count = nil) if offset.is_a?(Range) range = offset offset = range.begin count = range.end + (range.exclude_end? ? 0 : 1) - offset end count ||= @bytesize - offset validate_offset(offset) validate_offset(offset + count) raise RangeError, "negative slice length" if count.negative? Denebola::Rope.new(read_bytes(offset, count).force_encoding(Encoding::UTF_8)) end |
#check_invariants! ⇒ true
116 117 118 119 |
# File 'lib/canopus/lazy_rope.rb', line 116 def check_invariants! raise Error, "invalid line index" unless line_start(0).zero? && @starts.bytesize % 8 == 0 true end |
#close ⇒ void
This method returns an undefined value.
33 |
# File 'lib/canopus/lazy_rope.rb', line 33 def close = (@file.close unless @file.closed?) |
#each_chunk ⇒ Enumerator[String, void] #each_chunk ⇒ void
103 104 105 106 107 108 109 110 111 112 113 |
# File 'lib/canopus/lazy_rope.rb', line 103 def each_chunk return enum_for(__method__) unless block_given? offset = 0 while offset < @bytesize value = read_bytes(offset, [PAGE_SIZE, @bytesize - offset].min) finish = complete_utf8_length(value) raise Error, "invalid UTF-8 file" if finish.zero? yield value.byteslice(0, finish).force_encoding(Encoding::UTF_8) offset += finish end end |
#empty? ⇒ Boolean
32 |
# File 'lib/canopus/lazy_rope.rb', line 32 def empty? = @bytesize.zero? |
#lazy? ⇒ Boolean
28 |
# File 'lib/canopus/lazy_rope.rb', line 28 def lazy? = true |
#length ⇒ Integer
30 |
# File 'lib/canopus/lazy_rope.rb', line 30 def length = @length |
#line(row) ⇒ String
38 39 40 41 42 |
# File 'lib/canopus/lazy_rope.rb', line 38 def line(row) start = line_start(row) ending = row + 1 < line_count ? line_start(row + 1) : @bytesize read_bytes(start, ending - start).force_encoding(Encoding::UTF_8).sub(/(?:\r\n|[\r\n\u2028\u2029])\z/, "") end |
#line_count ⇒ Integer
29 |
# File 'lib/canopus/lazy_rope.rb', line 29 def line_count = @starts.bytesize / 8 |
#line_end(row) ⇒ Integer
43 44 45 46 47 48 |
# File 'lib/canopus/lazy_rope.rb', line 43 def line_end(row) ending = row + 1 < line_count ? line_start(row + 1) : @bytesize return ending if row + 1 == line_count tail = read_bytes([ending - 3, 0].max, [ending, 3].min) ending - (tail.end_with?("\r\n") ? 2 : tail.end_with?("\xE2\x80\xA8".b, "\xE2\x80\xA9".b) ? 3 : 1) end |
#line_start(row) ⇒ Integer
34 35 36 37 |
# File 'lib/canopus/lazy_rope.rb', line 34 def line_start(row) raise RangeError, "line outside document" unless row.is_a?(Integer) && row.between?(0, line_count - 1) @starts.unpack1("Q<", offset: row * 8) end |
#line_window(row, from: 0, max_bytes: 16_384) ⇒ [String, Integer]
49 50 51 52 53 54 55 56 |
# File 'lib/canopus/lazy_rope.rb', line 49 def line_window(row, from: 0, max_bytes: 16_384) start, ending = line_start(row), line_end(row) offset = (start + from).clamp(start, ending) offset -= 1 while offset > start && offset < ending && (read_bytes(offset, 1).getbyte(0) & 0xc0) == 0x80 value = read_bytes(offset, [max_bytes, ending - offset].min) value = value.byteslice(0, complete_utf8_length(value)) unless value.empty? [value.force_encoding(Encoding::UTF_8), offset - start] end |
#offset_at(point) ⇒ Integer
79 80 81 82 83 84 |
# File 'lib/canopus/lazy_rope.rb', line 79 def offset_at(point) start, ending = line_start(point.row), line_end(point.row) base = prefix_count(start) raise RangeError, "column outside line" unless point.column.is_a?(Integer) && point.column.between?(0, prefix_count(ending) - base) offset_for_count(base + point.column) end |
#offset_at_utf16(units) ⇒ Integer
99 100 101 102 |
# File 'lib/canopus/lazy_rope.rb', line 99 def offset_at_utf16(units) raise RangeError, "UTF-16 offset outside document" unless units.is_a?(Integer) && units.between?(0, @utf16_length) offset_for_count(units, utf16: true) end |
#offset_at_utf16_point(point) ⇒ Integer
89 90 91 92 93 94 |
# File 'lib/canopus/lazy_rope.rb', line 89 def offset_at_utf16_point(point) base = prefix_count(line_start(point.row), utf16: true) ending = prefix_count(line_end(point.row), utf16: true) raise RangeError, "UTF-16 column outside line" unless point.column.is_a?(Integer) && point.column.between?(0, ending - base) offset_for_count(base + point.column, utf16: true) end |
#point_at(offset) ⇒ Denebola::Point
69 70 71 72 73 74 75 76 77 78 |
# File 'lib/canopus/lazy_rope.rb', line 69 def point_at(offset) validate_offset(offset) low, high = 0, line_count while low < high middle = (low + high) / 2 line_start(middle) <= offset ? low = middle + 1 : high = middle end row = [low - 1, 0].max Denebola::Point.new(row, prefix_count(offset) - prefix_count(line_start(row))) end |
#to_s ⇒ String
114 |
# File 'lib/canopus/lazy_rope.rb', line 114 def to_s = each_chunk.to_a.join |
#utf16_length ⇒ Integer
31 |
# File 'lib/canopus/lazy_rope.rb', line 31 def utf16_length = @utf16_length |
#utf16_offset_at(offset) ⇒ Integer
95 96 97 98 |
# File 'lib/canopus/lazy_rope.rb', line 95 def utf16_offset_at(offset) validate_offset(offset) prefix_count(offset, utf16: true) end |
#utf16_point_at(offset) ⇒ Denebola::Point
85 86 87 88 |
# File 'lib/canopus/lazy_rope.rb', line 85 def utf16_point_at(offset) point = point_at(offset) Denebola::Point.new(point.row, prefix_count(offset, utf16: true) - prefix_count(line_start(point.row), utf16: true)) end |