Rcov Code Coverage Results

C0 code coverage information.

Total Coverage: 81.9%

File Path Percent run
lib/achoo/term.rb 40
lib/achoo/achievo/date_field.rb 49
lib/achoo/ui/date_chooser.rb 58
lib/achoo/awake.rb 85
lib/achoo/term/menu.rb 91
lib/achoo/term/table.rb 93
lib/achoo/system/cstruct.rb 94
lib/achoo/temporal/timespan.rb 94
lib/achoo/extensions.rb 100
lib/achoo/ui.rb 100
lib/achoo/temporal/open_timespan.rb 100
lib/achoo/achievo.rb 100
lib/achoo/system.rb 100
lib/achoo/system/log_entry.rb 100
lib/achoo/temporal.rb 100
lib/achoo.rb 100

lib/achoo/term.rb

   # encoding: utf-8
   
   require 'achoo'
   
   module Achoo
     class Term
   
       autoload :Menu,  'achoo/term/menu'
       autoload :Table, 'achoo/term/table'
   
       BOLD      = 1
       UNDERLINE = 4
       RED       = 31
       YELLOW    = 33
   
       def self.effect(code, text); "\e[#{code}m#{text}\e[0m"; end
   
       def self.bold(text);      effect(BOLD,      text); end
       def self.underline(text); effect(UNDERLINE, text); end
       def self.warn(text);      effect(YELLOW,    text); end
       def self.fatal(text);     effect(RED,       text); end
   
       def self.password
         `stty -echo`
         pas = ask('Password')
         `stty echo`
         pas
       end
   
       def self.ask(question='')
         answer = nil
         loop do
           print bold("#{question}> ")
           $stdout.flush
           answer = gets
           
           # Answer is nil if user hits C-d on an empty input
           if answer.nil?
             puts
             exit
           end
           
           answer.strip! unless answer.nil?
   
           # FIX move this to achoo.rb?
           unless $stdin.tty?
             puts answer
           end
           break unless a_little_something(answer)
         end
         answer
       end
   
       def self.choose(question, entries, special=nil, additional_valid_answers=[])
         menu = Menu.new(question, entries, special, additional_valid_answers)
         menu.print_ask_and_validate
       end
   
       private
   
       def self.a_little_something(answer)
         return false if answer.nil?
   
         case answer.downcase
         when 'bless you!', 'gesundheit!'
           puts "Thank you!"
           return true
         else
           return false
         end
       end
   
       def self.shadowbox(text)
         x =  "┌──────────────────────────────────────────┐ \n"
         x << "│ #{text.center(40)} " <<                 "│▒\n"
         x << "└──────────────────────────────────────────┘▒\n"
         x << "  ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒\n"
         x
       end
   
     end
   end

lib/achoo/achievo/date_field.rb

   require 'achoo/achievo'
   
   module Achoo
     module Achievo
   
       def self.DateField(attr_name, field_name)
         Module.new do
           define_method("#{attr_name}=") do |date|
             # Day and month must be prefixed with '0' if single
             # digit. Date.day and Date.month doesn't do this. Use strftime
             send("#{attr_name}_day_field=",   date.strftime('%d'))
             send("#{attr_name}_month_field=", date.strftime('%m'))
             send("#{attr_name}_year_field=",  date.year)
           end
           
           define_method("#{attr_name}") do
             Date.new(*[send("#{attr_name}_year_field"),
                        send("#{attr_name}_month_field"),
                        send("#{attr_name}_day_field")].collect {|e| e.to_i})
           end
           
           %w(day month year).each do |e|
             define_method("#{attr_name}_#{e}_field") do
               @form.field_with(:name => "#{field_name}[#{e}]").value
             end 
                 
             define_method("#{attr_name}_#{e}_field=") do |val|
               @form.field_with(:name => "#{field_name}[#{e}]").value = val
             end 
           end
         end
       end
   
     end
   end

lib/achoo/ui/date_chooser.rb

   require 'achoo/term'
   require 'achoo/ui'
   
   module Achoo
     module UI
   
       class DateChooser
   
         PROMPT = "Date ([today] | ?)"
         FORMAT = "        today | (+|-)n | [[[YY]YY]-[M]M]-[D]D"
   
         def choose
           loop do
             answer = Term::ask PROMPT 
             begin
               date = handle_answer(answer)
               return date if date
             rescue ArgumentError => e
               puts e
             end
           end
         end
         
         def parse_date(date_str, base=Date.today)
           raise ArgumentError.new('Invalid date') if date_str.nil?
           
           # Today (default)
           if date_str == 'today' || date_str.empty?
             return Date.today
           end
           
           # Base offset
           case date_str.chars.first
           when '-'
             return base - Integer(date_str[1..-1])
           when '+'
             return base + Integer(date_str[1..-1])
           end
           
           # 
           date = date_str.split('-').collect {|d| d.to_i}
           case date.length
           when 1
             return Date.civil(base.year, base.month, *date)
           when 2
             return Date.civil(base.year, *date)
           when 3
             date[0] += 2000 if date[0] < 100
             return Date.civil(*date)
           end
           
           raise ArgumentError.new('Invalid date')
         end
         
         private
         
         def handle_answer(answer)
           if answer == '?'
             print_help_message
             return false
           else
             return parse_date(answer)
           end
         end
         
         def print_help_message   
           puts "Accepted formats:"
           puts date_format_help_string
           puts
           system 'cal -3m'
         end
         
         def date_format_help_string
           FORMAT
         end
   
       end
     end
   end

lib/achoo/awake.rb

   require 'achoo'
   require 'achoo/extensions'
   require 'achoo/system'
   require 'achoo/temporal'
   
   module Achoo
     class Awake
   
       def initialize
         log = wtmp.merge!(suspend).reverse
         log.unshift(System::LogEntry.new(Time.now, :now))
         @sessions = sessions(log)
       end
   
       def at(date)
         span = Temporal::Timespan.new(date, date+1)
         @sessions.each do |s|
           print_session(s, span) if s[0].overlaps?(span)
         end
       end
     
       def all
         @sessions.each do |s|
           print_session(s)
         end
       end
   
       private
   
       def print_session(s, span=nil)
         puts "Powered on: " << s[0].to_s
         s[1].each do |t|
           puts "  Awake: " << t.to_s if span.nil? || t.overlaps?(span)
         end
       end
   
       def sessions(log)
         sessions = []
         group(log).each do |g|
           raise "Session doesn't begin with a boot event" unless g.last.event == :boot
           
           session = []
           case g.first.event
           when :now, :halt, :suspend
             # We know the end of the session
             session << Temporal::Timespan.new(g.last.time, g.first.time)
           else # :awake, :boot
             # We don't know the end of the session
             session << Temporal::OpenTimespan.new(g.last.time, g.first.time)
             g.unshift(System::LogEntry.new(-1, :crash))
           end
         
           raise "Session must consist of even number of events. Found #{g.length}" unless g.length.even?
           
   
           session << []
           unless g.length == 2
             i = 0
             while i < g.length-1
               klass = g[i].event == :crash ? Temporal::OpenTimespan : Temporal::Timespan
               session[1] << klass.new(g[i+1].time, g[i].time)
               i += 2
             end
           end
         
           sessions << session
         end
         sessions
       end
   
       def suspend
         System::PMSuspend.new
       end
   
       def wtmp
         log     = System::Wtmp.new
         new_log = []
         log.each do |entry|
           if entry.boot_event?
             new_log << System::LogEntry.new(entry.time, :boot)
           elsif entry.halt_event?
             new_log << System::LogEntry.new(entry.time, :halt)
           end
         end
         new_log
       end
   
       def group(log)
         grouped_log = []
         group = nil
         log.each do |i|
           if i.event == :halt || i.event == :now
             group = [i]
           else
             if group.nil?
               # Crashed
               group = []
             end
             group << i
             if i.event == :boot
               grouped_log << group
               group = nil
             end    
           end
         end
         grouped_log
       end
   
   
     end
   end

lib/achoo/term/menu.rb

   require 'achoo/term'
   
   module Achoo
     class Term
       class Menu
   
         def initialize(question, entries, special=nil, additional_valid_answers=[])
           @question = question
           @entries  = entries
           @special  = special
   
           @valid = {}
           @valid['0'] = true unless @special.nil?
           1.upto(@entries.length).each {|i| @valid[i.to_s] = true}
           additional_valid_answers.each {|a| @valid[a] = true}
         end
   
         def print_ask_and_validate()
           return nil if @entries.empty?
   
           print_menu()
           return '1' if only_one_option?
           
           loop do
             answer = Term.ask(@question)
             if @valid[answer]
               return answer
             else
               puts "Invalid value. Must be one of " << @valid.keys.sort.join(',')
             end
           end
         end
         
         private
   
         def print_menu
           format = menu_item_format
           @entries.each_with_index do |entry, i|
             printf format, i+1, entry
           end
           printf format, 0, @special unless @special.nil?
         end
   
         def only_one_option?
           @entries.length == 1 && @special.nil?
         end
         
         def menu_item_format
           max_digits = Math.log10(@entries.length).floor + 1
           " %#{max_digits}d. %s\n"
         end
   
       end
     end
   end

lib/achoo/term/table.rb

   # encoding: utf-8
   
   require 'achoo/term'
   
   
   # Unicode box drawing characters
   #
   # http://en.wikipedia.org/wiki/Box-drawing_characters
   #
   #
   #      0 1 2 3 4 5 6 7 8 9 A B C D E F
   #
   # 2500 ─ ━ │ ┃ ┄ ┅ ┆ ┇ ┈ ┉ ┊ ┋ ┌ ┍ ┎ ┏
   #
   # 2510 ┐ ┑ ┒ ┓ └ ┕ ┖ ┗ ┘ ┙ ┚ ┛ ├ ┝ ┞ ┟
   #
   # 2520 ┠ ┡ ┢ ┣ ┤ ┥ ┦ ┧ ┨ ┩ ┪ ┫ ┬ ┭ ┮ ┯
   #
   # 2530 ┰ ┱ ┲ ┳ ┴ ┵ ┶ ┷ ┸ ┹ ┺ ┻ ┼ ┽ ┾ ┿
   #
   # 2540 ╀ ╁ ╂ ╃ ╄ ╅ ╆ ╇ ╈ ╉ ╊ ╋ ╌ ╍ ╎ ╏
   #
   # 2550 ═ ║ ╒ ╓ ╔ ╕ ╖ ╗ ╘ ╙ ╚ ╛ ╜ ╝ ╞ ╟
   #
   # 2560 ╠ ╡ ╢ ╣ ╤ ╥ ╦ ╧ ╨ ╩ ╪ ╫ ╬ ╭ ╮ ╯
   #
   # 2570 ╰ ╱ ╲ ╳ ╴ ╵ ╶ ╷ ╸ ╹ ╺ ╻ ╼ ╽ ╾ ╿
   
   module Achoo
     class Term
       class Table
   
         def initialize(headers, data_rows, summaries=nil)
           @headers   = headers
           @data_rows = data_rows
           @summaries = summaries
         end
   
         def print(io=$stdout)
           lengths   = calculate_table_cell_widths
           format    = build_format(lengths)
           headers   = center_table_headers(lengths)
           separator = lengths.map {|length| '─'*(length+2)}
       
   
           io.print '┌' << separator.join('┬') << "┐\n"
           io.print '│ ' << headers.join(' │ ') << " │\n"
           io.print '├' << separator.join('┼') << "┤\n"
           @data_rows.each {|r| io.printf format, *r }
           unless @summaries.nil? || @data_rows.length == 1
             io.print '├' << separator.join('┼') << "┤\n"
             io.printf format, *@summaries
           end
           io.print '└' << separator.join('┴') << "┘\n"
         end
   
         private
   
         def center_table_headers(lengths)
           headers = @headers.dup
           lengths.each_with_index do |len,i|
             headers[i] = headers[i].center(len)
           end
           headers
         end
   
         def calculate_table_cell_widths
           lengths = []
           @headers.each_with_index do |h, i|
             lengths[i] = h.length
           end
           @data_rows.each do |r|
             r.each_with_index do |d, i|
               lengths[i] = [d.length, lengths[i]].max
             end
           end
           lengths
         end
   
         def build_format(lengths)
           is_column_left_justified = Array.new(lengths.count)
           is_column_left_justified.fill(false)
       
           @data_rows.each do |r|
             r.each_index do |c|
               if !r[c].strip.empty? && !r[c].match(/^\d+[:.,]?\d*$/)
                 is_column_left_justified[c] = true
               end
             end
           end
       
           lengths.reduce('│') do |f, l|
             justify = is_column_left_justified.shift ? '-' : ''
             f + " %#{justify}#{l}s │"
           end + "\n"
         end
   
       end
     end
   end

lib/achoo/system/cstruct.rb

   require 'achoo/system'
   
   module Achoo
     module System
   
       class CStruct
   
         def initialize(bytes=nil)
           @values = []
           unpack(bytes) unless bytes.nil?
         end
   
         class << self
   
           attr :template
   
           def inherited(subclass)
             subclass.instance_variable_set(:@template, '') 
             subclass.instance_variable_set(:@count, 0)
           end 
   
           def char(name);  add_type(name, :char,  'c', 0); end
           def short(name); add_type(name, :short, 's', 0); end
           def long(name);  add_type(name, :long,  'l', 0); end
           def quad(name);  add_type(name, :quad,  'q', 0); end
   
           def string(name, length)
             add_type(name, :string, 'A', '', length)
           end
   
           def bin_size
             @bin_size ||= template.split('').select {|c| c =~ /[[:alpha:]]/}.map do |c|
               c == 'A' ? '' : 0
             end.pack(template).length
           end
   
           private 
   
           def add_type(name, type, temp, zero, length=nil)
             template << temp
             template << length.to_s if type == :string
             index = @count
             @count += 1
   
             send(:define_method, name) do
               @values[index]
             end
   
             send(:define_method, "#{name}=") do |val|
               @values[index] = val
             end
           end
   
         end
   
         def unpack(str)
           @values = str.unpack(self.class.template)
         end
   
         def pack
           t = self.class.template.tr('A', 'a')
           @values.pack(t)
         end
   
       end
     end
   end

lib/achoo/temporal/timespan.rb

   require 'achoo/temporal'
   require 'time'
   
   module Achoo
     module Temporal
       class Timespan < Range
   
         def initialize(start, end_)
           raise ArgumentError.new('Nil in parameters not allowed') if start.nil? || end_.nil?
   
           super(to_time(start), to_time(end_))
         end
   
         def to_s
           "(%s) %s" % [duration_string, from_to_string]
         end
   
         def contains?(timeish_or_timespan)
           if timeish_or_timespan.is_a? Timespan
             time = timeish_or_timespan
             include?(time.first) && include?(time.last)
           else
             include?(to_time(timeish_or_timespan))
           end
         end
   
         def overlaps?(timespan)
           include?(timespan.first) || include?(timespan.last) ||
             timespan.contains?(self)
         end
   
         private 
         
         def to_time(timeish)
           case timeish
           when Time
             timeish.clone
           when DateTime
             Time.local(timeish.year, timeish.month, timeish.day, timeish.hour,timeish.minute, timeish.second)
           when Date
             Time.local(timeish.year, timeish.month, timeish.day)
           else
             if timeish.respond_to?(:to_s)
               Time.parse(timeish.to_s)
             else
               raise ArgumentError.new("Don't know how to convert #{timeish.class} to Time")
             end
           end
         end
   
         def duration_string
           "%d+%02d:%02d" % duration
         end
   
         def duration
           delta = last - first
           d     = delta.to_i / 1.day
   
           delta = delta - d.days
           h     = delta.to_i / 1.hour
   
           delta = delta - h.hours
           m     = delta.to_i / 1.minute
   
           return [d, h, m]
         end
   
         def from_to_string
           today      = Date.today
           start_date = first.send(:to_date)
           end_date   = last.send(:to_date)
           
           "%s - %s" % [from_as_string(start_date, today),
                        to_as_string(start_date, end_date, today)]
         end
   
         def from_as_string(start_date, today)
           format = if start_date == today
                      "Today"
                    elsif start_date.month == today.month && 
                        start_date.year == today.year
                      "%a %e."
                    elsif start_date.year == today.year
                      "%a %e. %b"
                    else
                      "%a %e. %b %Y"
                    end
           from = first.strftime(format << " %R")
         end
   
         def to_as_string(start_date, end_date, today)
           format = if end_date == start_date
                      "%R"
                    elsif end_date == today
                      "Today %R"
                    elsif end_date.month == today.month && 
                      end_date.year == today.year
                      "%a %e. %R"
                    elsif end_date.year == today.year
                      "%a %e. %b %R"
                    else
                      "%a %e. %b %Y %R"
                    end
           to = last.strftime(format)
         end
   
       end
     end
   end
   

lib/achoo/extensions.rb

   class Array
   
     %w(merge!).each do |method|
       raise "Method already defined: #{name}\##{method}" \
         if method_defined?(method)
     end
   
     #
     # Useful for merging two sorted arrays with Comparable elements.
     #
     # The content of the two input arrays should not be trusted after
     # being mistreated by this method.
     # 
     def merge!(other)
       # FIX raise exception if merge is defined?
       array = []
       until empty? or other.empty?
         if first <= other.first
           array << shift
         else
           array << other.shift
         end
       end
       array.concat(self).concat(other)
       array
     end
   
   end
   
   
   class Integer
   
     %w(day days hour hours minute minutes).each do |method|
       raise "Method already defined: #{name}\##{method}" \
         if method_defined?(method)
     end
       
     def day; self * 86400; end
     alias days day
   
     def hour; self * 3600; end
     alias hours hour
   
     def minute; self * 60; end
     alias minutes minute
   
   end

lib/achoo/ui.rb

   require 'achoo'
   
   module Achoo
     module UI
       autoload :Commands,                    'achoo/ui/commands'
       autoload :Common,                      'achoo/ui/common'
       autoload :DateChooser,                 'achoo/ui/date_chooser'
       autoload :DateChoosers,                'achoo/ui/date_choosers'
       autoload :ExceptionHandling,           'achoo/ui/exception_handling'
       autoload :MonthChooser,                'achoo/ui/month_chooser'
       autoload :OptionallyRangedDateChooser, 'achoo/ui/optionally_ranged_date_chooser'
       autoload :RegisterHours,               'achoo/ui/register_hours'
     end
   end

lib/achoo/temporal/open_timespan.rb

   require 'achoo/temporal'
   
   module Achoo
     module Temporal
       class OpenTimespan < Timespan
   
         def to_s
           s = super
           s.sub!(/^\([^)]+\)/, '(?+??:??)')
           s.sub!(/- .*$/, '- ?')
           s
         end
   
       end
     end
   end

lib/achoo/achievo.rb

   require 'achoo'
   require 'achoo/achievo/date_field'
   
   module Achoo
     module Achievo
       autoload :HourAdministrationForm,     'achoo/achievo/hour_administration_form.rb'
       autoload :HourRegistrationForm,       'achoo/achievo/hour_registration_form.rb'
       autoload :HourRegistrationFormRanged, 'achoo/achievo/hour_registration_form_ranged.rb'
       autoload :LockMonthForm,              'achoo/achievo/lock_month_form.rb'
       autoload :LoginForm,                  'achoo/achievo/login_form.rb'
       autoload :Table,                      'achoo/achievo/table.rb'
     end
   end

lib/achoo/system.rb

   require 'achoo'
   
   module Achoo
     module System
       autoload :CStruct,    'achoo/system/cstruct'
       autoload :LogEntry,   'achoo/system/log_entry'
       autoload :PMSuspend,  'achoo/system/pm_suspend'
       autoload :UTMPRecord, 'achoo/system/utmp_record'
       autoload :Wtmp,       'achoo/system/wtmp'
     end
   end

lib/achoo/system/log_entry.rb

   require 'achoo/system'
   
   module Achoo
     module System
   
       class LogEntry 
         include Comparable
       
         attr :time
         attr :event
       
         def initialize(time, event)
           @time  = time
           @event = event
         end
         
         def <=>(other_entry)
           time <=> other_entry.time
         end
       end
   
     end
   end
   

lib/achoo/temporal.rb

   require 'achoo'
   
   module Achoo
     module Temporal
       autoload :Timespan,     'achoo/temporal/timespan.rb'
       autoload :OpenTimespan, 'achoo/temporal/open_timespan.rb'
     end
   end

lib/achoo.rb

   module Achoo
   end

Generated on Tue May 04 19:54:00 +0000 2010