omnifocus 1.5.2 → 2.0.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.
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