module Redwood class PersonManager include Singleton def initialize fn @fn = fn @@people = {} ## read in stored people IO.readlines(fn).map do |l| l =~ /^(.*)?:\s+(\d+)\s+(.*)$/ or raise "can't parse: #{l}" email, time, name = $1, $2, $3 @@people[email] = Person.new name, email, time, false end if File.exists? fn self.class.i_am_the_instance self end def save File.open(@fn, "w") do |f| @@people.each do |email, p| next if p.email == p.name next if p.email =~ /=/ # drop rfc2047-encoded, and lots of other useless emails. definitely a heuristic. f.puts "#{p.email}: #{p.timestamp} #{p.name}" end end end def self.people_for s, opts={} return [] if s.nil? s.split_on_commas.map { |ss| self.person_for ss, opts } end def self.person_for s, opts={} p = Person.from_address(s) or return nil p.definitive = true if opts[:definitive] register p end def self.register p oldp = @@people[p.email] if oldp.nil? || p.better_than?(oldp) @@people[p.email] = p end @@people[p.email].touch! @@people[p.email] end end ## don't create these by hand. rather, go through personmanager, to ## ensure uniqueness and overriding. class Person attr_accessor :name, :email, :timestamp bool_accessor :definitive def initialize name, email, timestamp=0, definitive=false raise ArgumentError, "email can't be nil" unless email if name @name = name.gsub(/^\s+|\s+$/, "").gsub(/\s+/, " ") if @name =~ /^(['"]\s*)(.*?)(\s*["'])$/ @name = $2 end end @email = email.gsub(/^\s+|\s+$/, "").gsub(/\s+/, " ").downcase @definitive = definitive @timestamp = timestamp end ## heuristic: whether the name attached to this email is "real", i.e. ## we should bother to store it. def generic? @email =~ /no\-?reply/ end def better_than? o return false if o.definitive? || generic? return true if definitive? o.name.nil? || (name && name.length > o.name.length && name =~ /[a-z]/) end def to_s; "#@name <#@email>" end def touch!; @timestamp = Time.now.to_i end # def == o; o && o.email == email; end # alias :eql? :== # def hash; [name, email].hash; end def shortname case @name when /\S+, (\S+)/ $1 when /(\S+) \S+/ $1 when nil @email else @name end end def longname if @name && @email "#@name <#@email>" else @email end end def mediumname; @name || @email; end def full_address if @name && @email if @name =~ /[",@]/ "#{@name.inspect} <#@email>" # escape quotes else "#@name <#@email>" end else @email end end ## when sorting addresses, sort by this def sort_by_me case @name when /^(\S+), \S+/ $1 when /^\S+ \S+ (\S+)/ $1 when /^\S+ (\S+)/ $1 when nil @email else @name end.downcase end def self.from_address s return nil if s.nil? ## try and parse an email address and name name, email = case s when /["'](.*?)["'] <(.*?)>/, /([^,]+) <(.*?)>/ a, b = $1, $2 [a.gsub('\"', '"'), b] when /<((\S+?)@\S+?)>/ [$2, $1] when /((\S+?)@\S+)/ [$2, $1] else [nil, s] end Person.new name, email end def eql? o; email.eql? o.email end def hash; email.hash end end end