y-rb 0.6.0-x86_64-linux-gnu

Sign up to get free protection for your applications and to get access to all the features.
data/lib/y/map.rb ADDED
@@ -0,0 +1,199 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Y
6
+ # A map can be used to store and retrieve key-value pairs.
7
+ #
8
+ # The map is the replicated counterpart to a Hash. It supports a subset
9
+ # of the Hash operations, like adding, getting and deleting values by key.
10
+ #
11
+ # Someone should not instantiate a map directly, but use {Y::Doc#get_map}
12
+ # instead.
13
+ #
14
+ # @example
15
+ # doc = Y::Doc.new
16
+ # map = doc.get_map("my map")
17
+ #
18
+ # map[:hello] = "world"
19
+ # puts map[:hello]
20
+ class Map
21
+ include Enumerable
22
+
23
+ # @!attribute [r] document
24
+ #
25
+ # @return [Y::Doc] The document this map belongs to
26
+ attr_accessor :document
27
+
28
+ # Create a new map instance
29
+ #
30
+ # @param doc [Y::Doc]
31
+ def initialize(doc = nil)
32
+ @document = doc || Y::Doc.new
33
+
34
+ super()
35
+ end
36
+
37
+ # Attach a listener to get notified about any changes to the map
38
+ #
39
+ # @param callback [Proc]
40
+ # @param block [Block]
41
+ # @return [Integer]
42
+ def attach(callback, &block)
43
+ return ymap_observe(callback) unless callback.nil?
44
+
45
+ ymap_observe(block.to_proc) unless block.nil?
46
+ end
47
+
48
+ # Removes all map entries
49
+ #
50
+ # @return [Self]
51
+ def clear
52
+ document.current_transaction { |tx| ymap_clear(tx) }
53
+ self
54
+ end
55
+
56
+ # rubocop:disable Layout/LineLength
57
+
58
+ # Deletes the entry for the given key and returns its associated value.
59
+ #
60
+ # @example Deletes the entry and return associated value
61
+ #
62
+ # m = doc.get_map("my map")
63
+ # m[:bar] = 1
64
+ # m.delete(:bar) # => 1
65
+ # m # => {}
66
+ #
67
+ # @example Unknown key is handled in block
68
+ #
69
+ # m = doc.get_map("my map")
70
+ # m.delete(:nosuch) { |key| "Key #{key} not found" }# => "Key nosuch not found"
71
+ # m # => {}
72
+ #
73
+ # @param key [String, Symbol]
74
+ # @return [void]
75
+ def delete(key)
76
+ value = document.current_transaction { |tx| ymap_remove(tx, key) }
77
+ if block_given? && key?(key)
78
+ yield key
79
+ else
80
+ value
81
+ end
82
+ end
83
+
84
+ # rubocop:enable Layout/LineLength
85
+
86
+ # Detach listener
87
+ #
88
+ # @param subscription_id [Integer]
89
+ # @return [void]
90
+ def detach(subscription_id)
91
+ ymap_unobserve(subscription_id)
92
+ end
93
+
94
+ # @return [void]
95
+ def each(&block)
96
+ document.current_transaction { |tx| ymap_each(tx, block) }
97
+ end
98
+
99
+ # @return [true, false]
100
+ def key?(key)
101
+ document.current_transaction { |tx| ymap_contains(tx, key) }
102
+ end
103
+
104
+ alias has_key? key?
105
+
106
+ # @return [Object]
107
+ def [](key)
108
+ document.current_transaction { |tx| ymap_get(tx, key) }
109
+ end
110
+
111
+ # @return [void]
112
+ def []=(key, val)
113
+ document.current_transaction { |tx| ymap_insert(tx, key, val) }
114
+ end
115
+
116
+ # Returns size of map
117
+ #
118
+ # @return [Integer]
119
+ def size
120
+ document.current_transaction { |tx| ymap_size(tx) }
121
+ end
122
+
123
+ # Returns a Hash representation of this map
124
+ #
125
+ # @return [Hash]
126
+ def to_h
127
+ document.current_transaction { |tx| ymap_to_h(tx) }
128
+ end
129
+
130
+ # Returns a JSON representation of map
131
+ #
132
+ # @return [String] JSON string
133
+ def to_json(*_args)
134
+ to_h.to_json
135
+ end
136
+
137
+ # @!method ymap_clear(tx)
138
+ # Removes all key-value pairs from Map
139
+ #
140
+ # @param tx [Y::Transaction]
141
+
142
+ # @!method ymap_contains(tx, key)
143
+ # Check if a certain key is in the Map
144
+ #
145
+ # @param tx [Y::Transaction]
146
+ # @param key [String, Symbol]
147
+ # @return [Boolean] True, if and only if the key exists
148
+
149
+ # @!method ymap_each(tx, proc)
150
+ # Iterates over all key-value pairs in Map by calling the provided proc
151
+ # with the key and the value as arguments.
152
+ #
153
+ # @param tx [Y::Transaction]
154
+ # @param proc [Proc<String, Any>] A proc that is called for every element
155
+
156
+ # @!method ymap_get(tx, key)
157
+ # Returns stored value for key or nil if none is present
158
+ #
159
+ # @param tx [Y::Transaction]
160
+ # @param key [String, Symbol]
161
+ # @return [Object, nil] Value or nil
162
+
163
+ # @!method ymap_insert(tx, key, value)
164
+ # Insert value for key. In case the key already exists, the previous value
165
+ # will be overwritten.
166
+ #
167
+ # @param tx [Y::Transaction]
168
+ # @param key [String, Symbol]
169
+ # @param value [Object]
170
+
171
+ # @!method ymap_observe(callback)
172
+ #
173
+ # @param callback [Proc]
174
+ # @return [Integer]
175
+
176
+ # @!method ymap_remove(tx, key)
177
+ # Removes key-value pair from Map if key exists.
178
+ #
179
+ # @param tx [Y::Transaction]
180
+ # @param key [String, Symbol]
181
+
182
+ # @!method ymap_size(tx)
183
+ # Returns number of key-value pairs stored in map
184
+ #
185
+ # @param tx [Y::Transaction]
186
+ # @return [Integer] Number of key-value pairs
187
+
188
+ # @!method ymap_to_h(tx)
189
+ # Returns a Hash representation of the Map
190
+ #
191
+ # @param tx [Y::Transaction]
192
+ # @return [Hash] Hash representation of Map
193
+
194
+ # @!method ymap_unobserve(subscription_id)
195
+ #
196
+ # @param subscription_id [Integer]
197
+ # @return [void]
198
+ end
199
+ end
data/lib/y/text.rb ADDED
@@ -0,0 +1,383 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Y
4
+ # A text can be used insert and remove string fragments. It also supports
5
+ # formatting and the concept of embeds, which are supported data types that
6
+ # added as metadata.
7
+ #
8
+ # The text is the replicated counterpart to a String. It supports a subset
9
+ # of String operations, like appending, insert at position and slicing.
10
+ #
11
+ # Someone should not instantiate a text directly, but use {Y::Doc#get_text}
12
+ # instead.
13
+ #
14
+ # @example
15
+ # doc = Y::Doc.new
16
+ # text = doc.get_text("my text")
17
+ #
18
+ # text << "Hello, World!"
19
+ # puts text.to_s
20
+ class Text
21
+ # @!attribute [r] document
22
+ #
23
+ # @return [Y::Doc] The document this text belongs to
24
+ attr_accessor :document
25
+
26
+ # Create a new text instance
27
+ #
28
+ # @param doc [Y::Doc]
29
+ def initialize(doc = nil)
30
+ @document = doc || Y::Doc.new
31
+
32
+ super()
33
+ end
34
+
35
+ # Appends a string at the end of the text
36
+ #
37
+ # @param str [String]
38
+ # @return [void]
39
+ def <<(str)
40
+ document.current_transaction { |tx| ytext_push(tx, str) }
41
+ end
42
+
43
+ # Attach listener to text changes
44
+ #
45
+ # @example Listen to changes in text type
46
+ # local = Y::Doc.new
47
+ #
48
+ # text = local.get_text("my text")
49
+ # text.attach(->(delta) { pp delta }) # { insert: "Hello, World!" }
50
+ #
51
+ # local.transact do
52
+ # text << "Hello, World!"
53
+ # end
54
+ #
55
+ # @example Listen to changes in text type
56
+ # local = Y::Doc.new
57
+ #
58
+ # text = local.get_text("my text")
59
+ # text.attach(->(delta) { pp delta }) # { insert: "Hello, World!" }
60
+ #
61
+ # text << "Hello, World!"
62
+ #
63
+ # # todo: required, otherwise segfault
64
+ # local.commit
65
+ #
66
+ # @param callback [Proc]
67
+ # @param block [Block]
68
+ # @return [Integer]
69
+ def attach(callback, &block)
70
+ return ytext_observe(callback) unless callback.nil?
71
+
72
+ ytext_observe(block.to_proc) unless block.nil?
73
+ end
74
+
75
+ # Detach listener
76
+ #
77
+ # @param subscription_id [Integer]
78
+ # @return [void]
79
+ def detach(subscription_id)
80
+ ytext_unobserve(subscription_id)
81
+ end
82
+
83
+ # Diff
84
+ #
85
+ # @return[Array<YDiff>]
86
+ def diff
87
+ document.current_transaction do |tx|
88
+ ytext_diff(tx)
89
+ end
90
+ end
91
+
92
+ # Checks if text is empty
93
+ #
94
+ # @example Check if text is empty
95
+ # doc = Y::Doc.new
96
+ # text = doc.get_text("my text")
97
+ #
98
+ # text.empty? # true
99
+ #
100
+ # @return [TrueClass,FalseClass]
101
+ def empty?
102
+ length.zero?
103
+ end
104
+
105
+ # rubocop:disable Metrics/MethodLength
106
+
107
+ # Insert a value at position and with optional attributes. This method is
108
+ # similar to [String#insert](https://ruby-doc.org/core-3.1.2/String.html),
109
+ # except for the optional third `attrs` argument.
110
+ #
111
+ # @example Insert a string at position
112
+ # doc = Y::Doc.new
113
+ # text = doc.get_text("my text")
114
+ # text << "Hello, "
115
+ #
116
+ # text.insert(7, "World!")
117
+ #
118
+ # puts text.to_s == "Hello, World!" # true
119
+ #
120
+ # The value can be any of the supported types:
121
+ # - Boolean
122
+ # - String
123
+ # - Numeric
124
+ # - Array (where element types must be supported)
125
+ # - Hash (where the the types of key and values must be supported)
126
+ #
127
+ # @param index [Integer]
128
+ # @param value [String, Numeric, Array, Hash]
129
+ # @param attrs [Hash, nil]
130
+ # @return [void]
131
+ def insert(index, value, attrs = nil)
132
+ document.current_transaction do |tx|
133
+ if value.is_a?(String)
134
+ ytext_insert(tx, index, value) if attrs.nil?
135
+ unless attrs.nil?
136
+ ytext_insert_with_attributes(tx, index, value, attrs)
137
+ end
138
+ return nil
139
+ end
140
+
141
+ if can_insert?(value)
142
+ ytext_insert_embed(tx, index, value) if attrs.nil?
143
+ unless attrs.nil?
144
+ ytext_insert_embed_with_attributes(tx, index, value, attrs)
145
+ end
146
+ return nil
147
+ end
148
+
149
+ raise ArgumentError,
150
+ "Can't insert value. `#{value.class.name}` isn't supported."
151
+ end
152
+ end
153
+
154
+ # rubocop:enable Metrics/MethodLength
155
+
156
+ # Applies formatting to text
157
+ #
158
+ # @example Add formatting to first word
159
+ # doc = Y::Doc.new
160
+ # text = doc.get_text("my text")
161
+ #
162
+ # attrs = {format: "bold"}
163
+ # text.format(0, 2, attrs)
164
+ #
165
+ # @param index [Integer]
166
+ # @param length [Integer]
167
+ # @param attrs [Hash]
168
+ # @return [void]
169
+ def format(index, length, attrs)
170
+ document.current_transaction do |tx|
171
+ ytext_format(tx, index, length, attrs)
172
+ end
173
+ end
174
+
175
+ # Returns length of text
176
+ #
177
+ # @return [Integer] Length of text
178
+ def length
179
+ document.current_transaction { |tx| ytext_length(tx) }
180
+ end
181
+
182
+ alias size length
183
+
184
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
185
+
186
+ # Removes a part from text
187
+ #
188
+ # **Attention:** In comparison to String#slice, {Text#slice!} will not
189
+ # return the substring that gets removed. Even this being technically
190
+ # possible, it requires us to read the substring before removing it, which
191
+ # is not desirable in most situations.
192
+ #
193
+ # @example Removes a single character
194
+ # doc = Y::Doc.new
195
+ #
196
+ # text = doc.get_text("my text")
197
+ # text << "Hello"
198
+ #
199
+ # text.slice!(0)
200
+ #
201
+ # text.to_s == "ello" # true
202
+ #
203
+ # @example Removes a range of characters
204
+ # doc = Y::Doc.new
205
+ #
206
+ # text = doc.get_text("my text")
207
+ # text << "Hello"
208
+ #
209
+ # text.slice!(1..2)
210
+ # text.to_s == "Hlo" # true
211
+ #
212
+ # text.slice!(1...2)
213
+ # text.to_s == "Ho" # true
214
+ #
215
+ # @example Removes a range of chars from start and for given length
216
+ # doc = Y::Doc.new
217
+ #
218
+ # text = doc.get_text("my text")
219
+ # text << "Hello"
220
+ #
221
+ # text.slice!(0, 3)
222
+ #
223
+ # text.to_s == "lo" # true
224
+ #
225
+ # @overload slice!(index)
226
+ # Removes a single character at index
227
+ #
228
+ # @overload slice!(start, length)
229
+ # Removes a range of characters
230
+ #
231
+ # @overload slice!(range)
232
+ # Removes a range of characters
233
+ #
234
+ # @return [void]
235
+ def slice!(*args)
236
+ document.current_transaction do |tx|
237
+ if args.empty?
238
+ raise ArgumentError,
239
+ "Provide one of `index`, `range`, `start, length` as arguments"
240
+ end
241
+
242
+ if args.size == 1
243
+ arg = args.first
244
+
245
+ if arg.is_a?(Range)
246
+ ytext_remove_range(tx, arg.first, arg.last - arg.first)
247
+ return nil
248
+ end
249
+
250
+ if arg.is_a?(Numeric)
251
+ ytext_remove_range(tx, arg.to_int, 1)
252
+ return nil
253
+ end
254
+ end
255
+
256
+ if args.size == 2
257
+ start, length = args
258
+
259
+ if start.is_a?(Numeric) && length.is_a?(Numeric)
260
+ ytext_remove_range(tx, start, length)
261
+ return nil
262
+ end
263
+ end
264
+
265
+ raise ArgumentError, "Please check your arguments, can't slice."
266
+ end
267
+ end
268
+
269
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
270
+
271
+ # Returns string representation of text
272
+ #
273
+ # @example
274
+ # doc = Y::Doc.new
275
+ # text = doc.get_text("my text")
276
+ # text << "Hello"
277
+ #
278
+ # puts text.to_s # "Hello"
279
+ #
280
+ # @return [String]
281
+ def to_s
282
+ document.current_transaction { |tx| ytext_to_s(tx) }
283
+ end
284
+
285
+ private
286
+
287
+ def can_insert?(value)
288
+ value.is_a?(NilClass) ||
289
+ value.is_a?(Symbol) ||
290
+ [true, false].include?(value) ||
291
+ value.is_a?(Numeric) ||
292
+ value.is_a?(Enumerable) ||
293
+ value.is_a?(Hash)
294
+ end
295
+
296
+ # @!method ytext_diff(tx)
297
+ # Returns text changes as list of diffs
298
+ #
299
+ # @param transaction [Y::Transaction]
300
+ # @return [Array<YDiff>]
301
+
302
+ # @!method ytext_insert(tx, index, chunk)
303
+ # Insert into text at position
304
+ #
305
+ # @param transaction [Y::Transaction]
306
+ # @param index [Integer]
307
+ # @param chunk [String]
308
+ # @return [nil]
309
+
310
+ # @!method ytext_insert_embed(tx, index, content)
311
+ # Insert into text at position
312
+ #
313
+ # @param tx [Y::Transaction]
314
+ # @param index [Integer]
315
+ # @param content [Y::Text|Y::Array|Y::Map]
316
+ # @return [nil]
317
+
318
+ # @!method ytext_insert_embed_with_attributes(tx, index, embed, attrs)
319
+ # Insert into text at position
320
+ #
321
+ # @param tx [Y::Transaction]
322
+ # @param index [Integer]
323
+ # @param embed [Y::Text, Y::Array, Y::Map]
324
+ # @param attrs [Hash]
325
+ # @return [nil]
326
+
327
+ # @!method ytext_insert_with_attributes(tx, index, chunk, attrs)
328
+ # Insert into text at position
329
+ #
330
+ # @param tx [Y::Transaction]
331
+ # @param index [Integer]
332
+ # @param chunk [String]
333
+ # @param attrs [Hash]
334
+ # @return [nil]
335
+
336
+ # @!method ytext_push(tx, value)
337
+ # Returns length of text
338
+ #
339
+ # @param tx [Y::Transaction]
340
+ # @param value [String]
341
+ # @return [nil]
342
+
343
+ # @!method ytext_remove_range(tx, index, length)
344
+ # Removes a range from text
345
+ #
346
+ # @param tx [Y::Transaction]
347
+ # @param index [Integer]
348
+ # @param length [Integer]
349
+ # @return [nil]
350
+
351
+ # @!method ytext_format(tx, index, length, attrs)
352
+ # Formats a text range
353
+ #
354
+ # @param tx [Y::Transaction]
355
+ # @param index [Integer]
356
+ # @param length [Integer]
357
+ # @param attrs [Hash]
358
+ # @return [nil]
359
+
360
+ # @!method ytext_length(tx)
361
+ # Returns length of text
362
+ #
363
+ # @param tx [Y::Transaction]
364
+ # @return [Integer]
365
+
366
+ # @!method ytext_observe(proc)
367
+ # Observe text changes
368
+ #
369
+ # @param proc [Proc]
370
+ # @return [Integer]
371
+
372
+ # @!method ytext_to_s()
373
+ # Returns string representation of text
374
+ #
375
+ # @return [String]
376
+
377
+ # @!method ytext_unobserve(subscription_id)
378
+ # Detach listener
379
+ #
380
+ # @param subscription_id [Integer]
381
+ # @return [void]
382
+ end
383
+ end