#!/usr/bin/env python
+#
+# meathook -- script transactions with the Freshmeat server
import sys
if sys.version[:6] < '2.2.0':
error("you must upgrade to Python 2.2.0 or better to use this script.")
-import xmlrpclib, netrc
+import xmlrpclib, netrc, email.Parser, optparse
class FreshmeatSessionException:
def __init__(self, msg):
freshmeat_xmlrpc = "http://dev.freshmeat.net/xmlrpc/"
def __init__(self, login=None, password=None, verbose=0):
- "Initialize an XML-RPC session to Freshmeat by loggin in."
+ "Initialize an XML-RPC session to Freshmeat by logging in."
+ self.verbose = verbose
# If user didn't supply credentials, fetch from ~/.netrc
if not login:
try:
# Open xml-rpc connection to Freshmeat
self.session = xmlrpclib.Server(FreshmeatSession.freshmeat_xmlrpc,
verbose=verbose)
- # Log in to the service
- self.sid = self.session.login(login, password)
+ # Log in to Freshmeat
+ response = self.session.login( \
+ {"username":login, "password":password})
+ self.sid = response['SID']
+ self.lifetime = response['Validity']
+ if verbose:
+ print "Session ID = %s, lifetime = %s" % (self.sid, self.lifetime)
+
+ def set_project(self, name):
+ "Set the current project."
+ if self.verbose:
+ print "About to look up project"
+ self.pid = self.session.lookup_project_id({'SID':self.sid,'name':name})
+ if self.verbose:
+ print "Project ID is:", self.pid
+ self.branch_list = self.session.fetch_branch_list(self.sid, self.pid)
+ if self.verbose:
+ print "Project branch list is:", self.branch_list
+
+ def add_release(self, release_info, branch=None):
+ "Add a new release to the current project."
+ if not branch and len(self.branch_list) == 1:
+ branch = self.branch_list.keys()[0]
+ if not branch:
+ raise FreshmeatSessionException("branch specification required")
+ self.session.add_release(self.sid, self.pid, branch, dir(release_info))
+
+ def withdraw_release(self, release):
+ pass
def logout(self):
+ "End the session."
return self.session.logout(self.sid)
+freshmeat_field_map = (
+ ("Project", "p", "project"),
+ ("Branch", "b", "project"),
+ ("Version", "v", "version"),
+ ("Changes", "c", "changes"),
+ ("Release-Focus", "r", "release_focus"),
+ ("Hide", "x", "hide_from_frontpage"),
+ ("Home-Page-URL", "H", "url_homepage"),
+ ("Gzipped-Tar-URL", "G", "url_tgz"),
+ ("Bzipped-Tar-URL", "B", "url_bz2"),
+ ("Zipped-Tar-URL", "Z", "url_zip"),
+ ("RPM-URL", "R", "url_rom"),
+ ("Debian-URL", "D", "url_deb"),
+ ("OSX-URL", "O", "url_osx"),
+ ("BSD-Port-URL", "P", "url_bsdport"),
+ ("Purchase-URL", "U", "url_purchase"),
+ ("CVS-URL", "C", "url_cvs"),
+ ("Mailing-List-URL", "L", "url_list"),
+ ("Mirror-Site-URL", "M", "url_mirror"),
+ ("Demo-URL", "E", "url_demo"),
+ )
+
+class FreshmeatMetadata:
+ "Complete release information for a project/branch pair."
+ # Member names are identical to the XML-RPC structure members
+ # for an add_release request.
+ def __init__(self):
+ self.version = None # Version string of new release
+ self.changes = None # Changes list of new release
+ self.release_focus = None # Release focus ID of new release
+ self.hide_from_frontpage = None # 'Y' if release is to be hidden
+ self.url_homepage = None # Homepage
+ self.url_tgz = None # Tar/GZ
+ self.url_bz2 = None # Tar/BZ2
+ self.url_zip = None # Zip
+ self.url_changelog = None # Changelog
+ self.url_rpm = None # RPM package
+ self.url_deb = None # Debian package
+ self.url_osx = None # OS X package
+ self.url_bsdport = None # BSD Ports URL
+ self.url_purchase = None # Purchase
+ self.url_cvs = None # CVS tree (cvsweb)
+ self.url_list = None # Mailing list archive
+ self.url_mirror = None # Mirror site
+ self.url_demo = None # Demo site
+
+ def field_to_header(hdr):
+ hdr = "-".join(map(lambda x: x.capitalize(), hdr.split("_")))
+ for (alias, shortopt, name) in field_map:
+ if hdr == name:
+ hdr = alias
+ break
+ return hdr
+
+ def __repr__(self):
+ res = ""
+ return res
+
+class FreshmeatMetadataFactory:
+ "Factory class for producing Metadata records"
+
+ def __init__(self):
+ self.message_parser = email.Parser.Parser()
+ self.argument_parser = optparse.OptionParser( \
+ usage="usage: %prog [options]")
+ for (msg_field, shortopt, rpc_field) in freshmeat_field_map:
+ self.argument_parser.add_option("-" + shortopt,
+ "--" + msg_field.lower(),
+ dest=rpc_field,
+ help="Set the %s field"%msg_field)
+ self.argument_parser.add_option('-n', '--no-stdin', dest='read',
+ default=True, action='store_false',
+ help='Suppress reading fields from stdin.')
+ self.argument_parser.add_option('-V', '--verbose', dest='verbose',
+ default=True, action='store_false',
+ help='Enable verbose debugging.')
+
+ def header_to_field(self, hdr):
+ hdr = hdr.lower().replace("-", "_")
+ for (alias, shortopt, name) in freshmeat_field_map:
+ if hdr == alias.lower().replace("-", "_"):
+ hdr = name
+ break
+ return hdr
+
+ def getMetadata(self, stream):
+ "Make a Metadata object."
+ data = {}
+ (options, args) = self.argument_parser.parse_args()
+ if options.read:
+ message = self.message_parser.parse(stream)
+ for (key, value) in message.items():
+ data.update({self.header_to_field(key) : value})
+ if not 'comment' in data:
+ data['comment'] = message.get_payload()
+ self.verbose = options.verbose
+
+ # Merge in options from the command line;
+ # they override what's on stdin.
+ data['verbose'] = False
+ for (key, value) in options.__dict__.items():
+ if key not in ('read', 'verbose') and value != None:
+ data[key] = value
+ return data
if __name__ == "__main__":
+ # First, gather update data from stdin and command-line switches
+ factory = FreshmeatMetadataFactory()
+ data = factory.getMetadata(sys.stdin)
try:
- session = FreshmeatSession()
+ # Establish session
+ session = FreshmeatSession(data['verbose'])
+ # Connect with the appropriate project
+ session.set_project(data['project'])
except FreshmeatSessionException, e:
print "meathook:", e.msg
sys.exit(1)
except xmlrpclib.Fault, f:
- print "meathook: %d, %s" % (f.faultCode, f.faultString)
+ print "meathook: %d %s" % (f.faultCode, f.faultString)
sys.exit(1)
+
# end