omnifocus 1.5.2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
data.tar.gz.sig CHANGED
Binary file
@@ -1,3 +1,14 @@
1
+ === 2.0.0 / 2012-02-02
2
+
3
+ * 2 minor enhancements:
4
+
5
+ * Added bin/of
6
+ * Added deprecation notices to bin/omnifocus*
7
+
8
+ * 1 bug fix:
9
+
10
+ * _plugins should skip classes explicitly.
11
+
1
12
  === 1.5.2 / 2011-08-25
2
13
 
3
14
  * 1 bug fix:
@@ -3,6 +3,7 @@ History.txt
3
3
  Manifest.txt
4
4
  README.txt
5
5
  Rakefile
6
+ bin/of
6
7
  bin/omnifocus
7
8
  bin/omnifocus_new
8
9
  lib/omnifocus.rb
data/README.txt CHANGED
@@ -16,7 +16,7 @@ Synchronizes bug tracking systems to omnifocus.
16
16
 
17
17
  == SYNOPSIS:
18
18
 
19
- % omnifocus
19
+ % of sync
20
20
  scanning ticket RF#3802
21
21
  removing parsetree # 314159
22
22
  creating parsetree # 3802
data/bin/of ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/ruby -w
2
+
3
+ require 'rubygems'
4
+ require 'omnifocus'
5
+ require 'abbrev'
6
+
7
+ methods = OmniFocus.public_instance_methods(false).grep(/^cmd_/)
8
+ methods.map! { |s| s[4..-1] }
9
+
10
+ tbl = Abbrev::abbrev methods
11
+
12
+ cmd = ARGV.shift or abort "need a subcommand: sync, schedule, etc"
13
+ msg = tbl[cmd]
14
+
15
+ abort "unknown command: #{cmd}" unless msg
16
+
17
+ OmniFocus.send msg, ARGV
@@ -3,4 +3,5 @@
3
3
  require 'rubygems'
4
4
  require 'omnifocus'
5
5
 
6
- OmniFocus.run
6
+ warn "omnifocus is going away. Use `of sync` instead."
7
+ OmniFocus.sync ARGV
@@ -1,29 +1,12 @@
1
1
  #!/usr/bin/ruby -w
2
2
 
3
- require 'rubygems'
4
- require 'mechanize'
5
- require 'appscript'
3
+ ENV['GEM_HOME'] = ENV['GEM_PATH'] = "/Users/ryan/.gem/sandboxes/omnifocus"
6
4
 
7
- project_name = ARGV.shift
8
- title = ($stdin.tty? ? ARGV.join(" ") : $stdin.read).strip
5
+ require 'rubygems'
6
+ require 'omnifocus'
7
+ require 'time'
9
8
 
10
9
  include Appscript
11
10
 
12
- omnifocus = app('OmniFocus').default_document
13
-
14
- unless project_name && ! title.empty? then
15
- cmd = File.basename $0
16
- projects = omnifocus.sections.projects.name.get.flatten.sort
17
-
18
- abort "usage: #{cmd} project title\n\nprojects = nil, #{projects.join ", "}"
19
- end
20
-
21
- if project_name == "nil" then
22
- omnifocus.make :new => :inbox_task, :with_properties => {:name => title}
23
- else
24
- projects = omnifocus.sections.projects[its.name.eq(project_name)]
25
- project = projects.get.flatten.grep(Appscript::Reference).first
26
- project.make :new => :task, :with_properties => {:name => title}
27
-
28
- puts "created task in #{project_name}: #{title}"
29
- end
11
+ warn "omnifocus_new is going away. Use `of new` instead"
12
+ OmniFocus.neww ARGV
@@ -14,6 +14,8 @@ class Appscript::Reference # :nodoc:
14
14
  end
15
15
  end if RUBY_VERSION >= "1.9"
16
16
 
17
+ include Appscript
18
+
17
19
  ##
18
20
  # Synchronizes bug tracking systems to omnifocus.
19
21
  #
@@ -24,7 +26,7 @@ end if RUBY_VERSION >= "1.9"
24
26
  # bts_id: a string uniquely identifying a task: SYSTEM(-projectname)?#id
25
27
 
26
28
  class OmniFocus
27
- VERSION = '1.5.2'
29
+ VERSION = '2.0.0'
28
30
 
29
31
  ##
30
32
  # bug_db = {
@@ -47,24 +49,20 @@ class OmniFocus
47
49
  ##
48
50
  # Load any file matching "omnifocus/*.rb"
49
51
 
50
- def self.load_plugins
51
- filter = ARGV.shift
52
- loaded = {}
53
- Gem.find_files("omnifocus/*.rb").each do |path|
54
- name = File.basename path
55
- next if loaded[name]
56
- next unless path.index filter if filter
57
- require path
58
- loaded[name] = true
59
- end
60
- end
61
-
62
- ##
63
- # Load plugins and then execute the script
64
-
65
- def self.run
66
- load_plugins
67
- self.new.run
52
+ def self._load_plugins
53
+ @__loaded__ ||=
54
+ begin
55
+ filter = ARGV.shift
56
+ loaded = {}
57
+ Gem.find_files("omnifocus/*.rb").each do |path|
58
+ name = File.basename path
59
+ next if loaded[name]
60
+ next unless path.index filter if filter
61
+ require path
62
+ loaded[name] = true
63
+ end
64
+ true
65
+ end
68
66
  end
69
67
 
70
68
  def initialize
@@ -77,10 +75,7 @@ class OmniFocus
77
75
  end
78
76
 
79
77
  def omnifocus
80
- unless defined? @omnifocus then
81
- @omnifocus = Appscript.app('OmniFocus').default_document
82
- end
83
- @omnifocus
78
+ @omnifocus ||= Appscript.app('OmniFocus').default_document
84
79
  end
85
80
 
86
81
  def all_tasks
@@ -118,7 +113,7 @@ class OmniFocus
118
113
  # bug_db hash if they match a bts_id.
119
114
 
120
115
  def prepopulate_existing_tasks
121
- prefixen = self.class.plugins.map { |klass| klass::PREFIX rescue nil }
116
+ prefixen = self.class._plugins.map { |klass| klass::PREFIX rescue nil }
122
117
  of_tasks = nil
123
118
 
124
119
  prefix_re = /^(#{Regexp.union prefixen}(?:-[\w.-]+)?\#\d+)/
@@ -204,19 +199,24 @@ class OmniFocus
204
199
  ##
205
200
  # Return all the plugin modules that have been loaded.
206
201
 
207
- def self.plugins
208
- constants.reject { |mod| mod =~ /^[A-Z_]+$/ }.map { |mod| const_get mod }
202
+ def self._plugins
203
+ _load_plugins
204
+
205
+ constants.
206
+ reject { |mod| mod =~ /^[A-Z_]+$/ }.
207
+ map { |mod| const_get mod }.
208
+ reject { |mod| Class === mod }
209
209
  end
210
210
 
211
- def run
211
+ def cmd_sync args
212
212
  # do this all up front so we can REALLY fuck shit up with plugins
213
- self.class.plugins.each do |plugin|
213
+ self.class._plugins.each do |plugin|
214
214
  extend plugin
215
215
  end
216
216
 
217
217
  prepopulate_existing_tasks
218
218
 
219
- self.class.plugins.each do |plugin|
219
+ self.class._plugins.each do |plugin|
220
220
  name = plugin.name.split(/::/).last.downcase
221
221
  warn "scanning #{name}"
222
222
  send "populate_#{name}_tasks"
@@ -232,3 +232,416 @@ class OmniFocus
232
232
  update_tasks
233
233
  end
234
234
  end
235
+
236
+ class Object
237
+ def nilify
238
+ self == :missing_value ? nil : self
239
+ end
240
+ end
241
+
242
+ class OmniFocus
243
+ def self.method_missing(msg, *args)
244
+ of = OmniFocus.new
245
+ of.send("cmd_#{msg}", *args)
246
+ end
247
+
248
+ def top hash, n=10
249
+ hash.sort_by { |k,v| [-v, k] }.first(n).map { |k,v|
250
+ "%4d %s" % [v,k[0,21]]
251
+ }
252
+ end
253
+
254
+ def cmd_neww args
255
+ project_name = args.shift
256
+ title = ($stdin.tty? ? args.join(" ") : $stdin.read).strip
257
+
258
+ unless project_name && ! title.empty? then
259
+ cmd = File.basename $0
260
+ projects = omnifocus.flattened_projects.name.get.sort
261
+
262
+ warn "usage: #{cmd} project_name title - create a project task"
263
+ warn " #{cmd} nil title - create an inbox task"
264
+ warn " #{cmd} project project_name - create a new project"
265
+ warn ""
266
+ warn "project_names = #{projects.join ", "}"
267
+ exit 1
268
+ end
269
+
270
+ case project_name.downcase
271
+ when "nil" then
272
+ omnifocus.make :new => :inbox_task, :with_properties => {:name => title}
273
+ when "project" then
274
+ rep = weekly
275
+ start_date = hour 0
276
+ due_date1 = hour 16
277
+ due_date2 = hour 16.5
278
+
279
+ cont = context("Releasing").thing
280
+ proj = make nerd_projects, :project, title, :review_interval => rep
281
+
282
+ props = {
283
+ :repetition => rep,
284
+ :context => cont,
285
+ :start_date => start_date
286
+ }
287
+
288
+ make proj, :task, "Release #{title}", props.merge(:due_date => due_date1)
289
+ make proj, :task, "Triage #{title}", props.merge(:due_date => due_date2)
290
+ else
291
+ projects = omnifocus.sections.projects[its.name.eq(project_name)]
292
+ project = projects.get.flatten.grep(Appscript::Reference).first
293
+ project.make :new => :task, :with_properties => {:name => title}
294
+
295
+ puts "created task in #{project_name}: #{title}"
296
+ end
297
+ end
298
+
299
+ def weekly n=1
300
+ {
301
+ :unit => :week,
302
+ :steps => n,
303
+ :fixed_ => true,
304
+ }
305
+ end
306
+
307
+ def hour n
308
+ t = Time.now
309
+ midnight = Time.gm t.year, t.month, t.day
310
+ midnight -= t.utc_offset
311
+ midnight + (n * 3600).to_i
312
+ end
313
+
314
+ def cmd_projects args
315
+ h = Hash.new 0
316
+ n = 0
317
+
318
+ self.active_projects.each do |project|
319
+ name = project.name
320
+ count = project.unscheduled_tasks.size
321
+ ri = project.review_interval
322
+ time = "#{ri[:steps]}#{ri[:unit].to_s[0,1]}"
323
+
324
+ next unless count > 0
325
+
326
+ n += count
327
+ h["#{name} (#{time})"] = count
328
+ end
329
+
330
+ puts "%5d: %3d%%: %s" % [n, 100, "Total"]
331
+ puts
332
+ h.sort_by { |name, count| -count }.each do |name, count|
333
+ puts "%5d: %3d%%: %s" % [count, 100 * count / n, name]
334
+ end
335
+ end
336
+
337
+ def cmd_wtf args
338
+ filter = its.completed.eq(false).and(its.repetition.eq(:missing_value))
339
+
340
+ h1 = Hash.new 0
341
+ omnifocus.flattened_contexts.get.each do |context|
342
+ context.tasks[filter].get.each do |task|
343
+ h1[[task.containing_project.name.get, context.name.get].join(": ")] += 1
344
+ end
345
+ end
346
+
347
+ h2 = Hash.new 0
348
+ omnifocus.flattened_contexts.get.each do |context|
349
+ h2[context.name.get] += context.tasks[filter].count
350
+ end
351
+
352
+ h3 = Hash.new 0
353
+ omnifocus.flattened_projects.get.each do |project|
354
+ h3[project.name.get] += project.tasks[filter].count
355
+ end
356
+
357
+ top(h1).zip(top(h2), top(h3)).each do |a|
358
+ puts "%-26s%-26s%-26s" % a
359
+ end
360
+ end
361
+
362
+ def cmd_help args
363
+ methods = OmniFocus.public_instance_methods(false).grep(/^cmd_/)
364
+ methods.map! { |s| s[4..-1] }
365
+
366
+ puts "Available subcommands:"
367
+
368
+ methods.sort.each do |m|
369
+ puts " #{m}"
370
+ end
371
+ end
372
+
373
+ def cmd_schedule args
374
+ name = args.shift or abort "need a context or project name"
375
+
376
+ of = OmniFocus.new
377
+ cp = context(name) || project(name)
378
+
379
+ abort "Context/Project not found: #{name}" unless cp
380
+
381
+ print_aggregate_report cp.tasks, :long
382
+ end
383
+
384
+ def cmd_fix_review_dates args
385
+ no_autosave_during do
386
+ projs = omnifocus.flattened_projects
387
+
388
+ projs.get.each do |proj|
389
+ nrd = proj.next_review_date.get
390
+ wday = nrd.wday
391
+ wday = 7 if wday == 0
392
+ if nrd.wday != 5 then
393
+ new_day = nrd - (86400*(wday-5))
394
+ proj.next_review_date.set new_day
395
+ end
396
+ end
397
+ end
398
+ end
399
+
400
+ def cmd_time args
401
+ m = 0
402
+
403
+ all_tasks.map { |task|
404
+ task.estimated_minutes.get
405
+ }.grep(Numeric).each { |t|
406
+ m += t
407
+ }
408
+
409
+ puts "all tasks = #{m} minutes"
410
+ puts " = %.2f hours" % (m / 60.0)
411
+ end
412
+
413
+ def cmd_review args
414
+ print_aggregate_report all_projects
415
+ end
416
+
417
+ class Thingy
418
+ attr_accessor :omnifocus, :thing
419
+ def initialize of, thing
420
+ @omnifocus = of
421
+ @thing = thing
422
+ end
423
+
424
+ def active
425
+ its.completed.eq(false)
426
+ end
427
+
428
+ def method_missing m, *a
429
+ warn [m,*a].inspect
430
+ thing.send m, *a
431
+ end
432
+
433
+ def name
434
+ thing.name.get
435
+ end
436
+
437
+ def id
438
+ thing.id_.get
439
+ end
440
+
441
+ def inspect
442
+ "#{self.class}[#{self.id}]"
443
+ end
444
+ end
445
+
446
+ class Project < Thingy
447
+ def unscheduled
448
+ its.due_date.eq(:missing_value)
449
+ end
450
+
451
+ def unscheduled_tasks
452
+ thing.tasks[active.and(unscheduled)].get.map { |t|
453
+ Task.new omnifocus, t
454
+ }
455
+ end
456
+
457
+ def review_interval
458
+ thing.review_interval.get
459
+ end
460
+
461
+ def next_review_date
462
+ thing.next_review_date.get
463
+ end
464
+
465
+ def tasks
466
+ thing.tasks[active].get.map { |t| Task.new omnifocus, t }
467
+ end
468
+ end
469
+
470
+ class Task < Thingy
471
+ def start_date= t
472
+ thing.start_date.set t
473
+ end
474
+
475
+ def start_date
476
+ thing.start_date.get.nilify
477
+ end
478
+
479
+ def due_date= t
480
+ thing.due_date.set t
481
+ end
482
+
483
+ def due_date
484
+ thing.due_date.get.nilify
485
+ end
486
+
487
+ def repetition
488
+ thing.repetition.get.nilify
489
+ end
490
+
491
+ def completed
492
+ thing.completed.get.nilify
493
+ end
494
+ end
495
+
496
+ class Context < Thingy
497
+ def tasks
498
+ thing.tasks[active].get.map { |t| Task.new omnifocus, t }
499
+ end
500
+ end
501
+
502
+ def print_aggregate_report collection, long = false
503
+ h, p = self.aggregate collection
504
+
505
+ self.print_occurrence_table h, p
506
+
507
+ puts
508
+
509
+ self.print_details h, long
510
+ end
511
+
512
+ def aggregate collection
513
+ h = Hash.new { |h,k| h[k] = Hash.new { |h2,k2| h2[k2] = [] } }
514
+ p = Hash.new 0
515
+
516
+ collection.each do |thing|
517
+ name = thing.name
518
+ ri = case thing
519
+ when Project then
520
+ thing.review_interval
521
+ when Task then
522
+ thing.repetition
523
+ else
524
+ raise "unknown type: #{thing.class}"
525
+ end
526
+ date = case thing
527
+ when Project then
528
+ thing.next_review_date
529
+ when Task then
530
+ thing.due_date
531
+ else
532
+ raise "unknown type: #{thing.class}"
533
+ end
534
+
535
+ date = if date then
536
+ date.strftime("%Y-%m-%d %a")
537
+ else
538
+ "unscheduled"
539
+ end
540
+
541
+ time = ri ? "#{ri[:steps]}#{ri[:unit].to_s[0,1]}" : "NR"
542
+
543
+ p[time] += 1
544
+ h[date][time] << name
545
+ end
546
+
547
+ return h, p
548
+ end
549
+
550
+ def print_occurrence_table h, p
551
+ p = p.sort_by { |priority, _|
552
+ case priority
553
+ when /(\d+)(.)/ then
554
+ n, u = $1.to_i, $2
555
+ n *= {"d" => 1, "w" => 7, "m" => 28, "y" => 365}[u]
556
+ when "NR" then
557
+ 1/0.0
558
+ else
559
+ warn "unparsed: #{priority.inspect}"
560
+ 0
561
+ end
562
+ }
563
+
564
+ units = p.map(&:first)
565
+
566
+ total = 0
567
+ hdr = "%14s%s %3s " + "%2s " * units.size
568
+ fmt = "%14s: %3d " + "%2s " * units.size
569
+ puts hdr % ["date", "\\", "tot", *units]
570
+ h.sort.each do |date, plan|
571
+ counts = units.map { |n| plan[n].size }
572
+ subtot = counts.inject(&:+)
573
+ total += subtot
574
+ puts fmt % [date, subtot, *counts]
575
+ end
576
+ puts hdr % ["total", ":", total, *p.map(&:last)]
577
+ end
578
+
579
+ def print_details h, long = false
580
+ h.sort.each do |date, plan|
581
+ puts date
582
+ plan.sort.each do |period, things|
583
+ next if things.empty?
584
+ if long then
585
+ things.sort.each do |thing|
586
+ puts " #{period}: #{thing}"
587
+ end
588
+ else
589
+ puts " #{period}: #{things.sort.join ', '}"
590
+ end
591
+ end
592
+ end
593
+ end
594
+
595
+ def active_project
596
+ its.status.eq(:active)
597
+ end
598
+
599
+ def all_projects
600
+ self.omnifocus.flattened_projects.get.map { |p|
601
+ Project.new omnifocus, p
602
+ }
603
+ end
604
+
605
+ def all_contexts
606
+ self.omnifocus.flattened_contexts.get.map { |c|
607
+ Context.new omnifocus, c
608
+ }
609
+ end
610
+
611
+ def context name
612
+ context = self.omnifocus.flattened_contexts[name].get rescue nil
613
+ Context.new omnifocus, context if context
614
+ end
615
+
616
+ def project name
617
+ project = self.omnifocus.flattened_projects[name].get
618
+ Project.new omnifocus, project if project
619
+ end
620
+
621
+ def active_projects
622
+ self.omnifocus.flattened_projects[active_project].get.map { |p|
623
+ Project.new omnifocus, p
624
+ }
625
+ end
626
+
627
+ def regular_tasks
628
+ (its.value.class_.eq(:item).not).and(its.value.class_.eq(:folder).not)
629
+ end
630
+
631
+ def window
632
+ self.omnifocus.document_windows[1]
633
+ end
634
+
635
+ def selected_tasks
636
+ window.content.selected_trees[regular_tasks].value.get.map { |t|
637
+ Task.new self, t
638
+ }
639
+ end
640
+
641
+ def no_autosave_during
642
+ self.omnifocus.will_autosave.set false
643
+ yield
644
+ ensure
645
+ self.omnifocus.will_autosave.set true
646
+ end
647
+ end
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: 7
4
+ hash: 15
5
5
  prerelease:
6
6
  segments:
7
- - 1
8
- - 5
9
7
  - 2
10
- version: 1.5.2
8
+ - 0
9
+ - 0
10
+ version: 2.0.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: 2011-08-25 00:00:00 Z
40
+ date: 2012-02-03 00:00:00 Z
41
41
  dependencies:
42
42
  - !ruby/object:Gem::Dependency
43
43
  name: rb-appscript
@@ -78,33 +78,49 @@ dependencies:
78
78
  requirements:
79
79
  - - ~>
80
80
  - !ruby/object:Gem::Version
81
- hash: 9
81
+ hash: 21
82
82
  segments:
83
83
  - 2
84
- - 5
85
- version: "2.5"
84
+ - 11
85
+ version: "2.11"
86
86
  type: :development
87
87
  version_requirements: *id003
88
88
  - !ruby/object:Gem::Dependency
89
- name: hoe
89
+ name: rdoc
90
90
  prerelease: false
91
91
  requirement: &id004 !ruby/object:Gem::Requirement
92
92
  none: false
93
93
  requirements:
94
94
  - - ~>
95
95
  - !ruby/object:Gem::Version
96
- hash: 27
96
+ hash: 19
97
97
  segments:
98
- - 2
99
- - 12
100
- version: "2.12"
98
+ - 3
99
+ - 10
100
+ version: "3.10"
101
101
  type: :development
102
102
  version_requirements: *id004
103
+ - !ruby/object:Gem::Dependency
104
+ name: hoe
105
+ prerelease: false
106
+ requirement: &id005 !ruby/object:Gem::Requirement
107
+ none: false
108
+ requirements:
109
+ - - ~>
110
+ - !ruby/object:Gem::Version
111
+ hash: 25
112
+ segments:
113
+ - 2
114
+ - 13
115
+ version: "2.13"
116
+ type: :development
117
+ version_requirements: *id005
103
118
  description: Synchronizes bug tracking systems to omnifocus.
104
119
  email:
105
120
  - kushali@rubyforge.org
106
121
  - ryand-ruby@zenspider.com
107
122
  executables:
123
+ - of
108
124
  - omnifocus
109
125
  - omnifocus_new
110
126
  extensions: []
@@ -119,6 +135,7 @@ files:
119
135
  - Manifest.txt
120
136
  - README.txt
121
137
  - Rakefile
138
+ - bin/of
122
139
  - bin/omnifocus
123
140
  - bin/omnifocus_new
124
141
  - lib/omnifocus.rb
@@ -154,7 +171,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
154
171
  requirements: []
155
172
 
156
173
  rubyforge_project: seattlerb
157
- rubygems_version: 1.8.9
174
+ rubygems_version: 1.8.12
158
175
  signing_key:
159
176
  specification_version: 3
160
177
  summary: Synchronizes bug tracking systems to omnifocus.
metadata.gz.sig CHANGED
Binary file