timesheet 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest.txt +1 -0
- data/lib/timesheet.rb +2 -1
- data/lib/timesheet/report_item.rb +53 -0
- data/lib/timesheet/time_entry.rb +14 -0
- data/lib/timesheet/time_report.rb +14 -19
- data/lib/timesheet/timesheet_parser.rb +3 -3
- data/spec/time_entry_spec.rb +18 -2
- data/spec/time_report_spec.rb +3 -2
- metadata +3 -2
data/Manifest.txt
CHANGED
data/lib/timesheet.rb
CHANGED
@@ -9,6 +9,7 @@ require 'yaml'
|
|
9
9
|
require 'yaml/store'
|
10
10
|
require 'timesheet/range_extensions'
|
11
11
|
require 'timesheet/time_entry'
|
12
|
+
require 'timesheet/report_item'
|
12
13
|
require 'timesheet/time_log'
|
13
14
|
require 'timesheet/time_report'
|
14
15
|
require 'timesheet/trollop'
|
@@ -16,7 +17,7 @@ require 'timesheet/timesheet_parser'
|
|
16
17
|
|
17
18
|
class Timesheet
|
18
19
|
|
19
|
-
VERSION = '0.2.
|
20
|
+
VERSION = '0.2.2'
|
20
21
|
|
21
22
|
def self.run(params)
|
22
23
|
command_hash = {}
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
|
3
|
+
class ReportItem < DelegateClass(TimeEntry)
|
4
|
+
|
5
|
+
include Comparable
|
6
|
+
|
7
|
+
def initialize( report, time_entry)
|
8
|
+
@time_entry = time_entry
|
9
|
+
super(@time_entry)
|
10
|
+
@report = report
|
11
|
+
end
|
12
|
+
|
13
|
+
def start_time
|
14
|
+
if @time_entry.to_range.include?(@report.report_start)
|
15
|
+
@report.report_start
|
16
|
+
else
|
17
|
+
@time_entry.start_time
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def end_time
|
22
|
+
if to_range.include?(@report.report_end)
|
23
|
+
@report.report_end
|
24
|
+
else
|
25
|
+
@time_entry.end_time
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def formatted_record_number
|
30
|
+
sprintf("%5d", @time_entry.record_number)
|
31
|
+
end
|
32
|
+
|
33
|
+
def formatted_times
|
34
|
+
puts start_time
|
35
|
+
puts end_time
|
36
|
+
str = ""
|
37
|
+
str << start_time.strftime("%m/%d/%Y at %I:%M %p")
|
38
|
+
str << " to "
|
39
|
+
str << end_time.strftime("%m/%d/%Y at ") if ((start_time.year != end_time.year) || (start_time.yday != end_time.yday))
|
40
|
+
str << end_time.strftime("%I:%M %p")
|
41
|
+
end
|
42
|
+
|
43
|
+
def formatted_duration
|
44
|
+
str = ""
|
45
|
+
str << @time_entry.duration.strftime("%d Days ") if @time_entry.duration.days > 0
|
46
|
+
str << @time_entry.duration.strftime("%hh %mm")
|
47
|
+
end
|
48
|
+
|
49
|
+
def <=>(other_time_entry)
|
50
|
+
self.start_time <=> other_time_entry.start_time
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
data/lib/timesheet/time_entry.rb
CHANGED
@@ -46,5 +46,19 @@ class TimeEntry
|
|
46
46
|
def to_range
|
47
47
|
Range.new(@start_time, @end_time, true)
|
48
48
|
end
|
49
|
+
|
50
|
+
def to_s
|
51
|
+
label_width = 10
|
52
|
+
str = ""
|
53
|
+
str << "#{"class:".ljust(label_width)}#{self.class}\n"
|
54
|
+
str << "#{"record:".ljust(label_width)}#{record_number}\n"
|
55
|
+
str << "#{"project:".ljust(label_width)}#{project}\n"
|
56
|
+
str << "#{"start:".ljust(label_width)}#{start_time.strftime("%m/%d/%Y at %I:%M %p")}\n"
|
57
|
+
str << "#{"end:".ljust(label_width)}#{end_time.strftime("%m/%d/%Y at %I:%M %p")}\n"
|
58
|
+
str << "#{"duration:".ljust(label_width)}#{duration}\n"
|
59
|
+
str << "#{"comment:".ljust(label_width)}#{comment}\n"
|
60
|
+
|
61
|
+
str
|
62
|
+
end
|
49
63
|
|
50
64
|
end
|
@@ -4,17 +4,16 @@ require 'time'
|
|
4
4
|
class TimeReport
|
5
5
|
|
6
6
|
def initialize(entries)
|
7
|
-
@entries = entries
|
7
|
+
@entries = (entries.nil?) ? [] : entries.map { |entry| ReportItem.new(self, entry)}
|
8
8
|
end
|
9
9
|
|
10
10
|
attr_accessor :entries
|
11
|
+
attr_reader :report_start
|
12
|
+
attr_reader :report_end
|
11
13
|
|
12
|
-
def
|
13
|
-
@
|
14
|
-
|
15
|
-
e.end_time = end_time if e.to_range.include?(end_time)
|
16
|
-
e
|
17
|
-
end
|
14
|
+
def constrain(start_time, end_time)
|
15
|
+
@report_start = start_time
|
16
|
+
@report_end = end_time
|
18
17
|
end
|
19
18
|
|
20
19
|
def report(command_options, outstream = STDOUT)
|
@@ -22,7 +21,7 @@ class TimeReport
|
|
22
21
|
if (@entries.nil? || @entries.empty?)
|
23
22
|
outstream.puts("No data available.")
|
24
23
|
else
|
25
|
-
|
24
|
+
constrain(command_options[:start], command_options[:end])
|
26
25
|
|
27
26
|
detail_report(outstream) if command_options[:detail]
|
28
27
|
summary_report(outstream) if command_options[:summary]
|
@@ -33,29 +32,25 @@ class TimeReport
|
|
33
32
|
private
|
34
33
|
|
35
34
|
def detail_report(outstream)
|
35
|
+
|
36
|
+
# field_names = ["Id", "Project", "Start - Stop", "Hours", "Comment"]
|
37
|
+
# field_methods = [:formatted_record_number, :project, :formatted_times, :formatted_duration, :comment]
|
38
|
+
|
36
39
|
table = Ruport::Data::Table({ :column_names => ["Id", "Project", "Start - Stop", "Hours", "Comment"]}) do |table|
|
37
40
|
@entries.sort.each do |entry|
|
38
41
|
row = []
|
39
42
|
|
40
43
|
# format record number
|
41
|
-
row <<
|
44
|
+
row << entry.formatted_record_number
|
42
45
|
|
43
46
|
# format project
|
44
47
|
row << entry.project
|
45
48
|
|
46
49
|
# format start - stop
|
47
|
-
|
48
|
-
str << entry.start_time.strftime("%m/%d/%Y at %I:%M %p")
|
49
|
-
str << " to "
|
50
|
-
str << entry.end_time.strftime("%m/%d/%Y at ") if ( (entry.start_time.year != entry.end_time.year) && (entry.start_time.yday != entry.end_time.yday))
|
51
|
-
str << entry.end_time.strftime("%I:%M %p")
|
52
|
-
row << str
|
50
|
+
row << entry.formatted_times
|
53
51
|
|
54
52
|
# format duration
|
55
|
-
|
56
|
-
str << entry.duration.strftime("%d Days ") if entry.duration.days > 0
|
57
|
-
str << entry.duration.strftime("%hh %mm")
|
58
|
-
row << str
|
53
|
+
row << entry.formatted_duration
|
59
54
|
|
60
55
|
# format comment
|
61
56
|
row << entry.comment
|
@@ -212,7 +212,7 @@ EOS
|
|
212
212
|
|
213
213
|
when :current_week
|
214
214
|
periods[:start] = Chronic.parse("this week #{WEEK_START} at 12 am")
|
215
|
-
periods[:end] = periods[:start] +
|
215
|
+
periods[:end] = periods[:start] + 1.week
|
216
216
|
|
217
217
|
when :current_month
|
218
218
|
today = Date.today
|
@@ -225,8 +225,8 @@ EOS
|
|
225
225
|
periods[:end] = periods[:start] + 1.day
|
226
226
|
|
227
227
|
when :last_week
|
228
|
-
periods[:start] = Chronic.parse("
|
229
|
-
periods[:end] = periods[:start] +
|
228
|
+
periods[:start] = Chronic.parse("this week #{WEEK_START} at 12 am") - 1.week
|
229
|
+
periods[:end] = periods[:start] + 1.week
|
230
230
|
|
231
231
|
when :last_month
|
232
232
|
today = Date.today
|
data/spec/time_entry_spec.rb
CHANGED
@@ -17,7 +17,8 @@ describe TimeEntry do
|
|
17
17
|
context "Basic Features" do
|
18
18
|
|
19
19
|
before :each do
|
20
|
-
@time_entry = TimeEntry.new("Project", Time.local(2009, 12, 1, 9, 0, 0), Time.local(2009, 12, 1, 17, 0, 0))
|
20
|
+
@time_entry = TimeEntry.new("Project", Time.local(2009, 12, 1, 9, 0, 0), Time.local(2009, 12, 1, 17, 0, 0), "a comment")
|
21
|
+
@time_entry.record_number = 1
|
21
22
|
end
|
22
23
|
|
23
24
|
|
@@ -34,7 +35,8 @@ describe TimeEntry do
|
|
34
35
|
end
|
35
36
|
|
36
37
|
it "should have an initial comment of nil" do
|
37
|
-
|
38
|
+
time_entry = TimeEntry.new("Project", Time.local(2009, 12, 1, 9, 0, 0), Time.local(2009, 12, 1, 17, 0, 0))
|
39
|
+
time_entry.comment.should == nil
|
38
40
|
end
|
39
41
|
|
40
42
|
it "should calculate its duration from start and end times" do
|
@@ -74,6 +76,20 @@ describe TimeEntry do
|
|
74
76
|
it "should not allow assignment to duration" do
|
75
77
|
@time_entry.should_not respond_to :duration=
|
76
78
|
end
|
79
|
+
|
80
|
+
it "should have a to_s method to display its contents" do
|
81
|
+
@time_entry.should respond_to :to_s
|
82
|
+
@time_entry.to_s.should eql( <<EOS
|
83
|
+
class: TimeEntry
|
84
|
+
record: 1
|
85
|
+
project: Project
|
86
|
+
start: 12/01/2009 at 09:00 AM
|
87
|
+
end: 12/01/2009 at 05:00 PM
|
88
|
+
duration: 0 seconds 0 minutes 8 hours 0 days
|
89
|
+
comment: a comment
|
90
|
+
EOS
|
91
|
+
)
|
92
|
+
end
|
77
93
|
|
78
94
|
end
|
79
95
|
|
data/spec/time_report_spec.rb
CHANGED
@@ -13,13 +13,13 @@ describe TimeReport do
|
|
13
13
|
end
|
14
14
|
|
15
15
|
it "should be able to trim its entries to a specific time span" do
|
16
|
-
@time_report.
|
16
|
+
@time_report.constrain(Time.local(2009, 12, 1, 11, 0, 0), Time.local(2009, 12, 5, 12, 0, 0))
|
17
17
|
@time_report.entries[0].start_time.should eql(Time.local(2009, 12, 1, 11, 0, 0))
|
18
18
|
@time_report.entries[-1].end_time.should eql(Time.local(2009, 12, 5, 12, 0, 0))
|
19
19
|
end
|
20
20
|
|
21
21
|
it "should leave entries alone when the trim span is outside the entry range" do
|
22
|
-
@time_report.
|
22
|
+
@time_report.constrain(Time.local(2009, 11, 1, 0, 0, 0), Time.local(2009, 12, 31, 0, 0, 0))
|
23
23
|
@time_report.entries[0].start_time.should eql(Time.local(2009, 12, 1, 9, 0, 0))
|
24
24
|
@time_report.entries[-1].end_time.should eql(Time.local(2009, 12, 5, 17, 0, 0))
|
25
25
|
end
|
@@ -92,6 +92,7 @@ EOS
|
|
92
92
|
|
93
93
|
it "should be able to produce a byday report" do
|
94
94
|
@entries << TimeEntry.new("ProjectB", Time.local(2009, 12, 1, 17, 0, 0), Time.local(2009, 12, 1, 19, 0, 0), "another comment")
|
95
|
+
@time_report = TimeReport.new(@entries)
|
95
96
|
|
96
97
|
command_options = {:byday => true, :start => Time.local(2009, 12, 1), :end => Time.local(2009, 12, 6)}
|
97
98
|
stream = StringIO.new
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: timesheet
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John F. Schank III
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2010-
|
12
|
+
date: 2010-02-07 00:00:00 -05:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -225,6 +225,7 @@ files:
|
|
225
225
|
- lib/timesheet.rb
|
226
226
|
- lib/timesheet/range_extensions.rb
|
227
227
|
- lib/timesheet/time_entry.rb
|
228
|
+
- lib/timesheet/report_item.rb
|
228
229
|
- lib/timesheet/time_log.rb
|
229
230
|
- lib/timesheet/time_report.rb
|
230
231
|
- lib/timesheet/timesheet_parser.rb
|