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
data/lib/achoo/binary.rb DELETED
@@ -1,7 +0,0 @@
1
- class Achoo
2
- module Binary
3
- autoload :CStruct, 'achoo/binary/cstruct'
4
- autoload :UTMPRecord, 'achoo/binary/utmp_record'
5
- end
6
- end
7
-
@@ -1,60 +0,0 @@
1
- require 'achoo/binary'
2
-
3
- class Achoo::Binary::CStruct
4
-
5
- def initialize(bytes=nil)
6
- @values = []
7
- unpack(bytes) unless bytes.nil?
8
- end
9
-
10
- class << self
11
-
12
- attr :template
13
-
14
- def inherited(subclass)
15
- subclass.instance_variable_set(:@template, '')
16
- subclass.instance_variable_set(:@count, 0)
17
- end
18
-
19
- def char(name); add_type(name, :char, 'c', 0); end
20
- def short(name); add_type(name, :short, 's', 0); end
21
- def long(name); add_type(name, :long, 'l', 0); end
22
- def quad(name); add_type(name, :quad, 'q', 0); end
23
-
24
- def string(name, length); add_type(name, :string, 'A', '', length); end
25
-
26
- def bin_size
27
- @bin_size ||= template.split('').select {|c| c =~ /[[:alpha:]]/}.map do |c|
28
- c == 'A' ? '' : 0
29
- end.pack(template).length
30
- end
31
-
32
- private
33
-
34
- def add_type(name, type, temp, zero, length=nil)
35
- template << temp
36
- template << length.to_s if type == :string
37
- index = @count
38
- @count += 1
39
-
40
- send(:define_method, name) do
41
- @values[index]
42
- end
43
-
44
- send(:define_method, "#{name}=") do |val|
45
- @values[index] = val
46
- end
47
- end
48
-
49
- end
50
-
51
- def unpack(str)
52
- @values = str.unpack(self.class.template)
53
- end
54
-
55
- def pack
56
- t = self.class.template.tr('A', 'a')
57
- @values.pack(t)
58
- end
59
-
60
- end
@@ -1,59 +0,0 @@
1
- require 'achoo/binary'
2
-
3
- class Achoo::Binary::UTMPRecord < Achoo::Binary::CStruct
4
- long :record_type
5
- long :process_id
6
- string :device_name, 32
7
- string :inittab_id, 4
8
- string :username, 32
9
- string :hostname, 256
10
- short :termination_status
11
- short :exit_status
12
- long :session_id
13
- long :seconds
14
- long :milliseconds
15
- long :ip_address1
16
- long :ip_address2
17
- long :ip_address3
18
- long :ip_address4
19
- string :unused, 20
20
-
21
-
22
- TYPE_MAP = [:empty,
23
- :run_lvl,
24
- :boot,
25
- :new_time,
26
- :old_time,
27
- :init,
28
- :login,
29
- :normal,
30
- :term,
31
- :account,
32
- ]
33
-
34
- def time
35
- return nil if seconds.nil?
36
- @time ||= Time.at(seconds, milliseconds)
37
- end
38
-
39
- def record_type_symbol
40
- TYPE_MAP[record_type]
41
- end
42
-
43
- def record_type_symbol=(sym)
44
- @values[0] = (TYPE_MAP.find_index(sym))
45
- end
46
-
47
- def to_s
48
- sprintf "%s %-7s %-8s %s", time.strftime('%F_%T'), record_type_symbol, username, device_name
49
- end
50
-
51
- def boot_event?
52
- record_type_symbol == :boot
53
- end
54
-
55
- def halt_event?
56
- record_type_symbol == :term && device_name == ':0'
57
- end
58
-
59
- end
data/lib/achoo/form.rb DELETED
@@ -1,18 +0,0 @@
1
-
2
- class Achoo; end
3
-
4
- class Achoo::Form
5
-
6
- def date=(date)
7
- # Day and month must be prefixed with '0' if single
8
- # digit. Date.day and Date.month doesn't do this. Use strftime
9
- day_field.value = date.strftime('%d')
10
- month_field.value = date.strftime('%m')
11
- year_field.value = date.year
12
- end
13
-
14
- def date
15
- Date.new(year_field.value.to_i, month_field.value.to_i,
16
- day_field.value.to_i)
17
- end
18
- end
@@ -1,131 +0,0 @@
1
- require 'achoo/form'
2
- require 'achoo/term/table'
3
-
4
- class Achoo::HourAdministrationForm < Achoo::Form
5
-
6
- def initialize(agent)
7
- @agent = agent
8
- @page = nil
9
- end
10
-
11
- def show_registered_hours_for_day(date)
12
- show_registered_hours(date, 'dayview', '#rl_1 tr')
13
- end
14
-
15
- def show_registered_hours_for_week(date)
16
- show_registered_hours(date, 'weekview', '//form[@name="weekview"]/following::table/tr')
17
- end
18
-
19
- def flexi_time(date)
20
- set_page_to_view_for_date('dayview', date)
21
-
22
- @page.body.match(/Flexi time balance: (-?\d+:\d+)/)[1]
23
- end
24
-
25
- private
26
-
27
- def show_registered_hours(date, view, query)
28
- set_page_to_view_for_date(view, date)
29
-
30
- columns = choose_source_columns(view, query)
31
- source_rows = @page.search(query)
32
- headers = extract_headers(source_rows, columns, view)
33
- data_rows = extract_data_rows(source_rows, columns)
34
- summaries = extract_summaries(source_rows, data_rows, columns)
35
-
36
- Achoo::Term::Table.new(headers, data_rows, summaries).print
37
- end
38
-
39
- def set_page_to_view_for_date(view, date)
40
- @page ||= @agent.get(RC[:hour_admin_url])
41
-
42
- link = @page.link_with(:text => view.capitalize)
43
- @form = @page.form(view)
44
- unless link.nil?
45
- puts "Fetching #{view} ..."
46
- @page = link.click
47
- @form = @page.form(view)
48
- end
49
- unless date == self.date
50
- @page = get_page_for(date)
51
- @form = @page.form(view)
52
- end
53
-
54
- @page
55
- end
56
-
57
- def choose_source_columns(view, query)
58
- columns = nil
59
- if view == 'dayview'
60
- # Ignore 'Billing billed', 'Billing marked', and 'Billing total'
61
- columns = [0,1,2,3,6,8,9]
62
- # Achievo prepends an extra column dynamically if there are
63
- # data rows.
64
- unless @page.search(query + ' td').empty?
65
- columns.collect! {|c| c + 1}
66
- end
67
- end
68
- columns
69
- end
70
-
71
- def extract_headers(source_rows, columns, view)
72
- headers = source_rows.first.css('th')
73
- unless columns.nil?
74
- headers = headers.to_a.values_at(*columns)
75
- end
76
- headers = headers.map {|th| th.content.strip}
77
- if view == 'weekview'
78
- headers = headers.map {|th| th.gsub(/\s+/, ' ') }
79
- end
80
- headers
81
- end
82
-
83
- def extract_data_rows(source_rows, columns)
84
- data_rows = []
85
- source_rows.each do |tr|
86
- cells = tr.css('td')
87
- next if cells.empty?
88
- unless columns.nil?
89
- cells = cells.to_a.values_at(*columns)
90
- end
91
- data_rows << fix_empty_cells(cells.map {|td| td.content.strip})
92
- end
93
- data_rows
94
- end
95
-
96
- def extract_summaries(source_rows, data_rows, columns)
97
- summaries = nil
98
- unless data_rows.empty?
99
- summaries = source_rows.last.css('th')
100
- unless columns.nil?
101
- summaries = summaries.to_a.values_at(*columns)
102
- end
103
- summaries = summaries.map {|th| th.content.strip }
104
- fix_empty_cells(summaries)
105
- end
106
- summaries
107
- end
108
-
109
- def get_page_for(date)
110
- puts "Fetching data for #{date} ..."
111
- self.date = date
112
- @page = @form.submit
113
- end
114
-
115
- def day_field
116
- @form.field_with(:name => 'viewdate[day]')
117
- end
118
-
119
- def month_field
120
- @form.field_with(:name => 'viewdate[month]')
121
- end
122
-
123
- def year_field
124
- @form.field_with(:name => 'viewdate[year]')
125
- end
126
-
127
- def fix_empty_cells(row)
128
- row.collect! {|c| c == "\302\240" ? ' ' : c} # UTF-8 NO-BREAK-SPACE
129
- end
130
-
131
- end
@@ -1,227 +0,0 @@
1
- require 'achoo/form'
2
- require 'achoo/hour_administration_form'
3
-
4
- class Achoo::HourRegistrationForm < Achoo::Form
5
-
6
- def initialize(agent)
7
- @agent = agent
8
- @page = @agent.get(RC[:hour_registration_url])
9
- @form = @page.form('entryform')
10
-
11
- if @form.nil?
12
- # Happens if the user has viewed a day or week report for a
13
- # locked month. Fetching todays day report should fix this in
14
- # most cases.
15
-
16
- # FIX Ugly call to a private method using send()
17
- haf = Achoo::HourAdministrationForm.new(@agent)
18
- @page = haf.send(:set_page_to_view_for_date, 'dayview', Date.today)
19
- @form = @page.form('entryform')
20
- end
21
-
22
- if @form.nil?
23
- raise "Failed to retrieve the hour registration form.\nThe likely cause is that you have locked the current month, which is a silly thing to do."
24
- end
25
-
26
- @projects_seen = {}
27
- @phases_seen = {}
28
-
29
- # Need to preselect this for some reason
30
- @form.field_with(:name => 'billpercent').options.first.select
31
- # Preselecting this one as well, just in case
32
- @form.field_with(:name => 'workperiod').options.first.select
33
- end
34
-
35
- def project
36
- extract_number_from_projectid(@form.projectid)
37
- end
38
-
39
- def project=(projectid)
40
- @form.projectid = "project.id='#{projectid}'"
41
- end
42
-
43
- def remark=(remark)
44
- @form.remark = remark
45
- end
46
-
47
- def hours=(hours)
48
- @form.time = hours
49
- end
50
-
51
- def phase
52
- @form.phaseid.match(/phase\.id='(\d+)'/)[1]
53
- end
54
-
55
- def phase=(phaseid)
56
- @form.phaseid = "phase.id='#{phaseid}'"
57
- end
58
-
59
- def workperiod=(workperiod)
60
- @form.workperiod = "workperiod.id='#{workperiod}'"
61
- end
62
-
63
- def billing=(billing)
64
- @form.billpercent = "billpercent.id='#{billing}'"
65
- end
66
-
67
-
68
- def worktime_periods
69
- @form.field_with(:name => 'workperiod').options.collect do |opt|
70
- [opt.value.match(/workperiod\.id='(\d+)'/)[1], opt.text]
71
- end
72
- end
73
-
74
- def billing_options
75
- @form.field_with(:name => 'billpercent').options.collect do |opt|
76
- [opt.value.match(/billpercent\.id='(\d+)'/)[1], opt.text]
77
- end
78
- end
79
-
80
- def phases_for_selected_project
81
- partial_page = retrieve_project_phases_page
82
- page = create_page_from_partial(partial_page)
83
- field = page.forms.first.field_with(:name => 'phaseid')
84
-
85
- phases = []
86
- if field.respond_to?(:options)
87
- field.options.each do |opt|
88
- phases << [extract_number_from_phaseid(opt.value), opt.text]
89
- end
90
- else
91
- partial_page.body.match(/(^[^<]+)&nbsp;&nbsp;</)
92
- phases << [extract_number_from_phaseid(field.value), $1]
93
- end
94
-
95
- phases.each {|p| @phases_seen[p[0]] = p[1]}
96
-
97
- return phases
98
- end
99
-
100
- def recent_projects
101
- projects = []
102
- @form.field_with(:name => 'projectid').options.each do |opt|
103
- val = opt.value["project.id='".length..-2]
104
- projects << [val, opt.text]
105
- end
106
-
107
- projects.each {|p| @projects_seen[p[0]] = p[1]}
108
-
109
- projects
110
- end
111
-
112
- def all_projects
113
- puts "Getting project page #1..."
114
- projects_page = @agent.get(projects_url)
115
- projects = scrape_projects(projects_page)
116
-
117
- i = 2
118
- while (link = projects_page.link_with(:text => 'Next'))
119
- puts "Getting project page ##{i}..."
120
- projects_page = link.click
121
- projects.merge!(scrape_projects(projects_page))
122
- i += 1
123
- end
124
-
125
- projects.keys.sort.collect do |name|
126
- id = projects[name][0]
127
- text = "#{projects[name][1]}: #{name}"
128
- @projects_seen[id] = text
129
- [id, text]
130
- end
131
- end
132
-
133
- def print_values
134
- format = "%10s: \"%s\"\n"
135
- printf format, 'date', date_to_s
136
- printf format, 'project', @projects_seen[project]
137
- printf format, 'phase', @phases_seen[phase]
138
- printf format, 'remark', @form.remark
139
- printf format, 'hours', @form.time
140
- printf format, 'worktime', @form.field_with(:name => 'workperiod').options.first.text
141
- printf format, 'billing', @form.field_with(:name => 'billpercent').options.first.text
142
-
143
- # @form.fields.each do |field|
144
- # printf format, field.name, field.value
145
- # end
146
-
147
- end
148
-
149
- def submit
150
- @form.submit()
151
- end
152
-
153
- private
154
-
155
- def date_to_s
156
- date.strftime("%Y-%m-%d")
157
- end
158
-
159
- def retrieve_project_phases_page
160
- old = {
161
- :atkaction => @form.atkaction,
162
- :action => @form.action,
163
- }
164
-
165
- @form.action = RC[:url]+"/dispatch.php?atkpartial=attribute.phaseid.refresh"
166
- @form.atkaction = 'add'
167
- partial_page = @form.submit
168
- @form.action = old[:action]
169
- @form.atkaction = old[:atkaction]
170
-
171
- return partial_page
172
- end
173
-
174
- def create_page_from_partial(partial_page)
175
- body = "<html><head></head><body><form>#{partial_page.body}</form></body></html>"
176
- page = Mechanize::Page.new(nil, {'content-type' => 'text/html; charset=iso-8859-1'},
177
- body, nil, @agent)
178
- end
179
-
180
- def day_field
181
- @form.field_with(:name => 'activitydate[day]')
182
- end
183
-
184
- def month_field
185
- @form.field_with(:name => 'activitydate[month]')
186
- end
187
-
188
- def year_field
189
- @form.field_with(:name => 'activitydate[year]')
190
- end
191
-
192
- def extract_number_from_projectid(projectid)
193
- projectid.match(/project\.id='(\d+)'/)[1]
194
- end
195
-
196
- def extract_number_from_phaseid(projectid)
197
- projectid.match(/phase\.id='(\d+)'/)[1]
198
- end
199
-
200
- def projects_url
201
- atk_submit_to_url(@page.link_with(:text => 'Select project').href)
202
- end
203
-
204
- def atk_submit_to_url(atk_submit)
205
- href = atk_submit['javascript:atkSubmit("__'.length..-3]
206
- href.gsub!('_13F', '?')
207
- href.gsub!('_13D', '=')
208
- href.gsub!('_126', '&')
209
- href.gsub!('_125', '%')
210
- return RC[:url] + '/' + href
211
- end
212
-
213
- def scrape_projects(projects_page)
214
- projects = {}
215
- projects_page.search('table#rl_1 tr').each do |tr|
216
- cells = tr.search('td')
217
- next if cells.empty?
218
- projects[cells[1].text.strip] = [
219
- cells[1].at_css('a').attribute('href').to_s.match('project.id%3D%27(\d+)%27')[1],
220
- cells[0].text.strip,
221
- ]
222
- end
223
-
224
- return projects
225
- end
226
-
227
- end