rubyforge-2.0.4/0000755000175000017500000000000011565305613012532 5ustar clintclintrubyforge-2.0.4/metadata.yml0000644000175000017500000000401411565305613015034 0ustar clintclint--- !ruby/object:Gem::Specification name: rubyforge version: !ruby/object:Gem::Version version: 2.0.4 platform: ruby authors: - Ryan Davis - Eric Hodel - Ara T Howard - Tom Copeland autorequire: bindir: bin cert_chain: [] date: 2010-03-01 00:00:00 -05:00 default_executable: dependencies: - !ruby/object:Gem::Dependency name: json_pure type: :runtime version_requirement: version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 1.1.7 version: description: |- A script which automates a limited set of rubyforge operations. * Run 'rubyforge help' for complete usage. * Setup: For first time users AND upgrades to 0.4.0: * rubyforge setup (deletes your username and password, so run sparingly!) * edit ~/.rubyforge/user-config.yml * rubyforge config * For all rubyforge upgrades, run 'rubyforge config' to ensure you have latest. email: - ryand-ruby@zenspider.com - drbrain@segment7.net - ara.t.howard@gmail.com - tom@infoether.com executables: - rubyforge extensions: [] extra_rdoc_files: - History.txt - Manifest.txt - README.txt files: - History.txt - Manifest.txt - README.txt - Rakefile - bin/rubyforge - lib/rubyforge.rb - lib/rubyforge/client.rb - test/test_rubyforge.rb - test/test_rubyforge_client.rb has_rdoc: true homepage: http://codeforpeople.rubyforge.org/rubyforge/ licenses: [] post_install_message: rdoc_options: - --main - README.txt require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: "0" version: required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: "0" version: requirements: [] rubyforge_project: codeforpeople rubygems_version: 1.3.5 signing_key: specification_version: 3 summary: A script which automates a limited set of rubyforge operations test_files: - test/test_rubyforge.rb - test/test_rubyforge_client.rb rubyforge-2.0.4/README.txt0000644000175000017500000000332511565305613014233 0ustar clintclint= Rubyforge * http://codeforpeople.rubyforge.org/rubyforge/ * http://rubyforge.org/projects/codeforpeople/ == Description A script which automates a limited set of rubyforge operations. * Run 'rubyforge help' for complete usage. * Setup: For first time users AND upgrades to 0.4.0: * rubyforge setup (deletes your username and password, so run sparingly!) * edit ~/.rubyforge/user-config.yml * rubyforge config * For all rubyforge upgrades, run 'rubyforge config' to ensure you have latest. == Synopsis rubyforge [options]* mode [mode_args]* == REQUIREMENTS * hoe * json * rubygems == INSTALL * sudo gem install rubyforge == LICENSE (The MIT License) Copyright (c) Ryan Davis, Eric Hodel, Ara T Howard. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. rubyforge-2.0.4/Rakefile0000644000175000017500000000171411565305613014202 0ustar clintclint# -*- ruby -*- require 'rubygems' require 'hoe' require 'json' abort "you _must_ install this gem to release it" if ENV['VERSION'] && ENV['VERSION'] != RubyForge::VERSION Hoe.plugin :email Hoe.spec "rubyforge" do developer 'Ryan Davis', 'ryand-ruby@zenspider.com' developer 'Eric Hodel', 'drbrain@segment7.net' developer 'Ara T Howard', 'ara.t.howard@gmail.com' developer 'Tom Copeland', 'tom@infoether.com' multiruby_skip << "rubinius" extra_deps << ["json_pure",">= 1.1.7"] self.rubyforge_name = "codeforpeople" self.need_tar = false end task :postrelease => :announce task :backup do Dir.chdir File.expand_path("~/.rubyforge") do cp "user-config.yml", "user-config.yml.bak" cp "auto-config.yml", "auto-config.yml.bak" end end task :restore do Dir.chdir File.expand_path("~/.rubyforge") do cp "user-config.yml.bak", "user-config.yml" cp "auto-config.yml.bak", "auto-config.yml" end end # vim:syntax=ruby rubyforge-2.0.4/bin/0000755000175000017500000000000011565305613013302 5ustar clintclintrubyforge-2.0.4/bin/rubyforge0000755000175000017500000001534111565305613015240 0ustar clintclint#! /usr/bin/env ruby $VERBOSE = true $:.unshift(File::join(File::dirname(File::dirname(__FILE__)), "lib")) require 'getoptlong' require 'rubyforge' PROGRAM = File::basename $0 USAGE = <<-EOL SYNOPSIS #{ PROGRAM } [options]* mode [mode_args]* DESCRIPTION simplistic script which automates a limited set of rubyforge operations MODES setup() initializes your .rubyforge directory. you need to run this first before doing anything else. example : #{ PROGRAM } setup config([project]) Helps you populate your auto-config.yml file by scraping rubyforge and getting your groups, projects, and releases. example : #{ PROGRAM } config #{ PROGRAM } config myproject names() Prints out the names of your configured groups and projects. example : #{ PROGRAM } names create_package(group_id, package_name) creates the named package under the specified group. example : #{ PROGRAM } create_package 1024 traits #{ PROGRAM } create_package codeforpeople.com traits 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_release codeforpeople.com traits 0.8.0 traits-0.8.0.gem #{ PROGRAM } add_release codeforpeople.com traits 0.8.0 traits-0.8.0.tgz #{ PROGRAM } add_release 1024 1242 0.8.0 traits-0.8.0.gem #{ PROGRAM } add_release 1024 1242 0.8.0 traits-0.8.0.gem add_file(group_id, package_id, release_id, userfile) add a file to an existing release under the specified group_id, package_id, and release_id example : #{ PROGRAM } add_file codeforpeople.com traits 0.8.0 traits-0.8.0.gem #{ PROGRAM } add_file codeforpeople.com traits 0.8.0 traits-0.8.0.tgz #{ PROGRAM } add_file 1024 1242 0.8.0 traits-0.8.0.gem delete_package(group_id, package_name) deletes a package and all its files. example : #{ PROGRAM } delete_package codeforpeople.com traits #{ PROGRAM } delete_package 1024 traits post_news(group_id, summary, details) posts a news item to the specified group example : #{ PROGRAM } post_news codeforpeople.com "new release" "this release is great!" #{ PROGRAM } post_news 1024 traits "new release" "this release is great!" NOTES - In order to use group_id, package_id, or release_id by name, rather than number, you must edit the rubyforge[group_ids] and rubyforge[package_ids] translation tables in your config.yml. See the config command for more information and help. TODO - add error checking. this may require mods to the REST API as well to ensure that it returns useful error codes. OPTIONS global : --help , -h this message --config , -c specify a config file (default #{ RubyForge::CONFIG_F }) --username , -u specify username, taken from config otherwise --password , -p specify password, taken from config otherwise 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 EOL mode = ARGV.shift opts = GetoptLong::new( [ "--help" , "-h" , GetoptLong::NO_ARGUMENT ], [ "--force" , "-F" , GetoptLong::NO_ARGUMENT ], [ "--username" , "-u" , GetoptLong::REQUIRED_ARGUMENT ], [ "--password" , "-p" , 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, (k, v)| h.update k.delete('-') => v } rubyforge = RubyForge.new rubyforge.configure opts mode = "help" if opts["help"] case mode when %r/help/ USAGE.display when %r/login/ puts "No need to call login since each API request is authenticated separately" when %r/logout/ puts "No need to call logout since each API request is authenticated separately" when %r/setup/ rubyforge.setup when %r/config/ project = ARGV.shift if project then rubyforge.scrape_project(project) else rubyforge.scrape_config end when %r/names/ rf = rubyforge.autoconfig puts "groups : #{rf["group_ids"].keys.sort.join(", ")}" puts "packages: #{rf["package_ids"].keys.sort.join(", ")}" 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 group_id = Integer(group_id) rescue group_id rubyforge.create_package group_id, package_name when %r/post_news/ group_id, summary, details = ARGV abort "no " unless group_id group_id = Integer(group_id) rescue group_id rubyforge.post_news group_id, summary, details when %r/delete_package/ group_id, package_id = ARGV abort "no " unless group_id abort "no " unless package_id group_id = Integer(group_id) rescue group_id package_id = Integer(package_id) rescue package_id rubyforge.delete_package group_id, package_id when %r/add_release/ 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 group_id = Integer(group_id) rescue group_id package_id = Integer(package_id) rescue package_id rubyforge.add_release group_id, package_id, release_name, userfile when %r/add_file/ group_id, package_id, release_id, userfile = ARGV abort "no " unless group_id abort "no " unless package_id abort "no " unless release_id abort "no " unless userfile group_id = Integer(group_id) rescue group_id package_id = Integer(package_id) rescue package_id release_id = Integer(release_id) rescue release_id rubyforge.add_file group_id, package_id, release_id, userfile else abort USAGE end rubyforge-2.0.4/test/0000755000175000017500000000000011565305613013511 5ustar clintclintrubyforge-2.0.4/test/test_rubyforge_client.rb0000644000175000017500000000252311565305613020441 0ustar clintclintrequire 'test/unit' unless defined? $ZENTEST and $ZENTEST require 'rubyforge' class RubyForge::FakeAgent class << self attr_accessor :t_data, :t_request end def initialize(*args) end def request(request, data) self.class.t_request = request self.class.t_data = data response = Net::HTTPOK.new('1.1', 200, '') def response.read_body; ''; end return response end class Post def initialize(*args) @args = args @stuff = {} end def [] key @stuff[key.downcase] end def []= key, val @stuff[key.downcase] = val end def method_missing(*stuff) # warn stuff.inspect end end end class TestRubyForgeClient < Test::Unit::TestCase def setup @client = RubyForge::Client.new @client.agent_class = RubyForge::FakeAgent RubyForge::FakeAgent.t_data = :unassigned RubyForge::FakeAgent.t_request = :unassigned end def test_post_with_params @client.post_content('http://example.com', { :f => 'adsf aoeu'}, {}, {"username" => "tom", "password" => "secret"}) assert_equal('f=adsf+aoeu', RubyForge::FakeAgent.t_data) @client.post_content('http://example.com', { :a => 'b', :c => 'd' }, {}, {"username" => "tom", "password" => "secret"}) assert_equal('a=b&c=d', RubyForge::FakeAgent.t_data) end end rubyforge-2.0.4/test/test_rubyforge.rb0000644000175000017500000002110711565305613017102 0ustar clintclintrequire 'test/unit' unless defined? $ZENTEST and $ZENTEST $TESTING = true require 'rubyforge' require 'tmpdir' class RubyForge attr_writer :client alias :old_save_autoconfig :save_autoconfig def save_autoconfig # raise "not during test" end end class RubyForge::FakeClient def form; end def post_content(*args) FakeRubyForge::HTML end def get_content(*args) URI::HTTP.data.join("\n") end end class FakeRubyForge < RubyForge JSON = '{"release_id" : 42}' attr_accessor :page, :form, :extheader, :requests, :scrape def run(page, form, extheader={}) @page, @form, @extheader = page, form, extheader @requests ||= [] @requests << { :url => page, :form => form, :headers => extheader } JSON end def scrape_project(proj) @scrape ||= [] @scrape << proj end end # TODO: remove this and make rubyforge use Client exclusively class URI::HTTP def self.data @data ||= [] end def read self.class.data.shift or raise "no more data" end end class TestRubyForge < Test::Unit::TestCase def setup srand(0) util_new FakeRubyForge end def teardown # if defined? @old_autoconfig then # @rubyforge.autoconfig.replace @old_autoconfig # @rubyforge.save_autoconfig # end end def test_new_with_proxy_uses_a_proxy_class client = RubyForge::Client.new('http://localhost:8808/') assert client.agent_class.proxy_class?, 'agent class should be a proxy' end def test_new_with_bad_proxy_uses_normal_http client = RubyForge::Client.new('asdfkjhalksdfjh') assert !client.agent_class.proxy_class?, 'agent class should not be a proxy' end def test_initialize_bad user_data = { "uri" => "http://api.rubyforge.org", "is_private" => false, "username" => "username", "password" => "password" } assert_raise RuntimeError do rf = RubyForge.new user_data rf.configure "username" => nil end assert_raise RuntimeError do rf = RubyForge.new user_data rf.configure "password" => nil end end def test_setup # TODO raise NotImplementedError, 'Need to write test_setup' end def test_create_package @rubyforge.create_package(42, 'woot_pkg') util_run('/groups/42/packages', "package[is_public]" => 1, "package[name]" => "woot_pkg") end def test_delete_package @rubyforge.delete_package(42, 666) util_delete_package end def test_delete_package_package_name @rubyforge.delete_package(42, "woot_pkg") util_delete_package end def test_delete_package_undefined_package_name assert_raise RuntimeError do @rubyforge.delete_package(42, "blah") end end def test_delete_package_group_name @rubyforge.delete_package("seattlerb", 666) util_delete_package end def test_delete_package_undefined_group_name assert_raise RuntimeError do @rubyforge.delete_package("blah", 666) end end def test_post_news @rubyforge.post_news("seattlerb", "my summary", "my news") util_run("/groups/42/news_bytes", "news_byte[details]" => "my news", "news_byte[summary]" => "my summary") end def test_add_release_undefined_package_name assert_raise RuntimeError do @rubyforge.add_release(42, "blah", '1.2.3', __FILE__) end end def test_add_release_undefined_group_name assert_raise RuntimeError do @rubyforge.add_release("blah", 666, '1.2.3', __FILE__) end end def test_lookup_id assert_equal 43, @rubyforge.lookup("package", 43) end def test_lookup_string_number assert_raise RuntimeError do @rubyforge.lookup("package", "43") end end def test_lookup_name @rubyforge.autoconfig["package_ids"]["ringy_dingy"] = 314 assert_equal 314, @rubyforge.lookup("package", "ringy_dingy") end def test_lookup_undefined assert_raise RuntimeError do @rubyforge.lookup("package", "blah") end end def test_add_file @rubyforge.autoconfig["package_ids"]["ringy_dingy"] = 314 @rubyforge.autoconfig["release_ids"]["ringy_dingy"] ||= {} @rubyforge.autoconfig["release_ids"]["ringy_dingy"]["1.2.3"] = 43 filepath, contents = make_a_tmp_file @rubyforge.add_file('seattlerb', 'ringy_dingy', '1.2.3', filepath) util_run('/releases/43/files.js', { "file[type_id]" => 9999, "file[processor_id]" => 8000, "file[filename]"=> File.basename(filepath), "contents" => File.read(filepath) }) end def test_add_release @rubyforge.add_release(42, 666, '1.2.3') util_add_release end def test_add_release_with_a_file filepath, contents = make_a_tmp_file @rubyforge.add_release(42, 666, '1.2.3', filepath) add_release = ({ :url=>"/packages/666/releases", :form=>{ "release[name]" => "1.2.3", "release[notes]" => nil, "release[changes]" => nil, "release[preformatted]"=>0, "release[release_date]" => "today"}, :headers=> {}}) add_file = ({ :url => '/releases/42/files.js', :form => {"file[type_id]" => 9999, "file[processor_id]" => 8000, "file[filename]"=> File.basename(filepath), "contents" => File.read(filepath) }, :headers => {}}) expected = [add_release, add_file] result = @rubyforge.requests result.each do |r| r[:form].delete "userfile" end assert_equal expected, result end def test_add_release_package_name @rubyforge.add_release(42, "woot_pkg", '1.2.3') util_add_release end def test_add_release_group_name @rubyforge.add_release("seattlerb", 666, '1.2.3') util_add_release end def test_scrape_project orig_stdout = $stdout orig_stderr = $stderr $stdout = StringIO.new $stderr = StringIO.new util_new RubyForge # TODO: switch to Fake @rubyforge.autoconfig.each { |k,v| v.clear } URI::HTTP.data << '{"group" : {"group_id":1513}}' URI::HTTP.data << '[{"package" : {"package_id":4566, "package_name":"1.3.1"}}]' # @rubyforge.scrape << < <-EOF # URI::HTTP.data << <<-EOF # # EOF # @rubyforge.scrape_project('my_project') rescue "Hm, for some reason this technique of loading up data on URI::HTTP isn't working here. Not sure why." # # expected = { # "group_ids" => { "my_project" => 1513 }, # "package_ids" => { "ar_mailer" => 4566 }, # "processor_ids" => { "i386" => 1000, "i387" => 1001 }, # "release_ids" => { # "ar_mailer" => { "1.2.0" => 12185, "1.3.1" => 13368 } # }, # "type_ids" => {}, # } # # assert_equal expected, @rubyforge.autoconfig ensure $stdout = orig_stdout $stderr = orig_stderr end def util_new(klass) user_data = { "uri" => "http://api.rubyforge.org", "is_private" => false, "username" => "username", "password" => "password" } auto_data = { "group_ids" => {}, "package_ids" => {}, "release_ids" => Hash.new { |h,k| h[k] = {} }, "type_ids" => {}, "processor_ids" => {"Any"=>8000}, } @rubyforge = klass.new user_data, auto_data @rubyforge.client = RubyForge::FakeClient.new @rubyforge.userconfig["release_date"] = "today" @rubyforge.autoconfig["type_ids"][".rb"] = 9999 @rubyforge.autoconfig["group_ids"]["seattlerb"] = 42 @rubyforge.autoconfig["package_ids"]["woot_pkg"] = 666 end def util_run(page, form={}, extheader={}) form_result = @rubyforge.form assert_equal page, @rubyforge.page.to_s assert_equal form, form_result assert_equal extheader, @rubyforge.extheader end def util_add_release util_run("/packages/666/releases", { "release[name]" => "1.2.3", "release[notes]" => nil, "release[changes]" => nil, "release[preformatted]"=>0, "release[release_date]" => "today"}) end def util_delete_package util_run('/packages/666', "_method" => "delete") end def make_a_tmp_file content = "Merely a test" tmp_file = File.join(Dir.tmpdir, "test.rb") File.open(tmp_file, "w") { |f| f.syswrite(content) } [tmp_file, content] end end rubyforge-2.0.4/History.txt0000644000175000017500000000755311565305613014746 0ustar clintclint=== 2.0.4 / 2010-02-28 * Stubbed out login and logout commands to print a warning and return === 2.0.3 / 2009-10-11 * Added check to ensure user-config contains 'api.rubyforge.org' (Ryan Davis) * Removed DEBUG global === 2.0.2 / 2009-10-05 * Switched JSON gem dependency over to json_pure. === 2.0.1 / 2009-10-02 * Added a missing dependency on JSON gem to the spec. === 2.0.0 / 2009-09-21 * Modified to use RubyForge REST API rather than scraping HTML. === 1.0.5 / 2009-09-15 * Added experimental script to sync RF trackers with release names. * Fixed that damned processor_id bug. (hinegardner) * The century usually doesn't change across runs. Refactor. * Fix use of Time.utc if args.size < 10, tho seems unnecessary. === 1.0.4 / 2009-07-21 * Uses the passed in proxy, if provided. (Thanks sdabet, RF #24071). * Update group_id pattern for scraping project configs you are only a member of. * Update regexp used to validate login page to be less restrictive. * Fixed --help to not require an argument. * add --force flag for login command to ignore previous cookie. (or use logout) === 1.0.3 / 2009-02-26 * Fixed nil error in our Net::HTTP patches. * Removed password from warning if the login possibly failed. === 1.0.2 / 2009-01-05 * All webby commands now login automatically. * Login now no-ops if it already has a session cookie. * Added logout command. * Much more of the config is self-repairing, but still not bulletproof yet. === 1.0.1 / 2008-10-22 * Fixed multipart form upload so it isn't url escaping the data. DOH. * Affects release_notes and release_changes, but never reported for 5 months. === 1.0.0 / 2008-05-20 * Removed HTTPAccess2, thanks to Aaron Patterson. Even tho he's whiny. * Changed initialize/configure to make testing scream. 100x faster. === 0.4.5 / 2008-03-11 * Update for Ruby 1.9.0. * Updated History, Rakefile, and Readme for new hoe abilities. * Added config backup/restore rake tasks (for testing). === 0.4.4 / 2007-08-13 * New type_id values will merge with extant data. (self-repairing data is Good) * Scrape processor_ids, merging in with extant data. * Default to "Other" if a file's type is unrecognized. === 0.4.3 / 2007-07-23 * Set mode on .rubyforge directory to 700. * Fix fetching of user id when user has no releases. === 0.4.2 / 2007-05-21 * Fix for windoze users (spaces in path). * Added check for extant release. * Added default hash for first-time releases. === 0.4.1 / 2007-03-08 * Verify that login succeeded and warn against if not (prolly should raise). * Print a friendly error if you have the wrong package id. * Handle upload error in add_release a bit better. === 0.4.0 / 2007-01-09 * config.yml split and moved to user-config.yml (up to the user to do). * auto-config.yml now generated via config command. * @config renamed to @userconfig. * @config["rubyforge"] moved to @autoconfig. * Added save_autoconfig. * Pulled scrape_project from scrape_config. * scrape_config no longer takes a user param. Use opts to specify. * scrape_project, add_project, add/remove_release now save automatically. === 0.3.2 / 2006-11-29 * Fixed file uploads for windows. * Correctly scrape releases with funky characters. === 0.3.1 / 2006-10-24 * Added SSL login. * Added yet more debugging output if $DEBUG. === 0.3.0 / 2006-09-30 * Added more debugging output if $DEBUG * Added news posting. * Added multiple file release to add_release (uses add_file for extras). * add_release now returns release_id * Fixed config scraper to include '-' in names. === 0.2.1 / 2006-09-14 * Gemspec was too loose about packaging. Now using manifest. === 0.2.0 / 2006-09-13 * Split original script into script and library. * Added tests for library. * Refactored heavily. * Added "config" command to scrape group/project/release ids from rubyforge. * Added "names" command to help pick groups and projects. * Added "add_file" command to add a file to an existing release. rubyforge-2.0.4/lib/0000755000175000017500000000000011565305613013300 5ustar clintclintrubyforge-2.0.4/lib/rubyforge.rb0000644000175000017500000002563111565305613015640 0ustar clintclint#! /usr/bin/env ruby -w require 'json' require 'enumerator' require 'fileutils' require 'yaml' require 'open-uri' require 'rubyforge/client' $TESTING = false unless defined? $TESTING class RubyForge # :stopdoc: VERSION = '2.0.4' HOME = ENV["HOME"] || ENV["HOMEPATH"] || File::expand_path("~") RUBYFORGE_D = File::join HOME, ".rubyforge" CONFIG_F = File::join RUBYFORGE_D, "user-config.yml" # We must use __FILE__ instead of DATA because this is now a library # and DATA is relative to $0, not __FILE__. config = File.read(__FILE__).split(/__END__/).last.gsub(/#\{(.*)\}/) {eval $1} CONFIG = YAML.load(config) # :startdoc: # TODO: add an autoconfig method that is self-repairing, removing key checks attr_reader :userconfig, :autoconfig def initialize(userconfig=nil, autoconfig=nil, opts=nil) # def initialize(userconfig=CONFIG_F, opts={}) @userconfig, @autoconfig = userconfig, autoconfig @autoconfig ||= CONFIG["rubyforge"].dup @userconfig.merge! opts if opts @client = nil @uri = nil end # These are no-ops now, but we'll keep them here for backwards compatibility def login ; end def logout ; end def configure opts = {} user_path = CONFIG_F dir, file = File.split(user_path) @userconfig = if test(?e, user_path) then YAML.load_file(user_path) else CONFIG end.merge(opts) @autoconfig_path = File.join(dir, file.sub(/^user/, 'auto')) @autoconfig = if test(?e, @autoconfig_path) then YAML.load_file(@autoconfig_path) else CONFIG["rubyforge"].dup end @autoconfig["type_ids"] = CONFIG['rubyforge']['type_ids'].dup raise "no " unless @userconfig["username"] raise "no " unless @userconfig["password"] self end def force @userconfig['force'] end def uri uri = @userconfig['uri'] abort "Using new REST api, but uri isn't api.rubyforge.org. run `rubyforge setup` and fix please" if uri =~ /rubyforge.org/ and uri !~ /api.rubyforge.org/ @uri ||= URI.parse uri end def setup FileUtils::mkdir_p RUBYFORGE_D, :mode => 0700 unless test ?d, RUBYFORGE_D test ?e, CONFIG_F and FileUtils::mv CONFIG_F, "#{CONFIG_F}.bak" config = CONFIG.dup config.delete "rubyforge" open(CONFIG_F, "w") { |f| f.write YAML.dump(config) } edit = (ENV["EDITOR"] || ENV["EDIT"] || "vi") + " '#{CONFIG_F}'" system edit or puts "edit '#{CONFIG_F}'" end def save_autoconfig File.open(@autoconfig_path, "w") do |file| YAML.dump @autoconfig, file end end def scrape_config username = @userconfig['username'] %w(group package processor release).each do |type| @autoconfig["#{type}_ids"].clear if @autoconfig["#{type}_ids"] end json = get_via_rest_api "/users/#{username}/groups.js" projects = json.collect {|group| group['group']['unix_group_name'] } puts "Fetching #{projects.size} projects" projects.each do |project| scrape_project(project) end end def get_via_rest_api(path) url = "#{self.uri}#{path}" puts "Hitting REST API: #{url}" if $DEBUG JSON.parse(client.get_content(url, {}, {}, @userconfig)) end def scrape_project(project) data = { "group_ids" => {}, "package_ids" => {}, "processor_ids" => Hash.new { |h,k| h[k] = {} }, "release_ids" => Hash.new { |h,k| h[k] = {} }, } unless data["group_ids"].has_key? project then json = get_via_rest_api "/groups/#{project}.js" group_id = json["group"]["group_id"].to_i data["group_ids"][project] = group_id end # Get project's packages json = get_via_rest_api "/groups/#{project}/packages.js" json.each do |package| data["package_ids"][package["package"]["name"]] = package["package"]["package_id"] # Get releases for this package json = get_via_rest_api "/packages/#{package["package"]["package_id"]}/releases.js" json.each do |release| data["release_ids"][package["package"]["name"]][release["name"]] = release["release_id"] end end # Get processor ids if @autoconfig['processor_ids'].nil? || @autoconfig['processor_ids'].empty? puts "Fetching processor ids" if $DEBUG json = get_via_rest_api "/processors.js" json.each do |processor| data["processor_ids"][processor["processor"]["name"]] = processor["processor"]["processor_id"] end end data.each do |key, val| @autoconfig[key] ||= {} @autoconfig[key].merge! val end save_autoconfig end def create_package(group_id, package_name) page = "/groups/#{group_id}/packages" group_id = lookup "group", group_id is_private = @userconfig["is_private"] is_public = is_private ? 0 : 1 form = { "package[name]" => package_name, "package[is_public]" => is_public } run page, form group_name = @autoconfig["group_ids"].invert[group_id] scrape_project(group_name) end ## # Posts news item to +group_id+ (can be name) with +subject+ and +body+ def post_news(group_id, subject, body) # TODO - what was the post_changes parameter for? form = { "news_byte[summary]" => subject, "news_byte[details]" => body } group_id = lookup "group", group_id url = "/groups/#{group_id}/news_bytes" run url, form end def delete_package(group_id, package_id) group_id = lookup "group", group_id package_id = lookup "package", package_id package_name = @autoconfig["package_ids"].invert[package_id] @autoconfig["package_ids"].delete package_name @autoconfig["release_ids"].delete package_name save_autoconfig url = "/packages/#{package_id}" run url, {"_method" => "delete"} end def add_release(group_id, package_id, release_name, *files) group_id = lookup "group", group_id package_id = lookup "package", package_id release_date = @userconfig["release_date"] release_notes = @userconfig["release_notes"] release_changes = @userconfig["release_changes"] preformatted = @userconfig["preformatted"] release_date ||= Time.now.strftime("%Y-%m-%d %H:%M") release_notes = IO::read(release_notes) if test(?e, release_notes) if release_notes release_changes = IO::read(release_changes) if test(?e, release_changes) if release_changes preformatted = preformatted ? 1 : 0 form = { "release[name]" => release_name, "release[release_date]" => release_date, "release[notes]" => release_notes, "release[changes]" => release_changes, "release[preformatted]" => preformatted, } url = "/packages/#{package_id}/releases" json = run url, form release_id = JSON.parse(json)["release_id"].to_i rescue nil unless release_id then puts json if $DEBUG raise "Couldn't get release_id, upload failed?" end # FIXME #raise "Invalid package_id #{package_id}" if html[/Invalid package_id/] #raise "You have already released this version." if html[/That filename already exists in this project/] files.each do |file| add_file(group_id, package_id, release_id, file) end package_name = @autoconfig["package_ids"].invert[package_id] raise "unknown package name for #{package_id}" if package_name.nil? @autoconfig["release_ids"][package_name] ||= {} @autoconfig["release_ids"][package_name][release_name] = release_id save_autoconfig release_id end ## # add a file to an existing release under the specified group_id, # package_id, and release_id # # example : # add_file("codeforpeople", "traits", "0.8.0", "traits-0.8.0.gem") # add_file("codeforpeople", "traits", "0.8.0", "traits-0.8.0.tgz") # add_file(1024, 1242, "0.8.0", "traits-0.8.0.gem") def add_file(group_name, package_name, release_name, userfile) type_id = @userconfig["type_id"] group_id = lookup "group", group_name package_id = lookup "package", package_name release_id = (Integer === release_name) ? release_name : lookup("release", package_name)[release_name] url = "/releases/#{release_id}/files.js" userfile = open userfile, 'rb' type_id ||= userfile.path[%r|\.[^\./]+$|] type_id = (lookup "type", type_id rescue lookup "type", ".oth") processor_id = @userconfig["processor_id"] processor_id ||= "Any" processor_id = lookup "processor", processor_id form = { "file[filename]" => File.basename(userfile.path), "file[processor_id]" => processor_id, "file[type_id]" => type_id, "contents" => userfile.read } run url, form end def client return @client if @client @client = RubyForge::Client::new ENV["HTTP_PROXY"] @client.debug_dev = STDERR if ENV["RUBYFORGE_DEBUG"] || ENV["DEBUG"] || $DEBUG @client end def run(page, form, extheader={}) # :nodoc: uri = self.uri + page puts "client.post_content #{uri.inspect}, #{form.inspect}, #{extheader.inspect}" if $DEBUG response = client.post_content uri, form, extheader, @userconfig puts response if $DEBUG response end def lookup(type, val) # :nodoc: unless Fixnum === val then key = val.to_s val = @autoconfig["#{type}_ids"][key] raise "no <#{type}_id> configured for <#{ key }>" unless val end val end end __END__ # # base rubyforge uri - store in #{ CONFIG_F } # uri : http://api.rubyforge.org # # this must be your username # username : tom # # this must be your password # password : password # # defaults for some values # is_private : false # AUTOCONFIG: rubyforge : # # map your group names to their rubyforge ids # group_ids : codeforpeople : 1024 support : 5 # # map your package names to their rubyforge ids # package_ids : traits : 1241 # # map your package names to their rubyforge ids # release_ids : traits : 1.2.3 : 666 # # 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.tar.bz2 : 5010 .src.gz : 5020 .src.tar.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 .pem : 1500 # # map processor names to rubyforge ids # processor_ids : Other : 9999 rubyforge-2.0.4/lib/rubyforge/0000755000175000017500000000000011565305613015304 5ustar clintclintrubyforge-2.0.4/lib/rubyforge/client.rb0000644000175000017500000000725411565305613017117 0ustar clintclintrequire 'webrick/cookie' require 'net/http' require 'net/https' # clean up warnings caused by web servers that send down 2 digit years class Time CENTURY = Time.now.year / 100 * 100 class << self alias :old_utc :utc def utc(*args) args[0] += CENTURY if args[0] < 100 old_utc(*args) end end end unless Time.respond_to? :old_utc # clean up "using default DH parameters" warning for https class Net::HTTP alias :old_use_ssl= :use_ssl= def use_ssl= flag self.old_use_ssl = flag @ssl_context.tmp_dh_callback = proc {} if @ssl_context end end unless Net::HTTP.public_instance_methods.include? "old_use_ssl=" class RubyForge class Client attr_accessor :debug_dev, :ssl_verify_mode, :agent_class def initialize(proxy = nil) @debug_dev = nil @ssl_verify_mode = OpenSSL::SSL::VERIFY_NONE if proxy begin proxy_uri = URI.parse(proxy) @agent_class = Net::HTTP::Proxy(proxy_uri.host,proxy_uri.port) rescue URI::InvalidURIError end end @agent_class ||= Net::HTTP end def post_content(uri, form = {}, headers = {}, userconfig = nil) uri = URI.parse(uri) unless uri.is_a?(URI) request = agent_class::Post.new(uri.request_uri) execute(request, uri, form, headers, userconfig) end def get_content(uri, query = {}, headers = {}, userconfig = nil) uri = URI.parse(uri) unless uri.is_a?(URI) request = agent_class::Get.new(uri.request_uri) execute(request, uri, query, headers, userconfig) end def execute(request, uri, parameters = {}, headers = {}, userconfig = nil) { 'content-type' => 'application/x-www-form-urlencoded' }.merge(headers).each { |k,v| request[k] = v } http = agent_class.new( uri.host, uri.port ) if uri.scheme == 'https' && uri.host !~ /localhost/ http.use_ssl = true http.verify_mode = OpenSSL::SSL::VERIFY_NONE end request.basic_auth(userconfig["username"], userconfig["password"]) request_data = case request['Content-Type'] when /boundary=(.*)$/ boundary_data_for($1, parameters) else query_string_for(parameters) end request['Content-Length'] = request_data.length.to_s response = http.request(request, request_data) return response.body if response.class <= Net::HTTPSuccess if response.class <= Net::HTTPRedirection location = response['Location'] unless location =~ /^http/ location = "#{uri.scheme}://#{uri.host}#{location}" end uri = URI.parse(location) execute(agent_class::Get.new(uri.request_uri), uri) end end def boundary_data_for(boundary, parameters) parameters.sort_by {|k,v| k.to_s }.map { |k,v| parameter = "--#{boundary}\r\nContent-Disposition: form-data; name=\"" + WEBrick::HTTPUtils.escape_form(k.to_s) + "\"" if v.respond_to? :path parameter += "; filename=\"#{File.basename(v.path)}\"\r\n" parameter += "Content-Transfer-Encoding: binary\r\n" parameter += "Content-Type: text/plain" end parameter += "\r\n\r\n" if v.respond_to? :path parameter += v.read else parameter += v.to_s end parameter }.join("\r\n") + "\r\n--#{boundary}--\r\n" end def query_string_for(parameters) parameters.sort_by {|k,v| k.to_s }.map { |k,v| k && [ WEBrick::HTTPUtils.escape_form(k.to_s), WEBrick::HTTPUtils.escape_form(v.to_s) ].join('=') }.compact.join('&') end end end rubyforge-2.0.4/Manifest.txt0000644000175000017500000000023111565305613015035 0ustar clintclintHistory.txt Manifest.txt README.txt Rakefile bin/rubyforge lib/rubyforge.rb lib/rubyforge/client.rb test/test_rubyforge.rb test/test_rubyforge_client.rb