namelessjon-todoist 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,44 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+ require 'rcov/rcovtask'
5
+
6
+ begin
7
+ require 'jeweler'
8
+ Jeweler::Tasks.new do |s|
9
+ s.name = "todoist"
10
+ s.summary = "a library for interacting with the Todoist public API"
11
+ s.email = "jonathan.stott@gmail.com"
12
+ s.homepage = "http://github.com/namelessjon/todoist"
13
+ 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."
14
+ s.authors = ["Jonathan Stott"]
15
+ s.files = FileList["lib/**/*.rb", "spec/**/*.rb", 'Rakefile', "[A-Z]+"]
16
+ s.add_dependency('highline', '~> 1.5')
17
+ s.add_dependency('thor', '~> 0.9')
18
+ s.add_dependency('httparty', '~> 0.3')
19
+ end
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 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
+ Rcov::RcovTask.new do |t|
39
+ t.libs << "spec"
40
+ t.test_files = FileList['spec/**/*_spec.rb']
41
+ t.verbose = true
42
+ end
43
+
44
+ task :default => :rcov
@@ -0,0 +1,90 @@
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
+ print " " * (task.indent.to_i - 1)
65
+ print (task.content =~ /\* /) ? '' : '- '
66
+ puts task
67
+ end
68
+ puts ""
69
+ end
70
+
71
+ def setup_todoist_base
72
+ if File.file?(File.expand_path('~/.todoistrc'))
73
+ require 'yaml'
74
+ conf = YAML.load_file(File.expand_path('~/.todoistrc'))
75
+ Todoist::Base.setup(conf['token'], conf['premium'])
76
+ else
77
+ puts <<-eos
78
+ You need to setup ~/.todoistrc, for example:
79
+ token: 91b1ce9e153fc5390ac3db4f0ded9f31a1a7ba23 # use your api key
80
+ premium: false # if this is true, ssl will be used.
81
+ eos
82
+ exit(1)
83
+ end
84
+ end
85
+
86
+ end
87
+
88
+ TodoistCLI.start
89
+
90
+ # vim: set ft=ruby:
@@ -0,0 +1,8 @@
1
+ module Todoist
2
+ end
3
+
4
+ dir = File.dirname(__FILE__)
5
+
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
@@ -0,0 +1,2 @@
1
+ require 'rubygems'
2
+ require 'bacon'
@@ -0,0 +1,7 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe 'Todoist' do
4
+ it "is" do
5
+ true.should.equal true
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,90 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: namelessjon-todoist
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jonathan Stott
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-02-15 00:00:00 -08:00
13
+ default_executable: todoist
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: highline
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ~>
22
+ - !ruby/object:Gem::Version
23
+ version: "1.5"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: thor
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: "0.9"
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: httparty
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: "0.3"
44
+ version:
45
+ 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.
46
+ email: jonathan.stott@gmail.com
47
+ executables:
48
+ - todoist
49
+ extensions: []
50
+
51
+ extra_rdoc_files: []
52
+
53
+ files:
54
+ - lib/todoist/base.rb
55
+ - lib/todoist/project.rb
56
+ - lib/todoist/task.rb
57
+ - lib/todoist.rb
58
+ - spec/spec_helper.rb
59
+ - spec/todoist_spec.rb
60
+ - Rakefile
61
+ - bin/todoist
62
+ has_rdoc: true
63
+ homepage: http://github.com/namelessjon/todoist
64
+ post_install_message:
65
+ rdoc_options:
66
+ - --inline-source
67
+ - --charset=UTF-8
68
+ require_paths:
69
+ - lib
70
+ required_ruby_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: "0"
75
+ version:
76
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: "0"
81
+ version:
82
+ requirements: []
83
+
84
+ rubyforge_project:
85
+ rubygems_version: 1.2.0
86
+ signing_key:
87
+ specification_version: 2
88
+ summary: a library for interacting with the Todoist public API
89
+ test_files: []
90
+