y-rb 0.1.4.alpha.3-x86_64-linux-musl

Sign up to get free protection for your applications and to get access to all the features.
data/lib/y/array.rb ADDED
@@ -0,0 +1,352 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Y
4
+ # An array can be used to store and retrieve elements.
5
+ #
6
+ # The array is the replicated counterpart to a Ruby Array. It supports a
7
+ # subset of the Ruby Array operations, like adding, getting and deleting
8
+ # values by position or ranges.
9
+ #
10
+ # Someone should not instantiate an array directly, but use {Y::Doc#get_array}
11
+ # instead.
12
+ #
13
+ # @example
14
+ # doc = Y::Doc.new
15
+ # array = doc.get_array("my array")
16
+ #
17
+ # array << 1
18
+ # array.push(2)
19
+ # array.concat([3, 4, 5])
20
+ #
21
+ # array.to_a == [1, 2, 3, 4, 5] # true
22
+ class Array
23
+ # @!attribute [r] document
24
+ #
25
+ # @return [Y::Doc] The document this array belongs to
26
+ attr_accessor :document
27
+
28
+ # Create a new array instance
29
+ #
30
+ # @param [Y::Doc] doc
31
+ def initialize(doc = nil)
32
+ @document = doc || Y::Doc.new
33
+
34
+ super()
35
+ end
36
+
37
+ # Retrieves element at position
38
+ #
39
+ # @return [Object]
40
+ def [](index)
41
+ yarray_get(index)
42
+ end
43
+
44
+ # Inserts value at position
45
+ #
46
+ # @param [Integer] index
47
+ # @param [true|false|Float|Integer|String|Array|Hash] value
48
+ # @return [void]
49
+ def []=(index, value)
50
+ yarray_insert(transaction, index, value)
51
+ end
52
+
53
+ # Adds an element to the end of the array
54
+ #
55
+ # @return [void]
56
+ def <<(value)
57
+ yarray_push_back(transaction, value)
58
+ end
59
+
60
+ # Attach listener to array changes
61
+ #
62
+ # @example Listen to changes in array type
63
+ # local = Y::Doc.new
64
+ #
65
+ # arr = local.get_array("my array")
66
+ # arr.attach(->(delta) { pp delta })
67
+ #
68
+ # local.transact do
69
+ # arr << 1
70
+ # end
71
+ #
72
+ # @param [Proc] callback
73
+ # @param [Block] block
74
+ # @return [Integer]
75
+ def attach(callback, &block)
76
+ return yarray_observe(callback) unless callback.nil?
77
+
78
+ yarray_observe(block.to_proc) unless block.nil?
79
+ end
80
+
81
+ # Adds to array all elements from each Array in `other_arrays`.
82
+ #
83
+ # If one of the arguments isn't an Array, it is silently ignored.
84
+ #
85
+ # @example Add multiple values to array
86
+ # doc = Y::Doc.new
87
+ # arr = doc.get_array("my array")
88
+ # arr.concat([1, 2, 3])
89
+ #
90
+ # arr.to_a == [1, 2, 3] # true
91
+ #
92
+ # @param [Array<Array<Object>>] other_arrays
93
+ # @return [void]
94
+ def concat(*other_arrays)
95
+ combined = other_arrays.reduce([]) do |values, arr|
96
+ values.concat(arr) if arr.is_a?(::Array)
97
+ end
98
+
99
+ yarray_insert_range(transaction, size, combined)
100
+ end
101
+
102
+ # Detach listener
103
+ #
104
+ # @param [Integer] subscription_id
105
+ # @return [void]
106
+ def detach(subscription_id)
107
+ yarray_unobserve(subscription_id)
108
+ end
109
+
110
+ # @return [void]
111
+ def each(&block)
112
+ yarray_each(block)
113
+ end
114
+
115
+ # Check if the array is empty
116
+ #
117
+ # @return [true|false]
118
+ def empty?
119
+ size.zero?
120
+ end
121
+
122
+ # Returns first element in array if there is at least one
123
+ #
124
+ # @return [Object|nil]
125
+ def first
126
+ yarray_get(0)
127
+ end
128
+
129
+ # Returns last element in array if there is at least one element
130
+ #
131
+ # @return [Object|nil]
132
+ def last
133
+ len = yarray_length
134
+ return yarray_get(yarray_length - 1) if len.positive?
135
+
136
+ nil
137
+ end
138
+
139
+ # rubocop:disable Naming/MethodParameterName
140
+
141
+ # Removes last (n) element(s) from array
142
+ #
143
+ # @param [Integer|nil] n Number of elements to remove
144
+ # @return [void]
145
+ def pop(n = nil)
146
+ len = size
147
+ yarray_remove(transaction, len - 1) if n.nil?
148
+ yarray_remove_range(transaction, len - n, n) unless n.nil?
149
+ end
150
+
151
+ # rubocop:enable Naming/MethodParameterName
152
+
153
+ alias push <<
154
+
155
+ # rubocop:disable Naming/MethodParameterName
156
+
157
+ # Removes first (n) element(s) from array
158
+ #
159
+ # @param [Integer|nil] n Number of elements to remove
160
+ # @return [void]
161
+ def shift(n = nil)
162
+ yarray_remove(transaction, 0) if n.nil?
163
+ yarray_remove_range(transaction, 0, n) unless nil?
164
+ end
165
+
166
+ # rubocop:enable Naming/MethodParameterName
167
+
168
+ # Size of array
169
+ #
170
+ # @return [Integer]
171
+ def size
172
+ yarray_length
173
+ end
174
+
175
+ alias length size
176
+
177
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
178
+
179
+ # Removes one or more elements from array
180
+ #
181
+ # **Attention:** In comparison to Array#slice, {Array#slice!} will not
182
+ # return the values that got removed. Even this being technically
183
+ # possible, it requires us to read the elements before removing them, which
184
+ # is not desirable in most situations.
185
+ #
186
+ # @example Removes a single element
187
+ # doc = Y::Doc.new
188
+ #
189
+ # arr = doc.get_text("my array")
190
+ # arr << 1
191
+ # arr << 2
192
+ # arr << 3
193
+ #
194
+ # arr.slice!(1)
195
+ #
196
+ # arr.to_a == [1, 3] # true
197
+ #
198
+ # @overload slice!(n)
199
+ # Removes nth element from array
200
+ #
201
+ # @overload slice!(start, length)
202
+ # Removes a range of elements
203
+ #
204
+ # @overload slice!(range)
205
+ # Removes a range of elements
206
+ #
207
+ # @return [void]
208
+ def slice!(*args)
209
+ if args.size.zero?
210
+ raise ArgumentError,
211
+ "Provide one of `index`, `range`, `start, length` as arguments"
212
+ end
213
+
214
+ if args.size == 1
215
+ arg = args.first
216
+
217
+ if arg.is_a?(Range)
218
+ if arg.exclude_end?
219
+ yarray_remove_range(transaction, arg.first,
220
+ arg.last - arg.first)
221
+ end
222
+ unless arg.exclude_end?
223
+ yarray_remove_range(transaction, arg.first,
224
+ arg.last + 1 - arg.first)
225
+ end
226
+ return nil
227
+ end
228
+
229
+ if arg.is_a?(Numeric)
230
+ yarray_remove(transaction, arg.to_int)
231
+ return nil
232
+ end
233
+ end
234
+
235
+ if args.size == 2
236
+ first, second = args
237
+
238
+ if first.is_a?(Numeric) && second.is_a?(Numeric)
239
+ yarray_remove_range(transaction, first, second)
240
+ return nil
241
+ end
242
+ end
243
+
244
+ raise ArgumentError, "Please check your arguments, can't slice."
245
+ end
246
+
247
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
248
+
249
+ # Convert this array to a Ruby Array
250
+ #
251
+ # @return [Array<Object>]
252
+ def to_a
253
+ yarray_to_a
254
+ end
255
+
256
+ # Adds an element to the beginning of the array
257
+ #
258
+ # @return [void]
259
+ def unshift(value)
260
+ yarray_push_front(transaction, value)
261
+ end
262
+
263
+ alias prepend unshift
264
+
265
+ private
266
+
267
+ # @!method yarray_each(proc)
268
+ # Iterates over all elements in Array by calling the provided proc
269
+ # with the value as argument.
270
+ #
271
+ # @param [Proc<Object>] proc A proc that is called for every element
272
+
273
+ # @!method yarray_get(index)
274
+ # Retrieves content as specified index
275
+ #
276
+ # @param [Integer] index
277
+ # @return [Object]
278
+
279
+ # @!method yarray_insert(transaction, index, content)
280
+ # Inserts content at specified index
281
+ #
282
+ # @param [Y::Transaction] transaction
283
+ # @param [Integer] index
284
+ # @param [Boolean, Float, Integer, Array, Hash, Text] content
285
+ # @return [void]
286
+
287
+ # @!method yarray_insert_range(transaction, index, arr)
288
+ # Inserts all elements of a given array at specified index
289
+ #
290
+ # @param [Y::Transaction] transaction
291
+ # @param [Integer] index
292
+ # @param [Array<Boolean, Float, Integer, Array, Hash, Text>] arr
293
+ # @return [void]
294
+
295
+ # @!method yarray_length
296
+ # Returns length of array
297
+ #
298
+ # @return [Integer] Length of array
299
+
300
+ # @!method yarray_push_back(transaction, value)
301
+ # Adds an element to the end of the array
302
+ #
303
+ # @param [Y::Transaction] transaction
304
+ # @param [Object] value
305
+ # @return [void]
306
+
307
+ # @!method yarray_push_front(transaction, value)
308
+ # Adds an element to the front of the array
309
+ #
310
+ # @param [Y::Transaction] transaction
311
+ # @param [Object] value
312
+ # @return [void]
313
+
314
+ # @!method yarray_observe(callback)
315
+ #
316
+ # @param [Proc] callback
317
+ # @return [Integer]
318
+
319
+ # @!method yarray_remove(transaction, index)
320
+ # Removes a single element from array at index
321
+ #
322
+ # @param [Y::Transaction] transaction
323
+ # @param [Integer] index
324
+ # @return [void]
325
+
326
+ # @!method yarray_remove_range(transaction, index, length)
327
+ # Removes a range of elements from array
328
+ #
329
+ # @param [Y::Transaction] transaction
330
+ # @param [Integer] index
331
+ # @param [Integer] length
332
+ # @return [void]
333
+
334
+ # @!method yarray_to_a
335
+ # Transforms the array into a Ruby array
336
+ #
337
+ # @return [Array]
338
+
339
+ # @!method yarray_unobserve(subscription_id)
340
+ #
341
+ # @param [Integer] subscription_id
342
+ # @return [void]
343
+
344
+ # A reference to the current active transaction of the document this map
345
+ # belongs to.
346
+ #
347
+ # @return [Y::Transaction] A transaction object
348
+ def transaction
349
+ document.current_transaction
350
+ end
351
+ end
352
+ end
data/lib/y/doc.rb ADDED
@@ -0,0 +1,220 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "transaction"
4
+
5
+ module Y
6
+ # @example Create a local and remote doc and syncs the diff
7
+ # local = Y::Doc.new
8
+ # local_map = local.get_map("my map")
9
+ # local_map[:hello] = "world"
10
+ #
11
+ # remote = Y::Doc.new
12
+ #
13
+ # diff = local.diff(remote.state)
14
+ # remote.sync(diff)
15
+ #
16
+ # remote_map = remote.get_map("my_map")
17
+ # pp remote_map.to_h #=> {hello: "world"}
18
+ class Doc
19
+ ZERO_STATE = [0].freeze
20
+ private_constant :ZERO_STATE
21
+
22
+ # Commit current transaction
23
+ #
24
+ # This is a convenience method that invokes {Y::Transaction#commit} on the
25
+ # current transaction used by this document.
26
+ #
27
+ # @return [void]
28
+ def commit
29
+ current_transaction.commit
30
+ end
31
+
32
+ # The currently active transaction for this document
33
+ # @return [Y::Transaction]
34
+ def current_transaction
35
+ @current_transaction ||= begin
36
+ transaction = ydoc_transact
37
+ transaction.document = self
38
+ transaction
39
+ end
40
+ end
41
+
42
+ # Create a diff between this document and another document. The diff is
43
+ # created based on a state vector provided by the other document. It only
44
+ # returns the missing blocks, as binary encoded sequence.
45
+ #
46
+ # @param [::Array<Int>] state The state to create the diff against
47
+ # @return [::Array<Int>] Binary encoded diff
48
+ def diff(state = ZERO_STATE)
49
+ ydoc_encode_diff_v1(state)
50
+ end
51
+
52
+ # Creates a full diff for the current document. It is similar to {#diff},
53
+ # but does not take a state. Instead it creates an empty state and passes it
54
+ # to the encode_diff function.
55
+ #
56
+ # @return [::Array<Int>] Binary encoded diff
57
+ def full_diff
58
+ empty_state = Y::Doc.new.state
59
+ ydoc_encode_diff_v1(empty_state)
60
+ end
61
+
62
+ # Gets or creates a new array by name
63
+ #
64
+ # If the optional values array is present, fills the array up with elements
65
+ # from the provided array. If the array already exists and isn't
66
+ # empty, elements are pushed to the end of the array.
67
+ #
68
+ # @param [String] name The name of the structure
69
+ # @param [::Array] values Optional initial values
70
+ # @return [Y::Array]
71
+ def get_array(name, values = nil)
72
+ array = current_transaction.get_array(name)
73
+ array.document = self
74
+ array.concat(values) unless values.nil?
75
+ array
76
+ end
77
+
78
+ # Gets or creates a new map by name
79
+ #
80
+ # If the optional input hash is present, fills the map up with key-value
81
+ # pairs from the provided input hash. If the map already exists and isn't
82
+ # empty, any existing keys are overridden and new keys are added.
83
+ #
84
+ # @param [String] name The name of the structure
85
+ # @param [Hash] input Optional initial map key-value pairs
86
+ # @return [Y::Map]
87
+ def get_map(name, input = nil)
88
+ map = current_transaction.get_map(name)
89
+ map.document = self
90
+ input&.each { |key, value| map[key] = value }
91
+ map
92
+ end
93
+
94
+ # Gets or creates a new text by name
95
+ #
96
+ # If the optional input string is provided, fills a new text with the string
97
+ # at creation time. If the text isn't new and not empty, appends the input
98
+ # to the end of the text.
99
+ #
100
+ # @param [String] name The name of the structure
101
+ # @param [String] input Optional initial text value
102
+ # @return [Y::Text]
103
+ def get_text(name, input = nil)
104
+ text = current_transaction.get_text(name)
105
+ text.document = self
106
+ text << input unless input.nil?
107
+ text
108
+ end
109
+
110
+ # Gets or creates a new XMLElement by name
111
+ #
112
+ # @param [String] name The name of the structure
113
+ # @return [Y::XMLElement]
114
+ def get_xml_element(name)
115
+ xml_element = current_transaction.get_xml_element(name)
116
+ xml_element.document = self
117
+ xml_element
118
+ end
119
+
120
+ # Gets or creates a new XMLText by name
121
+ #
122
+ # @param [String] name The name of the structure
123
+ # @param [String] input Optional initial text value
124
+ # @return [Y::XMLText]
125
+ def get_xml_text(name, input = nil)
126
+ xml_text = current_transaction.get_xml_text(name)
127
+ xml_text.document = self
128
+ xml_text << input unless input.nil?
129
+ xml_text
130
+ end
131
+
132
+ # Creates a state vector of this document. This can be used to compare the
133
+ # state of two documents with each other and to later on sync them.
134
+ #
135
+ # @return [::Array<Int>] Binary encoded state vector
136
+ def state
137
+ current_transaction.state
138
+ end
139
+
140
+ # Synchronizes this document with the diff from another document
141
+ #
142
+ # @param [::Array<Int>] diff Binary encoded update
143
+ # @return [void]
144
+ def sync(diff)
145
+ current_transaction.apply(diff)
146
+ end
147
+
148
+ # Restores a specific document from an update that contains full state
149
+ #
150
+ # This is doing the same as {#sync}, but it exists to be explicit about
151
+ # the intent. This is the companion to {#full_diff}.
152
+ #
153
+ # @param [::Array<Int>] full_diff Binary encoded update
154
+ # @return [void]
155
+ def restore(full_diff)
156
+ current_transaction.apply(full_diff)
157
+ end
158
+
159
+ # rubocop:disable Metrics/MethodLength
160
+
161
+ # Creates a new transaction and provides it to the given block
162
+ #
163
+ # @example Insert into text
164
+ # doc = Y::Doc.new
165
+ # text = doc.get_text("my text")
166
+ #
167
+ # doc.transact do
168
+ # text << "Hello, World!"
169
+ # end
170
+ #
171
+ # @yield [transaction]
172
+ # @yieldparam [Y::Transaction] transaction
173
+ # @yieldreturn [void]
174
+ # @return [Y::Transaction]
175
+ def transact
176
+ current_transaction.commit
177
+
178
+ if block_given?
179
+ # create new transaction just for the lifetime of this block
180
+ tmp_transaction = ydoc_transact
181
+ tmp_transaction.document = self
182
+
183
+ # override transaction for the lifetime of the block
184
+ @current_transaction = tmp_transaction
185
+
186
+ yield tmp_transaction
187
+
188
+ tmp_transaction.commit
189
+ end
190
+
191
+ # create new transaction
192
+ @current_transaction = ydoc_transact
193
+ @current_transaction.document = self
194
+
195
+ current_transaction
196
+ end
197
+
198
+ # rubocop:enable Metrics/MethodLength
199
+
200
+ # @!method ydoc_encode_diff_v1
201
+ # Encodes the diff of current document state vs provided state
202
+ #
203
+ # @example Create transaction on doc
204
+ # doc = Y::Doc.new
205
+ # tx = doc.ydoc_encode_diff_v1(other_state)
206
+ #
207
+ # @return [Array<Integer>] Binary encoded update
208
+ # @!visibility private
209
+
210
+ # @!method ydoc_transact
211
+ # Creates a new transaction for the document
212
+ #
213
+ # @example Create transaction on doc
214
+ # doc = Y::Doc.new
215
+ # tx = doc.ydoc_transact
216
+ #
217
+ # @return [Y::Transaction] The transaction object
218
+ # @!visibility private
219
+ end
220
+ end