rubyforge 0.0.0 → 1.0.4

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.
@@ -0,0 +1,145 @@
1
+ require 'webrick/cookie'
2
+ require 'net/http'
3
+ require 'net/https'
4
+ require 'rubyforge/cookie_manager'
5
+
6
+ # clean up warnings caused by web servers that send down 2 digit years
7
+ class Time
8
+ class << self
9
+ alias :old_utc :utc
10
+
11
+ def utc(*args)
12
+ century = Time.now.year / 100 * 100
13
+ args[0] += century if args[0] < 100
14
+ old_utc(*args)
15
+ end
16
+ end
17
+ end unless Time.respond_to? :old_utc
18
+
19
+ # clean up "using default DH parameters" warning for https
20
+ class Net::HTTP
21
+ alias :old_use_ssl= :use_ssl=
22
+ def use_ssl= flag
23
+ self.old_use_ssl = flag
24
+ @ssl_context.tmp_dh_callback = proc {} if @ssl_context
25
+ end
26
+ end unless Net::HTTP.public_instance_methods.include? "old_use_ssl="
27
+
28
+ class RubyForge
29
+ class Client
30
+ attr_accessor :debug_dev, :ssl_verify_mode, :agent_class
31
+
32
+ def initialize(proxy = nil)
33
+ @debug_dev = nil
34
+ @ssl_verify_mode = OpenSSL::SSL::VERIFY_NONE
35
+ @cookie_manager = CookieManager.new
36
+ if proxy
37
+ begin
38
+ proxy_uri = URI.parse(proxy)
39
+ @agent_class = Net::HTTP::Proxy(proxy_uri.host,proxy_uri.port)
40
+ rescue URI::InvalidURIError
41
+ end
42
+ end
43
+ @agent_class ||= Net::HTTP
44
+ end
45
+
46
+ def cookie_store
47
+ @cookie_manager
48
+ end
49
+
50
+ def cookie_store=(path)
51
+ @cookie_manager = CookieManager.load(path)
52
+ end
53
+
54
+ def post_content(uri, form = {}, headers = {})
55
+ uri = URI.parse(uri) unless uri.is_a?(URI)
56
+ request = agent_class::Post.new(uri.request_uri)
57
+ execute(request, uri, form, headers)
58
+ end
59
+
60
+ def get_content(uri, query = {}, headers = {})
61
+ uri = URI.parse(uri) unless uri.is_a?(URI)
62
+ request = agent_class::Get.new(uri.request_uri)
63
+ execute(request, uri, query, headers)
64
+ end
65
+
66
+ def execute(request, uri, parameters = {}, headers = {})
67
+ {
68
+ 'content-type' => 'application/x-www-form-urlencoded'
69
+ }.merge(headers).each { |k,v| request[k] = v }
70
+
71
+ @cookie_manager[uri].each { |k,v|
72
+ request['Cookie'] = v.to_s
73
+ } if @cookie_manager[uri]
74
+
75
+ http = agent_class.new( uri.host, uri.port )
76
+
77
+ if uri.scheme == 'https'
78
+ http.use_ssl = true
79
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
80
+ end
81
+
82
+ request_data = case request['Content-Type']
83
+ when /boundary=(.*)$/
84
+ boundary_data_for($1, parameters)
85
+ else
86
+ query_string_for(parameters)
87
+ end
88
+ request['Content-Length'] = request_data.length.to_s
89
+
90
+ response = http.request(request, request_data)
91
+ (response.get_fields('Set-Cookie') || []).each do |raw_cookie|
92
+ WEBrick::Cookie.parse_set_cookies(raw_cookie).each { |baked_cookie|
93
+ baked_cookie.domain ||= url.host
94
+ baked_cookie.path ||= url.path
95
+ @cookie_manager.add(uri, baked_cookie)
96
+ }
97
+ end
98
+
99
+ return response.body if response.class <= Net::HTTPSuccess
100
+
101
+ if response.class <= Net::HTTPRedirection
102
+ location = response['Location']
103
+ unless location =~ /^http/
104
+ location = "#{uri.scheme}://#{uri.host}#{location}"
105
+ end
106
+ uri = URI.parse(location)
107
+
108
+ execute(agent_class::Get.new(uri.request_uri), uri)
109
+ end
110
+ end
111
+
112
+ def boundary_data_for(boundary, parameters)
113
+ parameters.sort_by {|k,v| k.to_s }.map { |k,v|
114
+ parameter = "--#{boundary}\r\nContent-Disposition: form-data; name=\"" +
115
+ WEBrick::HTTPUtils.escape_form(k.to_s) + "\""
116
+
117
+ if v.respond_to? :path
118
+ parameter += "; filename=\"#{File.basename(v.path)}\"\r\n"
119
+ parameter += "Content-Transfer-Encoding: binary\r\n"
120
+ parameter += "Content-Type: text/plain"
121
+ end
122
+ parameter += "\r\n\r\n"
123
+
124
+ if v.respond_to? :path
125
+ parameter += v.read
126
+ else
127
+ parameter += v.to_s
128
+ end
129
+
130
+ parameter
131
+ }.join("\r\n") + "\r\n--#{boundary}--\r\n"
132
+ end
133
+
134
+ def query_string_for(parameters)
135
+ parameters.sort_by {|k,v| k.to_s }.map { |k,v|
136
+ k && [ WEBrick::HTTPUtils.escape_form(k.to_s),
137
+ WEBrick::HTTPUtils.escape_form(v.to_s) ].join('=')
138
+ }.compact.join('&')
139
+ end
140
+
141
+ def save_cookie_store
142
+ @cookie_manager.save!
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,60 @@
1
+ require 'yaml'
2
+
3
+ class RubyForge
4
+ class CookieManager
5
+ class << self
6
+ def load(path)
7
+ cm = YAML.load_file(path) rescue CookieManager.new(path)
8
+ cm = CookieManager.new(path) unless cm.is_a?(CookieManager)
9
+ cm.clean_stale_cookies
10
+ end
11
+ end
12
+
13
+ attr_accessor :cookies_file
14
+ def initialize(cookies_file = nil)
15
+ @jar = Hash.new { |hash,domain_name|
16
+ hash[domain_name.downcase] = {}
17
+ }
18
+ @cookies_file = cookies_file
19
+ end
20
+
21
+ def [](uri)
22
+ # FIXME we need to do more matching on hostname.... This is not
23
+ # bulletproof
24
+ uri = (URI === uri ? uri.host : uri).downcase
25
+ @jar[uri] ||= {}
26
+ end
27
+
28
+ def clear uri
29
+ self[uri].clear
30
+ self.save!
31
+ end
32
+
33
+ def empty?
34
+ @jar.empty? || @jar.all? { |k,v| v.empty? }
35
+ end
36
+
37
+ def save!
38
+ clean_stale_cookies
39
+ File.open(@cookies_file, 'wb') { |f|
40
+ f.write(YAML.dump(self))
41
+ }
42
+ end
43
+
44
+ def add(uri, cookie)
45
+ no_dot_domain = cookie.domain.gsub(/^\./, '')
46
+ return unless uri.host =~ /#{no_dot_domain}$/i
47
+ @jar[no_dot_domain][cookie.name] = cookie
48
+ clean_stale_cookies
49
+ end
50
+
51
+ def clean_stale_cookies
52
+ @jar.each do |domain, cookies|
53
+ cookies.each do |name, cookie|
54
+ cookies.delete(name) if cookie.expires < Time.now
55
+ end
56
+ end
57
+ self
58
+ end
59
+ end
60
+ end
data/lib/rubyforge.rb ADDED
@@ -0,0 +1,493 @@
1
+ #! /usr/bin/env ruby -w
2
+
3
+ require 'enumerator'
4
+ require 'fileutils'
5
+ require 'yaml'
6
+ require 'open-uri'
7
+ require 'rubyforge/client'
8
+
9
+ $TESTING = false unless defined? $TESTING
10
+
11
+ class RubyForge
12
+
13
+ # :stopdoc:
14
+ VERSION = '1.0.4'
15
+ HOME = ENV["HOME"] || ENV["HOMEPATH"] || File::expand_path("~")
16
+ RUBYFORGE_D = File::join HOME, ".rubyforge"
17
+ CONFIG_F = File::join RUBYFORGE_D, "user-config.yml"
18
+ COOKIE_F = File::join RUBYFORGE_D, "cookie.dat"
19
+
20
+ # We must use __FILE__ instead of DATA because this is now a library
21
+ # and DATA is relative to $0, not __FILE__.
22
+ config = File.read(__FILE__).split(/__END__/).last.gsub(/#\{(.*)\}/) {eval $1}
23
+ CONFIG = YAML.load(config)
24
+ # :startdoc:
25
+
26
+ # TODO: add an autoconfig method that is self-repairing, removing key checks
27
+ attr_reader :userconfig, :autoconfig
28
+
29
+ def initialize(userconfig=nil, autoconfig=nil, opts=nil)
30
+ # def initialize(userconfig=CONFIG_F, opts={})
31
+ @userconfig, @autoconfig = userconfig, autoconfig
32
+
33
+ @autoconfig ||= CONFIG["rubyforge"].dup
34
+ @userconfig.merge! opts if opts
35
+
36
+ @client = nil
37
+ @uri = nil
38
+ end
39
+
40
+ def configure opts = {}
41
+ user_path = CONFIG_F
42
+ dir, file = File.split(user_path)
43
+
44
+ @userconfig = if test(?e, user_path) then
45
+ YAML.load_file(user_path)
46
+ else
47
+ CONFIG
48
+ end.merge(opts)
49
+ @autoconfig_path = File.join(dir, file.sub(/^user/, 'auto'))
50
+ @autoconfig = if test(?e, @autoconfig_path) then
51
+ YAML.load_file(@autoconfig_path)
52
+ else
53
+ CONFIG["rubyforge"].dup
54
+ end
55
+ @autoconfig["type_ids"] = CONFIG['rubyforge']['type_ids'].dup
56
+
57
+ raise "no <username>" unless @userconfig["username"]
58
+ raise "no <password>" unless @userconfig["password"]
59
+ raise "no <cookie_jar>" unless @userconfig["cookie_jar"]
60
+
61
+ self
62
+ end
63
+
64
+ def force
65
+ @userconfig['force']
66
+ end
67
+
68
+ def cookie_store
69
+ client.cookie_store
70
+ end
71
+
72
+ def uri
73
+ @uri ||= URI.parse @userconfig['uri']
74
+ end
75
+
76
+ def setup
77
+ FileUtils::mkdir_p RUBYFORGE_D, :mode => 0700 unless test ?d, RUBYFORGE_D
78
+ test ?e, CONFIG_F and FileUtils::mv CONFIG_F, "#{CONFIG_F}.bak"
79
+ config = CONFIG.dup
80
+ config.delete "rubyforge"
81
+
82
+ open(CONFIG_F, "w") { |f|
83
+ f.write YAML.dump(config)
84
+ }
85
+ FileUtils::touch COOKIE_F
86
+ edit = (ENV["EDITOR"] || ENV["EDIT"] || "vi") + " '#{CONFIG_F}'"
87
+ system edit or puts "edit '#{CONFIG_F}'"
88
+ end
89
+
90
+ def save_autoconfig
91
+ File.open(@autoconfig_path, "w") do |file|
92
+ YAML.dump @autoconfig, file
93
+ end
94
+ end
95
+
96
+ def scrape_config
97
+ username = @userconfig['username']
98
+
99
+ %w(group package processor release).each do |type|
100
+ @autoconfig["#{type}_ids"].clear if @autoconfig["#{type}_ids"]
101
+ end
102
+
103
+ puts "Getting #{username}"
104
+ html = URI.parse("http://rubyforge.org/users/#{username}/index.html").read
105
+
106
+ projects = html.scan(%r%/projects/([^/]+)/%).flatten
107
+
108
+ puts "Fetching #{projects.size} projects"
109
+ projects.each do |project|
110
+ next if project == "support"
111
+ scrape_project(project)
112
+ end
113
+ end
114
+
115
+ def scrape_project(project)
116
+ data = {
117
+ "group_ids" => {},
118
+ "package_ids" => {},
119
+ "processor_ids" => Hash.new { |h,k| h[k] = {} },
120
+ "release_ids" => Hash.new { |h,k| h[k] = {} },
121
+ }
122
+
123
+ puts "Updating #{project}"
124
+
125
+ unless data["group_ids"].has_key? project then
126
+ html = URI.parse("http://rubyforge.org/projects/#{project}/index.html").read
127
+ group_id = html[%r/(memberlist.php|frs|tracker|mail)\/?\?group_id=\d+/][%r/\d+/].to_i
128
+
129
+ data["group_ids"][project] = group_id
130
+ end
131
+
132
+ group_id = data["group_ids"][project]
133
+
134
+ html = URI.parse("http://rubyforge.org/frs/?group_id=#{group_id}").read
135
+
136
+ package = nil
137
+ html.scan(/<h3>[^<]+|release_id=\d+">[^>]+|filemodule_id=\d+/).each do |s|
138
+ case s
139
+ when /<h3>([^<]+)/ then
140
+ package = $1.strip
141
+ when /release_id=(\d+)">([^<]+)/ then
142
+ data["release_ids"][package][$2] = $1.to_i
143
+ when /filemodule_id=(\d+)/ then
144
+ data["package_ids"][package] = $1.to_i
145
+ end
146
+ end
147
+
148
+ if not data['release_ids'][package].empty? and
149
+ (@autoconfig['processor_ids'].nil? or
150
+ @autoconfig['processor_ids'].empty?) then
151
+ puts "Fetching processor ids"
152
+
153
+ login
154
+
155
+ html = client.get_content "http://rubyforge.org/frs/admin/qrs.php?package=&group_id=#{group_id}"
156
+
157
+ html =~ /<select name="processor_id">(.*?)<\/select>/m
158
+ processors = $1
159
+ processors.scan(/<option value="(\d{4})">([^<]+)/) do
160
+ data["processor_ids"][$2] = $1.to_i
161
+ end if processors
162
+ end
163
+
164
+ data.each do |key, val|
165
+ @autoconfig[key] ||= {}
166
+ @autoconfig[key].merge! val
167
+ end
168
+
169
+ save_autoconfig
170
+ end
171
+
172
+ def logout
173
+ cookie_store.clear "rubyforge.org"
174
+ end
175
+
176
+ def login
177
+ return if(!force and cookie_store['rubyforge.org']['session_ser']) rescue false
178
+
179
+ page = self.uri + "/account/login.php"
180
+ page.scheme = 'https'
181
+ page = URI.parse page.to_s # set SSL port correctly
182
+
183
+ username = @userconfig["username"]
184
+ password = @userconfig["password"]
185
+
186
+ form = {
187
+ "return_to" => "",
188
+ "form_loginname" => username,
189
+ "form_pw" => password,
190
+ "login" => "Login"
191
+ }
192
+
193
+ response = run page, form
194
+
195
+ re = %r/personal\s+page/iom
196
+ unless response =~ re
197
+ warn("%s:%d: warning: potentially failed login using %s" %
198
+ [__FILE__, __LINE__, username]) unless $TESTING
199
+ end
200
+
201
+ response
202
+ end
203
+
204
+ def create_package(group_id, package_name)
205
+ page = "/frs/admin/index.php"
206
+
207
+ group_id = lookup "group", group_id
208
+ is_private = @userconfig["is_private"]
209
+ is_public = is_private ? 0 : 1
210
+
211
+ form = {
212
+ "func" => "add_package",
213
+ "group_id" => group_id,
214
+ "package_name" => package_name,
215
+ "is_public" => is_public,
216
+ "submit" => "Create This Package",
217
+ }
218
+
219
+ run page, form
220
+
221
+ group_name = @autoconfig["group_ids"].invert[group_id]
222
+ scrape_project(group_name)
223
+ end
224
+
225
+ ##
226
+ # Posts news item to +group_id+ (can be name) with +subject+ and +body+
227
+
228
+ def post_news(group_id, subject, body)
229
+ page = "/news/submit.php"
230
+ group_id = lookup "group", group_id
231
+
232
+ form = {
233
+ "group_id" => group_id,
234
+ "post_changes" => "y",
235
+ "summary" => subject,
236
+ "details" => body,
237
+ "submit" => "Submit",
238
+ }
239
+
240
+ run page, form
241
+ end
242
+
243
+ def delete_package(group_id, package_id)
244
+ page = "/frs/admin/index.php"
245
+
246
+ group_id = lookup "group", group_id
247
+ package_id = lookup "package", package_id
248
+
249
+ form = {
250
+ "func" => "delete_package",
251
+ "group_id" => group_id,
252
+ "package_id" => package_id,
253
+ "sure" => "1",
254
+ "really_sure" => "1",
255
+ "submit" => "Delete",
256
+ }
257
+
258
+ package_name = @autoconfig["package_ids"].invert[package_id]
259
+ @autoconfig["package_ids"].delete package_name
260
+ @autoconfig["release_ids"].delete package_name
261
+ save_autoconfig
262
+
263
+ run page, form
264
+ end
265
+
266
+ def add_release(group_id, package_id, release_name, *files)
267
+ userfile = files.shift
268
+ page = "/frs/admin/qrs.php"
269
+
270
+ group_id = lookup "group", group_id
271
+ package_id = lookup "package", package_id
272
+ userfile = open userfile, 'rb'
273
+ release_date = @userconfig["release_date"]
274
+ type_id = @userconfig["type_id"]
275
+ processor_id = @userconfig["processor_id"]
276
+ release_notes = @userconfig["release_notes"]
277
+ release_changes = @userconfig["release_changes"]
278
+ preformatted = @userconfig["preformatted"]
279
+
280
+ release_date ||= Time.now.strftime("%Y-%m-%d %H:%M")
281
+
282
+ type_id ||= userfile.path[%r|\.[^\./]+$|]
283
+ type_id = (lookup "type", type_id rescue lookup "type", ".oth")
284
+
285
+ processor_id ||= "Any"
286
+ processor_id = lookup "processor", processor_id
287
+
288
+ release_notes = IO::read(release_notes) if
289
+ test(?e, release_notes) if release_notes
290
+
291
+ release_changes = IO::read(release_changes) if
292
+ test(?e, release_changes) if release_changes
293
+
294
+ preformatted = preformatted ? 1 : 0
295
+
296
+ form = {
297
+ "group_id" => group_id,
298
+ "package_id" => package_id,
299
+ "release_name" => release_name,
300
+ "release_date" => release_date,
301
+ "type_id" => type_id,
302
+ "processor_id" => processor_id,
303
+ "release_notes" => release_notes,
304
+ "release_changes" => release_changes,
305
+ "preformatted" => preformatted,
306
+ "userfile" => userfile,
307
+ "submit" => "Release File"
308
+ }
309
+
310
+ boundary = Array::new(8){ "%2.2d" % rand(42) }.join('__')
311
+ boundary = "multipart/form-data; boundary=___#{ boundary }___"
312
+
313
+ html = run(page, form, 'content-type' => boundary)
314
+ raise "Invalid package_id #{package_id}" if html[/Invalid package_id/]
315
+ raise "You have already released this version." if html[/That filename already exists in this project/]
316
+
317
+ release_id = html[/release_id=\d+/][/\d+/].to_i rescue nil
318
+
319
+ unless release_id then
320
+ puts html if $DEBUG
321
+ raise "Couldn't get release_id, upload failed\?"
322
+ end
323
+
324
+ puts "RELEASE ID = #{release_id}" if $DEBUG
325
+
326
+ files.each do |file|
327
+ add_file(group_id, package_id, release_id, file)
328
+ end
329
+
330
+ package_name = @autoconfig["package_ids"].invert[package_id]
331
+ raise "unknown package name for #{package_id}" if package_name.nil?
332
+ @autoconfig["release_ids"][package_name] ||= {}
333
+ @autoconfig["release_ids"][package_name][release_name] = release_id
334
+ save_autoconfig
335
+
336
+ release_id
337
+ end
338
+
339
+ ##
340
+ # add a file to an existing release under the specified group_id,
341
+ # package_id, and release_id
342
+ #
343
+ # example :
344
+ # add_file("codeforpeople", "traits", "0.8.0", "traits-0.8.0.gem")
345
+ # add_file("codeforpeople", "traits", "0.8.0", "traits-0.8.0.tgz")
346
+ # add_file(1024, 1242, "0.8.0", "traits-0.8.0.gem")
347
+
348
+ def add_file(group_name, package_name, release_name, userfile)
349
+ page = '/frs/admin/editrelease.php'
350
+ type_id = @userconfig["type_id"]
351
+ group_id = lookup "group", group_name
352
+ package_id = lookup "package", package_name
353
+ release_id = (Integer === release_name) ? release_name : lookup("release", package_name)[release_name]
354
+ processor_id = @userconfig["processor_id"]
355
+
356
+ page = "/frs/admin/editrelease.php?group_id=#{group_id}&release_id=#{release_id}&package_id=#{package_id}"
357
+
358
+ userfile = open userfile, 'rb'
359
+
360
+ type_id ||= userfile.path[%r|\.[^\./]+$|]
361
+ type_id = (lookup "type", type_id rescue lookup "type", ".oth")
362
+
363
+ processor_id ||= "Any"
364
+ processor_id = lookup "processor", processor_id
365
+
366
+ form = {
367
+ "step2" => 1,
368
+ "type_id" => type_id,
369
+ "processor_id" => processor_id,
370
+ "userfile" => userfile,
371
+ "submit" => "Add This File"
372
+ }
373
+
374
+ boundary = Array::new(8){ "%2.2d" % rand(42) }.join('__')
375
+ boundary = "multipart/form-data; boundary=___#{ boundary }___"
376
+
377
+ run page, form, 'content-type' => boundary
378
+ end
379
+
380
+ def client
381
+ return @client if @client
382
+
383
+ @client = RubyForge::Client::new ENV["HTTP_PROXY"]
384
+ @client.debug_dev = STDERR if ENV["RUBYFORGE_DEBUG"] || ENV["DEBUG"] || $DEBUG
385
+ @client.cookie_store = @userconfig["cookie_jar"]
386
+
387
+ @client
388
+ end
389
+
390
+ def run(page, form, extheader={}) # :nodoc:
391
+ uri = self.uri + page
392
+ if $DEBUG then
393
+ puts "client.post_content #{uri.inspect}, #{form.inspect}, #{extheader.inspect}"
394
+ end
395
+
396
+ response = client.post_content uri, form, extheader
397
+
398
+ client.save_cookie_store
399
+
400
+ if $DEBUG then
401
+ response.sub!(/\A.*end tabGenerator -->/m, '')
402
+ response.gsub!(/\t/, ' ')
403
+ response.gsub!(/\n{3,}/, "\n\n")
404
+ puts response
405
+ end
406
+
407
+ return response
408
+ end
409
+
410
+ def lookup(type, val) # :nodoc:
411
+ unless Fixnum === val then
412
+ key = val.to_s
413
+ val = @autoconfig["#{type}_ids"][key]
414
+ raise "no <#{type}_id> configured for <#{ key }>" unless val
415
+ end
416
+ val
417
+ end
418
+ end
419
+
420
+ __END__
421
+ #
422
+ # base rubyforge uri - store in #{ CONFIG_F }
423
+ #
424
+ uri : http://rubyforge.org
425
+ #
426
+ # this must be your username
427
+ #
428
+ username : username
429
+ #
430
+ # this must be your password
431
+ #
432
+ password : password
433
+ #
434
+ # defaults for some values
435
+ #
436
+ cookie_jar : #{ COOKIE_F }
437
+ is_private : false
438
+ # AUTOCONFIG:
439
+ rubyforge :
440
+ #
441
+ # map your group names to their rubyforge ids
442
+ #
443
+ group_ids :
444
+ codeforpeople : 1024
445
+ #
446
+ # map your package names to their rubyforge ids
447
+ #
448
+ package_ids :
449
+ traits : 1241
450
+ #
451
+ # map your package names to their rubyforge ids
452
+ #
453
+ release_ids :
454
+ traits :
455
+ 1.2.3 : 666
456
+ #
457
+ # mapping file exts to rubyforge ids
458
+ #
459
+ type_ids :
460
+ .deb : 1000
461
+ .rpm : 2000
462
+ .zip : 3000
463
+ .bz2 : 3100
464
+ .gz : 3110
465
+ .src.zip : 5000
466
+ .src.bz2 : 5010
467
+ .src.tar.bz2 : 5010
468
+ .src.gz : 5020
469
+ .src.tar.gz : 5020
470
+ .src.rpm : 5100
471
+ .src : 5900
472
+ .jpg : 8000
473
+ .txt : 8100
474
+ .text : 8100
475
+ .htm : 8200
476
+ .html : 8200
477
+ .pdf : 8300
478
+ .oth : 9999
479
+ .ebuild : 1300
480
+ .exe : 1100
481
+ .dmg : 1200
482
+ .tar.gz : 5000
483
+ .tgz : 5000
484
+ .gem : 1400
485
+ .pgp : 8150
486
+ .sig : 8150
487
+ .pem : 1500
488
+
489
+ #
490
+ # map processor names to rubyforge ids
491
+ #
492
+ processor_ids :
493
+ Other : 9999