zzamboni-things2thl 0.5.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
data/ChangeLog CHANGED
@@ -1,6 +1,67 @@
1
+ 2009-05-21 Diego Zamboni <diego@zzamboni.org>
2
+
3
+ * VERSION, lib/Things2THL.rb: Version bump to 0.7.0
4
+
5
+ 2009-05-21 Diego Zamboni <diego@zzamboni.org>
6
+
7
+ * README, bin/things2thl, lib/Things2THL.rb: Added a new mode of
8
+ operation --projects-areas-as-lists (-B). As the name implies, in
9
+ this mode both projects and areas from Things are stored as lists in
10
+ THL. This means that projects are NOT nested inside areas, as is the
11
+ case in the other two modes. Instead, the lists for areas will
12
+ contain only single tasks that were inside the area in Things. If --projects-top-level and --areas-top-level are not given, all the
13
+ lists will be created in the main folders group. Otherwise, they
14
+ will appear within the corresponding folder.
15
+
16
+ 2009-05-21 Diego Zamboni <diego@zzamboni.org>
17
+
18
+ * bin/things2thl, lib/Things2THL.rb: Added --areas-top-level option
19
+ to specify a folder in which imported Areas should be created.
20
+
21
+ 2009-05-20 Diego Zamboni <diego@zzamboni.org>
22
+
23
+ * README: Updated README
24
+
25
+ 2009-05-20 Diego Zamboni <diego@zzamboni.org>
26
+
27
+ * things2thl.gemspec: Regenerated gemspec for version 0.6.0
28
+
29
+ 2009-05-20 Diego Zamboni <diego@zzamboni.org>
30
+
31
+ * ChangeLog, VERSION, lib/Things2THL.rb: Version bump to 0.6.0
32
+
33
+ 2009-05-20 Diego Zamboni <diego@zzamboni.org>
34
+
35
+ * Rakefile, lib/Things2THL.rb: Vastly improved conversion of the
36
+ notes field. The HTML stored in the Things notes is parsed and
37
+ rendered as text, including all the appropriate URLs and links. It
38
+ does not look exactly as the original, but is quite usable.
39
+
1
40
  2009-05-19 Diego Zamboni <diego@zzamboni.org>
2
41
 
3
- * lib/Things2THL.rb: Version bump to 0.5.0
42
+ * README: Updated to state limitation of transferring repeating
43
+ tasks.
44
+
45
+ 2009-05-19 Diego Zamboni <diego@zzamboni.org>
46
+
47
+ * lib/Things2THL.rb: Make Scheduled and Logbook a list instead of a
48
+ folder when --projects-as-tasks. Someday stays always as a folder,
49
+ because it can contain areas (suspended areas)
50
+
51
+ 2009-05-19 Diego Zamboni <diego@zzamboni.org>
52
+
53
+ * lib/Things2THL.rb: Transfer activation_date as start_date so that
54
+ scheduled (one-time) tasks are set appropriately. Still need to
55
+ figure out how to identify repeating tasks.
56
+
57
+ 2009-05-19 Diego Zamboni <diego@zzamboni.org>
58
+
59
+ * things2thl.gemspec: Regenerated gemspec for version 0.5.0
60
+
61
+ 2009-05-19 Diego Zamboni <diego@zzamboni.org>
62
+
63
+ * ChangeLog, README, VERSION, lib/Things2THL.rb: Version bump to
64
+ 0.5.0
4
65
 
5
66
  2009-05-19 Diego Zamboni <diego@zzamboni.org>
6
67
 
data/README CHANGED
@@ -1,4 +1,4 @@
1
- Things2THL
1
+ Things2THL 0.7.0
2
2
  http://zzamboni.github.com/things2thl/
3
3
 
4
4
  Conversion program to transfer data from Things
@@ -17,9 +17,10 @@ support.
17
17
  Then install things2thl by running:
18
18
  $ sudo gem install zzamboni-things2thl --source http://gems.github.com/
19
19
 
20
- Note: things2thl need the rb-appscript library from
21
- http://appscript.sourceforge.net/rb-appscript/install.html. If you
22
- don't have it already, it will be automatically installed by gem.
20
+ Note: things2thl need the rb-appscript
21
+ (http://appscript.sourceforge.net/rb-appscript/) and hpricot
22
+ (http://wiki.github.com/why/hpricot) . If you don't have them already,
23
+ they will be automatically installed by gem.
23
24
 
24
25
 
25
26
 
@@ -31,6 +32,9 @@ Usage: things2thl <mode of operation> [options]
31
32
  Modes of operation (required):
32
33
  -L, --projects-as-lists Convert projects in Things to lists in THL
33
34
  -T, --projects-as-tasks Convert projects in Things to tasks in THL
35
+ -B, --projects-areas-as-tasks Convert both projects and areas in Things
36
+ to lists in THL. This implies that
37
+ projects are not nested inside areas.
34
38
 
35
39
  Options:
36
40
  --[no-]areas Transfer areas from Things (default: yes)
@@ -53,6 +57,8 @@ Options:
53
57
  'Projects' list is always created, but
54
58
  this option can be used to specify
55
59
  its name.
60
+ --areas-top-level FOLDER The named folder will be created to
61
+ contain all areas.
56
62
  -c, --completed Transfer also completed/canceled tasks
57
63
  and projects (default: no)
58
64
  --[no-]archive-completed If transferring completed/canceled tasks,
@@ -67,15 +73,9 @@ Options you should seldom need:
67
73
  --thl THLAPP Location of the The Hit List application
68
74
  (default: /Applications/The Hit List.app)
69
75
 
70
-
71
76
  Functionality still missing:
72
77
  ---------------------------
73
78
 
74
- - Handle rich-text notes (with attachments, links, etc.) properly
75
-
76
- Plan: not sure yet. Need to investigate how notes are stored in
77
- THL (Things uses an XML format)
78
-
79
79
  - Handling delegation ("People" feature in Things)
80
80
 
81
81
  Not sure how to transfer this to THL. Ideas are welcome.
@@ -89,9 +89,9 @@ Known issues:
89
89
  completed/canceled tasks, they will all appear in your "completed
90
90
  today" view.
91
91
 
92
- - Tasks in the Things "Scheduled" focus are transferred, but the
93
- scheduling itself is not transferred, because this information is
94
- not accessible through AS from either Things not THL.
92
+ - One-time tasks in the Things "Scheduled" focus are transferred, but
93
+ repeating tasks are not. The Things Applescript interface does not
94
+ provide access to repeating tasks.
95
95
 
96
96
  - The format of time estimate tags is fixed for the moment. May add
97
- customization if I get any requests for it.
97
+ customization if I get any requests for it.
data/Rakefile CHANGED
@@ -11,6 +11,7 @@ begin
11
11
  gemspec.description = "Library and command-line tool for migrating Things data to The Hit List"
12
12
  gemspec.authors = ["Diego Zamboni"]
13
13
  gemspec.add_dependency('rb-appscript', '>=0.5.1')
14
+ gemspec.add_dependency('hpricot', '>=0.6')
14
15
  end
15
16
  rescue LoadError
16
17
  puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.0
1
+ 0.7.0
data/bin/things2thl CHANGED
@@ -21,6 +21,10 @@ opts = OptionParser.new do |opts|
21
21
  "Convert projects in Things to lists in THL" ) { options.structure = :projects_as_lists }
22
22
  opts.on("-T", "--projects-as-tasks",
23
23
  "Convert projects in Things to tasks in THL" ) { options.structure = :projects_as_tasks }
24
+ opts.on("-B", "--projects-areas-as-tasks",
25
+ "Convert both projects and areas in Things",
26
+ " to lists in THL. This implies that",
27
+ " projects are not nested inside areas.") { options.structure = :projects_areas_as_lists }
24
28
 
25
29
  opts.separator ''
26
30
  opts.separator("Options:")
@@ -53,6 +57,11 @@ opts = OptionParser.new do |opts|
53
57
  " its name.") do |projfolder|
54
58
  options.projectsfolder = projfolder
55
59
  end
60
+ opts.on("--areas-top-level FOLDER",
61
+ "The named folder will be created to",
62
+ " contain all areas.") do |areafolder|
63
+ options.areasfolder = areafolder
64
+ end
56
65
 
57
66
  opts.on("-c", "--completed",
58
67
  "Transfer also completed/canceled tasks",
data/lib/Things2THL.rb CHANGED
@@ -5,13 +5,14 @@ require "ostruct"
5
5
  require 'time'
6
6
  begin; require 'rubygems'; rescue LoadError; end
7
7
  require 'appscript'; include Appscript
8
+ require 'hpricot'
8
9
 
9
10
  ######################################################################
10
11
 
11
12
  module Things2THL
12
13
  module Version
13
14
  MAJOR = 0
14
- MINOR = 5
15
+ MINOR = 7
15
16
  PATCH = 0
16
17
 
17
18
  STRING = [MAJOR, MINOR, PATCH].join(".")
@@ -64,13 +65,14 @@ module Things2THL
64
65
  :due_date => :due_date,
65
66
  :completion_date => :completed_date,
66
67
  :cancellation_date => :canceled_date,
68
+ :activation_date => :start_date,
67
69
  :status => {
68
70
  :completed => :completed,
69
71
  :canceled => :canceled,
70
72
  },
71
- :notes => :notes,
72
73
  },
73
74
  Proc.new {|node,prop,obj|
75
+ obj.add_notes(node,prop)
74
76
  obj.fix_completed_canceled(node, prop)
75
77
  obj.archive_completed(prop)
76
78
  obj.process_tags(node, prop, true, true)
@@ -90,13 +92,14 @@ module Things2THL
90
92
  :due_date => :due_date,
91
93
  :completion_date => :completed_date,
92
94
  :cancellation_date => :canceled_date,
95
+ :activation_date => :start_date,
93
96
  :status => {
94
97
  :completed => :completed,
95
98
  :canceled => :canceled,
96
99
  },
97
- :notes => :notes,
98
100
  },
99
101
  Proc.new {|node,prop,obj|
102
+ obj.add_notes(node,prop)
100
103
  obj.fix_completed_canceled(node, prop)
101
104
  obj.archive_completed(prop)
102
105
  obj.process_tags(node, prop, false, true)
@@ -109,19 +112,57 @@ module Things2THL
109
112
  :due_date => :due_date,
110
113
  :completion_date => :completed_date,
111
114
  :cancellation_date => :canceled_date,
115
+ :activation_date => :start_date,
112
116
  :status => {
113
117
  :completed => :completed,
114
118
  :canceled => :canceled,
115
119
  },
116
- :notes => :notes,
117
120
  },
118
121
  Proc.new {|node,prop,obj|
122
+ obj.add_notes(node,prop)
119
123
  obj.fix_completed_canceled(node, prop)
120
124
  obj.archive_completed(prop)
121
125
  obj.process_tags(node, prop, false, true)
122
126
  obj.check_today(node, prop)
123
127
  }
124
128
  ]
129
+ },
130
+ :projects_areas_as_lists => {
131
+ :area => [:list,
132
+ {
133
+ :name => :name,
134
+ }],
135
+ :project => [:list,
136
+ {
137
+ :name => :name,
138
+ :creation_date => :created_date,
139
+ },
140
+ Proc.new {|node,prop,obj|
141
+ obj.add_list_notes(node,prop)
142
+ obj.add_project_duedate(node,prop)
143
+ }
144
+ ],
145
+ :selected_to_do => [:task,
146
+ {
147
+ :name => :title,
148
+ :creation_date => :created_date,
149
+ :due_date => :due_date,
150
+ :completion_date => :completed_date,
151
+ :cancellation_date => :canceled_date,
152
+ :activation_date => :start_date,
153
+ :status => {
154
+ :completed => :completed,
155
+ :canceled => :canceled,
156
+ },
157
+ },
158
+ Proc.new {|node,prop,obj|
159
+ obj.add_notes(node,prop)
160
+ obj.fix_completed_canceled(node, prop)
161
+ obj.archive_completed(prop)
162
+ obj.process_tags(node, prop, true, true)
163
+ obj.check_today(node, prop)
164
+ }
165
+ ]
125
166
  }
126
167
  }
127
168
 
@@ -259,7 +300,13 @@ module Things2THL
259
300
  # Get the type of the THL node that corresponds to the given Things node,
260
301
  # depending on the options specified
261
302
  def thl_node_type(node)
262
- Constants::STRUCTURES[options.structure][node.type][0]
303
+ case node
304
+ when Symbol
305
+ type=node
306
+ else
307
+ type=node.type
308
+ end
309
+ Constants::STRUCTURES[options.structure][type][0]
263
310
  end
264
311
 
265
312
  # Get the name/title for a THL node.
@@ -370,10 +417,12 @@ module Things2THL
370
417
  nil
371
418
  when 'Inbox', 'Next'
372
419
  find_or_create(:list, focusname, top_level_node)
373
- when 'Scheduled', 'Someday', 'Logbook'
420
+ when 'Scheduled', 'Logbook'
421
+ find_or_create((thl_node_type(:project) == :task) ? :list : :folder, focusname, top_level_node)
422
+ when 'Someday'
374
423
  find_or_create(:folder, focusname, top_level_node)
375
424
  when 'Projects'
376
- if options.structure == :projects_as_tasks
425
+ if thl_node_type(:project) == :task
377
426
  find_or_create(:list, options.projectsfolder || 'Projects', top_level_node)
378
427
  else
379
428
  if options.projectsfolder
@@ -393,10 +442,12 @@ module Things2THL
393
442
  thl.inbox.get
394
443
  when 'Next'
395
444
  top_level_node
396
- when 'Scheduled', 'Someday', 'Logbook'
445
+ when 'Scheduled', 'Logbook'
446
+ find_or_create((thl_node_type(:project) == :task) ? :list : :folder, focusname, top_level_node)
447
+ when 'Someday'
397
448
  find_or_create(:folder, focusname, top_level_node)
398
449
  when 'Projects'
399
- if options.structure == :projects_as_tasks
450
+ if thl_node_type(:project) == :task
400
451
  find_or_create(:list, options.projectsfolder || 'Projects', top_level_node)
401
452
  else
402
453
  if options.projectsfolder
@@ -426,7 +477,11 @@ module Things2THL
426
477
  if node.suspended
427
478
  return top_level_for_focus('Someday')
428
479
  else
429
- return top_level_node
480
+ if options.areasfolder
481
+ return find_or_create(:folder, options.areasfolder || 'Areas', top_level_node)
482
+ else
483
+ return top_level_node
484
+ end
430
485
  end
431
486
  end
432
487
 
@@ -467,7 +522,6 @@ module Things2THL
467
522
  # we need to find or create an auxiliary list to contain it.
468
523
  def container_for(node)
469
524
  # If its top-level container is nil, it means we need to skip this node
470
- # unless it's an area, areas don't have a focus
471
525
  tlcontainer=top_level_for_node(node)
472
526
  return nil unless tlcontainer
473
527
 
@@ -476,7 +530,7 @@ module Things2THL
476
530
  when :area
477
531
  tlcontainer
478
532
  when :project
479
- if options.areas && node.area?
533
+ if options.areas && (options.structure != :projects_areas_as_lists) && node.area?
480
534
  get_cached_or_process(node.area)
481
535
  else
482
536
  tlcontainer
@@ -702,6 +756,30 @@ module Things2THL
702
756
  prop[:__newnodes__].push(newnode)
703
757
  end
704
758
 
759
+ def hextostring(hexstr)
760
+ [hexstr.delete(" ")].pack("H*")
761
+ end
762
+
763
+ def aliastostring(node)
764
+ hextostring((node/'alias').inner_text)
765
+ end
766
+
767
+ # Process Things notes before adding them to THL.
768
+ def convert_notes(notes)
769
+ return unless notes
770
+ # First, parse the notes as XML and extract the contents of the <note> tag
771
+ note_html = (Hpricot.XML(notes)/'/note').inner_html
772
+ # Then parse the HTML
773
+ html = Hpricot(note_html)
774
+ # Then swap any <alias> tags with their unencoded content
775
+ # TODO: this is a hack - it would be much better to understand the binary structure of the <alias> tag.
776
+ while html.at('alias')
777
+ html.at('alias').swap( aliastostring(html).gsub(/^.*\000\022\000.(.*?)\000.*$/m, 'file:///\1') )
778
+ end
779
+ # Finally return the HTML in "plain text" format, which shows the links in brackets
780
+ html.to_plain_text
781
+ end
782
+
705
783
  # Add a new task containing project notes when the project is a THL list,
706
784
  # since THL lists cannot have notes
707
785
  def add_list_notes(node, prop)
@@ -713,7 +791,7 @@ module Things2THL
713
791
  if node.notes? && new_node_type == :list
714
792
  newnode = {
715
793
  :new => :task,
716
- :with_properties => { :title => "Notes for '#{prop[:name]}'", :notes => node.notes }}
794
+ :with_properties => { :title => "Notes for '#{prop[:name]}'", :notes => convert_notes(node.notes) }}
717
795
  # Mark as completed if the project is completed
718
796
  if node.status == :completed || node.status == :canceled
719
797
  newnode[:with_properties][:completed] = true
@@ -727,6 +805,11 @@ module Things2THL
727
805
  end
728
806
  end
729
807
 
808
+ # Transfer processed notes
809
+ def add_notes(node, prop)
810
+ prop[:notes] = convert_notes(node.notes) if node.notes?
811
+ end
812
+
730
813
  # When projects are lists, if the project has a due date, we add a bogus task to it
731
814
  # to represent its due date, since lists in THL cannot have due dates.
732
815
  def add_project_duedate(node, prop)
@@ -755,6 +838,7 @@ module Things2THL
755
838
  options.quiet = false
756
839
  options.archivecompleted = true
757
840
  options.projectsfolder = nil
841
+ options.areasfolder = nil
758
842
  options.contexttagsregex = '^@'
759
843
  options.timetagsregex = '^(\d+)(min|sec|hr)$'
760
844
  options.timetags = false
@@ -765,3 +849,13 @@ module Things2THL
765
849
  Converter.new(opt_struct, things_db, thl_location)
766
850
  end
767
851
  end
852
+
853
+ ######################################################################
854
+ # For debugging and testing
855
+
856
+ if $0 == __FILE__
857
+ c=Things2THL.new
858
+ th=c.things
859
+ thl=c.thl
860
+ true
861
+ end
data/things2thl.gemspec CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{things2thl}
5
- s.version = "0.5.0"
5
+ s.version = "0.7.0"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Diego Zamboni"]
9
- s.date = %q{2009-05-19}
9
+ s.date = %q{2009-05-21}
10
10
  s.default_executable = %q{things2thl}
11
11
  s.description = %q{Library and command-line tool for migrating Things data to The Hit List}
12
12
  s.email = %q{diego@zzamboni.org}
@@ -38,10 +38,13 @@ Gem::Specification.new do |s|
38
38
 
39
39
  if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
40
40
  s.add_runtime_dependency(%q<rb-appscript>, [">= 0.5.1"])
41
+ s.add_runtime_dependency(%q<hpricot>, [">= 0.6"])
41
42
  else
42
43
  s.add_dependency(%q<rb-appscript>, [">= 0.5.1"])
44
+ s.add_dependency(%q<hpricot>, [">= 0.6"])
43
45
  end
44
46
  else
45
47
  s.add_dependency(%q<rb-appscript>, [">= 0.5.1"])
48
+ s.add_dependency(%q<hpricot>, [">= 0.6"])
46
49
  end
47
50
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zzamboni-things2thl
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Diego Zamboni
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-05-19 00:00:00 -07:00
12
+ date: 2009-05-21 00:00:00 -07:00
13
13
  default_executable: things2thl
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -22,6 +22,16 @@ dependencies:
22
22
  - !ruby/object:Gem::Version
23
23
  version: 0.5.1
24
24
  version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: hpricot
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0.6"
34
+ version:
25
35
  description: Library and command-line tool for migrating Things data to The Hit List
26
36
  email: diego@zzamboni.org
27
37
  executables: