timetrap 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE.txt +23 -0
- data/README.md +183 -0
- data/Rakefile +60 -0
- data/VERSION.yml +4 -0
- data/bin/dev_t +4 -0
- data/bin/t +11 -0
- data/lib/timetrap/cli.rb +227 -0
- data/lib/timetrap/formatters/csv.rb +19 -0
- data/lib/timetrap/formatters/ical.rb +29 -0
- data/lib/timetrap/formatters/text.rb +55 -0
- data/lib/timetrap/helpers.rb +59 -0
- data/lib/timetrap/models.rb +80 -0
- data/lib/timetrap.rb +80 -0
- data/spec/timetrap_spec.rb +622 -0
- metadata +129 -0
@@ -0,0 +1,80 @@
|
|
1
|
+
module Timetrap
|
2
|
+
class Entry < Sequel::Model
|
3
|
+
plugin :schema
|
4
|
+
|
5
|
+
class << self
|
6
|
+
# a class level instance variable that controls whether or not all entries
|
7
|
+
# should respond to #start and #end with times rounded to 15 minute
|
8
|
+
# increments.
|
9
|
+
attr_accessor :round
|
10
|
+
end
|
11
|
+
|
12
|
+
def round?
|
13
|
+
!!self.class.round
|
14
|
+
end
|
15
|
+
|
16
|
+
def start= time
|
17
|
+
self[:start]= Chronic.parse(time) || time
|
18
|
+
end
|
19
|
+
|
20
|
+
def end= time
|
21
|
+
self[:end]= Chronic.parse(time) || time
|
22
|
+
end
|
23
|
+
|
24
|
+
def start
|
25
|
+
round? ? rounded_start : self[:start]
|
26
|
+
end
|
27
|
+
|
28
|
+
def end
|
29
|
+
round? ? rounded_end : self[:end]
|
30
|
+
end
|
31
|
+
|
32
|
+
def end_or_now
|
33
|
+
self.end || (round? ? round(Time.now) : Time.now)
|
34
|
+
end
|
35
|
+
|
36
|
+
def rounded_start
|
37
|
+
round(self[:start])
|
38
|
+
end
|
39
|
+
|
40
|
+
def rounded_end
|
41
|
+
round(self[:end])
|
42
|
+
end
|
43
|
+
|
44
|
+
def round time
|
45
|
+
return nil unless time
|
46
|
+
Time.at(
|
47
|
+
if (r = time.to_i % 900) < 450
|
48
|
+
time.to_i - r
|
49
|
+
else
|
50
|
+
time.to_i + (900 - r)
|
51
|
+
end
|
52
|
+
)
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.sheets
|
56
|
+
map{|e|e.sheet}.uniq.sort
|
57
|
+
end
|
58
|
+
|
59
|
+
# do a quick pseudo migration. This should only get executed on the first run
|
60
|
+
set_schema do
|
61
|
+
primary_key :id
|
62
|
+
column :note, :string
|
63
|
+
column :start, :timestamp
|
64
|
+
column :end, :timestamp
|
65
|
+
column :sheet, :string
|
66
|
+
end
|
67
|
+
create_table unless table_exists?
|
68
|
+
end
|
69
|
+
|
70
|
+
class Meta < Sequel::Model(:meta)
|
71
|
+
plugin :schema
|
72
|
+
|
73
|
+
set_schema do
|
74
|
+
primary_key :id
|
75
|
+
column :key, :string
|
76
|
+
column :value, :string
|
77
|
+
end
|
78
|
+
create_table unless table_exists?
|
79
|
+
end
|
80
|
+
end
|
data/lib/timetrap.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'chronic'
|
3
|
+
require 'sequel'
|
4
|
+
require 'sequel/extensions/inflector'
|
5
|
+
require 'Getopt/Declare'
|
6
|
+
require File.join(File.dirname(__FILE__), 'timetrap', 'helpers')
|
7
|
+
require File.join(File.dirname(__FILE__), 'timetrap', 'cli')
|
8
|
+
DB_NAME = defined?(TEST_MODE) ? nil : "#{ENV['HOME']}/.timetrap.db"
|
9
|
+
# connect to database. This will create one if it doesn't exist
|
10
|
+
DB = Sequel.sqlite DB_NAME
|
11
|
+
require File.join(File.dirname(__FILE__), 'timetrap', 'models')
|
12
|
+
Dir["#{File.dirname(__FILE__)}/timetrap/formatters/*.rb"].each do |path|
|
13
|
+
require path
|
14
|
+
end
|
15
|
+
module Timetrap
|
16
|
+
extend self
|
17
|
+
|
18
|
+
def current_sheet= sheet
|
19
|
+
m = Meta.find_or_create(:key => 'current_sheet')
|
20
|
+
m.value = sheet
|
21
|
+
m.save
|
22
|
+
end
|
23
|
+
|
24
|
+
def current_sheet
|
25
|
+
unless Meta.find(:key => 'current_sheet')
|
26
|
+
Meta.create(:key => 'current_sheet', :value => 'default')
|
27
|
+
end
|
28
|
+
Meta.find(:key => 'current_sheet').value
|
29
|
+
end
|
30
|
+
|
31
|
+
def entries sheet = nil
|
32
|
+
Entry.filter(:sheet => sheet).order_by(:start)
|
33
|
+
end
|
34
|
+
|
35
|
+
def running?
|
36
|
+
!!active_entry
|
37
|
+
end
|
38
|
+
|
39
|
+
def active_entry
|
40
|
+
Entry.find(:sheet => Timetrap.current_sheet, :end => nil)
|
41
|
+
end
|
42
|
+
|
43
|
+
def stop time = nil
|
44
|
+
while a = active_entry
|
45
|
+
time ||= Time.now
|
46
|
+
a.end = time
|
47
|
+
a.save
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def start note, time = nil
|
52
|
+
raise AlreadyRunning if running?
|
53
|
+
time ||= Time.now
|
54
|
+
Entry.create(:sheet => Timetrap.current_sheet, :note => note, :start => time).save
|
55
|
+
rescue => e
|
56
|
+
CLI.say e.message
|
57
|
+
end
|
58
|
+
|
59
|
+
def switch sheet
|
60
|
+
self.current_sheet = sheet
|
61
|
+
end
|
62
|
+
|
63
|
+
def kill_sheet sheet
|
64
|
+
Entry.filter(:sheet => sheet).destroy
|
65
|
+
end
|
66
|
+
|
67
|
+
def format format_klass, entries
|
68
|
+
format_klass.new(entries).output
|
69
|
+
end
|
70
|
+
|
71
|
+
class AlreadyRunning < StandardError
|
72
|
+
def message
|
73
|
+
"Timetrap is already running"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
CLI.args = Getopt::Declare.new(<<-EOF)
|
78
|
+
#{CLI::USAGE}
|
79
|
+
EOF
|
80
|
+
end
|