timetrap 1.1.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/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
|