milton 1.0.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.
@@ -0,0 +1,6 @@
1
+ === 1.0.0 / 2009-02-23
2
+
3
+ * 1 major enhancement
4
+
5
+ * Birthday!
6
+
@@ -0,0 +1,8 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ bin/milton
6
+ bin/recorder
7
+ lib/milton.rb
8
+ test/test_milton.rb
@@ -0,0 +1,63 @@
1
+ = milton
2
+
3
+ * http://seattlerb.rubyforge.org/milton
4
+
5
+ == DESCRIPTION:
6
+
7
+ Milton fills out your ADP ezLaborManager timesheet
8
+
9
+ == FEATURES/PROBLEMS:
10
+
11
+ * Fills out timesheets for arbitrary weeks
12
+ * Does not account for time off
13
+
14
+ == SYNOPSIS:
15
+
16
+ To fill out the current week:
17
+
18
+ milton
19
+
20
+ To view your timesheet for the current week:
21
+
22
+ milton --view
23
+
24
+ To fill out a timesheet for an arbitrary week:
25
+
26
+ milton --date=02/25/2009
27
+
28
+ To view a timesheet for an arbitrary week:
29
+
30
+ milton --view --date=02/25/2009
31
+
32
+ == REQUIREMENTS:
33
+
34
+ * ADP ezLaborManager client name, username and password
35
+
36
+ == INSTALL:
37
+
38
+ * sudo gem install milton
39
+
40
+ == LICENSE:
41
+
42
+ (The MIT License)
43
+
44
+ Copyright (c) 2009 Aaron Patterson, Eric Hodel
45
+
46
+ Permission is hereby granted, free of charge, to any person obtaining
47
+ a copy of this software and associated documentation files (the
48
+ 'Software'), to deal in the Software without restriction, including
49
+ without limitation the rights to use, copy, modify, merge, publish,
50
+ distribute, sublicense, and/or sell copies of the Software, and to
51
+ permit persons to whom the Software is furnished to do so, subject to
52
+ the following conditions:
53
+
54
+ The above copyright notice and this permission notice shall be
55
+ included in all copies or substantial portions of the Software.
56
+
57
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
58
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
59
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
60
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
61
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
62
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
63
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,12 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+ require './lib/milton.rb'
6
+
7
+ Hoe.new('milton', Milton::VERSION) do |p|
8
+ p.developer('Aaron Patterson', 'aaronp@rubyforge.org')
9
+ p.extra_deps = [['mechanize', '>= 0.9.2']]
10
+ end
11
+
12
+ # vim: syntax=Ruby
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby -w
2
+
3
+ require 'milton'
4
+
5
+ Milton.run
6
+
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby -w
2
+
3
+ require 'webrick'
4
+ require 'webrick/httpproxy'
5
+ require 'logger'
6
+
7
+ last_request_time = Time.now
8
+ human_requests = Logger.new('requests.log')
9
+
10
+ s = WEBrick::HTTPProxyServer.new(
11
+ :Logger => nil,
12
+ :Port => 8080,
13
+ :ProxyContentHandler => lambda { |req,res|
14
+ # If the last request time is ten seconds ago, let's say that a human
15
+ # made it
16
+ if Time.now - last_request_time >= 10
17
+ human_requests.debug("#{req}")
18
+ end
19
+ last_request_time = Time.now
20
+ }
21
+ )
22
+
23
+ Signal.trap('INT') do
24
+ s.shutdown
25
+ end
26
+
27
+ s.start
@@ -0,0 +1,293 @@
1
+ require 'rubygems'
2
+ require 'mechanize'
3
+ require 'logger'
4
+ require 'cgi'
5
+ require 'date'
6
+ require 'optparse'
7
+ require 'optparse/date'
8
+ require 'yaml'
9
+
10
+ ##
11
+ # Milton fills out timesheets for the ADP ezLaborManager.
12
+
13
+ class Milton
14
+
15
+ VERSION = '1.0.0'
16
+
17
+ def self.load_config config_file
18
+ unless File.exist? config_file then
19
+ open config_file, 'wb' do |f|
20
+ f.write YAML.dump({
21
+ 'client_name' => 'Your client name',
22
+ 'username' => 'Your username',
23
+ 'password' => 'Your password',
24
+ })
25
+ end
26
+
27
+ raise "Please fill out #{config_file}. We've created a template there for you to edit."
28
+ end
29
+
30
+ YAML.load_file config_file
31
+ end
32
+
33
+ def self.parse_args argv
34
+ options = {
35
+ 'date' => nil,
36
+ 'view' => false
37
+ }
38
+
39
+ opts = OptionParser.new do |opt|
40
+ opt.program_name = File.basename $0
41
+ opt.version = Milton::VERSION
42
+ opt.release = nil
43
+ opt.banner = <<-EOF
44
+ Usage: #{opt.program_name} [options]
45
+
46
+ Milton fills out your ADP timesheet for you. By default it fills it out for
47
+ the current week with eight hours/day.
48
+ EOF
49
+
50
+ opt.separator nil
51
+
52
+ opt.on('--view',
53
+ 'Only view your current timesheet') do |value|
54
+ options['view'] = value
55
+ end
56
+
57
+ opt.on('--date=DATE', Date,
58
+ 'Select week by day') do |value|
59
+ options['date'] = value
60
+ end
61
+
62
+ opt.on('--month [DATE]', Date, 'Select month by day') do |value|
63
+ options['view'] = true # For your safety, you can only view months
64
+ options['month'] = value || Date.today
65
+ end
66
+
67
+ opt.on('--fuck-the-man',
68
+ 'Do not include lunch in your timesheet') do |value|
69
+ options['rows_per_day'] = 1
70
+ end
71
+ end
72
+
73
+ opts.parse! argv
74
+
75
+ options
76
+ end
77
+
78
+ def self.run argv = ARGV
79
+ config_file = File.join Gem.user_home, '.milton'
80
+
81
+ options = parse_args argv
82
+
83
+ config = load_config config_file
84
+
85
+ options.merge! config
86
+
87
+ new.run options
88
+ end
89
+
90
+ def initialize &block
91
+ @agent = WWW::Mechanize.new
92
+ @page = nil
93
+ @username = nil
94
+ yield self if block_given?
95
+ end
96
+
97
+ ##
98
+ # Sets the client name +name+
99
+
100
+ def client_name= name
101
+ page = @agent.get('http://workforceportal.elabor.com/ezLaborManagerNetRedirect/clientlogin.aspx')
102
+ @page = page.form('ClientLoginForm') { |form|
103
+ form.txtClientName = name
104
+ form.hdnTimeZone = 'Pacific Standard Time'
105
+ form['__EVENTTARGET'] = 'btnSubmit'
106
+ }.submit
107
+ @page = @page.form_with(:action => /ezlmportaldc2.adp.com/).submit
108
+ @page = @page.form_with(:action => /adp\.com/).submit
109
+ end
110
+
111
+ ##
112
+ # Logs in +username+ with +password+
113
+
114
+ def login username, password
115
+ @username = username
116
+
117
+ @page = @page.form('Login') { |form|
118
+ form['txtUserID'] = username
119
+ form['txtPassword'] = password
120
+ form['__EVENTTARGET'] = 'btnLogin'
121
+ }.submit
122
+ change_password if @page.body =~ /Old Password/
123
+ @page = @page.link_with(:text => 'Time Sheet').click
124
+ end
125
+
126
+ ##
127
+ # Selects the current week's timesheet
128
+
129
+ def select_current_week
130
+ select_week_of Date.today
131
+ end
132
+
133
+ def rows_per_day= rows = 2
134
+ @rows_per_day = rows
135
+ @page = @page.form('Form1') { |form|
136
+ form['__EVENTTARGET'] = 'SETNOOFROWS'
137
+ form['__EVENTARGUMENT'] = rows.to_s
138
+ form['__PageDirty'] = 'False'
139
+ }.submit
140
+ end
141
+
142
+ def run config
143
+ self.client_name = config['client_name']
144
+ login config['username'], config['password']
145
+
146
+ date = config['date']
147
+ month = config['month']
148
+
149
+ if date then
150
+ select_week_of date
151
+ elsif month
152
+ select_month_of month
153
+ else
154
+ select_current_week
155
+ end
156
+
157
+ unless config['view'] then
158
+ self.rows_per_day = config['rows_per_day'] || 2
159
+ fill_timesheet
160
+ end
161
+
162
+ extract_timesheet
163
+ end
164
+
165
+ ##
166
+ # Fills in timesheet rows that don't already have data
167
+
168
+ def fill_timesheet
169
+ rows = []
170
+ last_date = nil
171
+
172
+ parse_timesheet.each do |data|
173
+ next if data[0].to_i > 0
174
+
175
+ department = data[6]
176
+ employee_id = data[7]
177
+ date = Date.parse(CGI.unescape(data[1])).strftime('%m/%d/%Y')
178
+
179
+ start, finish = starting_and_ending_timestamp(date, last_date)
180
+
181
+ rows << ['0','','False','True','False','False','False',
182
+ "#{date} 12:00:00 AM",
183
+ start,'',
184
+ finish,
185
+ '8','',
186
+ department,
187
+ employee_id,
188
+ '','','','','','','','','','','','','','','','','','','','','EDIT','','','','','2','','0','False']
189
+
190
+ # This reset is for the timestamp calculations.
191
+ last_date = date
192
+ end
193
+
194
+ @page = @page.form('Form1') { |form|
195
+ ## FIXME: Fill out this form
196
+ form['hdnRETURNDATA'] = rows.map { |row|
197
+ row.map { |value|
198
+ CGI.escape(value)
199
+ }.join('~~')
200
+ }.join('~||~')
201
+
202
+ form['__EVENTTARGET'] = 'TG:btnSubmitTop'
203
+ form['__PageDirty'] = 'True'
204
+ }.submit
205
+ end
206
+
207
+ ##
208
+ # Prints out your timesheet for the selected time frame
209
+
210
+ def extract_timesheet
211
+ timesheet = parse_timesheet
212
+
213
+ department = timesheet.first[6]
214
+ employee_id = timesheet.first[7]
215
+
216
+ puts "Employee #{@username} id #{employee_id}, department #{department}"
217
+
218
+ puts "-" * 80
219
+
220
+ timesheet.each do |row|
221
+ if row[0] == '0' then
222
+ puts "#{row[2]} no time entered"
223
+ else
224
+ puts "#{row[2]} #{row[3]} to #{row[4]} for %2s hours" % row[5]
225
+ end
226
+ end
227
+ end
228
+
229
+ ##
230
+ # Selects the timesheet for the week containing +date+
231
+
232
+ def select_week_of date
233
+ monday = date - date.wday + 1
234
+ friday = monday + 4
235
+ select_range(monday, friday)
236
+ end
237
+
238
+ def select_month_of date
239
+ first = date - date.mday + 1
240
+ last = Date.new(first.year, first.month, -1)
241
+ select_range(first, last)
242
+ end
243
+
244
+ private
245
+
246
+ def change_password
247
+ puts "Your password needs to be updated. :-("
248
+ puts "\t#{@page.uri}"
249
+ exit
250
+ end
251
+
252
+ def select_range start, finish
253
+ @page = @page.form('Form1') { |form|
254
+ form['__EVENTTARGET'] = 'ctrlDtRangeSelector'
255
+ form['ctrlDtRangeSelector:SelectionItem'] = '3' # Set to this week
256
+ form['ctrlDtRangeSelector:BeginDate'] = start.strftime('%m/%d/%Y')
257
+ form['ctrlDtRangeSelector:EndDate'] = finish.strftime('%m/%d/%Y')
258
+ form['__PageDirty'] = 'False'
259
+ }.submit
260
+ end
261
+
262
+ ##
263
+ # Returns an array of arrays containing: row id, day start time, date, start
264
+ # time, end time, hours, department, employee id. All values are strings.
265
+
266
+ def parse_timesheet
267
+ @page.body.scan(/TCMS.oTD.push\((\[.*\])\)/).map do |match|
268
+ match[0].gsub(/"/, '').split(',').map { |x|
269
+ CGI.unescape(x.strip).delete('[]')
270
+ }.values_at(0, 7, 8, 9, 11, 12, 14, 15)
271
+ end
272
+ end
273
+
274
+ ##
275
+ # Returns the starting and ending EZLabor-style timestamps for the
276
+ # current date row in the timesheet.
277
+ def starting_and_ending_timestamp(date, last_date)
278
+ if @rows_per_day == 2
279
+ if last_date == date
280
+ start_timestamp = "#{date} 08:30 AM"
281
+ end_timestamp = '12:00 PM'
282
+ else
283
+ start_timestamp = "#{date} 12:30 PM"
284
+ end_timestamp = '05:00 PM'
285
+ end
286
+ else
287
+ start_timestamp = "#{date} 08:30 AM"
288
+ end_timestamp = '04:30 PM'
289
+ end
290
+ return start_timestamp, end_timestamp
291
+ end
292
+ end
293
+
File without changes
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: milton
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Aaron Patterson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-06-04 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: mechanize
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.9.2
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: hoe
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.12.2
34
+ version:
35
+ description: Milton fills out your ADP ezLaborManager timesheet
36
+ email:
37
+ - aaronp@rubyforge.org
38
+ executables:
39
+ - milton
40
+ - recorder
41
+ extensions: []
42
+
43
+ extra_rdoc_files:
44
+ - History.txt
45
+ - Manifest.txt
46
+ - README.txt
47
+ files:
48
+ - History.txt
49
+ - Manifest.txt
50
+ - README.txt
51
+ - Rakefile
52
+ - bin/milton
53
+ - bin/recorder
54
+ - lib/milton.rb
55
+ - test/test_milton.rb
56
+ has_rdoc: true
57
+ homepage: http://seattlerb.rubyforge.org/milton
58
+ licenses: []
59
+
60
+ post_install_message:
61
+ rdoc_options:
62
+ - --main
63
+ - README.txt
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: "0"
71
+ version:
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: "0"
77
+ version:
78
+ requirements: []
79
+
80
+ rubyforge_project: milton
81
+ rubygems_version: 1.3.3
82
+ signing_key:
83
+ specification_version: 3
84
+ summary: Milton fills out your ADP ezLaborManager timesheet
85
+ test_files:
86
+ - test/test_milton.rb