PyDocURLProtocol

This example implements a subclass of NSURLProtocol that can be used to load the pydoc documentation of a module.

It also includes a simple documentation browser using WebKit and the PyDocURLProtocol class.

Sources

PyDocBrowser.py

from PyObjCTools import AppHelper

import Foundation, AppKit, WebKit
import PyDocURLProtocol
import PyDocEvents

PyDocURLProtocol.setup()

# the web browser doesn"t have or need any code really

if __name__ == "__main__":
    AppHelper.runEventLoop()

PyDocEvents.py

"""
Minimal applescript support.

The PyDocEventHandler handles just the event that is used to open URLs. Thanks
to this class you can use ``open pydoc:///os.open`` from a command-line, or
add ``pydoc:///`` to HTML files.
"""
from Cocoa import NSObject, NSAppleEventManager

import struct
import objc

def fourCharToInt(code):
    return struct.unpack('>l', code)[0]

class PyDocEventHandler (NSObject):
    webview = objc.IBOutlet('webview')
    urlfield = objc.IBOutlet('urlfield')

    def handleEvent_withReplyEvent_(self, event, replyEvent):
        theURL = event.descriptorForKeyword_(fourCharToInt(b'----'))

        self.urlfield.setStringValue_(theURL.stringValue())
        self.webview.takeStringURLFrom_(theURL)


    def awakeFromNib(self):
        manager = NSAppleEventManager.sharedAppleEventManager()

        # Add a handler for the event GURL/GURL. One might think that
        # Carbon.AppleEvents.kEISInternetSuite/kAEISGetURL would work,
        # but the system headers (and hence the Python wrapper for those)
        # are wrong.
        manager.setEventHandler_andSelector_forEventClass_andEventID_(
            self, 'handleEvent:withReplyEvent:',
                fourCharToInt(b'GURL'),
                fourCharToInt(b'GURL'))

PyDocURLProtocol.py

from __future__ import print_function
from Cocoa import NSURLProtocol, NSURLResponse, NSError, NSString, NSURL, NSData
from Cocoa import NSURLErrorDomain, NSURLErrorResourceUnavailable, NSURLCacheStorageNotAllowed
import objc
from pydochelper import gethtmldoc
import sys

PY3K = (sys.version_info[0] == 3)

PYDOCSCHEME = u"pydoc"

class PyDocURLProtocol(NSURLProtocol):

    def canInitWithRequest_(klass, request):
        if request.URL().scheme() == PYDOCSCHEME:
            return True
        return False

    def canonicalRequestForRequest_(klass, request):
        return request

    def startLoading(self):
        client = self.client()
        request = self.request()
        urlpath = request.URL().standardizedURL().path()
        modpath = urlpath.replace("/", "."
            ).lstrip("."
            ).replace(".html", "")

        if not PY3K:
            modpath = modpath.encode("utf-8")

        try:
            data = gethtmldoc(modpath)
            if PY3K:
                data = data.encode("utf-8")
        except Exception as e:
            client.URLProtocol_didFailWithError_(
                self,
                NSError.errorWithDomain_code_userInfo_(
                    NSURLErrorDomain,
                    NSURLErrorResourceUnavailable,
                    None,
                ),
            )
        else:
            response = NSURLResponse.alloc().initWithURL_MIMEType_expectedContentLength_textEncodingName_(
                request.URL(),
                "text/html",
                len(data),
                "utf-8",
            )
            client.URLProtocol_didReceiveResponse_cacheStoragePolicy_(
                self,
                response,
                NSURLCacheStorageNotAllowed,
            )
            client.URLProtocol_didLoadData_(
                self,
                NSData.dataWithBytes_length_(data, len(data)),
            )
            client.URLProtocolDidFinishLoading_(self)

    def stopLoading(self):
        pass

def setup():
    NSURLProtocol.registerClass_(PyDocURLProtocol)

def teardown():
    NSURLProtocol.unregisterClass_(PyDocURLProtocol)

def main(*args):
    if not args:
        args = ("dict",)

    setup()
    try:
        for arg in args:
            url = NSURL.URLWithString_(u"pydoc:///%s" % (arg,))
            print(NSString.stringWithContentsOfURL_(url))
    finally:
        teardown()

if __name__ == "__main__":
    main(*sys.argv[1:])

pydochelper.py

import pydoc

__all__ = ['gethtmldoc']

def gethtmldoc(thing, forceload=0):
    obj, name = pydoc.resolve(thing, forceload)
    page = pydoc.html.page(
        pydoc.describe(obj),
        pydoc.html.document(obj, name)
    )
    return page

setup.py

"""
Script for building the example.

Usage:
    python3 setup.py py2app
"""
from setuptools import setup

plist = dict(
    NSMainNibFile="PyDocBrowser",
    NSAppleScriptEnabled=True,
    CFBundleURLTypes=[
        dict(
            CFBundleURLName="Python Documention URL",
            CFBundleURLSchemes=["pydoc"],
        )
    ]
)

setup(
    app=["PyDocBrowser.py"],
    data_files=["PyDocBrowser.nib"],
    options=dict(py2app=dict(plist=plist)),
    setup_requires=[
        "py2app",
        "pyobjc-framework-Cocoa",
        "pyobjc-framework-WebKit",
    ]
)

Resources