whimsy-asf 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/asf.gemspec +32 -0
- data/asf.version +1 -0
- data/lib/whimsy/asf.rb +9 -0
- data/lib/whimsy/asf/agenda.rb +117 -0
- data/lib/whimsy/asf/agenda/attachments.rb +42 -0
- data/lib/whimsy/asf/agenda/back.rb +29 -0
- data/lib/whimsy/asf/agenda/committee.rb +20 -0
- data/lib/whimsy/asf/agenda/exec-officer.rb +34 -0
- data/lib/whimsy/asf/agenda/front.rb +72 -0
- data/lib/whimsy/asf/agenda/minutes.rb +23 -0
- data/lib/whimsy/asf/agenda/special.rb +107 -0
- data/lib/whimsy/asf/auth.rb +28 -0
- data/lib/whimsy/asf/committee.rb +124 -0
- data/lib/whimsy/asf/icla.rb +67 -0
- data/lib/whimsy/asf/ldap.rb +232 -0
- data/lib/whimsy/asf/mail.rb +81 -0
- data/lib/whimsy/asf/member.rb +82 -0
- data/lib/whimsy/asf/nominees.rb +32 -0
- data/lib/whimsy/asf/podlings.rb +23 -0
- data/lib/whimsy/asf/rack.rb +81 -0
- data/lib/whimsy/asf/site.rb +24 -0
- data/lib/whimsy/asf/svn.rb +27 -0
- data/lib/whimsy/asf/watch.rb +41 -0
- metadata +149 -0
| @@ -0,0 +1,28 @@ | |
| 1 | 
            +
            module ASF
         | 
| 2 | 
            +
             | 
| 3 | 
            +
              class Authorization
         | 
| 4 | 
            +
                include Enumerable
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                def self.find_by_id(value)
         | 
| 7 | 
            +
                  new.select {|auth, ids| ids.include? value}.map(&:first)
         | 
| 8 | 
            +
                end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                def initialize(file='asf')
         | 
| 11 | 
            +
                  @file = file
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                def each
         | 
| 15 | 
            +
                  auth = ASF::SVN['infra/infrastructure/trunk/subversion/authorization']
         | 
| 16 | 
            +
                  File.read("#{auth}/#{@file}-authorization-template").
         | 
| 17 | 
            +
                    scan(/^([-\w]+)=(\w.*)$/).each do |pmc, ids|
         | 
| 18 | 
            +
                    yield pmc, ids.split(',')
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
              class Person
         | 
| 24 | 
            +
                def auth
         | 
| 25 | 
            +
                  @auths ||= ASF::Authorization.find_by_id(name)
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
            end
         | 
| @@ -0,0 +1,124 @@ | |
| 1 | 
            +
            module ASF
         | 
| 2 | 
            +
             | 
| 3 | 
            +
              class Base
         | 
| 4 | 
            +
              end
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              class Committee < Base
         | 
| 7 | 
            +
                attr_accessor :info, :names, :emeritus, :report
         | 
| 8 | 
            +
                def initialize(*args)
         | 
| 9 | 
            +
                  @info = []
         | 
| 10 | 
            +
                  @emeritus = []
         | 
| 11 | 
            +
                  super
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                # mapping of committee names to canonical names (generally from ldap)
         | 
| 15 | 
            +
                # See also www/roster/committee.cgi
         | 
| 16 | 
            +
                @@aliases = Hash.new {|hash, name| name}
         | 
| 17 | 
            +
                @@aliases.merge! \
         | 
| 18 | 
            +
                  'community development'       => 'comdev',
         | 
| 19 | 
            +
                  'conference planning'         => 'concom',
         | 
| 20 | 
            +
                  'conferences'                 => 'concom',
         | 
| 21 | 
            +
                  'http server'                 => 'httpd',
         | 
| 22 | 
            +
                  'httpserver'                  => 'httpd',
         | 
| 23 | 
            +
                  'java community process'      => 'jcp',
         | 
| 24 | 
            +
                  'lucene.net'                  => 'lucenenet',
         | 
| 25 | 
            +
                  'quetzalcoatl'                => 'quetz',
         | 
| 26 | 
            +
                  'security team'               => 'security',
         | 
| 27 | 
            +
                  'open climate workbench'      => 'climate',
         | 
| 28 | 
            +
                  'c++ standard library'        => 'stdcxx',
         | 
| 29 | 
            +
                  'travel assistance'           => 'tac',
         | 
| 30 | 
            +
                  'traffic server'              => 'trafficserver',
         | 
| 31 | 
            +
                  'web services'                => 'ws',
         | 
| 32 | 
            +
                  'xml graphics'                => 'xmlgraphics'
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                @@namemap = Proc.new do |name|
         | 
| 35 | 
            +
                  cname = @@aliases[name.downcase]
         | 
| 36 | 
            +
                  cname
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                def self.load_committee_info
         | 
| 40 | 
            +
                  board = ASF::SVN['private/committers/board']
         | 
| 41 | 
            +
                  file = "#{board}/committee-info.txt"
         | 
| 42 | 
            +
                  if @committee_info and File.mtime(file) == @committee_mtime
         | 
| 43 | 
            +
                    return @committee_info 
         | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
                  @committee_mtime = File.mtime(file)
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                  info = File.read(file).split(/^\* /)
         | 
| 48 | 
            +
                  head, report = info.shift.split(/^\d\./)[1..2]
         | 
| 49 | 
            +
                  head.scan(/^\s+(\w.*?)\s\s+.*<(\w+)@apache\.org>/).each do |name, id|
         | 
| 50 | 
            +
                    find(name).chair = ASF::Person.find(id) 
         | 
| 51 | 
            +
                  end
         | 
| 52 | 
            +
                  @nonpmcs = head.sub(/.*?also has/m,'').
         | 
| 53 | 
            +
                    scan(/^\s+(\w.*?)\s\s+.*<\w+@apache\.org>/).flatten.uniq.
         | 
| 54 | 
            +
                    map {|name| find(name)}
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                  info.each do |roster|
         | 
| 57 | 
            +
                    committee = find(@@namemap.call(roster[/(\w.*?)\s+\(/,1]))
         | 
| 58 | 
            +
                    roster.gsub! /^.*\(\s*emeritus\s*\).*/i do |line|
         | 
| 59 | 
            +
                      committee.emeritus += line.scan(/<(.*?)@apache\.org>/).flatten
         | 
| 60 | 
            +
                      ''
         | 
| 61 | 
            +
                    end
         | 
| 62 | 
            +
                    committee.info = roster.scan(/<(.*?)@apache\.org>/).flatten
         | 
| 63 | 
            +
                    committee.names = Hash[roster.gsub(/\(\w+\)/, '').
         | 
| 64 | 
            +
                      scan(/^\s*(.*?)\s*<(.*?)@apache\.org>/).map {|list| list.reverse}]
         | 
| 65 | 
            +
                  end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                  report.scan(/^([^\n]+)\n---+\n(.*?)\n\n/m).each do |period, committees|
         | 
| 68 | 
            +
                    committees.scan(/^   \s*(.*)/).each do |committee|
         | 
| 69 | 
            +
                      committee, comment = committee.first.split(/\s+#\s+/,2)
         | 
| 70 | 
            +
                      committee = find(committee)
         | 
| 71 | 
            +
                      if comment
         | 
| 72 | 
            +
                        committee.report = "#{period}: #{comment}"
         | 
| 73 | 
            +
                      elsif period == 'Next month'
         | 
| 74 | 
            +
                        committee.report = 'Every month'
         | 
| 75 | 
            +
                      else
         | 
| 76 | 
            +
                        committee.report = period
         | 
| 77 | 
            +
                      end
         | 
| 78 | 
            +
                    end
         | 
| 79 | 
            +
                  end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                  @committee_info = ASF::Committee.collection.values
         | 
| 82 | 
            +
                end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                def self.nonpmcs
         | 
| 85 | 
            +
                  @nonpmcs
         | 
| 86 | 
            +
                end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                def self.find(name)
         | 
| 89 | 
            +
                  result = super(@@aliases[name.downcase])
         | 
| 90 | 
            +
                  result.display_name = name if name =~ /[A-Z]/
         | 
| 91 | 
            +
                  result
         | 
| 92 | 
            +
                end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                def chair
         | 
| 95 | 
            +
                  Committee.load_committee_info
         | 
| 96 | 
            +
                  @chair
         | 
| 97 | 
            +
                end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                def display_name
         | 
| 100 | 
            +
                  Committee.load_committee_info
         | 
| 101 | 
            +
                  @display_name || name
         | 
| 102 | 
            +
                end
         | 
| 103 | 
            +
             | 
| 104 | 
            +
                def display_name=(name)
         | 
| 105 | 
            +
                  @display_name ||= name
         | 
| 106 | 
            +
                end
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                def chair=(person)
         | 
| 109 | 
            +
                  @chair = person
         | 
| 110 | 
            +
                end
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                def info=(list)
         | 
| 113 | 
            +
                  @info = list
         | 
| 114 | 
            +
                end
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                def names=(hash)
         | 
| 117 | 
            +
                  @names = hash
         | 
| 118 | 
            +
                end
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                def nonpmc?
         | 
| 121 | 
            +
                  Committee.nonpmcs.include? self
         | 
| 122 | 
            +
                end
         | 
| 123 | 
            +
              end
         | 
| 124 | 
            +
            end
         | 
| @@ -0,0 +1,67 @@ | |
| 1 | 
            +
            module ASF
         | 
| 2 | 
            +
             | 
| 3 | 
            +
              class ICLA
         | 
| 4 | 
            +
                include Enumerable
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                def self.find_by_id(value)
         | 
| 7 | 
            +
                  return if value == 'notinavail'
         | 
| 8 | 
            +
                  new.each do |id, name, email|
         | 
| 9 | 
            +
                    if id == value
         | 
| 10 | 
            +
            	  return Struct.new(:id, :name, :email).new(id, name, email)
         | 
| 11 | 
            +
                    end 
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
                  nil
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                def self.find_by_email(value)
         | 
| 17 | 
            +
                  value = value.downcase
         | 
| 18 | 
            +
                  ICLA.new.each do |id, name, email|
         | 
| 19 | 
            +
                    if email.downcase == value
         | 
| 20 | 
            +
            	  return Struct.new(:id, :name, :email).new(id, name, email)
         | 
| 21 | 
            +
                    end 
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
                  nil
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                def self.availids
         | 
| 27 | 
            +
                  return @availids if @availids
         | 
| 28 | 
            +
                  availids = []
         | 
| 29 | 
            +
                  ICLA.new.each {|id, name, email| availids << id unless id == 'notinavail'}
         | 
| 30 | 
            +
                  @availids = availids
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                def each(&block)
         | 
| 34 | 
            +
                  officers = ASF::SVN['private/foundation/officers']
         | 
| 35 | 
            +
                  iclas = File.read("#{officers}/iclas.txt")
         | 
| 36 | 
            +
                  iclas.scan(/^([-\w]+):.*?:(.*?):(.*?):/).each(&block)
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
              end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
              class Person
         | 
| 41 | 
            +
                def icla
         | 
| 42 | 
            +
                  @icla ||= ASF::ICLA.find_by_id(name)
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                def icla?
         | 
| 46 | 
            +
                  ICLA.availids.include? name
         | 
| 47 | 
            +
                end
         | 
| 48 | 
            +
              end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
              # Search archive for historical records of people who were committers
         | 
| 51 | 
            +
              # but never submitted an ICLA (some of which are still ASF members or
         | 
| 52 | 
            +
              # members of a PMC).
         | 
| 53 | 
            +
              def self.search_archive_by_id(value)
         | 
| 54 | 
            +
                require 'net/http'
         | 
| 55 | 
            +
                require 'nokogiri'
         | 
| 56 | 
            +
                historical_committers = 'http://people.apache.org/~rubys/committers.html'
         | 
| 57 | 
            +
                doc = Nokogiri::HTML(Net::HTTP.get(URI.parse(historical_committers)))
         | 
| 58 | 
            +
                doc.search('tr').each do |tr|
         | 
| 59 | 
            +
                  tds = tr.search('td')
         | 
| 60 | 
            +
                  next unless tds.length == 3
         | 
| 61 | 
            +
                  return tds[1].text if tds[0].text == value
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
                nil
         | 
| 64 | 
            +
              rescue
         | 
| 65 | 
            +
                nil
         | 
| 66 | 
            +
              end
         | 
| 67 | 
            +
            end
         | 
| @@ -0,0 +1,232 @@ | |
| 1 | 
            +
            require 'wunderbar'
         | 
| 2 | 
            +
            require 'rubygems'
         | 
| 3 | 
            +
            require 'ldap'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module ASF
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              # determine whether or not the LDAP API can be used
         | 
| 8 | 
            +
              def self.init_ldap
         | 
| 9 | 
            +
                @ldap = nil
         | 
| 10 | 
            +
                begin
         | 
| 11 | 
            +
                  conf = '/etc/ldap/ldap.conf'
         | 
| 12 | 
            +
                  host = File.read(conf).scan(/^uri\s+ldaps:\/\/(\S+?):(\d+)/i).first
         | 
| 13 | 
            +
                  if host
         | 
| 14 | 
            +
                     Wunderbar.info "Connecting to LDAP server: [ldaps://#{host[0]}:#{host[1]}]"
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
                rescue Errno::ENOENT
         | 
| 17 | 
            +
                  host = nil
         | 
| 18 | 
            +
                  Wunderbar.error "No LDAP server defined, there must be a LDAP ldaps:// URI in /etc/ldap/ldap.conf"
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
                begin
         | 
| 21 | 
            +
                  @ldap = LDAP::SSLConn.new(host.first, host.last.to_i) if host
         | 
| 22 | 
            +
                rescue LDAP::ResultError=>re
         | 
| 23 | 
            +
                  Wunderbar.error "Error binding to LDAP server: message: ["+ re.message + "]"
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
              end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
              # search with a scope of one
         | 
| 28 | 
            +
              def self.search_one(base, filter, attrs=nil)
         | 
| 29 | 
            +
                init_ldap unless defined? @ldap
         | 
| 30 | 
            +
                return [] unless @ldap
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                Wunderbar.info "ldapsearch -x -LLL -b #{base} -s one #{filter} " +
         | 
| 33 | 
            +
                  "#{[attrs].flatten.join(' ')}"
         | 
| 34 | 
            +
                
         | 
| 35 | 
            +
                begin
         | 
| 36 | 
            +
                  result = @ldap.search2(base, LDAP::LDAP_SCOPE_ONELEVEL, filter, attrs)
         | 
| 37 | 
            +
                rescue
         | 
| 38 | 
            +
                  result = []
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                result.map! {|hash| hash[attrs]} if String === attrs
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                result
         | 
| 44 | 
            +
              end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
              def self.pmc_chairs
         | 
| 47 | 
            +
                @pmc_chairs ||= Service.find('pmc-chairs').members
         | 
| 48 | 
            +
              end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
              def self.committers
         | 
| 51 | 
            +
                @committers ||= Group.find('committers').members
         | 
| 52 | 
            +
              end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
              def self.members
         | 
| 55 | 
            +
                @members ||= Group.find('member').members
         | 
| 56 | 
            +
              end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
              class Base
         | 
| 59 | 
            +
                attr_reader :name
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                def self.base
         | 
| 62 | 
            +
                  @base
         | 
| 63 | 
            +
                end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                def base
         | 
| 66 | 
            +
                  self.class.base
         | 
| 67 | 
            +
                end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                def self.collection
         | 
| 70 | 
            +
                  @collection ||= Hash.new
         | 
| 71 | 
            +
                end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                def self.[] name
         | 
| 74 | 
            +
                  collection[name] || new(name)
         | 
| 75 | 
            +
                end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                def self.find name
         | 
| 78 | 
            +
                  collection[name] || new(name)
         | 
| 79 | 
            +
                end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                def self.new name
         | 
| 82 | 
            +
                  collection[name] || super
         | 
| 83 | 
            +
                end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                def initialize name
         | 
| 86 | 
            +
                  self.class.collection[name] = self
         | 
| 87 | 
            +
                  @name = name
         | 
| 88 | 
            +
                end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                unless Object.respond_to? :id
         | 
| 91 | 
            +
                  def id
         | 
| 92 | 
            +
                    @name
         | 
| 93 | 
            +
                  end
         | 
| 94 | 
            +
                end
         | 
| 95 | 
            +
              end
         | 
| 96 | 
            +
             | 
| 97 | 
            +
              class LazyHash < Hash
         | 
| 98 | 
            +
                def initialize(&initializer)
         | 
| 99 | 
            +
                  @initializer = initializer
         | 
| 100 | 
            +
                end
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                def load
         | 
| 103 | 
            +
                 return unless @initializer
         | 
| 104 | 
            +
                 merge! @initializer.call || {}
         | 
| 105 | 
            +
                 @initializer = super
         | 
| 106 | 
            +
                end
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                def [](key)
         | 
| 109 | 
            +
                  result = super
         | 
| 110 | 
            +
                  if not result and not keys.include? key and @initializer
         | 
| 111 | 
            +
                    merge! @initializer.call || {}
         | 
| 112 | 
            +
                    @initializer = nil
         | 
| 113 | 
            +
                    result = super
         | 
| 114 | 
            +
                  end
         | 
| 115 | 
            +
                  result
         | 
| 116 | 
            +
                end
         | 
| 117 | 
            +
              end
         | 
| 118 | 
            +
             | 
| 119 | 
            +
              class Person < Base
         | 
| 120 | 
            +
                @base = 'ou=people,dc=apache,dc=org'
         | 
| 121 | 
            +
             | 
| 122 | 
            +
                def self.list(filter='uid=*')
         | 
| 123 | 
            +
                  ASF.search_one(base, filter, 'uid').flatten.map {|uid| find(uid)}
         | 
| 124 | 
            +
                end
         | 
| 125 | 
            +
             | 
| 126 | 
            +
                # pre-fetch a given attribute, for a given list of people
         | 
| 127 | 
            +
                def self.preload(attributes, people={})
         | 
| 128 | 
            +
                  attributes = [attributes].flatten
         | 
| 129 | 
            +
             | 
| 130 | 
            +
                  if people.empty?
         | 
| 131 | 
            +
                    filter = "(|#{attributes.map {|attribute| "(#{attribute}=*)"}.join})"
         | 
| 132 | 
            +
                  else
         | 
| 133 | 
            +
                    filter = "(|#{people.map {|person| "(uid=#{person.name})"}.join})"
         | 
| 134 | 
            +
                  end
         | 
| 135 | 
            +
                  
         | 
| 136 | 
            +
                  zero = Hash[attributes.map {|attribute| [attribute,nil]}]
         | 
| 137 | 
            +
             | 
| 138 | 
            +
                  data = ASF.search_one(base, filter, attributes + ['uid'])
         | 
| 139 | 
            +
                  data = Hash[data.map! {|hash| [find(hash['uid'].first), hash]}]
         | 
| 140 | 
            +
                  data.each {|person, hash| person.attrs.merge!(zero.merge(hash))}
         | 
| 141 | 
            +
             | 
| 142 | 
            +
                  if people.empty?
         | 
| 143 | 
            +
                    (collection.values - data.keys).each do |person| 
         | 
| 144 | 
            +
                      person.attrs.merge! zero
         | 
| 145 | 
            +
                    end
         | 
| 146 | 
            +
                  end
         | 
| 147 | 
            +
                end
         | 
| 148 | 
            +
             | 
| 149 | 
            +
                def attrs
         | 
| 150 | 
            +
                  @attrs ||= LazyHash.new {ASF.search_one(base, "uid=#{name}").first}
         | 
| 151 | 
            +
                end
         | 
| 152 | 
            +
             | 
| 153 | 
            +
                def public_name
         | 
| 154 | 
            +
                  cn = [attrs['cn']].flatten.first
         | 
| 155 | 
            +
                  cn.force_encoding('utf-8') if cn.respond_to? :force_encoding
         | 
| 156 | 
            +
                  return cn if cn
         | 
| 157 | 
            +
                  return icla.name if icla
         | 
| 158 | 
            +
                  ASF.search_archive_by_id(name)
         | 
| 159 | 
            +
                end
         | 
| 160 | 
            +
             | 
| 161 | 
            +
                def asf_member?
         | 
| 162 | 
            +
                  ASF::Member.status[name] or ASF.members.include? self
         | 
| 163 | 
            +
                end
         | 
| 164 | 
            +
             | 
| 165 | 
            +
                def banned?
         | 
| 166 | 
            +
                  not attrs['loginShell'] or attrs['loginShell'].include? "/usr/bin/false"
         | 
| 167 | 
            +
                end
         | 
| 168 | 
            +
             | 
| 169 | 
            +
                def mail
         | 
| 170 | 
            +
                  attrs['mail'] || []
         | 
| 171 | 
            +
                end
         | 
| 172 | 
            +
             | 
| 173 | 
            +
                def alt_email
         | 
| 174 | 
            +
                  attrs['asf-altEmail'] || []
         | 
| 175 | 
            +
                end
         | 
| 176 | 
            +
             | 
| 177 | 
            +
                def pgp_key_fingerprints
         | 
| 178 | 
            +
                  attrs['asf-pgpKeyFingerprint']
         | 
| 179 | 
            +
                end
         | 
| 180 | 
            +
             | 
| 181 | 
            +
                def urls
         | 
| 182 | 
            +
                  attrs['asf-personalURL'] || []
         | 
| 183 | 
            +
                end
         | 
| 184 | 
            +
             | 
| 185 | 
            +
                def committees
         | 
| 186 | 
            +
                  Committee.list("member=uid=#{name},#{base}")
         | 
| 187 | 
            +
                end
         | 
| 188 | 
            +
             | 
| 189 | 
            +
                def groups
         | 
| 190 | 
            +
                  Group.list("memberUid=#{name}")
         | 
| 191 | 
            +
                end
         | 
| 192 | 
            +
              end
         | 
| 193 | 
            +
             | 
| 194 | 
            +
              class Group < Base
         | 
| 195 | 
            +
                @base = 'ou=groups,dc=apache,dc=org'
         | 
| 196 | 
            +
             | 
| 197 | 
            +
                def self.list(filter='cn=*')
         | 
| 198 | 
            +
                  ASF.search_one(base, filter, 'cn').flatten.map {|cn| find(cn)}
         | 
| 199 | 
            +
                end
         | 
| 200 | 
            +
             | 
| 201 | 
            +
                def members
         | 
| 202 | 
            +
                  ASF.search_one(base, "cn=#{name}", 'memberUid').flatten.
         | 
| 203 | 
            +
                    map {|uid| Person.find(uid)}
         | 
| 204 | 
            +
                end
         | 
| 205 | 
            +
              end
         | 
| 206 | 
            +
             | 
| 207 | 
            +
              class Committee < Base
         | 
| 208 | 
            +
                @base = 'ou=pmc,ou=committees,ou=groups,dc=apache,dc=org'
         | 
| 209 | 
            +
             | 
| 210 | 
            +
                def self.list(filter='cn=*')
         | 
| 211 | 
            +
                  ASF.search_one(base, filter, 'cn').flatten.map {|cn| Committee.find(cn)}
         | 
| 212 | 
            +
                end
         | 
| 213 | 
            +
             | 
| 214 | 
            +
                def members
         | 
| 215 | 
            +
                  ASF.search_one(base, "cn=#{name}", 'member').flatten.
         | 
| 216 | 
            +
                    map {|uid| Person.find uid[/uid=(.*?),/,1]}
         | 
| 217 | 
            +
                end
         | 
| 218 | 
            +
              end
         | 
| 219 | 
            +
             | 
| 220 | 
            +
              class Service < Base
         | 
| 221 | 
            +
                @base = 'ou=groups,ou=services,dc=apache,dc=org'
         | 
| 222 | 
            +
             | 
| 223 | 
            +
                def self.list(filter='cn=*')
         | 
| 224 | 
            +
                  ASF.search_one(base, filter, 'cn').flatten
         | 
| 225 | 
            +
                end
         | 
| 226 | 
            +
             | 
| 227 | 
            +
                def members
         | 
| 228 | 
            +
                  ASF.search_one(base, "cn=#{name}", 'member').flatten.
         | 
| 229 | 
            +
                    map {|uid| Person.find uid[/uid=(.*?),/,1]}
         | 
| 230 | 
            +
                end
         | 
| 231 | 
            +
              end
         | 
| 232 | 
            +
            end
         |