y-rb 0.1.0

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