Module: Timetrap::CLI
- Extended by:
- CLI, Helpers
- Included in:
- CLI
- Defined in:
- lib/timetrap/cli.rb
Constant Summary
collapse
- USAGE =
<<-EOF
Timetrap - Simple Time Tracking
Usage: #{File.basename $0} COMMAND [OPTIONS] [ARGS...]
COMMAND can be abbreviated. For example `t in` and `t i` are equivalent.
COMMAND is one of:
* archive - Move entries to a hidden sheet (by default named '_[SHEET]') so
they're out of the way.
usage: t archive [--start DATE] [--end DATE] [SHEET]
-s, --start <date:qs> Include entries that start on this date or later
-e, --end <date:qs> Include entries that start on this date or earlier
* backend - Open an sqlite shell to the database.
usage: t backend
* configure - Write out a YAML config file. Print path to config file. The
file may contain ERB.
usage: t configure
Currently supported options are:
round_in_seconds: The duration of time to use for rounding with
the -r flag
database_file: The file path of the sqlite database
append_notes_delimiter: delimiter used when appending notes via
t edit --append
* display - Display the current timesheet or a specific. Pass `all' as
SHEET to display all sheets.
usage: t display [--ids] [--start DATE] [--end DATE] [--format FMT] [SHEET | all]
-v, --ids Print database ids (for use with edit)
-s, --start <date:qs> Include entries that start on this date or later
-e, --end <date:qs> Include entries that start on this date or earlier
-f, --format <format> The output format. Valid built-in formats are
ical, csv, json, ids, and text (default).
Documentation on defining custom formats can be
found in the README included in this
distribution.
* edit - Alter an entry's note, start, or end time. Defaults to the active entry.
usage: t edit [--id ID] [--start TIME] [--end TIME] [--append] [NOTES]
-i, --id <id:i> Alter entry with id <id> instead of the running entry
-s, --start <time:qs> Change the start time to <time>
-e, --end <time:qs> Change the end time to <time>
-z, --append Append to the current note instead of replacing it
the delimiter between appended notes is
configurable (see configure)
-m, --move <sheet> Move to another sheet
* in - Start the timer for the current timesheet.
usage: t in [--at TIME] [NOTES]
-a, --at <time:qs> Use this time instead of now
* kill - Delete a timesheet or an entry.
usage: t kill [--id ID] [TIMESHEET]
-i, --id <id:i> Alter entry with id <id> instead of the running entry
* list - Show the available timesheets.
usage: t list
* now - Show all running entries.
usage: t now
* out - Stop the timer for the a timesheet.
usage: t out [--at TIME] [TIMESHEET]
-a, --at <time:qs> Use this time instead of now
* resume - Start the timer for the current time sheet with the same note as
the last entry on the sheet. If there is no entry it takes the passed note.
usage: t resume [--at TIME] [NOTES]
-a, --at <time:qs> Use this time instead of now
* sheet - Switch to a timesheet creating it if necessary. When no sheet is
specified list all sheets.
usage: t sheet [TIMESHEET]
* week - Shortcut for display with start date set to monday of this week.
usage: t week [--ids] [--end DATE] [--format FMT] [SHEET | all]
OTHER OPTIONS
-h, --help Display this help.
-r, --round Round output to 15 minute start and end times.
-y, --yes Noninteractive, assume yes as answer to all prompts.
--debug Display stack traces for errors.
EXAMPLES
# create the "MyTimesheet" timesheet
$ t sheet MyTimesheet
# check in 5 minutes ago with a note
$ t in --at '5 minutes ago' doing some stuff
# check out
$ t out
# view current timesheet
$ t display
Submit bugs and feature requests to http://github.com/samg/timetrap/issues
EOF
Instance Attribute Summary collapse
Instance Method Summary
collapse
Methods included from Helpers
format_date, format_date_if_new, format_duration, format_seconds, format_time, format_total, load_formatter, same_day?, selected_entries, sheet_name_from_string
Instance Attribute Details
#args ⇒ Object
Returns the value of attribute args.
4
5
6
|
# File 'lib/timetrap/cli.rb', line 4
def args
@args
end
|
Instance Method Details
#archive ⇒ Object
163
164
165
166
167
168
169
170
171
172
173
|
# File 'lib/timetrap/cli.rb', line 163
def archive
ee = selected_entries
if ask_user "Archive #{ee.count} entries? "
ee.all.each do |e|
next unless e.end
e.update :sheet => "_#{e.sheet}"
end
else
warn "archive aborted!"
end
end
|
#backend ⇒ Object
209
210
211
|
# File 'lib/timetrap/cli.rb', line 209
def backend
exec "sqlite3 #{DB_NAME}"
end
|
#commands ⇒ Object
124
125
126
|
# File 'lib/timetrap/cli.rb', line 124
def commands
Timetrap::CLI::USAGE.scan(/\* \w+/).map{|s| s.gsub(/\* /, '')}
end
|
175
176
177
178
|
# File 'lib/timetrap/cli.rb', line 175
def configure
Config.configure!
puts "Config file is at #{Config::PATH.inspect}"
end
|
#deprecated_commands ⇒ Object
128
129
130
131
132
133
134
|
# File 'lib/timetrap/cli.rb', line 128
def deprecated_commands
{
'switch' => 'sheet',
'running' => 'now',
'format' => 'display'
}
end
|
#display ⇒ Object
262
263
264
265
266
267
268
269
|
# File 'lib/timetrap/cli.rb', line 262
def display
entries = selected_entries.order(:start).all
if entries == []
warn "No entries were selected to display."
else
puts load_formatter(args['-f'] || Config['default_formatter']).new(entries).output
end
end
|
#edit ⇒ Object
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
|
# File 'lib/timetrap/cli.rb', line 180
def edit
entry = args['-i'] ? Entry[args['-i']] : Timer.active_entry
unless entry
warn "can't find entry"
return
else
warn "editing entry ##{entry.id.inspect}"
end
entry.update :start => args['-s'] if args['-s'] =~ /.+/
entry.update :end => args['-e'] if args['-e'] =~ /.+/
if args['-m'] =~ /.+/
if entry == Timer.active_entry
Timer.current_sheet = args['-m']
end
entry.update :sheet => args['-m']
end
if unused_args =~ /.+/
note = unused_args
if args['-z']
note = [entry.note, note].join(Config['append_notes_delimiter'])
end
entry.update :note => note
end
end
|
#handle_invalid_command(command) ⇒ Object
146
147
148
149
150
151
152
153
154
155
156
|
# File 'lib/timetrap/cli.rb', line 146
def handle_invalid_command(command)
if !command
puts USAGE
elsif mapping = deprecated_commands.detect{|(k,v)| k =~ %r|^#{command}|}
deprecated, current = *mapping
warn "The #{deprecated.inspect} command is deprecated in favor of #{current.inspect}. Sorry for the inconvenience."
send current
else
warn "Invalid command: #{command.inspect}"
end
end
|
#in ⇒ Object
213
214
215
216
|
# File 'lib/timetrap/cli.rb', line 213
def in
Timer.start unused_args, args['-a']
warn "Checked into sheet #{Timer.current_sheet.inspect}."
end
|
#invoke ⇒ Object
116
117
118
119
120
121
122
|
# File 'lib/timetrap/cli.rb', line 116
def invoke
args['-h'] ? puts(USAGE) : invoke_command_if_valid
rescue StandardError, LoadError => e
raise e if args['--debug']
warn e.message
exit 1 unless defined? TEST_MODE
end
|
#invoke_command_if_valid ⇒ Object
136
137
138
139
140
141
142
143
144
|
# File 'lib/timetrap/cli.rb', line 136
def invoke_command_if_valid
command = args.unused.shift
set_global_options
case (valid = commands.select{|name| name =~ %r|^#{command}|}).size
when 1 then send valid[0]
else
handle_invalid_command(command)
end
end
|
#kill ⇒ Object
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
|
# File 'lib/timetrap/cli.rb', line 238
def kill
if e = Entry[args['-i']]
out = "are you sure you want to delete entry #{e.id}? "
out << "(#{e.note}) " if e.note.to_s =~ /.+/
if ask_user out
e.destroy
warn "it's dead"
else
warn "will not kill"
end
elsif (sheets = Entry.map{|e| e.sheet }.uniq).include?(sheet = unused_args)
victims = Entry.filter(:sheet => sheet).count
if ask_user "are you sure you want to delete #{victims} entries on sheet #{sheet.inspect}? "
Entry.filter(:sheet => sheet).destroy
warn "killed #{victims} entries"
else
warn "will not kill"
end
else
victim = args['-i'] ? args['-i'].to_s.inspect : sheet.inspect
warn ["can't find #{victim} to kill", 'sheets:', *sheets].join("\n")
end
end
|
#list ⇒ Object
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
|
# File 'lib/timetrap/cli.rb', line 281
def list
sheets = ([Timer.current_sheet] | Entry.sheets).map do |sheet|
sheet_atts = {:total => 0, :running => 0, :today => 0}
entries = Timetrap::Entry.filter(:sheet => sheet)
if entries.empty?
sheet_atts.merge(:name => sheet)
else
entries.inject(sheet_atts) do |m, e|
e_end = e.end_or_now
m[:name] ||= sheet
m[:total] += (e_end.to_i - e.start.to_i)
m[:running] += (e_end.to_i - e.start.to_i) unless e.end
m[:today] += (e_end.to_i - e.start.to_i) if same_day?(Time.now, e.start)
m
end
end
end.sort_by{|sheet| sheet[:name].downcase}
width = sheets.sort_by{|h|h[:name].length }.last[:name].length + 4
puts " %-#{width}s%-12s%-12s%s" % ["Timesheet", "Running", "Today", "Total Time"]
sheets.each do |sheet|
star = sheet[:name] == Timer.current_sheet ? '*' : ' '
puts "#{star}%-#{width}s%-12s%-12s%s" % [
sheet[:running],
sheet[:today],
sheet[:total]
].map(&method(:format_seconds)).unshift(sheet[:name])
end
end
|
#now ⇒ Object
310
311
312
313
314
315
316
317
318
319
320
321
|
# File 'lib/timetrap/cli.rb', line 310
def now
if !Timer.running?
puts "*#{Timer.current_sheet}: not running"
end
Timer.running_entries.each do |entry|
current = entry[:sheet] == Timer.current_sheet
out = current ? '*' : ' '
out << "#{entry[:sheet]}: #{format_duration(entry.start, entry.end_or_now)}".gsub(/ /, ' ')
out << " (#{entry.note})" if entry.note =~ /.+/
puts out
end
end
|
#out ⇒ Object
229
230
231
232
233
234
235
236
|
# File 'lib/timetrap/cli.rb', line 229
def out
sheet = sheet_name_from_string(unused_args)
if Timer.stop sheet, args['-a']
warn "Checked out of sheet #{sheet.inspect}."
else
warn "No running entry on sheet #{sheet.inspect}."
end
end
|
#parse(arguments) ⇒ Object
112
113
114
|
# File 'lib/timetrap/cli.rb', line 112
def parse arguments
args.parse arguments
end
|
#resume ⇒ Object
218
219
220
221
222
223
224
225
226
227
|
# File 'lib/timetrap/cli.rb', line 218
def resume
last_entry = Timer.entries(Timer.current_sheet).last
warn "No entry yet on this sheet yet. Started a new entry." unless last_entry
note = (last_entry ? last_entry.note : nil)
warn "Resuming #{note.inspect} from entry ##{last_entry.id}" if note
self.unused_args = note || unused_args
self.in
end
|
#set_global_options ⇒ Object
currently just sets whether output should be rounded to 15 min intervals
159
160
161
|
# File 'lib/timetrap/cli.rb', line 159
def set_global_options
Timetrap::Entry.round = true if args['-r']
end
|
#sheet ⇒ Object
271
272
273
274
275
276
277
278
279
|
# File 'lib/timetrap/cli.rb', line 271
def sheet
sheet = unused_args
unless sheet =~ /.+/
list
else
Timer.current_sheet = sheet
warn "Switching to sheet #{sheet.inspect}"
end
end
|
#week ⇒ Object
323
324
325
326
|
# File 'lib/timetrap/cli.rb', line 323
def week
args['-s'] = Date.today.wday == 1 ? Date.today.to_s : Date.parse(Chronic.parse(%q(last monday)).to_s).to_s
display
end
|