class ASF::ICLA

Provide access to the contents of iclas.txt.

N.B. only id and name should be considered public form and claRef may contain details of the legal name beyond that in the public name

Constants

OFFICERS

location of a working copy of the officers directory in SVN

SOURCE

location of the iclas.txt file; may be nil if not found.

SOURCE_URL

Attributes

claRef[RW]

cla name or SVN revision info; extracted from the form

email[RW]

email address from the ICLA (may include multiple values, separated by space or comma)

form[RW]

lists the name of the form on file; includes claRef information

id[RW]

availid of the ICLA, or notinavail if no id has been issued

name[RW]

public name for the individual; should match LDAP

Public Class Methods

array_count_match(source, target) click to toggle source

find number of matches in target array

# File lib/whimsy/asf/icla.rb, line 131
def self.array_count_match(source, target)
  count = 0
  source.each {|src| count += 1 if target.include? src}
  count
end
available?(id) click to toggle source

is the id available? See also ASF::Mail.taken?

# File lib/whimsy/asf/icla.rb, line 293
def self.available?(id)
  return !self.taken?(id)
end
availids() click to toggle source

list of all ids

# File lib/whimsy/asf/icla.rb, line 161
def self.availids
  return [] unless SOURCE
  refresh
  return @@availids if @@availids
  availids = []
  each {|icla| availids << icla.id unless icla.id == 'notinavail'}
  @@availids = availids
end
availids_reserved() click to toggle source

list of reserved availids

# File lib/whimsy/asf/icla.rb, line 270
def self.availids_reserved
  return @@availids_reserved if @@availids_reserved
  reserved = File.read(File.join(ASF::SVN['officers'], 'reserved-ids.yml')).scan(/^- (\S+)/).flatten.uniq
  # Add in badrcptto
  reserved += self.badmails
  @@availids_reserved = reserved.uniq
end
availids_taken() click to toggle source

list of all availids that are are taken or reserved See also ASF::Mail.taken?

# File lib/whimsy/asf/icla.rb, line 280
def self.availids_taken
  self.availids_reserved + self.availids
end
badmails() click to toggle source

list of mails rejected by badrcptto and badrcptto_patterns Not intended for external use

# File lib/whimsy/asf/icla.rb, line 241
def self.badmails
  qmc = ASF::SVN['qmail_control']
  # non-patterns
  brt = File.join(qmc, 'badrcptto')
  badmails = File.read(brt).scan(/^(\w.+)@apache\.org\s*$/).flatten
  # now parse patterns
  brtpat = File.join(qmc, 'badrcptto_patterns')
  File.read(brtpat).each_line do |line|
    m = line.match(/^\^(\w.+)\\@/)
    if m
      badmails << m[1]
      next
    end
    # ^(abc|def|ghi)(jkl|mno|pqr)\@
    m = line.match(/^\^\(([|\w]+)\)\(([|\w]+)\)\\@/)
    if m
      m[1].split('|').each do |one|
        m[2].split('|').each do |two|
          badmails << "#{one}#{two}"
        end
      end
    else
      Wunderbar.warn "Error parsing #{brtpat} : could not match #{line}"
    end
  end
  badmails.uniq
end
each(&block) click to toggle source

iterate over all of the ICLAs

# File lib/whimsy/asf/icla.rb, line 171
def self.each(&block)
  refresh
  if @@icla_index and not @@icla_index.empty?
    @@icla_index.each(&block)
  elsif SOURCE and File.exist?(SOURCE)
    @@icla_index = []
    File.read(SOURCE).scan(/^([-\w]+):(.*?):(.*?):(.*?):(.*)/).each do |list|
      icla = ICLA.new()
      icla.id = list[0]
      icla.legal_name = list[1]
      icla.name = list[2]
      icla.email = list[3]
      icla.form = list[4]
      match = icla.form.match(/^Signed CLA(?:;(\S+)| \((\+=.+)\))/)
      if match
        # match either the cla name or the SVN ref (+=...)
        icla.claRef = match[1] || match[2]
      end
      block.call(icla)
      @@icla_index << icla
    end
  end
end
find_by_email(value) click to toggle source

find ICLA by email

# File lib/whimsy/asf/icla.rb, line 93
def self.find_by_email(value)
  return unless SOURCE

  refresh
  unless @@email_index
    @@email_index = {}
    # Allow for multiple emails separated by comma or space
    each {|icla| icla.emails.each {|m| @@email_index[m.downcase] = icla}}
  end

  @@email_index[value.downcase]
end
find_by_id(value) click to toggle source

find ICLA by ID

# File lib/whimsy/asf/icla.rb, line 80
def self.find_by_id(value)
  return if value == 'notinavail' or not SOURCE

  refresh
  unless @@id_index
    @@id_index = {}
    each {|icla| @@id_index[icla.id] = icla}
  end

  @@id_index[value]
end
find_by_name(value, multiple=false) click to toggle source

find ICLA by (public) name There are multiple entries with the same name. So if there are multiple matches, it does not make sense to return a single entry. By default, only return an entry if there is a single match. (else nil) If the multiple param is true, return an array of all matching entries. The array may be empty; does not return nil.

N.B. matching by name is inherently inaccurate due to misspellings and duplicates. There are likely to be both false positives and false negatives. Use with caution!

# File lib/whimsy/asf/icla.rb, line 115
def self.find_by_name(value, multiple=false)
  return unless SOURCE
  refresh
  unless @@name_index
    # Collect all the entries for each matching name
    @@name_index = Hash.new {|h, k| h[k] = Array.new}
    each {|icla| @@name_index[icla.name] << icla }
  end

  entries = @@name_index[value]
  return entries if multiple # no filtering needed
  return entries.first if entries&.size == 1
  return nil
end
find_matches(value) click to toggle source

find close matches

# File lib/whimsy/asf/icla.rb, line 138
def self.find_matches(value)
  matches = []
  source = value.strip.downcase.split(' ')
  self.each do |icla|
    target = icla.legal_name.strip.downcase.split(' ')
    if target.sort == source.sort # order- and case-independent match
      matches << icla
    else
      cnt = self.array_count_match(source, target)
      if cnt >= 2
        matches << icla
      else
        cnt = self.array_count_match(target, source)
        if cnt >= 2
          matches << icla
        end
      end
    end
  end
  matches
end
lname(line) click to toggle source

rearrange line in an order suitable for sorting

# File lib/whimsy/asf/icla.rb, line 215
def self.lname(line)
  return '' if line.start_with? '#'
  _, name, rest = line.split(':', 3)
  return '' unless name

  # Drop trailing (comment string) or /* comment */
  name.sub!(/\(.+\)$/, '')
  name.sub!(/\/\*.+\*\/$/, '')
  return '' if name.strip.empty?

  name = ASF::Person.sortable_name(name)

  "#{name}:#{rest}"
end
preload() click to toggle source

load ICLA information for every committer

# File lib/whimsy/asf/icla.rb, line 67
def self.preload
  people = []
  each do |icla|
    unless icla.id == 'notinavail'
      person = ASF::Person.find(icla.id)
      people << person
      person.icla = icla
    end
  end
  people
end
refresh() click to toggle source

flush caches if source file changed

# File lib/whimsy/asf/icla.rb, line 45
def self.refresh
  if not SOURCE or File.mtime(SOURCE) != @@mtime
    @@mtime = SOURCE ? File.mtime(SOURCE) : Time.now
    @@id_index = nil
    @@email_index = nil
    @@name_index = nil
    @@icla_index = nil # cache of all iclas as an array
    @@svn_change = nil

    @@availids = nil
  end
end
sort(source) click to toggle source

sort an entire iclas.txt file

# File lib/whimsy/asf/icla.rb, line 231
def self.sort(source)
  headers = source.scan(/^#.*/)
  lines = source.scan(/^\w.*/)

  headers.join("\n") + "\n" +
    lines.sort_by {|line| lname(line + "\n")}.join("\n") + "\n"
end
svn_change() click to toggle source

Date and time of the last change in iclas.txt in the working copy

# File lib/whimsy/asf/icla.rb, line 59
def self.svn_change
  self.refresh
  if SOURCE
    @@svn_change ||= Time.parse(ASF::SVN.getInfoItem(SOURCE, 'last-changed-date')).gmtime
  end
end
taken?(id) click to toggle source

is the availid taken (in use or reserved)? See also ASF::Mail.taken?

# File lib/whimsy/asf/icla.rb, line 286
def self.taken?(id)
  return self.availids_reserved.include?(id) ||
         self.availids.include?(id)
end
unlisted_name_by_email(age=0, env=nil) click to toggle source

return a hash of unlisted ICLA names, keyed by email if age > 0, return the entries added in the last n days

# File lib/whimsy/asf/icla.rb, line 197
def self.unlisted_name_by_email(age=0, env=nil)
  if age > 0
    rev = "{%s}:HEAD" % (Date.today - age)
    diff, _err = ASF::SVN.svn('diff', SOURCE_URL, {revision: rev, env: env})
    raise _err unless diff
    return Hash[*diff.scan(/^[+]notinavail:.*?:(.*?):(.*?):Signed CLA/).flatten.reverse]
  end
  hash = {}
  self.each { |icla| hash[icla.email] = icla.name if icla.noId? }
  hash
end

Public Instance Methods

as_line() click to toggle source

show the original entry (reconstructed for now)

# File lib/whimsy/asf/icla.rb, line 210
def as_line
  [id, legal_name, name, email, form].join(':')
end
emails() click to toggle source

return emails split by comma or space

# File lib/whimsy/asf/icla.rb, line 303
def emails
  email.split(/[, ]/)
end
noId?() click to toggle source

does the entry not have an id?

# File lib/whimsy/asf/icla.rb, line 298
def noId?
  self.id == 'notinavail'
end