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.
- data/History.txt +100 -0
- data/Manifest.txt +11 -0
- data/README.txt +58 -0
- data/Rakefile +34 -0
- data/bin/rubyforge +231 -438
- data/lib/rubyforge/client.rb +145 -0
- data/lib/rubyforge/cookie_manager.rb +60 -0
- data/lib/rubyforge.rb +493 -0
- data/test/test_rubyforge.rb +400 -0
- data/test/test_rubyforge_client.rb +122 -0
- data/test/test_rubyforge_cookie_manager.rb +97 -0
- metadata +80 -32
|
@@ -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
|