1# coding=utf-8
2#
3# Copyright (C) 2018 - Martin Owens <doctormo@mgail.com>
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation; either version 2 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program; if not, write to the Free Software
17# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18#
19"""
20Provide some documentation to existing extensions about why they're failing.
21"""
22#
23# We ignore a lot of pylint warnings here:
24#
25# pylint: disable=invalid-name,unused-argument,missing-docstring,too-many-public-methods
26#
27
28import os
29import sys
30import traceback
31import warnings
32import argparse
33from argparse import ArgumentParser
34
35import inkex
36import inkex.utils
37import inkex.units
38from inkex.base import SvgThroughMixin, InkscapeExtension
39from inkex.localization import inkex_gettext as _
40from inkex.elements._base import BaseElement
41
42warnings.simplefilter("default")
43# To load each of the deprecated sub-modules (the ones without a namespace)
44# we will add the directory to our pythonpath so older scripts can find them
45
46INKEX_DIR = os.path.abspath(os.path.dirname(__file__))
47SIMPLE_DIR = os.path.join(INKEX_DIR, 'deprecated-simple')
48
49if os.path.isdir(SIMPLE_DIR):
50    sys.path.append(SIMPLE_DIR)
51
52try:
53    DEPRECATION_LEVEL = int(os.environ.get('INKEX_DEPRECATION_LEVEL', 1))
54except ValueError:
55    DEPRECATION_LEVEL = 1
56
57def _deprecated(msg, stack=2, level=DEPRECATION_LEVEL):
58    """Internal method for raising a deprecation warning"""
59    if level > 1:
60        msg += ' ; '.join(traceback.format_stack())
61    if level:
62        warnings.warn(msg, category=DeprecationWarning, stacklevel=stack + 1)
63
64class DeprecatedEffect(object):
65    """An Inkscape effect, takes SVG in and outputs SVG, providing a deprecated layer"""
66
67    def __init__(self):
68        super(DeprecatedEffect, self).__init__()
69
70        self._doc_ids = None
71
72        # These are things we reference in the deprecated code, they are provided
73        # by the new effects code, but we want to keep this as a Mixin so these
74        # items will keep pylint happy and let use check our code as we write.
75        if not hasattr(self, 'svg'):
76            from .elements import SvgDocumentElement
77            self.svg = SvgDocumentElement()
78        if not hasattr(self, 'arg_parser'):
79            self.arg_parser = ArgumentParser()
80        if not hasattr(self, 'run'):
81            self.run = self.affect
82
83    @classmethod
84    def _deprecated(cls, name, msg=_('{} is deprecated and should be removed'), stack=3):
85        """Give the user a warning about their extension using a deprecated API"""
86        _deprecated(
87            msg.format('Effect.' + name, cls=cls.__module__ + '.' + cls.__name__),
88            stack=stack)
89
90    @property
91    def OptionParser(self):
92        self._deprecated(
93            'OptionParser',
94            _('{} or `optparse` has been deprecated and replaced with `argparser`. '
95              'You must change `self.OptionParser.add_option` to '
96              '`self.arg_parser.add_argument`; the arguments are similar.'))
97        return self
98
99    def add_option(self, *args, **kw):
100        # Convert type string into type method as needed
101        if 'type' in kw:
102            kw['type'] = {
103                'string': str,
104                'int': int,
105                'float': float,
106                'inkbool': inkex.utils.Boolean,
107            }.get(kw['type'])
108        if kw.get('action', None) == 'store':
109            # Default store action not required, removed.
110            kw.pop('action')
111        args = [arg for arg in args if arg != ""]
112        self.arg_parser.add_argument(*args, **kw)
113
114    def effect(self):
115        self._deprecated('effect', _('{} method is now a required method. It should '
116                                     'be created on {cls}, even if it does nothing.'))
117
118    @property
119    def current_layer(self):
120        self._deprecated('current_layer',\
121            _('{} is now a method in the SvgDocumentElement class. Use `self.svg.get_current_layer()` instead.'))
122        return self.svg.get_current_layer()
123
124    @property
125    def view_center(self):
126        self._deprecated('view_center',\
127            _('{} is now a method in the SvgDocumentElement class. Use `self.svg.get_center_position()` instead.'))
128        return self.svg.namedview.center
129
130    @property
131    def selected(self):
132        self._deprecated('selected', _('{} is now a dict in the SvgDocumentElement class. Use `self.svg.selected`.'))
133        return dict([(elem.get('id'), elem) for elem in self.svg.selected])
134
135    @property
136    def doc_ids(self):
137        self._deprecated('doc_ids', _('{} is now a method in the SvgDocumentElement class. '
138                                      'Use `self.svg.get_ids()` instead.'))
139        if self._doc_ids is None:
140            self._doc_ids = dict.fromkeys(self.svg.get_ids())
141        return self._doc_ids
142
143    def getdocids(self):
144        self._deprecated('getdocids', _('Use `self.svg.get_ids()` instead of {} and `doc_ids`.'))
145        self._doc_ids = None
146        self.svg.ids.clear()
147
148    def getselected(self):
149        self._deprecated('getselected', _('{} has been removed'))
150
151    def getElementById(self, eid):
152        self._deprecated('getElementById',\
153            _('{} is now a method in the SvgDocumentElement class. Use `self.svg.getElementById(eid)` instead.'))
154        return self.svg.getElementById(eid)
155
156    def xpathSingle(self, xpath):
157        self._deprecated('xpathSingle', _('{} is now a new method in the SvgDocumentElement class. '
158                                          'Use `self.svg.getElement(path)` instead.'))
159        return self.svg.getElement(xpath)
160
161    def getParentNode(self, node):
162        self._deprecated('getParentNode',\
163            _('{} is no longer in use. Use the lxml `.getparent()` method instead.'))
164        return node.getparent()
165
166    def getNamedView(self):
167        self._deprecated('getNamedView',\
168            _('{} is now a property of the SvgDocumentElement class. '
169              'Use `self.svg.namedview` to access this element.'))
170        return self.svg.namedview
171
172    def createGuide(self, posX, posY, angle):
173        from .elements import Guide
174        self._deprecated('createGuide',\
175            _('{} is now a method of the namedview element object. '
176              'Use `self.svg.namedview.add(Guide().move_to(x, y, a))` instead.'))
177        return self.svg.namedview.add(Guide().move_to(posX, posY, angle))
178
179    def affect(self, args=sys.argv[1:], output=True):  # pylint: disable=dangerous-default-value
180        # We need a list as the default value to preserve backwards compatibility
181        self._deprecated('affect', _('{} is now `Effect.run()`. The `output` argument has changed.'))
182        self._args = args[-1:]
183        return self.run(args=args)
184
185    @property
186    def args(self):
187        self._deprecated('args', _('self.args[-1] is now self.options.input_file.'))
188        return self._args
189
190    @property
191    def svg_file(self):
192        self._deprecated('svg_file', _('self.svg_file is now self.options.input_file.'))
193        return self.options.input_file
194
195    def save_raw(self, ret):
196        # Derived class may implement "output()"
197        # Attention: 'cubify.py' implements __getattr__ -> hasattr(self, 'output') returns True
198        if hasattr(self.__class__, 'output'):
199            self._deprecated('output', 'Use `save()` or `save_raw()` instead.', stack=5)
200            return getattr(self, 'output')()
201        return inkex.base.InkscapeExtension.save_raw(self, ret)
202
203    def uniqueId(self, old_id, make_new_id=True):
204        self._deprecated('uniqueId', _('{} is now a method in the SvgDocumentElement class. '
205                                       ' Use `self.svg.get_unique_id(old_id)` instead.'))
206        return self.svg.get_unique_id(old_id)
207
208    def getDocumentWidth(self):
209        self._deprecated('getDocumentWidth', _('{} is now a property of the SvgDocumentElement class. '
210                                               'Use `self.svg.width` instead.'))
211        return self.svg.get('width')
212
213    def getDocumentHeight(self):
214        self._deprecated('getDocumentHeight', _('{} is now a property of the SvgDocumentElement class. '
215                                                'Use `self.svg.height` instead.'))
216        return self.svg.get('height')
217
218    def getDocumentUnit(self):
219        self._deprecated('getDocumentUnit', _('{} is now a property of the SvgDocumentElement class. '
220                                              'Use `self.svg.unit` instead.'))
221        return self.svg.unit
222
223    def unittouu(self, string):
224        self._deprecated('unittouu', _('{} is now a method in the SvgDocumentElement class. '
225                                       'Use `self.svg.unittouu(str)` instead.'))
226        return self.svg.unittouu(string)
227
228    def uutounit(self, val, unit):
229        self._deprecated('uutounit', _('{} is now a method in the SvgDocumentElement class. '
230                                       'Use `self.svg.uutounit(value, unit)` instead.'))
231        return self.svg.uutounit(val, unit)
232
233    def addDocumentUnit(self, value):
234        self._deprecated('addDocumentUnit', _('{} is now a method in the SvgDocumentElement class. '
235                                              'Use `self.svg.add_unit(value)` instead.'))
236        return self.svg.add_unit(value)
237
238class Effect(SvgThroughMixin, DeprecatedEffect, InkscapeExtension):
239    """An Inkscape effect, takes SVG in and outputs SVG"""
240    pass
241
242def deprecate(func):
243    """Function decorator for deprecation functions which have a one-liner
244    equivalent in the new API. The one-liner has to passed as a string
245    to the decorator.
246
247    >>> @deprecate
248    >>> def someOldFunction(*args):
249    >>>     '''Example replacement code someNewFunction('foo', ...)'''
250    >>>     someNewFunction('foo', *args)
251
252    Or if the args API is the same:
253
254    >>> someOldFunction = deprecate(someNewFunction)
255
256    """
257
258    def _inner(*args, **kwargs):
259        _deprecated('{0.__module__}.{0.__name__} -> {0.__doc__}'.format(func), stack=2)
260        return func(*args, **kwargs)
261    _inner.__name__ = func.__name__
262    if func.__doc__:
263        _inner.__doc__ = "Deprecated -> " + func.__doc__
264    return _inner
265
266class DeprecatedDict(dict):
267    @deprecate
268    def __getitem__(self, key):
269        return super(DeprecatedDict, self).__getitem__(key)
270
271    @deprecate
272    def __iter__(self):
273        return super(DeprecatedDict, self).__iter__()
274
275# legacy inkex members
276
277class lazyproxy(object):
278    """Proxy, use as decorator on a function with provides the wrapped object.
279    The decorated function is called when a member is accessed on the proxy.
280    """
281    def __init__(self, getwrapped):
282        '''
283        :param getwrapped: Callable which returns the wrapped object
284        '''
285        self._getwrapped = getwrapped
286
287    def __getattr__(self, name):
288        return getattr(self._getwrapped(), name)
289
290    def __call__(self, *args, **kwargs):
291        return self._getwrapped()(*args, **kwargs)
292
293@lazyproxy
294def optparse():
295    _deprecated('inkex.optparse was removed, use "import optparse"', stack=3)
296    import optparse as wrapped
297    return wrapped
298
299@lazyproxy
300def etree():
301    _deprecated('inkex.etree was removed, use "from lxml import etree"', stack=3)
302    from lxml import etree as wrapped
303    return wrapped
304
305@lazyproxy
306def InkOption():
307    import optparse
308    class wrapped(optparse.Option):
309        TYPES = optparse.Option.TYPES + ("inkbool", )
310        TYPE_CHECKER = dict(optparse.Option.TYPE_CHECKER)
311        TYPE_CHECKER["inkbool"] = lambda _1, _2, v: str(v).capitalize() == 'True'
312    return wrapped
313
314@lazyproxy
315def localize():
316    _deprecated('inkex.localize was moved to inkex.localization.localize.', stack=3)
317    from .localization import localize as wrapped
318    return wrapped
319
320def are_near_relative(a, b, eps):
321    _deprecated('inkex.are_near_relative was moved to '
322                'inkex.units.are_near_relative', stack=2)
323    return inkex.units.are_near_relative(a, b, eps)
324
325def debug(what):
326    _deprecated('inkex.debug was moved to inkex.utils.debug.', stack=2)
327    return inkex.utils.debug(what)
328
329# legacy inkex members <= 0.48.x
330
331def unittouu(string):
332    _deprecated('inkex.unittouu is now a method in the SvgDocumentElement class. '
333            'Use `self.svg.unittouu(str)` instead.', stack=2)
334    return inkex.units.convert_unit(string, 'px')
335
336# optparse.Values.ensure_value
337
338def ensure_value(self, attr, value):
339    _deprecated('Effect().options.ensure_value was removed.', stack=2)
340    if getattr(self, attr, None) is None:
341        setattr(self, attr, value)
342    return getattr(self, attr)
343
344argparse.Namespace.ensure_value = ensure_value # type: ignore
345
346@deprecate
347def zSort(inNode, idList):
348    """self.svg.get_z_selected()"""
349    sortedList = []
350    theid = inNode.get("id")
351    if theid in idList:
352        sortedList.append(theid)
353    for child in inNode:
354        if len(sortedList) == len(idList):
355            break
356        sortedList += zSort(child, idList)
357    return sortedList
358
359class DeprecatedSvgMixin(object):
360    """Mixin which adds deprecated API elements to the SvgDocumentElement"""
361    @property
362    def selected(self):
363        """svg.selection"""
364        return self.selection
365
366    @deprecate
367    def set_selected(self, *ids):
368        """svg.selection.set(*ids)"""
369        return self.selection.set(*ids)
370
371    @deprecate
372    def get_z_selected(self):
373        """svg.selection.paint_order()"""
374        return self.selection.paint_order()
375
376    @deprecate
377    def get_selected(self, *types):
378        """svg.selection.filter(*types).values()"""
379        return self.selection.filter(*types).values()
380
381    @deprecate
382    def get_selected_or_all(self, *types):
383        """Set select_all = True in extension class"""
384        if not self.selection:
385            self.selection.set_all()
386        return self.selection.filter(*types)
387
388    @deprecate
389    def get_selected_bbox(self):
390        """selection.bounding_box()"""
391        return self.selection.bounding_box()
392
393    @deprecate
394    def get_first_selected(self, *types):
395        """selection.filter(*types).first() or [0] if you'd like an error"""
396        return self.selection.filter(*types).first()
397
398
399# This can't be handled as a mixin class because of circular importing.
400def description(self, value):
401    """elem.desc = value"""
402    self.desc = value
403BaseElement.description = deprecate(description)
404