timetrap 1.5.3 → 1.6.0
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/.gitignore +1 -0
- data/README.md +114 -92
- data/VERSION.yml +2 -2
- data/lib/timetrap.rb +1 -61
- data/lib/timetrap/cli.rb +89 -64
- data/lib/timetrap/config.rb +2 -2
- data/lib/timetrap/helpers.rb +14 -6
- data/lib/timetrap/timer.rb +55 -0
- data/spec/spec.opts +1 -0
- data/spec/timetrap_spec.rb +201 -164
- data/timetrap.gemspec +3 -2
- metadata +6 -5
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -4,18 +4,8 @@ Timetrap
|
|
4
4
|
Timetrap is a simple command line time tracker written in ruby. It provides an
|
5
5
|
easy to use command line interface for tracking what you spend your time on.
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
natural language times (e.g. "30 minutes ago"), additional commands such as
|
10
|
-
`archive` and `configure`, and support for rounding.
|
11
|
-
|
12
|
-
Timetrap is also able to export entries to several formats (e.g. ical, csv) and
|
13
|
-
is designed to be easily extended to support additional export formats, by
|
14
|
-
creating a new formatter class (in ruby.)
|
15
|
-
|
16
|
-
Timetrap maintains its state in a sqlite3 database.
|
17
|
-
|
18
|
-
Timetrap is available as a gem on gemcutter (http://gemcutter.org/gems/timetrap)
|
7
|
+
Getting Started
|
8
|
+
---------------
|
19
9
|
|
20
10
|
To install:
|
21
11
|
|
@@ -23,67 +13,88 @@ To install:
|
|
23
13
|
|
24
14
|
This will place a ``t`` executable in your path.
|
25
15
|
|
26
|
-
|
27
|
-
http://bitbucket.org/trevor/timebook/src/
|
16
|
+
### Basic Usage
|
28
17
|
|
18
|
+
$ # get help
|
19
|
+
$ t --help
|
29
20
|
|
30
|
-
|
31
|
-
--------
|
21
|
+
Timetrap maintains a list of *timesheets*.
|
32
22
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
Usage
|
55
|
-
-----
|
56
|
-
|
57
|
-
The basic usage is as follows:
|
58
|
-
|
59
|
-
$ # create the "writing" timesheet
|
60
|
-
$ t switch writing
|
61
|
-
|
62
|
-
$ # check in 5 minutes ago
|
63
|
-
$ t in document timetrap --at "10 minutes ago"
|
64
|
-
|
65
|
-
$ # check out
|
66
|
-
$ t out
|
23
|
+
$ # create the "coding" timesheet
|
24
|
+
$ t sheet coding
|
25
|
+
Switching to sheet coding
|
26
|
+
|
27
|
+
All commands can be abbreviated.
|
28
|
+
|
29
|
+
$ # same as "t sheet coding"
|
30
|
+
$ t s coding
|
31
|
+
Switching to sheet coding
|
32
|
+
|
33
|
+
Each timesheet contains *entries*. Each entry has a start and end time, and a
|
34
|
+
note associated with it. An entry without an end time set is considered to be
|
35
|
+
*running*.
|
36
|
+
|
37
|
+
You check in to the current sheet with the `in` command.
|
38
|
+
|
39
|
+
$ # check in with "document timetrap" note
|
40
|
+
$ t in document timetrap
|
41
|
+
Checked into sheet "coding".
|
42
|
+
|
43
|
+
Commands like `display` and `now` will show you the running entry.
|
67
44
|
|
68
|
-
$ # view current timesheet
|
69
45
|
$ t display
|
46
|
+
Timesheet: coding
|
47
|
+
Day Start End Duration Notes
|
48
|
+
Sun Nov 28, 2010 12:26:10 - 0:00:03 document timetrap
|
49
|
+
0:00:03
|
50
|
+
---------------------------------------------------------
|
51
|
+
Total 0:00:03
|
52
|
+
|
53
|
+
$ t now
|
54
|
+
*coding: 0:01:02 (document timetrap)
|
55
|
+
|
56
|
+
If you make a mistake use the `edit` command.
|
57
|
+
|
58
|
+
$ # edit the running entry's note
|
59
|
+
$ t edit writing readme
|
60
|
+
editing entry #42
|
61
|
+
|
62
|
+
You check out with the `out` command.
|
63
|
+
|
64
|
+
$ t out
|
65
|
+
Checked out of sheet "coding"
|
66
|
+
|
67
|
+
### Natural Language Times
|
68
|
+
|
69
|
+
Commands such as `in`, `out`, `edit`, and `display` have flags that accept
|
70
|
+
times as arguments. Any time you pass Timetrap a time it will try to parse it
|
71
|
+
as a natural language time.
|
72
|
+
|
73
|
+
This is very handy if you start working and forget to start Timetrap. You can
|
74
|
+
check in 5 minutes ago using `in`'s `--at` flag.
|
75
|
+
|
76
|
+
$ t in --at "5 minutes ago"
|
70
77
|
|
71
|
-
|
72
|
-
(or creates it if it does not exist). ``t in document timetrap --at "10 minutes
|
73
|
-
ago"`` creates a new period in the current timesheet, and annotates it with the
|
74
|
-
description "document timetrap". The optional ``--at`` flag can be passed to start
|
75
|
-
the entry at a time other than the present. The ``--at`` flag is able to parse
|
76
|
-
natural language times (via Chronic: http://chronic.rubyforge.org/) and will
|
77
|
-
understand 'friday 13:00', 'mon 2:35', '4pm', etc. (also true of the ``edit``
|
78
|
-
command's ``--start`` and ``--end`` flags.) Note that this command would be in
|
79
|
-
error if the ``writing`` timesheet was already active. Finally, ``t out``
|
80
|
-
records the current time as the end time for the most recent period in the
|
81
|
-
``writing`` timesheet.
|
78
|
+
Command line flags also have short versions.
|
82
79
|
|
83
|
-
|
80
|
+
$ # equivilent to the command above
|
81
|
+
$ t i -a "5 minutes ago"
|
82
|
+
|
83
|
+
You can consult the Chronic gem (http://chronic.rubyforge.org/) for a full
|
84
|
+
list of parsable time formats, but all of these should work.
|
85
|
+
|
86
|
+
$ t out --at "in 30 minutes"
|
87
|
+
$ t edit --start "last monday at 10:30am"
|
88
|
+
$ t edit --end "tomorrow at noon"
|
89
|
+
$ t display --start "10am" --end "2pm"
|
90
|
+
$ t i -a "2010-11-29 12:30:00"
|
91
|
+
|
92
|
+
### Output Formats
|
93
|
+
|
94
|
+
Timetrap supports several output formats. The default is a plain text format.
|
84
95
|
|
85
96
|
$ t display
|
86
|
-
Timesheet:
|
97
|
+
Timesheet: coding
|
87
98
|
Day Start End Duration Notes
|
88
99
|
Mon Apr 13, 2009 15:46:51 - 17:03:50 1:16:59 improved display functionality
|
89
100
|
17:25:59 - 17:26:02 0:00:03
|
@@ -91,20 +102,31 @@ To display the current timesheet, invoke the ``t display`` command::
|
|
91
102
|
22:37:38 - 23:38:43 1:01:05 work on kill
|
92
103
|
2:18:52
|
93
104
|
Tue Apr 14, 2009 00:41:16 - 01:40:19 0:59:03 gem packaging
|
94
|
-
10:20:00 - 10:48:10 0:28:10
|
105
|
+
10:20:00 - 10:48:10 0:28:10 working on readme
|
95
106
|
1:27:13
|
96
107
|
---------------------------------------------------------
|
97
108
|
Total 3:46:05
|
98
109
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
110
|
+
You can also output csv for easy import into a spreadsheet.
|
111
|
+
|
112
|
+
$ t display --format csv
|
113
|
+
start,end,note,sheet
|
114
|
+
"2010-08-21 11:19:05","2010-08-21 12:12:04","migrated site","coding"
|
115
|
+
"2010-08-21 12:44:09","2010-08-21 12:48:46","DNS emails and install email packages","coding"
|
116
|
+
"2010-08-21 12:49:57","2010-08-21 13:10:12","A records","coding"
|
117
|
+
"2010-08-21 15:09:37","2010-08-21 16:32:26","setup for wiki","coding"
|
118
|
+
"2010-08-25 20:42:55","2010-08-25 21:41:49","rewrote index","coding"
|
119
|
+
"2010-08-29 15:44:39","2010-08-29 16:21:53","recaptcha","coding"
|
120
|
+
"2010-08-29 21:15:58","2010-08-29 21:30:31","backups","coding"
|
121
|
+
"2010-08-29 21:40:56","2010-08-29 22:32:26","backups","coding"
|
122
|
+
|
123
|
+
Or to ical format for import into a calendar program (remember commands can be abbreviated).
|
124
|
+
|
125
|
+
$ t d -f ical > MyTimeSheet.ics
|
104
126
|
|
105
127
|
Commands
|
106
128
|
--------
|
107
|
-
**
|
129
|
+
**archive**
|
108
130
|
Archives the selected entries (by moving them to a sheet called ``_[SHEET]``)
|
109
131
|
These entries can be seen by running ``t display _[SHEET]``.
|
110
132
|
usage: ``t archive [--start DATE] [--end DATE] [SHEET]``
|
@@ -145,10 +167,6 @@ Commands
|
|
145
167
|
|
146
168
|
usage: ``t edit [--id ID] [--start TIME] [--end TIME] [--append] [NOTES]``
|
147
169
|
|
148
|
-
**format**
|
149
|
-
Deprecated
|
150
|
-
Alias for display
|
151
|
-
|
152
170
|
**in**
|
153
171
|
Start the timer for the current timesheet. Must be called before out. Notes
|
154
172
|
may be specified for this period. This is exactly equivalent to
|
@@ -168,32 +186,27 @@ Commands
|
|
168
186
|
usage: ``t list``
|
169
187
|
|
170
188
|
**now**
|
171
|
-
Print
|
172
|
-
active and what notes are associated with the current period.
|
189
|
+
Print a description of all running entries.
|
173
190
|
|
174
191
|
usage: ``t now``
|
175
192
|
|
176
193
|
**out**
|
177
194
|
Stop the timer for the current timesheet. Must be called after in. Accepts an
|
178
|
-
optional --at flag.
|
179
|
-
|
180
|
-
usage: ``t out [--at TIME]``
|
181
|
-
|
182
|
-
**running**
|
183
|
-
Print all active sheets and any messages associated with them.
|
195
|
+
optional --at flag. Accepts an optional TIMESHEET name to check out of a
|
196
|
+
running, non-current sheet.
|
184
197
|
|
185
|
-
usage: ``t
|
198
|
+
usage: ``t out [--at TIME] [TIMESHEET]``
|
186
199
|
|
187
|
-
**
|
188
|
-
Switch to a
|
189
|
-
|
200
|
+
**sheet**
|
201
|
+
Switch to a timesheet creating it if necessary. The default timesheet is
|
202
|
+
called "default".
|
190
203
|
|
191
|
-
usage: ``t
|
204
|
+
usage: ``t sheet TIMESHEET``
|
192
205
|
|
193
206
|
**week**
|
194
207
|
Shortcut for display with start date set to monday of this week
|
195
208
|
|
196
|
-
usage: ``t week [--ids] [--end DATE] [--format FMT] [
|
209
|
+
usage: ``t week [--ids] [--end DATE] [--format FMT] [TIMESHEET | all]``
|
197
210
|
|
198
211
|
Global Options
|
199
212
|
--------
|
@@ -217,11 +230,20 @@ Configuration
|
|
217
230
|
Configuration of TimeTrap's behavior can be done through a YAML config file.
|
218
231
|
See ``t configure`` for details. Currently supported options are:
|
219
232
|
|
220
|
-
round_in_seconds
|
233
|
+
``round_in_seconds``: The duration of time to use for rounding with the -r flag
|
221
234
|
|
222
|
-
database_file
|
235
|
+
``database_file``: The file path of the sqlite database
|
223
236
|
|
224
|
-
append_notes_delimiter
|
237
|
+
``append_notes_delimiter``: delimiter used when appending notes via ``t edit --append``
|
238
|
+
|
239
|
+
Special Thanks
|
240
|
+
--------------
|
241
|
+
|
242
|
+
The initial version of Timetrap was heavily inspired by Trevor Caira's
|
243
|
+
Timebook, a small python utility.
|
244
|
+
|
245
|
+
Original Timebook available at:
|
246
|
+
http://bitbucket.org/trevor/timebook/src/
|
225
247
|
|
226
248
|
Bugs and Feature Requests
|
227
249
|
--------
|
data/VERSION.yml
CHANGED
data/lib/timetrap.rb
CHANGED
@@ -7,6 +7,7 @@ require 'Getopt/Declare'
|
|
7
7
|
require File.join(File.dirname(__FILE__), 'timetrap', 'config')
|
8
8
|
require File.join(File.dirname(__FILE__), 'timetrap', 'helpers')
|
9
9
|
require File.join(File.dirname(__FILE__), 'timetrap', 'cli')
|
10
|
+
require File.join(File.dirname(__FILE__), 'timetrap', 'timer')
|
10
11
|
DB_NAME = defined?(TEST_MODE) ? nil : Timetrap::Config['database_file']
|
11
12
|
# connect to database. This will create one if it doesn't exist
|
12
13
|
DB = Sequel.sqlite DB_NAME
|
@@ -16,67 +17,6 @@ Dir["#{File.dirname(__FILE__)}/timetrap/formatters/*.rb"].each do |path|
|
|
16
17
|
end
|
17
18
|
|
18
19
|
module Timetrap
|
19
|
-
extend self
|
20
|
-
|
21
|
-
def current_sheet= sheet
|
22
|
-
m = Meta.find_or_create(:key => 'current_sheet')
|
23
|
-
m.value = sheet
|
24
|
-
m.save
|
25
|
-
end
|
26
|
-
|
27
|
-
def current_sheet
|
28
|
-
unless Meta.find(:key => 'current_sheet')
|
29
|
-
Meta.create(:key => 'current_sheet', :value => 'default')
|
30
|
-
end
|
31
|
-
Meta.find(:key => 'current_sheet').value
|
32
|
-
end
|
33
|
-
|
34
|
-
def entries sheet = nil
|
35
|
-
Entry.filter(:sheet => sheet).order_by(:start)
|
36
|
-
end
|
37
|
-
|
38
|
-
def running?
|
39
|
-
!!active_entry
|
40
|
-
end
|
41
|
-
|
42
|
-
def active_entry
|
43
|
-
Entry.find(:sheet => Timetrap.current_sheet, :end => nil)
|
44
|
-
end
|
45
|
-
|
46
|
-
def stop time = nil
|
47
|
-
while a = active_entry
|
48
|
-
time ||= Time.now
|
49
|
-
a.end = time
|
50
|
-
a.save
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
def start note, time = nil
|
55
|
-
raise AlreadyRunning if running?
|
56
|
-
time ||= Time.now
|
57
|
-
Entry.create(:sheet => Timetrap.current_sheet, :note => note, :start => time).save
|
58
|
-
rescue => e
|
59
|
-
CLI.say e.message
|
60
|
-
end
|
61
|
-
|
62
|
-
def switch sheet
|
63
|
-
self.current_sheet = sheet
|
64
|
-
end
|
65
|
-
|
66
|
-
def kill_sheet sheet
|
67
|
-
Entry.filter(:sheet => sheet).destroy
|
68
|
-
end
|
69
|
-
|
70
|
-
def format format_klass, entries
|
71
|
-
format_klass.new(entries).output
|
72
|
-
end
|
73
|
-
|
74
|
-
class AlreadyRunning < StandardError
|
75
|
-
def message
|
76
|
-
"Timetrap is already running"
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
20
|
CLI.args = Getopt::Declare.new(<<-EOF)
|
81
21
|
#{CLI::USAGE}
|
82
22
|
EOF
|
data/lib/timetrap/cli.rb
CHANGED
@@ -64,18 +64,19 @@ COMMAND is one of:
|
|
64
64
|
* list - Show the available timesheets.
|
65
65
|
usage: t list
|
66
66
|
|
67
|
-
* now - Show
|
67
|
+
* now - Show all running entries.
|
68
68
|
usage: t now
|
69
69
|
|
70
|
-
* out - Stop the timer for the
|
71
|
-
usage: t out [--at TIME]
|
70
|
+
* out - Stop the timer for the a timesheet.
|
71
|
+
usage: t out [--at TIME] [TIMESHEET]
|
72
72
|
-a, --at <time:qs> Use this time instead of now
|
73
73
|
|
74
|
-
* running -
|
75
|
-
usage: t running
|
74
|
+
* running - Deprecated: alias for now.
|
76
75
|
|
77
|
-
*
|
78
|
-
usage: t
|
76
|
+
* sheet - Switch to a timesheet creating it if necessary.
|
77
|
+
usage: t sheet TIMESHEET
|
78
|
+
|
79
|
+
* switch - Deprecated: renamed to sheet.
|
79
80
|
|
80
81
|
* week - Shortcut for display with start date set to monday of this week.
|
81
82
|
usage: t week [--ids] [--end DATE] [--format FMT] [SHEET | all]
|
@@ -85,11 +86,12 @@ COMMAND is one of:
|
|
85
86
|
-h, --help Display this help.
|
86
87
|
-r, --round Round output to 15 minute start and end times.
|
87
88
|
-y, --yes Noninteractive, assume yes as answer to all prompts.
|
89
|
+
--debug Display stack traces for errors.
|
88
90
|
|
89
91
|
EXAMPLES
|
90
92
|
|
91
93
|
# create the "MyTimesheet" timesheet
|
92
|
-
$ t
|
94
|
+
$ t sheet MyTimesheet
|
93
95
|
|
94
96
|
# check in 5 minutes ago with a note
|
95
97
|
$ t in --at '5 minutes ago' doing some stuff
|
@@ -108,29 +110,28 @@ COMMAND is one of:
|
|
108
110
|
end
|
109
111
|
|
110
112
|
def invoke
|
111
|
-
args['-h'] ?
|
113
|
+
args['-h'] ? puts(USAGE) : invoke_command_if_valid
|
112
114
|
rescue => e
|
113
|
-
|
114
|
-
|
115
|
+
raise e if args['--debug']
|
116
|
+
warn e.message
|
117
|
+
exit 1 unless defined? TEST_MODE
|
115
118
|
end
|
116
119
|
|
117
120
|
def commands
|
118
121
|
Timetrap::CLI::USAGE.scan(/\* \w+/).map{|s| s.gsub(/\* /, '')}
|
119
122
|
end
|
120
123
|
|
121
|
-
def say *something
|
122
|
-
puts *something
|
123
|
-
end
|
124
|
-
|
125
124
|
def invoke_command_if_valid
|
126
125
|
command = args.unused.shift
|
127
126
|
set_global_options
|
128
127
|
case (valid = commands.select{|name| name =~ %r|^#{command}|}).size
|
129
|
-
when 0 then
|
130
|
-
when 1 then send valid[0]
|
128
|
+
when 0 then puts "Invalid command: #{command}"
|
131
129
|
else
|
132
|
-
|
133
|
-
|
130
|
+
if command
|
131
|
+
send valid[0]
|
132
|
+
else
|
133
|
+
puts USAGE
|
134
|
+
end
|
134
135
|
end
|
135
136
|
end
|
136
137
|
|
@@ -147,25 +148,30 @@ COMMAND is one of:
|
|
147
148
|
e.update :sheet => "_#{e.sheet}"
|
148
149
|
end
|
149
150
|
else
|
150
|
-
|
151
|
+
warn "archive aborted!"
|
151
152
|
end
|
152
153
|
end
|
153
154
|
|
154
155
|
def configure
|
155
156
|
Config.configure!
|
156
|
-
|
157
|
+
puts "Config file is at #{Config::PATH.inspect}"
|
157
158
|
end
|
158
159
|
|
159
160
|
def edit
|
160
|
-
entry = args['-i'] ? Entry[args['-i']] :
|
161
|
-
|
161
|
+
entry = args['-i'] ? Entry[args['-i']] : Timer.active_entry
|
162
|
+
unless entry
|
163
|
+
warn "can't find entry"
|
164
|
+
return
|
165
|
+
else
|
166
|
+
warn "editing entry ##{entry.id.inspect}"
|
167
|
+
end
|
162
168
|
entry.update :start => args['-s'] if args['-s'] =~ /.+/
|
163
169
|
entry.update :end => args['-e'] if args['-e'] =~ /.+/
|
164
170
|
|
165
171
|
# update sheet
|
166
172
|
if args['-m'] =~ /.+/
|
167
|
-
if entry ==
|
168
|
-
|
173
|
+
if entry == Timer.active_entry
|
174
|
+
Timer.current_sheet = args['-m']
|
169
175
|
end
|
170
176
|
entry.update :sheet => args['-m']
|
171
177
|
end
|
@@ -185,11 +191,17 @@ COMMAND is one of:
|
|
185
191
|
end
|
186
192
|
|
187
193
|
def in
|
188
|
-
|
194
|
+
Timer.start unused_args, args['-a']
|
195
|
+
warn "Checked into sheet #{Timer.current_sheet.inspect}."
|
189
196
|
end
|
190
197
|
|
191
198
|
def out
|
192
|
-
|
199
|
+
sheet = sheet_name_from_string(unused_args)
|
200
|
+
if Timer.stop sheet, args['-a']
|
201
|
+
warn "Checked out of sheet #{sheet.inspect}."
|
202
|
+
else
|
203
|
+
warn "No running entry on sheet #{sheet.inspect}."
|
204
|
+
end
|
193
205
|
end
|
194
206
|
|
195
207
|
def kill
|
@@ -198,21 +210,21 @@ COMMAND is one of:
|
|
198
210
|
out << "(#{e.note}) " if e.note.to_s =~ /.+/
|
199
211
|
if ask_user out
|
200
212
|
e.destroy
|
201
|
-
|
213
|
+
warn "it's dead"
|
202
214
|
else
|
203
|
-
|
215
|
+
warn "will not kill"
|
204
216
|
end
|
205
217
|
elsif (sheets = Entry.map{|e| e.sheet }.uniq).include?(sheet = unused_args)
|
206
218
|
victims = Entry.filter(:sheet => sheet).count
|
207
219
|
if ask_user "are you sure you want to delete #{victims} entries on sheet #{sheet.inspect}? "
|
208
|
-
|
209
|
-
|
220
|
+
Entry.filter(:sheet => sheet).destroy
|
221
|
+
warn "killed #{victims} entries"
|
210
222
|
else
|
211
|
-
|
223
|
+
warn "will not kill"
|
212
224
|
end
|
213
225
|
else
|
214
226
|
victim = args['-i'] ? args['-i'].to_s.inspect : sheet.inspect
|
215
|
-
|
227
|
+
warn "can't find #{victim} to kill", 'sheets:', *sheets
|
216
228
|
end
|
217
229
|
end
|
218
230
|
|
@@ -224,38 +236,52 @@ COMMAND is one of:
|
|
224
236
|
Timetrap::Formatters::Text
|
225
237
|
end
|
226
238
|
rescue
|
227
|
-
|
239
|
+
warn "Invalid format #{args['-f'].inspect} specified."
|
228
240
|
return
|
229
241
|
end
|
230
|
-
|
242
|
+
entries = selected_entries.order(:start).all
|
243
|
+
if entries == []
|
244
|
+
warn "No entries were selected to display."
|
245
|
+
else
|
246
|
+
puts fmt_klass.new(entries).output
|
247
|
+
end
|
231
248
|
end
|
232
249
|
alias_method :format, :display
|
233
250
|
|
234
|
-
|
235
|
-
def switch
|
251
|
+
def sheet
|
236
252
|
sheet = unused_args
|
237
|
-
|
238
|
-
|
253
|
+
unless sheet =~ /.+/
|
254
|
+
warn "No sheet specified"
|
255
|
+
else
|
256
|
+
Timer.current_sheet = sheet
|
257
|
+
warn "Switching to sheet #{sheet.inspect}"
|
258
|
+
end
|
239
259
|
end
|
240
260
|
|
261
|
+
alias_method :switch, :sheet
|
262
|
+
|
241
263
|
def list
|
242
|
-
sheets = Entry.sheets.map do |sheet|
|
264
|
+
sheets = ([Timer.current_sheet] | Entry.sheets).map do |sheet|
|
243
265
|
sheet_atts = {:total => 0, :running => 0, :today => 0}
|
244
|
-
Timetrap::Entry.filter(:sheet => sheet)
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
266
|
+
entries = Timetrap::Entry.filter(:sheet => sheet)
|
267
|
+
if entries.empty?
|
268
|
+
sheet_atts.merge(:name => sheet)
|
269
|
+
else
|
270
|
+
entries.inject(sheet_atts) do |m, e|
|
271
|
+
e_end = e.end_or_now
|
272
|
+
m[:name] ||= sheet
|
273
|
+
m[:total] += (e_end.to_i - e.start.to_i)
|
274
|
+
m[:running] += (e_end.to_i - e.start.to_i) unless e.end
|
275
|
+
m[:today] += (e_end.to_i - e.start.to_i) if same_day?(Time.now, e.start)
|
276
|
+
m
|
277
|
+
end
|
251
278
|
end
|
252
|
-
end
|
253
|
-
if sheets.empty? then say "No sheets found"; return end
|
279
|
+
end.sort_by{|sheet| sheet[:name].downcase}
|
254
280
|
width = sheets.sort_by{|h|h[:name].length }.last[:name].length + 4
|
255
|
-
|
281
|
+
puts " %-#{width}s%-12s%-12s%s" % ["Timesheet", "Running", "Today", "Total Time"]
|
256
282
|
sheets.each do |sheet|
|
257
|
-
star = sheet[:name] ==
|
258
|
-
|
283
|
+
star = sheet[:name] == Timer.current_sheet ? '*' : ' '
|
284
|
+
puts "#{star}%-#{width}s%-12s%-12s%s" % [
|
259
285
|
sheet[:running],
|
260
286
|
sheet[:today],
|
261
287
|
sheet[:total]
|
@@ -264,19 +290,18 @@ COMMAND is one of:
|
|
264
290
|
end
|
265
291
|
|
266
292
|
def now
|
267
|
-
if
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
293
|
+
if !Timer.running?
|
294
|
+
puts "*#{Timer.current_sheet}: not running"
|
295
|
+
end
|
296
|
+
Timer.running_entries.each do |entry|
|
297
|
+
current = entry[:sheet] == Timer.current_sheet
|
298
|
+
out = current ? '*' : ' '
|
299
|
+
out << "#{entry[:sheet]}: #{format_duration(entry.start, entry.end_or_now)}".gsub(/ /, ' ')
|
300
|
+
out << " (#{entry.note})" if entry.note =~ /.+/
|
301
|
+
puts out
|
273
302
|
end
|
274
303
|
end
|
275
|
-
|
276
|
-
def running
|
277
|
-
say "Running Timesheets:"
|
278
|
-
say Timetrap::Entry.filter(:end => nil).map{|e| " #{e.sheet}: #{e.note}"}.uniq.sort
|
279
|
-
end
|
304
|
+
alias_method :running, :display
|
280
305
|
|
281
306
|
def week
|
282
307
|
args['-s'] = Date.today.wday == 1 ? Date.today.to_s : Date.parse(Chronic.parse(%q(last monday)).to_s).to_s
|
@@ -291,7 +316,7 @@ COMMAND is one of:
|
|
291
316
|
|
292
317
|
def ask_user question
|
293
318
|
return true if args['-y']
|
294
|
-
print question
|
319
|
+
$stderr.print question
|
295
320
|
$stdin.gets =~ /\Aye?s?\Z/i
|
296
321
|
end
|
297
322
|
|