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 CHANGED
@@ -2,3 +2,4 @@
2
2
  .bundle
3
3
  Gemfile.lock
4
4
  pkg/*
5
+ .*.swp
@@ -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
- puts employee_activity_report.to_text_table
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
- class Cell
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
- def cell(location)
19
- worksheet.row(location.row)[location.column]
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
- self.headings.concat Timestream::SLOT_NAMES.collect { |name|
87
- name.to_s.capitalize
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
@@ -1,3 +1,3 @@
1
1
  module Timestream
2
- VERSION = "0.0.3"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -14,6 +14,8 @@ Gem::Specification.new do |s|
14
14
 
15
15
  s.rubyforge_project = "timestream"
16
16
 
17
+ s.add_dependency("chronic")
18
+ s.add_dependency("ruby-dbus")
17
19
  s.add_dependency("spreadsheet")
18
20
  s.add_dependency("terminal-table")
19
21
 
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: 25
4
+ hash: 27
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
+ - 1
8
9
  - 0
9
- - 3
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-12 00:00:00 -05:00
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: spreadsheet
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: terminal-table
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