Commits

tarek  committed e98be93 Merge

merge dance

  • Participants
  • Parent commits ce4f348, 4ea370f
  • Branches 0.6-maintenance

Comments (0)

Files changed (15)

 0.6.4
 -----
 
-* Added the generation of distribute_setup_3k.py during the release.
+* Added the generation of `distribute_setup_3k.py` during the release.
 
+* Added an upload_docs command to easily upload project documentation to
+  PyPI's http://packages.python.org.
 
 -----
 0.6.3

File CONTRIBUTORS.txt

 
 * Alex Grönholm
 * Hanno Schlichting
+* Jannis Leidel
 * Lennart Regebro
 * Martin von Loewis
 * Philip Envey
 Download ``distribute_setup.py`` and execute it, using the Python interpreter of
 your choice.
 
-If your shell has the ``wget`` program you can do::
+If your shell has the ``curl`` program you can do::
 
-    $ wget http://nightly.ziade.org/distribute_setup.py
+    $ curl -O http://nightly.ziade.org/distribute_setup.py
     $ python distribute_setup.py
 
 If you are under Python 3, use ``distribute_setup_3k.py``::
 
-    $ wget http://nightly.ziade.org/distribute_setup_3k.py
+    $ curl -O http://nightly.ziade.org/distribute_setup_3k.py
     $ python distribute_setup_3k.py
 
 Notice that both files are provided in the source release.
 
 Download the source tarball, uncompress it, then run the install command::
 
-    $ wget http://pypi.python.org/packages/source/d/distribute/distribute-0.6.4.tar.gz
+    $ curl -O http://pypi.python.org/packages/source/d/distribute/distribute-0.6.4.tar.gz
     $ tar -xzvf distribute-0.6.4.tar.gz
     $ cd distribute-0.6
     $ python setup.py install

File distribute.egg-info/entry_points.txt

 saveopts = setuptools.command.saveopts:saveopts
 egg_info = setuptools.command.egg_info:egg_info
 register = setuptools.command.register:register
+upload_docs = setuptools.command.upload_docs:upload_docs
 install_egg_info = setuptools.command.install_egg_info:install_egg_info
 alias = setuptools.command.alias:alias
 easy_install = setuptools.command.easy_install:easy_install

File docs/_theme/nature/static/nature.css_t

+/**
+ * Sphinx stylesheet -- default theme
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+ 
+@import url("basic.css");
+ 
+/* -- page layout ----------------------------------------------------------- */
+ 
+body {
+    font-family: Arial, sans-serif;
+    font-size: 100%;
+    background-color: #111;
+    color: #555;
+    margin: 0;
+    padding: 0;
+}
+
+div.documentwrapper {
+    float: left;
+    width: 100%;
+}
+
+div.bodywrapper {
+    margin: 0 0 0 300px;
+}
+
+hr{
+    border: 1px solid #B1B4B6;
+}
+ 
+div.document {
+    background-color: #eee;
+}
+ 
+div.body {
+    background-color: #ffffff;
+    color: #3E4349;
+    padding: 0 30px 30px 30px;
+    font-size: 0.8em;
+}
+ 
+div.footer {
+    color: #555;
+    width: 100%;
+    padding: 13px 0;
+    text-align: center;
+    font-size: 75%;
+}
+ 
+div.footer a {
+    color: #444;
+    text-decoration: underline;
+}
+ 
+div.related {
+    background-color: #6BA81E;
+    line-height: 32px;
+    color: #fff;
+    text-shadow: 0px 1px 0 #444;
+    font-size: 0.80em;
+}
+ 
+div.related a {
+    color: #E2F3CC;
+}
+ 
+div.sphinxsidebar {
+    font-size: 0.75em;
+    line-height: 1.5em;
+    width: 300px
+}
+
+div.sphinxsidebarwrapper{
+    padding: 20px 0;
+}
+ 
+div.sphinxsidebar h3,
+div.sphinxsidebar h4 {
+    font-family: Arial, sans-serif;
+    color: #222;
+    font-size: 1.2em;
+    font-weight: normal;
+    margin: 0;
+    padding: 5px 10px;
+    background-color: #ddd;
+    text-shadow: 1px 1px 0 white
+}
+
+div.sphinxsidebar h4{
+    font-size: 1.1em;
+}
+ 
+div.sphinxsidebar h3 a {
+    color: #444;
+}
+ 
+ 
+div.sphinxsidebar p {
+    color: #888;
+    padding: 5px 20px;
+}
+ 
+div.sphinxsidebar p.topless {
+}
+ 
+div.sphinxsidebar ul {
+    margin: 10px 10px 10px 20px;
+    padding: 0;
+    color: #000;
+}
+ 
+div.sphinxsidebar a {
+    color: #444;
+}
+ 
+div.sphinxsidebar input {
+    border: 1px solid #ccc;
+    font-family: sans-serif;
+    font-size: 1em;
+}
+
+div.sphinxsidebar input[type=text]{
+    margin-left: 20px;
+}
+ 
+/* -- body styles ----------------------------------------------------------- */
+ 
+a {
+    color: #005B81;
+    text-decoration: none;
+}
+ 
+a:hover {
+    color: #E32E00;
+    text-decoration: underline;
+}
+ 
+div.body h1,
+div.body h2,
+div.body h3,
+div.body h4,
+div.body h5,
+div.body h6 {
+    font-family: Arial, sans-serif;
+    background-color: #BED4EB;
+    font-weight: normal;
+    color: #212224;
+    margin: 30px 0px 10px 0px;
+    padding: 5px 0 5px 10px;
+    text-shadow: 0px 1px 0 white
+}
+ 
+div.body h1 { border-top: 20px solid white; margin-top: 0; font-size: 200%; }
+div.body h2 { font-size: 150%; background-color: #C8D5E3; }
+div.body h3 { font-size: 120%; background-color: #D8DEE3; }
+div.body h4 { font-size: 110%; background-color: #D8DEE3; }
+div.body h5 { font-size: 100%; background-color: #D8DEE3; }
+div.body h6 { font-size: 100%; background-color: #D8DEE3; }
+ 
+a.headerlink {
+    color: #c60f0f;
+    font-size: 0.8em;
+    padding: 0 4px 0 4px;
+    text-decoration: none;
+}
+ 
+a.headerlink:hover {
+    background-color: #c60f0f;
+    color: white;
+}
+ 
+div.body p, div.body dd, div.body li {
+    line-height: 1.5em;
+}
+ 
+div.admonition p.admonition-title + p {
+    display: inline;
+}
+
+div.highlight{
+    background-color: white;
+}
+
+div.note {
+    background-color: #eee;
+    border: 1px solid #ccc;
+}
+ 
+div.seealso {
+    background-color: #ffc;
+    border: 1px solid #ff6;
+}
+ 
+div.topic {
+    background-color: #eee;
+}
+ 
+div.warning {
+    background-color: #ffe4e4;
+    border: 1px solid #f66;
+}
+ 
+p.admonition-title {
+    display: inline;
+}
+ 
+p.admonition-title:after {
+    content: ":";
+}
+ 
+pre {
+    padding: 10px;
+    background-color: White;
+    color: #222;
+    line-height: 1.2em;
+    border: 1px solid #C6C9CB;
+    font-size: 1.2em;
+    margin: 1.5em 0 1.5em 0;
+    -webkit-box-shadow: 1px 1px 1px #d8d8d8;
+    -moz-box-shadow: 1px 1px 1px #d8d8d8;
+}
+ 
+tt {
+    background-color: #ecf0f3;
+    color: #222;
+    padding: 1px 2px;
+    font-size: 1.2em;
+    font-family: monospace;
+}

File docs/_theme/nature/static/pygments.css

+.c { color: #999988; font-style: italic } /* Comment */
+.k { font-weight: bold } /* Keyword */
+.o { font-weight: bold } /* Operator */
+.cm { color: #999988; font-style: italic } /* Comment.Multiline */
+.cp { color: #999999; font-weight: bold } /* Comment.preproc */
+.c1 { color: #999988; font-style: italic } /* Comment.Single */
+.gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
+.ge { font-style: italic } /* Generic.Emph */
+.gr { color: #aa0000 } /* Generic.Error */
+.gh { color: #999999 } /* Generic.Heading */
+.gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
+.go { color: #111 } /* Generic.Output */
+.gp { color: #555555 } /* Generic.Prompt */
+.gs { font-weight: bold } /* Generic.Strong */
+.gu { color: #aaaaaa } /* Generic.Subheading */
+.gt { color: #aa0000 } /* Generic.Traceback */
+.kc { font-weight: bold } /* Keyword.Constant */
+.kd { font-weight: bold } /* Keyword.Declaration */
+.kp { font-weight: bold } /* Keyword.Pseudo */
+.kr { font-weight: bold } /* Keyword.Reserved */
+.kt { color: #445588; font-weight: bold } /* Keyword.Type */
+.m { color: #009999 } /* Literal.Number */
+.s { color: #bb8844 } /* Literal.String */
+.na { color: #008080 } /* Name.Attribute */
+.nb { color: #999999 } /* Name.Builtin */
+.nc { color: #445588; font-weight: bold } /* Name.Class */
+.no { color: #ff99ff } /* Name.Constant */
+.ni { color: #800080 } /* Name.Entity */
+.ne { color: #990000; font-weight: bold } /* Name.Exception */
+.nf { color: #990000; font-weight: bold } /* Name.Function */
+.nn { color: #555555 } /* Name.Namespace */
+.nt { color: #000080 } /* Name.Tag */
+.nv { color: purple } /* Name.Variable */
+.ow { font-weight: bold } /* Operator.Word */
+.mf { color: #009999 } /* Literal.Number.Float */
+.mh { color: #009999 } /* Literal.Number.Hex */
+.mi { color: #009999 } /* Literal.Number.Integer */
+.mo { color: #009999 } /* Literal.Number.Oct */
+.sb { color: #bb8844 } /* Literal.String.Backtick */
+.sc { color: #bb8844 } /* Literal.String.Char */
+.sd { color: #bb8844 } /* Literal.String.Doc */
+.s2 { color: #bb8844 } /* Literal.String.Double */
+.se { color: #bb8844 } /* Literal.String.Escape */
+.sh { color: #bb8844 } /* Literal.String.Heredoc */
+.si { color: #bb8844 } /* Literal.String.Interpol */
+.sx { color: #bb8844 } /* Literal.String.Other */
+.sr { color: #808000 } /* Literal.String.Regex */
+.s1 { color: #bb8844 } /* Literal.String.Single */
+.ss { color: #bb8844 } /* Literal.String.Symbol */
+.bp { color: #999999 } /* Name.Builtin.Pseudo */
+.vc { color: #ff99ff } /* Name.Variable.Class */
+.vg { color: #ff99ff } /* Name.Variable.Global */
+.vi { color: #ff99ff } /* Name.Variable.Instance */
+.il { color: #009999 } /* Literal.Number.Integer.Long */

File docs/_theme/nature/theme.conf

+[theme]
+inherit = basic
+stylesheet = nature.css
+pygments_style = tango

File docs/conf.py

 
 import sys, os
 
-# If your extensions are in another directory, add it here. If the directory
-# is relative to the documentation root, use os.path.abspath to make it
-# absolute, like shown here.
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
 #sys.path.append(os.path.abspath('.'))
 
-# General configuration
-# ---------------------
+# -- General configuration -----------------------------------------------------
 
 # Add any Sphinx extension module names here, as strings. They can be extensions
 # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
 extensions = []
 
 # Add any paths that contain templates here, relative to this directory.
-templates_path = ['.templates']
+templates_path = ['_templates']
 
 # The suffix of source filenames.
 source_suffix = '.txt'
 # built documents.
 #
 # The short X.Y version.
-version = '0.6.1'
+version = '0.6.4'
 # The full version, including alpha/beta/rc tags.
-release = '0.6.1'
+release = '0.6.4'
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
 # The name of the Pygments (syntax highlighting) style to use.
 pygments_style = 'sphinx'
 
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
 
-# Options for HTML output
-# -----------------------
 
-# The style sheet to use for HTML and HTML Help pages. A file of that name
-# must exist either in Sphinx' static/ path, or in one of the custom paths
-# given in html_static_path.
-html_style = 'default.css'
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  Major themes that come with
+# Sphinx are currently 'default' and 'sphinxdoc'.
+html_theme = 'nature'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+html_theme_path = ['_theme']
 
 # The name for this set of Sphinx documents.  If None, it defaults to
 # "<project> v<release> documentation".
-#html_title = None
+html_title = "Distribute documentation"
 
 # A shorter title for the navigation bar.  Default is the same as html_title.
-#html_short_title = None
+html_short_title = "Distribute"
 
 # The name of an image file (relative to this directory) to place at the top
 # of the sidebar.
 # Add any paths that contain custom static files (such as style sheets) here,
 # relative to this directory. They are copied after the builtin static files,
 # so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['.static']
+#html_static_path = ['_static']
 
 # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
 # using the given strftime format.
 # If true, the index is split into individual pages for each letter.
 #html_split_index = False
 
-# If true, the reST sources are included in the HTML build as _sources/<name>.
-#html_copy_source = True
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
 
 # If true, an OpenSearch description file will be output, and all pages will
 # contain a <link> tag referring to it.  The value of this option must be the
 htmlhelp_basename = 'Distributedoc'
 
 
-# Options for LaTeX output
-# ------------------------
+# -- Options for LaTeX output --------------------------------------------------
 
 # The paper size ('letter' or 'a4').
 #latex_paper_size = 'letter'
 #latex_font_size = '10pt'
 
 # Grouping the document tree into LaTeX files. List of tuples
-# (source start file, target name, title, author, document class [howto/manual]).
+# (source start file, target name, title, author, documentclass [howto/manual]).
 latex_documents = [
   ('index', 'Distribute.tex', ur'Distribute Documentation',
    ur'The fellowship of the packaging', 'manual'),

File docs/easy_install.txt

 on `Command-Line Options`_ for more details on the ``--allow-hosts`` option.
 
 By default, there are no host restrictions in effect, but you can change this
-default by editing the appropriate `configuration files`_ and adding::
+default by editing the appropriate `configuration files`_ and adding:
+
+.. code-block:: ini
 
     [easy_install]
     allow_hosts = *.myintranet.example.com,*.python.org
 
 If you are setting up an intranet site for package downloads, you may want to
 configure the target machines to use your download site by default, adding
-something like this to their `configuration files`_::
+something like this to their `configuration files`_:
+
+.. code-block:: ini
 
     [easy_install]
     find_links = http://mypackages.example.com/somedir/
 EasyInstall respects standard distutils `Configuration Files`_, so you can use
 them to configure build options for packages that it installs from source.  For
 example, if you are on Windows using the MinGW compiler, you can configure the
-default compiler by putting something like this::
+default compiler by putting something like this:
+
+.. code-block:: ini
 
     [build]
     compiler = mingw32
 EasyInstall will look first for a ``setup.cfg`` file in the current directory,
 then a ``~/.pydistutils.cfg`` or ``$HOME\\pydistutils.cfg`` (on Unix-like OSes
 and Windows, respectively), and finally a ``distutils.cfg`` file in the
-``distutils`` package directory.  Here's a simple example::
+``distutils`` package directory.  Here's a simple example:
+
+.. code-block:: ini
 
     [easy_install]
 
 directory of your Python library.  The correct directory will be something like
 ``/usr/lib/python2.X/distutils`` on most Posix systems and something like
 ``C:\\Python2X\Lib\distutils`` on Windows machines.  Add the following lines
-to the file, substituting the correct Python version if necessary::
+to the file, substituting the correct Python version if necessary:
+
+.. code-block:: ini
 
     [install]
     install_lib = ~/lib/python2.3
 EasyInstall already knows this.
 
 Before installing EasyInstall/setuptools, just create a ``~/.pydistutils.cfg``
-file with the following contents (or add this to the existing contents)::
+file with the following contents (or add this to the existing contents):
+
+.. code-block:: ini
 
     [install]
     install_lib = ~/Library/Python/$py_version_short/site-packages
 and scripts in ``~/bin``, here's what you need to do:
 
 First, edit ``~/.pydistutils.cfg`` to include these settings, if you don't
-already have them::
+already have them:
+
+.. code-block:: ini
 
     [install]
     install_lib = ~/py-lib

File docs/python3.txt

 
 A typical setup.py can look something like this::
 
-      from setuptools import setup
-      
-      setup(name='your.module',
-            version = '1.0',
-            description='This is your awesome module',
-            author='You',
-            author_email='your@email',
-            package_dir = {'': 'src'},
-            packages = ['your', 'you.module'],
-            test_suite = 'your.module.tests',
-            use_2to3 = True,
-            convert_2to3_doctests = ['src/your/module/README.txt'],
-            use_2to3_fixers = ['your.fixers']
-            )
+    from setuptools import setup
+    
+    setup(
+        name='your.module',
+        version = '1.0',
+        description='This is your awesome module',
+        author='You',
+        author_email='your@email',
+        package_dir = {'': 'src'},
+        packages = ['your', 'you.module'],
+        test_suite = 'your.module.tests',
+        use_2to3 = True,
+        convert_2to3_doctests = ['src/your/module/README.txt'],
+        use_2to3_fixers = ['your.fixers']
+    )
 
 Differential conversion
 -----------------------
 error this is easy. Simply conditionally add the new parameters into an extra 
 dict and pass that dict into setup()::
 
-      from setuptools import setup
-      import sys
-      
-      extra = {}
-      if sys.version_info >= (3,):
-          extra['use_2to3'] = True
-          extra['convert_2to3_doctests'] = ['src/your/module/README.txt']
-          extra['use_2to3_fixers'] = ['your.fixers']
-      
-      setup(name='your.module',
-            version = '1.0',
-            description='This is your awesome module',
-            author='You',
-            author_email='your@email',
-            package_dir = {'': 'src'},
-            packages = ['your', 'you.module'],
-            test_suite = 'your.module.tests',
-            **extra
-            )
+    from setuptools import setup
+    import sys
+    
+    extra = {}
+    if sys.version_info >= (3,):
+        extra['use_2to3'] = True
+        extra['convert_2to3_doctests'] = ['src/your/module/README.txt']
+        extra['use_2to3_fixers'] = ['your.fixers']
+    
+    setup(
+        name='your.module',
+        version = '1.0',
+        description='This is your awesome module',
+        author='You',
+        author_email='your@email',
+        package_dir = {'': 'src'},
+        packages = ['your', 'you.module'],
+        test_suite = 'your.module.tests',
+        **extra
+    )
 
 This way the parameters will only be used under Python 3, where you have to
 use Distribute.

File docs/setuptools.txt

             '': ['*.txt', '*.rst'],
             # And include any *.msg files found in the 'hello' package, too:
             'hello': ['*.msg'],
-        }
+        },
 
         # metadata for upload to PyPI
         author = "Me",
 download `distribute_setup.py`_, and put it in the same directory as your ``setup.py``
 script.  (Be sure to add it to your revision control system, too.)  Then add
 these two lines to the very top of your setup script, before the script imports
-anything from setuptools::
+anything from setuptools:
+
+.. code-block:: python
 
     import distribute_setup
     distribute_setup.use_setuptools()
 If you expect your users to track in-development versions of your project via
 Subversion, there are a few additional steps you should take to ensure that
 things work smoothly with EasyInstall.  First, you should add the following
-to your project's ``setup.cfg`` file::
+to your project's ``setup.cfg`` file:
+
+.. code-block:: ini
 
     [egg_info]
     tag_build = .dev
 to download ``projectname==dev`` in order to get the latest in-development
 code.  Note that if your project depends on such in-progress code, you may wish
 to specify your ``install_requires`` (or other requirements) to include
-``==dev``, e.g.::
+``==dev``, e.g.:
+
+.. code-block:: python
 
     install_requires = ["OtherProject>=0.2a1.dev-r143,==dev"]
 
     The URL of the repository to upload to.  Defaults to
     http://pypi.python.org/pypi (i.e., the main PyPI installation).
 
+.. _upload_docs:
+
+``upload_docs`` - Upload package documentation to PyPI
+======================================================
+
+PyPI now supports uploading project documentation to the dedicated URL
+http://packages.python.org/<project>/.
+
+The ``upload_docs`` command will create the necessary zip file out of a
+documentation directory and will post to the repository.
+
+Note that to upload the documentation of a project, the corresponding version
+must already be registered with PyPI, using the distutils ``register``
+command -- just like the ``upload`` command.
+
+Assuming there is an ``Example`` project with documentation in the
+subdirectory ``docs``, e.g.::
+
+  Example/
+  |-- example.py
+  |-- setup.cfg
+  |-- setup.py
+  |-- docs
+  |   |-- build
+  |   |   `-- html
+  |   |   |   |-- index.html
+  |   |   |   `-- tips_tricks.html
+  |   |-- conf.py
+  |   |-- index.txt
+  |   `-- tips_tricks.txt
+
+You can simply pass the documentation directory path to the ``upload_docs``
+command::
+
+    python setup.py upload_docs --upload-dir=docs/build/html
+
+As with any other ``setuptools`` based command, you can define useful
+defaults in the ``setup.cfg`` of your Python project, e.g.:
+
+.. code-block:: ini
+
+    [upload_docs]
+    upload-dir = docs/build/html
+
+The ``upload_docs`` command has the following options:
+
+``--upload-dir``
+    The directory to be uploaded to the repository.
+
+``--show-response``
+    Display the full response text from server; this is useful for debugging
+    PyPI problems.
+
+``--repository=URL, -r URL``
+    The URL of the repository to upload to.  Defaults to
+    http://pypi.python.org/pypi (i.e., the main PyPI installation).
+
 
 ------------------------------------
 Extending and Reusing ``setuptools``
 are under revision control.
 
 For example, if you were going to create a plugin for a revision control system
-called "foobar", you would write a function something like this::
+called "foobar", you would write a function something like this:
+
+.. code-block:: python
 
     def find_files_for_foobar(dirname):
         # loop to yield paths that start with `dirname`
 release = egg_info -RDb ''
 source = register sdist binary
 binary = bdist_egg upload --show-response
+
+[build_sphinx]
+source-dir = docs/
+build-dir = docs/build
+all_files = 1
+
+[upload_docs]
+upload-dir = docs/build/html
     License :: OSI Approved :: Zope Public License
     Operating System :: OS Independent
     Programming Language :: Python
+    Programming Language :: Python :: 3
     Topic :: Software Development :: Libraries :: Python Modules
     Topic :: System :: Archiving :: Packaging
     Topic :: System :: Systems Administration

File setuptools/command/__init__.py

     'alias', 'bdist_egg', 'bdist_rpm', 'build_ext', 'build_py', 'develop',
     'easy_install', 'egg_info', 'install', 'install_lib', 'rotate', 'saveopts',
     'sdist', 'setopt', 'test', 'upload', 'install_egg_info', 'install_scripts',
-    'register', 'bdist_wininst',
+    'register', 'bdist_wininst', 'upload_docs',
 ]
 
 import sys

File setuptools/command/upload_docs.py

+# -*- coding: utf-8 -*-
+"""upload_docs
+
+Implements a Distutils 'upload_docs' subcommand (upload documentation to
+PyPI's packages.python.org).
+"""
+
+import os
+import socket
+import zipfile
+import httplib
+import base64
+import urlparse
+import tempfile
+import cStringIO as StringIO
+
+from distutils import log
+from distutils.errors import DistutilsOptionError
+from distutils.command.upload import upload
+
+class upload_docs(upload):
+
+    description = 'Upload documentation to PyPI'
+
+    user_options = [
+        ('repository=', 'r',
+         "url of repository [default: %s]" % upload.DEFAULT_REPOSITORY),
+        ('show-response', None,
+         'display full response text from server'),
+        ('upload-dir=', None, 'directory to upload'),
+        ]
+    boolean_options = upload.boolean_options
+
+    def initialize_options(self):
+        upload.initialize_options(self)
+        self.upload_dir = None
+
+    def finalize_options(self):
+        upload.finalize_options(self)
+        if self.upload_dir is None:
+            build = self.get_finalized_command('build')
+            self.upload_dir = os.path.join(build.build_base, 'docs')
+            self.mkpath(self.upload_dir)
+        self.ensure_dirname('upload_dir')
+        self.announce('Using upload directory %s' % self.upload_dir)
+
+    def create_zipfile(self):
+        name = self.distribution.metadata.get_name()
+        tmp_dir = tempfile.mkdtemp()
+        tmp_file = os.path.join(tmp_dir, "%s.zip" % name)
+        zip_file = zipfile.ZipFile(tmp_file, "w")
+        for root, dirs, files in os.walk(self.upload_dir):
+            if not files:
+                raise DistutilsOptionError(
+                    "no files found in upload directory '%s'"
+                    % self.upload_dir)
+            for name in files:
+                full = os.path.join(root, name)
+                relative = root[len(self.upload_dir):].lstrip(os.path.sep)
+                dest = os.path.join(relative, name)
+                zip_file.write(full, dest)
+        zip_file.close()
+        return tmp_file
+
+    def run(self):
+        zip_file = self.create_zipfile()
+        self.upload_file(zip_file)
+
+    def upload_file(self, filename):
+        content = open(filename, 'rb').read()
+        meta = self.distribution.metadata
+        data = {
+            ':action': 'doc_upload',
+            'name': meta.get_name(),
+            'content': (os.path.basename(filename), content),
+        }
+        # set up the authentication
+        auth = "Basic " + base64.encodestring(
+            self.username + ":" + self.password).strip()
+
+        # Build up the MIME payload for the POST data
+        boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
+        sep_boundary = '\n--' + boundary
+        end_boundary = sep_boundary + '--'
+        body = StringIO.StringIO()
+        for key, value in data.items():
+            # handle multiple entries for the same name
+            if type(value) != type([]):
+                value = [value]
+            for value in value:
+                if type(value) is tuple:
+                    fn = ';filename="%s"' % value[0]
+                    value = value[1]
+                else:
+                    fn = ""
+                value = str(value)
+                body.write(sep_boundary)
+                body.write('\nContent-Disposition: form-data; name="%s"'%key)
+                body.write(fn)
+                body.write("\n\n")
+                body.write(value)
+                if value and value[-1] == '\r':
+                    body.write('\n')  # write an extra newline (lurve Macs)
+        body.write(end_boundary)
+        body.write("\n")
+        body = body.getvalue()
+
+        self.announce("Submitting documentation to %s" % (self.repository),
+                      log.INFO)
+
+        # build the Request
+        # We can't use urllib2 since we need to send the Basic
+        # auth right with the first request
+        schema, netloc, url, params, query, fragments = \
+            urlparse.urlparse(self.repository)
+        assert not params and not query and not fragments
+        if schema == 'http':
+            http = httplib.HTTPConnection(netloc)
+        elif schema == 'https':
+            http = httplib.HTTPSConnection(netloc)
+        else:
+            raise AssertionError("unsupported schema "+schema)
+
+        data = ''
+        loglevel = log.INFO
+        try:
+            http.connect()
+            http.putrequest("POST", url)
+            http.putheader('Content-type',
+                           'multipart/form-data; boundary=%s'%boundary)
+            http.putheader('Content-length', str(len(body)))
+            http.putheader('Authorization', auth)
+            http.endheaders()
+            http.send(body)
+        except socket.error, e:
+            self.announce(str(e), log.ERROR)
+            return
+
+        r = http.getresponse()
+        if r.status == 200:
+            self.announce('Server response (%s): %s' % (r.status, r.reason),
+                          log.INFO)
+        elif r.status == 301:
+            location = r.getheader('Location')
+            if location is None:
+                location = 'http://packages.python.org/%s/' % meta.get_name()
+            self.announce('Upload successful. Visit %s' % location,
+                          log.INFO)
+        else:
+            self.announce('Upload failed (%s): %s' % (r.status, r.reason),
+                          log.ERROR)
+        if self.show_response:
+            print '-'*75, r.read(), '-'*75