Commits

Ronald Oussoren committed af08b83

First attempt at a command for uploading the documentation to PyPI

This is based on the python2 code for py2app, the port to py3k
is untested.

  • Participants
  • Parent commits f8a1bbc

Comments (0)

Files changed (1)

 TODO:
 * Tweak templates/layout to taste
 
-* Add 'upload' command
-
 * When that works: make packages.python.org/pyobjc* redirect
   to the documentation site (because this will add a "package documenation"
   link to to PyPI)
 """
 import sys
 import os
+import io
 import re
 import shutil
 import zipfile
 import argparse
 import subprocess
 import textwrap
+import configparser
+import http.client
 
 # All paths in the document map are
 # relative to the root of the pyobjc
     html_cmd = subparsers.add_parser("html", help="Build HTML tree")
     html_cmd.add_argument("--update", dest="html_update", action="store_true", help="update source before build", default=False)
 
-    html_cmd = subparsers.add_parser("show", help="Show HTML in browser")
+    show_cmd = subparsers.add_parser("show", help="Show HTML in browser")
+    linkcheck_cmd = subparsers.add_parser("linkcheck", help="Check links in HTML output")
     cleanup_cmd = subparsers.add_parser("cleanup", help="Remove HTML tree and collected documentation")
 
+    upload_cmd = subparsers.add_parser("upload", help="Upload HTML to packages.python.org/pyobjc")
+    upload_cmd.add_argument("--update", dest="html_update", action="store_true", help="update html before upload", default=False)
+    upload_cmd.add_argument("--dryrun", action="store_true", help="dry-run, don't actually upload", default=True)
+
     args = parser.parse_args()
     if not hasattr(args, "command") or args.command is None:
         # Regression in python3: subcommand's aren't required anymore???
 
     subprocess.call(['/usr/bin/open', os.path.join(MY_DIR, '_build', 'html', 'index.html')])
 
+def action_linkcheck(args):
+    if args.verbose:
+        print("* Check HTML links")
+
+    subprocess.check_call(["sphinx-build", "-b", "linkcheck", "-d", "_build/doctrees", MY_DIR, os.path.join(MY_DIR, "_build/linkcheck")],
+            cwd=MY_DIR)
+
+def action_upload(args):
+    cfg = configparser.ConfigParser()
+    cfg.read([os.path.expanduser("~/.pypirc")])
+    if not cfg.has_section("server-login"):
+        print("No PyPI configuration found", file=sys.stderr)
+        sys.exit(1)
+    if not cfg.has_option("server-login", "username"):
+        print("No PyPI username found", file=sys.stderr)
+        sys.exit(1)
+    if not cfg.has_option("server-login", "password"):
+        print("No PyPI username found", file=sys.stderr)
+        sys.exit(1)
+
+    username = cfg.get("server-login", "username")
+    password = cfg.get("server-login", "password")
+
+    if args.html_update:
+        action_html(args)
+
+    if args.verbose:
+        if args.dryrun:
+            print("* Upload HTML (dry-run)")
+        else:
+            print("* Upload HTML")
+
+    if not os.path.exists(os.path.join(MY_DIR, "dist")):
+        os.mkdir(os.path.join(MY_DIR, "dist"))
+
+    if args.verbose:
+        print("store documentation in zipfile")
+    path = os.path.join(MY_DIR, "dist", "pyobjc-website-docs.zip")
+    zf = zipfile.ZipFile(path, "w", compression=zipfile.ZIP_DEFLATED)
+
+    source_dir = os.pathjoin(MY_DIR, "_build", "html")
+    for toplevel, dirs, files in os.walk(source_dir):
+        for fn in files:
+            fullname = os.path.join(toplevel, fn)
+            relname = os.path.relpath(fullname, source_dir)
+
+            print ("%s -> %s"%(fullname, relname))
+            zf.write(fullname, relname)
+
+    zf.close()
+
+    if args.dryrun:
+        print("* Don't upload, dryrun")
+        sys.exit(0)
+
+    content = open(path, "rb").read()
+    
+    data = {
+        ':action': 'doc_upload',
+        'name': 'pyobjc',
+        'content': (os.path.basename(path), content),
+    }
+    auth = "Basic " + standard_b64encode(username + ":" + password)
+
+    if args.verbose:
+        print("uploading documentation to {}".format(repository))
+
+    boundary = b'--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
+    sep_boundary = b'\n--' + boundary
+    end_boundary = sep_boundary + b'--'
+    body = io.BytesIO()
+    for key, value in data.items():
+        if not isinstance(value, list):
+            value = [value]
+
+        for value in value:
+            if isinstance(value, tuple):
+                fn = ';filename="{}"'.format(value[0])
+                value = value[1]
+            else:
+                fn = ''
+
+            body.write(sep_boundary)
+            body.write(b'\nContent-Disposition: form-data; name="' + key.encode('utf-8') + '"')
+            body.write(fn.encode('utf-8'))
+            body.write(b"\n\n")
+            if isinstance(value, bytes):
+                body.write(value)
+            else:
+                body.write(value.encode('utf-8'))
+
+    body.write(end_boundary)
+    body.write(b'\n')
+    body = body.getvalue()
+
+    repository = "https://pypi.python.org/pypi"
+
+    schema, netloc, url, params, query, fragments = urlparse.urlparse(repository)
+
+    if schema == 'http':
+        http = http.client.HTTPConnection(netloc)
+    elif schema == 'https':
+        http = http.client.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 = socket.exc_info()[1]
+        self.announce(str(e), log.ERROR)
+        return
+
+    r = http.getresponse()
+    if r.status in (200, 301):
+        print("Upload succeeded ({}): {}".format(r.status, r.reason))
+    else:
+        print("Upload failed ({}): {}".format(r.status, r.reason))
+
+        print('-'*75) 
+        print(r.read())
+        print('-'*75)
+
 def action_html(args):
     import conf
 
     elif args.command == 'html':
         action_html(args)
 
+    elif args.command == 'linkcheck':
+        action_linkcheck(args)
+
+    elif args.command == 'upload':
+        action_upload(args)
+
     else:
         raise RuntimeError("Unhandled command: {}".format(args.command))