def initialize fn
@fn = fn
- @names = {}
- IO.readlines(fn).map { |l| l =~ /^(.*)?:\s+(.*)$/ && @names[$1] = $2 } if File.exists? fn
+ @@people = {}
+
+ ## read in stored people
+ IO.readlines(fn).map do |l|
+ l =~ /^(.*)?:\s+(\d+)\s+(.*)$/ or next
+ 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 name_for email; @names[email]; end
- def register email, name
- return unless name
+ def save
+ File.open(@fn, "w") do |f|
+ @@people.each do |email, p|
+ next if p.email == p.name
+ next if p.name =~ /=/ # drop rfc2047-encoded, and lots of other useless emails. definitely a heuristic.
+ f.puts "#{p.email}: #{p.timestamp} #{p.name}"
+ end
+ end
+ end
- name = name.gsub(/^\s+|\s+$/, "").gsub(/\s+/, " ")
+ def self.people_for s, opts={}
+ return [] if s.nil?
+ s.split_on_commas.map { |ss| self.person_for ss, opts }
+ end
- ## all else being equal, prefer longer names, unless the prior name
- ## doesn't contain any capitalization
- oldname = @names[email]
- @names[email] = name if oldname.nil? || oldname.length < name.length || (oldname !~ /[A-Z]/ && name =~ /[A-Z]/)
+ 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]
- def save; File.open(@fn, "w") { |f| @names.each { |email, name| f.puts "#{email}: #{name}" } }; end
-end
+ if oldp.nil? || p.better_than?(oldp)
+ @@people[p.email] = p
+ end
-class Person
- @@email_map = {}
+ @@people[p.email].touch!
+ @@people[p.email]
+ end
+end
- attr_accessor :name, :email
+## 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
+ 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
- PersonManager.register @email, name
- @name = PersonManager.name_for @email
+ @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 == o; o && o.email == email; end
- alias :eql? :==
- def hash; [name, email].hash; 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
def full_address
if @name && @email
- if @name =~ /"/
- "#{@name.inspect} <#@email>" # escape quotes
+ if @name =~ /[",@]/
+ "#{@name.inspect} <#{@email}>" # escape quotes
else
- "#@name <#@email>"
+ "#{@name} <#{@email}>"
end
else
- @email
+ email
end
end
end.downcase
end
- def self.for s
+ def self.from_address s
return nil if s.nil?
## try and parse an email address and name
[nil, s]
end
- @@email_map[email] ||= Person.new name, email
+ Person.new name, email
end
- def self.for_several s
- return [] if s.nil?
-
- begin
- s.split_on_commas.map { |ss| self.for ss }
- rescue StandardError => e
- raise "#{e.message}: for #{s.inspect}"
- end
+ def indexable_content
+ [name, email, email.split(/@/).first].join(" ")
end
+
+ def eql? o; email.eql? o.email end
+ def hash; email.hash end
end
end