achoo 0.3 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +12 -0
- data/README.rdoc +26 -30
- data/Rakefile +3 -0
- data/bin/achoo +2 -2
- data/lib/achoo.rb +1 -139
- data/lib/achoo/achievo.rb +13 -0
- data/lib/achoo/achievo/form.rb +22 -0
- data/lib/achoo/achievo/hour_administration_form.rb +91 -0
- data/lib/achoo/achievo/hour_registration_form.rb +230 -0
- data/lib/achoo/achievo/hour_registration_form_ranged.rb +49 -0
- data/lib/achoo/achievo/lock_month_form.rb +44 -0
- data/lib/achoo/achievo/login_form.rb +27 -0
- data/lib/achoo/achievo/table.rb +30 -0
- data/lib/achoo/app.rb +153 -0
- data/lib/achoo/awake.rb +86 -100
- data/lib/achoo/extensions.rb +24 -0
- data/lib/achoo/ical.rb +47 -40
- data/lib/achoo/rc_loader.rb +42 -42
- data/lib/achoo/system.rb +8 -3
- data/lib/achoo/system/cstruct.rb +67 -0
- data/lib/achoo/system/log_entry.rb +24 -0
- data/lib/achoo/system/pm_suspend.rb +17 -20
- data/lib/achoo/system/utmp_record.rb +64 -0
- data/lib/achoo/system/wtmp.rb +14 -10
- data/lib/achoo/temporal.rb +8 -0
- data/lib/achoo/temporal/open_timespan.rb +16 -0
- data/lib/achoo/temporal/timespan.rb +122 -0
- data/lib/achoo/term.rb +58 -56
- data/lib/achoo/term/menu.rb +2 -2
- data/lib/achoo/term/table.rb +59 -60
- data/lib/achoo/ui.rb +13 -9
- data/lib/achoo/ui/commands.rb +60 -38
- data/lib/achoo/ui/common.rb +10 -7
- data/lib/achoo/ui/date_chooser.rb +69 -65
- data/lib/achoo/ui/date_choosers.rb +15 -14
- data/lib/achoo/ui/exception_handling.rb +14 -12
- data/lib/achoo/ui/month_chooser.rb +37 -24
- data/lib/achoo/ui/optionally_ranged_date_chooser.rb +29 -25
- data/lib/achoo/ui/register_hours.rb +116 -114
- data/lib/achoo/vcs.rb +32 -30
- data/lib/achoo/vcs/git.rb +18 -14
- data/lib/achoo/vcs/subversion.rb +25 -23
- metadata +30 -24
- data/lib/achoo/binary.rb +0 -7
- data/lib/achoo/binary/cstruct.rb +0 -60
- data/lib/achoo/binary/utmp_record.rb +0 -59
- data/lib/achoo/form.rb +0 -18
- data/lib/achoo/hour_administration_form.rb +0 -131
- data/lib/achoo/hour_registration_form.rb +0 -227
- data/lib/achoo/hour_registration_form_ranged.rb +0 -45
- data/lib/achoo/lock_month_form.rb +0 -40
- data/lib/achoo/open_timespan.rb +0 -13
- 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
|
2
|
-
require 'achoo/
|
1
|
+
require 'achoo'
|
2
|
+
require 'achoo/extensions'
|
3
3
|
require 'achoo/system'
|
4
|
+
require 'achoo/temporal'
|
4
5
|
|
5
|
-
|
6
|
+
module Achoo
|
7
|
+
class Awake
|
6
8
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
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
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
-
|
23
|
-
|
24
|
-
|
22
|
+
def all
|
23
|
+
@sessions.each do |s|
|
24
|
+
print_session(s)
|
25
|
+
end
|
25
26
|
end
|
26
|
-
end
|
27
27
|
|
28
|
-
|
28
|
+
private
|
29
29
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
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
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
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
|
-
|
54
|
-
|
53
|
+
raise "Session must consist of even number of events. Found #{g.length}" unless g.length.even?
|
54
|
+
|
55
55
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
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
|
-
|
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
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
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
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
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
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
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
|
-
|
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
|