blob: 8e28f1e72d92014688154d95e6e9322301ef9192 [file] [log] [blame]
#!/usr/bin/env ruby
PAGETITLE = "Member nominations cross-check" # Wvisible:meeting
$LOAD_PATH.unshift '/srv/whimsy/lib'
require 'mail'
require 'wunderbar/bootstrap'
require 'whimsy/asf'
# link to members private-arch
MBOX = 'https://mail-search.apache.org/members/private-arch/members/'
# link to roster page
ROSTER = '/roster/committer'
# get a list of current members messages
year = Time.new.year.to_s
archive = Dir["/srv/mail/members/#{year}*/*"]
# select messages that have a subject line starting with [MEMBER NOMINATION]
emails = []
archive.each do |email|
next if email.end_with? '/index'
message = IO.read(email, mode: 'rb')
next unless message[/^Date: .*/].to_s.include? year
subject = message[/^Subject: .*/]
next unless subject.upcase.include? "MEMBER"
next unless subject.upcase =~ /NOMI[NM]ATION/
mail = Mail.new(message.encode(message.encoding, crlf_newline: true))
next if mail.subject.downcase == 'member nomination process'
emails << mail if mail.subject =~ /^\[?MEMBER(SHIP)? NOMI[MN]ATION\]?/i
end
# parse nominations for names and ids
MEETINGS = ASF::SVN['Meetings']
meeting = Dir[File.join(MEETINGS, '2*')].sort.last
nominations = IO.read(File.join(meeting, 'nominated-members.txt')).
scan(/^---+--\s+(.*?)\n/).flatten
nominations.shift if nominations.first == '<empty line>'
nominations.pop if nominations.last.empty?
nominations.map! do |line|
{name: line.gsub(/<.*|\(\w+@.*/, '').strip, id: line[/([.\w]+)@/, 1]}
end
# preload names
people = ASF::Person.preload('cn',
nominations.map {|nominee| ASF::Person.find(nominee[:id])})
# location of svn repository
svnurl = `cd #{meeting}; svn info`[/URL: (.*)/, 1]
# produce HTML output of reports, highlighting ones that have not (yet)
# been posted
_html do
_style %{
.missing {background-color: yellow}
.flexbox {display: flex; flex-flow: row wrap}
.flexitem {flex-grow: 1}
.flexitem:first-child {order: 2}
.flexitem:last-child {order: 1}
.count {margin-left: 4em}
}
_whimsy_body(
title: PAGETITLE,
related: {
'/members/memberless-pmcs' => 'PMCs with no/few ASF Members',
'https://svn.apache.org/repos/private/foundation/Meetings/' => 'Official Meeting Agenda Directory'
},
helpblock: -> {
_ 'This script checks new member nomination statements from members@ against the official meeting ballot files, and highlights differences. '
_ 'This probably only works in the period shortly before or after a Members meeting!'
}
) do
_div.flexbox do
_div.flexitem do
_h1_! do
_a 'Nominees', href: 'watch/nominees'
_ ' in '
_a 'svn', href: File.join(svnurl, 'nominated-members.txt')
end
_p.count "Count: #{nominations.count}"
_ul nominations.sort_by {|nominee| nominee[:name]} do |nominee|
_li! do
person = ASF::Person.find(nominee[:id])
match = /\b(#{nominee[:name]}|#{person.public_name})\b/i
if emails.any? {|mail| mail.subject.downcase =~ match}
_a.present person.public_name, href: "#{ROSTER}/#{nominee[:id]}"
else
_a.missing person.public_name, href: "#{ROSTER}/#{nominee[:id]}"
end
if nominee[:name] != person.public_name
_span " (as #{nominee[:name]})"
end
end
end
end
nominees = nominations.map! {|person| person[:name]}
nominees += people.map {|person| person.public_name}
_div.flexitem do
_h1_.posted! do
_a "Posted", href:
'https://mail-search.apache.org/members/private-arch/members/'
_ " nominations reports"
end
_p.count "Count: #{emails.count}"
# attempt to sort reports by nominee name
emails.sort_by! do |mail|
mail.subject.downcase.gsub('- ', '').sub(/\[.*?\]\s*/, '')
end
# output an unordered list of subjects linked to the message archive
_ul emails do |mail|
_li do
href = MBOX + mail.date.strftime('%Y%m') + '.mbox/' +
URI.escape('<' + mail.message_id + '>')
if nominees.any? {|name| mail.subject =~ /\b#{name}\b/i}
_a.present mail.subject, href: href
else
_a.missing mail.subject, href: href
end
end
end
end
end
end
end
# produce JSON output of reports
_json do
_ reports do |mail|
_subject mail.subject
_link MBOX + URI.escape('<' + mail.message_id + '>')
_missing missing.any? {|title| mail.subject.downcase =~ /\b#{title}\b/}
end
end