On Nov 4, 2017 3:34 PM, <ru...@apache.org> wrote:
> This is an automated email from the ASF dual-hosted git repository. > > rubys pushed a commit to branch master > in repository https://gitbox.apache.org/repos/asf/whimsy.git > > > The following commit(s) were added to refs/heads/master by this push: > new df26a69 initial import from svn:foundation/board/scripts/ > committers-report.rb > df26a69 is described below > > commit df26a69cbad10dbc5fdea7436a2ee816ffe00e26 > Author: Sam Ruby <ru...@intertwingly.net> > AuthorDate: Sat Nov 4 15:33:18 2017 -0400 > > initial import from svn:foundation/board/scripts/committers-report.rb > > replace ARGV with ASF::SVN > remove puts > --- > lib/whimsy/asf/board.rb | 10 +- > www/board/agenda/Gemfile | 2 + > www/board/agenda/routes.rb | 5 + > www/board/agenda/views/committers_report.text.rb | 275 > +++++++++++++++++++++++ > 4 files changed, 291 insertions(+), 1 deletion(-) > > diff --git a/lib/whimsy/asf/board.rb b/lib/whimsy/asf/board.rb > index 6e82d5a..7310220 100644 > --- a/lib/whimsy/asf/board.rb > +++ b/lib/whimsy/asf/board.rb > @@ -23,7 +23,15 @@ module ASF > > # time of next meeting > def self.nextMeeting > - self.calendar.select {|time| time > Time.now.utc}.min > + time = self.calendar.select {|time| time > Time.now.utc}.min > + > + if not time > + require 'chronic' > + time ||= Chronic.parse('3rd wednesday this month') > + time = Chronic.parse('3rd wednesday next month') if time < > Time.now.utc > + end > + > + time > end > > # list of PMCs reporting in the specified meeting > diff --git a/www/board/agenda/Gemfile b/www/board/agenda/Gemfile > index 918fc49..97fea07 100644 > --- a/www/board/agenda/Gemfile > +++ b/www/board/agenda/Gemfile > @@ -30,6 +30,8 @@ gem 'mime-types', '~> 2.6' > > gem 'rubyXL' > > +gem 'chronic' > + > group :test do > gem 'rspec' > gem 'puma' > diff --git a/www/board/agenda/routes.rb b/www/board/agenda/routes.rb > index 5fc58f7..c56b267 100755 > --- a/www/board/agenda/routes.rb > +++ b/www/board/agenda/routes.rb > @@ -269,6 +269,11 @@ get %r{/(\d\d\d\d-\d\d-\d\d).json} do |file| > end > end > > +# draft committers report > +get '/text/committers-report' do > + _text :committers_report > +end > + > # draft minutes > get '/text/minutes/:file' do |file| > file = "board_minutes_#{file.gsub('-','_')}.txt".untaint > diff --git a/www/board/agenda/views/committers_report.text.rb > b/www/board/agenda/views/committers_report.text.rb > new file mode 100644 > index 0000000..25c28c9 > --- /dev/null > +++ b/www/board/agenda/views/committers_report.text.rb > @@ -0,0 +1,275 @@ > +require 'chronic' > + > +## This is a script to generate an email for committ...@apache.org from > +## an agenda file. It also requires the calendar.txt so it can determine > +## the next meeting's date and committee-info.txt so it can determine > +## who the VP is for a project. > + > +# Add the right prefix to a number > +def prefixNumber(number) > + if number % 10 == 1 > + return number + "st" > + end > + if number % 10 == 2 > + return number + "nd" > + end > + return number + "th" > +end > + > +board_svn = ASF::SVN['private/foundation/board'] > +agenda_file = Dir["#{board_svn}/board_agenda_*.txt"].last.untaint > + > +##### Parse the agenda to find the data items above > + > +# Data items from agenda > +date = nil > +day = nil > +daynum = nil > +month = nil > +year = nil > +directors = Array.new > +officers = Array.new > +guests = Array.new > +minutes = Array.new > +resolutions = Array.new > +missing_reports = Array.new > + > +# State variables > +parsing_directors = false > +parsing_officers = false > +parsing_guests = false > +parsing_resolutions = false > +parsing_attachment = nil > +current_attachment = nil > + > +File.open(agenda_file).each do |line| > + > + # 1: Find the date, this is used in the title and various other places > + if !date && line =~ /(\w*) (\d\d?), (\d{4})$/ > + month = $1 > + daynum = $2 > + day = prefixNumber(daynum) > + year = $3 > + date = line.strip() > + next > + end > + > + # 2: Get the list of expected directors > + if line.strip == "Directors (expected to be) Present:" > + parsing_directors = true > + next > + end > + if parsing_directors > + if line.strip == "Directors (expected to be) Absent:" > + parsing_directors = false > + next > + end > + if line.strip == "" > + next > + end > + directors << line.strip > + next > + end > + > + if line.strip == "Executive Officers (expected to be) Present:" > + parsing_officers = true > + next > + end > + if parsing_officers > + if line.strip == "Executive Officers (expected to be) Absent:" > + parsing_officers = false > + next > + end > + if line.strip == "" > + next > + end > + officers << line.strip > + next > + end > + > + # 3: Get the list of expected guests > + # TODO: Same as directors code above, consider a function > + if line.strip == "Guests (expected):" > + parsing_guests = true > + next > + end > + if parsing_guests > + if line =~ /\d. Minutes from previous meetings/ > + parsing_guests = false > + next > + end > + if line.strip == "" > + next > + end > + guests << line.strip > + next > + end > + > + # 4: Get the list of listed minutes > + if line =~ /[A-Z]\. The meeting of (\w*) \d\d?, \d{4}$/ > + minutes << $1 > + next > + end > + > + # 5: Get the list of resolutions > + if line =~ /\d. Special Orders/ > + parsing_resolutions = true > + next > + end > + if parsing_resolutions > + if line =~ /\d. Discussion Items/ > + parsing_resolutions = false > + next > + end > + if line =~ /^\s*[A-Z]\. / > + resolutions << line.strip > + next > + end > + end > + > + # 6: Get the list of missing reports (aka empty attachments) > + if !parsing_attachment && line =~ /^Attachment (..?): (.*)/ > + parsing_attachment = $1 > + current_attachment = $2 > + next > + end > + if parsing_attachment > + if line.strip == "" > + next > + end > + if line.strip == "-----------------------------------------" > + if parsing_attachment.to_i.between?(1, 6) > + puts "Skipping missing President Committee attachment: > #{current_attachment}" > + else > + missing_reports << current_attachment > + end > + parsing_attachment = nil > + next > + end > + parsing_attachment = nil > + next > + end > + > +end > + > +##### 7: Find out the date of the next board report > + > +calendar_file = ASF::SVN['private/committers/board'] + "/calendar.txt" > +found_date = false > +next_meeting = nil > +File.open(calendar_file).each do |line| > + if line =~ /\$Date:/ > + next > + end > + if line =~ /#{month} #{year}/ > + found_date = true > + next > + end > + if found_date > + if line =~ /(\d+) (\w+) \d{4}/ > + next_meeting = prefixNumber($1) + " of " + $2 > + break > + end > + end > +end > +if !next_meeting > + puts "Error: Unable to determine the next meeting from > #{calendar_file}\n" > + nxt = Chronic.parse('3rd wednesday next month').to_s > + ## Returns something like: Wed Dec 21 12:00:00 -0500 2011 > + if nxt =~ /Wed (\w{3}) (\d\d).*(\d{4})$/ > + next_meeting = "#{$1} #{$2} #{$3}" > + end > + ##exit 1 > +end > +if !next_meeting > + puts "Error: Unable to determine the next meeting at all\n" > + exit 1 > +end > + > +##### 8: Find names of the VPs of TLPs in resolutions > + > +## this does not work, since new TLPs are not yet in committee-info.txt > +## instead we should parse this from the resolution > + > +committee_file = ASF::SVN['private/committers/board'] + > "/committee-info.txt" > +parsing_projects = false > +resolution_to_chair = Hash.new > +File.open(committee_file).each do |line| > + if line =~ /\d. APACHE SOFTWARE FOUNDATION COMMITTEES/ > + parsing_projects = true > + end > + if parsing_projects > + if line =~ /^\s+([\w\s]+)\s\s+([^<]*)<[^>]*>\s*$/ > + project = $1 > + chair = $2 > + project = project.strip > + resolutions.each() do |resolution| > + if resolution =~ /#{project}/ > + resolution_to_chair[resolution] = chair.strip > + end > + end > + end > + if line =~ /={76}/ > + parsing_projects = false > + break > + end > + end > +end > + > +##### Prepare the arrays for output > +t_directors = directors.join(", ") > +t_officers = officers.join(", ") > +t_guests = guests.join(", ") > +if !minutes.empty? > + t_minutes = "\nThe " + minutes.join(", ").sub(/, ([^,]*)$/, ' and \1') > + " minutes were " + (minutes.length > 1 ? "all " : "") + "approved. > \nMinutes will be posted to http://www.apache.org/ > foundation/records/minutes/\n" > +else > + t_minutes = "" > +end > +if !missing_reports.empty? > + t_missing_reports = "The following reports were not received and are > expected next month: \n\n " > + t_missing_reports += missing_reports.join("\n ") > + t_missing_reports += "\n" > +else > + t_missing_reports = "" > +end > +if !resolutions.empty? > + t_resolutions = "The following resolutions were passed unanimously: > \n\n" > + resolutions.each() do |resolution| > + t_resolutions += " #{resolution}"; > + # if(resolution_to_chair[resolution]) > + # t_resolutions += " (" + resolution_to_chair[resolution] +", VP)" > + # end > + t_resolutions += " (???, VP)\n" > + end > +else > + t_resolutions = "" > +end > + > +##### Write the report > +report = <<REPORT > +PLEASE EDIT THIS, IT IS ONLY AN ESTIMATE. > +From: chair...@apache.org > +To: committ...@apache.org > +Reply-To: bo...@apache.org > +Subject: ASF Board Meeting Summary - #{month} #{daynum}, #{year} > + > +The #{month} board meeting took place on the #{day}. > + > +The following directors were present: > + > + #{t_directors} > + > +The following officers were present: > + > + #{t_officers} > + > +The following guests were present: > + > + #{t_guests} > +#{t_minutes} > +All of the received reports to the board were approved. > + > +#{t_missing_reports} > +#{t_resolutions} > +The next board meeting will be on the #{next_meeting}. > +REPORT > > -- > To stop receiving notification emails like this one, please contact > ['"comm...@whimsical.apache.org" <comm...@whimsical.apache.org>']. >