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,24 @@
1
+ class Array
2
+
3
+ #
4
+ # Useful for merging two sorted arrays with Comparable
5
+ # elements.
6
+ #
7
+ # The content of the two input arrays should not be trusted after
8
+ # being mistreated by this method.
9
+ #
10
+ def merge!(other)
11
+ # FIX raise exception if merge is defined?
12
+ array = []
13
+ until empty? or other.empty?
14
+ if first <= other.first
15
+ array << shift
16
+ else
17
+ array << other.shift
18
+ end
19
+ end
20
+ array.concat(self).concat(other)
21
+ array
22
+ end
23
+
24
+ end
data/lib/achoo/ical.rb CHANGED
@@ -1,58 +1,65 @@
1
- require 'achoo/timespan'
2
- require 'achoo/ui/exception_handling'
1
+ require 'achoo'
2
+ require 'achoo/temporal'
3
+ require 'achoo/ui'
3
4
  require 'net/https'
4
5
  require 'ri_cal'
5
6
 
6
- class Achoo; end
7
+ module Achoo
8
+ class ICal
7
9
 
8
- class Achoo::ICal
10
+ include UI::ExceptionHandling
9
11
 
10
- Achoo::UI::ExceptionHandling
12
+ @@cache = {}
11
13
 
12
- def self.from_http_request(params)
13
- http = Net::HTTP.new(params[:host], params[:port])
14
- http.use_ssl = true
15
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
16
- ics = http.start do |http|
17
- request = Net::HTTP::Get.new(params[:path])
18
- request.basic_auth(params[:user], params[:pass])
19
- response = http.request(request)
20
- response.body
14
+ def self.from_http_request(params)
15
+ return @@cache[params] if @@cache[params]
16
+
17
+ http = Net::HTTP.new(params[:host], params[:port])
18
+ http.use_ssl = true
19
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
20
+ ics = http.start do |http|
21
+ request = Net::HTTP::Get.new(params[:path])
22
+ request.basic_auth(params[:user], params[:pass])
23
+ response = http.request(request)
24
+ response.body
25
+ end
26
+
27
+ @@cache[params] = self.new(ics)
21
28
  end
22
- self.new(ics)
23
- end
24
29
 
25
- def initialize(ics_str)
26
- @calendar = RiCal.parse_string(ics_str).first
27
- end
30
+ def initialize(ics_str)
31
+ @calendar = RiCal.parse_string(ics_str).first
32
+ end
28
33
 
29
- def print_events(date, io=$stdout)
30
- arg_start = date
31
- arg_end = date + 1
32
-
33
- @calendar.events.each do |e|
34
- begin
35
- if !e.x_properties['X-MICROSOFT-CDO-ALLDAYEVENT'].empty? && e.x_properties['X-MICROSOFT-CDO-ALLDAYEVENT'].first.value == 'TRUE'
36
- # FIX handle this
37
- elsif e.recurs?
38
- e.occurrences({:overlapping => [arg_start, arg_end]}).each do |o|
39
- print_event(o, io)
34
+ def print_events(date, io=$stdout)
35
+ arg_start = date
36
+ arg_end = date + 1
37
+
38
+ @calendar.events.each do |e|
39
+ begin
40
+ if !e.x_properties['X-MICROSOFT-CDO-ALLDAYEVENT'].empty? && e.x_properties['X-MICROSOFT-CDO-ALLDAYEVENT'].first.value == 'TRUE'
41
+ # FIX handle this
42
+ elsif e.recurs?
43
+ e.occurrences({:overlapping => [arg_start, arg_end]}).each do |o|
44
+ print_event(o, io)
45
+ end
46
+ elsif e.dtstart >= arg_start && e.dtstart <= arg_end \
47
+ || e.dtend >= arg_start && e.dtend <= arg_end
48
+ print_event(e, io)
40
49
  end
41
- elsif e.dtstart >= arg_start && e.dtstart <= arg_end \
42
- || e.dtend >= arg_start && e.dtend <= arg_end
43
- print_event(e, io)
50
+ rescue Exception => e
51
+ handle_exception("Failed to process calendar event", e)
44
52
  end
45
- rescue Exception => e
46
- handle_exception("Failed to process calendar event", e)
47
53
  end
48
54
  end
49
- end
50
55
 
51
- private
56
+ private
57
+
58
+ def print_event(e, io)
59
+ dti = Temporal::Timespan.new(e.dtstart.to_s, e.dtend.to_s)
60
+ io.printf "%s: %s\n", dti, e.summary
61
+ end
52
62
 
53
- def print_event(e, io)
54
- dti = Achoo::Timespan.new(e.dtstart.to_s, e.dtend.to_s)
55
- io.printf "%s: %s\n", dti, e.summary
56
63
  end
57
64
  end
58
65
 
@@ -1,55 +1,55 @@
1
+ require 'achoo'
1
2
  require 'achoo/term'
2
-
3
- class Achoo; end
4
-
5
- module Achoo::RCLoader
6
-
7
- def load_rc(rc_file="#{ENV['HOME']}/.achoo")
8
- #create_empty_rc_if_not_exists(rc_file)
9
- file_permissions_secure?(rc_file)
10
-
11
- load rc_file
12
-
13
- verify_rc_contents(rc_file)
14
- end
15
-
16
- private
17
-
18
- def file_permissions_secure?(rc_file)
19
- # FIX test to is to strict
20
- if File.stat(rc_file).mode != 0100600
21
- puts Achoo::Term.fatal "Insecure permissions on #{rc_file}"
22
- exit 1
3
+ require 'yaml'
4
+
5
+ module Achoo
6
+ module RCLoader
7
+
8
+ def load_rc(rc_file="#{ENV['HOME']}/.achoo")
9
+ #create_empty_rc_if_not_exists(rc_file)
10
+ file_permissions_secure?(rc_file)
11
+
12
+ self.class.const_set(:RC, YAML.load_file(rc_file))
13
+ if RC.is_a? String
14
+ abort "Failed to parse rc file. Do you use the old format? Please convert it to YAML."
15
+ end
16
+
17
+ verify_rc_contents(rc_file)
23
18
  end
24
- end
25
19
 
26
- def create_empty_rc_if_not_exists(rc_file)
27
- return if FileTest.exist?(rc_file)
20
+ private
28
21
 
29
- FileUtils.touch(rc_file)
30
- FileUtils.chmod(0600, rc_file)
31
- end
22
+ def file_permissions_secure?(rc_file)
23
+ # FIX test to is to strict
24
+ if File.stat(rc_file).mode != 0100600
25
+ puts Term.fatal "Insecure permissions on #{rc_file}"
26
+ exit 1
27
+ end
28
+ end
32
29
 
33
- def verify_rc_contents(rc_file)
34
- unless Object.const_defined?('RC')
35
- puts Achoo::Term.fatal "Malformed run control file: No RC constant defined"
36
- exit 1
30
+ def create_empty_rc_if_not_exists(rc_file)
31
+ return if FileTest.exist?(rc_file)
32
+
33
+ FileUtils.touch(rc_file)
34
+ FileUtils.chmod(0600, rc_file)
37
35
  end
38
36
 
39
- %w(url user password).each do |key|
40
- unless RC.has_key?(key.to_sym)
41
- puts Achoo::Term.fatal "Missing mandatory run control configuration variable: #{key}"
42
- exit 1
37
+ def verify_rc_contents(rc_file)
38
+ %w(url user password).each do |key|
39
+ unless RC.has_key?(key.to_sym)
40
+ puts Term.fatal "Missing mandatory run control configuration variable: #{key}"
41
+ exit 1
42
+ end
43
43
  end
44
- end
45
44
 
46
- %w(vcs_dirs ical).each do |key|
47
- unless RC.has_key?(key.to_sym)
48
- puts Achoo::Term.warn "Missing run control configuration variable: #{key}. " \
49
- + "Add it to #{rc_file} to get rid of this warning"
50
- RC[key] = []
45
+ %w(vcs_dirs ical).each do |key|
46
+ unless RC.has_key?(key.to_sym)
47
+ puts Term.warn "Missing run control configuration variable: #{key}. " \
48
+ + "Add it to #{rc_file} to get rid of this warning"
49
+ RC[key] = []
50
+ end
51
51
  end
52
52
  end
53
- end
54
53
 
54
+ end
55
55
  end
data/lib/achoo/system.rb CHANGED
@@ -1,6 +1,11 @@
1
- class Achoo
1
+ require 'achoo'
2
+
3
+ module Achoo
2
4
  module System
3
- autoload :Wtmp, 'achoo/system/wtmp.rb'
4
- autoload :PMSuspend, 'achoo/system/pm_suspend.rb'
5
+ autoload :CStruct, 'achoo/system/cstruct'
6
+ autoload :LogEntry, 'achoo/system/log_entry'
7
+ autoload :PMSuspend, 'achoo/system/pm_suspend'
8
+ autoload :UTMPRecord, 'achoo/system/utmp_record'
9
+ autoload :Wtmp, 'achoo/system/wtmp'
5
10
  end
6
11
  end
@@ -0,0 +1,67 @@
1
+ require 'achoo/system'
2
+
3
+ module Achoo
4
+ module System
5
+
6
+ class CStruct
7
+
8
+ def initialize(bytes=nil)
9
+ @values = []
10
+ unpack(bytes) unless bytes.nil?
11
+ end
12
+
13
+ class << self
14
+
15
+ attr :template
16
+
17
+ def inherited(subclass)
18
+ subclass.instance_variable_set(:@template, '')
19
+ subclass.instance_variable_set(:@count, 0)
20
+ end
21
+
22
+ def char(name); add_type(name, :char, 'c', 0); end
23
+ def short(name); add_type(name, :short, 's', 0); end
24
+ def long(name); add_type(name, :long, 'l', 0); end
25
+ def quad(name); add_type(name, :quad, 'q', 0); end
26
+
27
+ def string(name, length)
28
+ add_type(name, :string, 'A', '', length)
29
+ end
30
+
31
+ def bin_size
32
+ @bin_size ||= template.split('').select {|c| c =~ /[[:alpha:]]/}.map do |c|
33
+ c == 'A' ? '' : 0
34
+ end.pack(template).length
35
+ end
36
+
37
+ private
38
+
39
+ def add_type(name, type, temp, zero, length=nil)
40
+ template << temp
41
+ template << length.to_s if type == :string
42
+ index = @count
43
+ @count += 1
44
+
45
+ send(:define_method, name) do
46
+ @values[index]
47
+ end
48
+
49
+ send(:define_method, "#{name}=") do |val|
50
+ @values[index] = val
51
+ end
52
+ end
53
+
54
+ end
55
+
56
+ def unpack(str)
57
+ @values = str.unpack(self.class.template)
58
+ end
59
+
60
+ def pack
61
+ t = self.class.template.tr('A', 'a')
62
+ @values.pack(t)
63
+ end
64
+
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,24 @@
1
+ require 'achoo/system'
2
+
3
+ module Achoo
4
+ module System
5
+
6
+ class LogEntry
7
+ include Comparable
8
+
9
+ attr :time
10
+ attr :event
11
+
12
+ def initialize(time, event)
13
+ @time = time
14
+ @event = event
15
+ end
16
+
17
+ def <=>(other_entry)
18
+ time <=> other_entry.time
19
+ end
20
+ end
21
+
22
+ end
23
+ end
24
+
@@ -1,30 +1,27 @@
1
1
  require 'achoo/system'
2
2
  require 'time'
3
3
 
4
- class Achoo::System::PMSuspend < Array
4
+ module Achoo
5
+ module System
5
6
 
6
- class LogEntry
7
- attr :time
8
- attr :action
9
-
10
- def initialize(time_str, action)
11
- @action = action
12
- @time = Time.parse(time_str)
13
- end
14
- end
7
+ class PMSuspend < Array
15
8
 
16
- def initialize(glob='/var/log/pm-suspend.log*')
17
- super()
18
- Dir.glob(glob).sort.reverse.each do |file|
19
- next if file =~ /\.gz$/ # FIX uncompress?
20
- File.open(file, 'r') do |fh|
21
- fh.readlines.each do |l|
22
- l.chop!
23
- next unless l =~ /Awake|performing suspend/
24
- self << LogEntry.new(*l.split(': '))
9
+ def initialize(glob='/var/log/pm-suspend.log*')
10
+ super()
11
+ Dir.glob(glob).sort.reverse.each do |file|
12
+ next if file =~ /\.gz$/ # FIX uncompress?
13
+ File.open(file, 'r') do |fh|
14
+ fh.readlines.each do |l|
15
+ l.chop!
16
+ next unless l =~ /Awake|performing suspend/
17
+ time, event = *l.split(': ')
18
+ time = Time.parse(time)
19
+ self << LogEntry.new(time, event)
20
+ end
21
+ end
25
22
  end
26
23
  end
24
+
27
25
  end
28
26
  end
29
-
30
27
  end
@@ -0,0 +1,64 @@
1
+ require 'achoo/system'
2
+
3
+ module Achoo
4
+ module System
5
+
6
+ class UTMPRecord < CStruct
7
+ long :record_type
8
+ long :process_id
9
+ string :device_name, 32
10
+ string :inittab_id, 4
11
+ string :username, 32
12
+ string :hostname, 256
13
+ short :termination_status
14
+ short :exit_status
15
+ long :session_id
16
+ long :seconds
17
+ long :milliseconds
18
+ long :ip_address1
19
+ long :ip_address2
20
+ long :ip_address3
21
+ long :ip_address4
22
+ string :unused, 20
23
+
24
+
25
+ TYPE_MAP = [:empty,
26
+ :run_lvl,
27
+ :boot,
28
+ :new_time,
29
+ :old_time,
30
+ :init,
31
+ :login,
32
+ :normal,
33
+ :term,
34
+ :account,
35
+ ]
36
+
37
+ def time
38
+ return nil if seconds.nil?
39
+ @time ||= Time.at(seconds, milliseconds)
40
+ end
41
+
42
+ def record_type_symbol
43
+ TYPE_MAP[record_type]
44
+ end
45
+
46
+ def record_type_symbol=(sym)
47
+ @values[0] = (TYPE_MAP.find_index(sym))
48
+ end
49
+
50
+ def to_s
51
+ sprintf "%s %-7s %-8s %s", time.strftime('%F_%T'), record_type_symbol, username, device_name
52
+ end
53
+
54
+ def boot_event?
55
+ record_type_symbol == :boot
56
+ end
57
+
58
+ def halt_event?
59
+ record_type_symbol == :term && device_name == ':0'
60
+ end
61
+
62
+ end
63
+ end
64
+ end