#! /usr/bin/env ruby $VERBOSE = nil # # load gems/libs # %w( getoptlong enumerator http-access2 yaml fileutils ).each do |l| begin require "rubygems"; require_gem l; rescue LoadError; require l end end # # defaults # PROGRAM = File::basename $0 HOME = ENV["HOME"] || ENV["HOMEPATH"] || File::expand_path("~") RUBYFORGE_D = File::join HOME, ".rubyforge" CONFIG_F = File::join RUBYFORGE_D, "config.yml" COOKIE_F = File::join RUBYFORGE_D, "cookie.dat" # # usage # USAGE = <<-txt SYNOPSIS #{ PROGRAM } [options]* mode [mode_args]* DESCRIPTION simplistic script which automates a limited set of rubyforge operations MODES setup() initializes your #{ RUBYFORGE_D } directory. you need to run this first before doing anything else. example : #{ PROGRAM } setup login() sends username and password from #{ CONFIG_F } (or --username/--password options) and stores login cookie in #{ COOKIE_F }. this is required for subsquent operations work. example : #{ PROGRAM } login #{ PROGRAM } login --username zaphod --password 42 create_package(group_id, package_name) creates the named package under the specified group. example : #{ PROGRAM } create_package codeforpeople.com traits #{ PROGRAM } create_package 1024 traits notes : in order to use group_ids by name, rather than number, you must edit the rubyforge[group_ids] translation table in your #{ CONFIG_F }. add_release(group_id, package_id, release_name, userfile) release a file as release_name under the specified group_id and package_id. example : #{ PROGRAM } add_package codeforpeople.com traits 0.8.0 traits-0.8.0.gem #{ PROGRAM } add_package codeforpeople.com traits 0.8.0 traits-0.8.0.tgz #{ PROGRAM } add_package 1024 1242 0.8.0 traits-0.8.0.gem notes : in order to use group_ids and package_ids by name, rather than number, you must edit the rubyforge[group_ids] and rubyforge[package_ids] translation tables in your #{ CONFIG_F }. delete_package(group_id, package_name) deletes a package and all it's files. example : #{ PROGRAM } delete_package codeforpeople.com traits #{ PROGRAM } delete_package 1024 traits OPTIONS global : --help , -h this message --config , -c specify a config file (default #{ CONFIG_F }) --username , -u specify username, taken from config otherwise --password , -p specify password, taken from config otherwise --cookie_jar , -C specify cookie storage file (default #{ COOKIE_F }) add_release : --is_private , -P if true, release is not public --release_date , -r specify time of release (default 'now') --type_id , -t specify filetype code (default determined by ext) --processor_id , -o specify processor (default 'Any') --release_notes , -n specify release notes as string or file --release_changes , -a specify release changes as string or file --preformatted , -f specify whether release_notes/changes are preformatted txt USAGE.gsub! %r|^#{ USAGE[%r/^\s*/] }|, '' # # config # CONFIG = <<-yml # # base rubyforge uri - store in #{ CONFIG_F } # uri : http://rubyforge.org # # this must be your username # username : username # # this must be your password # password : password # # defaults for some values # defaults : cookie_jar : #{ COOKIE_F } is_private : false # # server side rubyforge configuration # rubyforge : # # map your package names to their rubyforge ids # package_ids : traits : 1241 # # map your group names to their rubyforge ids # group_ids : codeforpeople.com : 1024 # # mapping file exts to rubyforge ids # type_ids : .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 # # map processor names to rubyforge ids # processor_ids : i386 : 1000 IA64 : 6000 Alpha : 7000 Any : 8000 PPC : 2000 MIPS : 3000 Sparc : 4000 UltraSparc : 5000 Other : 9999 yml CONFIG.gsub! %r|^#{ CONFIG[%r/^\s*/] }|, '' # # load mode, global opts, and config # mode = ARGV.shift opts = GetoptLong::new( [ "--help" , "-h" , GetoptLong::REQUIRED_ARGUMENT ] , [ "--config" , "-c" , GetoptLong::REQUIRED_ARGUMENT ] , [ "--username" , "-u" , GetoptLong::REQUIRED_ARGUMENT ] , [ "--password" , "-p" , GetoptLong::REQUIRED_ARGUMENT ] , [ "--cookie_jar" , "-C" , GetoptLong::REQUIRED_ARGUMENT ] , [ "--is_private", "-P", GetoptLong::REQUIRED_ARGUMENT ], [ "--release_date" , "-r" , GetoptLong::REQUIRED_ARGUMENT ] , [ "--type_id" , "-t" , GetoptLong::REQUIRED_ARGUMENT ] , [ "--processor_id" , "-o" , GetoptLong::REQUIRED_ARGUMENT ] , [ "--release_notes" , "-n" , GetoptLong::REQUIRED_ARGUMENT ] , [ "--release_changes" , "-a" , GetoptLong::REQUIRED_ARGUMENT ] , [ "--preformatted" , "-f" , GetoptLong::NO_ARGUMENT ] ).enum_for.inject({}){|h,kv| h.update kv.first.delete('-') => kv.last} config = opts["config"] || CONFIG_F config = test(?e, config) ? IO::read(config) : CONFIG config = YAML::load config username = opts["username"] || config["username"] password = opts["password"] || config["password"] cookie_jar = opts["cookie_jar"] || config["defaults"]["cookie_jar"] abort "no " unless username abort "no " unless password abort "no " unless cookie_jar mode = "help" if opts["help"] # # run based on mode # msg, page, form, extheader = nil, nil, nil, {} case mode # # help mode # when %r/help/ USAGE.display exit # # setup mode # when %r/setup/ FileUtils::mkdir_p RUBYFORGE_D test ?e, CONFIG_F and FileUtils::mv CONFIG_F, "#{ CONFIG_F }.bak" open(CONFIG_F,"w"){|f| f.write CONFIG} FileUtils::touch COOKIE_F edit = (ENV["EDITOR"] || ENV["EDIT"] || "gvim") + " #{ CONFIG_F }" system edit or puts "edit #{ CONFIG_F }" exit # # login mode # when %r/login/ page, msg = "/account/login.php", "post_content" form = { "return_to" => "", "form_loginname" => username, "form_pw" => password, "login" => "Login" } # # create_package mode # when %r/create_package/ page, msg = "/frs/admin/index.php", "post_content" group_id, package_name = ARGV abort "no " unless group_id abort "no " unless package_name unless group_id.to_s =~ %r/^\d+$/ key = group_id.to_s group_id = config["rubyforge"]["group_ids"][key] abort "no configured for <#{ key }>" unless group_id end is_private = opts["is_private"] || config["defaults"]["is_private"] is_public = is_private ? 0 : 1 form = { "func" => "add_package", "group_id" => group_id, "package_name" => package_name, "is_public" => is_public, "submit" => "Create This Package", } # # delete_package mode # when %r/delete_package/ page, msg = "/frs/admin/index.php", "post_content" group_id, package_id = ARGV abort "no " unless group_id abort "no " unless package_id unless group_id.to_s =~ %r/^\d+$/ key = group_id.to_s group_id = config["rubyforge"]["group_ids"][key] abort "no configured for <#{ key }>" unless group_id end unless package_id.to_s =~ %r/^\d+$/ key = package_id.to_s package_id = config["rubyforge"]["package_ids"][key] abort "no configured for <#{ key }>" unless package_id end form = { "func" => "delete_package", "group_id" => group_id, "package_id" => package_id, "sure" => "1", "really_sure" => "1", "submit" => "Delete", } # # add_release mode # when %r/add_release/ page, msg = "/frs/admin/qrs.php", "post_content" group_id, package_id, release_name, userfile = ARGV abort "no " unless group_id abort "no " unless package_id abort "no " unless release_name abort "no " unless userfile unless group_id.to_s =~ %r/^\d+$/ key = group_id.to_s group_id = config["rubyforge"]["group_ids"][key] abort "no configured for <#{ key }>" unless group_id end unless package_id.to_s =~ %r/^\d+$/ key = package_id.to_s package_id = config["rubyforge"]["package_ids"][key] abort "no configured for <#{ key }>" unless package_id end userfile = open userfile release_date = opts["release_date"] type_id = opts["type_id"] processor_id = opts["processor_id"] release_notes = opts["release_notes"] release_changes = opts["release_changes"] preformatted = opts["preformatted"] release_date ||= Time::now.strftime("%Y-%m-%d %H:%M") type_id ||= userfile.path[%r|\.[^\./]+$|] unless type_id.to_s =~ %r/^\d+$/ key = type_id.to_s type_id = config["rubyforge"]["type_ids"][key] abort "no configured for <#{ key }>" unless type_id end processor_id ||= "Any" unless processor_id.to_s =~ %r/^\d+$/ key = processor_id.to_s processor_id = config["rubyforge"]["processor_ids"][key] abort "no configured for <#{ key }>" unless processor_id end release_notes = IO::read(release_notes) if release_notes and test(?e, release_notes) release_changes = IO::read(release_changes) if release_changes and test(?e, release_changes) preformatted = preformatted ? 1 : 0 form = { "group_id" => group_id, "package_id" => package_id, "release_name" => release_name, "release_date" => release_date, "type_id" => type_id, "processor_id" => processor_id, "release_notes" => release_notes, "release_changes" => release_changes, "preformatted" => preformatted, "userfile" => userfile, "submit" => "Release File" } boundary = Array::new(8){ "%2.2d" % rand(42) }.join('__') extheader['content-type'] = "multipart/form-data; boundary=___#{ boundary }___" # # bad mode # else abort USAGE end # # commit http transaction # if [msg, page, form].all? client = HTTPAccess2::Client::new ENV["HTTP_PROXY"] client.debug_dev = STDERR if ENV["RUBYFORGE_DEBUG"] || ENV["DEBUG"] client.set_cookie_store cookie_jar # # hack to fix http-access2 redirect bug/feature # client.redirect_uri_callback = lambda do |res| page = res.header['location'].first page =~ %r/http/ ? page : "#{ config['uri'] }/#{ page }" end response = client.send "#{ msg }", "#{ config['uri'] }/#{ page }", form, extheader client.save_cookie_store end exit 0 # # hack to fix http-access2 cookie selection bug # BEGIN { begin require "rubygems" require_gem "http-access2" rescue LoadError require "http-access2" end 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 }