From 4938c488539d723bd42fac1674d2c6c8c776a9ec Mon Sep 17 00:00:00 2001 From: Vinayak Mehta Date: Tue, 18 Dec 2018 07:43:52 +0530 Subject: [PATCH] Remove _errors and ghostscript test --- camelot/ext/ghostscript/__init__.py | 63 +++----------- camelot/ext/ghostscript/_errors.py | 123 ---------------------------- camelot/ext/ghostscript/_gsprint.py | 100 ++++++++-------------- camelot/parsers/lattice.py | 4 +- tests/test_errors.py | 15 ---- 5 files changed, 48 insertions(+), 257 deletions(-) delete mode 100644 camelot/ext/ghostscript/_errors.py diff --git a/camelot/ext/ghostscript/__init__.py b/camelot/ext/ghostscript/__init__.py index ab887df..2751ef7 100644 --- a/camelot/ext/ghostscript/__init__.py +++ b/camelot/ext/ghostscript/__init__.py @@ -4,6 +4,7 @@ ghostscript - A Python interface for the Ghostscript interpreter C-API """ # +# Modifications 2018 by Vinayak Mehta # Copyright 2010-2018 by Hartmut Goebel # # This program is free software: you can redistribute it and/or modify @@ -20,60 +21,22 @@ ghostscript - A Python interface for the Ghostscript interpreter C-API # along with this program. If not, see . # -from __future__ import absolute_import - -__author__ = "Hartmut Goebel " -__copyright__ = "Copyright 2010-2018 by Hartmut Goebel " -__licence__ = "GNU General Public License version 3 (GPL v3)" -__version__ = '0.6' - -__all__ = ['Ghostscript', 'revision', - 'GhostscriptError', 'PleaseDisplayUsage'] - -import atexit from . import _gsprint as gs -GhostscriptError = gs.GhostscriptError +__author__ = 'Hartmut Goebel ' +__copyright__ = 'Copyright 2010-2018 by Hartmut Goebel ' +__license__ = 'GNU General Public License version 3 (GPL v3)' +__version__ = '0.6' -def PleaseDisplayUsage(Warning): - """ - This exception is raised when Ghostscript asks the application to - display the usage. The application should catch the exception an - print the usage message. - """ - pass - - -def revision(): - """ - This function returns the revision numbers and strings of the - Ghostscript interpreter library as a dict. You should call it - before any other interpreter library functions to make sure that - the correct version of the Ghostscript interpreter has been - loaded. - """ - rev = gs.revision() - return dict((f, getattr(rev, f)) for f, _ in rev._fields_) - - -MAX_STRING_LENGTH = gs.MAX_STRING_LENGTH - - -class Ghostscript(object): - @staticmethod - def revision(): - return revision() - +class __Ghostscript(object): def __init__(self, instance, args, stdin=None, stdout=None, stderr=None): self._initialized = False self._callbacks = None if stdin or stdout or stderr: self.set_stdio(stdin, stdout, stderr) rc = gs.init_with_args(instance, args) - if rc == gs.e_Info: - raise PleaseDisplayUsage self._initialized = True if rc == gs.e_Quit: self.exit() @@ -101,7 +64,7 @@ class Ghostscript(object): stdin and gs._wrap_stdin(stdin) or None, stdout and gs._wrap_stdout(stdout) or None, stderr and gs._wrap_stderr(stderr) or None, - ) + ) gs.set_stdio(__instance__, *self._callbacks) def __del__(self): @@ -117,15 +80,10 @@ class Ghostscript(object): self._initialized = False -__Ghostscript = Ghostscript -__instance__ = None - - def Ghostscript(*args, **kwargs): + """Factory function for setting up a Ghostscript instance """ - Factory function for setting up a Ghostscript instance - """ - global __instance__, __object_count__ + global __instance__ # Ghostscript only supports a single instance if __instance__ is None: __instance__ = gs.new_instance() @@ -133,3 +91,6 @@ def Ghostscript(*args, **kwargs): stdin=kwargs.get('stdin', None), stdout=kwargs.get('stdout', None), stderr=kwargs.get('stderr', None)) + + +__instance__ = None diff --git a/camelot/ext/ghostscript/_errors.py b/camelot/ext/ghostscript/_errors.py deleted file mode 100644 index ca6bb32..0000000 --- a/camelot/ext/ghostscript/_errors.py +++ /dev/null @@ -1,123 +0,0 @@ -""" -Definition of Ghostscript error codes -""" -# -# Copyright (C) 2010-2018 by Hartmut Goebel -# -# Based on iapi.h which is -# Copyright (C) 1989, 1995, 1998, 1999 Aladdin Enterprises. All rights reserved. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# - -__author__ = "Hartmut Goebel " -__copyright__ = "Copyright 2010-2018 by Hartmut Goebel " -__licence__ = "GNU General Public License version 3 (GPL v3)" -__version__ = "0.6" - - -LEVEL1_ERROR_NAMES = ["unknownerror", "dictfull", "dictstackoverflow", - "dictstackunderflow", "execstackoverflow", - "interrupt", "invalidaccess", "invalidexit", - "invalidfileaccess", "invalidfont", - "invalidrestore", "ioerror", "limitcheck", - "nocurrentpoint", "rangecheck", "stackoverflow", - "stackunderflow", "syntaxerror", "timeout", - "typecheck", "undefined", "undefinedfilename", - "undefinedresult", "unmatchedmark", "VMerror"] - -LEVEL2_ERROR_NAMES = ["configurationerror", "invalidcontext", - "undefinedresource", "unregistered", - "invalidid"] - -ERROR_NAMES = LEVEL1_ERROR_NAMES + LEVEL2_ERROR_NAMES - -PSEUDO_ERROR_NAMES = ['Fatal', 'Quit', 'InterpreterExit', 'RemapColor', - 'ExecStackUnderflow', 'VMreclaim', 'NeedInput', - 'NeedStdin', 'NeedStdout', 'NeedStderr', 'Info'] - - -def error2name(ecode): - if ecode <= e_Fatal: - return PSEUDO_ERROR_NAMES[-ecode-100] - else: - return ERROR_NAMES[-ecode-1] - - -# -# Internal code for a fatal error. -# gs_interpret also returns this for a .quit with a positive exit code. -# -e_Fatal = -100 - -# -# Internal code for the .quit operator. -# The real quit code is an integer on the operand stack. -# gs_interpret returns this only for a .quit with a zero exit code. -# -e_Quit = -101 - -# -# Internal code for a normal exit from the interpreter. -# Do not use outside of interp.c. -# -e_InterpreterExit = -102 - -# -# Internal code that indicates that a procedure has been stored in the -# remap_proc of the graphics state, and should be called before retrying -# the current token. This is used for color remapping involving a call -# back into the interpreter -- inelegant, but effective. -# -e_RemapColor = -103 - -# -# Internal code to indicate we have underflowed the top block -# of the e-stack. -# -e_ExecStackUnderflow = -104 - -# -# Internal code for the vmreclaim operator with a positive operand. -# We need to handle this as an error because otherwise the interpreter -# won't reload enough of its state when the operator returns. -# -e_VMreclaim = -105 - -# -# Internal code for requesting more input from run_string. -# -e_NeedInput = -106 - -# -# Internal code for stdin callout. -# -e_NeedStdin = -107 - -# -# Internal code for stdout callout. -# -e_NeedStdout = -108 - -# -# Internal code for stderr callout. -# -e_NeedStderr = -109 - -# -# Internal code for a normal exit when usage info is displayed. -# This allows Window versions of Ghostscript to pause until -# the message can be read. -# -e_Info = -110 diff --git a/camelot/ext/ghostscript/_gsprint.py b/camelot/ext/ghostscript/_gsprint.py index 37a22a7..624e18e 100644 --- a/camelot/ext/ghostscript/_gsprint.py +++ b/camelot/ext/ghostscript/_gsprint.py @@ -4,6 +4,7 @@ ghostscript._gsprint - A low-level interface to the Ghostscript C-API using ctypes """ # +# Modifications 2018 by Vinayak Mehta # Copyright 2010-2018 by Hartmut Goebel # # Display_callback Structure by Lasse Fister in 2013 @@ -22,62 +23,38 @@ ghostscript._gsprint - A low-level interface to the Ghostscript C-API using ctyp # along with this program. If not, see . # -from __future__ import absolute_import - -__author__ = "Hartmut Goebel " -__copyright__ = "Copyright 2010-2018 by Hartmut Goebel " -__licence__ = "GNU General Public License version 3 (GPL v3)" -__version__ = "0.6" - import sys from ctypes import * -from ._errors import e_Quit, e_Info +# base/gserrors.h +# +# Internal code for a normal exit when usage info is displayed. +# This allows Window versions of Ghostscript to pause until +# the message can be read. +# +e_Info = -110 -MAX_STRING_LENGTH = 65535 +# +# Internal code for the .quit operator. +# The real quit code is an integer on the operand stack. +# gs_interpret returns this only for a .quit with a zero exit code. +# +e_Quit = -101 + +__author__ = 'Hartmut Goebel ' +__copyright__ = 'Copyright 2010-2018 by Hartmut Goebel ' +__license__ = 'GNU General Public License version 3 (GPL v3)' +__version__ = '0.6' gs_main_instance = c_void_p display_callback = c_void_p - -class Revision(Structure): - _fields_ = [ - ("product", c_char_p), - ("copyright", c_char_p), - ("revision", c_long), - ("revisiondate", c_long) - ] +# https://www.ghostscript.com/doc/current/API.htm -class GhostscriptError(Exception): - def __init__(self, ecode): - # :todo: - Exception.__init__(self, error2name(ecode)) - self.code = ecode - - -def revision(): - """ - Get version numbers and strings. - - This is safe to call at any time. - You should call this first to make sure that the correct version - of the Ghostscript is being used. - - Returns a Revision instance - """ - revision = Revision() - rc = libgs.gsapi_revision(pointer(revision), sizeof(revision)) - if rc: - raise ArgumentError("Revision structure size is incorrect, " - "requires %s bytes" % rc) - return revision - - -def new_instance(): # display_callback=None): - """ - Create a new instance of Ghostscript +def new_instance(): + """Create a new instance of Ghostscript This instance is passed to most other API functions. """ @@ -91,8 +68,7 @@ def new_instance(): # display_callback=None): def delete_instance(instance): - """ - Destroy an instance of Ghostscript + """Destroy an instance of Ghostscript Before you call this, Ghostscript must have finished. If Ghostscript has been initialised, you must call exit() @@ -105,11 +81,9 @@ c_stdstream_call_t = CFUNCTYPE(c_int, gs_main_instance, POINTER(c_char), c_int) def _wrap_stdin(infp): - """ - Wrap a filehandle into a C function to be used as `stdin` callback + """Wrap a filehandle into a C function to be used as `stdin` callback for ``set_stdio``. The filehandle has to support the readline() method. """ - def _wrap(instance, dest, count): try: data = infp.readline(count) @@ -127,12 +101,10 @@ def _wrap_stdin(infp): def _wrap_stdout(outfp): - """ - Wrap a filehandle into a C function to be used as `stdout` or + """Wrap a filehandle into a C function to be used as `stdout` or `stderr` callback for ``set_stdio``. The filehandle has to support the write() and flush() methods. """ - def _wrap(instance, str, count): outfp.write(str[:count]) outfp.flush() @@ -145,8 +117,7 @@ _wrap_stderr = _wrap_stdout def set_stdio(instance, stdin, stdout, stderr): - """ - Set the callback functions for stdio. + """Set the callback functions for stdio. ``stdin``, ``stdout`` and ``stderr`` have to be ``ctypes`` callback functions matching the ``_gsprint.c_stdstream_call_t`` @@ -173,13 +144,8 @@ def set_stdio(instance, stdin, stdout, stderr): return rc -# :todo: set_poll (instance, int(*poll_fn)(void *caller_handle)); -# :todo: set_display_callback(instance, callback): - - def init_with_args(instance, argv): - """ - Initialise the interpreter. + """Initialise the interpreter 1. If quit or EOF occur during init_with_args(), the return value will be e_Quit. This is not an error. You must call exit() and @@ -200,8 +166,7 @@ def init_with_args(instance, argv): def exit(instance): - """ - Exit the interpreter + """Exit the interpreter This must be called on shutdown if init_with_args() has been called, and just before delete_instance() @@ -230,7 +195,7 @@ def __win32_finddll(): # Look up different variants of Ghostscript and take the highest # version for which the DLL is to be found in the filesystem. for key_name in ('AFPL Ghostscript', 'Aladdin Ghostscript', - 'GPL Ghostscript', 'GNU Ghostscript'): + 'GNU Ghostscript', 'GPL Ghostscript'): try: k1 = OpenKey(HKEY_LOCAL_MACHINE, "Software\\%s" % key_name) for num in range(0, QueryInfoKey(k1)[0]): @@ -256,17 +221,18 @@ def __win32_finddll(): if sys.platform == 'win32': libgs = __win32_finddll() if not libgs: - raise RuntimeError('Can not find Ghostscript DLL in registry') + raise RuntimeError('Please make sure that Ghostscript is installed') libgs = windll.LoadLibrary(libgs) else: try: - libgs = cdll.LoadLibrary("libgs.so") + libgs = cdll.LoadLibrary('libgs.so') except OSError: # shared object file not found import ctypes.util + libgs = ctypes.util.find_library('gs') if not libgs: - raise RuntimeError('Can not find Ghostscript library (libgs)') + raise RuntimeError('Please make sure that Ghostscript is installed') libgs = cdll.LoadLibrary(libgs) del __win32_finddll diff --git a/camelot/parsers/lattice.py b/camelot/parsers/lattice.py index cfa3719..fb53e40 100644 --- a/camelot/parsers/lattice.py +++ b/camelot/parsers/lattice.py @@ -181,8 +181,10 @@ class Lattice(BaseParser): gs_call = '-q -sDEVICE=png16m -o {} -r600 {}'.format( self.imagename, self.filename) gs_call = gs_call.encode().split() - with Ghostscript(*gs_call) as gs: + null = open(os.devnull, 'wb') + with Ghostscript(*gs_call, stdout=null) as gs: pass + null.close() def _generate_table_bbox(self): self.image, self.threshold = adaptive_threshold( diff --git a/tests/test_errors.py b/tests/test_errors.py index e0dc24a..49cb4f3 100755 --- a/tests/test_errors.py +++ b/tests/test_errors.py @@ -74,21 +74,6 @@ def test_no_tables_found_warnings_suppressed(): pytest.fail('Unexpected warning: {}'.format(warning_text)) -def test_ghostscript_not_found(monkeypatch): - import distutils - - def _find_executable_patch(arg): - return '' - - monkeypatch.setattr(distutils.spawn, 'find_executable', _find_executable_patch) - - message = ('Please make sure that Ghostscript is installed and available' - ' on the PATH environment variable') - filename = os.path.join(testdir, 'foo.pdf') - with pytest.raises(Exception, message=message): - tables = camelot.read_pdf(filename) - - def test_no_password(): filename = os.path.join(testdir, 'health_protected.pdf') message = 'file has not been decrypted'