class ASF::MemberFiles

Constants

BOARD_REGEX

Same as MEMBER_REGEX, but no <uid> and no <email>

MEMBER_REGEX

This Regex is very similar to the one in the script used to create ballots: svn.apache.org/repos/private/foundation/Meetings/steve-tools/seed-issues.py

NAME2OUTPUTKEY
NOMINATED_BOARD
NOMINATED_MEMBERS

Public Class Methods

board_nominees() click to toggle source

Return hash of board nominees

# File lib/whimsy/asf/member-files.rb, line 244
def self.board_nominees
  nominees = {}
  ASF::MemberFiles.parse_file(NOMINATED_BOARD) do |hdr, nominee|
    # for board, the header currently looks like this:
    # <PUBLIC NAME>
    id = ASF::Person.find_by_name!(hdr) || hdr # default to full name
    nominee['Public Name'] = hdr # the board file does not have ids
    nominees[id] = nominee
  end
  nominees
end
latest_meeting(name=nil) click to toggle source

get the latest meeting directory or nomination file

# File lib/whimsy/asf/member-files.rb, line 41
def self.latest_meeting(name=nil)
  if name.nil? # we want the parent directory
    name = NOMINATED_MEMBERS # ensure the target directory has been set up
    File.dirname(Dir[File.join(ASF::SVN['Meetings'], '[2-9][0-9]*', name)].max)
  else
    Dir[File.join(ASF::SVN['Meetings'], '[2-9][0-9]*', name)].max
  end
end
make_board_nomination(fields = {}) click to toggle source

create a board nomination entry in the standard format

# File lib/whimsy/asf/member-files.rb, line 139
def self.make_board_nomination(fields = {})
  availid = fields[:availid] or raise ArgumentError.new(":availid is required")
  publicname = ASF::Person[availid]&.cn or raise ArgumentError.new(":availid #{availid} is invalid")
  nomby = fields[:nomby] or raise ArgumentError.new(":nomby is required")
  ASF::Person[nomby]&.dn or raise ArgumentError.new(":nomby is invalid")
  secby = fields[:secby] || ''
  statement = fields[:statement] or raise ArgumentError.new(":statement is required")
  [
    '',
    "   #{publicname}",
    "   Nominated by: #{nomby}@apache.org",
    "   Seconded by: #{secby}",
    '',
    '   Nomination Statement:',
    ASFString.reflow(statement, 4, 80),
    ''
  ].compact.join("\n") + "\n"
end
make_member_nomination(fields = {}) click to toggle source

create a member nomination entry in the standard format

# File lib/whimsy/asf/member-files.rb, line 116
def self.make_member_nomination(fields = {})
  availid = fields[:availid] or raise ArgumentError.new(":availid is required")
  publicname = ASF::Person[availid]&.cn or raise ArgumentError.new(":availid #{availid} is invalid")
  nomby = fields[:nomby] or raise ArgumentError.new(":nomby is required")
  ASF::Person[nomby]&.dn or raise ArgumentError.new(":nomby is invalid")
  secby = fields[:secby] || ''
  statement = fields[:statement] or raise ArgumentError.new(":statement is required")
  [
    '',
    " #{availid} <#{publicname}>",
    '',
    "   Nominee email: #{availid}@apache.org",
    "   Nominated by: #{nomby}@apache.org",
    "   Seconded by: #{secby}",
    '',
    '   Nomination Statement:',
    ASFString.reflow(statement, 4, 80),
    ''
  ].compact.join("\n") + "\n"
end
member_nominees() click to toggle source

Return hash of member nominees

# File lib/whimsy/asf/member-files.rb, line 226
def self.member_nominees
  nominees = {}
  ASF::MemberFiles.parse_file(NOMINATED_MEMBERS) do |hdr, nominee|
    # for members, the header currently looks like this:
    # availid <PUBLIC NAME>
    # In the past, it has had other layouts, for example:
    # availid PUBLIC NAME
    # PUBLIC NAME <email address>:
    id, name = hdr.split(' ', 2)
    # remove the spurious <> wrapper
    nominee['Public Name'] = name.sub(%r{^<}, '').chomp('>')
    # TODO: handle missing availid better
    nominees[id] = nominee
  end
  nominees
end
parse_file(name) { |header, nominee| ... } click to toggle source

Return a hash of nominees. key: availid (name for board nominees) value: hash of entries: keys: Public Name Nominee email Nominated by Seconded by => array of seconders Nomination Statement => array of text lines

# File lib/whimsy/asf/member-files.rb, line 59
def self.parse_file(name)
  case name
  when NOMINATED_BOARD
    regex = BOARD_REGEX
  when NOMINATED_MEMBERS
    regex = MEMBER_REGEX
  else
    raise ArgumentError.new "Unexpected name: #{name}"
  end
  # N.B. The format has changed over the years. This is the syntax as of 2021.
  # -----------------------------------------
  # <empty line>
  #  header line
  #    Nominee email: (not present in board file)
  #    Nominated by:
  #    Seconded by:

  #    Nomination Statement:

  # Find most recent file:
  nomfile = latest_meeting(name)

  lastheader = nil # what was the last valid header
  # It does not appear to be possible to have file open or read
  # automatically transcode strings, so we do it here.
  # This is necessary to avoid issues with matching Regexes.
  File.open(nomfile, mode: 'rb:UTF-8')
    .map(&:scrub)
    .slice_before(/^\s*-{35,60}\s*/)
    .drop(2) # instructions and sample block
    .each do |block|
    block.shift(1) # divider
    nominee = {}
    header = nil
    data = block.join.strip
    next if data == ''
    md = regex.match(data)
    raise  ArgumentError.new "Cannot parse #{data}" unless md
    md.named_captures.each do |k, v|
      case k
      when 'header'
        header = v.strip
      when 'uid', 'name'
        # not currently used
      else
        outkey = NAME2OUTPUTKEY[k]
        raise ArgumentError.new "Unexpected regex capture name: #{k}" if outkey.nil?
        v = v.split("\n") if k == 'statement' or k == 'seconds'
        nominee[outkey] = v
      end
    end
    yield header, nominee
  end
end
sort_board_nominees(contents, entries=nil) click to toggle source

Sort the board_nominees, optionally adding new entries

# File lib/whimsy/asf/member-files.rb, line 181
def self.sort_board_nominees(contents, entries=nil)
  sections = contents.split(%r{^-{10,}\n})
  header = sections.shift(2)
  sections.pop if sections.last.strip == ''
  sections.append(*entries) if entries # add new entries if any
  names = {}
  # replace 'each' by 'sort_by!' to sort by last name
  sections.each do |s|
    # sort by last name; check for duplicates
    m = s.match %r{\s+(.+)}
    if m
      name = m[1]
      raise ArgumentError.new("Duplicate id: #{name}") if names.include? name
      names[name] = 1
      name.split.last
    else
      'ZZ'
    end
  end
  # reconstitute the file
  [header, sections, ''].join("---------------------------------------\n")
end
sort_member_nominees(contents, entries=nil) click to toggle source

Sort the member_nominees, optionally adding new entries

# File lib/whimsy/asf/member-files.rb, line 159
def self.sort_member_nominees(contents, entries=nil)
  sections = contents.split(%r{^-{10,}\n})
  header = sections.shift(2)
  sections.append(*entries) if entries # add new entries if any
  ids = {}
  sections.sort_by! do |s|
    # sort by last name; check for duplicates
    m = s.match %r{(\S+) +<([^>]+)>}
    if m
      id = m[1]
      raise ArgumentError.new("Duplicate id: #{id}") if ids.include? id
      ids[id] = 1
      m[2].split.last
    else
      'ZZ'
    end
  end
  # reconstitute the file
  [header, sections, ''].join("-----------------------------------------\n")
end
update_board_nominees(env, wunderbar, entries=nil, msg=nil, opt={}) click to toggle source

update the board nominees

# File lib/whimsy/asf/member-files.rb, line 214
def self.update_board_nominees(env, wunderbar, entries=nil, msg=nil, opt={})
  nomfile = latest_meeting(NOMINATED_BOARD)
  opt[:diff] = true unless opt.include? :diff # default to true
  ASF::SVN.update(nomfile, msg || 'Updating board nominations', env, wunderbar, opt) do |_tmpdir, contents|
    sort_board_nominees(contents, entries)
  end
end
update_member_nominees(env, wunderbar, entries=nil, msg=nil, opt={}) click to toggle source

update the member nominees

# File lib/whimsy/asf/member-files.rb, line 205
def self.update_member_nominees(env, wunderbar, entries=nil, msg=nil, opt={})
  nomfile = latest_meeting(NOMINATED_MEMBERS)
  opt[:diff] = true unless opt.include? :diff # default to true
  ASF::SVN.update(nomfile, msg || 'Updating nominated members', env, wunderbar, opt) do |_tmpdir, contents|
    sort_member_nominees(contents, entries)
  end
end