timestream 0.0.3 → 0.1.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/bin/timestream +58 -1
- data/lib/timestream/employee_activity_report.rb +79 -19
- data/lib/timestream/hamster.rb +101 -0
- data/lib/timestream/version.rb +1 -1
- data/timestream.gemspec +2 -0
- metadata +35 -6
data/.gitignore
CHANGED
data/bin/timestream
CHANGED
@@ -1,8 +1,65 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
require 'rubygems'
|
4
|
+
|
5
|
+
require 'chronic'
|
6
|
+
require 'optparse'
|
4
7
|
require 'timestream/employee_activity_report'
|
8
|
+
require 'timestream/hamster'
|
9
|
+
require 'timestream/version'
|
10
|
+
|
11
|
+
options = {}
|
12
|
+
|
13
|
+
option_parser = OptionParser.new do |option_parser|
|
14
|
+
option_parser.banner = "Usage: timestream [--date-format=DATE_FORMAT] [--week YYYY-MM-DD]"
|
15
|
+
|
16
|
+
options[:date_format] = :day_of_week
|
17
|
+
option_parser.on('-d',
|
18
|
+
'--date-format DATE_FORMAT',
|
19
|
+
"Format dates according to DATE_FORMAT. Supports DATE_FORMAT of 'day_of_week', or 'erp'.") do |date_format|
|
20
|
+
options[:date_format] = date_format.to_sym
|
21
|
+
end
|
22
|
+
|
23
|
+
option_parser.on_tail('-h', '--help', 'Show this message') do
|
24
|
+
puts option_parser
|
25
|
+
exit
|
26
|
+
end
|
27
|
+
|
28
|
+
option_parser.on_tail('--version', 'Show version') do
|
29
|
+
puts Timestream::VERSION
|
30
|
+
exit
|
31
|
+
end
|
32
|
+
|
33
|
+
option_parser.on('--week WEEK',
|
34
|
+
"Read in data from Hamster and write it out to EAR for WEEK " \
|
35
|
+
"beginning on Saturday. Use Chronic gem for parsing WEEK, " \
|
36
|
+
"so you do strings like 'last saturday'") do |week|
|
37
|
+
time = Chronic.parse(week, :context => :past)
|
38
|
+
options[:saturday] = Date.new(time.year, time.month, time.day)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
option_parser.parse!
|
5
43
|
|
6
44
|
employee_activity_report = Timestream::EmployeeActivityReport.new ARGV[0]
|
7
45
|
|
8
|
-
|
46
|
+
DAYS_PER_WEEK = 7
|
47
|
+
HOURS_PER_DAY = 24
|
48
|
+
MINUTES_PER_HOUR = 60
|
49
|
+
SECONDS_PER_MINUTE = 60
|
50
|
+
HOURS_PER_WEEK = DAYS_PER_WEEK * HOURS_PER_DAY
|
51
|
+
MINUTES_PER_WEEK = HOURS_PER_WEEK * MINUTES_PER_HOUR
|
52
|
+
SECONDS_PER_WEEK = MINUTES_PER_WEEK * SECONDS_PER_MINUTE
|
53
|
+
|
54
|
+
if options[:saturday]
|
55
|
+
saturday_date = options[:saturday]
|
56
|
+
saturday_time = Time.local(saturday_date.year, saturday_date.month, saturday_date.day)
|
57
|
+
# next saturday - 1 second for 23:59 on Friday
|
58
|
+
friday_time = saturday_time + (SECONDS_PER_WEEK) - 1
|
59
|
+
hamster = Timestream::Hamster.new(saturday_time, friday_time)
|
60
|
+
|
61
|
+
employee_activity_report.updateFromHamster!(hamster)
|
62
|
+
end
|
63
|
+
|
64
|
+
puts employee_activity_report.to_text_table(options[:date_format])
|
65
|
+
|
@@ -4,25 +4,33 @@ require 'terminal-table/import'
|
|
4
4
|
|
5
5
|
module Timestream
|
6
6
|
class EmployeeActivityReport
|
7
|
-
|
8
|
-
attr_reader :column
|
9
|
-
|
10
|
-
def initialize(row, column)
|
11
|
-
@column = column
|
12
|
-
@row = row
|
13
|
-
end
|
14
|
-
|
15
|
-
attr_reader :row
|
16
|
-
end
|
7
|
+
ACTIVITY_COLUMN = 2
|
17
8
|
|
18
|
-
|
19
|
-
|
20
|
-
end
|
21
|
-
|
22
|
-
DATE_CELL = Cell.new(2, 1)
|
9
|
+
DATE_COLUMN = 1
|
10
|
+
DATE_ROW = 2
|
23
11
|
|
24
12
|
DAY_COLUMN_SPAN = 3
|
25
13
|
|
14
|
+
def date_column(date)
|
15
|
+
slot = date_slot(date)
|
16
|
+
slot_column(slot)
|
17
|
+
end
|
18
|
+
|
19
|
+
def date_slot(date)
|
20
|
+
day_delta = (date - saturday).to_i
|
21
|
+
|
22
|
+
case day_delta
|
23
|
+
# Saturday and Sunday map to weekend
|
24
|
+
when 0..1
|
25
|
+
0
|
26
|
+
# All others map to their own day
|
27
|
+
when 2..6
|
28
|
+
day_delta - 1
|
29
|
+
else
|
30
|
+
raise ArgumentError, "#{date} is not in the same week as #{saturday}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
26
34
|
def initialize(path)
|
27
35
|
@path = path
|
28
36
|
end
|
@@ -33,6 +41,14 @@ module Timestream
|
|
33
41
|
worksheet.row(index)
|
34
42
|
end
|
35
43
|
|
44
|
+
def saturday
|
45
|
+
@saturday ||= worksheet.row(DATE_ROW)[DATE_COLUMN]
|
46
|
+
end
|
47
|
+
|
48
|
+
def slot_column(slot)
|
49
|
+
9 + (slot * DAY_COLUMN_SPAN)
|
50
|
+
end
|
51
|
+
|
36
52
|
def spreadsheet
|
37
53
|
@spreadsheet ||= Spreadsheet.open path
|
38
54
|
end
|
@@ -79,13 +95,26 @@ module Timestream
|
|
79
95
|
@timestream_by_project_number
|
80
96
|
end
|
81
97
|
|
82
|
-
def to_text_table
|
98
|
+
def to_text_table(date_format=:day_of_week)
|
83
99
|
employee_activity_report = self
|
100
|
+
|
84
101
|
table {
|
85
102
|
self.headings = ['Project Number']
|
86
|
-
|
87
|
-
|
88
|
-
|
103
|
+
|
104
|
+
case date_format
|
105
|
+
when :day_of_week
|
106
|
+
date_format = "%A"
|
107
|
+
when :erp
|
108
|
+
date_format = "%a %m/%d"
|
109
|
+
end
|
110
|
+
|
111
|
+
Timestream::SLOT_NAMES.each_index do |sunday_offset|
|
112
|
+
# XXX assign weekend activity to sunday to simplify math
|
113
|
+
sunday = employee_activity_report.saturday + 1
|
114
|
+
slot_date = sunday + sunday_offset
|
115
|
+
|
116
|
+
self.headings << slot_date.strftime(date_format)
|
117
|
+
end
|
89
118
|
|
90
119
|
employee_activity_report.timestream_by_project_number.each do |project_number, timestream|
|
91
120
|
row = [project_number]
|
@@ -98,6 +127,37 @@ module Timestream
|
|
98
127
|
}
|
99
128
|
end
|
100
129
|
|
130
|
+
def updateFromHamster(hamster)
|
131
|
+
# Categories in Hamster are mapped to Activities in EAR
|
132
|
+
|
133
|
+
hamster.timestream_by_category.each do |category, timestream|
|
134
|
+
# XXX should this been creating EAR Timestreams instead of updating rows directly?
|
135
|
+
(12 .. 36).each do |row_index|
|
136
|
+
row = self.row(row_index)
|
137
|
+
activity = row[ACTIVITY_COLUMN]
|
138
|
+
|
139
|
+
# XXX Category is assumed to be a substring of Activity since Activity is very wordy.
|
140
|
+
if activity and activity.include? category
|
141
|
+
timestream.each do |date, hours|
|
142
|
+
column = self.date_column(date)
|
143
|
+
row[column] ||= 0
|
144
|
+
row[column] += hours
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def updateFromHamster!(hamster)
|
152
|
+
updateFromHamster(hamster)
|
153
|
+
|
154
|
+
# spreadsheet library recommends not writing back to same file, write to
|
155
|
+
# temporary file and then rename that file to original.
|
156
|
+
temporary_path = "#{path}.timestream"
|
157
|
+
spreadsheet.write(temporary_path)
|
158
|
+
File.rename(temporary_path, path)
|
159
|
+
end
|
160
|
+
|
101
161
|
def worksheet
|
102
162
|
@worksheet ||= spreadsheet.worksheet "EAR - Current Week"
|
103
163
|
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
require 'date'
|
4
|
+
require 'dbus'
|
5
|
+
require 'terminal-table/import'
|
6
|
+
require 'time'
|
7
|
+
|
8
|
+
module Timestream
|
9
|
+
class Hamster
|
10
|
+
class Fact
|
11
|
+
attr_reader :category_name
|
12
|
+
attr_reader :date
|
13
|
+
attr_reader :delta_in_seconds
|
14
|
+
|
15
|
+
def initialize(id, start_timestamp, end_timestamp, description, activity_name, activity_id, category_name, tag_list, date_timestamp, delta_in_seconds)
|
16
|
+
@id = id
|
17
|
+
|
18
|
+
# Convert start and end to Times
|
19
|
+
@start_time = Time.at(start_timestamp)
|
20
|
+
@end_time = Time.at(end_timestamp)
|
21
|
+
|
22
|
+
@description = description
|
23
|
+
@activity_name = activity_name
|
24
|
+
@activity_id = activity_id
|
25
|
+
@category_name = category_name
|
26
|
+
@tag_list = tag_list
|
27
|
+
|
28
|
+
# Convert date to a Date
|
29
|
+
date_time = Time.at(date_timestamp)
|
30
|
+
date = Date.new(date_time.year, date_time.month, date_time.day)
|
31
|
+
@date = date
|
32
|
+
|
33
|
+
@delta_in_seconds = delta_in_seconds
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def facts
|
38
|
+
dbus_facts = proxy.GetFacts(@start_time.to_i, @end_time.to_i, '')
|
39
|
+
# XXX for some reason GetFacts returns facts in a one-element array
|
40
|
+
dbus_facts = dbus_facts[0]
|
41
|
+
|
42
|
+
# convert dbus facts into instances
|
43
|
+
dbus_facts.collect { |dbus_fact|
|
44
|
+
Fact.new(*dbus_fact)
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
def initialize(start_time, end_time)
|
49
|
+
@start_time = start_time
|
50
|
+
@end_time = end_time
|
51
|
+
end
|
52
|
+
|
53
|
+
def proxy
|
54
|
+
@proxy ||= begin
|
55
|
+
session_bus = DBus::SessionBus.instance
|
56
|
+
hamster_service = session_bus.service('org.gnome.Hamster')
|
57
|
+
hamster = hamster_service.object('/org/gnome/Hamster')
|
58
|
+
# interfaces and therefore methods won't work if not first introspected
|
59
|
+
hamster.introspect
|
60
|
+
# set interface so methods can be called directly on object
|
61
|
+
hamster.default_iface = 'org.gnome.Hamster'
|
62
|
+
|
63
|
+
hamster
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class Timestream
|
68
|
+
def [](date)
|
69
|
+
@hours_by_date[date]
|
70
|
+
end
|
71
|
+
|
72
|
+
def []=(date, hours)
|
73
|
+
@hours_by_date[date] = hours
|
74
|
+
end
|
75
|
+
|
76
|
+
def initialize
|
77
|
+
@hours_by_date = Hash.new(0)
|
78
|
+
end
|
79
|
+
|
80
|
+
def each(&block)
|
81
|
+
@hours_by_date.sort.each(&block)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def timestream_by_category
|
86
|
+
if @timestream_by_category.nil?
|
87
|
+
@timestream_by_category = Hash.new { |hash, category|
|
88
|
+
hash[category] = Timestream.new
|
89
|
+
}
|
90
|
+
|
91
|
+
facts.each do |fact|
|
92
|
+
timestream = @timestream_by_category[fact.category_name]
|
93
|
+
# convert seconds to fractional hours
|
94
|
+
timestream[fact.date] += fact.delta_in_seconds.fdiv(60 * 60)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
@timestream_by_category
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
data/lib/timestream/version.rb
CHANGED
data/timestream.gemspec
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: timestream
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 27
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
+
- 1
|
8
9
|
- 0
|
9
|
-
|
10
|
-
version: 0.0.3
|
10
|
+
version: 0.1.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Luke Imhoff
|
@@ -15,11 +15,11 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-05-
|
18
|
+
date: 2011-05-16 00:00:00 -05:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
22
|
-
name:
|
22
|
+
name: chronic
|
23
23
|
prerelease: false
|
24
24
|
requirement: &id001 !ruby/object:Gem::Requirement
|
25
25
|
none: false
|
@@ -33,7 +33,7 @@ dependencies:
|
|
33
33
|
type: :runtime
|
34
34
|
version_requirements: *id001
|
35
35
|
- !ruby/object:Gem::Dependency
|
36
|
-
name:
|
36
|
+
name: ruby-dbus
|
37
37
|
prerelease: false
|
38
38
|
requirement: &id002 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
@@ -46,6 +46,34 @@ dependencies:
|
|
46
46
|
version: "0"
|
47
47
|
type: :runtime
|
48
48
|
version_requirements: *id002
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: spreadsheet
|
51
|
+
prerelease: false
|
52
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
hash: 3
|
58
|
+
segments:
|
59
|
+
- 0
|
60
|
+
version: "0"
|
61
|
+
type: :runtime
|
62
|
+
version_requirements: *id003
|
63
|
+
- !ruby/object:Gem::Dependency
|
64
|
+
name: terminal-table
|
65
|
+
prerelease: false
|
66
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
hash: 3
|
72
|
+
segments:
|
73
|
+
- 0
|
74
|
+
version: "0"
|
75
|
+
type: :runtime
|
76
|
+
version_requirements: *id004
|
49
77
|
description: Converts Hamster report to EAR to JDE Timecard
|
50
78
|
email:
|
51
79
|
- luke@cray.com
|
@@ -62,6 +90,7 @@ files:
|
|
62
90
|
- bin/timestream
|
63
91
|
- lib/timestream.rb
|
64
92
|
- lib/timestream/employee_activity_report.rb
|
93
|
+
- lib/timestream/hamster.rb
|
65
94
|
- lib/timestream/version.rb
|
66
95
|
- timestream.gemspec
|
67
96
|
has_rdoc: true
|