omnifocus 2.0.0 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data.tar.gz.sig +1 -4
- data/History.txt +9 -0
- data/lib/omnifocus.rb +233 -11
- metadata +13 -13
- metadata.gz.sig +0 -0
data.tar.gz.sig
CHANGED
data/History.txt
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
=== 2.1.0 / 2012-05-17
|
2
|
+
|
3
|
+
* 4 minor enhancements:
|
4
|
+
|
5
|
+
* Added "reschedule" command to evenly distribute review & release chaos.
|
6
|
+
* Added Project#review_interval=.
|
7
|
+
* Added Task#project.
|
8
|
+
* of fix_review_dates now fully re-distributes projects to be evenly spread out.
|
9
|
+
|
1
10
|
=== 2.0.0 / 2012-02-02
|
2
11
|
|
3
12
|
* 2 minor enhancements:
|
data/lib/omnifocus.rb
CHANGED
@@ -26,7 +26,7 @@ include Appscript
|
|
26
26
|
# bts_id: a string uniquely identifying a task: SYSTEM(-projectname)?#id
|
27
27
|
|
28
28
|
class OmniFocus
|
29
|
-
VERSION = '2.
|
29
|
+
VERSION = '2.1.0'
|
30
30
|
|
31
31
|
##
|
32
32
|
# bug_db = {
|
@@ -304,6 +304,10 @@ class OmniFocus
|
|
304
304
|
}
|
305
305
|
end
|
306
306
|
|
307
|
+
def add_hours t, n
|
308
|
+
t + (n * 3600).to_i
|
309
|
+
end
|
310
|
+
|
307
311
|
def hour n
|
308
312
|
t = Time.now
|
309
313
|
midnight = Time.gm t.year, t.month, t.day
|
@@ -381,22 +385,211 @@ class OmniFocus
|
|
381
385
|
print_aggregate_report cp.tasks, :long
|
382
386
|
end
|
383
387
|
|
384
|
-
def cmd_fix_review_dates args
|
388
|
+
def cmd_fix_review_dates args # TODO: merge into reschedule
|
389
|
+
skip = ARGV.first == "-n"
|
390
|
+
|
391
|
+
projs = Hash.new { |h,k| h[k] = [] }
|
392
|
+
|
393
|
+
all_projects.each do |proj|
|
394
|
+
name = proj.name
|
395
|
+
ri = proj.review_interval
|
396
|
+
|
397
|
+
projs[ri[:steps]] << proj
|
398
|
+
end
|
399
|
+
|
400
|
+
projs.each do |k, a|
|
401
|
+
# helps stabilize and prevent random shuffling
|
402
|
+
projs[k] = a.sort_by { |p| [p.next_review_date, p.name] }
|
403
|
+
end
|
404
|
+
|
405
|
+
now = hour 0
|
406
|
+
fri = if now.wday == 5 then
|
407
|
+
now
|
408
|
+
else
|
409
|
+
now - 86400 * (now.wday-5)
|
410
|
+
end
|
411
|
+
|
385
412
|
no_autosave_during do
|
386
|
-
projs
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
413
|
+
projs.each do |unit, a|
|
414
|
+
day = fri
|
415
|
+
|
416
|
+
steps = (a.size.to_f / unit).ceil
|
417
|
+
|
418
|
+
a.each_with_index do |proj, i|
|
419
|
+
if proj.next_review_date != day then
|
420
|
+
warn "Fixing #{unit} #{proj.name} to #{day}"
|
421
|
+
proj.thing.next_review_date.set day unless skip
|
422
|
+
end
|
423
|
+
|
424
|
+
day += 86400 * 7 if (i+1) % steps == 0
|
425
|
+
end
|
426
|
+
end
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
def distribute count, weeks
|
431
|
+
count = count.to_f
|
432
|
+
d = 5 * weeks
|
433
|
+
hits = (1..d).step(d/count).map(&:round)
|
434
|
+
(1..d).map { |n| hits.include?(n) ? weeks : nil }
|
435
|
+
end
|
436
|
+
|
437
|
+
def calculate_schedule projs
|
438
|
+
all = [
|
439
|
+
distribute(projs[1].size, 1),
|
440
|
+
distribute(projs[2].size, 2),
|
441
|
+
distribute(projs[3].size, 3),
|
442
|
+
distribute(projs[5].size, 5),
|
443
|
+
distribute(projs[7].size, 7),
|
444
|
+
]
|
445
|
+
|
446
|
+
# [[1, 1, 1, 1, 1],
|
447
|
+
# [2, 2, 2, 2, 2, nil, 2, 2, 2, 2],
|
448
|
+
# [3, nil, 3, 3, nil, 3, 3, nil, 3, 3, nil, 3, 3, nil, 3],
|
449
|
+
# ...
|
450
|
+
|
451
|
+
all.map! { |a|
|
452
|
+
a.concat [nil] * (35-a.size)
|
453
|
+
a.each_slice(5).to_a
|
454
|
+
}
|
455
|
+
|
456
|
+
# [[[1, 1, 1, 1, 1], [nil, nil, nil, nil, nil], ...
|
457
|
+
# [[2, 2, 2, 2, 2], [nil, 2, 2, 2, 2], ...
|
458
|
+
# [[3, nil, 3, 3, nil], [3, 3, nil, 3, 3], ...
|
459
|
+
# ...
|
460
|
+
|
461
|
+
weeks = all.transpose.map { |a, *r|
|
462
|
+
a.zip(*r).map(&:compact)
|
463
|
+
}
|
464
|
+
|
465
|
+
# [[[1, 2, 3, 5, 7], [1, 2], [1, 2, 3], [1, 2, 3], [1, 2, 5]],
|
466
|
+
# [[3], [2, 3, 7], [2], [2, 3, 5], [2, 3]],
|
467
|
+
# ...
|
468
|
+
|
469
|
+
weeks
|
470
|
+
end
|
471
|
+
|
472
|
+
def aggregate_releases
|
473
|
+
rels = context "Releasing"
|
474
|
+
|
475
|
+
tasks = Hash.new { |h,k| h[k] = [] } # name => tasks
|
476
|
+
projs = Hash.new { |h,k| h[k] = [] } # step => projs
|
477
|
+
|
478
|
+
rels.tasks.each do |task|
|
479
|
+
proj = task.project
|
480
|
+
tasks[proj.name] << task
|
481
|
+
projs[proj.review_interval[:steps]] << proj
|
482
|
+
end
|
483
|
+
|
484
|
+
projs.each do |k, a|
|
485
|
+
# helps stabilize and prevent random shuffling
|
486
|
+
projs[k] = a.uniq_by { |p| p.name }.sort_by { |p|
|
487
|
+
tasks[p.name].map(&:name).min
|
488
|
+
}
|
489
|
+
end
|
490
|
+
|
491
|
+
return rels, tasks, projs
|
492
|
+
end
|
493
|
+
|
494
|
+
def fix_project_review_intervals rels, skip
|
495
|
+
rels.tasks.each do |task|
|
496
|
+
proj = task.project
|
497
|
+
|
498
|
+
t_ri = task.repetition[:steps]
|
499
|
+
p_ri = proj.review_interval[:steps]
|
500
|
+
|
501
|
+
if t_ri != p_ri then
|
502
|
+
warn "Fixing #{task.name} to #{p_ri} weeks"
|
503
|
+
|
504
|
+
rep = {
|
505
|
+
:recurrence => "FREQ=WEEKLY;INTERVAL=#{p_ri}",
|
506
|
+
:repetition_method => :fixed_repetition,
|
507
|
+
}
|
508
|
+
|
509
|
+
task.thing.repetition_rule.set :to => rep unless skip
|
510
|
+
end
|
511
|
+
end
|
512
|
+
end
|
513
|
+
|
514
|
+
def fix_release_task_names projs, tasks, skip
|
515
|
+
projs.each do |step, projects|
|
516
|
+
projects.each do |project|
|
517
|
+
tasks[project.name].each do |task|
|
518
|
+
if task.name =~ /^(\d+(\.\d+)?)/ then
|
519
|
+
if $1.to_i != step then
|
520
|
+
new_name = task.name.sub(/^(\d+(\.\d+)?)/, step.to_s)
|
521
|
+
puts "renaming to #{new_name}"
|
522
|
+
task.thing.name.set new_name unless skip
|
523
|
+
end
|
524
|
+
end
|
525
|
+
end
|
526
|
+
end
|
527
|
+
end
|
528
|
+
end
|
529
|
+
|
530
|
+
def fix_release_task_schedule projs, tasks, skip
|
531
|
+
weeks = calculate_schedule projs
|
532
|
+
|
533
|
+
now = hour 0
|
534
|
+
mon = if now.wday == 1 then
|
535
|
+
now
|
536
|
+
else
|
537
|
+
now - 86400 * (now.wday-1)
|
538
|
+
end
|
539
|
+
|
540
|
+
weeks.each_with_index do |week, wi|
|
541
|
+
week.each_with_index do |day, di|
|
542
|
+
next if day.empty?
|
543
|
+
delta = wi*7 + di
|
544
|
+
date = mon + 86400 * delta
|
545
|
+
|
546
|
+
day.each do |rank|
|
547
|
+
p = projs[rank].shift
|
548
|
+
t = tasks[p.name]
|
549
|
+
|
550
|
+
t.each do |task|
|
551
|
+
if task.start_date != date then
|
552
|
+
due_date1 = add_hours date, 16
|
553
|
+
due_date2 = add_hours date, 16.5
|
554
|
+
|
555
|
+
warn "Fixing #{p.name} to #{date.strftime "%Y-%m-%d"}"
|
556
|
+
|
557
|
+
next if skip
|
558
|
+
|
559
|
+
case task.name
|
560
|
+
when /Release/ then
|
561
|
+
task.start_date = date
|
562
|
+
task.due_date = due_date1
|
563
|
+
when /Triage/ then
|
564
|
+
task.start_date = date
|
565
|
+
task.due_date = due_date2
|
566
|
+
else
|
567
|
+
warn "Unknown task name: #{task.name}"
|
568
|
+
end
|
569
|
+
end
|
570
|
+
end
|
395
571
|
end
|
396
572
|
end
|
397
573
|
end
|
398
574
|
end
|
399
575
|
|
576
|
+
def cmd_reschedule args
|
577
|
+
skip = ARGV.first == "-n"
|
578
|
+
|
579
|
+
rels, tasks, projs = aggregate_releases
|
580
|
+
|
581
|
+
no_autosave_during do
|
582
|
+
warn "Checking project review intervals..."
|
583
|
+
fix_project_review_intervals rels, skip
|
584
|
+
|
585
|
+
warn "Checking releasing task numeric prefixes (if any)"
|
586
|
+
fix_release_task_names projs, tasks, skip
|
587
|
+
|
588
|
+
warn "Checking releasing task schedules"
|
589
|
+
fix_release_task_schedule projs, tasks, skip
|
590
|
+
end
|
591
|
+
end
|
592
|
+
|
400
593
|
def cmd_time args
|
401
594
|
m = 0
|
402
595
|
|
@@ -458,6 +651,10 @@ class OmniFocus
|
|
458
651
|
thing.review_interval.get
|
459
652
|
end
|
460
653
|
|
654
|
+
def review_interval= h
|
655
|
+
thing.review_interval.set :to => h
|
656
|
+
end
|
657
|
+
|
461
658
|
def next_review_date
|
462
659
|
thing.next_review_date.get
|
463
660
|
end
|
@@ -468,6 +665,10 @@ class OmniFocus
|
|
468
665
|
end
|
469
666
|
|
470
667
|
class Task < Thingy
|
668
|
+
def project
|
669
|
+
Project.new omnifocus, thing.containing_project.get
|
670
|
+
end
|
671
|
+
|
471
672
|
def start_date= t
|
472
673
|
thing.start_date.set t
|
473
674
|
end
|
@@ -645,3 +846,24 @@ class OmniFocus
|
|
645
846
|
self.omnifocus.will_autosave.set true
|
646
847
|
end
|
647
848
|
end
|
849
|
+
|
850
|
+
class Array
|
851
|
+
def merge! o
|
852
|
+
o.each_with_index do |x, i|
|
853
|
+
self[i] << x
|
854
|
+
end
|
855
|
+
end
|
856
|
+
end
|
857
|
+
|
858
|
+
module Enumerable
|
859
|
+
def uniq_by
|
860
|
+
r, s = [], {}
|
861
|
+
each do |e|
|
862
|
+
v = yield(e)
|
863
|
+
next if s[v]
|
864
|
+
r << e
|
865
|
+
s[v] = true
|
866
|
+
end
|
867
|
+
r
|
868
|
+
end
|
869
|
+
end unless [].respond_to?(:uniq_by)
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: omnifocus
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 11
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 2
|
8
|
+
- 1
|
8
9
|
- 0
|
9
|
-
|
10
|
-
version: 2.0.0
|
10
|
+
version: 2.1.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- aja
|
@@ -37,7 +37,7 @@ cert_chain:
|
|
37
37
|
FBHgymkyj/AOSqKRIpXPhjC6
|
38
38
|
-----END CERTIFICATE-----
|
39
39
|
|
40
|
-
date: 2012-
|
40
|
+
date: 2012-05-18 00:00:00 Z
|
41
41
|
dependencies:
|
42
42
|
- !ruby/object:Gem::Dependency
|
43
43
|
name: rb-appscript
|
@@ -78,11 +78,11 @@ dependencies:
|
|
78
78
|
requirements:
|
79
79
|
- - ~>
|
80
80
|
- !ruby/object:Gem::Version
|
81
|
-
hash:
|
81
|
+
hash: 7
|
82
82
|
segments:
|
83
|
-
-
|
84
|
-
-
|
85
|
-
version: "
|
83
|
+
- 3
|
84
|
+
- 0
|
85
|
+
version: "3.0"
|
86
86
|
type: :development
|
87
87
|
version_requirements: *id003
|
88
88
|
- !ruby/object:Gem::Dependency
|
@@ -108,11 +108,11 @@ dependencies:
|
|
108
108
|
requirements:
|
109
109
|
- - ~>
|
110
110
|
- !ruby/object:Gem::Version
|
111
|
-
hash:
|
111
|
+
hash: 7
|
112
112
|
segments:
|
113
|
-
-
|
114
|
-
-
|
115
|
-
version: "
|
113
|
+
- 3
|
114
|
+
- 0
|
115
|
+
version: "3.0"
|
116
116
|
type: :development
|
117
117
|
version_requirements: *id005
|
118
118
|
description: Synchronizes bug tracking systems to omnifocus.
|
@@ -171,7 +171,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
171
171
|
requirements: []
|
172
172
|
|
173
173
|
rubyforge_project: seattlerb
|
174
|
-
rubygems_version: 1.8.
|
174
|
+
rubygems_version: 1.8.17
|
175
175
|
signing_key:
|
176
176
|
specification_version: 3
|
177
177
|
summary: Synchronizes bug tracking systems to omnifocus.
|
metadata.gz.sig
CHANGED
Binary file
|