todd 0.0.9

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/todd_old.rb ADDED
@@ -0,0 +1,161 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'todd_model'
4
+ require 'todd_util'
5
+ require 'digest/md5'
6
+ require 'pp'
7
+
8
+ config = {
9
+ :default_db_path => "/home/carl/dev/todd/test.db",
10
+ :config_filename => ".todd",
11
+ :default_category => "default",
12
+ :default_config => "todd_hash: %s\n",
13
+ :output_format => "minimal"
14
+ }
15
+
16
+ module Docs
17
+ Short = {
18
+ :init => ["todd init ", "Initialize a Todd list in the local directory."],
19
+ :add => ["todd add <string> ", "Add an item to the local Todd list."],
20
+ :rm => ["todd rm <id> ", "Remove an item from the local todo list."],
21
+ :list => ["todd list ", "List all items in all categories."],
22
+ :find => ["todd find <query> ", "List all items which match the query."],
23
+ :start => ["todd start <id> ", "Start the timer on task with id == <id>."],
24
+ :stop => ["todd stop <id> ", "Stop the timer on task with id == <id>."],
25
+ :listd => ["todd listd ", "List all deleted tasks."]
26
+ }
27
+ end
28
+
29
+ def docs_for(method)
30
+ puts "%s\t%s" % Docs::Short[method.to_sym]
31
+ end
32
+
33
+ class Todd
34
+
35
+ @config = {}
36
+
37
+ def initialize(config)
38
+ @config = config
39
+ @todo_list = TodoList.first(:conditions => { :md5_id => @config[:todd_hash] })
40
+ end
41
+
42
+ # COMMANDS
43
+
44
+ def init
45
+ if File.exists?(@config[:config_filename])
46
+ puts "Todd is already initialized in the local directory"
47
+ return
48
+ end
49
+
50
+ current_dir = Dir.getwd
51
+ current_dir_md5 = Digest::MD5.hexdigest(current_dir)
52
+
53
+ TodoList.create(:md5_id => current_dir_md5)
54
+
55
+ File.open(@config[:config_filename], 'w') do |f|
56
+ f.write(sprintf(@config[:default_config],current_dir_md5))
57
+ end
58
+
59
+ puts "Initialized Todd in #{current_dir}"
60
+ end
61
+
62
+ def add task_str
63
+ category_name = @config[:default_category]
64
+ task_str = task_str.sub(/#(\S*)/) do |cat|
65
+ puts "Cat name: #{$1}"
66
+ category_name = $1
67
+ ""
68
+ end
69
+ task_str.strip!
70
+
71
+ puts "Task: #{task_str}"
72
+
73
+ category = @todo_list.categories.find_or_create_by_name(category_name)
74
+ task = category.tasks.create(:title => task_str)
75
+ end
76
+
77
+ def rm id
78
+ t = Task.find(id).destroy
79
+ end
80
+
81
+ def list
82
+ puts format_bundle @todo_list.bundle, @config[:output_format]
83
+ end
84
+
85
+ def find
86
+ end
87
+
88
+ def start id
89
+ t = Task.find(id).start!
90
+ puts t ? "Task #{t.id} started" : "Task already running"
91
+ end
92
+
93
+ def stop id
94
+ t = Task.find(id).stop!
95
+ puts t ? "Task #{t.id} stopped" : "Task already stopped"
96
+ puts format_bundle t.bundle, @config[:output_format] if t
97
+ end
98
+
99
+ def listd
100
+ puts "Not Implemented"
101
+ end
102
+
103
+ def restore
104
+ puts "Not Implemented"
105
+ end
106
+
107
+ def add_remote
108
+ puts "Not Implemented"
109
+ end
110
+
111
+ def sync
112
+ puts "Not Implemented"
113
+ end
114
+ end
115
+
116
+ # Parse the .todd file if it exists
117
+
118
+ if File.exists? config[:config_filename]
119
+ begin
120
+ conf = YAML.load(File.open(config[:config_filename], 'r').read)
121
+ conf.each { |key, val|
122
+ config[key.to_sym] = val
123
+ } unless conf == nil
124
+ rescue ScriptError=>e
125
+ warn("Error reading #{config[:config_filename]}, you might have an error in your local .todd file")
126
+ puts "Exception Trace:"
127
+ pp e
128
+ exit
129
+ end
130
+ else
131
+ unless ARGV.include? "init"
132
+ warn("Error finding #{config[:config_filename]}")
133
+ exit
134
+ end
135
+ end
136
+
137
+ todd = Todd.new(config)
138
+
139
+ if ARGV.length > 0
140
+ command = ARGV.shift
141
+
142
+ break if command == "help"
143
+
144
+ begin
145
+ todd.send(command.to_sym, *ARGV)
146
+ rescue ArgumentError => e
147
+ puts e
148
+ pp e.backtrace
149
+ docs_for command
150
+ end
151
+
152
+ exit
153
+ end
154
+
155
+ ## show help
156
+
157
+ puts "Usage: todd [OPTION] [ARGS]"
158
+
159
+ Docs::Short.keys.each do |command|
160
+ docs_for command
161
+ end
data/db/database.yml ADDED
@@ -0,0 +1,2 @@
1
+ adapter: sqlite3
2
+ database: /home/carl/dev/todd/db/todd.db
@@ -0,0 +1,48 @@
1
+ class InitializeDatabase < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :todo_lists do |t|
4
+ t.string :md5_id, :null => false
5
+
6
+ t.timestamps
7
+ end
8
+
9
+ create_table :categories do |t|
10
+ t.string :name, :null => false
11
+
12
+ t.references :todo_list
13
+
14
+ t.timestamps
15
+ end
16
+
17
+ create_table :tasks do |t|
18
+ t.string :title, :null => false
19
+ t.text :notes, :default => ""
20
+ t.boolean :running, :default => false
21
+ t.datetime :start_time
22
+ t.datetime :total_time, :default => Time.at(0)
23
+
24
+ t.references :category
25
+
26
+ t.timestamps
27
+ end
28
+
29
+ create_table :archived_tasks do |t|
30
+ t.string :title, :null => false
31
+ t.text :notes, :default => ""
32
+ t.boolean :running, :default => false
33
+ t.datetime :start_time
34
+ t.datetime :total_time, :default => Time.at(0)
35
+
36
+ t.references :category
37
+
38
+ t.timestamps
39
+ end
40
+ end
41
+
42
+ def self.down
43
+ drop_table :archived_tasks
44
+ drop_table :tasks
45
+ drop_table :categories
46
+ drop_table :todo_lists
47
+ end
48
+ end
@@ -0,0 +1,28 @@
1
+ class AddPunchTable < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :punches do |t|
4
+ t.boolean :running, :default => false
5
+ t.datetime :start
6
+ t.datetime :stop
7
+
8
+ t.references :task
9
+ end
10
+
11
+ remove_column :tasks, :start_time
12
+ add_column :tasks, :current_punch, :integer
13
+
14
+ remove_column :archived_tasks, :start_time
15
+ add_column :archived_tasks, :current_punch, :integer
16
+
17
+ end
18
+
19
+ def self.down
20
+ drop_table :punches
21
+
22
+ add_column :tasks, :start_time, :boolean
23
+ remove_column :tasks, :current_punch
24
+
25
+ add_column :archived_tasks, :start_time, :boolean
26
+ remove_column :archived_tasks, :current_punch
27
+ end
28
+ end
@@ -0,0 +1,19 @@
1
+ class RemoveArchivedTasks < ActiveRecord::Migration
2
+ def self.up
3
+ drop_table :archived_tasks
4
+ end
5
+
6
+ def self.down
7
+ create_table :archived_tasks do |t|
8
+ t.string :title, :null => false
9
+ t.text :notes, :default => ""
10
+ t.boolean :running, :default => false
11
+ t.datetime :total_time, :default => Time.at(0)
12
+ t.integer :current_punch
13
+
14
+ t.references :category
15
+
16
+ t.timestamps
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,15 @@
1
+ class AddArchivedColumnToTasks < ActiveRecord::Migration
2
+ def self.up
3
+ add_column :tasks, :archived, :boolean, :default => false
4
+
5
+ Todd::Task.reset_column_information
6
+ Todd::Task.find_each do |t|
7
+ t.archived = false
8
+ t.save
9
+ end
10
+ end
11
+
12
+ def self.down
13
+ remove_column :tasks, :archived
14
+ end
15
+ end
data/db/todd.db ADDED
Binary file
data/lib/todd.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'digest/md5'
3
+ require 'pp'
4
+ require 'active_record'
5
+ require 'singleton'
6
+
7
+ require 'todd/util'
8
+ require 'todd/model'
9
+ require 'todd/version'
10
+ require 'todd/config'
@@ -0,0 +1,67 @@
1
+ module Todd
2
+ class Base
3
+ include Singleton
4
+
5
+ def self.setup
6
+ self.instance
7
+ end
8
+
9
+ def self.[](key)
10
+ @__instance__.config[key]
11
+ end
12
+
13
+ attr_reader :config
14
+
15
+ def initialize
16
+ @config = {
17
+ :default_db_path => "/home/carl/dev/todd/test.db",
18
+ :config_filename => ".todd",
19
+ :default_category => "default",
20
+ :default_config => "todd_hash: %s\n",
21
+ :output_format => "minimal"
22
+ }
23
+
24
+ if File.exists? @config[:config_filename]
25
+ begin
26
+ conf = YAML.load(File.open(@config[:config_filename], 'r').read)
27
+ conf.each { |key, val|
28
+ @config[key.to_sym] = val
29
+ } unless conf == nil
30
+ rescue scripterror => e
31
+ warn("error reading #{@config[:config_filename]}, you might have an error in your local .todd file.")
32
+ puts "exception trace:"
33
+ pp e
34
+ exit
35
+ end
36
+ else
37
+ unless ARGV.include? "init"
38
+ warn("Error finding #{@config[:config_filename]}")
39
+ warn("If you want to use Todd in this dir, please run todd init")
40
+ exit
41
+ end
42
+ end
43
+ end
44
+
45
+ def self.init_local_dir
46
+ if File.exists? self[:config_filename]
47
+ warn "Todd is already initialized in the local directory"
48
+ return
49
+ end
50
+
51
+ current_dir = Dir.getwd
52
+ current_dir_md5 = Digest::MD5.hexdigest(current_dir)
53
+
54
+ TodoList.create(:md5_id => current_dir_md5)
55
+
56
+ File.open(self[:config_filename], 'w') do |f|
57
+ f.write(sprintf(self[:default_config], current_dir_md5))
58
+ end
59
+
60
+ puts "Initialized Todd in #{current_dir}"
61
+ end
62
+
63
+ def self.set_formatting format
64
+ @__instance__.config[:output_format] = format
65
+ end
66
+ end
67
+ end
data/lib/todd/model.rb ADDED
@@ -0,0 +1,254 @@
1
+ module Todd
2
+
3
+ class DB
4
+
5
+ def self.initialize_db
6
+ conf_path = File.dirname(__FILE__) + '/../../db/database.yml'
7
+ ActiveRecord::Base.establish_connection(YAML::load(File.open(conf_path)))
8
+ ActiveRecord::Base.logger = Logger.new(File.open('logs/database.log', 'a'))
9
+ ActiveRecord::Base.colorize_logging = false
10
+ end
11
+
12
+ def self.get_current_list
13
+ TodoList.first(:conditions => { :md5_id => Base[:todd_hash] })
14
+ end
15
+
16
+ def self.find query, category_ids, archived = false
17
+ found = []
18
+ query ||= 'last'
19
+
20
+ case query
21
+ when 'all'
22
+ found = Task.find(:all, :conditions => {:category_id => category_ids, :archived => archived})
23
+ when 'last'
24
+ found << Task.find(:first, :conditions => {:category_id => category_ids, :archived => archived}, :order => "updated_at DESC")
25
+ when 'active', 'running', 'started'
26
+ found = Task.find(:all, :conditions => {:category_id => category_ids, :running => true, :archived => archived})
27
+ when 'inactive', 'stopped'
28
+ found = Task.find(:all, :conditions => {:category_id => category_ids, :running => false, :archived => archived})
29
+ when /(\d+)\.\.(\d+)/
30
+ found = Task.find(:all, :conditions => {:category_id => category_ids, :id => $1.to_i..$2.to_i, :archived => archived})
31
+ when /(\d+)/
32
+ found << Task.find($1.to_i, :conditions => {:category_id => category_ids, :archived => archived})
33
+ else
34
+ warn "Invalid query: #{query}"
35
+ exit
36
+ end
37
+
38
+ raise ActiveRecord::RecordNotFound if found == [nil]
39
+
40
+ if block_given?
41
+ found.each do |f|
42
+ yield f
43
+ end
44
+ end
45
+
46
+ found
47
+ rescue ActiveRecord::RecordNotFound => e
48
+ puts "Not task matched query"
49
+ end
50
+
51
+ end
52
+
53
+ class TodoList < ActiveRecord::Base
54
+ has_many :categories, :dependent => :destroy
55
+ has_many :tasks, :through => :categories
56
+ validates_uniqueness_of :md5_id
57
+
58
+ def category_ids
59
+ return @_category_ids if @_category_ids
60
+
61
+ @_category_ids = []
62
+ self.categories.find_each do |cat|
63
+ @_category_ids << cat.id
64
+ end
65
+
66
+ @_category_ids
67
+ end
68
+
69
+ def bundle query = nil, archived = false
70
+ bund = {
71
+ :type => :todolist,
72
+ :categories => []
73
+ }
74
+
75
+ self.categories.each do |cat|
76
+ next unless cat.visible?
77
+ sub_bundle = cat.bundle(query, archived)
78
+ bund[:categories] << sub_bundle unless sub_bundle[:tasks] == []
79
+ end
80
+
81
+ bund
82
+ end
83
+
84
+ def add task_str
85
+ category_name = Base[:default_category]
86
+ task_str = task_str.sub(/#(\S*)/) do |cat|
87
+ puts "Category: #{$1}"
88
+ category_name = $1
89
+ "" # this kills the category in the task_str
90
+ end
91
+ task_str.strip!
92
+
93
+ puts "Task #{task_str}"
94
+
95
+ category = self.categories.find_or_create_by_name(category_name)
96
+ task = category.tasks.create(:title => task_str)
97
+ end
98
+
99
+ def rm query
100
+ DB.find(query, category_ids) do |t|
101
+ puts "Removed Task #{t.id}"
102
+ t.destroy
103
+ end
104
+ end
105
+
106
+ def archive query
107
+ DB.find(query, category_ids) do |t|
108
+ t = t.archive!
109
+ puts t ? "Archived Task #{t.id}" : "Cannot archive active task"
110
+ end
111
+ end
112
+
113
+ def restore query
114
+ DB.find(query, category_ids, true) do |t|
115
+ puts "Restored Task #{t.id}"
116
+ t.restore!
117
+ end
118
+ end
119
+
120
+ def start query
121
+ DB.find(query, category_ids) do |t|
122
+ t = t.start!
123
+ puts t ? "Task #{t.id} started" : "Task already running"
124
+ end
125
+ end
126
+
127
+ def stop query
128
+ DB.find(query, category_ids) do |t|
129
+ t = t.stop!
130
+ puts t ? "Task #{t.id} stopped" : "Task already stopped"
131
+ puts Util.format_bundle t.bundle if t
132
+ end
133
+ end
134
+
135
+
136
+ end
137
+
138
+ class Category < ActiveRecord::Base
139
+ belongs_to :todo_list
140
+ has_many :tasks, :dependent => :destroy
141
+ has_many :archived_tasks
142
+ validates_uniqueness_of :name
143
+
144
+ def bundle query = nil, archived = false
145
+ bund = {
146
+ :type => :category,
147
+ :name => name,
148
+ :tasks => []
149
+ }
150
+
151
+ found = (query != nil) ? DB.find(query, [id], archived) : self.tasks
152
+ found.each do |task|
153
+ bund[:tasks] << task.bundle if task.archived == archived
154
+ end
155
+
156
+ bund
157
+ end
158
+
159
+ def visible?
160
+ (tasks.count > 0)
161
+ end
162
+ end
163
+
164
+ class Task < ActiveRecord::Base
165
+ belongs_to :category
166
+
167
+ def bundle
168
+ {
169
+ :type => :task,
170
+ :id => id,
171
+ :title => title,
172
+ :active => running,
173
+ :session_time => session_time,
174
+ :total_time => total_time
175
+ }
176
+ end
177
+
178
+ def start!
179
+ return nil if running
180
+
181
+ punch = Punch.new
182
+ punch.clock_in!
183
+
184
+ self[:current_punch] = punch.id
185
+ toggle(:running)
186
+
187
+ self.save
188
+
189
+ self
190
+ end
191
+
192
+ def stop!
193
+ return nil if !running
194
+
195
+ punch = Punch.find(current_punch)
196
+ session_time = punch.clock_out!
197
+
198
+ self[:total_time] += session_time if session_time
199
+ toggle(:running)
200
+
201
+ self.save
202
+
203
+ self
204
+ end
205
+
206
+ def archive!
207
+ return nil if running
208
+
209
+ self[:archived] = true
210
+ self.save
211
+
212
+ self
213
+ end
214
+
215
+ def restore!
216
+ self[:archived] = false
217
+ self.save
218
+
219
+ self
220
+ end
221
+
222
+ def session_time
223
+ running ? Punch.find(current_punch).session_time : 0
224
+ end
225
+ end
226
+
227
+ class Punch < ActiveRecord::Base
228
+ belongs_to :task
229
+
230
+ def clock_in!
231
+ return false if running
232
+ self[:start] = Time.now
233
+ toggle(:running)
234
+
235
+ self.save
236
+
237
+ true
238
+ end
239
+
240
+ def clock_out!
241
+ return nil unless running
242
+ self[:stop] = Time.now
243
+ toggle(:running)
244
+
245
+ self.save
246
+
247
+ session_time
248
+ end
249
+
250
+ def session_time
251
+ running ? (Time.now - start) : (stop - start)
252
+ end
253
+ end
254
+ end