asana-client 0.0.1

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.
Files changed (3) hide show
  1. data/bin/asana +6 -0
  2. data/lib/asana-client.rb +381 -0
  3. metadata +78 -0
data/bin/asana ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'asana-client'
4
+
5
+ Asana.init
6
+ Asana.parse ARGV
@@ -0,0 +1,381 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ ##
4
+ # Asana API library and command-line client
5
+ # Tommy MacWilliam <tmacwilliam@cs.harvard.edu>
6
+ #
7
+ #
8
+
9
+ require "rubygems"
10
+ require "JSON"
11
+ require "net/https"
12
+ require "yaml"
13
+ require "chronic"
14
+
15
+ module Asana
16
+
17
+ API_URL = "https://app.asana.com/api/1.0/"
18
+
19
+ # initialize config values
20
+ def Asana.init
21
+ begin
22
+ @@config = YAML.load_file File.expand_path "~/.asana-client"
23
+ rescue
24
+ abort "Configuration file could not be found.\nSee https://github.com/tmac721/asana-client for installation instructions."
25
+ end
26
+ end
27
+
28
+ # parse argumens
29
+ def Asana.parse(args)
30
+ # no arguments given
31
+ if args.empty?
32
+ abort "Nothing to do here."
33
+ end
34
+
35
+ # concatenate array into a string
36
+ string = args.join " "
37
+
38
+ # finish n: complete the task with id n
39
+ if string =~ /^finish (\d+)$/
40
+ Asana::Task.finish $1
41
+ puts "Task completed!"
42
+ exit
43
+ end
44
+
45
+ # workspace: display tasks in that workspace
46
+ if string =~ /^(\w+)$/
47
+ # get corresponding workspace object
48
+ workspace = Asana::Workspace.find $1
49
+ abort "Workspace not found!" unless workspace
50
+
51
+ # display all tasks in workspace
52
+ puts workspace.tasks unless workspace.tasks.empty?
53
+ exit
54
+ end
55
+
56
+ # workspace/project: display tasks in that project
57
+ if string =~ /^(\w+)\/(\w+)$/
58
+ # get corresponding workspace
59
+ workspace = Asana::Workspace.find $1
60
+ abort "Workspace not found!" unless workspace
61
+
62
+ # get corresponding project
63
+ project = Asana::Project.find workspace, $2
64
+ abort "Project not found!" unless project
65
+
66
+ # display all tasks in project
67
+ puts project.tasks unless project.tasks.empty?
68
+ exit
69
+ end
70
+
71
+ # extract assignee, if any
72
+ assignee = nil
73
+ args.each do |word|
74
+ if word =~ /^@(\w+)$/
75
+ assignee = word[1..-1]
76
+ args.delete word
77
+ end
78
+ end
79
+
80
+ # extract due date, if any
81
+ due = Chronic.parse(args.reverse[0..1].reverse.join(" "))
82
+ if !due.nil? && due.to_s =~ /(\d+)-(\d+)-(\d+)/
83
+ # penultimate word is part of the date or a stop word, so remove it
84
+ if Chronic.parse(args.reverse[1]) || ["due", "on", "for"].include?(args.reverse[1].downcase)
85
+ args.pop
86
+ end
87
+
88
+ # extract date from datetime and remove date from task name
89
+ args.pop
90
+ due = "#{$1}-#{$2}-#{$3}"
91
+ end
92
+
93
+ # reset string, because we modifed argv
94
+ string = args.join " "
95
+
96
+ # workspace task name: create task in that workspace
97
+ if string =~ /^(\w+) ([\w ]+)/
98
+ # get corresponding workspace
99
+ workspace = Asana::Workspace.find $1
100
+
101
+ # create task in workspace
102
+ Asana::Task.create workspace, $2, assignee, due
103
+ puts "Task created in #{workspace.name}!"
104
+ exit
105
+ end
106
+
107
+ # workspace/project task name: create task in that workspace
108
+ if string =~ /^(\w+)\/(\w+) ([\w ]+)/
109
+ # get corresponding workspace
110
+ workspace = Asana::Workspace.find $1
111
+
112
+ # create task in workspace
113
+ task = Asana::Task.create workspace, $3, assignee, due
114
+
115
+ # get corresponding project
116
+ project = Asana::Project.find workspace, $2
117
+ abort "Project not found!" unless project
118
+
119
+ # add task to project
120
+ Asana.post "tasks/#{task['data']['id']}/addProject", { "project" => project.id }
121
+ puts "Task created in #{workspace.name}/#{project.name}!"
122
+ exit
123
+ end
124
+ end
125
+
126
+ # perform a GET request and return the response body as an object
127
+ def Asana.get(url)
128
+ return Asana.http_request(Net::HTTP::Get, url, nil, nil)
129
+ end
130
+
131
+ # perform a PUT request and return the response body as an object
132
+ def Asana.put(url, data, query = nil)
133
+ return Asana.http_request(Net::HTTP::Put, url, data, query)
134
+ end
135
+
136
+ # perform a POST request and return the response body as an object
137
+ def Asana.post(url, data, query = nil)
138
+ return Asana.http_request(Net::HTTP::Post, url, data, query)
139
+ end
140
+
141
+ # perform an HTTP request to the Asana API
142
+ def Asana.http_request(type, url, data, query)
143
+ # set up http object
144
+ uri = URI.parse API_URL + url
145
+ http = Net::HTTP.new uri.host, uri.port
146
+ http.use_ssl = true
147
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
148
+
149
+ # all requests are json
150
+ header = {
151
+ "Content-Type" => "application/json"
152
+ }
153
+
154
+ # make request
155
+ req = type.new("#{uri.path}?#{uri.query}", header)
156
+ req.basic_auth @@config["api_key"], ''
157
+ if req.respond_to?(:set_form_data) && !data.nil?
158
+ req.set_form_data data
159
+ end
160
+ res = http.start { |http| http.request req }
161
+
162
+ # return request object
163
+ return JSON.parse(res.body)
164
+ end
165
+
166
+ # get all of the users workspaces
167
+ def Asana.workspaces
168
+ spaces = self.get "workspaces"
169
+ list = []
170
+
171
+ # convert array to hash indexed on workspace name
172
+ spaces["data"].each do |space|
173
+ list.push Workspace.new :id => space["id"], :name => space["name"]
174
+ end
175
+
176
+ list
177
+ end
178
+
179
+ class Project
180
+ attr_accessor :id, :name, :workspace
181
+
182
+ def initialize(hash)
183
+ self.id = hash[:id] || 0
184
+ self.name = hash[:name] || ""
185
+ self.workspace = hash[:workspace] || nil
186
+ end
187
+
188
+ # search for a project within a workspace
189
+ def self.find(workspace, name)
190
+ # if given string for workspace, convert to object
191
+ if workspace.is_a? String
192
+ workspace = Asana::Workspace.find workspace
193
+ end
194
+
195
+ # check if any workspace contains the given name, and return first hit
196
+ name.downcase!
197
+ if workspace
198
+ workspace.projects.each do |project|
199
+ if project.name.downcase.include? name
200
+ return project
201
+ end
202
+ end
203
+ end
204
+
205
+ nil
206
+ end
207
+
208
+ # get all tasks associated with the current project
209
+ def tasks
210
+ task_objects = Asana.get "tasks?workspace=#{workspace.id}&project=#{self.id}"
211
+ list = []
212
+
213
+ task_objects["data"].each do |task|
214
+ list.push Task.new :id => task["id"], :name => task["name"],
215
+ :workspace => self.workspace, :project => self
216
+ end
217
+
218
+ list
219
+ end
220
+ end
221
+
222
+ class Task
223
+ attr_accessor :id, :name, :workspace, :project
224
+
225
+ def initialize(hash)
226
+ self.id = hash[:id] || 0
227
+ self.name = hash[:name] || ""
228
+ self.workspace = hash[:workspace] || nil
229
+ self.project = hash[:project] || nil
230
+ end
231
+
232
+ # create a new task on the server
233
+ def self.create(workspace, name, assignee = nil, due = nil)
234
+ # if given string for workspace, convert to object
235
+ if workspace.is_a? String
236
+ workspace = Asana::Workspace.find workspace
237
+ end
238
+ abort "Workspace not found" unless workspace
239
+
240
+ # if assignee was given, get user
241
+ if !assignee.nil?
242
+ assignee = Asana::User.find workspace, assignee
243
+ abort "Assignee not found" unless assignee
244
+ end
245
+
246
+ # add task to workspace
247
+ params = {
248
+ "workspace" => workspace.id,
249
+ "name" => name,
250
+ "assignee" => (assignee.nil?) ? "me" : assignee.id
251
+ }
252
+
253
+ # attach due date if given
254
+ if !due.nil?
255
+ params["due_on"] = due
256
+ end
257
+
258
+ # add task to workspace
259
+ Asana.post "tasks", params
260
+ end
261
+
262
+ # comment on a task
263
+ def self.comment(id, text)
264
+ Asana.post "tasks/#{id}/stories", { "text" => text }
265
+ end
266
+
267
+ # comment on the current task
268
+ def comment(text)
269
+ self.comment(self.id, text)
270
+ end
271
+
272
+ # finish a task
273
+ def self.finish(id)
274
+ Asana.put "tasks/#{id}", { "completed" => true }
275
+ end
276
+
277
+ # finish the current task
278
+ def finish
279
+ self.finish(self.id)
280
+ end
281
+
282
+ def to_s
283
+ "(#{self.id}) #{self.name}"
284
+ end
285
+ end
286
+
287
+ class User
288
+ attr_accessor :id, :name
289
+
290
+ def initialize(hash)
291
+ self.id = hash[:id] || 0
292
+ self.name = hash[:name] || ""
293
+ end
294
+
295
+ def self.find(workspace, name)
296
+ # if given string for workspace, convert to object
297
+ if workspace.is_a? String
298
+ workspace = Asana::Workspace.find workspace
299
+ end
300
+
301
+ # check if any workspace contains the given name, and return first hit
302
+ name.downcase!
303
+ workspace.users.each do |user|
304
+ if user.name.downcase.include? name
305
+ return user
306
+ end
307
+ end
308
+
309
+ nil
310
+ end
311
+
312
+ def to_s
313
+ self.name
314
+ end
315
+ end
316
+
317
+ class Workspace
318
+ attr_accessor :id, :name
319
+
320
+ def initialize(hash)
321
+ self.id = hash[:id] || 0
322
+ self.name = hash[:name] || ""
323
+ end
324
+
325
+ # search a workspace by name
326
+ def self.find(name)
327
+ # check if any workspace contains the given name, and return first hit
328
+ name.downcase!
329
+ Asana.workspaces.each do |workspace|
330
+ if workspace.name.downcase.include? name
331
+ return workspace
332
+ end
333
+ end
334
+
335
+ nil
336
+ end
337
+
338
+ # get all projects associated with a workspace
339
+ def projects
340
+ project_objects = Asana.get "projects?workspace=#{self.id}"
341
+ list = []
342
+
343
+ project_objects["data"].each do |project|
344
+ list.push Project.new :id => project["id"], :name => project["name"], :workspace => self
345
+ end
346
+
347
+ list
348
+ end
349
+
350
+ # get tasks assigned to me within this workspace
351
+ def tasks
352
+ task_objects = Asana.get "tasks?workspace=#{self.id}&assignee=me"
353
+ list = []
354
+
355
+ task_objects["data"].each do |task|
356
+ list.push Task.new :id => task["id"], :name => task["name"],
357
+ :workspace => self
358
+ end
359
+
360
+ list
361
+ end
362
+
363
+ # get all users in the workspace
364
+ def users
365
+ user_objects = Asana.get "workspaces/#{self.id}/users"
366
+ list = []
367
+
368
+ user_objects["data"].each do |user|
369
+ list.push User.new :id => user["id"], :name => user["name"]
370
+ end
371
+
372
+ list
373
+ end
374
+ end
375
+ end
376
+
377
+
378
+ if __FILE__ == $0
379
+ Asana.init
380
+ Asana.parse ARGV
381
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: asana-client
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.1
6
+ platform: ruby
7
+ authors:
8
+ - Tommy MacWilliam
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2012-04-25 00:00:00 -04:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: chronic
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ">="
23
+ - !ruby/object:Gem::Version
24
+ version: 0.6.7
25
+ type: :runtime
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
28
+ name: json
29
+ prerelease: false
30
+ requirement: &id002 !ruby/object:Gem::Requirement
31
+ none: false
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: 1.6.6
36
+ type: :runtime
37
+ version_requirements: *id002
38
+ description: Command-line client and library for browsing, creating, and completing Asana tasks.
39
+ email: tmacwilliam@cs.harvard.edu
40
+ executables:
41
+ - asana
42
+ extensions: []
43
+
44
+ extra_rdoc_files: []
45
+
46
+ files:
47
+ - lib/asana-client.rb
48
+ - bin/asana
49
+ has_rdoc: true
50
+ homepage: https://github.com/tmac721/asana-client
51
+ licenses: []
52
+
53
+ post_install_message:
54
+ rdoc_options: []
55
+
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: "0"
64
+ required_rubygems_version: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: "0"
70
+ requirements: []
71
+
72
+ rubyforge_project:
73
+ rubygems_version: 1.6.2
74
+ signing_key:
75
+ specification_version: 3
76
+ summary: Ruby client for Asana's REST API
77
+ test_files: []
78
+