PyDocURLProtocol
================

* :download:`Download example <PyObjCExample-PyDocURLProtocol.zip>`

A PyObjC Example without documentation

.. rst-class:: tabber

Sources
-------

.. rst-class:: tabbertab

PyDocBrowser.py
...............

.. sourcecode:: python

    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()

.. rst-class:: tabbertab

PyDocEvents.py
..............

.. sourcecode:: python

    """
    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"),
            )

.. rst-class:: tabbertab

PyDocURLProtocol.py
...................

.. sourcecode:: python

    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:])

.. rst-class:: tabbertab

pydochelper.py
..............

.. sourcecode:: python

    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

.. rst-class:: tabbertab

setup.py
........

.. sourcecode:: python

    """
    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"],
    )

