taskchampion-rb 0.2.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.
@@ -0,0 +1,480 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Synchronization workflow examples for TaskChampion Ruby bindings
5
+ # This file demonstrates different sync patterns and server configurations
6
+
7
+ require 'taskchampion'
8
+ require 'securerandom'
9
+ require 'tmpdir'
10
+ require 'fileutils'
11
+
12
+ puts "TaskChampion Ruby Synchronization Examples"
13
+ puts "=" * 45
14
+
15
+ # Create temporary directories for this example
16
+ temp_base = Dir.mktmpdir("taskchampion-sync")
17
+ client1_dir = File.join(temp_base, "client1")
18
+ client2_dir = File.join(temp_base, "client2")
19
+ server_dir = File.join(temp_base, "server")
20
+
21
+ begin
22
+ # Ensure directories exist
23
+ [client1_dir, client2_dir, server_dir].each { |dir| FileUtils.mkdir_p(dir) }
24
+
25
+ puts "\nSetup:"
26
+ puts "Client 1: #{client1_dir}"
27
+ puts "Client 2: #{client2_dir}"
28
+ puts "Server: #{server_dir}"
29
+
30
+ # ========================================
31
+ # 1. LOCAL FILE SYNCHRONIZATION
32
+ # ========================================
33
+
34
+ puts "\n" + "=" * 50
35
+ puts "1. LOCAL FILE SYNCHRONIZATION"
36
+ puts "=" * 50
37
+
38
+ puts "\nCreating two client replicas..."
39
+
40
+ # Create first client replica
41
+ client1 = Taskchampion::Replica.new_on_disk(client1_dir, create_if_missing: true)
42
+
43
+ # Create second client replica
44
+ client2 = Taskchampion::Replica.new_on_disk(client2_dir, create_if_missing: true)
45
+
46
+ # Add tasks to client 1
47
+ puts "\nAdding tasks to Client 1..."
48
+ ops1 = Taskchampion::Operations.new
49
+
50
+ uuid1 = SecureRandom.uuid
51
+ task1 = client1.create_task(uuid1, ops1)
52
+ task1.set_description("Task from Client 1", ops1)
53
+ task1.set_priority("H", ops1)
54
+ task1.add_tag(Taskchampion::Tag.new("client1"), ops1)
55
+
56
+ uuid2 = SecureRandom.uuid
57
+ task2 = client1.create_task(uuid2, ops1)
58
+ task2.set_description("Shared task", ops1)
59
+ task2.add_tag(Taskchampion::Tag.new("shared"), ops1)
60
+
61
+ client1.commit_operations(ops1)
62
+ puts "Client 1 has #{client1.task_uuids.length} tasks"
63
+
64
+ # Add tasks to client 2
65
+ puts "\nAdding tasks to Client 2..."
66
+ ops2 = Taskchampion::Operations.new
67
+
68
+ uuid3 = SecureRandom.uuid
69
+ task3 = client2.create_task(uuid3, ops2)
70
+ task3.set_description("Task from Client 2", ops2)
71
+ task3.set_priority("M", ops2)
72
+ task3.add_tag(Taskchampion::Tag.new("client2"), ops2)
73
+
74
+ client2.commit_operations(ops2)
75
+ puts "Client 2 has #{client2.task_uuids.length} tasks"
76
+
77
+ # Sync Client 1 to server (upload)
78
+ puts "\nSyncing Client 1 to server..."
79
+ begin
80
+ client1.sync_to_local(server_dir, avoid_snapshots: false)
81
+ puts "✓ Client 1 synced to server successfully"
82
+ rescue => e
83
+ puts "✗ Client 1 sync failed: #{e.message}"
84
+ end
85
+
86
+ # Sync Client 2 to server (upload) and from server (download)
87
+ puts "\nSyncing Client 2 with server..."
88
+ begin
89
+ client2.sync_to_local(server_dir, avoid_snapshots: false)
90
+ puts "✓ Client 2 synced with server successfully"
91
+ puts "Client 2 now has #{client2.task_uuids.length} tasks"
92
+ rescue => e
93
+ puts "✗ Client 2 sync failed: #{e.message}"
94
+ end
95
+
96
+ # Sync Client 1 from server (download changes from Client 2)
97
+ puts "\nSyncing Client 1 from server..."
98
+ begin
99
+ client1.sync_to_local(server_dir, avoid_snapshots: false)
100
+ puts "✓ Client 1 synced from server successfully"
101
+ puts "Client 1 now has #{client1.task_uuids.length} tasks"
102
+ rescue => e
103
+ puts "✗ Client 1 sync failed: #{e.message}"
104
+ end
105
+
106
+ # Verify both clients have all tasks
107
+ puts "\nVerification after sync:"
108
+ client1_tasks = client1.task_uuids.map { |uuid| client1.task(uuid) }.compact
109
+ client2_tasks = client2.task_uuids.map { |uuid| client2.task(uuid) }.compact
110
+
111
+ puts "Client 1 tasks:"
112
+ client1_tasks.each do |task|
113
+ tags = task.tags.map(&:name).join(", ")
114
+ puts " - #{task.description} [#{tags}]"
115
+ end
116
+
117
+ puts "Client 2 tasks:"
118
+ client2_tasks.each do |task|
119
+ tags = task.tags.map(&:name).join(", ")
120
+ puts " - #{task.description} [#{tags}]"
121
+ end
122
+
123
+ # ========================================
124
+ # 2. CONFLICT RESOLUTION
125
+ # ========================================
126
+
127
+ puts "\n" + "=" * 50
128
+ puts "2. CONFLICT RESOLUTION"
129
+ puts "=" * 50
130
+
131
+ # Create conflicting changes
132
+ puts "\nCreating conflicting changes..."
133
+
134
+ # Client 1 modifies the shared task
135
+ shared_task_c1 = client1.task(uuid2)
136
+ if shared_task_c1
137
+ ops1_conflict = Taskchampion::Operations.new
138
+ shared_task_c1.set_description("Shared task - modified by Client 1", ops1_conflict)
139
+ shared_task_c1.set_priority("H", ops1_conflict)
140
+ shared_task_c1.add_tag(Taskchampion::Tag.new("modified-c1"), ops1_conflict)
141
+ client1.commit_operations(ops1_conflict)
142
+ puts "✓ Client 1 modified shared task"
143
+ end
144
+
145
+ # Client 2 also modifies the same shared task
146
+ shared_task_c2 = client2.task(uuid2)
147
+ if shared_task_c2
148
+ ops2_conflict = Taskchampion::Operations.new
149
+ shared_task_c2.set_description("Shared task - modified by Client 2", ops2_conflict)
150
+ shared_task_c2.set_priority("L", ops2_conflict)
151
+ shared_task_c2.add_tag(Taskchampion::Tag.new("modified-c2"), ops2_conflict)
152
+ client2.commit_operations(ops2_conflict)
153
+ puts "✓ Client 2 modified shared task"
154
+ end
155
+
156
+ # Sync and see how conflicts are resolved
157
+ puts "\nSyncing conflicting changes..."
158
+
159
+ # Client 1 syncs first
160
+ begin
161
+ client1.sync_to_local(server_dir, avoid_snapshots: false)
162
+ puts "✓ Client 1 synced (first to server)"
163
+ rescue => e
164
+ puts "✗ Client 1 sync failed: #{e.message}"
165
+ end
166
+
167
+ # Client 2 syncs (will resolve conflicts)
168
+ begin
169
+ client2.sync_to_local(server_dir, avoid_snapshots: false)
170
+ puts "✓ Client 2 synced (conflict resolution)"
171
+ rescue => e
172
+ puts "✗ Client 2 sync failed: #{e.message}"
173
+ end
174
+
175
+ # Client 1 syncs again to get resolved state
176
+ begin
177
+ client1.sync_to_local(server_dir, avoid_snapshots: false)
178
+ puts "✓ Client 1 synced (getting resolved state)"
179
+ rescue => e
180
+ puts "✗ Client 1 sync failed: #{e.message}"
181
+ end
182
+
183
+ # Show final state after conflict resolution
184
+ puts "\nFinal state after conflict resolution:"
185
+ final_task_c1 = client1.task(uuid2)
186
+ final_task_c2 = client2.task(uuid2)
187
+
188
+ if final_task_c1
189
+ tags = final_task_c1.tags.map(&:name).join(", ")
190
+ puts "Client 1 sees: '#{final_task_c1.description}' priority=#{final_task_c1.priority} tags=[#{tags}]"
191
+ end
192
+
193
+ if final_task_c2
194
+ tags = final_task_c2.tags.map(&:name).join(", ")
195
+ puts "Client 2 sees: '#{final_task_c2.description}' priority=#{final_task_c2.priority} tags=[#{tags}]"
196
+ end
197
+
198
+ # ========================================
199
+ # 3. SYNC PATTERNS AND BEST PRACTICES
200
+ # ========================================
201
+
202
+ puts "\n" + "=" * 50
203
+ puts "3. SYNC PATTERNS AND BEST PRACTICES"
204
+ puts "=" * 50
205
+
206
+ # Pattern 1: Sync before and after work session
207
+ puts "\nPattern 1: Sync before and after work session"
208
+
209
+ def work_session(replica, server_dir, client_name)
210
+ puts "\n#{client_name}: Starting work session..."
211
+
212
+ # 1. Sync before work (get latest changes)
213
+ begin
214
+ replica.sync_to_local(server_dir, avoid_snapshots: false)
215
+ puts " ✓ Pre-work sync completed"
216
+ rescue => e
217
+ puts " ✗ Pre-work sync failed: #{e.message}"
218
+ return false
219
+ end
220
+
221
+ # 2. Do work
222
+ operations = Taskchampion::Operations.new
223
+ work_uuid = SecureRandom.uuid
224
+ task = replica.create_task(work_uuid, operations)
225
+ task.set_description("Work session task from #{client_name}", operations)
226
+ task.add_tag(Taskchampion::Tag.new("work-session"), operations)
227
+ replica.commit_operations(operations)
228
+ puts " ✓ Created work session task"
229
+
230
+ # 3. Sync after work (share changes)
231
+ begin
232
+ replica.sync_to_local(server_dir, avoid_snapshots: false)
233
+ puts " ✓ Post-work sync completed"
234
+ return true
235
+ rescue => e
236
+ puts " ✗ Post-work sync failed: #{e.message}"
237
+ return false
238
+ end
239
+ end
240
+
241
+ # Simulate work sessions
242
+ work_session(client1, server_dir, "Client 1")
243
+ work_session(client2, server_dir, "Client 2")
244
+
245
+ # Pattern 2: Periodic sync with error handling
246
+ puts "\nPattern 2: Periodic sync with error handling"
247
+
248
+ def periodic_sync(replica, server_dir, client_name)
249
+ max_retries = 3
250
+ retry_delay = 1 # seconds
251
+
252
+ (1..max_retries).each do |attempt|
253
+ begin
254
+ puts " #{client_name}: Sync attempt #{attempt}/#{max_retries}"
255
+ replica.sync_to_local(server_dir, avoid_snapshots: false)
256
+ puts " ✓ Sync successful"
257
+ return true
258
+ rescue => e
259
+ puts " ✗ Sync attempt #{attempt} failed: #{e.message}"
260
+
261
+ if attempt < max_retries
262
+ puts " Retrying in #{retry_delay} seconds..."
263
+ sleep(retry_delay)
264
+ retry_delay *= 2 # Exponential backoff
265
+ else
266
+ puts " Max retries reached, giving up"
267
+ return false
268
+ end
269
+ end
270
+ end
271
+ end
272
+
273
+ periodic_sync(client1, server_dir, "Client 1")
274
+
275
+ # Pattern 3: Sync status checking
276
+ puts "\nPattern 3: Sync status and storage information"
277
+
278
+ def sync_status(replica, client_name)
279
+ puts "\n#{client_name} Status:"
280
+ puts " Local operations: #{replica.num_local_operations}"
281
+ puts " Undo points: #{replica.num_undo_points}"
282
+ puts " Total tasks: #{replica.task_uuids.length}"
283
+ end
284
+
285
+ sync_status(client1, "Client 1")
286
+ sync_status(client2, "Client 2")
287
+
288
+ # ========================================
289
+ # 4. REMOTE SERVER SYNC (EXAMPLES)
290
+ # ========================================
291
+
292
+ puts "\n" + "=" * 50
293
+ puts "4. REMOTE SERVER SYNC (EXAMPLES)"
294
+ puts "=" * 50
295
+
296
+ puts "\nNote: These examples show the API but won't actually connect"
297
+ puts " without a real TaskWarrior server or cloud storage setup.\n"
298
+
299
+ # Example: Sync to remote TaskWarrior server
300
+ puts "Example: Sync to remote TaskWarrior server"
301
+ puts "```ruby"
302
+ puts "begin"
303
+ puts " replica.sync_to_remote("
304
+ puts " url: 'https://taskserver.example.com:53589',"
305
+ puts " client_id: 'your-client-id-here',"
306
+ puts " encryption_secret: 'your-encryption-secret',"
307
+ puts " avoid_snapshots: false"
308
+ puts " )"
309
+ puts " puts '✓ Successfully synced to remote server'"
310
+ puts "rescue Taskchampion::SyncError => e"
311
+ puts " puts '✗ Remote sync failed: #{e.message}'"
312
+ puts "rescue Taskchampion::ConfigError => e"
313
+ puts " puts '✗ Configuration error: #{e.message}'"
314
+ puts "end"
315
+ puts "```\n"
316
+
317
+ # Example: Sync to Google Cloud Platform
318
+ puts "Example: Sync to Google Cloud Storage"
319
+ puts "```ruby"
320
+ puts "begin"
321
+ puts " replica.sync_to_gcp("
322
+ puts " bucket: 'my-taskwarrior-bucket',"
323
+ puts " credential_path: '/path/to/service-account.json',"
324
+ puts " encryption_secret: 'your-encryption-secret',"
325
+ puts " avoid_snapshots: false"
326
+ puts " )"
327
+ puts " puts '✓ Successfully synced to Google Cloud'"
328
+ puts "rescue Taskchampion::SyncError => e"
329
+ puts " puts '✗ GCP sync failed: #{e.message}'"
330
+ puts "rescue Taskchampion::ConfigError => e"
331
+ puts " puts '✗ GCP configuration error: #{e.message}'"
332
+ puts "end"
333
+ puts "```\n"
334
+
335
+ # ========================================
336
+ # 5. SNAPSHOT MANAGEMENT
337
+ # ========================================
338
+
339
+ puts "\n" + "=" * 50
340
+ puts "5. SNAPSHOT MANAGEMENT"
341
+ puts "=" * 50
342
+
343
+ puts "\nSnapshots help optimize sync performance for large task databases."
344
+ puts "They can be avoided for smaller datasets or debugging purposes.\n"
345
+
346
+ # Example with snapshots (default)
347
+ puts "Sync with snapshots (default, faster for large datasets):"
348
+ begin
349
+ client1.sync_to_local(server_dir, avoid_snapshots: false)
350
+ puts "✓ Sync with snapshots completed"
351
+ rescue => e
352
+ puts "✗ Sync failed: #{e.message}"
353
+ end
354
+
355
+ # Example without snapshots
356
+ puts "\nSync without snapshots (slower, but more predictable):"
357
+ begin
358
+ client2.sync_to_local(server_dir, avoid_snapshots: true)
359
+ puts "✓ Sync without snapshots completed"
360
+ rescue => e
361
+ puts "✗ Sync failed: #{e.message}"
362
+ end
363
+
364
+ # ========================================
365
+ # 6. MULTI-CLIENT WORKFLOW
366
+ # ========================================
367
+
368
+ puts "\n" + "=" * 50
369
+ puts "6. MULTI-CLIENT WORKFLOW SIMULATION"
370
+ puts "=" * 50
371
+
372
+ puts "\nSimulating a realistic multi-client workflow..."
373
+
374
+ # Simulate desktop client
375
+ puts "\n[Desktop Client] Creating project tasks..."
376
+ desktop_ops = Taskchampion::Operations.new
377
+
378
+ project_tasks = [
379
+ "Design user interface",
380
+ "Implement authentication",
381
+ "Write unit tests",
382
+ "Deploy to staging"
383
+ ]
384
+
385
+ project_uuids = []
386
+ project_tasks.each_with_index do |desc, index|
387
+ uuid = SecureRandom.uuid
388
+ project_uuids << uuid
389
+ task = client1.create_task(uuid, desktop_ops)
390
+ task.set_description(desc, desktop_ops)
391
+ task.set_priority(["H", "H", "M", "L"][index], desktop_ops)
392
+ task.add_tag(Taskchampion::Tag.new("project"), desktop_ops)
393
+ task.add_tag(Taskchampion::Tag.new("web-app"), desktop_ops)
394
+ end
395
+
396
+ client1.commit_operations(desktop_ops)
397
+ client1.sync_to_local(server_dir, avoid_snapshots: false)
398
+ puts " ✓ Desktop client created and synced #{project_tasks.length} project tasks"
399
+
400
+ # Simulate mobile client
401
+ puts "\n[Mobile Client] Adding personal tasks and checking project status..."
402
+ client2.sync_to_local(server_dir, avoid_snapshots: false) # Get project tasks
403
+
404
+ mobile_ops = Taskchampion::Operations.new
405
+
406
+ personal_tasks = [
407
+ "Buy groceries",
408
+ "Call dentist",
409
+ "Review project status"
410
+ ]
411
+
412
+ personal_tasks.each do |desc|
413
+ uuid = SecureRandom.uuid
414
+ task = client2.create_task(uuid, mobile_ops)
415
+ task.set_description(desc, mobile_ops)
416
+ task.add_tag(Taskchampion::Tag.new("personal"), mobile_ops)
417
+ end
418
+
419
+ # Complete first project task from mobile
420
+ project_task = client2.task(project_uuids.first)
421
+ if project_task
422
+ project_task.set_status(Taskchampion::Status.completed, mobile_ops)
423
+ project_task.set_end(Time.now, mobile_ops)
424
+ puts " ✓ Completed '#{project_task.description}' from mobile"
425
+ end
426
+
427
+ client2.commit_operations(mobile_ops)
428
+ client2.sync_to_local(server_dir, avoid_snapshots: false)
429
+ puts " ✓ Mobile client added personal tasks and updated project"
430
+
431
+ # Desktop client gets updates
432
+ puts "\n[Desktop Client] Syncing to see mobile updates..."
433
+ client1.sync_to_local(server_dir, avoid_snapshots: false)
434
+
435
+ # Show final state
436
+ puts "\nFinal synchronized state:"
437
+ all_tasks_c1 = client1.task_uuids.map { |uuid| client1.task(uuid) }.compact
438
+
439
+ project_tasks_final = all_tasks_c1.select { |t| t.has_tag?(Taskchampion::Tag.new("project")) }
440
+ personal_tasks_final = all_tasks_c1.select { |t| t.has_tag?(Taskchampion::Tag.new("personal")) }
441
+
442
+ puts "\nProject tasks:"
443
+ project_tasks_final.each do |task|
444
+ status_icon = task.completed? ? "✓" : "○"
445
+ puts " #{status_icon} #{task.description} [#{task.priority}]"
446
+ end
447
+
448
+ puts "\nPersonal tasks:"
449
+ personal_tasks_final.each do |task|
450
+ status_icon = task.completed? ? "✓" : "○"
451
+ puts " #{status_icon} #{task.description}"
452
+ end
453
+
454
+ puts "\n" + "=" * 50
455
+ puts "SYNCHRONIZATION EXAMPLES COMPLETED"
456
+ puts "=" * 50
457
+
458
+ puts "\nKey takeaways:"
459
+ puts "✓ Local file sync enables multi-client workflows"
460
+ puts "✓ TaskChampion handles conflict resolution automatically"
461
+ puts "✓ Always sync before and after work sessions"
462
+ puts "✓ Use proper error handling and retries"
463
+ puts "✓ Monitor sync status with storage information"
464
+ puts "✓ Consider snapshot settings for performance"
465
+
466
+ rescue => e
467
+ puts "\nError during sync examples: #{e.class} - #{e.message}"
468
+ puts e.backtrace.first(5).join("\n") if e.backtrace
469
+ ensure
470
+ # Clean up temporary directories
471
+ if temp_base && File.exist?(temp_base)
472
+ FileUtils.remove_entry(temp_base)
473
+ puts "\nCleaned up temporary directories: #{temp_base}"
474
+ end
475
+ end
476
+
477
+ puts "\nFor more information, see:"
478
+ puts "- examples/basic_usage.rb - Basic TaskChampion usage"
479
+ puts "- docs/API_REFERENCE.md - Complete API documentation"
480
+ puts "- docs/THREAD_SAFETY.md - Thread safety guidelines"
@@ -0,0 +1,20 @@
1
+ [package]
2
+ name = "taskchampion"
3
+ version = "0.1.0"
4
+ edition = "2021"
5
+ authors = ["Tim Case <tim@2drops.net>"]
6
+ publish = false
7
+
8
+ [lib]
9
+ name = "taskchampion"
10
+ crate-type = ["cdylib"]
11
+
12
+ [dependencies]
13
+ magnus = { version = "0.7", features = ["rb-sys"] }
14
+ rb-sys = "0.9"
15
+ taskchampion = "2.0"
16
+ chrono = "0.4"
17
+ uuid = "1.0"
18
+
19
+ [features]
20
+ default = ["rb-sys/stable-api-compiled-fallback"]
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mkmf"
4
+ require "rb_sys/mkmf"
5
+
6
+ create_rust_makefile("taskchampion/taskchampion")
@@ -0,0 +1,132 @@
1
+ use magnus::{class, function, method, prelude::*, Error, RModule, Symbol, TryConvert};
2
+ pub use taskchampion::storage::AccessMode as TCAccessMode;
3
+
4
+ #[magnus::wrap(class = "Taskchampion::AccessMode", free_immediately)]
5
+ #[derive(Clone, Copy, PartialEq)]
6
+ pub struct AccessMode(AccessModeKind);
7
+
8
+ #[derive(Clone, Copy, PartialEq, Hash)]
9
+ enum AccessModeKind {
10
+ ReadOnly,
11
+ ReadWrite,
12
+ }
13
+
14
+ impl AccessMode {
15
+ // Constructor methods
16
+ fn read_only() -> Self {
17
+ AccessMode(AccessModeKind::ReadOnly)
18
+ }
19
+
20
+ fn read_write() -> Self {
21
+ AccessMode(AccessModeKind::ReadWrite)
22
+ }
23
+
24
+ // Predicate methods
25
+ fn is_read_only(&self) -> bool {
26
+ matches!(self.0, AccessModeKind::ReadOnly)
27
+ }
28
+
29
+ fn is_read_write(&self) -> bool {
30
+ matches!(self.0, AccessModeKind::ReadWrite)
31
+ }
32
+
33
+ // String representations
34
+ fn to_s(&self) -> &'static str {
35
+ match self.0 {
36
+ AccessModeKind::ReadOnly => "read_only",
37
+ AccessModeKind::ReadWrite => "read_write",
38
+ }
39
+ }
40
+
41
+ fn inspect(&self) -> String {
42
+ format!("#<Taskchampion::AccessMode:{}>", self.to_s())
43
+ }
44
+
45
+ // Equality - handles any Ruby object type
46
+ fn eq(&self, other: magnus::Value) -> bool {
47
+ match <&AccessMode>::try_convert(other) {
48
+ Ok(other_mode) => self.0 == other_mode.0,
49
+ Err(_) => false, // Not an AccessMode object, so not equal
50
+ }
51
+ }
52
+
53
+ fn eql(&self, other: magnus::Value) -> bool {
54
+ match <&AccessMode>::try_convert(other) {
55
+ Ok(other_mode) => self.0 == other_mode.0,
56
+ Err(_) => false, // Not an AccessMode object, so not equal
57
+ }
58
+ }
59
+
60
+ fn hash(&self) -> u64 {
61
+ use std::collections::hash_map::DefaultHasher;
62
+ use std::hash::{Hash, Hasher};
63
+
64
+ let mut hasher = DefaultHasher::new();
65
+ std::mem::discriminant(&self.0).hash(&mut hasher);
66
+ hasher.finish()
67
+ }
68
+
69
+ // For internal use
70
+ pub fn from_symbol(sym: Symbol) -> Result<Self, Error> {
71
+ match sym.to_string().as_str() {
72
+ "read_only" => Ok(AccessMode(AccessModeKind::ReadOnly)),
73
+ "read_write" => Ok(AccessMode(AccessModeKind::ReadWrite)),
74
+ _ => Err(Error::new(
75
+ magnus::exception::arg_error(),
76
+ "Invalid access mode, expected :read_only or :read_write",
77
+ )),
78
+ }
79
+ }
80
+
81
+ pub fn to_symbol(&self) -> Symbol {
82
+ match self.0 {
83
+ AccessModeKind::ReadOnly => Symbol::new("read_only"),
84
+ AccessModeKind::ReadWrite => Symbol::new("read_write"),
85
+ }
86
+ }
87
+ }
88
+
89
+ impl From<TCAccessMode> for AccessMode {
90
+ fn from(mode: TCAccessMode) -> Self {
91
+ match mode {
92
+ TCAccessMode::ReadOnly => AccessMode(AccessModeKind::ReadOnly),
93
+ TCAccessMode::ReadWrite => AccessMode(AccessModeKind::ReadWrite),
94
+ }
95
+ }
96
+ }
97
+
98
+ impl From<AccessMode> for TCAccessMode {
99
+ fn from(mode: AccessMode) -> Self {
100
+ match mode.0 {
101
+ AccessModeKind::ReadOnly => TCAccessMode::ReadOnly,
102
+ AccessModeKind::ReadWrite => TCAccessMode::ReadWrite,
103
+ }
104
+ }
105
+ }
106
+
107
+ pub fn init(module: &RModule) -> Result<(), Error> {
108
+ let class = module.define_class("AccessMode", class::object())?;
109
+
110
+ // Constructor methods
111
+ class.define_singleton_method("read_only", function!(AccessMode::read_only, 0))?;
112
+ class.define_singleton_method("read_write", function!(AccessMode::read_write, 0))?;
113
+
114
+ // Predicate methods
115
+ class.define_method("read_only?", method!(AccessMode::is_read_only, 0))?;
116
+ class.define_method("read_write?", method!(AccessMode::is_read_write, 0))?;
117
+
118
+ // String representations
119
+ class.define_method("to_s", method!(AccessMode::to_s, 0))?;
120
+ class.define_method("inspect", method!(AccessMode::inspect, 0))?;
121
+
122
+ // Equality
123
+ class.define_method("==", method!(AccessMode::eq, 1))?;
124
+ class.define_method("eql?", method!(AccessMode::eql, 1))?;
125
+ class.define_method("hash", method!(AccessMode::hash, 0))?;
126
+
127
+ // Keep the constants for backward compatibility
128
+ module.const_set("READ_ONLY", Symbol::new("read_only"))?;
129
+ module.const_set("READ_WRITE", Symbol::new("read_write"))?;
130
+
131
+ Ok(())
132
+ }