yuureinohebi / gsut.py

#/usr/bin/env python3
"""Ghostscript utilities toolkit, like GLut, but better!"""
import sys
from functools import partial
import libgs
import gsu

class GhostScriptInterpreter(object):
    """A high level interface for Ghostscript. To push words, you only have
    to call methods on the object"""
    def __init__(self):
        gs = libgs.gsapi_new_instance()
        libgs.gsapi_init_with_args(gs, [
                    # doesn't matter that much, we use the default name
                    'gs',
                    # quiet out copyright messages and stuff
                    '-q'
                ])

        # create references to C functions, we have to keep these references
        # along, otherwise the GC might discard them and the callback fails
        self._stdout_handler_c = libgs.c_stdstream_t(self._stdout_handler)
        self._stderr_handler_c = libgs.c_stdstream_t(self._stderr_handler)
        self._stdin_handler_c = libgs.c_stdstream_t(self._stdin_handler)

        # set up stdio
        libgs.gsapi_set_stdio(gs, self._stdin_handler_c,
                self._stdout_handler_c,
                self._stderr_handler_c)
        # save for later use
        self._instance = gs
        self._stdout = ''
        self._stdout_expected = False

    def _stdout_handler(self, caller_handle, buf, length):
        """Handles the stdout of the Ghostscript interpreter"""
        value = str(buf, 'utf-8')
        if self._stdout_expected:
            self._stdout += value
        else:
            print(value, end='')
        # return length to signalize we handled the data
        return length

    def _stdin_handler(self, caller_handle, buf, length):
        # not yet known how to proceed with stdin
        print("stdin handler called", file=sys.stderr)
        return length

    _stderr_handler = _stdin_handler

    def number(self, value):
        """Pushes a number on the stack by converting into a string"""
        return self.raw(str(value))

    def string(self, value):
        return self.raw("({})".format(value))

    def raw(self, code):
        """Runs raw PostScript code on the current instance"""
        # prepend space to avoid clashing with stuff that might already
        # be on the stack
        gsu.run_code(self._instance, " {}".format(code))
        # always return self to make sure chainability works
        return self

    def __getattr__(self, name):
        """Returns a function that will push the value once called"""
        return partial(self.raw, code=name)

    def _parse_items(self, items):
        accumulator = list()
        for item in items:
            # a number?
            try:
                accumulator.append(int(item))
                continue
            except ValueError:
                pass

            # a string?
            if item.startswith('(') and item.endswith(')'):
                accumulator.append(item[1:-1])
                continue

            # an array?
            if item.startswith('[') and item.endswith(']'):
                contents = item[1:-1]
                accumulator.append(self._parse_items(contents.split()))

            # yet unhandled type
            print('Unhandled type', item, file=sys.stderr)
            accumulator.append(item)

        return accumulator

    def _pstack(self):
        """Returns the stack contents
        It also converts the primitive types int and string into
        Python types"""
        # now we expect stdout
        self._stdout_expected = True
        # pstack and flush to get the stack contents
        self.pstack().flush()
        # we don't expect more stdout
        self._stdout_expected = False
        # feed the lines into the parser
        stack_contents = self._parse_items(self._stdout.splitlines())
        # reset stdout, for further runs
        self._stdout = ''
        return stack_contents

def main():
    gsi = GhostScriptInterpreter()
    print(gsi.number(1).number(2).add()._pstack())
    gsi.pop()
    print(gsi.string("abc")._pstack())
    gsi.pop()
    # strange bug, seems to be a problem with ghostscript itself
    #print(gsi._pstack())
    #print(gsi.number(42)._pstack())

if __name__ == '__main__':
    main()
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.