require "enumerator"
require 'reap/task'
require "reap/vendor/http-access2"
# ___ _ _____ _
# | _ \___| |___ __ _ ___ ___ |_ _|_ _ __| |__
# | / -_) / -_) _` (_- -_) | |/ _` (_-< / /
# |_|_\___|_\___\__,_/__/\___| |_|\__,_/__/_\_\
#
# = Release Task
#
# This task releases files to RubyForge and other GForge instaces
# or SourceForge clones. In its most simple usage it looks like:
#
# project = MetaProject::Project::XForge::RubyForge.new('xforge')
# # Create a new release of the xforge project on Rubyforge.
# task :release => [:gem] do
# Rake::XForge::Release.new(project) {}
# end
#
# The previous example will use defaults where it can. It will prompt
# you for your xForge user name and password before it uploads all
# gems under the pkg folder and creates a RubyForge release.
#
# While defaults are nice, you may want a little more control. You can
# specify additional attributes:
#
# * #user_name
# * #password
# * #changes_file
# * #version
# * #files
# * #release_name
# * #release_notes
# * #release_changes
# * #package_name
#
# Example:
# project = MetaProject::Project::XForge::RubyForge.new('xforge')
# task :release => [:gem] do
# release_files = FileList[ 'pkg/*.gem', 'CHANGES' ]
#
# Rake::XForge::Release.new(project) do |xf|
# # Never hardcode user name and password in the Rakefile!
# xf.user_name = ENV['RUBYFORGE_USER']
# xf.password = ENV['RUBYFORGE_PASSWORD']
# xf.files = release_files.to_a
# xf.release_name = "XForge 0.1"
# end
#
# This can be invoked from the command line:
#
# rake release RUBYFORGE_USER=myuser \
# RUBYFORGE_PASSWORD=mypassword
#
# If you don't like blocks, you can do like this:
#
# project = MetaProject::Project::XForge::RubyForge.new('xforge')
# task :release => [:gem] do
# xf = Rake::XForge::Release.new(project)
# ... # Set additional attributes
# xf.execute
# end
class Reap::Release < Reap::Task
section_required true
task_desc "Release distribution files."
task_help %{
reap release
Release distribution to Rubyforge, or other
GForge based service.
host URL of host service
username Username of host service
project Project name at host
package Package name
groupid Group id number
architecture Architecture (Any, i386, PPC, etc.)
release Release name
private Private release?
changelog Change log file
notelog Release notes file
dir Distribution directory
exclude Distribution types to exclude
}
task_attr :rel
#attr_accessor :project, :version
#attr_accessor :host, :username, :password
#attr_accessor :package, :packageid, :groupid, :private, :processor
#attr_accessor :name, :changes, :notes, :changelog, :notelog
#attr_accessor :cookiejar
def init
#rel.version ||= master.date
rel.host ||= 'rubyforge.org'
rel.host.chomp!('/')
rel.username ||= master.rubyforge.username
#rel.password ||= master.rubyforge.password
rel.project ||= master.rubyforge.project || master.name
rel.package ||= master.rubyforge.package
#rel.packageid ||= master.rubyforge.packageid
rel.groupid ||= master.rubyforge.groupid
rel.private ||= false
rel.processor ||= 'Any'
rel.date ||= Time::now.strftime('%Y-%m-%d %H:%M')
rel.exclude ||= []
rel.exclude << 'tar.gz' if rel.exclude.include?( 'tgz' )
rel.exclude << 'tgz' if rel.exclude.include?( 'tar.gz' )
rel.exclude << 'tar.bz2' if rel.exclude.include?( 'tbz' )
rel.exclude << 'tbz' if rel.exclude.include?( 'tar.bz2' )
rel.cookie_jar ||= File::join(File::expand_path("~"), ".rubyforge.cookie_jar")
# Do not inherit
rel.dir = section.dir || master.project.dir
rel.release = section.release || master.version || rel.date
end
# Run release task.
def run
abort "missing field -- package" unless rel.package
#abort "missing field -- packageid" unless rel.packageid
abort "missing field -- groupid" unless rel.groupid
# add more...
# case rel.host
# when 'rubyforge', 'rubyforge.org'
# else
# puts %{Unrecognized release host '#{rel.host}'. Skipped.}
# skip = true
# end
puts "Reap is preparing release ..."
rtypes = [ 'tgz', 'tbz', 'tar.gz', 'tar.bz2', 'deb', 'gem', 'ebuild' ]
rtypes -= rel.exclude
rtypes = rtypes.collect { |rt| Regexp.escape( rt ) }
re_rtypes = Regexp.new( '[.](' << rtypes.join('|') << ')$' )
dir = File.join( rel.dir, "#{rel.name}-#{rel.version}" )
files = Dir.entries(dir).select { |f|
f =~ re_rtypes or f == 'PKGBUILD'
}
files = files.collect { |f| File.join( rel.dir, f ) }
if files.empty?
puts "No files to release at #{dir}."
exit -1
end
# ask for password
print "Password for #{rel.username}: "
until passwd = $stdin.gets.strip ; sleep 1 ; end
@password = passwd
login {
unless package?
exit 0
create_package
end
if release?
exit 0
files.each do |f|
remove_file( f ) if file?( f )
add_file( f )
end
else
exit 0
add_release( files.unshift )
files.each { |f| add_file( f ) }
end
}
end
private
FILETYPES = {
".deb" => 1000,
".rpm" => 2000,
".zip" => 3000,
".bz2" => 3100,
".gz" => 3110,
".src.zip" => 5000,
".src.bz2" => 5010,
".src.gz" => 5020,
".src.rpm" => 5100,
".src" => 5900,
".jpg" => 8000,
".txt" => 8100,
".text" => 8100,
".htm" => 8200,
".html" => 8200,
".pdf" => 8300,
".oth" => 9999,
".ebuild" => 1300,
".exe" => 1100,
".dmg" => 1200,
".tar.gz" => 5000,
".tgz" => 5000,
".gem" => 1400,
".pgp" => 8150,
".sig" => 8150,
}
PROCESSORS = {
"i386" => 1000,
"IA64" => 6000,
"Alpha" => 7000,
"Any" => 8000,
"PPC" => 2000,
"MIPS" => 3000,
"Sparc" => 4000,
"UltraSparc" => 5000,
"Other" => 9999,
}
# login
def login( &block )
# login
page = "/account/login.php"
method = "post_content"
form = {
"return_to" => "",
"form_loginname" => rel.username,
"form_pw" => @password,
"login" => "Login"
}
http_transaction( page, method, form )
# do whatever
block.call
# logout
page = "/account/logout.php"
method = "" #"post_content"
form = {}
http_transaction( page, method, form )
end
# Package exists?
def package?
page = "/frs/"
method = "post_content"
form = {
"group_id" => rel.groupid
}
scrape = http_transaction( page, method, form )
restr = ''
restr << Regexp.escape( rel.package )
restr << '\s*'
restr << Regexp.escape( %{ rel.groupid,
"package_name" => rel.package,
"func" => "add_package",
"is_public" => (rel.private ? 0 : 1),
"submit" => "Create This Package"
}
http_transaction( page, method, form )
end
# Release exits?
def release?
page = "/frs/admin/showreleases.php"
method = "post_content"
form = {
"package_id" => rel.packageid,
"group_id" => rel.groupid
}
scrape = http_transaction( page, method, form )
restr = ''
restr << Regexp.escape( %{"editrelease.php?group_id=#{rel.groupid}} )
restr << Regexp.escape( %{&package_id=#{rel.packageid}} )
restr << Regexp.escape( %{&release_id=} )
restr << '(\d+)'
restr << Regexp.escape( %{">#{rel.release}} )
re = Regexp.new( restr )
md = re.match( scrape )
if md
rel.releaseid = md[1]
end
end
# Add a new release.
def add_release( userfile )
page = "/frs/admin/qrs.php"
method = "post_content"
type_id = rel.type || userfile[%r|\.[^\./]+$|]
type_id = FILETYPES[type_id]
proc_id = PROCESSORS[rel.processor]
# how to use these?
notes = rel.notes ? rel.notes : ( rel.notelog ? open(rel.notelog) : nil )
changes = rel.changes ? rel.changes : ( rel.changelog ? open(rel.changelog) : nil )
userfile = open(rel.userfile)
preformatted = '1'
form = {
"group_id" => rel.groupid,
"package_id" => rel.packageid,
"release_name" => rel.name,
"release_date" => rel.date,
"type_id" => type_id,
"processor_id" => proc_id,
"preformatted" => preformatted,
"userfile" => userfile,
"submit" => "Release File"
}
boundary = Array::new(8){ "%2.2d" % rand(42) }.join('__')
extheader = { 'content-type'=>"multipart/form-data; boundary=___#{ boundary }___" }
http_transaction( page, method, form, extheader )
end
# Does file exist?
# Note this is a little bit fragile.
# If two releases have the same exact file name in them
# there could be a problem --that's probably not likely,
# maybe even impossible, but as of yet, I can't yet rule it out.
def file?( file )
page = "/frs/"
method = "post_content"
form = {
"group_id" => rel.groupid
}
scrape = http_transaction( page, method, form )
restr = ''
restr << Regexp.escape( rel.package )
restr << '\s*'
restr << Regexp.escape( %{ rel.groupid,
"package_id" => rel.packageid,
"release_id" => rel.releaseid,
"file_id" => rel.fileid,
"step3" => "Delete File",
"im_sure" => '1',
"submit" => "Delete File "
}
http_transaction( page, method, form )
end
# Add file to release.
def add_file( userfile )
page = '/frs/admin/editrelease.php'
method = "post_content"
type_id = rel.type || userfile[%r|\.[^\./]+$|]
type_id = FILETYPES[type_id]
proc_id = PROCESSORS[rel.processor]
userfile = open( userfile )
form = {
"group_id" => rel.groupid,
"package_id" => rel.packageid,
"release_id" => rel.releaseid,
"step2" => '1',
"userfile" => userfile,
"type_id" => type_id,
"processor_id" => proc_id,
"submit" => "Add This File"
}
http_transaction( page, method, form ) #, extheader )
end
# http transaction
def http_transaction( page, method, form, extheader={} )
client = HTTPAccess2::Client::new ENV['HTTP_PROXY']
client.debug_dev = STDERR if ENV['DEBUG']
client.set_cookie_store( rel.cookie_jar )
# fixes http-access2 bug
client.redirect_uri_callback = lambda do |res|
page = res.header['location'].first
page = page =~ %r/http/ ? page : "http://#{ rel.host }/#{ page }"
page
end
page.sub!(/^\//, '')
uri = "http://#{ rel.host }/#{ page }"
response = client.send method, uri, form, extheader
client.save_cookie_store
return response
end
end
# fixes http-access2 bug
BEGIN {
require "reap/vendor/http-access2"
module WebAgent::CookieUtils
def domain_match(host, domain)
case domain
when /\d+\.\d+\.\d+\.\d+/
return (host == domain)
when '.'
return true
when /^\./
#return tail_match?(domain, host)
return tail_match?(host, domain)
else
return (host == domain)
end
end
end
}