whimsy-asf 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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,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: []
|