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.
Files changed (4) hide show
  1. data/bin/todos +14 -0
  2. data/lib/todos_cli.rb +235 -0
  3. data/lib/todos_database.rb +52 -0
  4. 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
+