blob: 5cfd60ca337b1f8f7093858b1a0082d9b31ad1b6 [file] [log] [blame]
require_relative 'person/override-dates.rb'
module ASF
#
# An instance of this class represents a person. Data comes from a variety
# of sources: LDAP, <tt>asf--authorization-template</tt>, <tt>iclas.txt</tt>,
# <tt>members.txt</tt>, <tt>nominated-members.txt</tt>, and
# <tt>potential-member-watch-list.txt</tt>.
class Person
# sort support
def self.asciize(name)
if name.match %r{[^\x00-\x7F]}
# digraphs. May be culturally sensitive
name.gsub! %r{\u00df}, 'ss'
name.gsub! %r{\u00e4|a\u0308}, 'ae'
name.gsub! %r{\u00e5|a\u030a}, 'aa'
name.gsub! %r{\u00e6}, 'ae'
name.gsub! %r{\u00f1|n\u0303}, 'ny'
name.gsub! %r{\u00f6|o\u0308}, 'oe'
name.gsub! %r{\u00fc|u\u0308}, 'ue'
# latin 1
name.gsub! %r{\u00c9}, 'e'
name.gsub! %r{\u00d3}, 'o'
name.gsub! %r{[\u00e0-\u00e5]}, 'a'
name.gsub! %r{\u00e7}, 'c'
name.gsub! %r{[\u00e8-\u00eb]}, 'e'
name.gsub! %r{[\u00ec-\u00ef]}, 'i'
name.gsub! %r{[\u00f2-\u00f6]|\u00f8}, 'o'
name.gsub! %r{[\u00f9-\u00fc]}, 'u'
name.gsub! %r{[\u00fd\u00ff]}, 'y'
# Latin Extended-A
name.gsub! %r{[\u0100-\u0105]}, 'a'
name.gsub! %r{[\u0106-\u010d]}, 'c'
name.gsub! %r{[\u010e-\u0111]}, 'd'
name.gsub! %r{[\u0112-\u011b]}, 'e'
name.gsub! %r{[\u011c-\u0123]}, 'g'
name.gsub! %r{[\u0124-\u0127]}, 'h'
name.gsub! %r{[\u0128-\u0131]}, 'i'
name.gsub! %r{[\u0132-\u0133]}, 'ij'
name.gsub! %r{[\u0134-\u0135]}, 'j'
name.gsub! %r{[\u0136-\u0138]}, 'k'
name.gsub! %r{[\u0139-\u0142]}, 'l'
name.gsub! %r{[\u0143-\u014b]}, 'n'
name.gsub! %r{[\u014C-\u0151]}, 'o'
name.gsub! %r{[\u0152-\u0153]}, 'oe'
name.gsub! %r{[\u0154-\u0159]}, 'r'
name.gsub! %r{[\u015a-\u0162]}, 's'
name.gsub! %r{[\u0162-\u0167]}, 't'
name.gsub! %r{[\u0168-\u0173]}, 'u'
name.gsub! %r{[\u0174-\u0175]}, 'w'
name.gsub! %r{[\u0176-\u0178]}, 'y'
name.gsub! %r{[\u0179-\u017e]}, 'z'
# denormalized diacritics
name.gsub! %r{[\u0300-\u036f]}, ''
end
name.strip.gsub %r{[^\w]+}, '-'
end
# generational suffixes
SUFFIXES = /^([Jj][Rr]\.?|I{2,3}|I?V|VI{1,3}|[A-Z]\.)$/
# rearrange line in an order suitable for sorting
def self.sortable_name(name)
name = name.split.reverse
suffix = (name.shift if name.first =~ SUFFIXES)
suffix += ' ' + name.shift if name.first =~ SUFFIXES
name << name.shift
# name << name.shift if name.first=='van'
name.last.sub! %r{^IJ}, 'Ij'
name.unshift(suffix) if suffix
name.map! {|word| asciize(word)}
name.reverse.join(' ').downcase
end
# parse a name into LDAP fields
def self.ldap_name(name)
words = name.gsub(',', '').split(' ')
result = {'cn' => name}
result['title'] = words.shift if words.first == 'Dr.' or words.first == 'Dr'
if words.last =~ /^Ph\.D\.?/
title = words.pop # Always pop (||= short-circuits the pop)
result['title'] ||= title
end
result['generationQualifier'] = words.pop if words.last =~ SUFFIXES
result['givenName'] = words.shift # TODO does gn allow multiple words?
# extract surnames like van Gogh etc
if words.size >= 3 and words[-3..-2] == %w(de la) or words[-3..-2] == %w(van der) or words[-3..-2] == %w(van de) or words[-3..-2] == %w(van den) or words[-3..-2] == %w(von der)
result['sn'] = words[-3..-1].join(' ')
result['unused'] = words[0..-4]
elsif words.size >= 2 and %w(von van Van de De del Del den le Le O Di Du dos St.).include? words[-2]
result['sn'] = words[-2..-1].join(' ')
result['unused'] = words[0..-3]
else
result['sn'] = words.pop
result['unused'] = words
end
result
end
# return name in a sortable order (last name first)
def sortable_name
Person.sortable_name(self.public_name)
end
# determine account creation date. Notes:
# * LDAP info is not accurate for dates prior to 2009. See
# person/override-dates.rb
# * createTimestamp isn't loaded by default (but can either be preloaded
# or fetched explicitly)
def createTimestamp
result = @@create_date[name]
result ||= attrs['createTimestamp'][0] rescue nil # in case not loaded
result ||= ASF.search_one(base, "uid=#{name}", 'createTimestamp')[0][0]
result
end
def createDate
createTimestamp[0..7]
end
# return person's public name, searching a variety of sources, starting
# with iclas.txt, then LDAP, and finally the archives.
def public_name
return icla.name if icla
cn = [attrs['cn']].flatten.first
cn.force_encoding('utf-8') if cn.respond_to? :force_encoding
return cn if cn
ASF.search_archive_by_id(name)
end
# Returns <tt>true</tt> if this person is listed as an ASF member in
# _either_ LDAP or <tt>members.txt</tt>. Note: LDAP includes
# infrastructure staff members that may not be ASF Members.
def asf_member?
ASF::Member.status[name] or ASF.members.include? self
end
# Returns <tt>true</tt> if this person is listed as an ASF member in
# _either_ LDAP or <tt>members.txt</tt> or this person is listed as
# an PMC chair in LDAP.
def asf_officer_or_member?
asf_member? or ASF.pmc_chairs.include? self
end
end
end