achoo 0.3 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/CHANGES +12 -0
  2. data/README.rdoc +26 -30
  3. data/Rakefile +3 -0
  4. data/bin/achoo +2 -2
  5. data/lib/achoo.rb +1 -139
  6. data/lib/achoo/achievo.rb +13 -0
  7. data/lib/achoo/achievo/form.rb +22 -0
  8. data/lib/achoo/achievo/hour_administration_form.rb +91 -0
  9. data/lib/achoo/achievo/hour_registration_form.rb +230 -0
  10. data/lib/achoo/achievo/hour_registration_form_ranged.rb +49 -0
  11. data/lib/achoo/achievo/lock_month_form.rb +44 -0
  12. data/lib/achoo/achievo/login_form.rb +27 -0
  13. data/lib/achoo/achievo/table.rb +30 -0
  14. data/lib/achoo/app.rb +153 -0
  15. data/lib/achoo/awake.rb +86 -100
  16. data/lib/achoo/extensions.rb +24 -0
  17. data/lib/achoo/ical.rb +47 -40
  18. data/lib/achoo/rc_loader.rb +42 -42
  19. data/lib/achoo/system.rb +8 -3
  20. data/lib/achoo/system/cstruct.rb +67 -0
  21. data/lib/achoo/system/log_entry.rb +24 -0
  22. data/lib/achoo/system/pm_suspend.rb +17 -20
  23. data/lib/achoo/system/utmp_record.rb +64 -0
  24. data/lib/achoo/system/wtmp.rb +14 -10
  25. data/lib/achoo/temporal.rb +8 -0
  26. data/lib/achoo/temporal/open_timespan.rb +16 -0
  27. data/lib/achoo/temporal/timespan.rb +122 -0
  28. data/lib/achoo/term.rb +58 -56
  29. data/lib/achoo/term/menu.rb +2 -2
  30. data/lib/achoo/term/table.rb +59 -60
  31. data/lib/achoo/ui.rb +13 -9
  32. data/lib/achoo/ui/commands.rb +60 -38
  33. data/lib/achoo/ui/common.rb +10 -7
  34. data/lib/achoo/ui/date_chooser.rb +69 -65
  35. data/lib/achoo/ui/date_choosers.rb +15 -14
  36. data/lib/achoo/ui/exception_handling.rb +14 -12
  37. data/lib/achoo/ui/month_chooser.rb +37 -24
  38. data/lib/achoo/ui/optionally_ranged_date_chooser.rb +29 -25
  39. data/lib/achoo/ui/register_hours.rb +116 -114
  40. data/lib/achoo/vcs.rb +32 -30
  41. data/lib/achoo/vcs/git.rb +18 -14
  42. data/lib/achoo/vcs/subversion.rb +25 -23
  43. metadata +30 -24
  44. data/lib/achoo/binary.rb +0 -7
  45. data/lib/achoo/binary/cstruct.rb +0 -60
  46. data/lib/achoo/binary/utmp_record.rb +0 -59
  47. data/lib/achoo/form.rb +0 -18
  48. data/lib/achoo/hour_administration_form.rb +0 -131
  49. data/lib/achoo/hour_registration_form.rb +0 -227
  50. data/lib/achoo/hour_registration_form_ranged.rb +0 -45
  51. data/lib/achoo/lock_month_form.rb +0 -40
  52. data/lib/achoo/open_timespan.rb +0 -13
  53. data/lib/achoo/timespan.rb +0 -119
@@ -0,0 +1,49 @@
1
+ require 'achoo/achievo'
2
+
3
+ module Achoo
4
+ module Achievo
5
+ class HourRegistrationFormRanged < HourRegistrationForm
6
+
7
+ def initialize(agent)
8
+ super
9
+
10
+ @page = @agent.get(atk_submit_to_url(@page.link_with(:text => 'Select range').href))
11
+ @form = @page.form('entryform')
12
+ end
13
+
14
+ def date=(date_range)
15
+ super(date_range[0])
16
+
17
+ to_day_field.value = date_range[1].strftime('%d')
18
+ to_month_field.value = date_range[1].strftime('%m')
19
+ to_year_field.value = date_range[1].year
20
+ end
21
+
22
+ def date
23
+ start = super
24
+ finish = Date.new(to_year_field.value.to_i, to_month_field.value.to_i,
25
+ to_day_field.value.to_i)
26
+ [start, finish]
27
+ end
28
+
29
+ private
30
+
31
+ def to_day_field
32
+ @form.field_with(:name => 'todate[day]')
33
+ end
34
+
35
+ def to_month_field
36
+ @form.field_with(:name => 'todate[month]')
37
+ end
38
+
39
+ def to_year_field
40
+ @form.field_with(:name => 'todate[year]')
41
+ end
42
+
43
+ def date_to_s
44
+ date.map {|d| d.strftime("%Y-%m-%d")}.join(" -> ")
45
+ end
46
+
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,44 @@
1
+ require 'achoo/achievo'
2
+
3
+ module Achoo
4
+ module Achievo
5
+ class LockMonthForm
6
+
7
+ def initialize(agent)
8
+ @agent = agent
9
+ end
10
+
11
+ def lock_month(period)
12
+ page = @agent.get(RC[:lock_months_url])
13
+ @form = page.form('entryform')
14
+
15
+ @form.period = period
16
+ unless user_select.nil?
17
+ user_select.options.each do |opt|
18
+ if opt.text.match(/\(#{RC[:user]}\)$/)
19
+ opt.select
20
+ end
21
+ end
22
+ end
23
+
24
+ end
25
+
26
+ def print_values
27
+ puts "Month: #{@form.period}"
28
+ puts " User: #{user_select.value}" unless user_select.nil?
29
+
30
+ end
31
+
32
+ def submit
33
+ @form.submit
34
+ end
35
+
36
+ private
37
+
38
+ def user_select
39
+ @form.field_with(:name => 'userid')
40
+ end
41
+
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,27 @@
1
+ require 'achoo/achievo'
2
+
3
+ module Achoo
4
+ module Achievo
5
+ module LoginForm
6
+
7
+ def self.login(agent)
8
+ puts "Fetching data ..."
9
+ page = agent.get(RC[:url])
10
+
11
+ return if page.forms.empty? # already logged in
12
+
13
+ puts "Logging in ..."
14
+
15
+ form = page.forms.first
16
+ form.auth_user = RC[:user]
17
+ form.auth_pw = RC[:password]
18
+ page = agent.submit(form, form.buttons.first)
19
+
20
+ if page.body.match(/Username and\/or password are incorrect. Please try again./)
21
+ raise "Username and/or password are incorrect."
22
+ end
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,30 @@
1
+ require 'achoo/achievo'
2
+
3
+ module Achoo
4
+ module Achievo
5
+ class Table < Array
6
+
7
+ def initialize(source_rows)
8
+ super()
9
+ source_rows.each do |tr|
10
+ cells = tr.css('td, th')
11
+ cells = cells.collect {|c| c.content.strip}
12
+ self << fix_empty_cells(cells)
13
+ end
14
+ end
15
+
16
+ def select_columns(&block)
17
+ columns = transpose
18
+ columns = columns.select &block
19
+ replace(columns.transpose)
20
+ end
21
+
22
+ private
23
+
24
+ def fix_empty_cells(row)
25
+ row.collect {|c| c == "\302\240" ? ' ' : c} # UTF-8 NO-BREAK-SPACE
26
+ end
27
+
28
+ end
29
+ end
30
+ end
data/lib/achoo/app.rb ADDED
@@ -0,0 +1,153 @@
1
+ require 'achoo'
2
+ require 'achoo/achievo'
3
+ require 'achoo/term'
4
+ require 'achoo/ui'
5
+ require 'logger'
6
+ require 'mechanize'
7
+
8
+
9
+ module Achoo
10
+ class App
11
+
12
+ include UI::Commands
13
+ include UI::ExceptionHandling
14
+ include UI::RegisterHours
15
+
16
+
17
+ def initialize(log=false)
18
+ @agent = Mechanize.new
19
+ if log
20
+ @agent.log = Logger.new("achoo_http.log")
21
+ end
22
+ end
23
+
24
+
25
+ def start
26
+ begin
27
+ print_welcome
28
+ warm_up_ical_cache
29
+ login
30
+ scrape_urls
31
+ command_loop
32
+ rescue SystemExit => e
33
+ raise
34
+ rescue Exception => e
35
+ handle_fatal_exception("Something bad happened. Shutting down.", e)
36
+ end
37
+ end
38
+
39
+
40
+ private
41
+
42
+
43
+ def print_welcome
44
+ puts Term.shadowbox("Welcome to Achoo!")
45
+ end
46
+
47
+
48
+ def command_loop
49
+ while true
50
+ begin
51
+ trap("INT", "DEFAULT");
52
+ choices = ["Register hours",
53
+ "Show flexitime balance",
54
+ "Day hour report",
55
+ "Week hour report",
56
+ "Holiday balance",
57
+ "Lock month",
58
+ ]
59
+ choices << "Time survey report" if RC[:reports]
60
+ answer = Term.choose('[1]',
61
+ choices,
62
+ "Exit",
63
+ ['q', 'Q', ''])
64
+ dispatch(answer)
65
+ rescue Interrupt
66
+ puts # Add a new line in case we are prompting
67
+ end
68
+ end
69
+ end
70
+
71
+
72
+ def dispatch(command)
73
+ case command
74
+ when '0', 'q', 'Q'
75
+ exit
76
+ when '1', ''
77
+ register_hours(@agent)
78
+ when '2'
79
+ show_flexi_time(@agent)
80
+ when '3'
81
+ show_registered_hours_for_day(@agent)
82
+ when '4'
83
+ show_registered_hours_for_week(@agent)
84
+ when '5'
85
+ show_holiday_report(@agent)
86
+ when '6'
87
+ lock_month(@agent)
88
+ when '7'
89
+ view_report(@agent)
90
+ end
91
+ end
92
+
93
+
94
+ def scrape_urls
95
+ page = @agent.get(@agent.current_page.frames.find {|f| f.name == 'menu'}.href)
96
+ menu_links = page.search('a.menuItemLevel2')
97
+
98
+ RC[:hour_registration_url] = menu_link_to_url(menu_links, 'Time Registration')
99
+ RC[:lock_months_url] = menu_link_to_url(menu_links, 'Lock months')
100
+ RC[:holiday_report_url] = menu_link_to_url(menu_links, 'Holiday report')
101
+ RC[:hour_admin_url] = RC[:hour_registration_url]
102
+ end
103
+
104
+
105
+ def menu_link_to_url(menu_links, text)
106
+ a_tag = menu_links.find {|a|
107
+ a.text.strip == text
108
+ }
109
+
110
+ if a_tag.nil?
111
+ raise Exception.new("Could not find the '#{text}' link in Achievo.\nMake sure that language is 'English' and theme is 'no value' in Achievo's user preferences.\nYou must delete ~/.achoo_cookies.yml for these changes to take affect.")
112
+ end
113
+
114
+ url = a_tag.attribute('onclick').value.match(/window\.open\('([^']+)/)[1]
115
+ return "#{RC[:url]}#{url}"
116
+ end
117
+
118
+
119
+ def login
120
+ load_cookies
121
+ Achievo::LoginForm.login(@agent)
122
+ save_cookies
123
+ end
124
+
125
+
126
+ def load_cookies
127
+ cookies_file = "#{ENV['HOME']}/.achoo_cookies.yml"
128
+ if FileTest.exists? cookies_file
129
+ @agent.cookie_jar.load(cookies_file)
130
+ end
131
+ end
132
+
133
+
134
+ def save_cookies
135
+ @agent.cookie_jar.save_as("#{ENV['HOME']}/.achoo_cookies.yml")
136
+ end
137
+
138
+
139
+ def warm_up_ical_cache
140
+ Thread.new do
141
+ RC[:ical].each do |config|
142
+ begin
143
+ ICal.from_http_request(config)
144
+ rescue Exception => e
145
+ # Ignore, we are just doing this to populate the cache
146
+ end
147
+ end
148
+ end
149
+ end
150
+
151
+ end
152
+ end
153
+
data/lib/achoo/awake.rb CHANGED
@@ -1,130 +1,116 @@
1
- require 'achoo/open_timespan'
2
- require 'achoo/timespan'
1
+ require 'achoo'
2
+ require 'achoo/extensions'
3
3
  require 'achoo/system'
4
+ require 'achoo/temporal'
4
5
 
5
- class Achoo; end
6
+ module Achoo
7
+ class Awake
6
8
 
7
- class Achoo::Awake
8
-
9
- def initialize
10
- log = array_merge(wtmp, suspend) {|a, b| a[0] >= b[0] }
11
- log.unshift([Time.now, :now])
12
- @sessions = sessions(log)
13
- end
9
+ def initialize
10
+ log = wtmp.merge!(suspend)
11
+ log.unshift(System::LogEntry.new(Time.now, :now))
12
+ @sessions = sessions(log)
13
+ end
14
14
 
15
- def at(date)
16
- span = Achoo::Timespan.new(date, date+1)
17
- @sessions.each do |s|
18
- print_session(s, span) if s[0].overlaps?(span)
15
+ def at(date)
16
+ span = Temporal::Timespan.new(date, date+1)
17
+ @sessions.each do |s|
18
+ print_session(s, span) if s[0].overlaps?(span)
19
+ end
19
20
  end
20
- end
21
21
 
22
- def all
23
- @sessions.each do |s|
24
- print_session(s)
22
+ def all
23
+ @sessions.each do |s|
24
+ print_session(s)
25
+ end
25
26
  end
26
- end
27
27
 
28
- private
28
+ private
29
29
 
30
- def print_session(s, span=nil)
31
- puts "Powered on: " << s[0].to_s
32
- s[1].each do |t|
33
- puts " Awake: " << t.to_s if span.nil? || t.overlaps?(span)
30
+ def print_session(s, span=nil)
31
+ puts "Powered on: " << s[0].to_s
32
+ s[1].each do |t|
33
+ puts " Awake: " << t.to_s if span.nil? || t.overlaps?(span)
34
+ end
34
35
  end
35
- end
36
36
 
37
- def sessions(log)
38
- sessions = []
39
- group(log).each do |g|
40
- raise "Session doesn't begin with a boot event" unless g.last[1] == :boot
41
-
42
- session = []
43
- case g.first[1]
44
- when :now, :halt, :suspend
45
- # We know the end of the session
46
- session << Achoo::Timespan.new(g.last[0], g.first[0])
47
- else # :awake, :boot
48
- # We don't know the end of the session
49
- session << Achoo::OpenTimespan.new(g.last[0], g.first[0])
50
- g.unshift([-1, :crash])
51
- end
37
+ def sessions(log)
38
+ sessions = []
39
+ group(log).each do |g|
40
+ raise "Session doesn't begin with a boot event" unless g.last.event == :boot
41
+
42
+ session = []
43
+ case g.first.event
44
+ when :now, :halt, :suspend
45
+ # We know the end of the session
46
+ session << Temporal::Timespan.new(g.last.time, g.first.time)
47
+ else # :awake, :boot
48
+ # We don't know the end of the session
49
+ session << Temporal::OpenTimespan.new(g.last.time, g.first.time)
50
+ g.unshift(System::LogEntry.new(-1, :crash))
51
+ end
52
52
 
53
- raise "Session must consist of even number of events. Found #{g.length}" unless g.length.even?
54
-
53
+ raise "Session must consist of even number of events. Found #{g.length}" unless g.length.even?
54
+
55
55
 
56
- session << []
57
- unless g.length == 2
58
- i = 0
59
- while i < g.length-1
60
- klass = g[i][1] == :crash ? Achoo::OpenTimespan : Achoo::Timespan
61
- session[1] << klass.new(g[i+1][0], g[i][0])
62
- i += 2
56
+ session << []
57
+ unless g.length == 2
58
+ i = 0
59
+ while i < g.length-1
60
+ klass = g[i].event == :crash ? Temporal::OpenTimespan : Temporal::Timespan
61
+ session[1] << klass.new(g[i+1].time, g[i].time)
62
+ i += 2
63
+ end
63
64
  end
64
- end
65
65
 
66
- sessions << session
67
- end
68
- sessions
69
- end
70
-
71
- def array_merge(a1, a2)
72
- array = []
73
- until a1.empty? or a2.empty?
74
- if yield(a1.first, a2.first)
75
- array << a1.shift
76
- else
77
- array << a2.shift
66
+ sessions << session
78
67
  end
68
+ sessions
79
69
  end
80
- array.concat(a1).concat(a2)
81
- array
82
- end
83
70
 
84
- def suspend
85
- log = Achoo::System::PMSuspend.new.reverse
86
- new_log = []
87
- log.each do |entry|
88
- new_log << [entry.time, entry.action == 'Awake.' ? :awake : :suspend]
71
+ def suspend
72
+ log = System::PMSuspend.new.reverse
73
+ new_log = []
74
+ log.each do |entry|
75
+ new_log << System::LogEntry.new(entry.time, entry.event == 'Awake.' ? :awake : :suspend)
76
+ end
77
+ new_log
89
78
  end
90
- new_log
91
- end
92
79
 
93
- def wtmp
94
- log = Achoo::System::Wtmp.new.reverse
95
- new_log = []
96
- log.each do |entry|
97
- if entry.boot_event?
98
- new_log << [entry.time, :boot]
99
- elsif entry.halt_event?
100
- new_log << [entry.time, :halt]
80
+ def wtmp
81
+ log = System::Wtmp.new.reverse
82
+ new_log = []
83
+ log.each do |entry|
84
+ if entry.boot_event?
85
+ new_log << System::LogEntry.new(entry.time, :boot)
86
+ elsif entry.halt_event?
87
+ new_log << System::LogEntry.new(entry.time, :halt)
88
+ end
101
89
  end
90
+ new_log
102
91
  end
103
- new_log
104
- end
105
92
 
106
- def group(log)
107
- grouped_log = []
108
- group = nil
109
- log.each do |i|
110
- if i[1] == :halt || i[1] == :now
111
- group = [i]
112
- else
113
- if group.nil?
114
- # Crashed
115
- group = []
116
- end
117
- if i[1] == :boot
118
- group << i
119
- grouped_log << group
120
- group = nil
93
+ def group(log)
94
+ grouped_log = []
95
+ group = nil
96
+ log.each do |i|
97
+ if i.event == :halt || i.event == :now
98
+ group = [i]
121
99
  else
100
+ if group.nil?
101
+ # Crashed
102
+ group = []
103
+ end
122
104
  group << i
123
- end
105
+ if i.event == :boot
106
+ grouped_log << group
107
+ group = nil
108
+ end
109
+ end
124
110
  end
111
+ grouped_log
125
112
  end
126
- grouped_log
127
- end
128
113
 
129
114
 
115
+ end
130
116
  end