gsquire 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/bin/gsquire CHANGED
@@ -60,22 +60,10 @@ class App < Thor
60
60
 
61
61
  output = options.output.gsub /{account}/, account
62
62
  output << ".#{options.format}"
63
-
63
+
64
64
  case fmt = options.format.to_sym
65
65
  when :json
66
66
  File.open(output, 'w') {|f| f.write JSON.pretty_generate result }
67
- =begin Yeah, I'm committing commented code, I feel bad.
68
- when :dot, :png
69
- graph = GraphViz.new 'Tasks', :type => :digraph
70
- result.each do |tasklist|
71
- list_node = graph.add_nodes tasklist['id'], :label => "#{tasklist['name']} (#{tasklist['id']})"
72
- tasklist['tasks'].each do |task|
73
- task_node = graph.add_nodes task['id'], :label => "#{task['name']} (#{task['id']})"
74
- graph.add_edges task_node, list_node
75
- end
76
- end
77
- graph.output(fmt => output)
78
- =end
79
67
  end
80
68
 
81
69
  task_count = result.inject(0) {|sum, h| sum += h[:tasks].size }
@@ -88,32 +76,18 @@ class App < Thor
88
76
  method_option :format, :aliases => '-f', :type => :string, :default => 'json', :desc => "Input file format (valid: #{INPUT_FORMATS.join(', ')})"
89
77
  method_option :pretend, :aliases => '-p', :type => :boolean, :default => false, :desc => "Run but do not make any changes"
90
78
  method_option :debug, :aliases => '-d', :type => :boolean, :default => false, :desc => "Turn on debugging"
91
- #method_option :graph, :aliases => '-g', :type => :boolean, :default => false, :desc => "Generates a graph image of both the import and result sets"
92
79
  def import(account, input)
93
80
  if not INPUT_FORMATS.include? options.format
94
81
  say_status "export", "invalid format #{options.format}", :red
95
82
  exit 1
96
83
  end
97
84
 
98
- =begin I'm doing it again, fuck me.
99
- if options.graph?
100
- begin
101
- require 'graphviz'
102
- rescue LoadError
103
- raise Thor::Error, "'ruby-graphviz' gem is required to generate a graph visualization"
104
- end
105
-
106
- graph_src = GraphViz.new 'Import', :type => :digraph
107
- graph_dest = GraphViz.new 'Result', :type => :digraph
108
- end
109
- =end
110
-
111
85
  tasklists = case options.format.to_sym
112
86
  when :json
113
87
  JSON.parse(File.read input)
114
88
  end
115
89
 
116
- parents, orphans = prepare_tasklists!(tasklists)
90
+ tasklists, parents = prepare_import(tasklists)
117
91
 
118
92
  if options.pretend?
119
93
  client = nil
@@ -127,6 +101,8 @@ class App < Thor
127
101
  end
128
102
  end
129
103
 
104
+ id_mapping = {}
105
+
130
106
  total_tasks = 0
131
107
  tasklists.each do |tasklist|
132
108
  task_count = 0
@@ -138,34 +114,10 @@ class App < Thor
138
114
  new_tasklist = client.create_tasklist(tasklist)
139
115
  end
140
116
 
141
- =begin When you do it twice, you stop caring.
142
- if options.graph?
143
- graph_src.add_node(tasklist['id'])
144
- graph_dest.add_node(new_tasklist['id'])
145
- end
146
- =end
147
-
148
117
  debug "tasklist:create '#{new_tasklist['title']}' (#{new_tasklist['id']})"
149
118
 
150
119
  tasklist['tasks'].each do |task|
151
- if orphans.include? task['id']
152
- debug "skip: orphaned task #{task['title']} (#{task['id']})"
153
- next
154
- end
155
-
156
- if task.has_key? 'parent'
157
- if parents.fetch(task['parent'], :placeholder) == :placeholder
158
- parent = tasklist['tasks'].find {|t| t['id'] == task['parent']}
159
- tasks = [task, parent].map {|t| "#{t['id'].rjust(35)} #{t['position'].to_s.rjust(20)}" }.join("\n")
160
- debug "skip: child called not yet initialized parent:\n#{tasks}"
161
- next
162
- end
163
-
164
- debug "parent:rename #{task['parent']} #{parents[task['parent']]}"
165
- task['parent'] = parents[task['parent']]
166
- end
167
-
168
- task['title'].strip!
120
+ task_id = task.delete 'id'
169
121
 
170
122
  if options.pretend?
171
123
  new_task = task.dup.update 'id' => "task-#{task_id_seq.next}"
@@ -173,14 +125,11 @@ class App < Thor
173
125
  new_task = client.create_task(task, new_tasklist['id'])
174
126
  end
175
127
 
176
- debug "task:create '#{new_task['title']}' (#{new_task['id']}, #{task['id']})#{new_task['parent'] ? " [child of #{new_task['parent']}]" : ""}"
128
+ id_mapping[task_id] = { 'id' => new_task['id'], 'tasklist' => new_tasklist['id'] }
177
129
 
178
- task_count += 1
130
+ debug "task:create '#{new_task['title']}' (#{new_task['id']}, #{task_id})#{new_task['parent'] ? " [child of #{new_task['parent']}]" : ""}"
179
131
 
180
- if parents.include? task['id']
181
- debug "trying to set parent id #{task['id']} twice" if parents[task['id']] != :placeholder
182
- parents[task['id']] = new_task['id']
183
- end
132
+ task_count += 1
184
133
  end
185
134
 
186
135
  total_tasks += task_count
@@ -192,6 +141,21 @@ class App < Thor
192
141
  pluralize(task_count, "task"))
193
142
  end
194
143
 
144
+ parents.each do |parent, children|
145
+ new_parent = id_mapping[parent]['id']
146
+
147
+ children.each do |id|
148
+ new_id = id_mapping[id]['id']
149
+ list_id = id_mapping[id]['tasklist']
150
+
151
+ unless options.pretend?
152
+ client.move_task new_id, list_id, :parent => new_parent
153
+ end
154
+
155
+ debug "task:move (#{id}, #{new_id}) tasklist (#{list_id}) children of (#{parent}, #{new_parent})"
156
+ end
157
+ end
158
+
195
159
  say_status "import:#{options.format}",
196
160
  format("%s [%s %s, %s %s]",
197
161
  cyan(account, :bold),
@@ -300,30 +264,30 @@ to get one and bring it here to him!
300
264
  say_status "debug", msg, :white if options.debug?
301
265
  end
302
266
 
303
- # Handle parent-child relationships
304
- def prepare_tasklists!(tasklists)
267
+ # Handle parent-child relationships and ordering
268
+ def prepare_import(tasklists)
305
269
  parents = {}
306
- orphans = []
307
- tasklist_ids = tasklists.map {|list| list['id'] }
308
- tasklists.each do |tasklist|
309
- task_ids = tasklist['tasks'].map {|task| task['id'] }
310
- tasklist['tasks'].each do |task|
311
- next unless task.has_key? 'parent'
312
- if not task_ids.include?(task['parent'])
313
- if tasklist_ids.include?(task['parent'])
314
- task.delete 'parent'
315
- else
316
- orphans << task['id']
317
- end
318
- else
319
- parents[task['parent']] ||= :placeholder
270
+ tasklists = tasklists.map do |list|
271
+ list_data = GSquire.resource(list).data
272
+ list_data.delete 'id'
273
+ # can't order by task['position'] since it doesn't reflect the actual order
274
+ # and inserts without the 'previous' parameter puts tasks on top like a stack
275
+ # so we need to revert the original task array
276
+ tasks = list['tasks'].reverse
277
+ list_data['tasks'] = tasks.map do |task|
278
+ task_data = GSquire.resource(task).data
279
+ if task_data.include? 'parent'
280
+ parents[task_data['parent']] ||= []
281
+ parents[task_data['parent']] << task_data['id']
282
+ task_data.delete 'parent'
320
283
  end
284
+ # we don't clear the 'id' attribute to be able to use it with parents hash later
285
+ task_data
321
286
  end
322
-
323
- tasklist['tasks'].sort_by! {|t| t.include?('parent') ? 1 : 0 }
287
+ list_data
324
288
  end
325
289
 
326
- [parents, orphans]
290
+ [tasklists, parents]
327
291
  end
328
292
 
329
293
  def id_seq
@@ -1,6 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
- require 'hashie'
3
+ require 'gsquire/models'
4
4
 
5
5
  module GSquire
6
6
  class Accounts
@@ -23,7 +23,7 @@ module GSquire
23
23
  end
24
24
 
25
25
  def mash(hash)
26
- Hashie::Mash.new hash
26
+ GSquire.resource hash
27
27
  end
28
28
 
29
29
  def content_type(env)
@@ -6,7 +6,6 @@ require 'gsquire/accounts'
6
6
  require 'gsquire/logging'
7
7
 
8
8
  module GSquire
9
-
10
9
  #
11
10
  # This is the entry-point class for GSquire. Clients should use it to implement applications that use GSquire.
12
11
  #
@@ -1,5 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
+ require 'cgi'
3
4
  require 'json'
4
5
  require 'uri'
5
6
 
@@ -12,66 +13,97 @@ module GSquire
12
13
 
13
14
  attr_accessor :oauth_token
14
15
 
16
+ # @param [OAuth2::AccessToken] Authorized oauth token
15
17
  def initialize(token)
16
18
  @oauth_token = token
17
19
  end
18
20
 
19
- # Pulls all task lists for authorized user
20
- # @return [Array] Array of task list hashes
21
+ # Pulls all tasklists for authorized user
22
+ # @return [Array] Array of tasklist hashes
21
23
  def tasklists
22
24
  get gtasks_tasklists_url
23
25
  end
24
26
 
25
- # Pulls a task list
26
- # @param [String] tasklist_id ('@default') Task list ID
27
- # @return [Hash] task list
28
- def tasklist(tasklist_id = '@default')
29
- get gtasks_tasklist_url(tasklist_id)
27
+ # Pulls a tasklist
28
+ # @param [String] tasklist_id ('@default') Tasklist id
29
+ # @option opts [true|false] :pull_tasks (false) Pulls tasklist with all tasks preloaded in the `'tasks'` key
30
+ # @return [Hash] tasklist
31
+ def tasklist(tasklist_id = '@default', opts={})
32
+ tasklist = get gtasks_tasklist_url(tasklist_id)
33
+ tasklist['tasks'] = tasks tasklist_id if opts[:pull_tasks]
34
+ tasklist
30
35
  end
31
36
 
37
+ # Creates a tasklist
38
+ # @param [Hash] tasklist Tasklist data
32
39
  def create_tasklist(tasklist)
33
40
  post gtasks_tasklists_url, strip(:tasklist, :create, tasklist)
34
41
  end
35
42
 
43
+ # Updates a tasklist
44
+ # @param [Hash] tasklist Tasklist data
36
45
  def update_tasklist(tasklist)
37
46
  put gtasks_tasklist_url(tasklist[:id]), strip(:tasklist, :update, tasklist)
38
47
  end
39
48
 
49
+ # Deletes a tasklist
50
+ # @param [String] tasklist_id ('@default') Tasklist id
40
51
  def delete_tasklist(tasklist_id)
41
52
  delete gtasks_tasklist_url(tasklist_id)
42
53
  end
43
54
 
44
- # Pulls all tasks of a task list
45
- # @param [String] tasklist_id ('@default') Task list ID
55
+ # Pulls all tasks of a tasklist
56
+ # @param [String] tasklist_id ('@default') Tasklist id
46
57
  # @return [Array] Array of task hashes
47
58
  def tasks(tasklist_id = '@default')
48
59
  get gtasks_tasks_url(tasklist_id)
49
60
  end
50
61
 
51
- # Pulls a task of a task list
52
- # @param [String] task_id Task ID
53
- # @param [String] tasklist_id ('@default') Task list ID
62
+ # Pulls a task of a tasklist
63
+ # @param [String] task_id Task id
64
+ # @param [String] tasklist_id ('@default') Tasklist id
54
65
  # @return [Hash] Task hash
55
66
  def task(task_id, tasklist_id = '@default')
56
67
  get gtasks_task_url(task_id, tasklist_id)
57
68
  end
58
69
 
70
+ # Creates a task in the given tasklist
71
+ # @param [Hash] task Task data
72
+ # @param [String] tasklist_id ('@default') Tasklist id
59
73
  def create_task(task, tasklist_id = '@default')
60
74
  post gtasks_tasks_url(tasklist_id), strip(:task, :create, task)
61
75
  end
62
76
 
77
+ # Moves a task inside the given tasklist
78
+ # @param [String] task_id Task id
79
+ # @param [String] tasklist_id ('@default') Tasklist id
80
+ # @option params [String] :parent Parent task id. Will move the task under the parent id
81
+ # @option params [String] :previous Previous task id. Will move the task to after the task with this id
82
+ def move_task(task_id, tasklist_id = '@default', params={})
83
+ return if params.empty?
84
+ post gtasks_task_move_url(task_id, tasklist_id, params)
85
+ end
86
+
63
87
  protected
64
88
 
65
89
  def get(url)
66
90
  _ oauth_token.get(url)
67
91
  end
68
92
 
69
- def post(url, content)
70
- _ oauth_token.post(url, body: content.to_json, headers: {'Content-Type' => 'application/json'})
93
+ def post(url, content={})
94
+ opts = {}
95
+ if not content.empty?
96
+ opts.merge!(body: content.to_json, headers: {'Content-Type' => 'application/json'})
97
+ end
98
+ _ oauth_token.post(url, opts)
71
99
  end
72
100
 
73
- def put(url, content)
74
- _ oauth_token.put(url, body: content.to_json, headers: {'Content-Type' => 'application/json'})
101
+ def put(url, content={})
102
+ opts = {}
103
+ if not content.empty?
104
+ opts.merge!(body: content.to_json, headers: {'Content-Type' => 'application/json'})
105
+ end
106
+ _ oauth_token.put(url, opts)
75
107
  end
76
108
 
77
109
  def delete(url)
@@ -94,6 +126,16 @@ module GSquire
94
126
  gtasks_urls(:task, tasklist_id, task_id)
95
127
  end
96
128
 
129
+ def gtasks_task_move_url(task_id, tasklist_id = '@default', params)
130
+ uri = gtasks_urls(:task_move, tasklist_id, task_id)
131
+ uri.query = params.collect { |k, v| "#{k.to_s}=#{CGI::escape(v.to_s)}" }.join('&')
132
+ uri
133
+ end
134
+
135
+ def gtasks_tasks_clear_url(tasklist_id = '@default')
136
+ gtasks_urls(:tasks_clear, tasklist_id)
137
+ end
138
+
97
139
  def gtasks_urls(resource, *params)
98
140
  segments = case resource
99
141
  when :tasklists
@@ -104,6 +146,10 @@ module GSquire
104
146
  "/lists/$/tasks"
105
147
  when :task
106
148
  "/lists/$/tasks/$"
149
+ when :task_move
150
+ "/lists/$/tasks/$/move"
151
+ when :tasks_clear
152
+ "/lists/$/clear"
107
153
  end.split('/')
108
154
  subpath = segments.map {|seg| seg == '$' ? params.shift : seg }.join('/')
109
155
  GOOGLE_TASKS_API.merge(GOOGLE_TASKS_API.path + subpath)
@@ -0,0 +1,23 @@
1
+ # encoding: utf-8
2
+
3
+ require 'gsquire/models/task'
4
+ require 'gsquire/models/tasklist'
5
+
6
+ module GSquire
7
+ RESOURCE_KINDS = {
8
+ "tasks#taskList" => Tasklist,
9
+ "tasks#task" => Task
10
+ }.freeze
11
+
12
+ class << self
13
+ def resource(hash)
14
+ return nil unless hash.include? 'kind'
15
+
16
+ unless RESOURCE_KINDS.include? hash['kind']
17
+ raise ArgumentError, "Unknown resource kind #{hash['kind'].inspect}"
18
+ end
19
+
20
+ RESOURCE_KINDS[hash['kind']].new(hash)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ # encoding: utf-8
2
+
3
+ require 'hashie'
4
+
5
+ module GSquire
6
+ class Task < Hashie::Mash
7
+ DATA_ATTRIBUTES = %w[
8
+ id
9
+ title
10
+ parent
11
+ notes
12
+ status
13
+ due
14
+ completed
15
+ deleted
16
+ hidden
17
+ ].freeze
18
+
19
+ def data
20
+ select {|k,v| DATA_ATTRIBUTES.include? k }
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,16 @@
1
+ # encoding: utf-8
2
+
3
+ require 'hashie'
4
+
5
+ module GSquire
6
+ class Tasklist < Hashie::Mash
7
+ DATA_ATTRIBUTES = %w[
8
+ id
9
+ title
10
+ ].freeze
11
+
12
+ def data
13
+ select {|k,v| DATA_ATTRIBUTES.include? k }
14
+ end
15
+ end
16
+ end
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module GSquire
4
- VERSION = "0.0.3"
4
+ VERSION = "0.0.4"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gsquire
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-01-03 00:00:00.000000000Z
12
+ date: 2012-01-04 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: oauth2
16
- requirement: &16757400 !ruby/object:Gem::Requirement
16
+ requirement: &13973740 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 0.5.0
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *16757400
24
+ version_requirements: *13973740
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: thor
27
- requirement: &16756920 !ruby/object:Gem::Requirement
27
+ requirement: &13973000 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ~>
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: 0.14.6
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *16756920
35
+ version_requirements: *13973000
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: hashie
38
- requirement: &16756460 !ruby/object:Gem::Requirement
38
+ requirement: &13972540 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ~>
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: 1.1.0
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *16756460
46
+ version_requirements: *13972540
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: rdoc
49
- requirement: &16756080 !ruby/object:Gem::Requirement
49
+ requirement: &13972160 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: '0'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *16756080
57
+ version_requirements: *13972160
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: rdiscount
60
- requirement: &16755620 !ruby/object:Gem::Requirement
60
+ requirement: &13971680 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ! '>='
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: '0'
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *16755620
68
+ version_requirements: *13971680
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: yard
71
- requirement: &16755200 !ruby/object:Gem::Requirement
71
+ requirement: &13971240 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ! '>='
@@ -76,10 +76,10 @@ dependencies:
76
76
  version: '0'
77
77
  type: :development
78
78
  prerelease: false
79
- version_requirements: *16755200
79
+ version_requirements: *13971240
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: awesome_print
82
- requirement: &16754780 !ruby/object:Gem::Requirement
82
+ requirement: &13970820 !ruby/object:Gem::Requirement
83
83
  none: false
84
84
  requirements:
85
85
  - - ! '>='
@@ -87,7 +87,7 @@ dependencies:
87
87
  version: '0'
88
88
  type: :development
89
89
  prerelease: false
90
- version_requirements: *16754780
90
+ version_requirements: *13970820
91
91
  description: ! "Back in the Age of Heroes, GSquire would carry thy armor\n and
92
92
  sword, would get thy lordship dressed and accompany in battle. He would\n fight
93
93
  side by side with those who stood brave against the toughest of the\n foes. These
@@ -119,6 +119,9 @@ files:
119
119
  - lib/gsquire/application.rb
120
120
  - lib/gsquire/client.rb
121
121
  - lib/gsquire/logging.rb
122
+ - lib/gsquire/models.rb
123
+ - lib/gsquire/models/task.rb
124
+ - lib/gsquire/models/tasklist.rb
122
125
  - lib/gsquire/version.rb
123
126
  homepage: http://commita.com/community
124
127
  licenses: []