taskinator 0.3.10 → 0.3.11
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 +4 -4
- data/Gemfile +2 -0
- data/Gemfile.lock +14 -9
- data/lib/taskinator.rb +10 -10
- data/lib/taskinator/log_stats.rb +49 -0
- data/lib/taskinator/persistence.rb +19 -11
- data/lib/taskinator/process.rb +15 -11
- data/lib/taskinator/redis_connection.rb +0 -2
- data/lib/taskinator/task_worker.rb +1 -0
- data/lib/taskinator/tasks.rb +13 -1
- data/lib/taskinator/version.rb +1 -1
- data/spec/spec_helper.rb +4 -2
- data/spec/taskinator/persistence_spec.rb +15 -3
- data/spec/taskinator/process_spec.rb +10 -8
- data/taskinator.gemspec +1 -0
- metadata +16 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e57a5add32ebc7ffc6af22bbe9768db6c89f896f
|
4
|
+
data.tar.gz: 434f7323883114425d9f24351f503557a2cc72c2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 917159bb5af8a31a8ea8ef8dc76b2cdb1a0c78336fc163fd38af2b63e327692550e3eecf92275bdc5f3fe24f7e17ab93d053809bb3a23b99ed69b1d9b01d9938
|
7
|
+
data.tar.gz: 0ff149a53cb1952b160e9cf3c4d3eb196da080c2dd0f63112a63c0511cec01258f5d47a548326ddd03cbeda5cf8fd056fb85c54d57677e2a0a41aba363d30eec
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
taskinator (0.3.
|
4
|
+
taskinator (0.3.11)
|
5
5
|
builder (>= 3.2.2)
|
6
6
|
connection_pool (>= 2.2.0)
|
7
7
|
globalid (~> 0.3)
|
@@ -9,6 +9,7 @@ PATH
|
|
9
9
|
redis (>= 3.2.1)
|
10
10
|
redis-namespace (>= 1.5.2)
|
11
11
|
redis-semaphore (>= 0.2.4)
|
12
|
+
statsd-ruby (~> 1.2.0)
|
12
13
|
|
13
14
|
GEM
|
14
15
|
remote: https://rubygems.org/
|
@@ -20,7 +21,7 @@ GEM
|
|
20
21
|
thread_safe (~> 0.3, >= 0.3.4)
|
21
22
|
tzinfo (~> 1.1)
|
22
23
|
builder (3.2.2)
|
23
|
-
byebug (9.0.
|
24
|
+
byebug (9.0.6)
|
24
25
|
coderay (1.1.1)
|
25
26
|
concurrent-ruby (1.0.2)
|
26
27
|
connection_pool (2.2.0)
|
@@ -34,12 +35,14 @@ GEM
|
|
34
35
|
activesupport (>= 3.0, < 5.1)
|
35
36
|
diff-lcs (1.2.5)
|
36
37
|
docile (1.1.5)
|
38
|
+
fakeredis (0.6.0)
|
39
|
+
redis (~> 3.2)
|
37
40
|
globalid (0.3.7)
|
38
41
|
activesupport (>= 4.1.0)
|
39
42
|
i18n (0.7.0)
|
40
43
|
json (1.8.3)
|
41
44
|
method_source (0.8.2)
|
42
|
-
minitest (5.9.
|
45
|
+
minitest (5.9.1)
|
43
46
|
mono_logger (1.1.0)
|
44
47
|
multi_json (1.12.1)
|
45
48
|
pry (0.10.4)
|
@@ -52,7 +55,7 @@ GEM
|
|
52
55
|
rack (1.6.4)
|
53
56
|
rack-protection (1.5.3)
|
54
57
|
rack
|
55
|
-
rake (11.
|
58
|
+
rake (11.3.0)
|
56
59
|
redis (3.3.1)
|
57
60
|
redis-namespace (1.5.2)
|
58
61
|
redis (~> 3.0, >= 3.0.4)
|
@@ -73,7 +76,7 @@ GEM
|
|
73
76
|
rspec-core (~> 3.5.0)
|
74
77
|
rspec-expectations (~> 3.5.0)
|
75
78
|
rspec-mocks (~> 3.5.0)
|
76
|
-
rspec-core (3.5.
|
79
|
+
rspec-core (3.5.4)
|
77
80
|
rspec-support (~> 3.5.0)
|
78
81
|
rspec-expectations (3.5.0)
|
79
82
|
diff-lcs (>= 1.2.0, < 2.0)
|
@@ -85,11 +88,11 @@ GEM
|
|
85
88
|
rspec (~> 3.0, >= 3.0.0)
|
86
89
|
sidekiq (>= 2.4.0)
|
87
90
|
rspec-support (3.5.0)
|
88
|
-
sidekiq (4.
|
91
|
+
sidekiq (4.2.3)
|
89
92
|
concurrent-ruby (~> 1.0)
|
90
93
|
connection_pool (~> 2.2, >= 2.2.0)
|
94
|
+
rack-protection (>= 1.5.0)
|
91
95
|
redis (~> 3.2, >= 3.2.1)
|
92
|
-
sinatra (>= 1.4.7)
|
93
96
|
simplecov (0.12.0)
|
94
97
|
docile (~> 1.1.0)
|
95
98
|
json (>= 1.8, < 3)
|
@@ -100,7 +103,8 @@ GEM
|
|
100
103
|
rack-protection (~> 1.4)
|
101
104
|
tilt (>= 1.3, < 3)
|
102
105
|
slop (3.6.0)
|
103
|
-
|
106
|
+
statsd-ruby (1.2.1)
|
107
|
+
term-ansicolor (1.4.0)
|
104
108
|
tins (~> 1.0)
|
105
109
|
thor (0.19.1)
|
106
110
|
thread_safe (0.3.5)
|
@@ -119,6 +123,7 @@ DEPENDENCIES
|
|
119
123
|
bundler (>= 1.6.0)
|
120
124
|
coveralls (>= 0.7.0)
|
121
125
|
delayed_job (~> 4.1.0)
|
126
|
+
fakeredis (~> 0.6.0)
|
122
127
|
pry (>= 0.9.0)
|
123
128
|
pry-byebug (>= 1.3.0)
|
124
129
|
rake (>= 10.3.0)
|
@@ -130,4 +135,4 @@ DEPENDENCIES
|
|
130
135
|
taskinator!
|
131
136
|
|
132
137
|
BUNDLED WITH
|
133
|
-
1.
|
138
|
+
1.13.4
|
data/lib/taskinator.rb
CHANGED
@@ -6,6 +6,8 @@ require 'benchmark'
|
|
6
6
|
|
7
7
|
require 'taskinator/version'
|
8
8
|
|
9
|
+
require 'taskinator/log_stats'
|
10
|
+
|
9
11
|
require 'taskinator/complete_on'
|
10
12
|
require 'taskinator/redis_connection'
|
11
13
|
require 'taskinator/logger'
|
@@ -70,16 +72,6 @@ module Taskinator
|
|
70
72
|
redis_pool.with(&block)
|
71
73
|
end
|
72
74
|
|
73
|
-
def redis_mutex(lockid, options={}, &block)
|
74
|
-
raise ArgumentError, "requires a block" unless block_given?
|
75
|
-
m = Benchmark.measure do
|
76
|
-
redis do |r|
|
77
|
-
Redis::Semaphore.new(lockid, {:redis => r}.merge(options)).lock(&block)
|
78
|
-
end
|
79
|
-
end
|
80
|
-
logger.debug("Time spent in mutex: #{m.real}")
|
81
|
-
end
|
82
|
-
|
83
75
|
def redis_pool
|
84
76
|
@redis ||= Taskinator::RedisConnection.create
|
85
77
|
end
|
@@ -96,6 +88,14 @@ module Taskinator
|
|
96
88
|
Taskinator::Logging.logger = log
|
97
89
|
end
|
98
90
|
|
91
|
+
def statsd_client
|
92
|
+
Taskinator::LogStats.client
|
93
|
+
end
|
94
|
+
|
95
|
+
def statsd_client=(client)
|
96
|
+
Taskinator::LogStats.client = client
|
97
|
+
end
|
98
|
+
|
99
99
|
# the queue adapter to use
|
100
100
|
# supported adapters include
|
101
101
|
# :delayed_job, :redis and :sidekiq
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'statsd'
|
2
|
+
|
3
|
+
module Taskinator
|
4
|
+
module LogStats
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def initialize_client
|
8
|
+
@client = Statsd.new()
|
9
|
+
end
|
10
|
+
|
11
|
+
def client
|
12
|
+
defined?(@client) ? @client : initialize_client
|
13
|
+
end
|
14
|
+
|
15
|
+
def client=(statsd_client)
|
16
|
+
@client = (statsd_client ? statsd_client : initialize_client)
|
17
|
+
end
|
18
|
+
|
19
|
+
def duration(stat, duration)
|
20
|
+
client.timing(stat, duration * 1000)
|
21
|
+
end
|
22
|
+
|
23
|
+
def timing(stat, &block)
|
24
|
+
result = nil
|
25
|
+
duration = Benchmark.realtime do
|
26
|
+
result = yield
|
27
|
+
end
|
28
|
+
duration(stat, duration)
|
29
|
+
result
|
30
|
+
end
|
31
|
+
|
32
|
+
def gauge(stat, count)
|
33
|
+
client.gauge(stat, count)
|
34
|
+
end
|
35
|
+
|
36
|
+
def count(stat, count)
|
37
|
+
client.count(stat, count)
|
38
|
+
end
|
39
|
+
|
40
|
+
def increment(stat)
|
41
|
+
client.increment(stat)
|
42
|
+
end
|
43
|
+
|
44
|
+
def decrement(stat)
|
45
|
+
client.decrement(stat)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -191,6 +191,12 @@ module Taskinator
|
|
191
191
|
|
192
192
|
end
|
193
193
|
|
194
|
+
def deincr_pending_tasks
|
195
|
+
Taskinator.redis do |conn|
|
196
|
+
conn.incrby("#{key}.pending", -1)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
194
200
|
# retrieves the process options of the root process
|
195
201
|
# this is so that meta data of the process can be maintained
|
196
202
|
# and accessible to instrumentation subscribers
|
@@ -203,13 +209,13 @@ module Taskinator
|
|
203
209
|
end
|
204
210
|
end
|
205
211
|
|
206
|
-
EXPIRE_IN =
|
212
|
+
EXPIRE_IN = 30 * 60 # 30 minutes
|
207
213
|
|
208
|
-
def cleanup(
|
214
|
+
def cleanup(expire_in=EXPIRE_IN)
|
209
215
|
Taskinator.redis do |conn|
|
210
216
|
|
211
217
|
# use the "clean up" visitor
|
212
|
-
RedisCleanupVisitor.new(conn, self,
|
218
|
+
RedisCleanupVisitor.new(conn, self, expire_in).visit
|
213
219
|
|
214
220
|
# remove from the list
|
215
221
|
conn.srem(Persistence.processes_list_key(scope), uuid)
|
@@ -276,6 +282,8 @@ module Taskinator
|
|
276
282
|
@base_visitor.incr_task_count
|
277
283
|
end
|
278
284
|
end
|
285
|
+
@conn.set("#{@key}.count", tasks.count)
|
286
|
+
@conn.set("#{@key}.pending", tasks.count)
|
279
287
|
end
|
280
288
|
|
281
289
|
def visit_attribute(attribute)
|
@@ -377,7 +385,7 @@ module Taskinator
|
|
377
385
|
end
|
378
386
|
|
379
387
|
def visit_tasks(tasks)
|
380
|
-
builder.tag!('tasks') do |xml|
|
388
|
+
builder.tag!('tasks', :count => tasks.count) do |xml|
|
381
389
|
tasks.each do |task|
|
382
390
|
xml.tag!('task', :key => task.key) do |xml2|
|
383
391
|
XmlSerializationVisitor.new(xml2, task, @base_visitor).visit
|
@@ -495,7 +503,7 @@ module Taskinator
|
|
495
503
|
# tasks are a linked list, so just get the first one
|
496
504
|
Taskinator.redis do |conn|
|
497
505
|
uuid = conn.lindex("#{@key}:tasks", 0)
|
498
|
-
tasks
|
506
|
+
tasks.attach(lazy_instance_for(Task, uuid), conn.get("#{@key}.count").to_i) if uuid
|
499
507
|
end
|
500
508
|
end
|
501
509
|
|
@@ -576,28 +584,28 @@ module Taskinator
|
|
576
584
|
class RedisCleanupVisitor < Taskinator::Visitor::Base
|
577
585
|
|
578
586
|
attr_reader :instance
|
579
|
-
attr_reader :
|
587
|
+
attr_reader :expire_in # seconds
|
580
588
|
|
581
|
-
def initialize(conn, instance,
|
589
|
+
def initialize(conn, instance, expire_in)
|
582
590
|
@conn = conn
|
583
591
|
@instance = instance
|
584
|
-
@
|
592
|
+
@expire_in = expire_in.to_i
|
585
593
|
@key = instance.key
|
586
594
|
end
|
587
595
|
|
588
596
|
def visit
|
589
597
|
@instance.accept(self)
|
590
|
-
@conn.
|
598
|
+
@conn.expire(@key, expire_in)
|
591
599
|
end
|
592
600
|
|
593
601
|
def visit_process(attribute)
|
594
602
|
process = @instance.send(attribute)
|
595
|
-
RedisCleanupVisitor.new(@conn, process,
|
603
|
+
RedisCleanupVisitor.new(@conn, process, expire_in).visit if process
|
596
604
|
end
|
597
605
|
|
598
606
|
def visit_tasks(tasks)
|
599
607
|
tasks.each do |task|
|
600
|
-
RedisCleanupVisitor.new(@conn, task,
|
608
|
+
RedisCleanupVisitor.new(@conn, task, expire_in).visit
|
601
609
|
end
|
602
610
|
end
|
603
611
|
|
data/lib/taskinator/process.rb
CHANGED
@@ -228,6 +228,8 @@ module Taskinator
|
|
228
228
|
if tasks.empty?
|
229
229
|
complete! # weren't any tasks to start with
|
230
230
|
else
|
231
|
+
Taskinator.statsd_client.count("taskinator.#{definition.name.underscore.parameterize}.pending", tasks.count)
|
232
|
+
Taskinator.logger.info("Enqueuing #{tasks.count} tasks for process '#{uuid}'.")
|
231
233
|
tasks.each(&:enqueue!)
|
232
234
|
end
|
233
235
|
end
|
@@ -256,18 +258,20 @@ module Taskinator
|
|
256
258
|
end
|
257
259
|
|
258
260
|
def task_completed(task)
|
261
|
+
# skip if failed
|
262
|
+
return if failed?
|
263
|
+
|
264
|
+
# deincrement the count of pending concurrent tasks
|
265
|
+
pending = deincr_pending_tasks
|
266
|
+
|
267
|
+
Taskinator.statsd_client.count("taskinator.#{definition.name.underscore.parameterize}.pending", pending)
|
268
|
+
Taskinator.logger.info("Completed task for process '#{uuid}'. Pending is #{pending}.")
|
269
|
+
|
259
270
|
# when complete on first, then don't bother with subsequent tasks completing
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
# simultaneously can't complete the process twice,
|
265
|
-
# which enqueues/starts the same subsequent task
|
266
|
-
Taskinator.redis_mutex(uuid) do
|
267
|
-
# double check, since the status may have
|
268
|
-
# changed while waiting in the mutex
|
269
|
-
complete! if tasks_completed?
|
270
|
-
end
|
271
|
+
if (complete_on == CompleteOn::First)
|
272
|
+
complete! unless completed?
|
273
|
+
else
|
274
|
+
complete! if pending < 1
|
271
275
|
end
|
272
276
|
end
|
273
277
|
|
data/lib/taskinator/tasks.rb
CHANGED
@@ -7,19 +7,31 @@ module Taskinator
|
|
7
7
|
attr_reader :head
|
8
8
|
alias_method :first, :head
|
9
9
|
|
10
|
+
attr_reader :count
|
11
|
+
alias_method :length, :count
|
12
|
+
|
10
13
|
def initialize(first=nil)
|
11
|
-
@
|
14
|
+
@count = 0
|
15
|
+
add(first) if first
|
16
|
+
end
|
17
|
+
|
18
|
+
def attach(task, count)
|
19
|
+
@head = task
|
20
|
+
@count = count
|
21
|
+
task
|
12
22
|
end
|
13
23
|
|
14
24
|
def add(task)
|
15
25
|
if @head.nil?
|
16
26
|
@head = task
|
27
|
+
@count = 1
|
17
28
|
else
|
18
29
|
current = @head
|
19
30
|
while current.next
|
20
31
|
current = current.next
|
21
32
|
end
|
22
33
|
current.next = task
|
34
|
+
@count += 1
|
23
35
|
end
|
24
36
|
task
|
25
37
|
end
|
data/lib/taskinator/version.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -6,15 +6,17 @@ require 'coveralls'
|
|
6
6
|
require 'pry'
|
7
7
|
require 'active_support/notifications'
|
8
8
|
|
9
|
-
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
|
9
|
+
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([
|
10
10
|
SimpleCov::Formatter::HTMLFormatter,
|
11
11
|
Coveralls::SimpleCov::Formatter
|
12
|
-
]
|
12
|
+
])
|
13
13
|
|
14
14
|
SimpleCov.start do
|
15
15
|
add_filter 'spec'
|
16
16
|
end
|
17
17
|
|
18
|
+
require 'fakeredis/rspec'
|
19
|
+
|
18
20
|
require 'delayed_job'
|
19
21
|
|
20
22
|
require 'sidekiq'
|
@@ -341,6 +341,18 @@ describe Taskinator::Persistence, :redis => true do
|
|
341
341
|
|
342
342
|
end
|
343
343
|
|
344
|
+
describe "#deincr_pending_tasks" do
|
345
|
+
it {
|
346
|
+
Taskinator.redis do |conn|
|
347
|
+
conn.set("#{subject.key}.pending", 99)
|
348
|
+
end
|
349
|
+
|
350
|
+
pending = subject.deincr_pending_tasks
|
351
|
+
|
352
|
+
expect(pending).to eq(98)
|
353
|
+
}
|
354
|
+
end
|
355
|
+
|
344
356
|
describe "#process_options" do
|
345
357
|
it {
|
346
358
|
Taskinator.redis do |conn|
|
@@ -368,7 +380,7 @@ describe Taskinator::Persistence, :redis => true do
|
|
368
380
|
Taskinator.redis do |conn|
|
369
381
|
expect(conn.hget(process.key, :uuid)).to eq(process.uuid)
|
370
382
|
|
371
|
-
process.cleanup(
|
383
|
+
process.cleanup(0) # immediately
|
372
384
|
|
373
385
|
expect(conn.hget(process.key, :uuid)).to be_nil
|
374
386
|
|
@@ -389,7 +401,7 @@ describe Taskinator::Persistence, :redis => true do
|
|
389
401
|
Taskinator.redis do |conn|
|
390
402
|
expect(conn.hget(process.key, :uuid)).to eq(process.uuid)
|
391
403
|
|
392
|
-
process.cleanup(
|
404
|
+
process.cleanup(2)
|
393
405
|
|
394
406
|
# still available...
|
395
407
|
expect(conn.hget(process.key, :uuid)).to_not be_nil
|
@@ -397,7 +409,7 @@ describe Taskinator::Persistence, :redis => true do
|
|
397
409
|
expect(conn.hget(task.key, :uuid)).to_not be_nil
|
398
410
|
end
|
399
411
|
|
400
|
-
sleep
|
412
|
+
sleep 3
|
401
413
|
|
402
414
|
# gone!
|
403
415
|
expect(conn.hget(process.key, :uuid)).to be_nil
|
@@ -419,16 +419,17 @@ describe Taskinator::Process do
|
|
419
419
|
|
420
420
|
describe "#task_completed" do
|
421
421
|
it "completes when tasks complete (CompleteOn::First)" do
|
422
|
-
allow_any_instance_of(Taskinator::Task).to receive(:completed?) { true }
|
423
|
-
|
424
422
|
process = Taskinator::Process.define_concurrent_process_for(definition, Taskinator::CompleteOn::First)
|
425
|
-
|
426
423
|
tasks.each {|t| process.tasks << t }
|
427
424
|
|
428
|
-
|
425
|
+
allow(process).to receive(:deincr_pending_tasks) { tasks.count - 1 }
|
426
|
+
|
427
|
+
expect(process).to receive(:complete!).once.and_call_original
|
429
428
|
|
430
429
|
process.task_completed(tasks.first)
|
431
430
|
|
431
|
+
expect(process.completed?).to be(true)
|
432
|
+
|
432
433
|
# remaining tasks should do nothing...
|
433
434
|
tasks.each do |task|
|
434
435
|
process.task_completed(task)
|
@@ -436,16 +437,17 @@ describe Taskinator::Process do
|
|
436
437
|
end
|
437
438
|
|
438
439
|
it "completes when tasks complete (CompleteOn::Last)" do
|
439
|
-
allow_any_instance_of(Taskinator::Task).to receive(:completed?) { true }
|
440
|
-
|
441
440
|
process = Taskinator::Process.define_concurrent_process_for(definition, Taskinator::CompleteOn::Last)
|
442
|
-
|
443
441
|
tasks.each {|t| process.tasks << t }
|
444
442
|
|
445
|
-
|
443
|
+
pending_count = tasks.count
|
444
|
+
allow(process).to receive(:deincr_pending_tasks) { pending_count -= 1 }
|
445
|
+
|
446
|
+
expect(process).to receive(:complete!).once.and_call_original
|
446
447
|
|
447
448
|
tasks.each do |task|
|
448
449
|
process.task_completed(task)
|
450
|
+
expect(process.completed?).to be(false) unless pending_count < 1
|
449
451
|
end
|
450
452
|
end
|
451
453
|
end
|
data/taskinator.gemspec
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: taskinator
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.11
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Stefano
|
@@ -108,6 +108,20 @@ dependencies:
|
|
108
108
|
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '0.3'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: statsd-ruby
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 1.2.0
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 1.2.0
|
111
125
|
description: Simple process orchestration
|
112
126
|
email:
|
113
127
|
- virtualstaticvoid@gmail.com
|
@@ -139,6 +153,7 @@ files:
|
|
139
153
|
- lib/taskinator/definition/builder.rb
|
140
154
|
- lib/taskinator/executor.rb
|
141
155
|
- lib/taskinator/instrumentation.rb
|
156
|
+
- lib/taskinator/log_stats.rb
|
142
157
|
- lib/taskinator/logger.rb
|
143
158
|
- lib/taskinator/persistence.rb
|
144
159
|
- lib/taskinator/process.rb
|