whimsy-asf 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,81 @@
1
+
2
+ module ASF
3
+
4
+ class Mail
5
+ def self.list
6
+ return @list if @list
7
+
8
+ list = Hash.new
9
+
10
+ # load info from LDAP
11
+ ASF::Person.preload(['mail', 'asf-altEmail'])
12
+ ASF::Person.collection.each do |name, person|
13
+ (person.mail+person.alt_email).each do |mail|
14
+ list[mail.downcase] = person
15
+ end
16
+ end
17
+
18
+ # load all member emails in one pass
19
+ ASF::Member.each do |id, text|
20
+ Member.emails(text).each {|mail| list[mail.downcase] ||= Person[id]}
21
+ end
22
+
23
+ # load all ICLA emails in one pass
24
+ ASF::ICLA.new.each do |id, name, email|
25
+ list[email.downcase] ||= Person.find(id)
26
+ next if id == 'notinavail'
27
+ list["#{id.downcase}@apache.org"] ||= Person.find(id)
28
+ end
29
+
30
+ @list = list
31
+ end
32
+
33
+ def self.lists(public_private= false)
34
+ apmail_bin = ASF::SVN['infra/infrastructure/apmail/trunk/bin']
35
+ file = File.join(apmail_bin, '.archives')
36
+ if not @lists or File.mtime(file) != @list_mtime
37
+ @list_mtime = File.mtime(file)
38
+ @lists = Hash[File.read(file).scan(
39
+ /^\s+"(\w[-\w]+)", "\/home\/apmail\/(public|private)-arch\//
40
+ )]
41
+ end
42
+
43
+ public_private ? @lists : @lists.keys
44
+ end
45
+ end
46
+
47
+ class Person < Base
48
+ def self.find_by_email(value)
49
+ value.downcase!
50
+
51
+ person = Mail.list[value]
52
+ return person if person
53
+ end
54
+
55
+ def obsolete_emails
56
+ return @obsolete_emails if @obsolete_emails
57
+ result = []
58
+ if icla
59
+ unless active_emails.any? {|mail| mail.downcase == icla.email.downcase}
60
+ result << icla.email
61
+ end
62
+ end
63
+ @obsolete_emails = result
64
+ end
65
+
66
+ def active_emails
67
+ (mail + alt_email + member_emails).uniq
68
+ end
69
+
70
+ def all_mail
71
+ active_emails + obsolete_emails
72
+ end
73
+ end
74
+
75
+ class Committee
76
+ def mail_list
77
+ return 'hc' if name == 'httpcomponents'
78
+ name
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,82 @@
1
+ module ASF
2
+ class Member
3
+ include Enumerable
4
+ attr_accessor :full
5
+
6
+ def self.find_text_by_id(value)
7
+ new.each do |id, text|
8
+ return text if id==value
9
+ end
10
+ nil
11
+ end
12
+
13
+ def self.each(&block)
14
+ new.each(&block)
15
+ end
16
+
17
+ def self.list
18
+ result = Hash[self.new(true).map {|name, text| [name, {text: text}]}]
19
+ self.status.each do |name, value|
20
+ result[name]['status'] = value
21
+ end
22
+ result
23
+ end
24
+
25
+ def self.find_by_email(value)
26
+ value = value.downcase
27
+ each do |id, text|
28
+ emails(text).each do |email|
29
+ return Person[id] if email.downcase == value
30
+ end
31
+ end
32
+ nil
33
+ end
34
+
35
+ def self.status
36
+ return @status if @status
37
+ status = {}
38
+ foundation = ASF::SVN['private/foundation']
39
+ sections = File.read("#{foundation}/members.txt").split(/(.*\n===+)/)
40
+ sections.shift(3)
41
+ sections.each_slice(2) do |header, text|
42
+ header.sub!(/s\n=+/,'')
43
+ text.scan(/Avail ID: (.*)/).flatten.each {|id| status[id] = header}
44
+ end
45
+ @status = status
46
+ end
47
+
48
+ def initialize(full = nil)
49
+ @full = (full.nil? ? ASF::Person.new($USER).asf_member? : full)
50
+ end
51
+
52
+ def each
53
+ foundation = ASF::SVN['private/foundation']
54
+ File.read("#{foundation}/members.txt").split(/^ \*\) /).each do |section|
55
+ id = section[/Avail ID: (.*)/,1]
56
+ section = '' unless @full
57
+ yield id, section.sub(/\n.*\n===+\s*?\n(.*\n)+.*/,'').strip if id
58
+ end
59
+ nil
60
+ end
61
+
62
+ def self.find(id)
63
+ each {|availid| return true if availid == id}
64
+ return false
65
+ end
66
+
67
+ def self.emails(text)
68
+ emails = text.to_s.scan(/Email: (.*(?:\n\s+\S+@.*)*)/).flatten.
69
+ join(' ').split(/\s+/).grep(/@/)
70
+ end
71
+ end
72
+
73
+ class Person
74
+ def members_txt
75
+ @members_txt ||= ASF::Member.find_text_by_id(name)
76
+ end
77
+
78
+ def member_emails
79
+ ASF::Member.emails(members_txt)
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,32 @@
1
+ module ASF
2
+
3
+ class Person < Base
4
+
5
+ def self.member_nominees
6
+ return @member_nominees if @member_nominees
7
+
8
+ meetings = ASF::SVN['private/foundation/Meetings']
9
+ nominations = Dir["#{meetings}/*/nominated-members.txt"].sort.last.untaint
10
+
11
+ nominations = File.read(nominations).split(/^\s*---+\s*/)
12
+ nominations.shift(2)
13
+
14
+ nominees = {}
15
+ nominations.each do |nomination|
16
+ id = nomination[/^\s?\w+.*<(\S+)@apache.org>/,1]
17
+ id ||= nomination[/^\s?\w+.*\((\S+)@apache.org\)/,1]
18
+ id ||= nomination[/^\s?\w+.*\(([a-z]+)\)/,1]
19
+
20
+ next unless id
21
+
22
+ nominees[find(id)] = nomination
23
+ end
24
+
25
+ @member_nominees = nominees
26
+ end
27
+
28
+ def member_nomination
29
+ Person.member_nominees[self]
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,23 @@
1
+ require 'nokogiri'
2
+ require_relative '../asf'
3
+
4
+ module ASF
5
+ class Podlings
6
+ include Enumerable
7
+
8
+ def each
9
+ incubator_content = ASF::SVN['asf/incubator/public/trunk/content']
10
+ podlings = Nokogiri::XML(File.read("#{incubator_content}/podlings.xml"))
11
+ podlings.search('podling').map do |node|
12
+ data = {
13
+ name: node['name'],
14
+ status: node['status'],
15
+ description: node.at('description').text,
16
+ mentors: node.search('mentor').map {|mentor| mentor['username']}
17
+ }
18
+ data[:champion] = node.at('champion')['availid'] if node.at('champion')
19
+ yield node['resource'], data
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,81 @@
1
+ require_relative '../asf.rb'
2
+ require 'rack'
3
+ require 'etc'
4
+
5
+ module ASF
6
+ module Auth
7
+ DIRECTORS = {
8
+ 'rbowen' => 'rb',
9
+ 'cutting' => 'dc',
10
+ 'bdelacretaz' => 'bd',
11
+ 'rgardler' => 'rg',
12
+ 'jim' => 'jj',
13
+ 'mattmann' => 'cm',
14
+ 'brett' => 'bp',
15
+ 'rubys' => 'sr',
16
+ 'gstein' => 'gs'
17
+ }
18
+
19
+ # Simply 'use' the following class in config.ru to limit access
20
+ # to the application to ASF members and officers and the EA.
21
+ class MembersAndOfficers < Rack::Auth::Basic
22
+ def initialize(app)
23
+ super(app, "ASF Members and Officers", &proc {})
24
+ end
25
+
26
+ def call(env)
27
+ authorized = false
28
+
29
+ user = env['REMOTE_USER'] ||= ENV['USER'] || Etc.getpwuid.name
30
+ person = ASF::Person.new(user)
31
+
32
+ authorized ||= DIRECTORS[user]
33
+ authorized ||= person.asf_member?
34
+ authorized ||= ASF.pmc_chairs.include? person
35
+ authorized ||= (user == 'ea')
36
+
37
+ if authorized
38
+ class << env; attr_accessor :user, :password; end
39
+ if env['HTTP_AUTHORIZATION']
40
+ require 'base64'
41
+ env.user, env.password = Base64.decode64(env['HTTP_AUTHORIZATION'][
42
+ /^Basic ([A-Za-z0-9+\/=]+)$/,1]).split(':',2)
43
+ else
44
+ env.user = user
45
+ end
46
+
47
+ @app.call(env)
48
+ else
49
+ unauthorized
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ # Apache httpd on whimsy-vm is behind a proxy that converts https
56
+ # requests into http requests. Update the environment variables to
57
+ # match.
58
+ class HTTPS_workarounds
59
+ def initialize(app)
60
+ @app = app
61
+ end
62
+
63
+ def call(env)
64
+ if env['HTTPS'] == 'on'
65
+ env['SCRIPT_URI'].sub!(/^http:/, 'https:')
66
+ env['SERVER_PORT'] = '443'
67
+
68
+ # for reasons I don't understand, Passenger on whimsy doesn't
69
+ # forward root directory requests directly, so as a workaround
70
+ # these requests are rewritten and the following code maps
71
+ # the requests back:
72
+ if env['PATH_INFO'] == '/index.html'
73
+ env['PATH_INFO'] = '/'
74
+ env['SCRIPT_URI'] += '/'
75
+ end
76
+ end
77
+ return @app.call(env)
78
+ end
79
+ end
80
+
81
+ end
@@ -0,0 +1,24 @@
1
+ require_relative '../asf'
2
+
3
+ module ASF
4
+
5
+ class Site
6
+ @@list = {}
7
+
8
+ def self.list
9
+ Committee.load_committee_info
10
+ templates = ASF::SVN['asf/infrastructure/site/trunk/templates']
11
+ file = "#{templates}/blocks/projects.mdtext"
12
+ return @@list if not @@list.empty? and File.mtime(file) == @@mtime
13
+ @@mtime = File.mtime(file)
14
+
15
+ projects = File.read(file)
16
+ projects.scan(/\[(.*?)\]\((http.*?) "(.*)"\)/).each do |name, link, text|
17
+ @@list[Committee.find(name).name] = {link: link, text: text}
18
+ end
19
+
20
+ @@list
21
+ end
22
+ end
23
+
24
+ end
@@ -0,0 +1,27 @@
1
+ require 'uri'
2
+ require 'thread'
3
+
4
+ module ASF
5
+
6
+ class SVN
7
+ @base = URI.parse('https://svn.apache.org/repos/')
8
+ @mock = 'file:///var/tools/svnrep/'
9
+ @semaphore = Mutex.new
10
+
11
+ def self.repos
12
+ @semaphore.synchronize do
13
+ @repos ||= Hash[Dir['/home/whimsysvn/svn/*'].map { |name|
14
+ Dir.chdir name.untaint do
15
+ [`svn info`[/URL: (.*)/,1].sub(/^http:/,'https:'), Dir.pwd.untaint]
16
+ end
17
+ }]
18
+ end
19
+ end
20
+
21
+ def self.[](name)
22
+ repos[(@mock+name.sub('private/','')).to_s.sub(/\/*$/, '')] ||
23
+ repos[(@base+name).to_s.sub(/\/*$/, '')] # lose trailing slash
24
+ end
25
+ end
26
+
27
+ end
@@ -0,0 +1,41 @@
1
+ module ASF
2
+
3
+ class Person < Base
4
+
5
+ def self.member_watch_list
6
+ return @member_watch_list if @member_watch_list
7
+
8
+ foundation = ASF::SVN['private/foundation']
9
+ text = File.read "#{foundation}/potential-member-watch-list.txt"
10
+
11
+ nominations = text.scan(/^\s+\*\)\s+\w.*?\n\s*(?:---|\Z)/m)
12
+
13
+ i = 0
14
+ member_watch_list = {}
15
+ nominations.each do |nomination|
16
+ id = nil
17
+ name = nomination[/\*\)\s+(.+?)\s+(\(|\<|$)/,1]
18
+ id ||= nomination[/\*\)\s.+?\s\((.*?)\)/,1]
19
+ id ||= nomination[/\*\)\s.+?\s<(.*?)@apache.org>/,1]
20
+
21
+ unless id
22
+ id = "notinavail_#{i+=1}"
23
+ find(id).attrs['cn'] = name
24
+ end
25
+
26
+ member_watch_list[find(id)] = nomination
27
+ end
28
+
29
+ @member_watch_list = member_watch_list
30
+ end
31
+
32
+ def member_watch
33
+ text = Person.member_watch_list[self]
34
+ if text
35
+ text.sub!(/\A\s*\n/,'')
36
+ text.sub!(/\n---\Z/,'')
37
+ end
38
+ text
39
+ end
40
+ end
41
+ end
metadata ADDED
@@ -0,0 +1,149 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: whimsy-asf
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Sam Ruby
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-09-22 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: nokogiri
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rack
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: ruby-ldap
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: tzinfo
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: wunderbar
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :runtime
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ description: ! " This package contains a set of classes which encapsulate access\n
95
+ \ to a number of data sources such as LDAP, ICLAs, auth lists, etc.\n"
96
+ email: rubys@intertwingly.net
97
+ executables: []
98
+ extensions: []
99
+ extra_rdoc_files: []
100
+ files:
101
+ - asf.gemspec
102
+ - asf.version
103
+ - lib/whimsy/asf.rb
104
+ - lib/whimsy/asf/member.rb
105
+ - lib/whimsy/asf/ldap.rb
106
+ - lib/whimsy/asf/mail.rb
107
+ - lib/whimsy/asf/icla.rb
108
+ - lib/whimsy/asf/svn.rb
109
+ - lib/whimsy/asf/podlings.rb
110
+ - lib/whimsy/asf/site.rb
111
+ - lib/whimsy/asf/auth.rb
112
+ - lib/whimsy/asf/nominees.rb
113
+ - lib/whimsy/asf/agenda/back.rb
114
+ - lib/whimsy/asf/agenda/front.rb
115
+ - lib/whimsy/asf/agenda/special.rb
116
+ - lib/whimsy/asf/agenda/attachments.rb
117
+ - lib/whimsy/asf/agenda/exec-officer.rb
118
+ - lib/whimsy/asf/agenda/minutes.rb
119
+ - lib/whimsy/asf/agenda/committee.rb
120
+ - lib/whimsy/asf/agenda.rb
121
+ - lib/whimsy/asf/rack.rb
122
+ - lib/whimsy/asf/committee.rb
123
+ - lib/whimsy/asf/watch.rb
124
+ homepage: https://whimsy.apache.org/
125
+ licenses:
126
+ - Apache License, Version 2.0
127
+ post_install_message:
128
+ rdoc_options: []
129
+ require_paths:
130
+ - lib
131
+ required_ruby_version: !ruby/object:Gem::Requirement
132
+ none: false
133
+ requirements:
134
+ - - ! '>='
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
137
+ required_rubygems_version: !ruby/object:Gem::Requirement
138
+ none: false
139
+ requirements:
140
+ - - ! '>='
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
143
+ requirements: []
144
+ rubyforge_project:
145
+ rubygems_version: 1.8.23
146
+ signing_key:
147
+ specification_version: 3
148
+ summary: Whimsy 'model' of the ASF
149
+ test_files: []