-# Makefile for the freshmeat-submit tool
+# Makefile for the freecode-submit tool
-VERS=$(shell sed <freshmeat-submit -n -e '/version="\(.*\)"/s//\1/p')
+VERS=$(shell sed <freecode-submit -n -e '/version="\(.*\)"/s//\1/p')
-CODE = freshmeat-submit
-DOCS = README AUTHORS freshmeat-submit.xml COPYING
+CODE = freecode-submit
+DOCS = README AUTHORS freecode-submit.xml COPYING
SOURCES = $(CODE) $(DOCS) Makefile control gold-mega.png
-all: freshmeat-submit.1
+all: freecode-submit.1
-freshmeat-submit.1: freshmeat-submit.xml
- xmlto man freshmeat-submit.xml
+freecode-submit.1: freecode-submit.xml
+ xmlto man freecode-submit.xml
-freshmeat-submit.html: freshmeat-submit.xml
- xmlto html-nochunks freshmeat-submit.xml
+freecode-submit.html: freecode-submit.xml
+ xmlto html-nochunks freecode-submit.xml
-install: freshmeat-submit.1 uninstall
- cp freshmeat-submit ${ROOT}/usr/bin/freshmeat-submit
+install: freecode-submit.1 uninstall
+ cp freecode-submit ${ROOT}/usr/bin/freecode-submit
install -m 755 -o 0 -g 0 -d $(ROOT)/usr/share/man/man1/
- install -m 755 -o 0 -g 0 freshmeat-submit.1 $(ROOT)/usr/share/man/man1/freshmeat-submit.1
+ install -m 755 -o 0 -g 0 freecode-submit.1 $(ROOT)/usr/share/man/man1/freecode-submit.1
uninstall:
- rm -f ${ROOT}/usr/bin/freshmeat-submit
- rm -f ${ROOT}/usr/share/man/man1/freshmeat-submit.1
+ rm -f ${ROOT}/usr/bin/freecode-submit
+ rm -f ${ROOT}/usr/share/man/man1/freecode-submit.1
-freshmeat-submit-$(VERS).tar.gz: $(SOURCES) freshmeat-submit.1
- find $(SOURCES) freshmeat-submit.1 -type f | sed "s:^:freshmeat-submit-$(VERS)/:" >MANIFEST
- (cd ..; ln -s freshmeat-submit freshmeat-submit-$(VERS))
- (cd ..; tar -czf freshmeat-submit/freshmeat-submit-$(VERS).tar.gz `cat freshmeat-submit/MANIFEST`)
- (cd ..; rm freshmeat-submit-$(VERS))
+freecode-submit-$(VERS).tar.gz: $(SOURCES) freecode-submit.1
+ find $(SOURCES) freecode-submit.1 -type f | sed "s:^:freecode-submit-$(VERS)/:" >MANIFEST
+ (cd ..; ln -s freecode-submit freecode-submit-$(VERS))
+ (cd ..; tar -czf freecode-submit/freecode-submit-$(VERS).tar.gz `cat freecode-submit/MANIFEST`)
+ (cd ..; rm freecode-submit-$(VERS))
pychecker:
@echo "Expect: Object (data) has no attribute (update)"
- @ln -f freshmeat-submit freshmeat-submit.py
- @-pychecker --only --limit 50 freshmeat-submit.py
- @rm -f freshmeat-submit.py
+ @ln -f freecode-submit freecode-submit.py
+ @-pychecker --only --limit 50 freecode-submit.py
+ @rm -f freecode-submit.py
clean:
- rm -f *.pyc *.html freshmeat-submit.1 MANIFEST ChangeLog SHIPPER.* *~
+ rm -f *.pyc *.html freecode-submit.1 MANIFEST ChangeLog SHIPPER.* *~
-dist: freshmeat-submit-$(VERS).tar.gz
+dist: freecode-submit-$(VERS).tar.gz
-release: freshmeat-submit-$(VERS).tar.gz freshmeat-submit.html
+release: freecode-submit-$(VERS).tar.gz freecode-submit.html
shipper -u -m -t; make clean
- freshmeat-submit project release news
+ freecode-submit project release news
+
+Thu Nov 03 2011 Eric S. Raymond <esr@snark.thyrsus.com> - 2.3
+ Deal with the Freshmeat to Freecode name change.
+ Means the name of this tool has to change.
Thu Nov 24 2010 Eric S. Raymond <esr@snark.thyrsus.com> - 2.2
Release withdrawal, previously broken, now works.
- (This required a Freshmeat API change.)
+ (This required a site API change.)
Thu Oct 21 2010 Eric S. Raymond <esr@snark.thyrsus.com> - 2.1
Now has a query mode that can retrieve project metadata in editable form.
License changed to BSD.
Mon Oct 17 2010 Eric S. Raymond <esr@snark.thyrsus.com> - 2.0
- Updated to work with the JSON-based 3.0 freshmeat.net API.
+ Updated to work with the JSON-based 3.0 API.
Major rewrite and simplification of the code.
Mon Aug 2 2004 Eric S. Raymond <esr@snark.thyrsus.com> - 1.6
- freshmeat-submit
+ freecode-submit
-freshmeat-submit is a script that supports remote submitssion of release
-updates to Freshmeat via its XML-RPC interface. It is intended for use
+freecode-submit is a script that supports remote submitssion of release
+updates to Freecode via its XML-RPC interface. It is intended for use
in project release scripts.
+
+(This project was formerly called freshmeat-submit. It changed its name
+in 2011 when Freecode did.)
+
Eric S, Raymond
December 2003
# This is not a real Debian control file, but the syntax is upward-compatible.
# It's project metadata for the shipper tool
-Package: freshmeat-submit
+Package: freecode-submit
-Description: submit release information to freshmeat.net
- freshmeat-submit is a script that supports remote submission of
- release updates to Freshmeat via its JSON API. It is intended for use
+Description: submit release information to freecode.net
+ freecode-submit is a script that supports remote submission of
+ release updates to Freecode via its JSON API. It is intended for use
in project release scripts. It reads the metadata from an
RFC-2822-like message on standard input, possibly with overrides by
command-line switches.
-XBS-Destinations: freshmeat
+XBS-Destinations: freecode
-Homepage: http://www.catb.org/~esr/freshmeat-submit
+Homepage: http://www.catb.org/~esr/freecode-submit
XBS-Logo: gold-mega.png
--- /dev/null
+#!/usr/bin/env python
+#
+# freecode-submit -- script transactions with the Freecode server
+
+import sys, re, urllib2, json, netrc, email.Parser, optparse
+
+version="2.3"
+
+class Update:
+ "Encapsulate dictionaries describing a project metadata update."
+ def __init__(self):
+ self.name = None
+ self.per_project = {}
+ self.urlassoc = []
+ self.per_release = {}
+ def __repr__(self):
+ return "Update(" + repr(self.__dict__) + ")"
+
+# The Freecode API implementation is sensitive to the kind of HTTP request
+# method you use. The general rule is:
+#
+# Reading records: GET
+# Adding new records: POST
+# Updating existing records: PUT
+# Deleting existing records: DELETE
+#
+# From http://help.freecode.net/faqs/api-7/data-api-intro:
+# 200 OK - Request was successful, the requested content is included
+# 201 Created - Creation of a new resource was successful
+# 401 Unauthorized - You need to provide an authentication code with this
+# request
+# 403 Forbidden - You don't have permission to access this URI
+# 404 Not Found - The requested resource was not found
+# 409 Conflict - The validation of your submitted data failed, please check
+# the response body for error pointers
+# 500 Server Error - The request hit a problem and was aborted, please report
+# as a bug if it persists
+# 503 Service Unavailable - You hit your API credit limit
+
+class RequestWithMethod(urllib2.Request):
+ "Hack for forcing the method in a request - allows PUT and DELETE"
+ def __init__(self, method, *args, **kwargs):
+ # This assignment works directly in older Python versions
+ self._method = method
+ urllib2.Request.__init__(self, *args, **kwargs)
+ def get_method(self):
+ "Deduce the HTTP method to use."
+ # This method works in newer Pythons (2.6.5, possibly earlier).
+ if self._method:
+ return self._method
+ elif self.has_data():
+ return "POST"
+ else:
+ return "GET"
+
+class FreecodeSessionException(Exception):
+ def __init__(self, msg):
+ self.msg = msg
+
+class FreecodeSession:
+ "Encapsulate the state of a Freecode API session."
+ server = "http://freecode.net/"
+
+ def __init__(self, auth=None, verbose=0, emit_enable=True):
+ "Initialize Freecode session credentials."
+ self.auth = auth
+ self.verbose = verbose
+ self.emit_enable = emit_enable
+ self.project = None
+ # If user didn't supply credentials, fetch from ~/.netrc
+ if not self.auth:
+ try:
+ credentials = netrc.netrc()
+ except netrc.NetrcParseError, e:
+ raise FreecodeSessionException("ill-formed .netrc: %s:%s %s" \
+ % (e.filename, e.lineno, e.msg))
+ except IOError, e:
+ raise FreecodeSessionException(("missing .netrc file %s" % \
+ str(e).split()[-1]))
+ ret = credentials.authenticators("freecode")
+ if not ret:
+ raise FreecodeSessionException("no credentials for Freecode")
+ login, self.auth, password = ret
+
+ def on_project(self, name):
+ "Select project by Freecode shortname."
+ if self.verbose:
+ print "Selecting project: %s" % name
+ self.project = name
+ query = FreecodeSession.server + "projects/%s.json?auth_code=%s" \
+ % (self.project, self.auth)
+ handle = urllib2.urlopen(query)
+ content = json.loads(handle.read())
+ self.project_data = content['project']
+ #if self.verbose:
+ # print "Project data: %s" % self.project_data
+ self.permalink = self.project_data['permalink']
+ self.id = self.project_data['id']
+
+ def edit_request(self, url, method="GET", request={}, force=False):
+ "Wrap a JSON object with the auth code and ship it as a request"
+ url = FreecodeSession.server + url
+ data = {"auth_code" : self.auth}
+ data.update(request)
+ data = json.dumps(data)
+ headers = {"Content-Type" : "application/json"}
+ if self.verbose:
+ print "Request URL:", method, url
+ #if self.verbose:
+ # print "Request headers:", headers
+ if self.verbose:
+ print "Request data:", data
+ if self.emit_enable or force:
+ req = RequestWithMethod(method=method,
+ url=url,
+ data=data,
+ headers=headers)
+ handle = urllib2.urlopen(req)
+ if self.verbose:
+ print handle.info()
+ content = handle.read()
+ if self.verbose:
+ print "Response:", content
+ return content
+
+ def publish_release(self, data):
+ "Add a new release to the current project."
+ if self.verbose:
+ print "Publishing %s release: %s" % (self.project, `data`)
+ self.edit_request("projects/" + self.permalink + "/releases.json",
+ "POST",
+ {"release": data})
+
+ def withdraw_release(self, version):
+ "Withdraw a specified release from the current project."
+ if self.verbose:
+ print "Withdrawing %s release: %s" % (self.project, version)
+ releases = self.edit_request("projects/%s/releases/pending.json" \
+ % self.permalink, force=True)
+ releases = json.loads(releases)
+ for release in releases:
+ properties = release["release"]
+ if properties.get("version") == version:
+ vid = properties["id"]
+ print "I see", properties
+ break
+ else:
+ raise FreecodeSessionException("couldn't find release %s"%version)
+ deletelink = "projects/%s/releases/%s.json" % (self.permalink, vid)
+ self.edit_request(deletelink, "DELETE", {})
+
+ def update_core(self, coredata):
+ "Update the core data for a project."
+ if self.verbose:
+ print "Core data update for %s is: %s" % (self.project, coredata)
+ self.edit_request("projects/" + self.permalink + ".json",
+ "PUT",
+ {"project": coredata})
+
+ def update_urls(self, urlassoc):
+ "Update URL list for a project."
+ if self.verbose:
+ print "URL list update for %s is: %s" % (self.project, urlassoc)
+ # First, get permalinks for all existing URLs
+ query = FreecodeSession.server + "projects/%s/urls.json?auth_code=%s" \
+ % (self.permalink, self.auth)
+ handle = urllib2.urlopen(query)
+ content = json.loads(handle.read())
+ permadict = {}
+ for item in content:
+ inner = item['url']
+ permadict[inner['label']] = inner['permalink']
+ # OK, now run through the update list...
+ for (label, url) in urlassoc:
+ if label in permadict:
+ # updating where we need to
+ self.edit_request("projects/%s/urls/%s.json"
+ % (self.permalink, permadict[label]),
+ "PUT",
+ {"url" : {"label": label, "location" : url}})
+ else:
+ # and adding where we don't.
+ self.edit_request("projects/%s/urls.json"
+ % (self.permalink,),
+ "POST",
+ {"url" : {"label": label, "location" : url}})
+ # Delete URLs that weren't in the update list
+ remove = set(permadict.keys()) - set(map(lambda x: x[0], urlassoc))
+ for label in remove:
+ self.edit_request("projects/%s/urls/%s.json?auth_code=%s"
+ % (self.permalink, permadict[label], self.auth),
+ "DELETE", {})
+class FreecodeMetadataFactory:
+ "Factory class for producing Freecode records in JSON."
+ freecode_field_map = (
+ ("Project", "P", "name"), # Project
+ ("Summary", "S", "oneliner"), # Project
+ ("Description", "D", "description"), # Project
+ ("License-List", "L", "license_list"), # Project
+ ("Project-Tag-List", "T", "project_tag_list"), # Project
+ ("Version", "v", "version"), # Release
+ ("Changes", "c", "changelog"), # Release
+ ("Hide", "x", "hidden_from_frontpage"), # Release
+ ("Release-Tag-List", "t", "tag_list"), # Release
+ # Note: Release Focus is no longer a real Freecode field.
+ # We remap it below.
+ ("Release-Focus", "r", "release_focus"), # Release
+ )
+ # Conventional values for release focus
+ focus_values = (
+ "Initial freecode announcement",
+ "Documentation",
+ "Code cleanup",
+ "Minor feature enhancements",
+ "Major feature enhancements",
+ "Minor bugfixes",
+ "Major bugfixes",
+ "Minor security fixes",
+ "Major security fixes",
+ )
+ # Which attributes have project scope, all others have release scupe
+ projectwide = ('name',
+ 'description',
+ 'oneliner',
+ 'license_list',
+ 'project_tag_list')
+
+ 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 FreecodeMetadataFactory.freecode_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('-q', '--query', dest='query',
+ help='Query metadata for PROJECT',metavar="PROJECT")
+ self.argument_parser.add_option('-d', '--delete', dest='delete',
+ default=False, action='store_true',
+ help='Suppress reading fields from stdin.')
+ 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('-N', '--dryrun', dest='dryrun',
+ default=False, action='store_true',
+ help='Suppress reading fields from stdin.')
+ self.argument_parser.add_option('-V', '--verbose', dest='verbose',
+ default=False, action='store_true',
+ help='Enable verbose debugging.')
+ self.argument_parser.add_option('-?', '--showversion', dest='showversion',
+ default=False, action='store_true',
+ help='Show version and quit.')
+
+ def header_to_field(self, hdr):
+ lhdr = hdr.lower().replace("-", "_")
+ for (alias, shortopt, field) in FreecodeMetadataFactory.freecode_field_map:
+ if lhdr == alias.lower().replace("-", "_").replace("/", "_"):
+ return field
+ raise FreecodeSessionException("Illegal field name %s" % hdr)
+
+ def getMetadata(self, stream):
+ "Return an Update object describing project and release attributes."
+ data = {}
+ urls = {}
+ (options, args) = self.argument_parser.parse_args()
+ # Stuff from stdin if present
+ prior_version = data.get("version")
+ if not (options.query or options.showversion) and options.read:
+ message = self.message_parser.parse(stream)
+ for (key, value) in message.items():
+ value = re.sub("\n +", " ", value).strip()
+ if key.endswith("-URL"):
+ key = key.replace("-", " ")
+ urls.update({key[:-4] : value})
+ else:
+ if key.endswith("List"):
+ value = map(lambda x: x.strip(), value.split())
+ data.update({self.header_to_field(key) : value})
+ if not 'changelog' in data:
+ payload = message.get_payload().strip()
+ if payload:
+ data['changelog'] = payload + "\n"
+ if prior_version and data.get("version") != prior_version:
+ raise FreecodeSessionException("Version conflict on stdin.")
+ # Merge in options from the command line;
+ # they override what's on stdin.
+ controls = ('query', 'delete', 'read', 'dryrun', 'verbose', 'showversion')
+ prior_version = data.get("version")
+ for (key, value) in options.__dict__.items():
+ if key not in controls and value != None:
+ data[key] = value
+ del options.__dict__[key]
+ if data.get("version") != prior_version and not options.delete:
+ raise FreecodeSessionException("Version conflict in options.")
+ # Hidden flag special handling
+ if "hidden_from_frontpage" in data:
+ data["hidden_from_frontpage"] = data["hidden_from_frontpage"] in ("Y", "y")
+ # Release-Focus field special handling.
+ if "release_focus" in data:
+ if data["release_focus"] in '123456789':
+ data["release_focus"] = FreecodeMetadataFactory.focus_values[int(data["release_focus"])-1]
+ if data["release_focus"] not in FreecodeMetadataFactory.focus_values:
+ raise FreecodeSessionException("Invalid release focus")
+ if "tag_list" not in data:
+ data["tag_list"] = data["release_focus" ]
+ else:
+ data["tag_list"].append(data["release_focus" ])
+ del data["release_focus"]
+ # Now merge in the URLs, doing symbol substitution
+ urllist = []
+ for (label, furl) in urls.items():
+ for (k, v) in data.items():
+ if type(v) == type(""):
+ furl = furl.replace('${' + k + '}', v)
+ urllist.append((label, furl))
+ # Sort out what things go where
+ update = Update()
+ if options.showversion:
+ pass
+ elif options.query:
+ update.name = options.query
+ else:
+ update.name = data.pop('name')
+ update.urlassoc = urllist
+ for (k, v) in data.items():
+ if k in FreecodeMetadataFactory.projectwide:
+ # Hack to get around a namespace collision
+ if k == "project_release_tag":
+ k = "release_tag"
+ update.per_project[k] = v
+ else:
+ update.per_release[k] = v
+ # Return this
+ return (options, update)
+
+if __name__ == "__main__":
+ try:
+ # First, gather update data from stdin and command-line switches
+ factory = FreecodeMetadataFactory()
+ (options, update) = factory.getMetadata(sys.stdin)
+ # Some switches shouldn't be passed to the server
+ query = 'query' in options.__dict__ and options.query
+ verbose = 'verbose' in options.__dict__ and options.verbose
+ delete = 'delete' in options.__dict__ and options.delete
+ dryrun = 'dryrun' in options.__dict__ and options.dryrun
+ showversion = 'showversion' in options.__dict__ and options.showversion
+ if showversion:
+ print "freecode-submit", version
+ raise SystemExit, 0
+ # Time to ship the update.
+ # Establish session
+ session = FreecodeSession(verbose=int(verbose), emit_enable=not dryrun)
+ session.on_project(update.name)
+ if options.query:
+ print "Project: %s" % session.project_data["name"]
+ print "Summary: %s" % session.project_data["oneliner"]
+ print "Description: %s" % session.project_data["description"].replace("\n", "\n ").rstrip()
+ print "License-List: %s" % ",".join(session.project_data["license_list"])
+ print "Project-Tag-List: %s" % ",".join(session.project_data["tag_list"])
+ for assoc in session.project_data['approved_urls']:
+ #print "Querying", assoc["redirector"]
+ #req = RequestWithMethod(method="HEAD",
+ # url=assoc["redirector"],
+ # data={},
+ # headers={})
+ #handle = urllib2.urlopen(req)
+ #print "==="
+ #print handle.info()
+ #print "==="
+ print "%s-URL: %s" % (assoc["label"].replace(" ","-"), assoc["redirector"])
+ if 'recent_releases' in session.project_data:
+ most_recent = session.project_data['recent_releases'][0]
+ print "Version: %s" % most_recent['version']
+ print "Tag-List: %s" % ",".join(most_recent['tag_list'])
+ if most_recent.get('hidden_from_frontpage'):
+ print "Hide: Y"
+ else:
+ print "Hide: N"
+ print ""
+ print most_recent["changelog"]
+ else:
+ # OK, now actually add or delete the release.
+ if update.per_project:
+ session.update_core(update.per_project)
+ if update.urlassoc:
+ session.update_urls(update.urlassoc)
+ if delete:
+ session.withdraw_release(update.per_release['version'])
+ elif update.per_release and update.per_release.keys()!=["version"]:
+ session.publish_release(update.per_release)
+ except FreecodeSessionException, e:
+ print >>sys.stderr,"freecode-submit:", e.msg
+ sys.exit(1)
+ except urllib2.HTTPError, f:
+ print >>sys.stderr,"freecode-submit: HTTPError %s" % (f.code)
+ print >>sys.stderr,f.read()
+ sys.exit(1)
+ except urllib2.URLError, f:
+ print >>sys.stderr,"freecode-submit: URLError %s" % (f.reason,)
+ print >>sys.stderr,f.read()
+ sys.exit(1)
+
+# end
+
--- /dev/null
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!DOCTYPE refentry PUBLIC
+ "-//OASIS//DTD DocBook XML V4.1.2//EN"
+ "docbook/docbookx.dtd">
+<refentry id='freecode-submit.1'>
+<refmeta>
+<refentrytitle>freecode-submit</refentrytitle>
+<manvolnum>1</manvolnum>
+<refmiscinfo class='source'>freecode-submit</refmiscinfo>
+<refmiscinfo class='manual'>Development Tools</refmiscinfo>
+<refmiscinfo class='date'>Oct 17 2010</refmiscinfo>
+</refmeta>
+<refnamediv id='name'>
+<refname>freecode-submit</refname>
+<refpurpose>submit release announcements and project metadata updates to freecode.net</refpurpose>
+</refnamediv>
+<refsynopsisdiv id='synopsis'>
+
+<cmdsynopsis>
+ <command>freecode-submit</command>
+ <arg>-q</arg>
+ <arg>-V</arg>
+ <arg>-n</arg>
+ <arg>-N</arg>
+ <arg>-d</arg>
+ <arg>--showversion</arg>
+ <arg><replaceable>data-options</replaceable></arg>
+ <arg rep='repeat'><replaceable>data-options</replaceable></arg>
+</cmdsynopsis>
+</refsynopsisdiv>
+
+<refsect1 id='description'><title>DESCRIPTION</title>
+
+<para><application>freecode-submit</application> is a command-line tool
+for submitting release announcements to freecode.net. It is intended
+for batch-mode operation in project release scripts, and designed so
+that its input format can easily be generated by scripts.</para>
+
+<para><application>freecode-submit</application> accepts release
+information from a job card (an email-message-like data block on
+standard input) or from command-line flags. Values set with
+command-line flags override those collected from the job card. The
+accumulated data is entered into the freecode database via its
+API.</para>
+
+<para>A few flags control the operation of
+<application>freecode-submit</application> itself.</para>
+
+<variablelist>
+<varlistentry>
+<term>-q, --query</term>
+<listitem>
+<para>Query the specified project. Delivers to standard output a job
+card describing the project metadata that can be edited and fed back
+to this tool. The data with release rather than project scope is for
+the most recent release.</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>-d, --delete</term>
+<listitem>
+<para>Delete the specified release rather than submitting it. With
+this option, all data other than Project and Release are
+ignored. Note: only works if the release has been submitted but not
+yet passed by a moderator; if the release is already posted, invoking
+this mode will throw an error.</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>-n, --no-stdin</term>
+<listitem>
+<para>Process command-line options only; don't read data from
+standard input.</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>-N, --dryrun</term>
+<listitem>
+<para>Generate JSON, but don't ship any modification requests to Freecode.
+Useful for debugging in combination with -V. Note, this mode may generate
+requests which read data from Freecode in order to get permalink data.
+</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>-V, --verbose</term>
+<listitem>
+<para>Enable verbose debugging of the transaction. This
+option is mainly for developers.</para>
+</listitem>
+</varlistentry>
+<varlistentry>
+<term>--showversion</term>
+<listitem>
+<para>Show the version of this program and exit.</para>
+</listitem>
+</varlistentry>
+</variablelist>
+
+<para>Release information is accepted from sources in the
+following order:</para>
+
+<orderedlist>
+<listitem>
+<para>An RFC-2822-format job card, strongly resembling an ordinary
+email message, on standard input. The message header fields each
+specify a particular item of release information; the body (if
+present) becomes the change announcement text. Within URL fields, the
+string ${version} will be replaced with the contents of the Version
+field.</para>
+</listitem>
+
+<listitem>
+<para>Command-line switches.</para>
+</listitem>
+</orderedlist>
+
+<para>Here is a table of metadata fields and their
+corresponding command-line switches:</para>
+
+<informaltable>
+<tgroup cols='5' rowsep='1'>
+<thead>
+<row>
+ <entry>Message field</entry>
+ <entry>Scope</entry>
+ <entry>Short switch</entry>
+ <entry>Long switch</entry>
+ <entry>Explanation</entry>
+</row>
+</thead>
+<tbody>
+
+<row>
+<entry>Project:</entry> <entry>Project</entry>
+<entry>-p</entry><entry>--project</entry>
+<entry>
+<para>Name of the project (freecode shortname) to operate on. You cannot
+change the project-name attribute with this program; to do that, you
+need to use the Freecode web interface.</para>
+</entry>
+</row>
+
+<row>
+<entry>Summary:</entry> <entry>Project</entry>
+<entry>-S</entry><entry>--summary</entry>
+<entry>
+<para>One-line summary of the project.</para>
+</entry>
+</row>
+
+<row>
+<entry>Description:</entry> <entry>Project</entry>
+<entry>-D</entry><entry>--description</entry>
+<entry>
+<para>Multiline description of the project. (Use RFC-822-style continuation
+lines)</para>
+</entry>
+</row>
+
+<row>
+<entry>License-List:</entry> <entry>Project</entry>
+<entry>-L</entry><entry>--license-list</entry>
+<entry>
+<para>Specify the licenses under which the project is issued. This
+may be a comma-separated list.</para>
+</entry>
+</row>
+
+<row>
+<entry>Project-Tag-List:</entry><entry>Release</entry>
+<entry>-T</entry><entry>--project-tag-list</entry>
+<entry>
+<para>Tags to attach to this project. May be a comma-separated list.</para>
+</entry>
+</row>
+
+<row>
+<entry>Version:</entry> <entry>Release</entry>
+<entry>-v</entry><entry>--version</entry>
+<entry>
+<para>Version string to be associated with the release.</para>
+</entry>
+</row>
+
+<row>
+<entry>Changes:</entry> <entry>Release</entry>
+<entry>-c</entry><entry>--changes</entry>
+<entry>
+<para>The Changes field. If no Changes field or option is already
+present, the changes text is taken from the body of the RFC-822
+message on standard input.</para>
+</entry>
+</row>
+
+<row>
+<entry>Release-Focus:</entry><entry>Release</entry>
+<entry>-r</entry><entry>--release-focus</entry>
+<entry>
+<para>Purpose of this release. See the table of release-focus types
+below.</para>
+</entry>
+</row>
+
+<row>
+<entry>Release-Tag-List:</entry> <entry>Release</entry>
+<entry>-t</entry><entry>--tag-list</entry>
+<entry>
+<para>Tags to attach to this release. May be a comma-separated list.</para>
+</entry>
+</row>
+
+<row>
+<entry>Hide:</entry> <entry>Release</entry>
+<entry>-x</entry><entry>--hide</entry>
+<entry>
+<para>If this field is present and has the value “Y”, this
+release will not be visible on the project page.</para>
+</entry>
+</row>
+
+</tbody>
+</tgroup>
+</informaltable>
+
+<para>Additionally, you can specify tagged URL fields for the
+Freecode project record with header lines ending in "-URL:". The tag
+will be the header with the "-URL:" suffix stripped off. Some examples
+of how to set frequently-used URL types:</para>
+
+<informaltable>
+<tgroup cols='3' rowsep='1'>
+<thead>
+<row>
+ <entry>Message field</entry>
+ <entry>Tag</entry>
+ <entry>Usage</entry>
+</row>
+</thead>
+<tbody>
+
+<row>
+ <entry>Website-URL:</entry>
+ <entry>Website</entry>
+ <entry>Project website home page. If you specify this, it will be used to
+ activate the "More Information" link and Home icon on the project page.</entry>
+</row>
+
+<row>
+ <entry>Download-URL:</entry>
+ <entry>Download</entry>
+ <entry>Preferred download URL. If you specify this, it will be used to
+ activate the "Download" link anf foppy-disk on the project page.</entry>
+</row>
+
+<row>
+ <entry>Tar/GZ-URL:</entry>
+ <entry>Tar/GZ</entry>
+ <entry>tar archive compressed with gzip</entry>
+</row>
+
+<row>
+ <entry>Tar/BZ-URL:</entry>
+ <entry>Tar/BZ</entry>
+ <entry>tar archive compressed with bzip</entry>
+</row>
+
+<row>
+ <entry>Changelog-URL</entry>
+ <entry>Changelog</entry>
+ <entry>project change log</entry>
+</row>
+
+</tbody>
+</tgroup>
+</informaltable>
+
+<para>Note that the list of URLs you ship does not merely add to the
+existing list but overwrites it - that is how you delete old URLs
+which are no longer valid.</para>
+
+<para>The Summary, Description, License-List, Project-Tag-List and all
+URL fields, when specified, actually change the record for the
+project. Only the Tag-List, Changes, Version, Release-Focus, and Hide
+fields are actually stored for each version.</para>
+
+<para>Here are the release-focus types. Either the string or the
+integer is acceptable. Case of the string is significant.</para>
+
+<informaltable>
+<tgroup cols='2' rowsep='1'>
+<tbody>
+<row><entry>Initial freecode announcement</entry><entry>1</entry></row>
+<row><entry>Documentation</entry><entry>2</entry></row>
+<row><entry>Code cleanup</entry><entry>3</entry></row>
+<row><entry>Minor feature enhancements</entry><entry>4</entry></row>
+<row><entry>Major feature enhancements</entry><entry>5</entry></row>
+<row><entry>Minor bugfixes</entry><entry>6</entry></row>
+<row><entry>Major bugfixes</entry><entry>7</entry></row>
+<row><entry>Minor security fixes</entry><entry>8</entry></row>
+<row><entry>Major security fixes</entry><entry>9</entry></row>
+</tbody>
+</tgroup>
+</informaltable>
+
+<para>The following elements of Freecode project metadata cannot yet
+be modified using this tool: <varname>name</varname>,
+<varname>translation list</varname>, <varname>programming
+language list</varname>, <varname>operating system
+list</varname>, <varname>approved screenshots</varname>,
+and dependency records.</para>
+
+<para>Support for updating all these elements except
+<varname>name</varname> is planned in future releases.</para>
+</refsect1>
+
+<refsect1><title>USAGE</title>
+
+<para>Here is an example of a release-information record that could be
+fed to <application>freecode-submit</application> on standard input:</para>
+
+<programlisting>
+Project: doclifter
+Version: 1.1
+Release-Focus: Major feature enhancements
+Hide: N
+Website-URL: http://www.catb.org/~esr/doclifter/
+Tar/GZ-URL: http://www.catb.org/~esr/doclifter/doclifter-${version}.tar.gz
+
+Fixed a bug in conditional evaluation that twadmin(8) tickled. Better
+detection of pure inclusions. Better blank-section elimination.
+Kleene star recognized as ... synonym in command synopses. Correct
+some bugs in semantic-hint collection. Recognize Qt manual pages and
+use their conventions. Better lifting of mandoc-generated
+pages. Translate groff-style \[...] escapes, flag unknown ones. Can
+now parse K&R style function prototypes as well as ANSI ones. This
+version lifts 96% of 9829 manual pages in a full Red Hat 9
+installation with Fedora updates to *validated* XML-DocBook.
+</programlisting>
+</refsect1>
+
+<refsect1><title>WARNING</title>
+<para>Since this program was first written, Freecode has changed its
+data API significantly. While we have attempted to maintain backward
+compatibility, the data model has changed in significant ways.</para>
+
+<para>There is no longer a Branch attribute.</para>
+
+<para>The old Release-Focus field is gone. As a special case, this
+pragram will validate it and use it to fill in the project tags list.</para>
+
+<para>The old License field is now License-List and can take a
+comma-separated list of licenses.</para>
+
+<para>All the old URL fields are gone. They're replaced by
+the general mechanism for pairing URLs with labels.</para>
+
+<para>Earlier versions parsed RPM specfiles. This was poor
+separation of function and has been removed. That responsibility
+belong to the human or program generating the job card.</para>
+</refsect1>
+
+<refsect1><title>FILES</title>
+
+<variablelist>
+<varlistentry>
+<term><filename>~/.netrc</filename></term>
+<listitem>
+<para><application>freecode-submit</application> expects to find your
+Freecode authorization tag here. The correct form for an entry looks like
+this:</para>
+
+<screen>
+machine freecode
+ account <your_api_auth_code>
+</screen>
+
+<para>For security, make sure your <filename>~/.netrc</filename> file
+is not world-readable; we recommend doing <command>chmod 600
+~/.netrc</command>.</para>
+</listitem>
+</varlistentry>
+</variablelist>
+
+</refsect1>
+<refsect1><title>BUGS</title>
+
+<para>The URLs returned in a project metadata query are Freecode redirectors,
+not the actual URLS supplied by previous project updates. This is due to
+a limitation in the Freecode API which may be fixed in future versions
+of that API.</para>
+</refsect1>
+<refsect1><title>RETURN VALUES</title>
+
+<para>0 on success, 1 on failure. In case of failure, any error message
+passed back by the Freecode API is issued to standard error.</para>
+
+</refsect1>
+<refsect1 id='author'><title>AUTHORS</title>
+<para>Eric S. Raymond <email>esr@thyrsus.com</email>.</para>
+</refsect1>
+</refentry>
+++ /dev/null
-#!/usr/bin/env python
-#
-# freshmeat-submit -- script transactions with the Freshmeat server
-
-import sys, re, urllib2, json, netrc, email.Parser, optparse
-
-version="2.2"
-
-class Update:
- "Encapsulate dictionaries describing a project metadata update."
- def __init__(self):
- self.name = None
- self.per_project = {}
- self.urlassoc = []
- self.per_release = {}
- def __repr__(self):
- return "Update(" + repr(self.__dict__) + ")"
-
-# The Freshmeat API implementation is sensitive to the kind of HTTP request
-# method you use. The general rule is:
-#
-# Reading records: GET
-# Adding new records: POST
-# Updating existing records: PUT
-# Deleting existing records: DELETE
-#
-# From http://help.freshmeat.net/faqs/api-7/data-api-intro:
-# 200 OK - Request was successful, the requested content is included
-# 201 Created - Creation of a new resource was successful
-# 401 Unauthorized - You need to provide an authentication code with this
-# request
-# 403 Forbidden - You don't have permission to access this URI
-# 404 Not Found - The requested resource was not found
-# 409 Conflict - The validation of your submitted data failed, please check
-# the response body for error pointers
-# 500 Server Error - The request hit a problem and was aborted, please report
-# as a bug if it persists
-# 503 Service Unavailable - You hit your API credit limit
-
-class RequestWithMethod(urllib2.Request):
- "Hack for forcing the method in a request - allows PUT and DELETE"
- def __init__(self, method, *args, **kwargs):
- # This assignment works directly in older Python versions
- self._method = method
- urllib2.Request.__init__(self, *args, **kwargs)
- def get_method(self):
- "Deduce the HTTP method to use."
- # This method works in newer Pythons (2.6.5, possibly earlier).
- if self._method:
- return self._method
- elif self.has_data():
- return "POST"
- else:
- return "GET"
-
-class FreshmeatSessionException(Exception):
- def __init__(self, msg):
- self.msg = msg
-
-class FreshmeatSession:
- "Encapsulate the state of a Freshmeat API session."
- server = "http://freshmeat.net/"
-
- def __init__(self, auth=None, verbose=0, emit_enable=True):
- "Initialize Freshmeat session credentials."
- self.auth = auth
- self.verbose = verbose
- self.emit_enable = emit_enable
- self.project = None
- # If user didn't supply credentials, fetch from ~/.netrc
- if not self.auth:
- try:
- credentials = netrc.netrc()
- except netrc.NetrcParseError, e:
- raise FreshmeatSessionException("ill-formed .netrc: %s:%s %s" \
- % (e.filename, e.lineno, e.msg))
- except IOError, e:
- raise FreshmeatSessionException(("missing .netrc file %s" % \
- str(e).split()[-1]))
- ret = credentials.authenticators("freshmeat")
- if not ret:
- raise FreshmeatSessionException("no credentials for Freshmeat")
- login, self.auth, password = ret
-
- def on_project(self, name):
- "Select project by Freshmeat shortname."
- if self.verbose:
- print "Selecting project: %s" % name
- self.project = name
- query = FreshmeatSession.server + "projects/%s.json?auth_code=%s" \
- % (self.project, self.auth)
- handle = urllib2.urlopen(query)
- content = json.loads(handle.read())
- self.project_data = content['project']
- #if self.verbose:
- # print "Project data: %s" % self.project_data
- self.permalink = self.project_data['permalink']
- self.id = self.project_data['id']
-
- def edit_request(self, url, method="GET", request={}, force=False):
- "Wrap a JSON object with the auth code and ship it as a request"
- url = FreshmeatSession.server + url
- data = {"auth_code" : self.auth}
- data.update(request)
- data = json.dumps(data)
- headers = {"Content-Type" : "application/json"}
- if self.verbose:
- print "Request URL:", method, url
- #if self.verbose:
- # print "Request headers:", headers
- if self.verbose:
- print "Request data:", data
- if self.emit_enable or force:
- req = RequestWithMethod(method=method,
- url=url,
- data=data,
- headers=headers)
- handle = urllib2.urlopen(req)
- if self.verbose:
- print handle.info()
- content = handle.read()
- if self.verbose:
- print "Response:", content
- return content
-
- def publish_release(self, data):
- "Add a new release to the current project."
- if self.verbose:
- print "Publishing %s release: %s" % (self.project, `data`)
- self.edit_request("projects/" + self.permalink + "/releases.json",
- "POST",
- {"release": data})
-
- def withdraw_release(self, version):
- "Withdraw a specified release from the current project."
- if self.verbose:
- print "Withdrawing %s release: %s" % (self.project, version)
- releases = self.edit_request("projects/%s/releases/pending.json" \
- % self.permalink, force=True)
- releases = json.loads(releases)
- for release in releases:
- properties = release["release"]
- if properties.get("version") == version:
- vid = properties["id"]
- print "I see", properties
- break
- else:
- raise FreshmeatSessionException("couldn't find release %s"%version)
- deletelink = "projects/%s/releases/%s.json" % (self.permalink, vid)
- self.edit_request(deletelink, "DELETE", {})
-
- def update_core(self, coredata):
- "Update the core data for a project."
- if self.verbose:
- print "Core data update for %s is: %s" % (self.project, coredata)
- self.edit_request("projects/" + self.permalink + ".json",
- "PUT",
- {"project": coredata})
-
- def update_urls(self, urlassoc):
- "Update URL list for a project."
- if self.verbose:
- print "URL list update for %s is: %s" % (self.project, urlassoc)
- # First, get permalinks for all existing URLs
- query = FreshmeatSession.server + "projects/%s/urls.json?auth_code=%s" \
- % (self.permalink, self.auth)
- handle = urllib2.urlopen(query)
- content = json.loads(handle.read())
- permadict = {}
- for item in content:
- inner = item['url']
- permadict[inner['label']] = inner['permalink']
- # OK, now run through the update list...
- for (label, url) in urlassoc:
- if label in permadict:
- # updating where we need to
- self.edit_request("projects/%s/urls/%s.json"
- % (self.permalink, permadict[label]),
- "PUT",
- {"url" : {"label": label, "location" : url}})
- else:
- # and adding where we don't.
- self.edit_request("projects/%s/urls.json"
- % (self.permalink,),
- "POST",
- {"url" : {"label": label, "location" : url}})
- # Delete URLs that weren't in the update list
- remove = set(permadict.keys()) - set(map(lambda x: x[0], urlassoc))
- for label in remove:
- self.edit_request("projects/%s/urls/%s.json?auth_code=%s"
- % (self.permalink, permadict[label], self.auth),
- "DELETE", {})
-class FreshmeatMetadataFactory:
- "Factory class for producing Freshmeat records in JSON."
- freshmeat_field_map = (
- ("Project", "P", "name"), # Project
- ("Summary", "S", "oneliner"), # Project
- ("Description", "D", "description"), # Project
- ("License-List", "L", "license_list"), # Project
- ("Project-Tag-List", "T", "project_tag_list"), # Project
- ("Version", "v", "version"), # Release
- ("Changes", "c", "changelog"), # Release
- ("Hide", "x", "hidden_from_frontpage"), # Release
- ("Release-Tag-List", "t", "tag_list"), # Release
- # Note: Release Focus is no longer a real Freshmeat field.
- # We remap it below.
- ("Release-Focus", "r", "release_focus"), # Release
- )
- # Conventional values for release focus
- focus_values = (
- "Initial freshmeat announcement",
- "Documentation",
- "Code cleanup",
- "Minor feature enhancements",
- "Major feature enhancements",
- "Minor bugfixes",
- "Major bugfixes",
- "Minor security fixes",
- "Major security fixes",
- )
- # Which attributes have project scope, all others have release scupe
- projectwide = ('name',
- 'description',
- 'oneliner',
- 'license_list',
- 'project_tag_list')
-
- 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 FreshmeatMetadataFactory.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('-q', '--query', dest='query',
- help='Query metadata for PROJECT',metavar="PROJECT")
- self.argument_parser.add_option('-d', '--delete', dest='delete',
- default=False, action='store_true',
- help='Suppress reading fields from stdin.')
- 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('-N', '--dryrun', dest='dryrun',
- default=False, action='store_true',
- help='Suppress reading fields from stdin.')
- self.argument_parser.add_option('-V', '--verbose', dest='verbose',
- default=False, action='store_true',
- help='Enable verbose debugging.')
- self.argument_parser.add_option('-?', '--showversion', dest='showversion',
- default=False, action='store_true',
- help='Show version and quit.')
-
- def header_to_field(self, hdr):
- lhdr = hdr.lower().replace("-", "_")
- for (alias, shortopt, field) in FreshmeatMetadataFactory.freshmeat_field_map:
- if lhdr == alias.lower().replace("-", "_").replace("/", "_"):
- return field
- raise FreshmeatSessionException("Illegal field name %s" % hdr)
-
- def getMetadata(self, stream):
- "Return an Update object describing project and release attributes."
- data = {}
- urls = {}
- (options, args) = self.argument_parser.parse_args()
- # Stuff from stdin if present
- prior_version = data.get("version")
- if not (options.query or options.showversion) and options.read:
- message = self.message_parser.parse(stream)
- for (key, value) in message.items():
- value = re.sub("\n +", " ", value).strip()
- if key.endswith("-URL"):
- key = key.replace("-", " ")
- urls.update({key[:-4] : value})
- else:
- if key.endswith("List"):
- value = map(lambda x: x.strip(), value.split())
- data.update({self.header_to_field(key) : value})
- if not 'changelog' in data:
- payload = message.get_payload().strip()
- if payload:
- data['changelog'] = payload + "\n"
- if prior_version and data.get("version") != prior_version:
- raise FreshmeatSessionException("Version conflict on stdin.")
- # Merge in options from the command line;
- # they override what's on stdin.
- controls = ('query', 'delete', 'read', 'dryrun', 'verbose', 'showversion')
- prior_version = data.get("version")
- for (key, value) in options.__dict__.items():
- if key not in controls and value != None:
- data[key] = value
- del options.__dict__[key]
- if data.get("version") != prior_version and not options.delete:
- raise FreshmeatSessionException("Version conflict in options.")
- # Hidden flag special handling
- if "hidden_from_frontpage" in data:
- data["hidden_from_frontpage"] = data["hidden_from_frontpage"] in ("Y", "y")
- # Release-Focus field special handling.
- if "release_focus" in data:
- if data["release_focus"] in '123456789':
- data["release_focus"] = FreshmeatMetadataFactory.focus_values[int(data["release_focus"])-1]
- if data["release_focus"] not in FreshmeatMetadataFactory.focus_values:
- raise FreshmeatSessionException("Invalid release focus")
- if "tag_list" not in data:
- data["tag_list"] = data["release_focus" ]
- else:
- data["tag_list"].append(data["release_focus" ])
- del data["release_focus"]
- # Now merge in the URLs, doing symbol substitution
- urllist = []
- for (label, furl) in urls.items():
- for (k, v) in data.items():
- if type(v) == type(""):
- furl = furl.replace('${' + k + '}', v)
- urllist.append((label, furl))
- # Sort out what things go where
- update = Update()
- if options.showversion:
- pass
- elif options.query:
- update.name = options.query
- else:
- update.name = data.pop('name')
- update.urlassoc = urllist
- for (k, v) in data.items():
- if k in FreshmeatMetadataFactory.projectwide:
- # Hack to get around a namespace collision
- if k == "project_release_tag":
- k = "release_tag"
- update.per_project[k] = v
- else:
- update.per_release[k] = v
- # Return this
- return (options, update)
-
-if __name__ == "__main__":
- try:
- # First, gather update data from stdin and command-line switches
- factory = FreshmeatMetadataFactory()
- (options, update) = factory.getMetadata(sys.stdin)
- # Some switches shouldn't be passed to the server
- query = 'query' in options.__dict__ and options.query
- verbose = 'verbose' in options.__dict__ and options.verbose
- delete = 'delete' in options.__dict__ and options.delete
- dryrun = 'dryrun' in options.__dict__ and options.dryrun
- showversion = 'showversion' in options.__dict__ and options.showversion
- if showversion:
- print "freshmeat-submit", version
- raise SystemExit, 0
- # Time to ship the update.
- # Establish session
- session = FreshmeatSession(verbose=int(verbose), emit_enable=not dryrun)
- session.on_project(update.name)
- if options.query:
- print "Project: %s" % session.project_data["name"]
- print "Summary: %s" % session.project_data["oneliner"]
- print "Description: %s" % session.project_data["description"].replace("\n", "\n ").rstrip()
- print "License-List: %s" % ",".join(session.project_data["license_list"])
- print "Project-Tag-List: %s" % ",".join(session.project_data["tag_list"])
- for assoc in session.project_data['approved_urls']:
- #print "Querying", assoc["redirector"]
- #req = RequestWithMethod(method="HEAD",
- # url=assoc["redirector"],
- # data={},
- # headers={})
- #handle = urllib2.urlopen(req)
- #print "==="
- #print handle.info()
- #print "==="
- print "%s-URL: %s" % (assoc["label"].replace(" ","-"), assoc["redirector"])
- if 'recent_releases' in session.project_data:
- most_recent = session.project_data['recent_releases'][0]
- print "Version: %s" % most_recent['version']
- print "Tag-List: %s" % ",".join(most_recent['tag_list'])
- if most_recent.get('hidden_from_frontpage'):
- print "Hide: Y"
- else:
- print "Hide: N"
- print ""
- print most_recent["changelog"]
- else:
- # OK, now actually add or delete the release.
- if update.per_project:
- session.update_core(update.per_project)
- if update.urlassoc:
- session.update_urls(update.urlassoc)
- if delete:
- session.withdraw_release(update.per_release['version'])
- elif update.per_release and update.per_release.keys()!=["version"]:
- session.publish_release(update.per_release)
- except FreshmeatSessionException, e:
- print >>sys.stderr,"freshmeat-submit:", e.msg
- sys.exit(1)
- except urllib2.HTTPError, f:
- print >>sys.stderr,"freshmeat-submit: HTTPError %s" % (f.code)
- print >>sys.stderr,f.read()
- sys.exit(1)
- except urllib2.URLError, f:
- print >>sys.stderr,"freshmeat-submit: URLError %s" % (f.reason,)
- print >>sys.stderr,f.read()
- sys.exit(1)
-
-# end
-
+++ /dev/null
-<?xml version="1.0" encoding="ISO-8859-1"?>
-<!DOCTYPE refentry PUBLIC
- "-//OASIS//DTD DocBook XML V4.1.2//EN"
- "docbook/docbookx.dtd">
-<refentry id='freshmeat-submit.1'>
-<refmeta>
-<refentrytitle>freshmeat-submit</refentrytitle>
-<manvolnum>1</manvolnum>
-<refmiscinfo class='source'>freshmeat-submit</refmiscinfo>
-<refmiscinfo class='manual'>Development Tools</refmiscinfo>
-<refmiscinfo class='date'>Oct 17 2010</refmiscinfo>
-</refmeta>
-<refnamediv id='name'>
-<refname>freshmeat-submit</refname>
-<refpurpose>submit release announcements and project metadata updates to freshmeat.net</refpurpose>
-</refnamediv>
-<refsynopsisdiv id='synopsis'>
-
-<cmdsynopsis>
- <command>freshmeat-submit</command>
- <arg>-q</arg>
- <arg>-V</arg>
- <arg>-n</arg>
- <arg>-N</arg>
- <arg>-d</arg>
- <arg>--showversion</arg>
- <arg><replaceable>data-options</replaceable></arg>
- <arg rep='repeat'><replaceable>data-options</replaceable></arg>
-</cmdsynopsis>
-</refsynopsisdiv>
-
-<refsect1 id='description'><title>DESCRIPTION</title>
-
-<para><application>freshmeat-submit</application> is a command-line tool
-for submitting release announcements to freshmeat.net. It is intended
-for batch-mode operation in project release scripts, and designed so
-that its input format can easily be generated by scripts.</para>
-
-<para><application>freshmeat-submit</application> accepts release
-information from a job card (an email-message-like data block on
-standard input) or from command-line flags. Values set with
-command-line flags override those collected from the job card. The
-accumulated data is entered into the freshmeat database via its
-API.</para>
-
-<para>A few flags control the operation of
-<application>freshmeat-submit</application> itself.</para>
-
-<variablelist>
-<varlistentry>
-<term>-q, --query</term>
-<listitem>
-<para>Query the specified project. Delivers to standard output a job
-card describing the project metadata that can be edited and fed back
-to this tool. The data with release rather than project scope is for
-the most recent release.</para>
-</listitem>
-</varlistentry>
-<varlistentry>
-<term>-d, --delete</term>
-<listitem>
-<para>Delete the specified release rather than submitting it. With
-this option, all data other than Project and Release are
-ignored. Note: only works if the release has been submitted but not
-yet passed by a moderator; if the release is already posted, invoking
-this mode will throw an error.</para>
-</listitem>
-</varlistentry>
-<varlistentry>
-<term>-n, --no-stdin</term>
-<listitem>
-<para>Process command-line options only; don't read data from
-standard input.</para>
-</listitem>
-</varlistentry>
-<varlistentry>
-<term>-N, --dryrun</term>
-<listitem>
-<para>Generate JSON, but don't ship any modification requests to Freshmeat.
-Useful for debugging in combination with -V. Note, this mode may generate
-requests which read data from Freshmeat in order to get permalink data.
-</para>
-</listitem>
-</varlistentry>
-<varlistentry>
-<term>-V, --verbose</term>
-<listitem>
-<para>Enable verbose debugging of the transaction. This
-option is mainly for developers.</para>
-</listitem>
-</varlistentry>
-<varlistentry>
-<term>--showversion</term>
-<listitem>
-<para>Show the version of this program and exit.</para>
-</listitem>
-</varlistentry>
-</variablelist>
-
-<para>Release information is accepted from sources in the
-following order:</para>
-
-<orderedlist>
-<listitem>
-<para>An RFC-2822-format job card, strongly resembling an ordinary
-email message, on standard input. The message header fields each
-specify a particular item of release information; the body (if
-present) becomes the change announcement text. Within URL fields, the
-string ${version} will be replaced with the contents of the Version
-field.</para>
-</listitem>
-
-<listitem>
-<para>Command-line switches.</para>
-</listitem>
-</orderedlist>
-
-<para>Here is a table of metadata fields and their
-corresponding command-line switches:</para>
-
-<informaltable>
-<tgroup cols='5' rowsep='1'>
-<thead>
-<row>
- <entry>Message field</entry>
- <entry>Scope</entry>
- <entry>Short switch</entry>
- <entry>Long switch</entry>
- <entry>Explanation</entry>
-</row>
-</thead>
-<tbody>
-
-<row>
-<entry>Project:</entry> <entry>Project</entry>
-<entry>-p</entry><entry>--project</entry>
-<entry>
-<para>Name of the project (freshmeat shortname) to operate on. You cannot
-change the project-name attribute with this program; to do that, you
-need to use the Freshmeat web interface.</para>
-</entry>
-</row>
-
-<row>
-<entry>Summary:</entry> <entry>Project</entry>
-<entry>-S</entry><entry>--summary</entry>
-<entry>
-<para>One-line summary of the project.</para>
-</entry>
-</row>
-
-<row>
-<entry>Description:</entry> <entry>Project</entry>
-<entry>-D</entry><entry>--description</entry>
-<entry>
-<para>Multiline description of the project. (Use RFC-822-style continuation
-lines)</para>
-</entry>
-</row>
-
-<row>
-<entry>License-List:</entry> <entry>Project</entry>
-<entry>-L</entry><entry>--license-list</entry>
-<entry>
-<para>Specify the licenses under which the project is issued. This
-may be a comma-separated list.</para>
-</entry>
-</row>
-
-<row>
-<entry>Project-Tag-List:</entry><entry>Release</entry>
-<entry>-T</entry><entry>--project-tag-list</entry>
-<entry>
-<para>Tags to attach to this project. May be a comma-separated list.</para>
-</entry>
-</row>
-
-<row>
-<entry>Version:</entry> <entry>Release</entry>
-<entry>-v</entry><entry>--version</entry>
-<entry>
-<para>Version string to be associated with the release.</para>
-</entry>
-</row>
-
-<row>
-<entry>Changes:</entry> <entry>Release</entry>
-<entry>-c</entry><entry>--changes</entry>
-<entry>
-<para>The Changes field. If no Changes field or option is already
-present, the changes text is taken from the body of the RFC-822
-message on standard input.</para>
-</entry>
-</row>
-
-<row>
-<entry>Release-Focus:</entry><entry>Release</entry>
-<entry>-r</entry><entry>--release-focus</entry>
-<entry>
-<para>Purpose of this release. See the table of release-focus types
-below.</para>
-</entry>
-</row>
-
-<row>
-<entry>Release-Tag-List:</entry> <entry>Release</entry>
-<entry>-t</entry><entry>--tag-list</entry>
-<entry>
-<para>Tags to attach to this release. May be a comma-separated list.</para>
-</entry>
-</row>
-
-<row>
-<entry>Hide:</entry> <entry>Release</entry>
-<entry>-x</entry><entry>--hide</entry>
-<entry>
-<para>If this field is present and has the value “Y”, this
-release will not be visible on the project page.</para>
-</entry>
-</row>
-
-</tbody>
-</tgroup>
-</informaltable>
-
-<para>Additionally, you can specify tagged URL fields for the
-Freshmeat project record with header lines ending in "-URL:". The tag
-will be the header with the "-URL:" suffix stripped off. Some examples
-of how to set frequently-used URL types:</para>
-
-<informaltable>
-<tgroup cols='3' rowsep='1'>
-<thead>
-<row>
- <entry>Message field</entry>
- <entry>Tag</entry>
- <entry>Usage</entry>
-</row>
-</thead>
-<tbody>
-
-<row>
- <entry>Website-URL:</entry>
- <entry>Website</entry>
- <entry>Project website home page. If you specify this, it will be used to
- activate the "More Information" link and Home icon on the project page.</entry>
-</row>
-
-<row>
- <entry>Download-URL:</entry>
- <entry>Download</entry>
- <entry>Preferred download URL. If you specify this, it will be used to
- activate the "Download" link anf foppy-disk on the project page.</entry>
-</row>
-
-<row>
- <entry>Tar/GZ-URL:</entry>
- <entry>Tar/GZ</entry>
- <entry>tar archive compressed with gzip</entry>
-</row>
-
-<row>
- <entry>Tar/BZ-URL:</entry>
- <entry>Tar/BZ</entry>
- <entry>tar archive compressed with bzip</entry>
-</row>
-
-<row>
- <entry>Changelog-URL</entry>
- <entry>Changelog</entry>
- <entry>project change log</entry>
-</row>
-
-</tbody>
-</tgroup>
-</informaltable>
-
-<para>Note that the list of URLs you ship does not merely add to the
-existing list but overwrites it - that is how you delete old URLs
-which are no longer valid.</para>
-
-<para>The Summary, Description, License-List, Project-Tag-List and all
-URL fields, when specified, actually change the record for the
-project. Only the Tag-List, Changes, Version, Release-Focus, and Hide
-fields are actually stored for each version.</para>
-
-<para>Here are the release-focus types. Either the string or the
-integer is acceptable. Case of the string is significant.</para>
-
-<informaltable>
-<tgroup cols='2' rowsep='1'>
-<tbody>
-<row><entry>Initial freshmeat announcement</entry><entry>1</entry></row>
-<row><entry>Documentation</entry><entry>2</entry></row>
-<row><entry>Code cleanup</entry><entry>3</entry></row>
-<row><entry>Minor feature enhancements</entry><entry>4</entry></row>
-<row><entry>Major feature enhancements</entry><entry>5</entry></row>
-<row><entry>Minor bugfixes</entry><entry>6</entry></row>
-<row><entry>Major bugfixes</entry><entry>7</entry></row>
-<row><entry>Minor security fixes</entry><entry>8</entry></row>
-<row><entry>Major security fixes</entry><entry>9</entry></row>
-</tbody>
-</tgroup>
-</informaltable>
-
-<para>The following elements of Freshmeat project metadata cannot yet
-be modified using this tool: <varname>name</varname>,
-<varname>translation list</varname>, <varname>programming
-language list</varname>, <varname>operating system
-list</varname>, <varname>approved screenshots</varname>,
-and dependency records.</para>
-
-<para>Support for updating all these elements except
-<varname>name</varname> is planned in future releases.</para>
-</refsect1>
-
-<refsect1><title>USAGE</title>
-
-<para>Here is an example of a release-information record that could be
-fed to <application>freshmeat-submit</application> on standard input:</para>
-
-<programlisting>
-Project: doclifter
-Version: 1.1
-Release-Focus: Major feature enhancements
-Hide: N
-Website-URL: http://www.catb.org/~esr/doclifter/
-Tar/GZ-URL: http://www.catb.org/~esr/doclifter/doclifter-${version}.tar.gz
-
-Fixed a bug in conditional evaluation that twadmin(8) tickled. Better
-detection of pure inclusions. Better blank-section elimination.
-Kleene star recognized as ... synonym in command synopses. Correct
-some bugs in semantic-hint collection. Recognize Qt manual pages and
-use their conventions. Better lifting of mandoc-generated
-pages. Translate groff-style \[...] escapes, flag unknown ones. Can
-now parse K&R style function prototypes as well as ANSI ones. This
-version lifts 96% of 9829 manual pages in a full Red Hat 9
-installation with Fedora updates to *validated* XML-DocBook.
-</programlisting>
-</refsect1>
-
-<refsect1><title>WARNING</title>
-<para>Since this program was first written, Freshmeat has changed its
-data API significantly. While we have attempted to maintain backward
-compatibility, the data model has changed in significant ways.</para>
-
-<para>There is no longer a Branch attribute.</para>
-
-<para>The old Release-Focus field is gone. As a special case, this
-pragram will validate it and use it to fill in the project tags list.</para>
-
-<para>The old License field is now License-List and can take a
-comma-separated list of licenses.</para>
-
-<para>All the old URL fields are gone. They're replaced by
-the general mechanism for pairing URLs with labels.</para>
-
-<para>Earlier versions parsed RPM specfiles. This was poor
-separation of function and has been removed. That responsibility
-belong to the human or program generating the job card.</para>
-</refsect1>
-
-<refsect1><title>FILES</title>
-
-<variablelist>
-<varlistentry>
-<term><filename>~/.netrc</filename></term>
-<listitem>
-<para><application>freshmeat-submit</application> expects to find your
-Freshmeat authorization tag here. The correct form for an entry looks like
-this:</para>
-
-<screen>
-machine freshmeat
- account <your_api_auth_code>
-</screen>
-
-<para>For security, make sure your <filename>~/.netrc</filename> file
-is not world-readable; we recommend doing <command>chmod 600
-~/.netrc</command>.</para>
-</listitem>
-</varlistentry>
-</variablelist>
-
-</refsect1>
-<refsect1><title>BUGS</title>
-
-<para>The URLs returned in a project metadata query are Freshmeat redirectors,
-not the actual URLS supplied by previous project updates. This is due to
-a limitation in the Freshmeat API which may be fixed in future versions
-of that API.</para>
-</refsect1>
-<refsect1><title>RETURN VALUES</title>
-
-<para>0 on success, 1 on failure. In case of failure, any error message
-passed back by the Freshmeat API is issued to standard error.</para>
-
-</refsect1>
-<refsect1 id='author'><title>AUTHORS</title>
-<para>Eric S. Raymond <email>esr@thyrsus.com</email>.</para>
-</refsect1>
-</refentry>