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/History.md +137 -0
- data/Manifest +24 -0
- data/Rakefile +20 -0
- data/Readme.md +84 -0
- data/TODO +12 -0
- data/bin/todd +145 -0
- data/bin/todd.rb +145 -0
- data/bin/todd_old.rb +161 -0
- data/db/database.yml +2 -0
- data/db/migrations/0214102019_initialize_database.rb +48 -0
- data/db/migrations/0221102255_add_punch_table.rb +28 -0
- data/db/migrations/0222102121_remove_archived_tasks.rb +19 -0
- data/db/migrations/0222102124_add_archived_column_to_tasks.rb +15 -0
- data/db/todd.db +0 -0
- data/lib/todd.rb +10 -0
- data/lib/todd/config.rb +67 -0
- data/lib/todd/model.rb +254 -0
- data/lib/todd/util.rb +151 -0
- data/lib/todd/version.rb +3 -0
- data/tasks/enviroment.rake +5 -0
- data/tasks/migrate.rake +4 -0
- data/tasks/migration.rake +17 -0
- data/tasks/revert.rake +5 -0
- data/tasks/revert_db.rake +7 -0
- data/todd.gemspec +43 -0
- metadata +138 -0
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,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
data/lib/todd/config.rb
ADDED
|
@@ -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
|