Source code for inkex.utils

# coding=utf-8
#
# Copyright (C) 2010 Nick Drobchenko, nick@cnc-club.ru
# Copyright (C) 2005 Aaron Spike, aaron@ekips.org
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
#
"""
Basic common utility functions for calculated things
"""
from __future__ import absolute_import, print_function, unicode_literals

import os
import sys
import shutil

from itertools import tee
from argparse import ArgumentTypeError

# When python2 support is gone, enable tempfile's version
# from tempfile import TemporaryDirectory

# All the names that get added to the inkex API itself.
__all__ = ('AbortExtension', 'inkbool', 'errormsg', 'addNS', 'NSS')

(X, Y) = range(2)

# a dictionary of all of the xmlns prefixes in a standard inkscape doc
NSS = {
    'sodipodi': 'http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd',
    'cc': 'http://creativecommons.org/ns#',
    'ccOLD': 'http://web.resource.org/cc/',
    'svg': 'http://www.w3.org/2000/svg',
    'dc': 'http://purl.org/dc/elements/1.1/',
    'rdf': 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
    'inkscape': 'http://www.inkscape.org/namespaces/inkscape',
    'xlink': 'http://www.w3.org/1999/xlink',
    'xml': 'http://www.w3.org/XML/1998/namespace'
}
SSN = dict((b, a) for (a, b) in NSS.items())

class TemporaryDirectory(object): # pylint: disable=too-few-public-methods
    """Tiny replacement for python3's version."""
    def __init__(self, suffix="", prefix="tmp"):
        self.suffix = suffix
        self.prefix = prefix
        self.path = None
    def __enter__(self):
        from tempfile import mkdtemp
        self.path = mkdtemp(self.suffix, self.prefix, None)
        return self.path
    def __exit__(self, exc, value, traceback):
        if os.path.isdir(self.path):
            shutil.rmtree(self.path)

[docs]def inkbool(value): """Turn a boolean string into a python boolean""" if value.upper() == 'TRUE': return True elif value.upper() == 'FALSE': return False return None
def debug(what): """Print debug message if debugging is switched on""" errormsg(what) return what
[docs]def errormsg(msg): """Intended for end-user-visible error messages. (Currently just writes to stderr with an appended newline, but could do something better in future: e.g. could add markup to distinguish error messages from status messages or debugging output.) Note that this should always be combined with translation: import inkex ... inkex.errormsg(_("This extension requires two selected paths.")) """ try: sys.stderr.write(msg) except TypeError: sys.stderr.write(unicode(msg)) except UnicodeEncodeError: # Python 2: # Fallback for cases where sys.stderr.encoding is not Unicode. # Python 3: # This will not work as write() does not accept byte strings, but AFAIK # we should never reach this point as the default error handler is # 'backslashreplace'. # This will be None by default if stderr is piped, so use ASCII as a # last resort. encoding = sys.stderr.encoding or 'ascii' sys.stderr.write(msg.encode(encoding, 'backslashreplace')) # Write '\n' separately to avoid dealing with different string types. sys.stderr.write('\n')
[docs]class AbortExtension(Exception): """Raised to print a message to the user without backtrace""" def __init__(self, message=""): self.message = message
[docs] def write(self): """write the error message out to the user""" errormsg(self.message)
class DependencyError(NotImplementedError): """Raised when we need an external python module that isn't available""" def to(kind): # pylint: disable=invalid-name """ Decorator which will turn a generator into a list, tuple or other object type. """ def _inner(call): def _outer(*args, **kw): return kind(call(*args, **kw)) return _outer return _inner def strargs(string, kind=float): """Returns a list of floats from a string with commas or space separators""" return [kind(val) for val in string.replace(',', ' ').split()]
[docs]def addNS(tag, ns=None): # pylint: disable=invalid-name """Add a known namespace to a name for use with lxml""" if tag.startswith('{') and ns: _, tag = removeNS(tag) if not tag.startswith('{'): tag = tag.replace('__', ':') if ':' in tag: (ns, tag) = tag.rsplit(':', 1) if ns in NSS: ns = NSS[ns] if ns is not None: return "{%s}%s" % (ns, tag) return tag
def removeNS(name, url=False): # pylint: disable=invalid-name """The reverse of addNS, finds any namespace and returns tuple (ns, tag)""" if name: if name[0] == '{': (nsp, tag) = name[1:].split('}', 1) return (nsp, tag) if url else (SSN.get(nsp, 'svg'), tag) if ':' in name: (nsp, tag) = name.rsplit(':', 1) return (NSS[nsp], tag) if url else (nsp, tag) return (NSS['svg'], name) if url else ('svg', name) class classproperty(object): # pylint: disable=invalid-name, too-few-public-methods """Combine classmethod and property decorators""" def __init__(self, func): self.func = func def __get__(self, obj, owner): return self.func(owner) def filename_arg(name): """Existing file to read or option used in script arguments""" filename = os.path.abspath(os.path.expanduser(name)) if not os.path.isfile(filename): raise ArgumentTypeError("File not found: {}".format(name)) return filename def pairwise(iterable, start=True): "Iterate over a list with overlapping pairs (see itertools recipes)" first, then = tee(iterable) starter = [(None, next(then, None))] if not start: starter = [] return starter + list(zip(first, then))