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,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: []
|