todos 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.
- data/bin/todos +14 -0
- data/lib/todos_cli.rb +235 -0
- data/lib/todos_database.rb +52 -0
- metadata +76 -0
data/bin/todos
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'sequel'
|
6
|
+
|
7
|
+
DB_PATH = File.join(ENV['HOME'], 'todos.sqlite3')
|
8
|
+
DB = Sequel.connect("sqlite://#{DB_PATH}")
|
9
|
+
|
10
|
+
require 'todos_database.rb'
|
11
|
+
require 'todos_cli.rb'
|
12
|
+
|
13
|
+
Todos::Commands::GeneralCommands.run
|
14
|
+
|
data/lib/todos_cli.rb
ADDED
@@ -0,0 +1,235 @@
|
|
1
|
+
module Todos
|
2
|
+
|
3
|
+
module Asker
|
4
|
+
def self.multi(fields)
|
5
|
+
params = {}
|
6
|
+
fields.each do |field|
|
7
|
+
params.merge! single(field)
|
8
|
+
end
|
9
|
+
params
|
10
|
+
end
|
11
|
+
def self.single(field)
|
12
|
+
{field => ask(field)}
|
13
|
+
end
|
14
|
+
def self.ask(field)
|
15
|
+
print "#{field.to_s.capitalize.tr('_', ' ')}:_ "
|
16
|
+
gets.strip
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
module Menu
|
21
|
+
def self.append_features(klass)
|
22
|
+
def klass.menu
|
23
|
+
@commands ||= constants.reject{ |command| command =~ /basic/i }.collect { |command| const_get(command).extend(MenuItem) }.sort { |a, b| a::KEY <=> b::KEY }
|
24
|
+
print_menu
|
25
|
+
process_user_input
|
26
|
+
end
|
27
|
+
def klass.print_menu
|
28
|
+
puts
|
29
|
+
@commands.each do |command|
|
30
|
+
puts command.menu_item
|
31
|
+
end
|
32
|
+
end
|
33
|
+
def klass.process_user_input
|
34
|
+
print 'Command:_ '
|
35
|
+
user_input = gets.strip.downcase
|
36
|
+
puts
|
37
|
+
command = @commands.detect { |command| command::KEY.eql? user_input }
|
38
|
+
call_command(command) if command
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
module MenuItem
|
44
|
+
def menu_item
|
45
|
+
"#{self::KEY}\t#{self::DESCRIPTION}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
module Commands
|
50
|
+
|
51
|
+
module BasicList
|
52
|
+
def print_list(items)
|
53
|
+
items.each do |item|
|
54
|
+
puts item
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class GeneralCommands
|
60
|
+
class BasicTaskList
|
61
|
+
extend BasicList
|
62
|
+
def self.task_list_body(tasks)
|
63
|
+
unless tasks.empty?
|
64
|
+
print_list tasks
|
65
|
+
else
|
66
|
+
puts 'There are no tasks'
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.execute(conditions)
|
71
|
+
tasks = Task.with_all.filter(conditions)
|
72
|
+
task_list_body(tasks)
|
73
|
+
unless tasks.empty?
|
74
|
+
task = Task.filter(conditions.merge(Asker.single(:id)))
|
75
|
+
unless task.empty?
|
76
|
+
BasicTaskManipulation.run(task)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
class ListDoneTasks < BasicTaskList
|
83
|
+
KEY = 'd'
|
84
|
+
DESCRIPTION = 'List done tasks'
|
85
|
+
def self.execute
|
86
|
+
puts 'Done tasks'
|
87
|
+
task_list_body(Task.with_all.filter(:done => true))
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class ListUndoneTasks < BasicTaskList
|
92
|
+
KEY = 'l'
|
93
|
+
DESCRIPTION = 'List undone tasks'
|
94
|
+
def self.execute
|
95
|
+
puts 'Tasks to do'
|
96
|
+
super(:done => false)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
class AddTask
|
101
|
+
KEY = 'a'
|
102
|
+
DESCRIPTION = 'Add new task'
|
103
|
+
REQUIRED_ATTRIBUTES = [:description, :priority]
|
104
|
+
SUCCESS_MESSAGE = 'Task created'
|
105
|
+
FAIL_MESSAGE = 'Could not create task'
|
106
|
+
|
107
|
+
def self.execute
|
108
|
+
params = Asker.multi(REQUIRED_ATTRIBUTES)
|
109
|
+
Task.association_reflections.each do |k, v|
|
110
|
+
params.merge!(v[:key] => const_get(v[:class_name]).find_or_create(:name => Asker.ask(k)).id)
|
111
|
+
end
|
112
|
+
puts Task.insert(params).zero? ? FAIL_MESSAGE: SUCCESS_MESSAGE
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
class Exit
|
117
|
+
KEY = 'x'
|
118
|
+
DESCRIPTION = 'Exit the program'
|
119
|
+
def self.execute
|
120
|
+
exit
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
class ListContexts
|
125
|
+
KEY = 'c'
|
126
|
+
DESCRIPTION = 'List contexts'
|
127
|
+
extend BasicList
|
128
|
+
def self.execute
|
129
|
+
puts 'List of contests'
|
130
|
+
contexts = Context.active
|
131
|
+
unless contexts.empty?
|
132
|
+
print_list(contexts)
|
133
|
+
conditions = { :done => false }.merge(Asker.single(:context_id))
|
134
|
+
BasicTaskList.execute(conditions)
|
135
|
+
else
|
136
|
+
puts 'There are no contexts'
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
class ListProjects
|
142
|
+
KEY = 'p'
|
143
|
+
DESCRIPTION = 'List projects'
|
144
|
+
extend BasicList
|
145
|
+
def self.execute
|
146
|
+
puts 'List of projects'
|
147
|
+
projects = Project.active
|
148
|
+
unless projects.empty?
|
149
|
+
print_list(projects)
|
150
|
+
conditions = { :done => false }.merge(Asker.single(:project_id))
|
151
|
+
BasicTaskList.execute(conditions)
|
152
|
+
else
|
153
|
+
puts 'There are no projects'
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
include Menu
|
159
|
+
|
160
|
+
def self.run
|
161
|
+
loop { menu }
|
162
|
+
end
|
163
|
+
|
164
|
+
def self.call_command(command)
|
165
|
+
command.execute
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
class BasicTaskManipulation
|
170
|
+
|
171
|
+
module BasicMessages
|
172
|
+
FAIL_MESSAGE = 'Could not update task'
|
173
|
+
SUCCESS_MESSAGE = 'Task successfuly updated'
|
174
|
+
end
|
175
|
+
class RemoveTask
|
176
|
+
KEY = 'r'
|
177
|
+
DESCRIPTION = 'Remove task'
|
178
|
+
FAIL_MESSAGE = 'Could not remove task'
|
179
|
+
SUCCESS_MESSAGE = 'Task successfuly removed'
|
180
|
+
def self.execute(task)
|
181
|
+
task.delete
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
class MarkTaskDone
|
186
|
+
KEY = 'd'
|
187
|
+
DESCRIPTION = 'Mark task as done'
|
188
|
+
include BasicMessages
|
189
|
+
def self.execute(task)
|
190
|
+
task.update(:done => true)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
class ChangeTaskPriority
|
195
|
+
KEY = 'i'
|
196
|
+
DESCRIPTION = 'Change priority of the task'
|
197
|
+
include BasicMessages
|
198
|
+
def self.execute(task)
|
199
|
+
task.update(Asker.single(:priority))
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
class ChangeTaskProject
|
204
|
+
KEY = 'p'
|
205
|
+
DESCRIPTION = 'Change project of the task'
|
206
|
+
include BasicMessages
|
207
|
+
def self.execute(task)
|
208
|
+
project_id = Project.find_or_create(:name => Asker.ask(:project)).id
|
209
|
+
task.update(:project_id => project_id)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
class ChangeTaskContext
|
214
|
+
KEY = 'c'
|
215
|
+
DESCRIPTION = 'Change context of the task'
|
216
|
+
include BasicMessages
|
217
|
+
def self.execute(task)
|
218
|
+
context_id = Context.find_or_create(:name => Asker.ask(:context)).id
|
219
|
+
task.update(:context_id => context_id)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
include Menu
|
224
|
+
|
225
|
+
def self.run(task)
|
226
|
+
@task = task
|
227
|
+
menu
|
228
|
+
end
|
229
|
+
|
230
|
+
def self.call_command(command)
|
231
|
+
puts command.execute(@task).to_i.zero? ? command::FAIL_MESSAGE : command::SUCCESS_MESSAGE
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
unless DB.table_exists? :tasks
|
2
|
+
DB.create_table :tasks do
|
3
|
+
primary_key :id
|
4
|
+
TrueClass :done, :default => false
|
5
|
+
Integer :project_id, :default => 0, :null => false
|
6
|
+
Integer :context_id, :default => 0, :null => false
|
7
|
+
String :description, :default => '', :null => false, :size => 200
|
8
|
+
String :priority, :default => '', :null => false, :size => 1
|
9
|
+
end
|
10
|
+
DB.create_table :projects do
|
11
|
+
primary_key :id
|
12
|
+
String :name, :default => '', :null => false, :size => 50
|
13
|
+
end
|
14
|
+
DB.create_table :contexts do
|
15
|
+
primary_key :id
|
16
|
+
String :name, :default => '', :null => false, :size => 50
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Project < Sequel::Model
|
21
|
+
one_to_many :tasks
|
22
|
+
def to_s
|
23
|
+
"#{id}\t#{name.empty? ? 'n/a' : name}"
|
24
|
+
end
|
25
|
+
def self.active
|
26
|
+
select('projects.*'.lit).join(:tasks, :project_id => :id).group(:project_id).order('lower(name)'.lit).filter(:tasks__done => false)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Context < Sequel::Model
|
31
|
+
one_to_many :tasks
|
32
|
+
def to_s
|
33
|
+
"#{id}\t#{name.empty? ? 'n/a' : name}"
|
34
|
+
end
|
35
|
+
def self.active
|
36
|
+
select('contexts.*'.lit).join(:tasks, :context_id => :id).group(:context_id).order('lower(name)'.lit).filter(:tasks__done => false)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class Task < Sequel::Model
|
41
|
+
many_to_one :project
|
42
|
+
many_to_one :context
|
43
|
+
def to_s
|
44
|
+
"#{pk}\t#{description}\t" +
|
45
|
+
"#{' Project: ' + project.name unless project.name.empty?}" +
|
46
|
+
"#{' Context: ' + context.name unless context.name.empty?}" +
|
47
|
+
"#{' Priority: ' + priority unless priority.empty?}"
|
48
|
+
end
|
49
|
+
def self.with_all
|
50
|
+
select('tasks.*, projects.name, contexts.name'.lit).join(:projects, :id => :project_id).join(:contexts, :id => :tasks__context_id).order('done, length(priority) desc, lower(priority)'.lit)
|
51
|
+
end
|
52
|
+
end
|
metadata
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: todos
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Iwakura Taro
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-12-03 00:00:00 +02:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: sqlite3-ruby
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.2.4
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: sequel
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 3.6.0
|
34
|
+
version:
|
35
|
+
description: " Interactive command line application for managing list of tasks.\n Allow create new tasks, remove tasks, mark task as done and\n filter tasks on project or context.\n"
|
36
|
+
email: taro@mail333.com
|
37
|
+
executables:
|
38
|
+
- todos
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files: []
|
42
|
+
|
43
|
+
files:
|
44
|
+
- lib/todos_cli.rb
|
45
|
+
- lib/todos_database.rb
|
46
|
+
- bin/todos
|
47
|
+
has_rdoc: true
|
48
|
+
homepage:
|
49
|
+
licenses: []
|
50
|
+
|
51
|
+
post_install_message:
|
52
|
+
rdoc_options: []
|
53
|
+
|
54
|
+
require_paths:
|
55
|
+
- lib
|
56
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: "0"
|
61
|
+
version:
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: "0"
|
67
|
+
version:
|
68
|
+
requirements:
|
69
|
+
- sqlite3-3.6.19 or greater
|
70
|
+
rubyforge_project:
|
71
|
+
rubygems_version: 1.3.5
|
72
|
+
signing_key:
|
73
|
+
specification_version: 3
|
74
|
+
summary: Interactive CLI tool for managing list of tasks
|
75
|
+
test_files: []
|
76
|
+
|