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