todoist 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009-2009 Jonathan Stott
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,22 @@
1
+ todoist
2
+ =======
3
+
4
+ The todoist gem is intended to facilitate access to the todoist (http://todoist.com)
5
+ task management service, offering methods to directly query the API and also
6
+ convinient wrapper objects around the responses.
7
+
8
+
9
+ example
10
+ -------
11
+
12
+
13
+ Todoist::Base.setup(api_token)
14
+
15
+ Todoist::Task.all.each do |task|
16
+ puts task
17
+ end
18
+
19
+ COPYRIGHT
20
+ =========
21
+
22
+ Copyright (c) 2008 Jonathan Stott. See LICENSE for details.
data/Rakefile CHANGED
@@ -1,4 +1,38 @@
1
- require 'config/requirements'
2
- require 'config/hoe' # setup Hoe + all gem configuration
3
-
4
- Dir['tasks/**/*.rake'].each { |rake| load rake }
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ begin
6
+ require 'jeweler'
7
+ Jeweler::Tasks.new do |s|
8
+ s.name = "todoist"
9
+ s.summary = "a library for interacting with the Todoist public API"
10
+ s.email = "jonathan.stott@gmail.com"
11
+ s.homepage = "http://github.com/namelessjon/todoist"
12
+ s.description = "The todoist gem offers convinience methods and wrappers for the todoist list management service, easing retrival and parsing of the responses. It also offers a simple command-line client."
13
+ s.authors = ["Jonathan Stott"]
14
+ s.files = FileList["lib/**/*.rb", "spec/**/*.rb", 'Rakefile', "[A-Z]+"]
15
+ s.add_dependency('highline', '~> 1.5')
16
+ s.add_dependency('thor', '~> 0.9')
17
+ s.add_dependency('httparty', '~> 0.3')
18
+ end
19
+ Jeweler::GemcutterTasks.new
20
+ rescue LoadError
21
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
22
+ end
23
+
24
+ Rake::TestTask.new(:spec) do |t|
25
+ t.libs << 'lib'
26
+ t.pattern = 'spec/**/*_spec.rb'
27
+ t.verbose = true
28
+ end
29
+
30
+ Rake::RDocTask.new do |rdoc|
31
+ rdoc.rdoc_dir = 'rdoc'
32
+ rdoc.title = 'Todoist'
33
+ rdoc.options << '--line-numbers' << '--inline-source'
34
+ rdoc.rdoc_files.include('README*')
35
+ rdoc.rdoc_files.include('lib/**/*.rb')
36
+ end
37
+
38
+ task :default => :spec
@@ -0,0 +1,91 @@
1
+ require 'thor'
2
+ require 'highline'
3
+
4
+ HighLine.track_eof = false
5
+
6
+ dir = File.dirname(__FILE__)
7
+ require File.join(dir, '..', 'lib', 'todoist')
8
+
9
+ class TodoistCLI < Thor
10
+
11
+ desc "tasks", "list of projects with uncompleted tasks, and their tasks"
12
+ def tasks(name=nil)
13
+ setup_todoist_base
14
+
15
+ Todoist::Project.all.each do |project|
16
+ next if name and project.name !~ /#{name}/
17
+
18
+ if project.task_count > 0
19
+ print_project(project)
20
+ end
21
+ end
22
+ end
23
+
24
+ desc "complete", "Completes the task with the given content"
25
+ method_options :all => :boolean
26
+ def complete(content)
27
+ setup_todoist_base
28
+
29
+ @h = HighLine.new
30
+
31
+ to_complete = []
32
+ Todoist::Task.all.each do |task|
33
+ next if task.content !~ /#{content}/
34
+ to_complete << task
35
+ end
36
+
37
+ tasks = []
38
+ if to_complete.size > 1 and !options[:all]
39
+ @h.say("Choose tasks to delete:")
40
+ to_complete.each do |task|
41
+ if @h.agree("Delete #{task} (#{task.project})?")
42
+ tasks << task
43
+ end
44
+ end
45
+ else
46
+ tasks = to_complete
47
+ end
48
+
49
+ Todoist::Task.complete(tasks)
50
+ end
51
+
52
+ desc "overdue", "Fetch all overdue tasks"
53
+ def overdue
54
+ setup_todoist_base
55
+ Todoist::Task.overdue.each do |task|
56
+ puts task
57
+ end
58
+ end
59
+
60
+ protected
61
+ def print_project(project)
62
+ puts project
63
+ project.tasks.each do |task|
64
+ next if task.complete?
65
+ print " " * (task.indent.to_i - 1)
66
+ print (task.content =~ /\* /) ? '' : '- '
67
+ puts task
68
+ end
69
+ puts ""
70
+ end
71
+
72
+ def setup_todoist_base
73
+ if File.file?(File.expand_path('~/.todoistrc'))
74
+ require 'yaml'
75
+ conf = YAML.load_file(File.expand_path('~/.todoistrc'))
76
+ Todoist::Base.setup(conf['token'], conf['premium'])
77
+ else
78
+ puts <<-eos
79
+ You need to setup ~/.todoistrc, for example:
80
+ token: 91b1ce9e153fc5390ac3db4f0ded9f31a1a7ba23 # use your api key
81
+ premium: false # if this is true, ssl will be used.
82
+ eos
83
+ exit(1)
84
+ end
85
+ end
86
+
87
+ end
88
+
89
+ TodoistCLI.start
90
+
91
+ # vim: set ft=ruby:
@@ -1,20 +1,8 @@
1
- $:.unshift(File.dirname(__FILE__)) unless
2
- $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
-
4
1
  module Todoist
5
-
6
2
  end
7
3
 
8
- begin
9
- require 'json'
10
- rescue LoadError
11
- require 'rubygems'
12
- require 'json'
13
- end
4
+ dir = File.dirname(__FILE__)
14
5
 
15
- require 'cgi'
16
- require 'net/http'
17
- require 'net/https'
18
- require 'todoist/errors'
19
- require 'todoist/connection'
20
- require 'todoist/project'
6
+ require File.join(dir, 'todoist', 'base')
7
+ require File.join(dir, 'todoist', 'project')
8
+ require File.join(dir, 'todoist', 'task')
@@ -0,0 +1,38 @@
1
+ require 'httparty'
2
+ require 'json'
3
+ module Todoist
4
+ ##
5
+ # The Todoist::Base class is responsible for making all queries to the
6
+ # todoist web API.
7
+ class Base
8
+ include HTTParty
9
+ format :json
10
+
11
+ ##
12
+ # Sets up the Todoist::Base class for making requests, setting the API key
13
+ # and if the account is a premium one or not.
14
+ def self.setup(token, premium = false)
15
+ self.default_params :token => token
16
+ @@premium = premium
17
+ set_base_uri
18
+ end
19
+
20
+ private
21
+ def self.set_base_uri
22
+ if premium?
23
+ base_uri 'https://todoist.com/API'
24
+ else
25
+ base_uri 'http://todoist.com/API'
26
+ end
27
+ end
28
+
29
+ def self.premium?
30
+ (@@premium) ? true : false
31
+ end
32
+
33
+ def id_array(ids)
34
+ ids.flatten.map {|i| i.to_i }.to_json
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,126 @@
1
+ module Todoist
2
+ ##
3
+ # Project
4
+ #
5
+ # A todoist project.
6
+ class Project
7
+ attr_reader :name, :id, :user_id, :color, :collapsed, :order, :indent
8
+
9
+ ##
10
+ # Get all projects
11
+ #
12
+ # Fetches all the user's todist projects.
13
+ #
14
+ # @return [Array] An Array of todoist project instances.
15
+ def self.all
16
+ projects = []
17
+ Base.get('/getProjects').each do |project|
18
+ projects << new_from_api_request(project)
19
+ end
20
+ projects
21
+ end
22
+
23
+ ##
24
+ # Get a project
25
+ #
26
+ # Fetches a todoist project
27
+ #
28
+ # @param [Integer,Todoist::Project] id The id, or project, to fetch
29
+ #
30
+ # @return [Todoist::Project] The fetched project.
31
+ def self.get(id)
32
+ new_from_api_request(get_project(id))
33
+ end
34
+
35
+ ##
36
+ # Create a new project
37
+ #
38
+ # Creates a new Todoist project.
39
+ #
40
+ # @param [String] name The name of the project
41
+ #
42
+ # @param [Hash] parameters The other parameters which make up the project.
43
+ #
44
+ # @return [Todoist::Project] The new todoist project.
45
+ def initialize(name, parameters={})
46
+ @name = name
47
+ @id = parameters['id']
48
+ @user_id = parameters['user_id']
49
+ @color = parameters['color']
50
+ @collapsed = parameters['collapsed']
51
+ @order = parameters['item_order']
52
+ @count = parameters['cache_count']
53
+ @indent = parameters['indent']
54
+ end
55
+
56
+
57
+ def to_s
58
+ "#{name}"
59
+ end
60
+
61
+ def to_i
62
+ id
63
+ end
64
+
65
+ def inspect
66
+ "<Project:#{name}:#{id}:#{task_count}:user_id=#{user_id} color='#{color}' collapsed=#{collapsed?} order=#{order} indent=#{indent}>"
67
+ end
68
+
69
+ def collapsed?
70
+ (collapsed == 1) ? true : false
71
+ end
72
+
73
+
74
+ ##
75
+ # The task count
76
+ #
77
+ # The number of tasks a project has, according to its cache_count when it was fetched
78
+ #
79
+ # @return [Integer] The number of tasks this project has
80
+ def task_count
81
+ @count
82
+ end
83
+
84
+ ##
85
+ # Get uncompleted tasks for the project
86
+ #
87
+ # @return [Array] An Array of Todoist::Tasks
88
+ def tasks
89
+ Task.uncompleted(self)
90
+ end
91
+
92
+ ##
93
+ # Get completed tasks for the project
94
+ #
95
+ # @return [Array] An Array of completed Todoist::Tasks
96
+ def completed_tasks
97
+ Task.completed(self)
98
+ end
99
+
100
+ ##
101
+ # Add task
102
+ #
103
+ # Adds a task to the project.
104
+ #
105
+ # @param [String] content The content of the new task
106
+ #
107
+ # @param [Hash] opts The options for the new task
108
+ #
109
+ # @return [Todoist::Task] The new task.
110
+ def add_task(content, opts={})
111
+ Task.new(content, self, opts).save
112
+ end
113
+
114
+ private
115
+
116
+ def self.new_from_api_request(project)
117
+ name = project.delete('name')
118
+ new(name, project)
119
+ end
120
+
121
+ def self.get_project(id)
122
+ Base.get('/getProject', :query => {:project_id => id.to_i })
123
+ end
124
+
125
+ end
126
+ end
@@ -0,0 +1,252 @@
1
+ require 'time'
2
+ module Todoist
3
+ ##
4
+ # Todoist Task
5
+ #
6
+ # A todoist task.
7
+ class Task
8
+ attr_accessor :content, :priority, :task_details, :project_id
9
+ attr_reader :date, :id
10
+ ##
11
+ # Get tasks for a project
12
+ #
13
+ # Retrieves all uncompleted tasks from the todoist service for the project id.
14
+ #
15
+ # @param [Integer,Todoist::Project] project The project to get tasks for
16
+ #
17
+ # @return [Array] An array of todoist tasks.
18
+ def self.uncompleted(project)
19
+ make_tasks(Base.get('/getUncompletedItems', :query => { :project_id => project.to_i }))
20
+ end
21
+
22
+ ##
23
+ # Get completed tasks for project
24
+ #
25
+ # Retrieves completed tasks from the todoist service for the project id.
26
+ #
27
+ # @param [Integer,Todoist::Project] project The project to get tasks for
28
+ #
29
+ # @return [Array] An array of todoist tasks.
30
+ def self.completed(project)
31
+ make_tasks(Base.get('/getCompletedItems', :query => { :project_id => project.to_i }))
32
+ end
33
+
34
+ ##
35
+ # Complete tasks
36
+ #
37
+ # Completes a list of tasks
38
+ #
39
+ # @param [Array,Integer,Todist::Task] ids A list of tasks to complete.
40
+ def self.complete(*ids)
41
+ make_tasks(Base.get('/completeItems', :query => {:ids => id_array(ids)}))
42
+ end
43
+
44
+ ##
45
+ # Get Tasks by ID
46
+ #
47
+ # Retrives a list of tasks from the todoist service.
48
+ #
49
+ # @param [Array,Integer,Todist::Task] ids A list of tasks to retrieve
50
+ #
51
+ # @return [Array] An array of Todist::Tasks
52
+ def self.get(*ids)
53
+ make_tasks(Base.get('/getItemsById', :query => {:ids => id_array(ids)}))
54
+ end
55
+
56
+ ##
57
+ # Get all tasks
58
+ #
59
+ # Retrieves all the uncompleted tasks
60
+ #
61
+ # @return [Array] An array of Todist::Tasks
62
+ def self.all
63
+ query('viewall')['viewall']
64
+ end
65
+
66
+ ##
67
+ # Get overdue tasks
68
+ #
69
+ # Retrieves all the overdue tasks
70
+ #
71
+ # @return [Array] An array of overdue Todist::Tasks
72
+ def self.overdue
73
+ query('overdue')['overdue']
74
+ end
75
+
76
+
77
+ ##
78
+ # Query
79
+ #
80
+ # Use the task query API to get back several arrays of tasks.
81
+ #
82
+ # @param [String,Array] query A query or a list of queries to perform
83
+ #
84
+ # @return [Hash] a hash keyed on query, containing arrays of tasks.
85
+ #
86
+ # Allowed queries
87
+ # viewall:: All tasks
88
+ # overdue:: All overdue tasks
89
+ # p[123]:: All tasks of priority 1, 2 ,3
90
+ def self.query(*queries)
91
+ query = '["' + queries.flatten.map { |q| q.to_s }.join('","') + '"]'
92
+ results = {}
93
+ response = Base.get('/query', :query => { :queries => query })
94
+
95
+ response.each do |q|
96
+ if q['type'] == 'viewall'
97
+ tasks = []
98
+ q['data'].each do |stuff|
99
+ tasks << make_tasks(stuff['uncompleted'])
100
+ end
101
+ results[q['type']] = tasks.flatten
102
+ else
103
+ results[q['type']] = make_tasks(q['data'])
104
+ end
105
+ end
106
+ results
107
+ end
108
+
109
+ ##
110
+ # Create a new task
111
+ #
112
+ # @param [String] content The content of the new task.
113
+ #
114
+ # @param [Integer,Todoist::Project] project The project to create the new task in
115
+ #
116
+ # @param [Hash] opts Optional priority and due date for creation.
117
+ def self.create(content, project, opts={})
118
+ query = {:project_id => project.to_i, :content => content.to_s }
119
+ query['priority'] = opts.delete('priority') if opts.has_key?('priority')
120
+ query['date_string'] = opts.delete('date_string') if opts.has_key?('date_string')
121
+
122
+ new_from_api(Base.get('/addItem', :query => query))
123
+ end
124
+
125
+ ##
126
+ # Updates a task
127
+ #
128
+ # @param [String] content The new content of the task.
129
+ #
130
+ # @param [Integer,Todoist::Task] id The task to update
131
+ #
132
+ # @param [Hash] opts Optional priority and due date for creation.
133
+ def self.update(content, id, opts={})
134
+ query = {:id => project.to_i, :content => content.to_s }
135
+ query['priority'] = opts.delete('priority') if opts.has_key?('priority')
136
+ query['date_string'] = opts.delete('date_string') if opts.has_key?('date_string')
137
+
138
+ new_from_api(Base.get('/updateItem', :query => query))
139
+ end
140
+
141
+
142
+
143
+ def initialize(content, project_id, d={})
144
+ @content = content
145
+ @project_id = project_id.to_i
146
+ @date = d.delete('date') || d.delete('date_string')
147
+ @priority = d.delete('priority')
148
+ @id = d.delete('id')
149
+ @task_details = d
150
+ end
151
+
152
+ ##
153
+ # Is the task complete
154
+ def complete?
155
+ (@task_details['in_history'] == 1) ? true : false
156
+ end
157
+
158
+
159
+ ##
160
+ # Complete
161
+ #
162
+ # Completes the todoist task
163
+ def complete
164
+ self.class.complete(self) unless complete?
165
+ @task_details['in_history'] = 1
166
+ end
167
+
168
+ ##
169
+ # Is the task overdue?
170
+ def overdue?
171
+ return false unless due_date
172
+ Time.now > Time.parse(due_date)
173
+ end
174
+
175
+ ##
176
+ # Project
177
+ #
178
+ # Retreives the project for the task
179
+ def project
180
+ Project.get(project_id)
181
+ end
182
+
183
+ ##
184
+ # Saves the task
185
+ #
186
+ # Save the task, either creating a new task on the todoist service, or
187
+ # updating a previously retrieved task with new content, priority etc.
188
+ def save
189
+ opts = {}
190
+ opts['priority'] = priority if priority
191
+ opts['date_string'] = date if date
192
+ # if we don't have an id, then we can assume this is a new task.
193
+ unless (task_details.has_key?('id'))
194
+ result = self.class.create(self.content, self.project_id, opts)
195
+ else
196
+ result = self.class.update(self.content, self.id, opts)
197
+ end
198
+
199
+ self.content = result.content
200
+ self.project_id = result.project_id
201
+ self.task_details = result.task_details
202
+ self
203
+ end
204
+
205
+
206
+
207
+ def to_s
208
+ @content
209
+ end
210
+
211
+ def to_i
212
+ id
213
+ end
214
+
215
+ def inspect
216
+ "<Todoist::Task:#{content}:#{id}:#{project_id}:#{task_details.inspect}>"
217
+ end
218
+
219
+ def method_missing(*args, &block)
220
+ # the method name
221
+ m = args.shift
222
+ if @task_details.has_key?(m.to_s)
223
+ return @task_details[m.to_s]
224
+ else
225
+ raise NoMethodError, "undefined method `#{m}' for #{self.inspect}"
226
+ end
227
+ end
228
+
229
+ private
230
+
231
+ def self.new_from_api(task)
232
+ content = task.delete('content')
233
+ project_id = task.delete('project_id')
234
+ new(content, project_id, task)
235
+ end
236
+
237
+ def self.make_tasks(tasks)
238
+ new_tasks = []
239
+ tasks.each do |task|
240
+ new_tasks << new_from_api(task)
241
+ end
242
+ new_tasks
243
+ end
244
+
245
+ def self.id_array(*ids)
246
+ "[" + ids.flatten.map { |q| q.to_i }.join(",") + "]"
247
+ end
248
+
249
+
250
+ # {"due_date": null, "collapsed": 0, "labels": [], "is_dst": 0, "has_notifications": 0, "checked": 0, "indent": 1, "children": null, "content": "Finish this gem", "user_id": 34615, "mm_offset": 0, "in_history": 0, "id": 4152190, "priority": 4, "item_order": 1, "project_id": 528294, "chains": null, "date_string": ""}
251
+ end
252
+ end