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 ADDED
@@ -0,0 +1,23 @@
1
+ http://www.opensource.org/licenses/mit-license.php
2
+
3
+ The MIT License
4
+
5
+ Copyright (c) 2009 Sam Goldstein
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in
15
+ all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,183 @@
1
+ Timetrap
2
+ ========
3
+
4
+ Timetrap is a utility which provides an easy to use command line interface for
5
+ tracking what you spend your time on. It is a ruby port of Trevor Caira's
6
+ Timebook, a small python utility. It contains several enhancement over
7
+ Timebook notably the ability to parse natural language time strings. This
8
+ makes commands such as ``t in --at "30 minutes ago"`` possible. Timetrap is
9
+ also able to export entries to several formats (e.g. ical, csv) and is designed
10
+ to be easily extended to support additional export formats.
11
+ Timetrap maintains its state in a sqlite3 database.
12
+
13
+ To install:
14
+
15
+ $ gem sources -a http://gems.github.com (you only have to do this once)
16
+ $ sudo gem install samg-timetrap
17
+
18
+ This will place a ``t`` executable in your path.
19
+
20
+ Original Timebook available at:
21
+ http://bitbucket.org/trevor/timebook/src/
22
+
23
+
24
+ Concepts
25
+ --------
26
+
27
+ Timetrap maintains a list of *timesheets* -- distinct lists of timed *periods*.
28
+ Each period has a start and end time, with the exception of the most recent
29
+ period, which may have no end time set. This indicates that this period is
30
+ still running. Timesheets containing such periods are considered *active*. It
31
+ is possible to have multiple timesheets active simultaneously, though a single
32
+ time sheet may only have one period running at once.
33
+
34
+ Interactions with timetrap are performed through the ``t`` command on the
35
+ command line. ``t`` is followed by one of timetrap's subcommands. Often used
36
+ subcommands include ``in``, ``out``, ``switch``, ``now``, ``list`` and
37
+ ``display``. Commands may be abbreviated as long as they are unambiguous: thus
38
+ ``t switch foo`` and ``t s foo`` are identical. With the default command set,
39
+ no two commands share the first same letter, thus it is only necessary to type
40
+ the first letter of a command. Likewise, commands which display timesheets
41
+ accept abbreviated timesheet names. ``t display f`` is thus equivalent to ``t
42
+ display foo`` if ``foo`` is the only timesheet which begins with "f". Note that
43
+ this does not apply to ``t switch``, since this command also creates
44
+ timesheets. (Using the earlier example, if ``t switch f`` is entered, it would
45
+ thus be ambiguous whether a new timesheet ``f`` or switching to the existing
46
+ timesheet ``foo`` was desired).
47
+
48
+ Usage
49
+ -----
50
+
51
+ The basic usage is as follows:
52
+
53
+ $ t switch writing
54
+ $ t in document timetrap --at "10 minutes ago"
55
+ $ t out
56
+
57
+ The first command, ``t switch writing``, switches to the timesheet "writing"
58
+ (or creates it if it does not exist). ``t in document timetrap --at "10 minutes
59
+ ago"`` creates a new period in the current timesheet, and annotates it with the
60
+ description "document timetrap". The optional ``--at`` flag can be passed to start
61
+ the entry at a time other than the present. The ``--at`` flag is able to parse
62
+ natural language times (via Chronic: http://chronic.rubyforge.org/) and will
63
+ understand 'friday 13:00', 'mon 2:35', '4pm', etc. (also true of the ``edit``
64
+ command's ``--start`` and ``--end`` flags.) Note that this command would be in
65
+ error if the ``writing`` timesheet was already active. Finally, ``t out``
66
+ records the current time as the end time for the most recent period in the
67
+ ``writing`` timesheet.
68
+
69
+ To display the current timesheet, invoke the ``t display`` command::
70
+
71
+ $ t display
72
+ Timesheet: timetrap
73
+ Day Start End Duration Notes
74
+ Mon Apr 13, 2009 15:46:51 - 17:03:50 1:16:59 improved display functionality
75
+ 17:25:59 - 17:26:02 0:00:03
76
+ 18:38:07 - 18:38:52 0:00:45 working on list
77
+ 22:37:38 - 23:38:43 1:01:05 work on kill
78
+ 2:18:52
79
+ Tue Apr 14, 2009 00:41:16 - 01:40:19 0:59:03 gem packaging
80
+ 10:20:00 - 10:48:10 0:28:10 enhance edit
81
+ 1:27:13
82
+ ---------------------------------------------------------
83
+ Total 3:46:05
84
+
85
+ Each period in the timesheet is listed on a row. If the timesheet is active,
86
+ the final period in the timesheet will have no end time. After each day, the
87
+ total time tracked in the timesheet for that day is listed. Note that this is
88
+ computed by summing the durations of the periods beginning in the day. In the
89
+ last row, the total time tracked in the timesheet is shown.
90
+
91
+ Commands
92
+ --------
93
+ **archives**
94
+ Archives the selected entries (by moving them to a sheet called ``_[SHEET]``)
95
+ These entries can be seen by running ``t display _[SHEET]``.
96
+ usage: ``t archive [--start DATE] [--end DATE] [SHEET]``
97
+
98
+ **backend**
99
+ Run an interactive database session on the timetrap database. Requires the
100
+ sqlite3 command.
101
+
102
+ usage: ``t backend``
103
+
104
+ **display**
105
+ Display a given timesheet. If no timesheet is specified, show the current
106
+ timesheet. If ``all`` is passed as SHEET display all timesheets. Accepts
107
+ an optional ``--ids`` flag which will include the entries' ids in the output.
108
+ This is useful when editing an non running entry with ``edit``.
109
+
110
+ Display is designed to support a variety of export formats that can be
111
+ specified by passing the ``--format`` flag. This currently defaults to
112
+ text. iCal and csv output are also supported.
113
+
114
+ Display also allows the use of a ``--round`` or ``-r`` flag which will round
115
+ all times to 15 minute increments. See global options below.
116
+
117
+ usage: ``t display [--ids] [--round] [--start DATE] [--end DATE] [--format FMT] [SHEET | all]``
118
+
119
+ **edit**
120
+ Inserts a note associated with the an entry in the timesheet, or edits the
121
+ start or end times. Defaults to the current time although an ``--id`` flag can
122
+ be passed with the entry's id (see display.)
123
+
124
+ usage: ``t edit [--id ID] [--start TIME] [--end TIME] [NOTES]``
125
+
126
+ **format**
127
+ Deprecated
128
+ Alias for display
129
+
130
+ **in**
131
+ Start the timer for the current timesheet. Must be called before out. Notes
132
+ may be specified for this period. This is exactly equivalent to
133
+ ``t in; t edit NOTES``. Accepts an optional --at flag.
134
+
135
+ usage: ``t in [--at TIME] [NOTES]``
136
+
137
+ **kill**
138
+ Delete a timesheet or an entry. Entry's are referenced using an ``--id``
139
+ flag (see display). Sheets are referenced by name.
140
+
141
+ usage: ``t kill [--id ID] [TIMESHEET]``
142
+
143
+ **list**
144
+ List the available timesheets.
145
+
146
+ usage: ``t list``
147
+
148
+ **now**
149
+ Print the current sheet, whether it's active, and if so, how long it has been
150
+ active and what notes are associated with the current period.
151
+
152
+ usage: ``t now``
153
+
154
+ **out**
155
+ Stop the timer for the current timesheet. Must be called after in. Accepts an
156
+ optional --at flag.
157
+
158
+ usage: ``t out [--at TIME]``
159
+
160
+ **running**
161
+ Print all active sheets and any messages associated with them.
162
+
163
+ usage: ``t running``
164
+
165
+ **switch**
166
+ Switch to a new timesheet. this causes all future operation (except switch)
167
+ to operate on that timesheet. The default timesheet is called "default".
168
+
169
+ usage: ``t switch TIMESHEET``
170
+
171
+ **week**
172
+ Shortcut for display with start date set to monday of this week
173
+
174
+ usage: ``t week [--ids] [--end DATE] [--format FMT] [SHEET | all]``
175
+
176
+ Global Options
177
+ --------
178
+
179
+ **rounding**
180
+ passing a ``--round`` or ``-r`` flag to any command will round entry start
181
+ and end times to the closest 15 minute increment. This flag only affects the
182
+ display commands (e.g. display, list, week, etc.) and is non-destructive.
183
+ The actual start and end time stored by Timetrap are unaffected.
data/Rakefile ADDED
@@ -0,0 +1,60 @@
1
+ require 'spec/rake/spectask'
2
+ require 'rake/rdoctask'
3
+ require 'rake/gempackagetask'
4
+
5
+ task :default => :spec
6
+
7
+ desc "Run all specs in spec directory"
8
+ Spec::Rake::SpecTask.new(:spec) do |t|
9
+ t.spec_files = FileList['spec/**/*_spec.rb']
10
+ end
11
+
12
+ Rake::RDocTask.new do |rd|
13
+ rd.main = "README"
14
+ rd.rdoc_dir = 'doc'
15
+ rd.rdoc_files.include("README", "**/*.rb")
16
+ end
17
+
18
+ begin
19
+ require 'jeweler'
20
+ Jeweler::Tasks.new do |s|
21
+ s.name = %q{timetrap}
22
+ s.version = "0.1.2"
23
+
24
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
25
+ s.authors = ["Sam Goldstein"]
26
+ s.date = %q{2009-04-14}
27
+ s.description = %q{Command line time tracker}
28
+ s.email = %q{sgrock@gmail.com}
29
+ s.has_rdoc = true
30
+ s.homepage = "http://github.com/samg/timetrap/tree/master"
31
+ s.rdoc_options = ["--inline-source", "--charset=UTF-8"]
32
+ s.require_paths = ["lib"]
33
+ s.bindir = "bin"
34
+ s.executables = ['t']
35
+ s.summary = %q{Command line time tracker}
36
+ s.add_dependency("sequel", ">= 2.12.0")
37
+ s.add_dependency("chronic", ">= 0.2.3")
38
+ s.add_dependency("getopt-declare", ">= 1.28")
39
+ s.add_dependency("icalendar", ">= 1.1.0")
40
+
41
+ if s.respond_to? :specification_version then
42
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
43
+ s.specification_version = 2
44
+
45
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
46
+ s.add_runtime_dependency(%q<mime-types>, [">= 1.15"])
47
+ s.add_runtime_dependency(%q<diff-lcs>, [">= 1.1.2"])
48
+ else
49
+ s.add_dependency(%q<mime-types>, [">= 1.15"])
50
+ s.add_dependency(%q<diff-lcs>, [">= 1.1.2"])
51
+ end
52
+ else
53
+ s.add_dependency(%q<mime-types>, [">= 1.15"])
54
+ s.add_dependency(%q<diff-lcs>, [">= 1.1.2"])
55
+ end
56
+ end
57
+ rescue LoadError
58
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
59
+ end
60
+
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :patch: 1
3
+ :major: 1
4
+ :minor: 1
data/bin/dev_t ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ # Executable with absolute path to lib for hacking and development
3
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'timetrap')
4
+ Timetrap::CLI.invoke
data/bin/t ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+ begin
3
+ require 'timetrap'
4
+ rescue LoadError
5
+ if File.symlink? __FILE__
6
+ require File.dirname(File.readlink(__FILE__)) + '/../lib/timetrap'
7
+ else
8
+ require File.dirname(__FILE__) + '/../lib/timetrap'
9
+ end
10
+ end
11
+ Timetrap::CLI.invoke
@@ -0,0 +1,227 @@
1
+ module Timetrap
2
+ module CLI
3
+ extend Helpers
4
+ attr_accessor :args
5
+ extend self
6
+
7
+ USAGE = <<-EOF
8
+
9
+ Timetrap - Simple Time Tracking
10
+
11
+ Usage: #{File.basename $0} COMMAND [OPTIONS] [ARGS...]
12
+
13
+ where COMMAND is one of:
14
+ * archive - move entries to a hidden sheet (by default named '_[SHEET]') so
15
+ they're out of the way.
16
+ usage: t archive [--start DATE] [--end DATE] [SHEET]
17
+ -s, --start <date:qs> Include entries that start on this date or later
18
+ -e, --end <date:qs> Include entries that start on this date or earlier
19
+ * backend - open an sqlite shell to the database
20
+ usage: t backend
21
+ * display - display the current timesheet or a specific. Pass `all' as
22
+ SHEET to display all sheets.
23
+ usage: t display [--ids] [--start DATE] [--end DATE] [--format FMT] [SHEET | all]
24
+ -v, --ids Print database ids (for use with edit)
25
+ -s, --start <date:qs> Include entries that start on this date or later
26
+ -e, --end <date:qs> Include entries that start on this date or earlier
27
+ -f, --format <format> The output format. Currently supports ical, csv, and
28
+ text (default).
29
+ * edit - alter an entry's note, start, or end time. Defaults to the active entry
30
+ usage: t edit [--id ID] [--start TIME] [--end TIME] [NOTES]
31
+ -i, --id <id:i> Alter entry with id <id> instead of the running entry
32
+ -s, --start <time:qs> Change the start time to <time>
33
+ -e, --end <time:qs> Change the end time to <time>
34
+ * format - deprecated: alias for display
35
+ * in - start the timer for the current timesheet
36
+ usage: t in [--at TIME] [NOTES]
37
+ -a, --at <time:qs> Use this time instead of now
38
+ * kill - delete a timesheet
39
+ usage: t kill [--id ID] [TIMESHEET]
40
+ -i, --id <id:i> Alter entry with id <id> instead of the running entry
41
+ * list - show the available timesheets
42
+ usage: t list
43
+ * now - show the status of the current timesheet
44
+ usage: t now
45
+ * out - stop the timer for the current timesheet
46
+ usage: t out [--at TIME]
47
+ -a, --at <time:qs> Use this time instead of now
48
+ * running - show all running timesheets
49
+ usage: t running
50
+ * switch - switch to a new timesheet
51
+ usage: t switch TIMESHEET
52
+ * week - shortcut for display with start date set to monday of this week
53
+ usage: t week [--ids] [--end DATE] [--format FMT] [SHEET | all]
54
+
55
+ OTHER OPTIONS
56
+ -h, --help Display this help
57
+ -r, --round Round output to 15 minute start and end times.
58
+ EOF
59
+
60
+ def parse arguments
61
+ args.parse arguments
62
+ end
63
+
64
+ def invoke
65
+ args['-h'] ? say(USAGE) : invoke_command_if_valid
66
+ end
67
+
68
+ def commands
69
+ Timetrap::CLI::USAGE.scan(/\* \w+/).map{|s| s.gsub(/\* /, '')}
70
+ end
71
+
72
+ def say *something
73
+ puts *something
74
+ end
75
+
76
+ def invoke_command_if_valid
77
+ command = args.unused.shift
78
+ set_global_options
79
+ case (valid = commands.select{|name| name =~ %r|^#{command}|}).size
80
+ when 0 then say "Invalid command: #{command}"
81
+ when 1 then send valid[0]
82
+ else
83
+ say "Ambiguous command: #{command}" if command
84
+ say(USAGE)
85
+ end
86
+ end
87
+
88
+ # currently just sets whether output should be rounded to 15 min intervals
89
+ def set_global_options
90
+ Timetrap::Entry.round = true if args['-r']
91
+ end
92
+
93
+ def archive
94
+ ee = selected_entries
95
+ out = "Archive #{ee.count} entries? "
96
+ print out
97
+ if $stdin.gets =~ /\Aye?s?\Z/i
98
+ ee.all.each do |e|
99
+ next unless e.end
100
+ e.update :sheet => "_#{e.sheet}"
101
+ end
102
+ else
103
+ say "archive aborted!"
104
+ end
105
+ end
106
+
107
+ def edit
108
+ entry = args['-i'] ? Entry[args['-i']] : Timetrap.active_entry
109
+ say "can't find entry" && return unless entry
110
+ entry.update :start => args['-s'] if args['-s'] =~ /.+/
111
+ entry.update :end => args['-e'] if args['-e'] =~ /.+/
112
+ entry.update :note => unused_args if unused_args =~ /.+/
113
+ end
114
+
115
+ def backend
116
+ exec "sqlite3 #{DB_NAME}"
117
+ end
118
+
119
+ def in
120
+ Timetrap.start unused_args, args['-a']
121
+ end
122
+
123
+ def out
124
+ Timetrap.stop args['-a']
125
+ end
126
+
127
+ def kill
128
+ if e = Entry[args['-i']]
129
+ out = "are you sure you want to delete entry #{e.id}? "
130
+ out << "(#{e.note}) " if e.note.to_s =~ /.+/
131
+ print out
132
+ if $stdin.gets =~ /\Aye?s?\Z/i
133
+ e.destroy
134
+ say "it's dead"
135
+ else
136
+ say "will not kill"
137
+ end
138
+ elsif (sheets = Entry.map{|e| e.sheet }.uniq).include?(sheet = unused_args)
139
+ victims = Entry.filter(:sheet => sheet).count
140
+ print "are you sure you want to delete #{victims} entries on sheet #{sheet.inspect}? "
141
+ if $stdin.gets =~ /\Aye?s?\Z/i
142
+ Timetrap.kill_sheet sheet
143
+ say "killed #{victims} entries"
144
+ else
145
+ say "will not kill"
146
+ end
147
+ else
148
+ victim = args['-i'] ? args['-i'].to_s.inspect : sheet.inspect
149
+ say "can't find #{victim} to kill", 'sheets:', *sheets
150
+ end
151
+ end
152
+
153
+ def display
154
+ begin
155
+ fmt_klass = if args['-f']
156
+ Timetrap::Formatters.const_get("#{args['-f'].classify}")
157
+ else
158
+ Timetrap::Formatters::Text
159
+ end
160
+ rescue
161
+ say "Invalid format specified `#{args['-f']}'"
162
+ return
163
+ end
164
+ say Timetrap.format(fmt_klass, selected_entries.order(:start).all)
165
+ end
166
+ alias_method :format, :display
167
+
168
+
169
+ def switch
170
+ sheet = unused_args
171
+ if not sheet =~ /.+/ then say "No sheet specified"; return end
172
+ say "Switching to sheet " + Timetrap.switch(sheet)
173
+ end
174
+
175
+ def list
176
+ sheets = Entry.sheets.map do |sheet|
177
+ sheet_atts = {:total => 0, :running => 0, :today => 0}
178
+ Timetrap::Entry.filter(:sheet => sheet).inject(sheet_atts) do |m, e|
179
+ e_end = e.end_or_now
180
+ m[:name] ||= sheet
181
+ m[:total] += (e_end.to_i - e.start.to_i)
182
+ m[:running] += (e_end.to_i - e.start.to_i) unless e.end
183
+ m[:today] += (e_end.to_i - e.start.to_i) if same_day?(Time.now, e.start)
184
+ m
185
+ end
186
+ end
187
+ if sheets.empty? then say "No sheets found"; return end
188
+ width = sheets.sort_by{|h|h[:name].length }.last[:name].length + 4
189
+ say " %-#{width}s%-12s%-12s%s" % ["Timesheet", "Running", "Today", "Total Time"]
190
+ sheets.each do |sheet|
191
+ star = sheet[:name] == Timetrap.current_sheet ? '*' : ' '
192
+ say "#{star}%-#{width}s%-12s%-12s%s" % [
193
+ sheet[:running],
194
+ sheet[:today],
195
+ sheet[:total]
196
+ ].map(&method(:format_seconds)).unshift(sheet[:name])
197
+ end
198
+ end
199
+
200
+ def now
201
+ if Timetrap.running?
202
+ out = "#{Timetrap.current_sheet}: #{format_duration(Timetrap.active_entry.start, Timetrap.active_entry.end_or_now)}".gsub(/ /, ' ')
203
+ out << " (#{Timetrap.active_entry.note})" if Timetrap.active_entry.note =~ /.+/
204
+ say out
205
+ else
206
+ say "#{Timetrap.current_sheet}: not running"
207
+ end
208
+ end
209
+
210
+ def running
211
+ say "Running Timesheets:"
212
+ say Timetrap::Entry.filter(:end => nil).map{|e| " #{e.sheet}: #{e.note}"}.uniq.sort
213
+ end
214
+
215
+ def week
216
+ args['-s'] = Date.today.wday == 1 ? Date.today.to_s : Date.parse(Chronic.parse(%q(last monday)).to_s).to_s
217
+ display
218
+ end
219
+
220
+ private
221
+
222
+ def unused_args
223
+ args.unused.join(' ')
224
+ end
225
+
226
+ end
227
+ end
@@ -0,0 +1,19 @@
1
+ module Timetrap
2
+ module Formatters
3
+ class Csv
4
+ attr_reader :output
5
+
6
+ def initialize entries
7
+ @output = entries.inject("start,end,note\n") do |out, e|
8
+ next(out) unless e.end
9
+ out << %|"#{e.start.strftime(time_format)}","#{e.end.strftime(time_format)}","#{e.note}"\n|
10
+ end
11
+ end
12
+
13
+ private
14
+ def time_format
15
+ "%Y-%m-%d %H:%M:%S"
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,29 @@
1
+ require 'icalendar'
2
+ require 'date'
3
+ module Timetrap
4
+ module Formatters
5
+ class Ical
6
+ include Icalendar
7
+ def calendar
8
+ @calendar ||= Calendar.new
9
+ end
10
+
11
+ def output
12
+ calendar.to_ical
13
+ end
14
+
15
+ def initialize entries
16
+ entries.each do |e|
17
+ next unless e.end
18
+ calendar.event do
19
+ dtstart DateTime.parse(e.start.to_s)
20
+ dtend DateTime.parse(e.end.to_s)
21
+ summary e.note
22
+ description e.note
23
+ end
24
+ end
25
+ calendar.publish
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,55 @@
1
+ module Timetrap
2
+ module Formatters
3
+ class Text
4
+ attr_accessor :output
5
+ include Timetrap::Helpers
6
+
7
+ def initialize entries
8
+ self.output = ''
9
+ sheets = entries.inject({}) do |h, e|
10
+ h[e.sheet] ||= []
11
+ h[e.sheet] << e
12
+ h
13
+ end
14
+ (sheet_names = sheets.keys.sort).each do |sheet|
15
+ self.output << "Timesheet: #{sheet}\n"
16
+ id_heading = Timetrap::CLI.args['-v'] ? 'Id' : ' '
17
+ self.output << "#{id_heading} Day Start End Duration Notes\n"
18
+ last_start = nil
19
+ from_current_day = []
20
+ sheets[sheet].each_with_index do |e, i|
21
+ from_current_day << e
22
+ e_end = e.end_or_now
23
+ self.output << "%-4s%16s%11s -%9s%10s %s\n" % [
24
+ (Timetrap::CLI.args['-v'] ? e.id : ''),
25
+ format_date_if_new(e.start, last_start),
26
+ format_time(e.start),
27
+ format_time(e.end),
28
+ format_duration(e.start, e_end),
29
+ e.note
30
+ ]
31
+
32
+ nxt = sheets[sheet].map[i+1]
33
+ if nxt == nil or !same_day?(e.start, nxt.start)
34
+ self.output << "%52s\n" % format_total(from_current_day)
35
+ from_current_day = []
36
+ else
37
+ end
38
+ last_start = e.start
39
+ end
40
+ self.output << <<-OUT
41
+ ---------------------------------------------------------
42
+ OUT
43
+ self.output << " Total%43s\n" % format_total(sheets[sheet])
44
+ self.output << "\n" unless sheet == sheet_names.last
45
+ end
46
+ if sheets.size > 1
47
+ self.output << <<-OUT
48
+ -------------------------------------------------------------
49
+ OUT
50
+ self.output << "Grand Total%41s\n" % format_total(sheets.values.flatten)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,59 @@
1
+ module Timetrap
2
+ module Helpers
3
+
4
+ def selected_entries
5
+ ee = if (sheet = sheet_name_from_string(unused_args)) == 'all'
6
+ Timetrap::Entry.filter('sheet not like ? escape "!"', '!_%')
7
+ elsif sheet =~ /.+/
8
+ Timetrap::Entry.filter('sheet = ?', sheet)
9
+ else
10
+ Timetrap::Entry.filter('sheet = ?', Timetrap.current_sheet)
11
+ end
12
+ ee = ee.filter(:start >= Date.parse(args['-s'])) if args['-s']
13
+ ee = ee.filter(:start <= Date.parse(args['-e']) + 1) if args['-e']
14
+ ee
15
+ end
16
+
17
+ def format_time time
18
+ return '' unless time.respond_to?(:strftime)
19
+ time.strftime('%H:%M:%S')
20
+ end
21
+
22
+ def format_date time
23
+ return '' unless time.respond_to?(:strftime)
24
+ time.strftime('%a %b %d, %Y')
25
+ end
26
+
27
+ def format_date_if_new time, last_time
28
+ return '' unless time.respond_to?(:strftime)
29
+ same_day?(time, last_time) ? '' : format_date(time)
30
+ end
31
+
32
+ def same_day? time, other_time
33
+ format_date(time) == format_date(other_time)
34
+ end
35
+
36
+ def format_duration stime, etime
37
+ return '' unless stime and etime
38
+ secs = etime.to_i - stime.to_i
39
+ format_seconds secs
40
+ end
41
+
42
+ def format_seconds secs
43
+ "%2s:%02d:%02d" % [secs/3600, (secs%3600)/60, secs%60]
44
+ end
45
+
46
+ def format_total entries
47
+ secs = entries.inject(0){|m, e|e_end = e.end_or_now; m += e_end.to_i - e.start.to_i if e_end && e.start;m}
48
+ "%2s:%02d:%02d" % [secs/3600, (secs%3600)/60, secs%60]
49
+ end
50
+
51
+ def sheet_name_from_string string
52
+ return "all" if string =~ /^\W*all\W*$/
53
+ return "" unless string =~ /.+/
54
+ DB[:entries].filter(:sheet.like("#{string}%")).first[:sheet]
55
+ rescue
56
+ ""
57
+ end
58
+ end
59
+ end