Scripts/stdinreader

stdinreader.py

#!/usr/bin/env python3

from Foundation import (
    NSObject,
    NSFileHandle,
    NSNotificationCenter,
    NSFileHandleReadCompletionNotification,
    NSFileHandleNotificationDataItem,
    NSFileHandleError,
)
from PyObjCTools import AppHelper


class FileObserver(NSObject):
    def initWithFileDescriptor_readCallback_errorCallback_(
        self, fileDescriptor, readCallback, errorCallback
    ):
        self = self.init()
        self.readCallback = readCallback
        self.errorCallback = errorCallback
        self.fileHandle = NSFileHandle.alloc().initWithFileDescriptor_(fileDescriptor)
        self.nc = NSNotificationCenter.defaultCenter()
        self.nc.addObserver_selector_name_object_(
            self,
            "fileHandleReadCompleted:",
            NSFileHandleReadCompletionNotification,
            self.fileHandle,
        )
        self.fileHandle.readInBackgroundAndNotify()
        return self

    def fileHandleReadCompleted_(self, aNotification):
        ui = aNotification.userInfo()
        newData = ui.objectForKey_(NSFileHandleNotificationDataItem)
        if newData is None:
            if self.errorCallback is not None:
                self.errorCallback(self, ui.objectForKey_(NSFileHandleError))
            self.close()
        else:
            self.fileHandle.readInBackgroundAndNotify()
            if self.readCallback is not None:
                self.readCallback(self, str(newData))

    def close(self):
        self.nc.removeObserver_(self)
        if self.fileHandle is not None:
            self.fileHandle.closeFile()
            self.fileHandle = None
        # break cycles in case these functions are closed over
        # an instance of us
        self.readCallback = None
        self.errorCallback = None

    def __del__(self):
        # Without this, if a notification fires after we are GC'ed
        # then the app will crash because NSNotificationCenter
        # doesn't retain observers.  In this example, it doesn't
        # matter, but it's worth pointing out.
        self.close()


def prompt():
    sys.stdout.write("write something: ")
    sys.stdout.flush()


def gotLine(observer, line):
    if line:
        print("you wrote:", line.rstrip())
        prompt()
    else:
        print("")
        AppHelper.stopEventLoop()


def gotError(observer, err):
    print("error:", err)
    AppHelper.stopEventLoop()


if __name__ == "__main__":
    import sys

    observer = FileObserver.alloc().initWithFileDescriptor_readCallback_errorCallback_(
        sys.stdin.fileno(), gotLine, gotError
    )
    prompt()
    AppHelper.runConsoleEventLoop(installInterrupt=True)