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.
- checksums.yaml +7 -0
- data/.claude/settings.local.json +14 -0
- data/.rubocop.yml +21 -0
- data/CHANGELOG.md +15 -0
- data/Cargo.lock +3671 -0
- data/Cargo.toml +7 -0
- data/README.md +112 -0
- data/Rakefile +28 -0
- data/docs/API_REFERENCE.md +419 -0
- data/docs/THREAD_SAFETY.md +370 -0
- data/docs/breakthrough.md +246 -0
- data/docs/description.md +3 -0
- data/docs/phase_3_plan.md +482 -0
- data/docs/plan.md +612 -0
- data/example.md +465 -0
- data/examples/basic_usage.rb +278 -0
- data/examples/sync_workflow.rb +480 -0
- data/ext/taskchampion/Cargo.toml +20 -0
- data/ext/taskchampion/extconf.rb +6 -0
- data/ext/taskchampion/src/access_mode.rs +132 -0
- data/ext/taskchampion/src/annotation.rs +77 -0
- data/ext/taskchampion/src/dependency_map.rs +65 -0
- data/ext/taskchampion/src/error.rs +78 -0
- data/ext/taskchampion/src/lib.rs +41 -0
- data/ext/taskchampion/src/operation.rs +234 -0
- data/ext/taskchampion/src/operations.rs +180 -0
- data/ext/taskchampion/src/replica.rs +289 -0
- data/ext/taskchampion/src/status.rs +186 -0
- data/ext/taskchampion/src/tag.rs +77 -0
- data/ext/taskchampion/src/task.rs +388 -0
- data/ext/taskchampion/src/thread_check.rs +61 -0
- data/ext/taskchampion/src/util.rs +131 -0
- data/ext/taskchampion/src/working_set.rs +72 -0
- data/lib/taskchampion/version.rb +5 -0
- data/lib/taskchampion.rb +41 -0
- data/sig/taskchampion.rbs +4 -0
- data/taskchampion-0.2.0.gem +0 -0
- metadata +96 -0
data/example.md
ADDED
@@ -0,0 +1,465 @@
|
|
1
|
+
# TaskChampion-rb Examples
|
2
|
+
|
3
|
+
This document demonstrates typical Ruby usage patterns for TaskChampion-rb, showing how to build sophisticated task management applications with operational transformation, synchronization, and thread-safe access.
|
4
|
+
|
5
|
+
## š **Basic Setup & Task Creation**
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
require 'taskchampion'
|
9
|
+
require 'securerandom'
|
10
|
+
|
11
|
+
# Create a task database
|
12
|
+
replica = Taskchampion::Replica.new_on_disk("./my_tasks", true)
|
13
|
+
operations = Taskchampion::Operations.new
|
14
|
+
|
15
|
+
# Create a new task
|
16
|
+
uuid = SecureRandom.uuid
|
17
|
+
task = replica.create_task(uuid, operations)
|
18
|
+
|
19
|
+
# Commit the changes
|
20
|
+
replica.commit_operations(operations)
|
21
|
+
|
22
|
+
puts "Created task: #{task.uuid}"
|
23
|
+
```
|
24
|
+
|
25
|
+
## š **Real Task Management Workflow**
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
# Create a personal task manager
|
29
|
+
class TaskManager
|
30
|
+
def initialize(data_dir = "./tasks")
|
31
|
+
@replica = Taskchampion::Replica.new_on_disk(data_dir, true)
|
32
|
+
end
|
33
|
+
|
34
|
+
def add_task(description)
|
35
|
+
operations = Taskchampion::Operations.new
|
36
|
+
uuid = SecureRandom.uuid
|
37
|
+
|
38
|
+
task = @replica.create_task(uuid, operations)
|
39
|
+
@replica.commit_operations(operations)
|
40
|
+
|
41
|
+
puts "ā
Added task: #{description} (#{uuid[0..7]})"
|
42
|
+
task
|
43
|
+
end
|
44
|
+
|
45
|
+
def list_tasks
|
46
|
+
puts "\nš Your Tasks:"
|
47
|
+
@replica.task_uuids.each_with_index do |uuid, index|
|
48
|
+
task = @replica.task(uuid)
|
49
|
+
status = task.completed? ? "ā
" : "ā³"
|
50
|
+
puts "#{index + 1}. #{status} #{task.description} (#{uuid[0..7]})"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def task_count
|
55
|
+
@replica.task_uuids.length
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Usage
|
60
|
+
tm = TaskManager.new
|
61
|
+
tm.add_task("Buy groceries")
|
62
|
+
tm.add_task("Write documentation")
|
63
|
+
tm.add_task("Review pull requests")
|
64
|
+
tm.list_tasks
|
65
|
+
puts "Total tasks: #{tm.task_count}"
|
66
|
+
```
|
67
|
+
|
68
|
+
## š **Task Querying & Filtering**
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
# Find specific tasks
|
72
|
+
def find_active_tasks(replica)
|
73
|
+
replica.task_uuids.filter_map do |uuid|
|
74
|
+
task = replica.task(uuid)
|
75
|
+
task if task.active? && !task.completed?
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Working with task properties
|
80
|
+
def task_summary(replica, uuid)
|
81
|
+
task = replica.task(uuid)
|
82
|
+
return "Task not found" unless task
|
83
|
+
|
84
|
+
status_emoji = case
|
85
|
+
when task.completed? then "ā
"
|
86
|
+
when task.waiting? then "āøļø"
|
87
|
+
when task.blocked? then "š«"
|
88
|
+
else "ā³"
|
89
|
+
end
|
90
|
+
|
91
|
+
"#{status_emoji} #{task.description} (Priority: #{task.priority})"
|
92
|
+
end
|
93
|
+
|
94
|
+
# Usage
|
95
|
+
replica = Taskchampion::Replica.new_in_memory
|
96
|
+
operations = Taskchampion::Operations.new
|
97
|
+
|
98
|
+
uuid = SecureRandom.uuid
|
99
|
+
task = replica.create_task(uuid, operations)
|
100
|
+
replica.commit_operations(operations)
|
101
|
+
|
102
|
+
puts task_summary(replica, uuid)
|
103
|
+
```
|
104
|
+
|
105
|
+
## š·ļø **Working with Status & Operations**
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
# Status management
|
109
|
+
def mark_completed(replica, uuid)
|
110
|
+
operations = Taskchampion::Operations.new
|
111
|
+
|
112
|
+
# Note: Task mutation methods not yet implemented,
|
113
|
+
# but this shows the intended workflow
|
114
|
+
puts "Task #{uuid[0..7]} would be marked as completed"
|
115
|
+
|
116
|
+
# When implemented, would be:
|
117
|
+
# task = replica.task(uuid)
|
118
|
+
# task.set_status(Taskchampion::Status.completed, operations)
|
119
|
+
# replica.commit_operations(operations)
|
120
|
+
end
|
121
|
+
|
122
|
+
# Operations inspection
|
123
|
+
def show_operations_info(operations)
|
124
|
+
puts "Operations count: #{operations.length}"
|
125
|
+
puts "Empty? #{operations.empty?}"
|
126
|
+
|
127
|
+
operations.each do |op|
|
128
|
+
type = case
|
129
|
+
when op.create? then "CREATE"
|
130
|
+
when op.update? then "UPDATE"
|
131
|
+
when op.delete? then "DELETE"
|
132
|
+
else "OTHER"
|
133
|
+
end
|
134
|
+
puts "- #{type} operation"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# Working with status objects
|
139
|
+
pending = Taskchampion::Status.pending
|
140
|
+
completed = Taskchampion::Status.completed
|
141
|
+
|
142
|
+
puts "Status: #{pending.to_s}" # => "pending"
|
143
|
+
puts "Is pending? #{pending.pending?}" # => true
|
144
|
+
puts "Equal? #{pending == completed}" # => false
|
145
|
+
|
146
|
+
# Access modes
|
147
|
+
read_write = Taskchampion::AccessMode.read_write
|
148
|
+
read_only = Taskchampion::AccessMode.read_only
|
149
|
+
|
150
|
+
puts "Mode: #{read_write.to_s}" # => "read_write"
|
151
|
+
puts "Read only? #{read_only.read_only?}" # => true
|
152
|
+
```
|
153
|
+
|
154
|
+
## š **Task Organization & Dependencies**
|
155
|
+
|
156
|
+
```ruby
|
157
|
+
# Working with working sets
|
158
|
+
def show_task_organization(replica)
|
159
|
+
working_set = replica.working_set
|
160
|
+
dep_map = replica.dependency_map
|
161
|
+
|
162
|
+
puts "š Task Organization:"
|
163
|
+
puts "Largest index: #{working_set.largest_index}"
|
164
|
+
|
165
|
+
# Show tasks by index
|
166
|
+
(1..working_set.largest_index).each do |index|
|
167
|
+
uuid = working_set.by_index(index)
|
168
|
+
if uuid
|
169
|
+
task = replica.task(uuid)
|
170
|
+
deps = dep_map.dependencies(uuid)
|
171
|
+
|
172
|
+
dep_info = deps.empty? ? "" : " (depends on #{deps.length} tasks)"
|
173
|
+
puts "#{index}. #{task.description}#{dep_info}"
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# Working with dependencies
|
179
|
+
def analyze_dependencies(replica, uuid)
|
180
|
+
dep_map = replica.dependency_map
|
181
|
+
|
182
|
+
dependencies = dep_map.dependencies(uuid)
|
183
|
+
dependents = dep_map.dependents(uuid)
|
184
|
+
|
185
|
+
puts "Task #{uuid[0..7]}:"
|
186
|
+
puts " Depends on: #{dependencies.length} tasks"
|
187
|
+
puts " Blocks: #{dependents.length} tasks"
|
188
|
+
puts " Has dependencies? #{dep_map.has_dependency?(uuid)}"
|
189
|
+
end
|
190
|
+
```
|
191
|
+
|
192
|
+
## š ļø **Error Handling & Thread Safety**
|
193
|
+
|
194
|
+
```ruby
|
195
|
+
# Proper error handling
|
196
|
+
def safe_task_access(replica, uuid)
|
197
|
+
begin
|
198
|
+
task = replica.task(uuid)
|
199
|
+
task ? task.description : "Task not found"
|
200
|
+
rescue Taskchampion::ThreadError => e
|
201
|
+
"Access denied: #{e.message}"
|
202
|
+
rescue Taskchampion::Error => e
|
203
|
+
"TaskChampion error: #{e.message}"
|
204
|
+
rescue => e
|
205
|
+
"Unexpected error: #{e.message}"
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
# Thread safety demonstration
|
210
|
+
def demonstrate_thread_safety
|
211
|
+
replica = Taskchampion::Replica.new_in_memory
|
212
|
+
|
213
|
+
Thread.new do
|
214
|
+
begin
|
215
|
+
replica.task_uuids # This will raise ThreadError
|
216
|
+
rescue Taskchampion::ThreadError
|
217
|
+
puts "ā
Thread safety working - cross-thread access blocked"
|
218
|
+
end
|
219
|
+
end.join
|
220
|
+
end
|
221
|
+
|
222
|
+
# Operations manipulation
|
223
|
+
def demonstrate_operations
|
224
|
+
operations = Taskchampion::Operations.new
|
225
|
+
|
226
|
+
# Create some operations
|
227
|
+
uuid1 = SecureRandom.uuid
|
228
|
+
uuid2 = SecureRandom.uuid
|
229
|
+
|
230
|
+
op1 = Taskchampion::Operation.create(uuid1)
|
231
|
+
op2 = Taskchampion::Operation.create(uuid2)
|
232
|
+
|
233
|
+
# Add operations
|
234
|
+
operations.push(op1)
|
235
|
+
operations << op2 # Same as push
|
236
|
+
|
237
|
+
puts "Operations count: #{operations.length}"
|
238
|
+
puts "Operations: #{operations.inspect}"
|
239
|
+
|
240
|
+
# Iterate operations
|
241
|
+
operations.each do |op|
|
242
|
+
puts "Operation type: #{op.create? ? 'CREATE' : 'OTHER'}"
|
243
|
+
end
|
244
|
+
|
245
|
+
# Convert to array
|
246
|
+
ops_array = operations.to_a
|
247
|
+
puts "Array length: #{ops_array.length}"
|
248
|
+
|
249
|
+
# Clear operations
|
250
|
+
operations.clear
|
251
|
+
puts "After clear: #{operations.empty?}"
|
252
|
+
end
|
253
|
+
```
|
254
|
+
|
255
|
+
## šÆ **Complete Example: Simple CLI Task Manager**
|
256
|
+
|
257
|
+
```ruby
|
258
|
+
#!/usr/bin/env ruby
|
259
|
+
require 'taskchampion'
|
260
|
+
require 'securerandom'
|
261
|
+
|
262
|
+
class SimpleTasks
|
263
|
+
def initialize
|
264
|
+
@replica = Taskchampion::Replica.new_on_disk(
|
265
|
+
File.expand_path("~/.simple_tasks"),
|
266
|
+
true
|
267
|
+
)
|
268
|
+
end
|
269
|
+
|
270
|
+
def add(description)
|
271
|
+
operations = Taskchampion::Operations.new
|
272
|
+
uuid = SecureRandom.uuid
|
273
|
+
|
274
|
+
task = @replica.create_task(uuid, operations)
|
275
|
+
@replica.commit_operations(operations)
|
276
|
+
|
277
|
+
puts "Added: #{description} [#{uuid[0..7]}]"
|
278
|
+
end
|
279
|
+
|
280
|
+
def list
|
281
|
+
uuids = @replica.task_uuids
|
282
|
+
if uuids.empty?
|
283
|
+
puts "No tasks yet. Use 'add <description>' to create one."
|
284
|
+
return
|
285
|
+
end
|
286
|
+
|
287
|
+
puts "\nYour Tasks:"
|
288
|
+
uuids.each_with_index do |uuid, i|
|
289
|
+
task = @replica.task(uuid)
|
290
|
+
puts "#{i + 1}. #{task.description} [#{uuid[0..7]}]"
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
def stats
|
295
|
+
count = @replica.task_uuids.length
|
296
|
+
working_set = @replica.working_set
|
297
|
+
|
298
|
+
puts "\nš Stats:"
|
299
|
+
puts "Total tasks: #{count}"
|
300
|
+
puts "Largest index: #{working_set.largest_index}"
|
301
|
+
end
|
302
|
+
|
303
|
+
def show_task_details(index)
|
304
|
+
uuids = @replica.task_uuids
|
305
|
+
if index < 1 || index > uuids.length
|
306
|
+
puts "Invalid task number. Use 'list' to see tasks."
|
307
|
+
return
|
308
|
+
end
|
309
|
+
|
310
|
+
uuid = uuids[index - 1]
|
311
|
+
task = @replica.task(uuid)
|
312
|
+
|
313
|
+
puts "\nš Task Details:"
|
314
|
+
puts "UUID: #{task.uuid}"
|
315
|
+
puts "Description: #{task.description}"
|
316
|
+
puts "Status: #{task.status}"
|
317
|
+
puts "Priority: #{task.priority}"
|
318
|
+
puts "Created: #{task.entry}"
|
319
|
+
puts "Modified: #{task.modified}"
|
320
|
+
puts "Active: #{task.active?}"
|
321
|
+
puts "Completed: #{task.completed?}"
|
322
|
+
puts "Waiting: #{task.waiting?}"
|
323
|
+
puts "Blocked: #{task.blocked?}"
|
324
|
+
puts "Blocking others: #{task.blocking?}"
|
325
|
+
|
326
|
+
deps = task.dependencies
|
327
|
+
puts "Dependencies: #{deps.empty? ? 'None' : deps.join(', ')}"
|
328
|
+
|
329
|
+
tags = task.tags
|
330
|
+
puts "Tags: #{tags.empty? ? 'None' : tags.map(&:to_s).join(', ')}"
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
# CLI Usage
|
335
|
+
if ARGV.empty?
|
336
|
+
puts "Usage: #{$0} <command> [args]"
|
337
|
+
puts "Commands:"
|
338
|
+
puts " add <description> - Add a new task"
|
339
|
+
puts " list - List all tasks"
|
340
|
+
puts " show <number> - Show task details"
|
341
|
+
puts " stats - Show statistics"
|
342
|
+
exit 1
|
343
|
+
end
|
344
|
+
|
345
|
+
tasks = SimpleTasks.new
|
346
|
+
|
347
|
+
case ARGV[0]
|
348
|
+
when 'add'
|
349
|
+
description = ARGV[1..-1].join(' ')
|
350
|
+
if description.empty?
|
351
|
+
puts "Please provide a task description"
|
352
|
+
exit 1
|
353
|
+
end
|
354
|
+
tasks.add(description)
|
355
|
+
when 'list'
|
356
|
+
tasks.list
|
357
|
+
when 'show'
|
358
|
+
index = ARGV[1].to_i
|
359
|
+
tasks.show_task_details(index)
|
360
|
+
when 'stats'
|
361
|
+
tasks.stats
|
362
|
+
else
|
363
|
+
puts "Unknown command: #{ARGV[0]}"
|
364
|
+
exit 1
|
365
|
+
end
|
366
|
+
```
|
367
|
+
|
368
|
+
## š” **Key Ruby Patterns**
|
369
|
+
|
370
|
+
TaskChampion-rb follows standard Ruby conventions:
|
371
|
+
|
372
|
+
### **Boolean Methods**
|
373
|
+
```ruby
|
374
|
+
# Methods ending in ? return booleans
|
375
|
+
task.active? # => true/false
|
376
|
+
task.completed? # => true/false
|
377
|
+
status.pending? # => true/false
|
378
|
+
mode.read_only? # => true/false
|
379
|
+
operations.empty? # => true/false
|
380
|
+
```
|
381
|
+
|
382
|
+
### **Operators and Enumerable**
|
383
|
+
```ruby
|
384
|
+
# Natural Ruby operators
|
385
|
+
operations << operation # Append operator
|
386
|
+
operations.each {|op| puts op } # Block iteration
|
387
|
+
operations.length # Collection size
|
388
|
+
operations[index] # Array-like access
|
389
|
+
|
390
|
+
# Convert to array
|
391
|
+
ops_array = operations.to_a
|
392
|
+
```
|
393
|
+
|
394
|
+
### **String Representations**
|
395
|
+
```ruby
|
396
|
+
# Proper to_s and inspect methods
|
397
|
+
status.to_s # => "pending"
|
398
|
+
status.inspect # => "#<Taskchampion::Status:pending>"
|
399
|
+
operations.inspect # => "#<Taskchampion::Operations: 5 operations>"
|
400
|
+
```
|
401
|
+
|
402
|
+
### **Exception Hierarchy**
|
403
|
+
```ruby
|
404
|
+
begin
|
405
|
+
replica.task(uuid)
|
406
|
+
rescue Taskchampion::ThreadError => e
|
407
|
+
# Thread safety violation
|
408
|
+
rescue Taskchampion::Error => e
|
409
|
+
# General TaskChampion error
|
410
|
+
rescue => e
|
411
|
+
# Other errors
|
412
|
+
end
|
413
|
+
```
|
414
|
+
|
415
|
+
### **Flexible Constructors**
|
416
|
+
```ruby
|
417
|
+
# Multiple constructor patterns
|
418
|
+
Taskchampion::Replica.new_in_memory
|
419
|
+
Taskchampion::Replica.new_on_disk(path, create_if_missing)
|
420
|
+
Taskchampion::Replica.new_on_disk(path, create_if_missing, access_mode)
|
421
|
+
|
422
|
+
# Factory methods for enums
|
423
|
+
Taskchampion::Status.pending
|
424
|
+
Taskchampion::Status.completed
|
425
|
+
Taskchampion::AccessMode.read_write
|
426
|
+
```
|
427
|
+
|
428
|
+
## šļø **Architecture Benefits**
|
429
|
+
|
430
|
+
### **Thread Safety**
|
431
|
+
```ruby
|
432
|
+
# Automatic thread safety enforcement
|
433
|
+
replica = Taskchampion::Replica.new_in_memory
|
434
|
+
|
435
|
+
Thread.new do
|
436
|
+
# This will raise Taskchampion::ThreadError
|
437
|
+
replica.task_uuids
|
438
|
+
end
|
439
|
+
```
|
440
|
+
|
441
|
+
### **Operations-Based Consistency**
|
442
|
+
```ruby
|
443
|
+
# All changes go through operations for consistency
|
444
|
+
operations = Taskchampion::Operations.new
|
445
|
+
|
446
|
+
# Multiple operations can be batched
|
447
|
+
task1 = replica.create_task(uuid1, operations)
|
448
|
+
task2 = replica.create_task(uuid2, operations)
|
449
|
+
|
450
|
+
# Single commit applies all changes atomically
|
451
|
+
replica.commit_operations(operations)
|
452
|
+
```
|
453
|
+
|
454
|
+
### **Operational Transformation**
|
455
|
+
```ruby
|
456
|
+
# Operations can be inspected and manipulated
|
457
|
+
operations.each do |op|
|
458
|
+
puts "Operation: #{op.inspect}"
|
459
|
+
puts " Type: #{op.create? ? 'CREATE' : 'UPDATE'}"
|
460
|
+
puts " UUID: #{op.uuid}" unless op.undo_point?
|
461
|
+
puts " Timestamp: #{op.timestamp}"
|
462
|
+
end
|
463
|
+
```
|
464
|
+
|
465
|
+
This gives Ruby developers a familiar, idiomatic interface to powerful task management capabilities with built-in consistency guarantees and thread safety!
|