milton 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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