class ASF::Member
Public Class Methods
Source
# File lib/whimsy/asf/member.rb, line 63 def self._entry_status(entry) status = entry['status'] case status when nil :current when %r{^Emeritus } :emeritus when %r{^Deceased } :deceased else raise "Unexpected status: #{status.inspect} for #{id}" end end
return the status of an entry:
-
:current
-
:emeritus
-
:deceased
Source
# File lib/whimsy/asf/member.rb, line 132 def self.current self.list.keys - self.status.keys end
Return a list of availids of current members
Source
# File lib/whimsy/asf/member.rb, line 127 def self.deceased status.select {|_k, v| v.start_with? 'Deceased'}.keys end
Return a list of availids of deceased members
Source
# File lib/whimsy/asf/member.rb, line 23 def self.each(&block) new.each(&block) end
An iterator that returns a list of ids and associated members.txt entries.
Source
# File lib/whimsy/asf/member.rb, line 243 def self.emails(text) # RE looks for optional continuation lines starting with spaces followed by nonspaces followed by '@' text.to_s.scan(/Email: (.*(?:\n\s+\S+@.*)*)/).flatten. join(' ').split(/\s+/).grep(/@/) end
extract member emails from members.txt entry
Source
# File lib/whimsy/asf/member.rb, line 122 def self.emeritus status.select {|_k, v| v.start_with? 'Emeritus'}.keys end
Return a list of availids of emeritus members
Source
# File lib/whimsy/asf/member.rb, line 237 def self.find(id) each {|availid| return true if availid == id} return false end
Determine if the person associated with a given id is an ASF
member. Includes emeritus and deceased members Returns a boolean value.
Source
# File lib/whimsy/asf/member.rb, line 86 def self.find_by_email(value) value = value.downcase each do |id, text| emails(text).each do |email| return Person.find(id) if email.downcase == value end end nil end
Find the ASF::Person
associated with a given email
Source
# File lib/whimsy/asf/member.rb, line 15 def self.find_text_by_id(value) new.each do |id, text| return text if id == value end nil end
Return the members.txt value associated with a given id
Source
# File lib/whimsy/asf/member.rb, line 28 def self.get_name(txt) txt[/(.*?)\n/, 1].sub(/\s+\/\*.*/, '') end
extract 1st line and remove any trailing /* comment */
Source
# File lib/whimsy/asf/member.rb, line 137 def self.key2sym(key) Symbol === key ? key.downcase : key.gsub(%r{[^a-zA-Z0-9]}, '_').downcase.to_sym end
convert key to symbol: replace non-alphanumeric with ‘_’ and downcase
Source
# File lib/whimsy/asf/member.rb, line 36 def self.list result = Hash[self.new.map {|id, text| [id, {text: text, name: self.get_name(text)}] }] self.status.each do |name, value| result[name]['status'] = value end result end
return a list of members.txt
entries as a Hash. Keys are availids. Values are a Hash with the following keys: :text
, :name
, "status"
. Active members are those with no ‘status’ value
Source
# File lib/whimsy/asf/member.rb, line 183 def self.list_entries(keys_wanted=nil, raw=false, &block) Enumerator.new do |y| # split by heading underlines; drop text before first ASF::Member.text.split("\n").slice_before {|a| a.start_with? '================'}.drop(1).each_with_index do |sect, index| status = nil # assume the sections remain in the original order case index when 0 status = :current when 1 status = :emeritus when 2 status = :other when 3 status = :deceased else raise ArgumentError, "Unexpected section #{index} #{sect[1..2]}" end if status sect.pop unless status == :deceased # drop next section name (except at the end) # extract the entries, dropping the leading text sect.slice_before {|a| a.start_with? ' *)'}.drop(1).each do |entry| # we always want the name name = entry.first[4..-1].sub(%r{ +/\*.+}, '') # skip ' *) '; drop comment ids = [] entry.each {|e| ids << $1 if e =~ %r{^ +Avail ID: *(\S+)}} if ids.length > 1 Wunderbar.error "Duplicate ids: #{ids} in #{name} entry" end if keys_wanted y.yield [status, name, ids.first, entry, self.parse_entry(entry, keys_wanted, raw)] else y.yield [status, name, ids.first, entry] end end else # can this happen? raise ArgumentError, 'Unexpected section with nil status!' end end end.each(&block) end
return all user entries, whether or not they have an id Params: keys_wanted: array of key names to extract and return Returns: array of [status, user name, availid or nil, entry lines, [keys]]
Source
# File lib/whimsy/asf/member.rb, line 340 def self.make_entry(fields={}) fullname = fields[:fullname] or raise ArgumentError.new(':fullname is required') address = fields[:address] || '<postal address>' availid = fields[:availid] or raise ArgumentError.new(':availid is required') email = fields[:email] || "#{availid}@apache.org" country = fields[:country] || '<Country>' tele = fields[:tele] || '<phone number>' fax = fields[:fax] || '' [ fullname, # will be prefixed by ' *) ' # Each line of address is indented (address.gsub(/^/, ' ').gsub(/\r/, '') unless address.empty?), (" #{country}" unless country.empty?), (" Email: #{email}" unless email.empty?), (" Tel: #{tele}" unless tele.empty?), (" Fax: #{fax}" unless fax.empty?), ' Forms on File: ASF Membership Application', " Avail ID: #{availid}" ].compact.join("\n") + "\n" end
create an entry in the standard format: *) First Last
Street Town Country Email: id@apache.org Tel: 1234
Forms on File: ASF
Membership Application Avail ID: id Parameters: fields - hash:
:fullname - required :address - required, multi-line allowed :availid - required :email - optional :country - optional :tele - optional :fax - optional
Source
# File lib/whimsy/asf/member.rb, line 53 def self.member_status(id) entry = list[id] return nil if entry.nil? _entry_status(entry) end
return the status of a member:
-
nil - id not found
-
:current
-
:emeritus
-
:deceased
Source
# File lib/whimsy/asf/member.rb, line 81 def self.member_statuses self.list.map { |id, v| [id, self._entry_status(v)]}.to_h end
return a hash of all the member ids and their status:
-
:current
-
:emeritus
-
:deceased
Source
# File lib/whimsy/asf/member.rb, line 306 def self.normalize(text) text = ASF::Member.sort(text) pattern = /^Active.*?^=+\n+(.*?)^Emeritus/m text[/We now number (\d+) active members\./, 1] = text[pattern].scan(/^\s\*\)\s/).length.to_s text end
normalize text: sort and update active count
Source
# File lib/whimsy/asf/member.rb, line 147 def self.parse_entry(entry, keys_wanted=nil, raw=false) if keys_wanted.nil? keys_wanted = %i{email} end # init array to collect matching key values, even if repeated dict = keys_wanted.map{|k| [k,[]]}.to_h current_entry = nil entry.each do |line| if line =~ %r{^ +([^:]+): *(.*)} value = $2.strip # must be done first otherwise $2 is clobbered key = key2sym($1) if keys_wanted.include? key current_entry = dict[key] current_entry << value else current_entry = nil # new key, and not wanted end elsif current_entry and line.start_with? ' ' # needs to be indented at least this much to be a continuation line value = line.strip current_entry << value if value.size > 0 else current_entry = nil end end # remove comments from emails? unless raw if dict.include? :email dict[:email] = dict[:email].each.map {|v| v.split(' ').grep(/@/)}.flatten end end dict end
parse a single entry Params:
-
entry
-
keys_wanted array of key symbols to retrieve; defaults to [“Email”]
-
raw: if true, then return raw email entries (with comments)
Strings are forced to lower-case and non-alphanumeric replaced with ‘_’
Source
# File lib/whimsy/asf/member.rb, line 257 def self.sort(source) # split into sections sections = source.split(/^([A-Z].*\n===+\n\n)/) # sort sections that contain names sections.map! do |section| next section unless section =~ /^\s\*\)\s/ # split into entries, and normalize those entries entries = section.split(/^\s\*\)\s/) header = entries.shift entries.map! {|entry| ' *) ' + entry.strip + "\n\n"} # sort the entries entries.sort_by! do |entry| ASF::Person.sortable_name(entry[/\)\s(.*?)\s*(\/\*|$)/, 1]) end header + entries.join end sections.join end
sort an entire members.txt file
Source
# File lib/whimsy/asf/member.rb, line 101 def self.status begin @status = nil if @mtime != @@mtime @mtime = @@mtime return Hash[@status.to_a] if @status rescue end status = {} sections = ASF::Member.text.to_s.split(/(.*\n===+)/) sections.shift(3) sections.each_slice(2) do |header, text| header.sub!(/s\n=+/, '') text.scan(/Avail ID: (.*)/).flatten.each {|id| status[id] = header} end @status = WeakRef.new(status) status end
Return a hash of non-active ASF
members and their status. Keys are availids. Values are strings from the section header under which the member is listed: currently either Emeritus (Non-voting) Member
or Deceased Member
. N.B. Does NOT return active members
Source
# File lib/whimsy/asf/member.rb, line 251 def self.svn_change file = File.join(ASF::SVN['foundation'], 'members.txt') return Time.parse(ASF::SVN.getInfoItem(file, 'last-changed-date')).gmtime end
Return the Last Changed Date for members.txt
in svn as a Time
object.
Source
# File lib/whimsy/asf/member.rb, line 284 def self.text foundation = ASF::SVN.find('foundation') return nil unless foundation begin text = @@text[0..-1] if @@text rescue WeakRef::RefError @@mtime = 0 end member_file = File.join(foundation, 'members.txt') member_time = File.mtime(member_file) if member_time.to_i > @@mtime.to_i @@mtime = member_time text = File.read(member_file) @@text = WeakRef.new(text) end text end
cache the contents of members.txt. Primary purpose isn’t performance, but rather to have a local copy that can be updated and used until the svn working copy catches up
Source
# File lib/whimsy/asf/member.rb, line 315 def self.text=(text) text = self.normalize(text) # save @@mtime = Time.now @@text = WeakRef.new(text) end
update local copy of members.txt
Public Instance Methods
Source
# File lib/whimsy/asf/member.rb, line 226 def each ASF::Member.text.to_s.split(/^ \*\) /).each do |section| id = section[/Avail ID: (.*)/, 1] yield id, section.sub(/\n.*\n===+\s*?\n(.*\n)+.*/, '').strip if id end nil end
An iterator that returns a list of ids and associated members.txt entries.