achoo 0.3 → 0.4.1

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.
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