PyDocURLProtocol

A PyObjC Example without documentation

Sources

PyDocBrowser.py

import AppKit  # noqa: F401
import Foundation  # noqa: F401
import PyDocEvents  # noqa: F401
import PyDocURLProtocol  # noqa: F401
import WebKit  # noqa: F401
from PyObjCTools import AppHelper

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.
"""

import struct

import objc
from Cocoa import NSAppleEventManager, NSObject


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

import sys

from Cocoa import (
    NSURL,
    NSData,
    NSError,
    NSString,
    NSURLCacheStorageNotAllowed,
    NSURLErrorDomain,
    NSURLErrorResourceUnavailable,
    NSURLProtocol,
    NSURLResponse,
)
from pydochelper import gethtmldoc

PY3K = sys.version_info[0] == 3

PYDOCSCHEME = "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:
            client.URLProtocol_didFailWithError_(
                self,
                NSError.errorWithDomain_code_userInfo_(
                    NSURLErrorDomain, NSURLErrorResourceUnavailable, None
                ),
            )
        else:
            response = NSURLResponse.alloc().initWithURL_MIMEType_expectedContentLength_textEncodingName_(  # noqa: B950
                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_(f"pydoc:///{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 = {
    "NSMainNibFile": "PyDocBrowser",
    "NSAppleScriptEnabled": True,
    "CFBundleURLTypes": [
        {"CFBundleURLName": "Python Documentation URL", "CFBundleURLSchemes": ["pydoc"]}
    ],
}

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