rhype 0.1.0
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/COPYING +682 -0
- data/HISTORY +0 -0
- data/MANIFEST +25 -0
- data/NEWS +22 -0
- data/README +13 -0
- data/VERSION +1 -0
- data/bin/rhype +6 -0
- data/lib/rhype/command.help +29 -0
- data/lib/rhype/command.rb +82 -0
- data/lib/rhype/controller.rb +71 -0
- data/lib/rhype/host.rb +55 -0
- data/lib/rhype/hosts/gforge.rb +755 -0
- data/lib/rhype/hosts/rubyforge.rb +876 -0
- data/lib/rhype/index.rb +4 -0
- data/lib/rhype/metadata.rb +77 -0
- data/lib/rhype/service.rb +90 -0
- data/meta/author +1 -0
- data/meta/contact +1 -0
- data/meta/description +2 -0
- data/meta/homepage +1 -0
- data/meta/unixname +1 -0
- metadata +87 -0
@@ -0,0 +1,876 @@
|
|
1
|
+
require 'rhype/hosts/gforge'
|
2
|
+
|
3
|
+
module RHype
|
4
|
+
|
5
|
+
#def rubyforge(options={})
|
6
|
+
# opts = config.rubyforge || {}
|
7
|
+
# opts = opts.update(options)
|
8
|
+
# Rubyforge.new(self, opts)
|
9
|
+
#end
|
10
|
+
|
11
|
+
# = Rubyforge
|
12
|
+
#
|
13
|
+
# Interface with the RubyForge hosting service.
|
14
|
+
# Supports the following tasks:
|
15
|
+
#
|
16
|
+
# * release - Upload release packages
|
17
|
+
# * publish - Publish website
|
18
|
+
# * announce - Post news announcement
|
19
|
+
# * touch - Test connection
|
20
|
+
|
21
|
+
class Rubyforge < GForge
|
22
|
+
|
23
|
+
#register('rubyforge', 'rubyforge.org')
|
24
|
+
#service_action :publish => :deploy
|
25
|
+
#service_action :release => :deploy
|
26
|
+
#service_action :announce => :deploy
|
27
|
+
|
28
|
+
#service_action :touch
|
29
|
+
|
30
|
+
#HOME = ENV["HOME"] || ENV["HOMEPATH"] || File.expand_path("~")
|
31
|
+
DOMAIN = "rubyforge.org"
|
32
|
+
COOKIEJAR = File::join(Dir.tmpdir, 'reap', 'cookie.dat')
|
33
|
+
REPORT = /<h\d><span style="color:red">(.*?)<\/span><\/h\d>/
|
34
|
+
|
35
|
+
# Project unixname.
|
36
|
+
attr_accessor :unixname
|
37
|
+
|
38
|
+
# Project name.
|
39
|
+
attr_accessor :version
|
40
|
+
|
41
|
+
# Project's group id number.
|
42
|
+
attr_accessor :group_id
|
43
|
+
|
44
|
+
alias_accessor :group , :group_id
|
45
|
+
alias_accessor :groupid, :group_id
|
46
|
+
|
47
|
+
# Username for project account.
|
48
|
+
attr_accessor :username
|
49
|
+
|
50
|
+
# Password for project account.
|
51
|
+
attr_accessor :password
|
52
|
+
|
53
|
+
#
|
54
|
+
attr_accessor :dryrun
|
55
|
+
|
56
|
+
#
|
57
|
+
attr_accessor :quiet
|
58
|
+
|
59
|
+
#
|
60
|
+
attr_accessor :verbose
|
61
|
+
|
62
|
+
#
|
63
|
+
#attr_accessor :domain
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def initialize(options)
|
68
|
+
#@unixname = unixname
|
69
|
+
#@version = metadata.version
|
70
|
+
@username = ENV['RUBYFORGE_USERNAME']
|
71
|
+
@password = ENV['RUBYFORGE_PASSWORD']
|
72
|
+
@domain = DOMAIN
|
73
|
+
|
74
|
+
super(options)
|
75
|
+
|
76
|
+
@package_ids = {}
|
77
|
+
@release_ids = {}
|
78
|
+
@file_ids = {}
|
79
|
+
end
|
80
|
+
|
81
|
+
public
|
82
|
+
|
83
|
+
def dryrun? ; @dryrun ; end
|
84
|
+
def quiet? ; @quiet ; end
|
85
|
+
def verbose? ; @verbose ; end
|
86
|
+
|
87
|
+
# New RubyForge object.
|
88
|
+
#
|
89
|
+
#def initialize(project, options={})
|
90
|
+
#end
|
91
|
+
|
92
|
+
# URI = http:// + domain name
|
93
|
+
#
|
94
|
+
# TODO: Deal with https, and possible other protocols too.
|
95
|
+
|
96
|
+
def uri
|
97
|
+
@uri ||= URI.parse("http://" + domain)
|
98
|
+
end
|
99
|
+
|
100
|
+
#
|
101
|
+
|
102
|
+
def cookie_jar
|
103
|
+
COOKIEJAR
|
104
|
+
end
|
105
|
+
|
106
|
+
public
|
107
|
+
|
108
|
+
# Website location on server.
|
109
|
+
def siteroot
|
110
|
+
"/var/www/gforge-projects"
|
111
|
+
end
|
112
|
+
|
113
|
+
# What commands does this host support.
|
114
|
+
|
115
|
+
def commands
|
116
|
+
%w{ touch release publish post }
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
# Login to website.
|
121
|
+
|
122
|
+
def login # :yield:
|
123
|
+
load_project_cached
|
124
|
+
|
125
|
+
page = uri + "/account/login.php"
|
126
|
+
page.scheme = 'https'
|
127
|
+
page = URI.parse(page.to_s) # set SSL port correctly
|
128
|
+
|
129
|
+
form = {
|
130
|
+
"return_to" => "",
|
131
|
+
"form_loginname" => username,
|
132
|
+
"form_pw" => password,
|
133
|
+
"login" => "Login with SSL"
|
134
|
+
}
|
135
|
+
html = http_post(page, form)
|
136
|
+
|
137
|
+
if not html[/Personal Page/]
|
138
|
+
puts "Login failed."
|
139
|
+
re1 = Regexp.escape(%{<h2 style="color:red">})
|
140
|
+
re2 = Regexp.escape(%{</h2>})
|
141
|
+
html[/#{re1}(.*?)#{re2}/]
|
142
|
+
raise $1
|
143
|
+
else
|
144
|
+
@printed_project_name ||= (puts "Project: #{unixname}"; true)
|
145
|
+
end
|
146
|
+
|
147
|
+
if block_given?
|
148
|
+
begin
|
149
|
+
yield
|
150
|
+
ensure
|
151
|
+
logout
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# Logout of website.
|
157
|
+
|
158
|
+
def logout
|
159
|
+
page = "/account/logout.php"
|
160
|
+
form = {}
|
161
|
+
http_post(page, form)
|
162
|
+
end
|
163
|
+
|
164
|
+
# Touch base with server -- login and logout.
|
165
|
+
|
166
|
+
def touch(options={})
|
167
|
+
login
|
168
|
+
puts "Group ID: #{group_id}"
|
169
|
+
puts "Login/Logout successful."
|
170
|
+
logout
|
171
|
+
end
|
172
|
+
|
173
|
+
# Upload release packages to hosting service.
|
174
|
+
#
|
175
|
+
# This task releases files to RubyForge --it should work with other
|
176
|
+
# GForge instaces or SourceForge clones too.
|
177
|
+
#
|
178
|
+
# While defaults are nice, you may want a little more control. You can
|
179
|
+
# specify additional attributes:
|
180
|
+
#
|
181
|
+
# files package files to release.
|
182
|
+
# exclude Package formats to exclude from files.
|
183
|
+
# (from those created by pack)
|
184
|
+
# unixname Project name on host.
|
185
|
+
# package Package to which this release belongs (defaults to project)
|
186
|
+
# release Release name (default is version number)
|
187
|
+
# version Version of release
|
188
|
+
# date Date of release (defaults to Time.now)
|
189
|
+
# processor Processor/Architecture (any, i386, PPC, etc.)
|
190
|
+
# is_public Public release? (defualts to true)
|
191
|
+
#
|
192
|
+
# Rubyforge release also take two documentation elements.
|
193
|
+
# These can be either the straight text, or a file.
|
194
|
+
#
|
195
|
+
# changes Change log text
|
196
|
+
# notes Release notes text
|
197
|
+
#
|
198
|
+
# or
|
199
|
+
#
|
200
|
+
# changelog Change log file
|
201
|
+
# notelog Release notes file
|
202
|
+
#
|
203
|
+
# The release option can be a template by using %s in the
|
204
|
+
# string. The version number of your project will be sub'd
|
205
|
+
# in for the %s. This saves you from having to update
|
206
|
+
# the release name before every release.
|
207
|
+
#
|
208
|
+
#--
|
209
|
+
# What about releasing a pacman PKGBUILD?
|
210
|
+
#++
|
211
|
+
|
212
|
+
def release(options)
|
213
|
+
options = options.rekey
|
214
|
+
|
215
|
+
version = options[:version] || version()
|
216
|
+
|
217
|
+
changes = options[:changes]
|
218
|
+
notes = options[:notes]
|
219
|
+
|
220
|
+
changelog = options[:changelog]
|
221
|
+
notelog = options[:notelog]
|
222
|
+
|
223
|
+
unixname = options[:unixname] || unixname()
|
224
|
+
package = options[:package] || unixname()
|
225
|
+
release = options[:release] || version()
|
226
|
+
name = options[:name] || package
|
227
|
+
files = options[:files] || options[:file] || []
|
228
|
+
date = options[:date] || Time::now.strftime('%Y-%m-%d %H:%M')
|
229
|
+
processor = options[:processor] || 'Any'
|
230
|
+
store = options[:store] || 'pkg'
|
231
|
+
|
232
|
+
is_public = options[:is_public].nil? ? true : options[:is_public]
|
233
|
+
|
234
|
+
raise ArgumentError, "missing unixname" unless unixname
|
235
|
+
raise ArgumentError, "missing package" unless package
|
236
|
+
raise ArgumentError, "missing release" unless release
|
237
|
+
|
238
|
+
if notelog
|
239
|
+
release_notes = ::IO::read(notelog) if release_notes and test(?f, release_notes)
|
240
|
+
else
|
241
|
+
release_notes = notes
|
242
|
+
end
|
243
|
+
|
244
|
+
if changelog
|
245
|
+
release_changes = ::IO::read(changelog) if release_changes and test(?f, release_changes)
|
246
|
+
else
|
247
|
+
release_changes = changes
|
248
|
+
end
|
249
|
+
|
250
|
+
# Gather package files to release.
|
251
|
+
if files.empty?
|
252
|
+
files = Dir[File.join(store, '*')].select do |file|
|
253
|
+
/#{version}[.]/ =~ file
|
254
|
+
end
|
255
|
+
#files = Dir.glob(File.join(store,"#{name}-#{version}*"))
|
256
|
+
end
|
257
|
+
files = files.select{ |f| File.file?(f) }
|
258
|
+
|
259
|
+
abort "No package files." if files.empty?
|
260
|
+
|
261
|
+
files.each do |file|
|
262
|
+
abort "Not a file -- #{file}" unless File.exist?(file)
|
263
|
+
puts "Release file: #{file}"
|
264
|
+
end
|
265
|
+
|
266
|
+
# which package types
|
267
|
+
#rtypes = [ 'tgz', 'tbz', 'tar.gz', 'tar.bz2', 'deb', 'gem', 'ebuild', 'zip' ]
|
268
|
+
#rtypes -= exclude
|
269
|
+
#rtypes = rtypes.collect{ |rt| Regexp.escape( rt ) }
|
270
|
+
#re_rtypes = Regexp.new('[.](' << rtypes.join('|') << ')$')
|
271
|
+
|
272
|
+
puts "Releasing #{package} #{release}..." #unless options['quiet']
|
273
|
+
|
274
|
+
login do
|
275
|
+
|
276
|
+
raise ArgumentError, "missing group_id" unless group_id
|
277
|
+
|
278
|
+
unless package_id = package?(package)
|
279
|
+
if dryrun?
|
280
|
+
puts "Package '#{package}' does not exist."
|
281
|
+
puts "Create package #{package}."
|
282
|
+
abort "Cannot continue in dryrun mode."
|
283
|
+
else
|
284
|
+
#unless options['force']
|
285
|
+
q = "Package '#{package}' does not exist. Create?"
|
286
|
+
a = ask(q, 'yN')
|
287
|
+
abort "Task canceled." unless ['y', 'yes', 'okay'].include?(a.downcase)
|
288
|
+
#end
|
289
|
+
puts "Creating package #{package}..."
|
290
|
+
create_package(package, is_public)
|
291
|
+
unless package_id = package?(package)
|
292
|
+
raise "Package creation failed."
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
if release_id = release?(release, package_id)
|
297
|
+
#unless options[:force]
|
298
|
+
if dryrun?
|
299
|
+
puts "Release #{release} already exists."
|
300
|
+
else
|
301
|
+
q = "Release #{release} already exists. Re-release?"
|
302
|
+
a = ask(q, 'yN')
|
303
|
+
abort "Task canceled." unless ['y', 'yes', 'okay'].include?(a.downcase)
|
304
|
+
#puts "Use -f option to force re-release."
|
305
|
+
#return
|
306
|
+
end
|
307
|
+
files.each do |file|
|
308
|
+
fname = File.basename(file)
|
309
|
+
if file_id = file?(fname, package)
|
310
|
+
if dryrun?
|
311
|
+
puts "Remove file #{fname}."
|
312
|
+
else
|
313
|
+
puts "Removing file #{fname}..."
|
314
|
+
remove_file(file_id, release_id, package_id)
|
315
|
+
end
|
316
|
+
end
|
317
|
+
if dryrun?
|
318
|
+
puts "Add file #{fname}."
|
319
|
+
else
|
320
|
+
puts "Adding file #{fname}..."
|
321
|
+
add_file(file, release_id, package_id, processor)
|
322
|
+
end
|
323
|
+
end
|
324
|
+
else
|
325
|
+
if dryrun?
|
326
|
+
puts "Add release #{release}."
|
327
|
+
else
|
328
|
+
puts "Adding release #{release}..."
|
329
|
+
add_release(release, package_id, files,
|
330
|
+
:processor => processor,
|
331
|
+
:release_date => date,
|
332
|
+
:release_changes => release_changes,
|
333
|
+
:release_notes => release_notes,
|
334
|
+
:preformatted => '1'
|
335
|
+
)
|
336
|
+
unless release_id = release?(release, package_id)
|
337
|
+
raise "Release creation failed."
|
338
|
+
end
|
339
|
+
end
|
340
|
+
#files.each do |file|
|
341
|
+
# puts "Added file #{File.basename(file)}."
|
342
|
+
#end
|
343
|
+
end
|
344
|
+
end
|
345
|
+
puts "Release complete!" unless dryrun?
|
346
|
+
end
|
347
|
+
|
348
|
+
# Publish documents to website.
|
349
|
+
#
|
350
|
+
# TODO: Allow sitemap to hande single files.
|
351
|
+
# This requires adjusting filter system though.
|
352
|
+
#
|
353
|
+
def publish(options)
|
354
|
+
options = options.rekey(&:to_s)
|
355
|
+
|
356
|
+
raise "no username" unless username
|
357
|
+
|
358
|
+
sitemap = options['sitemap']
|
359
|
+
filter = options['filter']
|
360
|
+
quiet = options['quiet'] || !verbose? #quiet?
|
361
|
+
verbose = options['verbose'] || verbose?
|
362
|
+
dryrun = %w{dryrun noharm pretend}.any?{ |x| options[x] } || dryrun?
|
363
|
+
|
364
|
+
case sitemap
|
365
|
+
when Hash
|
366
|
+
when Array
|
367
|
+
sitemap.inject({}) do |h, (s, d)|
|
368
|
+
h[s] = d; h
|
369
|
+
end
|
370
|
+
else
|
371
|
+
sitemap = { sitemap.to_s => '.' }
|
372
|
+
end
|
373
|
+
|
374
|
+
sitemap.each do |from, to|
|
375
|
+
if !File.directory?(from)
|
376
|
+
raise ArgumentError, "Non-existant publishing directory -- #{from}."
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
#source = options['source']
|
381
|
+
#subdir = options['output'] || options['subdir']
|
382
|
+
|
383
|
+
sitemap.each do |source, subdir|
|
384
|
+
if subdir and subdir != '.'
|
385
|
+
destination = File.join(unixname, subdir)
|
386
|
+
else
|
387
|
+
destination = unixname
|
388
|
+
end
|
389
|
+
|
390
|
+
dir = source.to_s.chomp('/') + '/'
|
391
|
+
url = "#{username}@rubyforge.org:/var/www/gforge-projects/#{destination}"
|
392
|
+
|
393
|
+
op = ['-rLvz', '--delete-after'] # maybe -p ?
|
394
|
+
|
395
|
+
op << "-n" if dryrun
|
396
|
+
op << "-q" if quiet
|
397
|
+
op << "-v" if verbose
|
398
|
+
op << "--progress" unless quiet
|
399
|
+
|
400
|
+
# custom filter
|
401
|
+
op << "--filter='. #{filter}'" if filter
|
402
|
+
|
403
|
+
create_rsync_filter(source)
|
404
|
+
|
405
|
+
# per dir-merge filter
|
406
|
+
op << "--filter=': .rsync-filter'"
|
407
|
+
|
408
|
+
args = op + [dir, url]
|
409
|
+
|
410
|
+
if dryrun? # Actually rsync supports a dryrun mode. We could let this go thrugh ?
|
411
|
+
puts "rsync #{args.to_params}"
|
412
|
+
else
|
413
|
+
system "rsync #{args.to_params}"
|
414
|
+
# UploadUtils.rsync(options)
|
415
|
+
#system "#{op.join(' ')} #{dir} #{url}"
|
416
|
+
end
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
private
|
421
|
+
|
422
|
+
#
|
423
|
+
def create_rsync_filter(source)
|
424
|
+
protect = %w{usage statcvs statsvn robot.txt robots.txt wiki}
|
425
|
+
exclude = %w{.svn .gitignore}
|
426
|
+
|
427
|
+
rsync_file = File.join(source,'.rsync-filter')
|
428
|
+
unless FileTest.file?(rsync_file)
|
429
|
+
File.open(rsync_file, 'w') do |f|
|
430
|
+
exclude.each{|e| f << "- #{e}\n"}
|
431
|
+
protect.each{|e| f << "P #{e}\n"}
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
#protect.to_list.each do |x|
|
436
|
+
# s = x[0,1]
|
437
|
+
# x = x[1..-1] if s == '-' or s == '+'
|
438
|
+
# s == '-' ? protect.delete(x) : protect << x
|
439
|
+
#end
|
440
|
+
|
441
|
+
#exclude.to_list.each do |x|
|
442
|
+
# s = x[0,1]
|
443
|
+
# x = x[1..-1] if s == '-' or s == '+'
|
444
|
+
# s == '-' ? exclude.delete(x) : exclude << x
|
445
|
+
#end
|
446
|
+
end
|
447
|
+
|
448
|
+
public
|
449
|
+
|
450
|
+
# Submit a news item.
|
451
|
+
|
452
|
+
def announce(options)
|
453
|
+
options = options.rekey
|
454
|
+
|
455
|
+
if file = options[:file]
|
456
|
+
text = File.read(file).strip
|
457
|
+
i = text.index("\n")
|
458
|
+
subject = text[0...i].strip
|
459
|
+
message = text[i..-1].strip
|
460
|
+
else
|
461
|
+
subject = options[:subject] || "Announcing #{unixname}!"
|
462
|
+
message = options[:message] || options[:body]
|
463
|
+
end
|
464
|
+
|
465
|
+
if dryrun?
|
466
|
+
puts "announce-rubyforge: #{subject}"
|
467
|
+
else
|
468
|
+
post_news(subject, message)
|
469
|
+
puts "News item posted!"
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
|
474
|
+
private
|
475
|
+
|
476
|
+
# HTTP POST transaction.
|
477
|
+
|
478
|
+
def http_post(page, form, extheader={})
|
479
|
+
client = HTTPClient::new ENV["HTTP_PROXY"]
|
480
|
+
client.debug_dev = STDERR if ENV["REAP_DEBUG"] || $DEBUG
|
481
|
+
client.set_cookie_store(cookie_jar)
|
482
|
+
client.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
483
|
+
|
484
|
+
# HACK to fix http-client redirect bug/feature
|
485
|
+
client.redirect_uri_callback = lambda do |uri, res|
|
486
|
+
page = res.header['location'].first
|
487
|
+
page =~ %r/http/ ? page : @uri + page
|
488
|
+
end
|
489
|
+
|
490
|
+
uri = @uri + page
|
491
|
+
if $DEBUG then
|
492
|
+
puts "POST #{uri.inspect}"
|
493
|
+
puts "#{form.inspect}"
|
494
|
+
puts "#{extheader.inspect}" unless extheader.empty?
|
495
|
+
puts
|
496
|
+
end
|
497
|
+
|
498
|
+
response = client.post_content uri, form, extheader
|
499
|
+
|
500
|
+
if response[REPORT]
|
501
|
+
puts "(" + $1 + ")"
|
502
|
+
end
|
503
|
+
|
504
|
+
client.save_cookie_store
|
505
|
+
|
506
|
+
return response
|
507
|
+
end
|
508
|
+
|
509
|
+
#
|
510
|
+
|
511
|
+
def load_project_cached
|
512
|
+
@load_project_cache ||= load_project
|
513
|
+
end
|
514
|
+
|
515
|
+
# Loads information for project: group_id, package_ids and release_ids.
|
516
|
+
|
517
|
+
def load_project
|
518
|
+
html = URI.parse("http://#{domain}/projects/#{unixname}/index.html").read
|
519
|
+
|
520
|
+
group_id = html[/(frs|tracker)\/\?group_id=\d+/][/\d+/].to_i
|
521
|
+
@group_id = group_id
|
522
|
+
|
523
|
+
if $DEBUG
|
524
|
+
puts "GROUP_ID = #{group_id}"
|
525
|
+
end
|
526
|
+
|
527
|
+
html = URI.parse("http://rubyforge.org/frs/?group_id=#{group_id}").read
|
528
|
+
|
529
|
+
package = nil
|
530
|
+
html.scan(/<h3>[^<]+|release_id=\d+">[^>]+|filemodule_id=\d+/).each do |s|
|
531
|
+
case s
|
532
|
+
when /<h3>([^<]+)/ then
|
533
|
+
package = $1.strip
|
534
|
+
when /filemodule_id=(\d+)/ then
|
535
|
+
@package_ids[package] = $1.to_i
|
536
|
+
when /release_id=(\d+)">([^<]+)/ then
|
537
|
+
package_id = @package_ids[package]
|
538
|
+
@release_ids[[package_id,$2]] = $1.to_i
|
539
|
+
end
|
540
|
+
end
|
541
|
+
|
542
|
+
if $DEBUG
|
543
|
+
p @package_ids, @release_ids
|
544
|
+
end
|
545
|
+
end
|
546
|
+
|
547
|
+
# Returns password. If not already set, will ask for it.
|
548
|
+
|
549
|
+
def password
|
550
|
+
@password ||= ENV['RUBYFORGE_PASSWORD']
|
551
|
+
@password ||= (
|
552
|
+
print "Password for #{username}: "
|
553
|
+
until inp = $stdin.gets ; sleep 1 ; end ; puts
|
554
|
+
inp.strip
|
555
|
+
)
|
556
|
+
end
|
557
|
+
|
558
|
+
# Package exists? Returns package-id number.
|
559
|
+
|
560
|
+
def package?(package_name)
|
561
|
+
id = @package_ids[package_name]
|
562
|
+
return id if id
|
563
|
+
|
564
|
+
package_id = nil
|
565
|
+
|
566
|
+
page = "/frs/"
|
567
|
+
|
568
|
+
form = {
|
569
|
+
"group_id" => group_id
|
570
|
+
}
|
571
|
+
scrape = http_post(page, form)
|
572
|
+
|
573
|
+
restr = ''
|
574
|
+
restr << Regexp.escape( package_name )
|
575
|
+
restr << '\s*'
|
576
|
+
restr << Regexp.escape( '<a href="/frs/monitor.php?filemodule_id=' )
|
577
|
+
restr << '(\d+)'
|
578
|
+
restr << Regexp.escape( %{&group_id=#{group_id}} )
|
579
|
+
re = Regexp.new( restr )
|
580
|
+
|
581
|
+
md = re.match( scrape )
|
582
|
+
if md
|
583
|
+
package_id = md[1]
|
584
|
+
end
|
585
|
+
|
586
|
+
@package_ids[package_name] = package_id
|
587
|
+
end
|
588
|
+
|
589
|
+
# Create a new package.
|
590
|
+
|
591
|
+
def create_package( package_name, is_public=true )
|
592
|
+
page = "/frs/admin/index.php"
|
593
|
+
|
594
|
+
form = {
|
595
|
+
"func" => "add_package",
|
596
|
+
"group_id" => group_id,
|
597
|
+
"package_name" => package_name,
|
598
|
+
"is_public" => (is_public ? 1 : 0),
|
599
|
+
"submit" => "Create This Package"
|
600
|
+
}
|
601
|
+
|
602
|
+
http_post(page, form)
|
603
|
+
end
|
604
|
+
|
605
|
+
# Delete package.
|
606
|
+
|
607
|
+
def delete_package(package_id)
|
608
|
+
page = "/frs/admin/index.php"
|
609
|
+
|
610
|
+
form = {
|
611
|
+
"func" => "delete_package",
|
612
|
+
"group_id" => group_id,
|
613
|
+
"package_id" => package_id,
|
614
|
+
"sure" => "1",
|
615
|
+
"really_sure" => "1",
|
616
|
+
"submit" => "Delete",
|
617
|
+
}
|
618
|
+
|
619
|
+
http_post(page, form)
|
620
|
+
end
|
621
|
+
|
622
|
+
# Release exits? Returns release-id number.
|
623
|
+
|
624
|
+
def release?(release_name, package_id)
|
625
|
+
id = @release_ids[[release_name,package_id]]
|
626
|
+
return id if id
|
627
|
+
|
628
|
+
release_id = nil
|
629
|
+
|
630
|
+
page = "/frs/admin/showreleases.php"
|
631
|
+
|
632
|
+
form = {
|
633
|
+
"package_id" => package_id,
|
634
|
+
"group_id" => group_id
|
635
|
+
}
|
636
|
+
scrape = http_post( page, form )
|
637
|
+
|
638
|
+
restr = ''
|
639
|
+
restr << Regexp.escape( %{"editrelease.php?group_id=#{group_id}} )
|
640
|
+
restr << Regexp.escape( %{&package_id=#{package_id}} )
|
641
|
+
restr << Regexp.escape( %{&release_id=} )
|
642
|
+
restr << '(\d+)'
|
643
|
+
restr << Regexp.escape( %{">#{release_name}} )
|
644
|
+
re = Regexp.new( restr )
|
645
|
+
|
646
|
+
md = re.match( scrape )
|
647
|
+
if md
|
648
|
+
release_id = md[1]
|
649
|
+
end
|
650
|
+
|
651
|
+
@release_ids[[release_name,package_id]] = release_id
|
652
|
+
end
|
653
|
+
|
654
|
+
# Add a new release.
|
655
|
+
|
656
|
+
def add_release(release_name, package_id, *files)
|
657
|
+
page = "/frs/admin/qrs.php"
|
658
|
+
|
659
|
+
options = (Hash===files.last ? files.pop : {}).rekey
|
660
|
+
files = files.flatten
|
661
|
+
|
662
|
+
processor = options[:processor]
|
663
|
+
release_date = options[:release_date]
|
664
|
+
release_changes = options[:release_changes]
|
665
|
+
release_notes = options[:release_notes]
|
666
|
+
|
667
|
+
release_date ||= Time::now.strftime("%Y-%m-%d %H:%M")
|
668
|
+
|
669
|
+
file = files.shift
|
670
|
+
puts "Adding file #{File.basename(file)}..."
|
671
|
+
userfile = open(file, 'rb')
|
672
|
+
|
673
|
+
type_id = userfile.path[%r|\.[^\./]+$|]
|
674
|
+
type_id = FILETYPES[type_id]
|
675
|
+
processor_id = PROCESSORS[processor.downcase]
|
676
|
+
|
677
|
+
preformatted = '1'
|
678
|
+
|
679
|
+
form = {
|
680
|
+
"group_id" => group_id,
|
681
|
+
"package_id" => package_id,
|
682
|
+
"release_name" => release_name,
|
683
|
+
"release_date" => release_date,
|
684
|
+
"type_id" => type_id,
|
685
|
+
"processor_id" => processor_id,
|
686
|
+
"release_notes" => release_notes,
|
687
|
+
"release_changes" => release_changes,
|
688
|
+
"preformatted" => preformatted,
|
689
|
+
"userfile" => userfile,
|
690
|
+
"submit" => "Release File"
|
691
|
+
}
|
692
|
+
|
693
|
+
boundary = Array::new(8){ "%2.2d" % rand(42) }.join('__')
|
694
|
+
boundary = "multipart/form-data; boundary=___#{ boundary }___"
|
695
|
+
|
696
|
+
html = http_post(page, form, 'content-type' => boundary)
|
697
|
+
|
698
|
+
release_id = html[/release_id=\d+/][/\d+/].to_i
|
699
|
+
puts "RELEASE ID = #{release_id}" if $DEBUG
|
700
|
+
|
701
|
+
files.each do |file|
|
702
|
+
puts "Adding file #{File.basename(file)}..."
|
703
|
+
add_file(file, release_id, package_id, processor)
|
704
|
+
end
|
705
|
+
|
706
|
+
release_id
|
707
|
+
end
|
708
|
+
|
709
|
+
# File exists?
|
710
|
+
#
|
711
|
+
# NOTE this is a bit fragile. If two releases have the same exact
|
712
|
+
# file name in them there could be a problem --that's probably not
|
713
|
+
# likely, but I can't yet rule it out.
|
714
|
+
#
|
715
|
+
# TODO Remove package argument, it is no longer needed.
|
716
|
+
|
717
|
+
def file?(file, package)
|
718
|
+
id = @file_ids[[file, package]]
|
719
|
+
return id if id
|
720
|
+
|
721
|
+
file_id = nil
|
722
|
+
|
723
|
+
page = "/frs/"
|
724
|
+
|
725
|
+
form = {
|
726
|
+
"group_id" => group_id
|
727
|
+
}
|
728
|
+
scrape = http_post(page, form)
|
729
|
+
|
730
|
+
restr = ''
|
731
|
+
#restr << Regexp.escape( package )
|
732
|
+
#restr << '\s*'
|
733
|
+
restr << Regexp.escape( '<a href="/frs/download.php/' )
|
734
|
+
restr << '(\d+)'
|
735
|
+
restr << Regexp.escape( %{/#{file}} )
|
736
|
+
re = Regexp.new(restr)
|
737
|
+
|
738
|
+
md = re.match(scrape)
|
739
|
+
if md
|
740
|
+
file_id = md[1]
|
741
|
+
end
|
742
|
+
|
743
|
+
@file_ids[[file, package]] = file_id
|
744
|
+
end
|
745
|
+
|
746
|
+
# Remove file from release.
|
747
|
+
|
748
|
+
def remove_file(file_id, release_id, package_id)
|
749
|
+
page="/frs/admin/editrelease.php"
|
750
|
+
|
751
|
+
form = {
|
752
|
+
"group_id" => group_id,
|
753
|
+
"package_id" => package_id,
|
754
|
+
"release_id" => release_id,
|
755
|
+
"file_id" => file_id,
|
756
|
+
"step3" => "Delete File",
|
757
|
+
"im_sure" => '1',
|
758
|
+
"submit" => "Delete File "
|
759
|
+
}
|
760
|
+
|
761
|
+
http_post(page, form)
|
762
|
+
end
|
763
|
+
|
764
|
+
#
|
765
|
+
# Add file to release.
|
766
|
+
#
|
767
|
+
|
768
|
+
def add_file(file, release_id, package_id, processor=nil)
|
769
|
+
page = '/frs/admin/editrelease.php'
|
770
|
+
|
771
|
+
userfile = open file, 'rb'
|
772
|
+
|
773
|
+
type_id = userfile.path[%r|\.[^\./]+$|]
|
774
|
+
type_id = FILETYPES[type_id]
|
775
|
+
processor_id = PROCESSORS[processor.downcase]
|
776
|
+
|
777
|
+
form = {
|
778
|
+
"step2" => '1',
|
779
|
+
"group_id" => group_id,
|
780
|
+
"package_id" => package_id,
|
781
|
+
"release_id" => release_id,
|
782
|
+
"userfile" => userfile,
|
783
|
+
"type_id" => type_id,
|
784
|
+
"processor_id" => processor_id,
|
785
|
+
"submit" => "Add This File"
|
786
|
+
}
|
787
|
+
|
788
|
+
boundary = Array::new(8){ "%2.2d" % rand(42) }.join('__')
|
789
|
+
boundary = "multipart/form-data; boundary=___#{ boundary }___"
|
790
|
+
|
791
|
+
http_post(page, form, 'content-type' => boundary)
|
792
|
+
end
|
793
|
+
|
794
|
+
# Posts news item to +group_id+ (can be name) with +subject+ and +body+
|
795
|
+
|
796
|
+
def post_news(subject, body)
|
797
|
+
page = "/news/submit.php"
|
798
|
+
|
799
|
+
subject % [unixname, version]
|
800
|
+
|
801
|
+
form = {
|
802
|
+
"group_id" => group_id,
|
803
|
+
"post_changes" => "y",
|
804
|
+
"summary" => subject,
|
805
|
+
"details" => body,
|
806
|
+
"submit" => "Submit"
|
807
|
+
}
|
808
|
+
|
809
|
+
login do
|
810
|
+
http_post(page, form)
|
811
|
+
end
|
812
|
+
end
|
813
|
+
|
814
|
+
# Constant for file types accepted by Rubyforge
|
815
|
+
|
816
|
+
FILETYPES = {
|
817
|
+
".deb" => 1000,
|
818
|
+
".rpm" => 2000,
|
819
|
+
".zip" => 3000,
|
820
|
+
".bz2" => 3100,
|
821
|
+
".gz" => 3110,
|
822
|
+
".src.zip" => 5000,
|
823
|
+
".src.bz2" => 5010,
|
824
|
+
".src.tar.bz2" => 5010,
|
825
|
+
".src.gz" => 5020,
|
826
|
+
".src.tar.gz" => 5020,
|
827
|
+
".src.rpm" => 5100,
|
828
|
+
".src" => 5900,
|
829
|
+
".jpg" => 8000,
|
830
|
+
".txt" => 8100,
|
831
|
+
".text" => 8100,
|
832
|
+
".htm" => 8200,
|
833
|
+
".html" => 8200,
|
834
|
+
".pdf" => 8300,
|
835
|
+
".oth" => 9999,
|
836
|
+
".ebuild" => 1300,
|
837
|
+
".exe" => 1100,
|
838
|
+
".dmg" => 1200,
|
839
|
+
".tar.gz" => 3110,
|
840
|
+
".tgz" => 3110,
|
841
|
+
".gem" => 1400,
|
842
|
+
".pgp" => 8150,
|
843
|
+
".sig" => 8150
|
844
|
+
}
|
845
|
+
|
846
|
+
# Constant for processor types accepted by Rubyforge
|
847
|
+
|
848
|
+
PROCESSORS = {
|
849
|
+
"i386" => 1000,
|
850
|
+
"IA64" => 6000,
|
851
|
+
"Alpha" => 7000,
|
852
|
+
"Any" => 8000,
|
853
|
+
"PPC" => 2000,
|
854
|
+
"MIPS" => 3000,
|
855
|
+
"Sparc" => 4000,
|
856
|
+
"UltraSparc" => 5000,
|
857
|
+
"Other" => 9999,
|
858
|
+
|
859
|
+
"i386" => 1000,
|
860
|
+
"ia64" => 6000,
|
861
|
+
"alpha" => 7000,
|
862
|
+
"any" => 8000,
|
863
|
+
"ppc" => 2000,
|
864
|
+
"mips" => 3000,
|
865
|
+
"sparc" => 4000,
|
866
|
+
"ultrasparc" => 5000,
|
867
|
+
"other" => 9999,
|
868
|
+
|
869
|
+
"all" => 8000,
|
870
|
+
nil => 8000
|
871
|
+
}
|
872
|
+
|
873
|
+
end
|
874
|
+
|
875
|
+
end
|
876
|
+
|