asana-client 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+